diff --git a/DEPS b/DEPS index 61a9d172..1c01a98 100644 --- a/DEPS +++ b/DEPS
@@ -298,7 +298,7 @@ # 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': 'db56d8fbcf0e07da4c9ef31c5ae56739b7138fac', + 'v8_revision': '08af66db4e0f2c52bf714c5f5f163d14f785d451', # Three lines of non-changing comments so that # the commit queue can handle CLs rolling ANGLE # and whatever else without interference from each other. @@ -342,7 +342,7 @@ # Three lines of non-changing comments so that # the commit queue can handle CLs rolling freetype # and whatever else without interference from each other. - 'freetype_revision': '58be4879c5d3840315f037dca44e92384113f8f9', + 'freetype_revision': 'd7e640b9c6f7e0d640c8e2d9598f13ba8c7765a6', # Three lines of non-changing comments so that # the commit queue can handle CLs rolling HarfBuzz # and whatever else without interference from each other. @@ -358,7 +358,7 @@ # 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': 'ca6134f08323907d635905a7cc21abd4b1476135', + 'catapult_revision': '92a076097780315c2a9551cdc1bd4389b3f4cce5', # Three lines of non-changing comments so that # the commit queue can handle CLs rolling CrossBench # and whatever else without interference from each other. @@ -378,7 +378,7 @@ # Three lines of non-changing comments so that # the commit queue can handle CLs rolling devtools-frontend # and whatever else without interference from each other. - 'devtools_frontend_revision': '86e8e6cd176f746ceecfa8f9729d742adf57417e', + 'devtools_frontend_revision': 'df0eb0a6577bd189ee0eb952f35339cc6c09e76f', # Three lines of non-changing comments so that # the commit queue can handle CLs rolling libprotobuf-mutator # and whatever else without interference from each other. @@ -1563,7 +1563,7 @@ 'src/clank': { 'url': Var('chrome_git') + '/clank/internal/apps.git' + '@' + - '2444b358f5ae5fdcda516c76fcb06653f03a2441', + '40b8b87ab4d682574fed64494081b35a70ee1569', 'condition': 'checkout_android and checkout_src_internal', }, @@ -1717,7 +1717,7 @@ 'packages': [ { 'package': 'chromium/third_party/androidx', - 'version': 'QJeiOfn7XeeO-RE3FDRufWNkhrsiuvv3LiJWr9X4k9QC', + 'version': 'S2w6RUuVgvWR_5_Wroda8JA1Ye98v_unhD5qZEtEp1EC', }, ], 'condition': 'checkout_android and non_git_source', @@ -2593,7 +2593,7 @@ Var('pdfium_git') + '/pdfium.git' + '@' + Var('pdfium_revision'), 'src/third_party/perfetto': - Var('chromium_git') + '/external/github.com/google/perfetto.git' + '@' + '32fc4f31c38c6598c34f7dca0dd6aad2a86f373d', + Var('chromium_git') + '/external/github.com/google/perfetto.git' + '@' + '5192b52b266bf5840929a30ac36037cf4f6eb2b7', 'src/base/tracing/test/data': { 'bucket': 'perfetto', @@ -3143,7 +3143,7 @@ 'packages': [ { 'package': 'chromeos_internal/apps/projector_app/app', - 'version': 'zVXffSzzY6Uzs55-L9KoV1fdhl2OBMeWhVcJfmvQUr0C', + 'version': 'hYW8QDAJb2PG9n9JgFmztL4vZSig6ntE2_PhCjIa_WEC', }, ], 'condition': 'checkout_chromeos and checkout_src_internal', @@ -4741,7 +4741,7 @@ 'src/ios_internal': { 'url': Var('chrome_git') + '/chrome/ios_internal.git' + '@' + - 'dc62401870e2aaf9fc91284339f146da37729fee', + '9a9d45649b69d7ae40dc45ba9582ebb7fe5b244a', 'condition': 'checkout_ios and checkout_src_internal', },
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 75f1aaba..2d57782 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
@@ -267,6 +267,9 @@ AutofillFeatures.AUTOFILL_IMPROVE_CITY_FIELD_CLASSIFICATION, "Reduces city field false positive classifications"), Flag.baseFeature( + AutofillFeatures.AUTOFILL_IGNORE_CHECKABLE_ELEMENTS, + "Does not extract checkboxes and radio buttons"), + Flag.baseFeature( AutofillFeatures.AUTOFILL_OPTIMIZE_FORM_EXTRACTION, "Makes Autofill spend less time on extracting forms."), Flag.baseFeature(
diff --git a/android_webview/javatests/src/org/chromium/android_webview/test/AwAutofillTest.java b/android_webview/javatests/src/org/chromium/android_webview/test/AwAutofillTest.java index c6c5fb0..1098a8d2 100644 --- a/android_webview/javatests/src/org/chromium/android_webview/test/AwAutofillTest.java +++ b/android_webview/javatests/src/org/chromium/android_webview/test/AwAutofillTest.java
@@ -487,7 +487,10 @@ @Test @SmallTest - @CommandLineFlags.Add({"disable-features=AutofillServerCommunication"}) + @CommandLineFlags.Add({ + "disable-features=AutofillServerCommunication", + "enable-features=AutofillIgnoreCheckableElements" + }) @Feature({"AndroidWebView"}) public void testBasicAutofill() throws Throwable { final String url = @@ -509,7 +512,7 @@ <input type='color' id='color1'><input type='file' id='file1'> <input type='image' id='image1'> </form>"""); - final int totalControls = 4; // text1, checkbox1, select1, textarea1 + final int totalControls = 3; // text1, select1, textarea1 int cnt = 0; executeJavaScriptAndWaitForResult("document.getElementById('text1').select();"); dispatchDownAndUpKeyEvents(KeyEvent.KEYCODE_A); @@ -560,9 +563,9 @@ assertEquals("30", htmlInfo0.getAttribute("maxlength")); assertEquals("NAME_FIRST", htmlInfo0.getAttribute("ua-autofill-hints")); - // Verify checkbox control filled correctly in ViewStructure. + // Verify select control filled correctly in ViewStructure. TestViewStructure child1 = viewStructure.getChild(1); - assertEquals(View.AUTOFILL_TYPE_TOGGLE, child1.getAutofillType()); + assertEquals(View.AUTOFILL_TYPE_LIST, child1.getAutofillType()); assertEquals("", child1.getHint()); assertNull(child1.getAutofillHints()); assertFalse(child1.getDimensRect().isEmpty()); @@ -570,16 +573,15 @@ assertEquals(0, child1.getDimensScrollX()); assertEquals(0, child1.getDimensScrollY()); TestViewStructure.TestHtmlInfo htmlInfo1 = child1.getHtmlInfo(); - assertEquals("checkbox", htmlInfo1.getAttribute("type")); - assertEquals("checkbox1", htmlInfo1.getAttribute("id")); - assertEquals("showpassword", htmlInfo1.getAttribute("name")); - assertEquals("", htmlInfo1.getAttribute("label")); - assertNull(htmlInfo1.getAttribute("maxlength")); - assertNull(htmlInfo1.getAttribute("ua-autofill-hints")); + assertEquals("month", htmlInfo1.getAttribute("name")); + assertEquals("select1", htmlInfo1.getAttribute("id")); + CharSequence[] options = child1.getAutofillOptions(); + assertEquals("Jan", options[0]); + assertEquals("Feb", options[1]); - // Verify select control filled correctly in ViewStructure. + // Verify textarea control is filled correctly in ViewStructure. TestViewStructure child2 = viewStructure.getChild(2); - assertEquals(View.AUTOFILL_TYPE_LIST, child2.getAutofillType()); + assertEquals(View.AUTOFILL_TYPE_TEXT, child2.getAutofillType()); assertEquals("", child2.getHint()); assertNull(child2.getAutofillHints()); assertFalse(child2.getDimensRect().isEmpty()); @@ -587,30 +589,13 @@ assertEquals(0, child2.getDimensScrollX()); assertEquals(0, child2.getDimensScrollY()); TestViewStructure.TestHtmlInfo htmlInfo2 = child2.getHtmlInfo(); - assertEquals("month", htmlInfo2.getAttribute("name")); - assertEquals("select1", htmlInfo2.getAttribute("id")); - CharSequence[] options = child2.getAutofillOptions(); - assertEquals("Jan", options[0]); - assertEquals("Feb", options[1]); - - // Verify textarea control is filled correctly in ViewStructure. - TestViewStructure child3 = viewStructure.getChild(3); - assertEquals(View.AUTOFILL_TYPE_TEXT, child3.getAutofillType()); - assertEquals("", child3.getHint()); - assertNull(child3.getAutofillHints()); - assertFalse(child3.getDimensRect().isEmpty()); - // The field has no scroll, should always be zero. - assertEquals(0, child3.getDimensScrollX()); - assertEquals(0, child3.getDimensScrollY()); - TestViewStructure.TestHtmlInfo htmlInfo3 = child3.getHtmlInfo(); - assertEquals("textarea1", htmlInfo3.getAttribute("name")); + assertEquals("textarea1", htmlInfo2.getAttribute("name")); // Autofill form and verify filled values. SparseArray<AutofillValue> values = new SparseArray<AutofillValue>(); values.append(child0.getId(), AutofillValue.forText("Juan")); - values.append(child1.getId(), AutofillValue.forToggle(true)); - values.append(child2.getId(), AutofillValue.forList(1)); - values.append(child3.getId(), AutofillValue.forText("aaa")); + values.append(child1.getId(), AutofillValue.forList(1)); + values.append(child2.getId(), AutofillValue.forText("aaa")); cnt = getCallbackCount(); clearChangedValues(); invokeAutofill(values); @@ -618,10 +603,7 @@ waitForCallbackAndVerifyTypes( cnt, new Integer[] { - AUTOFILL_VALUE_CHANGED, - AUTOFILL_VALUE_CHANGED, - AUTOFILL_VALUE_CHANGED, - AUTOFILL_VALUE_CHANGED, + AUTOFILL_VALUE_CHANGED, AUTOFILL_VALUE_CHANGED, AUTOFILL_VALUE_CHANGED, }); // Verify form filled by Javascript @@ -629,18 +611,14 @@ executeJavaScriptAndWaitForResult("document.getElementById('text1').value;"); assertEquals("\"Juan\"", value0); String value1 = - executeJavaScriptAndWaitForResult("document.getElementById('checkbox1').value;"); - assertEquals("\"on\"", value1); - String value2 = executeJavaScriptAndWaitForResult("document.getElementById('select1').value;"); - assertEquals("\"2\"", value2); - String value3 = + assertEquals("\"2\"", value1); + String value2 = executeJavaScriptAndWaitForResult("document.getElementById('textarea1').value;"); - assertEquals("\"aaa\"", value3); + assertEquals("\"aaa\"", value2); ArrayList<Pair<Integer, AutofillValue>> changedValues = getChangedValues(); assertEquals("Juan", changedValues.get(0).second.getTextValue()); - assertTrue(changedValues.get(1).second.getToggleValue()); - assertEquals(1, changedValues.get(2).second.getListValue()); + assertEquals(1, changedValues.get(1).second.getListValue()); } /** Tests that a frame-transcending form is filled correctly. */ @@ -1536,7 +1514,10 @@ @Test @SmallTest - @CommandLineFlags.Add({"disable-features=AutofillServerCommunication"}) + @CommandLineFlags.Add({ + "disable-features=AutofillServerCommunication", + "enable-features=AutofillIgnoreCheckableElements" + }) @Feature({"AndroidWebView"}) public void testUaAutofillHints() throws Throwable { loadHTML( @@ -1555,7 +1536,7 @@ <input name=\"bill-country\" id=\"frmCountryB\"> <input type='submit'> </form>"""); - final int totalControls = 6; + final int totalControls = 5; int cnt = 0; executeJavaScriptAndWaitForResult("document.getElementById('frmAddressB').select();"); dispatchDownAndUpKeyEvents(KeyEvent.KEYCODE_A); @@ -1591,11 +1572,7 @@ TestViewStructure child4 = viewStructure.getChild(4); TestViewStructure.TestHtmlInfo htmlInfo4 = child4.getHtmlInfo(); - assertNull(htmlInfo4.getAttribute("ua-autofill-hints")); - - TestViewStructure child5 = viewStructure.getChild(5); - TestViewStructure.TestHtmlInfo htmlInfo5 = child5.getHtmlInfo(); - assertEquals("ADDRESS_HOME_COUNTRY", htmlInfo5.getAttribute("ua-autofill-hints")); + assertEquals("ADDRESS_HOME_COUNTRY", htmlInfo4.getAttribute("ua-autofill-hints")); } @Test
diff --git a/android_webview/test/data/web_tests/webexposed/global-interface-listing-expected.txt b/android_webview/test/data/web_tests/webexposed/global-interface-listing-expected.txt index ee63ffe2..e18c453 100644 --- a/android_webview/test/data/web_tests/webexposed/global-interface-listing-expected.txt +++ b/android_webview/test/data/web_tests/webexposed/global-interface-listing-expected.txt
@@ -9310,11 +9310,6 @@ setter onstart setter phrases setter processLocally -interface SpeechRecognitionContext - attribute @@toStringTag - getter phrases - method constructor - setter phrases interface SpeechRecognitionErrorEvent : Event attribute @@toStringTag getter error @@ -10682,7 +10677,6 @@ method clearStencil method clientWaitSync method colorMask - method commit method compileShader method compressedTexImage2D method compressedTexImage3D @@ -11234,7 +11228,6 @@ method clearDepth method clearStencil method colorMask - method commit method compileShader method compressedTexImage2D method compressedTexSubImage2D
diff --git a/build/config/siso/config.star b/build/config/siso/config.star index 4b0e4f5..960f09d3 100644 --- a/build/config/siso/config.star +++ b/build/config/siso/config.star
@@ -26,11 +26,6 @@ # because developers need objects and tests locally for debugging # and testing. "remote-link", - - # Unset timeout from rules to allow long remote steps. - # This is useful when remote actions are expected to take longer than - # the configured timeouts. e.g. remote linking for official builds. - "no-remote-timeout", ] def __check(ctx):
diff --git a/build/config/siso/main.star b/build/config/siso/main.star index 34bb20173..d08474a 100644 --- a/build/config/siso/main.star +++ b/build/config/siso/main.star
@@ -10,7 +10,6 @@ load("@builtin//struct.star", "module") load("./backend_config/backend.star", "backend") load("./blink_all.star", "blink_all") -load("./config.star", "config") load("./gn_logs.star", "gn_logs") load("./linux.star", chromium_linux = "chromium") load("./mac.star", chromium_mac = "chromium") @@ -29,13 +28,6 @@ rule["remote"] = False return step_config -def __unset_timeout(ctx, step_config): - if not config.get(ctx, "no-remote-timeout"): - return step_config - for rule in step_config["rules"]: - rule.pop("timeout", None) - return step_config - def init(ctx): print("runtime: os:%s arch:%s run:%d" % ( runtime.os, @@ -88,7 +80,6 @@ rule["remote_command"] = arg0 step_config = __disable_remote(ctx, step_config) - step_config = __unset_timeout(ctx, step_config) filegroups = {} filegroups.update(blink_all.filegroups(ctx))
diff --git a/cc/base/features.cc b/cc/base/features.cc index 1badd221..1391340 100644 --- a/cc/base/features.cc +++ b/cc/base/features.cc
@@ -59,10 +59,6 @@ #endif ); -BASE_FEATURE(kReclaimResourcesDelayedFlushInBackground, - "ReclaimResourcesDelayedFlushInBackground", - base::FEATURE_ENABLED_BY_DEFAULT); - BASE_FEATURE(kReclaimPrepaintTilesWhenIdle, "ReclaimPrepaintTilesWhenIdle", base::FEATURE_DISABLED_BY_DEFAULT);
diff --git a/cc/base/features.h b/cc/base/features.h index 0d898f9..2f2d17b 100644 --- a/cc/base/features.h +++ b/cc/base/features.h
@@ -35,12 +35,6 @@ // Use DMSAA instead of MSAA for rastering tiles. CC_BASE_EXPORT BASE_DECLARE_FEATURE(kUseDMSAAForTiles); -// When LayerTreeHostImpl::ReclaimResources() is called in background, trigger a -// additional delayed flush to reclaim resources. -// -// Enabled 03/2024, kept to run a holdback experiment. -CC_BASE_EXPORT BASE_DECLARE_FEATURE(kReclaimResourcesDelayedFlushInBackground); - // When no frames are produced in a certain time interval, reclaim prepaint // tiles. CC_BASE_EXPORT BASE_DECLARE_FEATURE(kReclaimPrepaintTilesWhenIdle);
diff --git a/cc/layers/picture_layer_impl.h b/cc/layers/picture_layer_impl.h index 8d423cf..5f73dd7 100644 --- a/cc/layers/picture_layer_impl.h +++ b/cc/layers/picture_layer_impl.h
@@ -214,7 +214,6 @@ bool CanRecreateHighResTilingForLCDTextAndRasterTransform( const PictureLayerTiling& high_res) const; void UpdateTilingsForRasterScaleAndTranslation(bool adjusted_raster_scale); - void AddLowResolutionTilingIfNeeded(); bool ShouldAdjustRasterScale() const; void RecalculateRasterScales(); void AdjustRasterScaleForTransformAnimation(
diff --git a/cc/scheduler/scheduler.cc b/cc/scheduler/scheduler.cc index b03e0d3..c31e9669 100644 --- a/cc/scheduler/scheduler.cc +++ b/cc/scheduler/scheduler.cc
@@ -14,6 +14,7 @@ #include "base/functional/bind.h" #include "base/location.h" #include "base/metrics/histogram_macros.h" +#include "base/rand_util.h" #include "base/task/delay_policy.h" #include "base/task/single_thread_task_runner.h" #include "base/time/time.h" @@ -196,9 +197,10 @@ void Scheduler::SetTreePrioritiesAndScrollState( TreePriority tree_priority, - ScrollHandlerState scroll_handler_state) { - state_machine_.SetTreePrioritiesAndScrollState(tree_priority, - scroll_handler_state); + ScrollHandlerState scroll_handler_state, + bool is_current_scroll_main_painted) { + state_machine_.SetTreePrioritiesAndScrollState( + tree_priority, scroll_handler_state, is_current_scroll_main_painted); ProcessScheduledActions(); } @@ -771,6 +773,10 @@ SchedulerStateMachine::BeginImplFrameDeadlineModeToString( deadline_mode_)); deadline_ = new_deadline; + if (base::ShouldRecordSubsampledMetric(0.001)) { + UMA_HISTOGRAM_ENUMERATION("Compositing.Scheduler.DeadlineMode", + deadline_mode_); + } static const unsigned char* debug_tracing_enabled = TRACE_EVENT_API_GET_CATEGORY_GROUP_ENABLED( TRACE_DISABLED_BY_DEFAULT("cc.debug.scheduler"));
diff --git a/cc/scheduler/scheduler.h b/cc/scheduler/scheduler.h index 6199fe9..5d3e730 100644 --- a/cc/scheduler/scheduler.h +++ b/cc/scheduler/scheduler.h
@@ -193,7 +193,8 @@ void DidReceiveCompositorFrameAck(); void SetTreePrioritiesAndScrollState(TreePriority tree_priority, - ScrollHandlerState scroll_handler_state); + ScrollHandlerState scroll_handler_state, + bool is_current_scroll_main_painted); // Commit step happens after the main thread has completed updating for a // BeginMainFrame request from the compositor, and blocks the main thread
diff --git a/cc/scheduler/scheduler_state_machine.cc b/cc/scheduler/scheduler_state_machine.cc index 7f7fde9..dad520a 100644 --- a/cc/scheduler/scheduler_state_machine.cc +++ b/cc/scheduler/scheduler_state_machine.cc
@@ -670,6 +670,14 @@ result = true; } + // Throttling main frame production when the current scroll is blocked on it + // is visually bad, as it loses the benefit of high refresh rate. Don't + // throttle. This is more expensive, but is required to reach perceptual + // visual parity between throttled and non-throttled scrolling. + if (is_current_scroll_main_painted_) { + result = false; + } + TRACE_EVENT_INSTANT("cc", __PRETTY_FUNCTION__, "result", result); return result; } @@ -1638,9 +1646,11 @@ void SchedulerStateMachine::SetTreePrioritiesAndScrollState( TreePriority tree_priority, - ScrollHandlerState scroll_handler_state) { + ScrollHandlerState scroll_handler_state, + bool is_current_scroll_main_painted) { tree_priority_ = tree_priority; scroll_handler_state_ = scroll_handler_state; + is_current_scroll_main_painted_ = is_current_scroll_main_painted; } void SchedulerStateMachine::SetCriticalBeginMainFrameToActivateIsFast(
diff --git a/cc/scheduler/scheduler_state_machine.h b/cc/scheduler/scheduler_state_machine.h index d7780929..f450525 100644 --- a/cc/scheduler/scheduler_state_machine.h +++ b/cc/scheduler/scheduler_state_machine.h
@@ -68,6 +68,7 @@ // The scheduler uses a deadline to wait for main thread updates before // submitting a compositor frame. BeginImplFrameDeadlineMode specifies when // the deadline should run. + // LINT.IfChange(BeginImplFrameDeadlineMode) enum class BeginImplFrameDeadlineMode { NONE = 0, // No deadline should be scheduled e.g. for synchronous // compositor. @@ -82,6 +83,8 @@ // frame arrives. kMaxValue = BLOCKED, }; + // LINT.ThenChange(//tools/metrics/histograms/metadata/compositing/enums.xml:BeginImplFrameDeadlineMode) + // TODO(nuskos): Update Scheduler::ScheduleBeginImplFrameDeadline event to // used typed macros so we can remove this ToString function. static const char* BeginImplFrameDeadlineModeToString( @@ -256,7 +259,8 @@ // Indicates whether to prioritize impl thread latency (i.e., animation // smoothness) over new content activation. void SetTreePrioritiesAndScrollState(TreePriority tree_priority, - ScrollHandlerState scroll_handler_state); + ScrollHandlerState scroll_handler_state, + bool is_current_scroll_main_painted); // Indicates if the main thread will likely respond within 1 vsync. void SetCriticalBeginMainFrameToActivateIsFast(bool is_fast); @@ -501,6 +505,7 @@ TreePriority tree_priority_ = NEW_CONTENT_TAKES_PRIORITY; ScrollHandlerState scroll_handler_state_ = ScrollHandlerState::SCROLL_DOES_NOT_AFFECT_SCROLL_HANDLER; + bool is_current_scroll_main_painted_ = false; bool critical_begin_main_frame_to_activate_is_fast_ = true; bool main_thread_missed_last_deadline_ = false; bool defer_begin_main_frame_ = false;
diff --git a/cc/scheduler/scheduler_state_machine_unittest.cc b/cc/scheduler/scheduler_state_machine_unittest.cc index da91b81..675a248 100644 --- a/cc/scheduler/scheduler_state_machine_unittest.cc +++ b/cc/scheduler/scheduler_state_machine_unittest.cc
@@ -8,7 +8,6 @@ #include <array> - #include "base/test/gtest_util.h" #include "base/test/scoped_feature_list.h" #include "base/time/time.h" @@ -16,6 +15,7 @@ #include "cc/base/features.h" #include "cc/metrics/begin_main_frame_metrics.h" #include "cc/scheduler/scheduler.h" +#include "cc/tiles/tile_priority.h" #include "components/viz/common/frame_sinks/begin_frame_args.h" #include "components/viz/test/begin_frame_args_test.h" #include "testing/gmock/include/gmock/gmock.h" @@ -1231,6 +1231,48 @@ EXPECT_EQ(begin_main_frame_count, 5); } +TEST(SchedulerStateMachineTest, + TestMainFrameThrottlingWithMainThreadScrolling) { + base::test::ScopedFeatureList scoped_feature_list_{ + features::kThrottleMainFrameTo60Hz}; + + SchedulerSettings default_scheduler_settings; + StateMachine state(default_scheduler_settings); + SET_UP_STATE(state); + + state.FrameIntervalUpdated(base::Hertz(120)); + state.AdvanceTimeBy(base::Seconds(1280)); // Start at an arbitrary point. + // Main thread scrolling. + state.SetTreePrioritiesAndScrollState( + SMOOTHNESS_TAKES_PRIORITY, + ScrollHandlerState::SCROLL_AFFECTS_SCROLL_HANDLER, true); + + int begin_main_frame_count = 0; + for (int i = 0; i < 10; i++) { + state.SetNeedsBeginMainFrame(); + begin_main_frame_count += + RunOneFrameAndReturnWhetherMainFrameIsIssued(state) ? 1 : 0; + state.AdvanceTimeBy(base::Hertz(120)); + } + + // No throttling. + EXPECT_EQ(begin_main_frame_count, 10); + + // Non-passive scroll handler, but not blocking. + state.SetTreePrioritiesAndScrollState( + SMOOTHNESS_TAKES_PRIORITY, + ScrollHandlerState::SCROLL_AFFECTS_SCROLL_HANDLER, false); + begin_main_frame_count = 0; + for (int i = 0; i < 10; i++) { + state.SetNeedsBeginMainFrame(); + begin_main_frame_count += + RunOneFrameAndReturnWhetherMainFrameIsIssued(state) ? 1 : 0; + state.AdvanceTimeBy(base::Hertz(120)); + } + // Throttling. + EXPECT_EQ(begin_main_frame_count, 5); +} + TEST(SchedulerStateMachineTest, TestProactiveMainFrameThrottling) { base::test::ScopedFeatureList scoped_feature_list; scoped_feature_list.InitAndEnableFeatureWithParameters( @@ -2484,7 +2526,7 @@ // Set smoothness priority (used while scrolling). state.SetTreePrioritiesAndScrollState( SMOOTHNESS_TAKES_PRIORITY, - ScrollHandlerState::SCROLL_DOES_NOT_AFFECT_SCROLL_HANDLER); + ScrollHandlerState::SCROLL_DOES_NOT_AFFECT_SCROLL_HANDLER, false); // Impl-side invalidation creates a pending tree which is not yet activated. bool needs_first_draw_on_activation = true; @@ -3114,7 +3156,7 @@ // should wait for main frame. state.SetTreePrioritiesAndScrollState( SMOOTHNESS_TAKES_PRIORITY, - ScrollHandlerState::SCROLL_DOES_NOT_AFFECT_SCROLL_HANDLER); + ScrollHandlerState::SCROLL_DOES_NOT_AFFECT_SCROLL_HANDLER, false); EXPECT_EQ(SchedulerStateMachine::BeginImplFrameDeadlineMode::BLOCKED, state.CurrentBeginImplFrameDeadlineMode()); @@ -3462,7 +3504,7 @@ EXPECT_FALSE(state.ShouldTriggerBeginImplFrameDeadlineImmediately()); state.SetTreePrioritiesAndScrollState( SMOOTHNESS_TAKES_PRIORITY, - ScrollHandlerState::SCROLL_DOES_NOT_AFFECT_SCROLL_HANDLER); + ScrollHandlerState::SCROLL_DOES_NOT_AFFECT_SCROLL_HANDLER, false); EXPECT_TRUE(state.ShouldTriggerBeginImplFrameDeadlineImmediately()); // Trigger the deadline. @@ -3551,7 +3593,7 @@ state.set_is_scrolling(true); state.SetTreePrioritiesAndScrollState( SMOOTHNESS_TAKES_PRIORITY, - ScrollHandlerState::SCROLL_DOES_NOT_AFFECT_SCROLL_HANDLER); + ScrollHandlerState::SCROLL_DOES_NOT_AFFECT_SCROLL_HANDLER, false); } // Tests that when we should wait for scroll events, that we do not send
diff --git a/cc/scheduler/scheduler_unittest.cc b/cc/scheduler/scheduler_unittest.cc index 7c12e9c0..3bfbac5 100644 --- a/cc/scheduler/scheduler_unittest.cc +++ b/cc/scheduler/scheduler_unittest.cc
@@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include "cc/scheduler/scheduler_state_machine.h" #ifdef UNSAFE_BUFFERS_BUILD // TODO(crbug.com/390223051): Remove C-library calls to fix the errors. #pragma allow_unsafe_libc_calls @@ -1699,7 +1700,7 @@ SetUpScheduler(EXTERNAL_BFS); scheduler_->SetTreePrioritiesAndScrollState( SMOOTHNESS_TAKES_PRIORITY, - ScrollHandlerState::SCROLL_DOES_NOT_AFFECT_SCROLL_HANDLER); + ScrollHandlerState::SCROLL_DOES_NOT_AFFECT_SCROLL_HANDLER, false); fake_compositor_timing_history_->SetAllEstimatesTo(kFastDuration); EXPECT_SCOPED(CheckMainFrameNotSkippedAfterLateCommit()); @@ -3383,7 +3384,7 @@ scheduler_->SetTreePrioritiesAndScrollState( SMOOTHNESS_TAKES_PRIORITY, - ScrollHandlerState::SCROLL_DOES_NOT_AFFECT_SCROLL_HANDLER); + ScrollHandlerState::SCROLL_DOES_NOT_AFFECT_SCROLL_HANDLER, false); scheduler_->SetCriticalBeginMainFrameToActivateIsFast(true); EXPECT_TRUE(scheduler_->ImplLatencyTakesPriority()); scheduler_->SetCriticalBeginMainFrameToActivateIsFast(false); @@ -3391,7 +3392,7 @@ scheduler_->SetTreePrioritiesAndScrollState( SMOOTHNESS_TAKES_PRIORITY, - ScrollHandlerState::SCROLL_AFFECTS_SCROLL_HANDLER); + ScrollHandlerState::SCROLL_AFFECTS_SCROLL_HANDLER, true); scheduler_->SetCriticalBeginMainFrameToActivateIsFast(true); EXPECT_FALSE(scheduler_->ImplLatencyTakesPriority()); scheduler_->SetCriticalBeginMainFrameToActivateIsFast(false); @@ -3399,7 +3400,7 @@ scheduler_->SetTreePrioritiesAndScrollState( SAME_PRIORITY_FOR_BOTH_TREES, - ScrollHandlerState::SCROLL_DOES_NOT_AFFECT_SCROLL_HANDLER); + ScrollHandlerState::SCROLL_DOES_NOT_AFFECT_SCROLL_HANDLER, false); scheduler_->SetCriticalBeginMainFrameToActivateIsFast(true); EXPECT_FALSE(scheduler_->ImplLatencyTakesPriority()); scheduler_->SetCriticalBeginMainFrameToActivateIsFast(false); @@ -3407,7 +3408,7 @@ scheduler_->SetTreePrioritiesAndScrollState( SAME_PRIORITY_FOR_BOTH_TREES, - ScrollHandlerState::SCROLL_AFFECTS_SCROLL_HANDLER); + ScrollHandlerState::SCROLL_AFFECTS_SCROLL_HANDLER, true); scheduler_->SetCriticalBeginMainFrameToActivateIsFast(true); EXPECT_FALSE(scheduler_->ImplLatencyTakesPriority()); scheduler_->SetCriticalBeginMainFrameToActivateIsFast(false); @@ -3527,8 +3528,10 @@ SetUpScheduler(EXTERNAL_BFS); fake_compositor_timing_history_->SetAllEstimatesTo(durations); client_->Reset(); - scheduler_->SetTreePrioritiesAndScrollState(tree_priority, - scroll_handler_state); + scheduler_->SetTreePrioritiesAndScrollState( + tree_priority, scroll_handler_state, + scroll_handler_state == + ScrollHandlerState::SCROLL_AFFECTS_SCROLL_HANDLER); scheduler_->SetNeedsBeginMainFrame(); EXPECT_FALSE(client_->last_begin_main_frame_args().IsValid()); EXPECT_SCOPED(AdvanceFrame()); @@ -3798,7 +3801,7 @@ // still prioritize impl thread latency. scheduler_->SetTreePrioritiesAndScrollState( SMOOTHNESS_TAKES_PRIORITY, - ScrollHandlerState::SCROLL_AFFECTS_SCROLL_HANDLER); + ScrollHandlerState::SCROLL_AFFECTS_SCROLL_HANDLER, true); scheduler_->SetNeedsRedraw(); // An interval of 2ms makes sure that the main frame is considered slow. base::TimeDelta interval = base::Milliseconds(2); @@ -3970,7 +3973,7 @@ scheduler_->SetNeedsBeginMainFrame(); scheduler_->SetTreePrioritiesAndScrollState( TreePriority::SMOOTHNESS_TAKES_PRIORITY, - ScrollHandlerState::SCROLL_DOES_NOT_AFFECT_SCROLL_HANDLER); + ScrollHandlerState::SCROLL_DOES_NOT_AFFECT_SCROLL_HANDLER, false); fake_compositor_timing_history_->SetAllEstimatesTo(kFastDuration); fake_compositor_timing_history_ ->SetBeginMainFrameQueueDurationNotCriticalEstimate(kSlowDuration);
diff --git a/cc/trees/layer_tree_host_impl.cc b/cc/trees/layer_tree_host_impl.cc index 40b97db1..718352d6 100644 --- a/cc/trees/layer_tree_host_impl.cc +++ b/cc/trees/layer_tree_host_impl.cc
@@ -2318,24 +2318,21 @@ // it to the GPU process to free up the memory. MaybeFlushPendingWork(); - if (base::FeatureList::IsEnabled( - features::kReclaimResourcesDelayedFlushInBackground)) { - // There are cases where the release callbacks executed from the call above - // don't actually free the GPU resource from this thread. For instance, for - // TextureLayer, - // TextureLayer::TransferableResourceHolder::~TransferableResourceHolder() - // posts a task to the main thread, and so flushing here is not sufficient. - // - // Ideally, we would not rely on a time-based delay, but given layering, - // threading and possibly unknown cases where the release can jump from - // thread to thread, this is likely a more practical solution. See - // crbug.com/1449271 for an example. - GetTaskRunner()->PostDelayedTask( - FROM_HERE, - base::BindOnce(&LayerTreeHostImpl::MaybeFlushPendingWork, - weak_factory_.GetWeakPtr()), - base::Seconds(1)); - } + // There are cases where the release callbacks executed from the call above + // don't actually free the GPU resource from this thread. For instance, for + // TextureLayer, + // TextureLayer::TransferableResourceHolder::~TransferableResourceHolder() + // posts a task to the main thread, and so flushing here is not sufficient. + // + // Ideally, we would not rely on a time-based delay, but given layering, + // threading and possibly unknown cases where the release can jump from + // thread to thread, this is likely a more practical solution. See + // crbug.com/1449271 for an example. + GetTaskRunner()->PostDelayedTask( + FROM_HERE, + base::BindOnce(&LayerTreeHostImpl::MaybeFlushPendingWork, + weak_factory_.GetWeakPtr()), + base::Seconds(1)); } void LayerTreeHostImpl::MaybeFlushPendingWork() {
diff --git a/cc/trees/proxy_impl.cc b/cc/trees/proxy_impl.cc index 29ff8f1..cadcb05 100644 --- a/cc/trees/proxy_impl.cc +++ b/cc/trees/proxy_impl.cc
@@ -30,6 +30,7 @@ #include "cc/metrics/compositor_timing_history.h" #include "cc/paint/paint_image.h" #include "cc/paint/paint_worklet_layer_painter.h" +#include "cc/scheduler/scheduler_state_machine.h" #include "cc/trees/commit_state.h" #include "cc/trees/compositor_commit_data.h" #include "cc/trees/layer_tree_frame_sink.h" @@ -528,6 +529,9 @@ host_impl_->IsPinchGestureActive() || host_impl_->page_scale_animation_active(); + bool is_current_scroll_main_painted = + host_impl_->IsCurrentScrollMainRepainted(); + // Schedule expiration if smoothness currently takes priority. if ((non_scroll_interaction_in_progress || precise_scrolling_in_progress) && !avoid_entering_smoothness) { @@ -549,7 +553,7 @@ // scroll offset change to be visible. if (host_impl_->active_tree()->GetDeviceViewport().size().IsEmpty() || host_impl_->EvictedUIResourcesExist() || - (host_impl_->IsCurrentScrollMainRepainted() && + (is_current_scroll_main_painted && base::FeatureList::IsEnabled( features::kMainRepaintScrollPrefersNewContent))) { // Once we enter NEW_CONTENTS_TAKES_PRIORITY mode, visible tiles on active @@ -565,12 +569,17 @@ // have a scroll listener. This gives the scroll listener a better chance of // handling scroll updates within the same frame. The tree itself is still // kept in prefer smoothness mode to allow checkerboarding. + // + // Note: `is_current_scroll_main_painted` does not imply + // SCROLL_AFFECTS_SCROLL_HANDLER, as on some platforms we don't attempt to + // synchronize non=passive scroll handlers. See `kSynchronizedScrolling`. ScrollHandlerState scroll_handler_state = host_impl_->ScrollAffectsScrollHandler() ? ScrollHandlerState::SCROLL_AFFECTS_SCROLL_HANDLER : ScrollHandlerState::SCROLL_DOES_NOT_AFFECT_SCROLL_HANDLER; - scheduler_->SetTreePrioritiesAndScrollState(tree_priority, - scroll_handler_state); + + scheduler_->SetTreePrioritiesAndScrollState( + tree_priority, scroll_handler_state, is_current_scroll_main_painted); } void ProxyImpl::PostDelayedAnimationTaskOnImplThread(base::OnceClosure task,
diff --git a/chrome/android/features/keyboard_accessory/internal/java/src/org/chromium/chrome/browser/keyboard_accessory/sheet_tabs/PasswordAccessorySheetViewBinder.java b/chrome/android/features/keyboard_accessory/internal/java/src/org/chromium/chrome/browser/keyboard_accessory/sheet_tabs/PasswordAccessorySheetViewBinder.java index cd8f9bc..8c7f3e3 100644 --- a/chrome/android/features/keyboard_accessory/internal/java/src/org/chromium/chrome/browser/keyboard_accessory/sheet_tabs/PasswordAccessorySheetViewBinder.java +++ b/chrome/android/features/keyboard_accessory/internal/java/src/org/chromium/chrome/browser/keyboard_accessory/sheet_tabs/PasswordAccessorySheetViewBinder.java
@@ -12,6 +12,7 @@ import android.view.View; import android.view.ViewGroup; +import androidx.appcompat.content.res.AppCompatResources; import androidx.recyclerview.widget.RecyclerView; import org.chromium.chrome.browser.autofill.helpers.FaviconHelper; @@ -90,14 +91,29 @@ bindChipView(view.getUsername(), info.getFields().get(0), view.getContext()); bindChipView(view.getPassword(), info.getFields().get(1), view.getContext()); - view.getTitle().setVisibility(info.isExactMatch() ? View.GONE : View.VISIBLE); - // Strip the trailing slash (for aesthetic reasons): - view.getTitle().setText(stripScheme(info.getOrigin()).replaceFirst("/$", "")); + view.getTitle() + .setVisibility( + info.isExactMatch() && !info.isBackupCredential() + ? View.GONE + : View.VISIBLE); + if (info.isBackupCredential()) { + view.getTitle().setText(R.string.password_accessory_recovery_password_title); + } else { + // Strip the trailing slash (for aesthetic reasons): + view.getTitle().setText(stripScheme(info.getOrigin()).replaceFirst("/$", "")); + } - // Set the default icon, then try to get a better one. - mFaviconRequestOrigin = info.getOrigin(); // Save the origin for returning callback. - view.setIconForBitmap(mFaviconHelper.getDefaultIcon(info.getOrigin())); - mFaviconHelper.fetchFavicon(info.getOrigin(), d -> setIcon(view, info.getOrigin(), d)); + if (info.isBackupCredential()) { + view.setIconForBitmap( + AppCompatResources.getDrawable( + view.getContext(), R.drawable.ic_history_24dp)); + } else { + // Set the default icon, then try to get a better one. + mFaviconRequestOrigin = info.getOrigin(); // Save the origin for returning callback. + view.setIconForBitmap(mFaviconHelper.getDefaultIcon(info.getOrigin())); + mFaviconHelper.fetchFavicon( + info.getOrigin(), d -> setIcon(view, info.getOrigin(), d)); + } } private void setIcon(
diff --git a/chrome/android/features/keyboard_accessory/internal/java/strings/android_keyboard_accessory_strings.grd b/chrome/android/features/keyboard_accessory/internal/java/strings/android_keyboard_accessory_strings.grd index 597f5c85..c4248f97 100644 --- a/chrome/android/features/keyboard_accessory/internal/java/strings/android_keyboard_accessory_strings.grd +++ b/chrome/android/features/keyboard_accessory/internal/java/strings/android_keyboard_accessory_strings.grd
@@ -262,6 +262,9 @@ <message name="IDS_SELECT_PASSKEY" desc="Title of the button that opens Android Credential Manager UI when there are no passkeys in the keyboard accessory. The user can select passkeys from other providers there."> Select passkey </message> + <message name="IDS_PASSWORD_ACCESSORY_RECOVERY_PASSWORD_TITLE" desc="Title marking that a saved credential offered for filling is a recovery password which the user can try filling if the main password doesn't work."> + Recovery password + </message> </messages> </release> </grit>
diff --git a/chrome/android/features/keyboard_accessory/internal/java/strings/android_keyboard_accessory_strings_grd/IDS_PASSWORD_ACCESSORY_RECOVERY_PASSWORD_TITLE.png.sha1 b/chrome/android/features/keyboard_accessory/internal/java/strings/android_keyboard_accessory_strings_grd/IDS_PASSWORD_ACCESSORY_RECOVERY_PASSWORD_TITLE.png.sha1 new file mode 100644 index 0000000..0824b5ae --- /dev/null +++ b/chrome/android/features/keyboard_accessory/internal/java/strings/android_keyboard_accessory_strings_grd/IDS_PASSWORD_ACCESSORY_RECOVERY_PASSWORD_TITLE.png.sha1
@@ -0,0 +1 @@ +e6115c2746af579441176058f5e2e5d4311c359b \ No newline at end of file
diff --git a/chrome/android/features/keyboard_accessory/javatests/src/org/chromium/chrome/browser/keyboard_accessory/sheet_tabs/PasswordAccessorySheetViewTest.java b/chrome/android/features/keyboard_accessory/javatests/src/org/chromium/chrome/browser/keyboard_accessory/sheet_tabs/PasswordAccessorySheetViewTest.java index 60bfc2c1..72d6e381 100644 --- a/chrome/android/features/keyboard_accessory/javatests/src/org/chromium/chrome/browser/keyboard_accessory/sheet_tabs/PasswordAccessorySheetViewTest.java +++ b/chrome/android/features/keyboard_accessory/javatests/src/org/chromium/chrome/browser/keyboard_accessory/sheet_tabs/PasswordAccessorySheetViewTest.java
@@ -344,6 +344,47 @@ @Test @MediumTest + public void testDisplaysBackupCredentialCorrectly() { + assertThat(mView.get().getChildCount(), is(0)); + final UserInfoField kUnusedInfoField = + new UserInfoField.Builder() + .setSuggestionType(AccessorySuggestionType.CREDENTIAL_USERNAME) + .setDisplayText("Unused Name") + .setA11yDescription("Unused Password") + .setCallback(cb -> {}) + .build(); + + ThreadUtils.runOnUiThreadBlocking( + () -> { + UserInfo mainCredentialInfo = new UserInfo("psl.matched.origin.com", false); + mainCredentialInfo.addField(kUnusedInfoField); + mainCredentialInfo.addField(kUnusedInfoField); + mModel.add( + new AccessorySheetDataPiece( + mainCredentialInfo, + AccessorySheetDataPiece.Type.PASSWORD_INFO)); + + UserInfo backupCredentialInfo = + new UserInfo("psl.matched.origin.com", false, null, true); + backupCredentialInfo.addField(kUnusedInfoField); + backupCredentialInfo.addField(kUnusedInfoField); + mModel.add( + new AccessorySheetDataPiece( + backupCredentialInfo, + AccessorySheetDataPiece.Type.PASSWORD_INFO)); + }); + + CriteriaHelper.pollUiThread(() -> Criteria.checkThat(mView.get().getChildCount(), is(2))); + assertThat(getUserInfoAt(0).getTitle().isShown(), is(true)); + assertThat(getUserInfoAt(0).getTitle().getText(), is("psl.matched.origin.com")); + assertThat(getUserInfoAt(1).getTitle().isShown(), is(true)); + assertThat( + getUserInfoAt(1).getTitle(), + withText(R.string.password_accessory_recovery_password_title)); + } + + @Test + @MediumTest public void testOptionToggleRenderedIfNotEmpty() throws ExecutionException { assertThat(mView.get().getChildCount(), is(0)); ThreadUtils.runOnUiThreadBlocking(
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/metrics/StartupLoadingMetricsTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/metrics/StartupLoadingMetricsTest.java index 285b212a..2f0e67a 100644 --- a/chrome/android/javatests/src/org/chromium/chrome/browser/metrics/StartupLoadingMetricsTest.java +++ b/chrome/android/javatests/src/org/chromium/chrome/browser/metrics/StartupLoadingMetricsTest.java
@@ -21,6 +21,7 @@ import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; +import org.chromium.base.BinderCallsListener; import org.chromium.base.ContextUtils; import org.chromium.base.ThreadUtils; import org.chromium.base.metrics.RecordHistogram; @@ -395,6 +396,15 @@ @Test @LargeTest public void testNtpBinderMetricRecordedCorrectly() throws Exception { + // Install BinderCallsListener. + boolean success = + ThreadUtils.runOnUiThreadBlocking( + () -> { + BinderCallsListener listener = BinderCallsListener.getInstance(); + return listener.installListener(); + }); + Assert.assertTrue(success); + HistogramWatcher ntpBinderWatcher = HistogramWatcher.newBuilder() .expectAnyRecordTimes(NTP_COLD_START_BINDER_HISTOGRAM, 1) @@ -402,6 +412,9 @@ runAndWaitForPageLoadMetricsRecorded( () -> mTabbedActivityTestRule.startMainActivityWithURL(UrlConstants.NTP_URL)); waitForHistogram(ntpBinderWatcher); + + // Clean up listener. + BinderCallsListener.setInstanceForTesting(null); } /**
diff --git a/chrome/android/modules/readaloud/public/BUILD.gn b/chrome/android/modules/readaloud/public/BUILD.gn index 8f8731e..29d2601 100644 --- a/chrome/android/modules/readaloud/public/BUILD.gn +++ b/chrome/android/modules/readaloud/public/BUILD.gn
@@ -30,6 +30,7 @@ "//components/prefs/android:java", "//content/public/android:content_java", "//third_party/androidx:androidx_annotation_annotation_java", + "//third_party/android_deps:guava_android_java", "//ui/android:ui_no_recycler_view_java", ] }
diff --git a/chrome/android/modules/readaloud/public/java/src/org/chromium/chrome/modules/readaloud/PlaybackArgs.java b/chrome/android/modules/readaloud/public/java/src/org/chromium/chrome/modules/readaloud/PlaybackArgs.java index b79fcf9..a06dc41 100644 --- a/chrome/android/modules/readaloud/public/java/src/org/chromium/chrome/modules/readaloud/PlaybackArgs.java +++ b/chrome/android/modules/readaloud/public/java/src/org/chromium/chrome/modules/readaloud/PlaybackArgs.java
@@ -15,6 +15,9 @@ import java.util.List; import java.util.Locale; +import com.google.common.collect.ImmutableList; + + /** Encapsulates information about the playback being requested. */ @NullMarked public class PlaybackArgs { @@ -31,7 +34,7 @@ private final long mDateModifiedMsSinceEpoch; /* The playback mode. Still unused. */ - private final PlaybackMode mPlaybackMode; + private final List<PlaybackMode> mPlaybackModes; /** Playback mode. */ public enum PlaybackMode { @@ -310,7 +313,7 @@ @Nullable String language, List<PlaybackVoice> voices, long dateModifiedMsSinceEpoch) { - this(mSource, isUrl, language, voices, dateModifiedMsSinceEpoch, PlaybackMode.UNSPECIFIED); + this(mSource, isUrl, language, voices, dateModifiedMsSinceEpoch, ImmutableList.of(PlaybackMode.UNSPECIFIED)); } public PlaybackArgs( @@ -319,14 +322,14 @@ @Nullable String language, List<PlaybackVoice> voices, long dateModifiedMsSinceEpoch, - PlaybackMode playbackMode) { + List<PlaybackMode> playbackModes) { this.mUrl = mSource; this.mSource = mSource; this.mIsSourceUrl = isUrl; this.mLanguage = language; this.mVoices = voices; this.mDateModifiedMsSinceEpoch = dateModifiedMsSinceEpoch; - this.mPlaybackMode = playbackMode; + this.mPlaybackModes = playbackModes; } /** Returns the URL of the playback page. */ @@ -360,9 +363,14 @@ return mDateModifiedMsSinceEpoch; } - /** Returns the playback mode. */ + /** Returns the playback mode. This method is to be deprecated and replaced by the list version. */ public PlaybackMode getPlaybackMode() { - return mPlaybackMode; + return mPlaybackModes.size() > 0 ? mPlaybackModes.get(0) : PlaybackMode.UNSPECIFIED; + } + + /** Returns the requested playback modes. */ + public List<PlaybackMode> getPlaybackModes() { + return mPlaybackModes; } // Override toString() to help with debug logging. @@ -387,8 +395,8 @@ + "\tdateModifiedMs=" + mDateModifiedMsSinceEpoch + "\n" - + "\tplaybackMode=" - + mPlaybackMode + + "\tplaybackModes=" + + mPlaybackModes + "\n" + "}"; }
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn index cf2283a6..8dae30ba 100644 --- a/chrome/browser/BUILD.gn +++ b/chrome/browser/BUILD.gn
@@ -4461,6 +4461,7 @@ "//chrome/browser/ui/webui/access_code_cast:impl", "//chrome/browser/ui/webui/actor_internals", "//chrome/browser/ui/webui/app_service_internals", + "//chrome/browser/ui/webui/autofill_ml_internals", "//chrome/browser/ui/webui/infobar_internals", "//chrome/browser/ui/webui/infobar_internals:impl", @@ -8684,6 +8685,7 @@ "//chrome/browser/v8_compile_hints/proto", "//chrome/browser/web_applications/mojom:mojom_web_apps_enum", "//chrome/common/request_header_integrity:buildflags", + "//components/autofill/core/browser/ml_model/logging:mojo_bindings", "//components/data_sharing/data_sharing_internals/webui:mojo_bindings", "//components/webui/chrome_urls/mojom:mojo_bindings", ] @@ -8733,6 +8735,7 @@ "//chrome/browser/ui/webui/web_app_internals:mojo_bindings", "//chrome/browser/ui/webui/whats_new:mojo_bindings", "//chrome/browser/webauthn/proto", + "//components/autofill/core/browser/ml_model/logging:mojo_bindings", ] if (!is_official_build) { public_deps +=
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc index e2e23f0f..d178612 100644 --- a/chrome/browser/about_flags.cc +++ b/chrome/browser/about_flags.cc
@@ -4848,6 +4848,12 @@ flag_descriptions::kExperimentalWebAssemblyJSPIName, flag_descriptions::kExperimentalWebAssemblyJSPIDescription, kOsAll, FEATURE_VALUE_TYPE(features::kEnableExperimentalWebAssemblyJSPI)}, + {"enable-experimental-webassembly-shared-everything", + flag_descriptions::kExperimentalWebAssemblySharedEverythingName, + flag_descriptions::kExperimentalWebAssemblySharedEverythingDescription, + kOsAll, + FEATURE_VALUE_TYPE( + features::kEnableExperimentalWebAssemblySharedEverything)}, {"enable-webassembly-baseline", flag_descriptions::kEnableWasmBaselineName, flag_descriptions::kEnableWasmBaselineDescription, kOsAll, FEATURE_VALUE_TYPE(features::kWebAssemblyBaseline)}, @@ -10197,24 +10203,11 @@ FEATURE_VALUE_TYPE( net::features::kCompressionDictionaryTransportRequireKnownRootCert)}, - {"enable-compute-pressure-rate-obfuscation-mitigation", - flag_descriptions::kComputePressureRateObfuscationMitigationName, - flag_descriptions::kComputePressureRateObfuscationMitigationDescription, - kOsAll, - FEATURE_VALUE_TYPE( - blink::features::kComputePressureRateObfuscationMitigation)}, - {"enable-container-type-no-layout-containment", flag_descriptions::kContainerTypeNoLayoutContainmentName, flag_descriptions::kContainerTypeNoLayoutContainmentDescription, kOsAll, FEATURE_VALUE_TYPE(blink::features::kContainerTypeNoLayoutContainment)}, - {"enable-compute-pressure-break-calibration-mitigation", - flag_descriptions::kComputePressureBreakCalibrationMitigationName, - flag_descriptions::kComputePressureBreakCalibrationMitigationDescription, - kOsAll, - FEATURE_VALUE_TYPE(features::kComputePressureBreakCalibrationMitigation)}, - #if BUILDFLAG(IS_ANDROID) {"deprecated-external-picker-function", flag_descriptions::kDeprecatedExternalPickerFunctionName, @@ -12035,10 +12028,6 @@ flag_descriptions::kDevToolsProjectSettingsName, flag_descriptions::kDevToolsProjectSettingsDescription, kOsDesktop, FEATURE_VALUE_TYPE(features::kDevToolsWellKnown)}, - {"devtools-automatic-workspace-folders", - flag_descriptions::kDevToolsAutomaticWorkspaceFoldersName, - flag_descriptions::kDevToolsAutomaticWorkspaceFoldersDescription, - kOsDesktop, FEATURE_VALUE_TYPE(features::kDevToolsAutomaticFileSystems)}, #endif // !BUILDFLAG(IS_ANDROID) #if BUILDFLAG(IS_ANDROID) @@ -12751,6 +12740,13 @@ chromeos::features::kMultiCaptureReworkedUsageIndicators)}, #endif // BUILDFLAG(IS_CHROMEOS) +#if BUILDFLAG(ENABLE_DICE_SUPPORT) + {"offer-migration-to-dice-users", + flag_descriptions::kOfferMigrationToDiceUsersName, + flag_descriptions::kOfferMigrationToDiceUsersDescription, kOsDesktop, + FEATURE_VALUE_TYPE(switches::kOfferMigrationToDiceUsers)} +#endif // BUILDFLAG(ENABLE_DICE_SUPPORT) + // Add new entries above this line. // NOTE: Adding a new flag requires adding a corresponding entry to enum
diff --git a/chrome/browser/ash/crosapi/login_ash.cc b/chrome/browser/ash/crosapi/login_ash.cc index 332b231..64a94c64 100644 --- a/chrome/browser/ash/crosapi/login_ash.cc +++ b/chrome/browser/ash/crosapi/login_ash.cc
@@ -40,67 +40,6 @@ receivers_.Add(this, std::move(receiver)); } -void LoginAsh::LaunchManagedGuestSession( - const std::optional<std::string>& password, - OptionalErrorCallback callback) { - ui::UserActivityDetector::Get()->HandleExternalUserActivity(); - - std::optional<std::string> error = CanLaunchSession(); - if (error) { - std::move(callback).Run(error); - return; - } - - user_manager::UserManager* user_manager = user_manager::UserManager::Get(); - for (const user_manager::User* user : user_manager->GetPersistedUsers()) { - if (!user || user->GetType() != user_manager::UserType::kPublicAccount) { - continue; - } - ash::UserContext context(user_manager::UserType::kPublicAccount, - user->GetAccountId()); - if (password) { - context.SetKey(ash::Key(*password)); - context.SetSamlPassword(ash::SamlPassword{*password}); - context.SetCanLockManagedGuestSession(true); - } - - auto* existing_user_controller = - ash::ExistingUserController::current_controller(); - existing_user_controller->Login(context, ash::SigninSpecifics()); - std::move(callback).Run(std::nullopt); - return; - } - std::move(callback).Run( - extensions::login_api_errors::kNoManagedGuestSessionAccounts); -} - -void LoginAsh::LaunchSamlUserSession(const std::string& email, - const GaiaId& gaia_id, - const std::string& password, - const std::string& oauth_code, - OptionalErrorCallback callback) { - ui::UserActivityDetector::Get()->HandleExternalUserActivity(); - std::optional<std::string> error = CanLaunchSession(); - if (error) { - std::move(callback).Run(error); - return; - } - - ash::UserContext context(user_manager::UserType::kRegular, - AccountId::FromUserEmailGaiaId(email, gaia_id)); - ash::Key key(password); - key.SetLabel(ash::kCryptohomeGaiaKeyLabel); - context.SetKey(key); - context.SetSamlPassword(ash::SamlPassword{password}); - context.SetPasswordKey(ash::Key(password)); - context.SetAuthFlow(ash::UserContext::AUTH_FLOW_GAIA_WITH_SAML); - context.SetIsUsingSamlPrincipalsApi(false); - context.SetAuthCode(oauth_code); - - ash::LoginDisplayHost::default_host()->CompleteLogin(context); - std::move(callback).Run(std::nullopt); -} - void LoginAsh::AddExternalLogoutRequestObserver( mojo::PendingRemote<mojom::ExternalLogoutRequestObserver> observer) { mojo::Remote<mojom::ExternalLogoutRequestObserver> remote( @@ -165,35 +104,4 @@ NOTIMPLEMENTED(); } -void LoginAsh::OnScreenLockerAuthenticate(OptionalErrorCallback callback, - bool success) { - if (!success) { - std::move(callback).Run( - extensions::login_api_errors::kAuthenticationFailed); - return; - } - - std::move(callback).Run(std::nullopt); -} - -void LoginAsh::OnOptionalErrorCallbackComplete( - OptionalErrorCallback callback, - const std::optional<std::string>& error) { - std::move(callback).Run(error); -} - -std::optional<std::string> LoginAsh::CanLaunchSession() { - if (session_manager::SessionManager::Get()->session_state() != - session_manager::SessionState::LOGIN_PRIMARY) { - return extensions::login_api_errors::kAlreadyActiveSession; - } - - auto* existing_user_controller = - ash::ExistingUserController::current_controller(); - if (existing_user_controller->IsSigninInProgress()) - return extensions::login_api_errors::kAnotherLoginAttemptInProgress; - - return std::nullopt; -} - } // namespace crosapi
diff --git a/chrome/browser/ash/crosapi/login_ash.h b/chrome/browser/ash/crosapi/login_ash.h index 4a4c0e10..b7af0f5d 100644 --- a/chrome/browser/ash/crosapi/login_ash.h +++ b/chrome/browser/ash/crosapi/login_ash.h
@@ -19,8 +19,6 @@ #include "mojo/public/cpp/bindings/receiver_set.h" #include "mojo/public/cpp/bindings/remote_set.h" -class GaiaId; - namespace crosapi { // The ash-chrome implementation of the Login crosapi interface. @@ -64,23 +62,6 @@ void REMOVED_12(const std::string& password, REMOVED_12Callback callback) override; - // Launches a managed guest session if one is set up via the admin console. - // If there are several managed guest sessions set up, it will launch the - // first available one. - // If a password is provided, the Managed Guest Session will be lockable and - // can be unlocked by providing the same password to - // `UnlockManagedGuestSession()`. - void LaunchManagedGuestSession(const std::optional<std::string>& password, - OptionalErrorCallback callback); - - // Launches a SAML user session with the provided email, gaiaId, password - // and oauth_code cookie. - void LaunchSamlUserSession(const std::string& email, - const GaiaId& gaia_id, - const std::string& password, - const std::string& oauth_code, - OptionalErrorCallback callback); - // Adds an observer for the external logout done events. void AddExternalLogoutDoneObserver(ExternalLogoutDoneObserver* observer); // Required for the below `base::ObserverList`: @@ -92,11 +73,6 @@ void NotifyOnRequestExternalLogout(); private: - void OnScreenLockerAuthenticate(OptionalErrorCallback callback, bool success); - void OnOptionalErrorCallbackComplete(OptionalErrorCallback callback, - const std::optional<std::string>& error); - std::optional<std::string> CanLaunchSession(); - mojo::ReceiverSet<mojom::Login> receivers_; // Support any number of observers.
diff --git a/chrome/browser/ash/login/app_mode/kiosk_launch_controller_unittest.cc b/chrome/browser/ash/login/app_mode/kiosk_launch_controller_unittest.cc index 0f73c07..aaebf36e 100644 --- a/chrome/browser/ash/login/app_mode/kiosk_launch_controller_unittest.cc +++ b/chrome/browser/ash/login/app_mode/kiosk_launch_controller_unittest.cc
@@ -666,7 +666,7 @@ HasViewState( AppLaunchSplashScreenView::AppLaunchState::kInstallingApplication)); - screen().CloseNetworkConfigureUI(); + screen().ContinueAppLaunch(); EXPECT_THAT( screen(), HasViewState(
diff --git a/chrome/browser/ash/login/app_mode/network_ui_controller.cc b/chrome/browser/ash/login/app_mode/network_ui_controller.cc index e8bbd1d..c347f08 100644 --- a/chrome/browser/ash/login/app_mode/network_ui_controller.cc +++ b/chrome/browser/ash/login/app_mode/network_ui_controller.cc
@@ -42,6 +42,17 @@ return connection_type; } +// Returns the `kAccountsPrefDeviceLocalAccountPromptForNetworkWhenOffline` +// setting if set, or `std::nullopt` otherwise. +std::optional<bool> GetPromptForNetworkWhenOfflineSetting() { + if (bool value; ash::CrosSettings::Get()->GetBoolean( + ash::kAccountsPrefDeviceLocalAccountPromptForNetworkWhenOffline, + &value)) { + return value; + } + return {}; +} + } // namespace namespace ash { @@ -153,7 +164,7 @@ if (network_showing_after_timeout) { SYSLOG(INFO) << "Network is online, closing network configure screen."; - splash_screen_->CloseNetworkConfigureUI(); + CloseNetworkConfigureUI(); } else { observer_->OnNetworkReady(); } @@ -163,6 +174,10 @@ observer_->OnNetworkLost(); } +void NetworkUiController::CloseNetworkConfigureUI() { + splash_screen_->ContinueAppLaunch(); +} + bool NetworkUiController::IsNetworkReady() const { return network_monitor_->GetState() == NetworkStateInformer::ONLINE; } @@ -217,13 +232,8 @@ return false; } - if (bool value; ash::CrosSettings::Get()->GetBoolean( - ash::kAccountsPrefDeviceLocalAccountPromptForNetworkWhenOffline, - &value)) { - return value; - } - // Default to true when the policy is missing. - return true; + // Default to true when the setting is missing. + return GetPromptForNetworkWhenOfflineSetting().value_or(true); } // static
diff --git a/chrome/browser/ash/login/app_mode/network_ui_controller.h b/chrome/browser/ash/login/app_mode/network_ui_controller.h index edeb5f8..7b373547 100644 --- a/chrome/browser/ash/login/app_mode/network_ui_controller.h +++ b/chrome/browser/ash/login/app_mode/network_ui_controller.h
@@ -107,6 +107,7 @@ void OnNetworkStateChanged(bool online); void MaybeShowNetworkConfigureUI(); void ShowNetworkConfigureUI(); + void CloseNetworkConfigureUI(); void OnNetworkWaitTimeout(); bool CanConfigureNetwork();
diff --git a/chrome/browser/ash/login/screens/app_launch_splash_screen.cc b/chrome/browser/ash/login/screens/app_launch_splash_screen.cc index 42b58d7..cc34fcc 100644 --- a/chrome/browser/ash/login/screens/app_launch_splash_screen.cc +++ b/chrome/browser/ash/login/screens/app_launch_splash_screen.cc
@@ -170,7 +170,7 @@ KioskAppLaunchError::GetErrorMessage(error)); } -void AppLaunchSplashScreen::CloseNetworkConfigureUI() { +void AppLaunchSplashScreen::ContinueAppLaunch() { if (!delegate_) { return; }
diff --git a/chrome/browser/ash/login/screens/app_launch_splash_screen.h b/chrome/browser/ash/login/screens/app_launch_splash_screen.h index 4ca57e3..f487f7f 100644 --- a/chrome/browser/ash/login/screens/app_launch_splash_screen.h +++ b/chrome/browser/ash/login/screens/app_launch_splash_screen.h
@@ -67,7 +67,7 @@ virtual void HideThrobber(); // Continues app launch after error screen is shown. - virtual void CloseNetworkConfigureUI(); + virtual void ContinueAppLaunch(); // Sets the network configuration controller. void SetDelegate(Delegate* delegate);
diff --git a/chrome/browser/ash/login/screens/error_screen.cc b/chrome/browser/ash/login/screens/error_screen.cc index 8a32ec8..08d15eb 100644 --- a/chrome/browser/ash/login/screens/error_screen.cc +++ b/chrome/browser/ash/login/screens/error_screen.cc
@@ -397,7 +397,7 @@ DCHECK_EQ(parent_screen_, AppLaunchSplashScreenView::kScreenId.AsId()); WizardController::default_controller() ->GetScreen<AppLaunchSplashScreen>() - ->CloseNetworkConfigureUI(); + ->ContinueAppLaunch(); } void ErrorScreen::LaunchHelpApp(int help_topic_id) {
diff --git a/chrome/browser/ash/login/screens/fake_app_launch_splash_screen.cc b/chrome/browser/ash/login/screens/fake_app_launch_splash_screen.cc index 309cd1fb..cf7ef6b 100644 --- a/chrome/browser/ash/login/screens/fake_app_launch_splash_screen.cc +++ b/chrome/browser/ash/login/screens/fake_app_launch_splash_screen.cc
@@ -26,7 +26,7 @@ NetworkStateInformer::State state, const std::string& network_name) {} -void FakeAppLaunchSplashScreen::CloseNetworkConfigureUI() { +void FakeAppLaunchSplashScreen::ContinueAppLaunch() { if (delegate_) { delegate_->OnNetworkConfigFinished(); }
diff --git a/chrome/browser/ash/login/screens/fake_app_launch_splash_screen.h b/chrome/browser/ash/login/screens/fake_app_launch_splash_screen.h index bc8c8a2..3e5255b 100644 --- a/chrome/browser/ash/login/screens/fake_app_launch_splash_screen.h +++ b/chrome/browser/ash/login/screens/fake_app_launch_splash_screen.h
@@ -25,7 +25,7 @@ // AppLaunchSplashScreen overrides: void ShowNetworkConfigureUI(NetworkStateInformer::State state, const std::string& network_name) override; - void CloseNetworkConfigureUI() override; + void ContinueAppLaunch() override; void ShowErrorMessage(KioskAppLaunchError::Error error) override; void HideThrobber() override;
diff --git a/chrome/browser/ash/policy/core/device_cloud_policy_store_ash.cc b/chrome/browser/ash/policy/core/device_cloud_policy_store_ash.cc index 3515230..fbe983d 100644 --- a/chrome/browser/ash/policy/core/device_cloud_policy_store_ash.cc +++ b/chrome/browser/ash/policy/core/device_cloud_policy_store_ash.cc
@@ -179,11 +179,9 @@ if (CanUseDeviceIdValidation()) { validator->ValidateDeviceId(install_attributes_->GetDeviceId(), CloudPolicyValidatorBase::DEVICE_ID_REQUIRED); + } else { + validator->ValidateDomain(install_attributes_->GetDomain()); } - - // TODO(b:256551074): The domain validation is planned to be removed when we - // confirm that the device_id validation works. - validator->ValidateDomain(install_attributes_->GetDomain()); validator->ValidatePolicyType(dm_protocol::kChromeDevicePolicyType); validator->ValidatePayload(); validator->ValidateValues(std::make_unique<ONCDevicePolicyValueValidator>());
diff --git a/chrome/browser/autofill/form_structure_browsertest.cc b/chrome/browser/autofill/form_structure_browsertest.cc index 1fc721e1..3cb21ac 100644 --- a/chrome/browser/autofill/form_structure_browsertest.cc +++ b/chrome/browser/autofill/form_structure_browsertest.cc
@@ -212,6 +212,7 @@ features::kAutofillEnableSupportForParsingWithSharedLabels, // TODO(crbug.com/40266396): Remove once launched. features::kAutofillEnableExpirationDateImprovements, + features::kAutofillIgnoreCheckableElements, features::kAutofillUnifyRationalizationAndSectioningOrder, }, // Disabled
diff --git a/chrome/browser/chrome_browser_interface_binders_webui.cc b/chrome/browser/chrome_browser_interface_binders_webui.cc index 05216ba..4e79b44 100644 --- a/chrome/browser/chrome_browser_interface_binders_webui.cc +++ b/chrome/browser/chrome_browser_interface_binders_webui.cc
@@ -90,6 +90,7 @@ #include "chrome/browser/ui/webui/actor_internals/actor_internals_ui.h" #include "chrome/browser/ui/webui/app_service_internals/app_service_internals.mojom.h" #include "chrome/browser/ui/webui/app_service_internals/app_service_internals_ui.h" +#include "chrome/browser/ui/webui/autofill_ml_internals/autofill_ml_internals_ui.h" #include "chrome/browser/ui/webui/commerce/product_specifications_ui.h" #include "chrome/browser/ui/webui/commerce/shopping_insights_side_panel_ui.h" #include "chrome/browser/ui/webui/customize_buttons/customize_buttons.mojom.h" @@ -132,6 +133,7 @@ #include "chrome/browser/ui/webui/web_app_internals/web_app_internals.mojom.h" #include "chrome/browser/ui/webui/web_app_internals/web_app_internals_ui.h" #include "chrome/browser/ui/webui/webui_gallery/webui_gallery_ui.h" +#include "components/autofill/core/browser/ml_model/logging/autofill_ml_internals.mojom.h" #include "components/commerce/core/mojom/product_specifications.mojom.h" #include "components/commerce/core/mojom/shopping_service.mojom.h" // nogncheck crbug.com/1125897 #include "components/optimization_guide/core/optimization_guide_features.h" @@ -772,6 +774,9 @@ AppServiceInternalsUI>(map); RegisterWebUIControllerInterfaceBinder< + ::autofill_ml_internals::mojom::PageHandler, AutofillMlInternalsUI>(map); + + RegisterWebUIControllerInterfaceBinder< access_code_cast::mojom::PageHandlerFactory, media_router::AccessCodeCastUI>(map);
diff --git a/chrome/browser/chromeos/extensions/login_screen/login/BUILD.gn b/chrome/browser/chromeos/extensions/login_screen/login/BUILD.gn index 65d249a..2da1a2262 100644 --- a/chrome/browser/chromeos/extensions/login_screen/login/BUILD.gn +++ b/chrome/browser/chromeos/extensions/login_screen/login/BUILD.gn
@@ -33,6 +33,7 @@ "//chrome/browser/ash/login", "//chrome/browser/ash/login/lock", "//chrome/browser/chromeos/extensions/login_screen/login/cleanup", + "//chrome/browser/ui/ash/login", "//chrome/browser/ui/ash/session", "//chrome/common:constants", "//chrome/common/extensions/api",
diff --git a/chrome/browser/chromeos/extensions/login_screen/login/login_api.cc b/chrome/browser/chromeos/extensions/login_screen/login/login_api.cc index f15941d..1f1fb7a 100644 --- a/chrome/browser/chromeos/extensions/login_screen/login/login_api.cc +++ b/chrome/browser/chromeos/extensions/login_screen/login/login_api.cc
@@ -15,13 +15,17 @@ #include "chrome/browser/ash/crosapi/crosapi_ash.h" #include "chrome/browser/ash/crosapi/crosapi_manager.h" #include "chrome/browser/ash/crosapi/login_ash.h" +#include "chrome/browser/ash/login/existing_user_controller.h" +#include "chrome/browser/ash/login/signin_specifics.h" #include "chrome/browser/browser_process.h" #include "chrome/browser/chromeos/extensions/login_screen/login/errors.h" #include "chrome/browser/chromeos/extensions/login_screen/login/login_api_lock_handler.h" #include "chrome/browser/chromeos/extensions/login_screen/login/shared_session_handler.h" #include "chrome/browser/lifetime/application_lifetime.h" +#include "chrome/browser/ui/ash/login/login_display_host.h" #include "chrome/common/extensions/api/login.h" #include "chrome/common/pref_names.h" +#include "chromeos/ash/components/login/auth/public/cryptohome_key_constants.h" #include "chromeos/ash/components/login/auth/public/key.h" #include "chromeos/ash/components/login/auth/public/user_context.h" #include "components/prefs/pref_service.h" @@ -115,6 +119,34 @@ .Then(std::move(callback)); } +base::expected<void, std::string> CanLaunchSession() { + if (session_manager::SessionManager::Get()->session_state() != + session_manager::SessionState::LOGIN_PRIMARY) { + return base::unexpected( + extensions::login_api_errors::kAlreadyActiveSession); + } + + auto* existing_user_controller = + ash::ExistingUserController::current_controller(); + if (existing_user_controller->IsSigninInProgress()) { + return base::unexpected( + extensions::login_api_errors::kAnotherLoginAttemptInProgress); + } + + return base::ok(); +} + +user_manager::User* FindPublicAccountUser() { + for (user_manager::User* user : + user_manager::UserManager::Get()->GetPersistedUsers()) { + CHECK(user); + if (user->GetType() == user_manager::UserType::kPublicAccount) { + return user; + } + } + return nullptr; +} + } // namespace namespace internal { @@ -157,15 +189,30 @@ api::login::LaunchManagedGuestSession::Params::Create(args()); EXTENSION_FUNCTION_VALIDATE(parameters); - auto callback = - base::BindOnce(&LoginLaunchManagedGuestSessionFunction::OnResult, this); + ui::UserActivityDetector::Get()->HandleExternalUserActivity(); - std::optional<std::string> password; - if (parameters->password) { - password = std::move(*parameters->password); + if (auto can_launch = CanLaunchSession(); !can_launch.has_value()) { + return RespondNow(Error(std::move(can_launch.error()))); } - GetLoginApi()->LaunchManagedGuestSession(password, std::move(callback)); - return did_respond() ? AlreadyResponded() : RespondLater(); + + const user_manager::User* user = FindPublicAccountUser(); + if (!user) { + return RespondNow( + Error(extensions::login_api_errors::kNoManagedGuestSessionAccounts)); + } + + ash::UserContext context(user_manager::UserType::kPublicAccount, + user->GetAccountId()); + if (parameters->password) { + context.SetKey(ash::Key(*parameters->password)); + context.SetSamlPassword(ash::SamlPassword{*parameters->password}); + context.SetCanLockManagedGuestSession(true); + } + + auto* existing_user_controller = + ash::ExistingUserController::current_controller(); + existing_user_controller->Login(context, ash::SigninSpecifics()); + return RespondNow(NoArguments()); } LoginExitCurrentSessionFunction::LoginExitCurrentSessionFunction() = default; @@ -265,14 +312,26 @@ auto parameters = api::login::LaunchSamlUserSession::Params::Create(args()); EXTENSION_FUNCTION_VALIDATE(parameters); - auto callback = - base::BindOnce(&LoginLaunchSamlUserSessionFunction::OnResult, this); + ui::UserActivityDetector::Get()->HandleExternalUserActivity(); + if (auto can_launch = CanLaunchSession(); !can_launch.has_value()) { + return RespondNow(Error(std::move(can_launch.error()))); + } - GetLoginApi()->LaunchSamlUserSession( - parameters->properties.email, GaiaId(parameters->properties.gaia_id), - parameters->properties.password, parameters->properties.oauth_code, - std::move(callback)); - return RespondLater(); + ash::UserContext context( + user_manager::UserType::kRegular, + AccountId::FromUserEmailGaiaId(parameters->properties.email, + GaiaId(parameters->properties.gaia_id))); + ash::Key key(parameters->properties.password); + key.SetLabel(ash::kCryptohomeGaiaKeyLabel); + context.SetKey(key); + context.SetSamlPassword(ash::SamlPassword{parameters->properties.password}); + context.SetPasswordKey(ash::Key(parameters->properties.password)); + context.SetAuthFlow(ash::UserContext::AUTH_FLOW_GAIA_WITH_SAML); + context.SetIsUsingSamlPrincipalsApi(false); + context.SetAuthCode(parameters->properties.oauth_code); + + ash::LoginDisplayHost::default_host()->CompleteLogin(context); + return RespondNow(NoArguments()); } LoginLaunchSharedManagedGuestSessionFunction::
diff --git a/chrome/browser/devtools/devtools_ui_bindings.cc b/chrome/browser/devtools/devtools_ui_bindings.cc index 23eb734..cf4ac76 100644 --- a/chrome/browser/devtools/devtools_ui_bindings.cc +++ b/chrome/browser/devtools/devtools_ui_bindings.cc
@@ -1322,15 +1322,6 @@ DCHECK_CURRENTLY_ON(BrowserThread::UI); CHECK(IsValidFrontendURL(web_contents_->GetLastCommittedURL()) && frontend_host_); - // This is a no-op if the DevToolsAutomaticFileSystems feature is turned off. - if (!base::FeatureList::IsEnabled(features::kDevToolsAutomaticFileSystems)) { - VLOG(1) << "Ignoring attempt to connect automatic file system " - << file_system_path << " with UUID " << file_system_uuid - << " because the DevToolsAutomaticFileSystems feature is disabled"; - ConnectAutomaticFileSystemDone(std::move(callback), false); - return; - } - // Ensure that the |file_system_uuid| is indeed a valid UUID. base::Uuid uuid = base::Uuid::ParseCaseInsensitive(file_system_uuid); if (!uuid.is_valid()) { @@ -1780,13 +1771,6 @@ std::move(ai_assistance_file_agent_dict)); } - base::Value::Dict devtools_automatic_file_systems_dict; - devtools_automatic_file_systems_dict.Set( - "enabled", - base::FeatureList::IsEnabled(::features::kDevToolsAutomaticFileSystems)); - response_dict.Set("devToolsAutomaticFileSystems", - std::move(devtools_automatic_file_systems_dict)); - base::Value::Dict devtools_well_known_dict; devtools_well_known_dict.Set( "enabled", base::FeatureList::IsEnabled(::features::kDevToolsWellKnown));
diff --git a/chrome/browser/devtools/features.cc b/chrome/browser/devtools/features.cc index 108b4052..aa370d7 100644 --- a/chrome/browser/devtools/features.cc +++ b/chrome/browser/devtools/features.cc
@@ -136,12 +136,6 @@ "DevToolsAnimationStylesInStylesTab", base::FEATURE_ENABLED_BY_DEFAULT); -// Whether DevTools will attempt to automatically connect Workspace folders. -// See http://go/chrome-devtools:automatic-workspace-folders-design for details. -BASE_FEATURE(kDevToolsAutomaticFileSystems, - "DevToolsAutomaticFileSystems", - base::FEATURE_ENABLED_BY_DEFAULT); - // Whether DevTools will attempt to load project settings from a well-known // URI. See https://goo.gle/devtools-json-design for additional details. // This is enabled by default starting with M-136.
diff --git a/chrome/browser/devtools/features.h b/chrome/browser/devtools/features.h index 134cf81..f6588605 100644 --- a/chrome/browser/devtools/features.h +++ b/chrome/browser/devtools/features.h
@@ -82,8 +82,6 @@ BASE_DECLARE_FEATURE(kDevToolsAnimationStylesInStylesTab); -BASE_DECLARE_FEATURE(kDevToolsAutomaticFileSystems); - BASE_DECLARE_FEATURE(kDevToolsWellKnown); BASE_DECLARE_FEATURE(kDevToolsAiGeneratedTimelineLabels);
diff --git a/chrome/browser/download/internal/android/java/src/org/chromium/chrome/browser/download/home/DownloadActivityV2Test.java b/chrome/browser/download/internal/android/java/src/org/chromium/chrome/browser/download/home/DownloadActivityV2Test.java index f54a0979..f3cb1ff 100644 --- a/chrome/browser/download/internal/android/java/src/org/chromium/chrome/browser/download/home/DownloadActivityV2Test.java +++ b/chrome/browser/download/internal/android/java/src/org/chromium/chrome/browser/download/home/DownloadActivityV2Test.java
@@ -482,6 +482,7 @@ @Test @MediumTest + @DisabledTest(message = "crbug.com/427410747") public void testDeleteDangerousUsingMenu() throws Exception { ThreadUtils.runOnUiThreadBlocking( () -> { @@ -507,6 +508,7 @@ @Test @MediumTest + @DisabledTest(message = "crbug.com/427410747") public void testDeleteDangerousUsingSelection() throws Exception { ThreadUtils.runOnUiThreadBlocking( () -> {
diff --git a/chrome/browser/file_system_access/file_system_access_features.cc b/chrome/browser/file_system_access/file_system_access_features.cc index 3ff46cd..a15a8d71 100644 --- a/chrome/browser/file_system_access/file_system_access_features.cc +++ b/chrome/browser/file_system_access/file_system_access_features.cc
@@ -21,6 +21,12 @@ // resolves any symbolic link. BASE_FEATURE(kFileSystemAccessSymbolicLinkCheck, "FileSystemAccessSymbolicLinkCheck", - base::FEATURE_ENABLED_BY_DEFAULT); +// TODO(crbug.com/428455312): Enable for Windows +#if BUILDFLAG(IS_WIN) + base::FEATURE_DISABLED_BY_DEFAULT +#else + base::FEATURE_ENABLED_BY_DEFAULT +#endif +); } // namespace features
diff --git a/chrome/browser/flag-metadata.json b/chrome/browser/flag-metadata.json index 675131e..b0658cdd 100644 --- a/chrome/browser/flag-metadata.json +++ b/chrome/browser/flag-metadata.json
@@ -2139,11 +2139,6 @@ "expiry_milestone": 140 }, { - "name": "devtools-automatic-workspace-folders", - "owners": [ "bmeurer@chromium.org", "alexrudenko@chromium.org" ], - "expiry_milestone": 140 - }, - { "name": "devtools-privacy-ui", "owners": [ "jalonthomas@chromium.org", "shuuran@chromium.org", "masnoble@chromium.org" ], "expiry_milestone": 150 @@ -2953,16 +2948,6 @@ "expiry_milestone": 133 }, { - "name": "enable-compute-pressure-break-calibration-mitigation", - "owners": [ "arnaud.mandy@intel.com" ], - "expiry_milestone": 140 - }, - { - "name": "enable-compute-pressure-rate-obfuscation-mitigation", - "owners": [ "arnaud.mandy@intel.com" ], - "expiry_milestone": 140 - }, - { "name": "enable-container-type-no-layout-containment", "owners": [ "ikilpatrick@chromium.org" ], "expiry_milestone": 140 @@ -3316,7 +3301,11 @@ }, { "name": "enable-experimental-webassembly-features", - "owners": [ "adamk@chromium.org", "hablich@chromium.org" ], + "owners": [ + "ecmziegler@chromium.org", + "jkummerow@chromium.org", + "wasm-team@google.com" + ], // This flag is used by web developers to test upcoming WebAssembly // features. "expiry_milestone": -1 @@ -3329,6 +3318,17 @@ "expiry_milestone": 140 }, { + "name": "enable-experimental-webassembly-shared-everything", + "owners": [ + "mliedtke@chromium.org", + "manoskouk@chromium.org", + "wasm-team@google.com" + ], + // This flag is used by web developers to test WebAssembly's + // shared-everything-threads features. + "expiry_milestone": 150 + }, + { "name": "enable-expkit-text-classifier", // This flags is use for 'confidence_score_threshold' only. "owners": [ "chrome-intelligence-core@google.com" ], @@ -5430,7 +5430,7 @@ { "name": "happy-eyeballs-v3", "owners": [ "bashi@chromium.org", "blink-network-stack@google.com" ], - "expiry_milestone": 140 + "expiry_milestone": 160 }, { "name": "hardware-media-key-handling", @@ -7056,6 +7056,11 @@ "expiry_milestone": 135 }, { + "name": "offer-migration-to-dice-users", + "owners": [ "ankushkush@google.com", "chrome-signin-team@google.com" ], + "expiry_milestone": 145 + }, + { "name": "offline-auto-fetch", "owners": [ "tbansal@google.com" ], "expiry_milestone": 135
diff --git a/chrome/browser/flag_descriptions.cc b/chrome/browser/flag_descriptions.cc index 15b3b12..897d372 100644 --- a/chrome/browser/flag_descriptions.cc +++ b/chrome/browser/flag_descriptions.cc
@@ -326,31 +326,6 @@ const char kClipboardMaximumAgeDescription[] = "Limit the maximum age for recent clipboard content"; -const char kComputePressureRateObfuscationMitigationName[] = - "Enable mitigation algorithm for rate obfuscation in compute pressure"; -const char kComputePressureRateObfuscationMitigationDescription[] = - "Rate Obfuscation Mitigation is used to avoid fingerprinting attacks. Its " - "usage introduces some timing penalties to the compute pressure results." - "This mitigation might introduce slight precision errors." - "When disabled this helps to test how predictable and accurate compute " - "pressure is, but the Compute Pressure API can be susceptible to " - "fingerprinting attacks."; - -const char kComputePressureBreakCalibrationMitigationName[] = - "Enable mitigation algorithm to break calibration attempt in compute " - "pressure"; -const char kComputePressureBreakCalibrationMitigationDescription[] = - "In a calibration process an attacker tries to manipulate the CPU so that " - "Compute Pressure API would report a transition into a certain pressure " - "state with the highest probability in response to the pressure exerted " - "by the fabricated workload." - "Break Calibration Mitigation is used to avoid calibration attempts by " - "introducing some randomness in the result of the platform collector." - "This mitigation might introduce slight precision errors." - "When disabled this helps to test how predictable and accurate compute " - "pressure is, but the Compute Pressure API can be susceptible to " - "calibration attempts."; - const char kContainerTypeNoLayoutContainmentName[] = "Enables the container-type property to have no layout containment"; const char kContainerTypeNoLayoutContainmentDescription[] = @@ -499,16 +474,6 @@ "explicitly disabled by Permissions-Policy, even during the gradual " "rollout of their deprecation."; -#if !BUILDFLAG(IS_ANDROID) -const char kDevToolsAutomaticWorkspaceFoldersName[] = - "DevTools Automatic Workspace Folders"; -const char kDevToolsAutomaticWorkspaceFoldersDescription[] = - "When this and the DevTools Project Settings flags are turned on, DevTools " - "will automatically add workspace folders based on a workspace " - "configuration " - "in the project settings."; -#endif // !BUILDFLAG(IS_ANDROID) - const char kDevToolsPrivacyUIName[] = "DevTools Privacy UI"; const char kDevToolsPrivacyUIDescription[] = "Enables the Privacy UI in the current 'Security' panel in DevTools."; @@ -2022,6 +1987,12 @@ "Integration (JSPI) " "API."; +const char kExperimentalWebAssemblySharedEverythingName[] = + "Experimental WebAssembly Shared-Everything Threads"; +const char kExperimentalWebAssemblySharedEverythingDescription[] = + "Enable web pages to use the experimental WebAssembly Shared-Everything " + "Threads feature. Note that this only covers a subset of the proposal."; + const char kEnableUnrestrictedUsbName[] = "Enable Isolated Web Apps to bypass USB restrictions"; const char kEnableUnrestrictedUsbDescription[] = @@ -2889,6 +2860,13 @@ "Enable support for using the system notification toasts and notification " "center on platforms where these are available."; +#if BUILDFLAG(ENABLE_DICE_SUPPORT) +const char kOfferMigrationToDiceUsersName[] = "Offer migration to Dice users"; +const char kOfferMigrationToDiceUsersDescription[] = + "When enabled, offers the implicitly signed-in users a dialog to migrate " + "to explicitly signed-in state."; +#endif // BUILDFLAG(ENABLE_DICE_SUPPORT) + const char kOmitCorsClientCertName[] = "Omit TLS client certificates if credential mode disallows"; const char kOmitCorsClientCertDescription[] =
diff --git a/chrome/browser/flag_descriptions.h b/chrome/browser/flag_descriptions.h index 3e1667c..78deaac 100644 --- a/chrome/browser/flag_descriptions.h +++ b/chrome/browser/flag_descriptions.h
@@ -216,12 +216,6 @@ extern const char kClipboardMaximumAgeName[]; extern const char kClipboardMaximumAgeDescription[]; -extern const char kComputePressureRateObfuscationMitigationName[]; -extern const char kComputePressureRateObfuscationMitigationDescription[]; - -extern const char kComputePressureBreakCalibrationMitigationName[]; -extern const char kComputePressureBreakCalibrationMitigationDescription[]; - extern const char kContainerTypeNoLayoutContainmentName[]; extern const char kContainerTypeNoLayoutContainmentDescription[]; @@ -310,11 +304,6 @@ extern const char kDemoModeComponentUpdaterTestTagDescription[]; #endif // BUILDFLAG(IS_CHROMEOS) -#if !BUILDFLAG(IS_ANDROID) -extern const char kDevToolsAutomaticWorkspaceFoldersName[]; -extern const char kDevToolsAutomaticWorkspaceFoldersDescription[]; -#endif // !BUILDFLAG(IS_ANDROID) - extern const char kDevToolsPrivacyUIName[]; extern const char kDevToolsPrivacyUIDescription[]; @@ -1151,6 +1140,9 @@ extern const char kExperimentalWebAssemblyJSPIName[]; extern const char kExperimentalWebAssemblyJSPIDescription[]; +extern const char kExperimentalWebAssemblySharedEverythingName[]; +extern const char kExperimentalWebAssemblySharedEverythingDescription[]; + extern const char kEnableUnrestrictedUsbName[]; extern const char kEnableUnrestrictedUsbDescription[]; @@ -1665,6 +1657,11 @@ extern const char kNotificationsSystemFlagName[]; extern const char kNotificationsSystemFlagDescription[]; +#if BUILDFLAG(ENABLE_DICE_SUPPORT) +extern const char kOfferMigrationToDiceUsersName[]; +extern const char kOfferMigrationToDiceUsersDescription[]; +#endif // BUILDFLAG(ENABLE_DICE_SUPPORT) + extern const char kOrganicRepeatableQueriesName[]; extern const char kOrganicRepeatableQueriesDescription[];
diff --git a/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java b/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java index 304a3be..12e8457 100644 --- a/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java +++ b/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java
@@ -1008,9 +1008,7 @@ TOUCH_TO_SEARCH_CALLOUT, /* defaultValue= */ false, /* defaultValueInTests= */ true); - public static final CachedFlag sTraceBinderIpc = - newCachedFlag( - TRACE_BINDER_IPC, /* defaultValue= */ false, /* defaultValueInTests= */ true); + public static final CachedFlag sTraceBinderIpc = newCachedFlag(TRACE_BINDER_IPC, false); public static final CachedFlag sUseActivityManagerForTabActivation = newCachedFlag(USE_ACTIVITY_MANAGER_FOR_TAB_ACTIVATION, true); public static final CachedFlag sUseChimeAndroidSdk =
diff --git a/chrome/browser/password_manager/chrome_password_change_service.cc b/chrome/browser/password_manager/chrome_password_change_service.cc index ed88e4c..b8fbff5 100644 --- a/chrome/browser/password_manager/chrome_password_change_service.cc +++ b/chrome/browser/password_manager/chrome_password_change_service.cc
@@ -7,6 +7,7 @@ #include "base/command_line.h" #include "base/metrics/histogram_functions.h" #include "base/task/single_thread_task_runner.h" +#include "chrome/browser/browser_process.h" #include "chrome/browser/optimization_guide/optimization_guide_keyed_service.h" #include "chrome/browser/optimization_guide/optimization_guide_keyed_service_factory.h" #include "chrome/browser/password_manager/password_change_delegate.h" @@ -15,6 +16,7 @@ #include "components/affiliations/core/browser/affiliation_utils.h" #include "components/password_manager/core/browser/features/password_features.h" #include "components/password_manager/core/browser/password_feature_manager.h" +#include "components/variations/service/variations_service.h" #include "content/public/browser/web_contents.h" #include "url/gurl.h" @@ -51,6 +53,13 @@ url, change_password_url, {}); } +std::string GetVariationConfigCountryCode() { + variations::VariationsService* variation_service = + g_browser_process->variations_service(); + return variation_service ? variation_service->GetLatestCountry() + : std::string(); +} + } // namespace ChromePasswordChangeService::ChromePasswordChangeService( @@ -89,7 +98,9 @@ #endif // BUILDFLAG(IS_ANDROID) } -bool ChromePasswordChangeService::IsPasswordChangeSupported(const GURL& url) { +bool ChromePasswordChangeService::IsPasswordChangeSupported( + const GURL& url, + const autofill::LanguageCode& page_language) { if (!IsPasswordChangeAvailable()) { return false; } @@ -98,6 +109,15 @@ return true; } + if (page_language != autofill::LanguageCode("en") && + page_language != autofill::LanguageCode("en-US")) { + return false; + } + + if (GetVariationConfigCountryCode() != "us") { + return false; + } + const bool has_change_url = affiliation_service_->GetChangePasswordURL(url).is_valid(); base::UmaHistogramBoolean(kHasPasswordChangeUrlHistogram, has_change_url);
diff --git a/chrome/browser/password_manager/chrome_password_change_service.h b/chrome/browser/password_manager/chrome_password_change_service.h index 144522b..5b665f6b 100644 --- a/chrome/browser/password_manager/chrome_password_change_service.h +++ b/chrome/browser/password_manager/chrome_password_change_service.h
@@ -67,7 +67,9 @@ // PasswordChangeServiceInterface implementation. bool IsPasswordChangeAvailable() override; - bool IsPasswordChangeSupported(const GURL& url) override; + bool IsPasswordChangeSupported( + const GURL& url, + const autofill::LanguageCode& page_language) override; private: // PasswordChangeDelegate::Observer impl.
diff --git a/chrome/browser/password_manager/chrome_password_change_service_unittest.cc b/chrome/browser/password_manager/chrome_password_change_service_unittest.cc index 1e0bbb6..3afdea8 100644 --- a/chrome/browser/password_manager/chrome_password_change_service_unittest.cc +++ b/chrome/browser/password_manager/chrome_password_change_service_unittest.cc
@@ -14,9 +14,16 @@ #include "chrome/browser/optimization_guide/optimization_guide_keyed_service.h" #include "chrome/browser/optimization_guide/optimization_guide_keyed_service_factory.h" #include "chrome/common/chrome_switches.h" +#include "chrome/test/base/testing_browser_process.h" #include "components/affiliations/core/browser/mock_affiliation_service.h" +#include "components/metrics/enabled_state_provider.h" +#include "components/metrics/metrics_state_manager.h" +#include "components/metrics/test/test_enabled_state_provider.h" #include "components/password_manager/core/browser/features/password_features.h" #include "components/password_manager/core/browser/mock_password_feature_manager.h" +#include "components/sync_preferences/testing_pref_service_syncable.h" +#include "components/variations/service/test_variations_service.h" +#include "components/variations/variations_switches.h" #include "content/public/test/browser_task_environment.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" @@ -89,10 +96,37 @@ class ChromePasswordChangeServiceTest : public testing::Test, public ChromePasswordChangeServiceBase { + public: + ChromePasswordChangeServiceTest() { + variations::TestVariationsService::RegisterPrefs(prefs_.registry()); + metrics_state_manager_ = metrics::MetricsStateManager::Create( + &prefs_, &enabled_state_provider_, std::wstring(), base::FilePath()); + variations_service_ = std::make_unique<variations::TestVariationsService>( + &prefs_, metrics_state_manager_.get()); + } + + void SetUp() override { + TestingBrowserProcess::GetGlobal()->SetVariationsService( + variations_service_.get()); + } + + void TearDown() override { + TestingBrowserProcess::GetGlobal()->SetVariationsService(nullptr); + } + + private: + sync_preferences::TestingPrefServiceSyncable prefs_; + metrics::TestEnabledStateProvider enabled_state_provider_{/*consent=*/false, + /*enabled=*/false}; + std::unique_ptr<metrics::MetricsStateManager> metrics_state_manager_; + std::unique_ptr<variations::TestVariationsService> variations_service_; }; #if !BUILDFLAG(IS_ANDROID) TEST_F(ChromePasswordChangeServiceTest, PasswordChangeSupportedForURL) { + base::CommandLine::ForCurrentProcess()->AppendSwitchASCII( + variations::switches::kVariationsOverrideCountry, "us"); + base::HistogramTester histogram_tester; GURL url("https://test.com/"); EXPECT_CALL(affiliation_service(), GetChangePasswordURL(url)) @@ -101,12 +135,16 @@ .WillOnce(testing::Return(true)); EXPECT_CALL(*feature_manager(), IsGenerationEnabled) .WillOnce(testing::Return(true)); - EXPECT_TRUE(change_service()->IsPasswordChangeSupported(url)); + EXPECT_TRUE(change_service()->IsPasswordChangeSupported( + url, autofill::LanguageCode("en"))); histogram_tester.ExpectUniqueSample( ChromePasswordChangeService::kHasPasswordChangeUrlHistogram, true, 1); } -TEST_F(ChromePasswordChangeServiceTest, PasswordChangeNotSupportedForUrl) { +TEST_F(ChromePasswordChangeServiceTest, NoChangePasswordUrl) { + base::CommandLine::ForCurrentProcess()->AppendSwitchASCII( + variations::switches::kVariationsOverrideCountry, "us"); + base::HistogramTester histogram_tester; GURL url("https://test.com/"); EXPECT_CALL(affiliation_service(), GetChangePasswordURL(url)) @@ -115,11 +153,42 @@ .WillOnce(testing::Return(true)); EXPECT_CALL(*feature_manager(), IsGenerationEnabled) .WillOnce(testing::Return(true)); - EXPECT_FALSE(change_service()->IsPasswordChangeSupported(url)); + EXPECT_FALSE(change_service()->IsPasswordChangeSupported( + url, autofill::LanguageCode("en"))); histogram_tester.ExpectUniqueSample( ChromePasswordChangeService::kHasPasswordChangeUrlHistogram, false, 1); } +TEST_F(ChromePasswordChangeServiceTest, DifferentCountry) { + base::CommandLine::ForCurrentProcess()->AppendSwitchASCII( + variations::switches::kVariationsOverrideCountry, "in"); + + base::HistogramTester histogram_tester; + GURL url("https://test.com/"); + EXPECT_CALL(affiliation_service(), GetChangePasswordURL(url)).Times(0); + EXPECT_CALL(mock_optimization_service(), ShouldModelExecutionBeAllowedForUser) + .WillOnce(testing::Return(true)); + EXPECT_CALL(*feature_manager(), IsGenerationEnabled) + .WillOnce(testing::Return(true)); + EXPECT_FALSE(change_service()->IsPasswordChangeSupported( + url, autofill::LanguageCode("en"))); +} + +TEST_F(ChromePasswordChangeServiceTest, DifferentLanguage) { + base::CommandLine::ForCurrentProcess()->AppendSwitchASCII( + variations::switches::kVariationsOverrideCountry, "us"); + + base::HistogramTester histogram_tester; + GURL url("https://test.com/"); + EXPECT_CALL(affiliation_service(), GetChangePasswordURL(url)).Times(0); + EXPECT_CALL(mock_optimization_service(), ShouldModelExecutionBeAllowedForUser) + .WillOnce(testing::Return(true)); + EXPECT_CALL(*feature_manager(), IsGenerationEnabled) + .WillOnce(testing::Return(true)); + EXPECT_FALSE(change_service()->IsPasswordChangeSupported( + url, autofill::LanguageCode("ru"))); +} + TEST_F(ChromePasswordChangeServiceTest, PasswordChangeNotSupportedSettingNotVisible) { GURL url("https://test.com/"); @@ -128,7 +197,8 @@ .WillOnce(testing::Return(false)); EXPECT_CALL(*feature_manager(), IsGenerationEnabled) .WillOnce(testing::Return(true)); - EXPECT_FALSE(change_service()->IsPasswordChangeSupported(url)); + EXPECT_FALSE(change_service()->IsPasswordChangeSupported( + url, autofill::LanguageCode("en"))); } TEST_F(ChromePasswordChangeServiceTest, @@ -139,7 +209,8 @@ GURL url("https://test.com/"); EXPECT_CALL(affiliation_service(), GetChangePasswordURL).Times(0); - EXPECT_TRUE(change_service()->IsPasswordChangeSupported(url)); + EXPECT_TRUE(change_service()->IsPasswordChangeSupported( + url, autofill::LanguageCode("en"))); } TEST_F(ChromePasswordChangeServiceTest, @@ -150,7 +221,8 @@ GURL url("https://www.test.com/"); EXPECT_CALL(affiliation_service(), GetChangePasswordURL).Times(0); - EXPECT_TRUE(change_service()->IsPasswordChangeSupported(url)); + EXPECT_TRUE(change_service()->IsPasswordChangeSupported( + url, autofill::LanguageCode("en"))); } #endif // !BUILDFLAG(IS_ANDROID)
diff --git a/chrome/browser/password_manager/password_change/change_password_form_finder.cc b/chrome/browser/password_manager/password_change/change_password_form_finder.cc index 9cfa514..0688a4ad 100644 --- a/chrome/browser/password_manager/password_change/change_password_form_finder.cc +++ b/chrome/browser/password_manager/password_change/change_password_form_finder.cc
@@ -6,6 +6,7 @@ #include "base/functional/bind.h" #include "base/strings/utf_string_conversions.h" +#include "base/task/single_thread_task_runner.h" #include "chrome/browser/optimization_guide/optimization_guide_keyed_service_factory.h" #include "chrome/browser/password_manager/password_change/button_click_helper.h" #include "chrome/browser/password_manager/password_change/change_password_form_waiter.h" @@ -30,8 +31,11 @@ ChangePasswordFormFinder::ChangePasswordFormFinder( content::WebContents* web_contents, + const GURL& change_password_url, ChangePasswordFormWaiter::PasswordFormFoundCallback callback) - : web_contents_(web_contents), callback_(std::move(callback)) { + : web_contents_(web_contents), + change_password_url_(change_password_url), + callback_(std::move(callback)) { capture_annotated_page_content_ = base::BindOnce(&optimization_guide::GetAIPageContent, web_contents, GetAIPageContentOptions()); @@ -39,15 +43,24 @@ web_contents, base::BindOnce(&ChangePasswordFormFinder::OnInitialFormWaitingResult, weak_ptr_factory_.GetWeakPtr())); + + base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask( + FROM_HERE, + base::BindOnce(&ChangePasswordFormFinder::OnFormNotFound, + weak_ptr_factory_.GetWeakPtr()), + kFormWaitingTimeout); } ChangePasswordFormFinder::ChangePasswordFormFinder( base::PassKey<class ChangePasswordFormFinderTest>, content::WebContents* web_contents, + const GURL& change_password_url, ChangePasswordFormWaiter::PasswordFormFoundCallback callback, base::OnceCallback<void(optimization_guide::OnAIPageContentDone)> capture_annotated_page_content) - : ChangePasswordFormFinder(web_contents, std::move(callback)) { + : ChangePasswordFormFinder(web_contents, + change_password_url, + std::move(callback)) { capture_annotated_page_content_ = std::move(capture_annotated_page_content); } @@ -56,6 +69,7 @@ void ChangePasswordFormFinder::OnInitialFormWaitingResult( password_manager::PasswordFormManager* form_manager) { CHECK(web_contents_); + CHECK(callback_); form_waiter_.reset(); if (form_manager) { @@ -72,6 +86,7 @@ void ChangePasswordFormFinder::OnPageContentReceived( std::optional<optimization_guide::AIPageContentResult> content) { CHECK(web_contents_); + CHECK(callback_); if (!content) { std::move(callback_).Run(nullptr); @@ -106,6 +121,8 @@ optimization_guide::proto::PasswordChangeSubmissionLoggingData> logging_data) { CHECK(web_contents_); + CHECK(callback_); + if (!execution_result.response.has_value()) { // TODO(crbug.com/407503334): Record metrics here. std::move(callback_).Run(nullptr); @@ -123,8 +140,15 @@ int dom_node_id = response.value().open_form_data().dom_node_id_to_click(); if (!dom_node_id) { + // Button to click is missing when the login page is displayed. Instead of + // failing immediately continue refreshing the page until timeout. + if (response.value().open_form_data().page_type() == + optimization_guide::proto::OpenFormResponseData_PageType_LOG_IN_PAGE) { + ProcessPasswordFormManagerOrRefresh(/*form_manager=*/nullptr); + } else { + std::move(callback_).Run(nullptr); + } // TODO(crbug.com/407503334): Record metrics here. - std::move(callback_).Run(nullptr); return; } @@ -136,6 +160,7 @@ void ChangePasswordFormFinder::OnButtonClicked(bool result) { CHECK(web_contents_); + CHECK(callback_); click_helper_.reset(); @@ -154,5 +179,28 @@ void ChangePasswordFormFinder::OnSubsequentFormWaitingResult( password_manager::PasswordFormManager* form_manager) { // TODO(crbug.com/407503334): Record metrics here. + CHECK(callback_); std::move(callback_).Run(form_manager); } + +void ChangePasswordFormFinder::ProcessPasswordFormManagerOrRefresh( + password_manager::PasswordFormManager* form_manager) { + if (form_manager) { + CHECK(callback_); + std::move(callback_).Run(form_manager); + return; + } + web_contents_->GetController().LoadURLWithParams( + content::NavigationController::LoadURLParams(change_password_url_)); + + form_waiter_ = std::make_unique<ChangePasswordFormWaiter>( + web_contents_, + base::BindOnce( + &ChangePasswordFormFinder::ProcessPasswordFormManagerOrRefresh, + weak_ptr_factory_.GetWeakPtr())); +} + +void ChangePasswordFormFinder::OnFormNotFound() { + CHECK(callback_); + std::move(callback_).Run(nullptr); +}
diff --git a/chrome/browser/password_manager/password_change/change_password_form_finder.h b/chrome/browser/password_manager/password_change/change_password_form_finder.h index d030a33..5dbe192 100644 --- a/chrome/browser/password_manager/password_change/change_password_form_finder.h +++ b/chrome/browser/password_manager/password_change/change_password_form_finder.h
@@ -27,13 +27,18 @@ // otherwise. class ChangePasswordFormFinder { public: + // Maximum waiting time for a change password form to appear. + static constexpr base::TimeDelta kFormWaitingTimeout = base::Seconds(30); + ChangePasswordFormFinder( content::WebContents* web_contents, + const GURL& change_password_url, ChangePasswordFormWaiter::PasswordFormFoundCallback callback); ChangePasswordFormFinder( base::PassKey<class ChangePasswordFormFinderTest>, content::WebContents* web_contents, + const GURL& change_password_url, ChangePasswordFormWaiter::PasswordFormFoundCallback callback, base::OnceCallback<void(optimization_guide::OnAIPageContentDone)> capture_annotated_page_content); @@ -68,7 +73,15 @@ void OnSubsequentFormWaitingResult( password_manager::PasswordFormManager* form_manager); + // Invokes `callback_` if `form_manager` is present, navigates to the + // `change_password_url_` and awaits for change password form again otherwise. + void ProcessPasswordFormManagerOrRefresh( + password_manager::PasswordFormManager* form_manager); + void OnFormNotFound(); + const raw_ptr<content::WebContents> web_contents_; + const GURL change_password_url_; + ChangePasswordFormWaiter::PasswordFormFoundCallback callback_; base::OnceCallback<void(optimization_guide::OnAIPageContentDone)> capture_annotated_page_content_;
diff --git a/chrome/browser/password_manager/password_change/change_password_form_finder_unittest.cc b/chrome/browser/password_manager/password_change/change_password_form_finder_unittest.cc index afa79dcd1..b18a41c 100644 --- a/chrome/browser/password_manager/password_change/change_password_form_finder_unittest.cc +++ b/chrome/browser/password_manager/password_change/change_password_form_finder_unittest.cc
@@ -42,6 +42,8 @@ using testing::Return; using testing::WithArg; +const char kUrlString[] = "https://www.foo.com/"; + class FakeChromePasswordManagerClient : public ChromePasswordManagerClient { public: static void CreateForWebContents(content::WebContents* contents) { @@ -171,9 +173,9 @@ base::MockCallback< base::OnceCallback<void(optimization_guide::OnAIPageContentDone)>> capture_annotated_page_content; - ChangePasswordFormFinder form_waiter(pass_key(), web_contents(), - completion_callback.Get(), - capture_annotated_page_content.Get()); + ChangePasswordFormFinder form_waiter( + pass_key(), web_contents(), GURL(kUrlString), completion_callback.Get(), + capture_annotated_page_content.Get()); ASSERT_TRUE(form_waiter.form_waiter()); EXPECT_CALL(capture_annotated_page_content, Run).Times(0); @@ -190,7 +192,7 @@ base::OnceCallback<void(optimization_guide::OnAIPageContentDone)>> capture_annotated_page_content; auto form_finder = std::make_unique<ChangePasswordFormFinder>( - pass_key(), web_contents(), completion_callback.Get(), + pass_key(), web_contents(), GURL(kUrlString), completion_callback.Get(), capture_annotated_page_content.Get()); ASSERT_TRUE(form_finder->form_waiter()); @@ -216,7 +218,7 @@ base::OnceCallback<void(optimization_guide::OnAIPageContentDone)>> capture_annotated_page_content; auto form_finder = std::make_unique<ChangePasswordFormFinder>( - pass_key(), web_contents(), completion_callback.Get(), + pass_key(), web_contents(), GURL(kUrlString), completion_callback.Get(), capture_annotated_page_content.Get()); GURL test_url("https://example.com/change-password"); @@ -263,7 +265,7 @@ base::OnceCallback<void(optimization_guide::OnAIPageContentDone)>> capture_annotated_page_content; auto form_finder = std::make_unique<ChangePasswordFormFinder>( - pass_key(), web_contents(), completion_callback.Get(), + pass_key(), web_contents(), GURL(kUrlString), completion_callback.Get(), capture_annotated_page_content.Get()); ASSERT_TRUE(form_finder->form_waiter()); @@ -295,7 +297,7 @@ base::OnceCallback<void(optimization_guide::OnAIPageContentDone)>> capture_annotated_page_content; auto form_finder = std::make_unique<ChangePasswordFormFinder>( - pass_key(), web_contents(), completion_callback.Get(), + pass_key(), web_contents(), GURL(kUrlString), completion_callback.Get(), capture_annotated_page_content.Get()); ASSERT_TRUE(form_finder->form_waiter()); @@ -328,3 +330,47 @@ form_finder->form_waiter()) ->OnPasswordFormParsed(form_manager.get()); } + +TEST_F(ChangePasswordFormFinderTest, ExecuteModelPredictsLoginPage) { + base::MockOnceCallback<void(password_manager::PasswordFormManager*)> + completion_callback; + base::MockCallback< + base::OnceCallback<void(optimization_guide::OnAIPageContentDone)>> + capture_annotated_page_content; + auto form_finder = std::make_unique<ChangePasswordFormFinder>( + pass_key(), web_contents(), GURL(kUrlString), completion_callback.Get(), + capture_annotated_page_content.Get()); + + ASSERT_TRUE(form_finder->form_waiter()); + static_cast<content::WebContentsObserver*>(form_finder->form_waiter()) + ->DocumentOnLoadCompletedInPrimaryMainFrame(); + ASSERT_FALSE(form_finder->click_helper()); + + EXPECT_CALL(*optimization_service(), ExecuteModel) + .WillOnce(WithArg<3>(Invoke( + [](optimization_guide::OptimizationGuideModelExecutionResultCallback + callback) { + optimization_guide::proto::PasswordChangeResponse response; + response.mutable_open_form_data()->set_dom_node_id_to_click(0); + response.mutable_open_form_data()->set_page_type( + ::optimization_guide::proto::OpenFormResponseData_PageType:: + OpenFormResponseData_PageType_LOG_IN_PAGE); + + auto result = + optimization_guide::OptimizationGuideModelExecutionResult( + optimization_guide::AnyWrapProto(response), + /*execution_info=*/nullptr); + base::SequencedTaskRunner::GetCurrentDefault()->PostTask( + FROM_HERE, + base::BindOnce(std::move(callback), std::move(result), + /*log_entry=*/nullptr)); + }))); + EXPECT_CALL(capture_annotated_page_content, Run) + .WillOnce(base::test::RunOnceCallback<0>( + optimization_guide::AIPageContentResult())); + task_environment()->FastForwardBy( + ChangePasswordFormWaiter::kChangePasswordFormWaitingTimeout); + + EXPECT_EQ(GURL(kUrlString), web_contents()->GetURL()); + EXPECT_TRUE(form_finder->form_waiter()); +}
diff --git a/chrome/browser/password_manager/password_change_delegate_impl.cc b/chrome/browser/password_manager/password_change_delegate_impl.cc index 423fb67..659944cf 100644 --- a/chrome/browser/password_manager/password_change_delegate_impl.cc +++ b/chrome/browser/password_manager/password_change_delegate_impl.cc
@@ -168,7 +168,7 @@ CHECK(executor_); logs_uploader_ = std::make_unique<ModelQualityLogsUploader>(executor_.get()); form_finder_ = std::make_unique<ChangePasswordFormFinder>( - executor_.get(), + executor_.get(), change_password_url_, base::BindOnce(&PasswordChangeDelegateImpl::OnPasswordChangeFormFound, weak_ptr_factory_.GetWeakPtr())); }
diff --git a/chrome/browser/preloading/search_preload/BUILD.gn b/chrome/browser/preloading/search_preload/BUILD.gn index 35115d45..1b3de718 100644 --- a/chrome/browser/preloading/search_preload/BUILD.gn +++ b/chrome/browser/preloading/search_preload/BUILD.gn
@@ -19,6 +19,7 @@ "search_preload_service.h", "search_preload_service_factory.cc", "search_preload_service_factory.h", + "search_preload_signal_result.h", ] public_deps = [ "//content/public/browser" ]
diff --git a/chrome/browser/preloading/search_preload/search_preload_browsertest.cc b/chrome/browser/preloading/search_preload/search_preload_browsertest.cc index b6415a5..e9aa742 100644 --- a/chrome/browser/preloading/search_preload/search_preload_browsertest.cc +++ b/chrome/browser/preloading/search_preload/search_preload_browsertest.cc
@@ -56,6 +56,44 @@ } // namespace alternative_content +class HistogramTesterWrapper { + public: + HistogramTesterWrapper() = default; + ~HistogramTesterWrapper() = default; + + template <typename T> + void ExpectUma(std::string_view name, + std::vector<T> values, + const base::Location& location = FROM_HERE) { + std::map<T, size_t> counts; + for (auto& value : values) { + counts[value]++; + } + + histogram_tester_.ExpectTotalCount(name, values.size(), location); + for (auto& [value, count] : counts) { + histogram_tester_.ExpectBucketCount(name, value, count, location); + } + } + + template <typename T> + void ExpectUma(std::string_view name, + std::initializer_list<T> values, + const base::Location& location = FROM_HERE) { + ExpectUma(name, std::vector<T>(values), location); + } + + // Special case for an empty initializer `{}`. + void ExpectUma(std::string_view name, + void* values, + const base::Location& location = FROM_HERE) { + ExpectUma(name, std::vector<int>({}), location); + } + + private: + base::HistogramTester histogram_tester_; +}; + constexpr static char kSearchTerms_502OnPrefetch[] = "502-on-prefetch"; std::optional<net::HttpNoVarySearchData> ParseNoVarySearchData(std::string s) { @@ -477,6 +515,7 @@ // - Prefetch is used. IN_PROC_BROWSER_TEST_F(SearchPreloadBrowserTest, OnAutocompleteResultChanged_TriggersPrefetch) { + HistogramTesterWrapper uma_tester; SetUpTemplateURLService(); SetUpSearchPreloadService({ .no_vary_search_data_cache = R"(key-order, params, except=("q"))", @@ -510,6 +549,12 @@ EXPECT_EQ(0, request_collector().CountByPath(urls.prerender)); EXPECT_EQ(0, request_collector().CountByPath(urls.navigation)); + uma_tester.ExpectUma("Omnibox.DsePreload.SignalResult.OnSuggest.Prefetch", + {SearchPreloadSignalResult::kPrefetchTriggered}); + uma_tester.ExpectUma("Omnibox.DsePreload.SignalResult.OnSuggest.Prerender", + {}); + uma_tester.ExpectUma("Omnibox.DsePreload.SignalResult.OnPress.Prefetch", {}); + histogram_tester().ExpectBucketCount( "Prerender.Experimental.PrerenderHostFinalStatus.Embedder_" "DefaultSearchEngine", @@ -529,6 +574,7 @@ // - Prefetch is used. IN_PROC_BROWSER_TEST_F(SearchPreloadBrowserTest, OnAutocompleteResultChanged_TriggeredPrefetchIsHeld) { + HistogramTesterWrapper uma_tester; SetUpTemplateURLService(); SetUpSearchPreloadService({ .no_vary_search_data_cache = R"(key-order, params, except=("q"))", @@ -569,6 +615,14 @@ EXPECT_EQ(0, request_collector().CountByPath(urls.prerender)); EXPECT_EQ(0, request_collector().CountByPath(urls.navigation)); + uma_tester.ExpectUma( + "Omnibox.DsePreload.SignalResult.OnSuggest.Prefetch", + {SearchPreloadSignalResult::kPrefetchTriggered, + SearchPreloadSignalResult::kNotTriggeredAlreadyTriggered}); + uma_tester.ExpectUma("Omnibox.DsePreload.SignalResult.OnSuggest.Prerender", + {}); + uma_tester.ExpectUma("Omnibox.DsePreload.SignalResult.OnPress.Prefetch", {}); + histogram_tester().ExpectBucketCount( "Prerender.Experimental.PrerenderHostFinalStatus.Embedder_" "DefaultSearchEngine", @@ -586,6 +640,7 @@ IN_PROC_BROWSER_TEST_F( SearchPreloadBrowserTest, OnAutocompleteResultChanged_TriggersPrefetchAndPrerender) { + HistogramTesterWrapper uma_tester; SetUpTemplateURLService(); SetUpSearchPreloadService({ .no_vary_search_data_cache = R"(key-order, params, except=("q"))", @@ -630,6 +685,12 @@ EXPECT_EQ(0, request_collector().CountByPath(urls.prerender)); EXPECT_EQ(0, request_collector().CountByPath(urls.navigation)); + uma_tester.ExpectUma("Omnibox.DsePreload.SignalResult.OnSuggest.Prefetch", + {SearchPreloadSignalResult::kPrefetchTriggered}); + uma_tester.ExpectUma("Omnibox.DsePreload.SignalResult.OnSuggest.Prerender", + {SearchPreloadSignalResult::kPrerenderTriggered}); + uma_tester.ExpectUma("Omnibox.DsePreload.SignalResult.OnPress.Prefetch", {}); + histogram_tester().ExpectBucketCount( "Prerender.Experimental.PrerenderHostFinalStatus.Embedder_" "DefaultSearchEngine", @@ -649,6 +710,7 @@ IN_PROC_BROWSER_TEST_F( SearchPreloadBrowserTest, OnAutocompleteResultChanged_TriggersPrefetchThenPrerender) { + HistogramTesterWrapper uma_tester; SetUpTemplateURLService(); SetUpSearchPreloadService({ .no_vary_search_data_cache = R"(key-order, params, except=("q"))", @@ -699,6 +761,14 @@ EXPECT_EQ(0, request_collector().CountByPath(urls.prerender)); EXPECT_EQ(0, request_collector().CountByPath(urls.navigation)); + uma_tester.ExpectUma( + "Omnibox.DsePreload.SignalResult.OnSuggest.Prefetch", + {SearchPreloadSignalResult::kPrefetchTriggered, + SearchPreloadSignalResult::kNotTriggeredAlreadyTriggered}); + uma_tester.ExpectUma("Omnibox.DsePreload.SignalResult.OnSuggest.Prerender", + {SearchPreloadSignalResult::kPrerenderTriggered}); + uma_tester.ExpectUma("Omnibox.DsePreload.SignalResult.OnPress.Prefetch", {}); + histogram_tester().ExpectBucketCount( "Prerender.Experimental.PrerenderHostFinalStatus.Embedder_" "DefaultSearchEngine", @@ -714,6 +784,7 @@ // - Prefetch is used. IN_PROC_BROWSER_TEST_F(SearchPreloadBrowserTest, OnNavigationLikely_TriggersPrefetch) { + HistogramTesterWrapper uma_tester; SetUpTemplateURLService(/*prefetch_likely_navigations=*/true); SetUpSearchPreloadService({ .no_vary_search_data_cache = R"(key-order, params, except=("q"))", @@ -751,6 +822,13 @@ // Prefetch is used. EXPECT_EQ(1, request_collector().CountByPath(urls.prefetch_on_press)); EXPECT_EQ(0, request_collector().CountByPath(urls.navigation)); + + uma_tester.ExpectUma("Omnibox.DsePreload.SignalResult.OnSuggest.Prefetch", + {}); + uma_tester.ExpectUma("Omnibox.DsePreload.SignalResult.OnSuggest.Prerender", + {}); + uma_tester.ExpectUma("Omnibox.DsePreload.SignalResult.OnPress.Prefetch", + {SearchPreloadSignalResult::kPrefetchTriggered}); } // `OnNavigationLikely()` doesn't trigger prefetch if default search provider @@ -792,6 +870,7 @@ // - Prefetch is used. IN_PROC_BROWSER_TEST_F(SearchPreloadBrowserTest, OnAutocompleteResultChanged_Then_OnNavigationLikely) { + HistogramTesterWrapper uma_tester; SetUpTemplateURLService(/*prefetch_likely_navigations=*/true); SetUpSearchPreloadService({ .no_vary_search_data_cache = R"(key-order, params, except=("q"))", @@ -833,6 +912,14 @@ EXPECT_EQ(1, request_collector().CountByPath(urls.prefetch_on_suggest)); EXPECT_EQ(0, request_collector().CountByPath(urls.prefetch_on_press)); EXPECT_EQ(0, request_collector().CountByPath(urls.navigation)); + + uma_tester.ExpectUma("Omnibox.DsePreload.SignalResult.OnSuggest.Prefetch", + {SearchPreloadSignalResult::kPrefetchTriggered}); + uma_tester.ExpectUma("Omnibox.DsePreload.SignalResult.OnSuggest.Prerender", + {}); + uma_tester.ExpectUma( + "Omnibox.DsePreload.SignalResult.OnPress.Prefetch", + {SearchPreloadSignalResult::kNotTriggeredAlreadyTriggered}); } // Scenario: @@ -846,7 +933,8 @@ // param. // - Prefetch is not used. IN_PROC_BROWSER_TEST_F(SearchPreloadBrowserTest, - TriggersPrefetchButMatchingFailsDueToNoVarySearchHint) { + TriggersPrefetchButMatchingFailedDueToNoVarySearchHint) { + HistogramTesterWrapper uma_tester; SetUpTemplateURLService(); SetUpSearchPreloadService({ .no_vary_search_data_cache = std::nullopt, @@ -881,6 +969,12 @@ EXPECT_EQ(1, request_collector().CountByPath(urls.prefetch_on_suggest)); EXPECT_EQ(1, request_collector().CountByPath(urls.navigation)); + uma_tester.ExpectUma("Omnibox.DsePreload.SignalResult.OnSuggest.Prefetch", + {SearchPreloadSignalResult::kPrefetchTriggered}); + uma_tester.ExpectUma("Omnibox.DsePreload.SignalResult.OnSuggest.Prerender", + {}); + uma_tester.ExpectUma("Omnibox.DsePreload.SignalResult.OnPress.Prefetch", {}); + // No-Vary-Search data cache is updated. histogram_tester().ExpectUniqueSample( "Omnibox.DsePreload.Prefetch.NoVarySearchDataCacheUpdate", @@ -907,6 +1001,7 @@ IN_PROC_BROWSER_TEST_F( SearchPreloadBrowserTest, TriggersPrefetchAndPrerenderButPrerenderFailsDueToNoVarySearchHint) { + HistogramTesterWrapper uma_tester; SetUpTemplateURLService(); SetUpSearchPreloadService({ .no_vary_search_data_cache = std::nullopt, @@ -945,6 +1040,12 @@ EXPECT_EQ(1, request_collector().CountByPath(urls.prefetch_on_suggest)); EXPECT_EQ(1, request_collector().CountByPath(urls.navigation)); + uma_tester.ExpectUma("Omnibox.DsePreload.SignalResult.OnSuggest.Prefetch", + {SearchPreloadSignalResult::kPrefetchTriggered}); + uma_tester.ExpectUma("Omnibox.DsePreload.SignalResult.OnSuggest.Prerender", + {SearchPreloadSignalResult::kPrerenderTriggered}); + uma_tester.ExpectUma("Omnibox.DsePreload.SignalResult.OnPress.Prefetch", {}); + histogram_tester().ExpectUniqueSample( "Prerender.Experimental.PrerenderHostFinalStatus.Embedder_" "DefaultSearchEngine", @@ -1052,6 +1153,7 @@ auto check = [&](std::string original_query, const bool is_triggered_expected) { + HistogramTesterWrapper uma_tester; request_collector().Reset(); std::string search_terms = original_query; @@ -1158,6 +1260,7 @@ auto check = [&](std::string original_query, const bool is_triggered_expected) { + HistogramTesterWrapper uma_tester; request_collector().Reset(); std::string search_terms = original_query; @@ -1179,6 +1282,19 @@ EXPECT_EQ(is_triggered_expected, request_collector().CountByPath(urls.prefetch_on_suggest)); EXPECT_EQ(0, request_collector().CountByPath(urls.prerender)); + + if (is_triggered_expected) { + uma_tester.ExpectUma("Omnibox.DsePreload.SignalResult.OnSuggest.Prefetch", + {SearchPreloadSignalResult::kPrefetchTriggered}); + uma_tester.ExpectUma( + "Omnibox.DsePreload.SignalResult.OnSuggest.Prerender", {}); + } else { + uma_tester.ExpectUma( + "Omnibox.DsePreload.SignalResult.OnSuggest.Prefetch", + {SearchPreloadSignalResult::kNotTriggeredLimitExceeded}); + uma_tester.ExpectUma( + "Omnibox.DsePreload.SignalResult.OnSuggest.Prerender", {}); + } }; check("one", true); @@ -1202,6 +1318,7 @@ auto check = [&](std::string original_query, const bool is_triggered_expected) { + HistogramTesterWrapper uma_tester; request_collector().Reset(); std::string search_terms = original_query; @@ -1230,6 +1347,15 @@ EXPECT_EQ(is_triggered_expected, request_collector().CountByPath(urls.prefetch_on_press)); EXPECT_EQ(0, request_collector().CountByPath(urls.prerender)); + + if (is_triggered_expected) { + uma_tester.ExpectUma("Omnibox.DsePreload.SignalResult.OnPress.Prefetch", + {SearchPreloadSignalResult::kPrefetchTriggered}); + } else { + uma_tester.ExpectUma( + "Omnibox.DsePreload.SignalResult.OnPress.Prefetch", + {SearchPreloadSignalResult::kNotTriggeredLimitExceeded}); + } }; check("one", true); @@ -1253,6 +1379,7 @@ auto check = [&](std::string original_query, const bool is_triggered_expected, std::vector<std::string> queries_cancelled_prerender) { + HistogramTesterWrapper uma_tester; request_collector().Reset(); std::string search_terms = original_query; @@ -1288,6 +1415,20 @@ EXPECT_EQ(is_triggered_expected, request_collector().CountByPath(urls.prefetch_on_suggest)); EXPECT_EQ(0, request_collector().CountByPath(urls.prerender)); + + if (is_triggered_expected) { + uma_tester.ExpectUma("Omnibox.DsePreload.SignalResult.OnSuggest.Prefetch", + {SearchPreloadSignalResult::kPrefetchTriggered}); + uma_tester.ExpectUma( + "Omnibox.DsePreload.SignalResult.OnSuggest.Prerender", + {SearchPreloadSignalResult::kPrerenderTriggered}); + } else { + uma_tester.ExpectUma( + "Omnibox.DsePreload.SignalResult.OnSuggest.Prefetch", + {SearchPreloadSignalResult::kNotTriggeredLimitExceeded}); + uma_tester.ExpectUma( + "Omnibox.DsePreload.SignalResult.OnSuggest.Prerender", {}); + } }; check("one", true, {}); @@ -1430,6 +1571,7 @@ auto check = [&](std::string original_query, const bool is_triggered_expected) { + HistogramTesterWrapper uma_tester; request_collector().Reset(); std::string search_terms = original_query;
diff --git a/chrome/browser/preloading/search_preload/search_preload_pipeline.cc b/chrome/browser/preloading/search_preload/search_preload_pipeline.cc index 9f24d1b..7805057 100644 --- a/chrome/browser/preloading/search_preload/search_preload_pipeline.cc +++ b/chrome/browser/preloading/search_preload/search_preload_pipeline.cc
@@ -47,7 +47,7 @@ web_contents.GetPrimaryMainFrame()->GetPageUkmSourceId()); } -bool SearchPreloadPipeline::StartPrefetch( +SearchPreloadSignalResult SearchPreloadPipeline::StartPrefetch( content::WebContents& web_contents, base::WeakPtr<SearchPreloadService> search_preload_service, const GURL& prefetch_url, @@ -62,7 +62,7 @@ // load of the prefetch. (There should be no other timeouts nor expiration.) // In general, retriggering may be useful. if (prefetch_handle_) { - return false; + return SearchPreloadSignalResult::kNotTriggeredAlreadyTriggered; } auto* preloading_data = @@ -101,21 +101,21 @@ search_preload_service)); } - return true; + return SearchPreloadSignalResult::kPrefetchTriggered; } -void SearchPreloadPipeline::StartPrerender( +SearchPreloadSignalResult SearchPreloadPipeline::StartPrerender( content::WebContents& web_contents, const GURL& prerender_url, content::PreloadingPredictor predictor) { // Don't trigger prerender if already triggered. if (prerender_handle_) { - return; + return SearchPreloadSignalResult::kNotTriggeredAlreadyTriggered; } // Assume that prefetch is alive. if (!IsPrefetchAlive()) { - return; + return SearchPreloadSignalResult::kNotTriggeredPrefetchNotAlive; } auto* preloading_data = @@ -154,6 +154,7 @@ content::PreloadingHoldbackStatus::kUnspecified, pipeline_info_, attempt, std::move(url_match_predicate), /*prerender_navigation_handle_callback=*/{}); + return SearchPreloadSignalResult::kPrerenderTriggered; } void SearchPreloadPipeline::CancelPrerender() {
diff --git a/chrome/browser/preloading/search_preload/search_preload_pipeline.h b/chrome/browser/preloading/search_preload/search_preload_pipeline.h index a792bec..db3e08e9 100644 --- a/chrome/browser/preloading/search_preload/search_preload_pipeline.h +++ b/chrome/browser/preloading/search_preload/search_preload_pipeline.h
@@ -5,6 +5,7 @@ #ifndef CHROME_BROWSER_PRELOADING_SEARCH_PRELOAD_SEARCH_PRELOAD_PIPELINE_H_ #define CHROME_BROWSER_PRELOADING_SEARCH_PRELOAD_SEARCH_PRELOAD_PIPELINE_H_ +#include "chrome/browser/preloading/search_preload/search_preload_signal_result.h" #include "content/public/browser/prefetch_handle.h" #include "content/public/browser/preload_pipeline_info.h" #include "content/public/browser/prerender_handle.h" @@ -34,7 +35,7 @@ // // Returns true iff prefetch is triggered, i.e. `WebContents::StartPrefetch()` // is called. - bool StartPrefetch( + SearchPreloadSignalResult StartPrefetch( content::WebContents& web_contents, base::WeakPtr<SearchPreloadService> search_preload_service, const GURL& prefetch_url, @@ -42,9 +43,10 @@ const std::optional<net::HttpNoVarySearchData>& no_vary_search_hint, bool is_navigation_likely); // Starts prerender if not triggered yet and prefetch is alive. - void StartPrerender(content::WebContents& web_contents, - const GURL& prerernder_url, - content::PreloadingPredictor predictor); + SearchPreloadSignalResult StartPrerender( + content::WebContents& web_contents, + const GURL& prerernder_url, + content::PreloadingPredictor predictor); // Cancels prerender if triggered. void CancelPrerender();
diff --git a/chrome/browser/preloading/search_preload/search_preload_pipeline_manager.cc b/chrome/browser/preloading/search_preload/search_preload_pipeline_manager.cc index 7ad6a30..6a4f027 100644 --- a/chrome/browser/preloading/search_preload/search_preload_pipeline_manager.cc +++ b/chrome/browser/preloading/search_preload/search_preload_pipeline_manager.cc
@@ -4,11 +4,13 @@ #include "chrome/browser/preloading/search_preload/search_preload_pipeline_manager.h" +#include "base/metrics/histogram_functions.h" #include "chrome/browser/preloading/chrome_preloading.h" #include "chrome/browser/preloading/prefetch/search_prefetch/search_prefetch_service.h" #include "chrome/browser/preloading/search_preload/search_preload_features.h" #include "chrome/browser/preloading/search_preload/search_preload_pipeline.h" #include "chrome/browser/preloading/search_preload/search_preload_service_factory.h" +#include "chrome/browser/preloading/search_preload/search_preload_signal_result.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/search_engines/template_url_service_factory.h" #include "components/omnibox/browser/autocomplete_match.h" @@ -118,20 +120,29 @@ return; } - // Erase to count prefetches. - EraseNotAlivePipelines(); + auto record_histograms = + [](std::tuple<std::optional<SearchPreloadSignalResult>, + std::optional<SearchPreloadSignalResult>> signal_results) { + auto [signal_result_prefetch, signal_result_prerender] = signal_results; + if (signal_result_prefetch.has_value()) { + base::UmaHistogramEnumeration( + "Omnibox.DsePreload.SignalResult.OnSuggest.Prefetch", + signal_result_prefetch.value()); + } + if (signal_result_prerender.has_value()) { + base::UmaHistogramEnumeration( + "Omnibox.DsePreload.SignalResult.OnSuggest.Prerender", + signal_result_prerender.value()); + } + }; if (base::FeatureList::IsEnabled( features::kDsePreload2OnSuggestNonDefalutMatch)) { for (const auto& match : result) { - // Limit the number of prefetches. - if (pipelines_.size() >= features::kDsePreload2MaxPrefetch.Get()) { - return; - } - - OnAutocompleteResultChangedProcessOne(profile, search_preload_service, - *template_url_service, match, - no_vary_search_hint); + auto signal_results = OnAutocompleteResultChangedProcessOne( + profile, search_preload_service, *template_url_service, match, + no_vary_search_hint); + record_histograms(std::move(signal_results)); } } else { if (!result.default_match()) { @@ -139,18 +150,16 @@ } const auto& match = *result.default_match(); - // Limit the number of prefetches. - if (pipelines_.size() >= features::kDsePreload2MaxPrefetch.Get()) { - return; - } - - OnAutocompleteResultChangedProcessOne(profile, search_preload_service, - *template_url_service, match, - no_vary_search_hint); + auto signal_results = OnAutocompleteResultChangedProcessOne( + profile, search_preload_service, *template_url_service, match, + no_vary_search_hint); + record_histograms(std::move(signal_results)); } } -void SearchPreloadPipelineManager::OnAutocompleteResultChangedProcessOne( +std::tuple<std::optional<SearchPreloadSignalResult>, + std::optional<SearchPreloadSignalResult>> +SearchPreloadPipelineManager::OnAutocompleteResultChangedProcessOne( Profile& profile, base::WeakPtr<SearchPreloadService> search_preload_service, TemplateURLService& template_url_service, @@ -174,17 +183,27 @@ } else if (should_prefetch) { confidence = 60; } else { - return; + return {std::nullopt, std::nullopt}; + } + + // Erase to count prefetches. + EraseNotAlivePipelines(); + // Limit the number of prefetches. + if (pipelines_.size() >= features::kDsePreload2MaxPrefetch.Get()) { + return {SearchPreloadSignalResult::kNotTriggeredLimitExceeded, + std::nullopt}; } std::optional<GURL> maybe_canonical_url = GetCanonicalUrlForSearchPreload(profile, match.destination_url); if (!maybe_canonical_url.has_value()) { - return; + return {SearchPreloadSignalResult::kNotTriggeredMisc, + should_prerender ? std::make_optional( + SearchPreloadSignalResult::kNotTriggeredMisc) + : std::nullopt}; } const GURL& canonical_url = maybe_canonical_url.value(); - // TODO(crbug.com/403198750): Limit the number of active pipelines. if (!pipelines_.contains(canonical_url)) { pipelines_.insert_or_assign( canonical_url, std::make_unique<SearchPreloadPipeline>(canonical_url)); @@ -192,21 +211,26 @@ pipelines_[canonical_url]->UpdateConfidence(GetWebContents(), confidence); CHECK(should_prefetch); + const GURL prefetch_url = GetPrefetchUrlFromMatch(*match.search_terms_args, template_url_service, /*is_navigation_likely=*/false); - pipelines_[canonical_url]->StartPrefetch( - GetWebContents(), search_preload_service, prefetch_url, - chrome_preloading_predictor::kDefaultSearchEngine, no_vary_search_hint, - /*is_navigation_likely=*/false); + const SearchPreloadSignalResult signal_result_prefetch = + pipelines_[canonical_url]->StartPrefetch( + GetWebContents(), search_preload_service, prefetch_url, + chrome_preloading_predictor::kDefaultSearchEngine, + no_vary_search_hint, + /*is_navigation_likely=*/false); // Trigger prerender without waiting prefetch. // // They are coordinated by `PrefetchMatchResolver`. For more details, see // https://docs.google.com/document/d/1IAIVrDBE-FnO14Qnghr8hsrxUeoFfeob5QIsV_UNRck/edit?tab=t.0#heading=h.vpxgrp4zne09 + std::optional<SearchPreloadSignalResult> signal_result_prerender = + std::nullopt; if (should_prerender) { - // Unlike prefetch, we cancel the existing prerender and start new one if we - // have a signal for prerender. This behavior comes from DSE preload 1 + // Unlike prefetch, we cancel the existing prerender and start new one if + // we have a signal for prerender. This behavior comes from DSE preload 1 // (`SearchPrefetchService`). // // TODO(https://crrev.com/421387697): Consider to use different policy. @@ -218,10 +242,12 @@ const GURL prerender_url = GetPrerenderUrlFromMatch( *match.search_terms_args, template_url_service); - pipelines_[canonical_url]->StartPrerender( + signal_result_prerender = pipelines_[canonical_url]->StartPrerender( GetWebContents(), prerender_url, chrome_preloading_predictor::kDefaultSearchEngine); } + + return {signal_result_prefetch, signal_result_prerender}; } bool SearchPreloadPipelineManager::OnNavigationLikely( @@ -230,96 +256,110 @@ const AutocompleteMatch& match, omnibox::mojom::NavigationPredictor navigation_predictor, const std::optional<net::HttpNoVarySearchData>& no_vary_search_hint) { - if (!features::IsDsePreload2OnPressEnabled()) { - return false; + const auto signal_result_prefetch = + [&]() -> std::optional<SearchPreloadSignalResult> { + if (!features::IsDsePreload2OnPressEnabled()) { + return std::nullopt; + } + + if (!features::DsePreload2OnPressIsPredictorEnabled(navigation_predictor)) { + return std::nullopt; + } + + if (profile.IsOffTheRecord() && + !features::IsDsePreload2OnPressIncognitoEnabled()) { + return SearchPreloadSignalResult::kNotTriggeredIncognito; + } + + if (!AutocompleteMatch::IsSearchType(match.type)) { + return SearchPreloadSignalResult::kNotTriggeredOnPressNotSearchType; + } + + auto* template_url_service = + TemplateURLServiceFactory::GetForProfile(&profile); + CHECK(template_url_service); + bool does_search_provider_opt_in = + template_url_service->GetDefaultSearchProvider() && + template_url_service->GetDefaultSearchProvider() + ->data() + .prefetch_likely_navigations; + if (!does_search_provider_opt_in) { + return SearchPreloadSignalResult:: + kNotTriggeredOnPressNoSearchProviderOptIn; + } + + // Erase to count prefetches. + EraseNotAlivePipelines(); + // Limit the number of prefetches. + if (pipelines_.size() >= features::kDsePreload2MaxPrefetch.Get()) { + return SearchPreloadSignalResult::kNotTriggeredLimitExceeded; + } + + const std::optional<GURL> maybe_canonical_url = + GetCanonicalUrlForSearchPreload(profile, match.destination_url); + if (!maybe_canonical_url.has_value()) { + return SearchPreloadSignalResult::kNotTriggeredMisc; + } + const GURL& canonical_url = maybe_canonical_url.value(); + + const std::optional<std::u16string> maybe_search_terms = + ExtractSearchTermsFromUrl(*template_url_service, match); + if (!maybe_search_terms.has_value()) { + return SearchPreloadSignalResult::kNotTriggeredMisc; + } + const std::u16string& search_terms = maybe_search_terms.value(); + + GURL prefetch_url; + if (match.search_terms_args) { + auto& search_terms_args = *match.search_terms_args.get(); + prefetch_url = + GetPrefetchUrlFromMatch(search_terms_args, *template_url_service, + /*is_navigation_likely=*/true); + } else { + // Search history suggestions (those that are not also server suggestions) + // don't have search term args. Generate search term args instead. + + auto search_terms_args_for_history_suggestion = + std::make_unique<TemplateURLRef::SearchTermsArgs>(search_terms); + auto& search_terms_args = *search_terms_args_for_history_suggestion.get(); + prefetch_url = + GetPrefetchUrlFromMatch(search_terms_args, *template_url_service, + /*is_navigation_likely=*/true); + } + + auto predictor = + [](omnibox::mojom::NavigationPredictor navigation_predictor) { + switch (navigation_predictor) { + case omnibox::mojom::NavigationPredictor::kMouseDown: + return chrome_preloading_predictor::kOmniboxMousePredictor; + case omnibox::mojom::NavigationPredictor::kUpOrDownArrowButton: + return chrome_preloading_predictor::kOmniboxSearchPredictor; + case omnibox::mojom::NavigationPredictor::kTouchDown: + return chrome_preloading_predictor::kOmniboxTouchDownPredictor; + } + }(navigation_predictor); + + // TODO(crbug.com/403198750): Limit the number of active pipelines. + if (!pipelines_.contains(canonical_url)) { + pipelines_.insert_or_assign( + canonical_url, + std::make_unique<SearchPreloadPipeline>(canonical_url)); + } + pipelines_[canonical_url]->UpdateConfidence(GetWebContents(), 100); + return pipelines_[canonical_url]->StartPrefetch( + GetWebContents(), search_preload_service, prefetch_url, predictor, + no_vary_search_hint, + /*is_navigation_likely=*/true); + }(); + + if (signal_result_prefetch.has_value()) { + base::UmaHistogramEnumeration( + "Omnibox.DsePreload.SignalResult.OnPress.Prefetch", + signal_result_prefetch.value()); } - if (!features::DsePreload2OnPressIsPredictorEnabled(navigation_predictor)) { - return false; - } - - if (profile.IsOffTheRecord() && - !features::IsDsePreload2OnPressIncognitoEnabled()) { - return false; - } - - if (!AutocompleteMatch::IsSearchType(match.type)) { - return false; - } - - auto* template_url_service = - TemplateURLServiceFactory::GetForProfile(&profile); - CHECK(template_url_service); - bool does_search_provider_opt_in = - template_url_service->GetDefaultSearchProvider() && - template_url_service->GetDefaultSearchProvider() - ->data() - .prefetch_likely_navigations; - if (!does_search_provider_opt_in) { - return false; - } - - // Erase to count prefetches. - EraseNotAlivePipelines(); - // Limit the number of prefetches. - if (pipelines_.size() >= features::kDsePreload2MaxPrefetch.Get()) { - return false; - } - - const std::optional<GURL> maybe_canonical_url = - GetCanonicalUrlForSearchPreload(profile, match.destination_url); - if (!maybe_canonical_url.has_value()) { - return false; - } - const GURL& canonical_url = maybe_canonical_url.value(); - - const std::optional<std::u16string> maybe_search_terms = - ExtractSearchTermsFromUrl(*template_url_service, match); - if (!maybe_search_terms.has_value()) { - return false; - } - const std::u16string& search_terms = maybe_search_terms.value(); - - GURL prefetch_url; - if (match.search_terms_args) { - auto& search_terms_args = *match.search_terms_args.get(); - prefetch_url = - GetPrefetchUrlFromMatch(search_terms_args, *template_url_service, - /*is_navigation_likely=*/true); - } else { - // Search history suggestions (those that are not also server suggestions) - // don't have search term args. Generate search term args instead. - - auto search_terms_args_for_history_suggestion = - std::make_unique<TemplateURLRef::SearchTermsArgs>(search_terms); - auto& search_terms_args = *search_terms_args_for_history_suggestion.get(); - prefetch_url = - GetPrefetchUrlFromMatch(search_terms_args, *template_url_service, - /*is_navigation_likely=*/true); - } - - auto predictor = - [](omnibox::mojom::NavigationPredictor navigation_predictor) { - switch (navigation_predictor) { - case omnibox::mojom::NavigationPredictor::kMouseDown: - return chrome_preloading_predictor::kOmniboxMousePredictor; - case omnibox::mojom::NavigationPredictor::kUpOrDownArrowButton: - return chrome_preloading_predictor::kOmniboxSearchPredictor; - case omnibox::mojom::NavigationPredictor::kTouchDown: - return chrome_preloading_predictor::kOmniboxTouchDownPredictor; - } - }(navigation_predictor); - - // TODO(crbug.com/403198750): Limit the number of active pipelines. - if (!pipelines_.contains(canonical_url)) { - pipelines_.insert_or_assign( - canonical_url, std::make_unique<SearchPreloadPipeline>(canonical_url)); - } - pipelines_[canonical_url]->UpdateConfidence(GetWebContents(), 100); - return pipelines_[canonical_url]->StartPrefetch( - GetWebContents(), search_preload_service, prefetch_url, predictor, - no_vary_search_hint, - /*is_navigation_likely=*/true); + return signal_result_prefetch == + SearchPreloadSignalResult::kPrefetchTriggered; } bool SearchPreloadPipelineManager::InvalidatePipelineForTesting(
diff --git a/chrome/browser/preloading/search_preload/search_preload_pipeline_manager.h b/chrome/browser/preloading/search_preload/search_preload_pipeline_manager.h index 31af94816..222f2a41 100644 --- a/chrome/browser/preloading/search_preload/search_preload_pipeline_manager.h +++ b/chrome/browser/preloading/search_preload/search_preload_pipeline_manager.h
@@ -86,7 +86,10 @@ void EraseNotAlivePipelines(); - void OnAutocompleteResultChangedProcessOne( + // Returns `(singal_result_prefetch, signal_result_prerender)`. + std::tuple<std::optional<SearchPreloadSignalResult>, + std::optional<SearchPreloadSignalResult>> + OnAutocompleteResultChangedProcessOne( Profile& profile, base::WeakPtr<SearchPreloadService> search_preload_service, TemplateURLService& template_url_service,
diff --git a/chrome/browser/preloading/search_preload/search_preload_signal_result.h b/chrome/browser/preloading/search_preload/search_preload_signal_result.h new file mode 100644 index 0000000..84dfee0d --- /dev/null +++ b/chrome/browser/preloading/search_preload/search_preload_signal_result.h
@@ -0,0 +1,45 @@ +// Copyright 2025 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_PRELOADING_SEARCH_PRELOAD_SEARCH_PRELOAD_SIGNAL_RESULT_H_ +#define CHROME_BROWSER_PRELOADING_SEARCH_PRELOAD_SEARCH_PRELOAD_SIGNAL_RESULT_H_ + +// Measures the difference between "the client judged it should preload" and +// "preload is actually triggered". +// +// The naming rule is `<Result><Mode>?<Reason>?`. For example, +// `kNotTriggeredOnPressNoSearchProviderOptIn` consists of +// `<Result> = NotTriggered, <Mode> = OnPress, <Reason> = +// NoSearchProviderOptIn`. +// +// LINT.IfChange +enum class SearchPreloadSignalResult { + // Not interested to record. + kNotTriggeredMisc = 0, + + // Prefetch/prerender is triggered by the signal. + kPrefetchTriggered = 1, + kPrerenderTriggered = 2, + + // Not triggered + + // A preload was already triggered by a signal. + kNotTriggeredAlreadyTriggered = 3, + // Tried to trigger prerender, but prefetch is not alive. + kNotTriggeredPrefetchNotAlive = 4, + // Limit exceeded. + kNotTriggeredLimitExceeded = 5, + // The profile is Incognito. + kNotTriggeredIncognito = 6, + // The pressed item is not search type. + kNotTriggeredOnPressNotSearchType = 7, + // No opt-in of the search provider. + kNotTriggeredOnPressNoSearchProviderOptIn = 8, + + // The max value of the PrefetchStatus. Update this when new enums are added. + kMaxValue = kNotTriggeredOnPressNoSearchProviderOptIn, +}; +// LINT.ThenChange(/tools/metrics/histograms/metadata/omnibox/histograms.xml:SearchPreloadSignalResult) + +#endif // CHROME_BROWSER_PRELOADING_SEARCH_PRELOAD_SEARCH_PRELOAD_SIGNAL_RESULT_H_
diff --git a/chrome/browser/readaloud/android/BUILD.gn b/chrome/browser/readaloud/android/BUILD.gn index 8c4fa9d1..fb5d681 100644 --- a/chrome/browser/readaloud/android/BUILD.gn +++ b/chrome/browser/readaloud/android/BUILD.gn
@@ -256,8 +256,8 @@ "//components/user_prefs/android:java", "//content/public/android:content_java", "//net/android:net_java", + "//third_party/android_deps:com_google_guava_guava_java", "//third_party/android_deps:espresso_java", - "//third_party/android_deps:guava_android_java", "//third_party/androidx:androidx_annotation_annotation_java", "//third_party/androidx:androidx_appcompat_appcompat_java", "//third_party/androidx:androidx_test_core_java",
diff --git a/chrome/browser/readaloud/android/java/src/org/chromium/chrome/browser/readaloud/ReadAloudController.java b/chrome/browser/readaloud/android/java/src/org/chromium/chrome/browser/readaloud/ReadAloudController.java index ac7a8077..61396f7 100644 --- a/chrome/browser/readaloud/android/java/src/org/chromium/chrome/browser/readaloud/ReadAloudController.java +++ b/chrome/browser/readaloud/android/java/src/org/chromium/chrome/browser/readaloud/ReadAloudController.java
@@ -19,6 +19,7 @@ import androidx.annotation.IntDef; import androidx.annotation.VisibleForTesting; +import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.hash.Hashing; @@ -217,7 +218,8 @@ ResettersForTesting.register(() -> sClock = oldValue); } - private static class ReadabilityInfo { + @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE) + static class ReadabilityInfo { private final Map<PlaybackArgs.PlaybackMode, ReadAloudReadabilityHooks.ReadabilityResult> mReadabilityInfoPerMode; private final long mResponseTimestamp; @@ -951,7 +953,8 @@ int sanitizedUrlHash = urlToHash(stripUserData(nonNullTab.getUrl()).getSpec()); ReadabilityInfo info = getReadabilityInfoIfUnexpired(sanitizedUrlHash); if (info != null && info.isReadable()) { - return getPlaybackModeForNewPlayback(info, tabLanguageStatus.mLanguage); + List<PlaybackMode> playbackModes = getPlaybackModesForNewPlayback(info, tabLanguageStatus.mLanguage); + return playbackModes.size() > 0 ? playbackModes.get(0) : PlaybackMode.UNSPECIFIED; } } return PlaybackMode.UNSPECIFIED; @@ -1111,12 +1114,12 @@ ReadabilityInfo readabilityInfo = getReadabilityInfoIfUnexpired(sanitizedUrlHash); final String playbackLanguage = getLanguageForNewPlayback(tab); - PlaybackMode playbackMode = - getPlaybackModeForNewPlayback(readabilityInfo, playbackLanguage); + List<PlaybackMode> playbackModes = + getPlaybackModesForNewPlayback(readabilityInfo, playbackLanguage); // Notify player UI that playback is happening soon and show UI in case there's an error // coming. - assumeNonNull(mPlayerCoordinator).playTabRequested(playbackMode); + assumeNonNull(mPlayerCoordinator).playTabRequested(playbackModes.get(0)); boolean isTranslated = isTranslated(tab); var voices = mPlaybackHooks.getVoicesFor(playbackLanguage); @@ -1132,13 +1135,13 @@ new PlaybackArgs( sanitizedUrl, /* isUrl= */ true, - isTranslated && playbackMode != PlaybackMode.OVERVIEW + isTranslated ? playbackLanguage : null, mPlaybackHooks.getPlaybackVoiceList( ReadAloudPrefs.getVoices(getPrefService())), /* dateModifiedMsSinceEpoch= */ dateModified, - /* playbackMode= */ playbackMode); + /* playbackModes= */ playbackModes); Log.d(TAG, "Creating playback with args: %s", args); Promise<Playback> promise = createPlayback(args); @@ -1149,7 +1152,7 @@ Playback.Metadata metadata = assumeNonNull(playback.getMetadata()); mFeedbackType.set(FeedbackType.NONE); maybeSetUpHighlighter(metadata); - updatePlaybackModeSelectionEnabled(readabilityInfo, playbackLanguage); + updatePlaybackModeSelectionEnabled(readabilityInfo, playbackLanguage, playback, playbackModes); updateVoiceMenu( isTranslated ? playbackLanguage @@ -1361,35 +1364,45 @@ return language.equals("en"); } - private PlaybackMode getPlaybackModeForNewPlayback(@Nullable ReadabilityInfo readabilityInfo, String webPageLanguage) { + @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE) + List<PlaybackMode> getPlaybackModesForNewPlayback(@Nullable ReadabilityInfo readabilityInfo, String webPageLanguage) { if (!isAudioOverviewsAllowed()) { // AO feature is disabled, return CLASSIC. - return PlaybackMode.CLASSIC; + return ImmutableList.of(PlaybackMode.CLASSIC); } if (!isLanguageSupportedForOverview(webPageLanguage)) { // Language unsupported for AO. - return PlaybackMode.CLASSIC; + return ImmutableList.of(PlaybackMode.CLASSIC); } if (readabilityInfo == null) { // Unexpected, but just to make sure (also simplifies the next conditions). - return PlaybackMode.CLASSIC; + return ImmutableList.of(PlaybackMode.CLASSIC); } + ImmutableList.Builder<PlaybackMode> modes = ImmutableList.builder(); PlaybackMode preferredPlaybackMode = ReadAloudPrefs.getPlaybackMode(getPrefService()); if (preferredPlaybackMode == PlaybackMode.OVERVIEW || preferredPlaybackMode == PlaybackMode.UNSPECIFIED) { // Preferred mode is either AO or unset (in which case we default to AO). if (readabilityInfo.isReadable(PlaybackMode.OVERVIEW)) { // Preferred mode is OVERVIEW and AO is supported. - return PlaybackMode.OVERVIEW; + modes.add(PlaybackMode.OVERVIEW); + if (readabilityInfo.isReadable(PlaybackMode.CLASSIC)) { + modes.add(PlaybackMode.CLASSIC); + } + return modes.build(); } // Preferred mode is OVERVIEW but is unsupported. Fallback to CLASSIC. - return PlaybackMode.CLASSIC; + return ImmutableList.of(PlaybackMode.CLASSIC); } // Preferred mode is CLASSIC. if (readabilityInfo.isReadable(PlaybackMode.CLASSIC)) { // Preferred mode is CLASSIC and supported. - return PlaybackMode.CLASSIC; + modes.add(PlaybackMode.CLASSIC); + if (readabilityInfo.isReadable(PlaybackMode.OVERVIEW)) { + modes.add(PlaybackMode.OVERVIEW); + } + return modes.build(); } - return PlaybackMode.OVERVIEW; + return ImmutableList.of(PlaybackMode.OVERVIEW); } private String getLanguageForNewPlayback(Tab tab) { @@ -1428,7 +1441,7 @@ } private void updatePlaybackModeSelectionEnabled( - @Nullable ReadabilityInfo readabilityInfo, String language) { + @Nullable ReadabilityInfo readabilityInfo, String language, Playback playback, List<PlaybackMode> supportedPlaybackModes) { if (!isAudioOverviewsAllowed()) { mPlaybackModeSelectionEnabled.set( PlaybackModeSelectionEnablementStatus.FEATURE_DISABLED); @@ -1440,6 +1453,28 @@ PlaybackModeSelectionEnablementStatus.MODE_SELECTION_DISABLED_UNKNOWN_REASON); return; } + + // This means that the actual playback mode used was the last one in the supported modes list. + // It happens in one of the following cases: + // 1. Only one mode was supported during readability. + // 2. More than one mode was supported during readability, but a fallback occurred during playback (e.g. because of a readability FP). + // In the latter case, if the selected mode is the last one in the list, we don't offer the button. + PlaybackMode actualPlaybackMode = assumeNonNull(playback.getMetadata()).playbackMode(); + int indexOfActualPlaybackMode = supportedPlaybackModes.indexOf(actualPlaybackMode); + if (supportedPlaybackModes.size() > 0 + && indexOfActualPlaybackMode >= 0 + && indexOfActualPlaybackMode >= supportedPlaybackModes.size() - 1) { + if (actualPlaybackMode == PlaybackMode.OVERVIEW) { + mPlaybackModeSelectionEnabled.set( + PlaybackModeSelectionEnablementStatus.MODE_SELECTION_DISABLED_CLASSIC_UNAVAILABLE); + } else { + mPlaybackModeSelectionEnabled.set( + PlaybackModeSelectionEnablementStatus.MODE_SELECTION_DISABLED_AO_UNAVAILABLE); + } + + return; + } + boolean classicSupported = readabilityInfo.isReadable(PlaybackMode.CLASSIC); boolean overviewSupported = readabilityInfo.isReadable(PlaybackMode.OVERVIEW); boolean isLanguageSupported = isLanguageSupportedForOverview(language);
diff --git a/chrome/browser/readaloud/android/java/src/org/chromium/chrome/browser/readaloud/ReadAloudControllerUnitTest.java b/chrome/browser/readaloud/android/java/src/org/chromium/chrome/browser/readaloud/ReadAloudControllerUnitTest.java index a53fbca..563668e 100644 --- a/chrome/browser/readaloud/android/java/src/org/chromium/chrome/browser/readaloud/ReadAloudControllerUnitTest.java +++ b/chrome/browser/readaloud/android/java/src/org/chromium/chrome/browser/readaloud/ReadAloudControllerUnitTest.java
@@ -5,7 +5,6 @@ package org.chromium.chrome.browser.readaloud; import static androidx.test.espresso.matcher.ViewMatchers.assertThat; - import static org.hamcrest.Matchers.hasItems; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -30,26 +29,13 @@ import android.app.Activity; import android.content.Intent; import android.view.WindowManager; - import androidx.appcompat.app.AppCompatActivity; - +import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; - -import org.junit.After; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.ArgumentCaptor; -import org.mockito.Captor; -import org.mockito.Mock; -import org.mockito.Mockito; -import org.mockito.junit.MockitoJUnit; -import org.mockito.junit.MockitoRule; -import org.robolectric.Robolectric; -import org.robolectric.annotation.Config; -import org.robolectric.shadows.ShadowLooper; - +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Locale; import org.chromium.base.ActivityState; import org.chromium.base.ApplicationState; import org.chromium.base.Callback; @@ -121,3317 +107,3580 @@ import org.chromium.ui.base.ActivityWindowAndroid; import org.chromium.url.GURL; import org.chromium.url.JUnitTestGURLs; - -import java.util.Arrays; -import java.util.HashSet; -import java.util.List; -import java.util.Locale; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; +import org.robolectric.Robolectric; +import org.robolectric.annotation.Config; +import org.robolectric.shadows.ShadowLooper; /** Unit tests for {@link ReadAloudController}. */ @RunWith(BaseRobolectricTestRunner.class) @Config( - manifest = Config.NONE, - shadows = {ShadowDeviceConditions.class}) + manifest = Config.NONE, + shadows = {ShadowDeviceConditions.class}) @EnableFeatures({ChromeFeatureList.READALOUD, ChromeFeatureList.READALOUD_PLAYBACK}) @DisableFeatures({ - ChromeFeatureList.READALOUD_IN_MULTI_WINDOW, - ChromeFeatureList.READALOUD_BACKGROUND_PLAYBACK, - ChromeFeatureList.READALOUD_TAP_TO_SEEK, - ChromeFeatureList.READALOUD_AUDIO_OVERVIEWS, + ChromeFeatureList.READALOUD_IN_MULTI_WINDOW, + ChromeFeatureList.READALOUD_BACKGROUND_PLAYBACK, + ChromeFeatureList.READALOUD_TAP_TO_SEEK, + ChromeFeatureList.READALOUD_AUDIO_OVERVIEWS, }) public class ReadAloudControllerUnitTest { - private static final GURL sTestGURL = JUnitTestGURLs.EXAMPLE_URL; - private static final GURL sTestRedirectGURL = JUnitTestGURLs.URL_1_WITH_PATH; - private static final Locale EN_US = new Locale("en", "US"); - private static final Locale FR_FR = new Locale("fr", "FR"); + private static final GURL sTestGURL = JUnitTestGURLs.EXAMPLE_URL; + private static final GURL sTestRedirectGURL = JUnitTestGURLs.URL_1_WITH_PATH; + private static final Locale EN_US = new Locale("en", "US"); + private static final Locale FR_FR = new Locale("fr", "FR"); - @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule(); - private MockTab mTab; - private ReadAloudController mController; - private ReadAloudController mController2; - private Activity mActivity; - private Locale mDefaultLocale; + private static final ReadAloudController.ReadabilityInfo ALL_SUPPORTED = + new ReadAloudController.ReadabilityInfo( + ImmutableMap.of( + PlaybackMode.CLASSIC, + new ReadAloudReadabilityHooks.ReadabilityResult(true, false), + PlaybackMode.OVERVIEW, + new ReadAloudReadabilityHooks.ReadabilityResult(true, false)), + 0); - private FakeTranslateBridgeJni mFakeTranslateBridge; - private ObservableSupplierImpl<Profile> mProfileSupplier; - private ObservableSupplierImpl<LayoutManager> mLayoutManagerSupplier; - @Mock private Profile mMockProfile; - @Mock private Profile mMockIncognitoProfile; - @Mock private ReadAloudReadabilityHooks mHooksImpl; - @Mock private ReadAloudPlaybackHooks mPlaybackHooks; - @Mock private Player mPlayerCoordinator; - @Mock private BottomSheetController mBottomSheetController; - @Mock private Extractor mExtractor; - @Mock private Highlighter mHighlighter; - @Mock private PlaybackListener.PhraseTiming mPhraseTiming; - @Mock private BottomControlsStacker mBottomControlsStacker; - @Mock private LayoutManager mLayoutManager; - @Mock private ReadAloudPrefs.Natives mReadAloudPrefsNatives; - @Mock private ReadAloudFeatures.Natives mReadAloudFeaturesNatives; - @Mock private UserPrefsJni mUserPrefsNatives; - @Mock private PrefService mPrefService; - @Mock private TemplateUrlService mTemplateUrlService; - @Mock private ActivityWindowAndroid mActivityWindowAndroid; - @Mock private ActivityLifecycleDispatcher mActivityLifecycleDispatcher; - @Mock Callback<PlaybackModeSelectionEnablementStatus> mPlaybackModeSelectionEnabledCallback; - MockTabModelSelector mTabModelSelector; + private static final ReadAloudController.ReadabilityInfo OVERVIEW_ONLY_SUPPORTED = + new ReadAloudController.ReadabilityInfo( + ImmutableMap.of( + PlaybackMode.CLASSIC, + new ReadAloudReadabilityHooks.ReadabilityResult(false, false), + PlaybackMode.OVERVIEW, + new ReadAloudReadabilityHooks.ReadabilityResult(true, false)), + 0); - @Captor ArgumentCaptor<ReadAloudReadabilityHooks.ReadabilityPerModeCallback> mCallbackCaptor; - @Captor ArgumentCaptor<ReadAloudPlaybackHooks.CreatePlaybackCallback> mPlaybackCallbackCaptor; - @Captor ArgumentCaptor<PlaybackArgs> mPlaybackArgsCaptor; - @Captor ArgumentCaptor<PlaybackListener> mPlaybackListenerCaptor; - @Captor ArgumentCaptor<LayoutStateObserver> mLayoutStateObserver; - @Captor ArgumentCaptor<FullscreenManager.Observer> mFullscreenObserver; + private static final ReadAloudController.ReadabilityInfo CLASSIC_ONLY_SUPPORTED = + new ReadAloudController.ReadabilityInfo( + ImmutableMap.of( + PlaybackMode.CLASSIC, + new ReadAloudReadabilityHooks.ReadabilityResult(true, false), + PlaybackMode.OVERVIEW, + new ReadAloudReadabilityHooks.ReadabilityResult(false, false)), + 0); - @Mock private Playback mPlayback; - @Mock private Playback.Metadata mMetadata; - @Mock private WebContents mWebContents; - @Mock private RenderFrameHost mRenderFrameHost; - @Mock private TemplateUrl mSearchEngine; - @Mock private SelectionClient mSelectionClient; - @Mock private SelectionPopupController mSelectionPopupController; - @Mock private NativePage mNativePage; - @Mock private LayoutStateProvider mLayoutStateProvider; - @Mock private FullscreenManager mFullscreenManager; - @Mock private Tracker mTracker; - private final GlobalRenderFrameHostId mGlobalRenderFrameHostId = - new GlobalRenderFrameHostId(1, 1); - public UserActionTester mUserActionTester; - private HistogramWatcher mHighlightingEnabledOnStartupHistogram; - private Promise<Long> mExtractorPromise; - OneshotSupplierImpl<LayoutStateProvider> mLayoutStateProviderSupplier = - new OneshotSupplierImpl<>(); + @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule(); + private MockTab mTab; + private ReadAloudController mController; + private ReadAloudController mController2; + private Activity mActivity; + private Locale mDefaultLocale; - private FakeClock mClock; + private FakeTranslateBridgeJni mFakeTranslateBridge; + private ObservableSupplierImpl<Profile> mProfileSupplier; + private ObservableSupplierImpl<LayoutManager> mLayoutManagerSupplier; + @Mock private Profile mMockProfile; + @Mock private Profile mMockIncognitoProfile; + @Mock private ReadAloudReadabilityHooks mHooksImpl; + @Mock private ReadAloudPlaybackHooks mPlaybackHooks; + @Mock private Player mPlayerCoordinator; + @Mock private BottomSheetController mBottomSheetController; + @Mock private Extractor mExtractor; + @Mock private Highlighter mHighlighter; + @Mock private PlaybackListener.PhraseTiming mPhraseTiming; + @Mock private BottomControlsStacker mBottomControlsStacker; + @Mock private LayoutManager mLayoutManager; + @Mock private ReadAloudPrefs.Natives mReadAloudPrefsNatives; + @Mock private ReadAloudFeatures.Natives mReadAloudFeaturesNatives; + @Mock private UserPrefsJni mUserPrefsNatives; + @Mock private PrefService mPrefService; + @Mock private TemplateUrlService mTemplateUrlService; + @Mock private ActivityWindowAndroid mActivityWindowAndroid; + @Mock private ActivityLifecycleDispatcher mActivityLifecycleDispatcher; + @Mock Callback<PlaybackModeSelectionEnablementStatus> mPlaybackModeSelectionEnabledCallback; + MockTabModelSelector mTabModelSelector; - /** FakeClock for setting the time. */ - static class FakeClock implements ReadAloudController.Clock { - private long mCurrentTimeMillis; + @Captor ArgumentCaptor<ReadAloudReadabilityHooks.ReadabilityPerModeCallback> mCallbackCaptor; + @Captor ArgumentCaptor<ReadAloudPlaybackHooks.CreatePlaybackCallback> mPlaybackCallbackCaptor; + @Captor ArgumentCaptor<PlaybackArgs> mPlaybackArgsCaptor; + @Captor ArgumentCaptor<PlaybackListener> mPlaybackListenerCaptor; + @Captor ArgumentCaptor<LayoutStateObserver> mLayoutStateObserver; + @Captor ArgumentCaptor<FullscreenManager.Observer> mFullscreenObserver; - FakeClock() { - mCurrentTimeMillis = 0; - } + @Mock private Playback mPlayback; + @Mock private Playback.Metadata mMetadata; + @Mock private WebContents mWebContents; + @Mock private RenderFrameHost mRenderFrameHost; + @Mock private TemplateUrl mSearchEngine; + @Mock private SelectionClient mSelectionClient; + @Mock private SelectionPopupController mSelectionPopupController; + @Mock private NativePage mNativePage; + @Mock private LayoutStateProvider mLayoutStateProvider; + @Mock private FullscreenManager mFullscreenManager; + @Mock private Tracker mTracker; + private final GlobalRenderFrameHostId mGlobalRenderFrameHostId = + new GlobalRenderFrameHostId(1, 1); + public UserActionTester mUserActionTester; + private HistogramWatcher mHighlightingEnabledOnStartupHistogram; + private Promise<Long> mExtractorPromise; + OneshotSupplierImpl<LayoutStateProvider> mLayoutStateProviderSupplier = + new OneshotSupplierImpl<>(); - @Override - public long currentTimeMillis() { - return mCurrentTimeMillis; - } + private FakeClock mClock; - void advanceCurrentTimeMillis(long millis) { - mCurrentTimeMillis += millis; - } + /** FakeClock for setting the time. */ + static class FakeClock implements ReadAloudController.Clock { + private long mCurrentTimeMillis; + + FakeClock() { + mCurrentTimeMillis = 0; } - @Before - public void setUp() { - TrackerFactory.setTrackerForTests(mTracker); - mDefaultLocale = Locale.getDefault(); - - mLayoutStateProviderSupplier.set(mLayoutStateProvider); - mProfileSupplier = new ObservableSupplierImpl<>(); - mProfileSupplier.set(mMockProfile); - doReturn(true).when(mMockProfile).isNativeInitialized(); - - mLayoutManagerSupplier = new ObservableSupplierImpl<>(); - mLayoutManagerSupplier.set(mLayoutManager); - mActivity = Robolectric.buildActivity(AppCompatActivity.class).setup().get(); - mActivity.setTheme(R.style.Theme_BrowserUI_DayNight); - - when(mMockProfile.isOffTheRecord()).thenReturn(false); - when(mMockIncognitoProfile.isOffTheRecord()).thenReturn(true); - UnifiedConsentServiceBridge.setUrlKeyedAnonymizedDataCollectionEnabled(true); - - PriceTrackingFeatures.setPriceAnnotationsEnabledForTesting(false); - - mFakeTranslateBridge = new FakeTranslateBridgeJni(); - TranslateBridgeJni.setInstanceForTesting(mFakeTranslateBridge); - ReadAloudPrefsJni.setInstanceForTesting(mReadAloudPrefsNatives); - ReadAloudFeaturesJni.setInstanceForTesting(mReadAloudFeaturesNatives); - UserPrefsJni.setInstanceForTesting(mUserPrefsNatives); - doReturn(mPrefService).when(mUserPrefsNatives).get(any()); - when(mPrefService.getBoolean(Pref.LISTEN_TO_THIS_PAGE_ENABLED)).thenReturn(true); - mTabModelSelector = - new MockTabModelSelector( - mMockProfile, - mMockIncognitoProfile, - /* tabCount= */ 2, - /* incognitoTabCount= */ 1, - (id, incognito) -> { - Profile profile = incognito ? mMockIncognitoProfile : mMockProfile; - MockTab tab = spy(MockTab.createAndInitialize(id, profile)); - return tab; - }); - when(mHooksImpl.isEnabled()).thenReturn(true); - when(mHooksImpl.getCompatibleLanguages()) - .thenReturn(new HashSet<>(Arrays.asList("en", "es", "fr", "ja"))); - initPlaybackHooks(); - ReadAloudController.setReadabilityHooks(mHooksImpl); - ReadAloudController.setPlaybackHooks(mPlaybackHooks); - - TemplateUrlServiceFactory.setInstanceForTesting(mTemplateUrlService); - doReturn(SearchEngineType.SEARCH_ENGINE_GOOGLE) - .when(mTemplateUrlService) - .getSearchEngineTypeFromTemplateUrl(anyString()); - doReturn("Google").when(mSearchEngine).getKeyword(); - doReturn(mSearchEngine).when(mTemplateUrlService).getDefaultSearchEngineTemplateUrl(); - - mHighlightingEnabledOnStartupHistogram = - HistogramWatcher.newSingleRecordWatcher( - "ReadAloud.HighlightingEnabled.OnStartup", true); - - mClock = new FakeClock(); - ReadAloudController.setClockForTesting(mClock); - - doReturn(false).when(mWebContents).isDestroyed(); - mTab = mTabModelSelector.getCurrentTab(); - mTab.setGurlOverrideForTesting(sTestGURL); - mTab.setWebContentsOverrideForTesting(mWebContents); - - TapToSeekSelectionManager.setSmartSelectionClient(mSelectionClient); - TapToSeekSelectionManager.setSelectionPopupController(mSelectionPopupController); - - mController = createController(); - verify(mLayoutStateProvider).addObserver(mLayoutStateObserver.capture()); - verify(mFullscreenManager).addObserver(mFullscreenObserver.capture()); - when(mMetadata.languageCode()).thenReturn("en"); - when(mMetadata.playbackMode()).thenReturn(PlaybackMode.CLASSIC); - when(mPlayback.getMetadata()).thenReturn(mMetadata); - when(mWebContents.getMainFrame()).thenReturn(mRenderFrameHost); - when(mRenderFrameHost.getGlobalRenderFrameHostId()).thenReturn(mGlobalRenderFrameHostId); - mController.setHighlighterForTests(mHighlighter); - mUserActionTester = new UserActionTester(); - mExtractorPromise = new Promise<>(); - when(mExtractor.getDateModified(any())).thenReturn(mExtractorPromise); - mExtractorPromise.fulfill(1234567123456L); - ShadowLooper.runUiThreadTasksIncludingDelayedTasks(); + @Override + public long currentTimeMillis() { + return mCurrentTimeMillis; } - void initPlaybackHooks() { - when(mPlaybackHooks.createPlayer(any())).thenReturn(mPlayerCoordinator); - when(mPlaybackHooks.createExtractor()).thenReturn(mExtractor); - doReturn(false).when(mPlaybackHooks).voicesInitialized(); - doReturn(List.of(new PlaybackVoice("en", "voiceA", ""))) - .when(mPlaybackHooks) - .getVoicesFor(anyString()); + void advanceCurrentTimeMillis(long millis) { + mCurrentTimeMillis += millis; } + } - private void resetPlaybackMocks() { - reset(mPlayback); - when(mPlayback.getMetadata()).thenReturn(mMetadata); - reset(mPlaybackHooks); - reset(mPlayerCoordinator); - initPlaybackHooks(); + @Before + public void setUp() { + TrackerFactory.setTrackerForTests(mTracker); + mDefaultLocale = Locale.getDefault(); + + mLayoutStateProviderSupplier.set(mLayoutStateProvider); + mProfileSupplier = new ObservableSupplierImpl<>(); + mProfileSupplier.set(mMockProfile); + doReturn(true).when(mMockProfile).isNativeInitialized(); + + mLayoutManagerSupplier = new ObservableSupplierImpl<>(); + mLayoutManagerSupplier.set(mLayoutManager); + mActivity = Robolectric.buildActivity(AppCompatActivity.class).setup().get(); + mActivity.setTheme(R.style.Theme_BrowserUI_DayNight); + + when(mMockProfile.isOffTheRecord()).thenReturn(false); + when(mMockIncognitoProfile.isOffTheRecord()).thenReturn(true); + UnifiedConsentServiceBridge.setUrlKeyedAnonymizedDataCollectionEnabled(true); + + PriceTrackingFeatures.setPriceAnnotationsEnabledForTesting(false); + + mFakeTranslateBridge = new FakeTranslateBridgeJni(); + TranslateBridgeJni.setInstanceForTesting(mFakeTranslateBridge); + ReadAloudPrefsJni.setInstanceForTesting(mReadAloudPrefsNatives); + ReadAloudFeaturesJni.setInstanceForTesting(mReadAloudFeaturesNatives); + UserPrefsJni.setInstanceForTesting(mUserPrefsNatives); + doReturn(mPrefService).when(mUserPrefsNatives).get(any()); + when(mPrefService.hasPrefPath("readaloud.playback_mode")).thenReturn(true); + when(mPrefService.getInteger("readaloud.playback_mode")) + .thenReturn(PlaybackMode.UNSPECIFIED.getValue()); + when(mPrefService.getBoolean(Pref.LISTEN_TO_THIS_PAGE_ENABLED)).thenReturn(true); + mTabModelSelector = + new MockTabModelSelector( + mMockProfile, + mMockIncognitoProfile, + /* tabCount= */ 2, + /* incognitoTabCount= */ 1, + (id, incognito) -> { + Profile profile = incognito ? mMockIncognitoProfile : mMockProfile; + MockTab tab = spy(MockTab.createAndInitialize(id, profile)); + return tab; + }); + when(mHooksImpl.isEnabled()).thenReturn(true); + when(mHooksImpl.getCompatibleLanguages()) + .thenReturn(new HashSet<>(Arrays.asList("en", "es", "fr", "ja"))); + initPlaybackHooks(); + ReadAloudController.setReadabilityHooks(mHooksImpl); + ReadAloudController.setPlaybackHooks(mPlaybackHooks); + + TemplateUrlServiceFactory.setInstanceForTesting(mTemplateUrlService); + doReturn(SearchEngineType.SEARCH_ENGINE_GOOGLE) + .when(mTemplateUrlService) + .getSearchEngineTypeFromTemplateUrl(anyString()); + doReturn("Google").when(mSearchEngine).getKeyword(); + doReturn(mSearchEngine).when(mTemplateUrlService).getDefaultSearchEngineTemplateUrl(); + + mHighlightingEnabledOnStartupHistogram = + HistogramWatcher.newSingleRecordWatcher("ReadAloud.HighlightingEnabled.OnStartup", true); + + mClock = new FakeClock(); + ReadAloudController.setClockForTesting(mClock); + + doReturn(false).when(mWebContents).isDestroyed(); + mTab = mTabModelSelector.getCurrentTab(); + mTab.setGurlOverrideForTesting(sTestGURL); + mTab.setWebContentsOverrideForTesting(mWebContents); + + TapToSeekSelectionManager.setSmartSelectionClient(mSelectionClient); + TapToSeekSelectionManager.setSelectionPopupController(mSelectionPopupController); + + mController = createController(); + verify(mLayoutStateProvider).addObserver(mLayoutStateObserver.capture()); + verify(mFullscreenManager).addObserver(mFullscreenObserver.capture()); + when(mMetadata.languageCode()).thenReturn("en"); + when(mMetadata.playbackMode()).thenReturn(PlaybackMode.CLASSIC); + when(mPlayback.getMetadata()).thenReturn(mMetadata); + when(mWebContents.getMainFrame()).thenReturn(mRenderFrameHost); + when(mRenderFrameHost.getGlobalRenderFrameHostId()).thenReturn(mGlobalRenderFrameHostId); + mController.setHighlighterForTests(mHighlighter); + mUserActionTester = new UserActionTester(); + mExtractorPromise = new Promise<>(); + when(mExtractor.getDateModified(any())).thenReturn(mExtractorPromise); + mExtractorPromise.fulfill(1234567123456L); + ShadowLooper.runUiThreadTasksIncludingDelayedTasks(); + } + + void initPlaybackHooks() { + when(mPlaybackHooks.createPlayer(any())).thenReturn(mPlayerCoordinator); + when(mPlaybackHooks.createExtractor()).thenReturn(mExtractor); + doReturn(false).when(mPlaybackHooks).voicesInitialized(); + doReturn(List.of(new PlaybackVoice("en", "voiceA", ""))) + .when(mPlaybackHooks) + .getVoicesFor(anyString()); + } + + private void resetPlaybackMocks() { + reset(mPlayback); + when(mPlayback.getMetadata()).thenReturn(mMetadata); + reset(mPlaybackHooks); + reset(mPlayerCoordinator); + initPlaybackHooks(); + } + + private ReadAloudController createController() { + var controller = + new ReadAloudController( + mActivity, + mProfileSupplier, + mTabModelSelector.getModel(false), + mTabModelSelector.getModel(true), + mBottomSheetController, + mBottomControlsStacker, + mLayoutManagerSupplier, + mActivityWindowAndroid, + mActivityLifecycleDispatcher, + mLayoutStateProviderSupplier, + mFullscreenManager); + ShadowLooper.runUiThreadTasksIncludingDelayedTasks(); + return controller; + } + + @After + public void tearDown() { + Locale.setDefault(mDefaultLocale); + mUserActionTester.tearDown(); + mController.destroy(); + if (mController2 != null) { + mController2.destroy(); } + ReadAloudController.resetReadabilityCacheForTesting(); + } - private ReadAloudController createController() { - var controller = - new ReadAloudController( - mActivity, - mProfileSupplier, - mTabModelSelector.getModel(false), - mTabModelSelector.getModel(true), - mBottomSheetController, - mBottomControlsStacker, - mLayoutManagerSupplier, - mActivityWindowAndroid, - mActivityLifecycleDispatcher, - mLayoutStateProviderSupplier, - mFullscreenManager); - ShadowLooper.runUiThreadTasksIncludingDelayedTasks(); - return controller; + @Test + public void testHideShowPlayer_tabSwitcher() { + requestAndStartPlayback(); + mLayoutStateObserver.getValue().onStartedShowing(LayoutType.TAB_SWITCHER); + verify(mPlayerCoordinator).hidePlayers(); + + mLayoutStateObserver.getValue().onFinishedHiding(LayoutType.TAB_SWITCHER); + verify(mPlayerCoordinator).restorePlayers(); + } + + @Test + public void testDontHidePlayerWithNoPlayback_tabSwitcherUi() { + mLayoutStateObserver.getValue().onStartedShowing(LayoutType.TAB_SWITCHER); + verify(mPlayerCoordinator, never()).hidePlayers(); + + mLayoutStateObserver.getValue().onFinishedHiding(LayoutType.TAB_SWITCHER); + verify(mPlayerCoordinator, never()).restorePlayers(); + } + + @Test + public void testHidePlayer_FullScreen() { + requestAndStartPlayback(); + mFullscreenObserver.getValue().onEnterFullscreen(mTab, new FullscreenOptions(true, true)); + verify(mPlayerCoordinator).hidePlayers(); + + mFullscreenObserver.getValue().onExitFullscreen(mTab); + verify(mPlayerCoordinator).restorePlayers(); + } + + @Test + public void testIsAvailable() { + // test set up: non incognito profile + MSBB Accepted + policy pref returns true + assertTrue(mController.isAvailable()); + + // test returns false when policy pref is false + when(mPrefService.getBoolean("readaloud.listen_to_this_page_enabled")).thenReturn(false); + assertFalse(mController.isAvailable()); + } + + @Test + public void testIsAvailable_offTheRecord() { + when(mMockProfile.isOffTheRecord()).thenReturn(true); + assertFalse(mController.isAvailable()); + } + + @Test + public void testIsAvailable_noMSBB() { + UnifiedConsentServiceBridge.setUrlKeyedAnonymizedDataCollectionEnabled(false); + assertFalse(mController.isAvailable()); + } + + @Test + public void testIsAvailable_inMultiWindow() { + shadowOf(mActivity).setInMultiWindowMode(true); + assertFalse(mController.isAvailable()); + + shadowOf(mActivity).setInMultiWindowMode(false); + assertTrue(mController.isAvailable()); + } + + @Test + @EnableFeatures({ChromeFeatureList.READALOUD_IN_MULTI_WINDOW}) + public void testIsAvailable_inMultiWindow_flag() { + shadowOf(mActivity).setInMultiWindowMode(true); + assertTrue(mController.isAvailable()); + + shadowOf(mActivity).setInMultiWindowMode(false); + assertTrue(mController.isAvailable()); + } + + @Test + public void testOnLoadStarted_differentDocument() { + // start a successful playback + mController.playTab(mTab, ReadAloudController.Entrypoint.MAGIC_TOOLBAR); + resolvePromises(); + verify(mPlaybackHooks).createPlayback(Mockito.any(), mPlaybackCallbackCaptor.capture()); + onPlaybackSuccess(mPlayback); + resolvePromises(); + + // Load new url + when(mTab.getUrl()).thenReturn(new GURL("https://en.wikipedia.org/wiki/Alphabet_Inc.")); + mController.getTabModelTabObserverforTests().onLoadStarted(mTab, true); + + verify(mHighlighter).handleTabReloaded(eq(mTab)); + verify(mPlayerCoordinator).dismissPlayers(); + } + + @Test + public void testOnLoadStarted_sameDocument() { + // start a successful playback + mController.playTab(mTab, ReadAloudController.Entrypoint.MAGIC_TOOLBAR); + resolvePromises(); + verify(mPlaybackHooks).createPlayback(Mockito.any(), mPlaybackCallbackCaptor.capture()); + onPlaybackSuccess(mPlayback); + resolvePromises(); + + // Load the same document + mController.getTabModelTabObserverforTests().onLoadStarted(mTab, false); + + // nothing should happen + verify(mHighlighter, never()).handleTabReloaded(eq(mTab)); + verify(mPlayerCoordinator, never()).dismissPlayers(); + } + + @Test + public void testReloadingPage() { + // Reload tab before any playback starts - tests null checks + mController.getTabModelTabObserverforTests().onPageLoadStarted(mTab, mTab.getUrl()); + + verify(mPlayerCoordinator, never()).dismissPlayers(); + verify(mPlayback, never()).release(); + + // now start playing a tab + requestAndStartPlayback(); + + // reload some other tab, playback should keep going + MockTab newTab = mTabModelSelector.addMockTab(); + newTab.setGurlOverrideForTesting(new GURL("https://en.wikipedia.org/wiki/Alphabet_Inc.")); + mController.getTabModelTabObserverforTests().onUrlUpdated(newTab); + + verify(mPlayerCoordinator, never()).dismissPlayers(); + verify(mPlayback, never()).release(); + + // now reload the playing tab, playback should still keep going + mController.getTabModelTabObserverforTests().onUrlUpdated(mTab); + + verify(mPlayerCoordinator, never()).dismissPlayers(); + verify(mPlayback, never()).release(); + } + + @Test + public void testOnActivityAttachmentChanged() { + // change tab attachment before any playback starts - tests null checks + mController + .getTabModelTabObserverforTests() + .onActivityAttachmentChanged(mTab, /* window= */ null); + + verify(mPlayerCoordinator, never()).dismissPlayers(); + verify(mPlayback, never()).release(); + + // now start playing a tab + requestAndStartPlayback(); + + // change attachement of some other tab, playback should keep going + MockTab newTab = mTabModelSelector.addMockTab(); + newTab.setGurlOverrideForTesting(new GURL("https://en.wikipedia.org/wiki/Alphabet_Inc.")); + mController + .getTabModelTabObserverforTests() + .onActivityAttachmentChanged(newTab, /* window= */ null); + + verify(mPlayerCoordinator, never()).dismissPlayers(); + verify(mPlayback, never()).release(); + + // now detach the playing tab + mController + .getTabModelTabObserverforTests() + .onActivityAttachmentChanged(mTab, /* window= */ null); + + verify(mPlayerCoordinator).dismissPlayers(); + verify(mPlayback).release(); + } + + @Test + public void testOnActivityAttachmentChanged_saveAndRestoreState() { + // start playing a tab + requestAndStartPlayback(); + + // now detach the playing tab + mController + .getTabModelTabObserverforTests() + .onActivityAttachmentChanged(mTab, /* window= */ null); + + verify(mPlayerCoordinator).dismissPlayers(); + verify(mPlayback).release(); + + // Load a different tab. Playback shouldn't be restored + // Load the previously playing tab. Saved playback state should be restored. + Tab tab = mTabModelSelector.addMockTab(); + TabModelUtils.selectTabById(mTabModelSelector, tab.getId(), TabSelectionType.FROM_NEW); + + verify(mPlaybackHooks, times(1)).createPlayback(any(), mPlaybackCallbackCaptor.capture()); + + // Load the previously playing tab. Saved playback state should be restored. + TabModelUtils.selectTabById(mTabModelSelector, mTab.getId(), TabSelectionType.FROM_NEW); + verify(mPlaybackHooks, times(2)).createPlayback(any(), mPlaybackCallbackCaptor.capture()); + + // Loading the same tab should not re-trigger playback + TabModelUtils.selectTabById(mTabModelSelector, mTab.getId(), TabSelectionType.FROM_NEW); + verify(mPlaybackHooks, times(2)).createPlayback(any(), mPlaybackCallbackCaptor.capture()); + } + + @Test + public void testIsRestoringPlayer() { + assertFalse(mController.isRestoringPlayer()); + + // Start playing a tab, detach, restore + requestAndStartPlayback(); + mController + .getTabModelTabObserverforTests() + .onActivityAttachmentChanged(mTab, /* window= */ null); + verify(mPlayerCoordinator).dismissPlayers(); + verify(mPlayback).release(); + + TabModelUtils.selectTabById(mTabModelSelector, mTab.getId(), TabSelectionType.FROM_NEW); + verify(mPlaybackHooks, times(2)).createPlayback(any(), mPlaybackCallbackCaptor.capture()); + + // Player is now being restored + assertTrue(mController.isRestoringPlayer()); + + // Mini player finishes showing, done restoring player + mController.onMiniPlayerShown(); + assertFalse(mController.isRestoringPlayer()); + } + + @Test + public void testClosingTab() { + // Close a tab before any playback starts - tests null checks + mController.getTabModelTabObserverforTests().willCloseTab(mTab); + + verify(mPlayerCoordinator, never()).dismissPlayers(); + verify(mPlayback, never()).release(); + + // now start playing a tab + requestAndStartPlayback(); + + // close some other tab, playback should keep going + MockTab newTab = mTabModelSelector.addMockTab(); + newTab.setGurlOverrideForTesting(new GURL("https://en.wikipedia.org/wiki/Alphabet_Inc.")); + mController.getTabModelTabObserverforTests().willCloseTab(newTab); + + verify(mPlayerCoordinator, never()).dismissPlayers(); + verify(mPlayback, never()).release(); + + // now close the playing tab + mController.getTabModelTabObserverforTests().willCloseTab(mTab); + + verify(mPlayerCoordinator).dismissPlayers(); + verify(mPlayback).release(); + } + + @Test + public void testClosingTab_errorUiDismissed() { + // start a playback with an error + mController.playTab(mTab, ReadAloudController.Entrypoint.MAGIC_TOOLBAR); + resolvePromises(); + verify(mPlaybackHooks, times(1)) + .createPlayback(Mockito.any(), mPlaybackCallbackCaptor.capture()); + mPlaybackCallbackCaptor.getValue().onFailure(new Exception("Very bad error")); + resolvePromises(); + + // Close this tab + mController.getTabModelTabObserverforTests().willCloseTab(mTab); + + // No playback but error UI should get dismissed + verify(mPlayerCoordinator).dismissPlayers(); + } + + // Helper function for checkReadabilityOnPageLoad_URLnotReadAloudSupported() to check + // the provided url is recognized as unreadable + private void checkURLNotReadAloudSupported(GURL url) { + mTab.setGurlOverrideForTesting(url); + + mController.maybeCheckReadability(mTab); + + verify(mHooksImpl, never()) + .isPageReadable( + Mockito.anyString(), Mockito.any(ReadAloudReadabilityHooks.ReadabilityCallback.class)); + } + + @Test + public void checkReadabilityOnPageLoad_URLnotReadAloudSupported() { + reset(mHooksImpl); + checkURLNotReadAloudSupported(new GURL("invalid")); + checkURLNotReadAloudSupported(GURL.emptyGURL()); + checkURLNotReadAloudSupported(new GURL("chrome://history/")); + checkURLNotReadAloudSupported(new GURL("about:blank")); + checkURLNotReadAloudSupported(new GURL("https://www.google.com/search?q=weather")); + checkURLNotReadAloudSupported(new GURL("https://myaccount.google.com/")); + checkURLNotReadAloudSupported(new GURL("https://myactivity.google.com/")); + } + + @Test + public void checkReadability_TabError() { + TabTestUtils.setIsShowingErrorPage(mTab, true); + assertFalse(mController.isReadable(mTab)); + verify(mHooksImpl, never()) + .isPageReadable( + Mockito.anyString(), + Mockito.any(ReadAloudReadabilityHooks.ReadabilityPerModeCallback.class)); + } + + @Test + public void checkReadability_success() { + mController.maybeCheckReadability(mTab); + + verify(mHooksImpl, times(1)).isPageReadable(eq(sTestGURL.getSpec()), mCallbackCaptor.capture()); + assertFalse(mController.isReadable(mTab)); + + mCallbackCaptor + .getValue() + .onSuccess( + sTestGURL.getSpec(), + ImmutableMap.of( + PlaybackMode.CLASSIC, + new ReadAloudReadabilityHooks.ReadabilityResult(true, false))); + assertTrue(mController.isReadable(mTab)); + assertFalse(mController.timepointsSupported(mTab, PlaybackMode.CLASSIC)); + + // now check that the second time the same url loads we don't resend a request + mController.maybeCheckReadability(mTab); + + verify(mHooksImpl, times(1)) + .isPageReadable( + Mockito.anyString(), + Mockito.any(ReadAloudReadabilityHooks.ReadabilityPerModeCallback.class)); + } + + @Test + public void checkReadability_noMSBB() { + mController.maybeCheckReadability(mTab); + + verify(mHooksImpl, times(1)).isPageReadable(eq(sTestGURL.getSpec()), mCallbackCaptor.capture()); + assertFalse(mController.isReadable(mTab)); + + mCallbackCaptor + .getValue() + .onSuccess( + sTestGURL.getSpec(), + ImmutableMap.of( + PlaybackMode.CLASSIC, + new ReadAloudReadabilityHooks.ReadabilityResult(true, false))); + UnifiedConsentServiceBridge.setUrlKeyedAnonymizedDataCollectionEnabled(false); + assertFalse(mController.isReadable(mTab)); + } + + @Test + public void checkReadability_onlyOnePendingRequest() { + mController.maybeCheckReadability(mTab); + mController.maybeCheckReadability(mTab); + mController.maybeCheckReadability(mTab); + + verify(mHooksImpl, times(1)).isPageReadable(Mockito.anyString(), mCallbackCaptor.capture()); + } + + @Test + public void checkReadability_notReadable_resultExpired() { + mController.maybeCheckReadability(mTab); + verify(mHooksImpl, times(1)).isPageReadable(eq(sTestGURL.getSpec()), mCallbackCaptor.capture()); + assertFalse(mController.isReadable(mTab)); + + mCallbackCaptor + .getValue() + .onSuccess( + sTestGURL.getSpec(), + ImmutableMap.of( + PlaybackMode.CLASSIC, + new ReadAloudReadabilityHooks.ReadabilityResult(false, false))); + assertFalse(mController.isReadable(mTab)); + + // check 1hr1s later for the same url, we should return false and request readability again + mClock.advanceCurrentTimeMillis(1 * 60 * 60 * 1000 + 1000); + + mController.maybeCheckReadability(mTab); + + verify(mHooksImpl, times(2)) + .isPageReadable( + Mockito.anyString(), + Mockito.any(ReadAloudReadabilityHooks.ReadabilityPerModeCallback.class)); + } + + @Test + public void checkReadability_readable_resultExpired() { + mController.maybeCheckReadability(mTab); + verify(mHooksImpl, times(1)).isPageReadable(eq(sTestGURL.getSpec()), mCallbackCaptor.capture()); + assertFalse(mController.isReadable(mTab)); + + mCallbackCaptor + .getValue() + .onSuccess( + sTestGURL.getSpec(), + ImmutableMap.of( + PlaybackMode.CLASSIC, + new ReadAloudReadabilityHooks.ReadabilityResult(true, false))); + assertTrue(mController.isReadable(mTab)); + + // check 1hr1s later for the same url, we should remove the record, return false and request + // readability again + mClock.advanceCurrentTimeMillis(1 * 60 * 60 * 1000 + 1000); + + assertFalse(mController.isReadable(mTab)); + mController.maybeCheckReadability(mTab); + + verify(mHooksImpl, times(2)) + .isPageReadable( + Mockito.anyString(), + Mockito.any(ReadAloudReadabilityHooks.ReadabilityPerModeCallback.class)); + } + + @Test + public void checkReadability_failure() { + mController.maybeCheckReadability(mTab); + + verify(mHooksImpl, times(1)).isPageReadable(eq(sTestGURL.getSpec()), mCallbackCaptor.capture()); + assertFalse(mController.isReadable(mTab)); + + mCallbackCaptor + .getValue() + .onFailure(sTestGURL.getSpec(), new Throwable("Something went wrong")); + assertFalse(mController.isReadable(mTab)); + assertFalse(mController.timepointsSupported(mTab, PlaybackMode.CLASSIC)); + + // now check that the second time the same url loads we will resend a request + mController.maybeCheckReadability(mTab); + + verify(mHooksImpl, times(2)) + .isPageReadable( + Mockito.anyString(), + Mockito.any(ReadAloudReadabilityHooks.ReadabilityPerModeCallback.class)); + } + + @Test + public void checkReadability_emptyURL() { + mController.maybeCheckReadability(mTab); + + verify(mHooksImpl, times(1)).isPageReadable(eq(sTestGURL.getSpec()), mCallbackCaptor.capture()); + boolean failed = false; + try { + mCallbackCaptor + .getValue() + .onSuccess( + "", + ImmutableMap.of( + PlaybackMode.CLASSIC, + new ReadAloudReadabilityHooks.ReadabilityResult(true, true))); + } catch (AssertionError e) { + failed = true; } + assertTrue(failed); + } - @After - public void tearDown() { - Locale.setDefault(mDefaultLocale); - mUserActionTester.tearDown(); - mController.destroy(); - if (mController2 != null) { - mController2.destroy(); - } - ReadAloudController.resetReadabilityCacheForTesting(); - } + @Test + public void checkReadability_offline() { + DeviceConditions.sForceConnectionTypeForTesting = true; + assertFalse(mController.isReadable(mTab)); + } - @Test - public void testHideShowPlayer_tabSwitcher() { - requestAndStartPlayback(); - mLayoutStateObserver.getValue().onStartedShowing(LayoutType.TAB_SWITCHER); - verify(mPlayerCoordinator).hidePlayers(); + @Test + public void testNetworkConnectionTypeChangedNotifiesReadabilityChanged() { + Runnable runnable = Mockito.mock(Runnable.class); + mController.addReadabilityUpdateListener(runnable); - mLayoutStateObserver.getValue().onFinishedHiding(LayoutType.TAB_SWITCHER); - verify(mPlayerCoordinator).restorePlayers(); - } + mController.onConnectionTypeChanged(0); + verify(runnable, times(1)).run(); + } - @Test - public void testDontHidePlayerWithNoPlayback_tabSwitcherUi() { - mLayoutStateObserver.getValue().onStartedShowing(LayoutType.TAB_SWITCHER); - verify(mPlayerCoordinator, never()).hidePlayers(); + @Test + public void isReadable_cacheSharedBetweenInstances() { + // Check readability + mController.maybeCheckReadability(mTab); - mLayoutStateObserver.getValue().onFinishedHiding(LayoutType.TAB_SWITCHER); - verify(mPlayerCoordinator, never()).restorePlayers(); - } + verify(mHooksImpl, times(1)).isPageReadable(eq(sTestGURL.getSpec()), mCallbackCaptor.capture()); + assertFalse(mController.isReadable(mTab)); - @Test - public void testHidePlayer_FullScreen() { - requestAndStartPlayback(); - mFullscreenObserver.getValue().onEnterFullscreen(mTab, new FullscreenOptions(true, true)); - verify(mPlayerCoordinator).hidePlayers(); + // The page is readable, result should be cached. + mCallbackCaptor + .getValue() + .onSuccess( + sTestGURL.getSpec(), + ImmutableMap.of( + PlaybackMode.CLASSIC, + new ReadAloudReadabilityHooks.ReadabilityResult(true, false))); + assertTrue(mController.isReadable(mTab)); + assertFalse(mController.timepointsSupported(mTab, PlaybackMode.CLASSIC)); - mFullscreenObserver.getValue().onExitFullscreen(mTab); - verify(mPlayerCoordinator).restorePlayers(); - } + // A second newly created controller should know that the page is readable. + mController2 = createController(); + assertTrue(mController2.isReadable(mTab)); + assertFalse(mController2.timepointsSupported(mTab, PlaybackMode.CLASSIC)); - @Test - public void testIsAvailable() { - // test set up: non incognito profile + MSBB Accepted + policy pref returns true - assertTrue(mController.isAvailable()); + // The second controller should not send requests to check the same URL's readability. + mController2.maybeCheckReadability(mTab); + // Still only one call. + verify(mHooksImpl, times(1)) + .isPageReadable( + Mockito.anyString(), + Mockito.any(ReadAloudReadabilityHooks.ReadabilityPerModeCallback.class)); + } - // test returns false when policy pref is false - when(mPrefService.getBoolean("readaloud.listen_to_this_page_enabled")).thenReturn(false); - assertFalse(mController.isAvailable()); - } + @Test + public void isReadable_languageSupported() { + mController.maybeCheckReadability(mTab); - @Test - public void testIsAvailable_offTheRecord() { - when(mMockProfile.isOffTheRecord()).thenReturn(true); - assertFalse(mController.isAvailable()); - } + verify(mHooksImpl, times(1)).isPageReadable(eq(sTestGURL.getSpec()), mCallbackCaptor.capture()); - @Test - public void testIsAvailable_noMSBB() { - UnifiedConsentServiceBridge.setUrlKeyedAnonymizedDataCollectionEnabled(false); - assertFalse(mController.isAvailable()); - } + mCallbackCaptor + .getValue() + .onSuccess( + sTestGURL.getSpec(), + ImmutableMap.of( + PlaybackMode.CLASSIC, + new ReadAloudReadabilityHooks.ReadabilityResult(true, false))); + assertTrue(mController.isReadable(mTab)); - @Test - public void testIsAvailable_inMultiWindow() { - shadowOf(mActivity).setInMultiWindowMode(true); - assertFalse(mController.isAvailable()); + // check that URL is supported when the language is set to a supported language + mFakeTranslateBridge.setCurrentLanguage("en"); + assertTrue(mController.isReadable(mTab)); + } - shadowOf(mActivity).setInMultiWindowMode(false); - assertTrue(mController.isAvailable()); - } + @Test + public void isReadable_resultExpired() { + mController.maybeCheckReadability(mTab); - @Test - @EnableFeatures({ChromeFeatureList.READALOUD_IN_MULTI_WINDOW}) - public void testIsAvailable_inMultiWindow_flag() { - shadowOf(mActivity).setInMultiWindowMode(true); - assertTrue(mController.isAvailable()); + verify(mHooksImpl).isPageReadable(eq(sTestGURL.getSpec()), mCallbackCaptor.capture()); - shadowOf(mActivity).setInMultiWindowMode(false); - assertTrue(mController.isAvailable()); - } + mCallbackCaptor + .getValue() + .onSuccess( + sTestGURL.getSpec(), + ImmutableMap.of( + PlaybackMode.CLASSIC, + new ReadAloudReadabilityHooks.ReadabilityResult(true, false))); + assertTrue(mController.isReadable(mTab)); - @Test - public void testOnLoadStarted_differentDocument() { - // start a successful playback - mController.playTab(mTab, ReadAloudController.Entrypoint.MAGIC_TOOLBAR); - resolvePromises(); - verify(mPlaybackHooks).createPlayback(Mockito.any(), mPlaybackCallbackCaptor.capture()); - onPlaybackSuccess(mPlayback); - resolvePromises(); + // advance by 1hr + mClock.advanceCurrentTimeMillis(1 * 60 * 60 * 1000); + assertTrue(mController.isReadable(mTab)); - // Load new url - when(mTab.getUrl()).thenReturn(new GURL("https://en.wikipedia.org/wiki/Alphabet_Inc.")); - mController.getTabModelTabObserverforTests().onLoadStarted(mTab, true); + // advance by 1s - we're past the 1h limit, the record should be deleted + mClock.advanceCurrentTimeMillis(1000); + assertFalse(mController.isReadable(mTab)); + // make sure readability isn't called again + verify(mHooksImpl, times(1)).isPageReadable(eq(sTestGURL.getSpec()), mCallbackCaptor.capture()); + } - verify(mHighlighter).handleTabReloaded(eq(mTab)); - verify(mPlayerCoordinator).dismissPlayers(); - } + @Test + public void isReadable_languageUnsupported() { + mController.maybeCheckReadability(mTab); - @Test - public void testOnLoadStarted_sameDocument() { - // start a successful playback - mController.playTab(mTab, ReadAloudController.Entrypoint.MAGIC_TOOLBAR); - resolvePromises(); - verify(mPlaybackHooks).createPlayback(Mockito.any(), mPlaybackCallbackCaptor.capture()); - onPlaybackSuccess(mPlayback); - resolvePromises(); + verify(mHooksImpl, times(1)).isPageReadable(eq(sTestGURL.getSpec()), mCallbackCaptor.capture()); - // Load the same document - mController.getTabModelTabObserverforTests().onLoadStarted(mTab, false); + mCallbackCaptor + .getValue() + .onSuccess( + sTestGURL.getSpec(), + ImmutableMap.of( + PlaybackMode.CLASSIC, + new ReadAloudReadabilityHooks.ReadabilityResult(true, false))); + assertTrue(mController.isReadable(mTab)); - // nothing should happen - verify(mHighlighter, never()).handleTabReloaded(eq(mTab)); - verify(mPlayerCoordinator, never()).dismissPlayers(); - } + // check that URL isn't supported when the language is set to an unsupported language + mFakeTranslateBridge.setCurrentLanguage("he"); + assertFalse(mController.isReadable(mTab)); + } - @Test - public void testReloadingPage() { - // Reload tab before any playback starts - tests null checks - mController.getTabModelTabObserverforTests().onPageLoadStarted(mTab, mTab.getUrl()); + @Test + public void testIsReadable_errorCases() { + assertFalse(mController.isReadable(null)); - verify(mPlayerCoordinator, never()).dismissPlayers(); - verify(mPlayback, never()).release(); + when(mTab.getUrl()).thenReturn(null); + assertFalse(mController.isReadable(mTab)); - // now start playing a tab - requestAndStartPlayback(); + when(mTab.getUrl()).thenReturn(sTestGURL); + when(mTab.getWebContents()).thenReturn(mWebContents); + doReturn(false).when(mMockProfile).isNativeInitialized(); + assertFalse(mController.isReadable(mTab)); - // reload some other tab, playback should keep going - MockTab newTab = mTabModelSelector.addMockTab(); - newTab.setGurlOverrideForTesting(new GURL("https://en.wikipedia.org/wiki/Alphabet_Inc.")); - mController.getTabModelTabObserverforTests().onUrlUpdated(newTab); + when(mTab.getUrl()).thenReturn(sTestGURL); + when(mTab.getWebContents()).thenReturn(mWebContents); + doReturn(true).when(mMockProfile).isNativeInitialized(); + doReturn(true).when(mTab).isNativePage(); + doReturn(mNativePage).when(mTab).getNativePage(); + doReturn(true).when(mNativePage).isPdf(); + assertFalse(mController.isReadable(mTab)); + } - verify(mPlayerCoordinator, never()).dismissPlayers(); - verify(mPlayback, never()).release(); + @Test + public void testReactingtoMSBBChange() { + mController.maybeCheckReadability(mTab); - // now reload the playing tab, playback should still keep going - mController.getTabModelTabObserverforTests().onUrlUpdated(mTab); + verify(mHooksImpl, times(1)).isPageReadable(eq(sTestGURL.getSpec()), mCallbackCaptor.capture()); - verify(mPlayerCoordinator, never()).dismissPlayers(); - verify(mPlayback, never()).release(); - } + // Disable MSBB. Sending requests to Google servers no longer allowed but using + // previous results is ok. + UnifiedConsentServiceBridge.setUrlKeyedAnonymizedDataCollectionEnabled(false); + mTab.setGurlOverrideForTesting(JUnitTestGURLs.GOOGLE_URL_CAT); + mController.maybeCheckReadability(mTab); - @Test - public void testOnActivityAttachmentChanged() { - // change tab attachment before any playback starts - tests null checks - mController - .getTabModelTabObserverforTests() - .onActivityAttachmentChanged(mTab, /* window= */ null); + verify(mHooksImpl, times(1)) + .isPageReadable( + Mockito.anyString(), + Mockito.any(ReadAloudReadabilityHooks.ReadabilityPerModeCallback.class)); + } - verify(mPlayerCoordinator, never()).dismissPlayers(); - verify(mPlayback, never()).release(); + @Test + public void testPlayTab() { + requestAndStartPlayback(); + verify(mPlayerCoordinator).addObserver(mController); - // now start playing a tab - requestAndStartPlayback(); + // test that previous playback is released when another playback is called + MockTab newTab = mTabModelSelector.addMockTab(); + newTab.setGurlOverrideForTesting(new GURL("https://en.wikipedia.org/wiki/Alphabet_Inc.")); + newTab.setWebContentsOverrideForTesting(mWebContents); + mController.playTab(newTab, ReadAloudController.Entrypoint.MAGIC_TOOLBAR); + resolvePromises(); + verify(mPlayback, times(1)).release(); + } - // change attachement of some other tab, playback should keep going - MockTab newTab = mTabModelSelector.addMockTab(); - newTab.setGurlOverrideForTesting(new GURL("https://en.wikipedia.org/wiki/Alphabet_Inc.")); - mController - .getTabModelTabObserverforTests() - .onActivityAttachmentChanged(newTab, /* window= */ null); + @Test + public void testSendPositiveFeedback() { + requestAndStartPlayback(); - verify(mPlayerCoordinator, never()).dismissPlayers(); - verify(mPlayback, never()).release(); + mController.onPositiveFeedback(); - // now detach the playing tab - mController - .getTabModelTabObserverforTests() - .onActivityAttachmentChanged(mTab, /* window= */ null); + verify(mPlayback) + .sendFeedback( + eq(FeedbackType.POSITIVE), + eq(NegativeFeedbackReason.OTHER), + Mockito.any(ReadAloudPlaybackHooks.SendFeedbackCallback.class)); + } - verify(mPlayerCoordinator).dismissPlayers(); - verify(mPlayback).release(); - } + @Test + public void testSendNegativeFeedback() { + requestAndStartPlayback(); - @Test - public void testOnActivityAttachmentChanged_saveAndRestoreState() { - // start playing a tab - requestAndStartPlayback(); + mController.onNegativeFeedback(NegativeFeedbackReason.OFFENSIVE); - // now detach the playing tab - mController - .getTabModelTabObserverforTests() - .onActivityAttachmentChanged(mTab, /* window= */ null); + verify(mPlayback) + .sendFeedback( + eq(FeedbackType.NEGATIVE), + eq(NegativeFeedbackReason.OFFENSIVE), + Mockito.any(ReadAloudPlaybackHooks.SendFeedbackCallback.class)); + } - verify(mPlayerCoordinator).dismissPlayers(); - verify(mPlayback).release(); + @Test + public void testPlayTab_playerClosedDuringLoad() { + // start a playback with an error + mController.playTab(mTab, ReadAloudController.Entrypoint.MAGIC_TOOLBAR); + resolvePromises(); - // Load a different tab. Playback shouldn't be restored - // Load the previously playing tab. Saved playback state should be restored. - Tab tab = mTabModelSelector.addMockTab(); - TabModelUtils.selectTabById(mTabModelSelector, tab.getId(), TabSelectionType.FROM_NEW); + mController.onRequestClosePlayers(); - verify(mPlaybackHooks, times(1)).createPlayback(any(), mPlaybackCallbackCaptor.capture()); + verify(mPlaybackHooks, times(1)) + .createPlayback(Mockito.any(), mPlaybackCallbackCaptor.capture()); + onPlaybackSuccess(mPlayback); + resolvePromises(); - // Load the previously playing tab. Saved playback state should be restored. - TabModelUtils.selectTabById(mTabModelSelector, mTab.getId(), TabSelectionType.FROM_NEW); - verify(mPlaybackHooks, times(2)).createPlayback(any(), mPlaybackCallbackCaptor.capture()); + verify(mPlayerCoordinator, never()) + .playbackReady(eq(mPlayback), eq(PlaybackListener.State.PLAYING)); + } - // Loading the same tab should not re-trigger playback - TabModelUtils.selectTabById(mTabModelSelector, mTab.getId(), TabSelectionType.FROM_NEW); - verify(mPlaybackHooks, times(2)).createPlayback(any(), mPlaybackCallbackCaptor.capture()); - } + @Test + public void testPlayTab_inMultiWindow() { + mFakeTranslateBridge.setCurrentLanguage("en"); + mTab.setGurlOverrideForTesting(new GURL("https://en.wikipedia.org/wiki/Google")); + mController.playTab(mTab, ReadAloudController.Entrypoint.MAGIC_TOOLBAR); + resolvePromises(); - @Test - public void testIsRestoringPlayer() { - assertFalse(mController.isRestoringPlayer()); + verify(mPlaybackHooks) + .createPlayback(mPlaybackArgsCaptor.capture(), mPlaybackCallbackCaptor.capture()); + assertEquals(null, mPlaybackArgsCaptor.getValue().getLanguage()); - // Start playing a tab, detach, restore - requestAndStartPlayback(); - mController - .getTabModelTabObserverforTests() - .onActivityAttachmentChanged(mTab, /* window= */ null); - verify(mPlayerCoordinator).dismissPlayers(); - verify(mPlayback).release(); + shadowOf(mActivity).setInMultiWindowMode(true); + onPlaybackSuccess(mPlayback); - TabModelUtils.selectTabById(mTabModelSelector, mTab.getId(), TabSelectionType.FROM_NEW); - verify(mPlaybackHooks, times(2)).createPlayback(any(), mPlaybackCallbackCaptor.capture()); + verify(mPlayerCoordinator).playbackFailed(); + } - // Player is now being restored - assertTrue(mController.isRestoringPlayer()); + @Test + @EnableFeatures({ChromeFeatureList.READALOUD_IN_MULTI_WINDOW}) + public void testPlayTab_inMultiWindow_flag() { + mFakeTranslateBridge.setCurrentLanguage("en"); + mTab.setGurlOverrideForTesting(new GURL("https://en.wikipedia.org/wiki/Google")); + mController.playTab(mTab, ReadAloudController.Entrypoint.MAGIC_TOOLBAR); + resolvePromises(); - // Mini player finishes showing, done restoring player - mController.onMiniPlayerShown(); - assertFalse(mController.isRestoringPlayer()); - } + verify(mPlaybackHooks) + .createPlayback(mPlaybackArgsCaptor.capture(), mPlaybackCallbackCaptor.capture()); + assertEquals(null, mPlaybackArgsCaptor.getValue().getLanguage()); - @Test - public void testClosingTab() { - // Close a tab before any playback starts - tests null checks - mController.getTabModelTabObserverforTests().willCloseTab(mTab); + shadowOf(mActivity).setInMultiWindowMode(true); + onPlaybackSuccess(mPlayback); - verify(mPlayerCoordinator, never()).dismissPlayers(); - verify(mPlayback, never()).release(); + verify(mPlayerCoordinator, times(1)) + .playbackReady(eq(mPlayback), eq(PlaybackListener.State.PLAYING)); + verify(mPlayerCoordinator).addObserver(mController); + } - // now start playing a tab - requestAndStartPlayback(); + @Test + public void testKeepScreenOnFlag() { + // default - don't keep the screen on + int flags = mActivity.getWindow().getAttributes().flags; + assertTrue((flags & WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) == 0); - // close some other tab, playback should keep going - MockTab newTab = mTabModelSelector.addMockTab(); - newTab.setGurlOverrideForTesting(new GURL("https://en.wikipedia.org/wiki/Alphabet_Inc.")); - mController.getTabModelTabObserverforTests().willCloseTab(newTab); + // play tab + requestAndStartPlayback(); + verify(mPlayback).addListener(mPlaybackListenerCaptor.capture()); + // update playback data so it isn't null + var data = Mockito.mock(PlaybackListener.PlaybackData.class); + doReturn(PlaybackListener.State.PLAYING).when(data).state(); + mPlaybackListenerCaptor.getValue().onPlaybackDataChanged(data); - verify(mPlayerCoordinator, never()).dismissPlayers(); - verify(mPlayback, never()).release(); + // keep the screen on while something is playing + flags = mActivity.getWindow().getAttributes().flags; + assertTrue((flags & WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) != 0); - // now close the playing tab - mController.getTabModelTabObserverforTests().willCloseTab(mTab); + doReturn(PlaybackListener.State.BUFFERING).when(data).state(); + mPlaybackListenerCaptor.getValue().onPlaybackDataChanged(data); - verify(mPlayerCoordinator).dismissPlayers(); - verify(mPlayback).release(); - } + // don't keep the screen on if paused/stopped/buffering + flags = mActivity.getWindow().getAttributes().flags; + assertTrue((flags & WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) == 0); - @Test - public void testClosingTab_errorUiDismissed() { - // start a playback with an error - mController.playTab(mTab, ReadAloudController.Entrypoint.MAGIC_TOOLBAR); - resolvePromises(); - verify(mPlaybackHooks, times(1)) - .createPlayback(Mockito.any(), mPlaybackCallbackCaptor.capture()); - mPlaybackCallbackCaptor.getValue().onFailure(new Exception("Very bad error")); - resolvePromises(); + doReturn(PlaybackListener.State.PLAYING).when(data).state(); + mPlaybackListenerCaptor.getValue().onPlaybackDataChanged(data); + // playing again - keep the screen on + flags = mActivity.getWindow().getAttributes().flags; + assertTrue((flags & WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) != 0); - // Close this tab - mController.getTabModelTabObserverforTests().willCloseTab(mTab); + mController.maybeStopPlayback( + mTab, ReadAloudMetrics.ReasonForStoppingPlayback.NEW_PLAYBACK_REQUEST); - // No playback but error UI should get dismissed - verify(mPlayerCoordinator).dismissPlayers(); - } + // playback stopped, clear the flag, don't keep the screen on + flags = mActivity.getWindow().getAttributes().flags; + assertTrue((flags & WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) == 0); + } - // Helper function for checkReadabilityOnPageLoad_URLnotReadAloudSupported() to check - // the provided url is recognized as unreadable - private void checkURLNotReadAloudSupported(GURL url) { - mTab.setGurlOverrideForTesting(url); + @Test + public void testPlayTab_sendsVoiceList() { + mFakeTranslateBridge.setCurrentLanguage("en"); + doReturn( + List.of( + new PlaybackVoice("en", "voiceA"), + new PlaybackVoice("es", "voiceB"), + new PlaybackVoice("fr", "voiceC"))) + .when(mPlaybackHooks) + .getPlaybackVoiceList(any()); + mTab.setGurlOverrideForTesting(new GURL("https://en.wikipedia.org/wiki/Google")); - mController.maybeCheckReadability(mTab); + mController.playTab(mTab, ReadAloudController.Entrypoint.MAGIC_TOOLBAR); + resolvePromises(); - verify(mHooksImpl, never()) - .isPageReadable( - Mockito.anyString(), - Mockito.any(ReadAloudReadabilityHooks.ReadabilityCallback.class)); - } + verify(mPlaybackHooks, times(1)).initVoices(); + verify(mPlaybackHooks, times(1)).createPlayback(mPlaybackArgsCaptor.capture(), any()); - @Test - public void checkReadabilityOnPageLoad_URLnotReadAloudSupported() { - reset(mHooksImpl); - checkURLNotReadAloudSupported(new GURL("invalid")); - checkURLNotReadAloudSupported(GURL.emptyGURL()); - checkURLNotReadAloudSupported(new GURL("chrome://history/")); - checkURLNotReadAloudSupported(new GURL("about:blank")); - checkURLNotReadAloudSupported(new GURL("https://www.google.com/search?q=weather")); - checkURLNotReadAloudSupported(new GURL("https://myaccount.google.com/")); - checkURLNotReadAloudSupported(new GURL("https://myactivity.google.com/")); - } + List<PlaybackVoice> voices = mPlaybackArgsCaptor.getValue().getVoices(); + assertNotNull(voices); + assertEquals(3, voices.size()); + assertEquals("en", voices.get(0).getLanguage()); + assertEquals("voiceA", voices.get(0).getVoiceId()); + assertEquals("es", voices.get(1).getLanguage()); + assertEquals("voiceB", voices.get(1).getVoiceId()); + assertEquals("fr", voices.get(2).getLanguage()); + assertEquals("voiceC", voices.get(2).getVoiceId()); + } - @Test - public void checkReadability_TabError() { - TabTestUtils.setIsShowingErrorPage(mTab, true); - assertFalse(mController.isReadable(mTab)); - verify(mHooksImpl, never()) - .isPageReadable( - Mockito.anyString(), - Mockito.any(ReadAloudReadabilityHooks.ReadabilityPerModeCallback.class)); - } - - @Test - public void checkReadability_success() { - mController.maybeCheckReadability(mTab); - - verify(mHooksImpl, times(1)) - .isPageReadable(eq(sTestGURL.getSpec()), mCallbackCaptor.capture()); - assertFalse(mController.isReadable(mTab)); - - mCallbackCaptor - .getValue() - .onSuccess( - sTestGURL.getSpec(), - ImmutableMap.of( - PlaybackMode.CLASSIC, - new ReadAloudReadabilityHooks.ReadabilityResult(true, false))); - assertTrue(mController.isReadable(mTab)); - assertFalse(mController.timepointsSupported(mTab, PlaybackMode.CLASSIC)); - - // now check that the second time the same url loads we don't resend a request - mController.maybeCheckReadability(mTab); - - verify(mHooksImpl, times(1)) - .isPageReadable( - Mockito.anyString(), - Mockito.any(ReadAloudReadabilityHooks.ReadabilityPerModeCallback.class)); - } - - @Test - public void checkReadability_noMSBB() { - mController.maybeCheckReadability(mTab); - - verify(mHooksImpl, times(1)) - .isPageReadable(eq(sTestGURL.getSpec()), mCallbackCaptor.capture()); - assertFalse(mController.isReadable(mTab)); - - mCallbackCaptor.getValue().onSuccess(sTestGURL.getSpec(), ImmutableMap.of(PlaybackMode.CLASSIC, new ReadAloudReadabilityHooks.ReadabilityResult(true, false))); - UnifiedConsentServiceBridge.setUrlKeyedAnonymizedDataCollectionEnabled(false); - assertFalse(mController.isReadable(mTab)); - } - - @Test - public void checkReadability_onlyOnePendingRequest() { - mController.maybeCheckReadability(mTab); - mController.maybeCheckReadability(mTab); - mController.maybeCheckReadability(mTab); - - verify(mHooksImpl, times(1)).isPageReadable(Mockito.anyString(), mCallbackCaptor.capture()); - } - - @Test - public void checkReadability_notReadable_resultExpired() { - mController.maybeCheckReadability(mTab); - verify(mHooksImpl, times(1)) - .isPageReadable(eq(sTestGURL.getSpec()), mCallbackCaptor.capture()); - assertFalse(mController.isReadable(mTab)); - - mCallbackCaptor.getValue().onSuccess(sTestGURL.getSpec(), ImmutableMap.of(PlaybackMode.CLASSIC, new ReadAloudReadabilityHooks.ReadabilityResult(false, false))); - assertFalse(mController.isReadable(mTab)); - - // check 1hr1s later for the same url, we should return false and request readability again - mClock.advanceCurrentTimeMillis(1 * 60 * 60 * 1000 + 1000); - - mController.maybeCheckReadability(mTab); - - verify(mHooksImpl, times(2)) - .isPageReadable( - Mockito.anyString(), - Mockito.any(ReadAloudReadabilityHooks.ReadabilityPerModeCallback.class)); - } - - @Test - public void checkReadability_readable_resultExpired() { - mController.maybeCheckReadability(mTab); - verify(mHooksImpl, times(1)) - .isPageReadable(eq(sTestGURL.getSpec()), mCallbackCaptor.capture()); - assertFalse(mController.isReadable(mTab)); - - mCallbackCaptor.getValue().onSuccess(sTestGURL.getSpec(), ImmutableMap.of(PlaybackMode.CLASSIC, new ReadAloudReadabilityHooks.ReadabilityResult(true, false))); - assertTrue(mController.isReadable(mTab)); - - // check 1hr1s later for the same url, we should remove the record, return false and request - // readability again - mClock.advanceCurrentTimeMillis(1 * 60 * 60 * 1000 + 1000); - - assertFalse(mController.isReadable(mTab)); - mController.maybeCheckReadability(mTab); - - verify(mHooksImpl, times(2)) - .isPageReadable( - Mockito.anyString(), - Mockito.any(ReadAloudReadabilityHooks.ReadabilityPerModeCallback.class)); - } - - @Test - public void checkReadability_failure() { - mController.maybeCheckReadability(mTab); - - verify(mHooksImpl, times(1)) - .isPageReadable(eq(sTestGURL.getSpec()), mCallbackCaptor.capture()); - assertFalse(mController.isReadable(mTab)); - - mCallbackCaptor - .getValue() - .onFailure(sTestGURL.getSpec(), new Throwable("Something went wrong")); - assertFalse(mController.isReadable(mTab)); - assertFalse(mController.timepointsSupported(mTab, PlaybackMode.CLASSIC)); - - // now check that the second time the same url loads we will resend a request - mController.maybeCheckReadability(mTab); - - verify(mHooksImpl, times(2)) - .isPageReadable( - Mockito.anyString(), - Mockito.any(ReadAloudReadabilityHooks.ReadabilityPerModeCallback.class)); - } - - @Test - public void checkReadability_emptyURL() { - mController.maybeCheckReadability(mTab); - - verify(mHooksImpl, times(1)) - .isPageReadable(eq(sTestGURL.getSpec()), mCallbackCaptor.capture()); - boolean failed = false; - try { - mCallbackCaptor - .getValue() - .onSuccess( - "", - ImmutableMap.of( - PlaybackMode.CLASSIC, - new ReadAloudReadabilityHooks.ReadabilityResult(true, true))); - } catch (AssertionError e) { - failed = true; - } - assertTrue(failed); - } - - @Test - public void checkReadability_offline() { - DeviceConditions.sForceConnectionTypeForTesting = true; - assertFalse(mController.isReadable(mTab)); - } - - @Test - public void testNetworkConnectionTypeChangedNotifiesReadabilityChanged() { - Runnable runnable = Mockito.mock(Runnable.class); - mController.addReadabilityUpdateListener(runnable); - - mController.onConnectionTypeChanged(0); - verify(runnable, times(1)).run(); - } - - @Test - public void isReadable_cacheSharedBetweenInstances() { - // Check readability - mController.maybeCheckReadability(mTab); - - verify(mHooksImpl, times(1)) - .isPageReadable(eq(sTestGURL.getSpec()), mCallbackCaptor.capture()); - assertFalse(mController.isReadable(mTab)); - - // The page is readable, result should be cached. - mCallbackCaptor - .getValue() - .onSuccess( - sTestGURL.getSpec(), - ImmutableMap.of( - PlaybackMode.CLASSIC, - new ReadAloudReadabilityHooks.ReadabilityResult(true, false))); - assertTrue(mController.isReadable(mTab)); - assertFalse(mController.timepointsSupported(mTab, PlaybackMode.CLASSIC)); - - // A second newly created controller should know that the page is readable. - mController2 = createController(); - assertTrue(mController2.isReadable(mTab)); - assertFalse(mController2.timepointsSupported(mTab, PlaybackMode.CLASSIC)); - - // The second controller should not send requests to check the same URL's readability. - mController2.maybeCheckReadability(mTab); - // Still only one call. - verify(mHooksImpl, times(1)) - .isPageReadable( - Mockito.anyString(), - Mockito.any(ReadAloudReadabilityHooks.ReadabilityPerModeCallback.class)); - } - - @Test - public void isReadable_languageSupported() { - mController.maybeCheckReadability(mTab); - - verify(mHooksImpl, times(1)) - .isPageReadable(eq(sTestGURL.getSpec()), mCallbackCaptor.capture()); - - mCallbackCaptor.getValue().onSuccess(sTestGURL.getSpec(), ImmutableMap.of(PlaybackMode.CLASSIC, new ReadAloudReadabilityHooks.ReadabilityResult(true, false))); - assertTrue(mController.isReadable(mTab)); - - // check that URL is supported when the language is set to a supported language - mFakeTranslateBridge.setCurrentLanguage("en"); - assertTrue(mController.isReadable(mTab)); - } - - @Test - public void isReadable_resultExpired() { - mController.maybeCheckReadability(mTab); - - verify(mHooksImpl).isPageReadable(eq(sTestGURL.getSpec()), mCallbackCaptor.capture()); - - mCallbackCaptor.getValue().onSuccess(sTestGURL.getSpec(), ImmutableMap.of(PlaybackMode.CLASSIC, new ReadAloudReadabilityHooks.ReadabilityResult(true, false))); - assertTrue(mController.isReadable(mTab)); - - // advance by 1hr - mClock.advanceCurrentTimeMillis(1 * 60 * 60 * 1000); - assertTrue(mController.isReadable(mTab)); - - // advance by 1s - we're past the 1h limit, the record should be deleted - mClock.advanceCurrentTimeMillis(1000); - assertFalse(mController.isReadable(mTab)); - // make sure readability isn't called again - verify(mHooksImpl, times(1)) - .isPageReadable(eq(sTestGURL.getSpec()), mCallbackCaptor.capture()); - } - - @Test - public void isReadable_languageUnsupported() { - mController.maybeCheckReadability(mTab); - - verify(mHooksImpl, times(1)) - .isPageReadable(eq(sTestGURL.getSpec()), mCallbackCaptor.capture()); - - mCallbackCaptor.getValue().onSuccess(sTestGURL.getSpec(), ImmutableMap.of(PlaybackMode.CLASSIC, new ReadAloudReadabilityHooks.ReadabilityResult(true, false))); - assertTrue(mController.isReadable(mTab)); - - // check that URL isn't supported when the language is set to an unsupported language - mFakeTranslateBridge.setCurrentLanguage("he"); - assertFalse(mController.isReadable(mTab)); - } - - @Test - public void testIsReadable_errorCases() { - assertFalse(mController.isReadable(null)); - - when(mTab.getUrl()).thenReturn(null); - assertFalse(mController.isReadable(mTab)); - - when(mTab.getUrl()).thenReturn(sTestGURL); - when(mTab.getWebContents()).thenReturn(mWebContents); - doReturn(false).when(mMockProfile).isNativeInitialized(); - assertFalse(mController.isReadable(mTab)); - - when(mTab.getUrl()).thenReturn(sTestGURL); - when(mTab.getWebContents()).thenReturn(mWebContents); - doReturn(true).when(mMockProfile).isNativeInitialized(); - doReturn(true).when(mTab).isNativePage(); - doReturn(mNativePage).when(mTab).getNativePage(); - doReturn(true).when(mNativePage).isPdf(); - assertFalse(mController.isReadable(mTab)); - } - - @Test - public void testReactingtoMSBBChange() { - mController.maybeCheckReadability(mTab); - - verify(mHooksImpl, times(1)) - .isPageReadable(eq(sTestGURL.getSpec()), mCallbackCaptor.capture()); - - // Disable MSBB. Sending requests to Google servers no longer allowed but using - // previous results is ok. - UnifiedConsentServiceBridge.setUrlKeyedAnonymizedDataCollectionEnabled(false); - mTab.setGurlOverrideForTesting(JUnitTestGURLs.GOOGLE_URL_CAT); - mController.maybeCheckReadability(mTab); - - verify(mHooksImpl, times(1)) - .isPageReadable( - Mockito.anyString(), - Mockito.any(ReadAloudReadabilityHooks.ReadabilityPerModeCallback.class)); - } - - @Test - public void testPlayTab() { - requestAndStartPlayback(); - verify(mPlayerCoordinator).addObserver(mController); - - // test that previous playback is released when another playback is called - MockTab newTab = mTabModelSelector.addMockTab(); - newTab.setGurlOverrideForTesting(new GURL("https://en.wikipedia.org/wiki/Alphabet_Inc.")); - newTab.setWebContentsOverrideForTesting(mWebContents); - mController.playTab(newTab, ReadAloudController.Entrypoint.MAGIC_TOOLBAR); - resolvePromises(); - verify(mPlayback, times(1)).release(); - } - - @Test - public void testSendPositiveFeedback() { - requestAndStartPlayback(); - - mController.onPositiveFeedback(); - - verify(mPlayback).sendFeedback(eq(FeedbackType.POSITIVE), eq(NegativeFeedbackReason.OTHER), Mockito.any(ReadAloudPlaybackHooks.SendFeedbackCallback.class)); - } - - @Test - public void testSendNegativeFeedback() { - requestAndStartPlayback(); - - mController.onNegativeFeedback(NegativeFeedbackReason.OFFENSIVE); - - verify(mPlayback).sendFeedback(eq(FeedbackType.NEGATIVE), eq(NegativeFeedbackReason.OFFENSIVE), Mockito.any(ReadAloudPlaybackHooks.SendFeedbackCallback.class)); - } - - @Test - public void testPlayTab_playerClosedDuringLoad() { - // start a playback with an error - mController.playTab(mTab, ReadAloudController.Entrypoint.MAGIC_TOOLBAR); - resolvePromises(); - - mController.onRequestClosePlayers(); - - verify(mPlaybackHooks, times(1)) - .createPlayback(Mockito.any(), mPlaybackCallbackCaptor.capture()); - onPlaybackSuccess(mPlayback); - resolvePromises(); - - verify(mPlayerCoordinator, never()) - .playbackReady(eq(mPlayback), eq(PlaybackListener.State.PLAYING)); - } - - @Test - public void testPlayTab_inMultiWindow() { - mFakeTranslateBridge.setCurrentLanguage("en"); - mTab.setGurlOverrideForTesting(new GURL("https://en.wikipedia.org/wiki/Google")); - mController.playTab(mTab, ReadAloudController.Entrypoint.MAGIC_TOOLBAR); - resolvePromises(); - - verify(mPlaybackHooks) - .createPlayback(mPlaybackArgsCaptor.capture(), mPlaybackCallbackCaptor.capture()); - assertEquals(null, mPlaybackArgsCaptor.getValue().getLanguage()); - - shadowOf(mActivity).setInMultiWindowMode(true); - onPlaybackSuccess(mPlayback); - - verify(mPlayerCoordinator).playbackFailed(); - } - - @Test - @EnableFeatures({ChromeFeatureList.READALOUD_IN_MULTI_WINDOW}) - public void testPlayTab_inMultiWindow_flag() { - mFakeTranslateBridge.setCurrentLanguage("en"); - mTab.setGurlOverrideForTesting(new GURL("https://en.wikipedia.org/wiki/Google")); - mController.playTab(mTab, ReadAloudController.Entrypoint.MAGIC_TOOLBAR); - resolvePromises(); - - verify(mPlaybackHooks) - .createPlayback(mPlaybackArgsCaptor.capture(), mPlaybackCallbackCaptor.capture()); - assertEquals(null, mPlaybackArgsCaptor.getValue().getLanguage()); - - shadowOf(mActivity).setInMultiWindowMode(true); - onPlaybackSuccess(mPlayback); - - verify(mPlayerCoordinator, times(1)) - .playbackReady(eq(mPlayback), eq(PlaybackListener.State.PLAYING)); - verify(mPlayerCoordinator).addObserver(mController); - } - - @Test - public void testKeepScreenOnFlag() { - // default - don't keep the screen on - int flags = mActivity.getWindow().getAttributes().flags; - assertTrue((flags & WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) == 0); - - // play tab - requestAndStartPlayback(); - verify(mPlayback).addListener(mPlaybackListenerCaptor.capture()); - // update playback data so it isn't null - var data = Mockito.mock(PlaybackListener.PlaybackData.class); - doReturn(PlaybackListener.State.PLAYING).when(data).state(); - mPlaybackListenerCaptor.getValue().onPlaybackDataChanged(data); - - // keep the screen on while something is playing - flags = mActivity.getWindow().getAttributes().flags; - assertTrue((flags & WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) != 0); - - doReturn(PlaybackListener.State.BUFFERING).when(data).state(); - mPlaybackListenerCaptor.getValue().onPlaybackDataChanged(data); - - // don't keep the screen on if paused/stopped/buffering - flags = mActivity.getWindow().getAttributes().flags; - assertTrue((flags & WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) == 0); - - doReturn(PlaybackListener.State.PLAYING).when(data).state(); - mPlaybackListenerCaptor.getValue().onPlaybackDataChanged(data); - // playing again - keep the screen on - flags = mActivity.getWindow().getAttributes().flags; - assertTrue((flags & WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) != 0); - - mController.maybeStopPlayback( - mTab, ReadAloudMetrics.ReasonForStoppingPlayback.NEW_PLAYBACK_REQUEST); - - // playback stopped, clear the flag, don't keep the screen on - flags = mActivity.getWindow().getAttributes().flags; - assertTrue((flags & WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) == 0); - } - - @Test - public void testPlayTab_sendsVoiceList() { - mFakeTranslateBridge.setCurrentLanguage("en"); - doReturn( - List.of( - new PlaybackVoice("en", "voiceA"), - new PlaybackVoice("es", "voiceB"), - new PlaybackVoice("fr", "voiceC"))) - .when(mPlaybackHooks) - .getPlaybackVoiceList(any()); - mTab.setGurlOverrideForTesting(new GURL("https://en.wikipedia.org/wiki/Google")); - - mController.playTab(mTab, ReadAloudController.Entrypoint.MAGIC_TOOLBAR); - resolvePromises(); - - verify(mPlaybackHooks, times(1)).initVoices(); - verify(mPlaybackHooks, times(1)).createPlayback(mPlaybackArgsCaptor.capture(), any()); - - List<PlaybackVoice> voices = mPlaybackArgsCaptor.getValue().getVoices(); - assertNotNull(voices); - assertEquals(3, voices.size()); - assertEquals("en", voices.get(0).getLanguage()); - assertEquals("voiceA", voices.get(0).getVoiceId()); - assertEquals("es", voices.get(1).getLanguage()); - assertEquals("voiceB", voices.get(1).getVoiceId()); - assertEquals("fr", voices.get(2).getLanguage()); - assertEquals("voiceC", voices.get(2).getVoiceId()); - } - - @Test - public void testPlayTab_EmptyUrl() { - mFakeTranslateBridge.setCurrentLanguage("en"); - mTab.setGurlOverrideForTesting(new GURL("")); - boolean failed = false; - try { - mController.playTab(mTab, ReadAloudController.Entrypoint.MAGIC_TOOLBAR); - } catch (AssertionError e) { - failed = true; - } - assertTrue(failed); - } - - @Test - public void testPlayTranslatedTab_tabLanguageEmpty() { - Locale.setDefault(FR_FR); - - mFakeTranslateBridge.setIsPageTranslated(true); - mFakeTranslateBridge.setCurrentLanguage(""); - mTab.setGurlOverrideForTesting(new GURL("https://en.wikipedia.org/wiki/Google")); - - mController.playTab(mTab, ReadAloudController.Entrypoint.MAGIC_TOOLBAR); - resolvePromises(); - - // Without translate bridge reporting a language for the page, fall back to the system - // language. - verify(mPlaybackHooks).createPlayback(mPlaybackArgsCaptor.capture(), any()); - assertEquals("fr", mPlaybackArgsCaptor.getValue().getLanguage()); - } - - @Test - public void testPlayTranslatedTab_unsupportedLanguage() { - doReturn(List.of()).when(mPlaybackHooks).getVoicesFor(anyString()); - mFakeTranslateBridge.setCurrentLanguage("zz-ZZ"); - mTab.setGurlOverrideForTesting(new GURL("https://en.wikipedia.org/wiki/Google")); - - mController.playTab(mTab, ReadAloudController.Entrypoint.MAGIC_TOOLBAR); - resolvePromises(); - - verify(mPlaybackHooks, never()).createPlayback(mPlaybackArgsCaptor.capture(), any()); - verify(mPlayerCoordinator).playbackFailed(); - } - - @Test - public void testPlayTranslatedTab_tabLanguageUnd() { - Locale.setDefault(FR_FR); - - mFakeTranslateBridge.setIsPageTranslated(true); - mFakeTranslateBridge.setCurrentLanguage("und"); - mTab.setGurlOverrideForTesting(new GURL("https://en.wikipedia.org/wiki/Google")); - - mController.playTab(mTab, ReadAloudController.Entrypoint.MAGIC_TOOLBAR); - resolvePromises(); - - // Without translate bridge reporting a language for the page, fall back to the system - // language. - verify(mPlaybackHooks).createPlayback(mPlaybackArgsCaptor.capture(), any()); - assertEquals("fr", mPlaybackArgsCaptor.getValue().getLanguage()); - } - - @Test - public void testPlayUntranslatedTab() { - mFakeTranslateBridge.setIsPageTranslated(false); - mFakeTranslateBridge.setCurrentLanguage("fr"); - mTab.setGurlOverrideForTesting(new GURL("https://en.wikipedia.org/wiki/Google")); - - mController.playTab(mTab, ReadAloudController.Entrypoint.MAGIC_TOOLBAR); - resolvePromises(); - - // If page isn't translated, don't send a language and instead let the server decide the - // language. - verify(mPlaybackHooks).createPlayback(mPlaybackArgsCaptor.capture(), any()); - assertEquals(null, mPlaybackArgsCaptor.getValue().getLanguage()); - } - - @Test - @EnableFeatures({ChromeFeatureList.READALOUD_AUDIO_OVERVIEWS}) - public void testPlaybackModeSelectionEnabledUpdated() { - mFakeTranslateBridge.setIsPageTranslated(false); - mFakeTranslateBridge.setCurrentLanguage("en"); - - when(mPrefService.getInteger("readaloud.playback_mode")) - .thenReturn(PlaybackMode.OVERVIEW.getValue()); - String testUrl = "https://en.wikipedia.org/wiki/Google"; - mTab.setGurlOverrideForTesting(new GURL(testUrl)); - - mController.maybeCheckReadability(mTab); - verify(mHooksImpl, times(1)) - .isPageReadable( - eq("https://en.wikipedia.org/wiki/Google"), mCallbackCaptor.capture()); - mCallbackCaptor - .getValue() - .onSuccess( - testUrl, - ImmutableMap.of( - PlaybackMode.CLASSIC, - new ReadAloudReadabilityHooks.ReadabilityResult(true, false), - PlaybackMode.OVERVIEW, - new ReadAloudReadabilityHooks.ReadabilityResult(true, false))); - - ObservableSupplier<PlaybackModeSelectionEnablementStatus> observable = - mController.getPlaybackModeSelectionEnabled(); - observable.addObserver(mPlaybackModeSelectionEnabledCallback); - + @Test + public void testPlayTab_EmptyUrl() { + mFakeTranslateBridge.setCurrentLanguage("en"); + mTab.setGurlOverrideForTesting(new GURL("")); + boolean failed = false; + try { mController.playTab(mTab, ReadAloudController.Entrypoint.MAGIC_TOOLBAR); - resolvePromises(); - - verify(mPlaybackHooks, times(1)) - .createPlayback(Mockito.any(), mPlaybackCallbackCaptor.capture()); - - reset(mPlaybackModeSelectionEnabledCallback); - - onPlaybackSuccess(mPlayback); - verify(mPlaybackHooks).createPlayback(mPlaybackArgsCaptor.capture(), any()); - assertEquals(PlaybackMode.OVERVIEW, mPlaybackArgsCaptor.getValue().getPlaybackMode()); - - verify(mPlaybackModeSelectionEnabledCallback) - .onResult(eq(PlaybackModeSelectionEnablementStatus.MODE_SELECTION_ENABLED)); + } catch (AssertionError e) { + failed = true; } + assertTrue(failed); + } - @Test - public void testPlaybackModeSelectionEnabledUpdated_disabledThroughFlag() { - mFakeTranslateBridge.setIsPageTranslated(false); - mFakeTranslateBridge.setCurrentLanguage("en"); + @Test + public void testPlayTranslatedTab_tabLanguageEmpty() { + Locale.setDefault(FR_FR); - when(mPrefService.getInteger("readaloud.playback_mode")) - .thenReturn(PlaybackMode.OVERVIEW.getValue()); - String testUrl = "https://en.wikipedia.org/wiki/Google"; - mTab.setGurlOverrideForTesting(new GURL(testUrl)); + mFakeTranslateBridge.setIsPageTranslated(true); + mFakeTranslateBridge.setCurrentLanguage(""); + mTab.setGurlOverrideForTesting(new GURL("https://en.wikipedia.org/wiki/Google")); - mController.maybeCheckReadability(mTab); - verify(mHooksImpl, times(1)) - .isPageReadable( - eq("https://en.wikipedia.org/wiki/Google"), mCallbackCaptor.capture()); - mCallbackCaptor - .getValue() - .onSuccess( - testUrl, - ImmutableMap.of( - PlaybackMode.CLASSIC, - new ReadAloudReadabilityHooks.ReadabilityResult(true, false), - PlaybackMode.OVERVIEW, - new ReadAloudReadabilityHooks.ReadabilityResult(true, false))); + mController.playTab(mTab, ReadAloudController.Entrypoint.MAGIC_TOOLBAR); + resolvePromises(); - ObservableSupplier<PlaybackModeSelectionEnablementStatus> observable = - mController.getPlaybackModeSelectionEnabled(); - observable.addObserver(mPlaybackModeSelectionEnabledCallback); + // Without translate bridge reporting a language for the page, fall back to the system + // language. + verify(mPlaybackHooks).createPlayback(mPlaybackArgsCaptor.capture(), any()); + assertEquals("fr", mPlaybackArgsCaptor.getValue().getLanguage()); + } + @Test + public void testPlayTranslatedTab_unsupportedLanguage() { + doReturn(List.of()).when(mPlaybackHooks).getVoicesFor(anyString()); + mFakeTranslateBridge.setCurrentLanguage("zz-ZZ"); + mTab.setGurlOverrideForTesting(new GURL("https://en.wikipedia.org/wiki/Google")); + + mController.playTab(mTab, ReadAloudController.Entrypoint.MAGIC_TOOLBAR); + resolvePromises(); + + verify(mPlaybackHooks, never()).createPlayback(mPlaybackArgsCaptor.capture(), any()); + verify(mPlayerCoordinator).playbackFailed(); + } + + @Test + public void testPlayTranslatedTab_tabLanguageUnd() { + Locale.setDefault(FR_FR); + + mFakeTranslateBridge.setIsPageTranslated(true); + mFakeTranslateBridge.setCurrentLanguage("und"); + mTab.setGurlOverrideForTesting(new GURL("https://en.wikipedia.org/wiki/Google")); + + mController.playTab(mTab, ReadAloudController.Entrypoint.MAGIC_TOOLBAR); + resolvePromises(); + + // Without translate bridge reporting a language for the page, fall back to the system + // language. + verify(mPlaybackHooks).createPlayback(mPlaybackArgsCaptor.capture(), any()); + assertEquals("fr", mPlaybackArgsCaptor.getValue().getLanguage()); + } + + @Test + public void testPlayUntranslatedTab() { + mFakeTranslateBridge.setIsPageTranslated(false); + mFakeTranslateBridge.setCurrentLanguage("fr"); + mTab.setGurlOverrideForTesting(new GURL("https://en.wikipedia.org/wiki/Google")); + + mController.playTab(mTab, ReadAloudController.Entrypoint.MAGIC_TOOLBAR); + resolvePromises(); + + // If page isn't translated, don't send a language and instead let the server decide the + // language. + verify(mPlaybackHooks).createPlayback(mPlaybackArgsCaptor.capture(), any()); + assertEquals(null, mPlaybackArgsCaptor.getValue().getLanguage()); + } + + @Test + @EnableFeatures({ChromeFeatureList.READALOUD_AUDIO_OVERVIEWS}) + public void testPlaybackModeSelectionEnabledUpdated() { + mFakeTranslateBridge.setIsPageTranslated(false); + mFakeTranslateBridge.setCurrentLanguage("en"); + + when(mMetadata.playbackMode()).thenReturn(PlaybackMode.OVERVIEW); + + when(mPrefService.getInteger("readaloud.playback_mode")) + .thenReturn(PlaybackMode.OVERVIEW.getValue()); + String testUrl = "https://en.wikipedia.org/wiki/Google"; + mTab.setGurlOverrideForTesting(new GURL(testUrl)); + + mController.maybeCheckReadability(mTab); + verify(mHooksImpl, times(1)) + .isPageReadable(eq("https://en.wikipedia.org/wiki/Google"), mCallbackCaptor.capture()); + mCallbackCaptor + .getValue() + .onSuccess( + testUrl, + ImmutableMap.of( + PlaybackMode.CLASSIC, + new ReadAloudReadabilityHooks.ReadabilityResult(true, false), + PlaybackMode.OVERVIEW, + new ReadAloudReadabilityHooks.ReadabilityResult(true, false))); + + ObservableSupplier<PlaybackModeSelectionEnablementStatus> observable = + mController.getPlaybackModeSelectionEnabled(); + observable.addObserver(mPlaybackModeSelectionEnabledCallback); + + mController.playTab(mTab, ReadAloudController.Entrypoint.MAGIC_TOOLBAR); + resolvePromises(); + + verify(mPlaybackHooks, times(1)) + .createPlayback(Mockito.any(), mPlaybackCallbackCaptor.capture()); + + reset(mPlaybackModeSelectionEnabledCallback); + + onPlaybackSuccess(mPlayback); + verify(mPlaybackHooks).createPlayback(mPlaybackArgsCaptor.capture(), any()); + assertEquals( + ImmutableList.of(PlaybackMode.OVERVIEW, PlaybackMode.CLASSIC), + mPlaybackArgsCaptor.getValue().getPlaybackModes()); + assertEquals(PlaybackMode.OVERVIEW, mPlaybackArgsCaptor.getValue().getPlaybackMode()); + + verify(mPlaybackModeSelectionEnabledCallback) + .onResult(eq(PlaybackModeSelectionEnablementStatus.MODE_SELECTION_ENABLED)); + } + + @Test + @DisableFeatures({ChromeFeatureList.READALOUD_AUDIO_OVERVIEWS}) + public void testPlaybackModeSelectionEnabledUpdated_disabledThroughFlag() { + mFakeTranslateBridge.setIsPageTranslated(false); + mFakeTranslateBridge.setCurrentLanguage("en"); + + when(mPrefService.getInteger("readaloud.playback_mode")) + .thenReturn(PlaybackMode.OVERVIEW.getValue()); + String testUrl = "https://en.wikipedia.org/wiki/Google"; + mTab.setGurlOverrideForTesting(new GURL(testUrl)); + + mController.maybeCheckReadability(mTab); + verify(mHooksImpl, times(1)) + .isPageReadable(eq("https://en.wikipedia.org/wiki/Google"), mCallbackCaptor.capture()); + mCallbackCaptor + .getValue() + .onSuccess( + testUrl, + ImmutableMap.of( + PlaybackMode.CLASSIC, + new ReadAloudReadabilityHooks.ReadabilityResult(true, false), + PlaybackMode.OVERVIEW, + new ReadAloudReadabilityHooks.ReadabilityResult(true, false))); + + ObservableSupplier<PlaybackModeSelectionEnablementStatus> observable = + mController.getPlaybackModeSelectionEnabled(); + observable.addObserver(mPlaybackModeSelectionEnabledCallback); + + mController.playTab(mTab, ReadAloudController.Entrypoint.MAGIC_TOOLBAR); + resolvePromises(); + + verify(mPlaybackHooks, times(1)) + .createPlayback(Mockito.any(), mPlaybackCallbackCaptor.capture()); + + reset(mPlaybackModeSelectionEnabledCallback); + + onPlaybackSuccess(mPlayback); + verify(mPlaybackHooks).createPlayback(mPlaybackArgsCaptor.capture(), any()); + assertEquals( + ImmutableList.of(PlaybackMode.CLASSIC), mPlaybackArgsCaptor.getValue().getPlaybackModes()); + assertEquals(PlaybackMode.CLASSIC, mPlaybackArgsCaptor.getValue().getPlaybackMode()); + + // We cannot assert that there's an update for FEATURE_DISABLED because that's the initial value + // of the observable + // and it doesn't really change. + // Instead, we assert that the state isn't enabled. + verify(mPlaybackModeSelectionEnabledCallback, never()) + .onResult(eq(PlaybackModeSelectionEnablementStatus.MODE_SELECTION_ENABLED)); + } + + @Test + @EnableFeatures({ChromeFeatureList.READALOUD_AUDIO_OVERVIEWS}) + public void testPlaybackModeSelectionEnabledUpdated_disabledThroughLanguage() { + mFakeTranslateBridge.setIsPageTranslated(false); + mFakeTranslateBridge.setCurrentLanguage("fr"); + + when(mPrefService.getInteger("readaloud.playback_mode")) + .thenReturn(PlaybackMode.OVERVIEW.getValue()); + String testUrl = "https://en.wikipedia.org/wiki/Google"; + mTab.setGurlOverrideForTesting(new GURL(testUrl)); + + mController.maybeCheckReadability(mTab); + verify(mHooksImpl, times(1)) + .isPageReadable(eq("https://en.wikipedia.org/wiki/Google"), mCallbackCaptor.capture()); + mCallbackCaptor + .getValue() + .onSuccess( + testUrl, + ImmutableMap.of( + PlaybackMode.CLASSIC, + new ReadAloudReadabilityHooks.ReadabilityResult(true, false), + PlaybackMode.OVERVIEW, + new ReadAloudReadabilityHooks.ReadabilityResult(true, false))); + + ObservableSupplier<PlaybackModeSelectionEnablementStatus> observable = + mController.getPlaybackModeSelectionEnabled(); + observable.addObserver(mPlaybackModeSelectionEnabledCallback); + + mController.playTab(mTab, ReadAloudController.Entrypoint.MAGIC_TOOLBAR); + resolvePromises(); + + verify(mPlaybackHooks, times(1)) + .createPlayback(Mockito.any(), mPlaybackCallbackCaptor.capture()); + + reset(mPlaybackModeSelectionEnabledCallback); + + onPlaybackSuccess(mPlayback); + verify(mPlaybackHooks).createPlayback(mPlaybackArgsCaptor.capture(), any()); + assertEquals( + ImmutableList.of(PlaybackMode.CLASSIC), mPlaybackArgsCaptor.getValue().getPlaybackModes()); + assertEquals(PlaybackMode.CLASSIC, mPlaybackArgsCaptor.getValue().getPlaybackMode()); + + verify(mPlaybackModeSelectionEnabledCallback) + .onResult(eq(PlaybackModeSelectionEnablementStatus.MODE_SELECTION_DISABLED_AO_UNAVAILABLE)); + + verify(mPlaybackModeSelectionEnabledCallback, never()) + .onResult(eq(PlaybackModeSelectionEnablementStatus.MODE_SELECTION_ENABLED)); + } + + @Test + @EnableFeatures({ChromeFeatureList.READALOUD_AUDIO_OVERVIEWS}) + public void testPlaybackModeSelectionEnabledUpdated_disabledThroughFallbackToClassic() { + mFakeTranslateBridge.setIsPageTranslated(false); + mFakeTranslateBridge.setCurrentLanguage("en"); + // Actual playback mode is classic. + when(mMetadata.playbackMode()).thenReturn(PlaybackMode.CLASSIC); + + when(mPrefService.getInteger("readaloud.playback_mode")) + .thenReturn(PlaybackMode.OVERVIEW.getValue()); + + String testUrl = "https://en.wikipedia.org/wiki/Google"; + mTab.setGurlOverrideForTesting(new GURL(testUrl)); + + mController.maybeCheckReadability(mTab); + verify(mHooksImpl, times(1)) + .isPageReadable(eq("https://en.wikipedia.org/wiki/Google"), mCallbackCaptor.capture()); + mCallbackCaptor + .getValue() + .onSuccess( + testUrl, + ImmutableMap.of( + PlaybackMode.CLASSIC, + new ReadAloudReadabilityHooks.ReadabilityResult(true, false), + PlaybackMode.OVERVIEW, + new ReadAloudReadabilityHooks.ReadabilityResult(true, false))); + + ObservableSupplier<PlaybackModeSelectionEnablementStatus> observable = + mController.getPlaybackModeSelectionEnabled(); + observable.addObserver(mPlaybackModeSelectionEnabledCallback); + + mController.playTab(mTab, ReadAloudController.Entrypoint.MAGIC_TOOLBAR); + resolvePromises(); + + verify(mPlaybackHooks, times(1)) + .createPlayback(Mockito.any(), mPlaybackCallbackCaptor.capture()); + + reset(mPlaybackModeSelectionEnabledCallback); + + onPlaybackSuccess(mPlayback); + + verify(mPlaybackHooks).createPlayback(mPlaybackArgsCaptor.capture(), any()); + assertEquals( + ImmutableList.of(PlaybackMode.OVERVIEW, PlaybackMode.CLASSIC), + mPlaybackArgsCaptor.getValue().getPlaybackModes()); + + verify(mPlaybackModeSelectionEnabledCallback) + .onResult(eq(PlaybackModeSelectionEnablementStatus.MODE_SELECTION_DISABLED_AO_UNAVAILABLE)); + + verify(mPlaybackModeSelectionEnabledCallback, never()) + .onResult(eq(PlaybackModeSelectionEnablementStatus.MODE_SELECTION_ENABLED)); + } + + @Test + @EnableFeatures({ChromeFeatureList.READALOUD_AUDIO_OVERVIEWS}) + public void testPlaybackModeSelectionEnabledUpdated_disabledThroughFallbackToOverview() { + mFakeTranslateBridge.setIsPageTranslated(false); + mFakeTranslateBridge.setCurrentLanguage("en"); + // Actual playback mode is overview. + when(mMetadata.playbackMode()).thenReturn(PlaybackMode.OVERVIEW); + + when(mPrefService.getInteger("readaloud.playback_mode")) + .thenReturn(PlaybackMode.CLASSIC.getValue()); + + String testUrl = "https://en.wikipedia.org/wiki/Google"; + mTab.setGurlOverrideForTesting(new GURL(testUrl)); + + mController.maybeCheckReadability(mTab); + verify(mHooksImpl, times(1)) + .isPageReadable(eq("https://en.wikipedia.org/wiki/Google"), mCallbackCaptor.capture()); + mCallbackCaptor + .getValue() + .onSuccess( + testUrl, + ImmutableMap.of( + PlaybackMode.CLASSIC, + new ReadAloudReadabilityHooks.ReadabilityResult(true, false), + PlaybackMode.OVERVIEW, + new ReadAloudReadabilityHooks.ReadabilityResult(true, false))); + + ObservableSupplier<PlaybackModeSelectionEnablementStatus> observable = + mController.getPlaybackModeSelectionEnabled(); + observable.addObserver(mPlaybackModeSelectionEnabledCallback); + + mController.playTab(mTab, ReadAloudController.Entrypoint.MAGIC_TOOLBAR); + resolvePromises(); + + verify(mPlaybackHooks, times(1)) + .createPlayback(Mockito.any(), mPlaybackCallbackCaptor.capture()); + + reset(mPlaybackModeSelectionEnabledCallback); + + onPlaybackSuccess(mPlayback); + + verify(mPlaybackHooks).createPlayback(mPlaybackArgsCaptor.capture(), any()); + assertEquals( + ImmutableList.of(PlaybackMode.CLASSIC, PlaybackMode.OVERVIEW), + mPlaybackArgsCaptor.getValue().getPlaybackModes()); + + verify(mPlaybackModeSelectionEnabledCallback) + .onResult( + eq(PlaybackModeSelectionEnablementStatus.MODE_SELECTION_DISABLED_CLASSIC_UNAVAILABLE)); + + verify(mPlaybackModeSelectionEnabledCallback, never()) + .onResult(eq(PlaybackModeSelectionEnablementStatus.MODE_SELECTION_ENABLED)); + } + + @Test + @EnableFeatures({ChromeFeatureList.READALOUD_AUDIO_OVERVIEWS}) + public void testPlaybackModeSelectionEnabledUpdated_disabledThroughClassicUnsupported() { + mFakeTranslateBridge.setIsPageTranslated(false); + mFakeTranslateBridge.setCurrentLanguage("en"); + + when(mPrefService.getInteger("readaloud.playback_mode")) + .thenReturn(PlaybackMode.OVERVIEW.getValue()); + String testUrl = "https://en.wikipedia.org/wiki/Google"; + mTab.setGurlOverrideForTesting(new GURL(testUrl)); + + mController.maybeCheckReadability(mTab); + verify(mHooksImpl, times(1)) + .isPageReadable(eq("https://en.wikipedia.org/wiki/Google"), mCallbackCaptor.capture()); + mCallbackCaptor + .getValue() + .onSuccess( + testUrl, + ImmutableMap.of( + PlaybackMode.CLASSIC, + new ReadAloudReadabilityHooks.ReadabilityResult(false, false), + PlaybackMode.OVERVIEW, + new ReadAloudReadabilityHooks.ReadabilityResult(true, false))); + + ObservableSupplier<PlaybackModeSelectionEnablementStatus> observable = + mController.getPlaybackModeSelectionEnabled(); + observable.addObserver(mPlaybackModeSelectionEnabledCallback); + + mController.playTab(mTab, ReadAloudController.Entrypoint.MAGIC_TOOLBAR); + resolvePromises(); + + verify(mPlaybackHooks, times(1)) + .createPlayback(Mockito.any(), mPlaybackCallbackCaptor.capture()); + + reset(mPlaybackModeSelectionEnabledCallback); + + onPlaybackSuccess(mPlayback); + verify(mPlaybackHooks).createPlayback(mPlaybackArgsCaptor.capture(), any()); + assertEquals( + ImmutableList.of(PlaybackMode.OVERVIEW), mPlaybackArgsCaptor.getValue().getPlaybackModes()); + assertEquals(PlaybackMode.OVERVIEW, mPlaybackArgsCaptor.getValue().getPlaybackMode()); + + verify(mPlaybackModeSelectionEnabledCallback) + .onResult( + eq(PlaybackModeSelectionEnablementStatus.MODE_SELECTION_DISABLED_CLASSIC_UNAVAILABLE)); + + verify(mPlaybackModeSelectionEnabledCallback, never()) + .onResult(eq(PlaybackModeSelectionEnablementStatus.MODE_SELECTION_ENABLED)); + } + + @Test + @EnableFeatures({ChromeFeatureList.READALOUD_AUDIO_OVERVIEWS}) + public void testPlayTabInOverviewMode() { + mFakeTranslateBridge.setIsPageTranslated(false); + mFakeTranslateBridge.setCurrentLanguage("en"); + when(mPrefService.getInteger("readaloud.playback_mode")) + .thenReturn(PlaybackMode.OVERVIEW.getValue()); + String testUrl = "https://en.wikipedia.org/wiki/Google"; + mTab.setGurlOverrideForTesting(new GURL(testUrl)); + + mController.maybeCheckReadability(mTab); + verify(mHooksImpl, times(1)) + .isPageReadable(eq("https://en.wikipedia.org/wiki/Google"), mCallbackCaptor.capture()); + mCallbackCaptor + .getValue() + .onSuccess( + testUrl, + ImmutableMap.of( + PlaybackMode.CLASSIC, + new ReadAloudReadabilityHooks.ReadabilityResult(true, false), + PlaybackMode.OVERVIEW, + new ReadAloudReadabilityHooks.ReadabilityResult(true, false))); + + mController.playTab(mTab, ReadAloudController.Entrypoint.MAGIC_TOOLBAR); + resolvePromises(); + + verify(mPlaybackHooks).createPlayback(mPlaybackArgsCaptor.capture(), any()); + assertEquals( + ImmutableList.of(PlaybackMode.OVERVIEW, PlaybackMode.CLASSIC), + mPlaybackArgsCaptor.getValue().getPlaybackModes()); + assertEquals(PlaybackMode.OVERVIEW, mPlaybackArgsCaptor.getValue().getPlaybackMode()); + } + + @Test + @EnableFeatures({ChromeFeatureList.READALOUD_AUDIO_OVERVIEWS}) + public void testNonEnglishTabClassicIsUsed() { + mFakeTranslateBridge.setIsPageTranslated(false); + mFakeTranslateBridge.setCurrentLanguage("fr"); + when(mPrefService.getInteger("readaloud.playback_mode")) + .thenReturn(PlaybackMode.OVERVIEW.getValue()); + String testUrl = "https://en.wikipedia.org/wiki/Google"; + mTab.setGurlOverrideForTesting(new GURL(testUrl)); + + mController.maybeCheckReadability(mTab); + verify(mHooksImpl, times(1)) + .isPageReadable(eq("https://en.wikipedia.org/wiki/Google"), mCallbackCaptor.capture()); + mCallbackCaptor + .getValue() + .onSuccess( + testUrl, + ImmutableMap.of( + PlaybackMode.CLASSIC, + new ReadAloudReadabilityHooks.ReadabilityResult(true, false), + PlaybackMode.OVERVIEW, + new ReadAloudReadabilityHooks.ReadabilityResult(true, false))); + + mController.playTab(mTab, ReadAloudController.Entrypoint.MAGIC_TOOLBAR); + resolvePromises(); + + verify(mPlaybackHooks).createPlayback(mPlaybackArgsCaptor.capture(), any()); + assertEquals( + ImmutableList.of(PlaybackMode.CLASSIC), mPlaybackArgsCaptor.getValue().getPlaybackModes()); + assertEquals(PlaybackMode.CLASSIC, mPlaybackArgsCaptor.getValue().getPlaybackMode()); + } + + @Test + @EnableFeatures({ChromeFeatureList.READALOUD_AUDIO_OVERVIEWS}) + public void testPreferenceUnspecifiedOverviewsDefault() { + mFakeTranslateBridge.setIsPageTranslated(false); + mFakeTranslateBridge.setCurrentLanguage("en"); + when(mPrefService.getInteger("readaloud.playback_mode")) + .thenReturn(PlaybackMode.UNSPECIFIED.getValue()); + String testUrl = "https://en.wikipedia.org/wiki/Google"; + mTab.setGurlOverrideForTesting(new GURL(testUrl)); + + mController.maybeCheckReadability(mTab); + verify(mHooksImpl, times(1)) + .isPageReadable(eq("https://en.wikipedia.org/wiki/Google"), mCallbackCaptor.capture()); + mCallbackCaptor + .getValue() + .onSuccess( + testUrl, + ImmutableMap.of( + PlaybackMode.CLASSIC, + new ReadAloudReadabilityHooks.ReadabilityResult(true, false), + PlaybackMode.OVERVIEW, + new ReadAloudReadabilityHooks.ReadabilityResult(true, false))); + + mController.playTab(mTab, ReadAloudController.Entrypoint.MAGIC_TOOLBAR); + resolvePromises(); + + verify(mPlaybackHooks).createPlayback(mPlaybackArgsCaptor.capture(), any()); + assertEquals( + ImmutableList.of(PlaybackMode.OVERVIEW, PlaybackMode.CLASSIC), + mPlaybackArgsCaptor.getValue().getPlaybackModes()); + assertEquals(PlaybackMode.OVERVIEW, mPlaybackArgsCaptor.getValue().getPlaybackMode()); + } + + @Test + @EnableFeatures({ChromeFeatureList.READALOUD_AUDIO_OVERVIEWS}) + public void testOverviewsUnreadableFallsbackToClassic() { + mFakeTranslateBridge.setIsPageTranslated(false); + mFakeTranslateBridge.setCurrentLanguage("en"); + when(mPrefService.getInteger("readaloud.playback_mode")) + .thenReturn(PlaybackMode.OVERVIEW.getValue()); + String testUrl = "https://en.wikipedia.org/wiki/Google"; + mTab.setGurlOverrideForTesting(new GURL(testUrl)); + + mController.maybeCheckReadability(mTab); + verify(mHooksImpl, times(1)) + .isPageReadable(eq("https://en.wikipedia.org/wiki/Google"), mCallbackCaptor.capture()); + mCallbackCaptor + .getValue() + .onSuccess( + testUrl, + ImmutableMap.of( + PlaybackMode.CLASSIC, + new ReadAloudReadabilityHooks.ReadabilityResult(true, false), + PlaybackMode.OVERVIEW, + new ReadAloudReadabilityHooks.ReadabilityResult(false, false))); + + mController.playTab(mTab, ReadAloudController.Entrypoint.MAGIC_TOOLBAR); + resolvePromises(); + + verify(mPlaybackHooks).createPlayback(mPlaybackArgsCaptor.capture(), any()); + assertEquals( + ImmutableList.of(PlaybackMode.CLASSIC), mPlaybackArgsCaptor.getValue().getPlaybackModes()); + assertEquals(PlaybackMode.CLASSIC, mPlaybackArgsCaptor.getValue().getPlaybackMode()); + } + + @Test + public void testVoicesMatchLanguage_pageTranslated() { + // translated page should use chrome language + var voiceEn = new PlaybackVoice("en", "asdf", ""); + var voiceFr = new PlaybackVoice("fr", "asdf", ""); + when(mMetadata.languageCode()).thenReturn("en"); + doReturn(List.of(voiceEn)).when(mPlaybackHooks).getVoicesFor(eq("en")); + doReturn(List.of(voiceFr)).when(mPlaybackHooks).getVoicesFor(eq("fr")); + doReturn(List.of(voiceEn, voiceFr)).when(mPlaybackHooks).getPlaybackVoiceList(any()); + mFakeTranslateBridge.setIsPageTranslated(true); + mFakeTranslateBridge.setCurrentLanguage("fr"); + mTab.setGurlOverrideForTesting(new GURL("https://en.wikipedia.org/wiki/Google")); + + mController.playTab(mTab, ReadAloudController.Entrypoint.MAGIC_TOOLBAR); + resolvePromises(); + + verify(mPlaybackHooks) + .createPlayback(mPlaybackArgsCaptor.capture(), mPlaybackCallbackCaptor.capture()); + assertEquals("fr", mPlaybackArgsCaptor.getValue().getLanguage()); + onPlaybackSuccess(mPlayback); + // Page is in French, voice options should have voices for "fr" + assertEquals("fr", mController.getCurrentLanguageVoicesSupplier().get().get(0).getLanguage()); + } + + @Test + public void testVoicesMatchLanguage_pageNotTranslated() { + // non translated page should use server detected content language + var voiceEn = new PlaybackVoice("en", "asdf", ""); + var voiceFr = new PlaybackVoice("fr", "asdf", ""); + doReturn(List.of(voiceEn)).when(mPlaybackHooks).getVoicesFor(eq("en")); + doReturn(List.of(voiceFr)).when(mPlaybackHooks).getVoicesFor(eq("fr")); + doReturn(List.of(voiceEn, voiceFr)).when(mPlaybackHooks).getPlaybackVoiceList(any()); + when(mMetadata.languageCode()).thenReturn("en"); + mFakeTranslateBridge.setIsPageTranslated(false); + mFakeTranslateBridge.setCurrentLanguage("fr"); + mTab.setGurlOverrideForTesting(new GURL("https://en.wikipedia.org/wiki/Google")); + + mController.playTab(mTab, ReadAloudController.Entrypoint.MAGIC_TOOLBAR); + resolvePromises(); + + verify(mPlaybackHooks) + .createPlayback(mPlaybackArgsCaptor.capture(), mPlaybackCallbackCaptor.capture()); + assertEquals(null, mPlaybackArgsCaptor.getValue().getLanguage()); + onPlaybackSuccess(mPlayback); + + assertEquals("en", mController.getCurrentLanguageVoicesSupplier().get().get(0).getLanguage()); + } + + @Test + public void testFailureIfServerLanguageUnsupported() { + // non translated page should use server detected content language + var voiceEn = new PlaybackVoice("en", "asdf", ""); + var voiceFr = new PlaybackVoice("fr", "asdf", ""); + doReturn(List.of(voiceEn)).when(mPlaybackHooks).getVoicesFor(eq("en")); + doReturn(List.of(voiceFr)).when(mPlaybackHooks).getVoicesFor(eq("fr")); + doReturn(List.of(voiceEn, voiceFr)).when(mPlaybackHooks).getPlaybackVoiceList(any()); + // unsupported + when(mMetadata.languageCode()).thenReturn("pl"); + mFakeTranslateBridge.setIsPageTranslated(false); + mFakeTranslateBridge.setCurrentLanguage("fr"); + mTab.setGurlOverrideForTesting(new GURL("https://en.wikipedia.org/wiki/Google")); + + mController.playTab(mTab, ReadAloudController.Entrypoint.MAGIC_TOOLBAR); + resolvePromises(); + + verify(mPlaybackHooks) + .createPlayback(mPlaybackArgsCaptor.capture(), mPlaybackCallbackCaptor.capture()); + assertEquals(null, mPlaybackArgsCaptor.getValue().getLanguage()); + onPlaybackSuccess(mPlayback); + + verify(mPlayerCoordinator).playbackFailed(); + } + + @Test + public void testPlayTab_onFailure() { + mFakeTranslateBridge.setCurrentLanguage("en"); + GURL gurl = new GURL("https://en.wikipedia.org/wiki/Google"); + mTab.setGurlOverrideForTesting(gurl); + mController.maybeCheckReadability(mTab); + // also check that a generic error doesn't invalidate readability result + verify(mHooksImpl).isPageReadable(eq(gurl.getSpec()), mCallbackCaptor.capture()); + mCallbackCaptor + .getValue() + .onSuccess( + gurl.getSpec(), + ImmutableMap.of( + PlaybackMode.CLASSIC, + new ReadAloudReadabilityHooks.ReadabilityResult(true, false))); + mController.playTab(mTab, ReadAloudController.Entrypoint.MAGIC_TOOLBAR); + resolvePromises(); + + assertTrue(mController.isReadable(mTab)); + verify(mPlaybackHooks, times(1)) + .createPlayback(Mockito.any(), mPlaybackCallbackCaptor.capture()); + mPlaybackCallbackCaptor.getValue().onFailure(new Throwable()); + resolvePromises(); + verify(mPlayerCoordinator, times(1)).playbackFailed(); + assertTrue(mController.isReadable(mTab)); + } + + @Test + public void testPlayTab_onFailure_unsupportedLink() { + mFakeTranslateBridge.setCurrentLanguage("en"); + GURL gurl = new GURL("https://en.wikipedia.org/wiki/Google"); + mTab.setGurlOverrideForTesting(gurl); + mController.maybeCheckReadability(mTab); + // also check that a readAloudUnsupported error does invalidate a false positive readability + // result + verify(mHooksImpl).isPageReadable(eq(gurl.getSpec()), mCallbackCaptor.capture()); + mCallbackCaptor + .getValue() + .onSuccess( + gurl.getSpec(), + ImmutableMap.of( + PlaybackMode.CLASSIC, + new ReadAloudReadabilityHooks.ReadabilityResult(true, false))); + + assertTrue(mController.isReadable(mTab)); + mController.playTab(mTab, ReadAloudController.Entrypoint.MAGIC_TOOLBAR); + resolvePromises(); + + verify(mPlaybackHooks, times(1)) + .createPlayback(Mockito.any(), mPlaybackCallbackCaptor.capture()); + mPlaybackCallbackCaptor + .getValue() + .onFailure( + new ReadAloudUnsupportedException( + "message", + /* cause= */ null, + ReadAloudUnsupportedException.RejectionReason.UNKNOWN_REJECTION_REASON)); + resolvePromises(); + verify(mPlayerCoordinator, times(1)).playbackFailed(); + assertFalse(mController.isReadable(mTab)); + } + + @Test + public void testStopPlayback() { + // Play tab + requestAndStartPlayback(); + + // Stop playback + mController.maybeStopPlayback( + mTab, ReadAloudMetrics.ReasonForStoppingPlayback.NEW_PLAYBACK_REQUEST); + verify(mPlayback).release(); + + resetPlaybackMocks(); + + // Subsequent playTab() should play without trying to release anything. + mController.playTab(mTab, ReadAloudController.Entrypoint.MAGIC_TOOLBAR); + resolvePromises(); + verify(mPlaybackHooks).createPlayback(any(), any()); + verify(mPlayback, never()).release(); + } + + @Test + public void testStopPlaybackWhenBackPressingToNewTab() { + // Play tab + requestAndStartPlayback(); + + // now simulate back press to a new tab page (doesn't trigger new url load) + when(mTab.getUrl()).thenReturn(new GURL("chrome-native://newtab/")); + mController.getTabModelTabObserverforTests().onUrlUpdated(mTab); + + verify(mPlayback).release(); + } + + @Test + public void testDontStopPlayback() { + // Play tab + requestAndStartPlayback(); + + // now simulate a situation updateUrl was called with the same url as the one playing - + // nothing should happen + mController.getTabModelTabObserverforTests().onUrlUpdated(mTab); + verify(mPlayback, never()).release(); + + // now update url from a different, non playing tab. The active playback should be + // unaffected. + MockTab tab = mTabModelSelector.addMockTab(); + tab.setWebContentsOverrideForTesting(mWebContents); + tab.setUrl(new GURL("")); + mController.getTabModelTabObserverforTests().onUrlUpdated(tab); + verify(mPlayback, never()).release(); + } + + @Test + public void highlightsRequested() { + // set up the highlighter + mController.setTimepointsSupportedForTest(mTab.getUrl().getSpec(), true); + mController.playTab(mTab, ReadAloudController.Entrypoint.MAGIC_TOOLBAR); + resolvePromises(); + verify(mPlaybackHooks, times(1)) + .createPlayback(Mockito.any(), mPlaybackCallbackCaptor.capture()); + onPlaybackSuccess(mPlayback); + verify(mHighlighter).initializeJs(eq(mTab), eq(mMetadata), any(Highlighter.Config.class)); + // Checks that the pref is read to set up highlighter state + // hasPrefPath is called twice, once during ReadAloudPrefs.isHighlightingEnabled and during + // ReadAloudPrefs.setHighlightingEnabled + verify(mPrefService, times(2)).hasPrefPath(eq(ReadAloudPrefs.HIGHLIGHTING_ENABLED_PATH)); + + // trigger highlights + mController.onPhraseChanged(mPhraseTiming); + + verify(mHighlighter).highlightText(eq(mGlobalRenderFrameHostId), eq(mTab), eq(mPhraseTiming)); + + // now disable highlighting - we should not trigger highlights anymore + mController.getHighlightingEnabledSupplier().set(false); + // Pref is updated. + verify(mPrefService).setBoolean(eq(ReadAloudPrefs.HIGHLIGHTING_ENABLED_PATH), eq(false)); + mController.onPhraseChanged(mPhraseTiming); + verify(mHighlighter, times(1)) + .highlightText(eq(mGlobalRenderFrameHostId), eq(mTab), eq(mPhraseTiming)); + } + + @Test + public void reloadingTab_highlightsNotCleared() { + // set up the highlighter + mController.setTimepointsSupportedForTest(mTab.getUrl().getSpec(), true); + mController.playTab(mTab, ReadAloudController.Entrypoint.MAGIC_TOOLBAR); + resolvePromises(); + verify(mPlaybackHooks, times(1)) + .createPlayback(Mockito.any(), mPlaybackCallbackCaptor.capture()); + onPlaybackSuccess(mPlayback); + verify(mHighlighter).initializeJs(eq(mTab), eq(mMetadata), any(Highlighter.Config.class)); + + // Reload tab to a different url. + mController + .getTabModelTabObserverforTests() + .onPageLoadStarted(mTab, new GURL("http://wikipedia.org")); + + verify(mHighlighter, never()).handleTabReloaded(any()); + } + + @Test + public void stoppingPlaybackClearsHighlighter() { + // set up the highlighter + mController.setTimepointsSupportedForTest(mTab.getUrl().getSpec(), true); + mController.playTab(mTab, ReadAloudController.Entrypoint.MAGIC_TOOLBAR); + resolvePromises(); + verify(mPlaybackHooks, times(1)) + .createPlayback(Mockito.any(), mPlaybackCallbackCaptor.capture()); + onPlaybackSuccess(mPlayback); + verify(mHighlighter).initializeJs(eq(mTab), eq(mMetadata), any(Highlighter.Config.class)); + + // stopping playback should clear highlighting. + mController.maybeStopPlayback( + mTab, ReadAloudMetrics.ReasonForStoppingPlayback.NEW_PLAYBACK_REQUEST); + + verify(mHighlighter).clearHighlights(eq(mGlobalRenderFrameHostId), eq(mTab)); + } + + @Test + public void testUserDataStrippedFromReadabilityCheck() { + GURL tabUrl = new GURL("http://user:pass@example.com"); + mTab.setGurlOverrideForTesting(tabUrl); + + mController.maybeCheckReadability(mTab); + + String sanitized = "http://example.com/"; + verify(mHooksImpl, times(1)).isPageReadable(eq(sanitized), mCallbackCaptor.capture()); + assertFalse(mController.isReadable(mTab)); + + mCallbackCaptor + .getValue() + .onSuccess( + sanitized, + ImmutableMap.of( + PlaybackMode.CLASSIC, new ReadAloudReadabilityHooks.ReadabilityResult(true, true))); + assertTrue(mController.isReadable(mTab)); + assertTrue(mController.timepointsSupported(mTab, PlaybackMode.CLASSIC)); + } + + @Test + public void testSetHighlighterMode() { + // highlighter can be null if page doesn't support highlighting, + // this just test null checkss + mController.setHighlighterMode(2); + verify(mHighlighter, never()).handleTabReloaded(mTab); + + mController.setTimepointsSupportedForTest(mTab.getUrl().getSpec(), true); + mController.playTab(mTab, ReadAloudController.Entrypoint.MAGIC_TOOLBAR); + resolvePromises(); + verify(mPlaybackHooks, times(1)) + .createPlayback(Mockito.any(), mPlaybackCallbackCaptor.capture()); + + onPlaybackSuccess(mPlayback); + + mController.setHighlighterMode(2); + verify(mHighlighter, times(1)).handleTabReloaded(mTab); + + // only do something if new mode is different + mController.setHighlighterMode(2); + verify(mHighlighter, times(1)).handleTabReloaded(mTab); + + mController.setHighlighterMode(1); + verify(mHighlighter, times(2)).handleTabReloaded(mTab); + } + + @Test + public void testSetPlaybackModeAndRestartPlayback() { + // First play tab. + mFakeTranslateBridge.setCurrentLanguage("en"); + mTab.setGurlOverrideForTesting(new GURL("https://en.wikipedia.org/wiki/Google")); + mController.playTab(mTab, ReadAloudController.Entrypoint.MAGIC_TOOLBAR); + resolvePromises(); + verify(mPlaybackHooks, times(1)) + .createPlayback(mPlaybackArgsCaptor.capture(), mPlaybackCallbackCaptor.capture()); + + onPlaybackSuccess(mPlayback); + reset(mPlaybackHooks); + + // Mock a playing playback. + var newVoice = new PlaybackVoice("lang", "NEW VOICE ID"); + doReturn(List.of(newVoice)).when(mPlaybackHooks).getPlaybackVoiceList(any()); + doReturn(List.of(newVoice)).when(mPlaybackHooks).getVoicesFor(anyString()); + var data = Mockito.mock(PlaybackData.class); + doReturn(99).when(data).paragraphIndex(); + doReturn(PlaybackListener.State.PLAYING).when(data).state(); + mController.onPlaybackDataChanged(data); + + // Set mode and restart. + mController.setPlaybackModeAndApplyToPlayback(PlaybackMode.OVERVIEW); + + verify(mTracker).notifyEvent(eq("read_aloud_playback_mode_clicked")); + + // Pref is updated. + verify(mPrefService) + .setInteger(eq("readaloud.playback_mode"), eq(PlaybackMode.OVERVIEW.getValue())); + + // Playback is stopped. + verify(mPlayback).release(); + + // Playback starts again. + verify(mPlaybackHooks, times(1)) + .createPlayback(mPlaybackArgsCaptor.capture(), mPlaybackCallbackCaptor.capture()); + assertEquals( + ImmutableList.of(PlaybackMode.CLASSIC), mPlaybackArgsCaptor.getValue().getPlaybackModes()); + assertEquals(PlaybackMode.CLASSIC, mPlaybackArgsCaptor.getValue().getPlaybackMode()); + + onPlaybackSuccess(mPlayback); + verify(mPlayback, times(2)).play(); + verify(mPlayback, never()).seekToParagraph(anyInt(), anyLong()); + } + + @Test + public void testSetVoiceAndRestartPlayback() { + // Voices setup + var oldVoice = new PlaybackVoice("lang", "OLD VOICE ID"); + doReturn(List.of(oldVoice)).when(mPlaybackHooks).getPlaybackVoiceList(any()); + + // First play tab. + mFakeTranslateBridge.setCurrentLanguage("en"); + mTab.setGurlOverrideForTesting(new GURL("https://en.wikipedia.org/wiki/Google")); + mController.playTab(mTab, ReadAloudController.Entrypoint.MAGIC_TOOLBAR); + resolvePromises(); + + // Verify the original voice list. + verify(mPlaybackHooks, times(1)) + .createPlayback(mPlaybackArgsCaptor.capture(), mPlaybackCallbackCaptor.capture()); + List<PlaybackVoice> gotVoices = mPlaybackArgsCaptor.getValue().getVoices(); + assertEquals(1, gotVoices.size()); + assertEquals("OLD VOICE ID", gotVoices.get(0).getVoiceId()); + onPlaybackSuccess(mPlayback); + + reset(mPlaybackHooks); + + // Set the new voice. + var newVoice = new PlaybackVoice("lang", "NEW VOICE ID"); + doReturn(List.of(newVoice)).when(mPlaybackHooks).getPlaybackVoiceList(any()); + doReturn(List.of(newVoice)).when(mPlaybackHooks).getVoicesFor(anyString()); + var data = Mockito.mock(PlaybackData.class); + doReturn(99).when(data).paragraphIndex(); + doReturn(PlaybackListener.State.PLAYING).when(data).state(); + mController.onPlaybackDataChanged(data); + mController.setVoiceOverrideAndApplyToPlayback(newVoice); + + // Pref is updated. + verify(mReadAloudPrefsNatives).setVoice(eq(mPrefService), eq("lang"), eq("NEW VOICE ID")); + + // Playback is stopped. + verify(mPlayback).release(); + doReturn(List.of(newVoice)).when(mPlaybackHooks).getVoicesFor(anyString()); + + // Playback starts again with new voice and original paragraph index. + verify(mPlaybackHooks, times(1)) + .createPlayback(mPlaybackArgsCaptor.capture(), mPlaybackCallbackCaptor.capture()); + gotVoices = mPlaybackArgsCaptor.getValue().getVoices(); + assertEquals(1, gotVoices.size()); + assertEquals("NEW VOICE ID", gotVoices.get(0).getVoiceId()); + + onPlaybackSuccess(mPlayback); + verify(mPlayback, times(2)).play(); + verify(mPlayback).seekToParagraph(eq(99), eq(0L)); + } + + @Test + public void testSetVoiceWhilePaused() { + // Play tab. + requestAndStartPlayback(); + verify(mPlayback).addListener(mPlaybackListenerCaptor.capture()); + + resetPlaybackMocks(); + + // Pause at paragraph 99. + var data = Mockito.mock(PlaybackListener.PlaybackData.class); + doReturn(PlaybackListener.State.PAUSED).when(data).state(); + doReturn(99).when(data).paragraphIndex(); + mPlaybackListenerCaptor.getValue().onPlaybackDataChanged(data); + + // Change voice setting. + var newVoice = new PlaybackVoice("lang", "NEW VOICE ID", "description"); + doReturn(List.of(newVoice)).when(mPlaybackHooks).getVoicesFor(anyString()); + doReturn(List.of(newVoice)).when(mPlaybackHooks).getPlaybackVoiceList(any()); + mController.setVoiceOverrideAndApplyToPlayback(newVoice); + + // Tab audio should be loaded with the new voice but it should not be playing. + verify(mPlaybackHooks) + .createPlayback(mPlaybackArgsCaptor.capture(), mPlaybackCallbackCaptor.capture()); + var voices = mPlaybackArgsCaptor.getValue().getVoices(); + assertEquals(1, voices.size()); + assertEquals("NEW VOICE ID", voices.get(0).getVoiceId()); + + onPlaybackSuccess(mPlayback); + verify(mPlayback, never()).play(); + verify(mPlayback).seekToParagraph(eq(99), eq(0L)); + } + + @Test + public void testPreviewVoice_whilePlaying_success() { + // Play tab. + requestAndStartPlayback(); + + reset(mPlaybackHooks); + + // Preview a voice. + var voice = new PlaybackVoice("en", "asdf", ""); + doReturn(List.of(voice)).when(mPlaybackHooks).getVoicesFor(anyString()); + doReturn(List.of(voice)).when(mPlaybackHooks).getPlaybackVoiceList(any()); + mController.previewVoice(voice); + + // Tab playback should stop. + verify(mPlayback).release(); + reset(mPlayerCoordinator); + reset(mPlayback); + + // Preview playback requested. + verify(mPlaybackHooks) + .createPlayback(mPlaybackArgsCaptor.capture(), mPlaybackCallbackCaptor.capture()); + reset(mPlaybackHooks); + + // Check preview playback args. + PlaybackArgs args = mPlaybackArgsCaptor.getValue(); + assertNotNull(args); + assertEquals("en", args.getLanguage()); + assertNotNull(args.getVoices()); + assertEquals(1, args.getVoices().size()); + assertEquals("en", args.getVoices().get(0).getLanguage()); + assertEquals("asdf", args.getVoices().get(0).getVoiceId()); + + // Preview playback succeeds. + Playback previewPlayback = Mockito.mock(Playback.class); + onPlaybackSuccess(previewPlayback); + verify(previewPlayback).play(); + verify(previewPlayback).addListener(mPlaybackListenerCaptor.capture()); + assertNotNull(mPlaybackListenerCaptor.getValue()); + + // Preview finishes playing. + var data = Mockito.mock(PlaybackListener.PlaybackData.class); + doReturn(PlaybackListener.State.STOPPED).when(data).state(); + mPlaybackListenerCaptor.getValue().onPlaybackDataChanged(data); + + verify(previewPlayback).release(); + } + + @Test + public void testPreviewVoice_whilePlaying_failure() { + // Play tab. + requestAndStartPlayback(); + + reset(mPlaybackHooks); + + // Preview a voice. + var voice = new PlaybackVoice("en", "asdf", ""); + doReturn(List.of(voice)).when(mPlaybackHooks).getVoicesFor(anyString()); + doReturn(List.of(voice)).when(mPlaybackHooks).getPlaybackVoiceList(any()); + mController.previewVoice(voice); + + verify(mPlaybackHooks) + .createPlayback(mPlaybackArgsCaptor.capture(), mPlaybackCallbackCaptor.capture()); + reset(mPlaybackHooks); + + // Preview fails. Nothing to verify here yet. + mPlaybackCallbackCaptor.getValue().onFailure(new Throwable()); + resolvePromises(); + } + + @Test + public void testPreviewVoice_previewDuringPreview() { + // Play tab. + requestAndStartPlayback(); + + reset(mPlaybackHooks); + + // Preview a voice. + var voice = new PlaybackVoice("en", "asdf", ""); + doReturn(List.of(voice)).when(mPlaybackHooks).getPlaybackVoiceList(any()); + doReturn(List.of(voice)).when(mPlaybackHooks).getVoicesFor(anyString()); + mController.previewVoice(voice); + + verify(mPlaybackHooks).createPlayback(any(), mPlaybackCallbackCaptor.capture()); + Playback previewPlayback = Mockito.mock(Playback.class); + onPlaybackSuccess(previewPlayback); + reset(mPlaybackHooks); + + // Start another preview. + doReturn(List.of(voice)).when(mPlaybackHooks).getVoicesFor(anyString()); + mController.previewVoice(new PlaybackVoice("en", "abcd", "")); + // Preview playback should be stopped and cleaned up. + verify(previewPlayback).release(); + reset(previewPlayback); + verify(mPlaybackHooks).createPlayback(any(), mPlaybackCallbackCaptor.capture()); + reset(mPlaybackHooks); + onPlaybackSuccess(previewPlayback); + verify(previewPlayback).addListener(mPlaybackListenerCaptor.capture()); + + // Preview finishes playing. + var data = Mockito.mock(PlaybackListener.PlaybackData.class); + doReturn(PlaybackListener.State.STOPPED).when(data).state(); + mPlaybackListenerCaptor.getValue().onPlaybackDataChanged(data); + + verify(previewPlayback).release(); + } + + @Test + public void testPreviewVoice_closeVoiceMenu() { + // Set up playback and restorable state. + requestAndStartPlayback(); + verify(mPlayback).play(); + resetPlaybackMocks(); + + var data = Mockito.mock(PlaybackListener.PlaybackData.class); + doReturn(PlaybackListener.State.STOPPED).when(data).state(); + doReturn(99).when(data).paragraphIndex(); + mController.onPlaybackDataChanged(data); + + // Preview a voice. + var voice = new PlaybackVoice("en", "asdf", ""); + doReturn(List.of(voice)).when(mPlaybackHooks).getPlaybackVoiceList(any()); + mController.previewVoice(voice); + + verify(mPlaybackHooks).createPlayback(any(), mPlaybackCallbackCaptor.capture()); + Playback previewPlayback = Mockito.mock(Playback.class); + onPlaybackSuccess(previewPlayback); + resetPlaybackMocks(); + + // Closing the voice menu should stop the preview. + mController.onVoiceMenuClosed(); + verify(previewPlayback).release(); + + // Tab audio should be loaded and played. Position should be restored. + verify(mPlaybackHooks) + .createPlayback(mPlaybackArgsCaptor.capture(), mPlaybackCallbackCaptor.capture()); + assertEquals(1234567123456L, mPlaybackArgsCaptor.getValue().getDateModifiedMsSinceEpoch()); + onPlaybackSuccess(mPlayback); + // Don't play, because original state was STOPPED. + verify(mPlayback, never()).play(); + verify(mPlayback).seekToParagraph(eq(99), eq(0L)); + } + + @Test + public void testRestorePlaybackState_whileLoading() { + // Request playback but don't succeed yet. + mController.playTab(mTab, ReadAloudController.Entrypoint.MAGIC_TOOLBAR); + resolvePromises(); + verify(mPlaybackHooks).createPlayback(any(), mPlaybackCallbackCaptor.capture()); + resetPlaybackMocks(); + + // User changes voices before the first playback is ready. + mController.setVoiceOverrideAndApplyToPlayback(new PlaybackVoice("en", "1234", "")); + // TODO(b/315028038): If changing voice during loading is possible, then we + // should instead cancel the first request and request again. + verify(mPlaybackHooks, never()).createPlayback(any(), any()); + } + + @Test + public void testRestorePlaybackState_previewThenChangeVoice() { + // When previewing a voice, tab playback should only be restored when closing + // the menu. This test makes sure it doesn't start up early when a voice is + // selected. + + // Set up playback and restorable state. + requestAndStartPlayback(); + verify(mPlayback).play(); + + resetPlaybackMocks(); + var data = Mockito.mock(PlaybackListener.PlaybackData.class); + doReturn(PlaybackListener.State.PLAYING).when(data).state(); + doReturn(99).when(data).paragraphIndex(); + mController.onPlaybackDataChanged(data); + + // Preview voice. + var voice = new PlaybackVoice("en", "asdf", ""); + doReturn(List.of(voice)).when(mPlaybackHooks).getPlaybackVoiceList(any()); + mController.previewVoice(voice); + verify(mPlaybackHooks).createPlayback(any(), mPlaybackCallbackCaptor.capture()); + resetPlaybackMocks(); + Playback previewPlayback = Mockito.mock(Playback.class); + onPlaybackSuccess(previewPlayback); + + // Select a voice. Tab shouldn't start playing. + mController.setVoiceOverrideAndApplyToPlayback(new PlaybackVoice("en", "1234", "")); + verify(mPlaybackHooks, never()).createPlayback(any(), any()); + + // Close the menu. Now the tab should resume playback. + mController.onVoiceMenuClosed(); + verify(mPlaybackHooks).createPlayback(any(), mPlaybackCallbackCaptor.capture()); + onPlaybackSuccess(mPlayback); + verify(mPlayback).play(); + verify(mPlayback).seekToParagraph(eq(99), eq(0L)); + } + + @Test + public void testTranslationListenerRegistration() { + // Play tab. + requestAndStartPlayback(); + + // One observer should be registered on the playing tab to stop playback if translated, and + // one is registered regardless of playback for refreshing the entrypoint. + assertEquals(2, mFakeTranslateBridge.getObserverCount()); + + // stopping playback should unregister the listener that stops playback + mController.maybeStopPlayback( + mTab, ReadAloudMetrics.ReasonForStoppingPlayback.NEW_PLAYBACK_REQUEST); + assertEquals(1, mFakeTranslateBridge.getObserverCount()); + } + + @Test + public void testTranslationListenerRegistration_nullWebContents() { + assertEquals(1, mFakeTranslateBridge.getObserverCount()); + + // Play tab. + when(mTab.getWebContents()).thenReturn(null); + requestAndStartPlayback(); + + assertEquals(1, mFakeTranslateBridge.getObserverCount()); + + mController.maybeStopPlayback( + mTab, ReadAloudMetrics.ReasonForStoppingPlayback.NEW_PLAYBACK_REQUEST); + assertEquals(1, mFakeTranslateBridge.getObserverCount()); + } + + @Test + public void testTranslationListenersUnregisteredOnTabDestroyed() { + // Play tab. + requestAndStartPlayback(); + // One observer should be registered on the playing tab to stop playback if translated, and + // one is registered regardless of playback for refreshing the entrypoint. + assertEquals(2, mFakeTranslateBridge.getObserverCount()); + + // Both should be removed if the tab is destroyed. + mController.getTabModelTabObserverforTests().onDestroyed(mTab); + assertEquals(0, mFakeTranslateBridge.getObserverCount()); + } + + @Test + public void testTranslationListenersUnregisteredBeforeWebContentsSwap() { + // Listener should be registered already because onTabSelected() is called when + // TabModelTabObserver is created. + assertEquals(1, mFakeTranslateBridge.getObserverCount()); + + mController.getTabModelTabObserverforTests().webContentsWillSwap(mTab); + assertEquals(0, mFakeTranslateBridge.getObserverCount()); + } + + @Test + public void testTranslationListenerRegisteredOnPageLoad() { + // Listener should be registered already because onTabSelected() is called when + // TabModelTabObserver is created. + assertEquals(1, mFakeTranslateBridge.getObserverCount()); + + // Destroying tab should remove the observer. + mController.getTabModelTabObserverforTests().onDestroyed(mTab); + assertEquals(0, mFakeTranslateBridge.getObserverCount()); + + // Do not register in onPageLoadStarted()! + mController.getTabModelTabObserverforTests().onPageLoadStarted(mTab, sTestGURL); + assertEquals(0, mFakeTranslateBridge.getObserverCount()); + + // Instead register in onContentChanged(). + mController.getTabModelTabObserverforTests().onContentChanged(mTab); + assertEquals(1, mFakeTranslateBridge.getObserverCount()); + } + + @Test + public void testTranslationListenersUnregistered_nullWebContents() { + assertEquals(1, mFakeTranslateBridge.getObserverCount()); + + // If tab has null web contents, we should still remove the observer from whatever + // WebContents it was added to. + doReturn(null).when(mTab).getWebContents(); + mController.getTabModelTabObserverforTests().onDestroyed(mTab); + assertEquals(0, mFakeTranslateBridge.getObserverCount()); + } + + @Test + public void testTranslationListener_tabWebContentsChanged() { + // An observer is added during ReadAloudController creation through onTabSelected(). + assertEquals(1, mFakeTranslateBridge.getObserverCount(mWebContents)); + + // Simulate WebContents changing. + WebContents otherWebContents = Mockito.mock(WebContents.class); + mTab.setWebContentsOverrideForTesting(otherWebContents); + mController.getTabModelTabObserverforTests().onContentChanged(mTab); + + // Observer should have been removed from old WebContents and added to the new one. + assertEquals(0, mFakeTranslateBridge.getObserverCount(mWebContents)); + assertEquals(1, mFakeTranslateBridge.getObserverCount(otherWebContents)); + } + + @Test + public void testTranslationListener_unsupportedURLTabSelected() { + // An observer is added during ReadAloudController creation through onTabSelected(). + assertEquals(1, mFakeTranslateBridge.getObserverCount(mWebContents)); + + // Select a different tab with an invalid URL. + WebContents otherWebContents = Mockito.mock(WebContents.class); + MockTab tab = mTabModelSelector.addMockTab(); + tab.setWebContentsOverrideForTesting(otherWebContents); + tab.setUrl(new GURL("")); + mController.getTabModelTabObserverforTests().onTabSelected(tab); + + // The observer should have been removed from the original WebContents. No need to observe + // translation on the new tab since it's not readable: the observer will be added on + // onContentChanged() if the user navigates to a readable page. + assertEquals(0, mFakeTranslateBridge.getObserverCount(mWebContents)); + assertEquals(0, mFakeTranslateBridge.getObserverCount(otherWebContents)); + } + + @Test + public void testTranslationListener_playingTabWebContentsChanged() { + // An observer is added during ReadAloudController creation through onTabSelected(). + assertEquals(1, mFakeTranslateBridge.getObserverCount(mWebContents)); + + // Play tab. + requestAndStartPlayback(); + assertEquals(2, mFakeTranslateBridge.getObserverCount(mWebContents)); + + // Switching WebContents of playing tab should remove the "playing tab" translation observer + // and the "current tab" translation observer since mTab was also the currently selected + // tab. + WebContents otherWebContents = Mockito.mock(WebContents.class); + mTab.setWebContentsOverrideForTesting(otherWebContents); + mController.getTabModelTabObserverforTests().onContentChanged(mTab); + assertEquals(0, mFakeTranslateBridge.getObserverCount(mWebContents)); + } + + @Test + public void testTranslationListener_onTabSelected() { + // An observer is added during ReadAloudController creation through onTabSelected(). + assertEquals(1, mFakeTranslateBridge.getObserverCount(mWebContents)); + + // Select a different tab with a valid URL. + WebContents otherWebContents = Mockito.mock(WebContents.class); + MockTab tab = mTabModelSelector.addMockTab(); + tab.setWebContentsOverrideForTesting(otherWebContents); + tab.setUrl(new GURL("https://some.cool.website/")); + mController.getTabModelTabObserverforTests().onTabSelected(tab); + + // The observer should have been removed from the original WebContents and the new tab's + // WebContents should be observed. + assertEquals(0, mFakeTranslateBridge.getObserverCount(mWebContents)); + assertEquals(1, mFakeTranslateBridge.getObserverCount(otherWebContents)); + } + + @Test + public void testTranslationListenersRemovedWhenControllerDestroyed() { + // An observer is added during ReadAloudController creation through onTabSelected(). + assertEquals(1, mFakeTranslateBridge.getObserverCount(mWebContents)); + + // Play tab. + requestAndStartPlayback(); + assertEquals(2, mFakeTranslateBridge.getObserverCount(mWebContents)); + + mController.destroy(); + assertEquals(0, mFakeTranslateBridge.getObserverCount(mWebContents)); + } + + @Test + public void testIsPageTranslated_nullWebContent() { + mFakeTranslateBridge.setIsPageTranslated(true); + + when(mTab.getWebContents()).thenReturn(null); + assertFalse(mController.isTranslated(mTab)); + } + + @Test + public void testIsPageTranslated() { + + mFakeTranslateBridge.setIsPageTranslated(true); + assertTrue(mController.isTranslated(mTab)); + } + + @Test + public void testIsTranslatedChangedStopsPlayback() { + // Play tab. + requestAndStartPlayback(); + + // Trigger isTranslated state changed. Playback should stop. + mController.getTranslationObserverForTest().onIsPageTranslatedChanged(mTab.getWebContents()); + verify(mPlayback).release(); + } + + @Test + public void testSuccessfulTranslationStopsPlayback() { + // Play tab. + requestAndStartPlayback(); + + // Finish translating (status code 0 means "no error"). Playback should stop. + mController.getTranslationObserverForTest().onPageTranslated("en", "es", 0); + verify(mPlayback).release(); + } + + @Test + public void testFailedTranslationDoesNotStopPlayback() { + // Play tab. + requestAndStartPlayback(); + + // Fail to translate (status code 1). Playback should not stop. + mController.getTranslationObserverForTest().onPageTranslated("en", "es", 1); + verify(mPlayback, never()).release(); + } + + @Test + public void testPageTranslatedNotifiesReadabilityChanged() { + Runnable runnable = Mockito.mock(Runnable.class); + mController.addReadabilityUpdateListener(runnable); + + var translationObserver = mController.getCurrentTabTranslationObserverForTest(); + translationObserver.onPageTranslated("en", "es", 1); + verify(runnable, times(1)).run(); + + translationObserver.onIsPageTranslatedChanged(null); + verify(runnable, times(2)).run(); + } + + @Test + public void testStoppingAnyPlayback() { + // Play tab. + requestAndStartPlayback(); + verify(mPlayback).play(); + + // request to stop any playback + mController.maybeStopPlayback( + null, ReadAloudMetrics.ReasonForStoppingPlayback.NEW_PLAYBACK_REQUEST); + verify(mPlayback).release(); + verify(mPlayerCoordinator).dismissPlayers(); + } + + @Test + public void testIsHighlightingSupported_noPlayback() { + mFakeTranslateBridge.setIsPageTranslated(false); + + assertFalse(mController.isHighlightingSupported(PlaybackMode.UNSPECIFIED)); + } + + @Test + public void testIsHighlightingSupported_pageTranslated() { + mFakeTranslateBridge.setIsPageTranslated(true); + mController.setTimepointsSupportedForTest(mTab.getUrl().getSpec(), true); + mController.playTab(mTab, ReadAloudController.Entrypoint.MAGIC_TOOLBAR); + resolvePromises(); + + assertFalse(mController.isHighlightingSupported(PlaybackMode.UNSPECIFIED)); + } + + @Test + public void testIsHighlightingSupported_notSupported() { + mFakeTranslateBridge.setIsPageTranslated(false); + mController.setTimepointsSupportedForTest(mTab.getUrl().getSpec(), false); + mController.playTab(mTab, ReadAloudController.Entrypoint.MAGIC_TOOLBAR); + resolvePromises(); + + assertFalse(mController.isHighlightingSupported(PlaybackMode.UNSPECIFIED)); + } + + @Test + public void testIsHighlightingSupported_supported() { + mFakeTranslateBridge.setIsPageTranslated(false); + mController.setTimepointsSupportedForTest(mTab.getUrl().getSpec(), true); + mController.playTab(mTab, ReadAloudController.Entrypoint.MAGIC_TOOLBAR); + resolvePromises(); + + assertTrue(mController.isHighlightingSupported(PlaybackMode.UNSPECIFIED)); + } + + @Test + public void testReadabilitySupplier() { + String testUrl = "https://en.wikipedia.org/wiki/Google"; + + Runnable runnable = Mockito.mock(Runnable.class); + mController.addReadabilityUpdateListener(runnable); + mTab.setGurlOverrideForTesting(new GURL(testUrl)); + mController.maybeCheckReadability(mTab); + + verify(mHooksImpl, times(1)).isPageReadable(eq(testUrl), mCallbackCaptor.capture()); + + mCallbackCaptor + .getValue() + .onSuccess( + testUrl, + ImmutableMap.of( + PlaybackMode.CLASSIC, + new ReadAloudReadabilityHooks.ReadabilityResult(true, false))); + verify(runnable).run(); + } + + @Test + public void testMetricRecorded_isReadable() { + final String histogramName = ReadAloudMetrics.IS_READABLE; + + var histogram = HistogramWatcher.newSingleRecordWatcher(histogramName, true); + mController.maybeCheckReadability(mTab); + verify(mHooksImpl, times(1)).isPageReadable(eq(sTestGURL.getSpec()), mCallbackCaptor.capture()); + mCallbackCaptor + .getValue() + .onSuccess( + sTestGURL.getSpec(), + ImmutableMap.of( + PlaybackMode.CLASSIC, + new ReadAloudReadabilityHooks.ReadabilityResult(true, false))); + histogram.assertExpected(); + + histogram = HistogramWatcher.newSingleRecordWatcher(histogramName, false); + mCallbackCaptor + .getValue() + .onSuccess( + sTestGURL.getSpec(), + ImmutableMap.of( + PlaybackMode.CLASSIC, + new ReadAloudReadabilityHooks.ReadabilityResult(false, false))); + histogram.assertExpected(); + } + + @Test + public void testMetricRecorded_readabilitySuccessful() { + final String histogramName = ReadAloudMetrics.READABILITY_SUCCESS; + + var histogram = HistogramWatcher.newSingleRecordWatcher(histogramName, true); + mController.maybeCheckReadability(mTab); + verify(mHooksImpl, times(1)).isPageReadable(eq(sTestGURL.getSpec()), mCallbackCaptor.capture()); + mCallbackCaptor + .getValue() + .onSuccess( + sTestGURL.getSpec(), + ImmutableMap.of( + PlaybackMode.CLASSIC, + new ReadAloudReadabilityHooks.ReadabilityResult(true, false))); + histogram.assertExpected(); + + histogram = HistogramWatcher.newSingleRecordWatcher(histogramName, false); + mController.maybeCheckReadability(mTab); + verify(mHooksImpl, times(1)).isPageReadable(eq(sTestGURL.getSpec()), mCallbackCaptor.capture()); + mCallbackCaptor + .getValue() + .onFailure(sTestGURL.getSpec(), new Throwable("Something went wrong")); + histogram.assertExpected(); + } + + @Test + public void testMetricRecorded_serverReadability() { + final String histogramName = ReadAloudMetrics.READABILITY_SERVER_SIDE; + + var histogram = HistogramWatcher.newSingleRecordWatcher(histogramName, true); + mController.maybeCheckReadability(mTab); + verify(mHooksImpl).isPageReadable(eq(sTestGURL.getSpec()), mCallbackCaptor.capture()); + mCallbackCaptor + .getValue() + .onSuccess( + sTestGURL.getSpec(), + ImmutableMap.of( + PlaybackMode.CLASSIC, + new ReadAloudReadabilityHooks.ReadabilityResult(true, false))); + histogram.assertExpected(); + + histogram = HistogramWatcher.newSingleRecordWatcher(histogramName, false); + mCallbackCaptor + .getValue() + .onSuccess( + sTestGURL.getSpec(), + ImmutableMap.of( + PlaybackMode.CLASSIC, + new ReadAloudReadabilityHooks.ReadabilityResult(false, false))); + histogram.assertExpected(); + + // nothing should be emitted on error + histogram = HistogramWatcher.newBuilder().expectNoRecords(histogramName).build(); + mController.maybeCheckReadability(mTab); + verify(mHooksImpl, times(1)).isPageReadable(eq(sTestGURL.getSpec()), mCallbackCaptor.capture()); + mCallbackCaptor + .getValue() + .onFailure(sTestGURL.getSpec(), new Throwable("Something went wrong")); + histogram.assertExpected(); + } + + @Test + @EnableFeatures(ChromeFeatureList.READALOUD_BACKGROUND_PLAYBACK) + public void testBackgroundPlaybackContinuesWhenActivityPaused() { + // Play tab. + requestAndStartPlayback(); + // set progress + var data = Mockito.mock(PlaybackData.class); + + doReturn(2).when(data).paragraphIndex(); + doReturn(1000000L).when(data).positionInParagraphNanos(); + mController.onPlaybackDataChanged(data); + + // App is backgrounded with the screen on. Playback should continue if the flag is on. + setIsScreenOnAndUnlocked(true); + mController.onApplicationStateChange(ApplicationState.HAS_PAUSED_ACTIVITIES); + verify(mPlayback, never()).release(); + // also the screen is still on, don't notify about screen state change + verify(mPlayerCoordinator, never()).onScreenStatusChanged(anyBoolean()); + + // now turn the screen off. + setIsScreenOnAndUnlocked(false); + mController.onApplicationStateChange(ApplicationState.HAS_STOPPED_ACTIVITIES); + verify(mPlayback, never()).release(); + // also the screen is still on, don't notify about screen state change + verify(mPlayerCoordinator).onScreenStatusChanged(true); + } + + @Test + @EnableFeatures(ChromeFeatureList.READALOUD_BACKGROUND_PLAYBACK) + public void testBackgroundPlayback_doesntCrashWhenNoPlayer() throws NullPointerException { + setIsScreenOnAndUnlocked(false); + mController.onApplicationStateChange(ApplicationState.HAS_STOPPED_ACTIVITIES); + } + + @Test + public void testPlaybackStopsAndStateSavedWhenAppBackgrounded_screenOn() { + // Play tab. + requestAndStartPlayback(); + // set progress + var data = Mockito.mock(PlaybackData.class); + + doReturn(2).when(data).paragraphIndex(); + doReturn(1000000L).when(data).positionInParagraphNanos(); + mController.onPlaybackDataChanged(data); + + // App is backgrounded with the screen on. Make sure playback stops. + setIsScreenOnAndUnlocked(true); + mController.onApplicationStateChange(ApplicationState.HAS_STOPPED_ACTIVITIES); + verify(mPlayback).release(); + reset(mPlayback); + when(mPlayback.getMetadata()).thenReturn(mMetadata); + + // Activity goes back in foreground. Restore progress. + mController.onActivityStateChange(mActivity, ActivityState.RESUMED); + verify(mPlaybackHooks, times(2)).createPlayback(any(), mPlaybackCallbackCaptor.capture()); + onPlaybackSuccess(mPlayback); + verify(mPlayback).seekToParagraph(2, 1000000L); + verify(mPlayback, never()).play(); + + // once saved state is restored, it's cleared and no further interactions with playback + // should happen. + resetPlaybackMocks(); + + mController.onApplicationStateChange(ApplicationState.HAS_PAUSED_ACTIVITIES); + mController.onApplicationStateChange(ApplicationState.HAS_RUNNING_ACTIVITIES); + verifyNoInteractions(mPlaybackHooks); + verifyNoInteractions(mPlayback); + } + + @Test + public void testPlaybackWhenAppStops_screenOff() { + // Play tab. + requestAndStartPlayback(); + // set progress + var data = Mockito.mock(PlaybackData.class); + + doReturn(2).when(data).paragraphIndex(); + doReturn(1000000L).when(data).positionInParagraphNanos(); + mController.onPlaybackDataChanged(data); + + // App is backgrounded when the screen is off. Playback should keep playing. + setIsScreenOnAndUnlocked(false); + mController.onApplicationStateChange(ApplicationState.HAS_STOPPED_ACTIVITIES); + verify(mPlayback, never()).release(); + } + + @Test + public void testPlaybackWhenAppStops_userHint() { + // Play tab. + requestAndStartPlayback(); + // set progress + var data = Mockito.mock(PlaybackData.class); + + doReturn(2).when(data).paragraphIndex(); + doReturn(1000000L).when(data).positionInParagraphNanos(); + mController.onPlaybackDataChanged(data); + + // App is backgrounded. Screen is off but there is user hint present - stop playback + mController.onUserLeaveHint(); + setIsScreenOnAndUnlocked(false); + mController.onApplicationStateChange(ApplicationState.HAS_STOPPED_ACTIVITIES); + + verify(mPlayback).release(); + resetPlaybackMocks(); + + // App goes back in foreground. Restore progress. + mController.onActivityStateChange(mActivity, ActivityState.RESUMED); + verify(mPlaybackHooks).createPlayback(any(), mPlaybackCallbackCaptor.capture()); + onPlaybackSuccess(mPlayback); + verify(mPlayback).seekToParagraph(2, 1000000L); + verify(mPlayback, never()).play(); + } + + private void setIsScreenOnAndUnlocked(boolean isScreenOnAndUnlocked) { + DeviceConditions deviceConditions = + new DeviceConditions( + /* powerConnected= */ false, + /* batteryPercentage= */ 75, + ConnectionType.CONNECTION_UNKNOWN, + /* powerSaveOn= */ false, + /* activeNetworkMetered= */ false, + isScreenOnAndUnlocked); + ShadowDeviceConditions.setCurrentConditions(deviceConditions); + } + + @Test + public void testPlaybackResumesWhenActivityResumes() { + // Play tab. + requestAndStartPlayback(); + // set progress + var data = Mockito.mock(PlaybackData.class); + + doReturn(2).when(data).paragraphIndex(); + doReturn(1000000L).when(data).positionInParagraphNanos(); + mController.onPlaybackDataChanged(data); + + // App is backgrounded with the screen on. Make sure playback stops. + setIsScreenOnAndUnlocked(true); + mController.onApplicationStateChange(ApplicationState.HAS_STOPPED_ACTIVITIES); + verify(mPlayback).release(); + resetPlaybackMocks(); + + // App returns to foreground, but activity hasn't resumed yet. + mController.onApplicationStateChange(ApplicationState.HAS_RUNNING_ACTIVITIES); + verify(mPlaybackHooks, never()).createPlayback(any(), any()); + + // Activity goes back in foreground. Restore progress. + mController.onActivityStateChange(mActivity, ActivityState.RESUMED); + verify(mPlaybackHooks).createPlayback(any(), mPlaybackCallbackCaptor.capture()); + onPlaybackSuccess(mPlayback); + verify(mPlayback).seekToParagraph(2, 1000000L); + verify(mPlayback, never()).play(); + } + + @Test + @EnableFeatures(ChromeFeatureList.READALOUD_BACKGROUND_PLAYBACK) + public void testPlaybackResumesWhenActivityResumes_backgroundPlaybackEnabled() { + // Play tab. + requestAndStartPlayback(); + // set progress + var data = Mockito.mock(PlaybackData.class); + + doReturn(2).when(data).paragraphIndex(); + doReturn(1000000L).when(data).positionInParagraphNanos(); + mController.onPlaybackDataChanged(data); + + // App is backgrounded with the screen on. Playback should not stop. + setIsScreenOnAndUnlocked(true); + mController.onApplicationStateChange(ApplicationState.HAS_STOPPED_ACTIVITIES); + resetPlaybackMocks(); + + // Activity goes back in foreground. Nothing should be restored; playback was never stopped + // in the first place. + mController.onActivityStateChange(mActivity, ActivityState.RESUMED); + verifyNoInteractions(mPlayback); + verifyNoInteractions(mPlaybackHooks); + } + + @Test + public void testMetricRecorded_eligibility() { + final String histogramName = ReadAloudMetrics.IS_USER_ELIGIBLE; + + var histogram = HistogramWatcher.newSingleRecordWatcher(histogramName, true); + mController.onProfileAvailable(mMockProfile); + histogram.assertExpected(); + + histogram = HistogramWatcher.newSingleRecordWatcher(histogramName, false); + when(mPrefService.getBoolean("readaloud.listen_to_this_page_enabled")).thenReturn(false); + mController.onProfileAvailable(mMockProfile); + histogram.assertExpected(); + } + + @Test + public void testMetricRecorded_ineligibilityReason() { + final String histogramName = ReadAloudMetrics.INELIGIBILITY_REASON; + + var histogram = + HistogramWatcher.newSingleRecordWatcher(histogramName, IneligibilityReason.POLICY_DISABLED); + when(mPrefService.getBoolean("readaloud.listen_to_this_page_enabled")).thenReturn(false); + mController.onProfileAvailable(mMockProfile); + histogram.assertExpected(); + when(mPrefService.getBoolean("readaloud.listen_to_this_page_enabled")).thenReturn(true); + + histogram = + HistogramWatcher.newSingleRecordWatcher( + histogramName, IneligibilityReason.DEFAULT_SEARCH_ENGINE_GOOGLE_FALSE); + doReturn(SearchEngineType.SEARCH_ENGINE_OTHER) + .when(mTemplateUrlService) + .getSearchEngineTypeFromTemplateUrl(anyString()); + mController.onProfileAvailable(mMockProfile); + histogram.assertExpected(); + } + + @Test + public void testMetricRecorded_isPlaybackCreationSuccessful_True() { + final String histogramName = ReadAloudMetrics.IS_TAB_PLAYBACK_CREATION_SUCCESSFUL; + + var histogram = HistogramWatcher.newSingleRecordWatcher(histogramName, true); + requestAndStartPlayback(); + histogram.assertExpected(); + } + + @Test + public void testMetricRecorded_isPlaybackCreationSuccessful_False() { + final String histogramName = ReadAloudMetrics.IS_TAB_PLAYBACK_CREATION_SUCCESSFUL; + + var histogram = HistogramWatcher.newSingleRecordWatcher(histogramName, false); + mController.playTab(mTab, ReadAloudController.Entrypoint.MAGIC_TOOLBAR); + resolvePromises(); + verify(mPlaybackHooks).createPlayback(Mockito.any(), mPlaybackCallbackCaptor.capture()); + mPlaybackCallbackCaptor.getValue().onFailure(new Exception("Very bad error")); + resolvePromises(); + histogram.assertExpected(); + } + + @Test + public void testMetricRecorded_playbackWithoutReadabilityCheck() { + final String histogramName = ReadAloudMetrics.TAB_PLAYBACK_WITHOUT_READABILITY_CHECK_ERROR; + var histogram = + HistogramWatcher.newSingleRecordWatcher( + histogramName, ReadAloudController.Entrypoint.OVERFLOW_MENU); + + mController.playTab(mTab, ReadAloudController.Entrypoint.OVERFLOW_MENU); + histogram.assertExpected(); + } + + @Test + public void testMetricRecorded_playbackSuccess() { + final String histogramName = ReadAloudMetrics.TAB_PLAYBACK_CREATION_SUCCESS; + var histogram = + HistogramWatcher.newSingleRecordWatcher( + histogramName, ReadAloudController.Entrypoint.MAGIC_TOOLBAR); + + requestAndStartPlayback(); + + histogram.assertExpected(); + } + + @Test + public void testMetricRecorded_playbackFailure() { + final String histogramName = ReadAloudMetrics.TAB_PLAYBACK_CREATION_FAILURE; + var histogram = + HistogramWatcher.newSingleRecordWatcher( + histogramName, ReadAloudController.Entrypoint.OVERFLOW_MENU); + // Play tab to set up playbackhooks + mController.playTab(mTab, ReadAloudController.Entrypoint.OVERFLOW_MENU); + resolvePromises(); + verify(mPlaybackHooks, times(1)) + .createPlayback(Mockito.any(), mPlaybackCallbackCaptor.capture()); + mPlaybackCallbackCaptor.getValue().onFailure(new Exception("Very bad error")); + resolvePromises(); + + histogram.assertExpected(); + } + + @Test + public void testMetricNotRecorded_isPlaybackCreationSuccessful() { + final String histogramName = ReadAloudMetrics.IS_TAB_PLAYBACK_CREATION_SUCCESSFUL; + var histogram = HistogramWatcher.newBuilder().expectNoRecords(histogramName).build(); + + // Play tab to set up playbackhooks + mController.playTab(mTab, ReadAloudController.Entrypoint.MAGIC_TOOLBAR); + resolvePromises(); + + // Preview a voice. + var voice = new PlaybackVoice("en", "asdf", ""); + doReturn(List.of(voice)).when(mPlaybackHooks).getVoicesFor(anyString()); + doReturn(List.of(voice)).when(mPlaybackHooks).getPlaybackVoiceList(any()); + mController.previewVoice(voice); + + histogram.assertExpected(); + } + + @Test + public void testMetricRecorded_playbackStarted() { + final String actionName = "ReadAloud.PlaybackStarted"; + ReadAloudMetrics.recordPlaybackStarted(); + assertThat(mUserActionTester.getActions(), hasItems(actionName)); + } + + @Test + public void testMetricRecorded_highlightingEnabledOnStartup() { + mHighlightingEnabledOnStartupHistogram.assertExpected(); + } + + @Test + public void testMetricRecorded_highlightingSupported_true() { + final String histogramName = "ReadAloud.HighlightingSupported"; + var histogram = HistogramWatcher.newSingleRecordWatcher(histogramName, true); + + mController.playTab(mTab, ReadAloudController.Entrypoint.MAGIC_TOOLBAR); + resolvePromises(); + + verify(mPlaybackHooks, times(1)) + .createPlayback(Mockito.any(), mPlaybackCallbackCaptor.capture()); + + mFakeTranslateBridge.setIsPageTranslated(false); + mController.setTimepointsSupportedForTest(mTab.getUrl().getSpec(), true); + onPlaybackSuccess(mPlayback); + + histogram.assertExpected(); + } + + @Test + public void testMetricRecorded_highlightingSupported_false() { + final String histogramName = "ReadAloud.HighlightingSupported"; + var histogram = HistogramWatcher.newSingleRecordWatcher(histogramName, false); + + mController.playTab(mTab, ReadAloudController.Entrypoint.MAGIC_TOOLBAR); + resolvePromises(); + + verify(mPlaybackHooks, times(1)) + .createPlayback(Mockito.any(), mPlaybackCallbackCaptor.capture()); + + mFakeTranslateBridge.setIsPageTranslated(false); + mController.setTimepointsSupportedForTest(mTab.getUrl().getSpec(), false); + onPlaybackSuccess(mPlayback); + + histogram.assertExpected(); + } + + @Test + public void testNavigateToPlayingTab() { + // Play tab. + requestAndStartPlayback(); + + MockTab newTab = mTabModelSelector.addMockTab(); + mTabModelSelector + .getModel(false) + .setIndex(mTabModelSelector.getModel(false).indexOf(newTab), TabSelectionType.FROM_USER); + // check that we switched to new tab + assertEquals(mTabModelSelector.getCurrentTab(), newTab); + + // navigate + mController.navigateToPlayingTab(); + + // should switch back to original one + assertEquals(mTabModelSelector.getCurrentTab(), mTab); + + // navigate + mController.navigateToPlayingTab(); + + // should still be on the playing tab + assertEquals(mTabModelSelector.getCurrentTab(), mTab); + } + + @Test + public void testDestroy() { + // Play tab + requestAndStartPlayback(); + + // Destroy should clean up playback, UI, synthetic trials, and more + mController.destroy(); + verify(mPlayback).release(); + verify(mPlayerCoordinator).destroy(); + } + + @Test + public void testMaybeShowPlayer() { + // no playback, request is a no op + mController.maybeShowPlayer(); + + verify(mPlayerCoordinator, never()).restorePlayers(); + + requestAndStartPlayback(); + mController.maybeShowPlayer(); + + verify(mPlayerCoordinator).restorePlayers(); + } + + @Test + public void testMaybeHideMiniPlayer() { + // no playback, request is a no op + mController.maybeHidePlayer(); + + verify(mPlayerCoordinator, never()).hidePlayers(); + + requestAndStartPlayback(); + mController.maybeHidePlayer(); + + verify(mPlayerCoordinator).hidePlayers(); + } + + @Test + public void testPauseAndHideOnIncognitoTabSelected() { + requestAndStartPlayback(); + + Tab tab = mTabModelSelector.addMockIncognitoTab(); + TabModelUtils.selectTabById(mTabModelSelector, tab.getId(), TabSelectionType.FROM_NEW); + + verify(mPlayback).pause(); + verify(mPlayerCoordinator).hidePlayers(); + } + + @Test + public void testRestorePlayerOnReturnFromIncognitoTab() { + requestAndStartPlayback(); + reset(mPlayback); + + Tab tab = mTabModelSelector.addMockIncognitoTab(); + TabModelUtils.selectTabById(mTabModelSelector, tab.getId(), TabSelectionType.FROM_NEW); + + verify(mPlayback).pause(); + verify(mPlayerCoordinator).hidePlayers(); + + TabModelUtils.selectTabById(mTabModelSelector, mTab.getId(), TabSelectionType.FROM_USER); + verify(mPlayback, never()).play(); + verify(mPlayerCoordinator).restorePlayers(); + } + + @Test + @EnableFeatures(ChromeFeatureList.READALOUD_BACKGROUND_PLAYBACK) + public void testCrossActivityPlayback_stopBackgroundPlayback() { + // Play in Chrome, then play in CCT. Chrome playback should stop only when CCT plays. + + // Create a second instance of ReadAloudController to simulate other app's CCT. + mController2 = createController(); + + // Play in Chrome. requestAndStartPlayback() verifies playback started. + requestAndStartPlayback(); + + resetPlaybackMocks(); + + // Simulate backgrounding the activity, and the entire app. Playback should not stop. + mController.onActivityStateChange(mActivity, ActivityState.STOPPED); + mController.onApplicationStateChange(ApplicationState.HAS_STOPPED_ACTIVITIES); + verifyNoInteractions(mPlayback); + + // Play in CCT. + var histogram = + HistogramWatcher.newSingleRecordWatcher( + ReadAloudMetrics.REASON_FOR_STOPPING_PLAYBACK, + ReadAloudMetrics.ReasonForStoppingPlayback.EXTERNAL_PLAYBACK_REQUEST); + mController2.playTab(mTab, ReadAloudController.Entrypoint.MAGIC_TOOLBAR); + resolvePromises(); + + // Chrome playback should stop. Reason should be recorded as EXTERNAL_PLAYBACK_REQUEST. + verify(mPlayback).release(); + histogram.assertExpected(); + + // CCT playback should start. + verify(mPlaybackHooks, times(1)) + .createPlayback(Mockito.any(), mPlaybackCallbackCaptor.capture()); + onPlaybackSuccess(mPlayback); + verify(mPlayerCoordinator, times(1)) + .playbackReady(eq(mPlayback), eq(PlaybackListener.State.PLAYING)); + } + + @Test + @EnableFeatures(ChromeFeatureList.READALOUD_BACKGROUND_PLAYBACK) + public void testCrossActivityPlayback_canRestoreIfSameTab() { + // Play in Chrome, play in CCT, then request playback for original tab in Chrome. Playback + // should be restored. + + // Create a second instance of ReadAloudController to simulate other app's CCT. + mController2 = createController(); + + // Play in Chrome. requestAndStartPlayback() verifies playback started. + requestAndStartPlayback(); + + // Simulate some progress. + var data = Mockito.mock(PlaybackData.class); + doReturn(2).when(data).paragraphIndex(); + doReturn(1000000L).when(data).positionInParagraphNanos(); + mController.onPlaybackDataChanged(data); + + resetPlaybackMocks(); + + // Play in CCT. + mController2.playTab(mTab, ReadAloudController.Entrypoint.MAGIC_TOOLBAR); + resolvePromises(); + verify(mPlayerCoordinator).setPlayerRestorable(true); + + // Chrome playback should stop. + verify(mPlayback).release(); + + // CCT playback should start. + verify(mPlaybackHooks, times(1)) + .createPlayback(Mockito.any(), mPlaybackCallbackCaptor.capture()); + onPlaybackSuccess(mPlayback); + verify(mPlayerCoordinator, times(1)) + .playbackReady(eq(mPlayback), eq(PlaybackListener.State.PLAYING)); + + resetPlaybackMocks(); + + // Return to Chrome. CCT playback should not stop. + mController.onActivityStateChange(mActivity, ActivityState.RESUMED); + verifyNoInteractions(mPlayback); + + // Tap an entrypoint on the same tab in Chrome. CCT should stop playing and playback should + // be restored (paused). + mController.playTab(mTab, ReadAloudController.Entrypoint.MAGIC_TOOLBAR); + // Release CCT playback + verify(mPlayback).release(); + // Simulate successful playback creation. + verify(mPlaybackHooks).createPlayback(any(), mPlaybackCallbackCaptor.capture()); + onPlaybackSuccess(mPlayback); + // Progress should be restored and play should not have been called. + verify(mPlayback).seekToParagraph(2, 1000000L); + verify(mPlayback, never()).play(); + } + + @Test + public void testRestorePlayback() { + // Play in Chrome, play in CCT, then request to restore playback for original Chrome. + // Playback + // should be restored. + + // Create a second instance of ReadAloudController to simulate other app's CCT. + mController2 = createController(); + + // Play in Chrome. requestAndStartPlayback() verifies playback started. + requestAndStartPlayback(); + + // Simulate some progress. + var data = Mockito.mock(PlaybackData.class); + doReturn(2).when(data).paragraphIndex(); + doReturn(1000000L).when(data).positionInParagraphNanos(); + mController.onPlaybackDataChanged(data); + + resetPlaybackMocks(); + + // Play in CCT. + mController2.playTab(mTab, ReadAloudController.Entrypoint.MAGIC_TOOLBAR); + resolvePromises(); + + // Chrome playback should stop. + verify(mPlayback).release(); + + // CCT playback should start. + verify(mPlaybackHooks, times(1)) + .createPlayback(Mockito.any(), mPlaybackCallbackCaptor.capture()); + onPlaybackSuccess(mPlayback); + verify(mPlayerCoordinator, times(1)) + .playbackReady(eq(mPlayback), eq(PlaybackListener.State.PLAYING)); + + resetPlaybackMocks(); + + // Return to Chrome. CCT playback should not stop. + mController.onActivityStateChange(mActivity, ActivityState.RESUMED); + verifyNoInteractions(mPlayback); + + // Request to restore playback. Called by the PlayerMediator when the play pause button is + // clicked on a UI without playback. + mController.restorePlayback(); + // Simulate successful playback creation. + verify(mPlaybackHooks).createPlayback(any(), mPlaybackCallbackCaptor.capture()); + onPlaybackSuccess(mPlayback); + // Progress should be restored and play should not have been called. + verify(mPlayback).seekToParagraph(2, 1000000L); + verify(mPlayback, never()).play(); + } + + @Test + @EnableFeatures(ChromeFeatureList.READALOUD_BACKGROUND_PLAYBACK) + public void testCrossActivityPlayback_doNotRestoreIfDifferentTab() { + // Play in Chrome, play in CCT, then request playback for a different tab in Chrome. A new + // playback should start and the old one should not be restored. + + // Create a second instance of ReadAloudController to simulate other app's CCT. + mController2 = createController(); + + // Play in Chrome. requestAndStartPlayback() verifies playback started. + requestAndStartPlayback(); + + // Simulate some progress. + var data = Mockito.mock(PlaybackData.class); + doReturn(2).when(data).paragraphIndex(); + doReturn(1000000L).when(data).positionInParagraphNanos(); + mController.onPlaybackDataChanged(data); + + resetPlaybackMocks(); + + // Play in CCT. + mController2.playTab(mTab, ReadAloudController.Entrypoint.MAGIC_TOOLBAR); + resolvePromises(); + + // Chrome playback should stop. + verify(mPlayback).release(); + + // CCT playback should start. + verify(mPlaybackHooks, times(1)) + .createPlayback(Mockito.any(), mPlaybackCallbackCaptor.capture()); + onPlaybackSuccess(mPlayback); + verify(mPlayerCoordinator, times(1)) + .playbackReady(eq(mPlayback), eq(PlaybackListener.State.PLAYING)); + + resetPlaybackMocks(); + + // Return to Chrome. CCT playback should not stop. + mController.onActivityStateChange(mActivity, ActivityState.RESUMED); + verifyNoInteractions(mPlayback); + + // Tap an entrypoint on a different tab in Chrome. + MockTab tab = mTabModelSelector.addMockTab(); + tab.setGurlOverrideForTesting(sTestGURL); + tab.setWebContentsOverrideForTesting(mWebContents); + mController.playTab(tab, ReadAloudController.Entrypoint.MAGIC_TOOLBAR); + resolvePromises(); + // CCT playback was released + verify(mPlayback).release(); + // Simulate successful playback creation and make sure playback starts. + verify(mPlaybackHooks).createPlayback(any(), mPlaybackCallbackCaptor.capture()); + onPlaybackSuccess(mPlayback); + verify(mPlayback).play(); + + // Make sure saved state was not restored and was instead cleared. + verify(mPlayback, never()).seekToParagraph(eq(2), eq(1000000L)); + resetPlaybackMocks(); + mController.onActivityStateChange(mActivity, ActivityState.RESUMED); + verifyNoInteractions(mPlaybackHooks); + verifyNoInteractions(mPlayback); + } + + // TODO(b/322052505): This test won't be necessary if we keep track of profile changes. + @Test + public void testNoRequestsIfProfileDestroyed() { + reset(mHooksImpl); + doReturn(false).when(mMockProfile).isNativeInitialized(); + mController = createController(); + ShadowLooper.runUiThreadTasksIncludingDelayedTasks(); + + // Check readability. + mController.maybeCheckReadability(mTab); + // No readability request should be made. + verify(mHooksImpl, never()) + .isPageReadable(any(), any(ReadAloudReadabilityHooks.ReadabilityPerModeCallback.class)); + + // Try playing the tab. + mFakeTranslateBridge.setCurrentLanguage("en"); + mTab.setGurlOverrideForTesting(new GURL("https://en.wikipedia.org/wiki/Google")); + mController.playTab(mTab, ReadAloudController.Entrypoint.MAGIC_TOOLBAR); + resolvePromises(); + // No playback request should be made. + verify(mPlaybackHooks, never()).createPlayback(any(), any()); + } + + @Test + public void testPause_notPlayingTab() { + mController.pause(); + // Not currently playing, so nothing should happen. + verify(mPlayback, never()).pause(); + } + + @Test + public void testPause_alreadyStopped() { + requestAndStartPlayback(); + var data = Mockito.mock(PlaybackListener.PlaybackData.class); + doReturn(PlaybackListener.State.STOPPED).when(data).state(); + mController.onPlaybackDataChanged(data); + + mController.pause(); + // Not currently playing, so nothing should happen. + verify(mPlayback, never()).pause(); + } + + @Test + public void testPause() { + requestAndStartPlayback(); + var data = Mockito.mock(PlaybackListener.PlaybackData.class); + doReturn(PlaybackListener.State.PLAYING).when(data).state(); + mController.onPlaybackDataChanged(data); + + mController.pause(); + verify(mPlayback).pause(); + } + + @Test + public void testMaybePauseForOutgoingIntent_pause() { + // Play. + requestAndStartPlayback(); + var data = Mockito.mock(PlaybackListener.PlaybackData.class); + doReturn(PlaybackListener.State.PLAYING).when(data).state(); + mController.onPlaybackDataChanged(data); + + // Simulate select-to-speak context menu click. Playback should pause. + Intent intent = new Intent(); + intent.setAction(Intent.ACTION_PROCESS_TEXT); + mController.maybePauseForOutgoingIntent(intent); + verify(mPlayback).pause(); + } + + @Test + public void testMaybePauseForOutgoingIntent_noPause() { + // Play. + requestAndStartPlayback(); + var data = Mockito.mock(PlaybackListener.PlaybackData.class); + doReturn(PlaybackListener.State.PLAYING).when(data).state(); + mController.onPlaybackDataChanged(data); + + // Simulate some unimportant context menu click. Playback should not pause. + Intent intent = new Intent(); + intent.setAction(Intent.ACTION_DEFINE); + mController.maybePauseForOutgoingIntent(intent); + verify(mPlayback, never()).pause(); + } + + @Test + public void testPlayTabWithDateExtraction() { + requestAndStartPlayback(); + verify(mPlayerCoordinator).addObserver(mController); + + verify(mPlaybackHooks, times(1)).createPlayback(mPlaybackArgsCaptor.capture(), any()); + + assertEquals(1234567123456L, mPlaybackArgsCaptor.getValue().getDateModifiedMsSinceEpoch()); + } + + @Test + public void testLogDateExtraction_hasDateModified() { + mFakeTranslateBridge.setCurrentLanguage("en"); + var histogram = HistogramWatcher.newSingleRecordWatcher("ReadAloud.HasDateModified", true); + requestAndStartPlayback(); + histogram.assertExpected(); + } + + @Test + public void testLogDateExtraction_noDateModified() { + mFakeTranslateBridge.setCurrentLanguage("en"); + var failedPromise = new Promise<Long>(); + when(mExtractor.getDateModified(any())).thenReturn(failedPromise); + failedPromise.reject(new Exception("")); + + var histogram = HistogramWatcher.newSingleRecordWatcher("ReadAloud.HasDateModified", false); + requestAndStartPlayback(); + histogram.assertExpected(); + } + + @Test + public void testIsPlayingCurrentTab() { + // should be false at first since currentlyPlayingTab is null + assertFalse(mController.isPlayingCurrentTab()); + // set to playing tab + requestAndStartPlayback(); + assertTrue(mController.isPlayingCurrentTab()); + // should be false after switching to a non playing tab + MockTab newTab = mTabModelSelector.addMockTab(); + mTabModelSelector + .getModel(false) + .setIndex(mTabModelSelector.getModel(false).indexOf(newTab), TabSelectionType.FROM_USER); + assertFalse(mController.isPlayingCurrentTab()); + // switch back to current tab + mTabModelSelector + .getModel(false) + .setIndex(mTabModelSelector.getModel(false).indexOf(mTab), TabSelectionType.FROM_USER); + assertTrue(mController.isPlayingCurrentTab()); + // back to null after stopping playback + mController.maybeStopPlayback( + mTab, ReadAloudMetrics.ReasonForStoppingPlayback.NEW_PLAYBACK_REQUEST); + assertFalse(mController.isPlayingCurrentTab()); + } + + @Test + @EnableFeatures(ChromeFeatureList.READALOUD_TAP_TO_SEEK) + public void testTapToSeek() { + // play tab + requestAndStartPlayback(); + verify(mPlayback).addListener(mPlaybackListenerCaptor.capture()); + // update playback data so it isn't null + var data = Mockito.mock(PlaybackListener.PlaybackData.class); + doReturn(PlaybackListener.State.PLAYING).when(data).state(); + mPlaybackListenerCaptor.getValue().onPlaybackDataChanged(data); + var histogram = HistogramWatcher.newSingleRecordWatcher(ReadAloudMetrics.TAP_TO_SEEK_TIME, 12); + when(mMetadata.fullText()) + .thenAnswer( + invocation -> { + mClock.advanceCurrentTimeMillis(12); + return "the quick brown fox jumps over the lazy dog"; + }); + PlaybackTextPart p = + new PlaybackTextPart() { + @Override + public int getOffset() { + return 0; + } + + @Override + public int getType() { + return PlaybackTextType.TEXT_TYPE_UNSPECIFIED; + } + + @Override + public int getParagraphIndex() { + return -1; + } + + @Override + public int getLength() { + return -1; + } + }; + PlaybackTextPart[] paragraphs = new PlaybackTextPart[] {p}; + when(mMetadata.paragraphs()).thenReturn(paragraphs); + mController.tapToSeek("the quick brown fox", 4, 9); + verify(mPlayback, times(1)).seekToWord(0, 4); + histogram.assertExpected(); + } + + @Test + @EnableFeatures(ChromeFeatureList.READALOUD_TAP_TO_SEEK) + public void testTapToSeek_differentTab() { + // play tab + requestAndStartPlayback(); + // switch tabs + MockTab newTab = mTabModelSelector.addMockTab(); + mTabModelSelector + .getModel(false) + .setIndex(mTabModelSelector.getModel(false).indexOf(newTab), TabSelectionType.FROM_USER); + // shouldn't seek + mController.tapToSeek("the quick brown fox", 4, 9); + verify(mPlayback, never()).seekToWord(0, 8); + } + + @Test + public void testDidFirstVisuallyNonEmptyPaint() { + GURL gurl = new GURL("https://en.wikipedia.org/wiki/Alphabet_Inc."); + when(mTab.getUrl()).thenReturn(gurl); + mController.getTabModelTabObserverforTests().didFirstVisuallyNonEmptyPaint(mTab); + ShadowLooper.runUiThreadTasksIncludingDelayedTasks(); + verify(mHooksImpl) + .isPageReadable( + eq(gurl.getPossiblyInvalidSpec()), + any(ReadAloudReadabilityHooks.ReadabilityPerModeCallback.class)); + } + + @Test + public void testOnTabSelected() { + MockTab tab = mTabModelSelector.addMockTab(); + + // should do nothing on empty url + tab.setUrl(new GURL("")); + mController.getTabModelTabObserverforTests().onTabSelected(tab); + verify(tab, never()).getUserDataHost(); + + // should get user data for actual urls + tab.setUrl(new GURL("https://en.wikipedia.org/wiki/Alphabet_Inc.")); + mController.getTabModelTabObserverforTests().onTabSelected(tab); + verify(tab, times(1)).getUserDataHost(); + } + + @Test + public void testTimepointsSupported_emptyUrl() { + // if somehow an empty url sneaks into timepoints supported + mController.setTimepointsSupportedForTest("", true); + when(mTab.getUrl()).thenReturn(new GURL("")); + // a tab with an empty url should not be supported + assertFalse(mController.timepointsSupported(mTab, PlaybackMode.CLASSIC)); + } + + @Test + public void testEmptyUrlReadability() { + // grab the callback + mController.maybeCheckReadability(mTab); + verify(mHooksImpl, times(1)).isPageReadable(eq(sTestGURL.getSpec()), mCallbackCaptor.capture()); + // if somehow an empty url sneaks into the readability maps + boolean failed = false; + try { + mCallbackCaptor + .getValue() + .onSuccess( + "", + ImmutableMap.of( + PlaybackMode.CLASSIC, + new ReadAloudReadabilityHooks.ReadabilityResult(true, true))); + } catch (AssertionError e) { + failed = true; + } + assertTrue(failed); + when(mTab.getUrl()).thenReturn(new GURL("")); + // empty urls should not be returned as readable + assertFalse(mController.isReadable(mTab)); + } + + @Test + public void testMetricRecorded_EmptyUrlPlayback() { + final String histogramName = ReadAloudMetrics.EMPTY_URL_PLAYBACK; + var histogram = + HistogramWatcher.newSingleRecordWatcher( + histogramName, ReadAloudController.Entrypoint.MAGIC_TOOLBAR); + + mFakeTranslateBridge.setCurrentLanguage("en"); + mTab.setGurlOverrideForTesting(new GURL("")); + + boolean failed = false; + try { mController.playTab(mTab, ReadAloudController.Entrypoint.MAGIC_TOOLBAR); - resolvePromises(); - - verify(mPlaybackHooks, times(1)) - .createPlayback(Mockito.any(), mPlaybackCallbackCaptor.capture()); - - reset(mPlaybackModeSelectionEnabledCallback); - - onPlaybackSuccess(mPlayback); - verify(mPlaybackHooks).createPlayback(mPlaybackArgsCaptor.capture(), any()); - assertEquals(PlaybackMode.CLASSIC, mPlaybackArgsCaptor.getValue().getPlaybackMode()); - - // We cannot assert that there's an update for FEATURE_DISABLED because that's the initial value of the observable - // and it doesn't really change. - // Instead, we assert that the state isn't enabled. - verify(mPlaybackModeSelectionEnabledCallback, never()) - .onResult(eq(PlaybackModeSelectionEnablementStatus.MODE_SELECTION_ENABLED)); + } catch (AssertionError e) { + failed = true; } + assertTrue(failed); + histogram.assertExpected(); + } - @Test - @EnableFeatures({ChromeFeatureList.READALOUD_AUDIO_OVERVIEWS}) - public void testPlaybackModeSelectionEnabledUpdated_disabledThroughLanguage() { - mFakeTranslateBridge.setIsPageTranslated(false); - mFakeTranslateBridge.setCurrentLanguage("fr"); + @Test + public void testMetricRecorded_EmptyUrlPlayback_RestoreState() { + final String histogramName = ReadAloudMetrics.EMPTY_URL_PLAYBACK; + var histogram = + HistogramWatcher.newSingleRecordWatcher( + histogramName, ReadAloudController.Entrypoint.RESTORED_PLAYBACK); - when(mPrefService.getInteger("readaloud.playback_mode")) - .thenReturn(PlaybackMode.OVERVIEW.getValue()); - String testUrl = "https://en.wikipedia.org/wiki/Google"; - mTab.setGurlOverrideForTesting(new GURL(testUrl)); - - mController.maybeCheckReadability(mTab); - verify(mHooksImpl, times(1)) - .isPageReadable( - eq("https://en.wikipedia.org/wiki/Google"), mCallbackCaptor.capture()); - mCallbackCaptor - .getValue() - .onSuccess( - testUrl, - ImmutableMap.of( - PlaybackMode.CLASSIC, - new ReadAloudReadabilityHooks.ReadabilityResult(true, false), - PlaybackMode.OVERVIEW, - new ReadAloudReadabilityHooks.ReadabilityResult(true, false))); - - ObservableSupplier<PlaybackModeSelectionEnablementStatus> observable = - mController.getPlaybackModeSelectionEnabled(); - observable.addObserver(mPlaybackModeSelectionEnabledCallback); - - mController.playTab(mTab, ReadAloudController.Entrypoint.MAGIC_TOOLBAR); - resolvePromises(); - - verify(mPlaybackHooks, times(1)) - .createPlayback(Mockito.any(), mPlaybackCallbackCaptor.capture()); - - reset(mPlaybackModeSelectionEnabledCallback); - - onPlaybackSuccess(mPlayback); - verify(mPlaybackHooks).createPlayback(mPlaybackArgsCaptor.capture(), any()); - assertEquals(PlaybackMode.CLASSIC, mPlaybackArgsCaptor.getValue().getPlaybackMode()); - - verify(mPlaybackModeSelectionEnabledCallback) - .onResult(eq(PlaybackModeSelectionEnablementStatus.MODE_SELECTION_DISABLED_AO_UNAVAILABLE)); - - verify(mPlaybackModeSelectionEnabledCallback, never()) - .onResult(eq(PlaybackModeSelectionEnablementStatus.MODE_SELECTION_ENABLED)); + mTab.setGurlOverrideForTesting(new GURL("https://en.wikipedia.org/wiki/Google")); + var data = Mockito.mock(PlaybackData.class); + ReadAloudController.RestoreState restoreState = + mController.new RestoreState(mTab, data, true, false, 0L); + mController.setStateToRestoreOnBringingToForegroundForTests(restoreState); + // for some reason the tab url goes null + mTab.setGurlOverrideForTesting(new GURL("")); + boolean failed = false; + try { + restoreState.restore(); + } catch (AssertionError e) { + failed = true; } + assertTrue(failed); + histogram.assertExpected(); + } - @Test - @EnableFeatures({ChromeFeatureList.READALOUD_AUDIO_OVERVIEWS}) - public void testPlaybackModeSelectionEnabledUpdated_disabledThroughClassicUnsupported() { - mFakeTranslateBridge.setIsPageTranslated(false); - mFakeTranslateBridge.setCurrentLanguage("en"); + @Test + public void testNoReadabilityUpdateAfterDestroy() { + Runnable readabilityObserver = Mockito.mock(Runnable.class); + mController.addReadabilityUpdateListener(readabilityObserver); - when(mPrefService.getInteger("readaloud.playback_mode")) - .thenReturn(PlaybackMode.OVERVIEW.getValue()); - String testUrl = "https://en.wikipedia.org/wiki/Google"; - mTab.setGurlOverrideForTesting(new GURL(testUrl)); + // Check readability + mController.maybeCheckReadability(mTab); + verify(mHooksImpl, times(1)).isPageReadable(eq(sTestGURL.getSpec()), mCallbackCaptor.capture()); + assertFalse(mController.isReadable(mTab)); - mController.maybeCheckReadability(mTab); - verify(mHooksImpl, times(1)) - .isPageReadable( - eq("https://en.wikipedia.org/wiki/Google"), mCallbackCaptor.capture()); - mCallbackCaptor - .getValue() - .onSuccess( - testUrl, - ImmutableMap.of( - PlaybackMode.CLASSIC, - new ReadAloudReadabilityHooks.ReadabilityResult(false, false), - PlaybackMode.OVERVIEW, - new ReadAloudReadabilityHooks.ReadabilityResult(true, false))); + // Simulate response coming back after ReadAloudController being destroyed. + mController.destroy(); + mCallbackCaptor + .getValue() + .onSuccess( + sTestGURL.getSpec(), + ImmutableMap.of( + PlaybackMode.CLASSIC, + new ReadAloudReadabilityHooks.ReadabilityResult(true, false))); - ObservableSupplier<PlaybackModeSelectionEnablementStatus> observable = - mController.getPlaybackModeSelectionEnabled(); - observable.addObserver(mPlaybackModeSelectionEnabledCallback); + verify(readabilityObserver, never()).run(); + } - mController.playTab(mTab, ReadAloudController.Entrypoint.MAGIC_TOOLBAR); - resolvePromises(); + @Test + public void testReasonForStoppingPlaybackLogged() { + final String histogramName = ReadAloudMetrics.REASON_FOR_STOPPING_PLAYBACK; + var histogram = + HistogramWatcher.newSingleRecordWatcher( + histogramName, ReadAloudMetrics.ReasonForStoppingPlayback.MANUAL_CLOSE); + requestAndStartPlayback(); - verify(mPlaybackHooks, times(1)) - .createPlayback(Mockito.any(), mPlaybackCallbackCaptor.capture()); + mController.maybeStopPlayback(mTab, ReadAloudMetrics.ReasonForStoppingPlayback.MANUAL_CLOSE); - reset(mPlaybackModeSelectionEnabledCallback); + histogram.assertExpected(); + } - onPlaybackSuccess(mPlayback); - verify(mPlaybackHooks).createPlayback(mPlaybackArgsCaptor.capture(), any()); - assertEquals(PlaybackMode.OVERVIEW, mPlaybackArgsCaptor.getValue().getPlaybackMode()); + @Test + @DisableFeatures({ChromeFeatureList.READALOUD_AUDIO_OVERVIEWS}) + public void testGetPlaybackModesForNewPlayback_audioOverviewsNotAllowed() { + assertEquals( + ImmutableList.of(PlaybackMode.CLASSIC), + mController.getPlaybackModesForNewPlayback(ALL_SUPPORTED, "en")); + } - verify(mPlaybackModeSelectionEnabledCallback) - .onResult(eq(PlaybackModeSelectionEnablementStatus.MODE_SELECTION_DISABLED_CLASSIC_UNAVAILABLE)); + @Test + @EnableFeatures({ChromeFeatureList.READALOUD_AUDIO_OVERVIEWS}) + public void testGetPlaybackModesForNewPlayback_languageUnsupportedForOverview() { + assertEquals( + ImmutableList.of(PlaybackMode.CLASSIC), + mController.getPlaybackModesForNewPlayback(ALL_SUPPORTED, "fr")); + } - verify(mPlaybackModeSelectionEnabledCallback, never()) - .onResult(eq(PlaybackModeSelectionEnablementStatus.MODE_SELECTION_ENABLED)); - } + @Test + @EnableFeatures({ChromeFeatureList.READALOUD_AUDIO_OVERVIEWS}) + public void testGetPlaybackModesForNewPlayback_overviewAndClassicWithOverviewDefault() { + assertEquals( + ImmutableList.of(PlaybackMode.OVERVIEW, PlaybackMode.CLASSIC), + mController.getPlaybackModesForNewPlayback(ALL_SUPPORTED, "en")); + } - @Test - @EnableFeatures({ChromeFeatureList.READALOUD_AUDIO_OVERVIEWS}) - public void testPlayTabInOverviewMode() { - mFakeTranslateBridge.setIsPageTranslated(false); - mFakeTranslateBridge.setCurrentLanguage("en"); - when(mPrefService.getInteger("readaloud.playback_mode")) - .thenReturn(PlaybackMode.OVERVIEW.getValue()); - String testUrl = "https://en.wikipedia.org/wiki/Google"; - mTab.setGurlOverrideForTesting(new GURL(testUrl)); + @Test + @EnableFeatures({ChromeFeatureList.READALOUD_AUDIO_OVERVIEWS}) + public void testGetPlaybackModesForNewPlayback_overviewAndClassicWithClassicPreference() { + when(mPrefService.getInteger("readaloud.playback_mode")) + .thenReturn(PlaybackMode.CLASSIC.getValue()); + assertEquals( + ImmutableList.of(PlaybackMode.CLASSIC, PlaybackMode.OVERVIEW), + mController.getPlaybackModesForNewPlayback(ALL_SUPPORTED, "en")); + } - mController.maybeCheckReadability(mTab); - verify(mHooksImpl, times(1)) - .isPageReadable( - eq("https://en.wikipedia.org/wiki/Google"), mCallbackCaptor.capture()); - mCallbackCaptor.getValue().onSuccess( - testUrl, - ImmutableMap.of( - PlaybackMode.CLASSIC, - new ReadAloudReadabilityHooks.ReadabilityResult(true, false), - PlaybackMode.OVERVIEW, - new ReadAloudReadabilityHooks.ReadabilityResult(true, false))); + @Test + @EnableFeatures({ChromeFeatureList.READALOUD_AUDIO_OVERVIEWS}) + public void testGetPlaybackModesForNewPlayback_classicPreferenceButUnsupported() { + when(mPrefService.getInteger("readaloud.playback_mode")) + .thenReturn(PlaybackMode.CLASSIC.getValue()); + assertEquals( + ImmutableList.of(PlaybackMode.OVERVIEW), + mController.getPlaybackModesForNewPlayback(OVERVIEW_ONLY_SUPPORTED, "en")); + } - mController.playTab(mTab, ReadAloudController.Entrypoint.MAGIC_TOOLBAR); - resolvePromises(); + private void requestAndStartPlayback() { + mFakeTranslateBridge.setCurrentLanguage("en"); + mTab.setGurlOverrideForTesting(new GURL("https://en.wikipedia.org/wiki/Google")); + mController.playTab(mTab, ReadAloudController.Entrypoint.MAGIC_TOOLBAR); + resolvePromises(); - verify(mPlaybackHooks).createPlayback(mPlaybackArgsCaptor.capture(), any()); - assertEquals(PlaybackMode.OVERVIEW, mPlaybackArgsCaptor.getValue().getPlaybackMode()); - } + verify(mPlaybackHooks, times(1)) + .createPlayback(Mockito.any(), mPlaybackCallbackCaptor.capture()); - @Test - @EnableFeatures({ChromeFeatureList.READALOUD_AUDIO_OVERVIEWS}) - public void testNonEnglishTabClassicIsUsed() { - mFakeTranslateBridge.setIsPageTranslated(false); - mFakeTranslateBridge.setCurrentLanguage("fr"); - when(mPrefService.getInteger("readaloud.playback_mode")) - .thenReturn(PlaybackMode.OVERVIEW.getValue()); - String testUrl = "https://en.wikipedia.org/wiki/Google"; - mTab.setGurlOverrideForTesting(new GURL(testUrl)); + onPlaybackSuccess(mPlayback); + verify(mPlayerCoordinator, times(1)) + .playbackReady(eq(mPlayback), eq(PlaybackListener.State.PLAYING)); + } - mController.maybeCheckReadability(mTab); - verify(mHooksImpl, times(1)) - .isPageReadable( - eq("https://en.wikipedia.org/wiki/Google"), mCallbackCaptor.capture()); - mCallbackCaptor.getValue().onSuccess( - testUrl, - ImmutableMap.of( - PlaybackMode.CLASSIC, - new ReadAloudReadabilityHooks.ReadabilityResult(true, false), - PlaybackMode.OVERVIEW, - new ReadAloudReadabilityHooks.ReadabilityResult(true, false))); + private void onPlaybackSuccess(Playback playback) { + mPlaybackCallbackCaptor.getValue().onSuccess(playback); + resolvePromises(); + } - mController.playTab(mTab, ReadAloudController.Entrypoint.MAGIC_TOOLBAR); - resolvePromises(); - - verify(mPlaybackHooks).createPlayback(mPlaybackArgsCaptor.capture(), any()); - assertEquals(PlaybackMode.CLASSIC, mPlaybackArgsCaptor.getValue().getPlaybackMode()); - } - - @Test - @EnableFeatures({ChromeFeatureList.READALOUD_AUDIO_OVERVIEWS}) - public void testPreferenceUnspecifiedOverviewsDefault() { - mFakeTranslateBridge.setIsPageTranslated(false); - mFakeTranslateBridge.setCurrentLanguage("en"); - when(mPrefService.getInteger("readaloud.playback_mode")) - .thenReturn(PlaybackMode.UNSPECIFIED.getValue()); - String testUrl = "https://en.wikipedia.org/wiki/Google"; - mTab.setGurlOverrideForTesting(new GURL(testUrl)); - - mController.maybeCheckReadability(mTab); - verify(mHooksImpl, times(1)) - .isPageReadable( - eq("https://en.wikipedia.org/wiki/Google"), mCallbackCaptor.capture()); - mCallbackCaptor.getValue().onSuccess( - testUrl, - ImmutableMap.of( - PlaybackMode.CLASSIC, - new ReadAloudReadabilityHooks.ReadabilityResult(true, false), - PlaybackMode.OVERVIEW, - new ReadAloudReadabilityHooks.ReadabilityResult(true, false))); - - mController.playTab(mTab, ReadAloudController.Entrypoint.MAGIC_TOOLBAR); - resolvePromises(); - - verify(mPlaybackHooks).createPlayback(mPlaybackArgsCaptor.capture(), any()); - assertEquals(PlaybackMode.OVERVIEW, mPlaybackArgsCaptor.getValue().getPlaybackMode()); - } - - @Test - @EnableFeatures({ChromeFeatureList.READALOUD_AUDIO_OVERVIEWS}) - public void testOverviewsUnreadableFallsbackToClassic() { - mFakeTranslateBridge.setIsPageTranslated(false); - mFakeTranslateBridge.setCurrentLanguage("en"); - when(mPrefService.getInteger("readaloud.playback_mode")) - .thenReturn(PlaybackMode.OVERVIEW.getValue()); - String testUrl = "https://en.wikipedia.org/wiki/Google"; - mTab.setGurlOverrideForTesting(new GURL(testUrl)); - - mController.maybeCheckReadability(mTab); - verify(mHooksImpl, times(1)) - .isPageReadable( - eq("https://en.wikipedia.org/wiki/Google"), mCallbackCaptor.capture()); - mCallbackCaptor.getValue().onSuccess( - testUrl, - ImmutableMap.of( - PlaybackMode.CLASSIC, - new ReadAloudReadabilityHooks.ReadabilityResult(true, false), - PlaybackMode.OVERVIEW, - new ReadAloudReadabilityHooks.ReadabilityResult(false, false))); - - mController.playTab(mTab, ReadAloudController.Entrypoint.MAGIC_TOOLBAR); - resolvePromises(); - - verify(mPlaybackHooks).createPlayback(mPlaybackArgsCaptor.capture(), any()); - assertEquals(PlaybackMode.CLASSIC, mPlaybackArgsCaptor.getValue().getPlaybackMode()); - } - - @Test - public void testVoicesMatchLanguage_pageTranslated() { - // translated page should use chrome language - var voiceEn = new PlaybackVoice("en", "asdf", ""); - var voiceFr = new PlaybackVoice("fr", "asdf", ""); - when(mMetadata.languageCode()).thenReturn("en"); - doReturn(List.of(voiceEn)).when(mPlaybackHooks).getVoicesFor(eq("en")); - doReturn(List.of(voiceFr)).when(mPlaybackHooks).getVoicesFor(eq("fr")); - doReturn(List.of(voiceEn, voiceFr)).when(mPlaybackHooks).getPlaybackVoiceList(any()); - mFakeTranslateBridge.setIsPageTranslated(true); - mFakeTranslateBridge.setCurrentLanguage("fr"); - mTab.setGurlOverrideForTesting(new GURL("https://en.wikipedia.org/wiki/Google")); - - mController.playTab(mTab, ReadAloudController.Entrypoint.MAGIC_TOOLBAR); - resolvePromises(); - - verify(mPlaybackHooks) - .createPlayback(mPlaybackArgsCaptor.capture(), mPlaybackCallbackCaptor.capture()); - assertEquals("fr", mPlaybackArgsCaptor.getValue().getLanguage()); - onPlaybackSuccess(mPlayback); - // Page is in French, voice options should have voices for "fr" - assertEquals( - "fr", mController.getCurrentLanguageVoicesSupplier().get().get(0).getLanguage()); - } - - @Test - public void testVoicesMatchLanguage_pageNotTranslated() { - // non translated page should use server detected content language - var voiceEn = new PlaybackVoice("en", "asdf", ""); - var voiceFr = new PlaybackVoice("fr", "asdf", ""); - doReturn(List.of(voiceEn)).when(mPlaybackHooks).getVoicesFor(eq("en")); - doReturn(List.of(voiceFr)).when(mPlaybackHooks).getVoicesFor(eq("fr")); - doReturn(List.of(voiceEn, voiceFr)).when(mPlaybackHooks).getPlaybackVoiceList(any()); - when(mMetadata.languageCode()).thenReturn("en"); - mFakeTranslateBridge.setIsPageTranslated(false); - mFakeTranslateBridge.setCurrentLanguage("fr"); - mTab.setGurlOverrideForTesting(new GURL("https://en.wikipedia.org/wiki/Google")); - - mController.playTab(mTab, ReadAloudController.Entrypoint.MAGIC_TOOLBAR); - resolvePromises(); - - verify(mPlaybackHooks) - .createPlayback(mPlaybackArgsCaptor.capture(), mPlaybackCallbackCaptor.capture()); - assertEquals(null, mPlaybackArgsCaptor.getValue().getLanguage()); - onPlaybackSuccess(mPlayback); - - assertEquals( - "en", mController.getCurrentLanguageVoicesSupplier().get().get(0).getLanguage()); - } - - @Test - public void testFailureIfServerLanguageUnsupported() { - // non translated page should use server detected content language - var voiceEn = new PlaybackVoice("en", "asdf", ""); - var voiceFr = new PlaybackVoice("fr", "asdf", ""); - doReturn(List.of(voiceEn)).when(mPlaybackHooks).getVoicesFor(eq("en")); - doReturn(List.of(voiceFr)).when(mPlaybackHooks).getVoicesFor(eq("fr")); - doReturn(List.of(voiceEn, voiceFr)).when(mPlaybackHooks).getPlaybackVoiceList(any()); - // unsupported - when(mMetadata.languageCode()).thenReturn("pl"); - mFakeTranslateBridge.setIsPageTranslated(false); - mFakeTranslateBridge.setCurrentLanguage("fr"); - mTab.setGurlOverrideForTesting(new GURL("https://en.wikipedia.org/wiki/Google")); - - mController.playTab(mTab, ReadAloudController.Entrypoint.MAGIC_TOOLBAR); - resolvePromises(); - - verify(mPlaybackHooks) - .createPlayback(mPlaybackArgsCaptor.capture(), mPlaybackCallbackCaptor.capture()); - assertEquals(null, mPlaybackArgsCaptor.getValue().getLanguage()); - onPlaybackSuccess(mPlayback); - - verify(mPlayerCoordinator).playbackFailed(); - } - - @Test - public void testPlayTab_onFailure() { - mFakeTranslateBridge.setCurrentLanguage("en"); - GURL gurl = new GURL("https://en.wikipedia.org/wiki/Google"); - mTab.setGurlOverrideForTesting(gurl); - mController.maybeCheckReadability(mTab); - // also check that a generic error doesn't invalidate readability result - verify(mHooksImpl).isPageReadable(eq(gurl.getSpec()), mCallbackCaptor.capture()); - mCallbackCaptor - .getValue() - .onSuccess( - gurl.getSpec(), ImmutableMap.of(PlaybackMode.CLASSIC, new ReadAloudReadabilityHooks.ReadabilityResult(true, false))); - mController.playTab(mTab, ReadAloudController.Entrypoint.MAGIC_TOOLBAR); - resolvePromises(); - - assertTrue(mController.isReadable(mTab)); - verify(mPlaybackHooks, times(1)) - .createPlayback(Mockito.any(), mPlaybackCallbackCaptor.capture()); - mPlaybackCallbackCaptor.getValue().onFailure(new Throwable()); - resolvePromises(); - verify(mPlayerCoordinator, times(1)).playbackFailed(); - assertTrue(mController.isReadable(mTab)); - } - - @Test - public void testPlayTab_onFailure_unsupportedLink() { - mFakeTranslateBridge.setCurrentLanguage("en"); - GURL gurl = new GURL("https://en.wikipedia.org/wiki/Google"); - mTab.setGurlOverrideForTesting(gurl); - mController.maybeCheckReadability(mTab); - // also check that a readAloudUnsupported error does invalidate a false positive readability - // result - verify(mHooksImpl).isPageReadable(eq(gurl.getSpec()), mCallbackCaptor.capture()); - mCallbackCaptor - .getValue() - .onSuccess( - gurl.getSpec(), ImmutableMap.of(PlaybackMode.CLASSIC, new ReadAloudReadabilityHooks.ReadabilityResult(true, false))); - - assertTrue(mController.isReadable(mTab)); - mController.playTab(mTab, ReadAloudController.Entrypoint.MAGIC_TOOLBAR); - resolvePromises(); - - verify(mPlaybackHooks, times(1)) - .createPlayback(Mockito.any(), mPlaybackCallbackCaptor.capture()); - mPlaybackCallbackCaptor - .getValue() - .onFailure( - new ReadAloudUnsupportedException( - "message", - /* cause= */ null, - ReadAloudUnsupportedException.RejectionReason - .UNKNOWN_REJECTION_REASON)); - resolvePromises(); - verify(mPlayerCoordinator, times(1)).playbackFailed(); - assertFalse(mController.isReadable(mTab)); - } - - @Test - public void testStopPlayback() { - // Play tab - requestAndStartPlayback(); - - // Stop playback - mController.maybeStopPlayback( - mTab, ReadAloudMetrics.ReasonForStoppingPlayback.NEW_PLAYBACK_REQUEST); - verify(mPlayback).release(); - - resetPlaybackMocks(); - - // Subsequent playTab() should play without trying to release anything. - mController.playTab(mTab, ReadAloudController.Entrypoint.MAGIC_TOOLBAR); - resolvePromises(); - verify(mPlaybackHooks).createPlayback(any(), any()); - verify(mPlayback, never()).release(); - } - - @Test - public void testStopPlaybackWhenBackPressingToNewTab() { - // Play tab - requestAndStartPlayback(); - - // now simulate back press to a new tab page (doesn't trigger new url load) - when(mTab.getUrl()).thenReturn(new GURL("chrome-native://newtab/")); - mController.getTabModelTabObserverforTests().onUrlUpdated(mTab); - - verify(mPlayback).release(); - } - - @Test - public void testDontStopPlayback() { - // Play tab - requestAndStartPlayback(); - - // now simulate a situation updateUrl was called with the same url as the one playing - - // nothing should happen - mController.getTabModelTabObserverforTests().onUrlUpdated(mTab); - verify(mPlayback, never()).release(); - - // now update url from a different, non playing tab. The active playback should be - // unaffected. - MockTab tab = mTabModelSelector.addMockTab(); - tab.setWebContentsOverrideForTesting(mWebContents); - tab.setUrl(new GURL("")); - mController.getTabModelTabObserverforTests().onUrlUpdated(tab); - verify(mPlayback, never()).release(); - } - - @Test - public void highlightsRequested() { - // set up the highlighter - mController.setTimepointsSupportedForTest(mTab.getUrl().getSpec(), true); - mController.playTab(mTab, ReadAloudController.Entrypoint.MAGIC_TOOLBAR); - resolvePromises(); - verify(mPlaybackHooks, times(1)) - .createPlayback(Mockito.any(), mPlaybackCallbackCaptor.capture()); - onPlaybackSuccess(mPlayback); - verify(mHighlighter).initializeJs(eq(mTab), eq(mMetadata), any(Highlighter.Config.class)); - // Checks that the pref is read to set up highlighter state - // hasPrefPath is called twice, once during ReadAloudPrefs.isHighlightingEnabled and during - // ReadAloudPrefs.setHighlightingEnabled - verify(mPrefService, times(2)).hasPrefPath(eq(ReadAloudPrefs.HIGHLIGHTING_ENABLED_PATH)); - - // trigger highlights - mController.onPhraseChanged(mPhraseTiming); - - verify(mHighlighter) - .highlightText(eq(mGlobalRenderFrameHostId), eq(mTab), eq(mPhraseTiming)); - - // now disable highlighting - we should not trigger highlights anymore - mController.getHighlightingEnabledSupplier().set(false); - // Pref is updated. - verify(mPrefService).setBoolean(eq(ReadAloudPrefs.HIGHLIGHTING_ENABLED_PATH), eq(false)); - mController.onPhraseChanged(mPhraseTiming); - verify(mHighlighter, times(1)) - .highlightText(eq(mGlobalRenderFrameHostId), eq(mTab), eq(mPhraseTiming)); - } - - @Test - public void reloadingTab_highlightsNotCleared() { - // set up the highlighter - mController.setTimepointsSupportedForTest(mTab.getUrl().getSpec(), true); - mController.playTab(mTab, ReadAloudController.Entrypoint.MAGIC_TOOLBAR); - resolvePromises(); - verify(mPlaybackHooks, times(1)) - .createPlayback(Mockito.any(), mPlaybackCallbackCaptor.capture()); - onPlaybackSuccess(mPlayback); - verify(mHighlighter).initializeJs(eq(mTab), eq(mMetadata), any(Highlighter.Config.class)); - - // Reload tab to a different url. - mController - .getTabModelTabObserverforTests() - .onPageLoadStarted(mTab, new GURL("http://wikipedia.org")); - - verify(mHighlighter, never()).handleTabReloaded(any()); - } - - @Test - public void stoppingPlaybackClearsHighlighter() { - // set up the highlighter - mController.setTimepointsSupportedForTest(mTab.getUrl().getSpec(), true); - mController.playTab(mTab, ReadAloudController.Entrypoint.MAGIC_TOOLBAR); - resolvePromises(); - verify(mPlaybackHooks, times(1)) - .createPlayback(Mockito.any(), mPlaybackCallbackCaptor.capture()); - onPlaybackSuccess(mPlayback); - verify(mHighlighter).initializeJs(eq(mTab), eq(mMetadata), any(Highlighter.Config.class)); - - // stopping playback should clear highlighting. - mController.maybeStopPlayback( - mTab, ReadAloudMetrics.ReasonForStoppingPlayback.NEW_PLAYBACK_REQUEST); - - verify(mHighlighter).clearHighlights(eq(mGlobalRenderFrameHostId), eq(mTab)); - } - - @Test - public void testUserDataStrippedFromReadabilityCheck() { - GURL tabUrl = new GURL("http://user:pass@example.com"); - mTab.setGurlOverrideForTesting(tabUrl); - - mController.maybeCheckReadability(mTab); - - String sanitized = "http://example.com/"; - verify(mHooksImpl, times(1)).isPageReadable(eq(sanitized), mCallbackCaptor.capture()); - assertFalse(mController.isReadable(mTab)); - - mCallbackCaptor - .getValue() - .onSuccess( - sanitized, - ImmutableMap.of( - PlaybackMode.CLASSIC, - new ReadAloudReadabilityHooks.ReadabilityResult(true, true))); - assertTrue(mController.isReadable(mTab)); - assertTrue(mController.timepointsSupported(mTab, PlaybackMode.CLASSIC)); - } - - @Test - public void testSetHighlighterMode() { - // highlighter can be null if page doesn't support highlighting, - // this just test null checkss - mController.setHighlighterMode(2); - verify(mHighlighter, never()).handleTabReloaded(mTab); - - mController.setTimepointsSupportedForTest(mTab.getUrl().getSpec(), true); - mController.playTab(mTab, ReadAloudController.Entrypoint.MAGIC_TOOLBAR); - resolvePromises(); - verify(mPlaybackHooks, times(1)) - .createPlayback(Mockito.any(), mPlaybackCallbackCaptor.capture()); - - onPlaybackSuccess(mPlayback); - - mController.setHighlighterMode(2); - verify(mHighlighter, times(1)).handleTabReloaded(mTab); - - // only do something if new mode is different - mController.setHighlighterMode(2); - verify(mHighlighter, times(1)).handleTabReloaded(mTab); - - mController.setHighlighterMode(1); - verify(mHighlighter, times(2)).handleTabReloaded(mTab); - } - - @Test - public void testSetPlaybackModeAndRestartPlayback() { - // First play tab. - mFakeTranslateBridge.setCurrentLanguage("en"); - mTab.setGurlOverrideForTesting(new GURL("https://en.wikipedia.org/wiki/Google")); - mController.playTab(mTab, ReadAloudController.Entrypoint.MAGIC_TOOLBAR); - resolvePromises(); - verify(mPlaybackHooks, times(1)) - .createPlayback(mPlaybackArgsCaptor.capture(), mPlaybackCallbackCaptor.capture()); - - onPlaybackSuccess(mPlayback); - reset(mPlaybackHooks); - - // Mock a playing playback. - var newVoice = new PlaybackVoice("lang", "NEW VOICE ID"); - doReturn(List.of(newVoice)).when(mPlaybackHooks).getPlaybackVoiceList(any()); - doReturn(List.of(newVoice)).when(mPlaybackHooks).getVoicesFor(anyString()); - var data = Mockito.mock(PlaybackData.class); - doReturn(99).when(data).paragraphIndex(); - doReturn(PlaybackListener.State.PLAYING).when(data).state(); - mController.onPlaybackDataChanged(data); - - // Set mode and restart. - mController.setPlaybackModeAndApplyToPlayback(PlaybackMode.OVERVIEW); - - verify(mTracker).notifyEvent(eq("read_aloud_playback_mode_clicked")); - - // Pref is updated. - verify(mPrefService) - .setInteger(eq("readaloud.playback_mode"), eq(PlaybackMode.OVERVIEW.getValue())); - - // Playback is stopped. - verify(mPlayback).release(); - - // Playback starts again. - verify(mPlaybackHooks, times(1)) - .createPlayback(mPlaybackArgsCaptor.capture(), mPlaybackCallbackCaptor.capture()); - assertEquals(PlaybackMode.CLASSIC, mPlaybackArgsCaptor.getValue().getPlaybackMode()); - - onPlaybackSuccess(mPlayback); - verify(mPlayback, times(2)).play(); - verify(mPlayback, never()).seekToParagraph(anyInt(), anyLong()); - } - - @Test - public void testSetVoiceAndRestartPlayback() { - // Voices setup - var oldVoice = new PlaybackVoice("lang", "OLD VOICE ID"); - doReturn(List.of(oldVoice)).when(mPlaybackHooks).getPlaybackVoiceList(any()); - - // First play tab. - mFakeTranslateBridge.setCurrentLanguage("en"); - mTab.setGurlOverrideForTesting(new GURL("https://en.wikipedia.org/wiki/Google")); - mController.playTab(mTab, ReadAloudController.Entrypoint.MAGIC_TOOLBAR); - resolvePromises(); - - // Verify the original voice list. - verify(mPlaybackHooks, times(1)) - .createPlayback(mPlaybackArgsCaptor.capture(), mPlaybackCallbackCaptor.capture()); - List<PlaybackVoice> gotVoices = mPlaybackArgsCaptor.getValue().getVoices(); - assertEquals(1, gotVoices.size()); - assertEquals("OLD VOICE ID", gotVoices.get(0).getVoiceId()); - onPlaybackSuccess(mPlayback); - - reset(mPlaybackHooks); - - // Set the new voice. - var newVoice = new PlaybackVoice("lang", "NEW VOICE ID"); - doReturn(List.of(newVoice)).when(mPlaybackHooks).getPlaybackVoiceList(any()); - doReturn(List.of(newVoice)).when(mPlaybackHooks).getVoicesFor(anyString()); - var data = Mockito.mock(PlaybackData.class); - doReturn(99).when(data).paragraphIndex(); - doReturn(PlaybackListener.State.PLAYING).when(data).state(); - mController.onPlaybackDataChanged(data); - mController.setVoiceOverrideAndApplyToPlayback(newVoice); - - // Pref is updated. - verify(mReadAloudPrefsNatives).setVoice(eq(mPrefService), eq("lang"), eq("NEW VOICE ID")); - - // Playback is stopped. - verify(mPlayback).release(); - doReturn(List.of(newVoice)).when(mPlaybackHooks).getVoicesFor(anyString()); - - // Playback starts again with new voice and original paragraph index. - verify(mPlaybackHooks, times(1)) - .createPlayback(mPlaybackArgsCaptor.capture(), mPlaybackCallbackCaptor.capture()); - gotVoices = mPlaybackArgsCaptor.getValue().getVoices(); - assertEquals(1, gotVoices.size()); - assertEquals("NEW VOICE ID", gotVoices.get(0).getVoiceId()); - - onPlaybackSuccess(mPlayback); - verify(mPlayback, times(2)).play(); - verify(mPlayback).seekToParagraph(eq(99), eq(0L)); - } - - @Test - public void testSetVoiceWhilePaused() { - // Play tab. - requestAndStartPlayback(); - verify(mPlayback).addListener(mPlaybackListenerCaptor.capture()); - - resetPlaybackMocks(); - - // Pause at paragraph 99. - var data = Mockito.mock(PlaybackListener.PlaybackData.class); - doReturn(PlaybackListener.State.PAUSED).when(data).state(); - doReturn(99).when(data).paragraphIndex(); - mPlaybackListenerCaptor.getValue().onPlaybackDataChanged(data); - - // Change voice setting. - var newVoice = new PlaybackVoice("lang", "NEW VOICE ID", "description"); - doReturn(List.of(newVoice)).when(mPlaybackHooks).getVoicesFor(anyString()); - doReturn(List.of(newVoice)).when(mPlaybackHooks).getPlaybackVoiceList(any()); - mController.setVoiceOverrideAndApplyToPlayback(newVoice); - - // Tab audio should be loaded with the new voice but it should not be playing. - verify(mPlaybackHooks) - .createPlayback(mPlaybackArgsCaptor.capture(), mPlaybackCallbackCaptor.capture()); - var voices = mPlaybackArgsCaptor.getValue().getVoices(); - assertEquals(1, voices.size()); - assertEquals("NEW VOICE ID", voices.get(0).getVoiceId()); - - onPlaybackSuccess(mPlayback); - verify(mPlayback, never()).play(); - verify(mPlayback).seekToParagraph(eq(99), eq(0L)); - } - - @Test - public void testPreviewVoice_whilePlaying_success() { - // Play tab. - requestAndStartPlayback(); - - reset(mPlaybackHooks); - - // Preview a voice. - var voice = new PlaybackVoice("en", "asdf", ""); - doReturn(List.of(voice)).when(mPlaybackHooks).getVoicesFor(anyString()); - doReturn(List.of(voice)).when(mPlaybackHooks).getPlaybackVoiceList(any()); - mController.previewVoice(voice); - - // Tab playback should stop. - verify(mPlayback).release(); - reset(mPlayerCoordinator); - reset(mPlayback); - - // Preview playback requested. - verify(mPlaybackHooks) - .createPlayback(mPlaybackArgsCaptor.capture(), mPlaybackCallbackCaptor.capture()); - reset(mPlaybackHooks); - - // Check preview playback args. - PlaybackArgs args = mPlaybackArgsCaptor.getValue(); - assertNotNull(args); - assertEquals("en", args.getLanguage()); - assertNotNull(args.getVoices()); - assertEquals(1, args.getVoices().size()); - assertEquals("en", args.getVoices().get(0).getLanguage()); - assertEquals("asdf", args.getVoices().get(0).getVoiceId()); - - // Preview playback succeeds. - Playback previewPlayback = Mockito.mock(Playback.class); - onPlaybackSuccess(previewPlayback); - verify(previewPlayback).play(); - verify(previewPlayback).addListener(mPlaybackListenerCaptor.capture()); - assertNotNull(mPlaybackListenerCaptor.getValue()); - - // Preview finishes playing. - var data = Mockito.mock(PlaybackListener.PlaybackData.class); - doReturn(PlaybackListener.State.STOPPED).when(data).state(); - mPlaybackListenerCaptor.getValue().onPlaybackDataChanged(data); - - verify(previewPlayback).release(); - } - - @Test - public void testPreviewVoice_whilePlaying_failure() { - // Play tab. - requestAndStartPlayback(); - - reset(mPlaybackHooks); - - // Preview a voice. - var voice = new PlaybackVoice("en", "asdf", ""); - doReturn(List.of(voice)).when(mPlaybackHooks).getVoicesFor(anyString()); - doReturn(List.of(voice)).when(mPlaybackHooks).getPlaybackVoiceList(any()); - mController.previewVoice(voice); - - verify(mPlaybackHooks) - .createPlayback(mPlaybackArgsCaptor.capture(), mPlaybackCallbackCaptor.capture()); - reset(mPlaybackHooks); - - // Preview fails. Nothing to verify here yet. - mPlaybackCallbackCaptor.getValue().onFailure(new Throwable()); - resolvePromises(); - } - - @Test - public void testPreviewVoice_previewDuringPreview() { - // Play tab. - requestAndStartPlayback(); - - reset(mPlaybackHooks); - - // Preview a voice. - var voice = new PlaybackVoice("en", "asdf", ""); - doReturn(List.of(voice)).when(mPlaybackHooks).getPlaybackVoiceList(any()); - doReturn(List.of(voice)).when(mPlaybackHooks).getVoicesFor(anyString()); - mController.previewVoice(voice); - - verify(mPlaybackHooks).createPlayback(any(), mPlaybackCallbackCaptor.capture()); - Playback previewPlayback = Mockito.mock(Playback.class); - onPlaybackSuccess(previewPlayback); - reset(mPlaybackHooks); - - // Start another preview. - doReturn(List.of(voice)).when(mPlaybackHooks).getVoicesFor(anyString()); - mController.previewVoice(new PlaybackVoice("en", "abcd", "")); - // Preview playback should be stopped and cleaned up. - verify(previewPlayback).release(); - reset(previewPlayback); - verify(mPlaybackHooks).createPlayback(any(), mPlaybackCallbackCaptor.capture()); - reset(mPlaybackHooks); - onPlaybackSuccess(previewPlayback); - verify(previewPlayback).addListener(mPlaybackListenerCaptor.capture()); - - // Preview finishes playing. - var data = Mockito.mock(PlaybackListener.PlaybackData.class); - doReturn(PlaybackListener.State.STOPPED).when(data).state(); - mPlaybackListenerCaptor.getValue().onPlaybackDataChanged(data); - - verify(previewPlayback).release(); - } - - @Test - public void testPreviewVoice_closeVoiceMenu() { - // Set up playback and restorable state. - requestAndStartPlayback(); - verify(mPlayback).play(); - resetPlaybackMocks(); - - var data = Mockito.mock(PlaybackListener.PlaybackData.class); - doReturn(PlaybackListener.State.STOPPED).when(data).state(); - doReturn(99).when(data).paragraphIndex(); - mController.onPlaybackDataChanged(data); - - // Preview a voice. - var voice = new PlaybackVoice("en", "asdf", ""); - doReturn(List.of(voice)).when(mPlaybackHooks).getPlaybackVoiceList(any()); - mController.previewVoice(voice); - - verify(mPlaybackHooks).createPlayback(any(), mPlaybackCallbackCaptor.capture()); - Playback previewPlayback = Mockito.mock(Playback.class); - onPlaybackSuccess(previewPlayback); - resetPlaybackMocks(); - - // Closing the voice menu should stop the preview. - mController.onVoiceMenuClosed(); - verify(previewPlayback).release(); - - // Tab audio should be loaded and played. Position should be restored. - verify(mPlaybackHooks) - .createPlayback(mPlaybackArgsCaptor.capture(), mPlaybackCallbackCaptor.capture()); - assertEquals(1234567123456L, mPlaybackArgsCaptor.getValue().getDateModifiedMsSinceEpoch()); - onPlaybackSuccess(mPlayback); - // Don't play, because original state was STOPPED. - verify(mPlayback, never()).play(); - verify(mPlayback).seekToParagraph(eq(99), eq(0L)); - } - - @Test - public void testRestorePlaybackState_whileLoading() { - // Request playback but don't succeed yet. - mController.playTab(mTab, ReadAloudController.Entrypoint.MAGIC_TOOLBAR); - resolvePromises(); - verify(mPlaybackHooks).createPlayback(any(), mPlaybackCallbackCaptor.capture()); - resetPlaybackMocks(); - - // User changes voices before the first playback is ready. - mController.setVoiceOverrideAndApplyToPlayback(new PlaybackVoice("en", "1234", "")); - // TODO(b/315028038): If changing voice during loading is possible, then we - // should instead cancel the first request and request again. - verify(mPlaybackHooks, never()).createPlayback(any(), any()); - } - - @Test - public void testRestorePlaybackState_previewThenChangeVoice() { - // When previewing a voice, tab playback should only be restored when closing - // the menu. This test makes sure it doesn't start up early when a voice is - // selected. - - // Set up playback and restorable state. - requestAndStartPlayback(); - verify(mPlayback).play(); - - resetPlaybackMocks(); - var data = Mockito.mock(PlaybackListener.PlaybackData.class); - doReturn(PlaybackListener.State.PLAYING).when(data).state(); - doReturn(99).when(data).paragraphIndex(); - mController.onPlaybackDataChanged(data); - - // Preview voice. - var voice = new PlaybackVoice("en", "asdf", ""); - doReturn(List.of(voice)).when(mPlaybackHooks).getPlaybackVoiceList(any()); - mController.previewVoice(voice); - verify(mPlaybackHooks).createPlayback(any(), mPlaybackCallbackCaptor.capture()); - resetPlaybackMocks(); - Playback previewPlayback = Mockito.mock(Playback.class); - onPlaybackSuccess(previewPlayback); - - // Select a voice. Tab shouldn't start playing. - mController.setVoiceOverrideAndApplyToPlayback(new PlaybackVoice("en", "1234", "")); - verify(mPlaybackHooks, never()).createPlayback(any(), any()); - - // Close the menu. Now the tab should resume playback. - mController.onVoiceMenuClosed(); - verify(mPlaybackHooks).createPlayback(any(), mPlaybackCallbackCaptor.capture()); - onPlaybackSuccess(mPlayback); - verify(mPlayback).play(); - verify(mPlayback).seekToParagraph(eq(99), eq(0L)); - } - - @Test - public void testTranslationListenerRegistration() { - // Play tab. - requestAndStartPlayback(); - - // One observer should be registered on the playing tab to stop playback if translated, and - // one is registered regardless of playback for refreshing the entrypoint. - assertEquals(2, mFakeTranslateBridge.getObserverCount()); - - // stopping playback should unregister the listener that stops playback - mController.maybeStopPlayback( - mTab, ReadAloudMetrics.ReasonForStoppingPlayback.NEW_PLAYBACK_REQUEST); - assertEquals(1, mFakeTranslateBridge.getObserverCount()); - } - - @Test - public void testTranslationListenerRegistration_nullWebContents() { - assertEquals(1, mFakeTranslateBridge.getObserverCount()); - - // Play tab. - when(mTab.getWebContents()).thenReturn(null); - requestAndStartPlayback(); - - assertEquals(1, mFakeTranslateBridge.getObserverCount()); - - mController.maybeStopPlayback( - mTab, ReadAloudMetrics.ReasonForStoppingPlayback.NEW_PLAYBACK_REQUEST); - assertEquals(1, mFakeTranslateBridge.getObserverCount()); - } - - @Test - public void testTranslationListenersUnregisteredOnTabDestroyed() { - // Play tab. - requestAndStartPlayback(); - // One observer should be registered on the playing tab to stop playback if translated, and - // one is registered regardless of playback for refreshing the entrypoint. - assertEquals(2, mFakeTranslateBridge.getObserverCount()); - - // Both should be removed if the tab is destroyed. - mController.getTabModelTabObserverforTests().onDestroyed(mTab); - assertEquals(0, mFakeTranslateBridge.getObserverCount()); - } - - @Test - public void testTranslationListenersUnregisteredBeforeWebContentsSwap() { - // Listener should be registered already because onTabSelected() is called when - // TabModelTabObserver is created. - assertEquals(1, mFakeTranslateBridge.getObserverCount()); - - mController.getTabModelTabObserverforTests().webContentsWillSwap(mTab); - assertEquals(0, mFakeTranslateBridge.getObserverCount()); - } - - @Test - public void testTranslationListenerRegisteredOnPageLoad() { - // Listener should be registered already because onTabSelected() is called when - // TabModelTabObserver is created. - assertEquals(1, mFakeTranslateBridge.getObserverCount()); - - // Destroying tab should remove the observer. - mController.getTabModelTabObserverforTests().onDestroyed(mTab); - assertEquals(0, mFakeTranslateBridge.getObserverCount()); - - // Do not register in onPageLoadStarted()! - mController.getTabModelTabObserverforTests().onPageLoadStarted(mTab, sTestGURL); - assertEquals(0, mFakeTranslateBridge.getObserverCount()); - - // Instead register in onContentChanged(). - mController.getTabModelTabObserverforTests().onContentChanged(mTab); - assertEquals(1, mFakeTranslateBridge.getObserverCount()); - } - - @Test - public void testTranslationListenersUnregistered_nullWebContents() { - assertEquals(1, mFakeTranslateBridge.getObserverCount()); - - // If tab has null web contents, we should still remove the observer from whatever - // WebContents it was added to. - doReturn(null).when(mTab).getWebContents(); - mController.getTabModelTabObserverforTests().onDestroyed(mTab); - assertEquals(0, mFakeTranslateBridge.getObserverCount()); - } - - @Test - public void testTranslationListener_tabWebContentsChanged() { - // An observer is added during ReadAloudController creation through onTabSelected(). - assertEquals(1, mFakeTranslateBridge.getObserverCount(mWebContents)); - - // Simulate WebContents changing. - WebContents otherWebContents = Mockito.mock(WebContents.class); - mTab.setWebContentsOverrideForTesting(otherWebContents); - mController.getTabModelTabObserverforTests().onContentChanged(mTab); - - // Observer should have been removed from old WebContents and added to the new one. - assertEquals(0, mFakeTranslateBridge.getObserverCount(mWebContents)); - assertEquals(1, mFakeTranslateBridge.getObserverCount(otherWebContents)); - } - - @Test - public void testTranslationListener_unsupportedURLTabSelected() { - // An observer is added during ReadAloudController creation through onTabSelected(). - assertEquals(1, mFakeTranslateBridge.getObserverCount(mWebContents)); - - // Select a different tab with an invalid URL. - WebContents otherWebContents = Mockito.mock(WebContents.class); - MockTab tab = mTabModelSelector.addMockTab(); - tab.setWebContentsOverrideForTesting(otherWebContents); - tab.setUrl(new GURL("")); - mController.getTabModelTabObserverforTests().onTabSelected(tab); - - // The observer should have been removed from the original WebContents. No need to observe - // translation on the new tab since it's not readable: the observer will be added on - // onContentChanged() if the user navigates to a readable page. - assertEquals(0, mFakeTranslateBridge.getObserverCount(mWebContents)); - assertEquals(0, mFakeTranslateBridge.getObserverCount(otherWebContents)); - } - - @Test - public void testTranslationListener_playingTabWebContentsChanged() { - // An observer is added during ReadAloudController creation through onTabSelected(). - assertEquals(1, mFakeTranslateBridge.getObserverCount(mWebContents)); - - // Play tab. - requestAndStartPlayback(); - assertEquals(2, mFakeTranslateBridge.getObserverCount(mWebContents)); - - // Switching WebContents of playing tab should remove the "playing tab" translation observer - // and the "current tab" translation observer since mTab was also the currently selected - // tab. - WebContents otherWebContents = Mockito.mock(WebContents.class); - mTab.setWebContentsOverrideForTesting(otherWebContents); - mController.getTabModelTabObserverforTests().onContentChanged(mTab); - assertEquals(0, mFakeTranslateBridge.getObserverCount(mWebContents)); - } - - @Test - public void testTranslationListener_onTabSelected() { - // An observer is added during ReadAloudController creation through onTabSelected(). - assertEquals(1, mFakeTranslateBridge.getObserverCount(mWebContents)); - - // Select a different tab with a valid URL. - WebContents otherWebContents = Mockito.mock(WebContents.class); - MockTab tab = mTabModelSelector.addMockTab(); - tab.setWebContentsOverrideForTesting(otherWebContents); - tab.setUrl(new GURL("https://some.cool.website/")); - mController.getTabModelTabObserverforTests().onTabSelected(tab); - - // The observer should have been removed from the original WebContents and the new tab's - // WebContents should be observed. - assertEquals(0, mFakeTranslateBridge.getObserverCount(mWebContents)); - assertEquals(1, mFakeTranslateBridge.getObserverCount(otherWebContents)); - } - - @Test - public void testTranslationListenersRemovedWhenControllerDestroyed() { - // An observer is added during ReadAloudController creation through onTabSelected(). - assertEquals(1, mFakeTranslateBridge.getObserverCount(mWebContents)); - - // Play tab. - requestAndStartPlayback(); - assertEquals(2, mFakeTranslateBridge.getObserverCount(mWebContents)); - - mController.destroy(); - assertEquals(0, mFakeTranslateBridge.getObserverCount(mWebContents)); - } - - @Test - public void testIsPageTranslated_nullWebContent() { - mFakeTranslateBridge.setIsPageTranslated(true); - - when(mTab.getWebContents()).thenReturn(null); - assertFalse(mController.isTranslated(mTab)); - } - - @Test - public void testIsPageTranslated() { - - mFakeTranslateBridge.setIsPageTranslated(true); - assertTrue(mController.isTranslated(mTab)); - } - - @Test - public void testIsTranslatedChangedStopsPlayback() { - // Play tab. - requestAndStartPlayback(); - - // Trigger isTranslated state changed. Playback should stop. - mController - .getTranslationObserverForTest() - .onIsPageTranslatedChanged(mTab.getWebContents()); - verify(mPlayback).release(); - } - - @Test - public void testSuccessfulTranslationStopsPlayback() { - // Play tab. - requestAndStartPlayback(); - - // Finish translating (status code 0 means "no error"). Playback should stop. - mController.getTranslationObserverForTest().onPageTranslated("en", "es", 0); - verify(mPlayback).release(); - } - - @Test - public void testFailedTranslationDoesNotStopPlayback() { - // Play tab. - requestAndStartPlayback(); - - // Fail to translate (status code 1). Playback should not stop. - mController.getTranslationObserverForTest().onPageTranslated("en", "es", 1); - verify(mPlayback, never()).release(); - } - - @Test - public void testPageTranslatedNotifiesReadabilityChanged() { - Runnable runnable = Mockito.mock(Runnable.class); - mController.addReadabilityUpdateListener(runnable); - - var translationObserver = mController.getCurrentTabTranslationObserverForTest(); - translationObserver.onPageTranslated("en", "es", 1); - verify(runnable, times(1)).run(); - - translationObserver.onIsPageTranslatedChanged(null); - verify(runnable, times(2)).run(); - } - - @Test - public void testStoppingAnyPlayback() { - // Play tab. - requestAndStartPlayback(); - verify(mPlayback).play(); - - // request to stop any playback - mController.maybeStopPlayback( - null, ReadAloudMetrics.ReasonForStoppingPlayback.NEW_PLAYBACK_REQUEST); - verify(mPlayback).release(); - verify(mPlayerCoordinator).dismissPlayers(); - } - - @Test - public void testIsHighlightingSupported_noPlayback() { - mFakeTranslateBridge.setIsPageTranslated(false); - - assertFalse(mController.isHighlightingSupported(PlaybackMode.UNSPECIFIED)); - } - - @Test - public void testIsHighlightingSupported_pageTranslated() { - mFakeTranslateBridge.setIsPageTranslated(true); - mController.setTimepointsSupportedForTest(mTab.getUrl().getSpec(), true); - mController.playTab(mTab, ReadAloudController.Entrypoint.MAGIC_TOOLBAR); - resolvePromises(); - - assertFalse(mController.isHighlightingSupported(PlaybackMode.UNSPECIFIED)); - } - - @Test - public void testIsHighlightingSupported_notSupported() { - mFakeTranslateBridge.setIsPageTranslated(false); - mController.setTimepointsSupportedForTest(mTab.getUrl().getSpec(), false); - mController.playTab(mTab, ReadAloudController.Entrypoint.MAGIC_TOOLBAR); - resolvePromises(); - - assertFalse(mController.isHighlightingSupported(PlaybackMode.UNSPECIFIED)); - } - - @Test - public void testIsHighlightingSupported_supported() { - mFakeTranslateBridge.setIsPageTranslated(false); - mController.setTimepointsSupportedForTest(mTab.getUrl().getSpec(), true); - mController.playTab(mTab, ReadAloudController.Entrypoint.MAGIC_TOOLBAR); - resolvePromises(); - - assertTrue(mController.isHighlightingSupported(PlaybackMode.UNSPECIFIED)); - } - - @Test - public void testReadabilitySupplier() { - String testUrl = "https://en.wikipedia.org/wiki/Google"; - - Runnable runnable = Mockito.mock(Runnable.class); - mController.addReadabilityUpdateListener(runnable); - mTab.setGurlOverrideForTesting(new GURL(testUrl)); - mController.maybeCheckReadability(mTab); - - verify(mHooksImpl, times(1)).isPageReadable(eq(testUrl), mCallbackCaptor.capture()); - - mCallbackCaptor.getValue().onSuccess(testUrl, ImmutableMap.of(PlaybackMode.CLASSIC, new ReadAloudReadabilityHooks.ReadabilityResult(true, false))); - verify(runnable).run(); - } - - @Test - public void testMetricRecorded_isReadable() { - final String histogramName = ReadAloudMetrics.IS_READABLE; - - var histogram = HistogramWatcher.newSingleRecordWatcher(histogramName, true); - mController.maybeCheckReadability(mTab); - verify(mHooksImpl, times(1)) - .isPageReadable(eq(sTestGURL.getSpec()), mCallbackCaptor.capture()); - mCallbackCaptor.getValue().onSuccess(sTestGURL.getSpec(), ImmutableMap.of(PlaybackMode.CLASSIC, new ReadAloudReadabilityHooks.ReadabilityResult(true, false))); - histogram.assertExpected(); - - histogram = HistogramWatcher.newSingleRecordWatcher(histogramName, false); - mCallbackCaptor.getValue().onSuccess(sTestGURL.getSpec(), ImmutableMap.of(PlaybackMode.CLASSIC, new ReadAloudReadabilityHooks.ReadabilityResult(false, false))); - histogram.assertExpected(); - } - - @Test - public void testMetricRecorded_readabilitySuccessful() { - final String histogramName = ReadAloudMetrics.READABILITY_SUCCESS; - - var histogram = HistogramWatcher.newSingleRecordWatcher(histogramName, true); - mController.maybeCheckReadability(mTab); - verify(mHooksImpl, times(1)) - .isPageReadable(eq(sTestGURL.getSpec()), mCallbackCaptor.capture()); - mCallbackCaptor.getValue().onSuccess(sTestGURL.getSpec(), ImmutableMap.of(PlaybackMode.CLASSIC, new ReadAloudReadabilityHooks.ReadabilityResult(true, false))); - histogram.assertExpected(); - - histogram = HistogramWatcher.newSingleRecordWatcher(histogramName, false); - mController.maybeCheckReadability(mTab); - verify(mHooksImpl, times(1)) - .isPageReadable(eq(sTestGURL.getSpec()), mCallbackCaptor.capture()); - mCallbackCaptor - .getValue() - .onFailure(sTestGURL.getSpec(), new Throwable("Something went wrong")); - histogram.assertExpected(); - } - - @Test - public void testMetricRecorded_serverReadability() { - final String histogramName = ReadAloudMetrics.READABILITY_SERVER_SIDE; - - var histogram = HistogramWatcher.newSingleRecordWatcher(histogramName, true); - mController.maybeCheckReadability(mTab); - verify(mHooksImpl).isPageReadable(eq(sTestGURL.getSpec()), mCallbackCaptor.capture()); - mCallbackCaptor - .getValue() - .onSuccess( - sTestGURL.getSpec(), - ImmutableMap.of(PlaybackMode.CLASSIC, new ReadAloudReadabilityHooks.ReadabilityResult(true, false))); - histogram.assertExpected(); - - histogram = HistogramWatcher.newSingleRecordWatcher(histogramName, false); - mCallbackCaptor - .getValue() - .onSuccess( - sTestGURL.getSpec(), - ImmutableMap.of(PlaybackMode.CLASSIC, new ReadAloudReadabilityHooks.ReadabilityResult(false, false))); - histogram.assertExpected(); - - // nothing should be emitted on error - histogram = HistogramWatcher.newBuilder().expectNoRecords(histogramName).build(); - mController.maybeCheckReadability(mTab); - verify(mHooksImpl, times(1)) - .isPageReadable(eq(sTestGURL.getSpec()), mCallbackCaptor.capture()); - mCallbackCaptor - .getValue() - .onFailure(sTestGURL.getSpec(), new Throwable("Something went wrong")); - histogram.assertExpected(); - } - - @Test - @EnableFeatures(ChromeFeatureList.READALOUD_BACKGROUND_PLAYBACK) - public void testBackgroundPlaybackContinuesWhenActivityPaused() { - // Play tab. - requestAndStartPlayback(); - // set progress - var data = Mockito.mock(PlaybackData.class); - - doReturn(2).when(data).paragraphIndex(); - doReturn(1000000L).when(data).positionInParagraphNanos(); - mController.onPlaybackDataChanged(data); - - // App is backgrounded with the screen on. Playback should continue if the flag is on. - setIsScreenOnAndUnlocked(true); - mController.onApplicationStateChange(ApplicationState.HAS_PAUSED_ACTIVITIES); - verify(mPlayback, never()).release(); - // also the screen is still on, don't notify about screen state change - verify(mPlayerCoordinator, never()).onScreenStatusChanged(anyBoolean()); - - // now turn the screen off. - setIsScreenOnAndUnlocked(false); - mController.onApplicationStateChange(ApplicationState.HAS_STOPPED_ACTIVITIES); - verify(mPlayback, never()).release(); - // also the screen is still on, don't notify about screen state change - verify(mPlayerCoordinator).onScreenStatusChanged(true); - } - - @Test - @EnableFeatures(ChromeFeatureList.READALOUD_BACKGROUND_PLAYBACK) - public void testBackgroundPlayback_doesntCrashWhenNoPlayer() throws NullPointerException { - setIsScreenOnAndUnlocked(false); - mController.onApplicationStateChange(ApplicationState.HAS_STOPPED_ACTIVITIES); - } - - @Test - public void testPlaybackStopsAndStateSavedWhenAppBackgrounded_screenOn() { - // Play tab. - requestAndStartPlayback(); - // set progress - var data = Mockito.mock(PlaybackData.class); - - doReturn(2).when(data).paragraphIndex(); - doReturn(1000000L).when(data).positionInParagraphNanos(); - mController.onPlaybackDataChanged(data); - - // App is backgrounded with the screen on. Make sure playback stops. - setIsScreenOnAndUnlocked(true); - mController.onApplicationStateChange(ApplicationState.HAS_STOPPED_ACTIVITIES); - verify(mPlayback).release(); - reset(mPlayback); - when(mPlayback.getMetadata()).thenReturn(mMetadata); - - // Activity goes back in foreground. Restore progress. - mController.onActivityStateChange(mActivity, ActivityState.RESUMED); - verify(mPlaybackHooks, times(2)).createPlayback(any(), mPlaybackCallbackCaptor.capture()); - onPlaybackSuccess(mPlayback); - verify(mPlayback).seekToParagraph(2, 1000000L); - verify(mPlayback, never()).play(); - - // once saved state is restored, it's cleared and no further interactions with playback - // should happen. - resetPlaybackMocks(); - - mController.onApplicationStateChange(ApplicationState.HAS_PAUSED_ACTIVITIES); - mController.onApplicationStateChange(ApplicationState.HAS_RUNNING_ACTIVITIES); - verifyNoInteractions(mPlaybackHooks); - verifyNoInteractions(mPlayback); - } - - @Test - public void testPlaybackWhenAppStops_screenOff() { - // Play tab. - requestAndStartPlayback(); - // set progress - var data = Mockito.mock(PlaybackData.class); - - doReturn(2).when(data).paragraphIndex(); - doReturn(1000000L).when(data).positionInParagraphNanos(); - mController.onPlaybackDataChanged(data); - - // App is backgrounded when the screen is off. Playback should keep playing. - setIsScreenOnAndUnlocked(false); - mController.onApplicationStateChange(ApplicationState.HAS_STOPPED_ACTIVITIES); - verify(mPlayback, never()).release(); - } - - @Test - public void testPlaybackWhenAppStops_userHint() { - // Play tab. - requestAndStartPlayback(); - // set progress - var data = Mockito.mock(PlaybackData.class); - - doReturn(2).when(data).paragraphIndex(); - doReturn(1000000L).when(data).positionInParagraphNanos(); - mController.onPlaybackDataChanged(data); - - // App is backgrounded. Screen is off but there is user hint present - stop playback - mController.onUserLeaveHint(); - setIsScreenOnAndUnlocked(false); - mController.onApplicationStateChange(ApplicationState.HAS_STOPPED_ACTIVITIES); - - verify(mPlayback).release(); - resetPlaybackMocks(); - - // App goes back in foreground. Restore progress. - mController.onActivityStateChange(mActivity, ActivityState.RESUMED); - verify(mPlaybackHooks).createPlayback(any(), mPlaybackCallbackCaptor.capture()); - onPlaybackSuccess(mPlayback); - verify(mPlayback).seekToParagraph(2, 1000000L); - verify(mPlayback, never()).play(); - } - - private void setIsScreenOnAndUnlocked(boolean isScreenOnAndUnlocked) { - DeviceConditions deviceConditions = - new DeviceConditions( - /* powerConnected= */ false, - /* batteryPercentage= */ 75, - ConnectionType.CONNECTION_UNKNOWN, - /* powerSaveOn= */ false, - /* activeNetworkMetered= */ false, - isScreenOnAndUnlocked); - ShadowDeviceConditions.setCurrentConditions(deviceConditions); - } - - @Test - public void testPlaybackResumesWhenActivityResumes() { - // Play tab. - requestAndStartPlayback(); - // set progress - var data = Mockito.mock(PlaybackData.class); - - doReturn(2).when(data).paragraphIndex(); - doReturn(1000000L).when(data).positionInParagraphNanos(); - mController.onPlaybackDataChanged(data); - - // App is backgrounded with the screen on. Make sure playback stops. - setIsScreenOnAndUnlocked(true); - mController.onApplicationStateChange(ApplicationState.HAS_STOPPED_ACTIVITIES); - verify(mPlayback).release(); - resetPlaybackMocks(); - - // App returns to foreground, but activity hasn't resumed yet. - mController.onApplicationStateChange(ApplicationState.HAS_RUNNING_ACTIVITIES); - verify(mPlaybackHooks, never()).createPlayback(any(), any()); - - // Activity goes back in foreground. Restore progress. - mController.onActivityStateChange(mActivity, ActivityState.RESUMED); - verify(mPlaybackHooks).createPlayback(any(), mPlaybackCallbackCaptor.capture()); - onPlaybackSuccess(mPlayback); - verify(mPlayback).seekToParagraph(2, 1000000L); - verify(mPlayback, never()).play(); - } - - @Test - @EnableFeatures(ChromeFeatureList.READALOUD_BACKGROUND_PLAYBACK) - public void testPlaybackResumesWhenActivityResumes_backgroundPlaybackEnabled() { - // Play tab. - requestAndStartPlayback(); - // set progress - var data = Mockito.mock(PlaybackData.class); - - doReturn(2).when(data).paragraphIndex(); - doReturn(1000000L).when(data).positionInParagraphNanos(); - mController.onPlaybackDataChanged(data); - - // App is backgrounded with the screen on. Playback should not stop. - setIsScreenOnAndUnlocked(true); - mController.onApplicationStateChange(ApplicationState.HAS_STOPPED_ACTIVITIES); - resetPlaybackMocks(); - - // Activity goes back in foreground. Nothing should be restored; playback was never stopped - // in the first place. - mController.onActivityStateChange(mActivity, ActivityState.RESUMED); - verifyNoInteractions(mPlayback); - verifyNoInteractions(mPlaybackHooks); - } - - @Test - public void testMetricRecorded_eligibility() { - final String histogramName = ReadAloudMetrics.IS_USER_ELIGIBLE; - - var histogram = HistogramWatcher.newSingleRecordWatcher(histogramName, true); - mController.onProfileAvailable(mMockProfile); - histogram.assertExpected(); - - histogram = HistogramWatcher.newSingleRecordWatcher(histogramName, false); - when(mPrefService.getBoolean("readaloud.listen_to_this_page_enabled")).thenReturn(false); - mController.onProfileAvailable(mMockProfile); - histogram.assertExpected(); - } - - @Test - public void testMetricRecorded_ineligibilityReason() { - final String histogramName = ReadAloudMetrics.INELIGIBILITY_REASON; - - var histogram = - HistogramWatcher.newSingleRecordWatcher( - histogramName, IneligibilityReason.POLICY_DISABLED); - when(mPrefService.getBoolean("readaloud.listen_to_this_page_enabled")).thenReturn(false); - mController.onProfileAvailable(mMockProfile); - histogram.assertExpected(); - when(mPrefService.getBoolean("readaloud.listen_to_this_page_enabled")).thenReturn(true); - - histogram = - HistogramWatcher.newSingleRecordWatcher( - histogramName, IneligibilityReason.DEFAULT_SEARCH_ENGINE_GOOGLE_FALSE); - doReturn(SearchEngineType.SEARCH_ENGINE_OTHER) - .when(mTemplateUrlService) - .getSearchEngineTypeFromTemplateUrl(anyString()); - mController.onProfileAvailable(mMockProfile); - histogram.assertExpected(); - } - - @Test - public void testMetricRecorded_isPlaybackCreationSuccessful_True() { - final String histogramName = ReadAloudMetrics.IS_TAB_PLAYBACK_CREATION_SUCCESSFUL; - - var histogram = HistogramWatcher.newSingleRecordWatcher(histogramName, true); - requestAndStartPlayback(); - histogram.assertExpected(); - } - - @Test - public void testMetricRecorded_isPlaybackCreationSuccessful_False() { - final String histogramName = ReadAloudMetrics.IS_TAB_PLAYBACK_CREATION_SUCCESSFUL; - - var histogram = HistogramWatcher.newSingleRecordWatcher(histogramName, false); - mController.playTab(mTab, ReadAloudController.Entrypoint.MAGIC_TOOLBAR); - resolvePromises(); - verify(mPlaybackHooks).createPlayback(Mockito.any(), mPlaybackCallbackCaptor.capture()); - mPlaybackCallbackCaptor.getValue().onFailure(new Exception("Very bad error")); - resolvePromises(); - histogram.assertExpected(); - } - - @Test - public void testMetricRecorded_playbackWithoutReadabilityCheck() { - final String histogramName = ReadAloudMetrics.TAB_PLAYBACK_WITHOUT_READABILITY_CHECK_ERROR; - var histogram = - HistogramWatcher.newSingleRecordWatcher( - histogramName, ReadAloudController.Entrypoint.OVERFLOW_MENU); - - mController.playTab(mTab, ReadAloudController.Entrypoint.OVERFLOW_MENU); - histogram.assertExpected(); - } - - @Test - public void testMetricRecorded_playbackSuccess() { - final String histogramName = ReadAloudMetrics.TAB_PLAYBACK_CREATION_SUCCESS; - var histogram = - HistogramWatcher.newSingleRecordWatcher( - histogramName, ReadAloudController.Entrypoint.MAGIC_TOOLBAR); - - requestAndStartPlayback(); - - histogram.assertExpected(); - } - - @Test - public void testMetricRecorded_playbackFailure() { - final String histogramName = ReadAloudMetrics.TAB_PLAYBACK_CREATION_FAILURE; - var histogram = - HistogramWatcher.newSingleRecordWatcher( - histogramName, ReadAloudController.Entrypoint.OVERFLOW_MENU); - // Play tab to set up playbackhooks - mController.playTab(mTab, ReadAloudController.Entrypoint.OVERFLOW_MENU); - resolvePromises(); - verify(mPlaybackHooks, times(1)) - .createPlayback(Mockito.any(), mPlaybackCallbackCaptor.capture()); - mPlaybackCallbackCaptor.getValue().onFailure(new Exception("Very bad error")); - resolvePromises(); - - histogram.assertExpected(); - } - - @Test - public void testMetricNotRecorded_isPlaybackCreationSuccessful() { - final String histogramName = ReadAloudMetrics.IS_TAB_PLAYBACK_CREATION_SUCCESSFUL; - var histogram = HistogramWatcher.newBuilder().expectNoRecords(histogramName).build(); - - // Play tab to set up playbackhooks - mController.playTab(mTab, ReadAloudController.Entrypoint.MAGIC_TOOLBAR); - resolvePromises(); - - // Preview a voice. - var voice = new PlaybackVoice("en", "asdf", ""); - doReturn(List.of(voice)).when(mPlaybackHooks).getVoicesFor(anyString()); - doReturn(List.of(voice)).when(mPlaybackHooks).getPlaybackVoiceList(any()); - mController.previewVoice(voice); - - histogram.assertExpected(); - } - - @Test - public void testMetricRecorded_playbackStarted() { - final String actionName = "ReadAloud.PlaybackStarted"; - ReadAloudMetrics.recordPlaybackStarted(); - assertThat(mUserActionTester.getActions(), hasItems(actionName)); - } - - @Test - public void testMetricRecorded_highlightingEnabledOnStartup() { - mHighlightingEnabledOnStartupHistogram.assertExpected(); - } - - @Test - public void testMetricRecorded_highlightingSupported_true() { - final String histogramName = "ReadAloud.HighlightingSupported"; - var histogram = HistogramWatcher.newSingleRecordWatcher(histogramName, true); - - mController.playTab(mTab, ReadAloudController.Entrypoint.MAGIC_TOOLBAR); - resolvePromises(); - - verify(mPlaybackHooks, times(1)) - .createPlayback(Mockito.any(), mPlaybackCallbackCaptor.capture()); - - mFakeTranslateBridge.setIsPageTranslated(false); - mController.setTimepointsSupportedForTest(mTab.getUrl().getSpec(), true); - onPlaybackSuccess(mPlayback); - - histogram.assertExpected(); - } - - @Test - public void testMetricRecorded_highlightingSupported_false() { - final String histogramName = "ReadAloud.HighlightingSupported"; - var histogram = HistogramWatcher.newSingleRecordWatcher(histogramName, false); - - mController.playTab(mTab, ReadAloudController.Entrypoint.MAGIC_TOOLBAR); - resolvePromises(); - - verify(mPlaybackHooks, times(1)) - .createPlayback(Mockito.any(), mPlaybackCallbackCaptor.capture()); - - mFakeTranslateBridge.setIsPageTranslated(false); - mController.setTimepointsSupportedForTest(mTab.getUrl().getSpec(), false); - onPlaybackSuccess(mPlayback); - - histogram.assertExpected(); - } - - @Test - public void testNavigateToPlayingTab() { - // Play tab. - requestAndStartPlayback(); - - MockTab newTab = mTabModelSelector.addMockTab(); - mTabModelSelector - .getModel(false) - .setIndex( - mTabModelSelector.getModel(false).indexOf(newTab), - TabSelectionType.FROM_USER); - // check that we switched to new tab - assertEquals(mTabModelSelector.getCurrentTab(), newTab); - - // navigate - mController.navigateToPlayingTab(); - - // should switch back to original one - assertEquals(mTabModelSelector.getCurrentTab(), mTab); - - // navigate - mController.navigateToPlayingTab(); - - // should still be on the playing tab - assertEquals(mTabModelSelector.getCurrentTab(), mTab); - } - - @Test - public void testDestroy() { - // Play tab - requestAndStartPlayback(); - - // Destroy should clean up playback, UI, synthetic trials, and more - mController.destroy(); - verify(mPlayback).release(); - verify(mPlayerCoordinator).destroy(); - } - - @Test - public void testMaybeShowPlayer() { - // no playback, request is a no op - mController.maybeShowPlayer(); - - verify(mPlayerCoordinator, never()).restorePlayers(); - - requestAndStartPlayback(); - mController.maybeShowPlayer(); - - verify(mPlayerCoordinator).restorePlayers(); - } - - @Test - public void testMaybeHideMiniPlayer() { - // no playback, request is a no op - mController.maybeHidePlayer(); - - verify(mPlayerCoordinator, never()).hidePlayers(); - - requestAndStartPlayback(); - mController.maybeHidePlayer(); - - verify(mPlayerCoordinator).hidePlayers(); - } - - @Test - public void testPauseAndHideOnIncognitoTabSelected() { - requestAndStartPlayback(); - - Tab tab = mTabModelSelector.addMockIncognitoTab(); - TabModelUtils.selectTabById(mTabModelSelector, tab.getId(), TabSelectionType.FROM_NEW); - - verify(mPlayback).pause(); - verify(mPlayerCoordinator).hidePlayers(); - } - - @Test - public void testRestorePlayerOnReturnFromIncognitoTab() { - requestAndStartPlayback(); - reset(mPlayback); - - Tab tab = mTabModelSelector.addMockIncognitoTab(); - TabModelUtils.selectTabById(mTabModelSelector, tab.getId(), TabSelectionType.FROM_NEW); - - verify(mPlayback).pause(); - verify(mPlayerCoordinator).hidePlayers(); - - TabModelUtils.selectTabById(mTabModelSelector, mTab.getId(), TabSelectionType.FROM_USER); - verify(mPlayback, never()).play(); - verify(mPlayerCoordinator).restorePlayers(); - } - - @Test - @EnableFeatures(ChromeFeatureList.READALOUD_BACKGROUND_PLAYBACK) - public void testCrossActivityPlayback_stopBackgroundPlayback() { - // Play in Chrome, then play in CCT. Chrome playback should stop only when CCT plays. - - // Create a second instance of ReadAloudController to simulate other app's CCT. - mController2 = createController(); - - // Play in Chrome. requestAndStartPlayback() verifies playback started. - requestAndStartPlayback(); - - resetPlaybackMocks(); - - // Simulate backgrounding the activity, and the entire app. Playback should not stop. - mController.onActivityStateChange(mActivity, ActivityState.STOPPED); - mController.onApplicationStateChange(ApplicationState.HAS_STOPPED_ACTIVITIES); - verifyNoInteractions(mPlayback); - - // Play in CCT. - var histogram = - HistogramWatcher.newSingleRecordWatcher( - ReadAloudMetrics.REASON_FOR_STOPPING_PLAYBACK, - ReadAloudMetrics.ReasonForStoppingPlayback.EXTERNAL_PLAYBACK_REQUEST); - mController2.playTab(mTab, ReadAloudController.Entrypoint.MAGIC_TOOLBAR); - resolvePromises(); - - // Chrome playback should stop. Reason should be recorded as EXTERNAL_PLAYBACK_REQUEST. - verify(mPlayback).release(); - histogram.assertExpected(); - - // CCT playback should start. - verify(mPlaybackHooks, times(1)) - .createPlayback(Mockito.any(), mPlaybackCallbackCaptor.capture()); - onPlaybackSuccess(mPlayback); - verify(mPlayerCoordinator, times(1)) - .playbackReady(eq(mPlayback), eq(PlaybackListener.State.PLAYING)); - } - - @Test - @EnableFeatures(ChromeFeatureList.READALOUD_BACKGROUND_PLAYBACK) - public void testCrossActivityPlayback_canRestoreIfSameTab() { - // Play in Chrome, play in CCT, then request playback for original tab in Chrome. Playback - // should be restored. - - // Create a second instance of ReadAloudController to simulate other app's CCT. - mController2 = createController(); - - // Play in Chrome. requestAndStartPlayback() verifies playback started. - requestAndStartPlayback(); - - // Simulate some progress. - var data = Mockito.mock(PlaybackData.class); - doReturn(2).when(data).paragraphIndex(); - doReturn(1000000L).when(data).positionInParagraphNanos(); - mController.onPlaybackDataChanged(data); - - resetPlaybackMocks(); - - // Play in CCT. - mController2.playTab(mTab, ReadAloudController.Entrypoint.MAGIC_TOOLBAR); - resolvePromises(); - verify(mPlayerCoordinator).setPlayerRestorable(true); - - // Chrome playback should stop. - verify(mPlayback).release(); - - // CCT playback should start. - verify(mPlaybackHooks, times(1)) - .createPlayback(Mockito.any(), mPlaybackCallbackCaptor.capture()); - onPlaybackSuccess(mPlayback); - verify(mPlayerCoordinator, times(1)) - .playbackReady(eq(mPlayback), eq(PlaybackListener.State.PLAYING)); - - resetPlaybackMocks(); - - // Return to Chrome. CCT playback should not stop. - mController.onActivityStateChange(mActivity, ActivityState.RESUMED); - verifyNoInteractions(mPlayback); - - // Tap an entrypoint on the same tab in Chrome. CCT should stop playing and playback should - // be restored (paused). - mController.playTab(mTab, ReadAloudController.Entrypoint.MAGIC_TOOLBAR); - // Release CCT playback - verify(mPlayback).release(); - // Simulate successful playback creation. - verify(mPlaybackHooks).createPlayback(any(), mPlaybackCallbackCaptor.capture()); - onPlaybackSuccess(mPlayback); - // Progress should be restored and play should not have been called. - verify(mPlayback).seekToParagraph(2, 1000000L); - verify(mPlayback, never()).play(); - } - - @Test - public void testRestorePlayback() { - // Play in Chrome, play in CCT, then request to restore playback for original Chrome. - // Playback - // should be restored. - - // Create a second instance of ReadAloudController to simulate other app's CCT. - mController2 = createController(); - - // Play in Chrome. requestAndStartPlayback() verifies playback started. - requestAndStartPlayback(); - - // Simulate some progress. - var data = Mockito.mock(PlaybackData.class); - doReturn(2).when(data).paragraphIndex(); - doReturn(1000000L).when(data).positionInParagraphNanos(); - mController.onPlaybackDataChanged(data); - - resetPlaybackMocks(); - - // Play in CCT. - mController2.playTab(mTab, ReadAloudController.Entrypoint.MAGIC_TOOLBAR); - resolvePromises(); - - // Chrome playback should stop. - verify(mPlayback).release(); - - // CCT playback should start. - verify(mPlaybackHooks, times(1)) - .createPlayback(Mockito.any(), mPlaybackCallbackCaptor.capture()); - onPlaybackSuccess(mPlayback); - verify(mPlayerCoordinator, times(1)) - .playbackReady(eq(mPlayback), eq(PlaybackListener.State.PLAYING)); - - resetPlaybackMocks(); - - // Return to Chrome. CCT playback should not stop. - mController.onActivityStateChange(mActivity, ActivityState.RESUMED); - verifyNoInteractions(mPlayback); - - // Request to restore playback. Called by the PlayerMediator when the play pause button is - // clicked on a UI without playback. - mController.restorePlayback(); - // Simulate successful playback creation. - verify(mPlaybackHooks).createPlayback(any(), mPlaybackCallbackCaptor.capture()); - onPlaybackSuccess(mPlayback); - // Progress should be restored and play should not have been called. - verify(mPlayback).seekToParagraph(2, 1000000L); - verify(mPlayback, never()).play(); - } - - @Test - @EnableFeatures(ChromeFeatureList.READALOUD_BACKGROUND_PLAYBACK) - public void testCrossActivityPlayback_doNotRestoreIfDifferentTab() { - // Play in Chrome, play in CCT, then request playback for a different tab in Chrome. A new - // playback should start and the old one should not be restored. - - // Create a second instance of ReadAloudController to simulate other app's CCT. - mController2 = createController(); - - // Play in Chrome. requestAndStartPlayback() verifies playback started. - requestAndStartPlayback(); - - // Simulate some progress. - var data = Mockito.mock(PlaybackData.class); - doReturn(2).when(data).paragraphIndex(); - doReturn(1000000L).when(data).positionInParagraphNanos(); - mController.onPlaybackDataChanged(data); - - resetPlaybackMocks(); - - // Play in CCT. - mController2.playTab(mTab, ReadAloudController.Entrypoint.MAGIC_TOOLBAR); - resolvePromises(); - - // Chrome playback should stop. - verify(mPlayback).release(); - - // CCT playback should start. - verify(mPlaybackHooks, times(1)) - .createPlayback(Mockito.any(), mPlaybackCallbackCaptor.capture()); - onPlaybackSuccess(mPlayback); - verify(mPlayerCoordinator, times(1)) - .playbackReady(eq(mPlayback), eq(PlaybackListener.State.PLAYING)); - - resetPlaybackMocks(); - - // Return to Chrome. CCT playback should not stop. - mController.onActivityStateChange(mActivity, ActivityState.RESUMED); - verifyNoInteractions(mPlayback); - - // Tap an entrypoint on a different tab in Chrome. - MockTab tab = mTabModelSelector.addMockTab(); - tab.setGurlOverrideForTesting(sTestGURL); - tab.setWebContentsOverrideForTesting(mWebContents); - mController.playTab(tab, ReadAloudController.Entrypoint.MAGIC_TOOLBAR); - resolvePromises(); - // CCT playback was released - verify(mPlayback).release(); - // Simulate successful playback creation and make sure playback starts. - verify(mPlaybackHooks).createPlayback(any(), mPlaybackCallbackCaptor.capture()); - onPlaybackSuccess(mPlayback); - verify(mPlayback).play(); - - // Make sure saved state was not restored and was instead cleared. - verify(mPlayback, never()).seekToParagraph(eq(2), eq(1000000L)); - resetPlaybackMocks(); - mController.onActivityStateChange(mActivity, ActivityState.RESUMED); - verifyNoInteractions(mPlaybackHooks); - verifyNoInteractions(mPlayback); - } - - // TODO(b/322052505): This test won't be necessary if we keep track of profile changes. - @Test - public void testNoRequestsIfProfileDestroyed() { - reset(mHooksImpl); - doReturn(false).when(mMockProfile).isNativeInitialized(); - mController = createController(); - ShadowLooper.runUiThreadTasksIncludingDelayedTasks(); - - // Check readability. - mController.maybeCheckReadability(mTab); - // No readability request should be made. - verify(mHooksImpl, never()) - .isPageReadable(any(), any(ReadAloudReadabilityHooks.ReadabilityPerModeCallback.class)); - - // Try playing the tab. - mFakeTranslateBridge.setCurrentLanguage("en"); - mTab.setGurlOverrideForTesting(new GURL("https://en.wikipedia.org/wiki/Google")); - mController.playTab(mTab, ReadAloudController.Entrypoint.MAGIC_TOOLBAR); - resolvePromises(); - // No playback request should be made. - verify(mPlaybackHooks, never()).createPlayback(any(), any()); - } - - @Test - public void testPause_notPlayingTab() { - mController.pause(); - // Not currently playing, so nothing should happen. - verify(mPlayback, never()).pause(); - } - - @Test - public void testPause_alreadyStopped() { - requestAndStartPlayback(); - var data = Mockito.mock(PlaybackListener.PlaybackData.class); - doReturn(PlaybackListener.State.STOPPED).when(data).state(); - mController.onPlaybackDataChanged(data); - - mController.pause(); - // Not currently playing, so nothing should happen. - verify(mPlayback, never()).pause(); - } - - @Test - public void testPause() { - requestAndStartPlayback(); - var data = Mockito.mock(PlaybackListener.PlaybackData.class); - doReturn(PlaybackListener.State.PLAYING).when(data).state(); - mController.onPlaybackDataChanged(data); - - mController.pause(); - verify(mPlayback).pause(); - } - - @Test - public void testMaybePauseForOutgoingIntent_pause() { - // Play. - requestAndStartPlayback(); - var data = Mockito.mock(PlaybackListener.PlaybackData.class); - doReturn(PlaybackListener.State.PLAYING).when(data).state(); - mController.onPlaybackDataChanged(data); - - // Simulate select-to-speak context menu click. Playback should pause. - Intent intent = new Intent(); - intent.setAction(Intent.ACTION_PROCESS_TEXT); - mController.maybePauseForOutgoingIntent(intent); - verify(mPlayback).pause(); - } - - @Test - public void testMaybePauseForOutgoingIntent_noPause() { - // Play. - requestAndStartPlayback(); - var data = Mockito.mock(PlaybackListener.PlaybackData.class); - doReturn(PlaybackListener.State.PLAYING).when(data).state(); - mController.onPlaybackDataChanged(data); - - // Simulate some unimportant context menu click. Playback should not pause. - Intent intent = new Intent(); - intent.setAction(Intent.ACTION_DEFINE); - mController.maybePauseForOutgoingIntent(intent); - verify(mPlayback, never()).pause(); - } - - @Test - public void testPlayTabWithDateExtraction() { - requestAndStartPlayback(); - verify(mPlayerCoordinator).addObserver(mController); - - verify(mPlaybackHooks, times(1)).createPlayback(mPlaybackArgsCaptor.capture(), any()); - - assertEquals(1234567123456L, mPlaybackArgsCaptor.getValue().getDateModifiedMsSinceEpoch()); - } - - @Test - public void testLogDateExtraction_hasDateModified() { - mFakeTranslateBridge.setCurrentLanguage("en"); - var histogram = HistogramWatcher.newSingleRecordWatcher("ReadAloud.HasDateModified", true); - requestAndStartPlayback(); - histogram.assertExpected(); - } - - @Test - public void testLogDateExtraction_noDateModified() { - mFakeTranslateBridge.setCurrentLanguage("en"); - var failedPromise = new Promise<Long>(); - when(mExtractor.getDateModified(any())).thenReturn(failedPromise); - failedPromise.reject(new Exception("")); - - var histogram = HistogramWatcher.newSingleRecordWatcher("ReadAloud.HasDateModified", false); - requestAndStartPlayback(); - histogram.assertExpected(); - } - - @Test - public void testIsPlayingCurrentTab() { - // should be false at first since currentlyPlayingTab is null - assertFalse(mController.isPlayingCurrentTab()); - // set to playing tab - requestAndStartPlayback(); - assertTrue(mController.isPlayingCurrentTab()); - // should be false after switching to a non playing tab - MockTab newTab = mTabModelSelector.addMockTab(); - mTabModelSelector - .getModel(false) - .setIndex( - mTabModelSelector.getModel(false).indexOf(newTab), - TabSelectionType.FROM_USER); - assertFalse(mController.isPlayingCurrentTab()); - // switch back to current tab - mTabModelSelector - .getModel(false) - .setIndex( - mTabModelSelector.getModel(false).indexOf(mTab), - TabSelectionType.FROM_USER); - assertTrue(mController.isPlayingCurrentTab()); - // back to null after stopping playback - mController.maybeStopPlayback( - mTab, ReadAloudMetrics.ReasonForStoppingPlayback.NEW_PLAYBACK_REQUEST); - assertFalse(mController.isPlayingCurrentTab()); - } - - @Test - @EnableFeatures(ChromeFeatureList.READALOUD_TAP_TO_SEEK) - public void testTapToSeek() { - // play tab - requestAndStartPlayback(); - verify(mPlayback).addListener(mPlaybackListenerCaptor.capture()); - // update playback data so it isn't null - var data = Mockito.mock(PlaybackListener.PlaybackData.class); - doReturn(PlaybackListener.State.PLAYING).when(data).state(); - mPlaybackListenerCaptor.getValue().onPlaybackDataChanged(data); - var histogram = - HistogramWatcher.newSingleRecordWatcher(ReadAloudMetrics.TAP_TO_SEEK_TIME, 12); - when(mMetadata.fullText()) - .thenAnswer( - invocation -> { - mClock.advanceCurrentTimeMillis(12); - return "the quick brown fox jumps over the lazy dog"; - }); - PlaybackTextPart p = - new PlaybackTextPart() { - @Override - public int getOffset() { - return 0; - } - - @Override - public int getType() { - return PlaybackTextType.TEXT_TYPE_UNSPECIFIED; - } - - @Override - public int getParagraphIndex() { - return -1; - } - - @Override - public int getLength() { - return -1; - } - }; - PlaybackTextPart[] paragraphs = new PlaybackTextPart[] {p}; - when(mMetadata.paragraphs()).thenReturn(paragraphs); - mController.tapToSeek("the quick brown fox", 4, 9); - verify(mPlayback, times(1)).seekToWord(0, 4); - histogram.assertExpected(); - } - - @Test - @EnableFeatures(ChromeFeatureList.READALOUD_TAP_TO_SEEK) - public void testTapToSeek_differentTab() { - // play tab - requestAndStartPlayback(); - // switch tabs - MockTab newTab = mTabModelSelector.addMockTab(); - mTabModelSelector - .getModel(false) - .setIndex( - mTabModelSelector.getModel(false).indexOf(newTab), - TabSelectionType.FROM_USER); - // shouldn't seek - mController.tapToSeek("the quick brown fox", 4, 9); - verify(mPlayback, never()).seekToWord(0, 8); - } - - @Test - public void testDidFirstVisuallyNonEmptyPaint() { - GURL gurl = new GURL("https://en.wikipedia.org/wiki/Alphabet_Inc."); - when(mTab.getUrl()).thenReturn(gurl); - mController.getTabModelTabObserverforTests().didFirstVisuallyNonEmptyPaint(mTab); - ShadowLooper.runUiThreadTasksIncludingDelayedTasks(); - verify(mHooksImpl) - .isPageReadable( - eq(gurl.getPossiblyInvalidSpec()), - any(ReadAloudReadabilityHooks.ReadabilityPerModeCallback.class)); - } - - @Test - public void testOnTabSelected() { - MockTab tab = mTabModelSelector.addMockTab(); - - // should do nothing on empty url - tab.setUrl(new GURL("")); - mController.getTabModelTabObserverforTests().onTabSelected(tab); - verify(tab, never()).getUserDataHost(); - - // should get user data for actual urls - tab.setUrl(new GURL("https://en.wikipedia.org/wiki/Alphabet_Inc.")); - mController.getTabModelTabObserverforTests().onTabSelected(tab); - verify(tab, times(1)).getUserDataHost(); - } - - @Test - public void testTimepointsSupported_emptyUrl() { - // if somehow an empty url sneaks into timepoints supported - mController.setTimepointsSupportedForTest("", true); - when(mTab.getUrl()).thenReturn(new GURL("")); - // a tab with an empty url should not be supported - assertFalse(mController.timepointsSupported(mTab, PlaybackMode.CLASSIC)); - } - - @Test - public void testEmptyUrlReadability() { - // grab the callback - mController.maybeCheckReadability(mTab); - verify(mHooksImpl, times(1)) - .isPageReadable(eq(sTestGURL.getSpec()), mCallbackCaptor.capture()); - // if somehow an empty url sneaks into the readability maps - boolean failed = false; - try { - mCallbackCaptor - .getValue() - .onSuccess( - "", - ImmutableMap.of( - PlaybackMode.CLASSIC, - new ReadAloudReadabilityHooks.ReadabilityResult(true, true))); - } catch (AssertionError e) { - failed = true; - } - assertTrue(failed); - when(mTab.getUrl()).thenReturn(new GURL("")); - // empty urls should not be returned as readable - assertFalse(mController.isReadable(mTab)); - } - - @Test - public void testMetricRecorded_EmptyUrlPlayback() { - final String histogramName = ReadAloudMetrics.EMPTY_URL_PLAYBACK; - var histogram = - HistogramWatcher.newSingleRecordWatcher( - histogramName, ReadAloudController.Entrypoint.MAGIC_TOOLBAR); - - mFakeTranslateBridge.setCurrentLanguage("en"); - mTab.setGurlOverrideForTesting(new GURL("")); - - boolean failed = false; - try { - mController.playTab(mTab, ReadAloudController.Entrypoint.MAGIC_TOOLBAR); - } catch (AssertionError e) { - failed = true; - } - assertTrue(failed); - histogram.assertExpected(); - } - - @Test - public void testMetricRecorded_EmptyUrlPlayback_RestoreState() { - final String histogramName = ReadAloudMetrics.EMPTY_URL_PLAYBACK; - var histogram = - HistogramWatcher.newSingleRecordWatcher( - histogramName, ReadAloudController.Entrypoint.RESTORED_PLAYBACK); - - mTab.setGurlOverrideForTesting(new GURL("https://en.wikipedia.org/wiki/Google")); - var data = Mockito.mock(PlaybackData.class); - ReadAloudController.RestoreState restoreState = - mController.new RestoreState(mTab, data, true, false, 0L); - mController.setStateToRestoreOnBringingToForegroundForTests(restoreState); - // for some reason the tab url goes null - mTab.setGurlOverrideForTesting(new GURL("")); - boolean failed = false; - try { - restoreState.restore(); - } catch (AssertionError e) { - failed = true; - } - assertTrue(failed); - histogram.assertExpected(); - } - - @Test - public void testNoReadabilityUpdateAfterDestroy() { - Runnable readabilityObserver = Mockito.mock(Runnable.class); - mController.addReadabilityUpdateListener(readabilityObserver); - - // Check readability - mController.maybeCheckReadability(mTab); - verify(mHooksImpl, times(1)) - .isPageReadable(eq(sTestGURL.getSpec()), mCallbackCaptor.capture()); - assertFalse(mController.isReadable(mTab)); - - // Simulate response coming back after ReadAloudController being destroyed. - mController.destroy(); - mCallbackCaptor.getValue().onSuccess(sTestGURL.getSpec(), ImmutableMap.of(PlaybackMode.CLASSIC, new ReadAloudReadabilityHooks.ReadabilityResult(true, false))); - - verify(readabilityObserver, never()).run(); - } - - @Test - public void testReasonForStoppingPlaybackLogged() { - final String histogramName = ReadAloudMetrics.REASON_FOR_STOPPING_PLAYBACK; - var histogram = - HistogramWatcher.newSingleRecordWatcher( - histogramName, ReadAloudMetrics.ReasonForStoppingPlayback.MANUAL_CLOSE); - requestAndStartPlayback(); - - mController.maybeStopPlayback( - mTab, ReadAloudMetrics.ReasonForStoppingPlayback.MANUAL_CLOSE); - - histogram.assertExpected(); - } - - private void requestAndStartPlayback() { - mFakeTranslateBridge.setCurrentLanguage("en"); - mTab.setGurlOverrideForTesting(new GURL("https://en.wikipedia.org/wiki/Google")); - mController.playTab(mTab, ReadAloudController.Entrypoint.MAGIC_TOOLBAR); - resolvePromises(); - - verify(mPlaybackHooks, times(1)) - .createPlayback(Mockito.any(), mPlaybackCallbackCaptor.capture()); - - onPlaybackSuccess(mPlayback); - verify(mPlayerCoordinator, times(1)) - .playbackReady(eq(mPlayback), eq(PlaybackListener.State.PLAYING)); - } - - private void onPlaybackSuccess(Playback playback) { - mPlaybackCallbackCaptor.getValue().onSuccess(playback); - resolvePromises(); - } - - private static void resolvePromises() { - ShadowLooper.runUiThreadTasksIncludingDelayedTasks(); - } + private static void resolvePromises() { + ShadowLooper.runUiThreadTasksIncludingDelayedTasks(); + } }
diff --git a/chrome/browser/resources/BUILD.gn b/chrome/browser/resources/BUILD.gn index ca7e404e..ad3b8fe 100644 --- a/chrome/browser/resources/BUILD.gn +++ b/chrome/browser/resources/BUILD.gn
@@ -32,6 +32,7 @@ "about_sys:resources", "access_code_cast:resources", "app_service_internals:resources", + "autofill_ml_internals:resources", "bookmarks:resources", "certificate_manager:resources", "commerce/product_specifications:resources",
diff --git a/chrome/browser/resources/autofill_ml_internals/BUILD.gn b/chrome/browser/resources/autofill_ml_internals/BUILD.gn new file mode 100644 index 0000000..89790a34 --- /dev/null +++ b/chrome/browser/resources/autofill_ml_internals/BUILD.gn
@@ -0,0 +1,28 @@ +# Copyright 2025 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 = "autofill_ml_internals" + + static_files = [ "autofill_ml_internals.html" ] + ts_files = [ + "app.html.ts", + "app.ts", + ] + css_files = [ "app.css" ] + + mojo_files_deps = [ "//components/autofill/core/browser/ml_model/logging:mojo_bindings_ts__generator" ] + mojo_files = [ "$root_gen_dir/components/autofill/core/browser/ml_model/logging/autofill_ml_internals.mojom-webui.ts" ] + + ts_deps = [ + "//third_party/lit/v3_0:build_ts", + "//ui/webui/resources/js:build_ts", + "//ui/webui/resources/mojo:build_ts", + ] + + webui_context_type = "trusted" +}
diff --git a/chrome/browser/resources/autofill_ml_internals/DIR_METADATA b/chrome/browser/resources/autofill_ml_internals/DIR_METADATA new file mode 100644 index 0000000..bc280336 --- /dev/null +++ b/chrome/browser/resources/autofill_ml_internals/DIR_METADATA
@@ -0,0 +1 @@ +mixins: "//components/autofill/COMMON_METADATA"
diff --git a/chrome/browser/resources/autofill_ml_internals/OWNERS b/chrome/browser/resources/autofill_ml_internals/OWNERS new file mode 100644 index 0000000..50a21bb --- /dev/null +++ b/chrome/browser/resources/autofill_ml_internals/OWNERS
@@ -0,0 +1 @@ +file://components/autofill/OWNERS
diff --git a/chrome/browser/resources/autofill_ml_internals/app.css b/chrome/browser/resources/autofill_ml_internals/app.css new file mode 100644 index 0000000..d887c89 --- /dev/null +++ b/chrome/browser/resources/autofill_ml_internals/app.css
@@ -0,0 +1,13 @@ +/* Copyright 2025 The Chromium Authors + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. */ + +/* #css_wrapper_metadata_start + * #type=style-lit + * #scheme=relative + * #css_wrapper_metadata_end */ + +#greeting { + color: blue; + padding: 20px; +}
diff --git a/chrome/browser/resources/autofill_ml_internals/app.html.ts b/chrome/browser/resources/autofill_ml_internals/app.html.ts new file mode 100644 index 0000000..fb6ac64 --- /dev/null +++ b/chrome/browser/resources/autofill_ml_internals/app.html.ts
@@ -0,0 +1,25 @@ +// Copyright 2025 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import {html} from '//resources/lit/v3_0/lit.rollup.js'; + +import type {AutofillMlInternalsAppElement} from './app.js'; +import type {MLPredictionLog} from './autofill_ml_internals.mojom-webui.js'; + +export function getHtml(this: AutofillMlInternalsAppElement) { + return html` + <h1>Autofill ML Internals</h1> + <p>${this.message_}</p> + + <h2>Prediction Logs</h2> + <div id="logs-container"> + ${this.logs_.map((log: MLPredictionLog) => html` + <div class="log-entry"> + <div>Form Signature: ${log.formSignature}</div> + <div>Field Signatures: ${log.fieldSignatures.join(', ')}</div> + </div> + `)} + </div> + `; +}
diff --git a/chrome/browser/resources/autofill_ml_internals/app.ts b/chrome/browser/resources/autofill_ml_internals/app.ts new file mode 100644 index 0000000..4fd7711a --- /dev/null +++ b/chrome/browser/resources/autofill_ml_internals/app.ts
@@ -0,0 +1,67 @@ +// Copyright 2025 The Chromium Authors +// 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 {CrLitElement} from '//resources/lit/v3_0/lit.rollup.js'; +import {loadTimeData} from 'chrome://resources/js/load_time_data.js'; + +import {getCss} from './app.css.js'; +import {getHtml} from './app.html.js'; +import {PageCallbackRouter, PageHandler} from './autofill_ml_internals.mojom-webui.js'; +import type {MLPredictionLog, PageHandlerRemote} from './autofill_ml_internals.mojom-webui.js'; + + +export class AutofillMlInternalsAppElement extends CrLitElement { + static get is() { + return 'autofill-ml-internals-app'; + } + + static override get styles() { + return getCss(); + } + + override render() { + return getHtml.bind(this)(); + } + + static override get properties() { + return { + message_: {type: String}, + logs_: {type: Array}, + }; + } + + protected accessor message_: string = loadTimeData.getString('message'); + protected accessor logs_: MLPredictionLog[] = []; + + private pageHandler_: PageHandlerRemote; + private callbackRouter_: PageCallbackRouter; + + constructor() { + super(); + + this.pageHandler_ = PageHandler.getRemote(); + this.callbackRouter_ = new PageCallbackRouter(); + + this.pageHandler_.setPage( + this.callbackRouter_.$.bindNewPipeAndPassRemote()); + + this.callbackRouter_.onLogAdded.addListener( + (log: MLPredictionLog) => this.onLogAdded_(log)); + } + + private onLogAdded_(log: MLPredictionLog) { + this.logs_ = [log, ...this.logs_]; + } +} + +declare global { + interface HTMLElementTagNameMap { + 'autofill-ml-internals-app': AutofillMlInternalsAppElement; + } +} + +customElements.define( + AutofillMlInternalsAppElement.is, AutofillMlInternalsAppElement);
diff --git a/chrome/browser/resources/autofill_ml_internals/autofill_ml_internals.html b/chrome/browser/resources/autofill_ml_internals/autofill_ml_internals.html new file mode 100644 index 0000000..861b55b --- /dev/null +++ b/chrome/browser/resources/autofill_ml_internals/autofill_ml_internals.html
@@ -0,0 +1,18 @@ +<!-- Copyright 2025 The Chromium Authors +Use of this source code is governed by a BSD-style license that can be +found in the LICENSE file. --> + +<!doctype html> +<html dir="ltr" lang="en"> + <meta charset="utf-8"> + <link rel="stylesheet" href="chrome://resources/css/text_defaults.css"> + <style> + body { + margin: 0; + } + </style> + <body> + <autofill-ml-internals-app></autofill-ml-internals-app> + <script type="module" src="app.js"></script> + </body> +</html>
diff --git a/chrome/browser/search_engines/android/java/src/org/chromium/chrome/browser/search_engines/choice_screen/ChoiceDialogCoordinator.java b/chrome/browser/search_engines/android/java/src/org/chromium/chrome/browser/search_engines/choice_screen/ChoiceDialogCoordinator.java index ea88d94..2b2a51f 100644 --- a/chrome/browser/search_engines/android/java/src/org/chromium/chrome/browser/search_engines/choice_screen/ChoiceDialogCoordinator.java +++ b/chrome/browser/search_engines/android/java/src/org/chromium/chrome/browser/search_engines/choice_screen/ChoiceDialogCoordinator.java
@@ -47,6 +47,11 @@ public class ChoiceDialogCoordinator implements ChoiceDialogMediator.Delegate { private static final String TAG = "ChoiceDialogCoordntr"; + // Number of blocked Chrome sessions after which we suppress the blocking dialog. This is + // intended as an escape hatch to mitigate potential bugs. + @VisibleForTesting + public static final int ESCAPE_HATCH_BLOCK_LIMIT = 10; + // TODO(b/365100489): Refactor this coordinator to implement the dialog's custom view fully // using the standard chromium MVC patterns. This class is a temporary shortcut. interface ViewHolder { @@ -247,16 +252,14 @@ int blockCount = ChromeSharedPreferences.getInstance() .readInt(SEARCH_ENGINE_CHOICE_PENDING_OS_CHOICE_DIALOG_SHOWN_ATTEMPTS); - int blockLimit = - SearchEnginesFeatureUtils.getInstance().clayBlockingEscapeHatchBlockLimit(); - if (blockCount >= blockLimit) { + if (blockCount >= ESCAPE_HATCH_BLOCK_LIMIT) { if (SearchEnginesFeatureUtils.getInstance().isChoiceApisDebugEnabled()) { Log.i( TAG, "The dialog is suppressed: Escape Hatch triggered, blocked %d times" + " (limit=%d).", blockCount, - blockLimit); + ESCAPE_HATCH_BLOCK_LIMIT); } return DialogSuppressionStatus.SUPPRESSED_ESCAPE_HATCH; }
diff --git a/chrome/browser/search_engines/android/javatests/src/org/chromium/chrome/browser/search_engines/choice_screen/ChoiceScreenRenderTest.java b/chrome/browser/search_engines/android/javatests/src/org/chromium/chrome/browser/search_engines/choice_screen/ChoiceScreenRenderTest.java index 999db35..d3f614e 100644 --- a/chrome/browser/search_engines/android/javatests/src/org/chromium/chrome/browser/search_engines/choice_screen/ChoiceScreenRenderTest.java +++ b/chrome/browser/search_engines/android/javatests/src/org/chromium/chrome/browser/search_engines/choice_screen/ChoiceScreenRenderTest.java
@@ -36,7 +36,6 @@ import org.chromium.base.test.params.ParameterizedRunner; import org.chromium.base.test.util.Batch; import org.chromium.base.test.util.Feature; -import org.chromium.base.test.util.Features; import org.chromium.chrome.browser.lifecycle.ActivityLifecycleDispatcher; import org.chromium.chrome.browser.search_engines.R; import org.chromium.chrome.test.ChromeJUnit4RunnerDelegate; @@ -44,7 +43,6 @@ import org.chromium.components.browser_ui.modaldialog.AppModalPresenter; import org.chromium.components.search_engines.FakeSearchEngineCountryDelegate; import org.chromium.components.search_engines.SearchEngineChoiceService; -import org.chromium.components.search_engines.SearchEnginesFeatures; import org.chromium.ui.modaldialog.ModalDialogManager; import org.chromium.ui.test.util.BlankUiTestActivity; import org.chromium.ui.test.util.NightModeTestUtils; @@ -55,7 +53,6 @@ /** Render tests for {@link ChoiceDialogCoordinator} */ @RunWith(ParameterizedRunner.class) @ParameterAnnotations.UseRunnerDelegate(ChromeJUnit4RunnerDelegate.class) -@Features.EnableFeatures(SearchEnginesFeatures.CLAY_BLOCKING) @Batch(Batch.PER_CLASS) public class ChoiceScreenRenderTest { public @ClassParameter static List<ParameterSet> params = new NightModeParams().getParameters();
diff --git a/chrome/browser/search_engines/android/junit/src/org/chromium/chrome/browser/search_engines/choice_screen/ChoiceDialogCoordinatorUnitTest.java b/chrome/browser/search_engines/android/junit/src/org/chromium/chrome/browser/search_engines/choice_screen/ChoiceDialogCoordinatorUnitTest.java index 1bf0aee..de19e0c 100644 --- a/chrome/browser/search_engines/android/junit/src/org/chromium/chrome/browser/search_engines/choice_screen/ChoiceDialogCoordinatorUnitTest.java +++ b/chrome/browser/search_engines/android/junit/src/org/chromium/chrome/browser/search_engines/choice_screen/ChoiceDialogCoordinatorUnitTest.java
@@ -37,7 +37,6 @@ import org.chromium.base.FakeTimeTestRule; import org.chromium.base.supplier.ObservableSupplierImpl; import org.chromium.base.test.BaseRobolectricTestRunner; -import org.chromium.base.test.util.Features; import org.chromium.base.test.util.HistogramWatcher; import org.chromium.chrome.browser.lifecycle.ActivityLifecycleDispatcher; import org.chromium.chrome.browser.lifecycle.PauseResumeWithNativeObserver; @@ -46,8 +45,6 @@ import org.chromium.chrome.browser.search_engines.choice_screen.ChoiceDialogMediator.DialogType; import org.chromium.chrome.browser.search_engines.choice_screen.ChoiceDialogMediator.LaunchChoiceScreenTapHandlingStatus; import org.chromium.components.search_engines.SearchEngineChoiceService; -import org.chromium.components.search_engines.SearchEnginesFeatureUtils; -import org.chromium.components.search_engines.SearchEnginesFeatures; import org.chromium.ui.modaldialog.DialogDismissalCause; import org.chromium.ui.modaldialog.ModalDialogManager; import org.chromium.ui.modaldialog.ModalDialogManager.ModalDialogManagerObserver; @@ -56,8 +53,9 @@ import org.chromium.ui.modaldialog.ModalDialogProperties; import org.chromium.ui.modelutil.PropertyModel; +import java.util.stream.IntStream; + @RunWith(BaseRobolectricTestRunner.class) -@Features.EnableFeatures(SearchEnginesFeatures.CLAY_BLOCKING) public class ChoiceDialogCoordinatorUnitTest { public @Rule MockitoRule mMockitoRule = MockitoJUnit.rule().strictness(Strictness.STRICT_STUBS); public @Rule FakeTimeTestRule mFakeTimeRule = new FakeTimeTestRule(); @@ -67,7 +65,6 @@ private @Mock ModalDialogManager mModalDialogManager; private @Mock ActivityLifecycleDispatcher mLifecycleDispatcher; private @Mock SearchEngineChoiceService mSearchEngineChoiceService; - private @Mock SearchEnginesFeatureUtils mSearchEnginesFeatureUtils; private @Captor ArgumentCaptor<PropertyModel> mModelCaptor; private @Captor ArgumentCaptor<PauseResumeWithNativeObserver> mLifecycleObserverCaptor; @@ -147,35 +144,44 @@ @Test public void testMaybeShow_doesNotShowEscapeHatch() { - SearchEnginesFeatureUtils.setInstanceForTesting(mSearchEnginesFeatureUtils); - doReturn(1).when(mSearchEnginesFeatureUtils).clayBlockingEscapeHatchBlockLimit(); var shouldShowSupplier = new ObservableSupplierImpl<>(true); doReturn(shouldShowSupplier) .when(mSearchEngineChoiceService) .getIsDeviceChoiceRequiredSupplier(); doReturn(true).when(mSearchEngineChoiceService).isDeviceChoiceDialogEligible(); - // Initial run, the dialog is shown + // For the first 10 runs, the dialog is shown. try (var histogramWatcher = HistogramWatcher.newBuilder() - .expectIntRecord("Search.OsDefaultsChoice.DialogShownAttempt", 1) - .expectIntRecord( + .expectIntRecords( + "Search.OsDefaultsChoice.DialogShownAttempt", + IntStream.rangeClosed( + 1, ChoiceDialogCoordinator.ESCAPE_HATCH_BLOCK_LIMIT) + .toArray()) + .expectIntRecordTimes( "Search.OsDefaultsChoice.DialogSuppressionStatus", - DialogSuppressionStatus.CAN_SHOW) + DialogSuppressionStatus.CAN_SHOW, + ChoiceDialogCoordinator.ESCAPE_HATCH_BLOCK_LIMIT) .build()) { - assertTrue(ChoiceDialogCoordinator.maybeShowInternal(this::createCoordinatorWithMocks)); + for (int i = 1; i <= ChoiceDialogCoordinator.ESCAPE_HATCH_BLOCK_LIMIT; i++) { + assertTrue( + ChoiceDialogCoordinator.maybeShowInternal( + this::createCoordinatorWithMocks)); - shadowOf(Looper.getMainLooper()).idle(); - verify(mModalDialogManager) - .showDialog(any(), eq(ModalDialogType.APP), eq(ModalDialogPriority.VERY_HIGH)); - assertEquals( - 1, - ChromeSharedPreferences.getInstance() - .readInt(SEARCH_ENGINE_CHOICE_PENDING_OS_CHOICE_DIALOG_SHOWN_ATTEMPTS)); + shadowOf(Looper.getMainLooper()).idle(); + verify(mModalDialogManager) + .showDialog( + any(), eq(ModalDialogType.APP), eq(ModalDialogPriority.VERY_HIGH)); + assertEquals( + i, + ChromeSharedPreferences.getInstance() + .readInt( + SEARCH_ENGINE_CHOICE_PENDING_OS_CHOICE_DIALOG_SHOWN_ATTEMPTS)); + reset(mModalDialogManager); + } } - // Second run, the dialog is suppressed - reset(mModalDialogManager); + // On the next run, the dialog is suppressed try (var histogramWatcher = HistogramWatcher.newBuilder() .expectNoRecords("Search.OsDefaultsChoice.DialogShownAttempt") @@ -189,7 +195,7 @@ shadowOf(Looper.getMainLooper()).idle(); verify(mModalDialogManager, never()).showDialog(any(), anyInt(), anyInt()); assertEquals( - 1, + ChoiceDialogCoordinator.ESCAPE_HATCH_BLOCK_LIMIT, ChromeSharedPreferences.getInstance() .readInt(SEARCH_ENGINE_CHOICE_PENDING_OS_CHOICE_DIALOG_SHOWN_ATTEMPTS)); }
diff --git a/chrome/browser/touch_to_fill/autofill/android/internal/java/src/org/chromium/chrome/browser/touch_to_fill/payments/TouchToFillPaymentMethodMediator.java b/chrome/browser/touch_to_fill/autofill/android/internal/java/src/org/chromium/chrome/browser/touch_to_fill/payments/TouchToFillPaymentMethodMediator.java index ac50849..5db390fc 100644 --- a/chrome/browser/touch_to_fill/autofill/android/internal/java/src/org/chromium/chrome/browser/touch_to_fill/payments/TouchToFillPaymentMethodMediator.java +++ b/chrome/browser/touch_to_fill/autofill/android/internal/java/src/org/chromium/chrome/browser/touch_to_fill/payments/TouchToFillPaymentMethodMediator.java
@@ -235,9 +235,10 @@ new FillableItemCollectionInfo(i + 1, mSuggestions.size()), cardImageFunction); sheetItems.add(new ListItem(CREDIT_CARD, model)); - // TODO(crbug.com/423849651): Add null checks. - PaymentsPayload paymentsPayload = (PaymentsPayload) suggestion.getPayload(); - cardBenefitsTermsAvailable |= paymentsPayload.shouldDisplayTermsAvailable(); + PaymentsPayload payload = suggestion.getPaymentsPayload(); + if (payload != null) { + cardBenefitsTermsAvailable |= payload.shouldDisplayTermsAvailable(); + } } if (cardBenefitsTermsAvailable) { @@ -442,9 +443,10 @@ if (!mInputProtector.shouldInputBeProcessed()) return; boolean is_virtual_card = suggestion.getSuggestionType() == SuggestionType.VIRTUAL_CREDIT_CARD_ENTRY; - // TODO(crbug.com/423849651): Add null checks. - PaymentsPayload payload = (PaymentsPayload) suggestion.getPayload(); - mDelegate.creditCardSuggestionSelected(payload.getGuid(), is_virtual_card); + PaymentsPayload payload = suggestion.getPaymentsPayload(); + if (payload != null) { + mDelegate.creditCardSuggestionSelected(payload.getGuid(), is_virtual_card); + } recordTouchToFillCreditCardOutcomeHistogram( is_virtual_card ? TouchToFillCreditCardOutcome.VIRTUAL_CARD @@ -498,15 +500,18 @@ == SuggestionType.VIRTUAL_CREDIT_CARD_ENTRY) ? suggestion.getCustomIconUrl() : new GURL(""); - // TODO(crbug.com/423849651): Add null checks. - PaymentsPayload payload = (PaymentsPayload) suggestion.getPayload(); + String labelDescription = ""; + PaymentsPayload payload = suggestion.getPaymentsPayload(); + if (payload != null) { + labelDescription = payload.getLabelContentDescription(); + } TouchToFillPaymentMethodProperties.CardImageMetaData cardImageMetaData = new TouchToFillPaymentMethodProperties.CardImageMetaData(drawableId, artUrl); PropertyModel.Builder creditCardSuggestionModelBuilder = new PropertyModel.Builder(NON_TRANSFORMING_CREDIT_CARD_SUGGESTION_KEYS) .withTransformingKey(CARD_IMAGE, cardImageFunction, cardImageMetaData) .with(MAIN_TEXT, suggestion.getLabel()) - .with(MAIN_TEXT_CONTENT_DESCRIPTION, payload.getLabelContentDescription()) + .with(MAIN_TEXT_CONTENT_DESCRIPTION, labelDescription) .with(MINOR_TEXT, suggestion.getSecondaryLabel()) // For virtual cards, show the "Virtual card" label on the second // line, and for non-virtual cards, show the expiration date. @@ -651,9 +656,10 @@ private static boolean hasOnlyLocalCards(List<AutofillSuggestion> suggestions) { for (AutofillSuggestion suggestion : suggestions) { - // TODO(crbug.com/423849651): Add null checks. - PaymentsPayload payload = (PaymentsPayload) suggestion.getPayload(); - if (!payload.isLocalPaymentsMethod()) return false; + PaymentsPayload payload = suggestion.getPaymentsPayload(); + if (payload != null && !payload.isLocalPaymentsMethod()) { + return false; + } } return true; }
diff --git a/chrome/browser/touch_to_fill/autofill/android/internal/java/src/org/chromium/chrome/browser/touch_to_fill/payments/TouchToFillPaymentMethodViewTest.java b/chrome/browser/touch_to_fill/autofill/android/internal/java/src/org/chromium/chrome/browser/touch_to_fill/payments/TouchToFillPaymentMethodViewTest.java index df8d5c8..d427773 100644 --- a/chrome/browser/touch_to_fill/autofill/android/internal/java/src/org/chromium/chrome/browser/touch_to_fill/payments/TouchToFillPaymentMethodViewTest.java +++ b/chrome/browser/touch_to_fill/autofill/android/internal/java/src/org/chromium/chrome/browser/touch_to_fill/payments/TouchToFillPaymentMethodViewTest.java
@@ -1203,7 +1203,7 @@ AutofillSuggestion suggestion, FillableItemCollectionInfo collectionInfo, Runnable actionCallback) { - PaymentsPayload payload = (PaymentsPayload) suggestion.getPayload(); + PaymentsPayload payload = suggestion.getPaymentsPayload(); PropertyModel.Builder creditCardSuggestionModelBuilder = new PropertyModel.Builder(NON_TRANSFORMING_CREDIT_CARD_SUGGESTION_KEYS) .with(MAIN_TEXT, suggestion.getLabel())
diff --git a/chrome/browser/ui/BUILD.gn b/chrome/browser/ui/BUILD.gn index 11eb082..85871b8 100644 --- a/chrome/browser/ui/BUILD.gn +++ b/chrome/browser/ui/BUILD.gn
@@ -4918,6 +4918,7 @@ "//chrome/browser/ui/views/intent_picker:intent_picker_page_action", "//chrome/browser/ui/views/page_action", "//chrome/browser/ui/webui/app_service_internals", + "//chrome/browser/ui/webui/autofill_ml_internals", "//chrome/browser/ui/webui/side_panel/customize_chrome", "//components/collaboration/public", "//components/commerce/core:metrics",
diff --git a/chrome/browser/ui/content_settings/content_setting_bubble_model.cc b/chrome/browser/ui/content_settings/content_setting_bubble_model.cc index f53fad8..6f2de65f 100644 --- a/chrome/browser/ui/content_settings/content_setting_bubble_model.cc +++ b/chrome/browser/ui/content_settings/content_setting_bubble_model.cc
@@ -1791,13 +1791,9 @@ switch (request_type) { case permissions::RequestType::kNotifications: delegate()->ShowContentSettingsPage(ContentSettingsType::NOTIFICATIONS); - base::RecordAction(base::UserMetricsAction( - "Permissions.Prompt.QuietBubble.Notifications.ManageClicked")); break; case permissions::RequestType::kGeolocation: delegate()->ShowContentSettingsPage(ContentSettingsType::GEOLOCATION); - base::RecordAction(base::UserMetricsAction( - "Permissions.Prompt.QuietBubble.Geolocation.ManageClicked")); break; default: NOTREACHED(); @@ -1845,10 +1841,6 @@ case QuietUiReason::kServicePredictedVeryUnlikelyGrant: case QuietUiReason::kOnDevicePredictedVeryUnlikelyGrant: manager->Accept(); - base::RecordAction(base::UserMetricsAction( - request_type == permissions::RequestType::kNotifications - ? "Permissions.Prompt.QuietBubble.Notifications.AllowClicked" - : "Permissions.Prompt.QuietBubble.Geolocation.AllowClicked")); break; case QuietUiReason::kTriggeredDueToAbusiveRequests: case QuietUiReason::kTriggeredDueToAbusiveContent:
diff --git a/chrome/browser/ui/extensions/extensions_overrides/simple_overrides_unittest.cc b/chrome/browser/ui/extensions/extensions_overrides/simple_overrides_unittest.cc index 29d4336..a6f0b35 100644 --- a/chrome/browser/ui/extensions/extensions_overrides/simple_overrides_unittest.cc +++ b/chrome/browser/ui/extensions/extensions_overrides/simple_overrides_unittest.cc
@@ -80,6 +80,7 @@ extensions::manifest_keys::kPermissions, extensions::manifest_keys::kPlatformAppBackground, extensions::manifest_keys::kPlatformAppContentSecurityPolicy, + extensions::manifest_keys::kProtocolHandlers, extensions::manifest_keys::kReplacementWebApp, extensions::manifest_keys::kSockets, extensions::manifest_keys::kTheme,
diff --git a/chrome/browser/ui/views/permissions/chip/chip_controller.cc b/chrome/browser/ui/views/permissions/chip/chip_controller.cc index 70ce0ae..23f0cd0 100644 --- a/chrome/browser/ui/views/permissions/chip/chip_controller.cc +++ b/chrome/browser/ui/views/permissions/chip/chip_controller.cc
@@ -744,9 +744,6 @@ if (permission_prompt_model_->GetPromptStyle() == PermissionPromptStyle::kChip) { RecordRequestChipButtonPressed("Permissions.Chip.TimeToInteraction"); - } else if (permission_prompt_model_->GetPromptStyle() == - PermissionPromptStyle::kQuietChip) { - RecordRequestChipButtonPressed("Permissions.QuietChip.TimeToInteraction"); } }
diff --git a/chrome/browser/ui/views/profiles/avatar_toolbar_button.cc b/chrome/browser/ui/views/profiles/avatar_toolbar_button.cc index 5cf580c..6734116 100644 --- a/chrome/browser/ui/views/profiles/avatar_toolbar_button.cc +++ b/chrome/browser/ui/views/profiles/avatar_toolbar_button.cc
@@ -508,8 +508,8 @@ CreateScopedInfiniteDelayOverrideForTesting(delay_type); } -void AvatarToolbarButton::TriggerTimeoutForTesting(AvatarDelayType delay_type) { - delegate_->TriggerTimeoutForTesting(delay_type); // IN-TEST +void AvatarToolbarButton::ClearActiveStateForTesting() { + delegate_->ClearActiveStateForTesting(); // IN-TEST } #if BUILDFLAG(ENABLE_DICE_SUPPORT)
diff --git a/chrome/browser/ui/views/profiles/avatar_toolbar_button.h b/chrome/browser/ui/views/profiles/avatar_toolbar_button.h index 04176048..4ffdf8ba 100644 --- a/chrome/browser/ui/views/profiles/avatar_toolbar_button.h +++ b/chrome/browser/ui/views/profiles/avatar_toolbar_button.h
@@ -143,8 +143,8 @@ // after delay expiry while controlling those events.. [[nodiscard]] static base::AutoReset<std::optional<base::TimeDelta>> CreateScopedInfiniteDelayOverrideForTesting(AvatarDelayType delay_type); - // Force stop any ongoing delay, this expects the proper state to be active. - void TriggerTimeoutForTesting(AvatarDelayType delay_type); + // Clears the active state (makes it inactive). + void ClearActiveStateForTesting(); #if BUILDFLAG(ENABLE_DICE_SUPPORT) // Specific override for the SigninPending text delay. Setting a zero value // make it possible to test the creation of browser after the delay has
diff --git a/chrome/browser/ui/views/profiles/avatar_toolbar_button_browsertest.cc b/chrome/browser/ui/views/profiles/avatar_toolbar_button_browsertest.cc index d9cda1f..33e9db95 100644 --- a/chrome/browser/ui/views/profiles/avatar_toolbar_button_browsertest.cc +++ b/chrome/browser/ui/views/profiles/avatar_toolbar_button_browsertest.cc
@@ -187,7 +187,7 @@ // By default make all delays infinite to avoid flakiness. The tests that // needs to test bypass the delay effects will have to enforce timing out // the delays using - // `AvatarToolbarButton::TriggerTimeoutForTesting()`. This allows to + // `AvatarToolbarButton::ClearActiveStateForTesting()`. This allows to // properly test the behavior pre/post delay without being time dependent. SetInfiniteAvatarDelay(AvatarDelayType::kNameGreeting); #if BUILDFLAG(ENABLE_DICE_SUPPORT) @@ -210,8 +210,8 @@ // Allows overriding the delay of different events that have a timing // duration. Sets the delay to infinite in order to be able to test the - // behavior while the delay is happening. In order to stop the delay, use - // `AvatarToolbarButton::TriggerTimeoutForTesting()` at any point. + // behavior while the delay is happening. In order to clear the current state, + // use `AvatarToolbarButton::ClearActiveStateForTesting()` at any point. void SetInfiniteAvatarDelay(AvatarDelayType delay_type) { delay_resets_.push_back( AvatarToolbarButton::CreateScopedInfiniteDelayOverrideForTesting( @@ -344,7 +344,7 @@ const std::u16string& email, const std::u16string& name = u"account_name") { AccountInfo account_info = SigninWithImage(email, name); - avatar->TriggerTimeoutForTesting(AvatarDelayType::kNameGreeting); + avatar->ClearActiveStateForTesting(); // Make sure the cross window animation replay is not triggered. This is // needed to clear the animation in all windows. delay_resets_.push_back( @@ -360,7 +360,7 @@ #if BUILDFLAG(ENABLE_DICE_SUPPORT) if (base::FeatureList::IsEnabled( switches::kEnableHistorySyncOptinExpansionPill)) { - avatar->TriggerTimeoutForTesting(AvatarDelayType::kHistorySyncOptin); + avatar->ClearActiveStateForTesting(); } #endif // BUILDFLAG(ENABLE_DICE_SUPPORT) } @@ -431,7 +431,7 @@ AccountInfo EnableSyncWithImageAndClearGreeting(AvatarToolbarButton* avatar, const std::u16string& email) { AccountInfo account_info = EnableSyncWithImage(email); - avatar->TriggerTimeoutForTesting(AvatarDelayType::kNameGreeting); + avatar->ClearActiveStateForTesting(); // Make sure the cross window animation replay is not triggered. This is // needed to clear the animation in all windows. delay_resets_.push_back( @@ -713,7 +713,7 @@ EXPECT_EQ(avatar->GetText(), l10n_util::GetStringFUTF16(IDS_AVATAR_BUTTON_GREETING, name)); - avatar->TriggerTimeoutForTesting(AvatarDelayType::kNameGreeting); + avatar->ClearActiveStateForTesting(); ClearHistorySyncOptinPromoIfEnabled(avatar); // Once the name is not shown anymore, we expect no text. EXPECT_EQ(avatar->GetText(), std::u16string()); @@ -745,7 +745,7 @@ EXPECT_EQ(avatar->GetText(), l10n_util::GetStringFUTF16(IDS_AVATAR_BUTTON_GREETING, name)); - avatar->TriggerTimeoutForTesting(AvatarDelayType::kNameGreeting); + avatar->ClearActiveStateForTesting(); // Once the name is not shown anymore, we expect no text. EXPECT_EQ(avatar->GetText(), std::u16string()); } @@ -993,7 +993,7 @@ EXPECT_EQ(avatar->GetRenderedTooltipText(gfx::Point()), account_name); - avatar->TriggerTimeoutForTesting(AvatarDelayType::kNameGreeting); + avatar->ClearActiveStateForTesting(); // Tooltip is the same after hiding the name. EXPECT_EQ(avatar->GetRenderedTooltipText(gfx::Point()), account_name); @@ -1089,7 +1089,7 @@ ASSERT_EQ(avatar->GetText(), l10n_util::GetStringFUTF16(IDS_AVATAR_BUTTON_GREETING, base::UTF8ToUTF16(account.given_name))); - avatar->TriggerTimeoutForTesting(AvatarDelayType::kNameGreeting); + avatar->ClearActiveStateForTesting(); // The greeting should NOT be followed by the history sync opt-in entry point // if sync is already enabled. EXPECT_TRUE(avatar->GetText().empty()); @@ -1123,7 +1123,7 @@ ASSERT_EQ(avatar->GetText(), l10n_util::GetStringFUTF16(IDS_AVATAR_BUTTON_GREETING, base::UTF8ToUTF16(account.given_name))); - avatar->TriggerTimeoutForTesting(AvatarDelayType::kNameGreeting); + avatar->ClearActiveStateForTesting(); // The greeting should NOT be followed by the history sync opt-in entry point // if promotions are disabled. EXPECT_TRUE(avatar->GetText().empty()); @@ -1147,7 +1147,7 @@ SigninWithImage(/*email=*/u"test@gmail.com", account_name); ASSERT_EQ(avatar->GetText(), l10n_util::GetStringFUTF16( IDS_AVATAR_BUTTON_GREETING, account_name)); - avatar->TriggerTimeoutForTesting(AvatarDelayType::kNameGreeting); + avatar->ClearActiveStateForTesting(); // The greeting should NOT be followed by the history sync opt-in entry point // if sync is not allowed. EXPECT_TRUE(avatar->GetText().empty()); @@ -1194,7 +1194,7 @@ SigninWithImage(/*email=*/u"test@gmail.com", account_name); ASSERT_EQ(avatar->GetText(), l10n_util::GetStringFUTF16( IDS_AVATAR_BUTTON_GREETING, account_name)); - avatar->TriggerTimeoutForTesting(AvatarDelayType::kNameGreeting); + avatar->ClearActiveStateForTesting(); // The greeting should NOT be followed by the history sync opt-in entry point // if sync is not allowed. EXPECT_TRUE(avatar->GetText().empty()); @@ -1241,7 +1241,7 @@ SigninWithImage(/*email=*/u"test@gmail.com", account_name); ASSERT_EQ(avatar->GetText(), l10n_util::GetStringFUTF16( IDS_AVATAR_BUTTON_GREETING, account_name)); - avatar->TriggerTimeoutForTesting(AvatarDelayType::kNameGreeting); + avatar->ClearActiveStateForTesting(); // The greeting should be followed by the history sync opt-in entry point. EXPECT_EQ(avatar->GetText(), l10n_util::GetStringUTF16(IDS_AVATAR_BUTTON_BROWSE_ACROSS_DEVICES)); @@ -1273,7 +1273,7 @@ SigninWithImage(/*email=*/u"test@gmail.com", account_name); ASSERT_EQ(avatar->GetText(), l10n_util::GetStringFUTF16( IDS_AVATAR_BUTTON_GREETING, account_name)); - avatar->TriggerTimeoutForTesting(AvatarDelayType::kNameGreeting); + avatar->ClearActiveStateForTesting(); // The greeting should be followed by the history sync opt-in entry point. EXPECT_EQ(avatar->GetText(), l10n_util::GetStringUTF16(IDS_AVATAR_BUTTON_BROWSE_ACROSS_DEVICES)); @@ -1305,7 +1305,7 @@ SigninWithImage(/*email=*/u"test@gmail.com", account_name); ASSERT_EQ(avatar->GetText(), l10n_util::GetStringFUTF16( IDS_AVATAR_BUTTON_GREETING, account_name)); - avatar->TriggerTimeoutForTesting(AvatarDelayType::kNameGreeting); + avatar->ClearActiveStateForTesting(); // The greeting should be followed by the history sync opt-in entry point. EXPECT_EQ(avatar->GetText(), l10n_util::GetStringUTF16(IDS_AVATAR_BUTTON_BROWSE_ACROSS_DEVICES)); @@ -1336,7 +1336,7 @@ SigninWithImage(/*email=*/u"test@gmail.com", account_name); ASSERT_EQ(avatar->GetText(), l10n_util::GetStringFUTF16( IDS_AVATAR_BUTTON_GREETING, account_name)); - avatar->TriggerTimeoutForTesting(AvatarDelayType::kNameGreeting); + avatar->ClearActiveStateForTesting(); // The greeting should be followed by the history sync opt-in entry point. EXPECT_EQ(avatar->GetText(), l10n_util::GetStringUTF16(IDS_AVATAR_BUTTON_BROWSE_ACROSS_DEVICES)); @@ -1372,7 +1372,7 @@ ASSERT_EQ(avatar->GetText(), l10n_util::GetStringFUTF16( IDS_AVATAR_BUTTON_GREETING, account_name)); SimulatePassphraseError(); - avatar->TriggerTimeoutForTesting(AvatarDelayType::kNameGreeting); + avatar->ClearActiveStateForTesting(); // No history sync opt-in entry point should be shown if the error is shown // before the greeting times out. EXPECT_EQ(avatar->GetText(), l10n_util::GetStringUTF16( @@ -1418,7 +1418,7 @@ const AccountInfo account_info = SigninWithImage(email, account_name); EXPECT_EQ(avatar->GetText(), l10n_util::GetStringFUTF16( IDS_AVATAR_BUTTON_GREETING, account_name)); - avatar->TriggerTimeoutForTesting(AvatarDelayType::kNameGreeting); + avatar->ClearActiveStateForTesting(); // The greeting should be followed by the history sync opt-in entry point. EXPECT_EQ( avatar->GetText(), @@ -1444,7 +1444,7 @@ const AccountInfo account_info = SigninWithImage(email, account_name); EXPECT_EQ(avatar->GetText(), l10n_util::GetStringFUTF16( IDS_AVATAR_BUTTON_GREETING, account_name)); - avatar->TriggerTimeoutForTesting(AvatarDelayType::kNameGreeting); + avatar->ClearActiveStateForTesting(); // The greeting should be followed by the history sync opt-in entry point. EXPECT_EQ( avatar->GetText(), @@ -1472,12 +1472,12 @@ const AccountInfo account_info = SigninWithImage(email, account_name); EXPECT_EQ(avatar->GetText(), l10n_util::GetStringFUTF16( IDS_AVATAR_BUTTON_GREETING, account_name)); - avatar->TriggerTimeoutForTesting(AvatarDelayType::kNameGreeting); + avatar->ClearActiveStateForTesting(); // The greeting should be followed by the history sync opt-in entry point. EXPECT_EQ( avatar->GetText(), l10n_util::GetStringUTF16(GetParam().expected_history_sync_message_id)); - avatar->TriggerTimeoutForTesting(AvatarDelayType::kHistorySyncOptin); + avatar->ClearActiveStateForTesting(); // The button should return to the normal state. EXPECT_TRUE(avatar->GetText().empty()); } @@ -1493,12 +1493,12 @@ ASSERT_EQ( avatar->GetText(), l10n_util::GetStringFUTF16(IDS_AVATAR_BUTTON_GREETING, u"Account name")); - avatar->TriggerTimeoutForTesting(AvatarDelayType::kNameGreeting); + avatar->ClearActiveStateForTesting(); // The greeting should be followed by the history sync opt-in entry point. EXPECT_EQ( avatar->GetText(), l10n_util::GetStringUTF16(GetParam().expected_history_sync_message_id)); - avatar->TriggerTimeoutForTesting(AvatarDelayType::kHistorySyncOptin); + avatar->ClearActiveStateForTesting(); // The button should return to the normal state. EXPECT_TRUE(avatar->GetText().empty()); } @@ -1531,12 +1531,12 @@ AddSignedInImage(account_info.account_id); EXPECT_EQ(avatar->GetText(), l10n_util::GetStringFUTF16( IDS_AVATAR_BUTTON_GREETING, account_name)); - avatar->TriggerTimeoutForTesting(AvatarDelayType::kNameGreeting); + avatar->ClearActiveStateForTesting(); // The greeting should be followed by the history sync opt-in entry point. EXPECT_EQ( avatar->GetText(), l10n_util::GetStringUTF16(GetParam().expected_history_sync_message_id)); - avatar->TriggerTimeoutForTesting(AvatarDelayType::kHistorySyncOptin); + avatar->ClearActiveStateForTesting(); // Once the history sync opt-in entry point collapses, the button should // return to the normal state. EXPECT_TRUE(avatar->GetText().empty()); @@ -1548,7 +1548,7 @@ EXPECT_EQ( avatar->GetText(), l10n_util::GetStringUTF16(GetParam().expected_history_sync_message_id)); - avatar->TriggerTimeoutForTesting(AvatarDelayType::kHistorySyncOptin); + avatar->ClearActiveStateForTesting(); // Once the history sync opt-in entry point collapses, the button should // return to the normal state. EXPECT_TRUE(avatar->GetText().empty()); @@ -1580,13 +1580,13 @@ SigninWithImage(/*email=*/u"test@gmail.com", account_name_1); ASSERT_EQ(avatar->GetText(), l10n_util::GetStringFUTF16( IDS_AVATAR_BUTTON_GREETING, account_name_1)); - avatar->TriggerTimeoutForTesting(AvatarDelayType::kNameGreeting); + avatar->ClearActiveStateForTesting(); // The greeting should be followed by the history sync opt-in entry point. EXPECT_EQ( avatar->GetText(), l10n_util::GetStringUTF16(GetParam().expected_history_sync_message_id)); int shown_count = 1; - avatar->TriggerTimeoutForTesting(AvatarDelayType::kHistorySyncOptin); + avatar->ClearActiveStateForTesting(); // The button comes back to the normal state. EXPECT_TRUE(avatar->GetText().empty()); for (; shown_count < user_education::features::GetNewBadgeShowCount(); @@ -1597,7 +1597,7 @@ EXPECT_EQ( avatar->GetText(), l10n_util::GetStringUTF16(GetParam().expected_history_sync_message_id)); - avatar->TriggerTimeoutForTesting(AvatarDelayType::kHistorySyncOptin); + avatar->ClearActiveStateForTesting(); // The button comes back to the normal state. EXPECT_TRUE(avatar->GetText().empty()); } @@ -1612,7 +1612,7 @@ SigninWithImage(/*email=*/u"test2@gmail.com", account_name_2); ASSERT_EQ(avatar->GetText(), l10n_util::GetStringFUTF16( IDS_AVATAR_BUTTON_GREETING, account_name_2)); - avatar->TriggerTimeoutForTesting(AvatarDelayType::kNameGreeting); + avatar->ClearActiveStateForTesting(); // The greeting should be followed by the history sync opt-in entry point // (rate limiting is per account). EXPECT_EQ( @@ -1695,7 +1695,7 @@ SigninWithImage(/*email=*/u"test@gmail.com", account_name); ASSERT_EQ(avatar->GetText(), l10n_util::GetStringFUTF16( IDS_AVATAR_BUTTON_GREETING, account_name)); - avatar->TriggerTimeoutForTesting(AvatarDelayType::kNameGreeting); + avatar->ClearActiveStateForTesting(); // The greeting should be followed by the history sync opt-in entry point. EXPECT_EQ( avatar->GetText(), @@ -1760,7 +1760,7 @@ SigninWithImage(/*email=*/u"test@gmail.com", account_name); ASSERT_EQ(avatar->GetText(), l10n_util::GetStringFUTF16( IDS_AVATAR_BUTTON_GREETING, account_name)); - avatar->TriggerTimeoutForTesting(AvatarDelayType::kNameGreeting); + avatar->ClearActiveStateForTesting(); // The greeting should be followed by the history sync opt-in entry point. EXPECT_EQ( avatar->GetText(), @@ -1775,7 +1775,7 @@ "Signin.SyncOptIn.IdentityPill.Shown", signin_metrics::AccessPoint::kHistorySyncOptinExpansionPillOnInactivity, /*expected_count=*/0); - avatar->TriggerTimeoutForTesting(AvatarDelayType::kHistorySyncOptin); + avatar->ClearActiveStateForTesting(); // The button comes back to the normal state. EXPECT_TRUE(avatar->GetText().empty()); // Simulate inactivity for enough time to trigger the new session. @@ -1844,7 +1844,7 @@ SigninWithImage(/*email=*/u"test@gmail.com", account_name_1); ASSERT_EQ(avatar->GetText(), l10n_util::GetStringFUTF16( IDS_AVATAR_BUTTON_GREETING, account_name_1)); - avatar->TriggerTimeoutForTesting(AvatarDelayType::kNameGreeting); + avatar->ClearActiveStateForTesting(); // The greeting should be followed by the history sync opt-in entry point. EXPECT_EQ( avatar->GetText(), @@ -1877,7 +1877,7 @@ SigninWithImage(/*email=*/u"test2@gmail.com", account_name_2); ASSERT_EQ(avatar->GetText(), l10n_util::GetStringFUTF16( IDS_AVATAR_BUTTON_GREETING, account_name_2)); - avatar->TriggerTimeoutForTesting(AvatarDelayType::kNameGreeting); + avatar->ClearActiveStateForTesting(); // The greeting should be followed by the history sync opt-in entry point // (rate limiting is per account). EXPECT_EQ( @@ -1911,7 +1911,7 @@ SigninWithImage(/*email=*/u"test@gmail.com", account_name); ASSERT_EQ(avatar_1->GetText(), l10n_util::GetStringFUTF16( IDS_AVATAR_BUTTON_GREETING, account_name)); - avatar_1->TriggerTimeoutForTesting(AvatarDelayType::kNameGreeting); + avatar_1->ClearActiveStateForTesting(); // The greeting should be followed by the history sync opt-in. EXPECT_EQ( @@ -1934,7 +1934,7 @@ "Signin.SyncOptIn.IdentityPill.Shown", signin_metrics::AccessPoint::kHistorySyncOptinExpansionPillOnInactivity, /*expected_count=*/0); - avatar_1->TriggerTimeoutForTesting(AvatarDelayType::kHistorySyncOptin); + avatar_1->ClearActiveStateForTesting(); // The button in both browsers comes back to the normal state. EXPECT_TRUE(avatar_1->GetText().empty()); EXPECT_TRUE(avatar_2->GetText().empty()); @@ -2352,7 +2352,7 @@ enterprise_util::SetUserAcceptedAccountManagement(browser()->profile(), true); EXPECT_EQ(avatar_button->GetText(), work_label); - EnableSyncWithImageAndClearGreeting(avatar_button, u"work@managed.com"); + EnableSyncWithImage(u"work@managed.com"); SimulateSyncPaused(); // Sync Paused has priority over the Work badge. ExpectSyncPaused(avatar_button); @@ -2436,12 +2436,12 @@ // shown. EXPECT_EQ(avatar->GetText(), l10n_util::GetStringFUTF16(IDS_AVATAR_BUTTON_GREETING, name)); - avatar->TriggerTimeoutForTesting(AvatarDelayType::kNameGreeting); + avatar->ClearActiveStateForTesting(); if (IsParamFeatureEnabled()) { // The greeting is followed by the history sync opt-in. EXPECT_EQ(avatar->GetText(), l10n_util::GetStringUTF16( IDS_AVATAR_BUTTON_BROWSE_ACROSS_DEVICES)); - avatar->TriggerTimeoutForTesting(AvatarDelayType::kHistorySyncOptin); + avatar->ClearActiveStateForTesting(); } // Once the name (or sync promo) is not shown anymore, we expect no text. EXPECT_EQ(avatar->GetText(), std::u16string()); @@ -2450,11 +2450,15 @@ // TODO(crbug.com/331746545): Check flaky test issue on windows. // TODO(crbug.com/331746545): Check flaky test issue on windows. #if BUILDFLAG(IS_WIN) -#define MAYBE_PRE_SignedInWithNewSessionKeepWorkBadge DISABLED_PRE_SignedInWithNewSessionKeepWorkBadge -#define MAYBE_SignedInWithNewSessionKeepWorkBadge DISABLED_SignedInWithNewSessionKeepWorkBadge +#define MAYBE_PRE_SignedInWithNewSessionKeepWorkBadge \ + DISABLED_PRE_SignedInWithNewSessionKeepWorkBadge +#define MAYBE_SignedInWithNewSessionKeepWorkBadge \ + DISABLED_SignedInWithNewSessionKeepWorkBadge #else -#define MAYBE_PRE_SignedInWithNewSessionKeepWorkBadge PRE_SignedInWithNewSessionKeepWorkBadge -#define MAYBE_SignedInWithNewSessionKeepWorkBadge SignedInWithNewSessionKeepWorkBadge +#define MAYBE_PRE_SignedInWithNewSessionKeepWorkBadge \ + PRE_SignedInWithNewSessionKeepWorkBadge +#define MAYBE_SignedInWithNewSessionKeepWorkBadge \ + SignedInWithNewSessionKeepWorkBadge #endif // Tests the flow for a managed sign-in. IN_PROC_BROWSER_TEST_P( @@ -2469,12 +2473,12 @@ // shown. EXPECT_EQ(avatar->GetText(), l10n_util::GetStringFUTF16(IDS_AVATAR_BUTTON_GREETING, name)); - avatar->TriggerTimeoutForTesting(AvatarDelayType::kNameGreeting); + avatar->ClearActiveStateForTesting(); if (IsParamFeatureEnabled()) { // The greeting is followed by the history sync opt-in. EXPECT_EQ(avatar->GetText(), l10n_util::GetStringUTF16( IDS_AVATAR_BUTTON_BROWSE_ACROSS_DEVICES)); - avatar->TriggerTimeoutForTesting(AvatarDelayType::kHistorySyncOptin); + avatar->ClearActiveStateForTesting(); } // Once the name (or sync promo) is not shown anymore, we expect no text since @@ -2505,12 +2509,12 @@ // the management acceptance after restart). EXPECT_EQ(avatar->GetText(), l10n_util::GetStringFUTF16( IDS_AVATAR_BUTTON_GREETING, u"TestName")); - avatar->TriggerTimeoutForTesting(AvatarDelayType::kNameGreeting); + avatar->ClearActiveStateForTesting(); if (IsParamFeatureEnabled()) { // The greeting is followed by the history sync opt-in. EXPECT_EQ(avatar->GetText(), l10n_util::GetStringUTF16( IDS_AVATAR_BUTTON_BROWSE_ACROSS_DEVICES)); - avatar->TriggerTimeoutForTesting(AvatarDelayType::kHistorySyncOptin); + avatar->ClearActiveStateForTesting(); } enterprise_util::SetUserAcceptedAccountManagement(browser()->profile(), @@ -2557,7 +2561,7 @@ if (IsParamFeatureEnabled()) { EXPECT_EQ(avatar->GetText(), l10n_util::GetStringUTF16( IDS_AVATAR_BUTTON_BROWSE_ACROSS_DEVICES)); - avatar->TriggerTimeoutForTesting(AvatarDelayType::kHistorySyncOptin); + avatar->ClearActiveStateForTesting(); } EXPECT_EQ(avatar->GetText(), u"Work"); } @@ -2636,11 +2640,9 @@ EXPECT_EQ(new_browser_avatar_button->GetText(), std::u16string()); // Simulate all the timer ends. - avatar->TriggerTimeoutForTesting(AvatarDelayType::kSigninPendingText); - opened_browser_avatar_button->TriggerTimeoutForTesting( - AvatarDelayType::kSigninPendingText); - new_browser_avatar_button->TriggerTimeoutForTesting( - AvatarDelayType::kSigninPendingText); + avatar->ClearActiveStateForTesting(); + opened_browser_avatar_button->ClearActiveStateForTesting(); + new_browser_avatar_button->ClearActiveStateForTesting(); EXPECT_EQ(avatar->GetText(), l10n_util::GetStringUTF16(IDS_AVATAR_BUTTON_SIGNIN_PAUSED)); @@ -2673,7 +2675,7 @@ l10n_util::GetStringFUTF16(IDS_AVATAR_BUTTON_GREETING, std::u16string(kGivenName))); - avatar->TriggerTimeoutForTesting(AvatarDelayType::kNameGreeting); + avatar->ClearActiveStateForTesting(); // The error text is expected to be shown even if the error delay has not // reached yet. EXPECT_EQ(avatar->GetText(),
diff --git a/chrome/browser/ui/views/profiles/avatar_toolbar_button_delegate.cc b/chrome/browser/ui/views/profiles/avatar_toolbar_button_delegate.cc index 5419d6a..d8b56e3 100644 --- a/chrome/browser/ui/views/profiles/avatar_toolbar_button_delegate.cc +++ b/chrome/browser/ui/views/profiles/avatar_toolbar_button_delegate.cc
@@ -260,6 +260,8 @@ explicit StateProvider(Profile* profile, StateObserver* state_observer) : profile_(*profile), state_observer_(*state_observer) {} + virtual ~StateProvider() = default; + // TODO(b/324018028): Consider changing `IsActive()` to be non-virtual and // return a member variable `is_active_` that can be controlled by the derived // classes that sets the active/inactive state when needed, also requesting @@ -333,7 +335,9 @@ // it should call this method to attempt to propagate the changes. void RequestUpdate() { state_observer_->OnStateProviderUpdateRequest(this); } - virtual ~StateProvider() = default; + // Clears the state (makes it inactive). Should be used only for testing + // purposes. + virtual void ClearForTesting() { NOTREACHED(); } protected: Profile& profile() const { return profile_.get(); } @@ -555,6 +559,8 @@ return GetShortProfileName(profile()); } + void ClearForTesting() override { OnIdentityAnimationTimeout(); } + // IdentityManager::Observer: // Needed if the first sync promo account should be displayed. void OnPrimaryAccountChanged( @@ -622,8 +628,6 @@ MaybeShowIdentityName(); } - void ForceDelayTimeoutForTesting() { OnIdentityAnimationTimeout(); } - private: // Initiates showing the identity. void OnUserIdentityChanged() { @@ -761,7 +765,7 @@ Collapse(); } - void ForceDelayTimeoutForTesting() { Collapse(); } + void ClearForTesting() { Collapse(); } // StateManagerObserver: void OnButtonStateChanged(std::optional<ButtonState> old_state, @@ -991,9 +995,7 @@ base::Unretained(this)); } - void ForceDelayTimeoutForTesting() { - coordinator_->ForceDelayTimeoutForTesting(); - } + void ClearForTesting() override { coordinator_->ClearForTesting(); } private: void OnButtonClick(bool is_source_accelerator) { @@ -1366,14 +1368,14 @@ return {kColorToolbarInkDropHover, kColorAvatarButtonNormalRipple}; } - // Only show the text when the delay timer is not running. - bool ShouldShowText() const { return !display_text_delay_timer_.IsRunning(); } - - void ForceTimerTimeoutForTesting() { + void ClearForTesting() override { display_text_delay_timer_.FireNow(); display_text_delay_timer_.Stop(); } + // Only show the text when the delay timer is not running. + bool ShouldShowText() const { return !display_text_delay_timer_.IsRunning(); } + private: // signin::IdentityManager::Observer: void OnErrorStateOfRefreshTokenUpdatedForAccount( @@ -1589,12 +1591,6 @@ // Special setter for the explicit state as it is controlled externally. void SetExplicitStateProvider( std::unique_ptr<ExplicitStateProvider> explicit_state_provider) { - if (auto it = states_.find(ButtonState::kExplicitTextShowing); - it != states_.end()) { - // Attempt to clear existing states if not already done. - static_cast<ExplicitStateProvider*>(it->second.get())->Clear(); - } - // Invalidate the pointer as the map will reorder it's element when adding a // new state and the pointer will not be valid anymore. The value will be // set later again with `ComputeButtonActiveState()`. @@ -2087,42 +2083,11 @@ } } -void AvatarToolbarButtonDelegate::TriggerTimeoutForTesting( - AvatarDelayType delay_type) { - switch (delay_type) { - case AvatarDelayType::kNameGreeting: - if (state_manager_->GetButtonActiveState() == - ButtonState::kShowIdentityName) { - auto* show_identity_state = - static_cast<internal::ShowIdentityNameStateProvider*>( - state_manager_->GetActiveStateProvider()); - CHECK(show_identity_state); - show_identity_state->ForceDelayTimeoutForTesting(); // IN-TEST - } - break; -#if BUILDFLAG(ENABLE_DICE_SUPPORT) - case AvatarDelayType::kSigninPendingText: - if (state_manager_->GetButtonActiveState() == - ButtonState::kSigninPending) { - auto* signin_pending_state = - static_cast<internal::SigninPendingStateProvider*>( - state_manager_->GetActiveStateProvider()); - CHECK(signin_pending_state); - signin_pending_state->ForceTimerTimeoutForTesting(); // IN-TEST - } - break; - case AvatarDelayType::kHistorySyncOptin: - if (state_manager_->GetButtonActiveState() == - ButtonState::kHistorySyncOptin) { - auto* history_sync_optin_state = - static_cast<internal::HistorySyncOptinStateProvider*>( - state_manager_->GetActiveStateProvider()); - CHECK(history_sync_optin_state); - history_sync_optin_state->ForceDelayTimeoutForTesting(); // IN-TEST - } - break; -#endif // BUILDFLAG(ENABLE_DICE_SUPPORT) - } +void AvatarToolbarButtonDelegate::ClearActiveStateForTesting() { + internal::StateProvider* active_state_provider = + state_manager_->GetActiveStateProvider(); + CHECK(active_state_provider); + active_state_provider->ClearForTesting(); } // static
diff --git a/chrome/browser/ui/views/profiles/avatar_toolbar_button_delegate.h b/chrome/browser/ui/views/profiles/avatar_toolbar_button_delegate.h index c04fc819..ba7060d 100644 --- a/chrome/browser/ui/views/profiles/avatar_toolbar_button_delegate.h +++ b/chrome/browser/ui/views/profiles/avatar_toolbar_button_delegate.h
@@ -97,7 +97,7 @@ // Testing functions: check `AvatarToolbarButton` equivalent functions. [[nodiscard]] static base::AutoReset<std::optional<base::TimeDelta>> CreateScopedInfiniteDelayOverrideForTesting(AvatarDelayType delay_type); - void TriggerTimeoutForTesting(AvatarDelayType delay_type); + void ClearActiveStateForTesting(); #if BUILDFLAG(ENABLE_DICE_SUPPORT) [[nodiscard]] static base::AutoReset<std::optional<base::TimeDelta>> CreateScopedZeroDelayOverrideSigninPendingTextForTesting();
diff --git a/chrome/browser/ui/webui/BUILD.gn b/chrome/browser/ui/webui/BUILD.gn index fb70459..0781c82d 100644 --- a/chrome/browser/ui/webui/BUILD.gn +++ b/chrome/browser/ui/webui/BUILD.gn
@@ -57,6 +57,7 @@ "//chrome/browser/ui/webui/access_code_cast", "//chrome/browser/ui/webui/actor_internals", "//chrome/browser/ui/webui/app_service_internals", + "//chrome/browser/ui/webui/autofill_ml_internals", "//chrome/browser/ui/webui/infobar_internals", "//chrome/browser/ui/webui/new_tab_footer", "//chrome/browser/ui/webui/privacy_sandbox",
diff --git a/chrome/browser/ui/webui/autofill_ml_internals/BUILD.gn b/chrome/browser/ui/webui/autofill_ml_internals/BUILD.gn new file mode 100644 index 0000000..22996bb1 --- /dev/null +++ b/chrome/browser/ui/webui/autofill_ml_internals/BUILD.gn
@@ -0,0 +1,28 @@ +# Copyright 2025 The Chromium Authors +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +assert(!is_android) +source_set("autofill_ml_internals") { + sources = [ + "autofill_ml_internals_page_handler.cc", + "autofill_ml_internals_page_handler.h", + "autofill_ml_internals_ui.cc", + "autofill_ml_internals_ui.h", + ] + + public_deps = [ + "//chrome/browser/autofill", + "//components/autofill/core/browser", + "//components/autofill/core/browser/ml_model/logging:mojo_bindings", + "//content/public/browser", + "//mojo/public/cpp/bindings", + "//ui/webui", + ] + + deps = [ + "//chrome/browser/profiles:profile", + "//chrome/browser/resources/autofill_ml_internals:resources", + "//chrome/common", + ] +}
diff --git a/chrome/browser/ui/webui/autofill_ml_internals/DIR_METADATA b/chrome/browser/ui/webui/autofill_ml_internals/DIR_METADATA new file mode 100644 index 0000000..bc280336 --- /dev/null +++ b/chrome/browser/ui/webui/autofill_ml_internals/DIR_METADATA
@@ -0,0 +1 @@ +mixins: "//components/autofill/COMMON_METADATA"
diff --git a/chrome/browser/ui/webui/autofill_ml_internals/OWNERS b/chrome/browser/ui/webui/autofill_ml_internals/OWNERS new file mode 100644 index 0000000..50a21bb --- /dev/null +++ b/chrome/browser/ui/webui/autofill_ml_internals/OWNERS
@@ -0,0 +1 @@ +file://components/autofill/OWNERS
diff --git a/chrome/browser/ui/webui/autofill_ml_internals/autofill_ml_internals_page_handler.cc b/chrome/browser/ui/webui/autofill_ml_internals/autofill_ml_internals_page_handler.cc new file mode 100644 index 0000000..2a765167 --- /dev/null +++ b/chrome/browser/ui/webui/autofill_ml_internals/autofill_ml_internals_page_handler.cc
@@ -0,0 +1,27 @@ +// Copyright 2025 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/autofill_ml_internals/autofill_ml_internals_page_handler.h" + +#include "components/autofill/core/browser/ml_model/logging/autofill_ml_internals.mojom.h" + +AutofillMlInternalsPageHandlerImpl::AutofillMlInternalsPageHandlerImpl( + mojo::PendingReceiver<autofill_ml_internals::mojom::PageHandler> receiver) + : receiver_(this, std::move(receiver)) {} + +AutofillMlInternalsPageHandlerImpl::~AutofillMlInternalsPageHandlerImpl() = + default; + +void AutofillMlInternalsPageHandlerImpl::SetPage( + mojo::PendingRemote<autofill_ml_internals::mojom::Page> page) { + page_.Bind(std::move(page)); + + // TODO: For demonstration, a dummy log is sent + // when the page connects. This should be replaced with a proper observer + // pattern to receive real logs. + auto log = autofill_ml_internals::mojom::MLPredictionLog::New(); + log->form_signature = "1234567890"; + log->field_signatures = {"1111", "2222", "3333"}; + page_->OnLogAdded(std::move(log)); +}
diff --git a/chrome/browser/ui/webui/autofill_ml_internals/autofill_ml_internals_page_handler.h b/chrome/browser/ui/webui/autofill_ml_internals/autofill_ml_internals_page_handler.h new file mode 100644 index 0000000..b6a2808 --- /dev/null +++ b/chrome/browser/ui/webui/autofill_ml_internals/autofill_ml_internals_page_handler.h
@@ -0,0 +1,34 @@ +// Copyright 2025 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_AUTOFILL_ML_INTERNALS_AUTOFILL_ML_INTERNALS_PAGE_HANDLER_H_ +#define CHROME_BROWSER_UI_WEBUI_AUTOFILL_ML_INTERNALS_AUTOFILL_ML_INTERNALS_PAGE_HANDLER_H_ + +#include "components/autofill/core/browser/ml_model/logging/autofill_ml_internals.mojom.h" +#include "mojo/public/cpp/bindings/pending_receiver.h" +#include "mojo/public/cpp/bindings/receiver.h" +#include "mojo/public/cpp/bindings/remote.h" + +class AutofillMlInternalsPageHandlerImpl + : public autofill_ml_internals::mojom::PageHandler { + public: + explicit AutofillMlInternalsPageHandlerImpl( + mojo::PendingReceiver<autofill_ml_internals::mojom::PageHandler> + receiver); + AutofillMlInternalsPageHandlerImpl( + const AutofillMlInternalsPageHandlerImpl&) = delete; + AutofillMlInternalsPageHandlerImpl& operator=( + const AutofillMlInternalsPageHandlerImpl&) = delete; + ~AutofillMlInternalsPageHandlerImpl() override; + + // autofill_ml_internals::mojom::PageHandler: + void SetPage( + mojo::PendingRemote<autofill_ml_internals::mojom::Page> page) override; + + private: + mojo::Receiver<autofill_ml_internals::mojom::PageHandler> receiver_; + mojo::Remote<autofill_ml_internals::mojom::Page> page_; +}; + +#endif // CHROME_BROWSER_UI_WEBUI_AUTOFILL_ML_INTERNALS_AUTOFILL_ML_INTERNALS_PAGE_HANDLER_H_
diff --git a/chrome/browser/ui/webui/autofill_ml_internals/autofill_ml_internals_ui.cc b/chrome/browser/ui/webui/autofill_ml_internals/autofill_ml_internals_ui.cc new file mode 100644 index 0000000..0f279c60 --- /dev/null +++ b/chrome/browser/ui/webui/autofill_ml_internals/autofill_ml_internals_ui.cc
@@ -0,0 +1,37 @@ +// Copyright 2025 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/autofill_ml_internals/autofill_ml_internals_ui.h" + +#include "chrome/browser/profiles/profile.h" +#include "chrome/browser/ui/webui/autofill_ml_internals/autofill_ml_internals_page_handler.h" +#include "chrome/common/webui_url_constants.h" +#include "chrome/grit/autofill_ml_internals_resources.h" +#include "chrome/grit/autofill_ml_internals_resources_map.h" +#include "components/autofill/core/browser/ml_model/logging/autofill_ml_internals.mojom.h" +#include "content/public/browser/web_ui.h" +#include "content/public/browser/web_ui_data_source.h" +#include "ui/webui/webui_util.h" + +AutofillMlInternalsUI::AutofillMlInternalsUI(content::WebUI* web_ui) + : ui::MojoWebUIController(web_ui) { + // Set up the chrome://autofill-ml-internals source. + content::WebUIDataSource* source = content::WebUIDataSource::CreateAndAdd( + Profile::FromWebUI(web_ui), chrome::kChromeUIAutofillMlInternalsHost); + + // Add required resources. + webui::SetupWebUIDataSource( + source, kAutofillMlInternalsResources, + IDR_AUTOFILL_ML_INTERNALS_AUTOFILL_ML_INTERNALS_HTML); + // Pass the message string to the frontend. This generates strings.m.js. + source->AddString("message", "Hello from the C++ backend!"); +} + +void AutofillMlInternalsUI::BindInterface( + mojo::PendingReceiver<autofill_ml_internals::mojom::PageHandler> receiver) { + page_handler_ = + std::make_unique<AutofillMlInternalsPageHandlerImpl>(std::move(receiver)); +} + +WEB_UI_CONTROLLER_TYPE_IMPL(AutofillMlInternalsUI) +AutofillMlInternalsUI::~AutofillMlInternalsUI() = default;
diff --git a/chrome/browser/ui/webui/autofill_ml_internals/autofill_ml_internals_ui.h b/chrome/browser/ui/webui/autofill_ml_internals/autofill_ml_internals_ui.h new file mode 100644 index 0000000..f640c4c --- /dev/null +++ b/chrome/browser/ui/webui/autofill_ml_internals/autofill_ml_internals_ui.h
@@ -0,0 +1,44 @@ +// Copyright 2025 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_AUTOFILL_ML_INTERNALS_AUTOFILL_ML_INTERNALS_UI_H_ +#define CHROME_BROWSER_UI_WEBUI_AUTOFILL_ML_INTERNALS_AUTOFILL_ML_INTERNALS_UI_H_ + +#include <memory> + +#include "chrome/common/webui_url_constants.h" +#include "components/autofill/core/browser/ml_model/logging/autofill_ml_internals.mojom.h" +#include "content/public/browser/internal_webui_config.h" +#include "ui/webui/mojo_web_ui_controller.h" + +class AutofillMlInternalsUI; + +// The WebUIConfig for chrome://autofill-ml-internals +class AutofillMlInternalsUIConfig + : public content::DefaultInternalWebUIConfig<AutofillMlInternalsUI> { + public: + AutofillMlInternalsUIConfig() + : content::DefaultInternalWebUIConfig<AutofillMlInternalsUI>( + chrome::kChromeUIAutofillMlInternalsHost) {} +}; + +// The WebUIController for chrome://autofill-ml-internals +class AutofillMlInternalsUI : public ui::MojoWebUIController { + public: + explicit AutofillMlInternalsUI(content::WebUI* web_ui); + AutofillMlInternalsUI(const AutofillMlInternalsUI&) = delete; + AutofillMlInternalsUI& operator=(const AutofillMlInternalsUI&) = delete; + ~AutofillMlInternalsUI() override; + + void BindInterface( + mojo::PendingReceiver<autofill_ml_internals::mojom::PageHandler> + receiver); + + private: + WEB_UI_CONTROLLER_TYPE_DECL(); + + std::unique_ptr<autofill_ml_internals::mojom::PageHandler> page_handler_; +}; + +#endif // CHROME_BROWSER_UI_WEBUI_AUTOFILL_ML_INTERNALS_AUTOFILL_ML_INTERNALS_UI_H_
diff --git a/chrome/browser/ui/webui/chrome_web_ui_configs.cc b/chrome/browser/ui/webui/chrome_web_ui_configs.cc index 688f01a..166d61b 100644 --- a/chrome/browser/ui/webui/chrome_web_ui_configs.cc +++ b/chrome/browser/ui/webui/chrome_web_ui_configs.cc
@@ -75,6 +75,7 @@ #include "chrome/browser/ui/webui/access_code_cast/access_code_cast_ui.h" #include "chrome/browser/ui/webui/actor_internals/actor_internals_ui.h" #include "chrome/browser/ui/webui/app_service_internals/app_service_internals_ui.h" +#include "chrome/browser/ui/webui/autofill_ml_internals/autofill_ml_internals_ui.h" #include "chrome/browser/ui/webui/bookmarks/bookmarks_ui.h" #include "chrome/browser/ui/webui/commerce/product_specifications_ui.h" #include "chrome/browser/ui/webui/commerce/shopping_insights_side_panel_ui.h" @@ -286,6 +287,7 @@ #endif // BUILDFLAG(GOOGLE_CHROME_BRANDING) map.AddWebUIConfig(std::make_unique<ActorInternalsUIConfig>()); map.AddWebUIConfig(std::make_unique<AppServiceInternalsUIConfig>()); + map.AddWebUIConfig(std::make_unique<AutofillMlInternalsUIConfig>()); map.AddWebUIConfig(std::make_unique<media_router::AccessCodeCastUIConfig>()); map.AddWebUIConfig(std::make_unique<BookmarksSidePanelUIConfig>()); map.AddWebUIConfig(std::make_unique<BookmarksUIConfig>());
diff --git a/chrome/browser/web_applications/isolated_web_apps/browser_navigator_iwa_browsertest.cc b/chrome/browser/web_applications/isolated_web_apps/browser_navigator_iwa_browsertest.cc index 43c3278..e619362 100644 --- a/chrome/browser/web_applications/isolated_web_apps/browser_navigator_iwa_browsertest.cc +++ b/chrome/browser/web_applications/isolated_web_apps/browser_navigator_iwa_browsertest.cc
@@ -113,7 +113,7 @@ web_app::ManifestBuilder() .SetName("app-1.0.0") .SetVersion("1.0.0") - .AddProtocolHandler("web+meow", "/index.html?params=%s")) + .AddProtocolHandler("meow", "/index.html?params=%s")) .BuildBundle(); url_info1_ = std::make_unique<web_app::IsolatedWebAppUrlInfo>( @@ -260,13 +260,13 @@ { // Eliminate all prompts/guards along the way. ExternalProtocolHandler::PermitLaunchUrl(); - ExternalProtocolHandler::SetBlockState("web+meow", url_info2_->origin(), + ExternalProtocolHandler::SetBlockState("meow", url_info2_->origin(), ExternalProtocolHandler::DONT_BLOCK, profile()); base::test::TestFuture<void> future; web_app::WebAppProvider::GetForWebApps(profile()) ->scheduler() - .UpdateProtocolHandlerUserApproval(url_info1_->app_id(), "web+meow", + .UpdateProtocolHandlerUserApproval(url_info1_->app_id(), "meow", web_app::ApiApprovalState::kAllowed, future.GetCallback()); ASSERT_TRUE(future.Wait()); @@ -277,12 +277,12 @@ GURL remapped_url = custom_handlers::ProtocolHandler::CreateProtocolHandler( - "web+meow", + "meow", url_info1_->origin().GetURL().Resolve("/index.html?params=%s")) - .TranslateUrl(GURL("web+meow://hru")); + .TranslateUrl(GURL("meow://hru")); ui_test_utils::UrlLoadObserver observer(remapped_url); - ASSERT_THAT(content::EvalJs(rfh, "window.open('web+meow://hru')"), + ASSERT_THAT(content::EvalJs(rfh, "window.open('meow://hru')"), content::EvalJsResult::IsOk()); observer.Wait();
diff --git a/chrome/browser_exposed_mojom_targets.gni b/chrome/browser_exposed_mojom_targets.gni index 626ee62..65ae53b 100644 --- a/chrome/browser_exposed_mojom_targets.gni +++ b/chrome/browser_exposed_mojom_targets.gni
@@ -80,6 +80,7 @@ "//components/attribution_reporting:registration_mojom", "//components/attribution_reporting:source_type_mojom", "//components/autofill/content/common/mojom:mojom", + "//components/autofill/core/browser/ml_model/logging:mojo_bindings", "//components/autofill/core/common/mojom:mojo_types", "//components/browsing_topics/mojom:mojo_bindings", "//components/commerce/core/internals/mojom:mojo_bindings",
diff --git a/chrome/chrome_paks.gni b/chrome/chrome_paks.gni index 54ee8a8..a394d9c4 100644 --- a/chrome/chrome_paks.gni +++ b/chrome/chrome_paks.gni
@@ -188,6 +188,7 @@ "$root_gen_dir/chrome/about_sys_resources.pak", "$root_gen_dir/chrome/access_code_cast_resources.pak", "$root_gen_dir/chrome/app_service_internals_resources.pak", + "$root_gen_dir/chrome/autofill_ml_internals_resources.pak", "$root_gen_dir/chrome/bookmarks_resources.pak", "$root_gen_dir/chrome/certificate_manager_resources.pak", "$root_gen_dir/chrome/commerce_product_specifications_resources.pak", @@ -236,6 +237,7 @@ ] deps += [ "//chrome/browser/resources:dev_ui_paks", + "//chrome/browser/resources/autofill_ml_internals:resources", "//chrome/browser/resources/commerce/product_specifications:resources", "//chrome/browser/resources/data_sharing:resources", "//chrome/browser/resources/lens/overlay:resources",
diff --git a/chrome/common/webui_url_constants.h b/chrome/common/webui_url_constants.h index d95ee776..05b1768 100644 --- a/chrome/common/webui_url_constants.h +++ b/chrome/common/webui_url_constants.h
@@ -339,6 +339,8 @@ inline constexpr char kAdPrivacySubPagePath[] = "/adPrivacy"; inline constexpr char kChromeUIAppServiceInternalsHost[] = "app-service-internals"; +inline constexpr char kChromeUIAutofillMlInternalsHost[] = + "autofill-ml-internals"; inline constexpr char kChromeUIBookmarksSidePanelHost[] = "bookmarks-side-panel.top-chrome"; inline constexpr char kChromeUIBookmarksSidePanelURL[] =
diff --git a/chromeos/CHROMEOS_LKGM b/chromeos/CHROMEOS_LKGM index efbfe68..0eb2662 100644 --- a/chromeos/CHROMEOS_LKGM +++ b/chromeos/CHROMEOS_LKGM
@@ -1 +1 @@ -16335.0.0-1069940 \ No newline at end of file +16336.0.0-1069948 \ No newline at end of file
diff --git a/chromeos/ash/components/kcer/kcer_nss/kcer_nss_fuzzer.cc b/chromeos/ash/components/kcer/kcer_nss/kcer_nss_fuzzer.cc index 4fdfa668..c428bff6d 100644 --- a/chromeos/ash/components/kcer/kcer_nss/kcer_nss_fuzzer.cc +++ b/chromeos/ash/components/kcer/kcer_nss/kcer_nss_fuzzer.cc
@@ -558,8 +558,13 @@ cert_builder_->SetInhibitAnyPolicy(/*skip_certs=*/GetUint64()); } if (GetBool()) { + base::Time max_time; + ASSERT_TRUE(base::Time::FromString("31 Dec 9999 23:59:59 GMT", &max_time)); base::Time not_before = base::Time() + base::Microseconds(GetUint64()); base::Time not_after = base::Time() + base::Microseconds(GetUint64()); + // BoringSSL doesn't allow setting the validity time above the year 9999. + not_before = std::min(max_time, not_before); + not_after = std::min(max_time, not_after); cert_builder_->SetValidity(not_before, not_after); } if (GetBool()) {
diff --git a/chromeos/ash/experiences/arc/video_accelerator/DEPS b/chromeos/ash/experiences/arc/video_accelerator/DEPS index 57de0d9..5c16050 100644 --- a/chromeos/ash/experiences/arc/video_accelerator/DEPS +++ b/chromeos/ash/experiences/arc/video_accelerator/DEPS
@@ -2,7 +2,7 @@ "+chromeos/components/cdm_factory_daemon", "+gpu/config/gpu_driver_bug_workarounds.h", "+gpu/config/gpu_preferences.h", - "+gpu/ipc/common/gpu_memory_buffer_support.h", + "+gpu/ipc/common/gpu_memory_buffer_impl_native_pixmap.h", "+media/base", "+media/gpu", "+media/media_buildflags.h",
diff --git a/chromeos/ash/experiences/arc/video_accelerator/gpu_arc_video_encode_accelerator.cc b/chromeos/ash/experiences/arc/video_accelerator/gpu_arc_video_encode_accelerator.cc index 460fd3dd..293e82c8 100644 --- a/chromeos/ash/experiences/arc/video_accelerator/gpu_arc_video_encode_accelerator.cc +++ b/chromeos/ash/experiences/arc/video_accelerator/gpu_arc_video_encode_accelerator.cc
@@ -16,6 +16,7 @@ #include "base/system/sys_info.h" #include "base/task/bind_post_task.h" #include "chromeos/ash/experiences/arc/video_accelerator/arc_video_accelerator_util.h" +#include "gpu/ipc/common/gpu_memory_buffer_impl_native_pixmap.h" #include "media/base/bitrate.h" #include "media/base/bitstream_buffer.h" #include "media/base/color_plane_layout.h" @@ -29,6 +30,7 @@ #include "media/video/video_encode_accelerator.h" #include "mojo/public/cpp/bindings/type_converter.h" #include "mojo/public/cpp/system/platform_handle.h" +#include "ui/ozone/public/ozone_platform.h" namespace arc { @@ -49,7 +51,9 @@ const gpu::GpuDriverBugWorkarounds& gpu_workarounds) : gpu_preferences_(gpu_preferences), gpu_workarounds_(gpu_workarounds), - bitstream_buffer_serial_(0) {} + bitstream_buffer_serial_(0), + client_native_pixmap_factory_( + ui::CreateClientNativePixmapFactoryOzone()) {} GpuArcVideoEncodeAccelerator::~GpuArcVideoEncodeAccelerator() { // Normally |client_count_| should always be > 0 if vea_ is set, but if it @@ -201,8 +205,9 @@ return; } std::unique_ptr<gfx::GpuMemoryBuffer> gpu_memory_buffer = - support_.CreateGpuMemoryBufferImplFromHandle( - std::move(gmb_handle).value(), coded_size_, *buffer_format, + gpu::GpuMemoryBufferImplNativePixmap::CreateFromHandle( + client_native_pixmap_factory_.get(), std::move(gmb_handle).value(), + coded_size_, *buffer_format, gfx::BufferUsage::VEA_READ_CAMERA_AND_CPU_READ_WRITE, base::NullCallback());
diff --git a/chromeos/ash/experiences/arc/video_accelerator/gpu_arc_video_encode_accelerator.h b/chromeos/ash/experiences/arc/video_accelerator/gpu_arc_video_encode_accelerator.h index 72d6e5a..e4eff61 100644 --- a/chromeos/ash/experiences/arc/video_accelerator/gpu_arc_video_encode_accelerator.h +++ b/chromeos/ash/experiences/arc/video_accelerator/gpu_arc_video_encode_accelerator.h
@@ -14,10 +14,10 @@ #include "chromeos/ash/experiences/arc/video_accelerator/video_frame_plane.h" #include "gpu/config/gpu_driver_bug_workarounds.h" #include "gpu/config/gpu_preferences.h" -#include "gpu/ipc/common/gpu_memory_buffer_support.h" #include "media/video/video_encode_accelerator.h" #include "mojo/public/cpp/bindings/pending_remote.h" #include "mojo/public/cpp/bindings/remote.h" +#include "ui/ozone/public/client_native_pixmap_factory_ozone.h" namespace arc { @@ -90,7 +90,7 @@ gfx::Size visible_size_; int32_t bitstream_buffer_serial_; std::unordered_map<uint32_t, UseBitstreamBufferCallback> use_bitstream_cbs_; - gpu::GpuMemoryBufferSupport support_; + std::unique_ptr<gfx::ClientNativePixmapFactory> client_native_pixmap_factory_; }; } // namespace arc
diff --git a/clank b/clank index 2444b35..40b8b87 160000 --- a/clank +++ b/clank
@@ -1 +1 @@ -Subproject commit 2444b358f5ae5fdcda516c76fcb06653f03a2441 +Subproject commit 40b8b87ab4d682574fed64494081b35a70ee1569
diff --git a/components/autofill/android/java/src/org/chromium/components/autofill/AutofillSuggestion.java b/components/autofill/android/java/src/org/chromium/components/autofill/AutofillSuggestion.java index 31ef394..1a612df 100644 --- a/components/autofill/android/java/src/org/chromium/components/autofill/AutofillSuggestion.java +++ b/components/autofill/android/java/src/org/chromium/components/autofill/AutofillSuggestion.java
@@ -47,9 +47,7 @@ * @param featureForIph The IPH feature for the autofill suggestion. If present, it'll be * attempted to be shown in the keyboard accessory. * @param customIconUrl The {@link GURL} for the custom icon, if any. - * @param payload Additional data passed with the suggestion. Currently only - * AutofillProfilePayload may passed. New payloads can be added by implementing the {@link - * AutofillSuggestion.Payload} interface. + * @param payload Additional data passed with the suggestion. */ @VisibleForTesting public AutofillSuggestion( @@ -142,8 +140,18 @@ return mIphDescriptionText; } - public @Nullable Payload getPayload() { - return mPayload; + public @Nullable AutofillProfilePayload getAutofillProfilePayload() { + if (mPayload instanceof AutofillProfilePayload) { + return (AutofillProfilePayload) mPayload; + } + return null; + } + + public @Nullable PaymentsPayload getPaymentsPayload() { + if (mPayload instanceof PaymentsPayload) { + return (PaymentsPayload) mPayload; + } + return null; } @Override
diff --git a/components/autofill/content/renderer/autofill_agent.cc b/components/autofill/content/renderer/autofill_agent.cc index b7dbde0..b9827782 100644 --- a/components/autofill/content/renderer/autofill_agent.cc +++ b/components/autofill/content/renderer/autofill_agent.cc
@@ -386,6 +386,8 @@ return true; } +// TODO(crbug.com/402071086): Remove when AutofillIgnoreCheckableElements is +// removed. bool IsCheckableElement(const WebFormControlElement& element) { using enum blink::mojom::FormControlType; return element && (element.FormControlTypeForAutofill() == kInputCheckbox ||
diff --git a/components/autofill/content/renderer/form_autofill_util.cc b/components/autofill/content/renderer/form_autofill_util.cc index 47300b4f..d21abdd7 100644 --- a/components/autofill/content/renderer/form_autofill_util.cc +++ b/components/autofill/content/renderer/form_autofill_util.cc
@@ -297,12 +297,16 @@ return GetAutofillFormControlType(element) == FormControlType::kSelectOne; } +// TODO(crbug.com/402071086): Remove when AutofillIgnoreCheckableElements is +// removed. bool IsCheckableElement(const WebFormControlElement& element) { using enum blink::mojom::FormControlType; return element && (element.FormControlTypeForAutofill() == kInputCheckbox || element.FormControlTypeForAutofill() == kInputRadio); } +// TODO(crbug.com/402071086): Remove when AutofillIgnoreCheckableElements is +// removed. bool IsCheckableElement(const WebElement& element) { return IsCheckableElement(element.DynamicTo<WebInputElement>()); } @@ -2308,7 +2312,11 @@ // IsAutofillableElement() return true. switch (type) { case blink::mojom::FormControlType::kInputCheckbox: - return FormControlType::kInputCheckbox; + if (!base::FeatureList::IsEnabled( + features::kAutofillIgnoreCheckableElements)) { + return FormControlType::kInputCheckbox; + } + break; case blink::mojom::FormControlType::kInputEmail: return FormControlType::kInputEmail; case blink::mojom::FormControlType::kInputMonth: @@ -2318,7 +2326,11 @@ case blink::mojom::FormControlType::kInputPassword: return FormControlType::kInputPassword; case blink::mojom::FormControlType::kInputRadio: - return FormControlType::kInputRadio; + if (!base::FeatureList::IsEnabled( + features::kAutofillIgnoreCheckableElements)) { + return FormControlType::kInputRadio; + } + break; case blink::mojom::FormControlType::kInputSearch: return FormControlType::kInputSearch; case blink::mojom::FormControlType::kInputTelephone:
diff --git a/components/autofill/content/renderer/form_autofill_util_browsertest.cc b/components/autofill/content/renderer/form_autofill_util_browsertest.cc index c45c6cd9..0493ca9 100644 --- a/components/autofill/content/renderer/form_autofill_util_browsertest.cc +++ b/components/autofill/content/renderer/form_autofill_util_browsertest.cc
@@ -218,7 +218,8 @@ FormAutofillUtilsTest() { scoped_feature_list_.InitWithFeatures( /*enabled_features=*/ - {features::kAutofillReplaceCachedWebElementsByRendererIds}, + {features::kAutofillReplaceCachedWebElementsByRendererIds, + features::kAutofillIgnoreCheckableElements}, /*disabled_features=*/{}); } ~FormAutofillUtilsTest() override = default; @@ -264,11 +265,13 @@ <button type=kButtonPopover>Foo</button> <fieldset></fieldset> <input type=button> + <input type=checkbox> <input type=color> <input type=datetime-local> <input type=file> <input type=hidden> <input type=image> + <input type=radio> <input type=range> <input type=reset> <input type=submit> @@ -279,12 +282,10 @@ <!-- These form controls are extracted. --> <input> - <input type=checkbox> <input type=email> <input type=month> <input type=number> <input type=password> - <input type=radio> <input type=search> <input type=tel> <input type=text> @@ -297,12 +298,11 @@ FormData form_data = *ExtractFormData(GetFormElementById(GetDocument(), "form-id")); using enum FormControlType; - EXPECT_THAT( - form_data.fields(), - FormControlTypesAre(kInputText, kInputCheckbox, kInputEmail, kInputMonth, - kInputNumber, kInputPassword, kInputRadio, - kInputSearch, kInputTelephone, kInputText, kInputUrl, - kInputDate, kSelectOne, kTextArea)); + EXPECT_THAT(form_data.fields(), + FormControlTypesAre(kInputText, kInputEmail, kInputMonth, + kInputNumber, kInputPassword, kInputSearch, + kInputTelephone, kInputText, kInputUrl, + kInputDate, kSelectOne, kTextArea)); } // Tests that WebFormElementToFormData() sets the @@ -2191,13 +2191,12 @@ Optional(Property( &FormData::fields, ElementsAre(Property(&FormFieldData::name, u"text_input"), - Property(&FormFieldData::name, u"check_input"), Property(&FormFieldData::name, u"number_input"), Property(&FormFieldData::name, u"select_input"))))); histogram_tester.ExpectTotalCount("Autofill.ExtractFormUnowned.FieldCount2", 0); histogram_tester.ExpectUniqueSample("Autofill.ExtractFormOwned.FieldCount2", - 4, 1); + 3, 1); } TEST_F(FormAutofillUtilsTest, ExtractFormData_UnownedForm) { @@ -2219,12 +2218,11 @@ Optional(Property( &FormData::fields, ElementsAre(Property(&FormFieldData::name, u"text_input"), - Property(&FormFieldData::name, u"check_input"), Property(&FormFieldData::name, u"number_input"), Property(&FormFieldData::name, u"select_input"))))); histogram_tester.ExpectTotalCount("Autofill.ExtractFormOwned.FieldCount2", 0); histogram_tester.ExpectUniqueSample("Autofill.ExtractFormUnowned.FieldCount2", - 4, 1); + 3, 1); } // Tests that GetOwnedFormControls() doesn't return disconnected elements.
diff --git a/components/autofill/content/renderer/form_cache.cc b/components/autofill/content/renderer/form_cache.cc index f5db7eaa..a161a389 100644 --- a/components/autofill/content/renderer/form_cache.cc +++ b/components/autofill/content/renderer/form_cache.cc
@@ -33,8 +33,8 @@ // (1) At least one form field is not-checkable. (See crbug.com/1489075.) // (2) At least one field has a non-empty autocomplete attribute. // (3) There is at least one iframe. -// TODO(crbug.com/40283901): Should an element that IsCheckableElement() also be -// IsAutofillableInputElement()? +// TODO(crbug.com/40283901): Remove check for radio buttons and checkboxes when +// we they're not extracted anymore. bool IsFormInteresting(const FormData& form) { auto is_checkable = [](FormControlType type) { return type == FormControlType::kInputCheckbox ||
diff --git a/components/autofill/core/browser/foundations/browser_autofill_manager.cc b/components/autofill/core/browser/foundations/browser_autofill_manager.cc index a7dd9b71..c5f7d78 100644 --- a/components/autofill/core/browser/foundations/browser_autofill_manager.cc +++ b/components/autofill/core/browser/foundations/browser_autofill_manager.cc
@@ -3199,12 +3199,13 @@ if (suggestions.empty()) { suggestions = GetLoyaltyCardSuggestions( *valuables_manager, - client().GetLastCommittedPrimaryMainFrameURL()); + client().GetLastCommittedPrimaryMainFrameURL(), + field.is_autofilled()); } else { ExtendEmailSuggestionsWithLoyaltyCardSuggestions( *valuables_manager, client().GetLastCommittedPrimaryMainFrameURL(), - autofill_field->is_autofilled(), suggestions); + field.is_autofilled(), suggestions); } } } @@ -3222,7 +3223,8 @@ client().GetValuablesDataManager()) { suggestions = GetLoyaltyCardSuggestions( *valuables_manager, - client().GetLastCommittedPrimaryMainFrameURL()); + client().GetLastCommittedPrimaryMainFrameURL(), + field.is_autofilled()); } } }
diff --git a/components/autofill/core/browser/ml_model/logging/BUILD.gn b/components/autofill/core/browser/ml_model/logging/BUILD.gn new file mode 100644 index 0000000..4420c66 --- /dev/null +++ b/components/autofill/core/browser/ml_model/logging/BUILD.gn
@@ -0,0 +1,10 @@ +# Copyright 2025 The Chromium Authors +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import("//mojo/public/tools/bindings/mojom.gni") + +mojom("mojo_bindings") { + sources = [ "autofill_ml_internals.mojom" ] + webui_module_path = "/" +}
diff --git a/components/autofill/core/browser/ml_model/logging/OWNERS b/components/autofill/core/browser/ml_model/logging/OWNERS new file mode 100644 index 0000000..c94847a --- /dev/null +++ b/components/autofill/core/browser/ml_model/logging/OWNERS
@@ -0,0 +1,3 @@ +file://components/autofill/OWNERS +per-file *.mojom=set noparent +per-file *.mojom=file://ipc/SECURITY_OWNERS
diff --git a/components/autofill/core/browser/ml_model/logging/autofill_ml_internals.mojom b/components/autofill/core/browser/ml_model/logging/autofill_ml_internals.mojom new file mode 100644 index 0000000..f9cec9b --- /dev/null +++ b/components/autofill/core/browser/ml_model/logging/autofill_ml_internals.mojom
@@ -0,0 +1,26 @@ +// Copyright 2025 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +module autofill_ml_internals.mojom; + +// Represents a single ML prediction log entry. +struct MLPredictionLog { + string form_signature; + array<string> field_signatures; +}; + +// The frontend implements this interface to receive log updates from the +// browser process. +interface Page { + // Called by the backend when a new prediction log is available. + OnLogAdded(MLPredictionLog log); +}; + +// The browser process implements this interface. The frontend uses this to +// establish communication. +interface PageHandler { + // The frontend calls this method to register its Page interface and start + // receiving log updates. + SetPage(pending_remote<Page> page); +};
diff --git a/components/autofill/core/browser/suggestions/valuables/valuable_suggestion_generator.cc b/components/autofill/core/browser/suggestions/valuables/valuable_suggestion_generator.cc index 82ad62b..a000e5e 100644 --- a/components/autofill/core/browser/suggestions/valuables/valuable_suggestion_generator.cc +++ b/components/autofill/core/browser/suggestions/valuables/valuable_suggestion_generator.cc
@@ -26,6 +26,28 @@ base::UTF8ToUTF16(merchant_name.substr(0, 1))); } +Suggestion CreateUndoOrClearFormSuggestion() { +#if BUILDFLAG(IS_IOS) + std::u16string value = + l10n_util::GetStringUTF16(IDS_AUTOFILL_CLEAR_FORM_MENU_ITEM); + // TODO(crbug.com/40266549): iOS still uses Clear Form logic, replace with + // Undo. + Suggestion suggestion(value, SuggestionType::kUndoOrClear); + suggestion.icon = Suggestion::Icon::kClear; +#else + std::u16string value = l10n_util::GetStringUTF16(IDS_AUTOFILL_UNDO_MENU_ITEM); + if constexpr (BUILDFLAG(IS_ANDROID)) { + value = base::i18n::ToUpper(value); + } + Suggestion suggestion(value, SuggestionType::kUndoOrClear); + suggestion.icon = Suggestion::Icon::kUndo; +#endif + // TODO(crbug.com/40266549): update "Clear Form" a11y announcement to "Undo" + suggestion.acceptance_a11y_announcement = + l10n_util::GetStringUTF16(IDS_AUTOFILL_A11Y_ANNOUNCE_CLEARED_FORM); + return suggestion; +} + // Set the URL for the loyalty card icon image or fallback icon to be shown in // the `suggestion`. void SetLoyaltyCardIconURL(Suggestion& suggestion, @@ -88,11 +110,26 @@ return suggestions; } +// Returns non loyalty cards suggestions which are displayed below loyalty cards +// suggestions in the Autofill popup. `trigger_field_is_autofilled` is used to +// conditionally add suggestion for clearing autofilled field. +std::vector<Suggestion> GetLoyaltyCardsFooterSuggestions( + bool trigger_field_is_autofilled) { + std::vector<Suggestion> footer_suggestions; + footer_suggestions.emplace_back(SuggestionType::kSeparator); + if (trigger_field_is_autofilled) { + footer_suggestions.push_back(CreateUndoOrClearFormSuggestion()); + } + footer_suggestions.push_back(CreateManageLoyaltyCardsSuggestion()); + return footer_suggestions; +} + } // namespace std::vector<Suggestion> GetLoyaltyCardSuggestions( const ValuablesDataManager& valuables_manager, - const GURL& url) { + const GURL& url, + bool trigger_field_is_autofilled) { std::vector<LoyaltyCard> all_loyalty_cards = valuables_manager.GetLoyaltyCardsToSuggest(); if (all_loyalty_cards.empty()) { @@ -110,8 +147,9 @@ if (affiliated_cards.empty() || non_affiliated_cards.empty()) { std::vector<Suggestion> suggestions = CreateSuggestionsFromLoyaltyCards(all_loyalty_cards, valuables_manager); - suggestions.emplace_back(SuggestionType::kSeparator); - suggestions.push_back(CreateManageLoyaltyCardsSuggestion()); + std::ranges::move( + GetLoyaltyCardsFooterSuggestions(trigger_field_is_autofilled), + std::back_inserter(suggestions)); return suggestions; } @@ -131,8 +169,9 @@ #endif submenu_suggestion.children = CreateSuggestionsFromLoyaltyCards( valuables_manager.GetLoyaltyCardsToSuggest(), valuables_manager); - suggestions.emplace_back(SuggestionType::kSeparator); - suggestions.push_back(CreateManageLoyaltyCardsSuggestion()); + std::ranges::move( + GetLoyaltyCardsFooterSuggestions(trigger_field_is_autofilled), + std::back_inserter(suggestions)); return suggestions; }
diff --git a/components/autofill/core/browser/suggestions/valuables/valuable_suggestion_generator.h b/components/autofill/core/browser/suggestions/valuables/valuable_suggestion_generator.h index 1043f519..8d1e83a 100644 --- a/components/autofill/core/browser/suggestions/valuables/valuable_suggestion_generator.h +++ b/components/autofill/core/browser/suggestions/valuables/valuable_suggestion_generator.h
@@ -18,7 +18,8 @@ // extracted from the `valuables_manager`. std::vector<Suggestion> GetLoyaltyCardSuggestions( const ValuablesDataManager& valuables_manager, - const GURL& url); + const GURL& url, + bool trigger_field_is_autofilled); // Extends `email_suggestions` with loyalty cards suggestions. void ExtendEmailSuggestionsWithLoyaltyCardSuggestions(
diff --git a/components/autofill/core/browser/suggestions/valuables/valuable_suggestion_generator_unittest.cc b/components/autofill/core/browser/suggestions/valuables/valuable_suggestion_generator_unittest.cc index 621fc66..63a6746 100644 --- a/components/autofill/core/browser/suggestions/valuables/valuable_suggestion_generator_unittest.cc +++ b/components/autofill/core/browser/suggestions/valuables/valuable_suggestion_generator_unittest.cc
@@ -41,6 +41,13 @@ } } +Matcher<Suggestion> EqualsManageLoyaltyCardsSuggestion() { + return EqualsSuggestion( + SuggestionType::kManageLoyaltyCard, + l10n_util::GetStringUTF16(IDS_AUTOFILL_MANAGE_LOYALTY_CARDS), + Suggestion::Icon::kSettings); +} + class ValuableSuggestionGeneratorTest : public testing::Test { public: ValuableSuggestionGeneratorTest() = default; @@ -49,6 +56,33 @@ return valuables_data_manager_; } + void SetUp() override { + const std::vector<LoyaltyCard> loyalty_cards = { + LoyaltyCard( + /*loyalty_card_id=*/ValuableId("loyalty_card_id_1"), + /*merchant_name=*/"CVS Pharmacy", + /*program_name=*/"CVS Extra", + /*program_logo=*/GURL("https://empty.url.com"), + /*loyalty_card_number=*/"987654321987654321", + {GURL("https://domain1.example"), + GURL("https://common-domain.example")}), + LoyaltyCard(/*loyalty_card_id=*/ValuableId("loyalty_card_id_3"), + /*merchant_name=*/"Walgreens", + /*program_name=*/"CustomerCard", + /*program_logo=*/GURL("https://empty.url.com"), + /*loyalty_card_number=*/"998766823", + {GURL("https://domain2.example"), + GURL("https://common-domain.example")}), + LoyaltyCard(/*loyalty_card_id=*/ValuableId("loyalty_card_id_2"), + /*merchant_name=*/"Ticket Maester", + /*program_name=*/"TourLoyal", + /*program_logo=*/GURL("https://empty.url.com"), + /*loyalty_card_number=*/"37262999281", + {GURL("https://domain2.example"), + GURL("https://common-domain.example")})}; + test_api(valuables_data_manager()).SetLoyaltyCards(loyalty_cards); + } + gfx::Image CustomIconForTest() { return gfx::test::CreateImage(32, 32); } private: @@ -57,88 +91,63 @@ TEST_F(ValuableSuggestionGeneratorTest, GetLoyaltyCardSuggestions_NoMatchingDomain) { - const std::vector<LoyaltyCard> loyalty_cards = { - LoyaltyCard( - /*loyalty_card_id=*/ValuableId("loyalty_card_id_1"), - /*merchant_name=*/"CVS Pharmacy", - /*program_name=*/"CVS Extra", - /*program_logo=*/GURL("https://empty.url.com"), - /*loyalty_card_number=*/"987654321987654321", - {GURL("https://domain1.example"), - GURL("https://common-domain.example")}), - LoyaltyCard(/*loyalty_card_id=*/ValuableId("loyalty_card_id_3"), - /*merchant_name=*/"Walgreens", - /*program_name=*/"CustomerCard", - /*program_logo=*/GURL("https://empty.url.com"), - /*loyalty_card_number=*/"998766823", - {GURL("https://domain2.example"), - GURL("https://common-domain.example")}), - LoyaltyCard(/*loyalty_card_id=*/ValuableId("loyalty_card_id_2"), - /*merchant_name=*/"Ticket Maester", - /*program_name=*/"TourLoyal", - /*program_logo=*/GURL("https://empty.url.com"), - /*loyalty_card_number=*/"37262999281", - {GURL("https://domain2.example"), - GURL("https://common-domain.example")})}; - test_api(valuables_data_manager()).SetLoyaltyCards(loyalty_cards); + EXPECT_THAT(GetLoyaltyCardSuggestions( + valuables_data_manager(), + GURL("https://not-existing-domain.example/test"), + /*trigger_field_is_autofilled=*/false), + testing::ElementsAre( + EqualsSuggestion( + SuggestionType::kLoyaltyCardEntry, u"987654321987654321", + /*is_main_text_primary=*/true, Suggestion::Icon::kNoIcon, + {{Suggestion::Text(u"CVS Pharmacy")}}, + Suggestion::Guid("loyalty_card_id_1")), + EqualsSuggestion( + SuggestionType::kLoyaltyCardEntry, u"37262999281", + /*is_main_text_primary=*/true, Suggestion::Icon::kNoIcon, + {{Suggestion::Text(u"Ticket Maester")}}, + Suggestion::Guid("loyalty_card_id_2")), + EqualsSuggestion( + SuggestionType::kLoyaltyCardEntry, u"998766823", + /*is_main_text_primary=*/true, Suggestion::Icon::kNoIcon, + {{Suggestion::Text(u"Walgreens")}}, + Suggestion::Guid("loyalty_card_id_3")), + EqualsSuggestion(SuggestionType::kSeparator), + EqualsManageLoyaltyCardsSuggestion())); +} - EXPECT_THAT( - GetLoyaltyCardSuggestions( - valuables_data_manager(), - GURL("https://not-existing-domain.example/test")), - testing::ElementsAre( - EqualsSuggestion( - SuggestionType::kLoyaltyCardEntry, u"987654321987654321", - /*is_main_text_primary=*/true, Suggestion::Icon::kNoIcon, - {{Suggestion::Text(u"CVS Pharmacy")}}, - Suggestion::Guid("loyalty_card_id_1")), - EqualsSuggestion(SuggestionType::kLoyaltyCardEntry, u"37262999281", - /*is_main_text_primary=*/true, - Suggestion::Icon::kNoIcon, - {{Suggestion::Text(u"Ticket Maester")}}, - Suggestion::Guid("loyalty_card_id_2")), - EqualsSuggestion(SuggestionType::kLoyaltyCardEntry, u"998766823", - /*is_main_text_primary=*/true, - Suggestion::Icon::kNoIcon, - {{Suggestion::Text(u"Walgreens")}}, - Suggestion::Guid("loyalty_card_id_3")), - EqualsSuggestion(SuggestionType::kSeparator), - EqualsSuggestion( - SuggestionType::kManageLoyaltyCard, - l10n_util::GetStringUTF16(IDS_AUTOFILL_MANAGE_LOYALTY_CARDS), - Suggestion::Icon::kSettings))); +TEST_F(ValuableSuggestionGeneratorTest, + GetLoyaltyCardSuggestions_NoMatchingDomainAndFieldAutofilled) { + EXPECT_THAT(GetLoyaltyCardSuggestions( + valuables_data_manager(), + GURL("https://not-existing-domain.example/test"), + /*trigger_field_is_autofilled=*/true), + testing::ElementsAre( + EqualsSuggestion( + SuggestionType::kLoyaltyCardEntry, u"987654321987654321", + /*is_main_text_primary=*/true, Suggestion::Icon::kNoIcon, + {{Suggestion::Text(u"CVS Pharmacy")}}, + Suggestion::Guid("loyalty_card_id_1")), + EqualsSuggestion( + SuggestionType::kLoyaltyCardEntry, u"37262999281", + /*is_main_text_primary=*/true, Suggestion::Icon::kNoIcon, + {{Suggestion::Text(u"Ticket Maester")}}, + Suggestion::Guid("loyalty_card_id_2")), + EqualsSuggestion( + SuggestionType::kLoyaltyCardEntry, u"998766823", + /*is_main_text_primary=*/true, Suggestion::Icon::kNoIcon, + {{Suggestion::Text(u"Walgreens")}}, + Suggestion::Guid("loyalty_card_id_3")), + EqualsSuggestion(SuggestionType::kSeparator), + EqualsSuggestion(SuggestionType::kUndoOrClear), + EqualsManageLoyaltyCardsSuggestion())); } TEST_F(ValuableSuggestionGeneratorTest, GetLoyaltyCardSuggestions_WithMatchingDomain) { - const std::vector<LoyaltyCard> loyalty_cards = { - LoyaltyCard( - /*loyalty_card_id=*/ValuableId("loyalty_card_id_1"), - /*merchant_name=*/"CVS Pharmacy", - /*program_name=*/"CVS Extra", - /*program_logo=*/GURL("https://empty.url.com"), - /*loyalty_card_number=*/"987654321987654321", - {GURL("https://domain1.example"), - GURL("https://common-domain.example")}), - LoyaltyCard(/*loyalty_card_id=*/ValuableId("loyalty_card_id_3"), - /*merchant_name=*/"Walgreens", - /*program_name=*/"CustomerCard", - /*program_logo=*/GURL("https://empty.url.com"), - /*loyalty_card_number=*/"998766823", - {GURL("https://domain2.example"), - GURL("https://common-domain.example")}), - LoyaltyCard(/*loyalty_card_id=*/ValuableId("loyalty_card_id_2"), - /*merchant_name=*/"Ticket Maester", - /*program_name=*/"TourLoyal", - /*program_logo=*/GURL("https://empty.url.com"), - /*loyalty_card_number=*/"37262999281", - {GURL("https://domain2.example"), - GURL("https://common-domain.example")})}; - test_api(valuables_data_manager()).SetLoyaltyCards(loyalty_cards); - std::vector<Suggestion> suggestions_with_matching_domain = GetLoyaltyCardSuggestions(valuables_data_manager(), - GURL("https://domain2.example/test")); + GURL("https://domain2.example/test"), + /*trigger_field_is_autofilled=*/false); EXPECT_THAT( suggestions_with_matching_domain, testing::ElementsAre( @@ -158,10 +167,60 @@ l10n_util::GetStringUTF16( IDS_AUTOFILL_LOYALTY_CARDS_ALL_YOUR_CARDS_SUBMENU_TITLE)), EqualsSuggestion(SuggestionType::kSeparator), + EqualsManageLoyaltyCardsSuggestion())); + const Suggestion& lc_submenu_suggestion = suggestions_with_matching_domain[3]; + EXPECT_EQ(lc_submenu_suggestion.acceptability, + Suggestion::Acceptability::kUnacceptable); + EXPECT_THAT(lc_submenu_suggestion.children, + testing::ElementsAre( + EqualsSuggestion( + SuggestionType::kLoyaltyCardEntry, u"987654321987654321", + /*is_main_text_primary=*/true, Suggestion::Icon::kNoIcon, + {{Suggestion::Text(u"CVS Pharmacy")}}, + Suggestion::Guid("loyalty_card_id_1")), + EqualsSuggestion( + SuggestionType::kLoyaltyCardEntry, u"37262999281", + /*is_main_text_primary=*/true, Suggestion::Icon::kNoIcon, + {{Suggestion::Text(u"Ticket Maester")}}, + Suggestion::Guid("loyalty_card_id_2")), + EqualsSuggestion( + SuggestionType::kLoyaltyCardEntry, u"998766823", + /*is_main_text_primary=*/true, Suggestion::Icon::kNoIcon, + {{Suggestion::Text(u"Walgreens")}}, + Suggestion::Guid("loyalty_card_id_3")))); +#if BUILDFLAG(GOOGLE_CHROME_BRANDING) + EXPECT_THAT(suggestions_with_matching_domain.back(), + HasTrailingIcon(Suggestion::Icon::kGoogleWallet)); +#endif +} + +TEST_F(ValuableSuggestionGeneratorTest, + GetLoyaltyCardSuggestions_WithMatchingDomainAndFieldAutofilled) { + std::vector<Suggestion> suggestions_with_matching_domain = + GetLoyaltyCardSuggestions(valuables_data_manager(), + GURL("https://domain2.example/test"), + /*trigger_field_is_autofilled=*/true); + EXPECT_THAT( + suggestions_with_matching_domain, + testing::ElementsAre( + EqualsSuggestion(SuggestionType::kLoyaltyCardEntry, u"37262999281", + /*is_main_text_primary=*/true, + Suggestion::Icon::kNoIcon, + {{Suggestion::Text(u"Ticket Maester")}}, + Suggestion::Guid("loyalty_card_id_2")), + EqualsSuggestion(SuggestionType::kLoyaltyCardEntry, u"998766823", + /*is_main_text_primary=*/true, + Suggestion::Icon::kNoIcon, + {{Suggestion::Text(u"Walgreens")}}, + Suggestion::Guid("loyalty_card_id_3")), + EqualsSuggestion(SuggestionType::kSeparator), EqualsSuggestion( - SuggestionType::kManageLoyaltyCard, - l10n_util::GetStringUTF16(IDS_AUTOFILL_MANAGE_LOYALTY_CARDS), - Suggestion::Icon::kSettings))); + SuggestionType::kAllLoyaltyCardsEntry, + l10n_util::GetStringUTF16( + IDS_AUTOFILL_LOYALTY_CARDS_ALL_YOUR_CARDS_SUBMENU_TITLE)), + EqualsSuggestion(SuggestionType::kSeparator), + EqualsSuggestion(SuggestionType::kUndoOrClear), + EqualsManageLoyaltyCardsSuggestion())); const Suggestion& lc_submenu_suggestion = suggestions_with_matching_domain[3]; EXPECT_EQ(lc_submenu_suggestion.acceptability, Suggestion::Acceptability::kUnacceptable); @@ -190,34 +249,10 @@ TEST_F(ValuableSuggestionGeneratorTest, GetLoyaltyCardSuggestions_AllMatchDomain) { - const std::vector<LoyaltyCard> loyalty_cards = { - LoyaltyCard( - /*loyalty_card_id=*/ValuableId("loyalty_card_id_1"), - /*merchant_name=*/"CVS Pharmacy", - /*program_name=*/"CVS Extra", - /*program_logo=*/GURL("https://empty.url.com"), - /*loyalty_card_number=*/"987654321987654321", - {GURL("https://domain1.example"), - GURL("https://common-domain.example")}), - LoyaltyCard(/*loyalty_card_id=*/ValuableId("loyalty_card_id_3"), - /*merchant_name=*/"Walgreens", - /*program_name=*/"CustomerCard", - /*program_logo=*/GURL("https://empty.url.com"), - /*loyalty_card_number=*/"998766823", - {GURL("https://domain2.example"), - GURL("https://common-domain.example")}), - LoyaltyCard(/*loyalty_card_id=*/ValuableId("loyalty_card_id_2"), - /*merchant_name=*/"Ticket Maester", - /*program_name=*/"TourLoyal", - /*program_logo=*/GURL("https://empty.url.com"), - /*loyalty_card_number=*/"37262999281", - {GURL("https://domain2.example"), - GURL("https://common-domain.example")})}; - test_api(valuables_data_manager()).SetLoyaltyCards(loyalty_cards); - EXPECT_THAT( GetLoyaltyCardSuggestions(valuables_data_manager(), - GURL("https://common-domain.example/test")), + GURL("https://common-domain.example/test"), + /*trigger_field_is_autofilled=*/false), testing::ElementsAre( EqualsSuggestion( SuggestionType::kLoyaltyCardEntry, u"987654321987654321", @@ -235,14 +270,12 @@ {{Suggestion::Text(u"Walgreens")}}, Suggestion::Guid("loyalty_card_id_3")), EqualsSuggestion(SuggestionType::kSeparator), - EqualsSuggestion( - SuggestionType::kManageLoyaltyCard, - l10n_util::GetStringUTF16(IDS_AUTOFILL_MANAGE_LOYALTY_CARDS), - Suggestion::Icon::kSettings))); + EqualsManageLoyaltyCardsSuggestion())); } TEST_F(ValuableSuggestionGeneratorTest, GetLoyaltyCardSuggestions_SuggestionsCustomIcon) { + test_api(valuables_data_manager()).ClearLoyaltyCards(); const GURL program_logo = GURL("https://empty.url.com"); gfx::Image fake_image = CustomIconForTest(); test_api(valuables_data_manager()) @@ -257,7 +290,8 @@ test_api(valuables_data_manager()).NotifyObservers(); std::vector<Suggestion> suggestions = GetLoyaltyCardSuggestions( - valuables_data_manager(), GURL("https://common-domain.example/test")); + valuables_data_manager(), GURL("https://common-domain.example/test"), + /*trigger_field_is_autofilled=*/false); EXPECT_THAT(suggestions, testing::ElementsAre( @@ -267,10 +301,7 @@ {{Suggestion::Text(u"CVS Pharmacy")}}, Suggestion::Guid("loyalty_card_id_1")), EqualsSuggestion(SuggestionType::kSeparator), - EqualsSuggestion(SuggestionType::kManageLoyaltyCard, - l10n_util::GetStringUTF16( - IDS_AUTOFILL_MANAGE_LOYALTY_CARDS), - Suggestion::Icon::kSettings))); + EqualsManageLoyaltyCardsSuggestion())); #if BUILDFLAG(GOOGLE_CHROME_BRANDING) EXPECT_THAT(suggestions.back(), HasTrailingIcon(Suggestion::Icon::kGoogleWallet)); @@ -366,10 +397,7 @@ {{Suggestion::Text(u"CVS Pharmacy")}}, Suggestion::Guid("loyalty_card_id_1")), EqualsSuggestion(SuggestionType::kSeparator), - EqualsSuggestion( - SuggestionType::kManageLoyaltyCard, - l10n_util::GetStringUTF16(IDS_AUTOFILL_MANAGE_LOYALTY_CARDS), - Suggestion::Icon::kSettings))); + EqualsManageLoyaltyCardsSuggestion())); #if BUILDFLAG(GOOGLE_CHROME_BRANDING) EXPECT_THAT(lc_submenu_suggestion, HasIcon(Suggestion::Icon::kGoogleWalletMonochrome)); @@ -378,6 +406,7 @@ #endif #endif // BUILDFLAG(IS_ANDROID) } + TEST_F(ValuableSuggestionGeneratorTest, ExtendEmailSuggestionsWithLoyaltyCardSuggestions_NoLoyaltyCards) { test_api(valuables_data_manager()).SetLoyaltyCards({}); @@ -498,15 +527,13 @@ {{Suggestion::Text(u"Ticket Maester")}}, Suggestion::Guid("loyalty_card_id_2")), EqualsSuggestion(SuggestionType::kSeparator), - EqualsSuggestion( - SuggestionType::kManageLoyaltyCard, - l10n_util::GetStringUTF16(IDS_AUTOFILL_MANAGE_LOYALTY_CARDS), - Suggestion::Icon::kSettings))); + EqualsManageLoyaltyCardsSuggestion())); #endif // BUILDFLAG(IS_ANDROID) } TEST_F(ValuableSuggestionGeneratorTest, GetLoyaltyCardSuggestions_SuggestionsIPH) { + test_api(valuables_data_manager()).ClearLoyaltyCards(); test_api(valuables_data_manager()) .AddLoyaltyCard(LoyaltyCard( /*loyalty_card_id=*/ValuableId("loyalty_card_id_1"), @@ -520,7 +547,8 @@ &feature_engagement::kIPHAutofillEnableLoyaltyCardsFeature; EXPECT_THAT( GetLoyaltyCardSuggestions(valuables_data_manager(), - GURL("https://common-domain.example/test")), + GURL("https://common-domain.example/test"), + /*trigger_field_is_autofilled=*/false), testing::ElementsAre(HasIphFeature(kIphFeature), HasNoIphFeature(), HasNoIphFeature())); }
diff --git a/components/autofill/core/common/autofill_features.cc b/components/autofill/core/common/autofill_features.cc index 37be8b8a..661bea8b 100644 --- a/components/autofill/core/common/autofill_features.cc +++ b/components/autofill/core/common/autofill_features.cc
@@ -303,6 +303,16 @@ "AutofillExtractOnlyNonAdFrames", base::FEATURE_DISABLED_BY_DEFAULT); +// LINT.IfChange(autofill_ignore_checkable_elements) +// If enabled, checkboxes and radio buttons aren't extracted anymore. +// TODO(crbug.com/40283901): Remove once launched. Also remove +// - autofill::FormControlType::kInputCheckbox +// - autofill::FormControlType::kInputRadio +BASE_FEATURE(kAutofillIgnoreCheckableElements, + "AutofillIgnoreCheckableElements", + base::FEATURE_DISABLED_BY_DEFAULT); +// LINT.ThenChange(//components/autofill/ios/form_util/resources/autofill_form_features.ts:autofill_ignore_checkable_elements) + // When enabled, address field swapping suggestions will not include a // suggestion matching the field's current value. This decreases noises in the // suggestion UI.
diff --git a/components/autofill/core/common/autofill_features.h b/components/autofill/core/common/autofill_features.h index 6e9e94d..a181fc8 100644 --- a/components/autofill/core/common/autofill_features.h +++ b/components/autofill/core/common/autofill_features.h
@@ -110,6 +110,8 @@ COMPONENT_EXPORT(AUTOFILL) BASE_DECLARE_FEATURE(kAutofillExtractOnlyNonAdFrames); COMPONENT_EXPORT(AUTOFILL) +BASE_DECLARE_FEATURE(kAutofillIgnoreCheckableElements); +COMPONENT_EXPORT(AUTOFILL) BASE_DECLARE_FEATURE(kAutofillOptimizeFormExtraction); COMPONENT_EXPORT(AUTOFILL) BASE_DECLARE_FEATURE(kAutofillImproveAddressFieldSwapping);
diff --git a/components/autofill/core/common/form_field_data.cc b/components/autofill/core/common/form_field_data.cc index 087da9c..f01412e 100644 --- a/components/autofill/core/common/form_field_data.cc +++ b/components/autofill/core/common/form_field_data.cc
@@ -459,7 +459,11 @@ i <= base::to_underlying(FormControlType::kMaxValue); ++i) { FormControlType type = static_cast<FormControlType>(i); if (mojom::IsKnownEnumValue(type) && - type_string == FormControlTypeToString(type)) { + type_string == FormControlTypeToString(type) && + ((type != FormControlType::kInputCheckbox && + type != FormControlType::kInputRadio) || + !base::FeatureList::IsEnabled( + features::kAutofillIgnoreCheckableElements))) { return type; } }
diff --git a/components/autofill/ios/form_util/autofill_form_features_injector.mm b/components/autofill/ios/form_util/autofill_form_features_injector.mm index 110e067..6759038 100644 --- a/components/autofill/ios/form_util/autofill_form_features_injector.mm +++ b/components/autofill/ios/form_util/autofill_form_features_injector.mm
@@ -45,6 +45,11 @@ features::kAutofillAcrossIframesIosThrottling)); AutofillFormFeaturesJavaScriptFeature::GetInstance() + ->SetAutofillIgnoreCheckableElements( + web_frame, base::FeatureList::IsEnabled( + features::kAutofillIgnoreCheckableElements)); + + AutofillFormFeaturesJavaScriptFeature::GetInstance() ->SetAutofillIsolatedContentWorld( web_frame, base::FeatureList::IsEnabled(kAutofillIsolatedWorldForJavascriptIos));
diff --git a/components/autofill/ios/form_util/autofill_form_features_injector_unittest.mm b/components/autofill/ios/form_util/autofill_form_features_injector_unittest.mm index a68d390..d8f2541d 100644 --- a/components/autofill/ios/form_util/autofill_form_features_injector_unittest.mm +++ b/components/autofill/ios/form_util/autofill_form_features_injector_unittest.mm
@@ -62,6 +62,7 @@ {kAutofillIsolatedWorldForJavascriptIos, autofill::features::kAutofillAcrossIframesIos, autofill::features::kAutofillAcrossIframesIosThrottling, + autofill::features::kAutofillIgnoreCheckableElements, kAutofillCorrectUserEditedBitInParsedField, kAutofillAllowDefaultPreventedSubmission, kAutofillDedupeFormSubmission, kAutofillReportFormSubmissionErrors, @@ -84,6 +85,8 @@ u"__gCrWeb.autofill_form_features." u"setAutofillAcrossIframesThrottling(true);", u"__gCrWeb.autofill_form_features." + u"setAutofillIgnoreCheckableElements(true);", + u"__gCrWeb.autofill_form_features." u"setAutofillCorrectUserEditedBitInParsedField(true);", u"__gCrWeb.autofill_form_features." u"setAutofillAllowDefaultPreventedSubmission(true);",
diff --git a/components/autofill/ios/form_util/autofill_form_features_java_script_feature.h b/components/autofill/ios/form_util/autofill_form_features_java_script_feature.h index 4250097..6b9bab7d 100644 --- a/components/autofill/ios/form_util/autofill_form_features_java_script_feature.h +++ b/components/autofill/ios/form_util/autofill_form_features_java_script_feature.h
@@ -33,6 +33,10 @@ // AutofillAcrossIframes in `frame`. void SetAutofillAcrossIframesThrottling(web::WebFrame* frame, bool enabled); + // Enables/disables whether checkboxes and radio buttons are ignored during + // form extraction. + void SetAutofillIgnoreCheckableElements(web::WebFrame* frame, bool enabled); + // Enables/disables the renderer side behaviours in `frame` needed for // Autofill features to work in an isolated content world. void SetAutofillIsolatedContentWorld(web::WebFrame* frame, bool enabled);
diff --git a/components/autofill/ios/form_util/autofill_form_features_java_script_feature.mm b/components/autofill/ios/form_util/autofill_form_features_java_script_feature.mm index c4d22a7..1f407d2 100644 --- a/components/autofill/ios/form_util/autofill_form_features_java_script_feature.mm +++ b/components/autofill/ios/form_util/autofill_form_features_java_script_feature.mm
@@ -61,6 +61,15 @@ base::Value::List().Append(enabled)); } +void AutofillFormFeaturesJavaScriptFeature::SetAutofillIgnoreCheckableElements( + web::WebFrame* frame, + bool enabled) { + CHECK(frame); + frame->CallJavaScriptFunction( + "autofill_form_features.setAutofillIgnoreCheckableElements", + base::Value::List().Append(enabled)); +} + void AutofillFormFeaturesJavaScriptFeature::SetAutofillIsolatedContentWorld( web::WebFrame* frame, bool enabled) {
diff --git a/components/autofill/ios/form_util/resources/autofill_form_features.ts b/components/autofill/ios/form_util/resources/autofill_form_features.ts index f6890b4..7fd9f39 100644 --- a/components/autofill/ios/form_util/resources/autofill_form_features.ts +++ b/components/autofill/ios/form_util/resources/autofill_form_features.ts
@@ -24,6 +24,13 @@ let autofillAcrossIframesThrottling: boolean = false; // LINT.ThenChange(//components/autofill/core/common/autofill_features.cc:autofill_across_iframes_ios) +// LINT.IfChange(autofill_ignore_checkable_elements) +/** + * If true, checkboxes and radio buttons aren't extracted anymore. + */ +let autofillIgnoreCheckableElements: boolean = false; +// LINT.ThenChange(//components/autofill/core/common/autofill_features.cc:autofill_ignore_checkable_elements) + // LINT.IfChange(autofill_isolated_content_world) /** Enables the logic necessary for Autofill to work from an isolated content world @@ -99,6 +106,20 @@ } /** + * @see autofillIgnoreCheckableElements + */ +function setAutofillIgnoreCheckableElements(enabled: boolean): void { + autofillIgnoreCheckableElements = enabled; +} + +/** + * @see autofillIgnoreCheckableElements + */ +function isAutofillIgnoreCheckableElementsEnabled(): boolean { + return autofillIgnoreCheckableElements; +} + +/** * @see autofillIsolatedContentWorld */ function setAutofillIsolatedContentWorld(enabled: boolean): void { @@ -191,6 +212,8 @@ isAutofillAcrossIframesEnabled, setAutofillAcrossIframesThrottling, isAutofillAcrossIframesThrottlingEnabled, + setAutofillIgnoreCheckableElements, + isAutofillIgnoreCheckableElementsEnabled, setAutofillIsolatedContentWorld, isAutofillIsolatedContentWorldEnabled, setAutofillCorrectUserEditedBitInParsedField,
diff --git a/components/autofill/ios/form_util/resources/fill_element_inference_util.ts b/components/autofill/ios/form_util/resources/fill_element_inference_util.ts index 3664879..32acd71 100644 --- a/components/autofill/ios/form_util/resources/fill_element_inference_util.ts +++ b/components/autofill/ios/form_util/resources/fill_element_inference_util.ts
@@ -324,7 +324,10 @@ * can be autofilled. */ gCrWebLegacy.fill.isAutofillableInputElement = function(element: Element): boolean { - return isTextField(element) || gCrWebLegacy.fill.isCheckableElement(element); + return isTextField(element) || + (gCrWebLegacy.fill.isCheckableElement(element) && + !gCrWebLegacy.autofill_form_features + .isAutofillIgnoreCheckableElementsEnabled()); }; /**
diff --git a/components/cronet/gn2bp/run_gn2bp.py b/components/cronet/gn2bp/run_gn2bp.py index e9f87153..e0276b0 100755 --- a/components/cronet/gn2bp/run_gn2bp.py +++ b/components/cronet/gn2bp/run_gn2bp.py
@@ -263,7 +263,10 @@ && vpython3 components/cronet/gn2bp/run_gn2bp.py --channel={import_channel} The state of Chromium, for the commit being imported, can be browsed at: - https://chromium.googlesource.com/chromium/src/+/{commit_hash}""") + https://chromium.googlesource.com/chromium/src/+/{commit_hash} + + NO_IFTTT=Imported from Chromium. + """) additional_parameters = [ '--ignore-noop', '--force-message',
diff --git a/components/gwp_asan/client/extreme_lightweight_detector_quarantine.cc b/components/gwp_asan/client/extreme_lightweight_detector_quarantine.cc index cc0f72a..be459d3 100644 --- a/components/gwp_asan/client/extreme_lightweight_detector_quarantine.cc +++ b/components/gwp_asan/client/extreme_lightweight_detector_quarantine.cc
@@ -9,6 +9,16 @@ namespace gwp_asan::internal { +// Making this non-trivial dtor to allow use of `base::NoDestructor`. +// This explicit dtor is not needed in most build configurations because +// `raw_ptr<T>` has a non-trivial dtor. However, `raw_ptr<T>` does not guarantee +// it and we want to avoid the code here getting affected by `raw_ptr<T>`'s +// internal implementation. +// TODO(yukishiino): Make this trivially destructible. +// NOLINTNEXTLINE(modernize-use-equals-default) +ExtremeLightweightDetectorQuarantineRoot:: + ~ExtremeLightweightDetectorQuarantineRoot() {} + ExtremeLightweightDetectorQuarantineBranch ExtremeLightweightDetectorQuarantineRoot::CreateBranch( const ExtremeLightweightDetectorQuarantineBranchConfig& config) {
diff --git a/components/gwp_asan/client/extreme_lightweight_detector_quarantine.h b/components/gwp_asan/client/extreme_lightweight_detector_quarantine.h index 060e562..f4d6c14 100644 --- a/components/gwp_asan/client/extreme_lightweight_detector_quarantine.h +++ b/components/gwp_asan/client/extreme_lightweight_detector_quarantine.h
@@ -68,6 +68,7 @@ explicit ExtremeLightweightDetectorQuarantineRoot( partition_alloc::PartitionRoot& allocator_root) : allocator_root_(allocator_root) {} + ~ExtremeLightweightDetectorQuarantineRoot(); ExtremeLightweightDetectorQuarantineBranch CreateBranch( const ExtremeLightweightDetectorQuarantineBranchConfig& config);
diff --git a/components/metrics/fre_source_trial.h b/components/metrics/fre_source_trial.h index 85cff1d..396452b 100644 --- a/components/metrics/fre_source_trial.h +++ b/components/metrics/fre_source_trial.h
@@ -33,4 +33,4 @@ } // namespace metrics::fre_source_trial -#endif // COMPONENTS_METRICS_FILE_METRICS_PROVIDER_FRE_KEEP_SOURCE_TRIAL_H_ +#endif // COMPONENTS_METRICS_FRE_SOURCE_TRIAL_H_
diff --git a/components/password_manager/core/browser/leak_detection_delegate.cc b/components/password_manager/core/browser/leak_detection_delegate.cc index b52f71e9..d5106c4b 100644 --- a/components/password_manager/core/browser/leak_detection_delegate.cc +++ b/components/password_manager/core/browser/leak_detection_delegate.cc
@@ -199,7 +199,7 @@ HasChangePasswordUrl has_change_url( client_->GetPasswordChangeService() && client_->GetPasswordChangeService()->IsPasswordChangeSupported( - details.origin)); + details.origin, client_->GetPageLanguage())); if (has_change_url) { details.leak_type |= CredentialLeakFlags::kHasChangePasswordUrl; }
diff --git a/components/password_manager/core/browser/leak_detection_delegate_unittest.cc b/components/password_manager/core/browser/leak_detection_delegate_unittest.cc index 4a7a786..fb94cfa 100644 --- a/components/password_manager/core/browser/leak_detection_delegate_unittest.cc +++ b/components/password_manager/core/browser/leak_detection_delegate_unittest.cc
@@ -90,6 +90,7 @@ GetPasswordChangeService, (), (const override)); + MOCK_METHOD(autofill::LanguageCode, GetPageLanguage, (), (const override)); MOCK_METHOD(version_info::Channel, GetChannel, (), (const override)); MOCK_METHOD(affiliations::AffiliationService*, GetAffiliationService, @@ -109,7 +110,10 @@ class MockPasswordChangeService : public PasswordChangeServiceInterface { public: MOCK_METHOD(bool, IsPasswordChangeAvailable, (), (override)); - MOCK_METHOD(bool, IsPasswordChangeSupported, (const GURL& url), (override)); + MOCK_METHOD(bool, + IsPasswordChangeSupported, + (const GURL&, const autofill::LanguageCode&), + (override)); }; } // namespace @@ -896,7 +900,10 @@ MockPasswordChangeService mock_password_change_service; EXPECT_CALL(client(), GetPasswordChangeService()) .WillRepeatedly(Return(&mock_password_change_service)); - EXPECT_CALL(mock_password_change_service, IsPasswordChangeSupported(form.url)) + EXPECT_CALL(client(), GetPageLanguage()) + .WillRepeatedly(Return(autofill::LanguageCode("en"))); + EXPECT_CALL(mock_password_change_service, + IsPasswordChangeSupported(form.url, autofill::LanguageCode("en"))) .WillOnce(Return(true)); EXPECT_CALL(client(), NotifyUserCredentialsWereLeaked(LeakedPasswordDetails( password_manager::CreateLeakType( @@ -918,7 +925,10 @@ .WillRepeatedly(Return(profile_store())); EXPECT_CALL(client(), GetPasswordChangeService()) .WillRepeatedly(Return(&mock_password_change_service)); - EXPECT_CALL(mock_password_change_service, IsPasswordChangeSupported(form.url)) + EXPECT_CALL(client(), GetPageLanguage()) + .WillRepeatedly(Return(autofill::LanguageCode("ru"))); + EXPECT_CALL(mock_password_change_service, + IsPasswordChangeSupported(form.url, autofill::LanguageCode("ru"))) .WillOnce(Return(true)); ExpectPasswords({});
diff --git a/components/password_manager/core/browser/password_change_service_interface.h b/components/password_manager/core/browser/password_change_service_interface.h index 017db990..eae75a2 100644 --- a/components/password_manager/core/browser/password_change_service_interface.h +++ b/components/password_manager/core/browser/password_change_service_interface.h
@@ -6,6 +6,7 @@ #define COMPONENTS_PASSWORD_MANAGER_CORE_BROWSER_PASSWORD_CHANGE_SERVICE_INTERFACE_H_ #include "base/functional/callback_forward.h" +#include "components/autofill/core/common/language_code.h" class GURL; @@ -17,8 +18,11 @@ // Checks whether current user is eligible to use password change. virtual bool IsPasswordChangeAvailable() = 0; - // Checks whether password change is eligible for a given `url`. - virtual bool IsPasswordChangeSupported(const GURL& url) = 0; + // Checks whether password change is eligible for a given `url` and + // `page_language`. + virtual bool IsPasswordChangeSupported( + const GURL& url, + const autofill::LanguageCode& page_language) = 0; }; } // namespace password_manager
diff --git a/components/password_manager/core/browser/password_form_filling_unittest.cc b/components/password_manager/core/browser/password_form_filling_unittest.cc index 5e2e0c3..1e38942 100644 --- a/components/password_manager/core/browser/password_form_filling_unittest.cc +++ b/components/password_manager/core/browser/password_form_filling_unittest.cc
@@ -90,7 +90,10 @@ class MockPasswordChangeService : public PasswordChangeServiceInterface { public: MOCK_METHOD(bool, IsPasswordChangeAvailable, (), (override)); - MOCK_METHOD(bool, IsPasswordChangeSupported, (const GURL& url), (override)); + MOCK_METHOD(bool, + IsPasswordChangeSupported, + (const GURL&, const autofill::LanguageCode&), + (override)); }; PasswordFormFillData::LoginCollection::const_iterator FindPasswordByUsername(
diff --git a/components/search_engines/android/java/src/org/chromium/components/search_engines/SearchEnginesFeatureUtils.java b/components/search_engines/android/java/src/org/chromium/components/search_engines/SearchEnginesFeatureUtils.java index 88e906fcc..51ce4b5e 100644 --- a/components/search_engines/android/java/src/org/chromium/components/search_engines/SearchEnginesFeatureUtils.java +++ b/components/search_engines/android/java/src/org/chromium/components/search_engines/SearchEnginesFeatureUtils.java
@@ -4,8 +4,6 @@ package org.chromium.components.search_engines; -import androidx.annotation.VisibleForTesting; - import org.chromium.base.CommandLine; import org.chromium.base.ResettersForTesting; import org.chromium.build.annotations.NullMarked; @@ -67,30 +65,11 @@ return CommandLine.getInstance().hasSwitch(ENABLE_CHOICE_APIS_FAKE_BACKEND_SWITCH); } - /** - * Number of blocked Chrome sessions after which we suppress the blocking dialog. This is - * intended as an escape hatch for initial iterations of the feature, to mitigate potential - * bugs. - */ - public int clayBlockingEscapeHatchBlockLimit() { - return clayBlockingFeatureParamAsInt("escape_hatch_block_limit", 10); - } - /** Number of times a failed backend call will be retried. */ public int choiceApisConnectionMaxRetries() { return CHOICE_APIS_CONNECTION_MAX_RETRIES; } - @VisibleForTesting - static int clayBlockingFeatureParamAsInt(String param, int defaultValue) { - assert SearchEnginesFeatures.isEnabled(SearchEnginesFeatures.CLAY_BLOCKING) - : "Avoid accessing params on disabled features!"; - - return SearchEnginesFeatureMap.getInstance() - .getFieldTrialParamByFeatureAsInt( - SearchEnginesFeatures.CLAY_BLOCKING, param, defaultValue); - } - // Do not instantiate this class. private SearchEnginesFeatureUtils() {} }
diff --git a/components/search_engines/android/java/src/org/chromium/components/search_engines/SearchEnginesFeatureUtilsUnitTest.java b/components/search_engines/android/java/src/org/chromium/components/search_engines/SearchEnginesFeatureUtilsUnitTest.java index 58f4fcd..659b9d0 100644 --- a/components/search_engines/android/java/src/org/chromium/components/search_engines/SearchEnginesFeatureUtilsUnitTest.java +++ b/components/search_engines/android/java/src/org/chromium/components/search_engines/SearchEnginesFeatureUtilsUnitTest.java
@@ -4,13 +4,9 @@ package org.chromium.components.search_engines; -import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; -import static org.chromium.components.search_engines.SearchEnginesFeatures.CLAY_BLOCKING; - import androidx.test.filters.SmallTest; import org.junit.After; @@ -18,7 +14,6 @@ import org.junit.runner.RunWith; import org.chromium.base.CommandLine; -import org.chromium.base.FeatureOverrides; import org.chromium.base.test.BaseRobolectricTestRunner; @SmallTest @@ -49,30 +44,4 @@ .appendSwitch(SearchEnginesFeatureUtils.ENABLE_CHOICE_APIS_FAKE_BACKEND_SWITCH); assertTrue(SearchEnginesFeatureUtils.getInstance().isChoiceApisFakeBackendEnabled()); } - - @Test - public void clayBlockingFeatureParamAsInt() { - FeatureOverrides.Builder overrides = FeatureOverrides.newBuilder().enable(CLAY_BLOCKING); - overrides.apply(); - assertEquals(42, SearchEnginesFeatureUtils.clayBlockingFeatureParamAsInt("int_param", 42)); - - overrides.param("int_param", 0).apply(); - assertEquals(0, SearchEnginesFeatureUtils.clayBlockingFeatureParamAsInt("int_param", 42)); - - overrides.param("int_param", 24).apply(); - assertEquals(24, SearchEnginesFeatureUtils.clayBlockingFeatureParamAsInt("int_param", 42)); - - overrides.param("int_param", "").apply(); - assertThrows( - NumberFormatException.class, - () -> SearchEnginesFeatureUtils.clayBlockingFeatureParamAsInt("int_param", 42)); - - overrides.param("int_param", -24).apply(); - assertEquals(-24, SearchEnginesFeatureUtils.clayBlockingFeatureParamAsInt("int_param", 42)); - - overrides.param("int_param", "bad input").apply(); - assertThrows( - NumberFormatException.class, - () -> SearchEnginesFeatureUtils.clayBlockingFeatureParamAsInt("int_param", 42)); - } }
diff --git a/components/search_engines/android/search_engines_feature_map.cc b/components/search_engines/android/search_engines_feature_map.cc index a47654c..4bdace6c 100644 --- a/components/search_engines/android/search_engines_feature_map.cc +++ b/components/search_engines/android/search_engines_feature_map.cc
@@ -21,7 +21,7 @@ // Array of features exposed through the Java BaseFeatureMap API. Entries in // this array refer to features defined in //search_engines features. const base::Feature* const kFeaturesExposedToJava[] = { - &switches::kClayBlocking, &switches::kClaySnackbar}; + &switches::kClaySnackbar}; // static base::android::FeatureMap* GetFeatureMap() {
diff --git a/components/search_engines/search_engines_switches.cc b/components/search_engines/search_engines_switches.cc index 817049d..befd748 100644 --- a/components/search_engines/search_engines_switches.cc +++ b/components/search_engines/search_engines_switches.cc
@@ -42,16 +42,6 @@ /*feature=*/&kInvalidateSearchEngineChoiceOnDeviceRestoreDetection, /*name=*/"is_retroactive", /*default_value=*/false}; -// Enables the search engine choice screen. Feature parameters below can -// affect the actual triggering logic. -// The default feature state is split by platform to ease potential merges -// that could be needed if we need to change the state while waterfalling this -// feature. -COMPONENT_EXPORT(SEARCH_ENGINES_SWITCHES) -BASE_FEATURE(kSearchEngineChoiceTrigger, - "SearchEngineChoiceTrigger", - base::FEATURE_ENABLED_BY_DEFAULT); - // Use an explicit "NO_REPROMPT" value as default to avoid reprompting users // who saw the choice screen in M121. COMPONENT_EXPORT(SEARCH_ENGINES_SWITCHES) @@ -64,19 +54,8 @@ /*name=*/"reprompt", /*default_value=*/kSearchEngineChoiceNoRepromptString}; -#if BUILDFLAG(IS_IOS) -COMPONENT_EXPORT(SEARCH_ENGINES_SWITCHES) -extern const base::FeatureParam<int> kSearchEngineChoiceMaximumSkipCount{ - &kSearchEngineChoiceTrigger, - /*name=*/"maximum_skip_count", - /*default_value=*/10}; -#endif - #if BUILDFLAG(IS_ANDROID) COMPONENT_EXPORT(SEARCH_ENGINES_SWITCHES) -BASE_FEATURE(kClayBlocking, "ClayBlocking", base::FEATURE_ENABLED_BY_DEFAULT); - -COMPONENT_EXPORT(SEARCH_ENGINES_SWITCHES) BASE_FEATURE(kClaySnackbar, "ClaySnackbar", base::FEATURE_ENABLED_BY_DEFAULT); #endif
diff --git a/components/search_engines/search_engines_switches.h b/components/search_engines/search_engines_switches.h index 9270f481..ee86f24 100644 --- a/components/search_engines/search_engines_switches.h +++ b/components/search_engines/search_engines_switches.h
@@ -32,9 +32,6 @@ COMPONENT_EXPORT(SEARCH_ENGINES_SWITCHES) extern const base::FeatureParam<bool> kInvalidateChoiceOnRestoreIsRetroactive; -COMPONENT_EXPORT(SEARCH_ENGINES_SWITCHES) -BASE_DECLARE_FEATURE(kSearchEngineChoiceTrigger); - // The string that's passed to // `switches::kSearchEngineChoiceTriggerRepromptParams` so that we don't // reprompt users with the choice screen. @@ -55,22 +52,7 @@ extern const base::FeatureParam<std::string> kSearchEngineChoiceTriggerRepromptParams; -#if BUILDFLAG(IS_IOS) -// Maximum number of time the search engine choice screen can be skipped -// because the application is started via an external intent. Once this -// count is reached, the search engine choice screen is presented on all -// restart until the user has made a decision. -COMPONENT_EXPORT(SEARCH_ENGINES_SWITCHES) -extern const base::FeatureParam<int> kSearchEngineChoiceMaximumSkipCount; - -#endif - #if BUILDFLAG(IS_ANDROID) -// Enables the blocking dialog that directs users to complete their choice of -// default apps (for Browser & Search) in Android. -COMPONENT_EXPORT(SEARCH_ENGINES_SWITCHES) -BASE_DECLARE_FEATURE(kClayBlocking); - // Enables showing a snackbar when users change their default search engine in // Android. COMPONENT_EXPORT(SEARCH_ENGINES_SWITCHES)
diff --git a/components/services/app_service/public/cpp/protocol_handler_info.cc b/components/services/app_service/public/cpp/protocol_handler_info.cc index 1ebdc84..f77cdf0f 100644 --- a/components/services/app_service/public/cpp/protocol_handler_info.cc +++ b/components/services/app_service/public/cpp/protocol_handler_info.cc
@@ -17,6 +17,9 @@ base::Value::Dict root; root.Set("protocol", protocol); root.Set("url", url.spec()); + if (!name.empty()) { + root.Set("name", name); + } return base::Value(std::move(root)); }
diff --git a/components/services/app_service/public/cpp/protocol_handler_info.h b/components/services/app_service/public/cpp/protocol_handler_info.h index 8c8f9f3..a762bda 100644 --- a/components/services/app_service/public/cpp/protocol_handler_info.h +++ b/components/services/app_service/public/cpp/protocol_handler_info.h
@@ -26,6 +26,7 @@ std::string protocol; GURL url; + std::string name; }; using ProtocolHandlers = std::vector<ProtocolHandlerInfo>;
diff --git a/components/signin/internal/identity_manager/mutable_profile_oauth2_token_service_delegate.cc b/components/signin/internal/identity_manager/mutable_profile_oauth2_token_service_delegate.cc index c2da2c0..5f8dab1 100644 --- a/components/signin/internal/identity_manager/mutable_profile_oauth2_token_service_delegate.cc +++ b/components/signin/internal/identity_manager/mutable_profile_oauth2_token_service_delegate.cc
@@ -359,6 +359,7 @@ // `GaiaAccessTokenFetcher` doesn't support bound refresh tokens. auto fetcher = std::make_unique<OAuth2MintAccessTokenFetcherAdapter>( consumer, url_loader_factory, gaia_id, refresh_token, + is_refresh_token_bound, signin::GetSigninScopedDeviceId(client_->GetPrefs()), std::string(version_info::GetVersionNumber()), std::string( @@ -645,13 +646,13 @@ if (load_account) { if (!revoke_token && should_reencrypt) { - did_reencrypt = true; - PersistCredentials(account_id, refresh_token + did_reencrypt = true; + PersistCredentials(account_id, refresh_token #if BUILDFLAG(ENABLE_BOUND_SESSION_CREDENTIALS) - , - wrapped_binding_key + , + wrapped_binding_key #endif // BUILDFLAG(ENABLE_BOUND_SESSION_CREDENTIALS) - ); + ); } RecordAccountAvailabilityStartup(account_id, refresh_token); @@ -870,6 +871,8 @@ const CoreAccountId& account_id) { bool should_update_credentials = true; std::string refresh_token = GetRefreshToken(account_id); + AccountMoveDecision move_decision = + AccountMoveDecision::kCanMoveWithRefreshToken; #if BUILDFLAG(ENABLE_BOUND_SESSION_CREDENTIALS) std::vector<uint8_t> wrapped_binding_key = GetWrappedBindingKey(account_id); if (!CanMoveAccountToService(*to_service, account_id, wrapped_binding_key)) { @@ -877,13 +880,17 @@ // `to_service` already has this account. Do not override the existing, // potentially valid token. should_update_credentials = false; + move_decision = AccountMoveDecision::kCannotMoveAlreadyExists; } else { // Insert an account without a token. refresh_token = GaiaConstants::kInvalidRefreshToken; wrapped_binding_key = std::vector<uint8_t>(); + move_decision = AccountMoveDecision::kCannotMoveInsertWithoutRefreshToken; } } #endif // BUILDFLAG(ENABLE_BOUND_SESSION_CREDENTIALS) + base::UmaHistogramEnumeration("Signin.MoveAccount.CanMoveToService", + move_decision); if (should_update_credentials) { to_service->UpdateCredentials(
diff --git a/components/signin/internal/identity_manager/mutable_profile_oauth2_token_service_delegate.h b/components/signin/internal/identity_manager/mutable_profile_oauth2_token_service_delegate.h index b43a796..6434d512b 100644 --- a/components/signin/internal/identity_manager/mutable_profile_oauth2_token_service_delegate.h +++ b/components/signin/internal/identity_manager/mutable_profile_oauth2_token_service_delegate.h
@@ -52,6 +52,17 @@ kExplicitRevoke = 2 }; +// These values are persisted to logs. Entries should not be renumbered and +// numeric values should never be reused. +// LINT.IfChange(AccountMoveDecision) +enum class AccountMoveDecision { + kCanMoveWithRefreshToken = 0, + kCannotMoveAlreadyExists = 1, + kCannotMoveInsertWithoutRefreshToken = 2, + kMaxValue = kCannotMoveInsertWithoutRefreshToken +}; +// LINT.ThenChange(//tools/metrics/histograms/metadata/signin/enums.xml:AccountMoveDecision) + class MutableProfileOAuth2TokenServiceDelegate : public ProfileOAuth2TokenServiceDelegate, public WebDataServiceConsumer,
diff --git a/components/signin/internal/identity_manager/mutable_profile_oauth2_token_service_delegate_unittest.cc b/components/signin/internal/identity_manager/mutable_profile_oauth2_token_service_delegate_unittest.cc index 553c40a..b0c4d056 100644 --- a/components/signin/internal/identity_manager/mutable_profile_oauth2_token_service_delegate_unittest.cc +++ b/components/signin/internal/identity_manager/mutable_profile_oauth2_token_service_delegate_unittest.cc
@@ -83,6 +83,7 @@ AccountCredentials account_before_move; std::vector<AccountCredentials> existing_accounts; AccountCredentials account_after_move; + AccountMoveDecision expected_move_decision; }; const ExtractCredentialsTestCase kExtractCredentialsTestCases[] = { @@ -90,30 +91,37 @@ .account_before_move = {GaiaId("A"), "new_tokenA", {1}}, .existing_accounts = {{GaiaId("A"), "old_tokenA", {2}}, {GaiaId("B"), "old_tokenB", {2}}}, - .account_after_move = {GaiaId("A"), "old_tokenA", {2}}}, + .account_after_move = {GaiaId("A"), "old_tokenA", {2}}, + .expected_move_decision = AccountMoveDecision::kCannotMoveAlreadyExists}, {.test_suffix = "NotMovedKeyConflictExistingUnbound", .account_before_move = {GaiaId("A"), "new_tokenA", {1}}, .existing_accounts = {{GaiaId("A"), "old_tokenA"}, {GaiaId("B"), "old_tokenB", {2}}}, - .account_after_move = {GaiaId("A"), "old_tokenA"}}, + .account_after_move = {GaiaId("A"), "old_tokenA"}, + .expected_move_decision = AccountMoveDecision::kCannotMoveAlreadyExists}, {.test_suffix = "MovedBoundOverridesExisting", .account_before_move = {GaiaId("A"), "new_tokenA", {1}}, .existing_accounts = {{GaiaId("A"), "old_tokenA", {2}}, {GaiaId("B"), "old_tokenB"}}, - .account_after_move = {GaiaId("A"), "new_tokenA", {1}}}, + .account_after_move = {GaiaId("A"), "new_tokenA", {1}}, + .expected_move_decision = AccountMoveDecision::kCanMoveWithRefreshToken}, {.test_suffix = "MovedUnboundOverridesExisting", .account_before_move = {GaiaId("A"), "new_tokenA"}, .existing_accounts = {{GaiaId("A"), "old_tokenA", {2}}, {GaiaId("B"), "old_tokenB", {2}}}, - .account_after_move = {GaiaId("A"), "new_tokenA"}}, + .account_after_move = {GaiaId("A"), "new_tokenA"}, + .expected_move_decision = AccountMoveDecision::kCanMoveWithRefreshToken}, {.test_suffix = "MovedBoundNoExisting", .account_before_move = {GaiaId("A"), "new_tokenA", {1}}, .existing_accounts = {{GaiaId("B"), "old_tokenB"}}, - .account_after_move = {GaiaId("A"), "new_tokenA", {1}}}, + .account_after_move = {GaiaId("A"), "new_tokenA", {1}}, + .expected_move_decision = AccountMoveDecision::kCanMoveWithRefreshToken}, {.test_suffix = "MovedWithoutTokenKeyConflictNoExisting", .account_before_move = {GaiaId("A"), "new_tokenA", {1}}, .existing_accounts = {{GaiaId("B"), "old_tokenB", {2}}}, - .account_after_move = {GaiaId("A"), GaiaConstants::kInvalidRefreshToken}}, + .account_after_move = {GaiaId("A"), GaiaConstants::kInvalidRefreshToken}, + .expected_move_decision = + AccountMoveDecision::kCannotMoveInsertWithoutRefreshToken}, }; #endif // BUILDFLAG(ENABLE_BOUND_SESSION_CREDENTIALS) @@ -180,13 +188,17 @@ token_web_data_->Init(base::NullCallback()); } + // "/GetToken" is a default endpoint for issuing access tokens. void AddSuccessfulOAuthTokenResponse() { client_->GetTestURLLoaderFactory()->AddResponse( GaiaUrls::GetInstance()->oauth2_token_url().spec(), GetValidTokenResponse("token", 3600)); } - void AddSuccessfulBoundTokenResponse() { + // "/IssueToken" is an endpoint for issuing access tokens used with bound + // refresh tokens or when `switches::kUseIssueTokenToFetchAccessTokens` is + // enabled. + void AddSuccessfulIssueTokenResponse() { client_->GetTestURLLoaderFactory()->AddResponse( GaiaUrls::GetInstance()->oauth2_issue_token_url().spec(), GetValidBoundTokenResponse("access_token", base::Seconds(3600), @@ -1940,7 +1952,7 @@ signin_metrics::SourceForRefreshTokenOperation::kUnknown, kFakeWrappedBindingKey); - AddSuccessfulBoundTokenResponse(); + AddSuccessfulIssueTokenResponse(); EXPECT_EQ(0, access_token_success_count_); EXPECT_EQ(0, access_token_failure_count_); @@ -1970,9 +1982,7 @@ account_id, "refresh_token", signin_metrics::SourceForRefreshTokenOperation::kUnknown); - // Even though the token is not bound, the delegate will use the same fetcher - // because `kUseIssueTokenToFetchAccessTokens` is enabled. - AddSuccessfulBoundTokenResponse(); + AddSuccessfulIssueTokenResponse(); EXPECT_EQ(0, access_token_success_count_); EXPECT_EQ(0, access_token_failure_count_); @@ -2037,6 +2047,7 @@ // Extract the credentials. ResetObserverCounts(); + base::HistogramTester histogram_tester; const CoreAccountId account_to_move = CoreAccountId::FromGaiaId(GetParam().account_before_move.gaia_id); oauth2_service_delegate_->ExtractCredentials(&target_token_service, @@ -2052,6 +2063,8 @@ GetParam().account_after_move.refresh_token); EXPECT_EQ(target_delegate->GetWrappedBindingKey(account_to_move), GetParam().account_after_move.binding_key); + histogram_tester.ExpectUniqueSample("Signin.MoveAccount.CanMoveToService", + GetParam().expected_move_decision, 1); } INSTANTIATE_TEST_SUITE_P(
diff --git a/components/signin/internal/identity_manager/token_binding_oauth2_access_token_fetcher_unittest.cc b/components/signin/internal/identity_manager/token_binding_oauth2_access_token_fetcher_unittest.cc index 785fa9ea..b30af49 100644 --- a/components/signin/internal/identity_manager/token_binding_oauth2_access_token_fetcher_unittest.cc +++ b/components/signin/internal/identity_manager/token_binding_oauth2_access_token_fetcher_unittest.cc
@@ -38,6 +38,7 @@ nullptr, GaiaId(), "", + true, "", "", "") {}
diff --git a/components/signin/public/base/signin_switches.cc b/components/signin/public/base/signin_switches.cc index 861e53c5..855a252 100644 --- a/components/signin/public/base/signin_switches.cc +++ b/components/signin/public/base/signin_switches.cc
@@ -203,6 +203,10 @@ "InterceptBubblesDismissibleByAvatarButton", base::FEATURE_ENABLED_BY_DEFAULT); +BASE_FEATURE(kOfferMigrationToDiceUsers, + "OfferMigrationToDiceUsers", + base::FEATURE_DISABLED_BY_DEFAULT); + #if BUILDFLAG(IS_IOS) BASE_FEATURE(kEnableIdentityInAuthError,
diff --git a/components/signin/public/base/signin_switches.h b/components/signin/public/base/signin_switches.h index 5e92daa..5d0008a 100644 --- a/components/signin/public/base/signin_switches.h +++ b/components/signin/public/base/signin_switches.h
@@ -148,6 +148,9 @@ COMPONENT_EXPORT(SIGNIN_SWITCHES) BASE_DECLARE_FEATURE(kInterceptBubblesDismissibleByAvatarButton); +COMPONENT_EXPORT(SIGNIN_SWITCHES) +BASE_DECLARE_FEATURE(kOfferMigrationToDiceUsers); + #if BUILDFLAG(IS_IOS) // Features to enable identities in auth error (stale token).
diff --git a/components/test/data/autofill/heuristics/output/017_checkout_advanceautoparts.com.out b/components/test/data/autofill/heuristics/output/017_checkout_advanceautoparts.com.out index 4e5ccbb..0cfe628 100644 --- a/components/test/data/autofill/heuristics/output/017_checkout_advanceautoparts.com.out +++ b/components/test/data/autofill/heuristics/output/017_checkout_advanceautoparts.com.out
@@ -1,22 +1,19 @@ EMAIL_ADDRESS | logonId | Email Address: | | logonId_1 UNKNOWN_TYPE | logonPassword | Password: | | logonId_1 -UNKNOWN_TYPE | rememberMe | Remember Me on Future Visits | true | logonId_1 -UNKNOWN_TYPE | billingShippingSame | Make shipping the same as my billing address | false | billingShippingSame_1 -NAME_FIRST | billFirstName | *First Name: | | billingShippingSame_1 -NAME_LAST | billLastName | *Last Name: | | billingShippingSame_1 -ADDRESS_HOME_LINE1 | billAddress1 | *Street Address 1: | | billingShippingSame_1 -ADDRESS_HOME_LINE2 | billAddress2 | Street Address 2: | | billingShippingSame_1 -ADDRESS_HOME_CITY | billCity | *City: | | billingShippingSame_1 -ADDRESS_HOME_STATE | billState | *State: | AL | billingShippingSame_1 -ADDRESS_HOME_ZIP | billZipCode | *Zip Code: | | billingShippingSame_1 -PHONE_HOME_CITY_CODE_WITH_TRUNK_PREFIX | billDayPhonePart1 | *Day Phone: | | billingShippingSame_1 -PHONE_HOME_NUMBER_PREFIX | billDayPhonePart2 | *Day Phone: | | billingShippingSame_1 -PHONE_HOME_NUMBER_SUFFIX | billDayPhonePart3 | *Day Phone: | | billingShippingSame_1 -EMAIL_ADDRESS | billEmail | *Email Address: | | billingShippingSame_1 -UNKNOWN_TYPE | sendMeEmail | Yes, please send me emails about news, special offers, exclusives and promotions from Advance Auto Parts. See our | checked | billingShippingSame_1 -UNKNOWN_TYPE | billPassword | Password: | | billingShippingSame_1 -UNKNOWN_TYPE | billPasswordVerify | Confirm Password: | | billingShippingSame_1 +NAME_FIRST | billFirstName | *First Name: | | billFirstName_1 +NAME_LAST | billLastName | *Last Name: | | billFirstName_1 +ADDRESS_HOME_LINE1 | billAddress1 | *Street Address 1: | | billFirstName_1 +ADDRESS_HOME_LINE2 | billAddress2 | Street Address 2: | | billFirstName_1 +ADDRESS_HOME_CITY | billCity | *City: | | billFirstName_1 +ADDRESS_HOME_STATE | billState | *State: | AL | billFirstName_1 +ADDRESS_HOME_ZIP | billZipCode | *Zip Code: | | billFirstName_1 +PHONE_HOME_CITY_CODE_WITH_TRUNK_PREFIX | billDayPhonePart1 | *Day Phone: | | billFirstName_1 +PHONE_HOME_NUMBER_PREFIX | billDayPhonePart2 | *Day Phone: | | billFirstName_1 +PHONE_HOME_NUMBER_SUFFIX | billDayPhonePart3 | *Day Phone: | | billFirstName_1 +EMAIL_ADDRESS | billEmail | *Email Address: | | billFirstName_1 +UNKNOWN_TYPE | billPassword | Password: | | billFirstName_1 +UNKNOWN_TYPE | billPasswordVerify | Confirm Password: | | billFirstName_1 NAME_FIRST | shipFirstName | *First Name: | | shipFirstName_2 NAME_LAST | shipLastName | *Last Name: | | shipFirstName_2 ADDRESS_HOME_LINE1 | shipAddress1 | *Street Address 1: | | shipFirstName_2
diff --git a/components/test/data/autofill/heuristics/output/018_checkout_ae.com.out b/components/test/data/autofill/heuristics/output/018_checkout_ae.com.out index 9c456f0..731513c7 100644 --- a/components/test/data/autofill/heuristics/output/018_checkout_ae.com.out +++ b/components/test/data/autofill/heuristics/output/018_checkout_ae.com.out
@@ -7,9 +7,6 @@ ADDRESS_HOME_CITY | city | City | | country_1 ADDRESS_HOME_STATE | state | State | | country_1 ADDRESS_HOME_ZIP | zip | Zip | | country_1 -UNKNOWN_TYPE | shippingChoice | Standard3 - 7 business days$7dates: Jun 08 - Jun 14 | Standard | country_1 -UNKNOWN_TYPE | shippingChoice | Two Day2 business days$15date: Jun 07 | SecondDay | country_1 -UNKNOWN_TYPE | shippingChoice | Overnight1 business day$20date: Jun 06 | Overnight | country_1 CREDIT_CARD_TYPE | ccType | Card Type: | | ccType_1 CREDIT_CARD_NUMBER | ccNumber | Card Number: | | ccType_1 @@ -20,33 +17,24 @@ UNKNOWN_TYPE | birthDayBMLField | Birthday: CLOSEWe need your birthday for the Bill Me Later® credit check process. | | birthMonthBMLField_2 UNKNOWN_TYPE | birthYearBMLField | Birthday: CLOSEWe need your birthday for the Bill Me Later® credit check process. | | birthMonthBMLField_2 UNKNOWN_TYPE | SSN | Last 4 digits of your social security #: CLOSEWe need the last 4 digits of your SSN for the Bill Me Later® credit check process. If you do not have an SSN, you can still pay by Credit Card or PayPal. | | birthMonthBMLField_2 -UNKNOWN_TYPE | agree | I agree to have the Terms and Conditions presented electronically. | yes | birthMonthBMLField_2 -UNKNOWN_TYPE | agreeTs | I agree to the Bill Me Later® Terms and Conditions. | yes | birthMonthBMLField_2 ADDRESS_HOME_COUNTRY | countryType | Country Type | usa | birthMonthBMLField_2 -UNKNOWN_TYPE | billSameAsShip | My Billing & Shipping addresses are the same | yes | birthMonthBMLField_2 -ADDRESS_HOME_COUNTRY | country | Country, APO/FPO | US | country_3 -NAME_FIRST | firstName | First Name | | country_3 -NAME_LAST | lastName | Last Name | | country_3 -ADDRESS_HOME_LINE1 | address1 | Address | | country_3 -ADDRESS_HOME_LINE2 | address2 | Address | | country_3 -ADDRESS_HOME_CITY | city | City | | country_3 -ADDRESS_HOME_STATE | state | State | | country_3 -ADDRESS_HOME_ZIP | zip | Zip | | country_3 -EMAIL_ADDRESS | email | Email CLOSEWe use this to send you an order confirmation and tracking info once your order ships. | | country_3 -EMAIL_ADDRESS | confirmEmail | Re-Type Email | | country_3 -PHONE_HOME_CITY_AND_NUMBER | billingPhone | Billing Phone Number CLOSEThis must match the phone number on your credit card statement. Don't worry... we never share your number with anyone. | | country_3 -UNKNOWN_TYPE | accessPassNumber | My AEREWARD$ # CLOSEAEREWARD$ NumberEnter the number found on the back of your AEREWARD$ card to earn rewards for your purchase.Learn More about AEREWARD$ | | country_3 -UNKNOWN_TYPE | aeRewardsSignUp | Sign up now and receive 25 points just for signing up! Learn More | true | country_3 -UNKNOWN_TYPE | aeAccount | Create my AE Account.CLOSECreate an AE Account to:Save Your Personal Info For Next TimeCheckout FasterTrack An Order Quicker Sign up now and receive 25 points just for signing up! Learn More | true | country_3 -UNKNOWN_TYPE | aeAccountPass | Password (6-15 Letters & Numbers) | | country_3 -UNKNOWN_TYPE | aeAccountPassConfirm | Confirm Password | | country_3 -UNKNOWN_TYPE | birthMonthBillAddField | Birthday: CLOSEWe'd love to wish you a happy birthday when it's time! However, you need to be 13 or older to create an account. | | country_3 -UNKNOWN_TYPE | birthDayBillAddField | Birthday: CLOSEWe'd love to wish you a happy birthday when it's time! However, you need to be 13 or older to create an account. | | country_3 -UNKNOWN_TYPE | birthYearBillAddField | Birthday: CLOSEWe'd love to wish you a happy birthday when it's time! However, you need to be 13 or older to create an account. | | country_3 -UNKNOWN_TYPE | aeEmail | AE | on | country_3 -UNKNOWN_TYPE | aerieEmail | aerie | on | country_3 -UNKNOWN_TYPE | kidsEmail | 77kids | on | country_3 -UNKNOWN_TYPE | loyaltyTerms | I accept the Terms & Conditions | | country_3 +ADDRESS_HOME_COUNTRY | country | Country, APO/FPO | US | birthMonthBMLField_2 +NAME_FIRST | firstName | First Name | | birthMonthBMLField_2 +NAME_LAST | lastName | Last Name | | birthMonthBMLField_2 +ADDRESS_HOME_LINE1 | address1 | Address | | birthMonthBMLField_2 +ADDRESS_HOME_LINE2 | address2 | Address | | birthMonthBMLField_2 +ADDRESS_HOME_CITY | city | City | | birthMonthBMLField_2 +ADDRESS_HOME_STATE | state | State | | birthMonthBMLField_2 +ADDRESS_HOME_ZIP | zip | Zip | | birthMonthBMLField_2 +EMAIL_ADDRESS | email | Email CLOSEWe use this to send you an order confirmation and tracking info once your order ships. | | birthMonthBMLField_2 +EMAIL_ADDRESS | confirmEmail | Re-Type Email | | birthMonthBMLField_2 +PHONE_HOME_CITY_AND_NUMBER | billingPhone | Billing Phone Number CLOSEThis must match the phone number on your credit card statement. Don't worry... we never share your number with anyone. | | birthMonthBMLField_2 +UNKNOWN_TYPE | accessPassNumber | My AEREWARD$ # CLOSEAEREWARD$ NumberEnter the number found on the back of your AEREWARD$ card to earn rewards for your purchase.Learn More about AEREWARD$ | | birthMonthBMLField_2 +UNKNOWN_TYPE | aeAccountPass | Password (6-15 Letters & Numbers) | | birthMonthBMLField_2 +UNKNOWN_TYPE | aeAccountPassConfirm | Confirm Password | | birthMonthBMLField_2 +UNKNOWN_TYPE | birthMonthBillAddField | Birthday: CLOSEWe'd love to wish you a happy birthday when it's time! However, you need to be 13 or older to create an account. | | birthMonthBMLField_2 +UNKNOWN_TYPE | birthDayBillAddField | Birthday: CLOSEWe'd love to wish you a happy birthday when it's time! However, you need to be 13 or older to create an account. | | birthMonthBMLField_2 +UNKNOWN_TYPE | birthYearBillAddField | Birthday: CLOSEWe'd love to wish you a happy birthday when it's time! However, you need to be 13 or older to create an account. | | birthMonthBMLField_2 MERCHANT_PROMO_CODE | CouponCode | Discount Code: CLOSEApplying DiscountsEnter your 8 or 20 digit code into the Discount Code field without any spaces, then click the Apply Discount button.Learn More about how to find and apply discounts | | CouponCode_1
diff --git a/components/test/data/autofill/heuristics/output/019_checkout_bedbathandbeyond.com.out b/components/test/data/autofill/heuristics/output/019_checkout_bedbathandbeyond.com.out index 8082f79..6d3aaa7 100644 --- a/components/test/data/autofill/heuristics/output/019_checkout_bedbathandbeyond.com.out +++ b/components/test/data/autofill/heuristics/output/019_checkout_bedbathandbeyond.com.out
@@ -20,9 +20,6 @@ PHONE_HOME_NUMBER_SUFFIX | mob_phone_part3 | Mobile phone | | first_nm_1 EMAIL_ADDRESS | email_addr | *Email | | first_nm_1 EMAIL_ADDRESS | retype_email_addr | *Re-type email | | first_nm_1 -UNKNOWN_TYPE | promo_email_flag | Receive information on special offers and new arrivals at Bed Bath & Beyond. | Y | first_nm_1 -UNKNOWN_TYPE | mobile_offers_opt_in | Bed Bath & Beyond may deliver mobile offers and promotions via text message in the future. Check the box if you would like to receive these mobile offers and promotions on your mobile phone. View our Privacy policy. Message & data rates may apply. | Y | first_nm_1 -UNKNOWN_TYPE | shipping_options | Same as billing information | B | first_nm_1 NAME_FIRST | ship_first_nm | *First name | | ship_first_nm_2 NAME_MIDDLE | ship_mid_nm | Middle name | | ship_first_nm_2 NAME_LAST | ship_last_nm | *Last name | | ship_first_nm_2
diff --git a/components/test/data/autofill/heuristics/output/020_checkout_cafepress.com.out b/components/test/data/autofill/heuristics/output/020_checkout_cafepress.com.out index 2408f4f..b13b1a3 100644 --- a/components/test/data/autofill/heuristics/output/020_checkout_cafepress.com.out +++ b/components/test/data/autofill/heuristics/output/020_checkout_cafepress.com.out
@@ -9,9 +9,6 @@ ADDRESS_HOME_ZIP | BillZip | Zip/Postal Code* | | BillName_1 PHONE_HOME_CITY_AND_NUMBER | BillPhone | Phone Number* | | BillName_1 EMAIL_ADDRESS | BillEmail | Email Address* | | BillName_1 -UNKNOWN_TYPE | cbStoreSpecial | | on | BillName_1 -UNKNOWN_TYPE | ShipDestination | billaddress | billaddress | BillName_1 -UNKNOWN_TYPE | ShipDestination | Ship to my billing address | shipaddress | BillName_1 NAME_FULL | ShipName | Recipient Name* | | ShipName_2 ADDRESS_HOME_COUNTRY | ShipCountry | Country* | UNITED STATES | ShipName_2 ADDRESS_HOME_LINE1 | ShipStreet1 | Address* | | ShipName_2 @@ -20,17 +17,9 @@ ADDRESS_HOME_STATE | ShipState | State/Province* | | ShipName_2 UNKNOWN_TYPE | ShipStateOther | State/Province* | | ShipName_2 ADDRESS_HOME_ZIP | ShipZip | Zip/Postal Code* | | ShipName_2 -UNKNOWN_TYPE | ShowGiftMsg | This is a gift order. Check this box to see gift options. | on | ShipName_2 UNKNOWN_TYPE | GiftMessage | One message per order You have | | -default UNKNOWN_TYPE | countdown | You have | 300 | -default -UNKNOWN_TYPE | ApplyGiftWrap | (+$0.00) | on | -default MERCHANT_PROMO_CODE | txtCoupon | Apply Promo Code | | ShipName_2 -UNKNOWN_TYPE | PaymentMethod | PayByCafeCash | PayByCafeCash | -default -UNKNOWN_TYPE | PaymentMethod | PayByGC | PayByGC | -default -UNKNOWN_TYPE | PaymentMethod | PayByCCGC | PayByCCGC | -default -UNKNOWN_TYPE | PaymentMethod | PayByCC | PayByCC | ShipName_2 CREDIT_CARD_NUMBER | txtCCAccount | Credit Card No.* | | txtCCAccount_3 CREDIT_CARD_EXP_MONTH | ccExp$MonthDrop | Expiration Date* | 06 (June) | txtCCAccount_3 CREDIT_CARD_EXP_4_DIGIT_YEAR | ccExp$YearDrop | Expiration Date* | 2011 | txtCCAccount_3 -UNKNOWN_TYPE | PaymentMethod | Expiration Date* | PayByCheck | -default -UNKNOWN_TYPE | PaymentMethod | PayByPayPal | PayByPayPal | ShipName_2
diff --git a/components/test/data/autofill/heuristics/output/021_checkout_cduniverse.com.out b/components/test/data/autofill/heuristics/output/021_checkout_cduniverse.com.out index cdf718fd..9a535afd 100644 --- a/components/test/data/autofill/heuristics/output/021_checkout_cduniverse.com.out +++ b/components/test/data/autofill/heuristics/output/021_checkout_cduniverse.com.out
@@ -17,6 +17,3 @@ ADDRESS_HOME_STATE | HT_Ship_State | State/Province | | HT_Ship_Name_2 ADDRESS_HOME_ZIP | HT_Ship_Zip | Zip/Postal Code | | HT_Ship_Name_2 ADDRESS_HOME_COUNTRY | HT_Ship_Country | Country | | HT_Ship_Name_2 -UNKNOWN_TYPE | HT_OK_to_email | Yes | -1 | HT_Ship_Name_2 -UNKNOWN_TYPE | HT_OK_to_email | No | 0 | HT_Ship_Name_2 -UNKNOWN_TYPE | HT_PrefersHTML | I prefer to receive HTML formatted email when available. | on | HT_Ship_Name_2
diff --git a/components/test/data/autofill/heuristics/output/022_checkout_crutchfield.com.out b/components/test/data/autofill/heuristics/output/022_checkout_crutchfield.com.out index 15af66a..a416633 100644 --- a/components/test/data/autofill/heuristics/output/022_checkout_crutchfield.com.out +++ b/components/test/data/autofill/heuristics/output/022_checkout_crutchfield.com.out
@@ -8,9 +8,6 @@ ADDRESS_HOME_ZIP | ctl00$ctl00$MainContentPlaceHolder$MainContentPlaceHolder$zip | Zip/Postal Code | | ctl00$ctl00$MainContentPlaceHolder$MainContentPlaceHolder$firstname_1 PHONE_HOME_CITY_AND_NUMBER | ctl00$ctl00$MainContentPlaceHolder$MainContentPlaceHolder$billphone2 | Billing Phone | | ctl00$ctl00$MainContentPlaceHolder$MainContentPlaceHolder$firstname_1 EMAIL_ADDRESS | ctl00$ctl00$MainContentPlaceHolder$MainContentPlaceHolder$email2 | Email | | ctl00$ctl00$MainContentPlaceHolder$MainContentPlaceHolder$firstname_1 -UNKNOWN_TYPE | ctl00$ctl00$MainContentPlaceHolder$MainContentPlaceHolder$chbox2 | Send me exclusive offers, deals and expert reviews. | on | ctl00$ctl00$MainContentPlaceHolder$MainContentPlaceHolder$firstname_1 -UNKNOWN_TYPE | ctl00$ctl00$MainContentPlaceHolder$MainContentPlaceHolder$same | Same as billing address. | same | ctl00$ctl00$MainContentPlaceHolder$MainContentPlaceHolder$firstname_1 -UNKNOWN_TYPE | ctl00$ctl00$MainContentPlaceHolder$MainContentPlaceHolder$giftOrder | This order is a gift. Prices will not appear on invoice. | gift | ctl00$ctl00$MainContentPlaceHolder$MainContentPlaceHolder$firstname_1 NAME_FIRST | ctl00$ctl00$MainContentPlaceHolder$MainContentPlaceHolder$shippingFirstname | First Name | | ctl00$ctl00$MainContentPlaceHolder$MainContentPlaceHolder$shippingFirstname_2 NAME_MIDDLE_INITIAL | ctl00$ctl00$MainContentPlaceHolder$MainContentPlaceHolder$shippingMi | M.I. | | ctl00$ctl00$MainContentPlaceHolder$MainContentPlaceHolder$shippingFirstname_2 NAME_LAST | ctl00$ctl00$MainContentPlaceHolder$MainContentPlaceHolder$shippingLastname | Last Name | | ctl00$ctl00$MainContentPlaceHolder$MainContentPlaceHolder$shippingFirstname_2
diff --git a/components/test/data/autofill/heuristics/output/023_checkout_gamestop.com.out b/components/test/data/autofill/heuristics/output/023_checkout_gamestop.com.out index 72e1cbf..1c9c90d 100644 --- a/components/test/data/autofill/heuristics/output/023_checkout_gamestop.com.out +++ b/components/test/data/autofill/heuristics/output/023_checkout_gamestop.com.out
@@ -10,7 +10,6 @@ PHONE_HOME_CITY_AND_NUMBER | ctl00$ctl00$ctl00$BaseContentPlaceHolder$mainContentPlaceHolder$MainContentPlaceHolder$AddressDetails$AddressDetails$textPhoneNumberDaytime | Phone Number: | | ctl00$ctl00$ctl00$BaseContentPlaceHolder$cHeader$searchtext_1 EMAIL_ADDRESS | ctl00$ctl00$ctl00$BaseContentPlaceHolder$mainContentPlaceHolder$MainContentPlaceHolder$AddressDetails$AddressDetails$txtEmailAddress | Purchaser's Email: | | ctl00$ctl00$ctl00$BaseContentPlaceHolder$cHeader$searchtext_1 EMAIL_ADDRESS | ctl00$ctl00$ctl00$BaseContentPlaceHolder$mainContentPlaceHolder$MainContentPlaceHolder$AddressDetails$AddressDetails$txtEmailAddressConfirm | Confirm Email: | | ctl00$ctl00$ctl00$BaseContentPlaceHolder$cHeader$searchtext_1 -UNKNOWN_TYPE | ctl00$ctl00$ctl00$BaseContentPlaceHolder$mainContentPlaceHolder$MainContentPlaceHolder$AddressDetails$AddressDetails$chkEmailOptIn | Confirm Email: | on | ctl00$ctl00$ctl00$BaseContentPlaceHolder$cHeader$searchtext_1 NAME_FIRST | ctl00$ctl00$ctl00$BaseContentPlaceHolder$mainContentPlaceHolder$MainContentPlaceHolder$aspShippingAddress$popAddressSelection$AddressDetails1$textFirstName | First Name: | | -default NAME_LAST | ctl00$ctl00$ctl00$BaseContentPlaceHolder$mainContentPlaceHolder$MainContentPlaceHolder$aspShippingAddress$popAddressSelection$AddressDetails1$textLastName | Last Name: | | -default ADDRESS_HOME_LINE1 | ctl00$ctl00$ctl00$BaseContentPlaceHolder$mainContentPlaceHolder$MainContentPlaceHolder$aspShippingAddress$popAddressSelection$AddressDetails1$textAddressLine1 | Address 1: | | -default @@ -20,4 +19,3 @@ ADDRESS_HOME_ZIP | ctl00$ctl00$ctl00$BaseContentPlaceHolder$mainContentPlaceHolder$MainContentPlaceHolder$aspShippingAddress$popAddressSelection$AddressDetails1$textZip | Zip/Postal: | | -default ADDRESS_HOME_COUNTRY | ctl00$ctl00$ctl00$BaseContentPlaceHolder$mainContentPlaceHolder$MainContentPlaceHolder$aspShippingAddress$popAddressSelection$AddressDetails1$ddlCountry | Country: | US | -default PHONE_HOME_CITY_AND_NUMBER | ctl00$ctl00$ctl00$BaseContentPlaceHolder$mainContentPlaceHolder$MainContentPlaceHolder$aspShippingAddress$popAddressSelection$AddressDetails1$textPhoneNumberDaytime | Phone Number: | | -default -UNKNOWN_TYPE | ctl00$ctl00$ctl00$BaseContentPlaceHolder$mainContentPlaceHolder$MainContentPlaceHolder$chkSameAsBillingAddress | | on | ctl00$ctl00$ctl00$BaseContentPlaceHolder$mainContentPlaceHolder$MainContentPlaceHolder$chkSameAsBillingAddress_2
diff --git a/components/test/data/autofill/heuristics/output/024_checkout_homedepot.com.out b/components/test/data/autofill/heuristics/output/024_checkout_homedepot.com.out index 82aee50..20f44e5 100644 --- a/components/test/data/autofill/heuristics/output/024_checkout_homedepot.com.out +++ b/components/test/data/autofill/heuristics/output/024_checkout_homedepot.com.out
@@ -13,4 +13,3 @@ PHONE_HOME_CITY_CODE_WITH_TRUNK_PREFIX | altfieldphone1_1 | Alternate Phone: | | firstName_1_1 PHONE_HOME_NUMBER_PREFIX | altfieldphone2_1 | Alternate Phone: | | firstName_1_1 PHONE_HOME_NUMBER_SUFFIX | altfieldphone3_1 | Alternate Phone: | | firstName_1_1 -UNKNOWN_TYPE | isBillingAddress_1 | Check here if the above shipping address is | on | firstName_1_1
diff --git a/components/test/data/autofill/heuristics/output/025_checkout_hsn.com.out b/components/test/data/autofill/heuristics/output/025_checkout_hsn.com.out index 1253f53a..1b55b01 100644 --- a/components/test/data/autofill/heuristics/output/025_checkout_hsn.com.out +++ b/components/test/data/autofill/heuristics/output/025_checkout_hsn.com.out
@@ -1,23 +1,13 @@ -UNKNOWN_TYPE | Body$BillingIsShippingCheckboxB | Use my billing address as my shipping address. | on | Body$BillingIsShippingCheckboxB_1 -NAME_FIRST | Body$BillingAddress$_firstName | First Name | | Body$BillingIsShippingCheckboxB_1 -NAME_LAST | Body$BillingAddress$_lastName | Last Name | | Body$BillingIsShippingCheckboxB_1 -ADDRESS_HOME_LINE1 | Body$BillingAddress$_address1 | Address Line 1 | | Body$BillingIsShippingCheckboxB_1 -ADDRESS_HOME_LINE2 | Body$BillingAddress$_address2 | Address Line 2(optional) | | Body$BillingIsShippingCheckboxB_1 -ADDRESS_HOME_CITY | Body$BillingAddress$_city | City | | Body$BillingIsShippingCheckboxB_1 -ADDRESS_HOME_STATE | Body$BillingAddress$_state | State | | Body$BillingIsShippingCheckboxB_1 -ADDRESS_HOME_ZIP | Body$BillingAddress$_zipcode | Zip Code | | Body$BillingIsShippingCheckboxB_1 -PHONE_HOME_CITY_AND_NUMBER | Body$BillingAddress$_PrimaryPhone | Primary Phone | | Body$BillingIsShippingCheckboxB_1 -UNKNOWN_TYPE | Body$BillingAddress$rdoPrimary | H | H | Body$BillingIsShippingCheckboxB_1 -UNKNOWN_TYPE | Body$BillingAddress$rdoPrimary | M | M | Body$BillingIsShippingCheckboxB_1 -UNKNOWN_TYPE | Body$BillingAddress$rdoPrimary | W | W | Body$BillingIsShippingCheckboxB_1 -PHONE_HOME_CITY_AND_NUMBER | Body$BillingAddress$_Alt1Phone | Alternate Phone2(optional) | | Body$BillingIsShippingCheckboxB_1 -UNKNOWN_TYPE | Body$BillingAddress$rdoAlt1 | H | H | Body$BillingIsShippingCheckboxB_1 -UNKNOWN_TYPE | Body$BillingAddress$rdoAlt1 | M | M | Body$BillingIsShippingCheckboxB_1 -UNKNOWN_TYPE | Body$BillingAddress$rdoAlt1 | W | W | Body$BillingIsShippingCheckboxB_1 -UNKNOWN_TYPE | Body$MobileAlerts | Send me special promotion mobile alerts | on | Body$BillingIsShippingCheckboxB_1 -EMAIL_ADDRESS | Body$NewEmailAddress2 | Email Address | | Body$BillingIsShippingCheckboxB_1 -UNKNOWN_TYPE | Body$WeeklyNewsletterSignup12 | Email me weekly HSN newsletters and special offers | on | Body$BillingIsShippingCheckboxB_1 -UNKNOWN_TYPE | Body$TSNewsletterSignup2 | Email me the daily Today's Special | on | Body$BillingIsShippingCheckboxB_1 +NAME_FIRST | Body$BillingAddress$_firstName | First Name | | Body$BillingAddress$_firstName_1 +NAME_LAST | Body$BillingAddress$_lastName | Last Name | | Body$BillingAddress$_firstName_1 +ADDRESS_HOME_LINE1 | Body$BillingAddress$_address1 | Address Line 1 | | Body$BillingAddress$_firstName_1 +ADDRESS_HOME_LINE2 | Body$BillingAddress$_address2 | Address Line 2(optional) | | Body$BillingAddress$_firstName_1 +ADDRESS_HOME_CITY | Body$BillingAddress$_city | City | | Body$BillingAddress$_firstName_1 +ADDRESS_HOME_STATE | Body$BillingAddress$_state | State | | Body$BillingAddress$_firstName_1 +ADDRESS_HOME_ZIP | Body$BillingAddress$_zipcode | Zip Code | | Body$BillingAddress$_firstName_1 +PHONE_HOME_CITY_AND_NUMBER | Body$BillingAddress$_PrimaryPhone | Primary Phone | | Body$BillingAddress$_firstName_1 +PHONE_HOME_CITY_AND_NUMBER | Body$BillingAddress$_Alt1Phone | Alternate Phone2(optional) | | Body$BillingAddress$_firstName_1 +EMAIL_ADDRESS | Body$NewEmailAddress2 | Email Address | | Body$BillingAddress$_firstName_1 NAME_FIRST | Body$ShippingAddress$_firstName | First Name | | -default NAME_LAST | Body$ShippingAddress$_lastName | Last Name | | -default ADDRESS_HOME_LINE1 | Body$ShippingAddress$_address1 | Address Line 1 | | -default @@ -26,15 +16,10 @@ ADDRESS_HOME_STATE | Body$ShippingAddress$_state | State | | -default ADDRESS_HOME_ZIP | Body$ShippingAddress$_zipcode | Zip Code | | -default PHONE_HOME_CITY_AND_NUMBER | Body$ShippingAddress$_telephone | Phone Number | | -default -UNKNOWN_TYPE | PaymentTypeSelection | Pay with a Credit or Debit Card | 0 | PaymentTypeSelection_2 -CREDIT_CARD_NUMBER | Body$CCNumber | Card Number | | Body$CCNumber_3 -CREDIT_CARD_EXP_MONTH | Body$CCExpirationDateMonth | Expiration Date | 0 | Body$CCNumber_3 -CREDIT_CARD_EXP_4_DIGIT_YEAR | Body$CCExpirationDateYear | Expiration Date | 0 | Body$CCNumber_3 -CREDIT_CARD_NAME_FULL | Body$CCNameOnCard | Name on Card | | Body$CCNumber_3 -UNKNOWN_TYPE | IsDebitCard | Yes | 1 | PaymentTypeSelection_2 -UNKNOWN_TYPE | IsDebitCard | No | 0 | PaymentTypeSelection_2 -UNKNOWN_TYPE | Body$SaveCC4Later | Save information for future purchases | on | PaymentTypeSelection_2 -UNKNOWN_TYPE | PaymentTypeSelection | Pay with PayPal | 1 | PaymentTypeSelection_2 -UNKNOWN_TYPE | Body$NewPassword | Create Password (optional) | | PaymentTypeSelection_2 -UNKNOWN_TYPE | Body$RepeatNewPassword | Repeat Password (optional) | | PaymentTypeSelection_2 -UNKNOWN_TYPE | Body$NewPasswordHint | Password Hint (optional) | | PaymentTypeSelection_2 +CREDIT_CARD_NUMBER | Body$CCNumber | Card Number | | Body$CCNumber_2 +CREDIT_CARD_EXP_MONTH | Body$CCExpirationDateMonth | Expiration Date | 0 | Body$CCNumber_2 +CREDIT_CARD_EXP_4_DIGIT_YEAR | Body$CCExpirationDateYear | Expiration Date | 0 | Body$CCNumber_2 +CREDIT_CARD_NAME_FULL | Body$CCNameOnCard | Name on Card | | Body$CCNumber_2 +UNKNOWN_TYPE | Body$NewPassword | Create Password (optional) | | Body$NewPassword_3 +UNKNOWN_TYPE | Body$RepeatNewPassword | Repeat Password (optional) | | Body$NewPassword_3 +UNKNOWN_TYPE | Body$NewPasswordHint | Password Hint (optional) | | Body$NewPassword_3
diff --git a/components/test/data/autofill/heuristics/output/028_checkout_jr.com.out b/components/test/data/autofill/heuristics/output/028_checkout_jr.com.out index d449d2f..54f2811d 100644 --- a/components/test/data/autofill/heuristics/output/028_checkout_jr.com.out +++ b/components/test/data/autofill/heuristics/output/028_checkout_jr.com.out
@@ -8,9 +8,6 @@ ADDRESS_HOME_COUNTRY | billingAddress.country | Country | US | billingAddress.firstName_1 PHONE_HOME_CITY_AND_NUMBER | billingAddress.phoneNumber | Contact Phone Number | | billingAddress.firstName_1 EMAIL_ADDRESS | customer.emailAddress | Email Address | | billingAddress.firstName_1 -UNKNOWN_TYPE | customer.optIn | Yes, send me J&R's promotional email newsletter. | on | billingAddress.firstName_1 -UNKNOWN_TYPE | storePickup | Pickup In-Store (Park Row, New York NY) | on | billingAddress.firstName_1 -UNKNOWN_TYPE | shipToBillingAddress | Ship to my billing address | on | billingAddress.firstName_1 NAME_FIRST | shippingAddress.firstName | First Name | | shippingAddress.firstName_2 NAME_LAST | shippingAddress.lastName | Last Name | | shippingAddress.firstName_2 ADDRESS_HOME_LINE1 | shippingAddress.address1 | Address | | shippingAddress.firstName_2 @@ -25,4 +22,3 @@ UNKNOWN_TYPE | customOrderHeader.giftFromName | Gift From | | shippingAddress.firstName_2 UNKNOWN_TYPE | customOrderHeader.giftMessage | Gift Message | | shippingAddress.firstName_2 EMAIL_ADDRESS | customOrderHeader.giftEmailAddress | Gift Email | | shippingAddress.firstName_2 -UNKNOWN_TYPE | customOrderHeader.giftHidePrices | Hide prices on the recipient's invoice and products on gift email. | on | shippingAddress.firstName_2
diff --git a/components/test/data/autofill/heuristics/output/029_checkout_kohls.com.out b/components/test/data/autofill/heuristics/output/029_checkout_kohls.com.out index c5b7457..e21a15e 100644 --- a/components/test/data/autofill/heuristics/output/029_checkout_kohls.com.out +++ b/components/test/data/autofill/heuristics/output/029_checkout_kohls.com.out
@@ -13,9 +13,6 @@ PHONE_HOME_EXTENSION | BILL_PHONE<>extension | Extension | | BILL_TO_ADDRESS<>firstName_1 PHONE_HOME_CITY_AND_NUMBER | bill_phone | Contact Phone: | | BILL_TO_ADDRESS<>firstName_1 EMAIL_ADDRESS | CURRENT_USER<>email | E-mail Address: | | BILL_TO_ADDRESS<>firstName_1 -UNKNOWN_TYPE | EMAIL_OPT_STATUS_ARRAY<>opt_status | Yes, sign me up for Sale Alerts. (optional) | true | BILL_TO_ADDRESS<>firstName_1 -UNKNOWN_TYPE | indShipIsBillTo | Yes | true | BILL_TO_ADDRESS<>firstName_1 -UNKNOWN_TYPE | indShipIsBillTo | No | false | BILL_TO_ADDRESS<>firstName_1 NAME_FIRST | SHIP_TO_ADDRESS<>firstName | First Name: | | SHIP_TO_ADDRESS<>firstName_2 NAME_LAST | SHIP_TO_ADDRESS<>lastName | Last Name: | | SHIP_TO_ADDRESS<>firstName_2 ADDRESS_HOME_LINE1 | SHIP_TO_ADDRESS<>address1 | Address: | | SHIP_TO_ADDRESS<>firstName_2
diff --git a/components/test/data/autofill/heuristics/output/030_checkout_lowes.com.out b/components/test/data/autofill/heuristics/output/030_checkout_lowes.com.out index 30e9f1d2..caf4735 100644 --- a/components/test/data/autofill/heuristics/output/030_checkout_lowes.com.out +++ b/components/test/data/autofill/heuristics/output/030_checkout_lowes.com.out
@@ -9,7 +9,6 @@ ADDRESS_HOME_STATE | state | State: | | address1_2 ADDRESS_HOME_ZIP | zipCode | ZIP Code: | | address1_2 ADDRESS_HOME_CITY | taxGeoCode | Municipality: As an additional way to make sure you receive your order, we ask that you select the name of your municipality (city, town, borough, village, etc.) followed by the county, township, or parish in which you reside. | | address1_2 -UNKNOWN_TYPE | billingAddress | This is also my billing address. | on | address1_2 EMAIL_ADDRESS | email1 | E-mail Address: | | address1_2 PHONE_HOME_CITY_CODE_WITH_TRUNK_PREFIX | billphone1 | Contact Phone: | | address1_2 PHONE_HOME_NUMBER_PREFIX | billphone2 | Contact Phone: | | address1_2
diff --git a/components/test/data/autofill/heuristics/output/031_checkout_macys.com.out b/components/test/data/autofill/heuristics/output/031_checkout_macys.com.out index 1f897ad9..162d31c 100644 --- a/components/test/data/autofill/heuristics/output/031_checkout_macys.com.out +++ b/components/test/data/autofill/heuristics/output/031_checkout_macys.com.out
@@ -1,11 +1,10 @@ -UNKNOWN_TYPE | selectedShippingAddress | Add a new shipping address | newAddress | selectedShippingAddress_1 -NAME_FIRST | currentShipment.shipmentAddress.firstName | First Name: | | selectedShippingAddress_1 -NAME_LAST | currentShipment.shipmentAddress.lastName | Last Name: | | selectedShippingAddress_1 -ADDRESS_HOME_LINE1 | currentShipment.shipmentAddress.address1 | Address: | | selectedShippingAddress_1 -ADDRESS_HOME_LINE2 | currentShipment.shipmentAddress.address2 | * Address: | | selectedShippingAddress_1 -ADDRESS_HOME_CITY | currentShipment.shipmentAddress.city | City: | | selectedShippingAddress_1 -ADDRESS_HOME_STATE | currentShipment.shipmentAddress.state | State: | NOSELECTION | selectedShippingAddress_1 -ADDRESS_HOME_ZIP | currentShipment.shipmentAddress.zipCode | ZipCode: | | selectedShippingAddress_1 -PHONE_HOME_CITY_CODE_WITH_TRUNK_PREFIX | currentShipment.shipmentAddress.dayPhone.areaCode | Phone: | | selectedShippingAddress_1 -PHONE_HOME_NUMBER | currentShipment.shipmentAddress.dayPhone.exchangeNbr | * Phone: | | selectedShippingAddress_1 -PHONE_HOME_CITY_AND_NUMBER | currentShipment.shipmentAddress.dayPhone.subscriberNbr | * Phone: | | selectedShippingAddress_1 +NAME_FIRST | currentShipment.shipmentAddress.firstName | First Name: | | currentShipment.shipmentAddress.firstName_1 +NAME_LAST | currentShipment.shipmentAddress.lastName | Last Name: | | currentShipment.shipmentAddress.firstName_1 +ADDRESS_HOME_LINE1 | currentShipment.shipmentAddress.address1 | Address: | | currentShipment.shipmentAddress.firstName_1 +ADDRESS_HOME_LINE2 | currentShipment.shipmentAddress.address2 | * Address: | | currentShipment.shipmentAddress.firstName_1 +ADDRESS_HOME_CITY | currentShipment.shipmentAddress.city | City: | | currentShipment.shipmentAddress.firstName_1 +ADDRESS_HOME_STATE | currentShipment.shipmentAddress.state | State: | NOSELECTION | currentShipment.shipmentAddress.firstName_1 +ADDRESS_HOME_ZIP | currentShipment.shipmentAddress.zipCode | ZipCode: | | currentShipment.shipmentAddress.firstName_1 +PHONE_HOME_CITY_CODE_WITH_TRUNK_PREFIX | currentShipment.shipmentAddress.dayPhone.areaCode | Phone: | | currentShipment.shipmentAddress.firstName_1 +PHONE_HOME_NUMBER | currentShipment.shipmentAddress.dayPhone.exchangeNbr | * Phone: | | currentShipment.shipmentAddress.firstName_1 +PHONE_HOME_CITY_AND_NUMBER | currentShipment.shipmentAddress.dayPhone.subscriberNbr | * Phone: | | currentShipment.shipmentAddress.firstName_1
diff --git a/components/test/data/autofill/heuristics/output/032_checkout_nordstrom.com.out b/components/test/data/autofill/heuristics/output/032_checkout_nordstrom.com.out index d9ff9a042..9049cd5 100644 --- a/components/test/data/autofill/heuristics/output/032_checkout_nordstrom.com.out +++ b/components/test/data/autofill/heuristics/output/032_checkout_nordstrom.com.out
@@ -10,7 +10,6 @@ ADDRESS_HOME_STATE | ctl00$mainContentPlaceHolder$billingAddressForm$stateProvince | State/Province | -1 | ctl00$mainContentPlaceHolder$emailAddress_1 ADDRESS_HOME_ZIP | ctl00$mainContentPlaceHolder$billingAddressForm$zipCode | Zip/Postal Code | | ctl00$mainContentPlaceHolder$emailAddress_1 ADDRESS_HOME_COUNTRY | ctl00$mainContentPlaceHolder$billingAddressForm$country | Country | 249 | ctl00$mainContentPlaceHolder$emailAddress_1 -UNKNOWN_TYPE | ctl00$mainContentPlaceHolder$shippingSameAsBilling | | on | ctl00$mainContentPlaceHolder$emailAddress_1 NAME_FIRST | ctl00$mainContentPlaceHolder$shippingAddressForm$firstName | First Name | | ctl00$mainContentPlaceHolder$shippingAddressForm$firstName_2 NAME_MIDDLE_INITIAL | ctl00$mainContentPlaceHolder$shippingAddressForm$middleInitial | M.I. | | ctl00$mainContentPlaceHolder$shippingAddressForm$firstName_2 NAME_LAST | ctl00$mainContentPlaceHolder$shippingAddressForm$lastName | Last Name | | ctl00$mainContentPlaceHolder$shippingAddressForm$firstName_2
diff --git a/components/test/data/autofill/heuristics/output/033_checkout_officemax.com.out b/components/test/data/autofill/heuristics/output/033_checkout_officemax.com.out index 073630a..f35dfb8 100644 --- a/components/test/data/autofill/heuristics/output/033_checkout_officemax.com.out +++ b/components/test/data/autofill/heuristics/output/033_checkout_officemax.com.out
@@ -11,7 +11,6 @@ PHONE_HOME_CITY_AND_NUMBER | phone | *Phone number: | | fname_1 EMAIL_ADDRESS | /atg/commerce/order/purchase/ShippingGroupFormHandler.emailAddress | *Email Address: | | fname_1 EMAIL_ADDRESS | /atg/commerce/order/purchase/ShippingGroupFormHandler.confirmEmailAddress | *Confirm Email Address: | | fname_1 -UNKNOWN_TYPE | emailOptIn | Yes! Send me exclusive coupons and special online savings via OfficeMax email.OfficeMax is committed to safeguarding your privacy. Our Privacy Policy outlines how OfficeMax handles your information. | https://www.officemax.com:443/ | fname_1 UNKNOWN_TYPE | /atg/commerce/order/purchase/ShippingGroupFormHandler.maxPerksNumber | MaxPerks® ID: < What is this? | | fname_1 UNKNOWN_TYPE | TaxExemptID | Tax Exempt ID: < What is this? | | fname_1
diff --git a/components/test/data/autofill/heuristics/output/034_checkout_overstock.com.out b/components/test/data/autofill/heuristics/output/034_checkout_overstock.com.out index c2ca838..e6d3f04 100644 --- a/components/test/data/autofill/heuristics/output/034_checkout_overstock.com.out +++ b/components/test/data/autofill/heuristics/output/034_checkout_overstock.com.out
@@ -7,8 +7,6 @@ ADDRESS_HOME_ZIP | BillingZipCode | Zip | 95014 | BillingFirstName_1 PHONE_HOME_CITY_AND_NUMBER | BillingDaytimePhone | Day Phone | 4088737223 | BillingFirstName_1 PHONE_HOME_CITY_AND_NUMBER | BillingEveningPhone | Evening Phone | | BillingFirstName_1 -UNKNOWN_TYPE | checkbox2 | This order is being sent as a gift | on | BillingFirstName_1 -UNKNOWN_TYPE | diffShip | My shipping address is the same as my billing address | on | BillingFirstName_1 NAME_FIRST | ShippingFirstName | First Name | | ShippingFirstName_2 NAME_LAST | ShippingLastName | Last Name | | ShippingFirstName_2 ADDRESS_HOME_LINE1 | lineone | Address Line 1 | 7540 Donegal Dr | ShippingFirstName_2 @@ -18,15 +16,10 @@ ADDRESS_HOME_STATE | state | State | CA | ShippingFirstName_2 ADDRESS_HOME_ZIP | zip | Zip | 95014 | ShippingFirstName_2 UNKNOWN_TYPE | product6496917 | Black-plated Tungsten Carbide Comfort Fit Band (8 mm) Options: 10 1 | GROUND6496917 | ShippingFirstName_2 -UNKNOWN_TYPE | CC | CreditCardValue | CreditCardValue | ShippingFirstName_2 CREDIT_CARD_NUMBER | CC_number | Credit Card #: | | CC_number_3 CREDIT_CARD_EXP_MONTH | exp_month | Expiration Date: | 0 | CC_number_3 CREDIT_CARD_EXP_4_DIGIT_YEAR | exp_year | Expiration Date: | | CC_number_3 -UNKNOWN_TYPE | CC | PayPal | PayPal | ShippingFirstName_2 -UNKNOWN_TYPE | CC | bml | bml | ShippingFirstName_2 -UNKNOWN_TYPE | UsePromoCode | Use Promo Code | on | ShippingFirstName_2 MERCHANT_PROMO_CODE | PromoCode | Use Promo Code | | -default -UNKNOWN_TYPE | UseGiftCards | Use Gift Card | on | ShippingFirstName_2 UNKNOWN_TYPE | GiftCardNumber0 | Gift Card 1: | | -default UNKNOWN_TYPE | PINNumber0 | PIN: | | -default UNKNOWN_TYPE | GiftCardNumber1 | Gift Card 2: | | -default
diff --git a/components/test/data/autofill/heuristics/output/035_checkout_petco.com.out b/components/test/data/autofill/heuristics/output/035_checkout_petco.com.out index 61c22ba..552cb72 100644 --- a/components/test/data/autofill/heuristics/output/035_checkout_petco.com.out +++ b/components/test/data/autofill/heuristics/output/035_checkout_petco.com.out
@@ -3,7 +3,6 @@ NAME_FIRST | ctl00$ctl00$cphBody$cphBody$txtSA_FirstName | First Name * | | ctl00$ctl00$cphBody$cphBody$txtSA_FirstName_1 NAME_LAST | ctl00$ctl00$cphBody$cphBody$txtSA_LastName | Last Name * | | ctl00$ctl00$cphBody$cphBody$txtSA_FirstName_1 ADDRESS_HOME_LINE1 | ctl00$ctl00$cphBody$cphBody$txtSA_Address1 | Address Line 1 * | | ctl00$ctl00$cphBody$cphBody$txtSA_FirstName_1 -UNKNOWN_TYPE | ctl00$ctl00$cphBody$cphBody$cbSA_IsPOBox | P.O. Box | on | ctl00$ctl00$cphBody$cphBody$txtSA_FirstName_1 ADDRESS_HOME_LINE2 | ctl00$ctl00$cphBody$cphBody$txtSA_Address2 | Address Line 2 | | ctl00$ctl00$cphBody$cphBody$txtSA_FirstName_1 ADDRESS_HOME_CITY | ctl00$ctl00$cphBody$cphBody$txtSA_City | City * | | ctl00$ctl00$cphBody$cphBody$txtSA_FirstName_1 ADDRESS_HOME_STATE | ctl00$ctl00$cphBody$cphBody$ddlSA_State | State * | | ctl00$ctl00$cphBody$cphBody$txtSA_FirstName_1 @@ -12,5 +11,3 @@ PHONE_HOME_NUMBER_PREFIX | ctl00$ctl00$cphBody$cphBody$txtSA_Phone2 | ZIP Code * | | ctl00$ctl00$cphBody$cphBody$txtSA_FirstName_1 PHONE_HOME_NUMBER_SUFFIX | ctl00$ctl00$cphBody$cphBody$txtSA_Phone3 | ZIP Code * | | ctl00$ctl00$cphBody$cphBody$txtSA_FirstName_1 PHONE_HOME_EXTENSION | ctl00$ctl00$cphBody$cphBody$txtSA_PhoneExt | Ext. | | ctl00$ctl00$cphBody$cphBody$txtSA_FirstName_1 -UNKNOWN_TYPE | ctl00$ctl00$cphBody$cphBody$cbSA_MatchBilling | This is also my billing address | on | ctl00$ctl00$cphBody$cphBody$txtSA_FirstName_1 -UNKNOWN_TYPE | ctl00$ctl00$cphBody$cphBody$cbSA_EmailSignup | Send me newsletters with exclusive online discounts, special announcements and health and behavior tips for my pet. | on | ctl00$ctl00$cphBody$cphBody$txtSA_FirstName_1
diff --git a/components/test/data/autofill/heuristics/output/036_checkout_petsmart.com.out b/components/test/data/autofill/heuristics/output/036_checkout_petsmart.com.out index 79cd28e..c724429 100644 --- a/components/test/data/autofill/heuristics/output/036_checkout_petsmart.com.out +++ b/components/test/data/autofill/heuristics/output/036_checkout_petsmart.com.out
@@ -9,8 +9,6 @@ ADDRESS_HOME_ZIP | billZip | • Zip/Postal Code: | | billCountry_1 PHONE_HOME_CITY_AND_NUMBER | billPhone | • Telephone: | | billCountry_1 EMAIL_ADDRESS | guestEmail | • Email Address: | | billCountry_1 -UNKNOWN_TYPE | shipOptions | Ship to the above billing address | useShip | billCountry_1 -UNKNOWN_TYPE | shipOptions | Ship to a different address | provideNew | billCountry_1 UNKNOWN_TYPE | mail_id | User Name: | | mail_id_1 UNKNOWN_TYPE | password | Password: | | mail_id_1
diff --git a/components/test/data/autofill/heuristics/output/037_checkout_qvc.com.out b/components/test/data/autofill/heuristics/output/037_checkout_qvc.com.out index 13b25b1..d6cdcff 100644 --- a/components/test/data/autofill/heuristics/output/037_checkout_qvc.com.out +++ b/components/test/data/autofill/heuristics/output/037_checkout_qvc.com.out
@@ -17,7 +17,6 @@ PHONE_HOME_CITY_CODE_WITH_TRUNK_PREFIX | BilltoWpArea | Work Phone: | | EmailAddress_1 PHONE_HOME_NUMBER | BilltoWpExchange | Work Phone: | | EmailAddress_1 PHONE_HOME_EXTENSION | BilltoWpExt | Work Phone: | | EmailAddress_1 -UNKNOWN_TYPE | SameAsBilltoCheckbox | Same as Bill-To: | 1 | EmailAddress_1 UNKNOWN_TYPE | ShiptoFirstName | * Name: | | -default ADDRESS_HOME_LINE1 | ShiptoAddress1 | * Address Line 1: | | -default ADDRESS_HOME_LINE2 | ShiptoAddress2 | Address Line 2: | | -default @@ -25,5 +24,3 @@ ADDRESS_HOME_STATE | ShiptoState | State/Province: | AL | -default ADDRESS_HOME_ZIP | ShiptoZipCode | * Postal Code: | | -default ADDRESS_HOME_COUNTRY | ShiptoCountry | * Country: | US | -default -UNKNOWN_TYPE | ShiptoRadiobutton | This order only | ThisOrderOnly | -default -UNKNOWN_TYPE | ShiptoRadiobutton | All future orders (Permanent Ship-To)** | PermanentShipto | -default
diff --git a/components/test/data/autofill/heuristics/output/038_checkout_sears.com.out b/components/test/data/autofill/heuristics/output/038_checkout_sears.com.out index 9be0ac2..4ca5b83 100644 --- a/components/test/data/autofill/heuristics/output/038_checkout_sears.com.out +++ b/components/test/data/autofill/heuristics/output/038_checkout_sears.com.out
@@ -1,7 +1,6 @@ EMAIL_ADDRESS | address_email | Email* | ne00_99@yahoo.com | address_email_1 NAME_FIRST | shipping_firstName | First Name* | | address_email_1 NAME_LAST | shipping_lastName | Last Name* | | address_email_1 -UNKNOWN_TYPE | shipping_country | Street Address Line 1* | US | -default ADDRESS_HOME_LINE1 | shipping_address1 | Street Address Line 1* | | address_email_1 ADDRESS_HOME_LINE2 | shipping_address2 | Street Address Line 2 | | address_email_1 ADDRESS_HOME_CITY | shipping_city | City* | | address_email_1 @@ -10,17 +9,15 @@ PHONE_HOME_CITY_AND_NUMBER | shipping_day1 | Phone Number* | | address_email_1 PHONE_HOME_EXTENSION | shipping_ext1 | Ext. | | address_email_1 ADDRESS_HOME_STATE | shipping_county | County | UNKNOWN | -default -UNKNOWN_TYPE | billingAddressCheckBox | Same as Delivery/Shipping Address | on | billingAddressCheckBox_2 -NAME_FIRST | firstName | First Name* | | billingAddressCheckBox_2 -NAME_LAST | lastName | Last Name* | | billingAddressCheckBox_2 -UNKNOWN_TYPE | country | Street Address Line 1* | US | -default -ADDRESS_HOME_LINE1 | address1 | Street Address Line 1* | | billingAddressCheckBox_2 -ADDRESS_HOME_LINE2 | address2 | Street Address Line 2 | | billingAddressCheckBox_2 -ADDRESS_HOME_CITY | city | City* | | billingAddressCheckBox_2 -ADDRESS_HOME_STATE | state | State* State* | AL | billingAddressCheckBox_2 -ADDRESS_HOME_ZIP | zipCode | ZIP Code* ZIP Code* | | billingAddressCheckBox_2 -PHONE_HOME_CITY_AND_NUMBER | day1 | Phone Number* | | billingAddressCheckBox_2 -PHONE_HOME_EXTENSION | ext1 | Ext. | | billingAddressCheckBox_2 +NAME_FIRST | firstName | First Name* | | firstName_2 +NAME_LAST | lastName | Last Name* | | firstName_2 +ADDRESS_HOME_LINE1 | address1 | Street Address Line 1* | | firstName_2 +ADDRESS_HOME_LINE2 | address2 | Street Address Line 2 | | firstName_2 +ADDRESS_HOME_CITY | city | City* | | firstName_2 +ADDRESS_HOME_STATE | state | State* State* | AL | firstName_2 +ADDRESS_HOME_ZIP | zipCode | ZIP Code* ZIP Code* | | firstName_2 +PHONE_HOME_CITY_AND_NUMBER | day1 | Phone Number* | | firstName_2 +PHONE_HOME_EXTENSION | ext1 | Ext. | | firstName_2 ADDRESS_HOME_STATE | county | County County | UNKNOWN | -default UNKNOWN_TYPE | KmartSpuSpecialInstr | | | -default
diff --git a/components/test/data/autofill/heuristics/output/039_checkout_target.com.out b/components/test/data/autofill/heuristics/output/039_checkout_target.com.out index f719e596..ee56082 100644 --- a/components/test/data/autofill/heuristics/output/039_checkout_target.com.out +++ b/components/test/data/autofill/heuristics/output/039_checkout_target.com.out
@@ -5,6 +5,3 @@ ADDRESS_HOME_STATE | state | State*: | | name_1 ADDRESS_HOME_ZIP | zip | Zip Code*: | | name_1 PHONE_HOME_CITY_AND_NUMBER | voice | Phone Number*: | | name_1 -UNKNOWN_TYPE | order.188:217866256151.ShippingSpeed | Standard Shipping | std-us | name_1 -UNKNOWN_TYPE | order.188:217866256151.ShippingSpeed | Two-Day Shipping | second | name_1 -UNKNOWN_TYPE | order.188:217866256151.ShippingSpeed | One-Day Shipping | next | name_1
diff --git a/components/test/data/autofill/heuristics/output/040_checkout_urbanoutfitters.com.out b/components/test/data/autofill/heuristics/output/040_checkout_urbanoutfitters.com.out index 3e5c79e..3ca1e9f 100644 --- a/components/test/data/autofill/heuristics/output/040_checkout_urbanoutfitters.com.out +++ b/components/test/data/autofill/heuristics/output/040_checkout_urbanoutfitters.com.out
@@ -1,7 +1,6 @@ NAME_FIRST | billingFirstName | First Name | First Name | billingFirstName_1 NAME_LAST | billingLastName | Last Name | Last Name | billingFirstName_1 ADDRESS_HOME_LINE1 | billingAddress | Street Address | Street Address | billingFirstName_1 -UNKNOWN_TYPE | billingPobox | This address is a P.O. Box | true | billingFirstName_1 ADDRESS_HOME_LINE2 | billingAddress2 | Apt / Flr / Bldg (optional) | Apt / Flr / Bldg (optional) | billingFirstName_1 ADDRESS_HOME_CITY | billingCity | City | City | billingFirstName_1 ADDRESS_HOME_STATE | billingStates | | | billingFirstName_1 @@ -9,14 +8,12 @@ ADDRESS_HOME_COUNTRY | billingCountries | | | billingFirstName_1 PHONE_HOME_CITY_AND_NUMBER | billingPhone | Phone | Phone | billingFirstName_1 PHONE_HOME_CITY_AND_NUMBER | altBillingPhone | Alternate Phone (optional) | Alternate Phone (optional) | billingFirstName_1 -UNKNOWN_TYPE | defaultBilling | Make this my default billing address | true | billingFirstName_1 UNKNOWN_TYPE | savedCreditCard | | | billingFirstName_1 CREDIT_CARD_TYPE | payment_cardtype | | | payment_cardtype_2 CREDIT_CARD_NUMBER | payment_acctnum | Card Number (no spaces or dashes) | Card Number (no spaces or dashes) | payment_cardtype_2 CREDIT_CARD_EXP_MONTH | expmonth | | | payment_cardtype_2 CREDIT_CARD_EXP_4_DIGIT_YEAR | expyear | | | payment_cardtype_2 CREDIT_CARD_VERIFICATION_CODE | payment_cidnew | CID / Security Code | CID / Security Code | payment_cardtype_2 -UNKNOWN_TYPE | makeDefaultCreditCard | Make this my default credit card | true | billingFirstName_1 UNKNOWN_TYPE | giftCardNumberFromBalance | Gift Card Number | Gift Card Number | giftCardNumberFromBalance_1 UNKNOWN_TYPE | giftCardPIN | Gift Card PIN | Gift Card PIN | giftCardNumberFromBalance_1 @@ -27,7 +24,6 @@ NAME_FIRST | shippingFirstName | First Name | First Name | shipto_1 NAME_LAST | shippingLastName | Last Name | Last Name | shipto_1 ADDRESS_HOME_LINE1 | shippingAddress | Street Address | Street Address | shipto_1 -UNKNOWN_TYPE | shippingPobox | This address is a P.O. Box | true | shipto_1 ADDRESS_HOME_LINE2 | shippingAddress2 | Apt / Flr / Bldg (optional) | Apt / Flr / Bldg (optional) | shipto_1 ADDRESS_HOME_CITY | shippingCity | City | City | shipto_1 ADDRESS_HOME_STATE | shippingStates | | | shipto_1 @@ -35,5 +31,3 @@ ADDRESS_HOME_COUNTRY | shippingCountries | | | shipto_1 PHONE_HOME_CITY_AND_NUMBER | shippingPhone | Phone | Phone | shipto_1 PHONE_HOME_CITY_AND_NUMBER | altShippingPhone | Alternate Phone (optional) | Alternate Phone (optional) | shipto_1 -UNKNOWN_TYPE | billingIsShipping | My billing address is the same as my shipping address | true | shipto_1 -UNKNOWN_TYPE | defaultShipping | Make this my default shipping address | true | shipto_1
diff --git a/components/test/data/autofill/heuristics/output/042_checkout_williams-sonoma.com.out b/components/test/data/autofill/heuristics/output/042_checkout_williams-sonoma.com.out index fb32812..0dee41f 100644 --- a/components/test/data/autofill/heuristics/output/042_checkout_williams-sonoma.com.out +++ b/components/test/data/autofill/heuristics/output/042_checkout_williams-sonoma.com.out
@@ -8,8 +8,5 @@ ADDRESS_HOME_ZIP | shipTos[0].address.zip | Zip* | | shipTos[0].address.fullName_1 PHONE_HOME_CITY_AND_NUMBER | shipTos[0].address.dayPhone | Daytime Phone* | | shipTos[0].address.fullName_1 PHONE_HOME_CITY_AND_NUMBER | shipTos[0].address.eveningPhone | Alternate Phone | | shipTos[0].address.fullName_1 -UNKNOWN_TYPE | shipTos[0].billingAddressUpdate | Yes, use this shipping address for my billing address. | on | shipTos[0].address.fullName_1 EMAIL_ADDRESS | shipTos[0].address.emailAddr | Email* | | shipTos[0].address.fullName_1 EMAIL_ADDRESS | shipTos[0].confirmEmailAddr | Confirm Email* | | shipTos[0].address.fullName_1 -UNKNOWN_TYPE | shipTos[0].shipType | Standard (5-7 business days) | Standard | shipTos[0].address.fullName_1 -UNKNOWN_TYPE | shipTos[0].shipType | Rush (2-3 business days) | Rush | shipTos[0].address.fullName_1
diff --git a/components/test/data/autofill/heuristics/output/043_register_adobe.com.out b/components/test/data/autofill/heuristics/output/043_register_adobe.com.out index 440900a..58a651d 100644 --- a/components/test/data/autofill/heuristics/output/043_register_adobe.com.out +++ b/components/test/data/autofill/heuristics/output/043_register_adobe.com.out
@@ -4,5 +4,3 @@ ADDRESS_HOME_COUNTRY | countryCode | Country | | adobeId_1 UNKNOWN_TYPE | subscribePass1 | PasswordMust be between 6-12 characters | | adobeId_1 UNKNOWN_TYPE | subscribePass2 | Confirm Password | | adobeId_1 -UNKNOWN_TYPE | agreeToTou | I have read and agree to the and the | on | adobeId_1 -UNKNOWN_TYPE | hostedOptIn | Yes! I would like to receive communications relating to Adobe, its products and services including product releases, product upgrades, seminars, events, surveys, training and special offers, and Adobe, and its agents may use data I have provided in accordance with the | on | adobeId_1
diff --git a/components/test/data/autofill/heuristics/output/044_register_amazon.com.out b/components/test/data/autofill/heuristics/output/044_register_amazon.com.out index 08e18bd..44b1b8c 100644 --- a/components/test/data/autofill/heuristics/output/044_register_amazon.com.out +++ b/components/test/data/autofill/heuristics/output/044_register_amazon.com.out
@@ -1,7 +1,5 @@ -UNKNOWN_TYPE | action | Create a new account (Recommended for Business accounts) | new-user | action_1 -UNKNOWN_TYPE | action | Use existing Amazon customer account | sign-in | action_1 -UNKNOWN_TYPE | userName | First and Last Name: | | action_1 -EMAIL_ADDRESS | email | E-mail Address: | | action_1 -EMAIL_ADDRESS | emailCheck | Re-type E-mail Address: | | action_1 -UNKNOWN_TYPE | password | Password: | | action_1 -UNKNOWN_TYPE | passwordCheck | Re-type Password: | | action_1 +UNKNOWN_TYPE | userName | First and Last Name: | | userName_1 +EMAIL_ADDRESS | email | E-mail Address: | | userName_1 +EMAIL_ADDRESS | emailCheck | Re-type E-mail Address: | | userName_1 +UNKNOWN_TYPE | password | Password: | | userName_1 +UNKNOWN_TYPE | passwordCheck | Re-type Password: | | userName_1
diff --git a/components/test/data/autofill/heuristics/output/045_register_aol.com.out b/components/test/data/autofill/heuristics/output/045_register_aol.com.out index 99ee78f7..9ca6840 100644 --- a/components/test/data/autofill/heuristics/output/045_register_aol.com.out +++ b/components/test/data/autofill/heuristics/output/045_register_aol.com.out
@@ -7,8 +7,6 @@ UNKNOWN_TYPE | wlw-select_key:{actionForm.dobMonth} | Select Month | | {actionForm.firstName}_1 UNKNOWN_TYPE | {actionForm.dobDay} | Date of Birth Why so Personal? | Day (dd) | {actionForm.firstName}_1 UNKNOWN_TYPE | {actionForm.dobYear} | Date of Birth Why so Personal? | Year (yyyy) | {actionForm.firstName}_1 -UNKNOWN_TYPE | wlw-radio_button_group_key:{actionForm.gender} | Female | Female | {actionForm.firstName}_1 -UNKNOWN_TYPE | wlw-radio_button_group_key:{actionForm.gender} | Male | Male | {actionForm.firstName}_1 ADDRESS_HOME_ZIP | {actionForm.zipCode} | Zip Code | | {actionForm.firstName}_1 UNKNOWN_TYPE | wlw-select_key:{actionForm.acctSecurityQuestion} | Set a Security Question | | {actionForm.firstName}_1 UNKNOWN_TYPE | {actionForm.acctSecurityAnswer} | Set a Security Question | Your Answer | {actionForm.firstName}_1
diff --git a/components/test/data/autofill/heuristics/output/047_register_continental.com.out b/components/test/data/autofill/heuristics/output/047_register_continental.com.out index a0d8179..d7571bb 100644 --- a/components/test/data/autofill/heuristics/output/047_register_continental.com.out +++ b/components/test/data/autofill/heuristics/output/047_register_continental.com.out
@@ -1,18 +1,10 @@ -ADDRESS_HOME_COUNTRY | ctl00$CustomerHeader$ddlCountries | CountryClose Please select your location or where you receive credit card billing statements to see pricing in your local currency.Available Languages: | US | -default -UNKNOWN_TYPE | ctl00$CustomerHeader$rdlang | Available Languages: | en-us | -default -UNKNOWN_TYPE | ctl00$CustomerHeader$rdlang | Available Languages: | es | -default -UNKNOWN_TYPE | ctl00$CustomerHeader$rdlang | Available Languages: | rd3 | -default -UNKNOWN_TYPE | ctl00$CustomerHeader$chkSave | Save this preference | on | -default +ADDRESS_HOME_COUNTRY | ctl00$CustomerHeader$ddlCountries | CountryClose Please select your location or where you receive credit card billing statements to see pricing in your local currency.Available Languages:Save this preference | US | -default UNKNOWN_TYPE | ctl00$ContentInfo$Name$cboTitle$cboTitle | Title: | | ctl00$ContentInfo$Name$cboTitle$cboTitle_1 NAME_FIRST | ctl00$ContentInfo$Name$FName$txtFName | First Name: | | ctl00$ContentInfo$Name$cboTitle$cboTitle_1 NAME_MIDDLE_INITIAL | ctl00$ContentInfo$Name$MName$txtMName | Middle Initial (optional): | | ctl00$ContentInfo$Name$cboTitle$cboTitle_1 NAME_LAST | ctl00$ContentInfo$Name$LName$txtLName | Last Name: | | ctl00$ContentInfo$Name$cboTitle$cboTitle_1 UNKNOWN_TYPE | ctl00$ContentInfo$Name$suffix$txtSuffix | Suffix (optional): | | ctl00$ContentInfo$Name$cboTitle$cboTitle_1 -UNKNOWN_TYPE | ctl00$ContentInfo$MemberAge$EnrollmentAge | This OnePass account is for an adult 18 years of age or older. This OnePass account is for an adult 18 years of age or older. | rdoAdult | ctl00$ContentInfo$Name$cboTitle$cboTitle_1 -UNKNOWN_TYPE | ctl00$ContentInfo$MemberAge$EnrollmentAge | This OnePass account is for a minor under the age of 18. This OnePass account is for a minor under the age of 18. | rdoMinor | ctl00$ContentInfo$Name$cboTitle$cboTitle_1 UNKNOWN_TYPE | ctl00$ContentInfo$BirthDate$txtDOB | Minor’s Date of Birth: | mm/dd/yyyy | ctl00$ContentInfo$Name$cboTitle$cboTitle_1 -UNKNOWN_TYPE | ctl00$ContentInfo$AddressType$AddressType | Home | rdoAddTypeHome | ctl00$ContentInfo$Name$cboTitle$cboTitle_1 -UNKNOWN_TYPE | ctl00$ContentInfo$AddressType$AddressType | Business/Other | rdoAddTypeBusiness | ctl00$ContentInfo$Name$cboTitle$cboTitle_1 ADDRESS_HOME_LINE1 | ctl00$ContentInfo$Address$address1$txtAddress1 | Street Address: | | ctl00$ContentInfo$Name$cboTitle$cboTitle_1 ADDRESS_HOME_LINE2 | ctl00$ContentInfo$Address$address2$txtAddress2 | Street Address: | | ctl00$ContentInfo$Name$cboTitle$cboTitle_1 ADDRESS_HOME_LINE3 | ctl00$ContentInfo$Address$address3$txtAddress2 | Street Address: | | ctl00$ContentInfo$Name$cboTitle$cboTitle_1 @@ -28,10 +20,6 @@ ADDRESS_HOME_COUNTRY | ctl00$ContentInfo$BusinessPhone$cboCountry$cboCountry | Country: | US | ctl00$ContentInfo$BusinessPhone$cboCountry$cboCountry_3 UNKNOWN_TYPE | ctl00$ContentInfo$HomeAirport$txtAirport | Home Airport (optional): | | ctl00$ContentInfo$BusinessPhone$cboCountry$cboCountry_3 EMAIL_ADDRESS | ctl00$ContentInfo$Email$txtEmail | E-mail Address: | | ctl00$ContentInfo$BusinessPhone$cboCountry$cboCountry_3 -UNKNOWN_TYPE | ctl00$ContentInfo$chkSubOpStatement | | on | ctl00$ContentInfo$BusinessPhone$cboCountry$cboCountry_3 -UNKNOWN_TYPE | ctl00$ContentInfo$chkSubopnewsoffers | | on | ctl00$ContentInfo$BusinessPhone$cboCountry$cboCountry_3 -UNKNOWN_TYPE | ctl00$ContentInfo$chkSubcocomspecials | | on | ctl00$ContentInfo$BusinessPhone$cboCountry$cboCountry_3 -UNKNOWN_TYPE | ctl00$ContentInfo$chkSubtripnotes | | on | ctl00$ContentInfo$BusinessPhone$cboCountry$cboCountry_3 UNKNOWN_TYPE | ctl00$ContentInfo$NewPin$txtNewPin$txtNewPin | New PIN: | | ctl00$ContentInfo$BusinessPhone$cboCountry$cboCountry_3 UNKNOWN_TYPE | ctl00$ContentInfo$NewPin$txtConfPin$txtConfPin | Re-type New PIN: | | ctl00$ContentInfo$BusinessPhone$cboCountry$cboCountry_3 UNKNOWN_TYPE | ctl00$ContentInfo$UsernamePassword$username$txtUsername | Username: | | ctl00$ContentInfo$BusinessPhone$cboCountry$cboCountry_3
diff --git a/components/test/data/autofill/heuristics/output/048_register_deviantart.com.out b/components/test/data/autofill/heuristics/output/048_register_deviantart.com.out index 1f53fd31..e2ea862 100644 --- a/components/test/data/autofill/heuristics/output/048_register_deviantart.com.out +++ b/components/test/data/autofill/heuristics/output/048_register_deviantart.com.out
@@ -1,15 +1,11 @@ EMAIL_ADDRESS | emailAddress | Email Address | | emailAddress_1 -UNKNOWN_TYPE | password | Password | | emailAddress_1 -UNKNOWN_TYPE | remember_me | Stay logged in | 1 | emailAddress_1 - -UNKNOWN_TYPE | existingAccount | Create a new Buyer Account | 0 | existingAccount_1 -UNKNOWN_TYPE | existingAccount | I have an existing deviantART account (email or username) | 1 | existingAccount_1 -EMAIL_ADDRESS | emailAddress | Email Address | | existingAccount_1 UNKNOWN_TYPE | password | Password | | -default -UNKNOWN_TYPE | remember_me | Stay logged in | 1 | -default -UNKNOWN_TYPE | passwordNew | Password | | existingAccount_1 -UNKNOWN_TYPE | passwordConfirm | Password (confirm) | | existingAccount_1 -NAME_FULL | name | Full Name | | existingAccount_1 -ADDRESS_HOME_COUNTRY | country | Country | 0 | existingAccount_1 +UNKNOWN_TYPE | passwordNew | Password | | emailAddress_1 +UNKNOWN_TYPE | passwordConfirm | Password (confirm) | | emailAddress_1 +NAME_FULL | name | Full Name | | emailAddress_1 +ADDRESS_HOME_COUNTRY | country | Country | 0 | emailAddress_1 + +EMAIL_ADDRESS | emailAddress | Email Address | | emailAddress_1 +UNKNOWN_TYPE | password | Password | | emailAddress_1 UNKNOWN_TYPE | q | deviantARTBrowse ArtPrints ShopT-Shirts & GearFind More ArtDaily DeviationsDiscover | | q_1
diff --git a/components/test/data/autofill/heuristics/output/049_register_ebay.com.out b/components/test/data/autofill/heuristics/output/049_register_ebay.com.out index b71cb75..b5d359d6 100644 --- a/components/test/data/autofill/heuristics/output/049_register_ebay.com.out +++ b/components/test/data/autofill/heuristics/output/049_register_ebay.com.out
@@ -36,7 +36,6 @@ UNKNOWN_TYPE | birthdate1 | Date of birth, Day | 0 | firstname_2 UNKNOWN_TYPE | birthdate3 | Year of birth, four digit format. | 0 | firstname_2 UNKNOWN_TYPE | tokentext | For added security - opens in a new window or tab, please enter the verification code hidden in the image. | | firstname_2 -UNKNOWN_TYPE | acceptq1 | I agree that:I accept the User Agreement - opens in a new window or tab and Privacy Policy.I may receive communications from eBay and can change my notification preferences in My eBay.I'm at least 18 years old. | 1 | firstname_2 UNKNOWN_TYPE | uword1 | Give us some word and we'll suggest some user IDs for you. Word 1 | | firstname_2 UNKNOWN_TYPE | uword2 | Word 2 | | firstname_2 UNKNOWN_TYPE | uword3 | Word 3 | | firstname_2 @@ -70,12 +69,10 @@ UNKNOWN_TYPE | birthdate1 | Date of birth, Day | 0 | firstname_3 UNKNOWN_TYPE | birthdate3 | Year of birth, four digit format. | 0 | firstname_3 UNKNOWN_TYPE | tokentext | For added security - opens in a new window or tab, please enter the verification code hidden in the image. | | firstname_3 -UNKNOWN_TYPE | acceptq1 | I agree that:I accept the User Agreement - opens in a new window or tab and Privacy Policy.I may receive communications from eBay and can change my notification preferences in My eBay.I'm at least 18 years old. | 1 | firstname_3 UNKNOWN_TYPE | uword1 | Give us some word and we'll suggest some user IDs for you. Word 1 | | firstname_3 UNKNOWN_TYPE | uword2 | Word 2 | | firstname_3 UNKNOWN_TYPE | uword3 | Word 3 | | firstname_3 UNKNOWN_TYPE | tokentext | For added security - opens in a new window or tab, please enter the verification code hidden in the image. | | firstname_3 -UNKNOWN_TYPE | acceptq1 | I agree that:I accept the User Agreement - opens in a new window or tab and Privacy Policy.I may receive communications from eBay and can change my notification preferences in My eBay.I'm at least 18 years old. | 1 | firstname_3 UNKNOWN_TYPE | uword1 | Give us some word and we'll suggest some user IDs for you. Word 1 | | uword1_1 UNKNOWN_TYPE | uword2 | Word 2 | | uword1_1
diff --git a/components/test/data/autofill/heuristics/output/050_register_ecomm.dell.com.out b/components/test/data/autofill/heuristics/output/050_register_ecomm.dell.com.out index 0219f4b..a1dc9914a 100644 --- a/components/test/data/autofill/heuristics/output/050_register_ecomm.dell.com.out +++ b/components/test/data/autofill/heuristics/output/050_register_ecomm.dell.com.out
@@ -7,8 +7,6 @@ ADDRESS_HOME_STATE | address$ctl00$address$addressValidator$_EditView$region$ctl01 | State | | name$ctl00$name$_EditView$first_1 ADDRESS_HOME_ZIP | address$ctl00$address$addressValidator$_EditView$postal_code | Zip Code | | name$ctl00$name$_EditView$first_1 ADDRESS_HOME_ZIP | address$ctl00$address$addressValidator$_EditView$postal_code2 | 4-digit Ext. | | name$ctl00$name$_EditView$first_1 -UNKNOWN_TYPE | address$ctl00$address$addressValidator$OverrideAddress | Use the address I entered. Note that a non-standardized address may affect your tax accuracy and your delivery time. | on | -default EMAIL_ADDRESS | email$ctl07 | Email Address (used to sign into your account) | | name$ctl00$name$_EditView$first_1 UNKNOWN_TYPE | CreateNewPassword$ctl07 | Create New Password | | name$ctl00$name$_EditView$first_1 UNKNOWN_TYPE | ConfirmPassword$ctl07 | Confirm New Password | | name$ctl00$name$_EditView$first_1 -UNKNOWN_TYPE | Subscription | | on | name$ctl00$name$_EditView$first_1
diff --git a/components/test/data/autofill/heuristics/output/051_register_epson.com.out b/components/test/data/autofill/heuristics/output/051_register_epson.com.out index bf8fb455..c7e41fc 100644 --- a/components/test/data/autofill/heuristics/output/051_register_epson.com.out +++ b/components/test/data/autofill/heuristics/output/051_register_epson.com.out
@@ -6,6 +6,5 @@ EMAIL_ADDRESS | emLogin | * E-mail address | | fname_1 UNKNOWN_TYPE | pw | * Password | | fname_1 UNKNOWN_TYPE | verifypw | * Re-Confirm Password | | fname_1 -UNKNOWN_TYPE | wantMsg | I'd like to hear about new product information, special discounts and offers exclusive to Epson customers only. Please sign me up! | on | fname_1 UNKNOWN_TYPE | query | United States | | query_1
diff --git a/components/test/data/autofill/heuristics/output/052_register_google.com.out b/components/test/data/autofill/heuristics/output/052_register_google.com.out index d8551f8..48b23e7e 100644 --- a/components/test/data/autofill/heuristics/output/052_register_google.com.out +++ b/components/test/data/autofill/heuristics/output/052_register_google.com.out
@@ -4,8 +4,6 @@ EMAIL_ADDRESS | Email | | | FirstName_1 UNKNOWN_TYPE | Passwd | | | FirstName_1 UNKNOWN_TYPE | PasswdAgain | Re-enter password: | | FirstName_1 -UNKNOWN_TYPE | PersistentCookie | Re-enter password: | yes | FirstName_1 -UNKNOWN_TYPE | smhck | Re-enter password: | 1 | FirstName_1 UNKNOWN_TYPE | selection | Security question: | choosequestion | FirstName_1 UNKNOWN_TYPE | ownquestion | Security question: | | -default UNKNOWN_TYPE | IdentityAnswer | Answer: | | FirstName_1
diff --git a/components/test/data/autofill/heuristics/output/053_register_gymboree.com.out b/components/test/data/autofill/heuristics/output/053_register_gymboree.com.out index 312731c..0e6e638 100644 --- a/components/test/data/autofill/heuristics/output/053_register_gymboree.com.out +++ b/components/test/data/autofill/heuristics/output/053_register_gymboree.com.out
@@ -12,9 +12,6 @@ UNKNOWN_TYPE | LOYALTY_ACCOUNT<>password | Password*! | | LOYALTY_ACCOUNT<>email_1 UNKNOWN_TYPE | LOYALTY_ACCOUNT<>confirmPassword | Confirm Password* | | LOYALTY_ACCOUNT<>email_1 UNKNOWN_TYPE | LOYALTY_ACCOUNT<>ATR_USER_Password_Hint | Password Hint* | | LOYALTY_ACCOUNT<>email_1 -UNKNOWN_TYPE | LOYALTY_ADDRESS<>ATR_indBillAsRewards | Save the address above as my default billing address. | true | LOYALTY_ACCOUNT<>email_1 -UNKNOWN_TYPE | LOYALTY_ACCOUNT<>sendEmail | Save the address above as my default billing address. I would like to receive promotions and special offers from Gymboree. | true | LOYALTY_ACCOUNT<>email_1 -UNKNOWN_TYPE | accept | I have read and accept the terms and conditions of Gymboree Rewards. (Rewards information and offers will be emailed to you.) | on | LOYALTY_ACCOUNT<>email_1 EMAIL_ADDRESS | SUBSCRIBER_INFO<>email | Exclusive E-Mail Sign Up | Sign Up Today! | SUBSCRIBER_INFO<>email_1
diff --git a/components/test/data/autofill/heuristics/output/054_register_hotels.com.out b/components/test/data/autofill/heuristics/output/054_register_hotels.com.out index afaf497e4..db37422 100644 --- a/components/test/data/autofill/heuristics/output/054_register_hotels.com.out +++ b/components/test/data/autofill/heuristics/output/054_register_hotels.com.out
@@ -14,6 +14,3 @@ PHONE_HOME_CITY_AND_NUMBER | profileInformation.customerContact.primaryPhone | Phone number | | profileInformation.email_1 COMPANY_NAME | profileInformation.businessName | Company name | | profileInformation.email_1 UNKNOWN_TYPE | profileInformation.currency | Preferred currency | USD | profileInformation.email_1 -UNKNOWN_TYPE | profileInformation.customerContact.loyaltyAccountDesired | Sign me up to get 1 free night anywhere for every 10 nights I stay with Hotels.com. By enrolling, I agree to the full Terms and Conditions of the program. Learn more. | true | profileInformation.email_1 -UNKNOWN_TYPE | subscribeNewsletter | Email me exclusive coupons, deals and travel information from hotels.com | true | profileInformation.email_1 -UNKNOWN_TYPE | termsConditionsConfirm | I have read and accepted the Terms and Conditions and Privacy Policy for this website* | true | profileInformation.email_1
diff --git a/components/test/data/autofill/heuristics/output/055_register_imdb.com.out b/components/test/data/autofill/heuristics/output/055_register_imdb.com.out index 8fafb6d..c34f358 100644 --- a/components/test/data/autofill/heuristics/output/055_register_imdb.com.out +++ b/components/test/data/autofill/heuristics/output/055_register_imdb.com.out
@@ -1,7 +1,5 @@ EMAIL_ADDRESS | email1 | E-mail: | | email1_1 EMAIL_ADDRESS | email2 | Confirm E-mail: | | email1_1 -UNKNOWN_TYPE | gender | Male | M | email1_1 -UNKNOWN_TYPE | gender | Female | F | email1_1 UNKNOWN_TYPE | year | Year of Birth: | | email1_1 ADDRESS_HOME_ZIP | postal | ZIP/Postal Code: | | email1_1 ADDRESS_HOME_COUNTRY | country | Country: | US | email1_1
diff --git a/components/test/data/autofill/heuristics/output/057_register_live.com.out b/components/test/data/autofill/heuristics/output/057_register_live.com.out index 5af88d5b..0995585 100644 --- a/components/test/data/autofill/heuristics/output/057_register_live.com.out +++ b/components/test/data/autofill/heuristics/output/057_register_live.com.out
@@ -11,7 +11,4 @@ UNKNOWN_TYPE | iSA | Secret answer: | | iAltEmail_2 NAME_FIRST | iFirstName | First name: | | iAltEmail_2 NAME_LAST | iLastName | Last name: | | iAltEmail_2 -UNKNOWN_TYPE | profile_gender | Male | m | iAltEmail_2 -UNKNOWN_TYPE | profile_gender | Female | f | iAltEmail_2 UNKNOWN_TYPE | iBirthYear | Birth year: | Example: 1990 | iAltEmail_2 -UNKNOWN_TYPE | iOptinEmail | Send me email with promotional offers and survey invitations from Windows Live, Bing, and MSN. (You can unsubscribe at any time.) | on | iAltEmail_2
diff --git a/components/test/data/autofill/heuristics/output/058_register_livejournal.com.out b/components/test/data/autofill/heuristics/output/058_register_livejournal.com.out index d510c8a6..515da45 100644 --- a/components/test/data/autofill/heuristics/output/058_register_livejournal.com.out +++ b/components/test/data/autofill/heuristics/output/058_register_livejournal.com.out
@@ -6,4 +6,3 @@ UNKNOWN_TYPE | Widget[CreateAccount]_bday_mm | Birthdate: | 1 | Widget[CreateAccount]_user_1 UNKNOWN_TYPE | Widget[CreateAccount]_bday_dd | Birthdate: | | Widget[CreateAccount]_user_1 UNKNOWN_TYPE | Widget[CreateAccount]_bday_yyyy | Birthdate: | | Widget[CreateAccount]_user_1 -UNKNOWN_TYPE | Widget[CreateAccount]_news | Yes, send me LiveJournal announcements via email. | 1 | Widget[CreateAccount]_user_1
diff --git a/components/test/data/autofill/heuristics/output/059_register_macys.com.out b/components/test/data/autofill/heuristics/output/059_register_macys.com.out index fcdc753..7359ad5 100644 --- a/components/test/data/autofill/heuristics/output/059_register_macys.com.out +++ b/components/test/data/autofill/heuristics/output/059_register_macys.com.out
@@ -11,14 +11,8 @@ UNKNOWN_TYPE | BirthDay | | NOSELECTION | FirstName_1 UNKNOWN_TYPE | BirthYear | | NOSELECTION | FirstName_1 UNKNOWN_TYPE | Gender | Gender | NOSELECTION | FirstName_1 -UNKNOWN_TYPE | NewsLetter | We'll let you know about exclusive sales and events,both online and in-store. | NewsLetter | FirstName_1 -UNKNOWN_TYPE | MobileMarketing | Yes, please text me about exclusive sales and events, both online and in-store. We'll send your first text message within 48 hours. | MobileMarketing | FirstName_1 PHONE_HOME_CITY_CODE_WITH_TRUNK_PREFIX | MobilePhoneAreaCode | | | FirstName_1 PHONE_HOME_NUMBER | MobilePhoneExchangeNbr | | | FirstName_1 PHONE_HOME_CITY_AND_NUMBER | MobilePhoneSubscriberNbr | | | FirstName_1 -UNKNOWN_TYPE | addacard | Yes, I'd like to add my Macy's Card to my profile. | on | FirstName_1 UNKNOWN_TYPE | MaskedAccountNumber | Macy's Account Number: | | FirstName_1 UNKNOWN_TYPE | SSN4 | Last 4 digits of SSN: | | FirstName_1 -UNKNOWN_TYPE | addToWallet | Save this store card in your macys.com wallet for faster checkout. | addToWallet | FirstName_1 -UNKNOWN_TYPE | emailAlert | Receive email notification when statements are ready for review andwhen payments are due. | emailAlert | FirstName_1 -UNKNOWN_TYPE | paperStatementDelivery | I want to turn off paper statement delivery and receive my statements online. | paperStatementDelivery | FirstName_1
diff --git a/components/test/data/autofill/heuristics/output/061_register_myspace.com.out b/components/test/data/autofill/heuristics/output/061_register_myspace.com.out index de3b2c5..0bf6c9c 100644 --- a/components/test/data/autofill/heuristics/output/061_register_myspace.com.out +++ b/components/test/data/autofill/heuristics/output/061_register_myspace.com.out
@@ -1,21 +1,14 @@ EMAIL_ADDRESS | Email | Email | | Email_1 UNKNOWN_TYPE | Password | Password | | Email_1 -UNKNOWN_TYPE | Remember | Keep me logged in | on | Email_1 -UNKNOWN_TYPE | accountType | Personal | 2 | accountType_1 -UNKNOWN_TYPE | accountType | Musician | 7 | accountType_1 -UNKNOWN_TYPE | accountType | Comedian | 15 | accountType_1 -UNKNOWN_TYPE | accountType | Filmmaker | 9 | accountType_1 -NAME_FIRST | tbxFirstName | First Name | | accountType_1 -NAME_LAST | tbxLastName | Last Name | | accountType_1 +NAME_FIRST | tbxFirstName | First Name | | tbxFirstName_1 +NAME_LAST | tbxLastName | Last Name | | tbxFirstName_1 UNKNOWN_TYPE | tbxMusicianName | Artist Name | | -default UNKNOWN_TYPE | ddlGenre | Genre | | -default UNKNOWN_TYPE | ddlLabel | Label Type | | -default -EMAIL_ADDRESS | tbxEmail | Email | | accountType_1 -UNKNOWN_TYPE | tbxPassword | Password | | accountType_1 -UNKNOWN_TYPE | tbxPassword1 | Confirm Password | | accountType_1 -UNKNOWN_TYPE | ddlMonth | Birthday | | accountType_1 -UNKNOWN_TYPE | ddlDay | | | accountType_1 -UNKNOWN_TYPE | ddlYear | | | accountType_1 -UNKNOWN_TYPE | rblGender | Male | M | accountType_1 -UNKNOWN_TYPE | rblGender | Female | F | accountType_1 +EMAIL_ADDRESS | tbxEmail | Email | | tbxFirstName_1 +UNKNOWN_TYPE | tbxPassword | Password | | tbxFirstName_1 +UNKNOWN_TYPE | tbxPassword1 | Confirm Password | | tbxFirstName_1 +UNKNOWN_TYPE | ddlMonth | Birthday | | tbxFirstName_1 +UNKNOWN_TYPE | ddlDay | | | tbxFirstName_1 +UNKNOWN_TYPE | ddlYear | | | tbxFirstName_1
diff --git a/components/test/data/autofill/heuristics/output/062_register_newegg.com.out b/components/test/data/autofill/heuristics/output/062_register_newegg.com.out index bdcaa85..f1779d4 100644 --- a/components/test/data/autofill/heuristics/output/062_register_newegg.com.out +++ b/components/test/data/autofill/heuristics/output/062_register_newegg.com.out
@@ -19,8 +19,6 @@ PHONE_HOME_NUMBER_PREFIX | BDayPhone_tel2 | Exchange | | LoginName_1 PHONE_HOME_NUMBER_SUFFIX | BDayPhone_tel3 | Last four digits | | LoginName_1 PHONE_HOME_EXTENSION | BDayPhone_ext1 | Ext | | LoginName_1 -UNKNOWN_TYPE | same | Yes | 1 | LoginName_1 -UNKNOWN_TYPE | same | No | 0 | LoginName_1 NAME_FIRST | SFirstName | First Name* | | -default NAME_MIDDLE_INITIAL | SMI | MI | | -default NAME_LAST | SLastName | Last Name* | | -default @@ -41,8 +39,6 @@ UNKNOWN_TYPE | hob | Where did you hear about Newegg.com? | 0 | custtype_2 UNKNOWN_TYPE | FirstPurchase | Is this your first Newegg.com purchase? | 0 | custtype_2 UNKNOWN_TYPE | GameHours | How many hours per week do you play PC games? | 0 | custtype_2 -UNKNOWN_TYPE | Spam | Sign me up for exclusive newsletter deals, sweepstakes, and 24-hour sales only available to subscribers | 1 | custtype_2 -UNKNOWN_TYPE | SMS | I would like to receive SMS messages via my wireless device. | 1 | custtype_2 UNKNOWN_TYPE | Description | Search site | | Description_1
diff --git a/components/test/data/autofill/heuristics/output/063_register_officedepot.com.out b/components/test/data/autofill/heuristics/output/063_register_officedepot.com.out index 137d942..86392df 100644 --- a/components/test/data/autofill/heuristics/output/063_register_officedepot.com.out +++ b/components/test/data/autofill/heuristics/output/063_register_officedepot.com.out
@@ -20,7 +20,6 @@ CREDIT_CARD_EXP_MONTH | paymentFormInfo.creditCardExpMonth | Expiration Date: | | paymentFormInfo.creditCardType_2 CREDIT_CARD_EXP_2_DIGIT_YEAR | paymentFormInfo.creditCardExpYear | Expiration Date: | -1 | paymentFormInfo.creditCardType_2 CREDIT_CARD_VERIFICATION_CODE | paymentFormInfo.creditCardCvv | CID | | paymentFormInfo.creditCardType_2 -UNKNOWN_TYPE | sameAsBilling | Same as Billing | on | addrsForm[0].firstName_1 NAME_FIRST | addrsForm[2].firstName | *First Name: | | addrsForm[2].firstName_3 NAME_MIDDLE_INITIAL | addrsForm[2].middleInitial | Middle Initial: | | addrsForm[2].firstName_3 NAME_LAST | addrsForm[2].lastName | *Last Name: | | addrsForm[2].firstName_3 @@ -38,6 +37,5 @@ UNKNOWN_TYPE | loginForm.loginName | *Login Name: | | addrsForm[2].firstName_3 UNKNOWN_TYPE | loginForm.password | *Password: | | addrsForm[2].firstName_3 UNKNOWN_TYPE | loginForm.passwordConfirm | *Password Confirm: | | addrsForm[2].firstName_3 -UNKNOWN_TYPE | loginForm.autoLogin | Log me in automatically(Cookies must be enabled) | on | addrsForm[2].firstName_3 UNKNOWN_TYPE | zip | | | zip_1
diff --git a/components/test/data/autofill/heuristics/output/065_register_pyramidcollection.com.out b/components/test/data/autofill/heuristics/output/065_register_pyramidcollection.com.out index 9a731fd..389be1e 100644 --- a/components/test/data/autofill/heuristics/output/065_register_pyramidcollection.com.out +++ b/components/test/data/autofill/heuristics/output/065_register_pyramidcollection.com.out
@@ -21,6 +21,5 @@ ADDRESS_HOME_ZIP | SZ1 | Zip Code * | | SF1_2 PHONE_HOME_CITY_AND_NUMBER | SP2 | Day Phone * | | SF1_2 PHONE_HOME_CITY_AND_NUMBER | SP3 | Evening Phone | | SF1_2 -UNKNOWN_TYPE | sameAsBilling | My Shipping address is same as billing address. | on | SF1_2 EMAIL_ADDRESS | email | ENTER TO WIN A $100 GIFT CARD! | enter email address | email_1
diff --git a/components/test/data/autofill/heuristics/output/066_register_rediff.com.out b/components/test/data/autofill/heuristics/output/066_register_rediff.com.out index 3288fd00..e41574e 100644 --- a/components/test/data/autofill/heuristics/output/066_register_rediff.com.out +++ b/components/test/data/autofill/heuristics/output/066_register_rediff.com.out
@@ -3,15 +3,12 @@ UNKNOWN_TYPE | passwd | Password | | name_1 UNKNOWN_TYPE | confirm_passwd | Retype password | | name_1 EMAIL_ADDRESS | altemail | Alternate Email Address | | name_1 -UNKNOWN_TYPE | chk_altemail | | on | name_1 UNKNOWN_TYPE | hintq | Select a Security Question | | -default UNKNOWN_TYPE | hinta | Enter an Answer | | -default UNKNOWN_TYPE | mothername | Mother's Maiden Name | | -default UNKNOWN_TYPE | DOB_Day | Date of Birth | | name_1 UNKNOWN_TYPE | DOB_Month | Date of Birth | | name_1 UNKNOWN_TYPE | DOB_Year | Date of Birth | | name_1 -UNKNOWN_TYPE | gender | Male | m | name_1 -UNKNOWN_TYPE | gender | Female | f | name_1 ADDRESS_HOME_COUNTRY | country | I live inCountry | 99 | name_1 ADDRESS_HOME_CITY | city | City | | name_1 UNKNOWN_TYPE | othercity | City : | | -default
diff --git a/components/test/data/autofill/heuristics/output/067_register_rei.com.out b/components/test/data/autofill/heuristics/output/067_register_rei.com.out index 3a9f5d0..9f66971 100644 --- a/components/test/data/autofill/heuristics/output/067_register_rei.com.out +++ b/components/test/data/autofill/heuristics/output/067_register_rei.com.out
@@ -7,4 +7,3 @@ UNKNOWN_TYPE | logonPasswordVerify | Re-type Password:* | | firstName_1 ADDRESS_HOME_ZIP | zipCode | ZIP (Postal) Code:* | | firstName_1 EMAIL_ADDRESS | email1 | E-mail Address:* | | firstName_1 -UNKNOWN_TYPE | gearmail | Yes, I'd like to be notified of news, offers and events at REI via this email address. | y | firstName_1
diff --git a/components/test/data/autofill/heuristics/output/068_register_rocketlawyer.com.out b/components/test/data/autofill/heuristics/output/068_register_rocketlawyer.com.out index 78199f53..2eb2ab2 100644 --- a/components/test/data/autofill/heuristics/output/068_register_rocketlawyer.com.out +++ b/components/test/data/autofill/heuristics/output/068_register_rocketlawyer.com.out
@@ -5,4 +5,3 @@ EMAIL_ADDRESS | ctl00$ctl00$ctl00$SiteMasterBody$ContentPlaceHolder1$ContentPlaceHolder1$txtConfirmUserName | Confirm Email Address * | | ctl00$ctl00$ctl00$SiteMasterBody$NewNavigationTabs$txtQuery_1 UNKNOWN_TYPE | ctl00$ctl00$ctl00$SiteMasterBody$ContentPlaceHolder1$ContentPlaceHolder1$txtPassword | Password * | | ctl00$ctl00$ctl00$SiteMasterBody$NewNavigationTabs$txtQuery_1 UNKNOWN_TYPE | ctl00$ctl00$ctl00$SiteMasterBody$ContentPlaceHolder1$ContentPlaceHolder1$txtConfirmPassword | Confirm Password * | | ctl00$ctl00$ctl00$SiteMasterBody$NewNavigationTabs$txtQuery_1 -UNKNOWN_TYPE | ctl00$ctl00$ctl00$SiteMasterBody$ContentPlaceHolder1$ContentPlaceHolder1$chkNewsletter | Yes, send me Rocket Lawyer partner offers, which are sent no more than twice per month and are from Rocket Lawyer's trusted business partners. | on | ctl00$ctl00$ctl00$SiteMasterBody$NewNavigationTabs$txtQuery_1
diff --git a/components/test/data/autofill/heuristics/output/069_register_signup.clicksor.com.out b/components/test/data/autofill/heuristics/output/069_register_signup.clicksor.com.out index 9b20f53..1531a8f 100644 --- a/components/test/data/autofill/heuristics/output/069_register_signup.clicksor.com.out +++ b/components/test/data/autofill/heuristics/output/069_register_signup.clicksor.com.out
@@ -12,5 +12,4 @@ ADDRESS_HOME_STATE | province | State: | | email1_1 ADDRESS_HOME_ZIP | zipCode | Zip code: | | email1_1 ADDRESS_HOME_COUNTRY | country | Country: | US | email1_1 -UNKNOWN_TYPE | termcheck | By checking this box I acknowledge that I have read and agree to the Clicksor™ | ON | email1_1 UNKNOWN_TYPE | securityCode | Enter the security code shown below: | | email1_1
diff --git a/components/test/data/autofill/heuristics/output/070_register_signup.live.com.out b/components/test/data/autofill/heuristics/output/070_register_signup.live.com.out index 5af88d5b..0995585 100644 --- a/components/test/data/autofill/heuristics/output/070_register_signup.live.com.out +++ b/components/test/data/autofill/heuristics/output/070_register_signup.live.com.out
@@ -11,7 +11,4 @@ UNKNOWN_TYPE | iSA | Secret answer: | | iAltEmail_2 NAME_FIRST | iFirstName | First name: | | iAltEmail_2 NAME_LAST | iLastName | Last name: | | iAltEmail_2 -UNKNOWN_TYPE | profile_gender | Male | m | iAltEmail_2 -UNKNOWN_TYPE | profile_gender | Female | f | iAltEmail_2 UNKNOWN_TYPE | iBirthYear | Birth year: | Example: 1990 | iAltEmail_2 -UNKNOWN_TYPE | iOptinEmail | Send me email with promotional offers and survey invitations from Windows Live, Bing, and MSN. (You can unsubscribe at any time.) | on | iAltEmail_2
diff --git a/components/test/data/autofill/heuristics/output/071_register_sourceforge.net.out b/components/test/data/autofill/heuristics/output/071_register_sourceforge.net.out index 2d1d400..9f6554a 100644 --- a/components/test/data/autofill/heuristics/output/071_register_sourceforge.net.out +++ b/components/test/data/autofill/heuristics/output/071_register_sourceforge.net.out
@@ -11,8 +11,5 @@ UNKNOWN_TYPE | X3XlRdtrBz0a6O_kXqynrr46rigU | Security Answer: | | X1mRVeMqejLnZpd1etxNGHllat2M_1 UNKNOWN_TYPE | X0WBbd4KZLSgCIb8WlZZNJ3g3fVk | Job Title: | | X1mRVeMqejLnZpd1etxNGHllat2M_1 UNKNOWN_TYPE | X229ZZcPc31emF7VTU0RPmITuTNc | Number of Employees: | | X1mRVeMqejLnZpd1etxNGHllat2M_1 -UNKNOWN_TYPE | X12VEYcbdixhIflk8_zHDFWB72qk | Receive a monthly newsletter that includes site news, project updates and more. | siteupdates | X1mRVeMqejLnZpd1etxNGHllat2M_1 -UNKNOWN_TYPE | X12VEYcbdixhIflk8_zHDFWB72qk | Receive a weekly newsletter that includes IT whitepapers, research, webcasts and more. | research | X1mRVeMqejLnZpd1etxNGHllat2M_1 -UNKNOWN_TYPE | X12VEYcbdixhIflk8_zHDFWB72qk | Receive information from SourceForge.net partners. | thirdparty | X1mRVeMqejLnZpd1etxNGHllat2M_1 UNKNOWN_TYPE | X1GJbe8rKlh_p74K4nXnhkGtC-8Q | You seem to have CSS turned off. Please don't fill out this field. | | -default UNKNOWN_TYPE | X1GJbe8rKlx_p74K4nXnhkGtC-8Q | You seem to have CSS turned off. Please don't fill out this field. | | -default
diff --git a/components/test/data/autofill/heuristics/output/072_register_supershuttle.com.out b/components/test/data/autofill/heuristics/output/072_register_supershuttle.com.out index c885f82..ccc6c2d 100644 --- a/components/test/data/autofill/heuristics/output/072_register_supershuttle.com.out +++ b/components/test/data/autofill/heuristics/output/072_register_supershuttle.com.out
@@ -8,4 +8,3 @@ PHONE_HOME_CITY_AND_NUMBER_WITHOUT_TRUNK_PREFIX | ctl00$cphRight$Registration1$txtIntPhoneNumber | Contact or Cell Phone Number | | ctl00$cphRight$Registration1$txtEmailAddress_2 UNKNOWN_TYPE | ctl00$cphRight$Registration1$txtPassword | Password | | ctl00$cphRight$Registration1$txtEmailAddress_2 UNKNOWN_TYPE | ctl00$cphRight$Registration1$txtConfirmPassword | Confirm Password | | ctl00$cphRight$Registration1$txtEmailAddress_2 -UNKNOWN_TYPE | ctl00$cphRight$Registration1$chkAcceptSpecialOffers | Email me regarding SuperShuttle special offers and promotions | on | ctl00$cphRight$Registration1$txtEmailAddress_2
diff --git a/components/test/data/autofill/heuristics/output/073_register_target.com.out b/components/test/data/autofill/heuristics/output/073_register_target.com.out index fd7c41f..79807d9 100644 --- a/components/test/data/autofill/heuristics/output/073_register_target.com.out +++ b/components/test/data/autofill/heuristics/output/073_register_target.com.out
@@ -25,6 +25,3 @@ EMAIL_ADDRESS | emailCheck | Re-enter email address:* | | userName_1 UNKNOWN_TYPE | password | Create a password:* | | userName_1 UNKNOWN_TYPE | passwordCheck | Re-enter password:* | | userName_1 -UNKNOWN_TYPE | subscribeEmail | Yes, please send me e-mails about special offers, exclusives and promotions from Target. | 1 | userName_1 -UNKNOWN_TYPE | ageCheck | Yes | yes | userName_1 -UNKNOWN_TYPE | ageCheck | No | no | userName_1
diff --git a/components/test/data/autofill/heuristics/output/074_register_threadless.com.out b/components/test/data/autofill/heuristics/output/074_register_threadless.com.out index aa0597d..898bd49 100644 --- a/components/test/data/autofill/heuristics/output/074_register_threadless.com.out +++ b/components/test/data/autofill/heuristics/output/074_register_threadless.com.out
@@ -10,5 +10,4 @@ UNKNOWN_TYPE | create_password | Password | | create_username_1 UNKNOWN_TYPE | retype_password | Re-type password | | create_username_1 EMAIL_ADDRESS | email | Email | | create_username_1 -UNKNOWN_TYPE | join_newsletter | Join our newsletter and be first to know about new tees and great deals! | on | create_username_1 UNKNOWN_TYPE | recaptcha_response_field | Type the words above Type the numbers you hear | | -default
diff --git a/components/test/data/autofill/heuristics/output/080_crbug_53075.out b/components/test/data/autofill/heuristics/output/080_crbug_53075.out index 509becbb..6cd5213 100644 --- a/components/test/data/autofill/heuristics/output/080_crbug_53075.out +++ b/components/test/data/autofill/heuristics/output/080_crbug_53075.out
@@ -13,7 +13,6 @@ ADDRESS_HOME_STATE | ecomms_county | County: | | ecomms_company_name_1 ADDRESS_HOME_ZIP | ecomms_postcode | Postcode: | | ecomms_company_name_1 ADDRESS_HOME_COUNTRY | ecomms_country | Country: | 224 | ecomms_company_name_1 -UNKNOWN_TYPE | delivery_address | specify a different delivery address | 1 | ecomms_company_name_1 UNKNOWN_TYPE | del_title | Title: | | -default NAME_FIRST | del_first_name | First Name: | | -default NAME_LAST | del_last_name | Last Name: | | -default @@ -24,4 +23,4 @@ ADDRESS_HOME_CITY | del_town | Town / City: | | -default ADDRESS_HOME_STATE | del_county | County: | | -default ADDRESS_HOME_ZIP | del_postcode | Postcode: | | -default -ADDRESS_HOME_COUNTRY | ecomms_del_country | Country: | 224 | -default +ADDRESS_HOME_COUNTRY | ecomms_del_country | Country: | 224 | ecomms_company_name_1
diff --git a/components/test/data/autofill/heuristics/output/081_crbug_64569.out b/components/test/data/autofill/heuristics/output/081_crbug_64569.out index 52e7837..5f0420d7 100644 --- a/components/test/data/autofill/heuristics/output/081_crbug_64569.out +++ b/components/test/data/autofill/heuristics/output/081_crbug_64569.out
@@ -1,17 +1,14 @@ -EMAIL_ADDRESS | email | Enter your email address | | email_1 +CREDIT_CARD_NAME_FULL | ccFullName | (as it appears on the card) | | ccFullName_1 +CREDIT_CARD_NUMBER | ccNumber | (no dashes or spaces) | | ccFullName_1 +CREDIT_CARD_EXP_MONTH | ccExpMonth | Expiration date | 1 | ccFullName_1 +CREDIT_CARD_EXP_4_DIGIT_YEAR | ccExpYear | Your name (as it appears on the card) Credit card number (no dashes or spaces) Expiration date | 2011 | ccFullName_1 +CREDIT_CARD_VERIFICATION_CODE | ccSecurity | Security code | | ccFullName_1 +NAME_FIRST | addrFirstName | First name | | addrFirstName_2 +NAME_LAST | addrLastName | Last name | | addrFirstName_2 +ADDRESS_HOME_LINE1 | addrStreet1 | (street address, PO box, company name) | | addrFirstName_2 +ADDRESS_HOME_LINE2 | addrStreet2 | (apt, suite, building, floor, etc) | | addrFirstName_2 +ADDRESS_HOME_CITY | addrCity | City | | addrFirstName_2 +ADDRESS_HOME_STATE | addrState | State | | addrFirstName_2 +ADDRESS_HOME_ZIP | addrZip | Postal code | | addrFirstName_2 -UNKNOWN_TYPE | payment_group | Payment Type We accept Visa, Mastercard, Discover, American Express, and PayPal. | cc | payment_group_1 -UNKNOWN_TYPE | payment_group | Payment Type We accept Visa, Mastercard, Discover, American Express, and PayPal. | paypal | payment_group_1 -CREDIT_CARD_NAME_FULL | ccFullName | (as it appears on the card) | | ccFullName_2 -CREDIT_CARD_NUMBER | ccNumber | (no dashes or spaces) | | ccFullName_2 -CREDIT_CARD_EXP_MONTH | ccExpMonth | Expiration date | 1 | ccFullName_2 -CREDIT_CARD_EXP_4_DIGIT_YEAR | ccExpYear | Your name (as it appears on the card) Credit card number (no dashes or spaces) Expiration date | 2011 | ccFullName_2 -CREDIT_CARD_VERIFICATION_CODE | ccSecurity | Security code | | ccFullName_2 -UNKNOWN_TYPE | sameinfo | Same as shipping address? | on | payment_group_1 -NAME_FIRST | addrFirstName | First name | | payment_group_1 -NAME_LAST | addrLastName | Last name | | payment_group_1 -ADDRESS_HOME_LINE1 | addrStreet1 | (street address, PO box, company name) | | payment_group_1 -ADDRESS_HOME_LINE2 | addrStreet2 | (apt, suite, building, floor, etc) | | payment_group_1 -ADDRESS_HOME_CITY | addrCity | City | | payment_group_1 -ADDRESS_HOME_STATE | addrState | State | | payment_group_1 -ADDRESS_HOME_ZIP | addrZip | Postal code | | payment_group_1 +EMAIL_ADDRESS | email | Enter your email address | | email_1
diff --git a/components/test/data/autofill/heuristics/output/083_crbug_87517.out b/components/test/data/autofill/heuristics/output/083_crbug_87517.out index 4467b34..b8aca25 100644 --- a/components/test/data/autofill/heuristics/output/083_crbug_87517.out +++ b/components/test/data/autofill/heuristics/output/083_crbug_87517.out
@@ -11,20 +11,11 @@ PHONE_HOME_CITY_AND_NUMBER | dphone | Telephone | | f1_1 EMAIL_ADDRESS | demail | Email * | | f1_1 UNKNOWN_TYPE | amt | $ | | f1_1 -UNKNOWN_TYPE | tributeSelect | My donation is In Honor or In Memory of someone special | on | f1_1 -UNKNOWN_TYPE | DonationType | This is a one time donation | OneTime | f1_1 -UNKNOWN_TYPE | DonationType | I would like to make this a recurring donation deducted | Recurs | f1_1 UNKNOWN_TYPE | ccrecurring | I would like to make this a recurring donation deducted | Monthly | f1_1 UNKNOWN_TYPE | remLen | ( You may enter up to 500 characters. ) | 500 | f1_1 UNKNOWN_TYPE | Comments | ( You may enter up to 500 characters. ) | | f1_1 -UNKNOWN_TYPE | tribute | In Honor Of | IHO | -default -UNKNOWN_TYPE | tribute | In Memory Of | IMO | -default NAME_FULL | Tribute_Name | Name: | | -default UNKNOWN_TYPE | Tribute_occasion | Occasion: | | -default -UNKNOWN_TYPE | Tribute_Disclosure | I wish to remain anonymous | on | -default -UNKNOWN_TYPE | Tribute_Notification | Please send a notification of my gift to the following address: | on | -default -UNKNOWN_TYPE | Tribute_IncludeAmount | Include the amount | 1 | -default -UNKNOWN_TYPE | Tribute_IncludeAmount | Do not include the amount | 0 | -default NAME_FULL | Tribute_NotifyName | Name: | | -default EMAIL_ADDRESS | Tribute_Email | Email: | | -default ADDRESS_HOME_LINE1 | Tribute_NotifyAddr | Street: | | -default @@ -36,7 +27,6 @@ CREDIT_CARD_VERIFICATION_CODE | csc | CSC Number * What Is This? | | cctype_2 CREDIT_CARD_EXP_MONTH | ExpMon | Month | 1 | cctype_2 CREDIT_CARD_EXP_4_DIGIT_YEAR | ExpYear | Year | 11 | cctype_2 -UNKNOWN_TYPE | SameAddress | Same As Contact Address | checkbox | f1_1 ADDRESS_HOME_LINE1 | CC_Addr | Address 1 * | | CC_Addr_3 ADDRESS_HOME_LINE2 | CC_Addr2 | Address 2 | | CC_Addr_3 ADDRESS_HOME_CITY | CC_City | City / State / Zip * | | CC_Addr_3
diff --git a/components/test/data/autofill/heuristics/output/085_crbug_98152.out b/components/test/data/autofill/heuristics/output/085_crbug_98152.out index 87a58b6..af8b2a2 100644 --- a/components/test/data/autofill/heuristics/output/085_crbug_98152.out +++ b/components/test/data/autofill/heuristics/output/085_crbug_98152.out
@@ -4,11 +4,10 @@ CREDIT_CARD_EXP_4_DIGIT_YEAR | CCForm.expirationYear | Expiration date | 2011 | CCForm.cardType_1 CREDIT_CARD_NAME_FULL | CCForm.name | Name as it appears on card | | CCForm.cardType_1 CREDIT_CARD_VERIFICATION_CODE | CCForm.cid | | | CCForm.cardType_1 -UNKNOWN_TYPE | isBillingAddress | Same as Shipping address | on | isBillingAddress_2 -COMPANY_NAME | CCForm.companyName | Company Name | | isBillingAddress_2 -ADDRESS_HOME_LINE1 | addressForm.street1 | Address Line 1 | | isBillingAddress_2 -ADDRESS_HOME_LINE2 | addressForm.street2 | Address Line 2 | | isBillingAddress_2 -ADDRESS_HOME_CITY | addressForm.city | City | | isBillingAddress_2 -ADDRESS_HOME_STATE | addressForm.state | State | | isBillingAddress_2 -ADDRESS_HOME_ZIP | addressForm.zip | Zip Code | | isBillingAddress_2 -PHONE_HOME_CITY_AND_NUMBER | addressForm.phone | Phone Number | | isBillingAddress_2 +COMPANY_NAME | CCForm.companyName | Company Name | | CCForm.companyName_2 +ADDRESS_HOME_LINE1 | addressForm.street1 | Address Line 1 | | CCForm.companyName_2 +ADDRESS_HOME_LINE2 | addressForm.street2 | Address Line 2 | | CCForm.companyName_2 +ADDRESS_HOME_CITY | addressForm.city | City | | CCForm.companyName_2 +ADDRESS_HOME_STATE | addressForm.state | State | | CCForm.companyName_2 +ADDRESS_HOME_ZIP | addressForm.zip | Zip Code | | CCForm.companyName_2 +PHONE_HOME_CITY_AND_NUMBER | addressForm.phone | Phone Number | | CCForm.companyName_2
diff --git a/components/test/data/autofill/heuristics/output/086_crbug_98269.out b/components/test/data/autofill/heuristics/output/086_crbug_98269.out index 748112e..62ee9e4a 100644 --- a/components/test/data/autofill/heuristics/output/086_crbug_98269.out +++ b/components/test/data/autofill/heuristics/output/086_crbug_98269.out
@@ -1,19 +1,14 @@ -UNKNOWN_TYPE | PaymentMethod | BL | BL | PaymentMethod_1 -PHONE_HOME_CITY_CODE_WITH_TRUNK_PREFIX | txtHomePhone1 | * Home Phone: | | PaymentMethod_1 -PHONE_HOME_NUMBER_PREFIX | txtHomePhone2 | * Home Phone: | | PaymentMethod_1 -PHONE_HOME_NUMBER_SUFFIX | txtHomePhone3 | * Home Phone: | | PaymentMethod_1 -EMAIL_ADDRESS | txtEmail | * Email Address: | | PaymentMethod_1 -UNKNOWN_TYPE | txtSSN | XXX-XX- | | PaymentMethod_1 -UNKNOWN_TYPE | selBirthMonth | * Date of Birth: | Month | PaymentMethod_1 -UNKNOWN_TYPE | selBirthDate | * Date of Birth: | Date | PaymentMethod_1 -UNKNOWN_TYPE | selBirthYear | * Date of Birth: | Year | PaymentMethod_1 -UNKNOWN_TYPE | chkTAC | I agree to have the | on | PaymentMethod_1 -UNKNOWN_TYPE | PaymentMethod | CK | CK | PaymentMethod_1 -UNKNOWN_TYPE | PaymentMethod | AddNew | AddNew | PaymentMethod_1 +PHONE_HOME_CITY_CODE_WITH_TRUNK_PREFIX | txtHomePhone1 | * Home Phone: | | txtHomePhone1_1 +PHONE_HOME_NUMBER_PREFIX | txtHomePhone2 | * Home Phone: | | txtHomePhone1_1 +PHONE_HOME_NUMBER_SUFFIX | txtHomePhone3 | * Home Phone: | | txtHomePhone1_1 +EMAIL_ADDRESS | txtEmail | * Email Address: | | txtHomePhone1_1 +UNKNOWN_TYPE | txtSSN | XXX-XX- | | txtHomePhone1_1 +UNKNOWN_TYPE | selBirthMonth | * Date of Birth: | Month | txtHomePhone1_1 +UNKNOWN_TYPE | selBirthDate | * Date of Birth: | Date | txtHomePhone1_1 +UNKNOWN_TYPE | selBirthYear | * Date of Birth: | Year | txtHomePhone1_1 CREDIT_CARD_TYPE | cardType | Type: | VI | cardType_2 CREDIT_CARD_NUMBER | CreditCardNumber | Number: | | cardType_2 CREDIT_CARD_EXP_DATE_4_DIGIT_YEAR | CreditCardExpYYMM | Expiration: | 092011 | cardType_2 CREDIT_CARD_VERIFICATION_CODE | CVV | Security Code: | | cardType_2 -UNKNOWN_TYPE | useGiftCard | Use a Gift Card. | on | PaymentMethod_1 -UNKNOWN_TYPE | GiftCardNum | Card Number: | | PaymentMethod_1 -UNKNOWN_TYPE | GiftCardPIN | Security ID Number: | | PaymentMethod_1 +UNKNOWN_TYPE | GiftCardNum | Card Number: | | txtHomePhone1_1 +UNKNOWN_TYPE | GiftCardPIN | Security ID Number: | | txtHomePhone1_1
diff --git a/components/test/data/autofill/heuristics/output/087_crbug_98286.out b/components/test/data/autofill/heuristics/output/087_crbug_98286.out index 5637a64..08f4ea2 100644 --- a/components/test/data/autofill/heuristics/output/087_crbug_98286.out +++ b/components/test/data/autofill/heuristics/output/087_crbug_98286.out
@@ -4,4 +4,3 @@ CREDIT_CARD_EXP_MONTH | drpdwnExpirationMonth | *Expiration date: | 00 | drpdwnCardTypes_1 CREDIT_CARD_EXP_4_DIGIT_YEAR | drpdwnExpirationYear | *Expiration date: | 2011 | drpdwnCardTypes_1 CREDIT_CARD_VERIFICATION_CODE | txtVisaVerificationNumber | VISA Card Verification # | | drpdwnCardTypes_1 -UNKNOWN_TYPE | chkUnreadable | I can't read the numbers | on | chkUnreadable_2
diff --git a/components/test/data/autofill/heuristics/output/089_crbug_224601.out b/components/test/data/autofill/heuristics/output/089_crbug_224601.out index b56fbe9..ab74dc4 100644 --- a/components/test/data/autofill/heuristics/output/089_crbug_224601.out +++ b/components/test/data/autofill/heuristics/output/089_crbug_224601.out
@@ -4,7 +4,6 @@ UNKNOWN_TYPE | JP1$txtDepartureDate | Out date | 18/04/2013 | txtSearch_1 UNKNOWN_TYPE | JP1$ddlDepartureHour | Hour | 15 | txtSearch_1 UNKNOWN_TYPE | JP1$ddlDepartureMin | Min | 00 | txtSearch_1 -UNKNOWN_TYPE | JP1$chkReturn | Return ticket? | on | txtSearch_1 UNKNOWN_TYPE | JP1$txtReturnDate | Return date | 18/04/2013 | -default UNKNOWN_TYPE | JP1$ddlReturnHour | Hour | 16 | -default UNKNOWN_TYPE | JP1$ddlReturnMin | Min | 00 | -default @@ -47,8 +46,5 @@ UNKNOWN_TYPE | Register1$txtAddress5 | Address Line 5: | | txtSearch_1 ADDRESS_HOME_ZIP | Register1$txtPostCode | Post Code: * | | txtSearch_1 ADDRESS_HOME_COUNTRY | Register1$ddlCountry | Country: * | Z0 | txtSearch_1 -UNKNOWN_TYPE | Register1$termsandconditions$chkTerms | I agree to the terms and conditions * | on | txtSearch_1 -UNKNOWN_TYPE | Register1$chkPrivacy1984 | Please tick here if you DO NOT wish to receive information and exclusive offers from South West Trains | on | txtSearch_1 -UNKNOWN_TYPE | Register1$chkPrivacy2003 | Please tick here if you DO wish to receive information and exclusive offers from our carefully selected partners | on | txtSearch_1 UNKNOWN_TYPE | Register1$txtPassword | Password: * | | txtSearch_1 UNKNOWN_TYPE | Register1$txtConfirmPassword | Confirm Password: * | | txtSearch_1
diff --git a/components/test/data/autofill/heuristics/output/092_checkout_alaskaair.com.out b/components/test/data/autofill/heuristics/output/092_checkout_alaskaair.com.out index c5c4e7b..a45b2a9c 100644 --- a/components/test/data/autofill/heuristics/output/092_checkout_alaskaair.com.out +++ b/components/test/data/autofill/heuristics/output/092_checkout_alaskaair.com.out
@@ -1,28 +1,22 @@ -UNKNOWN_TYPE | MyWalletInformation.UseMyWalletFunds | Use My Wallet Funds | true | -default -UNKNOWN_TYPE | GiftCardsAndCertificatesInformation.UseGiftCardsOrCertificates | Use Certificates or Gift Cards (not deposited in a My Wallet account) | true | -default -UNKNOWN_TYPE | CreditCardInformation.BillingCreditCardEntry.CardTypes_Selected | Card Type* | CreditCard | CreditCardInformation.BillingCreditCardEntry.CardTypes_Selected_1 -UNKNOWN_TYPE | CreditCardInformation.BillingCreditCardEntry.CardTypes_Selected | Alaska Airlines Commercial Account | AS | CreditCardInformation.BillingCreditCardEntry.CardTypes_Selected_1 -CREDIT_CARD_NUMBER | CreditCardInformation.BillingCreditCardEntry.CardNumber | Card Number | | CreditCardInformation.BillingCreditCardEntry.CardNumber_2 -CREDIT_CARD_EXP_MONTH | CreditCardInformation.BillingCreditCardEntry.ExpirationMonths_Selected | Expiration* | | CreditCardInformation.BillingCreditCardEntry.CardNumber_2 -CREDIT_CARD_EXP_4_DIGIT_YEAR | CreditCardInformation.BillingCreditCardEntry.ExpirationYears_Selected | Expiration* | | CreditCardInformation.BillingCreditCardEntry.CardNumber_2 -CREDIT_CARD_NAME_FULL | CreditCardInformation.BillingCreditCardEntry.CardPersonName | Name on Card | | CreditCardInformation.BillingCreditCardEntry.CardNumber_2 -ADDRESS_HOME_COUNTRY | CreditCardInformation.BillingAddressEntry.Countries_Selected | Country | US | CreditCardInformation.BillingCreditCardEntry.CardTypes_Selected_1 -ADDRESS_HOME_LINE1 | CreditCardInformation.BillingAddressEntry.Address1 | Address | | CreditCardInformation.BillingCreditCardEntry.CardTypes_Selected_1 -ADDRESS_HOME_LINE2 | CreditCardInformation.BillingAddressEntry.Address2 | Address Line 2 | | CreditCardInformation.BillingCreditCardEntry.CardTypes_Selected_1 -ADDRESS_HOME_CITY | CreditCardInformation.BillingAddressEntry.City | City | | CreditCardInformation.BillingCreditCardEntry.CardTypes_Selected_1 -ADDRESS_HOME_STATE | CreditCardInformation.BillingAddressEntry.USStates_Selected | State/Province | | CreditCardInformation.BillingCreditCardEntry.CardTypes_Selected_1 -ADDRESS_HOME_STATE | CreditCardInformation.BillingAddressEntry.CAStates_Selected | State/Province | | CreditCardInformation.BillingCreditCardEntry.CardTypes_Selected_1 -ADDRESS_HOME_STATE | CreditCardInformation.BillingAddressEntry.MXStates_Selected | State/Province | | CreditCardInformation.BillingCreditCardEntry.CardTypes_Selected_1 +CREDIT_CARD_NUMBER | CreditCardInformation.BillingCreditCardEntry.CardNumber | Card Number | | CreditCardInformation.BillingCreditCardEntry.CardNumber_1 +CREDIT_CARD_EXP_MONTH | CreditCardInformation.BillingCreditCardEntry.ExpirationMonths_Selected | Expiration* | | CreditCardInformation.BillingCreditCardEntry.CardNumber_1 +CREDIT_CARD_EXP_4_DIGIT_YEAR | CreditCardInformation.BillingCreditCardEntry.ExpirationYears_Selected | Expiration* | | CreditCardInformation.BillingCreditCardEntry.CardNumber_1 +CREDIT_CARD_NAME_FULL | CreditCardInformation.BillingCreditCardEntry.CardPersonName | Name on Card | | CreditCardInformation.BillingCreditCardEntry.CardNumber_1 +ADDRESS_HOME_COUNTRY | CreditCardInformation.BillingAddressEntry.Countries_Selected | Country | US | CreditCardInformation.BillingAddressEntry.Countries_Selected_2 +ADDRESS_HOME_LINE1 | CreditCardInformation.BillingAddressEntry.Address1 | Address | | CreditCardInformation.BillingAddressEntry.Countries_Selected_2 +ADDRESS_HOME_LINE2 | CreditCardInformation.BillingAddressEntry.Address2 | Address Line 2 | | CreditCardInformation.BillingAddressEntry.Countries_Selected_2 +ADDRESS_HOME_CITY | CreditCardInformation.BillingAddressEntry.City | City | | CreditCardInformation.BillingAddressEntry.Countries_Selected_2 +ADDRESS_HOME_STATE | CreditCardInformation.BillingAddressEntry.USStates_Selected | State/Province | | CreditCardInformation.BillingAddressEntry.Countries_Selected_2 +ADDRESS_HOME_STATE | CreditCardInformation.BillingAddressEntry.CAStates_Selected | State/Province | | CreditCardInformation.BillingAddressEntry.Countries_Selected_2 +ADDRESS_HOME_STATE | CreditCardInformation.BillingAddressEntry.MXStates_Selected | State/Province | | CreditCardInformation.BillingAddressEntry.Countries_Selected_2 UNKNOWN_TYPE | CreditCardInformation.BillingAddressEntry.OtherStates_Selected | State/Province | | -default -ADDRESS_HOME_ZIP | CreditCardInformation.BillingAddressEntry.PostalCode | Zip/Postal Code | | CreditCardInformation.BillingCreditCardEntry.CardTypes_Selected_1 -PHONE_HOME_COUNTRY_CODE | CreditCardInformation.BillingPhoneNumberEntry.CountryCode_Selected | Country Code | 1 | CreditCardInformation.BillingCreditCardEntry.CardTypes_Selected_1 -PHONE_HOME_CITY_AND_NUMBER_WITHOUT_TRUNK_PREFIX | CreditCardInformation.BillingPhoneNumberEntry.Number | Phone (with area code) | | CreditCardInformation.BillingCreditCardEntry.CardTypes_Selected_1 -UNKNOWN_TYPE | TripInsurance.QuoteID | Yes, add trip protection for a total of $21.00. | 657566350481481886 | -default -UNKNOWN_TYPE | TripInsurance.QuoteID | No, I choose not to protect my purchase. | 657566350481481887 | -default -CREDIT_CARD_NUMBER | AncillaryCreditCardInformation.BillingCreditCardEntry.CardNumber | Card Number | | CreditCardInformation.BillingCreditCardEntry.CardNumber_2 -CREDIT_CARD_EXP_MONTH | AncillaryCreditCardInformation.BillingCreditCardEntry.ExpirationMonths_Selected | Expiration* | | CreditCardInformation.BillingCreditCardEntry.CardNumber_2 -CREDIT_CARD_EXP_4_DIGIT_YEAR | AncillaryCreditCardInformation.BillingCreditCardEntry.ExpirationYears_Selected | Expiration* | | CreditCardInformation.BillingCreditCardEntry.CardNumber_2 -CREDIT_CARD_NAME_FULL | AncillaryCreditCardInformation.BillingCreditCardEntry.CardPersonName | Name on Card | | CreditCardInformation.BillingCreditCardEntry.CardNumber_2 +ADDRESS_HOME_ZIP | CreditCardInformation.BillingAddressEntry.PostalCode | Zip/Postal Code | | CreditCardInformation.BillingAddressEntry.Countries_Selected_2 +PHONE_HOME_COUNTRY_CODE | CreditCardInformation.BillingPhoneNumberEntry.CountryCode_Selected | Country Code | 1 | CreditCardInformation.BillingAddressEntry.Countries_Selected_2 +PHONE_HOME_CITY_AND_NUMBER_WITHOUT_TRUNK_PREFIX | CreditCardInformation.BillingPhoneNumberEntry.Number | Phone (with area code) | | CreditCardInformation.BillingAddressEntry.Countries_Selected_2 +CREDIT_CARD_NUMBER | AncillaryCreditCardInformation.BillingCreditCardEntry.CardNumber | Card Number | | CreditCardInformation.BillingCreditCardEntry.CardNumber_1 +CREDIT_CARD_EXP_MONTH | AncillaryCreditCardInformation.BillingCreditCardEntry.ExpirationMonths_Selected | Expiration* | | CreditCardInformation.BillingCreditCardEntry.CardNumber_1 +CREDIT_CARD_EXP_4_DIGIT_YEAR | AncillaryCreditCardInformation.BillingCreditCardEntry.ExpirationYears_Selected | Expiration* | | CreditCardInformation.BillingCreditCardEntry.CardNumber_1 +CREDIT_CARD_NAME_FULL | AncillaryCreditCardInformation.BillingCreditCardEntry.CardPersonName | Name on Card | | CreditCardInformation.BillingCreditCardEntry.CardNumber_1 ADDRESS_HOME_COUNTRY | AncillaryCreditCardInformation.BillingAddressEntry.Countries_Selected | Country | US | -default ADDRESS_HOME_LINE1 | AncillaryCreditCardInformation.BillingAddressEntry.Address1 | Address | | -default ADDRESS_HOME_LINE2 | AncillaryCreditCardInformation.BillingAddressEntry.Address2 | Address Line 2 | | -default @@ -38,4 +32,3 @@ UNKNOWN_TYPE | UserId | User ID or MP # | | UserId_1 UNKNOWN_TYPE | Password | Password | | UserId_1 UNKNOWN_TYPE | JumpToSelection | Jump To Section | https://www.alaskaair.com/www2/ssl/myalaskaair/myalaskaair.aspx | UserId_1 -UNKNOWN_TYPE | RememberMe | Remember my User ID on this computer. | true | UserId_1
diff --git a/components/test/data/autofill/heuristics/output/094_checkout_staples.com.out b/components/test/data/autofill/heuristics/output/094_checkout_staples.com.out index e84c278..c50f1af 100644 --- a/components/test/data/autofill/heuristics/output/094_checkout_staples.com.out +++ b/components/test/data/autofill/heuristics/output/094_checkout_staples.com.out
@@ -1,5 +1,4 @@ EMAIL_ADDRESS | emailAddress | * Indicates a required field | | emailAddress_1 -UNKNOWN_TYPE | emailPreference | Yes, I would like to receive emails about special money-saving offers from Staples. | emailYes | emailAddress_1 NAME_FIRST | sFirstName | First Name * | | sFirstName_1 NAME_LAST | sLastName | Last Name * | | sFirstName_1
diff --git a/components/test/data/autofill/heuristics/output/096_llbean.out b/components/test/data/autofill/heuristics/output/096_llbean.out index 506b56e..772034c 100644 --- a/components/test/data/autofill/heuristics/output/096_llbean.out +++ b/components/test/data/autofill/heuristics/output/096_llbean.out
@@ -2,16 +2,11 @@ NAME_FIRST | firstName | First Name | | personTitle_1 NAME_MIDDLE | middleName | Middle Name(optional) | | personTitle_1 NAME_LAST | lastName | Last Name | | personTitle_1 -UNKNOWN_TYPE | gender | Male | M | personTitle_1 -UNKNOWN_TYPE | gender | Female | F | personTitle_1 ADDRESS_HOME_COUNTRY | country | Country | USA | personTitle_1 ADDRESS_HOME_ZIP | JPNPostal | Postal Code | | -default UNKNOWN_TYPE | JPNPrefecture | Prefecture | | -default ADDRESS_HOME_CITY | JPNCity | City | | -default COMPANY_NAME | organizationName | Business, Attention, or In Care of Name (optional) | | -default -UNKNOWN_TYPE | orgNameInd | Business | BUS | -default -UNKNOWN_TYPE | orgNameInd | In care of | C/O | -default -UNKNOWN_TYPE | orgNameInd | Attention | ATTN | -default ADDRESS_HOME_LINE1 | address1 | Address | | personTitle_1 ADDRESS_HOME_LINE2 | address2 | Address Line 2 (optional) | | personTitle_1 ADDRESS_HOME_LINE3 | address3 | Address Line 3 (optional) | | -default @@ -27,12 +22,4 @@ PHONE_HOME_CITY_AND_NUMBER | phone1 | Daytime Phone Number (optional) | | personTitle_1 PHONE_HOME_CITY_AND_NUMBER | intlPhone1Prefix | International Country Code | | -default PHONE_HOME_CITY_AND_NUMBER | intlPhone1 | Daytime Phone Number | | -default -UNKNOWN_TYPE | phone1_Loc | Home | H | personTitle_1 -UNKNOWN_TYPE | phone1_Loc | Work | B | personTitle_1 -UNKNOWN_TYPE | phone1_Loc | Mobile | M | personTitle_1 -UNKNOWN_TYPE | ckoutShippingCheck | Include phone number | ckoutShippingCheck | personTitle_1 -UNKNOWN_TYPE | shipModeId | Fast | 10 | personTitle_1 -UNKNOWN_TYPE | shipModeId | Slow | 11 | personTitle_1 -UNKNOWN_TYPE | shipLiability | Liability | on | personTitle_1 -UNKNOWN_TYPE | shipAddGiftMsg | Message | addGiftMsg | personTitle_1 UNKNOWN_TYPE | giftMsg | Message | | -default
diff --git a/components/test/data/autofill/heuristics/output/097_register_alaskaair.com.out b/components/test/data/autofill/heuristics/output/097_register_alaskaair.com.out index c402bd7e..38e96096 100644 --- a/components/test/data/autofill/heuristics/output/097_register_alaskaair.com.out +++ b/components/test/data/autofill/heuristics/output/097_register_alaskaair.com.out
@@ -1,15 +1,13 @@ -UNKNOWN_TYPE | FormUserControl$_mileagePlanNumber$_mileagePlanChoice | Mileage Plan™ Number * | _needMileagePlanNbr | FormUserControl$_mileagePlanNumber$_mileagePlanChoice_1 -UNKNOWN_TYPE | FormUserControl$_mileagePlanNumber$_mileagePlanChoice | I have an Alaska Airlines Mileage Plan Number | _haveMileagePlanNbr | FormUserControl$_mileagePlanNumber$_mileagePlanChoice_1 -UNKNOWN_TYPE | FormUserControl$_mileagePlanNumber$_loyaltyNumber$_loyaltyNumber | Mileage Plan Number | | FormUserControl$_mileagePlanNumber$_mileagePlanChoice_1 -NAME_FIRST | FormUserControl$_personalIdentification$_firstName$_name | Legal First Name* | | FormUserControl$_mileagePlanNumber$_mileagePlanChoice_1 -UNKNOWN_TYPE | FormUserControl$_personalIdentification$_title$_title | Title | | FormUserControl$_mileagePlanNumber$_mileagePlanChoice_1 -NAME_MIDDLE | FormUserControl$_personalIdentification$_middleName$_middleName | Middle Name (if on ID) | | FormUserControl$_mileagePlanNumber$_mileagePlanChoice_1 -NAME_LAST | FormUserControl$_personalIdentification$_lastName$_name | Legal Last Name* | | FormUserControl$_mileagePlanNumber$_mileagePlanChoice_1 -UNKNOWN_TYPE | FormUserControl$_personalIdentification$_suffix$_suffix | Suffix | | FormUserControl$_mileagePlanNumber$_mileagePlanChoice_1 -UNKNOWN_TYPE | FormUserControl$_personalIdentification$_birthDate$_birthDate$_month | Month | | FormUserControl$_mileagePlanNumber$_mileagePlanChoice_1 -UNKNOWN_TYPE | FormUserControl$_personalIdentification$_birthDate$_birthDate$_day | Day | | FormUserControl$_mileagePlanNumber$_mileagePlanChoice_1 -UNKNOWN_TYPE | FormUserControl$_personalIdentification$_birthDate$_birthDate$_yearTextBox | Year | Year | FormUserControl$_mileagePlanNumber$_mileagePlanChoice_1 -UNKNOWN_TYPE | FormUserControl$_personalIdentification$_gender$_gender | Gender* | | FormUserControl$_mileagePlanNumber$_mileagePlanChoice_1 +UNKNOWN_TYPE | FormUserControl$_mileagePlanNumber$_loyaltyNumber$_loyaltyNumber | Mileage Plan Number | | FormUserControl$_mileagePlanNumber$_loyaltyNumber$_loyaltyNumber_1 +NAME_FIRST | FormUserControl$_personalIdentification$_firstName$_name | Legal First Name* | | FormUserControl$_mileagePlanNumber$_loyaltyNumber$_loyaltyNumber_1 +UNKNOWN_TYPE | FormUserControl$_personalIdentification$_title$_title | Title | | FormUserControl$_mileagePlanNumber$_loyaltyNumber$_loyaltyNumber_1 +NAME_MIDDLE | FormUserControl$_personalIdentification$_middleName$_middleName | Middle Name (if on ID) | | FormUserControl$_mileagePlanNumber$_loyaltyNumber$_loyaltyNumber_1 +NAME_LAST | FormUserControl$_personalIdentification$_lastName$_name | Legal Last Name* | | FormUserControl$_mileagePlanNumber$_loyaltyNumber$_loyaltyNumber_1 +UNKNOWN_TYPE | FormUserControl$_personalIdentification$_suffix$_suffix | Suffix | | FormUserControl$_mileagePlanNumber$_loyaltyNumber$_loyaltyNumber_1 +UNKNOWN_TYPE | FormUserControl$_personalIdentification$_birthDate$_birthDate$_month | Month | | FormUserControl$_mileagePlanNumber$_loyaltyNumber$_loyaltyNumber_1 +UNKNOWN_TYPE | FormUserControl$_personalIdentification$_birthDate$_birthDate$_day | Day | | FormUserControl$_mileagePlanNumber$_loyaltyNumber$_loyaltyNumber_1 +UNKNOWN_TYPE | FormUserControl$_personalIdentification$_birthDate$_birthDate$_yearTextBox | Year | Year | FormUserControl$_mileagePlanNumber$_loyaltyNumber$_loyaltyNumber_1 +UNKNOWN_TYPE | FormUserControl$_personalIdentification$_gender$_gender | Gender* | | FormUserControl$_mileagePlanNumber$_loyaltyNumber$_loyaltyNumber_1 NAME_FIRST | FormUserControl$_guardianInformation$_firstName$_name | First Name* | | -default NAME_LAST | FormUserControl$_guardianInformation$_lastName$_name | Last Name* | | FormUserControl$_guardianInformation$_lastName$_name_2 EMAIL_ADDRESS | FormUserControl$_guardianInformation$_email$_emailAddressTextBox | Email Address* | | FormUserControl$_guardianInformation$_lastName$_name_2 @@ -32,8 +30,4 @@ UNKNOWN_TYPE | FormUserControl$_userIdPassword$_reEnterPassword$_password | Re-enter Password* | | FormUserControl$_contactInformation$_emailAddress$_emailAddressTextBox_3 UNKNOWN_TYPE | FormUserControl$_secretQuestion$_question | Secret Question* | | FormUserControl$_contactInformation$_emailAddress$_emailAddressTextBox_3 UNKNOWN_TYPE | FormUserControl$_secretQuestion$_answer | Answer* | | FormUserControl$_contactInformation$_emailAddress$_emailAddressTextBox_3 -UNKNOWN_TYPE | FormUserControl$_subscriptionsOffers$_insiderNewsletter$_subscriptionCheckBox | Insider Newsletter A weekly email personalized to provide you with an insider glimpse of the best deals we have to offer - across the board. | on | FormUserControl$_contactInformation$_emailAddress$_emailAddressTextBox_3 ADDRESS_HOME_CITY | FormUserControl$_subscriptionsOffers$_primaryDepartureCity$_primaryDepartureCity$_city | Primary Departure City | | FormUserControl$_contactInformation$_emailAddress$_emailAddressTextBox_3 -UNKNOWN_TYPE | FormUserControl$_subscriptionsOffers$_eStatements$_subscriptionCheckBox | Mileage Plan E-Statements and Partner Offers A monthly recap of your Mileage Plan activity along with program news, exclusive partner offers, and countless ways to earn free travel faster. | on | FormUserControl$_contactInformation$_emailAddress$_emailAddressTextBox_3 -UNKNOWN_TYPE | FormUserControl$_subscriptionsOffers$_asQXAnnouncements$_subscriptionCheckBox | Alaska Announcements Occasional email that puts you "in the know" about sales, promotions, and other important travel information. | on | FormUserControl$_contactInformation$_emailAddress$_emailAddressTextBox_3 -UNKNOWN_TYPE | FormUserControl$_mpTermsAndConditions$_iAgree | I acknowledge that I have reviewed these Mileage Plan | on | FormUserControl$_contactInformation$_emailAddress$_emailAddressTextBox_3
diff --git a/components/test/data/autofill/heuristics/output/100_checkout_costco.com.out b/components/test/data/autofill/heuristics/output/100_checkout_costco.com.out index 8c359f7..9bf4f04d 100644 --- a/components/test/data/autofill/heuristics/output/100_checkout_costco.com.out +++ b/components/test/data/autofill/heuristics/output/100_checkout_costco.com.out
@@ -11,5 +11,3 @@ PHONE_HOME_CITY_AND_NUMBER | addressFormInlinePhoneNumber | PHONE NUMBER* | | addressFormInlineFirstName_1 EMAIL_ADDRESS | addressFormInlineEmail | EMAIL | | addressFormInlineFirstName_1 UNKNOWN_TYPE | addressFormInlineAddressNickName | ADDRESS NICKNAME* ?The Address Nickname is a short name you create to help you easily identify this address within your address book. | | addressFormInlineFirstName_1 -UNKNOWN_TYPE | saveAddressCheckboxInline | Add to address book. | on | addressFormInlineFirstName_1 -UNKNOWN_TYPE | setDefaultCheckboxInline | Save as default shipping address in Address Book | on | addressFormInlineFirstName_1
diff --git a/components/test/data/autofill/heuristics/output/102_checkout_m_macys.com.out b/components/test/data/autofill/heuristics/output/102_checkout_m_macys.com.out index cace22f..b7c5f20 100644 --- a/components/test/data/autofill/heuristics/output/102_checkout_m_macys.com.out +++ b/components/test/data/autofill/heuristics/output/102_checkout_m_macys.com.out
@@ -3,19 +3,17 @@ CREDIT_CARD_EXP_MONTH | creditCard.expMonth | expiration: | -1 | creditCard.cardType.code_1 CREDIT_CARD_EXP_4_DIGIT_YEAR | creditCard.expYear | expiration: | -1 | creditCard.cardType.code_1 CREDIT_CARD_VERIFICATION_CODE | creditCard.securityCode | please enter security code: | | creditCard.cardType.code_1 -UNKNOWN_TYPE | creditCard.billingContact.shippingAddressAsBillingAddress | Use my Shipping Address | true | creditCard.billingContact.shippingAddressAsBillingAddress_2 -NAME_FIRST | creditCard.billingContact.firstName | first name: | | creditCard.billingContact.shippingAddressAsBillingAddress_2 -NAME_LAST | creditCard.billingContact.lastName | last name: | | creditCard.billingContact.shippingAddressAsBillingAddress_2 -ADDRESS_HOME_LINE1 | creditCard.billingContact.address.addressLine1 | Address line 1: | | creditCard.billingContact.shippingAddressAsBillingAddress_2 -ADDRESS_HOME_LINE2 | creditCard.billingContact.address.addressLine2 | Address line 2: (optional) | | creditCard.billingContact.shippingAddressAsBillingAddress_2 -ADDRESS_HOME_CITY | creditCard.billingContact.address.city | City: | | creditCard.billingContact.shippingAddressAsBillingAddress_2 -ADDRESS_HOME_STATE | creditCard.billingContact.address.state | State: | -1 | creditCard.billingContact.shippingAddressAsBillingAddress_2 -ADDRESS_HOME_ZIP | creditCard.billingContact.address.postalCode | Zip code: | | creditCard.billingContact.shippingAddressAsBillingAddress_2 -EMAIL_ADDRESS | user.email | email address: | | creditCard.billingContact.shippingAddressAsBillingAddress_2 -PHONE_HOME_CITY_CODE_WITH_TRUNK_PREFIX | user.phone.areaCode | phone: | | creditCard.billingContact.shippingAddressAsBillingAddress_2 -PHONE_HOME_NUMBER | user.phone.exchangeNumber | phone: | | creditCard.billingContact.shippingAddressAsBillingAddress_2 -PHONE_HOME_CITY_AND_NUMBER | user.phone.subscriberNumber | phone: | | creditCard.billingContact.shippingAddressAsBillingAddress_2 -UNKNOWN_TYPE | user.profile.createAccount | Yes! Create an account for me at macys.com | true | creditCard.billingContact.shippingAddressAsBillingAddress_2 +NAME_FIRST | creditCard.billingContact.firstName | first name: | | creditCard.billingContact.firstName_2 +NAME_LAST | creditCard.billingContact.lastName | last name: | | creditCard.billingContact.firstName_2 +ADDRESS_HOME_LINE1 | creditCard.billingContact.address.addressLine1 | Address line 1: | | creditCard.billingContact.firstName_2 +ADDRESS_HOME_LINE2 | creditCard.billingContact.address.addressLine2 | Address line 2: (optional) | | creditCard.billingContact.firstName_2 +ADDRESS_HOME_CITY | creditCard.billingContact.address.city | City: | | creditCard.billingContact.firstName_2 +ADDRESS_HOME_STATE | creditCard.billingContact.address.state | State: | -1 | creditCard.billingContact.firstName_2 +ADDRESS_HOME_ZIP | creditCard.billingContact.address.postalCode | Zip code: | | creditCard.billingContact.firstName_2 +EMAIL_ADDRESS | user.email | email address: | | creditCard.billingContact.firstName_2 +PHONE_HOME_CITY_CODE_WITH_TRUNK_PREFIX | user.phone.areaCode | phone: | | creditCard.billingContact.firstName_2 +PHONE_HOME_NUMBER | user.phone.exchangeNumber | phone: | | creditCard.billingContact.firstName_2 +PHONE_HOME_CITY_AND_NUMBER | user.phone.subscriberNumber | phone: | | creditCard.billingContact.firstName_2 EMAIL_ADDRESS | user.profile.email | email address: | | user.profile.email_3 EMAIL_ADDRESS | user.profile.confirmEmail | Confirm email address: | | user.profile.email_3 UNKNOWN_TYPE | user.profile.password | Password: | | user.profile.email_3 @@ -23,5 +21,3 @@ UNKNOWN_TYPE | user.profile.month | Birthdate: | -1 | user.profile.email_3 UNKNOWN_TYPE | user.profile.date | Birthdate: | -1 | user.profile.email_3 UNKNOWN_TYPE | user.profile.year | Birthdate: | -1 | user.profile.email_3 -UNKNOWN_TYPE | user.profile.subscriptionInfo.emailSalesEvents | Yes, send me email updates from Macy’s.com about the latest trends, products and promotions online and in-store. | true | user.profile.email_3 -UNKNOWN_TYPE | user.savePaymentDetails | Yes, save payment information to my Macy's profile. | true | user.profile.email_3
diff --git a/components/test/data/autofill/heuristics/output/103_checkout_peapod.com.out b/components/test/data/autofill/heuristics/output/103_checkout_peapod.com.out index 88fe8fc..77b1470 100644 --- a/components/test/data/autofill/heuristics/output/103_checkout_peapod.com.out +++ b/components/test/data/autofill/heuristics/output/103_checkout_peapod.com.out
@@ -1,15 +1,14 @@ -UNKNOWN_TYPE | | My billing address and delivery address are the same. | on | _1 -NAME_HONORIFIC_PREFIX | billTitle | Title: | Ms. | _1 -NAME_FIRST | billFirstName | Shorty | Shorty | _1 -NAME_LAST | billLastName | Last Name: | Nge | _1 -ADDRESS_HOME_LINE1 | billAddressLine1 | Street Address: | 340 Main | _1 -ADDRESS_HOME_LINE2 | billAddressLine2 | Address Line 2: | Apt 2 | _1 -ADDRESS_HOME_CITY | billCity | City: | Agawam | _1 -ADDRESS_HOME_STATE | billState | MA | MA | _1 -ADDRESS_HOME_ZIP | billZip | Zip Code: | 01001 | _1 -PHONE_HOME_CITY_CODE_WITH_TRUNK_PREFIX | HomeArea | Contact Phone: | 310 | _1 -PHONE_HOME_NUMBER_PREFIX | HomePrefix | Contact Phone: | 555 | _1 -PHONE_HOME_NUMBER_SUFFIX | HomeSuffix | Contact Phone: | 1212 | _1 +NAME_HONORIFIC_PREFIX | billTitle | Title: | Ms. | billTitle_1 +NAME_FIRST | billFirstName | Shorty | Shorty | billTitle_1 +NAME_LAST | billLastName | Last Name: | Nge | billTitle_1 +ADDRESS_HOME_LINE1 | billAddressLine1 | Street Address: | 340 Main | billTitle_1 +ADDRESS_HOME_LINE2 | billAddressLine2 | Address Line 2: | Apt 2 | billTitle_1 +ADDRESS_HOME_CITY | billCity | City: | Agawam | billTitle_1 +ADDRESS_HOME_STATE | billState | MA | MA | billTitle_1 +ADDRESS_HOME_ZIP | billZip | Zip Code: | 01001 | billTitle_1 +PHONE_HOME_CITY_CODE_WITH_TRUNK_PREFIX | HomeArea | Contact Phone: | 310 | billTitle_1 +PHONE_HOME_NUMBER_PREFIX | HomePrefix | Contact Phone: | 555 | billTitle_1 +PHONE_HOME_NUMBER_SUFFIX | HomeSuffix | Contact Phone: | 1212 | billTitle_1 CREDIT_CARD_TYPE | /peapod/handler/iditarod/CheckoutRegistrationHandler.creditCardType | Card Type: | | /peapod/handler/iditarod/CheckoutRegistrationHandler.creditCardType_2 CREDIT_CARD_NUMBER | cardNum | | | /peapod/handler/iditarod/CheckoutRegistrationHandler.creditCardType_2 CREDIT_CARD_VERIFICATION_CODE | cid | | | /peapod/handler/iditarod/CheckoutRegistrationHandler.creditCardType_2
diff --git a/components/test/data/autofill/heuristics/output/104_checkout_m_kohls.com.out b/components/test/data/autofill/heuristics/output/104_checkout_m_kohls.com.out index 05b54477..fa6faf24 100644 --- a/components/test/data/autofill/heuristics/output/104_checkout_m_kohls.com.out +++ b/components/test/data/autofill/heuristics/output/104_checkout_m_kohls.com.out
@@ -16,4 +16,3 @@ PHONE_HOME_CITY_AND_NUMBER | billing_phonenum | Contact Phone: | | billing_firstname_1 EMAIL_ADDRESS | email_Txt | Email | | billing_firstname_1 EMAIL_ADDRESS | re_Email_Txt | Confirm Email Address | | billing_firstname_1 -UNKNOWN_TYPE | emailOutPutStatus | Yes, sign me up for Sale Alerts. (optional) | true | billing_firstname_1
diff --git a/components/test/data/autofill/heuristics/output/108_checkout_m_gap.com.out b/components/test/data/autofill/heuristics/output/108_checkout_m_gap.com.out index bffedcf..9074635 100644 --- a/components/test/data/autofill/heuristics/output/108_checkout_m_gap.com.out +++ b/components/test/data/autofill/heuristics/output/108_checkout_m_gap.com.out
@@ -35,8 +35,3 @@ ADDRESS_HOME_LINE1 | shippingAddressFieldGroup5-shippingAddressLine1 | *House Number and Street | | shippingAddressFieldGroup5-shippingLastName_5 ADDRESS_HOME_LINE2 | shippingAddressFieldGroup5-shippingAddressLine2 | Address line 2 | | shippingAddressFieldGroup5-shippingLastName_5 PHONE_HOME_CITY_AND_NUMBER | shippingDayPhone | * Day phone (incl. area code) | | shippingAddressFieldGroup5-shippingLastName_5 -UNKNOWN_TYPE | shippingBillingSameAsShipping | Use this address for my Billing information. | on | shippingAddressFieldGroup5-shippingLastName_5 -UNKNOWN_TYPE | shippingOptions | 7-9 business days - $0.00 | 7 | -default -UNKNOWN_TYPE | shippingOptions | 3-5 business days - $7.00 | 1 | shippingAddressFieldGroup5-shippingLastName_5 -UNKNOWN_TYPE | shippingOptions | 2 business days - $17.00 | 3 | shippingAddressFieldGroup5-shippingLastName_5 -UNKNOWN_TYPE | shippingOptions | 1 business day - $22.00 | 5 | shippingAddressFieldGroup5-shippingLastName_5
diff --git a/components/test/data/autofill/heuristics/output/109_checkout_m_nordstroms.com.out b/components/test/data/autofill/heuristics/output/109_checkout_m_nordstroms.com.out index 090e760..0173a9f7 100644 --- a/components/test/data/autofill/heuristics/output/109_checkout_m_nordstroms.com.out +++ b/components/test/data/autofill/heuristics/output/109_checkout_m_nordstroms.com.out
@@ -8,9 +8,6 @@ ADDRESS_HOME_STATE | | State | | _1 ADDRESS_HOME_ZIP | | ZIP Code | | _1 PHONE_HOME_CITY_AND_NUMBER | | Phone Information | | _1 -UNKNOWN_TYPE | | Use this as my billing address. | on | _1 -UNKNOWN_TYPE | multiple-address | No | false | _1 -UNKNOWN_TYPE | multiple-address | Yes | true | _1 UNKNOWN_TYPE | | Note Number | | _1 UNKNOWN_TYPE | | Access Code | | _1 @@ -19,14 +16,11 @@ UNKNOWN_TYPE | | Please do not fill out the next field, it is a security measure to insure that this page is being visited by a human. | | _1 MERCHANT_PROMO_CODE | | Enter a Promotion Code | | _1 UNKNOWN_TYPE | | Employee Number | | _1 -UNKNOWN_TYPE | discount | % | | _1 -UNKNOWN_TYPE | discount | % | | _1 UNKNOWN_TYPE | | CREDIT CARD | | _1 CREDIT_CARD_NUMBER | | Card Number Information | | _2 CREDIT_CARD_EXP_MONTH | | | | _2 CREDIT_CARD_EXP_4_DIGIT_YEAR | | | | _2 CREDIT_CARD_VERIFICATION_CODE | credit-ccv | Security Code Information | | _2 -UNKNOWN_TYPE | | Yes, I would like to save my credit card information. | on | _1 UNKNOWN_TYPE | | BILL TO | | _1 NAME_FIRST | | First Name | | _1 NAME_MIDDLE_INITIAL | | M.I. | | _1 @@ -39,4 +33,3 @@ ADDRESS_HOME_COUNTRY | | Country | 0 | _1 EMAIL_ADDRESS | | E-mail Information Information | | _1 PHONE_HOME_CITY_AND_NUMBER | | Phone Information Information | | _1 -UNKNOWN_TYPE | | Yes! Send me e-mail updates about the latest trends, products and promotions online and in store. | on | _1
diff --git a/components/test/data/autofill/heuristics/output/110_checkout_harryanddavid.com.out b/components/test/data/autofill/heuristics/output/110_checkout_harryanddavid.com.out index cfb89e0..6ac0564 100644 --- a/components/test/data/autofill/heuristics/output/110_checkout_harryanddavid.com.out +++ b/components/test/data/autofill/heuristics/output/110_checkout_harryanddavid.com.out
@@ -2,42 +2,38 @@ UNKNOWN_TYPE | GiftCardNumber | Gift Card Number | | GiftCardNumber_1 UNKNOWN_TYPE | | PIN* | Χ | -default UNKNOWN_TYPE | GiftCardPin | PIN* | | GiftCardNumber_1 -UNKNOWN_TYPE | paymentType | What is PayPal? | on | GiftCardNumber_1 -UNKNOWN_TYPE | paymentType | Card Number Expiration Date | on | GiftCardNumber_1 UNKNOWN_TYPE | | Card Number | Χ | -default CREDIT_CARD_NUMBER | clearTextAccount | Card Number | | clearTextAccount_2 CREDIT_CARD_EXP_MONTH | expire_month | Expiration Date | 01 | clearTextAccount_2 CREDIT_CARD_EXP_4_DIGIT_YEAR | expire_year | Expiration Date | 2014 | clearTextAccount_2 -UNKNOWN_TYPE | typeOfAddress | Business address | H | typeOfAddress_1 -UNKNOWN_TYPE | personTitle | Title | | typeOfAddress_1 -UNKNOWN_TYPE | recipientRelation | Relationship | 0 | typeOfAddress_1 +UNKNOWN_TYPE | personTitle | Title | | personTitle_1 +UNKNOWN_TYPE | recipientRelation | Relationship | 0 | personTitle_1 UNKNOWN_TYPE | | Company name* | Χ | -default -COMPANY_NAME | organizationName | Company name* | | typeOfAddress_1 +COMPANY_NAME | organizationName | Company name* | | personTitle_1 UNKNOWN_TYPE | | Attn. Line* | Χ | -default -UNKNOWN_TYPE | businessTitle | Attn. Line* | | typeOfAddress_1 +UNKNOWN_TYPE | businessTitle | Attn. Line* | | personTitle_1 UNKNOWN_TYPE | | First Name* | Χ | -default -NAME_FIRST | firstName | First Name* | | typeOfAddress_1 +NAME_FIRST | firstName | First Name* | | personTitle_1 UNKNOWN_TYPE | | Last Name* | Χ | -default -NAME_LAST | lastName | Last Name* | | typeOfAddress_1 +NAME_LAST | lastName | Last Name* | | personTitle_1 UNKNOWN_TYPE | | Additional info | Χ | -default -NAME_MIDDLE | middleName | Additional info | | typeOfAddress_1 +NAME_MIDDLE | middleName | Additional info | | personTitle_1 UNKNOWN_TYPE | | Street address* | Χ | -default -ADDRESS_HOME_LINE1 | address1 | Street address* | | typeOfAddress_1 +ADDRESS_HOME_LINE1 | address1 | Street address* | | personTitle_1 UNKNOWN_TYPE | | Apt.,suite, bldg. | Χ | -default -ADDRESS_HOME_LINE2 | address2 | Apt.,suite, bldg. | | typeOfAddress_1 -ADDRESS_HOME_COUNTRY | country | Country* | US | typeOfAddress_1 +ADDRESS_HOME_LINE2 | address2 | Apt.,suite, bldg. | | personTitle_1 +ADDRESS_HOME_COUNTRY | country | Country* | US | personTitle_1 UNKNOWN_TYPE | | City* | Χ | -default -ADDRESS_HOME_CITY | city | City* | | typeOfAddress_1 -ADDRESS_HOME_STATE | state | State* | AL | typeOfAddress_1 +ADDRESS_HOME_CITY | city | City* | | personTitle_1 +ADDRESS_HOME_STATE | state | State* | AL | personTitle_1 UNKNOWN_TYPE | | ZIP* | Χ | -default -ADDRESS_HOME_ZIP | zipCode | ZIP* | | typeOfAddress_1 +ADDRESS_HOME_ZIP | zipCode | ZIP* | | personTitle_1 UNKNOWN_TYPE | | Phone number | Χ | -default -PHONE_HOME_CITY_AND_NUMBER | phone1 | Phone number | | typeOfAddress_1 +PHONE_HOME_CITY_AND_NUMBER | phone1 | Phone number | | personTitle_1 UNKNOWN_TYPE | | Ext | Χ | -default -PHONE_HOME_EXTENSION | phone2 | Ext | | typeOfAddress_1 -UNKNOWN_TYPE | phone1Type | Cell phone | C | typeOfAddress_1 +PHONE_HOME_EXTENSION | phone2 | Ext | | personTitle_1 UNKNOWN_TYPE | | Email address* | Χ | -default -EMAIL_ADDRESS | email1 | Email address* | | typeOfAddress_1 +EMAIL_ADDRESS | email1 | Email address* | | personTitle_1 UNKNOWN_TYPE | | Confirm email address* | Χ | -default -EMAIL_ADDRESS | logonId | Confirm email address* | | typeOfAddress_1 +EMAIL_ADDRESS | logonId | Confirm email address* | | personTitle_1
diff --git a/components/test/data/autofill/heuristics/output/111_checkout_virgin_america.com.out b/components/test/data/autofill/heuristics/output/111_checkout_virgin_america.com.out index 2fd7529e..fa98037 100644 --- a/components/test/data/autofill/heuristics/output/111_checkout_virgin_america.com.out +++ b/components/test/data/autofill/heuristics/output/111_checkout_virgin_america.com.out
@@ -4,7 +4,4 @@ CREDIT_CARD_EXP_4_DIGIT_YEAR | expireYear | Expiration Date | | creditCard_1 CREDIT_CARD_VERIFICATION_CODE | securityNumber | CVV Number 3 or 4 digit number | | creditCard_1 UNKNOWN_TYPE | nickname | Card's nickname e.g. My Visa, Corporate card, etc | | nickname_2 -UNKNOWN_TYPE | useAddress | Use stored address | use-stored-address | nickname_2 -UNKNOWN_TYPE | stored-address | | ? | nickname_2 -UNKNOWN_TYPE | useAddress | Add new address | add-new-address | nickname_2 -UNKNOWN_TYPE | shouldSave | Save credit card info | on | nickname_2 +UNKNOWN_TYPE | stored-address | Use stored address | ? | nickname_2
diff --git a/components/test/data/autofill/heuristics/output/112_checkout_m_llbean.com.out b/components/test/data/autofill/heuristics/output/112_checkout_m_llbean.com.out index b335d33..a522de03 100644 --- a/components/test/data/autofill/heuristics/output/112_checkout_m_llbean.com.out +++ b/components/test/data/autofill/heuristics/output/112_checkout_m_llbean.com.out
@@ -2,8 +2,6 @@ NAME_FIRST | _1_firstName | First Name | | _1_personTitle_1 NAME_MIDDLE | _1_middleName | Middle Name(optional) | | _1_personTitle_1 NAME_LAST | _1_lastName | Last Name | | _1_personTitle_1 -UNKNOWN_TYPE | _1_gender | Male | M | _1_personTitle_1 -UNKNOWN_TYPE | _1_gender | Female | F | _1_personTitle_1 ADDRESS_HOME_COUNTRY | _1_country | Country USA & Territories | USA | _1_country_2 ADDRESS_HOME_ZIP | _1_JPNPostal | Postal Code | | _1_country_2 UNKNOWN_TYPE | _1_JPNPrefecture | Prefecture | | -default @@ -20,14 +18,7 @@ ADDRESS_HOME_CITY | _1_INTLCity | City | | -default ADDRESS_HOME_STATE | _1_INTLCounty | County | | -default ADDRESS_HOME_ZIP | _1_INTLPostal | Postal Code | | -default -UNKNOWN_TYPE | _1_addressSelect | Home | homeAddress | _1_zipCode_3 -UNKNOWN_TYPE | _1_addressSelect | Business | bizAddress | _1_zipCode_3 COMPANY_NAME | _1_otherAddress | Company Name (optional) | | _1_zipCode_3 PHONE_HOME_CITY_AND_NUMBER | _1_phone1 | Daytime Phone Number | | _1_zipCode_3 PHONE_HOME_CITY_AND_NUMBER | _1_intlPhone1Prefix | Daytime Phone Number | | _1_zipCode_3 PHONE_HOME_CITY_AND_NUMBER | _1_intlPhone1 | Daytime Phone Number | | _1_zipCode_3 -UNKNOWN_TYPE | _1_phone1_Loc | Home | H | _1_zipCode_3 -UNKNOWN_TYPE | _1_phone1_Loc | Work | B | _1_zipCode_3 -UNKNOWN_TYPE | _1_phone1_Loc | Mobile | M | _1_zipCode_3 -UNKNOWN_TYPE | _1_ckoutShippingCheck | Include my phone number on shipping forms | ckoutShippingCheck | _1_zipCode_3 -UNKNOWN_TYPE | _1_shipLiability | I accept liability for shipping these items uninsured | on | _1_zipCode_3
diff --git a/components/test/data/autofill/heuristics/output/114_cc_checkout_wayfair.com.out b/components/test/data/autofill/heuristics/output/114_cc_checkout_wayfair.com.out index bd859e5..7b49c45 100644 --- a/components/test/data/autofill/heuristics/output/114_cc_checkout_wayfair.com.out +++ b/components/test/data/autofill/heuristics/output/114_cc_checkout_wayfair.com.out
@@ -1,10 +1,7 @@ UNKNOWN_TYPE | payment_method | Select payment method | 1 | payment_method_1 -UNKNOWN_TYPE | credit_card[wf_payment_token] | Paying with multiple cards? | 0 | payment_method_1 CREDIT_CARD_NUMBER | credit_card[card_number] | Card number* | | credit_card[card_number]_2 CREDIT_CARD_EXP_MONTH | credit_card[expiration_month] | Expiration Month* | 1 | credit_card[card_number]_2 CREDIT_CARD_EXP_4_DIGIT_YEAR | credit_card[expiration_year] | Expiration Year* | 2016 | credit_card[card_number]_2 CREDIT_CARD_VERIFICATION_CODE | credit_card[security_code] | What is this? | | credit_card[card_number]_2 CREDIT_CARD_NAME_FULL | credit_card[card_holder_name] | Name on card* | Homer Simpson | credit_card[card_number]_2 -UNKNOWN_TYPE | credit_card[b_settle_immediately] | I am using a debit card one time use card | 1 | payment_method_1 -UNKNOWN_TYPE | apply_gift_card_or_promo | Apply a gift card | on | payment_method_1 UNKNOWN_TYPE | GiftPromo | Enter a gift card number | | -default
diff --git a/components/test/data/autofill/heuristics/output/115_checkout_walgreens.com.out b/components/test/data/autofill/heuristics/output/115_checkout_walgreens.com.out index ab3f028..36d8c036 100644 --- a/components/test/data/autofill/heuristics/output/115_checkout_walgreens.com.out +++ b/components/test/data/autofill/heuristics/output/115_checkout_walgreens.com.out
@@ -7,4 +7,3 @@ UNKNOWN_TYPE | addzipcodeExt | Optional | | addfirstName_1 EMAIL_ADDRESS | addemail | Email address: | | addfirstName_1 PHONE_HOME_CITY_AND_NUMBER | addphone | Phone number: | | addfirstName_1 -UNKNOWN_TYPE | signupfordeals | Get Walgreens online and in-store deals. | option2 | addfirstName_1
diff --git a/components/test/data/autofill/heuristics/output/116_cc_checkout_walgreens.com.out b/components/test/data/autofill/heuristics/output/116_cc_checkout_walgreens.com.out index 9c2b818..3d0264b 100644 --- a/components/test/data/autofill/heuristics/output/116_cc_checkout_walgreens.com.out +++ b/components/test/data/autofill/heuristics/output/116_cc_checkout_walgreens.com.out
@@ -1,16 +1,11 @@ -UNKNOWN_TYPE | paymentMethodType | CC | CC | paymentMethodType_1 -UNKNOWN_TYPE | 007 | VisaCheckout | VisaCheckout | paymentMethodType_1 -UNKNOWN_TYPE | paymentMethodType | Paypal | Paypal | paymentMethodType_1 -UNKNOWN_TYPE | paymentMethodType | BML | BML | paymentMethodType_1 -CREDIT_CARD_TYPE | addtype | | | addtype_2 -CREDIT_CARD_NUMBER | addcardnumber | Card Number: | | addtype_2 -CREDIT_CARD_EXP_MONTH | addmonth | | | addtype_2 -CREDIT_CARD_EXP_4_DIGIT_YEAR | addyear | | | addtype_2 -UNKNOWN_TYPE | preferred | Same as shipping address | on | paymentMethodType_1 -NAME_FIRST | addfirstName | First Name: | | paymentMethodType_1 -NAME_LAST | addlastName | Last Name: | | paymentMethodType_1 -ADDRESS_HOME_LINE1 | addstreet1 | Address: | | paymentMethodType_1 -ADDRESS_HOME_CITY | addcity | City: | | paymentMethodType_1 -ADDRESS_HOME_STATE | addstate | State: | | paymentMethodType_1 -ADDRESS_HOME_ZIP | addzipcode | ZIP Code: | | paymentMethodType_1 -UNKNOWN_TYPE | addzipcodeExt | Optional | | paymentMethodType_1 +CREDIT_CARD_TYPE | addtype | | | addtype_1 +CREDIT_CARD_NUMBER | addcardnumber | Card Number: | | addtype_1 +CREDIT_CARD_EXP_MONTH | addmonth | | | addtype_1 +CREDIT_CARD_EXP_4_DIGIT_YEAR | addyear | | | addtype_1 +NAME_FIRST | addfirstName | First Name: | | addfirstName_2 +NAME_LAST | addlastName | Last Name: | | addfirstName_2 +ADDRESS_HOME_LINE1 | addstreet1 | Address: | | addfirstName_2 +ADDRESS_HOME_CITY | addcity | City: | | addfirstName_2 +ADDRESS_HOME_STATE | addstate | State: | | addfirstName_2 +ADDRESS_HOME_ZIP | addzipcode | ZIP Code: | | addfirstName_2 +UNKNOWN_TYPE | addzipcodeExt | Optional | | addfirstName_2
diff --git a/components/test/data/autofill/heuristics/output/117_cc_checkout_macys.com.out b/components/test/data/autofill/heuristics/output/117_cc_checkout_macys.com.out index f87e3e8..42055c97 100644 --- a/components/test/data/autofill/heuristics/output/117_cc_checkout_macys.com.out +++ b/components/test/data/autofill/heuristics/output/117_cc_checkout_macys.com.out
@@ -1,18 +1,15 @@ -UNKNOWN_TYPE | payment.type | Credit Card | CREDITCARD | payment.type_1 -UNKNOWN_TYPE | payment.type | Paypal | PAYPAL | payment.type_1 -CREDIT_CARD_TYPE | creditCard.cardType.code | Card type | -1 | creditCard.cardType.code_2 -CREDIT_CARD_NUMBER | creditCard.cardNumber | Card number | | creditCard.cardType.code_2 -CREDIT_CARD_EXP_MONTH | creditCard.expMonth | Expiration | 01 | creditCard.cardType.code_2 -CREDIT_CARD_EXP_4_DIGIT_YEAR | creditCard.expYear | Expiration | 2016 | creditCard.cardType.code_2 -CREDIT_CARD_VERIFICATION_CODE | fake-password | Security code | | creditCard.cardType.code_2 -CREDIT_CARD_VERIFICATION_CODE | creditCard.securityCode | Security code | | creditCard.cardType.code_2 -UNKNOWN_TYPE | useMyShippingAddress | Use my shipping address | false | payment.type_1 -NAME_FIRST | billingContact.firstName | First name | | payment.type_1 -NAME_LAST | billingContact.lastName | Last name | | payment.type_1 -ADDRESS_HOME_LINE1 | billingAddress.addressLine1 | Address line 1 | | payment.type_1 -ADDRESS_HOME_LINE2 | billingAddress.addressLine2 | Address line 2 (optional) | | payment.type_1 -ADDRESS_HOME_CITY | billingAddress.city | City | | payment.type_1 -ADDRESS_HOME_STATE | billingAddress.state | State | -1 | payment.type_1 -ADDRESS_HOME_ZIP | billingAddress.zipCode | Zip code | | payment.type_1 -PHONE_HOME_CITY_AND_NUMBER | billingAddress.phone | Phone number | | payment.type_1 -EMAIL_ADDRESS | billingContact.email | Email address | | payment.type_1 +CREDIT_CARD_TYPE | creditCard.cardType.code | Card type | -1 | creditCard.cardType.code_1 +CREDIT_CARD_NUMBER | creditCard.cardNumber | Card number | | creditCard.cardType.code_1 +CREDIT_CARD_EXP_MONTH | creditCard.expMonth | Expiration | 01 | creditCard.cardType.code_1 +CREDIT_CARD_EXP_4_DIGIT_YEAR | creditCard.expYear | Expiration | 2016 | creditCard.cardType.code_1 +CREDIT_CARD_VERIFICATION_CODE | fake-password | Security code | | creditCard.cardType.code_1 +CREDIT_CARD_VERIFICATION_CODE | creditCard.securityCode | Security code | | creditCard.cardType.code_1 +NAME_FIRST | billingContact.firstName | First name | | billingContact.firstName_2 +NAME_LAST | billingContact.lastName | Last name | | billingContact.firstName_2 +ADDRESS_HOME_LINE1 | billingAddress.addressLine1 | Address line 1 | | billingContact.firstName_2 +ADDRESS_HOME_LINE2 | billingAddress.addressLine2 | Address line 2 (optional) | | billingContact.firstName_2 +ADDRESS_HOME_CITY | billingAddress.city | City | | billingContact.firstName_2 +ADDRESS_HOME_STATE | billingAddress.state | State | -1 | billingContact.firstName_2 +ADDRESS_HOME_ZIP | billingAddress.zipCode | Zip code | | billingContact.firstName_2 +PHONE_HOME_CITY_AND_NUMBER | billingAddress.phone | Phone number | | billingContact.firstName_2 +EMAIL_ADDRESS | billingContact.email | Email address | | billingContact.firstName_2
diff --git a/components/test/data/autofill/heuristics/output/118_checkout_cvs.com.out b/components/test/data/autofill/heuristics/output/118_checkout_cvs.com.out index 12b74d38..6eacc82a 100644 --- a/components/test/data/autofill/heuristics/output/118_checkout_cvs.com.out +++ b/components/test/data/autofill/heuristics/output/118_checkout_cvs.com.out
@@ -6,18 +6,11 @@ ADDRESS_HOME_STATE | stateName | State | 0 | fname_1 ADDRESS_HOME_ZIP | zip | ZIP Code | | fname_1 PHONE_HOME_CITY_AND_NUMBER | phone | Phone Number | | fname_1 -UNKNOWN_TYPE | orderConfirmation | Send order information to somebody@example.com. | on | fname_1 EMAIL_ADDRESS | cemail | Specify a different email address: | | fname_1 -UNKNOWN_TYPE | bopusPerson | ... will pick up order. | on | -default NAME_FIRST | bdfname | First Name | | -default NAME_LAST | bdlname | Last Name | | -default PHONE_HOME_CITY_AND_NUMBER | bdphone | Phone Number | | -default -UNKNOWN_TYPE | bopusSMSagree | Text me information from CVS/pharmacy, including information about my order. I Agree to the Mobile Terms of Use. | on | -default -UNKNOWN_TYPE | orderConfirmation1 | Send order information to . | on | -default EMAIL_ADDRESS | bdemail | Specify a different email address | | -default -UNKNOWN_TYPE | bopusSMSagree1 | Text me information from CVS/pharmacy�, including information about my order. | on | -default -UNKNOWN_TYPE | fsaValue | I would like to apply $0.00 of eligible items in this order to my FSA Debit Card. | on | -default -UNKNOWN_TYPE | fsaValue1 | I would like to apply $0.00 of eligible items in this order to my FSA Debit Card. | on | -default CREDIT_CARD_NUMBER | ccfield | FSA Card | | ccfield_2 PHONE_HOME_CITY_AND_NUMBER | expfield | First Name Last Name Street Address Apartment, Building, Floor, Etc City State ZIP Code Phone Number FSA Card Exp FSA Card Exp First Name Last Name Street Address Apartment, Building, Floor, Etc City State ZIP Code Phone Number Credit Card Exp Credit Card Exp First Name Last Name Street Address Apartment, Building, Floor, Etc City State ZIP Code Phone Number | | -default CREDIT_CARD_VERIFICATION_CODE | cvvfield | Specify a different email address: First Name Last Name Phone Number Specify a different email address CVV CVV CVV CVV | | ccfield_2 @@ -26,7 +19,6 @@ CREDIT_CARD_EXP_DATE_4_DIGIT_YEAR | expfield | Exp | | ccfield_2 CREDIT_CARD_VERIFICATION_CODE | cvvfield | CVV | | ccfield_2 CREDIT_CARD_VERIFICATION_CODE | cvvfield | |CVV | | ccfield_2 -UNKNOWN_TYPE | chk1 | Use ... as Billing Address. | on | -default NAME_FIRST | ffname | First Name | | -default NAME_LAST | flname | Last Name | | -default ADDRESS_HOME_LINE1 | fstreetAddr | Street Address | | -default @@ -43,13 +35,12 @@ CREDIT_CARD_EXP_DATE_4_DIGIT_YEAR | expfield | Exp | | ccfield_2 CREDIT_CARD_VERIFICATION_CODE | cvvfield | CVV | | ccfield_2 CREDIT_CARD_VERIFICATION_CODE | cvvfield | |CVV | | ccfield_2 -UNKNOWN_TYPE | billing | Use ... | on | billing_3 UNKNOWN_TYPE | newaddress | Specify a Different Billing Address Specify a Billing Address: | | -default NAME_FIRST | ufname | First Name | | -default NAME_LAST | ulname | Last Name | | -default ADDRESS_HOME_LINE1 | ustreetAddr | Street Address | | -default ADDRESS_HOME_LINE2 | ustreetAddr1 | Apartment, Building, Floor, Etc | | -default ADDRESS_HOME_CITY | ucity | City | | -default -ADDRESS_HOME_STATE | ustateName | State | 0 | billing_3 +ADDRESS_HOME_STATE | ustateName | State | 0 | -default ADDRESS_HOME_ZIP | uzip | ZIP Code | | -default PHONE_HOME_CITY_AND_NUMBER | uphone | Phone Number | | -default
diff --git a/components/test/data/autofill/heuristics/output/119_bug_465571.out b/components/test/data/autofill/heuristics/output/119_bug_465571.out index 2d131d8..1f40dad 100644 --- a/components/test/data/autofill/heuristics/output/119_bug_465571.out +++ b/components/test/data/autofill/heuristics/output/119_bug_465571.out
@@ -5,5 +5,3 @@ ADDRESS_HOME_STATE | address.state | * State: | | address.name_1 ADDRESS_HOME_ZIP | address.postalCode | * Zip: | | address.name_1 PHONE_HOME_CITY_AND_NUMBER | address.phoneNumber | * Phone Number: | | address.name_1 -UNKNOWN_TYPE | address.rememberedAsBoolean | Save this shipping address for future orders. | true | address.name_1 -UNKNOWN_TYPE | address.primary | Make this my primary shipping address. | true | -default
diff --git a/components/test/data/autofill/heuristics/output/124_bug_460832.out b/components/test/data/autofill/heuristics/output/124_bug_460832.out index b1ceb1a..4197775 100644 --- a/components/test/data/autofill/heuristics/output/124_bug_460832.out +++ b/components/test/data/autofill/heuristics/output/124_bug_460832.out
@@ -1,15 +1,10 @@ -UNKNOWN_TYPE | :27.moreactions | Event details | :27.rc | :27.moreactions_1 -UNKNOWN_TYPE | :29 | Event details | State | :27.moreactions_1 -UNKNOWN_TYPE | :2c-sd | Add end timetoTime zone | | :27.moreactions_1 -UNKNOWN_TYPE | :2c-st | Add end timetoTime zone | | :27.moreactions_1 -UNKNOWN_TYPE | :2c-et | Add end timetoTime zone | | :27.moreactions_1 -UNKNOWN_TYPE | :2c-ed | Add end timetoTime zone | | :27.moreactions_1 -UNKNOWN_TYPE | :2c-ad | All day | on | :27.moreactions_1 -UNKNOWN_TYPE | :2f.repeatcheckbox | Repeat... | on | :27.moreactions_1 +UNKNOWN_TYPE | :27.moreactions | Repeat... Event details | :27.rc | :27.moreactions_1 +UNKNOWN_TYPE | :29 | Repeat... Event details | State | :27.moreactions_1 +UNKNOWN_TYPE | :2c-sd | Add end timetoTime zoneAll day | | :27.moreactions_1 +UNKNOWN_TYPE | :2c-st | Add end timetoTime zoneAll day | | :27.moreactions_1 +UNKNOWN_TYPE | :2c-et | Add end timetoTime zoneAll day | | :27.moreactions_1 +UNKNOWN_TYPE | :2c-ed | Add end timetoTime zoneAll day | | :27.moreactions_1 UNKNOWN_TYPE | :1h.ta | Guests | Rooms, etc. | | :27.moreactions_1 -UNKNOWN_TYPE | :1kguests-modify | modify event | on | :27.moreactions_1 -UNKNOWN_TYPE | :1kguests-invite | invite others | on | :27.moreactions_1 -UNKNOWN_TYPE | :1kguests-see-guests | see guest list | on | :27.moreactions_1 UNKNOWN_TYPE | :2p | Enter a location | City | :27.moreactions_1 UNKNOWN_TYPE | :2s.pe | Add video call | | -default UNKNOWN_TYPE | :1b.calendar | Calendar | :1b.changeowner | :27.moreactions_1
diff --git a/components/test/data/autofill/heuristics/output/127_bug_463986.out b/components/test/data/autofill/heuristics/output/127_bug_463986.out index b4c860d9..4d10b1f 100644 --- a/components/test/data/autofill/heuristics/output/127_bug_463986.out +++ b/components/test/data/autofill/heuristics/output/127_bug_463986.out
@@ -54,4 +54,3 @@ UNKNOWN_TYPE | onlineTransferInstitute | Bank | ACHSECUR | -default UNKNOWN_TYPE | otAccountNumber | Account No. | | -default UNKNOWN_TYPE | otBankCode | Bank Code | | -default -UNKNOWN_TYPE | accept | I read and understood and I accept the terms in force in IKEA | on | accept_3
diff --git a/components/test/data/autofill/heuristics/output/131_bug_465587.out b/components/test/data/autofill/heuristics/output/131_bug_465587.out index 7667e7a6a..5e15d2d 100644 --- a/components/test/data/autofill/heuristics/output/131_bug_465587.out +++ b/components/test/data/autofill/heuristics/output/131_bug_465587.out
@@ -1,31 +1,20 @@ -UNKNOWN_TYPE | /bodybuilding/commerce/order/purchase/CardPaymentInfoFormHandler.billingInfo.paymentType | Credit Card | creditCard | /bodybuilding/commerce/order/purchase/CardPaymentInfoFormHandler.billingInfo.paymentType_1 -UNKNOWN_TYPE | /bodybuilding/commerce/order/purchase/CardPaymentInfoFormHandler.billingInfo.paymentType | Paypal | payPalPayment | /bodybuilding/commerce/order/purchase/CardPaymentInfoFormHandler.billingInfo.paymentType_1 -UNKNOWN_TYPE | /bodybuilding/commerce/order/purchase/CardPaymentInfoFormHandler.billingInfo.paymentType | Gift Certificate | giftCertificate | /bodybuilding/commerce/order/purchase/CardPaymentInfoFormHandler.billingInfo.paymentType_1 -CREDIT_CARD_TYPE | /bodybuilding/commerce/order/purchase/CardPaymentInfoFormHandler.billingInfo.creditCard.creditCardType | Credit Card Type | visa | /bodybuilding/commerce/order/purchase/CardPaymentInfoFormHandler.billingInfo.creditCard.creditCardType_2 -CREDIT_CARD_NAME_FULL | cardholder | Cardholder Name | | /bodybuilding/commerce/order/purchase/CardPaymentInfoFormHandler.billingInfo.creditCard.creditCardType_2 -CREDIT_CARD_NUMBER | cardnumber | Credit Card Number | | /bodybuilding/commerce/order/purchase/CardPaymentInfoFormHandler.billingInfo.creditCard.creditCardType_2 -CREDIT_CARD_EXP_MONTH | /bodybuilding/commerce/order/purchase/CardPaymentInfoFormHandler.billingInfo.creditCard.expirationMonth | Expiration Date | 01 | /bodybuilding/commerce/order/purchase/CardPaymentInfoFormHandler.billingInfo.creditCard.creditCardType_2 -CREDIT_CARD_EXP_4_DIGIT_YEAR | /bodybuilding/commerce/order/purchase/CardPaymentInfoFormHandler.billingInfo.creditCard.expirationYear | Expiration Date | 2015 | /bodybuilding/commerce/order/purchase/CardPaymentInfoFormHandler.billingInfo.creditCard.creditCardType_2 -CREDIT_CARD_VERIFICATION_CODE | secureid | CVC/CVV | | /bodybuilding/commerce/order/purchase/CardPaymentInfoFormHandler.billingInfo.creditCard.creditCardType_2 -UNKNOWN_TYPE | /bodybuilding/commerce/order/purchase/CardPaymentInfoFormHandler.billingInfo.billingSameAsShipping | true | true | /bodybuilding/commerce/order/purchase/CardPaymentInfoFormHandler.billingInfo.paymentType_1 -NAME_FIRST | /bodybuilding/commerce/order/purchase/CardPaymentInfoFormHandler.billingInfo.creditCard.billingAddress.firstName | First Name | | /bodybuilding/commerce/order/purchase/CardPaymentInfoFormHandler.billingInfo.paymentType_1 -NAME_LAST | /bodybuilding/commerce/order/purchase/CardPaymentInfoFormHandler.billingInfo.creditCard.billingAddress.lastName | Last Name | | /bodybuilding/commerce/order/purchase/CardPaymentInfoFormHandler.billingInfo.paymentType_1 -UNKNOWN_TYPE | /bodybuilding/commerce/order/purchase/CardPaymentInfoFormHandler.billingInfo.militaryAddressInd | APO FPO DPO | APO | /bodybuilding/commerce/order/purchase/CardPaymentInfoFormHandler.billingInfo.paymentType_1 -UNKNOWN_TYPE | /bodybuilding/commerce/order/purchase/CardPaymentInfoFormHandler.billingInfo.militaryAddressInd | APO | FPO | /bodybuilding/commerce/order/purchase/CardPaymentInfoFormHandler.billingInfo.paymentType_1 -UNKNOWN_TYPE | /bodybuilding/commerce/order/purchase/CardPaymentInfoFormHandler.billingInfo.militaryAddressInd | FPO | DPO | /bodybuilding/commerce/order/purchase/CardPaymentInfoFormHandler.billingInfo.paymentType_1 -ADDRESS_HOME_COUNTRY | /bodybuilding/commerce/order/purchase/CardPaymentInfoFormHandler.billingInfo.creditCard.billingAddress.country | Country | US | /bodybuilding/commerce/order/purchase/CardPaymentInfoFormHandler.billingInfo.paymentType_1 -ADDRESS_HOME_LINE1 | address1 | Address | | /bodybuilding/commerce/order/purchase/CardPaymentInfoFormHandler.billingInfo.paymentType_1 -ADDRESS_HOME_LINE2 | /bodybuilding/commerce/order/purchase/CardPaymentInfoFormHandler.billingInfo.creditCard.billingAddress.address2 | Address | | /bodybuilding/commerce/order/purchase/CardPaymentInfoFormHandler.billingInfo.paymentType_1 -ADDRESS_HOME_CITY | city | City | | /bodybuilding/commerce/order/purchase/CardPaymentInfoFormHandler.billingInfo.paymentType_1 -ADDRESS_HOME_STATE | /bodybuilding/commerce/order/purchase/CardPaymentInfoFormHandler.billingInfo.creditCard.billingAddress.state | State: | | /bodybuilding/commerce/order/purchase/CardPaymentInfoFormHandler.billingInfo.paymentType_1 -ADDRESS_HOME_STATE | /bodybuilding/commerce/order/purchase/CardPaymentInfoFormHandler.billingInfo.creditCard.billingAddress.state | State: | | /bodybuilding/commerce/order/purchase/CardPaymentInfoFormHandler.billingInfo.paymentType_1 -ADDRESS_HOME_STATE | /bodybuilding/commerce/order/purchase/CardPaymentInfoFormHandler.billingInfo.creditCard.billingAddress.state | State: | | /bodybuilding/commerce/order/purchase/CardPaymentInfoFormHandler.billingInfo.paymentType_1 -ADDRESS_HOME_STATE | /bodybuilding/commerce/order/purchase/CardPaymentInfoFormHandler.billingInfo.creditCard.billingAddress.state | State: | | /bodybuilding/commerce/order/purchase/CardPaymentInfoFormHandler.billingInfo.paymentType_1 -ADDRESS_HOME_ZIP | /bodybuilding/commerce/order/purchase/CardPaymentInfoFormHandler.billingInfo.creditCard.billingAddress.postalCode | Zip / Postal Code | | /bodybuilding/commerce/order/purchase/CardPaymentInfoFormHandler.billingInfo.paymentType_1 -PHONE_HOME_CITY_AND_NUMBER | /bodybuilding/commerce/order/purchase/CardPaymentInfoFormHandler.billingInfo.creditCard.billingAddress.phoneNumber | Phone | | /bodybuilding/commerce/order/purchase/CardPaymentInfoFormHandler.billingInfo.paymentType_1 -UNKNOWN_TYPE | /bodybuilding/commerce/cart/item/3281003514/item_qty | Optimum Nutrition Gold Standard 100% Whey Delicious Strawberry, 1 Lb. $14.98 | 1 | /bodybuilding/commerce/order/purchase/CardPaymentInfoFormHandler.billingInfo.paymentType_1 -UNKNOWN_TYPE | /bodybuilding/shipping/group/sg1157540843 | 1 Day Delivers in 1 business day $16.48 | 1 Day (Order By 3:00 p.m. PST) | /bodybuilding/commerce/order/purchase/CardPaymentInfoFormHandler.billingInfo.paymentType_1 -UNKNOWN_TYPE | /bodybuilding/shipping/group/sg1157540843 | 2 Day Delivers in 2 business days $11.98 | 2 Day | /bodybuilding/commerce/order/purchase/CardPaymentInfoFormHandler.billingInfo.paymentType_1 -UNKNOWN_TYPE | /bodybuilding/shipping/group/sg1157540843 | Standard Delivers in 2-3 business days $5.97 | Standard (3-5 Days) | /bodybuilding/commerce/order/purchase/CardPaymentInfoFormHandler.billingInfo.paymentType_1 -UNKNOWN_TYPE | /bodybuilding/commerce/order/purchase/CardPaymentInfoFormHandler.isSignupNewsletter | Orders are subject to Bodybuilding.com'sTerms of Use and Privacy Policy. | true | /bodybuilding/commerce/order/purchase/CardPaymentInfoFormHandler.billingInfo.paymentType_1 -UNKNOWN_TYPE | formhandler | Orders are subject to Bodybuilding.com'sTerms of Use and Privacy Policy. | /bodybuilding/commerce/order/purchase/CardPaymentInfoFormHandler | -default +CREDIT_CARD_TYPE | /bodybuilding/commerce/order/purchase/CardPaymentInfoFormHandler.billingInfo.creditCard.creditCardType | Credit Card Type | visa | /bodybuilding/commerce/order/purchase/CardPaymentInfoFormHandler.billingInfo.creditCard.creditCardType_1 +CREDIT_CARD_NAME_FULL | cardholder | Cardholder Name | | /bodybuilding/commerce/order/purchase/CardPaymentInfoFormHandler.billingInfo.creditCard.creditCardType_1 +CREDIT_CARD_NUMBER | cardnumber | Credit Card Number | | /bodybuilding/commerce/order/purchase/CardPaymentInfoFormHandler.billingInfo.creditCard.creditCardType_1 +CREDIT_CARD_EXP_MONTH | /bodybuilding/commerce/order/purchase/CardPaymentInfoFormHandler.billingInfo.creditCard.expirationMonth | Expiration Date | 01 | /bodybuilding/commerce/order/purchase/CardPaymentInfoFormHandler.billingInfo.creditCard.creditCardType_1 +CREDIT_CARD_EXP_4_DIGIT_YEAR | /bodybuilding/commerce/order/purchase/CardPaymentInfoFormHandler.billingInfo.creditCard.expirationYear | Expiration Date | 2015 | /bodybuilding/commerce/order/purchase/CardPaymentInfoFormHandler.billingInfo.creditCard.creditCardType_1 +CREDIT_CARD_VERIFICATION_CODE | secureid | CVC/CVV | | /bodybuilding/commerce/order/purchase/CardPaymentInfoFormHandler.billingInfo.creditCard.creditCardType_1 +NAME_FIRST | /bodybuilding/commerce/order/purchase/CardPaymentInfoFormHandler.billingInfo.creditCard.billingAddress.firstName | First Name | | /bodybuilding/commerce/order/purchase/CardPaymentInfoFormHandler.billingInfo.creditCard.billingAddress.firstName_2 +NAME_LAST | /bodybuilding/commerce/order/purchase/CardPaymentInfoFormHandler.billingInfo.creditCard.billingAddress.lastName | Last Name | | /bodybuilding/commerce/order/purchase/CardPaymentInfoFormHandler.billingInfo.creditCard.billingAddress.firstName_2 +ADDRESS_HOME_COUNTRY | /bodybuilding/commerce/order/purchase/CardPaymentInfoFormHandler.billingInfo.creditCard.billingAddress.country | Country | US | /bodybuilding/commerce/order/purchase/CardPaymentInfoFormHandler.billingInfo.creditCard.billingAddress.firstName_2 +ADDRESS_HOME_LINE1 | address1 | Address | | /bodybuilding/commerce/order/purchase/CardPaymentInfoFormHandler.billingInfo.creditCard.billingAddress.firstName_2 +ADDRESS_HOME_LINE2 | /bodybuilding/commerce/order/purchase/CardPaymentInfoFormHandler.billingInfo.creditCard.billingAddress.address2 | Address | | /bodybuilding/commerce/order/purchase/CardPaymentInfoFormHandler.billingInfo.creditCard.billingAddress.firstName_2 +ADDRESS_HOME_CITY | city | City | | /bodybuilding/commerce/order/purchase/CardPaymentInfoFormHandler.billingInfo.creditCard.billingAddress.firstName_2 +ADDRESS_HOME_STATE | /bodybuilding/commerce/order/purchase/CardPaymentInfoFormHandler.billingInfo.creditCard.billingAddress.state | State: | | /bodybuilding/commerce/order/purchase/CardPaymentInfoFormHandler.billingInfo.creditCard.billingAddress.firstName_2 +ADDRESS_HOME_STATE | /bodybuilding/commerce/order/purchase/CardPaymentInfoFormHandler.billingInfo.creditCard.billingAddress.state | State: | | /bodybuilding/commerce/order/purchase/CardPaymentInfoFormHandler.billingInfo.creditCard.billingAddress.firstName_2 +ADDRESS_HOME_STATE | /bodybuilding/commerce/order/purchase/CardPaymentInfoFormHandler.billingInfo.creditCard.billingAddress.state | State: | | /bodybuilding/commerce/order/purchase/CardPaymentInfoFormHandler.billingInfo.creditCard.billingAddress.firstName_2 +ADDRESS_HOME_STATE | /bodybuilding/commerce/order/purchase/CardPaymentInfoFormHandler.billingInfo.creditCard.billingAddress.state | State: | | /bodybuilding/commerce/order/purchase/CardPaymentInfoFormHandler.billingInfo.creditCard.billingAddress.firstName_2 +ADDRESS_HOME_ZIP | /bodybuilding/commerce/order/purchase/CardPaymentInfoFormHandler.billingInfo.creditCard.billingAddress.postalCode | Zip / Postal Code | | /bodybuilding/commerce/order/purchase/CardPaymentInfoFormHandler.billingInfo.creditCard.billingAddress.firstName_2 +PHONE_HOME_CITY_AND_NUMBER | /bodybuilding/commerce/order/purchase/CardPaymentInfoFormHandler.billingInfo.creditCard.billingAddress.phoneNumber | Phone | | /bodybuilding/commerce/order/purchase/CardPaymentInfoFormHandler.billingInfo.creditCard.billingAddress.firstName_2 +UNKNOWN_TYPE | /bodybuilding/commerce/cart/item/3281003514/item_qty | Optimum Nutrition Gold Standard 100% Whey Delicious Strawberry, 1 Lb. $14.98 | 1 | /bodybuilding/commerce/order/purchase/CardPaymentInfoFormHandler.billingInfo.creditCard.billingAddress.firstName_2 +UNKNOWN_TYPE | formhandler | Orders are subject to Bodybuilding.com'sTerms of Use and Privacy Policy. Please send me the | /bodybuilding/commerce/order/purchase/CardPaymentInfoFormHandler | -default
diff --git a/components/test/data/autofill/heuristics/output/133_bug_469472.out b/components/test/data/autofill/heuristics/output/133_bug_469472.out index fe72291..37ae9dd 100644 --- a/components/test/data/autofill/heuristics/output/133_bug_469472.out +++ b/components/test/data/autofill/heuristics/output/133_bug_469472.out
@@ -1,8 +1,4 @@ CREDIT_CARD_NUMBER | cc_number | Debit or Prepaid gift card number | | cc_number_1 -UNKNOWN_TYPE | credit_card_type | Visa | V | credit_card_type_2 -UNKNOWN_TYPE | credit_card_type | MasterCard | M | credit_card_type_2 -UNKNOWN_TYPE | credit_card_type | Discover | D | credit_card_type_2 -UNKNOWN_TYPE | credit_card_type | American Express | A | credit_card_type_2 CREDIT_CARD_EXP_MONTH | expdate_month | Month | | cc_number_1 CREDIT_CARD_EXP_2_DIGIT_YEAR | expdate_year | Year | | cc_number_1 CREDIT_CARD_VERIFICATION_CODE | cvv2_number | CSC What is this? For American Express, it's the four digits on the front of the card.For MasterCard, Visa or Discover, it's the last three digits in the signature area on the back of your card. | | cc_number_1
diff --git a/components/test/data/autofill/heuristics/output/138_cc_checkout_united.com.out b/components/test/data/autofill/heuristics/output/138_cc_checkout_united.com.out index a4b5d82..a694de3e 100644 --- a/components/test/data/autofill/heuristics/output/138_cc_checkout_united.com.out +++ b/components/test/data/autofill/heuristics/output/138_cc_checkout_united.com.out
@@ -13,7 +13,6 @@ ADDRESS_HOME_STATE | CreditCardViewModels[0].StateCode | State/province/region/county* | | -default ADDRESS_HOME_ZIP | CreditCardViewModels[0].PostalCode | ZIP/Postal code* | | -default ADDRESS_HOME_COUNTRY | CreditCardViewModels[0].CountryCode | Country* | US | -default -UNKNOWN_TYPE | FOPOption | Other forms of payment | OtherFop | FOPOption_2 CREDIT_CARD_TYPE | OtherFOPType | Form of payment | | CreditCardViewModels[0].CardTypeCode_1 CREDIT_CARD_NAME_FULL | AlipayCardHolderName | Full name as it appears on your card* | | CreditCardViewModels[0].CardTypeCode_1 ADDRESS_HOME_LINE1 | AlipayBillingAddressLine1 | Billing address line 1* | | -default @@ -21,24 +20,20 @@ ADDRESS_HOME_LINE3 | AlipayBillingAddressLine3 | Billing address line 3 (Optional) | | -default ADDRESS_HOME_CITY | AlipayCity | City/town/department* | | -default ADDRESS_HOME_ZIP | AlipayZipCode | ZIP/Postal code* | | -default -ADDRESS_HOME_COUNTRY | AlipayCountryCode | United States | US | FOPOption_2 +ADDRESS_HOME_COUNTRY | AlipayCountryCode | United States | US | -default ADDRESS_HOME_LINE1 | PayPalBillingAddress | Billing address* | | -default ADDRESS_HOME_CITY | PayPalCity | City/town/department* | | -default ADDRESS_HOME_STATE | PayPalState | State/province/region/county* | | -default ADDRESS_HOME_ZIP | PayPalZipCode | ZIP/Postal code* | | -default -ADDRESS_HOME_COUNTRY | PayPalCountryCode | United States | US | FOPOption_2 +ADDRESS_HOME_COUNTRY | PayPalCountryCode | United States | US | -default UNKNOWN_TYPE | WalletCCSecurityCode | Security code | | -default -UNKNOWN_TYPE | CashType | Western Union ($11.95 service charge per transaction) | WU | -default -UNKNOWN_TYPE | CashType | Airport Ticket Office (service charge per transaction may apply) | ATO | -default -UNKNOWN_TYPE | CashType | United Ticket Office (service charge per transaction may apply) | CTO | -default -UNKNOWN_TYPE | BmlModel.IfForSixMonthsNoPayment | Yes, I would like to make no payments for six months on orders greater than $250. | true | -default NAME_FIRST | BmlModel.FirstName | First name* | | -default NAME_LAST | BmlModel.LastName | Last name* | | -default ADDRESS_HOME_LINE1 | BmlModel.BMLAddressLine1 | Billing address* | | -default ADDRESS_HOME_CITY | BmlModel.BMLCity | City* | | -default ADDRESS_HOME_STATE | BmlModel.BMLStateCode | State/territory/province* | | -default ADDRESS_HOME_ZIP | BmlModel.BMLPostalCode | Zip/Postal code* | | -default -ADDRESS_HOME_COUNTRY | BmlModel.BMLPhoneCountryAccessCode | Phone Country | US | FOPOption_2 +ADDRESS_HOME_COUNTRY | BmlModel.BMLPhoneCountryAccessCode | Phone Country | US | -default PHONE_HOME_CITY_AND_NUMBER | BmlModel.BMLPhoneNumber | Phone number* | | -default PHONE_HOME_EXTENSION | BmlModel.BMLPhoneExtension | Extension (Optional) | | -default EMAIL_ADDRESS | BmlModel.BMLEmail | Email address* | | -default @@ -46,9 +41,7 @@ UNKNOWN_TYPE | BmlModel.DobDay | DD* | | -default UNKNOWN_TYPE | BmlModel.DobYear | YYYY* | | -default UNKNOWN_TYPE | BmlModel.SocialSN | Last 4 digits* | | -default -UNKNOWN_TYPE | BmlModel.IfBMLTermsElectronicAccepted | I agree to have the PayPal Credit terms and conditions presented electronically | true | -default -UNKNOWN_TYPE | BmlModel.IfBMLTermsAccepted | I agree to the PayPal Credit terms and conditions | true | -default UNKNOWN_TYPE | EmailAddressList | Your email address will be used to contact you about this reservation only. | | -default -EMAIL_ADDRESS | PurchaserEmailAddress | Email address* | homer.simpson.duffman@gmail.com | FOPOption_2 +EMAIL_ADDRESS | PurchaserEmailAddress | Email address* | homer.simpson.duffman@gmail.com | PurchaserEmailAddress_2 UNKNOWN_TYPE | PhoneList | | | -default -ADDRESS_HOME_COUNTRY | PhoneCountryCode | Country | US | PhoneCountryCode_3 +ADDRESS_HOME_COUNTRY | PhoneCountryCode | Country | US | PurchaserEmailAddress_2
diff --git a/components/test/data/autofill/heuristics/output/140_checkout_nike.com.out b/components/test/data/autofill/heuristics/output/140_checkout_nike.com.out index b613e85..362719b 100644 --- a/components/test/data/autofill/heuristics/output/140_checkout_nike.com.out +++ b/components/test/data/autofill/heuristics/output/140_checkout_nike.com.out
@@ -1,28 +1,19 @@ -UNKNOWN_TYPE | selectAddressType | Home/Office Address | singleAddress | selectAddressType_1 -UNKNOWN_TYPE | selectAddressType | APO/FPO | militaryAddress | selectAddressType_1 -NAME_FIRST | fname | First Name * | | selectAddressType_1 -NAME_LAST | lname | Last Name * | | selectAddressType_1 -ADDRESS_HOME_LINE1 | address1Field | Address * | | selectAddressType_1 -ADDRESS_HOME_LINE2 | address2Field | 1. SHIPPING | | selectAddressType_1 -ADDRESS_HOME_CITY | city | City * | | selectAddressType_1 -ADDRESS_HOME_STATE | singleState | State * | | selectAddressType_1 -ADDRESS_HOME_ZIP | postalCodeField | ZIP Code * | | selectAddressType_1 +NAME_FIRST | fname | First Name * | | fname_1 +NAME_LAST | lname | Last Name * | | fname_1 +ADDRESS_HOME_LINE1 | address1Field | Address * | | fname_1 +ADDRESS_HOME_LINE2 | address2Field | 1. SHIPPING | | fname_1 +ADDRESS_HOME_CITY | city | City * | | fname_1 +ADDRESS_HOME_STATE | singleState | State * | | fname_1 +ADDRESS_HOME_ZIP | postalCodeField | ZIP Code * | | fname_1 NAME_FIRST | fname | First Name * | | -default NAME_LAST | lname | Last Name * | | -default ADDRESS_HOME_LINE1 | address1Field | Address * | | -default ADDRESS_HOME_LINE2 | address2Field | Enter the military command or organization name if applicable | | -default -ADDRESS_HOME_CITY | apoCity | APO/FPO * | APO | selectAddressType_1 -ADDRESS_HOME_STATE | apoState | Region * | AA | selectAddressType_1 +ADDRESS_HOME_CITY | apoCity | APO/FPO * | APO | fname_1 +ADDRESS_HOME_STATE | apoState | Region * | AA | fname_1 ADDRESS_HOME_ZIP | postalCodeField | ZIP Code * | | -default -UNKNOWN_TYPE | shippingMethod_single | Standard (Arrives 2-4 Days) FREE | Ground Service | selectAddressType_1 -UNKNOWN_TYPE | shippingMethod_single | Standard (Arrives 2-4 Days) FREE | Two Day Air | selectAddressType_1 -UNKNOWN_TYPE | shippingMethod_single | Standard (Arrives 2-4 Days) FREE | Next Day Air | selectAddressType_1 -UNKNOWN_TYPE | becomeMember | JOIN NIKE+ FOR FREE SHIPPING ON EVERY ORDER, EVERY TIME. | on | selectAddressType_1 EMAIL_ADDRESS | reqEmail | Email * | | -default UNKNOWN_TYPE | reqPassword | Password * | | -default UNKNOWN_TYPE | monthDob | Date of Birth * | | -default UNKNOWN_TYPE | dayDob | Date of Birth * | | -default UNKNOWN_TYPE | yearDob | Date of Birth * | | -default -UNKNOWN_TYPE | gender | MALE | male | -default -UNKNOWN_TYPE | gender | FEMALE | female | -default -UNKNOWN_TYPE | receiveNikeOffers | I would like to be the first to hear about new products, innovations and exclusive offers. | on | -default
diff --git a/components/test/data/autofill/heuristics/output/142_cc_checkout_netaporter.com.out b/components/test/data/autofill/heuristics/output/142_cc_checkout_netaporter.com.out index 3bc11f8..4a634dbe 100644 --- a/components/test/data/autofill/heuristics/output/142_cc_checkout_netaporter.com.out +++ b/components/test/data/autofill/heuristics/output/142_cc_checkout_netaporter.com.out
@@ -1,9 +1,5 @@ -UNKNOWN_TYPE | cardType | Please note that the value of your order will only be charged to your card at the time of delivery | VISA | cardType_1 -UNKNOWN_TYPE | cardType | Please note that the value of your order will only be charged to your card at the time of delivery | MASTERCARD | cardType_1 -UNKNOWN_TYPE | cardType | Please note that the value of your order will only be charged to your card at the time of delivery | AMEX | cardType_1 -UNKNOWN_TYPE | cardType | Please note that the value of your order will only be charged to your card at the time of delivery | PAYPAL | cardType_1 -CREDIT_CARD_NUMBER | cardNumber | Card Number* | | cardNumber_2 -CREDIT_CARD_NAME_FULL | cardHoldersName | Name on card* | | cardNumber_2 -CREDIT_CARD_VERIFICATION_CODE | cVSNumber | Security Number* | | cardNumber_2 -CREDIT_CARD_EXP_MONTH | expiryMonth | Expiry Date* | | cardNumber_2 -CREDIT_CARD_EXP_2_DIGIT_YEAR | expiryYear | Expiry Date* | | cardNumber_2 +CREDIT_CARD_NUMBER | cardNumber | Card Number* | | cardNumber_1 +CREDIT_CARD_NAME_FULL | cardHoldersName | Name on card* | | cardNumber_1 +CREDIT_CARD_VERIFICATION_CODE | cVSNumber | Security Number* | | cardNumber_1 +CREDIT_CARD_EXP_MONTH | expiryMonth | Expiry Date* | | cardNumber_1 +CREDIT_CARD_EXP_2_DIGIT_YEAR | expiryYear | Expiry Date* | | cardNumber_1
diff --git a/components/test/data/autofill/heuristics/output/144_cc_checkout_m_jcp.com.out b/components/test/data/autofill/heuristics/output/144_cc_checkout_m_jcp.com.out index 30da0ca..2895f83 100644 --- a/components/test/data/autofill/heuristics/output/144_cc_checkout_m_jcp.com.out +++ b/components/test/data/autofill/heuristics/output/144_cc_checkout_m_jcp.com.out
@@ -2,17 +2,16 @@ CREDIT_CARD_EXP_MONTH | month-guest | Month | | card-number-guest_1 CREDIT_CARD_EXP_4_DIGIT_YEAR | year-guest | Expiration Date | | card-number-guest_1 CREDIT_CARD_VERIFICATION_CODE | c-v-v-guest | C V V | | card-number-guest_1 -UNKNOWN_TYPE | billingAsShipping | Same as shipping address | on | billingAsShipping_2 -NAME_FIRST | first-name-guest | First name | | billingAsShipping_2 -NAME_LAST | last-name-guest | Last name | | billingAsShipping_2 -ADDRESS_HOME_COUNTRY | country-guest | + Company (optional) | 1 | billingAsShipping_2 -COMPANY_NAME | company-guest | Company (Optional) | | billingAsShipping_2 -ADDRESS_HOME_LINE1 | address-1-guest | Street address | | billingAsShipping_2 -ADDRESS_HOME_LINE2 | address-2-guest | Street address 2 (optional) | | billingAsShipping_2 -UNKNOWN_TYPE | military-address-type | | | billingAsShipping_2 -ADDRESS_HOME_ZIP | zip-guest | Zip | | billingAsShipping_2 -ADDRESS_HOME_STATE | state-guest | | | billingAsShipping_2 -UNKNOWN_TYPE | military-state | | | billingAsShipping_2 -ADDRESS_HOME_CITY | city-guest | City | | billingAsShipping_2 -EMAIL_ADDRESS | email | Email address | | billingAsShipping_2 -PHONE_HOME_CITY_AND_NUMBER | phone-guest | Phone number | | billingAsShipping_2 +NAME_FIRST | first-name-guest | First name | | first-name-guest_2 +NAME_LAST | last-name-guest | Last name | | first-name-guest_2 +ADDRESS_HOME_COUNTRY | country-guest | + Company (optional) | 1 | first-name-guest_2 +COMPANY_NAME | company-guest | Company (Optional) | | first-name-guest_2 +ADDRESS_HOME_LINE1 | address-1-guest | Street address | | first-name-guest_2 +ADDRESS_HOME_LINE2 | address-2-guest | Street address 2 (optional) | | first-name-guest_2 +UNKNOWN_TYPE | military-address-type | | | first-name-guest_2 +ADDRESS_HOME_ZIP | zip-guest | Zip | | first-name-guest_2 +ADDRESS_HOME_STATE | state-guest | | | first-name-guest_2 +UNKNOWN_TYPE | military-state | | | first-name-guest_2 +ADDRESS_HOME_CITY | city-guest | City | | first-name-guest_2 +EMAIL_ADDRESS | email | Email address | | first-name-guest_2 +PHONE_HOME_CITY_AND_NUMBER | phone-guest | Phone number | | first-name-guest_2
diff --git a/components/test/data/autofill/heuristics/output/146_checkout_store.scholastic.com.out b/components/test/data/autofill/heuristics/output/146_checkout_store.scholastic.com.out index adaa744..991e213 100644 --- a/components/test/data/autofill/heuristics/output/146_checkout_store.scholastic.com.out +++ b/components/test/data/autofill/heuristics/output/146_checkout_store.scholastic.com.out
@@ -9,4 +9,3 @@ PHONE_HOME_NUMBER | phoneExchange | *Phone | | firstName_1 PHONE_HOME_CITY_AND_NUMBER | phoneNumber | *Phone | | firstName_1 UNKNOWN_TYPE | label | *Address Label | | firstName_1 -UNKNOWN_TYPE | ssoShipToPOBox | This address is a P.O. Box. | true | firstName_1
diff --git a/components/test/data/autofill/heuristics/output/147_panera.custhelp.com_app_ask.out b/components/test/data/autofill/heuristics/output/147_panera.custhelp.com_app_ask.out index ab0d216..74fc5a1 100644 --- a/components/test/data/autofill/heuristics/output/147_panera.custhelp.com_app_ask.out +++ b/components/test/data/autofill/heuristics/output/147_panera.custhelp.com_app_ask.out
@@ -1,7 +1,3 @@ -UNKNOWN_TYPE | Incident.CustomFields.c.escalated_gift_card_devalue | Yes | 1 | -default -UNKNOWN_TYPE | Incident.CustomFields.c.escalated_gift_card_devalue | No | 0 | -default -UNKNOWN_TYPE | Incident.CustomFields.c.escalated_catering_sales | Yes | 1 | -default -UNKNOWN_TYPE | Incident.CustomFields.c.escalated_catering_sales | No | 0 | -default UNKNOWN_TYPE | Incident.CustomFields.c.pa_team_escalated_team_assign.month | Month | | -default UNKNOWN_TYPE | Incident.CustomFields.c.pa_team_escalated_team_assign.day | Day | | -default UNKNOWN_TYPE | Incident.CustomFields.c.pa_team_escalated_team_assign.year | Year | | -default @@ -18,10 +14,6 @@ UNKNOWN_TYPE | Incident.CustomFields.c.pa_team_analyst_assignment.hour | Hour | | -default UNKNOWN_TYPE | Incident.CustomFields.c.pa_team_analyst_assignment.minute | Minute | | -default UNKNOWN_TYPE | Incident.CustomFields.c.pa_external_reference_nbr | PA External Reference Optional Optional | | -default -UNKNOWN_TYPE | Incident.CustomFields.c.pa_tracking_monitor_control | Yes | 1 | -default -UNKNOWN_TYPE | Incident.CustomFields.c.pa_tracking_monitor_control | No | 0 | -default -UNKNOWN_TYPE | Incident.CustomFields.c.gift_card_super_agent | Yes | 1 | -default -UNKNOWN_TYPE | Incident.CustomFields.c.gift_card_super_agent | No | 0 | -default UNKNOWN_TYPE | Incident.CustomFields.c.gift_card_request_type | Gift Card Request Type | | -default UNKNOWN_TYPE | Incident.CustomFields.c.gift_card_action | Gift Card Action | | -default UNKNOWN_TYPE | Incident.CustomFields.c.gift_card_issuer_person | Gift Card Issuer (person) | 588 | -default @@ -34,19 +26,9 @@ UNKNOWN_TYPE | Incident.CustomFields.c.gift_card_issuing_location | Gift Card Issuing Location Optional Optional | | -default UNKNOWN_TYPE | Incident.CustomFields.c.chat_type | Chat Topic Please select the Chat topic that best fits the issue | | -default UNKNOWN_TYPE | Incident.CustomFields.c.last_4_digits | last 4 digits Optional Optional Enter the last 4 digits of the customer's credit card | | -default -UNKNOWN_TYPE | Incident.CustomFields.c.pertains_to_cafe | Yes Does your question or comment pertain to a specific bakery-cafe? If it does, please select this option so we can be sure we are addressing the correct bakery-cafe. | 1 | -default -UNKNOWN_TYPE | Incident.CustomFields.c.pertains_to_cafe | No | 0 | -default -UNKNOWN_TYPE | Incident.CustomFields.c.send_to_franchise | Yes | 1 | -default -UNKNOWN_TYPE | Incident.CustomFields.c.send_to_franchise | No | 0 | -default -UNKNOWN_TYPE | Incident.CustomFields.c.order_cancellation | Yes | 1 | -default -UNKNOWN_TYPE | Incident.CustomFields.c.order_cancellation | No | 0 | -default UNKNOWN_TYPE | Incident.CustomFields.c.admin_workspace_selection | Workspace selection | | -default UNKNOWN_TYPE | Incident.CustomFields.c.esc_reasonforrefund | Reason for Refund v2 Reason for providing the guest a refund. | | -default UNKNOWN_TYPE | Incident.CustomFields.c.esc_paymentmethod | Payment Method | | -default -UNKNOWN_TYPE | Incident.CustomFields.c.email_opt_in | Yes | 1 | -default -UNKNOWN_TYPE | Incident.CustomFields.c.email_opt_in | No | 0 | -default -UNKNOWN_TYPE | Incident.CustomFields.c.franchise_forward | Yes | 1 | -default -UNKNOWN_TYPE | Incident.CustomFields.c.franchise_forward | No | 0 | -default UNKNOWN_TYPE | Incident.CustomFields.c.passthrough_textarea | Pass Through Text Area Optional Optional | | -default UNKNOWN_TYPE | Incident.Subject | Subject Optional Optional | | -default NAME_FIRST | Contact.Name.First | First Name * Required | | Contact.Name.First_1 @@ -54,12 +36,9 @@ EMAIL_ADDRESS | Contact.Emails.PRIMARY.Address | Email Address * Required | | Contact.Name.First_1 PHONE_HOME_CITY_AND_NUMBER | Contact.Phones.HOME.Number | Phone Number Optional Optional | | Contact.Name.First_1 UNKNOWN_TYPE | Contact.CustomFields.c.panera_my_panera_number | MyPanera Card Number Optional Optional | | Contact.Name.First_1 -UNKNOWN_TYPE | Incident.CustomFields.c.pertains_to_cafe | Does your question pertain to a specific bakery-cafe? | 1 | Contact.Name.First_1 ADDRESS_HOME_STATE | Z.CafeInfo.CafeState.ID | Screen Reader users press enter to select a Please share a bit more detail. This will help us assist you more quickly.. Please share a bit more detail. This will help us assist you more quickly. * Required This button does not work with screen readers. Please use the previous link instead. Details | | Contact.Name.First_1 ADDRESS_HOME_CITY | Z.CafeInfo.CafeCity.ID | Screen Reader users press enter to select a Please share a bit more detail. This will help us assist you more quickly.. Please share a bit more detail. This will help us assist you more quickly. * Required This button does not work with screen readers. Please use the previous link instead. Details | | Contact.Name.First_1 UNKNOWN_TYPE | X.Incident.CustomFields.CafeInfo.AssociatedCafe.ID | Bakery-Cafe | | -default UNKNOWN_TYPE | Incident.Threads | We’d love to hear your feedback. Please share your comments, questions or suggestions here. * Required | | Contact.Name.First_1 -UNKNOWN_TYPE | Incident.CustomFields.c.email_opt_in | If you would like a response from our Customer Care Team, please make sure this box is checked. | 1 | Contact.Name.First_1 -UNKNOWN_TYPE | rn_FormSubmit_86_Submission | Send | on | -default UNKNOWN_TYPE | rn_KeywordText_3_Text | Search by Keyword | | rn_KeywordText_3_Text_1
diff --git a/components/test/data/autofill/heuristics/output/148_payment_dickblick.com.out b/components/test/data/autofill/heuristics/output/148_payment_dickblick.com.out index b9a465ff..744fe4a6 100644 --- a/components/test/data/autofill/heuristics/output/148_payment_dickblick.com.out +++ b/components/test/data/autofill/heuristics/output/148_payment_dickblick.com.out
@@ -1,15 +1,11 @@ UNKNOWN_TYPE | unbxd_q | enter keyword or item number... | enter keyword or item number... | unbxd_q_1 MERCHANT_PROMO_CODE | CouponCode | Enter Your Gift Card or Coupon Number: | | -default UNKNOWN_TYPE | CouponPin | Enter Your Gift Card Pin Number: | | -default -UNKNOWN_TYPE | paymentMethod | Credit Card | CreditCard | unbxd_q_1 CREDIT_CARD_NUMBER | CreditCardNumber | Credit Card Number: | | CreditCardNumber_2 CREDIT_CARD_NAME_FULL | CreditCardHolderName | Name on Card: | | CreditCardNumber_2 CREDIT_CARD_EXP_MONTH | CreditCardMonth | Month: | 01 | CreditCardNumber_2 CREDIT_CARD_EXP_4_DIGIT_YEAR | CreditCardYear | Year: | 2017 | CreditCardNumber_2 CREDIT_CARD_VERIFICATION_CODE | CreditCardSecurityValue | Card Security Value: | | CreditCardNumber_2 -UNKNOWN_TYPE | paymentMethod | PayPal | PayPal | unbxd_q_1 -UNKNOWN_TYPE | paymentMethod | Check or Money Order | CheckOrMoneyOrder | unbxd_q_1 -UNKNOWN_TYPE | UseShippingAddressAsBillingAddress | My billing address is the same as my shipping address | true | unbxd_q_1 NAME_FIRST | BillingAddressFirstName | First Name: | | unbxd_q_1 NAME_LAST | BillingAddressLastName | Last Name: | | unbxd_q_1 COMPANY_NAME | BillingAddressCompanyName | School or Company Name: | | unbxd_q_1 @@ -25,22 +21,14 @@ PHONE_HOME_CITY_AND_NUMBER | ContactDaytimePhoneNumber | Daytime Telephone: | | unbxd_q_1 PHONE_HOME_CITY_AND_NUMBER | ContactEveningPhoneNumber | Evening Telephone: | | unbxd_q_1 EMAIL_ADDRESS | ContactEmailAddress | Email Address: | | unbxd_q_1 -UNKNOWN_TYPE | SendEmailSpecialSalesBlickWebSite | I want to receive emails about special sales and promotions on Blick's website. | true | unbxd_q_1 -UNKNOWN_TYPE | SendEmailSpecialSalesBlickRetailStores | I want to receive emails about special sales and promotions at Blick's retail stores. | true | unbxd_q_1 -UNKNOWN_TYPE | SendCatalogsAndFlyers | Please mail me catalogs and flyers. | true | unbxd_q_1 -UNKNOWN_TYPE | ShareMailingAddress | Allow my mailing address to be shared. | true | unbxd_q_1 UNKNOWN_TYPE | unbxd_q | enter keyword or item number... | enter keyword or item number... | unbxd_q_1 MERCHANT_PROMO_CODE | CouponCode | Enter Your Gift Card or Coupon Number: | | -default UNKNOWN_TYPE | CouponPin | Enter Your Gift Card Pin Number: | | -default -UNKNOWN_TYPE | paymentMethod | Credit Card | CreditCard | unbxd_q_1 CREDIT_CARD_NUMBER | CreditCardNumber | Credit Card Number: | | CreditCardNumber_2 CREDIT_CARD_NAME_FULL | CreditCardHolderName | Name on Card: | | CreditCardNumber_2 CREDIT_CARD_EXP_MONTH | CreditCardMonth | Month: | 01 | CreditCardNumber_2 CREDIT_CARD_EXP_4_DIGIT_YEAR | CreditCardYear | Year: | 2017 | CreditCardNumber_2 CREDIT_CARD_VERIFICATION_CODE | CreditCardSecurityValue | Card Security Value: | | CreditCardNumber_2 -UNKNOWN_TYPE | paymentMethod | PayPal | PayPal | unbxd_q_1 -UNKNOWN_TYPE | paymentMethod | Check or Money Order | CheckOrMoneyOrder | unbxd_q_1 -UNKNOWN_TYPE | UseShippingAddressAsBillingAddress | My billing address is the same as my shipping address | true | unbxd_q_1 NAME_FIRST | BillingAddressFirstName | First Name: | | BillingAddressFirstName_3 NAME_LAST | BillingAddressLastName | Last Name: | | BillingAddressFirstName_3 COMPANY_NAME | BillingAddressCompanyName | School or Company Name: | | BillingAddressFirstName_3 @@ -56,22 +44,14 @@ PHONE_HOME_CITY_AND_NUMBER | ContactDaytimePhoneNumber | Daytime Telephone: | | BillingAddressFirstName_3 PHONE_HOME_CITY_AND_NUMBER | ContactEveningPhoneNumber | Evening Telephone: | | BillingAddressFirstName_3 EMAIL_ADDRESS | ContactEmailAddress | Email Address: | | BillingAddressFirstName_3 -UNKNOWN_TYPE | SendEmailSpecialSalesBlickWebSite | I want to receive emails about special sales and promotions on Blick's website. | true | BillingAddressFirstName_3 -UNKNOWN_TYPE | SendEmailSpecialSalesBlickRetailStores | I want to receive emails about special sales and promotions at Blick's retail stores. | true | BillingAddressFirstName_3 -UNKNOWN_TYPE | SendCatalogsAndFlyers | Please mail me catalogs and flyers. | true | BillingAddressFirstName_3 -UNKNOWN_TYPE | ShareMailingAddress | Allow my mailing address to be shared. | true | BillingAddressFirstName_3 UNKNOWN_TYPE | unbxd_q | enter keyword or item number... | enter keyword or item number... | BillingAddressFirstName_3 MERCHANT_PROMO_CODE | CouponCode | Enter Your Gift Card or Coupon Number: | | -default UNKNOWN_TYPE | CouponPin | Enter Your Gift Card Pin Number: | | -default -UNKNOWN_TYPE | paymentMethod | Credit Card | CreditCard | BillingAddressFirstName_3 CREDIT_CARD_NUMBER | CreditCardNumber | Credit Card Number: | | CreditCardNumber_2 CREDIT_CARD_NAME_FULL | CreditCardHolderName | Name on Card: | | CreditCardNumber_2 CREDIT_CARD_EXP_MONTH | CreditCardMonth | Month: | 01 | CreditCardNumber_2 CREDIT_CARD_EXP_4_DIGIT_YEAR | CreditCardYear | Year: | 2017 | CreditCardNumber_2 CREDIT_CARD_VERIFICATION_CODE | CreditCardSecurityValue | Card Security Value: | | CreditCardNumber_2 -UNKNOWN_TYPE | paymentMethod | PayPal | PayPal | BillingAddressFirstName_3 -UNKNOWN_TYPE | paymentMethod | Check or Money Order | CheckOrMoneyOrder | BillingAddressFirstName_3 -UNKNOWN_TYPE | UseShippingAddressAsBillingAddress | My billing address is the same as my shipping address | true | BillingAddressFirstName_3 NAME_FIRST | BillingAddressFirstName | First Name: | | BillingAddressFirstName_4 NAME_LAST | BillingAddressLastName | Last Name: | | BillingAddressFirstName_4 COMPANY_NAME | BillingAddressCompanyName | School or Company Name: | | BillingAddressFirstName_4 @@ -87,7 +67,3 @@ PHONE_HOME_CITY_AND_NUMBER | ContactDaytimePhoneNumber | Daytime Telephone: | | BillingAddressFirstName_4 PHONE_HOME_CITY_AND_NUMBER | ContactEveningPhoneNumber | Evening Telephone: | | BillingAddressFirstName_4 EMAIL_ADDRESS | ContactEmailAddress | Email Address: | | BillingAddressFirstName_4 -UNKNOWN_TYPE | SendEmailSpecialSalesBlickWebSite | I want to receive emails about special sales and promotions on Blick's website. | true | BillingAddressFirstName_4 -UNKNOWN_TYPE | SendEmailSpecialSalesBlickRetailStores | I want to receive emails about special sales and promotions at Blick's retail stores. | true | BillingAddressFirstName_4 -UNKNOWN_TYPE | SendCatalogsAndFlyers | Please mail me catalogs and flyers. | true | BillingAddressFirstName_4 -UNKNOWN_TYPE | ShareMailingAddress | Allow my mailing address to be shared. | true | BillingAddressFirstName_4
diff --git a/components/test/data/autofill/heuristics/output/149_checkout_qvc.com_non_hidden.out b/components/test/data/autofill/heuristics/output/149_checkout_qvc.com_non_hidden.out index 95d0363..d167df2 100644 --- a/components/test/data/autofill/heuristics/output/149_checkout_qvc.com_non_hidden.out +++ b/components/test/data/autofill/heuristics/output/149_checkout_qvc.com_non_hidden.out
@@ -17,7 +17,6 @@ PHONE_HOME_CITY_CODE_WITH_TRUNK_PREFIX | BilltoWpArea | Work Phone: | | EmailAddress_1 PHONE_HOME_NUMBER | BilltoWpExchange | Work Phone: | | EmailAddress_1 PHONE_HOME_EXTENSION | BilltoWpExt | Work Phone: | | EmailAddress_1 -UNKNOWN_TYPE | SameAsBilltoCheckbox | Same as Bill-To: | 1 | EmailAddress_1 UNKNOWN_TYPE | ShiptoFirstName | * Name: | | EmailAddress_1 ADDRESS_HOME_LINE1 | ShiptoAddress1 | * Address Line 1: | | ShiptoAddress1_2 ADDRESS_HOME_LINE2 | ShiptoAddress2 | Address Line 2: | | ShiptoAddress1_2 @@ -25,5 +24,3 @@ ADDRESS_HOME_STATE | ShiptoState | State/Province: | AL | ShiptoAddress1_2 ADDRESS_HOME_ZIP | ShiptoZipCode | * Postal Code: | | ShiptoAddress1_2 ADDRESS_HOME_COUNTRY | ShiptoCountry | * Country: | US | ShiptoAddress1_2 -UNKNOWN_TYPE | ShiptoRadiobutton | This order only | ThisOrderOnly | ShiptoAddress1_2 -UNKNOWN_TYPE | ShiptoRadiobutton | All future orders (Permanent Ship-To)** | PermanentShipto | ShiptoAddress1_2
diff --git a/components/test/data/autofill/heuristics/output/150_checkout_venus.com_search_field.out b/components/test/data/autofill/heuristics/output/150_checkout_venus.com_search_field.out index ab738d7f..759b940 100644 --- a/components/test/data/autofill/heuristics/output/150_checkout_venus.com_search_field.out +++ b/components/test/data/autofill/heuristics/output/150_checkout_venus.com_search_field.out
@@ -9,8 +9,3 @@ ADDRESS_HOME_ZIP | ctl00$Body1$ucShippingAddress$txtZip | *Zip Code: | | ctl00$searchterm_1 ADDRESS_HOME_COUNTRY | ctl00$Body1$ucShippingAddress$ddlCountry | | US | ctl00$searchterm_1 PHONE_HOME_CITY_AND_NUMBER | ctl00$Body1$ucShippingAddress$txtDayPhone | *Phone xxx-xxx-xxxx | | ctl00$searchterm_1 -UNKNOWN_TYPE | ctl00$Body1$billShip | Yes, they are the same. | rbShipSame | ctl00$searchterm_1 -UNKNOWN_TYPE | ctl00$Body1$billShip | No, I need to add my billing address. | rbShipNotSame | ctl00$searchterm_1 -UNKNOWN_TYPE | ctl00$Body1$rptShipMethods$ctl00$shipMethod | 28 | 28 | ctl00$searchterm_1 -UNKNOWN_TYPE | ctl00$Body1$rptShipMethods$ctl01$shipMethod | 28 | 3 | ctl00$searchterm_1 -UNKNOWN_TYPE | ctl00$Body1$rptShipMethods$ctl02$shipMethod | 3 | 16 | ctl00$searchterm_1
diff --git a/components/test/data/autofill/heuristics/output/151_ticketmaster.com.out b/components/test/data/autofill/heuristics/output/151_ticketmaster.com.out index 8a322f42..b286fd2 100644 --- a/components/test/data/autofill/heuristics/output/151_ticketmaster.com.out +++ b/components/test/data/autofill/heuristics/output/151_ticketmaster.com.out
@@ -14,4 +14,3 @@ HTML_TYPE_POSTAL_CODE | postal_code | Postal Code * | | -billing HTML_TYPE_TEL | evening_phone | Phone * | | -billing HTML_TYPE_TEL | mobile_phone | Alternate Phone | | -billing -UNKNOWN_TYPE | store_payment | Store my card for faster checkout next time. | on | unit_2
diff --git a/components/test/data/autofill/heuristics/output/152_garbarino_document_number_not_cc.out b/components/test/data/autofill/heuristics/output/152_garbarino_document_number_not_cc.out index 7d6e681..46df76c 100644 --- a/components/test/data/autofill/heuristics/output/152_garbarino_document_number_not_cc.out +++ b/components/test/data/autofill/heuristics/output/152_garbarino_document_number_not_cc.out
@@ -5,7 +5,5 @@ UNKNOWN_TYPE | day_select | Fecha de Nacimiento * | 0 | firstName_1 UNKNOWN_TYPE | month_select | Fecha de Nacimiento * | 0 | firstName_1 UNKNOWN_TYPE | year_select | Fecha de Nacimiento * | 0 | firstName_1 -UNKNOWN_TYPE | gender | Masculino | MALE | firstName_1 -UNKNOWN_TYPE | gender | Femenino | FEMALE | firstName_1 HTML_TYPE_EMAIL | email | E-mail * | | firstName_1 HTML_TYPE_TEL | phone | Teléfono * | | firstName_1
diff --git a/components/test/data/autofill/heuristics/output/156_buyAlbum_bandcamp.com_price.out b/components/test/data/autofill/heuristics/output/156_buyAlbum_bandcamp.com_price.out index f23da09f..560b1bc 100644 --- a/components/test/data/autofill/heuristics/output/156_buyAlbum_bandcamp.com_price.out +++ b/components/test/data/autofill/heuristics/output/156_buyAlbum_bandcamp.com_price.out
@@ -1,7 +1,5 @@ PRICE | userPrice | Name your price: | | userPrice_1 UNKNOWN_TYPE | purchase-note-input | Include a message to Alvvays. | | userPrice_1 -UNKNOWN_TYPE | notify-me | Have a gift card? Redeem it here | on | userPrice_1 -UNKNOWN_TYPE | notify-me-label | Have a gift card? Redeem it here | on | userPrice_1 EMAIL_ADDRESS | address | Email a link to my free download to: | | userPrice_1 ADDRESS_HOME_COUNTRY | country | Have a gift card? Redeem it here | US | userPrice_1 ADDRESS_HOME_ZIP | postcode | zip code | | userPrice_1
diff --git a/components/test/data/autofill/heuristics/output/163_giftcard_hotels.com.out b/components/test/data/autofill/heuristics/output/163_giftcard_hotels.com.out index 6686261b..9247485 100644 --- a/components/test/data/autofill/heuristics/output/163_giftcard_hotels.com.out +++ b/components/test/data/autofill/heuristics/output/163_giftcard_hotels.com.out
@@ -2,19 +2,10 @@ HTML_TYPE_FAMILY_NAME | shoppingCartOrderDetails.travelDetails[0].hotelData.roomDetails[0].roomType.roomOccupations[0].lastName | Last name* | | room0-default HTML_TYPE_TEL | bookingContact.contactDetails.phoneNumber | Cell phone number*We’ll only contact you in an emergency | | contact-default HTML_TYPE_EMAIL | bookingContact.contactDetails.emailAddress | Email address*We’ll send your confirmation email to this address | | contact-default -UNKNOWN_TYPE | bookingContact.contactDetails.specialDeal | Check this box if you would like to receive our Hotels.com special offers email newsletter containing great hotel promotions. | true | bookingContact.contactDetails.specialDeal_1 -UNKNOWN_TYPE | shoppingCartOrderDetails.travelDetails[0].hotelData.roomDetails[0].roomType.roomOccupations[0].specialRequests | Special requests | | bookingContact.contactDetails.specialDeal_1 -UNKNOWN_TYPE | shoppingCartOrderDetails.travelDetails[0].hotelData.roomDetails[0].roomType.roomOccupations[0].accessibilityIds[0] | Accessible bathroom | 2420 | bookingContact.contactDetails.specialDeal_1 -UNKNOWN_TYPE | shoppingCartOrderDetails.travelDetails[0].hotelData.roomDetails[0].roomType.roomOccupations[0].accessibilityIds[1] | In-room accessibility | 2423 | bookingContact.contactDetails.specialDeal_1 -UNKNOWN_TYPE | shoppingCartOrderDetails.travelDetails[0].hotelData.roomDetails[0].roomType.roomOccupations[0].accessibilityIds[2] | Roll-in shower | 2421 | bookingContact.contactDetails.specialDeal_1 -UNKNOWN_TYPE | shoppingCartOrderDetails.travelDetails[0].hotelData.roomDetails[0].roomType.roomOccupations[0].accessibilityIds[3] | Wheelchair accessible parking | 2422 | bookingContact.contactDetails.specialDeal_1 -UNKNOWN_TYPE | payment-details-selector | Credit / Debit card | CREDITCARD | bookingContact.contactDetails.specialDeal_1 -UNKNOWN_TYPE | payment-details-selector | PayPal | PAYPAL | bookingContact.contactDetails.specialDeal_1 -UNKNOWN_TYPE | payment-details-selector | Gift Card | GIFTCARD | bookingContact.contactDetails.specialDeal_1 -UNKNOWN_TYPE | payment-details-selector | Apple Pay | APPLEPAY | bookingContact.contactDetails.specialDeal_1 -UNKNOWN_TYPE | giftCardNumber | Enter gift card number (16 digits) | | bookingContact.contactDetails.specialDeal_1 -ADDRESS_HOME_ZIP | giftCardPinCode | Enter gift card PIN code (8 digits)If your gift card has a silver panel, scratch it off to reveal the code | | bookingContact.contactDetails.specialDeal_1 -UNKNOWN_TYPE | paymentAmount | Enter the amount you`d like to pay with your gift card | | bookingContact.contactDetails.specialDeal_1 +UNKNOWN_TYPE | shoppingCartOrderDetails.travelDetails[0].hotelData.roomDetails[0].roomType.roomOccupations[0].specialRequests | Special requests | | shoppingCartOrderDetails.travelDetails[0].hotelData.roomDetails[0].roomType.roomOccupations[0].specialRequests_1 +UNKNOWN_TYPE | giftCardNumber | Enter gift card number (16 digits) | | shoppingCartOrderDetails.travelDetails[0].hotelData.roomDetails[0].roomType.roomOccupations[0].specialRequests_1 +ADDRESS_HOME_ZIP | giftCardPinCode | Enter gift card PIN code (8 digits)If your gift card has a silver panel, scratch it off to reveal the code | | shoppingCartOrderDetails.travelDetails[0].hotelData.roomDetails[0].roomType.roomOccupations[0].specialRequests_1 +UNKNOWN_TYPE | paymentAmount | Enter the amount you`d like to pay with your gift card | | shoppingCartOrderDetails.travelDetails[0].hotelData.roomDetails[0].roomType.roomOccupations[0].specialRequests_1 UNKNOWN_TYPE | payments.submittedPayments[0].storedPayment.spsId | Saved card | | stored-default HTML_TYPE_CREDIT_CARD_NAME_FIRST | payments.submittedPayments[0].billingDetailsForm.billingNameForm.billingFirstName | First name* | | payments.submittedPayments[0].billingDetailsForm.billingNameForm.billingFirstName_2 HTML_TYPE_CREDIT_CARD_NAME_LAST | payments.submittedPayments[0].billingDetailsForm.billingNameForm.billingLastName | Last name* | | payments.submittedPayments[0].billingDetailsForm.billingNameForm.billingFirstName_2 @@ -23,7 +14,6 @@ HTML_TYPE_CREDIT_CARD_EXP_MONTH | payments.submittedPayments[0].expiryMonth | Expiry month* | | payments.submittedPayments[0].billingDetailsForm.billingNameForm.billingFirstName_2 HTML_TYPE_CREDIT_CARD_EXP_2_DIGIT_YEAR | payments.submittedPayments[0].expiryYear | Expiry year* | | payments.submittedPayments[0].billingDetailsForm.billingNameForm.billingFirstName_2 HTML_TYPE_CREDIT_CARD_VERIFICATION_CODE | payments.submittedPayments[0].securityCode | Security code* | | payments.submittedPayments[0].billingDetailsForm.billingNameForm.billingFirstName_2 -UNKNOWN_TYPE | payments.submittedPayments[0].paymentStoringFormFields.hasToSavePayment | Save this card | true | bookingContact.contactDetails.specialDeal_1 -UNKNOWN_TYPE | payments.submittedPayments[0].paymentStoringFormFields.nickName | Card Nickname (optional) | | bookingContact.contactDetails.specialDeal_1 +UNKNOWN_TYPE | payments.submittedPayments[0].paymentStoringFormFields.nickName | Card Nickname (optional) | | shoppingCartOrderDetails.travelDetails[0].hotelData.roomDetails[0].roomType.roomOccupations[0].specialRequests_1 HTML_TYPE_COUNTRY_CODE | payments.submittedPayments[0].billingDetailsForm.billingAddressForm.billingCountry | Country* | US | -billing HTML_TYPE_POSTAL_CODE | payments.submittedPayments[0].billingDetailsForm.billingAddressForm.billingPostalCode | ZIP codeWe’ll use this to verify your credit card details | | -billing
diff --git a/components/test/data/autofill/heuristics/output/169_dominos_de.out b/components/test/data/autofill/heuristics/output/169_dominos_de.out index 5f797a2..3f6c818 100644 --- a/components/test/data/autofill/heuristics/output/169_dominos_de.out +++ b/components/test/data/autofill/heuristics/output/169_dominos_de.out
@@ -1,4 +1,3 @@ ADDRESS_HOME_ZIP | Customer.Postcode | Postleitzahl | | Customer.Postcode_1 ADDRESS_HOME_STREET_NAME | Customer.Street | Straßenname | | Customer.Postcode_1 ADDRESS_HOME_HOUSE_NUMBER | Customer.StreetNo | Hausnummer | | Customer.Postcode_1 -UNKNOWN_TYPE | Customer.RememberMyDetails | Filialauswahl merken | true | Customer.Postcode_1
diff --git a/components/test/data/autofill/heuristics/output/175_id_address_alfacart.com.out b/components/test/data/autofill/heuristics/output/175_id_address_alfacart.com.out index b2821280b..9778356 100644 --- a/components/test/data/autofill/heuristics/output/175_id_address_alfacart.com.out +++ b/components/test/data/autofill/heuristics/output/175_id_address_alfacart.com.out
@@ -7,4 +7,3 @@ UNKNOWN_TYPE | listVillage | Kelurahan * | | txtAddAddressNewAddress_1 ADDRESS_HOME_STREET_ADDRESS | txtAddressNewAddress | Alamat * | | txtAddAddressNewAddress_1 ADDRESS_HOME_ZIP | txtPostalCodeNewAddress | Kode pos * | | txtAddAddressNewAddress_1 -UNKNOWN_TYPE | isDelivery | Atur Sebagai Default Pengiriman | on | txtAddAddressNewAddress_1
diff --git a/components/test/data/autofill/heuristics/output/178_zip_file_extension.out b/components/test/data/autofill/heuristics/output/178_zip_file_extension.out index dee3855..3bbd0ee9 100644 --- a/components/test/data/autofill/heuristics/output/178_zip_file_extension.out +++ b/components/test/data/autofill/heuristics/output/178_zip_file_extension.out
@@ -6,12 +6,9 @@ UNKNOWN_TYPE | Iattachname2 | 附加檔案Attachment(檔名請使用英文及數字命名,勿用特殊字元及非英數字母以外字元,檔名勿超過 10 個字元,檔案格式限定為 .doc(x),.xls(x),.txt,.jpg,.bmp,.gif, .rar,.zip,.pdf,.mp3,.mp4,.avi , 每個檔案最多3MB,如果您附件數量過多或過大,請先壓縮後再傳送) | | Imrname_1 UNKNOWN_TYPE | Iattachname3 | 附加檔案Attachment(檔名請使用英文及數字命名,勿用特殊字元及非英數字母以外字元,檔名勿超過 10 個字元,檔案格式限定為 .doc(x),.xls(x),.txt,.jpg,.bmp,.gif, .rar,.zip,.pdf,.mp3,.mp4,.avi , 每個檔案最多3MB,如果您附件數量過多或過大,請先壓縮後再傳送) | | Imrname_1 UNKNOWN_TYPE | check_value | 請輸入右方確認碼(必填) | | Imrname_1 -UNKNOWN_TYPE | Epaper | 我要訂閱政策及法令宣導 | ON | Imrname_1 ADDRESS_HOME_STATE | ImailorAttr1 | 地區 Region | | Imrname_1 UNKNOWN_TYPE | ImailorAttr2 | 學歷 Level of Education | | Imrname_1 UNKNOWN_TYPE | ImailorAttr3 | 年齡 Age | | Imrname_1 UNKNOWN_TYPE | ImailorAttr4 | 職業 Occupation | | Imrname_1 PHONE_HOME_CITY_AND_NUMBER | Imrtel | 電話 TEL | | Imrname_1 -UNKNOWN_TYPE | Imrsex | 男 | 1 | Imrname_1 -UNKNOWN_TYPE | Imrsex | 女 | 0 | Imrname_1 ADDRESS_HOME_LINE1 | Imraddress | 地址 Address | | Imrname_1
diff --git a/components/user_data_importer/DEPS b/components/user_data_importer/DEPS index cd07b34..c5866beb 100644 --- a/components/user_data_importer/DEPS +++ b/components/user_data_importer/DEPS
@@ -3,11 +3,14 @@ "+components/autofill/core/browser/data_manager/payments", "+components/autofill/core/browser/data_model/payments", "+components/autofill/core/browser/foundations", + "+components/bookmarks/browser", + "+components/bookmarks/test", "+components/history/core/browser", "+components/history/core/test", "+components/password_manager/core/browser", "+components/password_manager/core/common", "+components/password_manager/services/csv_password", + "+components/reading_list/core", "+mojo/public/cpp/bindings", ]
diff --git a/components/user_data_importer/utility/BUILD.gn b/components/user_data_importer/utility/BUILD.gn index 6cd0aa95..5158c18 100644 --- a/components/user_data_importer/utility/BUILD.gn +++ b/components/user_data_importer/utility/BUILD.gn
@@ -17,9 +17,11 @@ deps = [ ":zip_ffi_glue", "//base", + "//components/bookmarks/browser", "//components/history/core/browser", "//components/password_manager/core/browser/import:importer", "//components/password_manager/core/browser/ui", + "//components/reading_list/core", "//components/user_data_importer/common", ] } @@ -78,6 +80,8 @@ "//base/test:test_support", "//components/affiliations/core/browser:test_support", "//components/autofill/core/browser:test_support", + "//components/bookmarks/browser", + "//components/bookmarks/test", "//components/history/core/browser", "//components/history/core/test", "//components/password_manager/core/browser/import:csv", @@ -86,6 +90,7 @@ "//components/password_manager/core/browser/ui", "//components/password_manager/core/browser/ui:credential_ui_entry", "//components/password_manager/core/common:constants", + "//components/reading_list/core:test_support", "//mojo/public/cpp/bindings", "//testing/gmock", "//testing/gtest",
diff --git a/components/user_data_importer/utility/safari_data_importer.cc b/components/user_data_importer/utility/safari_data_importer.cc index c74cd5d..362c4d53 100644 --- a/components/user_data_importer/utility/safari_data_importer.cc +++ b/components/user_data_importer/utility/safari_data_importer.cc
@@ -16,8 +16,10 @@ #include "base/types/expected_macros.h" #include "components/autofill/core/browser/data_manager/payments/payments_data_manager.h" #include "components/autofill/core/browser/data_model/payments/credit_card.h" +#include "components/bookmarks/browser/bookmark_model.h" #include "components/history/core/browser/history_service.h" #include "components/password_manager/core/browser/ui/saved_passwords_presenter.h" +#include "components/reading_list/core/reading_list_model.h" #include "components/user_data_importer/common/imported_bookmark_entry.h" #include "components/user_data_importer/utility/history_callback_from_rust.h" #include "components/user_data_importer/utility/zip_ffi_glue.rs.h" @@ -112,6 +114,8 @@ password_manager::SavedPasswordsPresenter* presenter, autofill::PaymentsDataManager* payments_data_manager, history::HistoryService* history_service, + bookmarks::BookmarkModel* bookmark_model, + ReadingListModel* reading_list_model, std::unique_ptr<BookmarkParser> bookmark_parser, std::string app_locale) : password_importer_(std::make_unique<password_manager::PasswordImporter>( @@ -119,6 +123,8 @@ /*user_confirmation_required=*/true)), payments_data_manager_(CHECK_DEREF(payments_data_manager)), history_service_(CHECK_DEREF(history_service)), + bookmark_model_(CHECK_DEREF(bookmark_model)), + reading_list_model_(CHECK_DEREF(reading_list_model)), bookmark_parser_(std::move(bookmark_parser)), task_runner_(base::SequencedTaskRunner::GetCurrentDefault()), app_locale_(std::move(app_locale)) {} @@ -413,6 +419,7 @@ pending_bookmarks_ = std::move(value.bookmarks); pending_reading_list_ = std::move(value.reading_list); + PostCallback(std::move(callback), pending_bookmarks_.size() + pending_reading_list_.size()); }
diff --git a/components/user_data_importer/utility/safari_data_importer.h b/components/user_data_importer/utility/safari_data_importer.h index 29cd811..8b879cc 100644 --- a/components/user_data_importer/utility/safari_data_importer.h +++ b/components/user_data_importer/utility/safari_data_importer.h
@@ -19,10 +19,16 @@ class SequencedTaskRunner; } // namespace base +namespace bookmarks { +class BookmarkModel; +} // namespace bookmarks + namespace history { class HistoryService; } // namespace history +class ReadingListModel; + namespace user_data_importer { struct ImportedBookmarkEntry; @@ -47,6 +53,8 @@ SafariDataImporter(password_manager::SavedPasswordsPresenter* presenter, autofill::PaymentsDataManager* payments_data_manager, history::HistoryService* history_service, + bookmarks::BookmarkModel* bookmark_model, + ReadingListModel* reading_list_model, std::unique_ptr<BookmarkParser> bookmark_parser, std::string app_locale); ~SafariDataImporter(); @@ -175,6 +183,12 @@ // Service used to import history URLs. const raw_ref<history::HistoryService> history_service_; + // Service used to import bookmarks. + const raw_ref<bookmarks::BookmarkModel> bookmark_model_; + + // Service used to import reading lists. + const raw_ref<ReadingListModel> reading_list_model_; + // The model-layer object used to parse bookmarks from an HTML file. std::unique_ptr<BookmarkParser> bookmark_parser_;
diff --git a/components/user_data_importer/utility/safari_data_importer_unittest.cc b/components/user_data_importer/utility/safari_data_importer_unittest.cc index 1d5f197..fcd8a010 100644 --- a/components/user_data_importer/utility/safari_data_importer_unittest.cc +++ b/components/user_data_importer/utility/safari_data_importer_unittest.cc
@@ -15,8 +15,11 @@ #include "base/test/mock_callback.h" #include "base/test/run_until.h" #include "base/test/task_environment.h" +#include "base/time/default_clock.h" #include "components/affiliations/core/browser/fake_affiliation_service.h" #include "components/autofill/core/browser/foundations/test_autofill_client.h" +#include "components/bookmarks/browser/bookmark_model.h" +#include "components/bookmarks/test/test_bookmark_client.h" #include "components/history/core/browser/history_service.h" #include "components/history/core/test/history_service_test_util.h" #include "components/password_manager/core/browser/import/csv_password_sequence.h" @@ -27,6 +30,9 @@ #include "components/password_manager/core/browser/ui/saved_passwords_presenter.h" #include "components/password_manager/core/common/password_manager_constants.h" #include "components/password_manager/services/csv_password/fake_password_parser_service.h" +#include "components/reading_list/core/fake_reading_list_model_storage.h" +#include "components/reading_list/core/reading_list_model.h" +#include "components/reading_list/core/reading_list_model_impl.h" #include "components/user_data_importer/utility/bookmark_parser.h" #include "mojo/public/cpp/bindings/pending_remote.h" #include "mojo/public/cpp/bindings/receiver.h" @@ -49,9 +55,21 @@ CHECK(history_dir_.CreateUniqueTempDir()); history_service_ = history::CreateHistoryService(history_dir_.GetPath(), /*create_db=*/false); + + auto bookmark_client = std::make_unique<bookmarks::TestBookmarkClient>(); + bookmark_model_ = + std::make_unique<bookmarks::BookmarkModel>(std::move(bookmark_client)); + + auto storage = std::make_unique<FakeReadingListModelStorage>(); + reading_list_model_ = std::make_unique<ReadingListModelImpl>( + std::move(storage), syncer::StorageType::kUnspecified, + syncer::WipeModelUponSyncDisabledBehavior::kNever, + base::DefaultClock::GetInstance()); + importer_ = std::make_unique<SafariDataImporter>( &presenter_, &client_.GetPersonalDataManager().payments_data_manager(), - history_service_.get(), MakeBookmarkParser(), "en-US"); + history_service_.get(), bookmark_model_.get(), + reading_list_model_.get(), MakeBookmarkParser(), "en-US"); mojo::PendingRemote<password_manager::mojom::CSVPasswordParser> pending_remote{receiver_.BindNewPipeAndPassRemote()}; @@ -325,6 +343,8 @@ autofill::TestAutofillClient client_; base::ScopedTempDir history_dir_; std::unique_ptr<history::HistoryService> history_service_; + std::unique_ptr<bookmarks::BookmarkModel> bookmark_model_; + std::unique_ptr<ReadingListModel> reading_list_model_; bool presenter_ready_ = false; password_manager::ImportResults import_results_; bool passwords_callback_called_ = false;
diff --git a/content/browser/android/tracing_controller_android.cc b/content/browser/android/tracing_controller_android.cc index f4f647a..dd868bada 100644 --- a/content/browser/android/tracing_controller_android.cc +++ b/content/browser/android/tracing_controller_android.cc
@@ -14,6 +14,7 @@ #include "base/logging.h" #include "base/trace_event/trace_event.h" #include "content/browser/tracing/tracing_controller_impl.h" +#include "content/public/browser/browser_thread.h" #include "content/public/browser/tracing_controller.h" #include "services/tracing/public/cpp/perfetto/perfetto_config.h" #include "services/tracing/public/cpp/perfetto/perfetto_session.h" @@ -94,6 +95,7 @@ const JavaParamRef<jstring>& jcategories, const JavaParamRef<jstring>& jtraceoptions, bool use_protobuf) { + DCHECK_CURRENTLY_ON(content::BrowserThread::UI); std::string categories = base::android::ConvertJavaStringToUTF8(env, jcategories); std::string options = @@ -122,6 +124,7 @@ bool compress_file, bool use_protobuf, const base::android::JavaParamRef<jobject>& callback) { + DCHECK_CURRENTLY_ON(content::BrowserThread::UI); base::FilePath file_path( base::android::ConvertJavaStringToUTF8(env, jfilepath)); ScopedJavaGlobalRef<jobject> global_callback(env, callback); @@ -169,6 +172,7 @@ base::FilePath TracingControllerAndroid::GenerateTracingFilePath( const std::string& basename) { + DCHECK_CURRENTLY_ON(content::BrowserThread::UI); JNIEnv* env = base::android::AttachCurrentThread(); ScopedJavaLocalRef<jstring> jfilename = Java_TracingControllerAndroidImpl_generateTracingFilePath( @@ -179,6 +183,7 @@ void TracingControllerAndroid::OnTracingStopped( const base::android::ScopedJavaGlobalRef<jobject>& callback) { + DCHECK_CURRENTLY_ON(content::BrowserThread::UI); JNIEnv* env = base::android::AttachCurrentThread(); base::android::ScopedJavaLocalRef<jobject> obj = weak_java_object_.get(env); if (obj.obj()) @@ -189,6 +194,7 @@ JNIEnv* env, const JavaParamRef<jobject>& obj, const JavaParamRef<jobject>& callback) { + DCHECK_CURRENTLY_ON(content::BrowserThread::UI); ScopedJavaGlobalRef<jobject> global_callback(env, callback); // TODO(skyostil): Get the categories from Perfetto instead. return TracingController::GetInstance()->GetCategories( @@ -199,6 +205,7 @@ void TracingControllerAndroid::OnKnownCategoriesReceived( const ScopedJavaGlobalRef<jobject>& callback, const std::set<std::string>& categories_received) { + DCHECK_CURRENTLY_ON(content::BrowserThread::UI); base::Value::List category_list; for (const std::string& category : categories_received) category_list.Append(category); @@ -236,6 +243,7 @@ JNIEnv* env, const JavaParamRef<jobject>& obj, const JavaParamRef<jobject>& callback) { + DCHECK_CURRENTLY_ON(content::BrowserThread::UI); ScopedJavaGlobalRef<jobject> global_callback(env, callback); auto weak_callback = base::BindOnce(&TracingControllerAndroid::OnTraceBufferUsageReceived, @@ -264,8 +272,10 @@ } // TODO(skyostil): Remove approximate_event_count since no-one is using // it. - std::move(shared_callback->data) - .Run(percent_full, /*approximate_event_count=*/0); + GetUIThreadTaskRunner({})->PostTask( + FROM_HERE, + base::BindOnce(std::move(shared_callback->data), percent_full, + /*approximate_event_count=*/0)); }); return true; } @@ -274,6 +284,7 @@ const ScopedJavaGlobalRef<jobject>& callback, float percent_full, size_t approximate_event_count) { + DCHECK_CURRENTLY_ON(content::BrowserThread::UI); JNIEnv* env = base::android::AttachCurrentThread(); base::android::ScopedJavaLocalRef<jobject> obj = weak_java_object_.get(env); if (obj.obj()) {
diff --git a/content/browser/web_contents/web_contents_impl.cc b/content/browser/web_contents/web_contents_impl.cc index 98b755c..31d5e4ea 100644 --- a/content/browser/web_contents/web_contents_impl.cc +++ b/content/browser/web_contents/web_contents_impl.cc
@@ -396,7 +396,8 @@ return false; } - blink::URLSyntaxErrorCode code = blink::IsValidCustomHandlerURLSyntax(url); + blink::URLSyntaxErrorCode code = + blink::IsValidCustomHandlerURLSyntax(url, security_level); if (code != blink::URLSyntaxErrorCode::kNoError) { return false; }
diff --git a/content/public/common/content_features.cc b/content/public/common/content_features.cc index afda2b77..4449ae36 100644 --- a/content/public/common/content_features.cc +++ b/content/public/common/content_features.cc
@@ -1297,6 +1297,11 @@ "WebAssemblyExperimentalJSPI", base::FEATURE_ENABLED_BY_DEFAULT); +// Enables WebAssembly Shared-Everything Threads. +BASE_FEATURE(kEnableExperimentalWebAssemblySharedEverything, + "WebAssemblyExperimentalSharedEverything", + base::FEATURE_DISABLED_BY_DEFAULT); + // Enable WebAssembly lazy compilation (JIT on first call). BASE_FEATURE(kWebAssemblyLazyCompilation, "WebAssemblyLazyCompilation",
diff --git a/content/public/common/content_features.h b/content/public/common/content_features.h index e1add07..87eee09 100644 --- a/content/public/common/content_features.h +++ b/content/public/common/content_features.h
@@ -287,6 +287,8 @@ CONTENT_EXPORT BASE_DECLARE_FEATURE(kWebAppSystemMediaControls); CONTENT_EXPORT BASE_DECLARE_FEATURE(kWebAssemblyBaseline); CONTENT_EXPORT BASE_DECLARE_FEATURE(kEnableExperimentalWebAssemblyJSPI); +CONTENT_EXPORT BASE_DECLARE_FEATURE( + kEnableExperimentalWebAssemblySharedEverything); #if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS) CONTENT_EXPORT BASE_DECLARE_FEATURE(kElementCaptureOfOtherTabs); #endif
diff --git a/content/renderer/render_process_impl.cc b/content/renderer/render_process_impl.cc index 8a2d0dc..876d01f 100644 --- a/content/renderer/render_process_impl.cc +++ b/content/renderer/render_process_impl.cc
@@ -168,6 +168,10 @@ "--experimental-wasm-jspi", "--no-experimental-wasm-jspi"); + SetV8FlagIfOverridden( + features::kEnableExperimentalWebAssemblySharedEverything, + "--experimental-wasm-shared", "--no-experimental-wasm-shared"); + SetV8FlagIfOverridden(features::kWebAssemblyLazyCompilation, "--wasm-lazy-compilation", "--no-wasm-lazy-compilation");
diff --git a/content/shell/browser/shell_platform_delegate_ios.mm b/content/shell/browser/shell_platform_delegate_ios.mm index e3e9ce53..8eef2ee 100644 --- a/content/shell/browser/shell_platform_delegate_ios.mm +++ b/content/shell/browser/shell_platform_delegate_ios.mm
@@ -12,6 +12,7 @@ #include "base/trace_event/trace_config.h" #include "content/public/browser/browser_accessibility_state.h" #include "content/public/browser/scoped_accessibility_mode.h" +#include "content/public/browser/web_contents.h" #include "content/shell/app/resource.h" #include "content/shell/browser/color_chooser/shell_color_chooser_ios.h" #include "content/shell/browser/shell.h"
diff --git a/docs/linux/build_instructions.md b/docs/linux/build_instructions.md index 63e06cd..e5a47b7 100644 --- a/docs/linux/build_instructions.md +++ b/docs/linux/build_instructions.md
@@ -159,7 +159,7 @@ *** note **Warning:** If you are a Google employee, do not follow the instructions below. See -[go/chrome-linux-build#setup-remote-execution](https://goto.google.com/chrome-linux-build#setup-remote-execution) +[go/chrome-linux-build#set-up-remote-execution](https://goto.google.com/chrome-linux-build#set-up-remote-execution) instead. *** @@ -200,7 +200,7 @@ *** note **Warning:** If you are a Google employee, do not follow the instructions below. See -[go/chrome-linux-build#setup-remote-execution](https://goto.google.com/chrome-linux-build#setup-remote-execution) +[go/chrome-linux-build#set-up-remote-execution](https://goto.google.com/chrome-linux-build#set-up-remote-execution) instead. ***
diff --git a/extensions/common/BUILD.gn b/extensions/common/BUILD.gn index 7b1bbc9..1184c59a 100644 --- a/extensions/common/BUILD.gn +++ b/extensions/common/BUILD.gn
@@ -431,6 +431,8 @@ "manifest_handlers/options_page_info.h", "manifest_handlers/permissions_parser.cc", "manifest_handlers/permissions_parser.h", + "manifest_handlers/protocol_handler_info.cc", + "manifest_handlers/protocol_handler_info.h", "manifest_handlers/replacement_apps.cc", "manifest_handlers/replacement_apps.h", "manifest_handlers/requirements_info.cc", @@ -547,6 +549,7 @@ "//build:chromeos_buildflags", "//components/crash/core/common", "//components/crx_file", + "//components/custom_handlers", "//components/pdf/common:util", "//components/safe_browsing/core/common", "//components/safe_browsing/core/common/hashprefix_realtime:hash_realtime_utils", @@ -563,6 +566,7 @@ "//services/device/public/cpp/usb", "//services/device/public/mojom:usb", "//services/network/public/mojom", + "//third_party/blink/public/common", "//third_party/boringssl", "//third_party/icu", "//third_party/re2", @@ -673,6 +677,7 @@ "manifest_handlers/manifest_v3_permissions_unittest.cc", "manifest_handlers/mime_types_handler_unittest.cc", "manifest_handlers/oauth2_manifest_unittest.cc", + "manifest_handlers/protocol_handler_manifest_unittest.cc", "manifest_handlers/replacement_apps_unittest.cc", "manifest_handlers/requirements_unittest.cc", "manifest_handlers/shared_module_manifest_unittest.cc",
diff --git a/extensions/common/DEPS b/extensions/common/DEPS index 11ccd90..a09db5e9 100644 --- a/extensions/common/DEPS +++ b/extensions/common/DEPS
@@ -15,7 +15,9 @@ "+services/network/public/mojom/cors_origin_pattern.mojom-forward.h", "+services/network/public/mojom/cors_origin_pattern.mojom.h", "+third_party/blink/public/common", + "+third_party/blink/public/common/custom_handlers", "+third_party/blink/public/common/privacy_budget", + "+third_party/blink/public/common/security", "+third_party/blink/public/mojom/blob", "+third_party/re2", "+third_party/zlib",
diff --git a/extensions/common/api/_manifest_features.json b/extensions/common/api/_manifest_features.json index 9b965f3..201146e 100644 --- a/extensions/common/api/_manifest_features.json +++ b/extensions/common/api/_manifest_features.json
@@ -228,6 +228,11 @@ "platforms": ["chromeos"] } ], + "protocol_handlers": { + "channel": "dev", + "extension_types": ["extension"], + "min_manifest_version": 3 + }, "homepage_url": { "channel": "stable", "extension_types": ["extension", "legacy_packaged_app"]
diff --git a/extensions/common/api/protocol_handlers.idl b/extensions/common/api/protocol_handlers.idl new file mode 100644 index 0000000..d25ed29 --- /dev/null +++ b/extensions/common/api/protocol_handlers.idl
@@ -0,0 +1,27 @@ +// Copyright 2025 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// `protocol_handlers` manifest key defintion. Protocol Handlers allow developers +// to let extensions register Custom Handlers for URL's schemes unknown to the +// Browser. This manifest key provides a similar behavior than the Web API +// implementing the Custom Handlers section of the HTML specification. +// https://html.spec.whatwg.org/#custom-handlers +[generate_error_messages] namespace protocolHandlers { + + // A ProtocolHandler registers the register a Custom Hanlder for an unknown + // URL scheme. + // https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/protocol_handlers + dictionary ProtocolHandler { + // A string definition of the protocol to handle. + DOMString protocol; + // A string representation of the protocol handlers, displayed to the user when prompting for permissions. + DOMString name; + // A string representing the URL of the protocol handler (must be a localizable property). + DOMString uriTemplate; + }; + + dictionary ManifestKeys { + ProtocolHandler[] protocol_handlers; + }; +};
diff --git a/extensions/common/api/schema.gni b/extensions/common/api/schema.gni index 4ba1aed2..b38e3d3 100644 --- a/extensions/common/api/schema.gni +++ b/extensions/common/api/schema.gni
@@ -21,6 +21,7 @@ "metrics_private.json", "offscreen.idl", "power.idl", + "protocol_handlers.idl", "runtime.json", "storage.json", "system_cpu.idl",
diff --git a/extensions/common/common_manifest_handlers.cc b/extensions/common/common_manifest_handlers.cc index 88d0bb22..5987d318 100644 --- a/extensions/common/common_manifest_handlers.cc +++ b/extensions/common/common_manifest_handlers.cc
@@ -34,6 +34,7 @@ #include "extensions/common/manifest_handlers/oauth2_manifest_handler.h" #include "extensions/common/manifest_handlers/offline_enabled_info.h" #include "extensions/common/manifest_handlers/options_page_info.h" +#include "extensions/common/manifest_handlers/protocol_handler_info.h" #include "extensions/common/manifest_handlers/replacement_apps.h" #include "extensions/common/manifest_handlers/requirements_info.h" #include "extensions/common/manifest_handlers/sandboxed_page_info.h" @@ -82,6 +83,7 @@ registry->RegisterHandler(std::make_unique<OAuth2ManifestHandler>()); registry->RegisterHandler(std::make_unique<OfflineEnabledHandler>()); registry->RegisterHandler(std::make_unique<OptionsPageHandler>()); + registry->RegisterHandler(std::make_unique<ProtocolHandlersParser>()); registry->RegisterHandler(std::make_unique<ReplacementAppsHandler>()); registry->RegisterHandler(std::make_unique<RequirementsHandler>()); registry->RegisterHandler(std::make_unique<SandboxedPageHandler>());
diff --git a/extensions/common/extension_features.cc b/extensions/common/extension_features.cc index c69a0f1..b666aad 100644 --- a/extensions/common/extension_features.cc +++ b/extensions/common/extension_features.cc
@@ -111,6 +111,10 @@ "AllowLegacyMV2Extensions", base::FEATURE_DISABLED_BY_DEFAULT); +BASE_FEATURE(kExtensionProtocolHandlers, + "ExtensionProtocolHandlers", + base::FEATURE_DISABLED_BY_DEFAULT); + BASE_FEATURE(kExtensionWARForRedirect, "ExtensionWARForRedirect", base::FEATURE_ENABLED_BY_DEFAULT);
diff --git a/extensions/common/extension_features.h b/extensions/common/extension_features.h index e239773..7dfbfa5b 100644 --- a/extensions/common/extension_features.h +++ b/extensions/common/extension_features.h
@@ -141,6 +141,14 @@ // accessible resource restrictions. BASE_DECLARE_FEATURE(kExtensionWARForRedirect); +// If enabled, allows an extension to specify protocol_handlers keys in the +// Manifest, registering a group of custom handlers so that the browser can +// handle navigation requests to URLs with unknown schemes. This feature +// provides similar behavior and capabilities than the one implemented by +// the 'registerProtocolHandler' Web API, defined in the Custom Handlers +// section of the HTML specification. +BASE_DECLARE_FEATURE(kExtensionProtocolHandlers); + // If enabled, only manifest v3 extensions is allowed while v2 will be disabled. // Note that this feature is now only checked by `ExtensionManagement` which // represents enterprise extension configurations. Flip the feature will block
diff --git a/extensions/common/manifest_constants.h b/extensions/common/manifest_constants.h index 7d79f69b..af48fe3f 100644 --- a/extensions/common/manifest_constants.h +++ b/extensions/common/manifest_constants.h
@@ -131,6 +131,7 @@ "app.background.scripts"; inline constexpr char kPlatformAppContentSecurityPolicy[] = "app.content_security_policy"; +inline constexpr char kProtocolHandlers[] = "protocol_handlers"; inline constexpr char kPublicKey[] = "key"; inline constexpr char kRemoveButton[] = "remove_button"; inline constexpr char kReplacementWebApp[] = "replacement_web_app"; @@ -544,6 +545,25 @@ "Invalid value for 'permissions[*]': *."; inline constexpr char16_t kInvalidPermissions[] = u"Invalid value for 'permissions'."; +inline constexpr char kInvalidProtocolHandlersEmpty[] = + "The 'protocol_handlers' value must be a non-empty list."; +inline constexpr char kInvalidProtocolHandlers[] = + "Invalid value for 'protocol_handlers'."; +inline constexpr char kProtocolHandlerEmptyName[] = + "The 'name' must be a non-empty string."; +inline constexpr char kProtocolHandlerSchemeNotInSafeList[] = + "Not allowed to register custom handlers for unprefixed schemes that are " + "not in the safe list."; +inline constexpr char kProtocolHandlerUrlTokenMissing[] = + "The custom handler url does not contain the '%s' token."; +inline constexpr char kProtocolHandlerUrlInvalidSyntax[] = + "The custom handler url syntax is not valid."; +inline constexpr char kProtocolHandlerUntrustworthyScheme[] = + "The custom handler scheme is not considered as 'Potentially Trustworthy'."; +inline constexpr char kProtocolHandlerOpaqueOrigin[] = + "The custom handler url must not have an 'Opaque Origin'"; +inline constexpr char kProtocolHandlerIncompabibleOrigins[] = + "The custom handler url must satisfy the 'Same Origin' restriction."; inline constexpr char16_t kInvalidReplacementWebApp[] = u"Invalid value for 'replacement_web_app'."; inline constexpr char kInvalidRulesetID[] =
diff --git a/extensions/common/manifest_handlers/protocol_handler_info.cc b/extensions/common/manifest_handlers/protocol_handler_info.cc new file mode 100644 index 0000000..ad2754c --- /dev/null +++ b/extensions/common/manifest_handlers/protocol_handler_info.cc
@@ -0,0 +1,171 @@ +// Copyright 2025 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "extensions/common/manifest_handlers/protocol_handler_info.h" + +#include "base/strings/utf_string_conversions.h" +#include "base/values.h" +#include "extensions/common/api/protocol_handlers.h" +#include "extensions/common/error_utils.h" +#include "extensions/common/extension_features.h" +#include "extensions/common/manifest.h" +#include "extensions/common/manifest_constants.h" +#include "third_party/blink/public/common/custom_handlers/protocol_handler_utils.h" +#include "third_party/blink/public/common/security/protocol_handler_security_level.h" + +namespace extensions { + +namespace keys = manifest_keys; +namespace errors = manifest_errors; + +using ProtocolHandlersManifestKeys = api::protocol_handlers::ManifestKeys; + +namespace { + +bool IsValidProtocolHandler(const std::string& protocol, + const std::string& name, + const GURL& url, + blink::ProtocolHandlerSecurityLevel security_level, + std::vector<InstallWarning>& warnings) { + // Implementation of the protocol handler arguments normalization steps + // defined in the spec. + // https://html.spec.whatwg.org/multipage/system-state.html#normalize-protocol-handler-parameters + bool is_valid = true; + + if (name.empty()) { + warnings.emplace_back(errors::kProtocolHandlerEmptyName); + is_valid = false; + } + + // Verify custom handler schemes for errors as described in steps 1 and 2 + if (!blink::IsValidCustomHandlerScheme(protocol, security_level)) { + warnings.emplace_back(errors::kProtocolHandlerSchemeNotInSafeList); + is_valid = false; + } + + switch (blink::IsValidCustomHandlerURLSyntax(url, security_level)) { + case blink::URLSyntaxErrorCode::kNoError: + break; + case blink::URLSyntaxErrorCode::kMissingToken: + warnings.emplace_back(errors::kProtocolHandlerUrlTokenMissing); + is_valid = false; + break; + case blink::URLSyntaxErrorCode::kInvalidUrl: + warnings.emplace_back(errors::kProtocolHandlerUrlInvalidSyntax); + is_valid = false; + break; + } + + // Verify custom handler URL security as described in steps 6 and 7 + if (!blink::IsAllowedCustomHandlerURL(url, security_level)) { + warnings.emplace_back(errors::kProtocolHandlerUntrustworthyScheme); + is_valid = false; + } + url::Origin url_origin = url::Origin::Create(url); + if (url.is_valid() && url_origin.opaque()) { + warnings.emplace_back(errors::kProtocolHandlerOpaqueOrigin); + is_valid = false; + } + + // TODO(crbug.com/40482153): We need to do a better analysis of the + // check defined here, based on the security_level and the SameOrigin policy. + url::Origin origin; + if (security_level < blink::ProtocolHandlerSecurityLevel::kUntrustedOrigins && + !origin.IsSameOriginWith(url)) { + warnings.emplace_back(errors::kProtocolHandlerIncompabibleOrigins); + is_valid = false; + } + + return is_valid; +} + +bool SupportsProtocolHandlers(const Extension& extension) { + return base::FeatureList::IsEnabled( + extensions_features::kExtensionProtocolHandlers); +} + +} // namespace + +ProtocolHandlers::ProtocolHandlers() = default; +ProtocolHandlers::~ProtocolHandlers() = default; + +// static +const ProtocolHandlersInfo* ProtocolHandlers::GetProtocolHandlers( + const Extension& extension) { + // Guard against incompatible extension manifest versions. + if (!SupportsProtocolHandlers(extension)) { + return nullptr; + } + + ProtocolHandlers* info = static_cast<ProtocolHandlers*>( + extension.GetManifestData(keys::kProtocolHandlers)); + return info ? &info->protocol_handlers : nullptr; +} + +ProtocolHandlersParser::ProtocolHandlersParser() = default; +ProtocolHandlersParser::~ProtocolHandlersParser() = default; + +std::unique_ptr<ProtocolHandlers> ParseEntryList( + const Extension& extension, + std::vector<InstallWarning>& install_warnings) { + std::u16string warning; + ProtocolHandlersManifestKeys manifest_keys; + if (!ProtocolHandlersManifestKeys::ParseFromDictionary( + extension.manifest()->available_values(), manifest_keys, warning)) { + install_warnings.emplace_back(base::UTF16ToUTF8(warning)); + return nullptr; + } + + if (manifest_keys.protocol_handlers.empty()) { + install_warnings.emplace_back(errors::kInvalidProtocolHandlersEmpty); + return nullptr; + } + + blink::ProtocolHandlerSecurityLevel security_level = + blink::ProtocolHandlerSecurityLevel::kExtensionFeatures; + + std::unique_ptr<ProtocolHandlers> info = std::make_unique<ProtocolHandlers>(); + for (const auto& protocol_handler : manifest_keys.protocol_handlers) { + apps::ProtocolHandlerInfo handler; + DCHECK(!protocol_handler.protocol.empty()); + DCHECK(!protocol_handler.uri_template.empty()); + handler.protocol = protocol_handler.protocol; + handler.name = protocol_handler.name; + handler.url = GURL(protocol_handler.uri_template); + + // Validation of Protocol Handlers according to the Custom Handlers section + // of the HTML spec. + // https://html.spec.whatwg.org/#normalize-protocol-handler-parameters + if (IsValidProtocolHandler(handler.protocol, handler.name, handler.url, + security_level, install_warnings)) { + info->protocol_handlers.push_back(handler); + } + } + return info; +} + +bool ProtocolHandlersParser::Parse(Extension* extension, + std::u16string* error) { + CHECK(extension); + CHECK_GE(extension->manifest_version(), 3); + + std::vector<InstallWarning> install_warnings; + auto info = ParseEntryList(*extension, install_warnings); + if (info) { + extension->SetManifestData(keys::kProtocolHandlers, std::move(info)); + } + + extension->AddInstallWarnings(std::move(install_warnings)); + + // Allow the extension to be installed, but handlers with warnings will be + // ignored and not registered as custom handlers. + return true; +} + +base::span<const char* const> ProtocolHandlersParser::Keys() const { + static constexpr const char* kKeys[] = {keys::kProtocolHandlers}; + return kKeys; +} + +} // namespace extensions
diff --git a/extensions/common/manifest_handlers/protocol_handler_info.h b/extensions/common/manifest_handlers/protocol_handler_info.h new file mode 100644 index 0000000..bc66ae5 --- /dev/null +++ b/extensions/common/manifest_handlers/protocol_handler_info.h
@@ -0,0 +1,51 @@ +// Copyright 2025 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_COMMON_MANIFEST_HANDLERS_PROTOCOL_HANDLER_INFO_H_ +#define EXTENSIONS_COMMON_MANIFEST_HANDLERS_PROTOCOL_HANDLER_INFO_H_ + +#include <set> +#include <string> +#include <vector> + +#include "base/memory/raw_ptr.h" +#include "base/values.h" +#include "components/services/app_service/public/cpp/protocol_handler_info.h" +#include "extensions/common/error_utils.h" +#include "extensions/common/extension.h" +#include "extensions/common/manifest_handler.h" + +namespace extensions { + +using ProtocolHandlersInfo = std::vector<apps::ProtocolHandlerInfo>; + +struct ProtocolHandlers : public Extension::ManifestData { + ProtocolHandlers(); + ~ProtocolHandlers() override; + + ProtocolHandlersInfo protocol_handlers; + + static const ProtocolHandlersInfo* GetProtocolHandlers( + const Extension& extension); +}; + +// Parses the "protocol_handlers" manifest key. +class ProtocolHandlersParser : public ManifestHandler { + public: + ProtocolHandlersParser(); + + ProtocolHandlersParser(const ProtocolHandlersParser&) = delete; + ProtocolHandlersParser& operator=(const ProtocolHandlersParser&) = delete; + + ~ProtocolHandlersParser() override; + + bool Parse(Extension* extension, std::u16string* error) override; + + private: + base::span<const char* const> Keys() const override; +}; + +} // namespace extensions + +#endif // EXTENSIONS_COMMON_MANIFEST_HANDLERS_PROTOCOL_HANDLER_INFO_H_
diff --git a/extensions/common/manifest_handlers/protocol_handler_manifest_unittest.cc b/extensions/common/manifest_handlers/protocol_handler_manifest_unittest.cc new file mode 100644 index 0000000..e72c947 --- /dev/null +++ b/extensions/common/manifest_handlers/protocol_handler_manifest_unittest.cc
@@ -0,0 +1,310 @@ +// Copyright 2025 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/strings/stringprintf.h" +#include "base/test/scoped_feature_list.h" +#include "base/test/values_test_util.h" +#include "components/version_info/channel.h" +#include "extensions/common/extension_features.h" +#include "extensions/common/features/feature_channel.h" +#include "extensions/common/manifest_constants.h" +#include "extensions/common/manifest_handlers/protocol_handler_info.h" +#include "extensions/common/manifest_test.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace extensions { + +namespace errors = manifest_errors; + +class ManifestProtocolHandlersTest : public ManifestTest { + public: + ManifestProtocolHandlersTest() { + feature_list_.InitAndEnableFeature( + extensions_features::kExtensionProtocolHandlers); + } + + protected: + ManifestData GetManifestData(const char* manifest_part) { + static constexpr char kManifestStub[] = + R"({ + "name": "Test", + "version": "0.0.1", + "manifest_version": 3, + "protocol_handlers": %s + })"; + base::Value manifest_value = + base::test::ParseJson(base::StringPrintf(kManifestStub, manifest_part)); + EXPECT_EQ(base::Value::Type::DICT, manifest_value.type()); + return ManifestData(std::move(manifest_value).TakeDict()); + } + + private: + base::test::ScopedFeatureList feature_list_; + ScopedCurrentChannel current_channel_{version_info::Channel::DEV}; +}; + +TEST_F(ManifestProtocolHandlersTest, GeneralSuccess) { + struct { + const char* title; + const char* protocol_handler; + const size_t expected_handlers_count; + } test_cases[] = { + {"Minimum required entry.", + R"([ + { + "protocol": "web+testingScheme", + "name": "Testing handler", + "uriTemplate": "https:/example.com/%s" + } + ])", + 1}, + {"Multiple protocols.", + R"([ + { + "protocol": "web+testingScheme", + "name": "Testing handler 1", + "uriTemplate": "https:/example1.com/%s" + }, + { + "protocol": "ipfs", + "name": "Testing handler 2", + "uriTemplate": "https:/example2.com/%s" + } + ])", + 2}, + }; + + for (const auto& test_case : test_cases) { + SCOPED_TRACE(test_case.title); + + scoped_refptr<Extension> extension(LoadAndExpectSuccess( + std::move(GetManifestData(test_case.protocol_handler)))); + ASSERT_TRUE(extension); + + const ProtocolHandlersInfo* handlers = + ProtocolHandlers::GetProtocolHandlers(*extension); + ASSERT_TRUE(handlers); + ASSERT_EQ(test_case.expected_handlers_count, handlers->size()); + } +} + +TEST_F(ManifestProtocolHandlersTest, InvalidManifestVersion) { + static constexpr char kManifestStub[] = + R"({ + "name": "Test", + "version": "0.0.1", + "manifest_version": 2, + "protocol_handlers": [ + { + "protocol": "testingScheme", + "name": "Testing handler", + "uriTemplate": "testingURI" + } + ] + })"; + + base::Value manifest_value = base::test::ParseJson(kManifestStub); + auto manifest_data = ManifestData(std::move(manifest_value).TakeDict()); + + LoadAndExpectWarning( + manifest_data, + "'protocol_handlers' requires manifest version of at least 3."); +} + +TEST_F(ManifestProtocolHandlersTest, InvalidManifestSyntax) { + struct { + const char* title; + const char* protocol_handler; + std::vector<std::string> expected_warnings; + } test_cases[] = { + { + "Invalid declaration: empty list.", + R"([])", + {errors::kInvalidProtocolHandlersEmpty}, + }, + // protocol invalid cases. + { + "Missing 'protocol' key: not declared.", + R"([ + { + "name": "Testing handler", + "uriTemplate": "testingURI" + } + ])", + {"Error at key 'protocol_handlers'. Parsing array failed at index 0: " + "'protocol' is required"}, + }, + { + "Missing 'name' key: not declared.", + R"([ + { + "protocol": "web+testingScheme", + "uriTemplate": "testingURI" + } + ])", + {"Error at key 'protocol_handlers'. Parsing array failed at index 0: " + "'name' is required"}, + }, + { + "Missing protocol key: wrong key.", + R"([ + { + "protocol": "testingScheme", + "name": "Testing handler 1", + "uriTemplate": "testingURI" + }, + { + "scheme": "testingScheme", + "name": "Testing handler 2", + "uriTemplate": "testingURI" + } + ])", + {"Error at key 'protocol_handlers'. Parsing array failed at index 1: " + "'protocol' is required"}, + }, + { + "Missing uriTemplate key: not declared.", + R"([ + { + "name": "Testing handler 1", + "protocol": "testingScheme", + } + ])", + {"Error at key 'protocol_handlers'. Parsing array failed at index 0: " + "'uriTemplate' is required"}, + }, + { + "Missing uriTemplate key: wrong key.", + R"([ + { + "protocol": "testingURI", + "name": "Testing handler 1", + "uriTemplate": "testingScheme" + }, + { + "protocol": "testingScheme", + "name": "Testing handler 1", + "url": "testingURI" + } + ])", + {"Error at key 'protocol_handlers'. Parsing array failed at index 1: " + "'uriTemplate' is required"}, + }, + }; + + for (const auto& test_case : test_cases) { + SCOPED_TRACE(test_case.title); + LoadAndExpectWarnings(GetManifestData(test_case.protocol_handler), + test_case.expected_warnings); + } +} + +TEST_F(ManifestProtocolHandlersTest, InvalidProtocolHandler) { + struct { + const char* title; + const char* protocol_handler; + std::vector<std::string> expected_warnings; + } test_cases[] = { + { + "Registering handler for an unprefix scheme.", + R"([ + { + "protocol": "glsearch", + "name": "Testing handler", + "uriTemplate": "https://www.google.com/search?q=%s" + } + ])", + {errors::kProtocolHandlerSchemeNotInSafeList}, + }, + { + "Protocol handlers with unprefix scheme and an empty name.", + R"([ + { + "protocol": "glsearch", + "name": "", + "uriTemplate": "https://www.google.com/search?q=%s" + } + ])", + {errors::kProtocolHandlerEmptyName, + errors::kProtocolHandlerSchemeNotInSafeList}, + }, + { + "Registering handler for a reserved scheme.", + R"([ + { + "protocol": "https", + "name": "Testing handler", + "uriTemplate": "https://www.google.com/search?q=%s" + } + ])", + {errors::kProtocolHandlerSchemeNotInSafeList}, + }, + { + "Custom handler URL without '%s' token.", + R"([ + { + "protocol": "ssh", + "name": "Testing handler", + "uriTemplate": "https://www.google.com/search" + } + ])", + {errors::kProtocolHandlerUrlTokenMissing}, + }, + { + "Custom handler URL with an invalid syntax.", + R"([ + { + "protocol": "web+glsearch", + "name": "Testing handler", + "uriTemplate": "https:/example.com:abc/path" + } + ])", + {errors::kProtocolHandlerUrlInvalidSyntax}, + }, + { + "Custom handler URL with an untrustworthy scheme.", + R"([ + { + "protocol": "web+glsearch", + "name": "Testing handler", + "uriTemplate": "http:/example.com/%s" + } + ])", + {errors::kProtocolHandlerUntrustworthyScheme}, + }, + { + "Custom handler URL with an untrustworthy scheme and an Opaque " + "Origin.", + R"([ + { + "protocol": "web+glsearch", + "name": "Testing handler", + "uriTemplate": "data:/%s" + } + ])", + {errors::kProtocolHandlerUntrustworthyScheme, + errors::kProtocolHandlerOpaqueOrigin}, + }, + { + "Custom handler URL with an invalid syntax and untrustworthy scheme.", + R"([ + { + "protocol": "web+glsearch", + "name": "Testing handler", + "uriTemplate": "http://[v8.:::]//url=%s" + } + ])", + {errors::kProtocolHandlerUrlInvalidSyntax, + errors::kProtocolHandlerUntrustworthyScheme}, + }, + }; + + for (const auto& test_case : test_cases) { + SCOPED_TRACE(test_case.title); + LoadAndExpectWarnings(GetManifestData(test_case.protocol_handler), + test_case.expected_warnings); + } +} + +} // namespace extensions
diff --git a/google_apis/gaia/oauth2_mint_access_token_fetcher_adapter.cc b/google_apis/gaia/oauth2_mint_access_token_fetcher_adapter.cc index b457d73..a209e3f 100644 --- a/google_apis/gaia/oauth2_mint_access_token_fetcher_adapter.cc +++ b/google_apis/gaia/oauth2_mint_access_token_fetcher_adapter.cc
@@ -9,6 +9,7 @@ #include <vector> #include "base/check.h" +#include "base/check_is_test.h" #include "base/containers/span.h" #include "base/memory/ref_counted.h" #include "base/metrics/histogram_functions.h" @@ -23,9 +24,14 @@ #include "services/network/public/cpp/shared_url_loader_factory.h" namespace { -void RecordEncryptionError(TokenBindingResponseEncryptionError error) { +void RecordEncryptionError(TokenBindingResponseEncryptionError error, + bool is_refresh_token_bound) { + static constexpr std::string_view kBoundHistogram = + "Signin.OAuth2MintToken.BoundFetchEncryptionError"; + static constexpr std::string_view kUnboundHistogram = + "Signin.OAuth2MintToken.UnboundFetchEncryptionError"; base::UmaHistogramEnumeration( - "Signin.OAuth2MintToken.BoundFetchEncryptionError", error); + is_refresh_token_bound ? kBoundHistogram : kUnboundHistogram, error); } std::string_view GetAssertionTypeHistogramSuffix( @@ -47,14 +53,20 @@ } void RecordFetchAuthError(const GoogleServiceAuthError& error, + bool is_refresh_token_bound, const std::string& binding_key_assertion) { - static constexpr std::string_view kFetchAuthErrorHistogram = + static constexpr std::string_view kBoundFetchAuthErrorHistogram = "Signin.OAuth2MintToken.BoundFetchAuthError"; - base::UmaHistogramEnumeration(kFetchAuthErrorHistogram, error.state(), + static constexpr std::string_view kUnboundFetchAuthErrorHistogram = + "Signin.OAuth2MintToken.UnboundFetchAuthError"; + std::string_view histogram_name = is_refresh_token_bound + ? kBoundFetchAuthErrorHistogram + : kUnboundFetchAuthErrorHistogram; + base::UmaHistogramEnumeration(histogram_name, error.state(), GoogleServiceAuthError::NUM_STATES); base::UmaHistogramEnumeration( - base::StrCat({kFetchAuthErrorHistogram, + base::StrCat({histogram_name, GetAssertionTypeHistogramSuffix(binding_key_assertion)}), error.state(), GoogleServiceAuthError::NUM_STATES); } @@ -65,6 +77,7 @@ scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory, const GaiaId& user_gaia_id, const std::string& refresh_token, + bool is_refresh_token_bound, const std::string& device_id, const std::string& client_version, const std::string& client_channel) @@ -72,6 +85,7 @@ url_loader_factory_(std::move(url_loader_factory)), user_gaia_id_(user_gaia_id), refresh_token_(refresh_token), + is_refresh_token_bound_(is_refresh_token_bound), device_id_(device_id), client_version_(client_version), client_channel_(client_channel) {} @@ -83,19 +97,19 @@ const std::string& client_id, const std::string& client_secret, const std::vector<std::string>& scopes) { - if (binding_key_assertion_.empty()) { - // The sentinel should be attached only if the `refresh_token_` is bound. - // For now, `OAuth2MintAccessTokenFetcherAdapter` is only used with bound - // tokens, so we can attach it unconditionally. This needs to be revised in - // the future. + if (is_refresh_token_bound_ && binding_key_assertion_.empty()) { binding_key_assertion_ = GaiaConstants::kTokenBindingAssertionSentinel; } - std::string bound_oauth_token = gaia::CreateBoundOAuthToken( - user_gaia_id_, refresh_token_, binding_key_assertion_); + std::string bound_oauth_token = + is_refresh_token_bound_ + ? gaia::CreateBoundOAuthToken(user_gaia_id_, refresh_token_, + binding_key_assertion_) + : std::string(); auto params = OAuth2MintTokenFlow::Parameters::CreateForClientFlow( client_id, std::vector<std::string_view>(scopes.begin(), scopes.end()), client_version_, client_channel_, device_id_, bound_oauth_token); if (mint_token_flow_factory_for_testing_) { + CHECK_IS_TEST(); mint_token_flow_ = mint_token_flow_factory_for_testing_.Run(this, std::move(params)); } else { @@ -112,6 +126,7 @@ void OAuth2MintAccessTokenFetcherAdapter::SetBindingKeyAssertion( std::string assertion) { CHECK(!assertion.empty()); + CHECK(is_refresh_token_bound_); binding_key_assertion_ = std::move(assertion); } @@ -133,7 +148,8 @@ if (result.is_token_encrypted) { if (token_decryptor_.is_null()) { RecordEncryptionError( - TokenBindingResponseEncryptionError::kResponseUnexpectedlyEncrypted); + TokenBindingResponseEncryptionError::kResponseUnexpectedlyEncrypted, + is_refresh_token_bound_); RecordMetricsAndFireError( GoogleServiceAuthError::FromUnexpectedServiceResponse( "Unexpectedly received an encrypted token")); @@ -142,19 +158,22 @@ std::string decryption_result = token_decryptor_.Run(result.access_token); if (decryption_result.empty()) { RecordEncryptionError( - TokenBindingResponseEncryptionError::kDecryptionFailed); + TokenBindingResponseEncryptionError::kDecryptionFailed, + is_refresh_token_bound_); RecordMetricsAndFireError( GoogleServiceAuthError::FromUnexpectedServiceResponse( "Failed to decrypt token")); return; } else { RecordEncryptionError( - TokenBindingResponseEncryptionError::kSuccessfullyDecrypted); + TokenBindingResponseEncryptionError::kSuccessfullyDecrypted, + is_refresh_token_bound_); } decrypted_token = std::move(decryption_result); } else { RecordEncryptionError( - TokenBindingResponseEncryptionError::kSuccessNoEncryption); + TokenBindingResponseEncryptionError::kSuccessNoEncryption, + is_refresh_token_bound_); decrypted_token = std::move(result.access_token); } // The token will expire in `time_to_live` seconds. Take a 10% error margin to @@ -164,7 +183,7 @@ response_builder.WithAccessToken(decrypted_token) .WithExpirationTime(expiration_time); RecordFetchAuthError(GoogleServiceAuthError::AuthErrorNone(), - binding_key_assertion_); + is_refresh_token_bound_, binding_key_assertion_); FireOnGetTokenSuccess(response_builder.build()); } void OAuth2MintAccessTokenFetcherAdapter::OnMintTokenFailure( @@ -181,6 +200,6 @@ void OAuth2MintAccessTokenFetcherAdapter::RecordMetricsAndFireError( const GoogleServiceAuthError& error) { - RecordFetchAuthError(error, binding_key_assertion_); + RecordFetchAuthError(error, is_refresh_token_bound_, binding_key_assertion_); FireOnGetTokenFailure(error); }
diff --git a/google_apis/gaia/oauth2_mint_access_token_fetcher_adapter.h b/google_apis/gaia/oauth2_mint_access_token_fetcher_adapter.h index fc80f42c..6c3da1a 100644 --- a/google_apis/gaia/oauth2_mint_access_token_fetcher_adapter.h +++ b/google_apis/gaia/oauth2_mint_access_token_fetcher_adapter.h
@@ -39,6 +39,7 @@ scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory, const GaiaId& user_gaia_id, const std::string& refresh_token, + bool is_refresh_token_bound, const std::string& device_id, const std::string& client_version, const std::string& client_channel); @@ -76,6 +77,7 @@ const scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory_; const GaiaId user_gaia_id_; const std::string refresh_token_; + const bool is_refresh_token_bound_; const std::string device_id_; const std::string client_version_; const std::string client_channel_;
diff --git a/google_apis/gaia/oauth2_mint_access_token_fetcher_adapter_unittest.cc b/google_apis/gaia/oauth2_mint_access_token_fetcher_adapter_unittest.cc index f2b5755..c630c466 100644 --- a/google_apis/gaia/oauth2_mint_access_token_fetcher_adapter_unittest.cc +++ b/google_apis/gaia/oauth2_mint_access_token_fetcher_adapter_unittest.cc
@@ -50,17 +50,21 @@ constexpr char kAssertionSentinel[] = "DBSC_CHALLENGE_IF_REQUIRED"; -constexpr char kFetchAuthErrorHistogram[] = +constexpr char kBoundFetchAuthErrorHistogram[] = "Signin.OAuth2MintToken.BoundFetchAuthError"; -constexpr std::string_view kFetchAuthErrorChallengeSentinelHistogram = - "Signin.OAuth2MintToken.BoundFetchAuthError.ChallengeSentinel"; -constexpr std::string_view kFetchAuthErrorAssertionFailedHistogram = - "Signin.OAuth2MintToken.BoundFetchAuthError.AssertionFailed"; -constexpr std::string_view kFetchAuthErrorSignedAssertionHistogram = - "Signin.OAuth2MintToken.BoundFetchAuthError.SignedAssertion"; -constexpr char kFetchEncryptionErrorHistogram[] = +constexpr std::string_view kChallengeSentinelHistogramSuffix = + ".ChallengeSentinel"; +constexpr std::string_view kAssertionFailedHistogramSuffix = ".AssertionFailed"; +constexpr std::string_view kSignedAssertionHistogramSuffix = ".SignedAssertion"; +constexpr std::string_view kNoAssertionHistogramSuffix = ".NoAssertion"; +constexpr std::string_view kBoundFetchEncryptionErrorHistogram = "Signin.OAuth2MintToken.BoundFetchEncryptionError"; +constexpr char kUnboundFetchAuthErrorHistogram[] = + "Signin.OAuth2MintToken.UnboundFetchAuthError"; +constexpr std::string_view kUnboundFetchEncryptionErrorHistogram = + "Signin.OAuth2MintToken.UnboundFetchEncryptionError"; + class MockOAuth2AccessTokenConsumer : public OAuth2AccessTokenConsumer { public: MockOAuth2AccessTokenConsumer() = default; @@ -160,6 +164,20 @@ expected.bound_oauth_token)); } +OAuth2MintTokenFlow::Parameters GetTestOAuth2MintTokenFlowParameters( + std::string_view bound_oauth_token) { + OAuth2MintTokenFlow::Parameters params; + params.client_id = kTestClientId; + params.version = kTestVersion; + params.channel = kTestChannel; + params.device_id = kTestDeviceId; + params.enable_granular_permissions = false; + params.mode = OAuth2MintTokenFlow::MODE_MINT_TOKEN_NO_FORCE; + params.scopes = {kTestScope}; + params.bound_oauth_token = bound_oauth_token; + return params; +} + } // namespace class OAuth2MintAccessTokenFetcherAdapterTest : public testing::Test { @@ -167,11 +185,12 @@ OAuth2MintAccessTokenFetcherAdapterTest() = default; ~OAuth2MintAccessTokenFetcherAdapterTest() override = default; - std::unique_ptr<OAuth2MintAccessTokenFetcherAdapter> CreateFetcher() { + std::unique_ptr<OAuth2MintAccessTokenFetcherAdapter> CreateFetcher( + bool is_refresh_token_bound = true) { auto fetcher = std::make_unique<OAuth2MintAccessTokenFetcherAdapter>( &mock_consumer_, url_loader_factory_.GetSafeWeakWrapper(), - kTestUserGaiaId, kTestRefreshToken, kTestDeviceId, kTestVersion, - kTestChannel); + kTestUserGaiaId, kTestRefreshToken, is_refresh_token_bound, + kTestDeviceId, kTestVersion, kTestChannel); fetcher->SetOAuth2MintTokenFlowFactoryForTesting(base::BindRepeating( &OAuth2MintAccessTokenFetcherAdapterTest::CreateMockFlow, base::Unretained(this))); @@ -189,12 +208,26 @@ return mock_flow; } - void VerifyFetchAuthErrorHistograms(GoogleServiceAuthError::State error, - std::string_view suffixed_histogram) { - histogram_tester().ExpectUniqueSample(kFetchAuthErrorHistogram, error, + void VerifyBoundFetchAuthErrorHistograms(GoogleServiceAuthError::State error, + std::string_view histogram_suffix) { + histogram_tester().ExpectUniqueSample(kBoundFetchAuthErrorHistogram, error, /*expected_bucket_count=*/1); - histogram_tester().ExpectUniqueSample(suffixed_histogram, error, + histogram_tester().ExpectUniqueSample( + base::StrCat({kBoundFetchAuthErrorHistogram, histogram_suffix}), error, + /*expected_bucket_count=*/1); + } + + void VerifyUnboundFetchAuthErrorHistograms( + GoogleServiceAuthError::State error) { + histogram_tester().ExpectUniqueSample(kUnboundFetchAuthErrorHistogram, + error, /*expected_bucket_count=*/1); + // Only the ".NoAssertion" version is expected for unbound fetches. + histogram_tester().ExpectUniqueSample( + base::StrCat( + {kUnboundFetchAuthErrorHistogram, kNoAssertionHistogramSuffix}), + error, + /*expected_bucket_count=*/1); } MockOAuth2AccessTokenConsumer* mock_consumer() { return &mock_consumer_; } @@ -219,16 +252,9 @@ // Need to start a fetcher to create a mock flow. fetcher->Start(kTestClientId, kTestClientSecret, {kTestScope}); EXPECT_TRUE(mock_flow()); - OAuth2MintTokenFlow::Parameters expected_params; - expected_params.client_id = kTestClientId; - expected_params.version = kTestVersion; - expected_params.channel = kTestChannel; - expected_params.device_id = kTestDeviceId; - expected_params.enable_granular_permissions = false; - expected_params.mode = OAuth2MintTokenFlow::MODE_MINT_TOKEN_NO_FORCE; - expected_params.scopes = {kTestScope}; - expected_params.bound_oauth_token = gaia::CreateBoundOAuthToken( - kTestUserGaiaId, kTestRefreshToken, kAssertionSentinel); + OAuth2MintTokenFlow::Parameters expected_params = + GetTestOAuth2MintTokenFlowParameters(gaia::CreateBoundOAuthToken( + kTestUserGaiaId, kTestRefreshToken, kAssertionSentinel)); EXPECT_THAT(mock_flow()->params(), ParamsEq(expected_params)); } @@ -237,16 +263,19 @@ fetcher->SetBindingKeyAssertion(kTestAssertion); fetcher->Start(kTestClientId, kTestClientSecret, {kTestScope}); EXPECT_TRUE(mock_flow()); - OAuth2MintTokenFlow::Parameters expected_params; - expected_params.client_id = kTestClientId; - expected_params.version = kTestVersion; - expected_params.channel = kTestChannel; - expected_params.device_id = kTestDeviceId; - expected_params.enable_granular_permissions = false; - expected_params.mode = OAuth2MintTokenFlow::MODE_MINT_TOKEN_NO_FORCE; - expected_params.scopes = {kTestScope}; - expected_params.bound_oauth_token = gaia::CreateBoundOAuthToken( - kTestUserGaiaId, kTestRefreshToken, kTestAssertion); + OAuth2MintTokenFlow::Parameters expected_params = + GetTestOAuth2MintTokenFlowParameters(gaia::CreateBoundOAuthToken( + kTestUserGaiaId, kTestRefreshToken, kTestAssertion)); + EXPECT_THAT(mock_flow()->params(), ParamsEq(expected_params)); +} + +TEST_F(OAuth2MintAccessTokenFetcherAdapterTest, ParamsUnbound) { + auto fetcher = CreateFetcher(/*is_refresh_token_bound=*/false); + // Need to start a fetcher to create a mock flow. + fetcher->Start(kTestClientId, kTestClientSecret, {kTestScope}); + EXPECT_TRUE(mock_flow()); + OAuth2MintTokenFlow::Parameters expected_params = + GetTestOAuth2MintTokenFlowParameters(/*bound_oauth_token=*/std::string()); EXPECT_THAT(mock_flow()->params(), ParamsEq(expected_params)); } @@ -259,8 +288,8 @@ mock_flow()->SimulateMintTokenSuccess(kTestAccessToken, {kTestScope}, kTimeToLive.InSeconds(), /*is_encrypted=*/false); - VerifyFetchAuthErrorHistograms(GoogleServiceAuthError::NONE, - kFetchAuthErrorChallengeSentinelHistogram); + VerifyBoundFetchAuthErrorHistograms(GoogleServiceAuthError::NONE, + kChallengeSentinelHistogramSuffix); } TEST_F(OAuth2MintAccessTokenFetcherAdapterTest, SuccessWithSignedAssertion) { @@ -273,8 +302,8 @@ mock_flow()->SimulateMintTokenSuccess(kTestAccessToken, {kTestScope}, kTimeToLive.InSeconds(), /*is_encrypted=*/false); - VerifyFetchAuthErrorHistograms(GoogleServiceAuthError::NONE, - kFetchAuthErrorSignedAssertionHistogram); + VerifyBoundFetchAuthErrorHistograms(GoogleServiceAuthError::NONE, + kSignedAssertionHistogramSuffix); } TEST_F(OAuth2MintAccessTokenFetcherAdapterTest, SuccessWithFailedAssertion) { @@ -288,8 +317,8 @@ mock_flow()->SimulateMintTokenSuccess(kTestAccessToken, {kTestScope}, kTimeToLive.InSeconds(), /*is_encrypted=*/false); - VerifyFetchAuthErrorHistograms(GoogleServiceAuthError::NONE, - kFetchAuthErrorAssertionFailedHistogram); + VerifyBoundFetchAuthErrorHistograms(GoogleServiceAuthError::NONE, + kAssertionFailedHistogramSuffix); } TEST_F(OAuth2MintAccessTokenFetcherAdapterTest, SuccessWithEncryption) { @@ -307,10 +336,10 @@ mock_flow()->SimulateMintTokenSuccess(kTestEncryptedToken, {kTestScope}, kTimeToLive.InSeconds(), /*is_encrypted=*/true); - VerifyFetchAuthErrorHistograms(GoogleServiceAuthError::NONE, - kFetchAuthErrorChallengeSentinelHistogram); + VerifyBoundFetchAuthErrorHistograms(GoogleServiceAuthError::NONE, + kChallengeSentinelHistogramSuffix); histogram_tester().ExpectUniqueSample( - kFetchEncryptionErrorHistogram, + kBoundFetchEncryptionErrorHistogram, TokenBindingResponseEncryptionError::kSuccessfullyDecrypted, /*expected_bucket_count=*/1); } @@ -328,10 +357,26 @@ mock_flow()->SimulateMintTokenSuccess(kTestAccessToken, {kTestScope}, kTimeToLive.InSeconds(), /*is_encrypted=*/false); - VerifyFetchAuthErrorHistograms(GoogleServiceAuthError::NONE, - kFetchAuthErrorChallengeSentinelHistogram); + VerifyBoundFetchAuthErrorHistograms(GoogleServiceAuthError::NONE, + kChallengeSentinelHistogramSuffix); histogram_tester().ExpectUniqueSample( - kFetchEncryptionErrorHistogram, + kBoundFetchEncryptionErrorHistogram, + TokenBindingResponseEncryptionError::kSuccessNoEncryption, + /*expected_bucket_count=*/1); +} + +TEST_F(OAuth2MintAccessTokenFetcherAdapterTest, SuccessUnbound) { + auto fetcher = CreateFetcher(/*is_refresh_token_bound=*/false); + fetcher->Start(kTestClientId, kTestClientSecret, {kTestScope}); + base::TimeDelta kTimeToLive = base::Hours(4); + EXPECT_CALL(*mock_consumer(), OnGetTokenSuccess(HasAccessTokenWithTtl( + kTestAccessToken, kTimeToLive))); + mock_flow()->SimulateMintTokenSuccess(kTestAccessToken, {kTestScope}, + kTimeToLive.InSeconds(), + /*is_encrypted=*/false); + VerifyUnboundFetchAuthErrorHistograms(GoogleServiceAuthError::NONE); + histogram_tester().ExpectUniqueSample( + kUnboundFetchEncryptionErrorHistogram, TokenBindingResponseEncryptionError::kSuccessNoEncryption, /*expected_bucket_count=*/1); } @@ -345,9 +390,22 @@ CREDENTIALS_REJECTED_BY_SERVER); EXPECT_CALL(*mock_consumer(), OnGetTokenFailure(error)); mock_flow()->SimulateMintTokenFailure(error); - VerifyFetchAuthErrorHistograms( + VerifyBoundFetchAuthErrorHistograms( GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS, - kFetchAuthErrorChallengeSentinelHistogram); + kChallengeSentinelHistogramSuffix); +} + +TEST_F(OAuth2MintAccessTokenFetcherAdapterTest, FailureUnbound) { + auto fetcher = CreateFetcher(/*is_refresh_token_bound=*/false); + fetcher->Start(kTestClientId, kTestClientSecret, {kTestScope}); + GoogleServiceAuthError error = + GoogleServiceAuthError::FromInvalidGaiaCredentialsReason( + GoogleServiceAuthError::InvalidGaiaCredentialsReason:: + CREDENTIALS_REJECTED_BY_SERVER); + EXPECT_CALL(*mock_consumer(), OnGetTokenFailure(error)); + mock_flow()->SimulateMintTokenFailure(error); + VerifyUnboundFetchAuthErrorHistograms( + GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS); } TEST_F(OAuth2MintAccessTokenFetcherAdapterTest, ChallengeRequired) { @@ -357,9 +415,9 @@ GoogleServiceAuthError::FromTokenBindingChallenge("challenge"); EXPECT_CALL(*mock_consumer(), OnGetTokenFailure(error)); mock_flow()->SimulateMintTokenFailure(error); - VerifyFetchAuthErrorHistograms( + VerifyBoundFetchAuthErrorHistograms( GoogleServiceAuthError::CHALLENGE_RESPONSE_REQUIRED, - kFetchAuthErrorChallengeSentinelHistogram); + kChallengeSentinelHistogramSuffix); } TEST_F(OAuth2MintAccessTokenFetcherAdapterTest, DecryptionFailure) { @@ -379,11 +437,11 @@ mock_flow()->SimulateMintTokenSuccess(kTestEncryptedToken, {kTestScope}, kTimeToLive.InSeconds(), /*is_encrypted=*/true); - VerifyFetchAuthErrorHistograms( + VerifyBoundFetchAuthErrorHistograms( GoogleServiceAuthError::UNEXPECTED_SERVICE_RESPONSE, - kFetchAuthErrorChallengeSentinelHistogram); + kChallengeSentinelHistogramSuffix); histogram_tester().ExpectUniqueSample( - kFetchEncryptionErrorHistogram, + kBoundFetchEncryptionErrorHistogram, TokenBindingResponseEncryptionError::kDecryptionFailed, /*expected_bucket_count=*/1); } @@ -399,11 +457,11 @@ mock_flow()->SimulateMintTokenSuccess(kTestAccessToken, {kTestScope}, kTimeToLive.InSeconds(), /*is_encrypted=*/true); - VerifyFetchAuthErrorHistograms( + VerifyBoundFetchAuthErrorHistograms( GoogleServiceAuthError::UNEXPECTED_SERVICE_RESPONSE, - kFetchAuthErrorChallengeSentinelHistogram); + kChallengeSentinelHistogramSuffix); histogram_tester().ExpectUniqueSample( - kFetchEncryptionErrorHistogram, + kBoundFetchEncryptionErrorHistogram, TokenBindingResponseEncryptionError::kResponseUnexpectedlyEncrypted, /*expected_bucket_count=*/1); } @@ -416,9 +474,9 @@ "state", &GoogleServiceAuthError::state, GoogleServiceAuthError::SCOPE_LIMITED_UNRECOVERABLE_ERROR))); mock_flow()->SimulateRemoteConsentSuccess(RemoteConsentResolutionData()); - VerifyFetchAuthErrorHistograms( + VerifyBoundFetchAuthErrorHistograms( GoogleServiceAuthError::SCOPE_LIMITED_UNRECOVERABLE_ERROR, - kFetchAuthErrorChallengeSentinelHistogram); + kChallengeSentinelHistogramSuffix); } TEST_F(OAuth2MintAccessTokenFetcherAdapterTest, CancelRequest) { @@ -427,6 +485,6 @@ fetcher->CancelRequest(); EXPECT_FALSE(mock_flow()); EXPECT_THAT( - histogram_tester().GetTotalCountsForPrefix(kFetchAuthErrorHistogram), + histogram_tester().GetTotalCountsForPrefix(kBoundFetchAuthErrorHistogram), testing::IsEmpty()); }
diff --git a/google_apis/gaia/oauth2_mint_token_flow_unittest.cc b/google_apis/gaia/oauth2_mint_token_flow_unittest.cc index ae380c3..d789128 100644 --- a/google_apis/gaia/oauth2_mint_token_flow_unittest.cc +++ b/google_apis/gaia/oauth2_mint_token_flow_unittest.cc
@@ -664,7 +664,7 @@ TEST_F(OAuth2MintTokenFlowTest, CreateApiCallBodyClientAccessTokenFlowWithBoundOAuthToken) { - CreateClientFlow(/*bound_oauth_token=*/std::string()); + CreateClientFlow("test_bound_oauth_token"); std::string body = flow_->CreateApiCallBody(); std::string expected_body( "force=false"
diff --git a/gpu/command_buffer/client/fake_gpu_memory_buffer.cc b/gpu/command_buffer/client/fake_gpu_memory_buffer.cc index fd44558..6fa22a4 100644 --- a/gpu/command_buffer/client/fake_gpu_memory_buffer.cc +++ b/gpu/command_buffer/client/fake_gpu_memory_buffer.cc
@@ -141,10 +141,6 @@ .width(); } -gfx::GpuMemoryBufferId FakeGpuMemoryBuffer::GetId() const { - return handle_.id; -} - gfx::GpuMemoryBufferType FakeGpuMemoryBuffer::GetType() const { return gfx::SHARED_MEMORY_BUFFER; } @@ -153,10 +149,4 @@ return handle_.Clone(); } -void FakeGpuMemoryBuffer::OnMemoryDump( - base::trace_event::ProcessMemoryDump* pmd, - const base::trace_event::MemoryAllocatorDumpGuid& buffer_dump_guid, - uint64_t tracing_process_id, - int importance) const {} - } // namespace gpu
diff --git a/gpu/command_buffer/client/fake_gpu_memory_buffer.h b/gpu/command_buffer/client/fake_gpu_memory_buffer.h index 1f5cc24d..b81a014e 100644 --- a/gpu/command_buffer/client/fake_gpu_memory_buffer.h +++ b/gpu/command_buffer/client/fake_gpu_memory_buffer.h
@@ -63,14 +63,8 @@ gfx::Size GetSize() const override; gfx::BufferFormat GetFormat() const override; int stride(size_t plane) const override; - gfx::GpuMemoryBufferId GetId() const override; gfx::GpuMemoryBufferType GetType() const override; gfx::GpuMemoryBufferHandle CloneHandle() const override; - void OnMemoryDump( - base::trace_event::ProcessMemoryDump* pmd, - const base::trace_event::MemoryAllocatorDumpGuid& buffer_dump_guid, - uint64_t tracing_process_id, - int importance) const override; private: gfx::Size size_;
diff --git a/gpu/command_buffer/client/test_gpu_memory_buffer_manager.cc b/gpu/command_buffer/client/test_gpu_memory_buffer_manager.cc index 4f2f548..f2b6c94 100644 --- a/gpu/command_buffer/client/test_gpu_memory_buffer_manager.cc +++ b/gpu/command_buffer/client/test_gpu_memory_buffer_manager.cc
@@ -72,18 +72,12 @@ gfx::GpuMemoryBufferType GetType() const override { return gfx::SHARED_MEMORY_BUFFER; } - gfx::GpuMemoryBufferId GetId() const override { return id_; } gfx::GpuMemoryBufferHandle CloneHandle() const override { gfx::GpuMemoryBufferHandle handle(region_.Duplicate()); handle.offset = base::checked_cast<uint32_t>(offset_); handle.stride = base::checked_cast<uint32_t>(stride_); return handle; } - void OnMemoryDump( - base::trace_event::ProcessMemoryDump* pmd, - const base::trace_event::MemoryAllocatorDumpGuid& buffer_dump_guid, - uint64_t tracing_process_id, - int importance) const override {} private: gfx::GpuMemoryBufferId id_; @@ -113,18 +107,12 @@ int stride(size_t plane) const override { return client_buffer_->stride(plane); } - gfx::GpuMemoryBufferId GetId() const override { return id_; } gfx::GpuMemoryBufferType GetType() const override { return client_buffer_->GetType(); } gfx::GpuMemoryBufferHandle CloneHandle() const override { return client_buffer_->CloneHandle(); } - void OnMemoryDump( - base::trace_event::ProcessMemoryDump* pmd, - const base::trace_event::MemoryAllocatorDumpGuid& buffer_dump_guid, - uint64_t tracing_process_id, - int importance) const override {} private: gfx::GpuMemoryBufferId id_;
diff --git a/gpu/command_buffer/tests/gl_manager.cc b/gpu/command_buffer/tests/gl_manager.cc index 8bbbea8..f302ac35 100644 --- a/gpu/command_buffer/tests/gl_manager.cc +++ b/gpu/command_buffer/tests/gl_manager.cc
@@ -97,16 +97,10 @@ DCHECK_LT(plane, gfx::NumberOfPlanesForLinearBufferFormat(format_)); return gfx::RowSizeForBufferFormat(size_.width(), format_, plane); } - gfx::GpuMemoryBufferId GetId() const override { NOTREACHED(); } gfx::GpuMemoryBufferType GetType() const override { return gfx::SHARED_MEMORY_BUFFER; } gfx::GpuMemoryBufferHandle CloneHandle() const override { NOTREACHED(); } - void OnMemoryDump( - base::trace_event::ProcessMemoryDump* pmd, - const base::trace_event::MemoryAllocatorDumpGuid& buffer_dump_guid, - uint64_t tracing_process_id, - int importance) const override {} base::RefCountedBytes* bytes() { return bytes_.get(); } @@ -148,16 +142,10 @@ DCHECK_LT(plane, gfx::NumberOfPlanesForLinearBufferFormat(format_)); return IOSurfaceGetWidthOfPlane(iosurface_.get(), plane); } - gfx::GpuMemoryBufferId GetId() const override { NOTREACHED(); } gfx::GpuMemoryBufferType GetType() const override { return gfx::IO_SURFACE_BUFFER; } gfx::GpuMemoryBufferHandle CloneHandle() const override { NOTREACHED(); } - void OnMemoryDump( - base::trace_event::ProcessMemoryDump* pmd, - const base::trace_event::MemoryAllocatorDumpGuid& buffer_dump_guid, - uint64_t tracing_process_id, - int importance) const override {} IOSurfaceRef iosurface() { return iosurface_.get(); }
diff --git a/gpu/ipc/common/gpu_memory_buffer_impl.cc b/gpu/ipc/common/gpu_memory_buffer_impl.cc index 18db690..a6a7392 100644 --- a/gpu/ipc/common/gpu_memory_buffer_impl.cc +++ b/gpu/ipc/common/gpu_memory_buffer_impl.cc
@@ -4,9 +4,6 @@ #include "gpu/ipc/common/gpu_memory_buffer_impl.h" -#include "base/trace_event/memory_allocator_dump_guid.h" -#include "base/trace_event/process_memory_dump.h" - namespace gpu { GpuMemoryBufferImpl::GpuMemoryBufferImpl(gfx::GpuMemoryBufferId id, @@ -34,21 +31,6 @@ return format_; } -gfx::GpuMemoryBufferId GpuMemoryBufferImpl::GetId() const { - return id_; -} - -void GpuMemoryBufferImpl::OnMemoryDump( - base::trace_event::ProcessMemoryDump* pmd, - const base::trace_event::MemoryAllocatorDumpGuid& buffer_dump_guid, - uint64_t tracing_process_id, - int importance) const { - auto shared_buffer_guid = - gfx::GetGenericSharedGpuMemoryGUIDForTracing(tracing_process_id, GetId()); - pmd->CreateSharedGlobalAllocatorDump(shared_buffer_guid); - pmd->AddOwnershipEdge(buffer_dump_guid, shared_buffer_guid, importance); -} - void GpuMemoryBufferImpl::AssertMapped() { #if DCHECK_IS_ON() base::AutoLock auto_lock(map_lock_);
diff --git a/gpu/ipc/common/gpu_memory_buffer_impl.h b/gpu/ipc/common/gpu_memory_buffer_impl.h index 98e18cc..1c00848 100644 --- a/gpu/ipc/common/gpu_memory_buffer_impl.h +++ b/gpu/ipc/common/gpu_memory_buffer_impl.h
@@ -33,12 +33,6 @@ // Overridden from gfx::GpuMemoryBuffer: gfx::Size GetSize() const override; gfx::BufferFormat GetFormat() const override; - gfx::GpuMemoryBufferId GetId() const override; - void OnMemoryDump( - base::trace_event::ProcessMemoryDump* pmd, - const base::trace_event::MemoryAllocatorDumpGuid& buffer_dump_guid, - uint64_t tracing_process_id, - int importance) const override; protected: GpuMemoryBufferImpl(gfx::GpuMemoryBufferId id,
diff --git a/gpu/ipc/common/gpu_memory_buffer_impl_native_pixmap.h b/gpu/ipc/common/gpu_memory_buffer_impl_native_pixmap.h index 84695be..c61c720 100644 --- a/gpu/ipc/common/gpu_memory_buffer_impl_native_pixmap.h +++ b/gpu/ipc/common/gpu_memory_buffer_impl_native_pixmap.h
@@ -13,6 +13,10 @@ #include "gpu/ipc/common/gpu_ipc_common_export.h" #include "gpu/ipc/common/gpu_memory_buffer_impl.h" +namespace arc { +class GpuArcVideoEncodeAccelerator; +} + namespace gfx { class ClientNativePixmap; class ClientNativePixmapFactory; @@ -57,6 +61,7 @@ private: // TODO(crbug.com/404905709): Eliminate these class' creation of GMBs and // remove this friending. + friend class arc::GpuArcVideoEncodeAccelerator; friend class media::V4L2JpegEncodeAccelerator; friend class media::VaapiJpegEncodeAccelerator; friend class GpuMemoryBufferSupport;
diff --git a/gpu/ipc/common/gpu_memory_buffer_impl_shared_memory.cc b/gpu/ipc/common/gpu_memory_buffer_impl_shared_memory.cc index 90e51422..21754c55 100644 --- a/gpu/ipc/common/gpu_memory_buffer_impl_shared_memory.cc +++ b/gpu/ipc/common/gpu_memory_buffer_impl_shared_memory.cc
@@ -19,8 +19,6 @@ #include "base/memory/ptr_util.h" #include "base/numerics/safe_math.h" #include "base/process/memory.h" -#include "base/trace_event/memory_allocator_dump_guid.h" -#include "base/trace_event/process_memory_dump.h" #include "ui/gfx/buffer_format_util.h" #include "ui/gl/gl_bindings.h" @@ -278,18 +276,4 @@ return handle; } -void GpuMemoryBufferImplSharedMemory::OnMemoryDump( - base::trace_event::ProcessMemoryDump* pmd, - const base::trace_event::MemoryAllocatorDumpGuid& buffer_dump_guid, - uint64_t tracing_process_id, - int importance) const { - pmd->CreateSharedMemoryOwnershipEdge(buffer_dump_guid, GetSharedMemoryGUID(), - importance); -} - -base::UnguessableToken GpuMemoryBufferImplSharedMemory::GetSharedMemoryGUID() - const { - return shared_memory_region_.GetGUID(); -} - } // namespace gpu
diff --git a/gpu/ipc/common/gpu_memory_buffer_impl_shared_memory.h b/gpu/ipc/common/gpu_memory_buffer_impl_shared_memory.h index 3c06fc6..36fc5d5 100644 --- a/gpu/ipc/common/gpu_memory_buffer_impl_shared_memory.h +++ b/gpu/ipc/common/gpu_memory_buffer_impl_shared_memory.h
@@ -60,14 +60,6 @@ int stride(size_t plane) const override; gfx::GpuMemoryBufferType GetType() const override; gfx::GpuMemoryBufferHandle CloneHandle() const override; - void OnMemoryDump( - base::trace_event::ProcessMemoryDump* pmd, - const base::trace_event::MemoryAllocatorDumpGuid& buffer_dump_guid, - uint64_t tracing_process_id, - int importance) const override; - - // Returns the shared memory GUID associated with buffer. - base::UnguessableToken GetSharedMemoryGUID() const; private: friend class GpuMemoryBufferSupport;
diff --git a/gpu/ipc/common/gpu_memory_buffer_support.cc b/gpu/ipc/common/gpu_memory_buffer_support.cc index a9aca7d3..f5a76373 100644 --- a/gpu/ipc/common/gpu_memory_buffer_support.cc +++ b/gpu/ipc/common/gpu_memory_buffer_support.cc
@@ -263,49 +263,4 @@ } } -AllocatedBufferInfo::AllocatedBufferInfo( - const gfx::GpuMemoryBufferHandle& handle, - const gfx::Size& size, - gfx::BufferFormat format) - : buffer_id_(handle.id), - type_(handle.type), - size_in_bytes_(gfx::BufferSizeForBufferFormat(size, format)) { - DCHECK_NE(gfx::EMPTY_BUFFER, type_); - - if (type_ == gfx::SHARED_MEMORY_BUFFER) { - shared_memory_guid_ = handle.region().GetGUID(); - } -} - -AllocatedBufferInfo::~AllocatedBufferInfo() = default; - -bool AllocatedBufferInfo::OnMemoryDump( - base::trace_event::ProcessMemoryDump* pmd, - int client_id, - uint64_t client_tracing_process_id) const { - base::trace_event::MemoryAllocatorDump* dump = pmd->CreateAllocatorDump( - base::StringPrintf("gpu/gpumemorybuffer/client_0x%" PRIX32 "/buffer_%d", - client_id, buffer_id_.id)); - if (!dump) { - return false; - } - - dump->AddScalar(base::trace_event::MemoryAllocatorDump::kNameSize, - base::trace_event::MemoryAllocatorDump::kUnitsBytes, - size_in_bytes_); - - // Create the shared ownership edge to avoid double counting memory. - if (type_ == gfx::SHARED_MEMORY_BUFFER) { - pmd->CreateSharedMemoryOwnershipEdge(dump->guid(), shared_memory_guid_, - /*importance=*/0); - } else { - auto shared_buffer_guid = gfx::GetGenericSharedGpuMemoryGUIDForTracing( - client_tracing_process_id, buffer_id_); - pmd->CreateSharedGlobalAllocatorDump(shared_buffer_guid); - pmd->AddOwnershipEdge(dump->guid(), shared_buffer_guid); - } - - return true; -} - } // namespace gpu
diff --git a/gpu/ipc/common/gpu_memory_buffer_support.h b/gpu/ipc/common/gpu_memory_buffer_support.h index da724178..4db879c 100644 --- a/gpu/ipc/common/gpu_memory_buffer_support.h +++ b/gpu/ipc/common/gpu_memory_buffer_support.h
@@ -44,10 +44,6 @@ }; } // namespace std -namespace arc { -class GpuArcVideoEncodeAccelerator; -} - namespace gpu { class ClientSharedImage; @@ -104,10 +100,6 @@ } private: - // TODO(crbug.com/404905709): Eliminate this class' creation of GMBs and - // remove this friending. - friend class arc::GpuArcVideoEncodeAccelerator; - // ClientSharedImage is the only entity that should be creating GMBs via // GpuMemoryBufferSupport. friend class ClientSharedImage; @@ -141,30 +133,6 @@ #endif }; -// Helper class to manage allocated GMB info and to provide interface to dump -// the memory consumed by that GMB. -class GPU_IPC_COMMON_EXPORT AllocatedBufferInfo { - public: - AllocatedBufferInfo(const gfx::GpuMemoryBufferHandle& handle, - const gfx::Size& size, - gfx::BufferFormat format); - ~AllocatedBufferInfo(); - - gfx::GpuMemoryBufferType type() const { return type_; } - - // Add a memory dump for this buffer to |pmd|. Returns false if adding the - // dump failed. - bool OnMemoryDump(base::trace_event::ProcessMemoryDump* pmd, - int client_id, - uint64_t client_tracing_process_id) const; - - private: - gfx::GpuMemoryBufferId buffer_id_; - gfx::GpuMemoryBufferType type_; - size_t size_in_bytes_; - base::UnguessableToken shared_memory_guid_; -}; - } // namespace gpu #endif // GPU_IPC_COMMON_GPU_MEMORY_BUFFER_SUPPORT_H_
diff --git a/infra/config/generated/builders/ci/linux-official/properties.json b/infra/config/generated/builders/ci/linux-official/properties.json index 8fed969..8ac649d 100644 --- a/infra/config/generated/builders/ci/linux-official/properties.json +++ b/infra/config/generated/builders/ci/linux-official/properties.json
@@ -52,7 +52,6 @@ "$build/siso": { "configs": [ "builder", - "no-remote-timeout", "remote-link" ], "enable_cloud_monitoring": true,
diff --git a/infra/config/generated/builders/ci/linux-official/shadow-properties.json b/infra/config/generated/builders/ci/linux-official/shadow-properties.json index 3c02c25..23a91f5 100644 --- a/infra/config/generated/builders/ci/linux-official/shadow-properties.json +++ b/infra/config/generated/builders/ci/linux-official/shadow-properties.json
@@ -2,7 +2,6 @@ "$build/siso": { "configs": [ "builder", - "no-remote-timeout", "remote-link" ], "enable_cloud_monitoring": true,
diff --git a/infra/config/generated/builders/try/linux-official/properties.json b/infra/config/generated/builders/try/linux-official/properties.json index ab1ad19..b7b048a 100644 --- a/infra/config/generated/builders/try/linux-official/properties.json +++ b/infra/config/generated/builders/try/linux-official/properties.json
@@ -46,7 +46,6 @@ "$build/siso": { "configs": [ "builder", - "no-remote-timeout", "remote-link" ], "enable_cloud_monitoring": true,
diff --git a/infra/config/lib/builders.star b/infra/config/lib/builders.star index c367466..113cfad 100644 --- a/infra/config/lib/builders.star +++ b/infra/config/lib/builders.star
@@ -810,7 +810,7 @@ if remote_jobs: siso["remote_jobs"] = remote_jobs siso_configs = defaults.get_value("siso_configs", siso_configs) - if use_siso_remote_linking and "remote-link" not in siso_configs: + if use_siso_remote_linking: siso_configs = siso_configs + ["remote-link"] siso["configs"] = siso_configs if siso_fail_if_reapi_used:
diff --git a/infra/config/subprojects/chromium/ci/chromium.star b/infra/config/subprojects/chromium/ci/chromium.star index 265d7b8..03c644bf 100644 --- a/infra/config/subprojects/chromium/ci/chromium.star +++ b/infra/config/subprojects/chromium/ci/chromium.star
@@ -426,6 +426,8 @@ }, siso_output_local_strategy = "greedy", siso_remote_jobs = siso.remote_jobs.HIGH_JOBS_FOR_CI, + # crbug.com/427503493: It produces large amount of dwo files (>700GB). + # Enabling remote linking without bytes avoids downloading them to the bot. siso_remote_linking = True, ) @@ -520,13 +522,6 @@ ), ), }), - # crbug.com/427503493: It produces large amount of dwo files (>700GB). - # Enabling remote linking without bytes avoids downloading them to the bot. - # It also sets no-remote-timeout for long remote linking steps. - siso_configs = [ - "builder", - "no-remote-timeout", - ], siso_remote_linking = True, )
diff --git a/infra/config/subprojects/chromium/try/tryserver.chromium.star b/infra/config/subprojects/chromium/try/tryserver.chromium.star index 750b179b..2dbff089 100644 --- a/infra/config/subprojects/chromium/try/tryserver.chromium.star +++ b/infra/config/subprojects/chromium/try/tryserver.chromium.star
@@ -60,11 +60,6 @@ ssd = True, # crbug.com/427503493: It produces large amount of dwo files (>700GB). # Enabling remote linking without bytes avoids downloading them to the bot. - # It also sets no-remote-timeout for long remote linking steps. - siso_configs = [ - "builder", - "no-remote-timeout", - ], siso_remote_linking = True, )
diff --git a/ios/chrome/browser/authentication/ui_bundled/authentication_flow/authentication_flow.h b/ios/chrome/browser/authentication/ui_bundled/authentication_flow/authentication_flow.h index 793e33c..66afd54 100644 --- a/ios/chrome/browser/authentication/ui_bundled/authentication_flow/authentication_flow.h +++ b/ios/chrome/browser/authentication/ui_bundled/authentication_flow/authentication_flow.h
@@ -53,8 +53,6 @@ // Starts the sign in flow for the identity given in the constructor. Displays // the signed in confirmation dialog allowing the user to sign out or configure // sync. -// It is safe to destroy this authentication flow when `completion` is called. -// `completion` must not be nil. - (void)startSignIn; // * Interrupts the current sign-in operation (if any). @@ -62,13 +60,13 @@ // * Calls synchronously the completion callback from // `startSignInWithCompletion` with the sign-in flag set to no. // -// Does noting if the sign-in flow is already done +// Does noting if the sign-in flow is already done. - (void)interrupt; // Identity to sign-in. @property(nonatomic, strong, readonly) id<SystemIdentity> identity; -// Sign-in access point +// Sign-in access point. @property(nonatomic, assign, readonly) signin_metrics::AccessPoint accessPoint; @end
diff --git a/ios/chrome/browser/authentication/ui_bundled/authentication_flow/authentication_flow.mm b/ios/chrome/browser/authentication/ui_bundled/authentication_flow/authentication_flow.mm index 9226c59..515a445 100644 --- a/ios/chrome/browser/authentication/ui_bundled/authentication_flow/authentication_flow.mm +++ b/ios/chrome/browser/authentication/ui_bundled/authentication_flow/authentication_flow.mm
@@ -378,7 +378,7 @@ initWithDelegate:self changeProfileHandler:changeProfileHandler]; - // Make sure -[AuthenticationFlow startSignInWithCompletion:] doesn't call + // Make sure -[AuthenticationFlow startSignIn] doesn't call // the completion block synchronously. // Related to http://crbug.com/1246480. __weak __typeof(self) weakSelf = self;
diff --git a/ios/chrome/browser/authentication/ui_bundled/change_profile/change_profile_authentication_continuation.mm b/ios/chrome/browser/authentication/ui_bundled/change_profile/change_profile_authentication_continuation.mm index 83f8fb0..5909182b 100644 --- a/ios/chrome/browser/authentication/ui_bundled/change_profile/change_profile_authentication_continuation.mm +++ b/ios/chrome/browser/authentication/ui_bundled/change_profile/change_profile_authentication_continuation.mm
@@ -104,6 +104,8 @@ } } else { if (!authentication_service->HasPrimaryIdentity( + signin::ConsentLevel::kSignin) || + authentication_service->HasPrimaryIdentityManaged( signin::ConsentLevel::kSignin)) { SigninForContext(context, contexts, authentication_service, scene_state, std::move(closure));
diff --git a/ios/chrome/browser/authentication/ui_bundled/fullscreen_signin_screen/coordinator/fullscreen_signin_screen_coordinator.mm b/ios/chrome/browser/authentication/ui_bundled/fullscreen_signin_screen/coordinator/fullscreen_signin_screen_coordinator.mm index 78b786f..2dbbb7eb 100644 --- a/ios/chrome/browser/authentication/ui_bundled/fullscreen_signin_screen/coordinator/fullscreen_signin_screen_coordinator.mm +++ b/ios/chrome/browser/authentication/ui_bundled/fullscreen_signin_screen/coordinator/fullscreen_signin_screen_coordinator.mm
@@ -108,11 +108,8 @@ [self.browser->GetCommandDispatcher() startDispatchingToTarget:self forProtocol:@protocol(TOSCommands)]; - id<TOSCommands> TOSHandler = - HandlerForProtocol(self.browser->GetCommandDispatcher(), TOSCommands); self.viewController = [[FullscreenSigninScreenViewController alloc] initWithContextStyle:_contextStyle]; - self.viewController.TOSHandler = TOSHandler; self.viewController.delegate = self; ProfileIOS* profile = self.profile->GetOriginalProfile();
diff --git a/ios/chrome/browser/authentication/ui_bundled/fullscreen_signin_screen/ui/fullscreen_signin_screen_view_controller.h b/ios/chrome/browser/authentication/ui_bundled/fullscreen_signin_screen/ui/fullscreen_signin_screen_view_controller.h index b3fa4022..1c92e82 100644 --- a/ios/chrome/browser/authentication/ui_bundled/fullscreen_signin_screen/ui/fullscreen_signin_screen_view_controller.h +++ b/ios/chrome/browser/authentication/ui_bundled/fullscreen_signin_screen/ui/fullscreen_signin_screen_view_controller.h
@@ -11,7 +11,6 @@ #import "ios/chrome/common/ui/promo_style/promo_style_view_controller.h" enum class SigninContextStyle; -@protocol TOSCommands; // Delegate for the fullscreen sign-in view controller. @protocol FullscreenSigninScreenViewControllerDelegate < @@ -27,7 +26,6 @@ : PromoStyleViewController <FullscreenSigninScreenConsumer> // Handler to open the terms of service dialog. -@property(nonatomic, weak) id<TOSCommands> TOSHandler; @property(nonatomic, weak) id<FullscreenSigninScreenViewControllerDelegate> delegate;
diff --git a/ios/chrome/browser/authentication/ui_bundled/signin/consistency_promo_signin/consistency_default_account/consistency_default_account_view_controller.mm b/ios/chrome/browser/authentication/ui_bundled/signin/consistency_promo_signin/consistency_default_account/consistency_default_account_view_controller.mm index 398ccff..db5998b 100644 --- a/ios/chrome/browser/authentication/ui_bundled/signin/consistency_promo_signin/consistency_default_account/consistency_default_account_view_controller.mm +++ b/ios/chrome/browser/authentication/ui_bundled/signin/consistency_promo_signin/consistency_default_account/consistency_default_account_view_controller.mm
@@ -118,22 +118,27 @@ - (void)viewDidLoad { [super viewDidLoad]; - // Set the navigation title in the left bar button item to have left - // alignment. - UILabel* titleLabel = [[UILabel alloc] init]; - titleLabel.adjustsFontForContentSizeCategory = YES; - titleLabel.font = GetNavigationBarTitleFont(); - titleLabel.text = - l10n_util::GetNSString(IDS_IOS_CONSISTENCY_PROMO_DEFAULT_ACCOUNT_TITLE); - titleLabel.textAlignment = NSTextAlignmentLeft; - titleLabel.adjustsFontSizeToFitWidth = YES; - titleLabel.minimumScaleFactor = 0.1; - titleLabel.translatesAutoresizingMaskIntoConstraints = NO; + if (@available(iOS 26, *)) { + self.navigationItem.title = + l10n_util::GetNSString(IDS_IOS_CONSISTENCY_PROMO_DEFAULT_ACCOUNT_TITLE); + } else { + // Set the navigation title in the left bar button item to have left + // alignment. + UILabel* titleLabel = [[UILabel alloc] init]; + titleLabel.adjustsFontForContentSizeCategory = YES; + titleLabel.font = GetNavigationBarTitleFont(); + titleLabel.text = + l10n_util::GetNSString(IDS_IOS_CONSISTENCY_PROMO_DEFAULT_ACCOUNT_TITLE); + titleLabel.textAlignment = NSTextAlignmentLeft; + titleLabel.adjustsFontSizeToFitWidth = YES; + titleLabel.minimumScaleFactor = 0.1; + titleLabel.translatesAutoresizingMaskIntoConstraints = NO; - // Add the title label to the navigation bar. - UIBarButtonItem* leftItem = - [[UIBarButtonItem alloc] initWithCustomView:titleLabel]; - self.navigationItem.leftBarButtonItem = leftItem; + // Add the title label to the navigation bar. + UIBarButtonItem* leftItem = + [[UIBarButtonItem alloc] initWithCustomView:titleLabel]; + self.navigationItem.leftBarButtonItem = leftItem; + } self.navigationController.navigationBar.minimumContentSizeCategory = UIContentSizeCategoryLarge; self.navigationController.navigationBar.maximumContentSizeCategory =
diff --git a/ios/chrome/browser/autofill/model/form_structure_browsertest.mm b/ios/chrome/browser/autofill/model/form_structure_browsertest.mm index ba54f90..7d5ac35 100644 --- a/ios/chrome/browser/autofill/model/form_structure_browsertest.mm +++ b/ios/chrome/browser/autofill/model/form_structure_browsertest.mm
@@ -205,6 +205,7 @@ features::kAutofillEnableSupportForParsingWithSharedLabels, // TODO(crbug.com/40266396): Remove once launched. features::kAutofillEnableExpirationDateImprovements, + features::kAutofillIgnoreCheckableElements, features::kAutofillUnifyRationalizationAndSectioningOrder, }, // Disabled
diff --git a/ios/chrome/browser/autofill/ui_bundled/manual_fill/password_view_controller_egtest.mm b/ios/chrome/browser/autofill/ui_bundled/manual_fill/password_view_controller_egtest.mm index 0195834..00b1c26 100644 --- a/ios/chrome/browser/autofill/ui_bundled/manual_fill/password_view_controller_egtest.mm +++ b/ios/chrome/browser/autofill/ui_bundled/manual_fill/password_view_controller_egtest.mm
@@ -37,6 +37,7 @@ #import "ui/base/l10n/l10n_util_mac.h" #import "url/gurl.h" +using chrome_test_util::ActionSheetItemWithAccessibilityLabelId; using chrome_test_util::ButtonWithAccessibilityLabelId; using chrome_test_util::CancelButton; using chrome_test_util::NavigationBarCancelButton; @@ -73,19 +74,6 @@ IDS_IOS_MANUAL_FALLBACK_NOT_SECURE_TITLE); } -// Matcher for the confirmation dialog Continue button. -id<GREYMatcher> ConfirmUsingOtherPasswordButton() { - return grey_allOf(ButtonWithAccessibilityLabelId( - IDS_IOS_CONFIRM_USING_OTHER_PASSWORD_CONTINUE), - grey_interactable(), nullptr); -} - -// Matcher for the confirmation dialog Cancel button. -id<GREYMatcher> CancelUsingOtherPasswordButton() { - return grey_allOf(ButtonWithAccessibilityLabelId(IDS_CANCEL), - grey_interactable(), nullptr); -} - // Matcher for the overflow menu button shown in the password cells. id<GREYMatcher> OverflowMenuButton(NSInteger cell_index) { return grey_allOf(grey_accessibilityID([ManualFillUtil @@ -319,7 +307,9 @@ assertWithMatcher:grey_notNil()]; // Acknowledge concerns using other passwords on a website. - [[EarlGrey selectElementWithMatcher:ConfirmUsingOtherPasswordButton()] + [[EarlGrey selectElementWithMatcher: + ActionSheetItemWithAccessibilityLabelId( + IDS_IOS_CONFIRM_USING_OTHER_PASSWORD_CONTINUE)] performAction:grey_tap()]; } @@ -573,7 +563,8 @@ performAction:grey_tap()]; // Cancel using other passwords on a website. - [[EarlGrey selectElementWithMatcher:CancelUsingOtherPasswordButton()] + [[EarlGrey selectElementWithMatcher:ActionSheetItemWithAccessibilityLabelId( + IDS_CANCEL)] performAction:grey_tap()]; // Verify that the other password list is not opened. @@ -722,7 +713,9 @@ performAction:grey_tap()]; // Acknowledge concerns using other passwords on a website. - [[EarlGrey selectElementWithMatcher:ConfirmUsingOtherPasswordButton()] + [[EarlGrey selectElementWithMatcher: + ActionSheetItemWithAccessibilityLabelId( + IDS_IOS_CONFIRM_USING_OTHER_PASSWORD_CONTINUE)] performAction:grey_tap()]; // Verify that the all saved password list is visible. @@ -928,8 +921,8 @@ assertWithMatcher:grey_not(grey_nil())]; // Dismiss the alert. - [[EarlGrey selectElementWithMatcher:chrome_test_util::OKButton()] - performAction:grey_tap()]; + [[EarlGrey selectElementWithMatcher:ActionSheetItemWithAccessibilityLabelId( + IDS_OK)] performAction:grey_tap()]; [ChromeEarlGreyUI cleanupAfterShowingAlert]; }
diff --git a/ios/chrome/browser/bookmarks/ui_bundled/home/bookmarks_home_view_controller.mm b/ios/chrome/browser/bookmarks/ui_bundled/home/bookmarks_home_view_controller.mm index 4342909..d6189d4 100644 --- a/ios/chrome/browser/bookmarks/ui_bundled/home/bookmarks_home_view_controller.mm +++ b/ios/chrome/browser/bookmarks/ui_bundled/home/bookmarks_home_view_controller.mm
@@ -422,6 +422,11 @@ // Place the search bar in the navigation bar. self.navigationItem.searchController = self.searchController; +#if defined(__IPHONE_26_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_26_0 + if (@available(iOS 26, *)) { + self.navigationItem.searchBarPlacementAllowsToolbarIntegration = NO; + } +#endif self.navigationItem.hidesSearchBarWhenScrolling = NO; self.searchTerm = @"";
diff --git a/ios/chrome/browser/bubble/ui_bundled/bubble_presenter_egtest.mm b/ios/chrome/browser/bubble/ui_bundled/bubble_presenter_egtest.mm index 4fb6986..38c9489 100644 --- a/ios/chrome/browser/bubble/ui_bundled/bubble_presenter_egtest.mm +++ b/ios/chrome/browser/bubble/ui_bundled/bubble_presenter_egtest.mm
@@ -107,6 +107,10 @@ // Tests that the pull-to-refresh IPH is attempted when user taps the omnibox // to reload the same page, and disappears after the user navigates away. - (void)testPullToRefreshIPHAfterReloadFromOmniboxAndDisappearsAfterNavigation { + if (@available(iOS 19.0, *)) { + // TODO(crbug.com/427699033): Re-enable test on iOS 26. + EARL_GREY_TEST_DISABLED(@"Test disabled on iOS 26."); + } RelaunchWithIPHFeature(@"IPH_iOSPullToRefreshFeature", /*safari_switcher=*/YES); if ([ChromeEarlGrey isIPadIdiom]) { @@ -163,6 +167,10 @@ // Tests that the pull-to-refresh IPH is NOT attempted when page loading fails. - (void)testPullToRefreshIPHShouldDisappearOnEnteringTabGrid { + if (@available(iOS 19.0, *)) { + // TODO(crbug.com/427699033): Re-enable test on iOS 26. + EARL_GREY_TEST_DISABLED(@"Test disabled on iOS 26."); + } RelaunchWithIPHFeature(@"IPH_iOSPullToRefreshFeature", /*safari_switcher=*/YES); if ([ChromeEarlGrey isIPadIdiom]) { @@ -194,6 +202,10 @@ // Tests that the pull-to-refresh IPH is NOT attempted when page loading fails. - (void)testPullToRefreshIPHShouldNotShowOnPageLoadFail { + if (@available(iOS 19.0, *)) { + // TODO(crbug.com/427699033): Re-enable test on iOS 26. + EARL_GREY_TEST_DISABLED(@"Test disabled on iOS 26."); + } RelaunchWithIPHFeature(@"IPH_iOSPullToRefreshFeature", /*safari_switcher=*/YES); if ([ChromeEarlGrey isIPadIdiom]) { @@ -256,6 +268,10 @@ // Tests that the pull-to-refresh IPH would be dismissed with the reason // `kSwipedAsInstructedByGestureIPH` when the user pulls down on the IPH. - (void)testPullToRefreshPerformAction { + if (@available(iOS 19.0, *)) { + // TODO(crbug.com/427699033): Re-enable test on iOS 26. + EARL_GREY_TEST_DISABLED(@"Test disabled on iOS 26."); + } RelaunchWithIPHFeature(@"IPH_iOSPullToRefreshFeature", /*safari_switcher=*/YES); if ([ChromeEarlGrey isIPadIdiom]) {
diff --git a/ios/chrome/browser/collaboration/model/messaging/BUILD.gn b/ios/chrome/browser/collaboration/model/messaging/BUILD.gn index 098a80c..f1942fa0 100644 --- a/ios/chrome/browser/collaboration/model/messaging/BUILD.gn +++ b/ios/chrome/browser/collaboration/model/messaging/BUILD.gn
@@ -46,14 +46,17 @@ "//base/test:test_support", "//components/collaboration/public:features", "//components/data_sharing/public", + "//components/saved_tab_groups/test_support", "//ios/chrome/browser/infobars/model", "//ios/chrome/browser/saved_tab_groups/model", "//ios/chrome/browser/shared/model/browser", "//ios/chrome/browser/shared/model/browser/test:test_support", "//ios/chrome/browser/shared/model/profile/test", "//ios/chrome/browser/shared/model/web_state_list/test:test_support", + "//ios/chrome/browser/shared/public/commands", "//ios/chrome/browser/shared/public/features", "//ios/chrome/test:test_support", "//ios/web/public/test", + "//third_party/ocmock", ] }
diff --git a/ios/chrome/browser/collaboration/model/messaging/infobar/BUILD.gn b/ios/chrome/browser/collaboration/model/messaging/infobar/BUILD.gn index 712b070..16c11d7 100644 --- a/ios/chrome/browser/collaboration/model/messaging/infobar/BUILD.gn +++ b/ios/chrome/browser/collaboration/model/messaging/infobar/BUILD.gn
@@ -6,6 +6,8 @@ sources = [ "collaboration_group_infobar_delegate.h", "collaboration_group_infobar_delegate.mm", + "collaboration_out_of_date_infobar_delegate.h", + "collaboration_out_of_date_infobar_delegate.mm", ] deps = [ @@ -20,6 +22,7 @@ "//ios/chrome/browser/share_kit/model", "//ios/chrome/browser/share_kit/model:factory", "//ios/chrome/browser/shared/model/browser", + "//ios/chrome/browser/shared/model/browser:utils", "//ios/chrome/browser/shared/model/profile", "//ios/chrome/browser/shared/model/web_state_list", "//ios/chrome/browser/shared/public/commands",
diff --git a/ios/chrome/browser/collaboration/model/messaging/infobar/collaboration_out_of_date_infobar_delegate.h b/ios/chrome/browser/collaboration/model/messaging/infobar/collaboration_out_of_date_infobar_delegate.h new file mode 100644 index 0000000..2dc1f183 --- /dev/null +++ b/ios/chrome/browser/collaboration/model/messaging/infobar/collaboration_out_of_date_infobar_delegate.h
@@ -0,0 +1,47 @@ +// Copyright 2025 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef IOS_CHROME_BROWSER_COLLABORATION_MODEL_MESSAGING_INFOBAR_COLLABORATION_OUT_OF_DATE_INFOBAR_DELEGATE_H_ +#define IOS_CHROME_BROWSER_COLLABORATION_MODEL_MESSAGING_INFOBAR_COLLABORATION_OUT_OF_DATE_INFOBAR_DELEGATE_H_ + +#import <Foundation/Foundation.h> + +#import "components/infobars/core/confirm_infobar_delegate.h" + +class ProfileIOS; +@protocol ApplicationCommands; + +// Shows an out-of-date message related to shared tab groups support in an +// infobar. +class CollaborationOutOfDateInfoBarDelegate : public ConfirmInfoBarDelegate { + public: + explicit CollaborationOutOfDateInfoBarDelegate( + id<ApplicationCommands> application_commands_handler); + + CollaborationOutOfDateInfoBarDelegate( + const CollaborationOutOfDateInfoBarDelegate&) = delete; + CollaborationOutOfDateInfoBarDelegate& operator=( + const CollaborationOutOfDateInfoBarDelegate&) = delete; + + ~CollaborationOutOfDateInfoBarDelegate() override; + + // Creates an out-of-date message infobar. + // Returns true if the infobar creation was successful, false otherwise. + static bool Create(ProfileIOS* profile); + + // InfoBarDelegate implementation. + InfoBarIdentifier GetIdentifier() const override; + + // ConfirmInfoBarDelegate implementation. + std::u16string GetMessageText() const override; + int GetButtons() const override; + std::u16string GetButtonLabel(InfoBarButton button) const override; + bool Accept() override; + ui::ImageModel GetIcon() const override; + + private: + id<ApplicationCommands> application_commands_handler_; +}; + +#endif // IOS_CHROME_BROWSER_COLLABORATION_MODEL_MESSAGING_INFOBAR_COLLABORATION_OUT_OF_DATE_INFOBAR_DELEGATE_H_
diff --git a/ios/chrome/browser/collaboration/model/messaging/infobar/collaboration_out_of_date_infobar_delegate.mm b/ios/chrome/browser/collaboration/model/messaging/infobar/collaboration_out_of_date_infobar_delegate.mm new file mode 100644 index 0000000..fe7c13d --- /dev/null +++ b/ios/chrome/browser/collaboration/model/messaging/infobar/collaboration_out_of_date_infobar_delegate.mm
@@ -0,0 +1,88 @@ +// Copyright 2025 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#import "ios/chrome/browser/collaboration/model/messaging/infobar/collaboration_out_of_date_infobar_delegate.h" + +#import "components/infobars/core/infobar_delegate.h" +#import "components/strings/grit/components_strings.h" +#import "ios/chrome/browser/infobars/model/infobar_ios.h" +#import "ios/chrome/browser/infobars/model/infobar_manager_impl.h" +#import "ios/chrome/browser/shared/model/browser/browser.h" +#import "ios/chrome/browser/shared/model/browser/browser_list_factory.h" +#import "ios/chrome/browser/shared/model/browser/browser_list_utils.h" +#import "ios/chrome/browser/shared/model/web_state_list/web_state_list.h" +#import "ios/chrome/browser/shared/public/commands/application_commands.h" +#import "ios/chrome/browser/shared/public/commands/command_dispatcher.h" +#import "ios/chrome/browser/shared/ui/symbols/symbols.h" +#import "ios/chrome/grit/ios_strings.h" +#import "ui/base/l10n/l10n_util.h" +#import "ui/base/models/image_model.h" +#import "ui/gfx/image/image.h" + +// static +bool CollaborationOutOfDateInfoBarDelegate::Create(ProfileIOS* profile) { + BrowserList* browser_list = BrowserListFactory::GetForProfile(profile); + Browser* browser = + browser_list_utils::GetMostActiveSceneBrowser(browser_list); + if (!browser) { + return false; + } + + web::WebState* active_web_state = + browser->GetWebStateList()->GetActiveWebState(); + if (!active_web_state) { + return false; + } + + infobars::InfoBarManager* infobar_manager = + InfoBarManagerImpl::FromWebState(active_web_state); + + id<ApplicationCommands> application_commands_handler = + HandlerForProtocol(browser->GetCommandDispatcher(), ApplicationCommands); + + std::unique_ptr<CollaborationOutOfDateInfoBarDelegate> delegate = + std::make_unique<CollaborationOutOfDateInfoBarDelegate>( + application_commands_handler); + std::unique_ptr<InfoBarIOS> infobar = std::make_unique<InfoBarIOS>( + InfobarType::kInfobarTypeCollaborationOutOfDate, std::move(delegate)); + return !!infobar_manager->AddInfoBar(std::move(infobar)); +} + +CollaborationOutOfDateInfoBarDelegate::CollaborationOutOfDateInfoBarDelegate( + id<ApplicationCommands> application_commands_handler) + : application_commands_handler_(application_commands_handler) {} + +CollaborationOutOfDateInfoBarDelegate:: + ~CollaborationOutOfDateInfoBarDelegate() {} + +infobars::InfoBarDelegate::InfoBarIdentifier +CollaborationOutOfDateInfoBarDelegate::GetIdentifier() const { + return TAB_SHARING_INFOBAR_DELEGATE; +} + +std::u16string CollaborationOutOfDateInfoBarDelegate::GetMessageText() const { + return l10n_util::GetStringUTF16( + IDS_COLLABORATION_SHARED_TAB_GROUPS_PANEL_OUT_OF_DATE_MESSAGE_CELL_TEXT); +} + +int CollaborationOutOfDateInfoBarDelegate::GetButtons() const { + return BUTTON_OK; +} + +std::u16string CollaborationOutOfDateInfoBarDelegate::GetButtonLabel( + InfoBarButton button) const { + return l10n_util::GetStringUTF16( + IDS_IOS_TAB_GROUPS_PANEL_OUT_OF_DATE_MESSAGE_UPDATE_BUTTON); +} + +bool CollaborationOutOfDateInfoBarDelegate::Accept() { + [application_commands_handler_ showAppStorePage]; + return true; +} + +ui::ImageModel CollaborationOutOfDateInfoBarDelegate::GetIcon() const { + UIImage* symbolImage = + DefaultSymbolWithPointSize(kTabGroupsSymbol, kInfobarSymbolPointSize); + return ui::ImageModel::FromImage(gfx::Image(symbolImage)); +}
diff --git a/ios/chrome/browser/collaboration/model/messaging/instant_messaging_service.h b/ios/chrome/browser/collaboration/model/messaging/instant_messaging_service.h index 429a313..0e5a3e2 100644 --- a/ios/chrome/browser/collaboration/model/messaging/instant_messaging_service.h +++ b/ios/chrome/browser/collaboration/model/messaging/instant_messaging_service.h
@@ -8,6 +8,7 @@ #import <set> #import "base/memory/raw_ptr.h" +#import "base/memory/weak_ptr.h" #import "base/uuid.h" #import "components/collaboration/public/messaging/messaging_backend_service.h" #import "components/keyed_service/core/keyed_service.h" @@ -37,6 +38,9 @@ void HideInstantaneousMessage( const std::set<base::Uuid>& message_ids) override; + // Displays the out-of-date infobar if required. + void DisplayOutOfDateMessageIfNeeded(bool should_display); + private: // Shows a collaboration group infobar for the given `instant_message`. // Returns `true` if the infobar has been displayed. @@ -44,6 +48,8 @@ collaboration::messaging::InstantMessage instant_message); raw_ptr<ProfileIOS> profile_ = nullptr; + + base::WeakPtrFactory<InstantMessagingService> weak_factory_{this}; }; } // namespace collaboration::messaging
diff --git a/ios/chrome/browser/collaboration/model/messaging/instant_messaging_service.mm b/ios/chrome/browser/collaboration/model/messaging/instant_messaging_service.mm index e489b77..bde0518 100644 --- a/ios/chrome/browser/collaboration/model/messaging/instant_messaging_service.mm +++ b/ios/chrome/browser/collaboration/model/messaging/instant_messaging_service.mm
@@ -8,17 +8,34 @@ #import "base/functional/callback.h" #import "base/uuid.h" +#import "components/saved_tab_groups/public/tab_group_sync_service.h" +#import "components/saved_tab_groups/public/versioning_message_controller.h" #import "ios/chrome/browser/collaboration/model/messaging/infobar/collaboration_group_infobar_delegate.h" +#import "ios/chrome/browser/collaboration/model/messaging/infobar/collaboration_out_of_date_infobar_delegate.h" #import "ios/chrome/browser/infobars/model/infobar_ios.h" #import "ios/chrome/browser/infobars/model/infobar_manager_impl.h" +#import "ios/chrome/browser/saved_tab_groups/model/tab_group_sync_service_factory.h" #import "ios/chrome/browser/shared/model/profile/profile_ios.h" namespace collaboration::messaging { InstantMessagingService::InstantMessagingService(ProfileIOS* profile) : profile_(profile) { - DCHECK(profile_); + CHECK(profile_); + auto* tab_group_sync_service = + tab_groups::TabGroupSyncServiceFactory::GetForProfile(profile_); + auto* versioning_message_controller = + tab_group_sync_service->GetVersioningMessageController(); + if (versioning_message_controller) { + versioning_message_controller->ShouldShowMessageUiAsync( + tab_groups::VersioningMessageController::MessageType:: + VERSION_OUT_OF_DATE_INSTANT_MESSAGE, + base::BindOnce( + &InstantMessagingService::DisplayOutOfDateMessageIfNeeded, + weak_factory_.GetWeakPtr())); + } } + InstantMessagingService::~InstantMessagingService() {} void InstantMessagingService::DisplayInstantaneousMessage( @@ -52,4 +69,29 @@ return infobar_displayed; } +void InstantMessagingService::DisplayOutOfDateMessageIfNeeded( + bool should_display) { + if (!should_display) { + return; + } + + // Try to display the infobar. + bool infobar_displayed = + CollaborationOutOfDateInfoBarDelegate::Create(profile_); + if (!infobar_displayed) { + return; + } + + // Notify the versioning message controller that the infobar was actually + // displayed. + auto* tab_group_sync_service = + tab_groups::TabGroupSyncServiceFactory::GetForProfile(profile_); + auto* versioning_message_controller = + tab_group_sync_service->GetVersioningMessageController(); + CHECK(versioning_message_controller); + versioning_message_controller->OnMessageUiShown( + tab_groups::VersioningMessageController::MessageType:: + VERSION_OUT_OF_DATE_INSTANT_MESSAGE); +} + } // namespace collaboration::messaging
diff --git a/ios/chrome/browser/collaboration/model/messaging/instant_messaging_service_unittest.mm b/ios/chrome/browser/collaboration/model/messaging/instant_messaging_service_unittest.mm index b6c65ba..ffa49b2 100644 --- a/ios/chrome/browser/collaboration/model/messaging/instant_messaging_service_unittest.mm +++ b/ios/chrome/browser/collaboration/model/messaging/instant_messaging_service_unittest.mm
@@ -8,22 +8,33 @@ #import "base/test/scoped_feature_list.h" #import "components/collaboration/public/features.h" #import "components/data_sharing/public/features.h" +#import "components/saved_tab_groups/test_support/mock_tab_group_sync_service.h" +#import "components/saved_tab_groups/test_support/mock_versioning_message_controller.h" #import "ios/chrome/browser/collaboration/model/messaging/instant_messaging_service_factory.h" #import "ios/chrome/browser/infobars/model/infobar_manager_impl.h" +#import "ios/chrome/browser/saved_tab_groups/model/tab_group_sync_service_factory.h" #import "ios/chrome/browser/shared/model/browser/browser_list.h" #import "ios/chrome/browser/shared/model/browser/browser_list_factory.h" #import "ios/chrome/browser/shared/model/browser/test/test_browser.h" #import "ios/chrome/browser/shared/model/profile/test/test_profile_ios.h" #import "ios/chrome/browser/shared/model/web_state_list/test/web_state_list_builder_from_description.h" +#import "ios/chrome/browser/shared/public/commands/application_commands.h" +#import "ios/chrome/browser/shared/public/commands/command_dispatcher.h" #import "ios/chrome/browser/shared/public/features/features.h" #import "ios/chrome/test/ios_chrome_scoped_testing_local_state.h" #import "ios/web/public/test/fakes/fake_navigation_manager.h" #import "ios/web/public/test/fakes/fake_web_state.h" #import "ios/web/public/test/web_task_environment.h" #import "testing/platform_test.h" +#import "third_party/ocmock/OCMock/OCMock.h" namespace collaboration::messaging { +namespace { + +using tab_groups::MockTabGroupSyncService; +using ::testing::Return; + // Returns an `InstantMessage`. InstantMessage CreateInstantMessage(InstantNotificationLevel level) { InstantMessage message; @@ -33,6 +44,17 @@ return message; } +// Creates a MockTabGroupSyncService with a VersioningMessageController. +std::unique_ptr<KeyedService> CreateMockTabGroupSyncService( + tab_groups::VersioningMessageController* versioning_message_controller, + web::BrowserState* context) { + auto tab_group_sync_service = + std::make_unique<::testing::NiceMock<MockTabGroupSyncService>>(); + ON_CALL(*tab_group_sync_service.get(), GetVersioningMessageController()) + .WillByDefault(Return(versioning_message_controller)); + return tab_group_sync_service; +} + class InstantMessagingServiceTest : public PlatformTest { public: InstantMessagingServiceTest() { @@ -44,9 +66,22 @@ }, /*disable_features=*/{}); - profile_ = TestProfileIOS::Builder().Build(); + TestProfileIOS::Builder test_profile_builder; + test_profile_builder.AddTestingFactory( + tab_groups::TabGroupSyncServiceFactory::GetInstance(), + base::BindOnce(&CreateMockTabGroupSyncService, + &versioning_message_controller_)); + // The InstantMessagingService should call the VersioningMessageController + // at initialization. + EXPECT_CALL(versioning_message_controller_, ShouldShowMessageUiAsync) + .Times(1); + profile_ = std::move(test_profile_builder).Build(); service_ = InstantMessagingServiceFactory::GetForProfile(profile_.get()); browser_ = std::make_unique<TestBrowser>(profile_.get()); + application_handler_ = OCMProtocolMock(@protocol(ApplicationCommands)); + [browser_->GetCommandDispatcher() + startDispatchingToTarget:application_handler_ + forProtocol:@protocol(ApplicationCommands)]; BrowserList* browser_list = BrowserListFactory::GetForProfile(profile_.get()); browser_list->AddBrowser(browser_.get()); @@ -80,11 +115,13 @@ base::test::ScopedFeatureList scoped_feature_list_; web::WebTaskEnvironment task_environment_; IOSChromeScopedTestingLocalState scoped_testing_local_state_; + tab_groups::MockVersioningMessageController versioning_message_controller_; std::unique_ptr<TestProfileIOS> profile_; std::unique_ptr<Browser> browser_; raw_ptr<InstantMessagingService> service_; raw_ptr<WebStateList> web_state_list_; raw_ptr<web::FakeWebState> active_web_state_; + id<ApplicationCommands> application_handler_; }; // Tests the DisplayInstantaneousMessage method for an undefined level message. @@ -120,4 +157,19 @@ service_->DisplayInstantaneousMessage(message, mock_callback.Get()); } +// Tests the DisplayOutOfDateMessageIfNeeded method when the message is needed. +TEST_F(InstantMessagingServiceTest, DisplayOutOfDateMessage_Needed) { + EXPECT_CALL(versioning_message_controller_, OnMessageUiShown).Times(1); + service_->DisplayOutOfDateMessageIfNeeded(true); +} + +// Tests the DisplayOutOfDateMessageIfNeeded method when the message is not +// needed. +TEST_F(InstantMessagingServiceTest, DisplayOutOfDateMessage_NotNeeded) { + EXPECT_CALL(versioning_message_controller_, OnMessageUiShown).Times(0); + service_->DisplayOutOfDateMessageIfNeeded(false); +} + +} // namespace + } // namespace collaboration::messaging
diff --git a/ios/chrome/browser/content_suggestions/ui_bundled/content_suggestions_collection_utils.h b/ios/chrome/browser/content_suggestions/ui_bundled/content_suggestions_collection_utils.h index b2d29461..c4609554 100644 --- a/ios/chrome/browser/content_suggestions/ui_bundled/content_suggestions_collection_utils.h +++ b/ios/chrome/browser/content_suggestions/ui_bundled/content_suggestions_collection_utils.h
@@ -74,7 +74,7 @@ CGFloat new_badge_alpha); // Configure the `mia_button` appearance. -void ConfigureInlineMIAButton(UIButton* mia_button, BOOL use_color_icon); +void ConfigureMIAButton(UIButton* mia_button, BOOL use_color_icon); // Returns the nearest ancestor of `view` that is kind of `of_class`. UIView* NearestAncestor(UIView* view, Class of_class);
diff --git a/ios/chrome/browser/content_suggestions/ui_bundled/content_suggestions_collection_utils.mm b/ios/chrome/browser/content_suggestions/ui_bundled/content_suggestions_collection_utils.mm index d393bc89..78c025c 100644 --- a/ios/chrome/browser/content_suggestions/ui_bundled/content_suggestions_collection_utils.mm +++ b/ios/chrome/browser/content_suggestions/ui_bundled/content_suggestions_collection_utils.mm
@@ -73,7 +73,7 @@ const CGFloat kNewBadgeOffsetFromButtonCenter = 14.0; // The height of the Fakebox. -const CGFloat kFakeboxHeight = 65; +const CGFloat kFakeboxHeight = 64; const CGFloat kFakeboxHeightNonDynamic = 45; // The height of the Fakebox when it is pinned to the top. @@ -338,7 +338,7 @@ } } -void ConfigureInlineMIAButton(UIButton* mia_button, BOOL use_color_icon) { +void ConfigureMIAButton(UIButton* mia_button, BOOL use_color_icon) { [mia_button setTranslatesAutoresizingMaskIntoConstraints:NO]; UIButtonConfiguration* buttonConfig = @@ -348,7 +348,7 @@ UIImage* magnifier_icon = CustomSymbolWithPointSize( kMagnifyingglassSparkSymbol, kSymbolContentSuggestionsPointSize); - use_color_icon = magnifier_icon == nil; + magnifier_icon = use_color_icon ? MakeSymbolMulticolor(magnifier_icon) : MakeSymbolMonochrome(magnifier_icon); [mia_button setImage:magnifier_icon forState:UIControlStateNormal];
diff --git a/ios/chrome/browser/content_suggestions/ui_bundled/content_suggestions_collection_utils_unittest.mm b/ios/chrome/browser/content_suggestions/ui_bundled/content_suggestions_collection_utils_unittest.mm index 4bc30f1..762233be 100644 --- a/ios/chrome/browser/content_suggestions/ui_bundled/content_suggestions_collection_utils_unittest.mm +++ b/ios/chrome/browser/content_suggestions/ui_bundled/content_suggestions_collection_utils_unittest.mm
@@ -271,7 +271,7 @@ scoped_feature_list.InitWithFeaturesAndParameters( /*enabled_features=*/{{kDeprecateFeedHeader, large_fakebox_params}}, /*disabled_features=*/{}); - EXPECT_EQ(65, FakeOmniboxHeight()); + EXPECT_EQ(64, FakeOmniboxHeight()); } TEST_F(ContentSuggestionsCollectionUtilsTest, pinnedFakeOmniboxHeight) {
diff --git a/ios/chrome/browser/default_promo/ui_bundled/default_browser_promo_non_modal_egtest.mm b/ios/chrome/browser/default_promo/ui_bundled/default_browser_promo_non_modal_egtest.mm index 02ee0d1..50035e81 100644 --- a/ios/chrome/browser/default_promo/ui_bundled/default_browser_promo_non_modal_egtest.mm +++ b/ios/chrome/browser/default_promo/ui_bundled/default_browser_promo_non_modal_egtest.mm
@@ -79,7 +79,14 @@ // Test that a non modal default modal promo appears when it is triggered by // using a pasted URL. -- (void)testNonModalAppearsFromPaste { +// TODO(crbug.com/413072087): Test flaky on device. The error is caused by a +// timeout while waiting for the promo to appear. +#if !TARGET_IPHONE_SIMULATOR +#define MAYBE_testNonModalAppearsFromPaste DISABLED_testNonModalAppearsFromPaste +#else +#define MAYBE_testNonModalAppearsFromPaste testNonModalAppearsFromPaste +#endif +- (void)MAYBE_testNonModalAppearsFromPaste { [self setupIPHConfig:"IPH_iOSPromoNonModalUrlPasteDefaultBrowser"]; // Copy URL to the clipboard
diff --git a/ios/chrome/browser/infobars/model/infobar_metrics_recorder.mm b/ios/chrome/browser/infobars/model/infobar_metrics_recorder.mm index f0380fbf..ccd1d02 100644 --- a/ios/chrome/browser/infobars/model/infobar_metrics_recorder.mm +++ b/ios/chrome/browser/infobars/model/infobar_metrics_recorder.mm
@@ -153,6 +153,16 @@ const char kInfobarCollaborationGroupBadgeTappedHistogram[] = "Mobile.Messages.Badge.Tapped.kInfobarCollaborationGroup"; +// Histogram names for collaboration out-of-date message banner. +const char kInfobarCollaborationOutOfDateBannerEventHistogram[] = + "Mobile.Messages.Banner.Event.kInfobarCollaborationOutOfDate"; +const char kInfobarCollaborationOutOfDateBannerDismissTypeHistogram[] = + "Mobile.Messages.Banner.Dismiss.kInfobarCollaborationOutOfDate"; +const char kInfobarCollaborationOutOfDateModalEventHistogram[] = + "Mobile.Messages.Modal.Event.kInfobarCollaborationOutOfDate"; +const char kInfobarCollaborationOutOfDateBadgeTappedHistogram[] = + "Mobile.Messages.Badge.Tapped.kInfobarCollaborationOutOfDate"; + } // namespace @interface InfobarMetricsRecorder () @@ -217,6 +227,10 @@ UMA_HISTOGRAM_ENUMERATION(kInfobarCollaborationGroupBannerEventHistogram, event); break; + case InfobarType::kInfobarTypeCollaborationOutOfDate: + UMA_HISTOGRAM_ENUMERATION( + kInfobarCollaborationOutOfDateBannerEventHistogram, event); + break; } } @@ -271,6 +285,11 @@ UMA_HISTOGRAM_ENUMERATION( kInfobarCollaborationGroupBannerDismissTypeHistogram, dismissType); break; + case InfobarType::kInfobarTypeCollaborationOutOfDate: + UMA_HISTOGRAM_ENUMERATION( + kInfobarCollaborationOutOfDateBannerDismissTypeHistogram, + dismissType); + break; } } @@ -323,6 +342,10 @@ UMA_HISTOGRAM_ENUMERATION(kInfobarCollaborationGroupModalEventHistogram, event); break; + case InfobarType::kInfobarTypeCollaborationOutOfDate: + UMA_HISTOGRAM_ENUMERATION( + kInfobarCollaborationOutOfDateModalEventHistogram, event); + break; } } @@ -370,6 +393,10 @@ UMA_HISTOGRAM_ENUMERATION(kInfobarCollaborationGroupBadgeTappedHistogram, state); break; + case InfobarType::kInfobarTypeCollaborationOutOfDate: + UMA_HISTOGRAM_ENUMERATION( + kInfobarCollaborationOutOfDateBadgeTappedHistogram, state); + break; } }
diff --git a/ios/chrome/browser/infobars/model/infobar_type.h b/ios/chrome/browser/infobars/model/infobar_type.h index 983ce46a..1fec62e 100644 --- a/ios/chrome/browser/infobars/model/infobar_type.h +++ b/ios/chrome/browser/infobars/model/infobar_type.h
@@ -34,7 +34,9 @@ // Message Infobar for non modal sign-in promo. kInfobarTypeSignin = 13, // Message Infobar for collaboration group update. - kInfobarTypeCollaborationGroup = 14 + kInfobarTypeCollaborationGroup = 14, + // Message Infobar for collaboration group update. + kInfobarTypeCollaborationOutOfDate = 15, }; // Message "Confirm Infobars" types, these are the generic kInfobarTypeConfirm
diff --git a/ios/chrome/browser/infobars/model/overlays/browser_agent/infobar_overlay_browser_agent_util.mm b/ios/chrome/browser/infobars/model/overlays/browser_agent/infobar_overlay_browser_agent_util.mm index 2015441..0e558be 100644 --- a/ios/chrome/browser/infobars/model/overlays/browser_agent/infobar_overlay_browser_agent_util.mm +++ b/ios/chrome/browser/infobars/model/overlays/browser_agent/infobar_overlay_browser_agent_util.mm
@@ -42,6 +42,9 @@ browser_agent->AddDefaultInfobarInteractionHandlerForInfobarType( InfobarType::kInfobarTypeCollaborationGroup); + browser_agent->AddDefaultInfobarInteractionHandlerForInfobarType( + InfobarType::kInfobarTypeCollaborationOutOfDate); + browser_agent->AddInfobarInteractionHandler( std::make_unique<InfobarInteractionHandler>( InfobarType::kInfobarTypeConfirm,
diff --git a/ios/chrome/browser/infobars/model/overlays/default_infobar_overlay_request_factory.mm b/ios/chrome/browser/infobars/model/overlays/default_infobar_overlay_request_factory.mm index b28cc51..99f1ceec 100644 --- a/ios/chrome/browser/infobars/model/overlays/default_infobar_overlay_request_factory.mm +++ b/ios/chrome/browser/infobars/model/overlays/default_infobar_overlay_request_factory.mm
@@ -27,6 +27,7 @@ case InfobarType::kInfobarTypeTranslate: case InfobarType::kInfobarTypeEnhancedSafeBrowsing: case InfobarType::kInfobarTypeCollaborationGroup: + case InfobarType::kInfobarTypeCollaborationOutOfDate: return OverlayRequest::CreateWithConfig< DefaultInfobarOverlayRequestConfig>(infobar_ios, overlay_type);
diff --git a/ios/chrome/browser/ntp/ui_bundled/new_tab_page_header_view.mm b/ios/chrome/browser/ntp/ui_bundled/new_tab_page_header_view.mm index 4cd9b8b..ad8c9ec 100644 --- a/ios/chrome/browser/ntp/ui_bundled/new_tab_page_header_view.mm +++ b/ios/chrome/browser/ntp/ui_bundled/new_tab_page_header_view.mm
@@ -518,7 +518,7 @@ } if (self.miaButton) { - content_suggestions::ConfigureInlineMIAButton(self.miaButton, useColorIcon); + content_suggestions::ConfigureMIAButton(self.miaButton, useColorIcon); } }
diff --git a/ios/chrome/browser/ntp/ui_bundled/new_tab_page_view_controller.mm b/ios/chrome/browser/ntp/ui_bundled/new_tab_page_view_controller.mm index e1670b8..f911c72 100644 --- a/ios/chrome/browser/ntp/ui_bundled/new_tab_page_view_controller.mm +++ b/ios/chrome/browser/ntp/ui_bundled/new_tab_page_view_controller.mm
@@ -54,6 +54,10 @@ // overscroll. const CGFloat kFeedContainerExtraHeight = 500; +// The spacing for the quick actions buttons. +const CGFloat kQuickActionSpacingTop = 6.0; +const CGFloat kQuickActionSpacingBotttom = 19.0; + // Vertical spacing between modules. CGFloat SpaceBetweenModules() { return GetDeprecateFeedHeaderParameterValueAsDouble( @@ -668,6 +672,10 @@ viewController == self.feedHeaderViewController) { heightAboveFeed += SpaceBetweenModules(); } + + if (viewController == _quickActionsViewController) { + heightAboveFeed += kQuickActionSpacingBotttom; + } } return heightAboveFeed; } @@ -1259,7 +1267,9 @@ self.fakeOmniboxConstraints = @[ [viewBelowHeader.topAnchor constraintEqualToAnchor:self.headerViewController.view.bottomAnchor - constant:SpaceBetweenModules()], + constant:self.quickActionsVisible + ? kQuickActionSpacingTop + : SpaceBetweenModules()], ]; } [NSLayoutConstraint activateConstraints:self.fakeOmniboxConstraints]; @@ -1509,11 +1519,16 @@ NSUInteger headerIndex = [self.viewControllersAboveFeed indexOfObject:self.headerViewController]; for (NSUInteger index = startIndex; index > headerIndex + 1; --index) { + BOOL isQuickActions = _quickActionsViewController == + self.viewControllersAboveFeed[index - 1]; UIView* view = self.viewControllersAboveFeed[index].view; UIView* viewAbove = self.viewControllersAboveFeed[index - 1].view; + + CGFloat spacingToUse = + isQuickActions ? kQuickActionSpacingBotttom : SpaceBetweenModules(); [NSLayoutConstraint activateConstraints:@[ [view.topAnchor constraintEqualToAnchor:viewAbove.bottomAnchor - constant:SpaceBetweenModules()], + constant:spacingToUse], ]]; } }
diff --git a/ios/chrome/browser/overlays/ui_bundled/infobar_banner/BUILD.gn b/ios/chrome/browser/overlays/ui_bundled/infobar_banner/BUILD.gn index b5671d5d..4a149d6d 100644 --- a/ios/chrome/browser/overlays/ui_bundled/infobar_banner/BUILD.gn +++ b/ios/chrome/browser/overlays/ui_bundled/infobar_banner/BUILD.gn
@@ -26,6 +26,7 @@ "//ios/chrome/browser/overlays/ui_bundled:util", "//ios/chrome/browser/overlays/ui_bundled/infobar_banner/autofill_address_profile", "//ios/chrome/browser/overlays/ui_bundled/infobar_banner/collaboration_group", + "//ios/chrome/browser/overlays/ui_bundled/infobar_banner/collaboration_out_of_date", "//ios/chrome/browser/overlays/ui_bundled/infobar_banner/confirm", "//ios/chrome/browser/overlays/ui_bundled/infobar_banner/passwords", "//ios/chrome/browser/overlays/ui_bundled/infobar_banner/permissions",
diff --git a/ios/chrome/browser/overlays/ui_bundled/infobar_banner/collaboration_out_of_date/BUILD.gn b/ios/chrome/browser/overlays/ui_bundled/infobar_banner/collaboration_out_of_date/BUILD.gn new file mode 100644 index 0000000..a089530d --- /dev/null +++ b/ios/chrome/browser/overlays/ui_bundled/infobar_banner/collaboration_out_of_date/BUILD.gn
@@ -0,0 +1,20 @@ +# Copyright 2025 The Chromium Authors +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +source_set("collaboration_out_of_date") { + sources = [ + "collaboration_out_of_date_infobar_banner_overlay_mediator.h", + "collaboration_out_of_date_infobar_banner_overlay_mediator.mm", + ] + + deps = [ + "//base", + "//ios/chrome/browser/collaboration/model/messaging/infobar", + "//ios/chrome/browser/infobars/ui_bundled/banners", + "//ios/chrome/browser/overlays/model/public/default", + "//ios/chrome/browser/overlays/ui_bundled:coordinators", + "//ios/chrome/browser/overlays/ui_bundled/infobar_banner:mediators", + "//ui/base", + ] +}
diff --git a/ios/chrome/browser/overlays/ui_bundled/infobar_banner/collaboration_out_of_date/DEPS b/ios/chrome/browser/overlays/ui_bundled/infobar_banner/collaboration_out_of_date/DEPS new file mode 100644 index 0000000..288c21e --- /dev/null +++ b/ios/chrome/browser/overlays/ui_bundled/infobar_banner/collaboration_out_of_date/DEPS
@@ -0,0 +1,3 @@ +include_rules = [ + "+ios/chrome/browser/collaboration/model/messaging/infobar", +]
diff --git a/ios/chrome/browser/overlays/ui_bundled/infobar_banner/collaboration_out_of_date/collaboration_out_of_date_infobar_banner_overlay_mediator.h b/ios/chrome/browser/overlays/ui_bundled/infobar_banner/collaboration_out_of_date/collaboration_out_of_date_infobar_banner_overlay_mediator.h new file mode 100644 index 0000000..0e7b75e --- /dev/null +++ b/ios/chrome/browser/overlays/ui_bundled/infobar_banner/collaboration_out_of_date/collaboration_out_of_date_infobar_banner_overlay_mediator.h
@@ -0,0 +1,16 @@ +// Copyright 2025 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef IOS_CHROME_BROWSER_OVERLAYS_UI_BUNDLED_INFOBAR_BANNER_COLLABORATION_OUT_OF_DATE_COLLABORATION_OUT_OF_DATE_INFOBAR_BANNER_OVERLAY_MEDIATOR_H_ +#define IOS_CHROME_BROWSER_OVERLAYS_UI_BUNDLED_INFOBAR_BANNER_COLLABORATION_OUT_OF_DATE_COLLABORATION_OUT_OF_DATE_INFOBAR_BANNER_OVERLAY_MEDIATOR_H_ + +#import "ios/chrome/browser/overlays/ui_bundled/infobar_banner/infobar_banner_overlay_mediator.h" + +// Mediator that configures an infobar banner for the out-of-date message +// related to shared tab groups support. +@interface CollaborationOutOfDateInfobarBannerOverlayMediator + : InfobarBannerOverlayMediator +@end + +#endif // IOS_CHROME_BROWSER_OVERLAYS_UI_BUNDLED_INFOBAR_BANNER_COLLABORATION_OUT_OF_DATE_COLLABORATION_OUT_OF_DATE_INFOBAR_BANNER_OVERLAY_MEDIATOR_H_
diff --git a/ios/chrome/browser/overlays/ui_bundled/infobar_banner/collaboration_out_of_date/collaboration_out_of_date_infobar_banner_overlay_mediator.mm b/ios/chrome/browser/overlays/ui_bundled/infobar_banner/collaboration_out_of_date/collaboration_out_of_date_infobar_banner_overlay_mediator.mm new file mode 100644 index 0000000..41d177bc --- /dev/null +++ b/ios/chrome/browser/overlays/ui_bundled/infobar_banner/collaboration_out_of_date/collaboration_out_of_date_infobar_banner_overlay_mediator.mm
@@ -0,0 +1,80 @@ +// Copyright 2025 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#import "ios/chrome/browser/overlays/ui_bundled/infobar_banner/collaboration_out_of_date/collaboration_out_of_date_infobar_banner_overlay_mediator.h" + +#import "base/strings/sys_string_conversions.h" +#import "ios/chrome/browser/collaboration/model/messaging/infobar/collaboration_out_of_date_infobar_delegate.h" +#import "ios/chrome/browser/infobars/ui_bundled/banners/infobar_banner_consumer.h" +#import "ios/chrome/browser/overlays/model/public/default/default_infobar_overlay_request_config.h" +#import "ios/chrome/browser/overlays/ui_bundled/infobar_banner/infobar_banner_overlay_mediator+consumer_support.h" +#import "ios/chrome/browser/overlays/ui_bundled/overlay_request_mediator+subclassing.h" +#import "ui/base/models/image_model.h" + +@interface CollaborationOutOfDateInfobarBannerOverlayMediator () +// The banner config from the request. +@property(nonatomic, readonly) DefaultInfobarOverlayRequestConfig* config; +// The delegate attached to the config. +@property(nonatomic, readonly) + CollaborationOutOfDateInfoBarDelegate* collaborationOutOfDateDelegate; +@end + +@implementation CollaborationOutOfDateInfobarBannerOverlayMediator + +#pragma mark - Accessors + +- (DefaultInfobarOverlayRequestConfig*)config { + return self.request + ? self.request->GetConfig<DefaultInfobarOverlayRequestConfig>() + : nullptr; +} + +- (CollaborationOutOfDateInfoBarDelegate*)collaborationOutOfDateDelegate { + return static_cast<CollaborationOutOfDateInfoBarDelegate*>( + self.config->delegate()); +} + +#pragma mark - OverlayRequestMediator + ++ (const OverlayRequestSupport*)requestSupport { + return DefaultInfobarOverlayRequestConfig::RequestSupport(); +} + +#pragma mark - InfobarOverlayRequestMediator + +- (void)bannerInfobarButtonWasPressed:(UIButton*)sender { + // This can happen if the user quickly navigates to another website while the + // banner is still appearing, causing the banner to be triggered before being + // removed. + if (!self.collaborationOutOfDateDelegate) { + return; + } + + self.collaborationOutOfDateDelegate->Accept(); + [self dismissOverlay]; +} + +@end + +@implementation + CollaborationOutOfDateInfobarBannerOverlayMediator (ConsumerSupport) + +// Configures consumer from the settings in `config`. +- (void)configureConsumer { + id<InfobarBannerConsumer> consumer = self.consumer; + CollaborationOutOfDateInfoBarDelegate* delegate = + self.collaborationOutOfDateDelegate; + + [consumer + setButtonText:base::SysUTF16ToNSString(delegate->GetButtonLabel( + CollaborationOutOfDateInfoBarDelegate::BUTTON_OK))]; + + UIImage* symbolImage = delegate->GetIcon().GetImage().ToUIImage(); + [consumer setIconImage:symbolImage]; + + [consumer setPresentsModal:NO]; + [consumer setTitleText:base::SysUTF16ToNSString(delegate->GetMessageText())]; +} + +@end
diff --git a/ios/chrome/browser/overlays/ui_bundled/infobar_banner/infobar_banner_overlay_coordinator.mm b/ios/chrome/browser/overlays/ui_bundled/infobar_banner/infobar_banner_overlay_coordinator.mm index 7db2e3c..478317b 100644 --- a/ios/chrome/browser/overlays/ui_bundled/infobar_banner/infobar_banner_overlay_coordinator.mm +++ b/ios/chrome/browser/overlays/ui_bundled/infobar_banner/infobar_banner_overlay_coordinator.mm
@@ -21,6 +21,7 @@ #import "ios/chrome/browser/overlays/model/public/overlay_response.h" #import "ios/chrome/browser/overlays/ui_bundled/infobar_banner/autofill_address_profile/save_address_profile_infobar_banner_overlay_mediator.h" #import "ios/chrome/browser/overlays/ui_bundled/infobar_banner/collaboration_group/collaboration_group_infobar_banner_overlay_mediator.h" +#import "ios/chrome/browser/overlays/ui_bundled/infobar_banner/collaboration_out_of_date/collaboration_out_of_date_infobar_banner_overlay_mediator.h" #import "ios/chrome/browser/overlays/ui_bundled/infobar_banner/confirm/confirm_infobar_banner_overlay_mediator.h" #import "ios/chrome/browser/overlays/ui_bundled/infobar_banner/features.h" #import "ios/chrome/browser/overlays/ui_bundled/infobar_banner/infobar_banner_overlay_mediator.h" @@ -252,6 +253,10 @@ case InfobarType::kInfobarTypeCollaborationGroup: mediatorClass = [CollaborationGroupInfobarBannerOverlayMediator class]; break; + case InfobarType::kInfobarTypeCollaborationOutOfDate: + mediatorClass = + [CollaborationOutOfDateInfobarBannerOverlayMediator class]; + break; default: NOTREACHED() << "Received unsupported infobarType."; }
diff --git a/ios/chrome/browser/reader_mode/model/reader_mode_distiller_page.mm b/ios/chrome/browser/reader_mode/model/reader_mode_distiller_page.mm index 7d6c108..a0901cd 100644 --- a/ios/chrome/browser/reader_mode/model/reader_mode_distiller_page.mm +++ b/ios/chrome/browser/reader_mode/model/reader_mode_distiller_page.mm
@@ -31,8 +31,7 @@ if (!main_frame) { return; } - if (!main_frame->GetSecurityOrigin().IsSameOriginWith( - url::Origin::Create(url))) { + if (!main_frame->GetSecurityOrigin().IsSameOriginWith(url)) { return; }
diff --git a/ios/chrome/browser/reader_mode/model/reader_mode_tab_helper.mm b/ios/chrome/browser/reader_mode/model/reader_mode_tab_helper.mm index 9f880d4..3a4bf33 100644 --- a/ios/chrome/browser/reader_mode/model/reader_mode_tab_helper.mm +++ b/ios/chrome/browser/reader_mode/model/reader_mode_tab_helper.mm
@@ -261,7 +261,11 @@ // Generic snapshot image generation on side-swipe has a long tail latency. // Force update the snapshot storage to ensure that the latest snapshot is // presented before a transition. - SnapshotTabHelper::FromWebState(web_state_)->UpdateSnapshotWithCallback(nil); + SnapshotTabHelper* snapshot_tab_helper = + SnapshotTabHelper::FromWebState(web_state_); + if (snapshot_tab_helper) { + snapshot_tab_helper->UpdateSnapshotWithCallback(nil); + } } void ReaderModeTabHelper::ReaderModeContentDidCancelRequest( @@ -428,7 +432,11 @@ // Cancel any ongoing distillation task. distiller_viewer_.reset(); // Update the snapshot with the original web page. - SnapshotTabHelper::FromWebState(web_state_)->UpdateSnapshotWithCallback(nil); + SnapshotTabHelper* snapshot_tab_helper = + SnapshotTabHelper::FromWebState(web_state_); + if (snapshot_tab_helper) { + snapshot_tab_helper->UpdateSnapshotWithCallback(nil); + } } void ReaderModeTabHelper::SetLastCommittedUrl(const GURL& url) {
diff --git a/ios/chrome/browser/reader_mode/model/reader_mode_test.mm b/ios/chrome/browser/reader_mode/model/reader_mode_test.mm index 11d8b693..589b864 100644 --- a/ios/chrome/browser/reader_mode/model/reader_mode_test.mm +++ b/ios/chrome/browser/reader_mode/model/reader_mode_test.mm
@@ -41,7 +41,6 @@ // Attach tab helpers ReaderModeTabHelper::CreateForWebState( web_state.get(), DistillerServiceFactory::GetForProfile(profile())); - SnapshotTabHelper::CreateForWebState(web_state.get()); return web_state; }
diff --git a/ios/chrome/browser/saved_tab_groups/model/BUILD.gn b/ios/chrome/browser/saved_tab_groups/model/BUILD.gn index c75994a6..074f577b 100644 --- a/ios/chrome/browser/saved_tab_groups/model/BUILD.gn +++ b/ios/chrome/browser/saved_tab_groups/model/BUILD.gn
@@ -38,6 +38,7 @@ "//ios/chrome/browser/share_kit/model", "//ios/chrome/browser/shared/coordinator/scene:scene_state_header", "//ios/chrome/browser/shared/model/browser", + "//ios/chrome/browser/shared/model/browser:utils", "//ios/chrome/browser/shared/model/profile", "//ios/chrome/browser/shared/model/profile:profile_keyed_service_factory", "//ios/chrome/browser/shared/model/web_state_list",
diff --git a/ios/chrome/browser/saved_tab_groups/model/ios_tab_group_sync_delegate.h b/ios/chrome/browser/saved_tab_groups/model/ios_tab_group_sync_delegate.h index 6d3794c..fae7e58 100644 --- a/ios/chrome/browser/saved_tab_groups/model/ios_tab_group_sync_delegate.h +++ b/ios/chrome/browser/saved_tab_groups/model/ios_tab_group_sync_delegate.h
@@ -92,10 +92,6 @@ const LocalTabGroupID& local_tab_group_id) override; private: - // Retrieves the browser associated with the scene with the highest level of - // activation. - Browser* GetMostActiveSceneBrowser(); - // Inserts the `distant_tab` using `tab_insertion_browser_agent` at // `web_state_index`. web::WebState* InsertDistantTab(
diff --git a/ios/chrome/browser/saved_tab_groups/model/ios_tab_group_sync_delegate.mm b/ios/chrome/browser/saved_tab_groups/model/ios_tab_group_sync_delegate.mm index 9c36918..4c55286 100644 --- a/ios/chrome/browser/saved_tab_groups/model/ios_tab_group_sync_delegate.mm +++ b/ios/chrome/browser/saved_tab_groups/model/ios_tab_group_sync_delegate.mm
@@ -27,6 +27,7 @@ #import "ios/chrome/browser/shared/coordinator/scene/scene_state.h" #import "ios/chrome/browser/shared/model/browser/browser.h" #import "ios/chrome/browser/shared/model/browser/browser_list.h" +#import "ios/chrome/browser/shared/model/browser/browser_list_utils.h" #import "ios/chrome/browser/shared/model/browser/browser_provider.h" #import "ios/chrome/browser/shared/model/browser/browser_provider_interface.h" #import "ios/chrome/browser/shared/model/web_state_list/tab_group.h" @@ -458,27 +459,6 @@ /*position=*/std::nullopt, saved_tab_group_id, tab_group->tab_group_id()); } -Browser* IOSTabGroupSyncDelegate::GetMostActiveSceneBrowser() { - std::set<Browser*> all_browsers = - browser_list_->BrowsersOfType(BrowserList::BrowserType::kRegular); - - Browser* browser = nullptr; - for (Browser* browser_to_check : all_browsers) { - // The pointer to the scene state is weak, so it could be nil. In that case, - // the activation level will be 0 (lowest). - if (browser && browser->GetSceneState().activationLevel >= - browser_to_check->GetSceneState().activationLevel) { - continue; - } - browser = browser_to_check; - if (browser_to_check->GetSceneState().activationLevel == - SceneActivationLevelForegroundActive) { - break; - } - } - return browser; -} - web::WebState* IOSTabGroupSyncDelegate::InsertDistantTab( const SavedTabGroupTab& tab, TabInsertionBrowserAgent* tab_insertion_browser_agent, @@ -588,7 +568,9 @@ } // If no browser was passed, get the most active one. - browser = browser ? browser : GetMostActiveSceneBrowser(); + browser = browser + ? browser + : browser_list_utils::GetMostActiveSceneBrowser(browser_list_); if (!browser) { return std::nullopt;
diff --git a/ios/chrome/browser/search_engine_choice/model/search_engine_choice_util.h b/ios/chrome/browser/search_engine_choice/model/search_engine_choice_util.h index df5d0eab..c39e77c 100644 --- a/ios/chrome/browser/search_engine_choice/model/search_engine_choice_util.h +++ b/ios/chrome/browser/search_engine_choice/model/search_engine_choice_util.h
@@ -9,6 +9,12 @@ // Utilities for the search engine choice screen. +// Maximum number of times the search engine choice screen can be skipped +// because the application is started via an external intent. Once this +// count is reached, the search engine choice screen is presented on all +// restarts until the user has made a decision. +constexpr int kSearchEngineChoiceMaximumSkipCount = 10; + // Whether or not the choice screen should be displayed for existing users. // The parameter `app_started_via_external_intent` is used only if // `is_first_run_entrypoint` is set to `false . The value is ignored otherwise.
diff --git a/ios/chrome/browser/search_engine_choice/model/search_engine_choice_util.mm b/ios/chrome/browser/search_engine_choice/model/search_engine_choice_util.mm index 6d154864..4b440c0 100644 --- a/ios/chrome/browser/search_engine_choice/model/search_engine_choice_util.mm +++ b/ios/chrome/browser/search_engine_choice/model/search_engine_choice_util.mm
@@ -71,8 +71,8 @@ *template_url_service); } - // If the app has been started via an external intent, and skip the Dialog - // promo up to switches::kSearchEngineChoiceMaximumSkipCount() times. + // If the app has been started via an external intent, skip the Dialog + // promo up to kSearchEngineChoiceMaximumSkipCount times. if (app_started_via_external_intent && !is_first_run_entrypoint && condition == search_engines::SearchEngineChoiceScreenConditions::kEligible) { @@ -80,7 +80,7 @@ const int count = pref_service->GetInteger( prefs::kDefaultSearchProviderChoiceScreenSkippedCount); - if (count < switches::kSearchEngineChoiceMaximumSkipCount.Get()) { + if (count < kSearchEngineChoiceMaximumSkipCount) { pref_service->SetInteger( prefs::kDefaultSearchProviderChoiceScreenSkippedCount, count + 1);
diff --git a/ios/chrome/browser/search_engine_choice/model/search_engine_choice_util_unittest.mm b/ios/chrome/browser/search_engine_choice/model/search_engine_choice_util_unittest.mm index d965df3..a2fc941d 100644 --- a/ios/chrome/browser/search_engine_choice/model/search_engine_choice_util_unittest.mm +++ b/ios/chrome/browser/search_engine_choice/model/search_engine_choice_util_unittest.mm
@@ -94,11 +94,11 @@ TEST_F( SearchEngineChoiceUtilTest, ShowChoiceScreenIfPoliciesAreNotSetStartedByExternalIntentAlmostMaxCount) { - ASSERT_GT(switches::kSearchEngineChoiceMaximumSkipCount.Get(), 0); + ASSERT_GT(kSearchEngineChoiceMaximumSkipCount, 0); PrefService* pref_service = profile().GetPrefs(); pref_service->SetInteger( prefs::kDefaultSearchProviderChoiceScreenSkippedCount, - switches::kSearchEngineChoiceMaximumSkipCount.Get() - 1); + kSearchEngineChoiceMaximumSkipCount - 1); EXPECT_FALSE(ShouldDisplaySearchEngineChoiceScreen( profile(), /*is_first_run_entrypoint=*/false, @@ -116,7 +116,7 @@ PrefService* pref_service = profile().GetPrefs(); pref_service->SetInteger( prefs::kDefaultSearchProviderChoiceScreenSkippedCount, - switches::kSearchEngineChoiceMaximumSkipCount.Get()); + kSearchEngineChoiceMaximumSkipCount); EXPECT_TRUE(ShouldDisplaySearchEngineChoiceScreen( profile(), /*is_first_run_entrypoint=*/false,
diff --git a/ios/chrome/browser/search_engine_choice/ui_bundled/search_engine_choice_egtest.mm b/ios/chrome/browser/search_engine_choice/ui_bundled/search_engine_choice_egtest.mm index 75b23cd..eab344e 100644 --- a/ios/chrome/browser/search_engine_choice/ui_bundled/search_engine_choice_egtest.mm +++ b/ios/chrome/browser/search_engine_choice/ui_bundled/search_engine_choice_egtest.mm
@@ -69,6 +69,10 @@ // Tests that search engine choice dialog is moved to the other active scene // when the current scene is removed. - (void)testOpenSecondWindow { + if (@available(iOS 19.0, *)) { + // TODO(crbug.com/427699033): Re-enable test on iOS 26. + EARL_GREY_TEST_DISABLED(@"Test disabled on iOS 26."); + } if (![ChromeEarlGrey areMultipleWindowsSupported]) { EARL_GREY_TEST_DISABLED(@"Multiple windows can't be opened."); }
diff --git a/ios/chrome/browser/share_kit/model/BUILD.gn b/ios/chrome/browser/share_kit/model/BUILD.gn index e934a52b..1dd1641 100644 --- a/ios/chrome/browser/share_kit/model/BUILD.gn +++ b/ios/chrome/browser/share_kit/model/BUILD.gn
@@ -9,8 +9,6 @@ "share_kit_avatar_primitive.h", "share_kit_delete_configuration.h", "share_kit_delete_configuration.mm", - "share_kit_face_pile_configuration.h", - "share_kit_face_pile_configuration.mm", "share_kit_flow_outcome.h", "share_kit_join_configuration.h", "share_kit_join_configuration.mm",
diff --git a/ios/chrome/browser/share_kit/model/share_kit_face_pile_configuration.h b/ios/chrome/browser/share_kit/model/share_kit_face_pile_configuration.h deleted file mode 100644 index e706d24..0000000 --- a/ios/chrome/browser/share_kit/model/share_kit_face_pile_configuration.h +++ /dev/null
@@ -1,28 +0,0 @@ -// Copyright 2024 The Chromium Authors -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef IOS_CHROME_BROWSER_SHARE_KIT_MODEL_SHARE_KIT_FACE_PILE_CONFIGURATION_H_ -#define IOS_CHROME_BROWSER_SHARE_KIT_MODEL_SHARE_KIT_FACE_PILE_CONFIGURATION_H_ - -#import <UIKit/UIKit.h> - -// Configuration object for the ShareKit FacePile API. -@interface ShareKitFacePileConfiguration : NSObject - -// Shared group ID. -@property(nonatomic, copy) NSString* collabID; - -// The background color for the face pile when it is not empty. -@property(nonatomic, strong) UIColor* backgroundColor; - -// Whether the face pile should be visible when the group is empty (not shared -// or shared with no members). -@property(nonatomic, assign) BOOL showsEmptyState; - -// The preferred size in points for the avatar icons. -@property(nonatomic, assign) CGFloat avatarSize; - -@end - -#endif // IOS_CHROME_BROWSER_SHARE_KIT_MODEL_SHARE_KIT_FACE_PILE_CONFIGURATION_H_
diff --git a/ios/chrome/browser/share_kit/model/share_kit_face_pile_configuration.mm b/ios/chrome/browser/share_kit/model/share_kit_face_pile_configuration.mm deleted file mode 100644 index 754a0f1..0000000 --- a/ios/chrome/browser/share_kit/model/share_kit_face_pile_configuration.mm +++ /dev/null
@@ -1,9 +0,0 @@ -// Copyright 2024 The Chromium Authors -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#import "ios/chrome/browser/share_kit/model/share_kit_face_pile_configuration.h" - -@implementation ShareKitFacePileConfiguration - -@end
diff --git a/ios/chrome/browser/share_kit/model/share_kit_service.h b/ios/chrome/browser/share_kit/model/share_kit_service.h index 7a68d4f..0729485 100644 --- a/ios/chrome/browser/share_kit/model/share_kit_service.h +++ b/ios/chrome/browser/share_kit/model/share_kit_service.h
@@ -13,7 +13,6 @@ @protocol ShareKitAvatarPrimitive; @class ShareKitAvatarConfiguration; @class ShareKitDeleteConfiguration; -@class ShareKitFacePileConfiguration; @class ShareKitJoinConfiguration; @class ShareKitLeaveConfiguration; @class ShareKitLookupGaiaIDConfiguration; @@ -52,9 +51,6 @@ // sessionID. virtual NSString* JoinTabGroup(ShareKitJoinConfiguration* config) = 0; - // Returns a new FacePile view for the given `config`. - virtual UIView* FacePileView(ShareKitFacePileConfiguration* config); - // Reads the info for the groups passed in `config` and returns the result // through the config callback. virtual void ReadGroups(ShareKitReadGroupsConfiguration* config) = 0;
diff --git a/ios/chrome/browser/share_kit/model/share_kit_service.mm b/ios/chrome/browser/share_kit/model/share_kit_service.mm index 0023f71..ac270f53 100644 --- a/ios/chrome/browser/share_kit/model/share_kit_service.mm +++ b/ios/chrome/browser/share_kit/model/share_kit_service.mm
@@ -7,7 +7,3 @@ ShareKitService::ShareKitService() = default; ShareKitService::~ShareKitService() = default; - -UIView* ShareKitService::FacePileView(ShareKitFacePileConfiguration* config) { - return nil; -}
diff --git a/ios/chrome/browser/share_kit/model/test_share_kit_service.mm b/ios/chrome/browser/share_kit/model/test_share_kit_service.mm index c144ae1..92edccd 100644 --- a/ios/chrome/browser/share_kit/model/test_share_kit_service.mm +++ b/ios/chrome/browser/share_kit/model/test_share_kit_service.mm
@@ -16,7 +16,6 @@ #import "ios/chrome/browser/saved_tab_groups/model/tab_group_service.h" #import "ios/chrome/browser/share_kit/model/fake_share_kit_flow_view_controller.h" #import "ios/chrome/browser/share_kit/model/share_kit_delete_configuration.h" -#import "ios/chrome/browser/share_kit/model/share_kit_face_pile_configuration.h" #import "ios/chrome/browser/share_kit/model/share_kit_join_configuration.h" #import "ios/chrome/browser/share_kit/model/share_kit_leave_configuration.h" #import "ios/chrome/browser/share_kit/model/share_kit_manage_configuration.h"
diff --git a/ios/chrome/browser/shared/model/browser/BUILD.gn b/ios/chrome/browser/shared/model/browser/BUILD.gn index 91fec81..235f8a6 100644 --- a/ios/chrome/browser/shared/model/browser/BUILD.gn +++ b/ios/chrome/browser/shared/model/browser/BUILD.gn
@@ -32,6 +32,19 @@ ] } +source_set("utils") { + sources = [ + "browser_list_utils.h", + "browser_list_utils.mm", + ] + + deps = [ + ":browser", + "//base", + "//ios/chrome/browser/shared/coordinator/scene:scene_state_header", + ] +} + source_set("unit_tests") { testonly = true sources = [
diff --git a/ios/chrome/browser/shared/model/browser/browser_list_utils.h b/ios/chrome/browser/shared/model/browser/browser_list_utils.h new file mode 100644 index 0000000..e310e59 --- /dev/null +++ b/ios/chrome/browser/shared/model/browser/browser_list_utils.h
@@ -0,0 +1,21 @@ +// Copyright 2025 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef IOS_CHROME_BROWSER_SHARED_MODEL_BROWSER_BROWSER_LIST_UTILS_H_ +#define IOS_CHROME_BROWSER_SHARED_MODEL_BROWSER_BROWSER_LIST_UTILS_H_ + +class Browser; +class BrowserList; + +namespace browser_list_utils { + +// Returns the most recently foregrounded regular browser from `browser_list`. +// If no regular browser is foregrounded, it returns the most recently +// foregrounded regular browser in the background. If no regular browser +// exists, it returns `nullptr`. +Browser* GetMostActiveSceneBrowser(BrowserList* browser_list); + +} // namespace browser_list_utils + +#endif // IOS_CHROME_BROWSER_SHARED_MODEL_BROWSER_BROWSER_LIST_UTILS_H_
diff --git a/ios/chrome/browser/shared/model/browser/browser_list_utils.mm b/ios/chrome/browser/shared/model/browser/browser_list_utils.mm new file mode 100644 index 0000000..29d6acb --- /dev/null +++ b/ios/chrome/browser/shared/model/browser/browser_list_utils.mm
@@ -0,0 +1,35 @@ +// Copyright 2025 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#import "ios/chrome/browser/shared/model/browser/browser_list_utils.h" + +#import "ios/chrome/browser/shared/coordinator/scene/scene_state.h" +#import "ios/chrome/browser/shared/model/browser/browser.h" +#import "ios/chrome/browser/shared/model/browser/browser_list.h" + +namespace browser_list_utils { + +Browser* GetMostActiveSceneBrowser(BrowserList* browser_list) { + std::set<Browser*> all_browsers = + browser_list->BrowsersOfType(BrowserList::BrowserType::kRegular); + + Browser* most_active_browser = nullptr; + for (Browser* browser_to_check : all_browsers) { + // The pointer to the scene state is weak, so it could be nil. In that case, + // the activation level will be 0 (lowest). + if (most_active_browser && + most_active_browser->GetSceneState().activationLevel >= + browser_to_check->GetSceneState().activationLevel) { + continue; + } + most_active_browser = browser_to_check; + if (browser_to_check->GetSceneState().activationLevel == + SceneActivationLevelForegroundActive) { + break; + } + } + return most_active_browser; +} + +} // namespace browser_list_utils
diff --git a/ios/chrome/browser/shared/ui/symbols/chrome_icon.h b/ios/chrome/browser/shared/ui/symbols/chrome_icon.h index ed64a1a..aec7f93 100644 --- a/ios/chrome/browser/shared/ui/symbols/chrome_icon.h +++ b/ios/chrome/browser/shared/ui/symbols/chrome_icon.h
@@ -18,14 +18,6 @@ + (UIImage*)searchIcon; + (UIImage*)chevronIcon; -// Returns a "template" UIBarButtonItem initialized with the given image. -// The returned bar button item will have its image rendering mode set to -// UIImageRenderingModeAlwaysTemplate and will thus take on the tint color of -// its container. -+ (UIBarButtonItem*)templateBarButtonItemWithImage:(UIImage*)image - target:(id)target - action:(SEL)action; - @end #endif // IOS_CHROME_BROWSER_SHARED_UI_SYMBOLS_CHROME_ICON_H_
diff --git a/ios/chrome/browser/shared/ui/symbols/chrome_icon.mm b/ios/chrome/browser/shared/ui/symbols/chrome_icon.mm index 5135cc9..2b5f9e9 100644 --- a/ios/chrome/browser/shared/ui/symbols/chrome_icon.mm +++ b/ios/chrome/browser/shared/ui/symbols/chrome_icon.mm
@@ -72,19 +72,4 @@ IconNamed(@"ic_chevron_right")); } -+ (UIBarButtonItem*)templateBarButtonItemWithImage:(UIImage*)image - target:(id)target - action:(SEL)action { - UIImage* templateImage = - [image imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; - UIBarButtonItem* barButtonItem = - [[UIBarButtonItem alloc] initWithImage:templateImage - style:UIBarButtonItemStylePlain - target:target - action:action]; - [barButtonItem setAccessibilityIdentifier:image.accessibilityIdentifier]; - [barButtonItem setAccessibilityLabel:image.accessibilityLabel]; - return barButtonItem; -} - @end
diff --git a/ios/chrome/browser/shared/ui/symbols/chrome_icon_unittest.mm b/ios/chrome/browser/shared/ui/symbols/chrome_icon_unittest.mm index 2beae01ee..0e29c33c 100644 --- a/ios/chrome/browser/shared/ui/symbols/chrome_icon_unittest.mm +++ b/ios/chrome/browser/shared/ui/symbols/chrome_icon_unittest.mm
@@ -46,33 +46,4 @@ EXPECT_FALSE([ChromeIcon searchIcon].flipsForRightToLeftLayoutDirection); } -// TODO(crbug.com/422438137): Re-enable the test. `doSomething` is not called. -TEST_F(ChromeIconTest, DISABLED_TemplateBarButtonItem) { - UIImage* image = [UIImage imageNamed:@"ic_close"]; - image.accessibilityIdentifier = @"identifier"; - image.accessibilityLabel = @"label"; - id mockTarget = [OCMockObject mockForProtocol:@protocol(MockTarget)]; - [[mockTarget expect] doSomething]; - - UIBarButtonItem* barButtonItem = - [ChromeIcon templateBarButtonItemWithImage:image - target:mockTarget - action:@selector(doSomething)]; - - EXPECT_NSEQ(@"identifier", barButtonItem.accessibilityIdentifier); - EXPECT_NSEQ(@"label", barButtonItem.accessibilityLabel); - EXPECT_EQ(image.size.width, barButtonItem.image.size.width); - EXPECT_EQ(image.size.height, barButtonItem.image.size.height); - EXPECT_EQ(image.scale, barButtonItem.image.scale); - EXPECT_EQ(image.capInsets.top, barButtonItem.image.capInsets.top); - EXPECT_EQ(image.capInsets.left, barButtonItem.image.capInsets.left); - EXPECT_EQ(image.capInsets.bottom, barButtonItem.image.capInsets.bottom); - EXPECT_EQ(image.capInsets.right, barButtonItem.image.capInsets.right); - EXPECT_EQ(image.flipsForRightToLeftLayoutDirection, - barButtonItem.image.flipsForRightToLeftLayoutDirection); - EXPECT_EQ(UIImageRenderingModeAlwaysTemplate, - barButtonItem.image.renderingMode); - EXPECT_OCMOCK_VERIFY(mockTarget); -} - } // namespace
diff --git a/ios/chrome/browser/tab_switcher/ui_bundled/tab_grid/grid/base_grid_mediator.mm b/ios/chrome/browser/tab_switcher/ui_bundled/tab_grid/grid/base_grid_mediator.mm index f2e87ea..cf2e7ae 100644 --- a/ios/chrome/browser/tab_switcher/ui_bundled/tab_grid/grid/base_grid_mediator.mm +++ b/ios/chrome/browser/tab_switcher/ui_bundled/tab_grid/grid/base_grid_mediator.mm
@@ -1605,7 +1605,7 @@ // Updates toolbars when the number of web state might be changed. - (void)updateToolbarAfterNumberOfItemsChanged { if (_modeHolder.mode == TabGridMode::kSelection && - self.webStateList->empty()) { + self.webStateList->regular_tabs_count() == 0) { // Exit selection mode if there are no more tabs. _modeHolder.mode = TabGridMode::kNormal; } else {
diff --git a/ios/chrome/browser/tab_switcher/ui_bundled/tab_grid/tab_groups/tab_group_coordinator.mm b/ios/chrome/browser/tab_switcher/ui_bundled/tab_grid/tab_groups/tab_group_coordinator.mm index 0ede16a..41943735 100644 --- a/ios/chrome/browser/tab_switcher/ui_bundled/tab_grid/tab_groups/tab_group_coordinator.mm +++ b/ios/chrome/browser/tab_switcher/ui_bundled/tab_grid/tab_groups/tab_group_coordinator.mm
@@ -26,7 +26,6 @@ #import "ios/chrome/browser/saved_tab_groups/model/ios_tab_group_sync_util.h" #import "ios/chrome/browser/saved_tab_groups/model/tab_group_sync_service_factory.h" #import "ios/chrome/browser/saved_tab_groups/ui/face_pile_providing.h" -#import "ios/chrome/browser/share_kit/model/share_kit_face_pile_configuration.h" #import "ios/chrome/browser/share_kit/model/share_kit_manage_configuration.h" #import "ios/chrome/browser/share_kit/model/share_kit_service.h" #import "ios/chrome/browser/share_kit/model/share_kit_service_factory.h"
diff --git a/ios/chrome/browser/tab_switcher/ui_bundled/tab_strip/coordinator/tab_strip_mediator.mm b/ios/chrome/browser/tab_switcher/ui_bundled/tab_strip/coordinator/tab_strip_mediator.mm index 1952cda..1cfe5de 100644 --- a/ios/chrome/browser/tab_switcher/ui_bundled/tab_strip/coordinator/tab_strip_mediator.mm +++ b/ios/chrome/browser/tab_switcher/ui_bundled/tab_strip/coordinator/tab_strip_mediator.mm
@@ -30,7 +30,6 @@ #import "ios/chrome/browser/saved_tab_groups/model/tab_group_service.h" #import "ios/chrome/browser/saved_tab_groups/model/tab_group_service_factory.h" #import "ios/chrome/browser/saved_tab_groups/ui/tab_group_utils.h" -#import "ios/chrome/browser/share_kit/model/share_kit_face_pile_configuration.h" #import "ios/chrome/browser/share_kit/model/share_kit_service.h" #import "ios/chrome/browser/shared/model/browser/browser.h" #import "ios/chrome/browser/shared/model/browser/browser_list.h"
diff --git a/ios/chrome/browser/tabs/model/BUILD.gn b/ios/chrome/browser/tabs/model/BUILD.gn index 45bfe18..03c745d 100644 --- a/ios/chrome/browser/tabs/model/BUILD.gn +++ b/ios/chrome/browser/tabs/model/BUILD.gn
@@ -106,6 +106,7 @@ "//ios/chrome/browser/policy_url_blocking/model", "//ios/chrome/browser/prerender/model", "//ios/chrome/browser/reader_mode/model", + "//ios/chrome/browser/reader_mode/model:features", "//ios/chrome/browser/reading_list/model", "//ios/chrome/browser/safe_browsing/model", "//ios/chrome/browser/safe_browsing/model/tailored_security",
diff --git a/ios/chrome/browser/tabs/model/tab_helper_util.mm b/ios/chrome/browser/tabs/model/tab_helper_util.mm index 9d3d978..45dd7f2 100644 --- a/ios/chrome/browser/tabs/model/tab_helper_util.mm +++ b/ios/chrome/browser/tabs/model/tab_helper_util.mm
@@ -89,6 +89,7 @@ #import "ios/chrome/browser/permissions/model/permissions_tab_helper.h" #import "ios/chrome/browser/policy_url_blocking/model/policy_url_blocking_tab_helper.h" #import "ios/chrome/browser/prerender/model/prerender_service_factory.h" +#import "ios/chrome/browser/reader_mode/model/features.h" #import "ios/chrome/browser/reader_mode/model/reader_mode_tab_helper.h" #import "ios/chrome/browser/reading_list/model/offline_page_tab_helper.h" #import "ios/chrome/browser/reading_list/model/reading_list_model_factory.h" @@ -215,8 +216,10 @@ AppLauncherTabHelper::CreateForWebState( web_state, [[AppLauncherAbuseDetector alloc] init], is_off_the_record); - ReaderModeTabHelper::CreateForWebState( - web_state, DistillerServiceFactory::GetForProfile(profile)); + if (IsReaderModeAvailable()) { + ReaderModeTabHelper::CreateForWebState( + web_state, DistillerServiceFactory::GetForProfile(profile)); + } } security_interstitials::IOSBlockingPageTabHelper::CreateForWebState( web_state);
diff --git a/ios/chrome/browser/toolbar/ui_bundled/tab_groups/coordinator/tab_group_indicator_mediator.mm b/ios/chrome/browser/toolbar/ui_bundled/tab_groups/coordinator/tab_group_indicator_mediator.mm index 575ea923..4e8c908 100644 --- a/ios/chrome/browser/toolbar/ui_bundled/tab_groups/coordinator/tab_group_indicator_mediator.mm +++ b/ios/chrome/browser/toolbar/ui_bundled/tab_groups/coordinator/tab_group_indicator_mediator.mm
@@ -19,7 +19,6 @@ #import "ios/chrome/browser/data_sharing/model/data_sharing_service_observer_bridge.h" #import "ios/chrome/browser/saved_tab_groups/model/ios_tab_group_sync_util.h" #import "ios/chrome/browser/saved_tab_groups/ui/tab_group_utils.h" -#import "ios/chrome/browser/share_kit/model/share_kit_face_pile_configuration.h" #import "ios/chrome/browser/share_kit/model/share_kit_manage_configuration.h" #import "ios/chrome/browser/share_kit/model/share_kit_service.h" #import "ios/chrome/browser/share_kit/model/sharing_state.h"
diff --git a/ios/chrome/widget_kit_extension/dino_game_widget.swift b/ios/chrome/widget_kit_extension/dino_game_widget.swift index cfdf98d..1330f41 100644 --- a/ios/chrome/widget_kit_extension/dino_game_widget.swift +++ b/ios/chrome/widget_kit_extension/dino_game_widget.swift
@@ -74,7 +74,6 @@ .foregroundColor(Color("widget_text_color")) .fontWeight(.semibold) .font(.subheadline) - .lineLimit(1) Spacer() if ChromeWidgetsMain.WidgetForMIMAvailable { AvatarForDinoGame(entry: entry)
diff --git a/ios/google_internal/frameworks/ChromeExtensionKeychainInternal.framework.dSYM.ios.zip.sha1 b/ios/google_internal/frameworks/ChromeExtensionKeychainInternal.framework.dSYM.ios.zip.sha1 index 7fe8ae7..003f788d 100644 --- a/ios/google_internal/frameworks/ChromeExtensionKeychainInternal.framework.dSYM.ios.zip.sha1 +++ b/ios/google_internal/frameworks/ChromeExtensionKeychainInternal.framework.dSYM.ios.zip.sha1
@@ -1 +1 @@ -643e5ee4758c958166e43b4ff1bb73395ce64ac0 \ No newline at end of file +9c9b261ff522426bf1306354f460074409f58092 \ No newline at end of file
diff --git a/ios/google_internal/frameworks/ChromeExtensionKeychainInternal.framework.dSYM.ios_asan.zip.sha1 b/ios/google_internal/frameworks/ChromeExtensionKeychainInternal.framework.dSYM.ios_asan.zip.sha1 index 4aa45d6..877ecec 100644 --- a/ios/google_internal/frameworks/ChromeExtensionKeychainInternal.framework.dSYM.ios_asan.zip.sha1 +++ b/ios/google_internal/frameworks/ChromeExtensionKeychainInternal.framework.dSYM.ios_asan.zip.sha1
@@ -1 +1 @@ -e4bc9cdc1a1f7568ddd724b55e2d4d49c4f877d5 \ No newline at end of file +f1a467d2fc7116eee7940081e024e613ce6ecf2d \ No newline at end of file
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 766a79e..efba0f4 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 @@ -8c93c13acb02ebe9543381598de23d1b55953ddb \ No newline at end of file +caa49b42989056ab6e320210322d4849b33f7d51 \ No newline at end of file
diff --git a/ios/google_internal/frameworks/ChromeInternal.framework.dSYM.ios_asan.zip.sha1 b/ios/google_internal/frameworks/ChromeInternal.framework.dSYM.ios_asan.zip.sha1 index 6b0ee4f..bf56edf 100644 --- a/ios/google_internal/frameworks/ChromeInternal.framework.dSYM.ios_asan.zip.sha1 +++ b/ios/google_internal/frameworks/ChromeInternal.framework.dSYM.ios_asan.zip.sha1
@@ -1 +1 @@ -d2d0dbc5f436bb216c01977c12916eb02774168e \ No newline at end of file +07ea28ee9c0f287d1c567ec63f4d9263beb7e76b \ 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 d710ed0..5434634 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 @@ -43d88724c76cd6a2fc8220dc281f8eae619e11df \ No newline at end of file +a2563778edd44050735f961ab4311029bcdedf12 \ No newline at end of file
diff --git a/ios/google_internal/frameworks/ChromeSSOInternal.framework.dSYM.ios_asan.zip.sha1 b/ios/google_internal/frameworks/ChromeSSOInternal.framework.dSYM.ios_asan.zip.sha1 index 42398e15..e0606ca2 100644 --- a/ios/google_internal/frameworks/ChromeSSOInternal.framework.dSYM.ios_asan.zip.sha1 +++ b/ios/google_internal/frameworks/ChromeSSOInternal.framework.dSYM.ios_asan.zip.sha1
@@ -1 +1 @@ -45b402bcf874b26ce1e40769a83d9c06a01e513e \ No newline at end of file +db96db0c2f160de2ee8325511135269a230f3cd3 \ No newline at end of file
diff --git a/ios/google_internal/frameworks/chrome_extension_keychain_internal_dynamic_framework.ios.zip.sha1 b/ios/google_internal/frameworks/chrome_extension_keychain_internal_dynamic_framework.ios.zip.sha1 index 92fd8618..089d69a 100644 --- a/ios/google_internal/frameworks/chrome_extension_keychain_internal_dynamic_framework.ios.zip.sha1 +++ b/ios/google_internal/frameworks/chrome_extension_keychain_internal_dynamic_framework.ios.zip.sha1
@@ -1 +1 @@ -ece3752e7a5df95f6e2a059f1ab9342c59dede9a \ No newline at end of file +febc2b8c24c08c7c67f092d6f0478414dfde553c \ No newline at end of file
diff --git a/ios/google_internal/frameworks/chrome_extension_keychain_internal_dynamic_framework.ios_asan.zip.sha1 b/ios/google_internal/frameworks/chrome_extension_keychain_internal_dynamic_framework.ios_asan.zip.sha1 index 1f855b2f..51d47b81 100644 --- a/ios/google_internal/frameworks/chrome_extension_keychain_internal_dynamic_framework.ios_asan.zip.sha1 +++ b/ios/google_internal/frameworks/chrome_extension_keychain_internal_dynamic_framework.ios_asan.zip.sha1
@@ -1 +1 @@ -23f9978d836dd681d7a39a74b5de9133e8d52678 \ No newline at end of file +363a8c122d6e2a63e0779cf7ddd14fd91aa7d8c4 \ No newline at end of file
diff --git a/ios/google_internal/frameworks/chrome_extension_keychain_internal_dynamic_framework.iossimulator.zip.sha1 b/ios/google_internal/frameworks/chrome_extension_keychain_internal_dynamic_framework.iossimulator.zip.sha1 index e2ec8e6..bd29cea 100644 --- a/ios/google_internal/frameworks/chrome_extension_keychain_internal_dynamic_framework.iossimulator.zip.sha1 +++ b/ios/google_internal/frameworks/chrome_extension_keychain_internal_dynamic_framework.iossimulator.zip.sha1
@@ -1 +1 @@ -d1478f6abea7ce7d673d19e006fae6b6bbf60c61 \ No newline at end of file +28cb75d7f5e5ff1083ebfa13792cc0081101318a \ No newline at end of file
diff --git a/ios/google_internal/frameworks/chrome_extension_keychain_internal_dynamic_framework.iossimulator_asan.zip.sha1 b/ios/google_internal/frameworks/chrome_extension_keychain_internal_dynamic_framework.iossimulator_asan.zip.sha1 index defaa011..540bb13 100644 --- a/ios/google_internal/frameworks/chrome_extension_keychain_internal_dynamic_framework.iossimulator_asan.zip.sha1 +++ b/ios/google_internal/frameworks/chrome_extension_keychain_internal_dynamic_framework.iossimulator_asan.zip.sha1
@@ -1 +1 @@ -559003cbd16327ba1df69d102cf9cc487c5929a5 \ No newline at end of file +d1d68ab20490306a22849108fb462d197f3ba10a \ 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 cf898c7..6eb7d39 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 @@ -2c737d9c46b86a0573bb16e9d9a5842e979ac76e \ No newline at end of file +4b8bd60a023e18124e14e909a429561eee00fe48 \ No newline at end of file
diff --git a/ios/google_internal/frameworks/chrome_internal_dynamic_framework.ios_asan.zip.sha1 b/ios/google_internal/frameworks/chrome_internal_dynamic_framework.ios_asan.zip.sha1 index 2140b6a5..38a90e3 100644 --- a/ios/google_internal/frameworks/chrome_internal_dynamic_framework.ios_asan.zip.sha1 +++ b/ios/google_internal/frameworks/chrome_internal_dynamic_framework.ios_asan.zip.sha1
@@ -1 +1 @@ -93790b6475c84afe45517a5490ebb6d8f9e8d62a \ No newline at end of file +1a3bbaddf46cbb4cf39192f34f1aa10cfce1f4b9 \ 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 3672fb20..c8b4d82 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 @@ -347695f4679ee2a0940e2ef8ef1329dcd5badb6e \ No newline at end of file +17ebf74348d24ca7af0f276466d26cca62c2460c \ No newline at end of file
diff --git a/ios/google_internal/frameworks/chrome_internal_dynamic_framework.iossimulator_asan.zip.sha1 b/ios/google_internal/frameworks/chrome_internal_dynamic_framework.iossimulator_asan.zip.sha1 index bb012ff..10fb023 100644 --- a/ios/google_internal/frameworks/chrome_internal_dynamic_framework.iossimulator_asan.zip.sha1 +++ b/ios/google_internal/frameworks/chrome_internal_dynamic_framework.iossimulator_asan.zip.sha1
@@ -1 +1 @@ -531548da84cbc0a75861842f164186586998a1c9 \ No newline at end of file +670e3f07ff3f27573592604bd7ee749cc78e1eef \ 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 289f6ef..78d0611 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 @@ -abdf00299e852f67ae7cf014b2b76e86892edc1e \ No newline at end of file +fb46418e71b390aa0f2a298929df800a42a67abf \ No newline at end of file
diff --git a/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.ios_asan.zip.sha1 b/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.ios_asan.zip.sha1 index d18102f..549d911 100644 --- a/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.ios_asan.zip.sha1 +++ b/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.ios_asan.zip.sha1
@@ -1 +1 @@ -0607e964b29cac37b5f3f4e90233cfb10564a7c3 \ No newline at end of file +8bf1613c48adb25300fb479aba5619cf0dd288a1 \ 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 938e8464..fe463a3 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 @@ -a282bafe24435de3da7fb1e95b085afd693a02e7 \ No newline at end of file +6b0de33c838e9d7ad033806337986e861075546d \ No newline at end of file
diff --git a/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.iossimulator_asan.zip.sha1 b/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.iossimulator_asan.zip.sha1 index 872069d0..cbb6c802 100644 --- a/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.iossimulator_asan.zip.sha1 +++ b/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.iossimulator_asan.zip.sha1
@@ -1 +1 @@ -af98fe0ca7660d8d45e9cc34af8fb8ab8e872a95 \ No newline at end of file +970b416aadbed991a274a8da175617b0a5d41149 \ 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 ffea91f..b43cd94f 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 @@ -abf47e0a02385f22db0c4f9c8efe5bdfccbb26f3 \ No newline at end of file +8f38e55f5a7574c6d06128ed401e660305f085e7 \ 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 b991249..c408418 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 @@ -707bbf52d0843f96c0a269a7fd4bcd7a00a7706c \ No newline at end of file +50bae84597c0920e48bd0e205fc6aa7a6760abde \ 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 5c7950c3..547f9c8 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 @@ -0d178119a645974919b8d962568fcef7be56333e \ No newline at end of file +e0dfe6810c72ebf470ddd92c8c2922f334806c88 \ No newline at end of file
diff --git a/ios/google_internal/frameworks/remoting_internal_dynamic_framework.ios_asan.zip.sha1 b/ios/google_internal/frameworks/remoting_internal_dynamic_framework.ios_asan.zip.sha1 index 8f614b6d..bddb6ee1 100644 --- a/ios/google_internal/frameworks/remoting_internal_dynamic_framework.ios_asan.zip.sha1 +++ b/ios/google_internal/frameworks/remoting_internal_dynamic_framework.ios_asan.zip.sha1
@@ -1 +1 @@ -ba13573e3c014fa1ec5cc721b1724cd45b7e2258 \ No newline at end of file +09db8a1eac9d7dd848f9862cfd34b6946d516669 \ 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 74a4cd71..a40d3e6 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 @@ -08c5ced2ade924ddf8306fbfde1fdf496cd2c5cb \ No newline at end of file +9f0527ad5bef64622b3dd39867f930c4f5bfa3cd \ No newline at end of file
diff --git a/ios/google_internal/frameworks/remoting_internal_dynamic_framework.iossimulator_asan.zip.sha1 b/ios/google_internal/frameworks/remoting_internal_dynamic_framework.iossimulator_asan.zip.sha1 index 1e19058..9d715d2 100644 --- a/ios/google_internal/frameworks/remoting_internal_dynamic_framework.iossimulator_asan.zip.sha1 +++ b/ios/google_internal/frameworks/remoting_internal_dynamic_framework.iossimulator_asan.zip.sha1
@@ -1 +1 @@ -b097e4058e9819890d14d408b621fad0b3df83c1 \ No newline at end of file +569337f331d9dc6f1d4831e77093c19117ef6c06 \ 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 0d5dd57..98c06d99 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 @@ -a846625fa5d62daf5822bdf55ad0cd633f7c65a0 \ No newline at end of file +bc324abaad84dae5b85d840cfdf83b9ae32c3da9 \ No newline at end of file
diff --git a/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.ios_asan.zip.sha1 b/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.ios_asan.zip.sha1 index 08570db..2d04862 100644 --- a/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.ios_asan.zip.sha1 +++ b/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.ios_asan.zip.sha1
@@ -1 +1 @@ -036f69e23d72dfebba9c05347ba76047a5018d33 \ No newline at end of file +03c0e00670c9b7272f78a9c4a7a922f712de1857 \ 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 fada6332..31fbcb6 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 @@ -f13a8045d4ec4ea554455df21381e8db2fbc2a90 \ No newline at end of file +38526642873bb37c7afa41c4029aadf4e2cd6932 \ No newline at end of file
diff --git a/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.iossimulator_asan.zip.sha1 b/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.iossimulator_asan.zip.sha1 index 71b8f14..2295a1b 100644 --- a/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.iossimulator_asan.zip.sha1 +++ b/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.iossimulator_asan.zip.sha1
@@ -1 +1 @@ -96117af89b107bf8805cbbf1e28394a09871696d \ No newline at end of file +3d3009deb7d24e603f81ac25e3ee572e7672eebc \ No newline at end of file
diff --git a/ios/web/web_state/web_state_unittest.mm b/ios/web/web_state/web_state_unittest.mm index 43bd83b..2284ecf 100644 --- a/ios/web/web_state/web_state_unittest.mm +++ b/ios/web/web_state/web_state_unittest.mm
@@ -260,7 +260,15 @@ // Tests that CreateFullPagePdf invokes completion callback nil when the // WebState content is not HTML (e.g. a PDF file). -TEST_F(WebStateTest, CreateFullPagePdfWebStatePdfContent) { +// TODO(crbug.com/428630864): Flaky on device. +#if TARGET_IPHONE_SIMULATOR +#define MAYBE_CreateFullPagePdfWebStatePdfContent \ + CreateFullPagePdfWebStatePdfContent +#else +#define MAYBE_CreateFullPagePdfWebStatePdfContent \ + DISABLED_CreateFullPagePdfWebStatePdfContent +#endif +TEST_F(WebStateTest, MAYBE_CreateFullPagePdfWebStatePdfContent) { CGRect fake_bounds = CGRectMake(0, 0, 100, 100); UIGraphicsPDFRenderer* pdf_renderer = [[UIGraphicsPDFRenderer alloc] initWithBounds:fake_bounds];
diff --git a/ios_internal b/ios_internal index dc62401..9a9d456 160000 --- a/ios_internal +++ b/ios_internal
@@ -1 +1 @@ -Subproject commit dc62401870e2aaf9fc91284339f146da37729fee +Subproject commit 9a9d45649b69d7ae40dc45ba9582ebb7fe5b244a
diff --git a/media/gpu/mac/video_toolbox_frame_converter.cc b/media/gpu/mac/video_toolbox_frame_converter.cc index b7506de..a1cb6a1 100644 --- a/media/gpu/mac/video_toolbox_frame_converter.cc +++ b/media/gpu/mac/video_toolbox_frame_converter.cc
@@ -270,9 +270,6 @@ base::BindOnce(&VideoToolboxFrameConverter::OnVideoFrameReleased, this, shared_image, std::move(image))); - // It should be possible to use VideoFrame::WrapExternalGpuMemoryBuffer(), - // which would allow the renderer to map the IOSurface, but this is more - // expensive whenever the renderer is not doing readback. scoped_refptr<VideoFrame> frame = VideoFrame::WrapSharedImage( video_pixel_format, shared_image, shared_image->creation_sync_token(), std::move(release_cb), coded_size, visible_rect, natural_size,
diff --git a/media/webrtc/audio_processor_test.cc b/media/webrtc/audio_processor_test.cc index 31dd687..8a8fe7f 100644 --- a/media/webrtc/audio_processor_test.cc +++ b/media/webrtc/audio_processor_test.cc
@@ -33,6 +33,7 @@ #include "media/base/audio_parameters.h" #include "media/base/audio_processing.h" #include "media/base/audio_sample_types.h" +#include "media/base/media_switches.h" #include "media/webrtc/constants.h" #include "media/webrtc/webrtc_features.h" #include "testing/gtest/include/gtest/gtest.h" @@ -777,4 +778,70 @@ EXPECT_TRUE(audio_processor->needs_playout_reference()); } +#if BUILDFLAG(SYSTEM_LOOPBACK_AS_AEC_REFERENCE) +class AudioProcessorCallbackLoopbackAecDelayTest : public ::testing::Test { + protected: + void SetUp() override { + params_ = + media::AudioParameters(media::AudioParameters::AUDIO_PCM_LOW_LATENCY, + ChannelLayoutConfig::Stereo(), 48000, 480); + data_bus_ = media::AudioBus::Create(params_.channels(), + params_.frames_per_buffer()); + data_bus_->Zero(); + + // Always enable the feature with test delay value (99 ms). + scoped_feature_list_.InitAndEnableFeatureWithParameters( + kSystemLoopbackAsAecReference, {{"added_delay_ms", "99"}}); + } + + void SetUseLoopbackAecReference(bool value) { + settings_.use_loopback_aec_reference = value; + } + + std::unique_ptr<AudioProcessor> CreateAudioProcessor() { + return AudioProcessor::Create( + mock_capture_callback_.Get(), LogCallbackForTesting(), settings_, + params_, AudioProcessor::GetDefaultOutputFormat(params_, settings_)); + } + + void ExpectCallbackWithOffset(base::TimeTicks capture_time, + base::TimeDelta offset) { + EXPECT_CALL(mock_capture_callback_, Run(_, capture_time - offset, _)) + .Times(1); + } + + void RunTestWithSettings(bool use_loopback_aec_reference, + base::TimeDelta expected_offset) { + SetUseLoopbackAecReference(use_loopback_aec_reference); + + auto audio_processor = CreateAudioProcessor(); + ASSERT_TRUE(audio_processor->has_webrtc_audio_processing()); + + base::TimeTicks capture_time = base::TimeTicks::Now(); + ExpectCallbackWithOffset(capture_time, expected_offset); + audio_processor->ProcessCapturedAudio(*data_bus_, capture_time, + /*num_preferred_channels*/ -1, + /*volume*/ 1.0); + } + + base::test::ScopedFeatureList scoped_feature_list_; + MockProcessedCaptureCallback mock_capture_callback_; + media::AudioParameters params_; + std::unique_ptr<media::AudioBus> data_bus_; + AudioProcessingSettings settings_; +}; + +TEST_F(AudioProcessorCallbackLoopbackAecDelayTest, + NoLoopbackSettingLeadsToNoDelayApplied) { + RunTestWithSettings(/*use_loopback_aec_reference=*/false, + /*expected_offset=*/base::Milliseconds(0)); +} + +TEST_F(AudioProcessorCallbackLoopbackAecDelayTest, + LoopbackSettingLeadsToDelayApplied) { + RunTestWithSettings(/*use_loopback_aec_reference=*/true, + /*expected_offset=*/base::Milliseconds(99)); +} +#endif + } // namespace media
diff --git a/net/BUILD.gn b/net/BUILD.gn index 2dbdac6..19ed969b 100644 --- a/net/BUILD.gn +++ b/net/BUILD.gn
@@ -2706,6 +2706,7 @@ "base/backoff_entry_unittest.cc", "base/base64_unittest.cc", "base/chunked_upload_data_stream_unittest.cc", + "base/connection_endpoint_metadata_unittest.cc", "base/data_url_unittest.cc", "base/does_url_match_filter_unittest.cc", "base/elements_upload_data_stream_unittest.cc",
diff --git a/net/base/connection_endpoint_metadata.cc b/net/base/connection_endpoint_metadata.cc index b9085dd..88a13af 100644 --- a/net/base/connection_endpoint_metadata.cc +++ b/net/base/connection_endpoint_metadata.cc
@@ -6,6 +6,7 @@ #include <optional> #include <string> +#include <tuple> #include <utility> #include <vector> @@ -39,6 +40,14 @@ ConnectionEndpointMetadata::ConnectionEndpointMetadata( ConnectionEndpointMetadata&&) = default; +bool ConnectionEndpointMetadata::operator<( + const ConnectionEndpointMetadata& other) const { + return std::tie(supported_protocol_alpns, ech_config_list, target_name, + trust_anchor_ids) < + std::tie(other.supported_protocol_alpns, other.ech_config_list, + other.target_name, other.trust_anchor_ids); +} + base::Value ConnectionEndpointMetadata::ToValue() const { base::Value::Dict dict;
diff --git a/net/base/connection_endpoint_metadata.h b/net/base/connection_endpoint_metadata.h index dd921440..4f527b3a78 100644 --- a/net/base/connection_endpoint_metadata.h +++ b/net/base/connection_endpoint_metadata.h
@@ -39,6 +39,8 @@ ConnectionEndpointMetadata& operator=(ConnectionEndpointMetadata&&) = default; bool operator==(const ConnectionEndpointMetadata& other) const = default; + // Needed to be an element of std::set. + bool operator<(const ConnectionEndpointMetadata& other) const; base::Value ToValue() const; static std::optional<ConnectionEndpointMetadata> FromValue(
diff --git a/net/base/connection_endpoint_metadata_unittest.cc b/net/base/connection_endpoint_metadata_unittest.cc new file mode 100644 index 0000000..a69e417 --- /dev/null +++ b/net/base/connection_endpoint_metadata_unittest.cc
@@ -0,0 +1,51 @@ +// Copyright 2025 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/base/connection_endpoint_metadata.h" + +#include <set> +#include <vector> + +#include "testing/gtest/include/gtest/gtest.h" + +namespace net { + +TEST(ConnectionEndpointMetadataTest, Set) { + auto metadatas = std::to_array<ConnectionEndpointMetadata>({ + {/*supported_protocol_alpns=*/{{"h2"}}, + /*ech_config_list=*/{}, + /*target_name=*/"a.test", + /*trust_anchor_ids=*/{}}, + {/*supported_protocol_alpns=*/{{"h3"}}, + /*ech_config_list=*/{}, + /*target_name=*/"a.test", + /*trust_anchor_ids=*/{}}, + {/*supported_protocol_alpns=*/{{"h2"}}, + /*ech_config_list=*/{0x01}, + /*target_name=*/"a.test", + /*trust_anchor_ids=*/{}}, + {/*supported_protocol_alpns=*/{{"h2"}}, + /*ech_config_list=*/{0x02}, + /*target_name=*/"a.test", + /*trust_anchor_ids=*/{}}, + {/*supported_protocol_alpns=*/{{"h2"}}, + /*ech_config_list=*/{0x01}, + /*target_name=*/"a.test", + /*trust_anchor_ids=*/{{0x01}}}, + {/*supported_protocol_alpns=*/{{"h2"}}, + /*ech_config_list=*/{0x01}, + /*target_name=*/"a.test", + /*trust_anchor_ids=*/{{0x02}}}, + {/*supported_protocol_alpns=*/{{"h2"}}, + /*ech_config_list=*/{}, + /*target_name=*/"b.test", + /*trust_anchor_ids=*/{}}, + }); + + std::set<ConnectionEndpointMetadata> metadata_set(metadatas.begin(), + metadatas.end()); + ASSERT_EQ(metadatas.size(), metadata_set.size()); +} + +} // namespace net
diff --git a/net/base/features.cc b/net/base/features.cc index 825cca62..b1e47c4c 100644 --- a/net/base/features.cc +++ b/net/base/features.cc
@@ -146,11 +146,11 @@ BASE_FEATURE(kPrefixCookieHttp, "PrefixCookieHttp", - base::FEATURE_DISABLED_BY_DEFAULT); + base::FEATURE_ENABLED_BY_DEFAULT); BASE_FEATURE(kPrefixCookieHostHttp, "PrefixCookieHostHttp", - base::FEATURE_DISABLED_BY_DEFAULT); + base::FEATURE_ENABLED_BY_DEFAULT); BASE_FEATURE(kSearchEnginePreconnectInterval, "SearchEnginePreconnectInterval",
diff --git a/services/webnn/ort/context_impl_ort.cc b/services/webnn/ort/context_impl_ort.cc index 4479d6b9..77654f1 100644 --- a/services/webnn/ort/context_impl_ort.cc +++ b/services/webnn/ort/context_impl_ort.cc
@@ -94,17 +94,26 @@ /*min_input=*/ {DataTypeConstraint::kAllDataTypesAtLeast8bits, kMaxRank}, /*pow_input=*/{kFloat16To32Int32To64, kMaxRank}, - /*equal_input=*/{}, - /*greater_input=*/{}, - /*greater_or_equal_input=*/{}, - /*lesser_input=*/{}, - /*lesser_or_equal_input=*/{}, - /*not_equal_input=*/{}, - /*logical_and_input=*/{}, - /*logical_or_input=*/{}, - /*logical_xor_input=*/{}, - /*logical_not_input=*/{}, - /*logical_output=*/{}, + /*equal_input=*/ + {DataTypeConstraint::kAllDataTypesAtLeast8bits, kMaxRank}, + /*greater_input=*/ + {DataTypeConstraint::kAllDataTypesAtLeast8bits, kMaxRank}, + /*greater_or_equal_input=*/ + {DataTypeConstraint::kAllDataTypesAtLeast8bits, kMaxRank}, + /*lesser_input=*/ + {DataTypeConstraint::kAllDataTypesAtLeast8bits, kMaxRank}, + /*lesser_or_equal_input=*/ + {DataTypeConstraint::kAllDataTypesAtLeast8bits, kMaxRank}, + /*not_equal_input=*/ + {DataTypeConstraint::kAllDataTypesAtLeast8bits, kMaxRank}, + /*logical_and_input=*/ + {DataTypeConstraint::kUint8, kMaxRank}, + /*logical_or_input=*/ + {DataTypeConstraint::kUint8, kMaxRank}, + /*logical_xor_input=*/ + {DataTypeConstraint::kUint8, kMaxRank}, + /*logical_not_input=*/{DataTypeConstraint::kUint8, kMaxRank}, + /*logical_output=*/DataTypeConstraint::kUint8, /*abs_input=*/{DataTypeConstraint::kAllDataTypesAtLeast8bits, kMaxRank}, /*ceil_input=*/{DataTypeConstraint::kFloat16To32, kMaxRank}, /*cos_input=*/{DataTypeConstraint::kFloat16To32, kMaxRank},
diff --git a/services/webnn/ort/graph_builder_ort.cc b/services/webnn/ort/graph_builder_ort.cc index b47bfde..f723b101 100644 --- a/services/webnn/ort/graph_builder_ort.cc +++ b/services/webnn/ort/graph_builder_ort.cc
@@ -34,6 +34,14 @@ constexpr base::cstring_view kOpTypeMax = "Max"; constexpr base::cstring_view kOpTypeMin = "Min"; constexpr base::cstring_view kOpTypePow = "Pow"; +constexpr base::cstring_view kOpTypeEqual = "Equal"; +constexpr base::cstring_view kOpTypeGreater = "Greater"; +constexpr base::cstring_view kOpTypeGreaterOrEqual = "GreaterOrEqual"; +constexpr base::cstring_view kOpTypeLesser = "Less"; +constexpr base::cstring_view kOpTypeLesserOrEqual = "LessOrEqual"; +constexpr base::cstring_view kOpTypeLogicalAnd = "And"; +constexpr base::cstring_view kOpTypeLogicalOr = "Or"; +constexpr base::cstring_view kOpTypeLogicalXor = "Xor"; // Element-wise unary ops constexpr base::cstring_view kOpTypeAbs = "Abs"; @@ -42,6 +50,7 @@ constexpr base::cstring_view kOpTypeExp = "Exp"; constexpr base::cstring_view kOpTypeFloor = "Floor"; constexpr base::cstring_view kOpTypeLog = "Log"; +constexpr base::cstring_view kOpTypeLogicalNot = "Not"; constexpr base::cstring_view kOpTypeNeg = "Neg"; constexpr base::cstring_view kOpTypeSign = "Sign"; constexpr base::cstring_view kOpTypeSin = "Sin"; @@ -82,6 +91,7 @@ constexpr base::cstring_view kOpTypeLpPool2d = "LpPool"; constexpr std::string_view kInserted = "Inserted"; +constexpr std::string_view kToEmulate = "ToEmulate"; constexpr std::string_view kUnderscore = "_"; std::string GetOperandName(std::string_view label, OperandId id) { @@ -254,35 +264,36 @@ return CreateInitializer<int64_t>(new_shape_dims, new_shape_value); } -void GraphBuilderOrt::AddCastNode(base::cstring_view name, +void GraphBuilderOrt::AddCastNode(base::cstring_view node_name, base::cstring_view input, base::cstring_view output, - OperandDataType to_data_type) { + ONNXTensorElementDataType to_data_type) { std::array<const char*, 1> inputs = {input.c_str()}; std::array<const char*, 1> outputs = {output.c_str()}; - constexpr base::cstring_view kAttrTo = "to"; - std::array<ScopedOrtOpAttr, 1> attributes = {model_editor_.CreateAttribute( - kAttrTo, static_cast<int64_t>(WebnnToOnnxDataType(to_data_type)))}; + int64_t attr_to_data = static_cast<int64_t>(to_data_type); + std::array<ScopedOrtOpAttr, 1> attributes = { + model_editor_.CreateAttribute(kAttrTo, attr_to_data)}; - model_editor_.AddNode(kOpTypeCast, name, inputs, outputs, attributes); + model_editor_.AddNode(kOpTypeCast, node_name, inputs, outputs, attributes); +} + +std::string GraphBuilderOrt::CreateCastNode( + base::cstring_view input, + ONNXTensorElementDataType to_data_type) { + const std::string output = GenerateOperandName(); + InsertCastNode(input, output, to_data_type); + return output; } void GraphBuilderOrt::InsertCastNode(base::cstring_view input, base::cstring_view output, - OperandDataType to_data_type) { + ONNXTensorElementDataType to_data_type) { const std::string node_name = GenerateNodeName(base::JoinString({kInserted, kOpTypeCast}, kUnderscore)); AddCastNode(node_name, input, output, to_data_type); } -std::string GraphBuilderOrt::CreateCastNode(base::cstring_view input, - OperandDataType to_data_type) { - const std::string output = GenerateOperandName(); - InsertCastNode(input, output, to_data_type); - return output; -} - void GraphBuilderOrt::AddExpandNode(base::cstring_view node_name, base::cstring_view input, base::cstring_view output, @@ -450,7 +461,7 @@ // operand dimension must be in the range of int32. // https://www.w3.org/TR/webnn/#valid-dimension CHECK_EQ(output_data_type, OperandDataType::kInt32); - InsertCastNode(int64_output, output, output_data_type); + InsertCastNode(int64_output, output, WebnnToOnnxDataType(output_data_type)); } } @@ -458,9 +469,9 @@ const std::string node_name = GenerateNodeName(cast.label); const std::string input = GetOperandNameById(cast.input_operand_id); const std::string output = GetOperandNameById(cast.output_operand_id); - - AddCastNode(node_name, input, output, - GetOperand(cast.output_operand_id).descriptor.data_type()); + const OperandDataType output_data_type = + GetOperand(cast.output_operand_id).descriptor.data_type(); + AddCastNode(node_name, input, output, WebnnToOnnxDataType(output_data_type)); } void GraphBuilderOrt::AddConv2dOperation(const mojom::Conv2d& conv2d) { @@ -562,6 +573,103 @@ } } +// TODO(crbug.com/426228071): Eliminate redundant cast ops for bool and uint8 +// data types conversion. +void GraphBuilderOrt::AddLogicalBinaryOperation( + const mojom::ElementWiseBinary& logical_binary, + base::cstring_view op_type) { + const std::string node_name = GenerateNodeName(logical_binary.label); + std::string lhs = GetOperandNameById(logical_binary.lhs_operand_id); + std::string rhs = GetOperandNameById(logical_binary.rhs_operand_id); + + // Some ONNX logical binary operations only support bool input. + if (logical_binary.kind == mojom::ElementWiseBinary::Kind::kLogicalAnd || + logical_binary.kind == mojom::ElementWiseBinary::Kind::kLogicalOr || + logical_binary.kind == mojom::ElementWiseBinary::Kind::kLogicalXor) { + CHECK_EQ(GetOperand(logical_binary.lhs_operand_id).descriptor.data_type(), + OperandDataType::kUint8); + lhs = CreateCastNode(lhs, ONNX_TENSOR_ELEMENT_DATA_TYPE_BOOL); + + CHECK_EQ(GetOperand(logical_binary.rhs_operand_id).descriptor.data_type(), + OperandDataType::kUint8); + rhs = CreateCastNode(rhs, ONNX_TENSOR_ELEMENT_DATA_TYPE_BOOL); + } + std::array<const char*, 2> inputs = {lhs.c_str(), rhs.c_str()}; + + const std::string bool_output = GenerateOperandName(); + std::array<const char*, 1> outputs = {bool_output.c_str()}; + model_editor_.AddNode(op_type, node_name, inputs, outputs); + + // ONNX logical operators only support bool output. WebNN logical operators + // support uint8 output. It is necessary to insert a cast operator after a + // logical operator. + const OperandDataType output_data_type = + GetOperand(logical_binary.output_operand_id).descriptor.data_type(); + const std::string output = + GetOperandNameById(logical_binary.output_operand_id); + CHECK_EQ(output_data_type, OperandDataType::kUint8); + InsertCastNode(bool_output, output, WebnnToOnnxDataType(output_data_type)); +} + +void GraphBuilderOrt::AddLogicalNotOperation( + const mojom::ElementWiseUnary& logical_not) { + const std::string node_name = GenerateNodeName(logical_not.label); + // ONNX logical not operation only supports bool input. + CHECK_EQ(GetOperand(logical_not.input_operand_id).descriptor.data_type(), + OperandDataType::kUint8); + std::string input = + CreateCastNode(GetOperandNameById(logical_not.input_operand_id), + ONNX_TENSOR_ELEMENT_DATA_TYPE_BOOL); + std::vector<const char*> inputs = {input.c_str()}; + + const std::string bool_output = GenerateOperandName(); + std::array<const char*, 1> outputs = {bool_output.c_str()}; + model_editor_.AddNode(kOpTypeLogicalNot, node_name, inputs, outputs); + + // ONNX `Not` operator only supports bool output, while WebNN `logicalNot` + // operator supports uint8 output. Insert a `Cast` operator for type + // conversion. + const OperandDataType output_data_type = + GetOperand(logical_not.output_operand_id).descriptor.data_type(); + const std::string output = GetOperandNameById(logical_not.output_operand_id); + CHECK_EQ(output_data_type, OperandDataType::kUint8); + InsertCastNode(bool_output, output, WebnnToOnnxDataType(output_data_type)); +} + +void GraphBuilderOrt::AddLogicalNotEqualOperation( + const mojom::ElementWiseBinary& not_equal) { + // Step 1: calculate `equal(a, b)`. + const std::string equal_node_name = GenerateNodeName(base::JoinString( + {kInserted, kOpTypeEqual, kToEmulate, not_equal.label}, kUnderscore)); + std::string lhs = GetOperandNameById(not_equal.lhs_operand_id); + std::string rhs = GetOperandNameById(not_equal.rhs_operand_id); + const std::string equal_output = GenerateOperandName(); + + std::array<const char*, 1> equal_outputs = {equal_output.c_str()}; + std::array<const char*, 2> equal_inputs = {lhs.c_str(), rhs.c_str()}; + model_editor_.AddNode(kOpTypeEqual, equal_node_name, equal_inputs, + equal_outputs); + + // Step 2: calculate `logicalNot(equal_output)` + const std::string not_output = GenerateOperandName(); + std::array<const char*, 1> not_outputs = {not_output.c_str()}; + const std::string not_node_name = GenerateNodeName(base::JoinString( + {kInserted, kOpTypeLogicalNot, kToEmulate, not_equal.label}, + kUnderscore)); + model_editor_.AddNode(kOpTypeLogicalNot, not_node_name, equal_outputs, + not_outputs); + + // ONNX logical operators only support bool output. To support output with the + // WebNN data type, it is necessary to insert a cast operator after a logical + // operator. + OperandId output_operand_id = not_equal.output_operand_id; + const OperandDataType output_data_type = + GetOperand(output_operand_id).descriptor.data_type(); + std::string output = GetOperandNameById(output_operand_id); + CHECK_EQ(output_data_type, OperandDataType::kUint8); + InsertCastNode(not_output, output, WebnnToOnnxDataType(output_data_type)); +} + void GraphBuilderOrt::AddElementWiseBinaryOperation( const mojom::ElementWiseBinary& element_wise_binary) { const DataTypeLimits& data_type_limits = context_properties_.data_type_limits; @@ -605,17 +713,60 @@ {lhs_descriptor, rhs_descriptor})); return AddBinaryOperation(element_wise_binary, kOpTypePow); } - case mojom::ElementWiseBinary::Kind::kEqual: - case mojom::ElementWiseBinary::Kind::kNotEqual: - case mojom::ElementWiseBinary::Kind::kGreater: - case mojom::ElementWiseBinary::Kind::kGreaterOrEqual: - case mojom::ElementWiseBinary::Kind::kLesser: - case mojom::ElementWiseBinary::Kind::kLesserOrEqual: - case mojom::ElementWiseBinary::Kind::kLogicalAnd: - case mojom::ElementWiseBinary::Kind::kLogicalOr: - case mojom::ElementWiseBinary::Kind::kLogicalXor: - NOTREACHED() << "[WebNN] Element-wise logical operations are not " - "supported."; + case mojom::ElementWiseBinary::Kind::kEqual: { + CHECK(data_type_limits.equal_input.SupportsAll( + {lhs_descriptor, rhs_descriptor})); + AddLogicalBinaryOperation(element_wise_binary, kOpTypeEqual); + break; + } + case mojom::ElementWiseBinary::Kind::kNotEqual: { + CHECK(data_type_limits.not_equal_input.SupportsAll( + {lhs_descriptor, rhs_descriptor})); + AddLogicalNotEqualOperation(element_wise_binary); + break; + } + case mojom::ElementWiseBinary::Kind::kGreater: { + CHECK(data_type_limits.greater_input.SupportsAll( + {lhs_descriptor, rhs_descriptor})); + AddLogicalBinaryOperation(element_wise_binary, kOpTypeGreater); + break; + } + case mojom::ElementWiseBinary::Kind::kGreaterOrEqual: { + CHECK(data_type_limits.greater_or_equal_input.SupportsAll( + {lhs_descriptor, rhs_descriptor})); + AddLogicalBinaryOperation(element_wise_binary, kOpTypeGreaterOrEqual); + break; + } + case mojom::ElementWiseBinary::Kind::kLesser: { + CHECK(data_type_limits.lesser_input.SupportsAll( + {lhs_descriptor, rhs_descriptor})); + AddLogicalBinaryOperation(element_wise_binary, kOpTypeLesser); + break; + } + case mojom::ElementWiseBinary::Kind::kLesserOrEqual: { + CHECK(data_type_limits.lesser_or_equal_input.SupportsAll( + {lhs_descriptor, rhs_descriptor})); + AddLogicalBinaryOperation(element_wise_binary, kOpTypeLesserOrEqual); + break; + } + case mojom::ElementWiseBinary::Kind::kLogicalAnd: { + CHECK(data_type_limits.logical_and_input.SupportsAll( + {lhs_descriptor, rhs_descriptor})); + AddLogicalBinaryOperation(element_wise_binary, kOpTypeLogicalAnd); + break; + } + case mojom::ElementWiseBinary::Kind::kLogicalOr: { + CHECK(data_type_limits.logical_or_input.SupportsAll( + {lhs_descriptor, rhs_descriptor})); + AddLogicalBinaryOperation(element_wise_binary, kOpTypeLogicalOr); + break; + } + case mojom::ElementWiseBinary::Kind::kLogicalXor: { + CHECK(data_type_limits.logical_xor_input.SupportsAll( + {lhs_descriptor, rhs_descriptor})); + AddLogicalBinaryOperation(element_wise_binary, kOpTypeLogicalXor); + break; + } } } @@ -649,6 +800,11 @@ CHECK(data_type_limits.log_input.Supports(input_descriptor)); return AddUnaryOperation(element_wise_unary, kOpTypeLog); } + case mojom::ElementWiseUnary::Kind::kLogicalNot: { + CHECK(data_type_limits.logical_not_input.Supports(input_descriptor)); + AddLogicalNotOperation(element_wise_unary); + break; + } case mojom::ElementWiseUnary::Kind::kNeg: { CHECK(data_type_limits.neg_input.Supports(input_descriptor)); return AddUnaryOperation(element_wise_unary, kOpTypeNeg); @@ -685,9 +841,6 @@ CHECK(data_type_limits.cast_input.Supports(input_descriptor)); return AddCastOperation(element_wise_unary); } - case mojom::ElementWiseUnary::Kind::kLogicalNot: - NOTREACHED() - << "[WebNN] Element-wise logical operations are not supported."; } } @@ -853,7 +1006,8 @@ std::string int64_indices = indices_descriptor.data_type() == OperandDataType::kInt64 ? indices - : CreateCastNode(indices, OperandDataType::kInt64); + : CreateCastNode(indices, + WebnnToOnnxDataType(OperandDataType::kInt64)); // Clamp the indices operand to prevent out-of-bounds reading which will cause // ORT CPU EP to throw a runtime error. @@ -1099,7 +1253,8 @@ std::string int64_indices = indices_descriptor.data_type() == OperandDataType::kInt64 ? indices - : CreateCastNode(indices, OperandDataType::kInt64); + : CreateCastNode(indices, + WebnnToOnnxDataType(OperandDataType::kInt64)); // Clamp the indices operand to prevent out-of-bounds writing which will cause // ORT CPU EP to throw a runtime error.
diff --git a/services/webnn/ort/graph_builder_ort.h b/services/webnn/ort/graph_builder_ort.h index 7c3a292..ccac704c 100644 --- a/services/webnn/ort/graph_builder_ort.h +++ b/services/webnn/ort/graph_builder_ort.h
@@ -112,15 +112,15 @@ // specifies the output's shape. std::string CreateInitializerForShape(base::span<const uint32_t> shape); - void AddCastNode(base::cstring_view name, + void AddCastNode(base::cstring_view node_name, base::cstring_view input, base::cstring_view output, - OperandDataType to_data_type); + ONNXTensorElementDataType to_data_type); + std::string CreateCastNode(base::cstring_view input, + ONNXTensorElementDataType to_data_type); void InsertCastNode(base::cstring_view input, base::cstring_view output, - OperandDataType to_data_type); - std::string CreateCastNode(base::cstring_view input, - OperandDataType to_data_type); + ONNXTensorElementDataType to_data_type); void AddExpandNode(base::cstring_view node_name, base::cstring_view input, @@ -154,6 +154,10 @@ void AddClampOperation(const mojom::Clamp& clamp); void AddConcatOperation(const mojom::Concat& concat); void AddConv2dOperation(const mojom::Conv2d& conv2d); + void AddLogicalBinaryOperation(const mojom::ElementWiseBinary& logical_binary, + base::cstring_view op_type); + void AddLogicalNotOperation(const mojom::ElementWiseUnary& logical_not); + void AddLogicalNotEqualOperation(const mojom::ElementWiseBinary& not_equal); void AddElementWiseBinaryOperation( const mojom::ElementWiseBinary& element_wise_binary); void AddElementWiseUnaryOperation(
diff --git a/testing/variations/fieldtrial_testing_config.json b/testing/variations/fieldtrial_testing_config.json index 69dcf32a..b36686a 100644 --- a/testing/variations/fieldtrial_testing_config.json +++ b/testing/variations/fieldtrial_testing_config.json
@@ -2343,6 +2343,27 @@ ] } ], + "AutofillIgnoreCheckableElements": [ + { + "platforms": [ + "android", + "android_webview", + "chromeos", + "ios", + "linux", + "mac", + "windows" + ], + "experiments": [ + { + "name": "Enabled", + "enable_features": [ + "AutofillIgnoreCheckableElements" + ] + } + ] + } + ], "AutofillImproveAddressFieldSwapping": [ { "platforms": [ @@ -3461,21 +3482,6 @@ ] } ], - "BinderIpcTracing": [ - { - "platforms": [ - "android" - ], - "experiments": [ - { - "name": "Enabled_20250522", - "enable_features": [ - "TraceBinderIpc" - ] - } - ] - } - ], "BiometricAuthPwdFillAndroid": [ { "platforms": [ @@ -5624,24 +5630,6 @@ ] } ], - "ClayBlockingEscapeHatch": [ - { - "platforms": [ - "android" - ], - "experiments": [ - { - "name": "Enabled", - "params": { - "escape_hatch_block_limit": "5" - }, - "enable_features": [ - "ClayBlocking" - ] - } - ] - } - ], "ClaySnackbarOnAndroid": [ { "platforms": [ @@ -25249,6 +25237,21 @@ ] } ], + "UseDHCPCD10": [ + { + "platforms": [ + "chromeos" + ], + "experiments": [ + { + "name": "Enabled", + "enable_features": [ + "UseDHCPCD10" + ] + } + ] + } + ], "UseDMSAAForTiles": [ { "platforms": [
diff --git a/third_party/androidx/build.gradle b/third_party/androidx/build.gradle index 47a05708..2b81993a 100644 --- a/third_party/androidx/build.gradle +++ b/third_party/androidx/build.gradle
@@ -306,7 +306,7 @@ google() maven { // This URL is generated by the fetch_all_androidx.py script. - url 'https://androidx.dev/snapshots/builds/13710579/artifacts/repository' + url 'https://androidx.dev/snapshots/builds/13712688/artifacts/repository' } mavenCentral() }
diff --git a/third_party/bidimapper/README.chromium b/third_party/bidimapper/README.chromium index c38081f..1b0e68a 100644 --- a/third_party/bidimapper/README.chromium +++ b/third_party/bidimapper/README.chromium
@@ -1,10 +1,10 @@ Name: Implementation of WebDriver BiDi standard Short Name: chromium-bidi -URL: https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-6.1.0.tgz -Version: 6.1.0 -Date: 2025-06-25 -Revision: 202441f57fb0943b3effd788d0c101260453092e -SHA-512: b510b0fa708a4e60a1e924391ddea4c8fe1d3b49e8b4d9cb159ab35f85b97a58343c047bd3377acaebb82fa7bb9763c5a24c2e58431b204dd340be118122a344 +URL: https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-7.0.0.tgz +Version: 7.0.0 +Date: 2025-06-27 +Revision: a7685a9b32f2c9831e9eeb23a40d0082ffb065fc +SHA-512: 5aa7711eedf30c619877e1af880c353f127d368708cf4259747eb3b6317212315bd7b0316b589e6cbc9c0da523d230d905e6c1729c37e6173c5f9ac125ceeb84 Update Mechanism: Manual License: Apache-2.0 License File: LICENSE @@ -34,7 +34,7 @@ The upstream is maintained by the Chromium developers. All the changes must be done there. -The source code of the current revision can be found at the following url: https://github.com/GoogleChromeLabs/chromium-bidi/archive/202441f57fb0943b3effd788d0c101260453092e.zip +The source code of the current revision can be found at the following url: https://github.com/GoogleChromeLabs/chromium-bidi/archive/a7685a9b32f2c9831e9eeb23a40d0082ffb065fc.zip -------------------- DEPENDENCY DIVIDER --------------------
diff --git a/third_party/bidimapper/mapper.js b/third_party/bidimapper/mapper.js index dfbf302..1d125873 100644 --- a/third_party/bidimapper/mapper.js +++ b/third_party/bidimapper/mapper.js
@@ -465,6 +465,12 @@ parseSetGeolocationOverrideParams(params) { return params; } + parseSetLocaleOverrideParams(params) { + return params; + } + parseSetScreenOrientationOverrideParams(params) { + return params; + } parseAddPreloadScriptParams(params) { return params; } @@ -1021,6 +1027,28 @@ await Promise.all(browsingContexts.map(async (context) => await context.cdpTarget.setGeolocationOverride(geolocation))); return {}; } + async setLocaleOverride(params) { + const locale = params.locale ?? null; + if (locale !== null && !isValidLocale(locale)) { + throw new InvalidArgumentException(`Invalid locale "${locale}"`); + } + const browsingContexts = await this.#getRelatedTopLevelBrowsingContexts(params.contexts, params.userContexts); + for (const userContextId of params.userContexts ?? []) { + const userContextConfig = this.#userContextStorage.getConfig(userContextId); + userContextConfig.locale = locale; + } + await Promise.all(browsingContexts.map(async (context) => await context.cdpTarget.setLocaleOverride(locale))); + return {}; + } + async setScreenOrientationOverride(params) { + const browsingContexts = await this.#getRelatedTopLevelBrowsingContexts(params.contexts, params.userContexts); + for (const userContextId of params.userContexts ?? []) { + const userContextConfig = this.#userContextStorage.getConfig(userContextId); + userContextConfig.screenOrientation = params.screenOrientation; + } + await Promise.all(browsingContexts.map(async (context) => await context.cdpTarget.setScreenOrientationOverride(params.screenOrientation))); + return {}; + } async #getRelatedTopLevelBrowsingContexts(browsingContextIds, userContextIds) { if (browsingContextIds === undefined && userContextIds === undefined) { throw new InvalidArgumentException('Either user contexts or browsing contexts must be provided'); @@ -1056,6 +1084,18 @@ return [...new Set(result).values()]; } } + function isValidLocale(locale) { + try { + new Intl.Locale(locale); + return true; + } + catch (e) { + if (e instanceof RangeError) { + return false; + } + throw e; + } + } /** * Copyright 2023 Google LLC. @@ -4572,6 +4612,10 @@ return await this.#cdpProcessor.sendCommand(this.#parser.parseSendCommandParams(command.params)); case 'emulation.setGeolocationOverride': return await this.#emulationProcessor.setGeolocationOverride(this.#parser.parseSetGeolocationOverrideParams(command.params)); + case 'emulation.setLocaleOverride': + return await this.#emulationProcessor.setLocaleOverride(this.#parser.parseSetLocaleOverrideParams(command.params)); + case 'emulation.setScreenOrientationOverride': + return await this.#emulationProcessor.setScreenOrientationOverride(this.#parser.parseSetScreenOrientationOverrideParams(command.params)); case 'input.performActions': return await this.#inputProcessor.performActions(this.#parser.parsePerformActionsParams(command.params)); case 'input.releaseActions': @@ -5107,6 +5151,8 @@ viewport; devicePixelRatio; geolocation; + locale; + screenOrientation; constructor(userContextId) { this.userContextId = userContextId; } @@ -7789,7 +7835,13 @@ #unblocked = new Deferred(); #unhandledPromptBehavior; #logger; - #previousViewport = { width: 0, height: 0 }; + #previousDeviceMetricsOverride = { + width: 0, + height: 0, + deviceScaleFactor: 0, + mobile: false, + dontSetVisibleSize: true, + }; #windowId; #deviceAccessEnabled = false; #cacheDisableState = false; @@ -8115,28 +8167,24 @@ await this.cdpClient.sendCommand('Emulation.clearDeviceMetricsOverride'); return; } - let newViewport; - if (viewport === undefined) { - newViewport = this.#previousViewport; + const newViewport = { ...this.#previousDeviceMetricsOverride }; + if (viewport === null) { + newViewport.width = 0; + newViewport.height = 0; } - else if (viewport === null) { - newViewport = { - width: 0, - height: 0, - }; + else if (viewport !== undefined) { + newViewport.width = viewport.width; + newViewport.height = viewport.height; } - else { - newViewport = viewport; + if (devicePixelRatio === null) { + newViewport.deviceScaleFactor = 0; + } + else if (devicePixelRatio !== undefined) { + newViewport.deviceScaleFactor = devicePixelRatio; } try { - await this.cdpClient.sendCommand('Emulation.setDeviceMetricsOverride', { - width: newViewport.width, - height: newViewport.height, - deviceScaleFactor: devicePixelRatio ? devicePixelRatio : 0, - mobile: false, - dontSetVisibleSize: true, - }); - this.#previousViewport = newViewport; + await this.cdpClient.sendCommand('Emulation.setDeviceMetricsOverride', newViewport); + this.#previousDeviceMetricsOverride = newViewport; } catch (err) { if (err.message.startsWith( @@ -8156,6 +8204,13 @@ this.#userContextConfig.geolocation !== null) { promises.push(this.setGeolocationOverride(this.#userContextConfig.geolocation)); } + if (this.#userContextConfig.screenOrientation !== undefined && + this.#userContextConfig.screenOrientation !== null) { + promises.push(this.setScreenOrientationOverride(this.#userContextConfig.screenOrientation)); + } + if (this.#userContextConfig.locale !== undefined) { + promises.push(this.setLocaleOverride(this.#userContextConfig.locale)); + } if (this.#userContextConfig.acceptInsecureCerts !== undefined) { promises.push(this.cdpClient.sendCommand('Security.setIgnoreCertificateErrors', { ignore: this.#userContextConfig.acceptInsecureCerts, @@ -8200,6 +8255,83 @@ throw new UnknownErrorException('Unexpected geolocation coordinates value'); } } + async setScreenOrientationOverride(screenOrientation) { + const newViewport = { ...this.#previousDeviceMetricsOverride }; + if (screenOrientation === null) { + delete newViewport.screenOrientation; + } + else { + newViewport.screenOrientation = + this.#toCdpScreenOrientationAngle(screenOrientation); + } + await this.cdpClient.sendCommand('Emulation.setDeviceMetricsOverride', newViewport); + this.#previousDeviceMetricsOverride = newViewport; + } + #toCdpScreenOrientationAngle(orientation) { + if (orientation.natural === "portrait" ) { + switch (orientation.type) { + case 'portrait-primary': + return { + angle: 0, + type: 'portraitPrimary', + }; + case 'landscape-primary': + return { + angle: 90, + type: 'landscapePrimary', + }; + case 'portrait-secondary': + return { + angle: 180, + type: 'portraitSecondary', + }; + case 'landscape-secondary': + return { + angle: 270, + type: 'landscapeSecondary', + }; + default: + throw new UnknownErrorException(`Unexpected screen orientation type ${orientation.type}`); + } + } + if (orientation.natural === "landscape" ) { + switch (orientation.type) { + case 'landscape-primary': + return { + angle: 0, + type: 'landscapePrimary', + }; + case 'portrait-primary': + return { + angle: 90, + type: 'portraitPrimary', + }; + case 'landscape-secondary': + return { + angle: 180, + type: 'landscapeSecondary', + }; + case 'portrait-secondary': + return { + angle: 270, + type: 'portraitSecondary', + }; + default: + throw new UnknownErrorException(`Unexpected screen orientation type ${orientation.type}`); + } + } + throw new UnknownErrorException(`Unexpected orientation natural ${orientation.natural}`); + } + async setLocaleOverride(locale) { + if (locale === null) { + await this.cdpClient.sendCommand('Emulation.setLocaleOverride', {}); + } + else { + await this.cdpClient.sendCommand('Emulation.setLocaleOverride', { + locale, + }); + } + } } const cdpToBidiTargetTypes = { @@ -15791,7 +15923,11 @@ defaultValue: z.string().optional(), })); })(BrowsingContext$1 || (BrowsingContext$1 = {})); - const EmulationCommandSchema = z.lazy(() => Emulation$1.SetGeolocationOverrideSchema); + const EmulationCommandSchema = z.lazy(() => z.union([ + Emulation$1.SetGeolocationOverrideSchema, + Emulation$1.SetLocaleOverrideSchema, + Emulation$1.SetScreenOrientationOverrideSchema, + ])); var Emulation$1; (function (Emulation) { Emulation.SetGeolocationOverrideSchema = z.lazy(() => z.object({ @@ -15840,6 +15976,55 @@ type: z.literal('positionUnavailable'), })); })(Emulation$1 || (Emulation$1 = {})); + (function (Emulation) { + Emulation.SetLocaleOverrideSchema = z.lazy(() => z.object({ + method: z.literal('emulation.setLocaleOverride'), + params: Emulation.SetLocaleOverrideParametersSchema, + })); + })(Emulation$1 || (Emulation$1 = {})); + (function (Emulation) { + Emulation.SetLocaleOverrideParametersSchema = z.lazy(() => z.object({ + locale: z.union([z.string(), z.null()]), + contexts: z + .array(BrowsingContext$1.BrowsingContextSchema) + .min(1) + .optional(), + userContexts: z.array(Browser$1.UserContextSchema).min(1).optional(), + })); + })(Emulation$1 || (Emulation$1 = {})); + (function (Emulation) { + Emulation.SetScreenOrientationOverrideSchema = z.lazy(() => z.object({ + method: z.literal('emulation.setScreenOrientationOverride'), + params: Emulation.SetScreenOrientationOverrideParametersSchema, + })); + })(Emulation$1 || (Emulation$1 = {})); + (function (Emulation) { + Emulation.ScreenOrientationNaturalSchema = z.lazy(() => z.enum(['portrait', 'landscape'])); + })(Emulation$1 || (Emulation$1 = {})); + (function (Emulation) { + Emulation.ScreenOrientationTypeSchema = z.lazy(() => z.enum([ + 'portrait-primary', + 'portrait-secondary', + 'landscape-primary', + 'landscape-secondary', + ])); + })(Emulation$1 || (Emulation$1 = {})); + (function (Emulation) { + Emulation.ScreenOrientationSchema = z.lazy(() => z.object({ + natural: Emulation.ScreenOrientationNaturalSchema, + type: Emulation.ScreenOrientationTypeSchema, + })); + })(Emulation$1 || (Emulation$1 = {})); + (function (Emulation) { + Emulation.SetScreenOrientationOverrideParametersSchema = z.lazy(() => z.object({ + screenOrientation: z.union([Emulation.ScreenOrientationSchema, z.null()]), + contexts: z + .array(BrowsingContext$1.BrowsingContextSchema) + .min(1) + .optional(), + userContexts: z.array(Browser$1.UserContextSchema).min(1).optional(), + })); + })(Emulation$1 || (Emulation$1 = {})); const NetworkCommandSchema = z.lazy(() => z.union([ Network$1.AddDataCollectorSchema, Network$1.AddInterceptSchema, @@ -17568,6 +17753,14 @@ return parseObject(params, Emulation$1.SetGeolocationOverrideParametersSchema); } Emulation.parseSetGeolocationOverrideParams = parseSetGeolocationOverrideParams; + function parseSetLocaleOverrideParams(params) { + return parseObject(params, Emulation$1.SetLocaleOverrideParametersSchema); + } + Emulation.parseSetLocaleOverrideParams = parseSetLocaleOverrideParams; + function parseSetScreenOrientationOverrideParams(params) { + return parseObject(params, Emulation$1.SetScreenOrientationOverrideParametersSchema); + } + Emulation.parseSetScreenOrientationOverrideParams = parseSetScreenOrientationOverrideParams; })(Emulation || (Emulation = {})); var Input; (function (Input) { @@ -17796,6 +17989,12 @@ parseSetGeolocationOverrideParams(params) { return Emulation.parseSetGeolocationOverrideParams(params); } + parseSetLocaleOverrideParams(params) { + return Emulation.parseSetLocaleOverrideParams(params); + } + parseSetScreenOrientationOverrideParams(params) { + return Emulation.parseSetScreenOrientationOverrideParams(params); + } parsePerformActionsParams(params) { return Input.parsePerformActionsParams(params); }
diff --git a/third_party/blink/common/custom_handlers/protocol_handler_utils.cc b/third_party/blink/common/custom_handlers/protocol_handler_utils.cc index 66b2f8e1..54235cc 100644 --- a/third_party/blink/common/custom_handlers/protocol_handler_utils.cc +++ b/third_party/blink/common/custom_handlers/protocol_handler_utils.cc
@@ -19,15 +19,18 @@ const char kToken[] = "%s"; -URLSyntaxErrorCode IsValidCustomHandlerURLSyntax(const GURL& full_url) { +URLSyntaxErrorCode IsValidCustomHandlerURLSyntax( + const GURL& full_url, + ProtocolHandlerSecurityLevel security_level) { std::string_view user_url = full_url.is_valid() ? full_url.spec() : std::string_view(""); - return IsValidCustomHandlerURLSyntax(full_url, user_url); + return IsValidCustomHandlerURLSyntax(full_url, user_url, security_level); } URLSyntaxErrorCode IsValidCustomHandlerURLSyntax( const GURL& full_url, - const std::string_view& user_url) { + std::string_view user_url, + ProtocolHandlerSecurityLevel security_level) { // It is a SyntaxError if the custom handler URL, as created by removing // the "%s" token and prepending the base url, does not resolve. if (full_url.is_empty() || !full_url.is_valid()) { @@ -36,9 +39,23 @@ // The specification requires that it is a SyntaxError if the "%s" token is // not present. - int index = user_url.find(kToken); - if (-1 == index) + size_t index = user_url.find(kToken); + if (index == std::string_view::npos) { return URLSyntaxErrorCode::kMissingToken; + } + + if (security_level == ProtocolHandlerSecurityLevel::kIsolatedAppFeatures) { + // For Isolated Web Apps, the protocol URL must conform to the following + // restrictions: + // * There must be exactly one placeholder; + // * This placeholder must be a part of query params. + if (user_url.rfind(kToken) != index) { + return URLSyntaxErrorCode::kInvalidUrl; + } + if (GURL(user_url).query().find(kToken) == std::string::npos) { + return URLSyntaxErrorCode::kInvalidUrl; + } + } return URLSyntaxErrorCode::kNoError; } @@ -47,9 +64,10 @@ ProtocolHandlerSecurityLevel security_level, bool* has_custom_scheme_prefix) { bool allow_scheme_prefix = - (security_level >= ProtocolHandlerSecurityLevel::kExtensionFeatures); - if (has_custom_scheme_prefix) + (security_level == ProtocolHandlerSecurityLevel::kExtensionFeatures); + if (has_custom_scheme_prefix) { *has_custom_scheme_prefix = false; + } static constexpr const char kWebPrefix[] = "web+"; static constexpr const char kExtPrefix[] = "ext+"; @@ -60,18 +78,21 @@ (allow_scheme_prefix && base::StartsWith(scheme, kExtPrefix, base::CompareCase::INSENSITIVE_ASCII))) { - if (has_custom_scheme_prefix) + if (has_custom_scheme_prefix) { *has_custom_scheme_prefix = true; + } // HTML5 requires that schemes with the |web+| prefix contain one or more // ASCII alphas after that prefix. auto scheme_name = scheme.substr(kPrefixLength); - if (scheme_name.empty()) - return false; - for (auto& character : scheme_name) { - if (!base::IsAsciiAlpha(character)) - return false; - } - return true; + return scheme_name.length() >= 1 && + std::ranges::all_of(scheme_name, &base::IsAsciiAlpha<char>); + } + + if (security_level == ProtocolHandlerSecurityLevel::kIsolatedAppFeatures) { + // Isolated Apps are allowed to claim any scheme that consists of ascii + // characters of length at least 2. + return scheme.length() >= 2 && + std::ranges::all_of(scheme, &base::IsAsciiAlpha<char>); } static constexpr const char* const kProtocolSafelist[] = { @@ -105,7 +126,9 @@ url.SchemeIsHTTPOrHTTPS() || security_level == ProtocolHandlerSecurityLevel::kSameOrigin || (security_level == ProtocolHandlerSecurityLevel::kExtensionFeatures && - CommonSchemeRegistry::IsExtensionScheme(url.scheme())); + CommonSchemeRegistry::IsExtensionScheme(url.scheme())) || + (security_level == ProtocolHandlerSecurityLevel::kIsolatedAppFeatures && + CommonSchemeRegistry::IsIsolatedAppScheme(url.scheme())); return has_valid_scheme && network::IsUrlPotentiallyTrustworthy(url); }
diff --git a/third_party/blink/common/custom_handlers/protocol_handler_utils_unittest.cc b/third_party/blink/common/custom_handlers/protocol_handler_utils_unittest.cc index d053764..a8ce3aad 100644 --- a/third_party/blink/common/custom_handlers/protocol_handler_utils_unittest.cc +++ b/third_party/blink/common/custom_handlers/protocol_handler_utils_unittest.cc
@@ -36,8 +36,8 @@ for (const auto& test_case : test_cases) { SCOPED_TRACE(test_case.title); GURL full_url(test_case.user_url); - URLSyntaxErrorCode code = - IsValidCustomHandlerURLSyntax(full_url, test_case.user_url); + URLSyntaxErrorCode code = IsValidCustomHandlerURLSyntax( + full_url, test_case.user_url, ProtocolHandlerSecurityLevel::kStrict); EXPECT_EQ(code, URLSyntaxErrorCode::kNoError); }; } @@ -100,8 +100,8 @@ for (const auto& test_case : test_cases) { SCOPED_TRACE(test_case.title); GURL full_url(test_case.user_url); - URLSyntaxErrorCode code = - IsValidCustomHandlerURLSyntax(full_url, test_case.user_url); + URLSyntaxErrorCode code = IsValidCustomHandlerURLSyntax( + full_url, test_case.user_url, ProtocolHandlerSecurityLevel::kStrict); EXPECT_EQ(code, test_case.expected_error); }; } @@ -154,7 +154,8 @@ SCOPED_TRACE(test_case.title); GURL full_url(test_case.user_url); EXPECT_FALSE(full_url.is_valid()); - URLSyntaxErrorCode code = IsValidCustomHandlerURLSyntax(full_url); + URLSyntaxErrorCode code = IsValidCustomHandlerURLSyntax( + full_url, ProtocolHandlerSecurityLevel::kStrict); EXPECT_EQ(code, test_case.expected_error); }; } @@ -318,4 +319,101 @@ }; } +TEST(ProtocolHandlerUtilTest, IsolatedAppFeaturesValidCustomScheme) { + struct { + std::string_view title; + std::string_view scheme; + bool allowed; + } test_cases[]{ + { + "Valid lowercase ASCII alphas", + "myproto", + true, + }, + { + "Valid mixed-case ASCII alphas", + "PrOtOcOl", + true, + }, + { + "Valid ASCII alphas with web+ prefix", + "web+meow", + true, + }, + { + "Invalid scheme: numbers", + "myproto3", + false, + }, + { + "Invalid scheme: dots/dashes", + "mypro.to3-sth", + false, + }, + { + "Invalid scheme: empty", + "", + false, + }, + { + "Invalid scheme: one char", + "C", + false, + }, + { + "Invalid scheme: empty after web+", + "web+", + false, + }, + }; + for (const auto& test_case : test_cases) { + SCOPED_TRACE(test_case.title); + bool allowed = IsValidCustomHandlerScheme( + test_case.scheme, ProtocolHandlerSecurityLevel::kIsolatedAppFeatures); + EXPECT_EQ(allowed, test_case.allowed); + }; +} + +TEST(ProtocolHandlerUtilTest, IsolatedAppFeaturesValidCustomURLSyntax) { + struct { + std::string_view title; + std::string_view url; + bool allowed; + } test_cases[]{ + { + "Valid IWA URL with placeholder in query", + "isolated-app://" + "amoiebz32b7o24tilu257xne2yf3nkblkploanxzm7ebeglseqpfeaacai?params=%" + "s", + true, + }, + { + "IWA URL without %s", + "isolated-app://" + "amoiebz32b7o24tilu257xne2yf3nkblkploanxzm7ebeglseqpfeaacai", + false, + }, + { + "IWA URL with non-query placeholder", + "isolated-app://%s", + false, + }, + { + "IWA URL with multiple placeholders", + "isolated-app://" + "amoiebz32b7o24tilu257xne2yf3nkblkploanxzm7ebeglseqpfeaacai?params=%" + "s&other_params=%s", + false, + }, + }; + for (const auto& test_case : test_cases) { + SCOPED_TRACE(test_case.title); + bool allowed = IsValidCustomHandlerURLSyntax( + GURL(test_case.url), + ProtocolHandlerSecurityLevel::kIsolatedAppFeatures) == + URLSyntaxErrorCode::kNoError; + EXPECT_EQ(allowed, test_case.allowed); + }; +} + } // namespace blink
diff --git a/third_party/blink/public/common/custom_handlers/protocol_handler_utils.h b/third_party/blink/public/common/custom_handlers/protocol_handler_utils.h index ab37dc8..ca7337ef 100644 --- a/third_party/blink/public/common/custom_handlers/protocol_handler_utils.h +++ b/third_party/blink/public/common/custom_handlers/protocol_handler_utils.h
@@ -50,9 +50,11 @@ // the spec states that it should throw a SyntaxError DOMException. URLSyntaxErrorCode BLINK_COMMON_EXPORT IsValidCustomHandlerURLSyntax(const GURL& full_url, - const std::string_view& user_url); + std::string_view user_url, + ProtocolHandlerSecurityLevel security_level); URLSyntaxErrorCode BLINK_COMMON_EXPORT -IsValidCustomHandlerURLSyntax(const GURL& full_url); +IsValidCustomHandlerURLSyntax(const GURL& full_url, + ProtocolHandlerSecurityLevel security_level); // This function returns whether the specified URL is allowed as a protocol // handler parameter, as described in steps 6 and 7 (except same origin) of the
diff --git a/third_party/blink/public/common/security/protocol_handler_security_level.h b/third_party/blink/public/common/security/protocol_handler_security_level.h index d06d7b9..5169b61 100644 --- a/third_party/blink/public/common/security/protocol_handler_security_level.h +++ b/third_party/blink/public/common/security/protocol_handler_security_level.h
@@ -22,6 +22,10 @@ // Allow extension features: ext+foo schemes and chrome-extension:// URLs. kExtensionFeatures, + + // Allow Isolated Web App features: unrestricted ascii schemes and + // isolated-app:// URLs. + kIsolatedAppFeatures, }; inline ProtocolHandlerSecurityLevel ProtocolHandlerSecurityLevelFrom( @@ -29,7 +33,7 @@ return security_level < 0 || security_level > static_cast<int>( - ProtocolHandlerSecurityLevel::kExtensionFeatures) + ProtocolHandlerSecurityLevel::kIsolatedAppFeatures) ? ProtocolHandlerSecurityLevel::kStrict : static_cast<ProtocolHandlerSecurityLevel>(security_level); }
diff --git a/third_party/blink/public/mojom/use_counter/metrics/web_feature.mojom b/third_party/blink/public/mojom/use_counter/metrics/web_feature.mojom index 5c1990ed..40c19a3 100644 --- a/third_party/blink/public/mojom/use_counter/metrics/web_feature.mojom +++ b/third_party/blink/public/mojom/use_counter/metrics/web_feature.mojom
@@ -1124,7 +1124,7 @@ kOffscreenCanvasTransferToImageBitmap2D = 1658, kOffscreenCanvasTransferToImageBitmapWebGL = 1659, kOBSOLETE_OffscreenCanvasCommit2D = 1660, - kOffscreenCanvasCommitWebGL = 1661, + kOBSOLETE_OffscreenCanvasCommitWebGL = 1661, kRTCConfigurationIceTransportPolicy = 1662, kRTCConfigurationIceTransports = 1664, kOBSOLETE_DocumentFullscreenElementInV0Shadow = 1665,
diff --git a/third_party/blink/renderer/bindings/generated_in_modules.gni b/third_party/blink/renderer/bindings/generated_in_modules.gni index 78d835e..9a5aaa8 100644 --- a/third_party/blink/renderer/bindings/generated_in_modules.gni +++ b/third_party/blink/renderer/bindings/generated_in_modules.gni
@@ -2864,8 +2864,6 @@ "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_speech_recognition.h", "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_speech_recognition_alternative.cc", "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_speech_recognition_alternative.h", - "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_speech_recognition_context.cc", - "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_speech_recognition_context.h", "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_speech_recognition_error_event.cc", "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_speech_recognition_error_event.h", "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_speech_recognition_event.cc",
diff --git a/third_party/blink/renderer/bindings/idl_in_modules.gni b/third_party/blink/renderer/bindings/idl_in_modules.gni index f12a189..b198db5 100644 --- a/third_party/blink/renderer/bindings/idl_in_modules.gni +++ b/third_party/blink/renderer/bindings/idl_in_modules.gni
@@ -790,7 +790,6 @@ "//third_party/blink/renderer/modules/speech/speech_grammar_list.idl", "//third_party/blink/renderer/modules/speech/speech_recognition.idl", "//third_party/blink/renderer/modules/speech/speech_recognition_alternative.idl", - "//third_party/blink/renderer/modules/speech/speech_recognition_context.idl", "//third_party/blink/renderer/modules/speech/speech_recognition_error_event.idl", "//third_party/blink/renderer/modules/speech/speech_recognition_error_event_init.idl", "//third_party/blink/renderer/modules/speech/speech_recognition_event.idl",
diff --git a/third_party/blink/renderer/core/css/css_properties.json5 b/third_party/blink/renderer/core/css/css_properties.json5 index 8cc92fb..1b62d30 100644 --- a/third_party/blink/renderer/core/css/css_properties.json5 +++ b/third_party/blink/renderer/core/css/css_properties.json5
@@ -2251,6 +2251,7 @@ valid_for_permission_element: true, valid_for_page_context: true, invalidate: ["border-width", "border-visual"], + affected_by_zoom: true, }, { name: "border-collapse", @@ -2404,6 +2405,7 @@ valid_for_permission_element: true, valid_for_page_context: true, invalidate: ["border-width", "border-visual"], + affected_by_zoom: true, }, { name: "border-right-color", @@ -2476,6 +2478,7 @@ valid_for_permission_element: true, valid_for_page_context: true, invalidate: ["border-width", "border-visual"], + affected_by_zoom: true, }, { name: "border-top-color", @@ -2593,6 +2596,7 @@ valid_for_permission_element: true, valid_for_page_context: true, invalidate: ["border-width", "border-visual"], + affected_by_zoom: true, }, { name: "border-shape",
diff --git a/third_party/blink/renderer/core/css/resolver/style_cascade.cc b/third_party/blink/renderer/core/css/resolver/style_cascade.cc index d99e5b5b..5fca188f 100644 --- a/third_party/blink/renderer/core/css/resolver/style_cascade.cc +++ b/third_party/blink/renderer/core/css/resolver/style_cascade.cc
@@ -2316,7 +2316,9 @@ CSSVariableData* StyleCascade::GetInitialVariableData( const CustomProperty& property) { const StyleInitialData* initial_data = state_.StyleBuilder().InitialData(); - DCHECK(initial_data); + if (!initial_data) { + return nullptr; + } return initial_data->GetVariableData(property.GetPropertyNameAtomicString()); }
diff --git a/third_party/blink/renderer/core/frame/web_frame_widget_impl.cc b/third_party/blink/renderer/core/frame/web_frame_widget_impl.cc index ac7056f6..e7c2cf2 100644 --- a/third_party/blink/renderer/core/frame/web_frame_widget_impl.cc +++ b/third_party/blink/renderer/core/frame/web_frame_widget_impl.cc
@@ -841,9 +841,19 @@ void WebFrameWidgetImpl::BindInputTargetClient( mojo::PendingReceiver<viz::mojom::blink::InputTargetClient> receiver) { // Both Browser and Viz attempts to bind this interface. There can be at max - // two remotes one for each Browser and Viz, so this check ensures we are not - // going past the 2 limit. - CHECK_LT(input_target_receivers_.size(), 2u); + // two remotes one for each Browser and Viz. + // Note: In some cases where GPU restarts due to a crash, there might be a + // race between BindInputTargetClient call from the new GPU process and the + // renderer running the disconnect handlers on input_target_receivers_ for the + // destroyed GPU process, implying there may be 3 receivers transiently. See + // crbug.com/424109284 for more details. + if (input_target_receivers_.size() >= 2) { + // TODO(424109284): Cleanup after investigation. + SCOPED_CRASH_KEY_STRING64( + "crbug424109284", "receivers_size", + base::NumberToString(input_target_receivers_.size())); + base::debug::DumpWithoutCrashing(); + } input_target_receivers_.Add( std::move(receiver), local_root_->GetTaskRunner(TaskType::kInternalInputBlocking));
diff --git a/third_party/blink/renderer/core/html/canvas/canvas_async_blob_creator.cc b/third_party/blink/renderer/core/html/canvas/canvas_async_blob_creator.cc index 22d1ba2..484294e 100644 --- a/third_party/blink/renderer/core/html/canvas/canvas_async_blob_creator.cc +++ b/third_party/blink/renderer/core/html/canvas/canvas_async_blob_creator.cc
@@ -36,6 +36,7 @@ #include "third_party/blink/renderer/platform/wtf/cross_thread_functional.h" #include "third_party/blink/renderer/platform/wtf/functional.h" #include "third_party/blink/renderer/platform/wtf/text/base64.h" +#include "third_party/blink/renderer/platform/wtf/text/strcat.h" #include "third_party/skia/include/core/SkSurface.h" #include "third_party/skia/include/encode/SkPngEncoder.h" @@ -521,8 +522,8 @@ [&](perfetto::EventContext ctx) { String data = "data:"; if (encoded_image) { - data = data + ImageEncoderUtils::MimeTypeName(mime_type_) + - ";base64," + Base64Encode(*encoded_image); + data = StrCat({data, ImageEncoderUtils::MimeTypeName(mime_type_), + ";base64,", Base64Encode(*encoded_image)}); } ctx.AddDebugAnnotation("data_url", data.Utf8()); ctx.AddDebugAnnotation(
diff --git a/third_party/blink/renderer/core/html/canvas/canvas_performance_monitor.cc b/third_party/blink/renderer/core/html/canvas/canvas_performance_monitor.cc index e6fbb22..f25b9c2 100644 --- a/third_party/blink/renderer/core/html/canvas/canvas_performance_monitor.cc +++ b/third_party/blink/renderer/core/html/canvas/canvas_performance_monitor.cc
@@ -11,6 +11,7 @@ #include "third_party/blink/renderer/core/html/canvas/canvas_rendering_context.h" #include "third_party/blink/renderer/platform/heap/process_heap.h" #include "third_party/blink/renderer/platform/wtf/bit_field.h" +#include "third_party/blink/renderer/platform/wtf/text/strcat.h" namespace { @@ -196,25 +197,23 @@ // Note: We cannot use the UMA_HISTOGRAM_* macros here due to dynamic // naming. See comments at top of base/metrics/histogram_macros.h for more // info. - WTF::String histogram_name_prefix = - WTF::String("Blink") + desc.GetHostTypeName(); - WTF::String histogram_name_radical = - WTF::String(desc.GetRenderingAPIName()); + String histogram_name_prefix = StrCat({"Blink", desc.GetHostTypeName()}); + String histogram_name_radical = String(desc.GetRenderingAPIName()); // Render task duration metric for all render tasks. { - WTF::String histogram_name = histogram_name_prefix + - kMeasurementName_RenderTaskDuration + - histogram_name_radical + kFilterName_All; + String histogram_name = + StrCat({histogram_name_prefix, kMeasurementName_RenderTaskDuration, + histogram_name_radical, kFilterName_All}); base::UmaHistogramMicrosecondsTimes(histogram_name.Latin1(), elapsed_time); } // Render task duration metric for rAF callbacks only. if (call_type_ == CallType::kAnimation) { - WTF::String histogram_name = - histogram_name_prefix + kMeasurementName_RenderTaskDuration + - histogram_name_radical + kFilterName_Animation; + String histogram_name = + StrCat({histogram_name_prefix, kMeasurementName_RenderTaskDuration, + histogram_name_radical, kFilterName_Animation}); base::UmaHistogramMicrosecondsTimes(histogram_name.Latin1(), elapsed_time); } @@ -223,37 +222,37 @@ if (desc.GetRenderingAPI() == CanvasRenderingContext::CanvasRenderingAPI::k2D) { if (draw_types_ & static_cast<uint32_t>(DrawType::kPath)) { - WTF::String histogram_name = histogram_name_prefix + - kMeasurementName_RenderTaskDuration + - histogram_name_radical + kFilterName_Path; + String histogram_name = + StrCat({histogram_name_prefix, kMeasurementName_RenderTaskDuration, + histogram_name_radical, kFilterName_Path}); base::UmaHistogramMicrosecondsTimes(histogram_name.Latin1(), elapsed_time); } if (draw_types_ & static_cast<uint32_t>(DrawType::kImage)) { - WTF::String histogram_name = histogram_name_prefix + - kMeasurementName_RenderTaskDuration + - histogram_name_radical + kFilterName_Image; + String histogram_name = + StrCat({histogram_name_prefix, kMeasurementName_RenderTaskDuration, + histogram_name_radical, kFilterName_Image}); base::UmaHistogramMicrosecondsTimes(histogram_name.Latin1(), elapsed_time); } if (draw_types_ & static_cast<uint32_t>(DrawType::kImageData)) { - WTF::String histogram_name = - histogram_name_prefix + kMeasurementName_RenderTaskDuration + - histogram_name_radical + kFilterName_ImageData; + String histogram_name = + StrCat({histogram_name_prefix, kMeasurementName_RenderTaskDuration, + histogram_name_radical, kFilterName_ImageData}); base::UmaHistogramMicrosecondsTimes(histogram_name.Latin1(), elapsed_time); } if (draw_types_ & static_cast<uint32_t>(DrawType::kText)) { - WTF::String histogram_name = histogram_name_prefix + - kMeasurementName_RenderTaskDuration + - histogram_name_radical + kFilterName_Text; + String histogram_name = + StrCat({histogram_name_prefix, kMeasurementName_RenderTaskDuration, + histogram_name_radical, kFilterName_Text}); base::UmaHistogramMicrosecondsTimes(histogram_name.Latin1(), elapsed_time); } if (draw_types_ & static_cast<uint32_t>(DrawType::kRectangle)) { - WTF::String histogram_name = - histogram_name_prefix + kMeasurementName_RenderTaskDuration + - histogram_name_radical + kFilterName_Rectangle; + String histogram_name = + StrCat({histogram_name_prefix, kMeasurementName_RenderTaskDuration, + histogram_name_radical, kFilterName_Rectangle}); base::UmaHistogramMicrosecondsTimes(histogram_name.Latin1(), elapsed_time); } @@ -263,16 +262,16 @@ CanvasRenderingContext::CanvasRenderingAPI::kWebgl2) { // Filtered histograms that apply to WebGL canvases if (draw_types_ & static_cast<uint32_t>(DrawType::kDrawArrays)) { - WTF::String histogram_name = - histogram_name_prefix + kMeasurementName_RenderTaskDuration + - histogram_name_radical + kFilterName_DrawArrays; + String histogram_name = + StrCat({histogram_name_prefix, kMeasurementName_RenderTaskDuration, + histogram_name_radical, kFilterName_DrawArrays}); base::UmaHistogramMicrosecondsTimes(histogram_name.Latin1(), elapsed_time); } if (draw_types_ & static_cast<uint32_t>(DrawType::kDrawElements)) { - WTF::String histogram_name = - histogram_name_prefix + kMeasurementName_RenderTaskDuration + - histogram_name_radical + kFilterName_DrawElements; + String histogram_name = + StrCat({histogram_name_prefix, kMeasurementName_RenderTaskDuration, + histogram_name_radical, kFilterName_DrawElements}); base::UmaHistogramMicrosecondsTimes(histogram_name.Latin1(), elapsed_time); } @@ -281,18 +280,18 @@ // PartitionAlloc heap size metric { - WTF::String histogram_name = histogram_name_prefix + - kMeasurementName_PartitionAlloc + - histogram_name_radical; + String histogram_name = + StrCat({histogram_name_prefix, kMeasurementName_PartitionAlloc, + histogram_name_radical}); base::UmaHistogramMemoryKB(histogram_name.Latin1(), static_cast<int>(partition_alloc_kb)); } // Blink garbage collected heap size metric { - WTF::String histogram_name = histogram_name_prefix + - kMeasurementName_BlinkGC + - histogram_name_radical; + String histogram_name = + StrCat({histogram_name_prefix, kMeasurementName_BlinkGC, + histogram_name_radical}); base::UmaHistogramMemoryKB(histogram_name.Latin1(), static_cast<int>(blink_gc_alloc_kb)); }
diff --git a/third_party/blink/renderer/core/html/canvas/canvas_rendering_context_host.cc b/third_party/blink/renderer/core/html/canvas/canvas_rendering_context_host.cc index 855bde1d..1f3c7d6 100644 --- a/third_party/blink/renderer/core/html/canvas/canvas_rendering_context_host.cc +++ b/third_party/blink/renderer/core/html/canvas/canvas_rendering_context_host.cc
@@ -91,12 +91,6 @@ return UnacceleratedStaticBitmapImage::Create(surface->makeImageSnapshot()); } -bool CanvasRenderingContextHost::Commit(scoped_refptr<CanvasResource>&&, - const SkIRect&) { - NOTIMPLEMENTED(); - return false; -} - bool CanvasRenderingContextHost::IsValidImageSize() const { const gfx::Size size = Size(); if (size.IsEmpty()) {
diff --git a/third_party/blink/renderer/core/html/canvas/canvas_rendering_context_host.h b/third_party/blink/renderer/core/html/canvas/canvas_rendering_context_host.h index 48b95a4d..040fc4f 100644 --- a/third_party/blink/renderer/core/html/canvas/canvas_rendering_context_host.h +++ b/third_party/blink/renderer/core/html/canvas/canvas_rendering_context_host.h
@@ -104,16 +104,17 @@ virtual bool ShouldAccelerate2dContext() const = 0; - virtual bool Commit(scoped_refptr<CanvasResource>&& canvas_resource, - const SkIRect& damage_rect); - virtual UkmParameters GetUkmParameters() = 0; bool IsValidImageSize() const; bool IsPaintable() const; + virtual bool IsHibernating() const { return false; } + virtual bool LowLatencyEnabled() const { return false; } + virtual void SetTransferToGPUTextureWasInvoked() {} + // Required by template functions in WebGLRenderingContextBase int width() const { return Size().width(); } int height() const { return Size().height(); }
diff --git a/third_party/blink/renderer/core/html/canvas/predefined_color_space.cc b/third_party/blink/renderer/core/html/canvas/predefined_color_space.cc index cb3cfb2..b11c834 100644 --- a/third_party/blink/renderer/core/html/canvas/predefined_color_space.cc +++ b/third_party/blink/renderer/core/html/canvas/predefined_color_space.cc
@@ -8,6 +8,7 @@ #include "third_party/blink/renderer/bindings/core/v8/v8_canvas_smpte_st_2086_metadata.h" #include "third_party/blink/renderer/bindings/core/v8/v8_predefined_color_space.h" #include "third_party/blink/renderer/platform/runtime_enabled_features.h" +#include "third_party/blink/renderer/platform/wtf/text/strcat.h" #include "third_party/skia/include/core/SkData.h" namespace blink { @@ -41,9 +42,9 @@ break; } if (needs_hdr && !RuntimeEnabledFeatures::CanvasHDREnabled()) { - exception_state.ThrowTypeError( - "The provided value '" + v8_color_space.AsString() + - "' is not a valid enum value of the type PredefinedColorSpace."); + exception_state.ThrowTypeError(StrCat( + {"The provided value '", v8_color_space.AsString(), + "' is not a valid enum value of the type PredefinedColorSpace."})); return false; } return true;
diff --git a/third_party/blink/renderer/core/html/fenced_frame/fence.cc b/third_party/blink/renderer/core/html/fenced_frame/fence.cc index c8d4d9a1..d4afe3e 100644 --- a/third_party/blink/renderer/core/html/fenced_frame/fence.cc +++ b/third_party/blink/renderer/core/html/fenced_frame/fence.cc
@@ -266,8 +266,8 @@ std::optional<mojom::blink::AutomaticBeaconType> beacon_type = GetAutomaticBeaconType(event->eventType()); if (!beacon_type.has_value()) { - AddConsoleMessage(event->eventType() + - " is not a valid automatic beacon event type."); + AddConsoleMessage(StrCat( + {event->eventType(), " is not a valid automatic beacon event type."})); return; } if (event->hasEventData() && @@ -279,8 +279,8 @@ } if (event->eventType() == blink::kDeprecatedFencedFrameTopNavigationBeaconType) { - AddConsoleMessage(event->eventType() + " is deprecated in favor of " + - kFencedFrameTopNavigationCommitBeaconType + ".", + AddConsoleMessage(StrCat({event->eventType(), " is deprecated in favor of ", + kFencedFrameTopNavigationCommitBeaconType, "."}), mojom::blink::ConsoleMessageLevel::kWarning); } LocalFrame* frame = DomWindow()->GetFrame();
diff --git a/third_party/blink/renderer/core/html/forms/html_form_control_element_with_state.cc b/third_party/blink/renderer/core/html/forms/html_form_control_element_with_state.cc index f8917bc2..4ee75ef 100644 --- a/third_party/blink/renderer/core/html/forms/html_form_control_element_with_state.cc +++ b/third_party/blink/renderer/core/html/forms/html_form_control_element_with_state.cc
@@ -29,6 +29,7 @@ #include "third_party/blink/renderer/core/html_names.h" #include "third_party/blink/renderer/core/input_type_names.h" #include "third_party/blink/renderer/platform/runtime_enabled_features.h" +#include "third_party/blink/renderer/platform/wtf/text/strcat.h" namespace blink { @@ -241,7 +242,7 @@ } // 16.7 Let IDL value be the concatenation of the indexth token in tokens, // a U+0020 SPACE character, and the previous value of IDL value. - idl_value = tokens[index] + " " + idl_value; + idl_value = StrCat({tokens[index], " ", idl_value}); } } @@ -259,7 +260,7 @@ // 19.4. Let IDL value be the concatenation of contact, a U+0020 SPACE // character, and the previous value of IDL value (which at this point // will always be field). - idl_value = contact + " " + idl_value; + idl_value = StrCat({contact, " ", idl_value}); // 19.5. If the indexth entry in tokens is the first entry, then skip to // the step labeled done. if (index == 0) { @@ -278,7 +279,7 @@ // character, and the previous value of IDL value (which at this point // will either be field or the concatenation of contact, a space, and // field). - idl_value = mode + " " + idl_value; + idl_value = StrCat({mode, " ", idl_value}); // 20.5 If the indexth entry in tokens is the first entry, then skip to // the step labeled done. if (index == 0) { @@ -300,7 +301,7 @@ return g_empty_string; // 25. Let IDL value be the concatenation of section, a U+0020 SPACE // character, and the previous value of IDL value. - idl_value = section + " " + idl_value; + idl_value = StrCat({section, " ", idl_value}); } // 30. Let the element's IDL-exposed autofill value be IDL value. return idl_value;
diff --git a/third_party/blink/renderer/core/html/forms/html_option_element.cc b/third_party/blink/renderer/core/html/forms/html_option_element.cc index 56a7bb2..be8e8198 100644 --- a/third_party/blink/renderer/core/html/forms/html_option_element.cc +++ b/third_party/blink/renderer/core/html/forms/html_option_element.cc
@@ -459,7 +459,7 @@ String HTMLOptionElement::TextIndentedToRespectGroupLabel() const { ContainerNode* parent = parentNode(); if (parent && IsA<HTMLOptGroupElement>(*parent)) - return " " + DisplayLabel(); + return StrCat({" ", DisplayLabel()}); return DisplayLabel(); }
diff --git a/third_party/blink/renderer/core/html/forms/image_input_type.cc b/third_party/blink/renderer/core/html/forms/image_input_type.cc index 55d98a4..e744258 100644 --- a/third_party/blink/renderer/core/html/forms/image_input_type.cc +++ b/third_party/blink/renderer/core/html/forms/image_input_type.cc
@@ -66,10 +66,8 @@ return; } - DEFINE_STATIC_LOCAL(String, dot_x_string, (".x")); - DEFINE_STATIC_LOCAL(String, dot_y_string, (".y")); - form_data.AppendFromElement(name + dot_x_string, click_location_.x()); - form_data.AppendFromElement(name + dot_y_string, click_location_.y()); + form_data.AppendFromElement(StrCat({name, ".x"}), click_location_.x()); + form_data.AppendFromElement(StrCat({name, ".y"}), click_location_.y()); } String ImageInputType::ResultForDialogSubmit() const {
diff --git a/third_party/blink/renderer/core/html/forms/internal_popup_menu.cc b/third_party/blink/renderer/core/html/forms/internal_popup_menu.cc index 70fabad..47f0b84 100644 --- a/third_party/blink/renderer/core/html/forms/internal_popup_menu.cc +++ b/third_party/blink/renderer/core/html/forms/internal_popup_menu.cc
@@ -552,7 +552,7 @@ const String& target, SegmentedBuffer& data, const ComputedStyle& style) { - PagePopupClient::AddString(target + "{ \n", data); + PagePopupClient::AddString(StrCat({target, "{ \n"}), data); const CSSPropertyID serialize_targets[] = { CSSPropertyID::kDisplay, CSSPropertyID::kBackgroundColor,
diff --git a/third_party/blink/renderer/core/html/forms/number_input_type.cc b/third_party/blink/renderer/core/html/forms/number_input_type.cc index 1915975..4a40459 100644 --- a/third_party/blink/renderer/core/html/forms/number_input_type.cc +++ b/third_party/blink/renderer/core/html/forms/number_input_type.cc
@@ -33,6 +33,7 @@ #include <limits> +#include "base/containers/span.h" #include "third_party/blink/public/strings/grit/blink_strings.h" #include "third_party/blink/renderer/core/dom/events/scoped_event_queue.h" #include "third_party/blink/renderer/core/events/before_text_inserted_event.h" @@ -288,7 +289,7 @@ // - Reject if the editing value contains 'e' and the caret is placed // neither at the beginning of the value nor just after 'e' else if (locale.IsSignPrefix(c)) { - String both_halves = left_half + right_half; + String both_halves = StrCat({left_half, right_half}); if (locale.HasTwoSignChars(both_halves) || (both_halves.Find(IsE) != kNotFound && !(left_half == "" || IsE(left_half[left_half.length() - 1])))) @@ -308,7 +309,7 @@ } // Add character - left_half = left_half + c; + left_half = StrCat({left_half, StringView(base::span_from_ref(c))}); final_event_text.Append(c); } event.SetText(final_event_text.ReleaseString());
diff --git a/third_party/blink/renderer/core/html/forms/select_mutation_observer.cc b/third_party/blink/renderer/core/html/forms/select_mutation_observer.cc index c964c94..99ee2b3d 100644 --- a/third_party/blink/renderer/core/html/forms/select_mutation_observer.cc +++ b/third_party/blink/renderer/core/html/forms/select_mutation_observer.cc
@@ -208,29 +208,30 @@ String SelectMutationObserver::FormatElementMessage(const String& element, const String& article, const String& example) { - return "An element which is not allowed in the content model of the " + - element + " element was found within " + article + element + - " element. These elements will not consistently be accessible to " - "people navigating by keyboard or using assistive technology. If " - "using disallowed elements for layout structure and styling, " - "consider using the allowed <div> element instead. Any text " - "existing within the " + - element + - " element should either be removed or relocated to a valid element " - "that allows text descendants, e.g., " + - example + "."; + return StrCat( + {"An element which is not allowed in the content model of the ", element, + " element was found within ", article, element, + " element. These elements will not consistently be accessible to people " + "navigating by keyboard or using assistive technology. If using " + "disallowed elements for layout structure and styling, consider using " + "the allowed <div> element instead. Any text existing within the ", + element, + " element should either be removed or relocated to a valid element that " + "allows text descendants, e.g., ", + example, "."}); } String SelectMutationObserver::FormatInteractiveElementMessage( const String& element, const String& article, const String& context) { - return "An interactive element which is not allowed in the content model " - "of the " + - element + " element was found within " + article + element + - " element. " + context + - "These elements will not consistently be accessible to people " - "navigating by keyboard or using assistive technology."; + return StrCat( + {"An interactive element which is not allowed in the content model of " + "the ", + element, " element was found within ", article, element, " element. ", + context, + "These elements will not consistently be accessible to people " + "navigating by keyboard or using assistive technology."}); } bool SelectMutationObserver::IsAllowedInteractiveElement(Node& node) {
diff --git a/third_party/blink/renderer/core/html/forms/text_control_element.cc b/third_party/blink/renderer/core/html/forms/text_control_element.cc index 2ac58b9..b7efa99 100644 --- a/third_party/blink/renderer/core/html/forms/text_control_element.cc +++ b/third_party/blink/renderer/core/html/forms/text_control_element.cc
@@ -802,10 +802,10 @@ ExceptionState& exception_state) { int min = minLength(); if (new_value < 0) { - exception_state.ThrowDOMException(DOMExceptionCode::kIndexSizeError, - "The value provided (" + - String::Number(new_value) + - ") is not positive or 0."); + exception_state.ThrowDOMException( + DOMExceptionCode::kIndexSizeError, + StrCat({"The value provided (", String::Number(new_value), + ") is not positive or 0."})); } else if (min >= 0 && new_value < min) { exception_state.ThrowDOMException( DOMExceptionCode::kIndexSizeError, @@ -820,10 +820,10 @@ ExceptionState& exception_state) { int max = maxLength(); if (new_value < 0) { - exception_state.ThrowDOMException(DOMExceptionCode::kIndexSizeError, - "The value provided (" + - String::Number(new_value) + - ") is not positive or 0."); + exception_state.ThrowDOMException( + DOMExceptionCode::kIndexSizeError, + StrCat({"The value provided (", String::Number(new_value), + ") is not positive or 0."})); } else if (max >= 0 && new_value > max) { exception_state.ThrowDOMException( DOMExceptionCode::kIndexSizeError,
diff --git a/third_party/blink/renderer/core/html/html_element.cc b/third_party/blink/renderer/core/html/html_element.cc index 4ca78d6..1c50c1c 100644 --- a/third_party/blink/renderer/core/html/html_element.cc +++ b/third_party/blink/renderer/core/html/html_element.cc
@@ -1148,10 +1148,11 @@ } else if (lower_value == keywords::kInherit) { removeAttribute(html_names::kContenteditableAttr); } else { - exception_state.ThrowDOMException(DOMExceptionCode::kSyntaxError, - "The value provided ('" + enabled + - "') is not one of 'true', 'false', " - "'plaintext-only', or 'inherit'."); + exception_state.ThrowDOMException( + DOMExceptionCode::kSyntaxError, + StrCat({"The value provided ('", enabled, + "') is not one of 'true', 'false', 'plaintext-only', or " + "'inherit'."})); } } @@ -1309,11 +1310,11 @@ DOMExceptionCode code, const char* msg) { if (exception_state) { String error_message = - String(msg) + - (include_event_handler_text - ? " This might have been the result of the \"beforetoggle\" " - "event handler changing the state of this popover." - : ""); + StrCat({msg, (include_event_handler_text + ? StringView(" This might have been the result of " + "the \"beforetoggle\" event handler " + "changing the state of this popover.") + : StringView())}); exception_state->ThrowDOMException(code, error_message); } };
diff --git a/third_party/blink/renderer/core/html/html_iframe_element.cc b/third_party/blink/renderer/core/html/html_iframe_element.cc index e1d8a90..31e24a4 100644 --- a/third_party/blink/renderer/core/html/html_iframe_element.cc +++ b/third_party/blink/renderer/core/html/html_iframe_element.cc
@@ -216,8 +216,8 @@ GetDocument().AddConsoleMessage(MakeGarbageCollected<ConsoleMessage>( mojom::blink::ConsoleMessageSource::kOther, mojom::blink::ConsoleMessageLevel::kError, - "Error while parsing the 'sandbox' attribute: " + - String::FromUTF8(parsed.error_message))); + StrCat({"Error while parsing the 'sandbox' attribute: ", + String::FromUTF8(parsed.error_message)}))); } } SetSandboxFlags(current_flags);
diff --git a/third_party/blink/renderer/core/html/html_permission_element.cc b/third_party/blink/renderer/core/html/html_permission_element.cc index 1e57769..28d8b733 100644 --- a/third_party/blink/renderer/core/html/html_permission_element.cc +++ b/third_party/blink/renderer/core/html/html_permission_element.cc
@@ -765,9 +765,9 @@ CHECK(permission_descriptors_.empty()); permission_descriptors_ = ParsePermissionDescriptorsFromString(GetType()); if (permission_descriptors_.empty()) { - AddConsoleError("The permission type '" + GetType().GetString() + - "' is not supported by the " - "permission element."); + AddConsoleError( + StrCat({"The permission type '", GetType().GetString(), + "' is not supported by the permission element."})); EnableFallbackMode(); return; }
diff --git a/third_party/blink/renderer/core/html/html_table_element.cc b/third_party/blink/renderer/core/html/html_table_element.cc index 264c873..a8d6e897 100644 --- a/third_party/blink/renderer/core/html/html_table_element.cc +++ b/third_party/blink/renderer/core/html/html_table_element.cc
@@ -184,7 +184,8 @@ if (index < -1) { exception_state.ThrowDOMException( DOMExceptionCode::kIndexSizeError, - "The index provided (" + String::Number(index) + ") is less than -1."); + StrCat({"The index provided (", String::Number(index), + ") is less than -1."})); return nullptr; } @@ -199,9 +200,9 @@ if (i != index) { exception_state.ThrowDOMException( DOMExceptionCode::kIndexSizeError, - "The index provided (" + String::Number(index) + - ") is greater than the number of rows in the table (" + - String::Number(i) + ")."); + StrCat({"The index provided (", String::Number(index), + ") is greater than the number of rows in the table (", + String::Number(i), ")."})); return nullptr; } break;
diff --git a/third_party/blink/renderer/core/html/html_table_row_element.cc b/third_party/blink/renderer/core/html/html_table_row_element.cc index 3dadaf8..f51050a 100644 --- a/third_party/blink/renderer/core/html/html_table_row_element.cc +++ b/third_party/blink/renderer/core/html/html_table_row_element.cc
@@ -35,6 +35,7 @@ #include "third_party/blink/renderer/core/html_names.h" #include "third_party/blink/renderer/platform/bindings/exception_state.h" #include "third_party/blink/renderer/platform/heap/garbage_collected.h" +#include "third_party/blink/renderer/platform/wtf/text/strcat.h" namespace blink { @@ -90,8 +91,9 @@ if (index < -1 || index > num_cells) { exception_state.ThrowDOMException( DOMExceptionCode::kIndexSizeError, - "The value provided (" + String::Number(index) + - ") is outside the range [-1, " + String::Number(num_cells) + "]."); + StrCat({"The value provided (", String::Number(index), + ") is outside the range [-1, ", String::Number(num_cells), + "]."})); return nullptr; } @@ -113,8 +115,9 @@ if (index < -1 || index >= num_cells) { exception_state.ThrowDOMException( DOMExceptionCode::kIndexSizeError, - "The value provided (" + String::Number(index) + - ") is outside the range [0, " + String::Number(num_cells) + ")."); + StrCat({"The value provided (", String::Number(index), + ") is outside the range [0, ", String::Number(num_cells), + ")."})); return; } // 2. If index is −1, remove the last element in the cells collection
diff --git a/third_party/blink/renderer/core/html/html_table_section_element.cc b/third_party/blink/renderer/core/html/html_table_section_element.cc index 3adb3980..61a868e 100644 --- a/third_party/blink/renderer/core/html/html_table_section_element.cc +++ b/third_party/blink/renderer/core/html/html_table_section_element.cc
@@ -31,6 +31,7 @@ #include "third_party/blink/renderer/core/html/html_table_row_element.h" #include "third_party/blink/renderer/platform/bindings/exception_state.h" #include "third_party/blink/renderer/platform/heap/garbage_collected.h" +#include "third_party/blink/renderer/platform/wtf/text/strcat.h" namespace blink { @@ -55,8 +56,9 @@ if (index < -1 || index > num_rows) { exception_state.ThrowDOMException( DOMExceptionCode::kIndexSizeError, - "The provided index (" + String::Number(index) + - " is outside the range [-1, " + String::Number(num_rows) + "]."); + StrCat({"The provided index (", String::Number(index), + " is outside the range [-1, ", String::Number(num_rows), + "]."})); return nullptr; } @@ -83,8 +85,9 @@ } else { exception_state.ThrowDOMException( DOMExceptionCode::kIndexSizeError, - "The provided index (" + String::Number(index) + - " is outside the range [-1, " + String::Number(num_rows) + "]."); + StrCat({"The provided index (", String::Number(index), + " is outside the range [-1, ", String::Number(num_rows), + "]."})); } }
diff --git a/third_party/blink/renderer/core/html/media/html_media_element.cc b/third_party/blink/renderer/core/html/media/html_media_element.cc index 89a23de..1457df51 100644 --- a/third_party/blink/renderer/core/html/media/html_media_element.cc +++ b/third_party/blink/renderer/core/html/media/html_media_element.cc
@@ -1879,7 +1879,8 @@ GetDocument().AddConsoleMessage(MakeGarbageCollected<ConsoleMessage>( mojom::ConsoleMessageSource::kSecurity, mojom::ConsoleMessageLevel::kError, - "Not allowed to load local resource: " + url.ElidedString())); + StrCat( + {"Not allowed to load local resource: ", url.ElidedString()}))); } DVLOG(3) << "isSafeToLoadURL(" << *this << ", " << UrlForLoggingMedia(url) << ") -> FALSE rejected by SecurityOrigin";
diff --git a/third_party/blink/renderer/core/html/media/media_remoting_interstitial.cc b/third_party/blink/renderer/core/html/media/media_remoting_interstitial.cc index b3eeeb0..8a90546 100644 --- a/third_party/blink/renderer/core/html/media/media_remoting_interstitial.cc +++ b/third_party/blink/renderer/core/html/media/media_remoting_interstitial.cc
@@ -13,6 +13,7 @@ #include "third_party/blink/renderer/platform/bindings/exception_state.h" #include "third_party/blink/renderer/platform/heap/garbage_collected.h" #include "third_party/blink/renderer/platform/text/platform_locale.h" +#include "third_party/blink/renderer/platform/wtf/text/strcat.h" namespace { @@ -90,8 +91,8 @@ String stop_text = GetVideoElement().GetLocale().QueryString(IDS_MEDIA_REMOTING_STOP_TEXT); if (error_code != IDS_MEDIA_REMOTING_STOP_TEXT) { - stop_text = GetVideoElement().GetLocale().QueryString(error_code) + ", " + - stop_text; + stop_text = StrCat({GetVideoElement().GetLocale().QueryString(error_code), + ", ", stop_text}); } toast_message_->setInnerText(stop_text); state_ = kToast;
diff --git a/third_party/blink/renderer/core/html/track/html_track_element.cc b/third_party/blink/renderer/core/html/track/html_track_element.cc index 99bd184..18aabc6 100644 --- a/third_party/blink/renderer/core/html/track/html_track_element.cc +++ b/third_party/blink/renderer/core/html/track/html_track_element.cc
@@ -36,6 +36,7 @@ #include "third_party/blink/renderer/core/html/track/loadable_text_track.h" #include "third_party/blink/renderer/core/html_names.h" #include "third_party/blink/renderer/platform/heap/garbage_collected.h" +#include "third_party/blink/renderer/platform/wtf/text/strcat.h" #define TRACK_LOG_LEVEL 3 @@ -48,7 +49,7 @@ if (url_string.length() < kMaximumURLLengthForLogging) { return url_string; } - return url_string.Substring(0, kMaximumURLLengthForLogging) + "..."; + return StrCat({url_string.Substring(0, kMaximumURLLengthForLogging), "..."}); } HTMLTrackElement::HTMLTrackElement(Document& document)
diff --git a/third_party/blink/renderer/core/inspector/inspector_dom_agent.cc b/third_party/blink/renderer/core/inspector/inspector_dom_agent.cc index 4b763f0..26afc21 100644 --- a/third_party/blink/renderer/core/inspector/inspector_dom_agent.cc +++ b/third_party/blink/renderer/core/inspector/inspector_dom_agent.cc
@@ -1352,15 +1352,34 @@ // Selector evaluation for (Document* document : docs) { DummyExceptionStateForTesting exception_state; + + // First, evaluate the query selector on the document StaticElementList* element_list = document->QuerySelectorAll( AtomicString(whitespace_trimmed_query), exception_state); - if (exception_state.HadException() || !element_list) { - continue; + if (element_list && !exception_state.HadException()) { + for (unsigned i = 0; i < element_list->length(); ++i) { + result_collector.insert(element_list->item(i)); + } } - unsigned size = element_list->length(); - for (unsigned i = 0; i < size; ++i) { - result_collector.insert(element_list->item(i)); + // Second, evaluate the query selector on each shadow root + Node* document_element = document->documentElement(); + Node* node = document_element; + if (!node) { + continue; + } + for (; node; node = NextNodeWithShadowDOMInMind( + *node, document_element, include_user_agent_shadow_dom)) { + if (auto* shadow_root = DynamicTo<ShadowRoot>(node)) { + element_list = shadow_root->QuerySelectorAll( + AtomicString(whitespace_trimmed_query), exception_state); + if (exception_state.HadException() || !element_list) { + continue; + } + for (unsigned i = 0; i < element_list->length(); ++i) { + result_collector.insert(element_list->item(i)); + } + } } }
diff --git a/third_party/blink/renderer/core/offscreencanvas/offscreen_canvas.cc b/third_party/blink/renderer/core/offscreencanvas/offscreen_canvas.cc index a6d65223..9e6280f 100644 --- a/third_party/blink/renderer/core/offscreencanvas/offscreen_canvas.cc +++ b/third_party/blink/renderer/core/offscreencanvas/offscreen_canvas.cc
@@ -104,19 +104,6 @@ external_memory_accounter_.Decrease(v8::Isolate::GetCurrent(), memory_usage_); } -bool OffscreenCanvas::Commit(scoped_refptr<CanvasResource>&& canvas_resource, - const SkIRect& damage_rect) { - if (!HasPlaceholderCanvas() || !canvas_resource) - return false; - RecordCanvasSizeToUMA(); - - current_frame_damage_rect_.join(damage_rect); - GetOrCreateResourceDispatcher()->DispatchFrameSync( - std::move(canvas_resource), current_frame_damage_rect_, IsOpaque()); - current_frame_damage_rect_ = SkIRect::MakeEmpty(); - return true; -} - void OffscreenCanvas::Dispose() { // We need to drop frame dispatcher, to prevent mojo calls from completing. disposing_ = true;
diff --git a/third_party/blink/renderer/core/offscreencanvas/offscreen_canvas.h b/third_party/blink/renderer/core/offscreencanvas/offscreen_canvas.h index bb21e021..0cd9817 100644 --- a/third_party/blink/renderer/core/offscreencanvas/offscreen_canvas.h +++ b/third_party/blink/renderer/core/offscreencanvas/offscreen_canvas.h
@@ -123,8 +123,6 @@ const SkIRect& damage_rect) override; void DidDraw(const SkIRect&) override; using CanvasRenderingContextHost::DidDraw; - bool Commit(scoped_refptr<CanvasResource>&& bitmap_image, - const SkIRect& damage_rect) override; bool ShouldAccelerate2dContext() const override; CanvasResourceDispatcher* GetOrCreateResourceDispatcher() override; void DiscardResourceDispatcher() override { frame_dispatcher_ = nullptr; }
diff --git a/third_party/blink/renderer/modules/canvas/offscreencanvas/offscreen_canvas_test.cc b/third_party/blink/renderer/modules/canvas/offscreencanvas/offscreen_canvas_test.cc index 9939b90..5400b083 100644 --- a/third_party/blink/renderer/modules/canvas/offscreencanvas/offscreen_canvas_test.cc +++ b/third_party/blink/renderer/modules/canvas/offscreencanvas/offscreen_canvas_test.cc
@@ -196,16 +196,17 @@ GetCanvasElement()->RemoveListener(listener); } -// Verifies that an offscreen_canvas()s PushFrame()/Commit() has the appropriate +// Verifies that an offscreen_canvas()s PushFrame() has the appropriate // opacity/blending information sent to the CompositorFrameSink. TEST_P(OffscreenCanvasTest, CompositorFrameOpacity) { ScopedTestingPlatformSupport<TestingPlatformSupport> platform; ScriptState::Scope scope(GetScriptState()); ::testing::InSequence s; - // To intercept SubmitCompositorFrame/SubmitCompositorFrameSync messages sent - // by OffscreenCanvas's CanvasResourceDispatcher, we have to override the Mojo - // EmbeddedFrameSinkProvider interface impl and its CompositorFrameSinkClient. + // To intercept SubmitCompositorFrame messages sent by OffscreenCanvas's + // CanvasResourceDispatcher, we have to override the Mojo + // EmbeddedFrameSinkProvider interface impl and its + // CompositorFrameSinkClient. MockEmbeddedFrameSinkProvider mock_embedded_frame_sink_provider; mojo::Receiver<mojom::blink::EmbeddedFrameSinkProvider> embedded_frame_sink_provider_receiver(&mock_embedded_frame_sink_provider); @@ -213,10 +214,10 @@ mock_embedded_frame_sink_provider.CreateScopedOverrideMojoInterface( &embedded_frame_sink_provider_receiver); - // Call here DidDraw() to simulate having drawn something before PushFrame()/ - // Commit(); DidDraw() will in turn cause a CanvasResourceDispatcher to be - // created and a CreateCompositorFrameSink() to be issued; this sink will get - // a SetNeedsBeginFrame() message sent upon construction. + // Call here DidDraw() to simulate having drawn something before PushFrame(); + // DidDraw() will in turn cause a CanvasResourceDispatcher to be created and + // a CreateCompositorFrameSink() to be issued; this sink will get a + // SetNeedsBeginFrame() message sent upon construction. mock_embedded_frame_sink_provider .set_num_expected_set_needs_begin_frame_on_sink_construction(1); EXPECT_CALL(mock_embedded_frame_sink_provider, @@ -251,30 +252,6 @@ offscreen_canvas().PushFrame(std::move(canvas_resource), SkIRect::MakeWH(10, 10)); platform->RunUntilIdle(); - - auto canvas_resource2 = CanvasResourceSharedImage::CreateSoftware( - offscreen_canvas().Size(), viz::SinglePlaneFormat::kBGRA_8888, - kPremul_SkAlphaType, gfx::ColorSpace::CreateSRGB(), - /*provider=*/nullptr, shared_image_interface_provider()); - EXPECT_CALL(mock_embedded_frame_sink_provider.mock_compositor_frame_sink(), - SubmitCompositorFrameSync_(_)) - .WillOnce(::testing::WithArg<0>( - ::testing::Invoke([context_alpha](const viz::CompositorFrame* frame) { - ASSERT_EQ(frame->render_pass_list.size(), 1u); - - const auto& quad_list = frame->render_pass_list[0]->quad_list; - ASSERT_EQ(quad_list.size(), 1u); - EXPECT_EQ(quad_list.front()->needs_blending, context_alpha); - - const auto& shared_quad_state_list = - frame->render_pass_list[0]->shared_quad_state_list; - ASSERT_EQ(shared_quad_state_list.size(), 1u); - EXPECT_NE(shared_quad_state_list.front()->are_contents_opaque, - context_alpha); - }))); - offscreen_canvas().Commit(std::move(canvas_resource2), - SkIRect::MakeWH(10, 10)); - platform->RunUntilIdle(); } TEST_P(OffscreenCanvasTest, GetRasterModeAutoRecovery) {
diff --git a/third_party/blink/renderer/modules/compression/compression_stream.cc b/third_party/blink/renderer/modules/compression/compression_stream.cc index e0da253..74e061b 100644 --- a/third_party/blink/renderer/modules/compression/compression_stream.cc +++ b/third_party/blink/renderer/modules/compression/compression_stream.cc
@@ -19,10 +19,12 @@ } ReadableStream* CompressionStream::readable() const { + CHECK(initialized_); return transform_->Readable(); } WritableStream* CompressionStream::writable() const { + CHECK(initialized_); return transform_->Writable(); } @@ -50,6 +52,8 @@ MakeGarbageCollected<DeflateTransformer>( script_state, deflate_format, deflate_level), exception_state); + CHECK(transform_); + initialized_ = true; } } // namespace blink
diff --git a/third_party/blink/renderer/modules/compression/compression_stream.h b/third_party/blink/renderer/modules/compression/compression_stream.h index 9f93cd4be..170d437 100644 --- a/third_party/blink/renderer/modules/compression/compression_stream.h +++ b/third_party/blink/renderer/modules/compression/compression_stream.h
@@ -30,6 +30,10 @@ private: Member<TransformStream> transform_; + + // TODO(crbug.com/427166012): Remove this when the crashes in this class have + // been diagnosed. + bool initialized_ = false; }; } // namespace blink
diff --git a/third_party/blink/renderer/modules/manifest/manifest_parser.cc b/third_party/blink/renderer/modules/manifest/manifest_parser.cc index 4f047cd..98f18e6d 100644 --- a/third_party/blink/renderer/modules/manifest/manifest_parser.cc +++ b/third_party/blink/renderer/modules/manifest/manifest_parser.cc
@@ -1661,9 +1661,13 @@ String error_message; bool is_valid_protocol = protocol.has_value(); + ProtocolHandlerSecurityLevel security_level = + execution_context_->IsIsolatedContext() + ? ProtocolHandlerSecurityLevel::kIsolatedAppFeatures + : ProtocolHandlerSecurityLevel::kStrict; if (is_valid_protocol && !VerifyCustomHandlerScheme(protocol.value(), error_message, - ProtocolHandlerSecurityLevel::kStrict)) { + security_level)) { AddErrorInfo(error_message); is_valid_protocol = false; } @@ -1692,7 +1696,7 @@ KURL full_url(manifest_url_, tokenless_url); if (!VerifyCustomHandlerURLSyntax(full_url, manifest_url_, user_url, - error_message)) { + security_level, error_message)) { AddErrorInfo(error_message); is_valid_url = false; }
diff --git a/third_party/blink/renderer/modules/mediastream/user_media_request.cc b/third_party/blink/renderer/modules/mediastream/user_media_request.cc index ea88e9b..a5fdfec 100644 --- a/third_party/blink/renderer/modules/mediastream/user_media_request.cc +++ b/third_party/blink/renderer/modules/mediastream/user_media_request.cc
@@ -80,6 +80,16 @@ // These values are persisted to logs. Entries should not be renumbered and // numeric values should never be reused. +enum class GetDisplayMediaSystemWindowOrExcludeConstraint { + kNotSpecified = 0, + kSystem = 1, + kWindow = 2, + kExclude = 3, + kMaxValue = kExclude +}; + +// These values are persisted to logs. Entries should not be renumbered and +// numeric values should never be reused. enum class GetDisplayMediaConstraintsDisplaySurface { kNotSpecified = 0, kTab = 1, @@ -296,6 +306,28 @@ base::UmaHistogramEnumeration(histogram_name, value); } +void RecordGetDisplayMediaSystemWindowOrExcludeConstraintUma( + std::optional<V8DisplayMediaSystemWindowOrExclude::Enum> + system_window_or_exclude, + const std::string& histogram_name) { + GetDisplayMediaSystemWindowOrExcludeConstraint value = + GetDisplayMediaSystemWindowOrExcludeConstraint::kNotSpecified; + if (system_window_or_exclude.has_value()) { + switch (system_window_or_exclude.value()) { + case V8DisplayMediaSystemWindowOrExclude::Enum::kExclude: + value = GetDisplayMediaSystemWindowOrExcludeConstraint::kExclude; + break; + case V8DisplayMediaSystemWindowOrExclude::Enum::kWindow: + value = GetDisplayMediaSystemWindowOrExcludeConstraint::kWindow; + break; + case V8DisplayMediaSystemWindowOrExclude::Enum::kSystem: + value = GetDisplayMediaSystemWindowOrExcludeConstraint::kSystem; + break; + } + } + base::UmaHistogramEnumeration(histogram_name, value); +} + void RecordPreferredDisplaySurfaceConstraintUma( const mojom::blink::PreferredDisplaySurface preferred_display_surface) { switch (preferred_display_surface) { @@ -522,6 +554,16 @@ } } result->set_window_audio_preference(value); + if (media_type == UserMediaRequestType::kDisplayMedia) { + std::optional<V8DisplayMediaSystemWindowOrExclude::Enum> + window_audio_preference; + if (options->hasWindowAudio()) { + window_audio_preference = options->windowAudio().AsEnum(); + } + RecordGetDisplayMediaSystemWindowOrExcludeConstraintUma( + window_audio_preference, + "Media.GetDisplayMedia.Constraints.WindowAudio"); + } } else { // if the feature is not enabled, we'll set kExclude to never share audio // when sharing windows.
diff --git a/third_party/blink/renderer/modules/navigatorcontentutils/navigator_content_utils.cc b/third_party/blink/renderer/modules/navigatorcontentutils/navigator_content_utils.cc index 854c566..a9fe952 100644 --- a/third_party/blink/renderer/modules/navigatorcontentutils/navigator_content_utils.cc +++ b/third_party/blink/renderer/modules/navigatorcontentutils/navigator_content_utils.cc
@@ -91,7 +91,7 @@ String error_message; if (!VerifyCustomHandlerURLSyntax(full_url, base_url, user_url, - error_message)) { + security_level, error_message)) { exception_state.ThrowDOMException(DOMExceptionCode::kSyntaxError, error_message); return false; @@ -140,10 +140,11 @@ bool VerifyCustomHandlerURLSyntax(const KURL& full_url, const KURL& base_url, const String& user_url, + ProtocolHandlerSecurityLevel security_level, String& error_message) { StringUtf8Adaptor url_adaptor(user_url); - URLSyntaxErrorCode code = - IsValidCustomHandlerURLSyntax(GURL(full_url), url_adaptor.AsStringView()); + URLSyntaxErrorCode code = IsValidCustomHandlerURLSyntax( + GURL(full_url), url_adaptor.AsStringView(), security_level); switch (code) { case URLSyntaxErrorCode::kNoError: return true;
diff --git a/third_party/blink/renderer/modules/navigatorcontentutils/navigator_content_utils.h b/third_party/blink/renderer/modules/navigatorcontentutils/navigator_content_utils.h index a5dfd2e3..80e21852 100644 --- a/third_party/blink/renderer/modules/navigatorcontentutils/navigator_content_utils.h +++ b/third_party/blink/renderer/modules/navigatorcontentutils/navigator_content_utils.h
@@ -56,6 +56,7 @@ bool VerifyCustomHandlerURLSyntax(const KURL& full_url, const KURL& base_url, const String& user_url, + ProtocolHandlerSecurityLevel security_level, String& error_message); // It is owned by Navigator, and an instance is created lazily by calling
diff --git a/third_party/blink/renderer/modules/speech/BUILD.gn b/third_party/blink/renderer/modules/speech/BUILD.gn index f62b927..60a38d5 100644 --- a/third_party/blink/renderer/modules/speech/BUILD.gn +++ b/third_party/blink/renderer/modules/speech/BUILD.gn
@@ -15,8 +15,6 @@ "speech_recognition.h", "speech_recognition_alternative.cc", "speech_recognition_alternative.h", - "speech_recognition_context.cc", - "speech_recognition_context.h", "speech_recognition_controller.cc", "speech_recognition_controller.h", "speech_recognition_error_event.cc",
diff --git a/third_party/blink/renderer/modules/speech/speech_recognition_context.cc b/third_party/blink/renderer/modules/speech/speech_recognition_context.cc deleted file mode 100644 index e3f204bd..0000000 --- a/third_party/blink/renderer/modules/speech/speech_recognition_context.cc +++ /dev/null
@@ -1,23 +0,0 @@ -// Copyright 2024 The Chromium Authors -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "third_party/blink/renderer/modules/speech/speech_recognition_context.h" - -namespace blink { - -SpeechRecognitionContext* SpeechRecognitionContext::Create( - SpeechRecognitionPhraseList* phrases) { - return MakeGarbageCollected<SpeechRecognitionContext>(phrases); -} - -SpeechRecognitionContext::SpeechRecognitionContext( - SpeechRecognitionPhraseList* phrases) - : phrases_(phrases) {} - -void SpeechRecognitionContext::Trace(Visitor* visitor) const { - visitor->Trace(phrases_); - ScriptWrappable::Trace(visitor); -} - -} // namespace blink
diff --git a/third_party/blink/renderer/modules/speech/speech_recognition_context.h b/third_party/blink/renderer/modules/speech/speech_recognition_context.h deleted file mode 100644 index 6c5558e..0000000 --- a/third_party/blink/renderer/modules/speech/speech_recognition_context.h +++ /dev/null
@@ -1,37 +0,0 @@ -// Copyright 2024 The Chromium Authors -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef THIRD_PARTY_BLINK_RENDERER_MODULES_SPEECH_SPEECH_RECOGNITION_CONTEXT_H_ -#define THIRD_PARTY_BLINK_RENDERER_MODULES_SPEECH_SPEECH_RECOGNITION_CONTEXT_H_ - -#include "third_party/blink/renderer/modules/modules_export.h" -#include "third_party/blink/renderer/modules/speech/speech_recognition_phrase_list.h" -#include "third_party/blink/renderer/platform/bindings/script_wrappable.h" -#include "third_party/blink/renderer/platform/heap/member.h" - -namespace blink { - -class MODULES_EXPORT SpeechRecognitionContext final : public ScriptWrappable { - DEFINE_WRAPPERTYPEINFO(); - - public: - static SpeechRecognitionContext* Create(SpeechRecognitionPhraseList* phrases); - - explicit SpeechRecognitionContext(SpeechRecognitionPhraseList* phrases); - ~SpeechRecognitionContext() override = default; - - // ScriptWrappable: - void Trace(Visitor* visitor) const override; - - // SpeechRecognitionContext: - SpeechRecognitionPhraseList* phrases() const { return phrases_.Get(); } - void setPhrases(SpeechRecognitionPhraseList* phrases) { phrases_ = phrases; } - - private: - Member<SpeechRecognitionPhraseList> phrases_; -}; - -} // namespace blink - -#endif // THIRD_PARTY_BLINK_RENDERER_MODULES_SPEECH_SPEECH_RECOGNITION_CONTEXT_H_
diff --git a/third_party/blink/renderer/modules/speech/speech_recognition_context.idl b/third_party/blink/renderer/modules/speech/speech_recognition_context.idl deleted file mode 100644 index 9271b67f..0000000 --- a/third_party/blink/renderer/modules/speech/speech_recognition_context.idl +++ /dev/null
@@ -1,13 +0,0 @@ -// Copyright 2024 The Chromium Authors -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -[ - Exposed=Window, - RuntimeEnabled=WebSpeechRecognitionContext, - SecureContext -] -interface SpeechRecognitionContext { - constructor(SpeechRecognitionPhraseList phrases); - attribute SpeechRecognitionPhraseList phrases; -};
diff --git a/third_party/blink/renderer/modules/webgl/webgl_rendering_context_base.cc b/third_party/blink/renderer/modules/webgl/webgl_rendering_context_base.cc index 555e80c6..5d72bbe 100644 --- a/third_party/blink/renderer/modules/webgl/webgl_rendering_context_base.cc +++ b/third_party/blink/renderer/modules/webgl/webgl_rendering_context_base.cc
@@ -838,13 +838,6 @@ gfx::Size(width, height)); } -void WebGLRenderingContextBase::commit() { - if (!GetDrawingBuffer() || (Host() && Host()->IsOffscreenCanvas())) - return; - - PushFrameWithCopy(/*for_commit_api=*/true); -} - scoped_refptr<StaticBitmapImage> WebGLRenderingContextBase::GetImage( FlushReason reason) { if (!GetDrawingBuffer()) @@ -1658,11 +1651,8 @@ if (!must_paint_to_canvas_ && !must_clear_now) return false; - if (!Host()->LowLatencyEnabled() && - GetDrawingBuffer()->IsUsingGpuCompositing()) { - // If LowLatency is not enabled, and it's using Gpu Compositing, it will try - // to export the mailbox, synctoken and callback mechanism for the - // compositor to present the frame in the offscrencanvas. + if (GetDrawingBuffer()->IsUsingGpuCompositing()) { + // Export the DrawingBuffer's SI directly if possible. if (PushFrameNoCopy()) return true; } @@ -1688,37 +1678,21 @@ CanvasRenderingContext::Dispose(); } -bool WebGLRenderingContextBase::PushFrameWithCopy( - bool for_commit_api /*=false*/) { +bool WebGLRenderingContextBase::PushFrameWithCopy() { bool submitted_frame = false; - // Note: we push a frame only if (a) there is fresh content to produce and (b) - // we successfully produced that content. - scoped_refptr<CanvasResource> resource = nullptr; - bool produced_frame = false; - - if (CanUseDrawingBufferSIWithoutCopyForLowLatency()) { - resource = ExportLowLatencyCanvasResource(kBackBuffer, - /*export_only_if_update=*/true); - produced_frame = !!resource; - } else { - bool resource_provider_was_updated = false; - auto* resource_provider = PaintRenderingResultsToCanvas( - kBackBuffer, &resource_provider_was_updated); - produced_frame = resource_provider && resource_provider_was_updated; - if (produced_frame) { - resource = - resource_provider->ProduceCanvasResource(FlushReason::kNon2DCanvas); - } - } - - if (produced_frame) { + // Note: we push a frame only if (a) there is fresh content to produce and + // (b) we successfully produced that content. + bool resource_provider_was_updated = false; + auto* resource_provider = PaintRenderingResultsToResourceProvider( + kBackBuffer, &resource_provider_was_updated); + if (resource_provider && resource_provider_was_updated) { const int width = GetDrawingBuffer()->Size().width(); const int height = GetDrawingBuffer()->Size().height(); auto size = SkIRect::MakeWH(width, height); - submitted_frame = for_commit_api - ? Host()->Commit(std::move(resource), size) - : Host()->PushFrame(std::move(resource), size); + submitted_frame = Host()->PushFrame( + resource_provider->ProduceCanvasResource(FlushReason::kNon2DCanvas), + size); } MarkLayerComposited(); return submitted_frame; @@ -1986,7 +1960,7 @@ } CanvasResourceProvider* provider = - PaintRenderingResultsToCanvas(source_buffer); + PaintRenderingResultsToResourceProvider(source_buffer); return provider ? provider->Snapshot(reason) : nullptr; } @@ -2002,12 +1976,10 @@ /*export_only_if_update=*/false); } - if (was_dirty) { - GetOrCreateCanvasResourceProvider(); - } - PaintRenderingResultsToCanvas(source_buffer); - if (has_dispatcher && was_dirty && GetOrCreateCanvasResourceProvider()) { - return resource_provider_.get()->ProduceCanvasResource(reason); + auto* resource_provider = + PaintRenderingResultsToResourceProvider(source_buffer); + if (has_dispatcher && was_dirty && resource_provider) { + return resource_provider->ProduceCanvasResource(reason); } return nullptr; } @@ -2098,13 +2070,14 @@ } CanvasResourceProvider* -WebGLRenderingContextBase::PaintRenderingResultsToCanvas( +WebGLRenderingContextBase::PaintRenderingResultsToResourceProvider( SourceDrawingBuffer source_buffer, bool* resource_provider_was_updated /*=nullptr*/) { CHECK(!CanUseDrawingBufferSIWithoutCopyForLowLatency()); - TRACE_EVENT0("blink", - "WebGLRenderingContextBase::PaintRenderingResultsToCanvas"); + TRACE_EVENT0( + "blink", + "WebGLRenderingContextBase::PaintRenderingResultsToResourceProvider"); if (resource_provider_was_updated != nullptr) { *resource_provider_was_updated = false; }
diff --git a/third_party/blink/renderer/modules/webgl/webgl_rendering_context_base.h b/third_party/blink/renderer/modules/webgl/webgl_rendering_context_base.h index d8394c3b..6fb8671 100644 --- a/third_party/blink/renderer/modules/webgl/webgl_rendering_context_base.h +++ b/third_party/blink/renderer/modules/webgl/webgl_rendering_context_base.h
@@ -639,8 +639,6 @@ void drawingBufferStorage(GLenum sizedformat, GLsizei width, GLsizei height); - void commit(); - ScriptPromise<IDLUndefined> makeXRCompatible(ScriptState*, ExceptionState&); bool IsXRCompatible() const; @@ -1967,7 +1965,7 @@ bool export_only_if_update); CanvasResourceProvider* GetOrCreateCanvasResourceProvider(); - CanvasResourceProvider* PaintRenderingResultsToCanvas( + CanvasResourceProvider* PaintRenderingResultsToResourceProvider( SourceDrawingBuffer source_buffer, bool* resource_provider_was_updated = nullptr); void TexImageHelperMediaVideoFrame( @@ -2005,7 +2003,7 @@ // PushFrameWithCopy will make a potential copy if the resource is accelerated // or a drawImage if the resource is non accelerated. - bool PushFrameWithCopy(bool for_commit_api = false); + bool PushFrameWithCopy(); // PushFrameNoCopy will try and export the content of the DrawingBuffer as a // ExtenralCanvasResource. bool PushFrameNoCopy();
diff --git a/third_party/blink/renderer/modules/webgl/webgl_rendering_context_base.idl b/third_party/blink/renderer/modules/webgl/webgl_rendering_context_base.idl index 6020152..ac5b0163 100644 --- a/third_party/blink/renderer/modules/webgl/webgl_rendering_context_base.idl +++ b/third_party/blink/renderer/modules/webgl/webgl_rendering_context_base.idl
@@ -717,8 +717,6 @@ [RuntimeEnabled=WebGLDrawingBufferStorage] void drawingBufferStorage(GLenum sizedformat, GLsizei width, GLsizei height); - [RuntimeEnabled=OffscreenCanvasCommit] void commit(); - // WebXR Device API support [RuntimeEnabled=WebXR, SecureContext, CallWith=ScriptState, RaisesException, HighEntropy, MeasureAs=WebGLRenderingContextMakeXRCompatible] Promise<undefined> makeXRCompatible(); };
diff --git a/third_party/blink/renderer/platform/graphics/canvas_resource_dispatcher.cc b/third_party/blink/renderer/platform/graphics/canvas_resource_dispatcher.cc index dfdba91..27f9bf1 100644 --- a/third_party/blink/renderer/platform/graphics/canvas_resource_dispatcher.cc +++ b/third_party/blink/renderer/platform/graphics/canvas_resource_dispatcher.cc
@@ -190,25 +190,6 @@ resource_id)); } -void CanvasResourceDispatcher::DispatchFrameSync( - scoped_refptr<CanvasResource>&& canvas_resource, - const SkIRect& damage_rect, - bool is_opaque) { - TRACE_EVENT0("blink", "CanvasResourceDispatcher::DispatchFrameSync"); - viz::CompositorFrame frame; - if (!PrepareFrame(std::move(canvas_resource), damage_rect, is_opaque, - &frame)) { - return; - } - - pending_compositor_frames_++; - WTF::Vector<viz::ReturnedResource> resources; - sink_->SubmitCompositorFrameSync( - parent_local_surface_id_allocator_.GetCurrentLocalSurfaceId(), - std::move(frame), std::nullopt, 0, &resources); - DidReceiveCompositorFrameAck(std::move(resources)); -} - void CanvasResourceDispatcher::DispatchFrame( scoped_refptr<CanvasResource>&& canvas_resource, const SkIRect& damage_rect,
diff --git a/third_party/blink/renderer/platform/graphics/canvas_resource_dispatcher.h b/third_party/blink/renderer/platform/graphics/canvas_resource_dispatcher.h index 5b89eb4..5e66eac 100644 --- a/third_party/blink/renderer/platform/graphics/canvas_resource_dispatcher.h +++ b/third_party/blink/renderer/platform/graphics/canvas_resource_dispatcher.h
@@ -91,9 +91,6 @@ bool is_opaque); // virtual for mocking virtual void OnMainThreadReceivedImage(); - void DispatchFrameSync(scoped_refptr<CanvasResource>&&, - const SkIRect& damage_rect, - bool is_opaque); void ReplaceBeginFrameAck(const viz::BeginFrameArgs& args) { current_begin_frame_ack_ = viz::BeginFrameAck(args, true); }
diff --git a/third_party/blink/renderer/platform/graphics/canvas_resource_host.h b/third_party/blink/renderer/platform/graphics/canvas_resource_host.h index 1afb78765..4044b3d 100644 --- a/third_party/blink/renderer/platform/graphics/canvas_resource_host.h +++ b/third_party/blink/renderer/platform/graphics/canvas_resource_host.h
@@ -21,11 +21,7 @@ virtual void NotifyGpuContextLost() = 0; virtual void InitializeForRecording(cc::PaintCanvas* canvas) const = 0; - virtual bool IsPrinting() const { return false; } - virtual bool IsHibernating() const { return false; } - - virtual void SetTransferToGPUTextureWasInvoked() {} virtual bool TransferToGPUTextureWasInvoked() { return false; } };
diff --git a/third_party/blink/renderer/platform/graphics/flush_reason.h b/third_party/blink/renderer/platform/graphics/flush_reason.h index 68714d0..7074a21e 100644 --- a/third_party/blink/renderer/platform/graphics/flush_reason.h +++ b/third_party/blink/renderer/platform/graphics/flush_reason.h
@@ -32,8 +32,7 @@ // Should not happen while printing. kHibernating = 4, - // `OffscreenCanvas::commit` was called. - // Should not happen while printing. + // Obsolete. kOffscreenCanvasCommit_OBSOLETE = 5, // `OffscreenCanvas` dispatched a frame to the compositor as part of the
diff --git a/third_party/blink/renderer/platform/graphics/image_decoding_store.h b/third_party/blink/renderer/platform/graphics/image_decoding_store.h index d40b830..7a0e10b 100644 --- a/third_party/blink/renderer/platform/graphics/image_decoding_store.h +++ b/third_party/blink/renderer/platform/graphics/image_decoding_store.h
@@ -79,7 +79,7 @@ // Base class for all cache entries. class CacheEntry : public DoublyLinkedListNode<CacheEntry> { USING_FAST_MALLOC(CacheEntry); - friend class WTF::DoublyLinkedListNode<CacheEntry>; + friend class DoublyLinkedListNode<CacheEntry>; public: enum CacheType {
diff --git a/third_party/blink/renderer/platform/p2p/ipc_socket_factory_test.cc b/third_party/blink/renderer/platform/p2p/ipc_socket_factory_test.cc index b4b33fa..3036b89 100644 --- a/third_party/blink/renderer/platform/p2p/ipc_socket_factory_test.cc +++ b/third_party/blink/renderer/platform/p2p/ipc_socket_factory_test.cc
@@ -11,7 +11,7 @@ #include "third_party/blink/renderer/platform/p2p/socket_dispatcher.h" #include "third_party/blink/renderer/platform/testing/fake_mojo_binding_context.h" #include "third_party/blink/renderer/platform/wtf/cross_thread_functional.h" -#include "third_party/webrtc/rtc_base/network/ecn_marking.h" +#include "third_party/webrtc/api/transport/ecn_marking.h" namespace blink {
diff --git a/third_party/blink/renderer/platform/runtime_enabled_features.json5 b/third_party/blink/renderer/platform/runtime_enabled_features.json5 index 03d98064..29489a8f 100644 --- a/third_party/blink/renderer/platform/runtime_enabled_features.json5 +++ b/third_party/blink/renderer/platform/runtime_enabled_features.json5
@@ -3296,10 +3296,6 @@ status: "stable", }, { - name: "OffscreenCanvasCommit", - status: "experimental", - }, - { name: "OffscreenCanvasGetContextAttributes", status: "stable", },
diff --git a/third_party/blink/renderer/platform/wtf/doubly_linked_list.h b/third_party/blink/renderer/platform/wtf/doubly_linked_list.h index 38d8022..248e0209 100644 --- a/third_party/blink/renderer/platform/wtf/doubly_linked_list.h +++ b/third_party/blink/renderer/platform/wtf/doubly_linked_list.h
@@ -31,7 +31,7 @@ #include "third_party/blink/renderer/platform/wtf/type_traits.h" #include "third_party/blink/renderer/platform/wtf/wtf_size_t.h" -namespace WTF { +namespace blink { // This class allows nodes to share code without dictating data member layout. template <typename T> @@ -131,7 +131,7 @@ private: struct TypeConstraints { constexpr TypeConstraints() { - static_assert(!IsStackAllocatedTypeV<T>); + static_assert(!WTF::IsStackAllocatedTypeV<T>); static_assert( !IsGarbageCollectedType<T>::value || !std::is_same<PointerType, T*>::value, @@ -298,9 +298,6 @@ return {node, true}; } -} // namespace WTF - -using WTF::DoublyLinkedListNode; -using WTF::DoublyLinkedList; +} // namespace blink #endif // THIRD_PARTY_BLINK_RENDERER_PLATFORM_WTF_DOUBLY_LINKED_LIST_H_
diff --git a/third_party/blink/renderer/platform/wtf/doubly_linked_list_test.cc b/third_party/blink/renderer/platform/wtf/doubly_linked_list_test.cc index 7f51b74..61a544c 100644 --- a/third_party/blink/renderer/platform/wtf/doubly_linked_list_test.cc +++ b/third_party/blink/renderer/platform/wtf/doubly_linked_list_test.cc
@@ -8,7 +8,7 @@ #include "third_party/blink/renderer/platform/wtf/functional.h" #include "third_party/blink/renderer/platform/wtf/wtf_test_helper.h" -namespace WTF { +namespace blink { namespace { @@ -16,7 +16,7 @@ class TestNode final : public DoublyLinkedListNode<TestNode> { USING_FAST_MALLOC(TestNode); - friend class WTF::DoublyLinkedListNode<TestNode>; + friend class DoublyLinkedListNode<TestNode>; public: TestNode(int i) : i_(i) { ++test_node_counter; } @@ -220,4 +220,4 @@ } // anonymous namespace -} // namespace WTF +} // namespace blink
diff --git a/third_party/blink/renderer/platform/wtf/text/text_codec_icu.cc b/third_party/blink/renderer/platform/wtf/text/text_codec_icu.cc index 629cd58..6578500 100644 --- a/third_party/blink/renderer/platform/wtf/text/text_codec_icu.cc +++ b/third_party/blink/renderer/platform/wtf/text/text_codec_icu.cc
@@ -24,21 +24,19 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifdef UNSAFE_BUFFERS_BUILD -// TODO(crbug.com/351564777): Remove this and convert code to safer constructs. -#pragma allow_unsafe_buffers -#endif - #include "third_party/blink/renderer/platform/wtf/text/text_codec_icu.h" -#include <memory> - #include <unicode/ucnv.h> #include <unicode/ucnv_cb.h> +#include <memory> + +#include "base/compiler_specific.h" +#include "base/containers/span.h" #include "base/feature_list.h" #include "base/memory/ptr_util.h" #include "base/notreached.h" +#include "base/types/to_address.h" #include "third_party/blink/public/common/features.h" #include "third_party/blink/renderer/platform/wtf/text/character_names.h" #include "third_party/blink/renderer/platform/wtf/text/string_builder.h" @@ -64,17 +62,14 @@ } namespace { -bool IncludeAlias(const char* alias) { +bool IncludeAlias(std::string_view alias) { #if !defined(USING_SYSTEM_ICU) // Chromium's build of ICU includes *-html aliases to manage the encoding // labels defined in the Encoding Standard, but these must not be // web-exposed. - const char* kSuffix = "-html"; - const size_t kSuffixLength = 5; - size_t alias_length = strlen(alias); - if ((alias_length >= kSuffixLength) && - !strcmp(alias + alias_length - kSuffixLength, kSuffix)) + if (alias.ends_with("-html")) { return false; + } #endif return true; } @@ -348,18 +343,22 @@ ucnv_setFallback(converter_icu_, true); } -int TextCodecIcu::DecodeToBuffer(UChar* target, - UChar* target_limit, - const char*& source, - const char* source_limit, - int32_t* offsets, - bool flush, - UErrorCode& err) { - UChar* target_start = target; +size_t TextCodecIcu::DecodeToBuffer(base::span<UChar> target, + base::span<const char>& source, + bool flush, + UErrorCode& err) { + auto* source_ptr = source.data(); + auto* target_ptr = target.data(); err = U_ZERO_ERROR; - ucnv_toUnicode(converter_icu_, &target, target_limit, &source, source_limit, - offsets, flush, &err); - return static_cast<int>(target - target_start); + // SAFETY: unsafe function call to c function ucnv_toUnicode, + // it's safe when `ucnv_toUnicode` stays in the span. + UNSAFE_BUFFERS({ + ucnv_toUnicode(converter_icu_, &target_ptr, base::to_address(target.end()), + &source_ptr, base::to_address(source.end()), nullptr, flush, + &err); + }); + source = source.subspan(static_cast<size_t>(source_ptr - source.data())); + return static_cast<size_t>(target_ptr - target.data()); } class ErrorCallbackSetter final { @@ -415,27 +414,22 @@ StringBuilder result; UChar buffer[kConversionBufferSize]; - UChar* buffer_limit = buffer + kConversionBufferSize; - const char* source = reinterpret_cast<const char*>(data.data()); - const char* source_limit = source + data.size(); - int32_t* offsets = nullptr; + auto buffer_span = base::span(buffer); + auto source_span = base::as_chars(data); UErrorCode err = U_ZERO_ERROR; do { - int uchars_decoded = - DecodeToBuffer(buffer, buffer_limit, source, source_limit, offsets, - flush != FlushBehavior::kDoNotFlush, err); - result.Append( - base::span(buffer).first(static_cast<size_t>(uchars_decoded))); + size_t uchars_decoded = DecodeToBuffer( + buffer_span, source_span, flush != FlushBehavior::kDoNotFlush, err); + result.Append(buffer_span.first(uchars_decoded)); } while (err == U_BUFFER_OVERFLOW_ERROR); if (U_FAILURE(err)) { // flush the converter so it can be reused, and not be bothered by this // error. do { - DecodeToBuffer(buffer, buffer_limit, source, source_limit, offsets, true, - err); - } while (source < source_limit); + DecodeToBuffer(buffer_span, source_span, /*flush=*/true, err); + } while (!source_span.empty()); saw_error = true; } @@ -491,7 +485,8 @@ String entity_u(TextCodec::GetUnencodableReplacement(code_point, handling)); entity_u.Ensure16Bit(); const UChar* entity_u_pointers[2] = { - entity_u.Characters16(), entity_u.Characters16() + entity_u.length(), + entity_u.Span16().data(), + base::to_address(entity_u.Span16().end()), }; ucnv_cbFromUWriteUChars(from_u_args, entity_u_pointers, entity_u_pointers[1], 0, err); @@ -646,32 +641,25 @@ public: TextCodecInput(const TextEncoding& encoding, base::span<const UChar> characters) - : begin_(characters.data()), - end_(characters.data() + characters.size()) {} + : buffer_span_(characters) {} TextCodecInput(const TextEncoding& encoding, base::span<const LChar> characters) { buffer_.ReserveInitialCapacity( base::checked_cast<wtf_size_t>(characters.size())); buffer_.AppendSpan(characters); - begin_ = buffer_.data(); - end_ = begin_ + buffer_.size(); + buffer_span_ = base::span(buffer_); } - const UChar* begin() const { return begin_; } - const UChar* end() const { return end_; } + base::span<const UChar> Span() const { return buffer_span_; } private: - const UChar* begin_; - const UChar* end_; + base::span<const UChar> buffer_span_; Vector<UChar> buffer_; }; std::string TextCodecIcu::EncodeInternal(const TextCodecInput& input, UnencodableHandling handling) { - const UChar* source = input.begin(); - const UChar* end = input.end(); - UErrorCode err = U_ZERO_ERROR; switch (handling) { @@ -721,22 +709,21 @@ if (U_FAILURE(err)) return std::string(); + const UChar* source = input.Span().data(); + const UChar* end = base::to_address(input.Span().end()); Vector<char> result; - wtf_size_t size = 0; do { - char buffer[kConversionBufferSize]; - char* target = buffer; - char* target_limit = target + kConversionBufferSize; + std::array<char, kConversionBufferSize> buffer; + char* target = buffer.data(); + char* target_limit = base::to_address(buffer.end()); err = U_ZERO_ERROR; ucnv_fromUnicode(converter_icu_, &target, target_limit, &source, end, nullptr, true, &err); - wtf_size_t count = static_cast<wtf_size_t>(target - buffer); - result.Grow(size + count); - memcpy(result.data() + size, buffer, count); - size += count; + wtf_size_t count = static_cast<wtf_size_t>(target - buffer.data()); + result.AppendSpan(base::span(buffer).first(count)); } while (err == U_BUFFER_OVERFLOW_ERROR); - return std::string(result.data(), size); + return std::string(result.data(), result.size()); } template <typename CharType>
diff --git a/third_party/blink/renderer/platform/wtf/text/text_codec_icu.h b/third_party/blink/renderer/platform/wtf/text/text_codec_icu.h index af1fd9a..809cfd2 100644 --- a/third_party/blink/renderer/platform/wtf/text/text_codec_icu.h +++ b/third_party/blink/renderer/platform/wtf/text/text_codec_icu.h
@@ -64,13 +64,10 @@ void CreateIcuConverter() const; void ReleaseIcuConverter() const; - int DecodeToBuffer(UChar* buffer, - UChar* buffer_limit, - const char*& source, - const char* source_limit, - int32_t* offsets, - bool flush, - UErrorCode&); + size_t DecodeToBuffer(base::span<UChar> target, + base::span<const char>& source, + bool flush, + UErrorCode&); TextEncoding encoding_; mutable UConverter* converter_icu_ = nullptr;
diff --git a/third_party/blink/renderer/platform/wtf/text/text_codec_latin1.cc b/third_party/blink/renderer/platform/wtf/text/text_codec_latin1.cc index 82569e66..5e1ea03 100644 --- a/third_party/blink/renderer/platform/wtf/text/text_codec_latin1.cc +++ b/third_party/blink/renderer/platform/wtf/text/text_codec_latin1.cc
@@ -23,23 +23,21 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifdef UNSAFE_BUFFERS_BUILD -// TODO(crbug.com/351564777): Remove this and convert code to safer constructs. -#pragma allow_unsafe_buffers -#endif - #include "third_party/blink/renderer/platform/wtf/text/text_codec_latin1.h" #include <unicode/utf16.h> + #include <memory> +#include "base/types/to_address.h" +#include "third_party/blink/renderer/platform/wtf/text/ascii_fast_path.h" #include "third_party/blink/renderer/platform/wtf/text/string_buffer.h" #include "third_party/blink/renderer/platform/wtf/text/text_codec_ascii_fast_path.h" #include "third_party/blink/renderer/platform/wtf/text/wtf_string.h" namespace blink { -static const UChar kTable[256] = { +static constexpr std::array<UChar, 256> kTable = { 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, // 00-07 0x0008, 0x0009, 0x000A, 0x000B, 0x000C, 0x000D, 0x000E, 0x000F, // 08-0F 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, // 10-17 @@ -120,41 +118,43 @@ String result = String::CreateUninitialized( base::checked_cast<wtf_size_t>(bytes.size()), characters); - const uint8_t* source = bytes.data(); - const uint8_t* end = source + bytes.size(); - const uint8_t* aligned_end = AlignToMachineWord(end); - LChar* destination = characters.data(); + base::span<const uint8_t> source = bytes; + const uint8_t* aligned_end = + AlignToMachineWord(base::to_address(source.end())); + base::span<LChar> destination = characters; - while (source < end) { - if (IsASCII(*source)) { + while (!source.empty()) { + if (IsASCII(source[0])) { // Fast path for ASCII. Most Latin-1 text will be ASCII. - if (IsAlignedToMachineWord(source)) { - while (source < aligned_end) { - MachineWord chunk = *reinterpret_cast_ptr<const MachineWord*>(source); + if (IsAlignedToMachineWord(source.data())) { + while (source.data() < aligned_end) { + MachineWord chunk = + *reinterpret_cast_ptr<const MachineWord*>(source.data()); if (!IsAllAscii<LChar>(chunk)) { goto useLookupTable; } - CopyAsciiMachineWord(destination, source); - source += sizeof(MachineWord); - destination += sizeof(MachineWord); + static constexpr size_t kMachineWordSize = sizeof(MachineWord); + CopyAsciiMachineWord( + destination.take_first<kMachineWordSize>().data(), + source.take_first<kMachineWordSize>().data()); } - if (source == end) + if (source.empty()) { break; + } } - *destination = *source; + destination.take_first<1u>()[0] = source.take_first_elem(); } else { useLookupTable: - if (kTable[*source] > 0xff) + if (kTable[source[0]] > 0xff) { goto upConvertTo16Bit; + } - *destination = static_cast<LChar>(kTable[*source]); + destination.take_first<1u>()[0] = + static_cast<LChar>(kTable[source.take_first_elem()]); } - - ++source; - ++destination; } return result; @@ -164,47 +164,47 @@ String result16 = String::CreateUninitialized( base::checked_cast<wtf_size_t>(bytes.size()), characters16); - UChar* destination16 = characters16.data(); - // Zero extend and copy already processed 8 bit data - LChar* ptr8 = characters.data(); - LChar* end_ptr8 = destination; + const size_t characters_converted = + static_cast<size_t>(destination.data() - characters.data()); + auto source8_span = characters.first(characters_converted); + auto [converted_span, destination16] = + characters16.split_at(characters_converted); - while (ptr8 < end_ptr8) - *destination16++ = *ptr8++; + for (size_t i = 0; i < characters_converted; ++i) { + converted_span[i] = source8_span[i]; + } // Handle the character that triggered the 16 bit path - *destination16 = kTable[*source]; - ++source; - ++destination16; + destination16.take_first<1u>()[0] = kTable[source.take_first_elem()]; - while (source < end) { - if (IsASCII(*source)) { + while (!source.empty()) { + if (IsASCII(source[0])) { // Fast path for ASCII. Most Latin-1 text will be ASCII. - if (IsAlignedToMachineWord(source)) { - while (source < aligned_end) { - MachineWord chunk = *reinterpret_cast_ptr<const MachineWord*>(source); + if (IsAlignedToMachineWord(source.data())) { + while (source.data() < aligned_end) { + MachineWord chunk = + *reinterpret_cast_ptr<const MachineWord*>(source.data()); if (!IsAllAscii<LChar>(chunk)) { goto useLookupTable16; } - CopyAsciiMachineWord(destination16, source); - source += sizeof(MachineWord); - destination16 += sizeof(MachineWord); + static constexpr size_t kMachineWordSize = sizeof(MachineWord); + CopyAsciiMachineWord( + destination16.take_first<kMachineWordSize>().data(), + source.take_first<kMachineWordSize>().data()); } - if (source == end) + if (source.empty()) { break; + } } - *destination16 = *source; + destination16.take_first<1u>()[0] = source.take_first_elem(); } else { useLookupTable16: - *destination16 = kTable[*source]; + destination16.take_first<1u>()[0] = kTable[source.take_first_elem()]; } - - ++source; - ++destination16; } return result16; @@ -215,7 +215,6 @@ base::span<const CharType> char_data, UnencodableHandling handling) { DCHECK_NE(handling, UnencodableHandling::kNoUnencodables); - const auto* characters = char_data.data(); const wtf_size_t length = base::checked_cast<wtf_size_t>(char_data.size()); wtf_size_t target_length = length; std::string result; @@ -225,7 +224,7 @@ UChar32 c; // If CharType is LChar the U16_NEXT call reads a byte and increments; // since the convention is that LChar is already latin1 this is safe. - U16_NEXT(characters, i, length, c); + U16_NEXT(char_data, i, length, c); // If input was a surrogate pair (non-BMP character) then we overestimated // the length. if (c > 0xffff)
diff --git a/third_party/blink/renderer/platform/wtf/text/text_codec_user_defined.cc b/third_party/blink/renderer/platform/wtf/text/text_codec_user_defined.cc index df17888..ce8be3e 100644 --- a/third_party/blink/renderer/platform/wtf/text/text_codec_user_defined.cc +++ b/third_party/blink/renderer/platform/wtf/text/text_codec_user_defined.cc
@@ -23,11 +23,6 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifdef UNSAFE_BUFFERS_BUILD -// TODO(crbug.com/351564777): Remove this and convert code to safer constructs. -#pragma allow_unsafe_buffers -#endif - #include "third_party/blink/renderer/platform/wtf/text/text_codec_user_defined.h" #include <memory> @@ -71,7 +66,6 @@ base::span<const CharType> char_data, UnencodableHandling handling) { DCHECK_NE(handling, UnencodableHandling::kNoUnencodables); - const auto* characters = char_data.data(); const wtf_size_t length = base::checked_cast<wtf_size_t>(char_data.size()); wtf_size_t target_length = length; std::string result; @@ -80,7 +74,7 @@ for (wtf_size_t i = 0; i < length;) { UChar32 c; // TODO(jsbell): Will the input for x-user-defined ever be LChars? - U16_NEXT(characters, i, length, c); + U16_NEXT(char_data, i, length, c); // If the input was a surrogate pair (non-BMP character) then we // overestimated the length. if (c > 0xffff)
diff --git a/third_party/blink/tools/blinkpy/presubmit/audit_non_blink_usage.py b/third_party/blink/tools/blinkpy/presubmit/audit_non_blink_usage.py index 2bae53e..af83604 100755 --- a/third_party/blink/tools/blinkpy/presubmit/audit_non_blink_usage.py +++ b/third_party/blink/tools/blinkpy/presubmit/audit_non_blink_usage.py
@@ -2455,6 +2455,15 @@ }, { 'paths': [ + 'third_party/blink/renderer/core/frame/web_frame_widget_impl.cc', + ], + 'allowed': [ + # Temporarily added to generate the value of a crash key. + 'base::NumberToString', + ], + }, + { + 'paths': [ 'third_party/blink/renderer/core/origin_trials/origin_trial_context.cc', ], 'allowed': [
diff --git a/third_party/blink/web_tests/external/wpt/css/css-values/if-initial-unregistered.html b/third_party/blink/web_tests/external/wpt/css/css-values/if-initial-unregistered.html new file mode 100644 index 0000000..9ba58cc --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/css/css-values/if-initial-unregistered.html
@@ -0,0 +1,20 @@ +<!DOCTYPE html> +<title>CSS Values and Units Test: Explicitly testing for the initial value of a custom property</title> +<link rel="help" href="https://drafts.csswg.org/css-values-5/#if-notation"> +<link rel="help" href="https://issues.chromium.org/issues/428131125"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div> + PASS if green +</div> +<style> + div { + color: if(style(--x:initial): green; else: red); + } +</style> +<script> + test(() => { + let cs = getComputedStyle(document.querySelector('div')); + assert_equals(cs.getPropertyValue('color'), 'rgb(0, 128, 0)'); + }, 'if-testing the initial keyword without any property registrations'); +</script>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-viewport/zoom/border-ref.html b/third_party/blink/web_tests/external/wpt/css/css-viewport/zoom/border-ref.html new file mode 100644 index 0000000..14a1dab3 --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/css/css-viewport/zoom/border-ref.html
@@ -0,0 +1,17 @@ +<!DOCTYPE html> +<title>border:inherit applies zoom (ref)</title> +<link rel="help" href="https://drafts.csswg.org/css-viewport/"> +<style> + body > div { + border: 1px solid black; + } + body > div > div { + width: 100px; + height: 100px; + border: 10px solid black; + background: green; + } +</style> +<div> + <div></div> +</div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-viewport/zoom/border.html b/third_party/blink/web_tests/external/wpt/css/css-viewport/zoom/border.html new file mode 100644 index 0000000..89d24f1 --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/css/css-viewport/zoom/border.html
@@ -0,0 +1,20 @@ +<!DOCTYPE html> +<title>border:inherit applies zoom</title> +<link rel="help" href="https://drafts.csswg.org/css-viewport/"> +<link rel="match" href="border-ref.html"> +<style> + body > div { + border: 1px solid black; + } + body > div > div { + zoom: 10; + width: 10px; + height: 10px; + border: 0px solid black; + border-width: inherit; + background: green; + } +</style> +<div> + <div></div> +</div>
diff --git a/third_party/blink/web_tests/http/tests/serviceworker/webexposed/global-interface-listing-service-worker-expected.txt b/third_party/blink/web_tests/http/tests/serviceworker/webexposed/global-interface-listing-service-worker-expected.txt index 680c26c..016570d 100644 --- a/third_party/blink/web_tests/http/tests/serviceworker/webexposed/global-interface-listing-service-worker-expected.txt +++ b/third_party/blink/web_tests/http/tests/serviceworker/webexposed/global-interface-listing-service-worker-expected.txt
@@ -2727,7 +2727,6 @@ method clearStencil method clientWaitSync method colorMask - method commit method compileShader method compressedTexImage2D method compressedTexImage3D @@ -3280,7 +3279,6 @@ method clearDepth method clearStencil method colorMask - method commit method compileShader method compressedTexImage2D method compressedTexSubImage2D
diff --git a/third_party/blink/web_tests/inspector-protocol/dom/dom-performSearch-expected.txt b/third_party/blink/web_tests/inspector-protocol/dom/dom-performSearch-expected.txt new file mode 100644 index 0000000..d9f850e --- /dev/null +++ b/third_party/blink/web_tests/inspector-protocol/dom/dom-performSearch-expected.txt
@@ -0,0 +1,123 @@ +Tests DOM.performSearch command. +Nodes: +{ + id : <number> + result : { + node : { + attributes : [ + [0] : class + [1] : find-me + [2] : id + [3] : in-main-frame + ] + backendNodeId : <number> + childNodeCount : 0 + localName : div + nodeId : <number> + nodeName : DIV + nodeType : 1 + nodeValue : + } + } + sessionId : <string> +} +{ + id : <number> + result : { + node : { + attributes : [ + [0] : class + [1] : find-me + [2] : id + [3] : in-shadow-dom-in-main-frame + ] + backendNodeId : <number> + childNodeCount : 0 + localName : div + nodeId : <number> + nodeName : DIV + nodeType : 1 + nodeValue : + } + } + sessionId : <string> +} +{ + id : <number> + result : { + node : { + attributes : [ + [0] : class + [1] : find-me + [2] : id + [3] : in-iframe + ] + backendNodeId : <number> + childNodeCount : 0 + localName : div + nodeId : <number> + nodeName : DIV + nodeType : 1 + nodeValue : + } + } + sessionId : <string> +} +{ + id : <number> + result : { + node : { + attributes : [ + [0] : class + [1] : find-me + [2] : id + [3] : in-shadow-dom-in-iframe + ] + backendNodeId : <number> + childNodeCount : 0 + localName : div + nodeId : <number> + nodeName : DIV + nodeType : 1 + nodeValue : + } + } + sessionId : <string> +} +{ + id : <number> + result : { + node : { + attributes : [ + ] + backendNodeId : <number> + childNodeCount : 2 + frameId : <string> + localName : html + nodeId : <number> + nodeName : HTML + nodeType : 1 + nodeValue : + } + } + sessionId : <string> +} +{ + id : <number> + result : { + node : { + attributes : [ + ] + backendNodeId : <number> + childNodeCount : 2 + frameId : <string> + localName : html + nodeId : <number> + nodeName : HTML + nodeType : 1 + nodeValue : + } + } + sessionId : <string> +} +
diff --git a/third_party/blink/web_tests/inspector-protocol/dom/dom-performSearch.js b/third_party/blink/web_tests/inspector-protocol/dom/dom-performSearch.js new file mode 100644 index 0000000..3461f53 --- /dev/null +++ b/third_party/blink/web_tests/inspector-protocol/dom/dom-performSearch.js
@@ -0,0 +1,23 @@ +(async function(/** @type {import('test_runner').TestRunner} */ testRunner) { + const {dp} = await testRunner.startURL('resources/dom-perform-search.html', 'Tests DOM.performSearch command.'); + await dp.DOM.getDocument(); + + let performSearchResult = await dp.DOM.performSearch({query: '.find-me'}); + let nodesResponse = await dp.DOM.getSearchResults({searchId: performSearchResult.result.searchId, fromIndex: 0, toIndex: 4}); + + testRunner.log("Nodes:"); + + for (const nodeId of nodesResponse.result.nodeIds) { + const nodeResponse = await dp.DOM.describeNode({ nodeId }); + testRunner.log(nodeResponse); + } + + performSearchResult = await dp.DOM.performSearch({query: 'html'}); + nodesResponse = await dp.DOM.getSearchResults({searchId: performSearchResult.result.searchId, fromIndex: 0, toIndex: 2}); + + for (const nodeId of nodesResponse.result.nodeIds) { + const nodeResponse = await dp.DOM.describeNode({ nodeId }); + testRunner.log(nodeResponse); + } + testRunner.completeTest(); +})
diff --git a/third_party/blink/web_tests/inspector-protocol/dom/resources/dom-perform-search-iframe.html b/third_party/blink/web_tests/inspector-protocol/dom/resources/dom-perform-search-iframe.html new file mode 100644 index 0000000..f4fee05 --- /dev/null +++ b/third_party/blink/web_tests/inspector-protocol/dom/resources/dom-perform-search-iframe.html
@@ -0,0 +1,11 @@ +<!DOCTYPE html> +<html> + <body> + <div class="find-me" id="in-iframe"></div> + <div class="host"> + <template shadowrootmode="closed"> + <div class="find-me" id="in-shadow-dom-in-iframe"></div> + </template> + </div> + </body> +</html>
diff --git a/third_party/blink/web_tests/inspector-protocol/dom/resources/dom-perform-search.html b/third_party/blink/web_tests/inspector-protocol/dom/resources/dom-perform-search.html new file mode 100644 index 0000000..a670850 --- /dev/null +++ b/third_party/blink/web_tests/inspector-protocol/dom/resources/dom-perform-search.html
@@ -0,0 +1,12 @@ +<!DOCTYPE html> +<html> + <body> + <div class="find-me" id="in-main-frame"></div> + <iframe src="dom-perform-search-iframe.html"></iframe> + <div class="host"> + <template shadowrootmode="open"> + <div class="find-me" id="in-shadow-dom-in-main-frame"></div> + </template> + </div> + </body> +</html>
diff --git a/third_party/blink/web_tests/webexposed/global-interface-listing-dedicated-worker-expected.txt b/third_party/blink/web_tests/webexposed/global-interface-listing-dedicated-worker-expected.txt index 916186a1..23b4a88e 100644 --- a/third_party/blink/web_tests/webexposed/global-interface-listing-dedicated-worker-expected.txt +++ b/third_party/blink/web_tests/webexposed/global-interface-listing-dedicated-worker-expected.txt
@@ -3105,7 +3105,6 @@ [Worker] method clearStencil [Worker] method clientWaitSync [Worker] method colorMask -[Worker] method commit [Worker] method compileShader [Worker] method compressedTexImage2D [Worker] method compressedTexImage3D @@ -3658,7 +3657,6 @@ [Worker] method clearDepth [Worker] method clearStencil [Worker] method colorMask -[Worker] method commit [Worker] method compileShader [Worker] method compressedTexImage2D [Worker] method compressedTexSubImage2D
diff --git a/third_party/blink/web_tests/webexposed/global-interface-listing-expected.txt b/third_party/blink/web_tests/webexposed/global-interface-listing-expected.txt index b7af048..0836e314 100644 --- a/third_party/blink/web_tests/webexposed/global-interface-listing-expected.txt +++ b/third_party/blink/web_tests/webexposed/global-interface-listing-expected.txt
@@ -9813,11 +9813,6 @@ setter onstart setter phrases setter processLocally -interface SpeechRecognitionContext - attribute @@toStringTag - getter phrases - method constructor - setter phrases interface SpeechRecognitionErrorEvent : Event attribute @@toStringTag getter error @@ -11386,7 +11381,6 @@ method clearStencil method clientWaitSync method colorMask - method commit method compileShader method compressedTexImage2D method compressedTexImage3D @@ -11939,7 +11933,6 @@ method clearDepth method clearStencil method colorMask - method commit method compileShader method compressedTexImage2D method compressedTexSubImage2D
diff --git a/third_party/blink/web_tests/webexposed/global-interface-listing-shared-worker-expected.txt b/third_party/blink/web_tests/webexposed/global-interface-listing-shared-worker-expected.txt index 63e7586..0b2cc2e 100644 --- a/third_party/blink/web_tests/webexposed/global-interface-listing-shared-worker-expected.txt +++ b/third_party/blink/web_tests/webexposed/global-interface-listing-shared-worker-expected.txt
@@ -2631,7 +2631,6 @@ [Worker] method clearStencil [Worker] method clientWaitSync [Worker] method colorMask -[Worker] method commit [Worker] method compileShader [Worker] method compressedTexImage2D [Worker] method compressedTexImage3D @@ -3184,7 +3183,6 @@ [Worker] method clearDepth [Worker] method clearStencil [Worker] method colorMask -[Worker] method commit [Worker] method compileShader [Worker] method compressedTexImage2D [Worker] method compressedTexSubImage2D
diff --git a/third_party/catapult b/third_party/catapult index ca6134f..92a0760 160000 --- a/third_party/catapult +++ b/third_party/catapult
@@ -1 +1 @@ -Subproject commit ca6134f08323907d635905a7cc21abd4b1476135 +Subproject commit 92a076097780315c2a9551cdc1bd4389b3f4cce5
diff --git a/third_party/devtools-frontend/src b/third_party/devtools-frontend/src index 86e8e6c..df0eb0a 160000 --- a/third_party/devtools-frontend/src +++ b/third_party/devtools-frontend/src
@@ -1 +1 @@ -Subproject commit 86e8e6cd176f746ceecfa8f9729d742adf57417e +Subproject commit df0eb0a6577bd189ee0eb952f35339cc6c09e76f
diff --git a/third_party/freetype/README.chromium b/third_party/freetype/README.chromium index 86d3f387..4bbdcec6 100644 --- a/third_party/freetype/README.chromium +++ b/third_party/freetype/README.chromium
@@ -1,7 +1,7 @@ Name: FreeType URL: http://www.freetype.org/ -Version: VER-2-13-3-274-g58be4879c -Revision: 58be4879c5d3840315f037dca44e92384113f8f9 +Version: VER-2-13-3-283-gd7e640b9c +Revision: d7e640b9c6f7e0d640c8e2d9598f13ba8c7765a6 CPEPrefix: cpe:/a:freetype:freetype:2.13.3 License: FTL License File: src/docs/FTL.TXT
diff --git a/third_party/freetype/src b/third_party/freetype/src index 58be487..d7e640b 160000 --- a/third_party/freetype/src +++ b/third_party/freetype/src
@@ -1 +1 @@ -Subproject commit 58be4879c5d3840315f037dca44e92384113f8f9 +Subproject commit d7e640b9c6f7e0d640c8e2d9598f13ba8c7765a6
diff --git a/third_party/lit/v3_0/BUILD.gn b/third_party/lit/v3_0/BUILD.gn index 4b74665..a3ab4ec 100644 --- a/third_party/lit/v3_0/BUILD.gn +++ b/third_party/lit/v3_0/BUILD.gn
@@ -27,6 +27,7 @@ "//chrome/browser/resources/about_sys:build_ts", "//chrome/browser/resources/app_home:build_ts", "//chrome/browser/resources/app_settings:build_ts", + "//chrome/browser/resources/autofill_ml_internals:build_ts", "//chrome/browser/resources/bookmarks:build_ts", "//chrome/browser/resources/browser_switch:build_ts", "//chrome/browser/resources/certificate_manager:build_ts",
diff --git a/third_party/perfetto b/third_party/perfetto index 32fc4f31..5192b52 160000 --- a/third_party/perfetto +++ b/third_party/perfetto
@@ -1 +1 @@ -Subproject commit 32fc4f31c38c6598c34f7dca0dd6aad2a86f373d +Subproject commit 5192b52b266bf5840929a30ac36037cf4f6eb2b7
diff --git a/third_party/webrtc_overrides/BUILD.gn b/third_party/webrtc_overrides/BUILD.gn index 2b3076c..0c64bfc 100644 --- a/third_party/webrtc_overrides/BUILD.gn +++ b/third_party/webrtc_overrides/BUILD.gn
@@ -66,6 +66,7 @@ "//third_party/webrtc/api/metronome", "//third_party/webrtc/api/rtc_event_log:rtc_event_log_factory", "//third_party/webrtc/api/task_queue:task_queue", + "//third_party/webrtc/api/transport:ecn_marking", "//third_party/webrtc/api/transport:enums", "//third_party/webrtc/api/transport:field_trial_based_config", "//third_party/webrtc/api/transport:goog_cc", @@ -156,7 +157,6 @@ "//third_party/webrtc/rtc_base:threading", "//third_party/webrtc/rtc_base:timestamp_aligner", "//third_party/webrtc/rtc_base:timeutils", - "//third_party/webrtc/rtc_base/network:ecn_marking", "//third_party/webrtc/rtc_base/network:received_packet", "//third_party/webrtc/rtc_base/system:rtc_export", "//third_party/webrtc/rtc_base/third_party/sigslot",
diff --git a/tools/gritsettings/resource_ids.spec b/tools/gritsettings/resource_ids.spec index 3499d72a..f24d7bf 100644 --- a/tools/gritsettings/resource_ids.spec +++ b/tools/gritsettings/resource_ids.spec
@@ -1070,6 +1070,10 @@ "META": {"sizes": {"includes": [5]}}, "includes": [7290], }, + "<(SHARED_INTERMEDIATE_DIR)/chrome/browser/resources/autofill_ml_internals/resources.grd": { + "META": {"sizes": {"includes": [10]}}, + "includes": [7295], + }, "<(SHARED_INTERMEDIATE_DIR)/components/commerce/core/internals/resources/resources.grd": { "META": {"sizes": {"includes": [30]}}, "includes": [7300],
diff --git a/tools/metrics/actions/actions.xml b/tools/metrics/actions/actions.xml index c341dbb..9f665e7 100644 --- a/tools/metrics/actions/actions.xml +++ b/tools/metrics/actions/actions.xml
@@ -33544,6 +33544,7 @@ </action> <action name="Permissions.Prompt.QuietBubble.Geolocation.AllowClicked"> + <obsolete>Deprecated.</obsolete> <owner>engedy@chromium.org</owner> <owner>src/components/permissions/PERMISSIONS_OWNERS</owner> <description> @@ -33555,6 +33556,7 @@ </action> <action name="Permissions.Prompt.QuietBubble.Geolocation.ManageClicked"> + <obsolete>Deprecated.</obsolete> <owner>engedy@chromium.org</owner> <owner>src/components/permissions/PERMISSIONS_OWNERS</owner> <description> @@ -33566,6 +33568,7 @@ </action> <action name="Permissions.Prompt.QuietBubble.Notifications.AllowClicked"> + <obsolete>Deprecated.</obsolete> <owner>engedy@chromium.org</owner> <owner>src/components/permissions/PERMISSIONS_OWNERS</owner> <description> @@ -33577,6 +33580,7 @@ </action> <action name="Permissions.Prompt.QuietBubble.Notifications.ManageClicked"> + <obsolete>Deprecated.</obsolete> <owner>engedy@chromium.org</owner> <owner>src/components/permissions/PERMISSIONS_OWNERS</owner> <description>
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml index 8e89041..f66f27ee 100644 --- a/tools/metrics/histograms/enums.xml +++ b/tools/metrics/histograms/enums.xml
@@ -8498,6 +8498,7 @@ <int value="-2039343702" label="KeepAliveRendererForKeepaliveRequests:enabled"/> <int value="-2038973140" label="VideoConference:enabled"/> + <int value="-2038766884" label="OfferMigrationToDiceUsers:enabled"/> <int value="-2038281501" label="ClipboardHistoryWebContentsPaste:enabled"/> <int value="-2038175774" label="AndroidAutofillViewStructureWithFormHierarchyLayer:enabled"/> @@ -8895,6 +8896,8 @@ <int value="-1903166011" label="IsolatedWebAppDevMode:disabled"/> <int value="-1902142840" label="CalendarModelDebugMode:enabled"/> <int value="-1901503351" label="CrosBatteryChargeLimit:disabled"/> + <int value="-1899788521" + label="WebAssemblyExperimentalSharedEverything:disabled"/> <int value="-1899715534" label="GamepadPollingInterval:enabled"/> <int value="-1899652563" label="ReportFeedUserActions:enabled"/> <int value="-1899342084" label="ArcGhostWindow:enabled"/> @@ -11383,6 +11386,8 @@ <int value="-985964106" label="PriceTrackingIconColors:disabled"/> <int value="-985845117" label="Cardboard:enabled"/> <int value="-985445963" label="TabSearchFuzzySearch:disabled"/> + <int value="-985031079" + label="WebAssemblyExperimentalSharedEverything:enabled"/> <int value="-984340827" label="OidcAuthProfileManagement:enabled"/> <int value="-984052166" label="DoodlesOnLocalNtp:enabled"/> <int value="-983845861" label="ShowCastPermissionRejectedError:disabled"/> @@ -13624,6 +13629,7 @@ <int value="-138820512" label="DesktopPWAsTabStrip:enabled"/> <int value="-138773929" label="PassiveDocumentEventListeners:enabled"/> <int value="-138760381" label="AutofillManualFallbackAndroid:enabled"/> + <int value="-137950493" label="OfferMigrationToDiceUsers:disabled"/> <int value="-137716319" label="ExtensionManifestV2Unsupported:enabled"/> <int value="-137394236" label="TabStripContextMenuAndroid:disabled"/> <int value="-137303226" label="enable-chromevox-developer-option"/>
diff --git a/tools/metrics/histograms/metadata/autofill/histograms.xml b/tools/metrics/histograms/metadata/autofill/histograms.xml index c0017ef4b..644b7c93 100644 --- a/tools/metrics/histograms/metadata/autofill/histograms.xml +++ b/tools/metrics/histograms/metadata/autofill/histograms.xml
@@ -2937,7 +2937,7 @@ </histogram> <histogram name="Autofill.FastCheckout.RunOutcome" - enum="FastCheckoutRunOutcome" expires_after="2025-06-30"> + enum="FastCheckoutRunOutcome" expires_after="2025-12-14"> <owner>bwolfgang@google.com</owner> <owner>jkeitel@google.com</owner> <owner>vizcay@google.com</owner>
diff --git a/tools/metrics/histograms/metadata/blink/enums.xml b/tools/metrics/histograms/metadata/blink/enums.xml index 2eae05e8..97d21af2 100644 --- a/tools/metrics/histograms/metadata/blink/enums.xml +++ b/tools/metrics/histograms/metadata/blink/enums.xml
@@ -1975,7 +1975,7 @@ <int value="1658" label="OffscreenCanvasTransferToImageBitmap2D"/> <int value="1659" label="OffscreenCanvasTransferToImageBitmapWebGL"/> <int value="1660" label="OBSOLETE_OffscreenCanvasCommit2D"/> - <int value="1661" label="OffscreenCanvasCommitWebGL"/> + <int value="1661" label="OBSOLETE_OffscreenCanvasCommitWebGL"/> <int value="1662" label="RTCConfigurationIceTransportPolicy"/> <int value="1663" label="RTCConfigurationIceTransportPolicyNone"/> <int value="1664" label="RTCConfigurationIceTransports"/>
diff --git a/tools/metrics/histograms/metadata/compositing/enums.xml b/tools/metrics/histograms/metadata/compositing/enums.xml index a2448ff..389f83f 100644 --- a/tools/metrics/histograms/metadata/compositing/enums.xml +++ b/tools/metrics/histograms/metadata/compositing/enums.xml
@@ -33,6 +33,19 @@ <int value="3" label="No, candidate uses an unsupported strategy"/> </enum> +<!--LINT.IfChange(BeginImplFrameDeadlineMode) --> + +<enum name="BeginImplFrameDeadlineMode"> + <int value="0" label="None"/> + <int value="1" label="Immediate"/> + <int value="2" label="Wait for scroll"/> + <int value="3" label="Regular"/> + <int value="4" label="Late"/> + <int value="5" label="Blocked"/> +</enum> + +<!--LINT.ThenChange(//cc/scheduler/scheduler_state_machine.cc:BeginImplFrameDeadlineMode) --> + <enum name="BufferFormat"> <summary>The possible format of buffers. See ui/gfx/buffer_types.h.</summary> <int value="0" label="R_8"/>
diff --git a/tools/metrics/histograms/metadata/compositing/histograms.xml b/tools/metrics/histograms/metadata/compositing/histograms.xml index ab33eec2..4e93d46 100644 --- a/tools/metrics/histograms/metadata/compositing/histograms.xml +++ b/tools/metrics/histograms/metadata/compositing/histograms.xml
@@ -744,6 +744,16 @@ </summary> </histogram> +<histogram name="Compositing.Scheduler.DeadlineMode" + enum="BeginImplFrameDeadlineMode" expires_after="2025-10-30"> + <owner>lizeb@chromium.org</owner> + <owner>jonross@chromium.org</owner> + <summary> + Report the scheduler deadline mode when it changes, which may be multiple + times (but at least once per frame). This histogram is subsampled. + </summary> +</histogram> + <histogram name="Compositing.SurfaceAggregator.AggregateUs" units="microseconds" expires_after="2025-10-05"> <owner>kylechar@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/media/enums.xml b/tools/metrics/histograms/metadata/media/enums.xml index e1a49676..91a5628 100644 --- a/tools/metrics/histograms/metadata/media/enums.xml +++ b/tools/metrics/histograms/metadata/media/enums.xml
@@ -1079,6 +1079,13 @@ <int value="2" label="Exclude"/> </enum> +<enum name="GetDisplayMediaSystemWindowOrExcludeConstraint"> + <int value="0" label="Not specified"/> + <int value="1" label="System"/> + <int value="2" label="Window"/> + <int value="3" label="Exclude"/> +</enum> + <enum name="GetDisplayMediaTransientActivation"> <int value="0" label="Present"/> <int value="1" label="Missing"/>
diff --git a/tools/metrics/histograms/metadata/media/histograms.xml b/tools/metrics/histograms/metadata/media/histograms.xml index 55b2417..49cee0e 100644 --- a/tools/metrics/histograms/metadata/media/histograms.xml +++ b/tools/metrics/histograms/metadata/media/histograms.xml
@@ -3848,6 +3848,17 @@ </summary> </histogram> +<histogram name="Media.GetDisplayMedia.Constraints.WindowAudio" + enum="GetDisplayMediaSystemWindowOrExcludeConstraint" + expires_after="2026-06-27"> + <owner>brandstrom@chromium.org</owner> + <owner>kron@chromium.org</owner> + <owner>webrtc-audio-uma@google.com</owner> + <summary> + Records whether getDisplayMedia was called with the windowAudio constraint. + </summary> +</histogram> + <histogram name="Media.GetDisplayMedia.RequiresUserActivationResult" enum="GetDisplayMediaTransientActivation" expires_after="2025-03-14"> <owner>eladalon@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/omnibox/enums.xml b/tools/metrics/histograms/metadata/omnibox/enums.xml index 26db41d..c74ae422 100644 --- a/tools/metrics/histograms/metadata/omnibox/enums.xml +++ b/tools/metrics/histograms/metadata/omnibox/enums.xml
@@ -286,6 +286,32 @@ <int value="3" label="kCompleted"/> </enum> +<!-- LINT.IfChange(SearchPreloadSignalResult) --> + +<enum name="SearchPreloadSignalResult"> + <int value="0" label="kNotTriggeredMisc">kNotTriggeredMisc</int> + <int value="1" label="kPrefetchTriggered">kPrefetchTriggered</int> + <int value="2" label="kPrerenderTriggered">kPrerenderTriggered</int> + <int value="3" label="kNotTriggeredAlreadyTriggered"> + kNotTriggeredAlreadyTriggered + </int> + <int value="4" label="kNotTriggeredPrefetchNotAlive"> + kNotTriggeredPrefetchNotAlive + </int> + <int value="5" label="kNotTriggeredLimitExceeded"> + kNotTriggeredLimitExceeded + </int> + <int value="6" label="kNotTriggeredIncognito">kNotTriggeredIncognito</int> + <int value="7" label="kNotTriggeredOnPressNotSearchType"> + kNotTriggeredOnPressNotSearchType + </int> + <int value="8" label="kNotTriggeredOnPressNoSearchProviderOptIn"> + kNotTriggeredOnPressNoSearchProviderOptIn + </int> +</enum> + +<!-- LINT.ThenChange(//chrome/browser/preloading/search_preload/search_preload_signal_result.h) --> + <enum name="SearchSuggestRequestEvent"> <int value="1" label="requests sent">Request was sent.</int> <int value="2" label="requests invalidated">
diff --git a/tools/metrics/histograms/metadata/omnibox/histograms.xml b/tools/metrics/histograms/metadata/omnibox/histograms.xml index 1688d688..b367d4e5 100644 --- a/tools/metrics/histograms/metadata/omnibox/histograms.xml +++ b/tools/metrics/histograms/metadata/omnibox/histograms.xml
@@ -38,6 +38,11 @@ or decrease is necessarily good."/> </variants> +<variants name="DsePreloadTriggerType"> + <variant name=".OnPress" summary="on-press trigger"/> + <variant name=".OnSuggest" summary="on-suggest trigger"/> +</variants> + <variants name="KeywordType"> <variant name="" summary="aggregated across all keyword types"/> <variant name=".DefaultSearchEngineSetByPolicy" @@ -224,6 +229,11 @@ <variant name="ZeroSuggest" summary="zero-prefix suggest"/> </variants> +<variants name="PreloadType"> + <variant name=".Prefetch" summary="prefetch"/> + <variant name=".Prerender" summary="prerender"/> +</variants> + <!-- LINT.IfChange(RemoteRequestSuccess) --> <variants name="RemoteRequestSuccess"> @@ -1063,6 +1073,24 @@ </summary> </histogram> +<histogram + name="Omnibox.DsePreload.SignalResult{DsePreloadTriggerType}{PreloadType}" + enum="SearchPreloadSignalResult" expires_after="2025-12-01"> + <owner>khalidpeer@chromium.org</owner> + <owner>chrome-desktop-search@google.com</owner> + <owner>kenoss@chromium.org</owner> + <owner>chrome-prerendering@google.com</owner> + <summary> + Measures the difference between "the client judged it should + preload" and "preload is actually triggered". + + Records the result of signal when the client judged it should trigger + {PreloadType} in DSE {DsePreloadTriggerType}. + </summary> + <token key="DsePreloadTriggerType" variants="DsePreloadTriggerType"/> + <token key="PreloadType" variants="PreloadType"/> +</histogram> + <histogram name="Omnibox.EnteredKeywordMode2" enum="OmniboxEnteredKeywordMode2" expires_after="2025-11-16"> <owner>jdonnelly@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/permissions/histograms.xml b/tools/metrics/histograms/metadata/permissions/histograms.xml index 836b021d..88a7264b 100644 --- a/tools/metrics/histograms/metadata/permissions/histograms.xml +++ b/tools/metrics/histograms/metadata/permissions/histograms.xml
@@ -1636,19 +1636,6 @@ </summary> </histogram> -<histogram name="Permissions.QuietChip.TimeToInteraction" units="ms" - expires_after="2025-02-04"> - <owner>elklm@chromium.org</owner> - <owner>engedy@chromium.org</owner> - <summary> - Records how long it takes for the user to click on the quiet chip after it - was shown. Chip framework is only available on Desktop. Recorded only if the - user clicks on the quiet chip. We can learn what % of prompts get ignored by - comparing this metric and - `PermissionPromptDisposition.LocationBarLeftQuietChip`. - </summary> -</histogram> - <histogram name="Permissions.QuietNotificationPrompts.EnabledStateInPrefsChangedTo" enum="BooleanEnabled" expires_after="2025-09-01"> @@ -1681,12 +1668,15 @@ </histogram> <histogram name="Permissions.QuietPrompt.Preignore" enum="PermissionType" - expires_after="2025-11-15"> + expires_after="2026-06-25"> <owner>elklm@chromium.org</owner> <owner>src/components/permissions/PERMISSIONS_OWNERS</owner> <summary> Records when a permission request was preignored because it will be shown as a quiet chip. + + Warning: this histogram was expired from 2025-11-15 to 2024-06-27; data may + be missing. </summary> </histogram> @@ -1948,19 +1938,6 @@ </summary> </histogram> -<histogram name="SiteEngagementService.OriginsWithMaxEngagement" units="units" - expires_after="2021-01-01"> - <owner>calamity@chromium.org</owner> - <owner>dominickn@chromium.org</owner> - <summary> - The number of origins which have reached the absolute site engagement point - cap, recorded at startup per non-incognito profile, and then upon the first - engagement-increasing event every hour thereafter. Thus, each bin N contains - the number of clients where at least N origins have reached the maximum - absolute site engagement point cap. - </summary> -</histogram> - <histogram name="WebsiteSettings.Action" enum="WebsiteSettingsAction" expires_after="2025-11-16"> <owner>estark@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/settings/histograms.xml b/tools/metrics/histograms/metadata/settings/histograms.xml index 9d372e6..acca242 100644 --- a/tools/metrics/histograms/metadata/settings/histograms.xml +++ b/tools/metrics/histograms/metadata/settings/histograms.xml
@@ -1058,7 +1058,8 @@ <owner>antoniosartori@chromium.org</owner> <summary> Records the number of revoked disruptive notification sites per a Safety Hub - run. + run. Note that this are actually proposed revocations, which will + potentially become effective only in successive runs. </summary> </histogram>
diff --git a/tools/metrics/histograms/metadata/signin/enums.xml b/tools/metrics/histograms/metadata/signin/enums.xml index 430d81fa..8013518c 100644 --- a/tools/metrics/histograms/metadata/signin/enums.xml +++ b/tools/metrics/histograms/metadata/signin/enums.xml
@@ -182,6 +182,16 @@ <!-- LINT.ThenChange(//components/signin/core/browser/account_management_type_metrics_recorder.h:AccountManagementTypesSummary) --> +<!-- LINT.IfChange(AccountMoveDecision) --> + +<enum name="AccountMoveDecision"> + <int value="0" label="Can move with refresh token"/> + <int value="1" label="Cannot move, account already exists"/> + <int value="2" label="Cannot move, insert without refresh token"/> +</enum> + +<!-- LINT.ThenChange(//components/signin/internal/identity_manager/mutable_profile_oauth2_token_service_delegate.h:AccountMoveDecision) --> + <!-- LINT.IfChange(AccountProfileStartupState) --> <enum name="AccountProfileStartupState">
diff --git a/tools/metrics/histograms/metadata/signin/histograms.xml b/tools/metrics/histograms/metadata/signin/histograms.xml index 67955d80..2f0827b 100644 --- a/tools/metrics/histograms/metadata/signin/histograms.xml +++ b/tools/metrics/histograms/metadata/signin/histograms.xml
@@ -2169,6 +2169,21 @@ </summary> </histogram> +<histogram name="Signin.MoveAccount.CanMoveToService" + enum="AccountMoveDecision" expires_after="2025-12-14"> + <owner>alexilin@chromium.org</owner> + <owner>droger@chromium.org</owner> + <owner>chrome-signin-team@google.com</owner> + <summary> + Records the outcome of an attempt to move an account between profiles, + specifically regarding whether the account's refresh token and binding key + can be transferred. + + Recorded on every attempt to move an account between profiles, mainly via + the sign-in intercept feature. + </summary> +</histogram> + <histogram name="Signin.Multilogin.NumberOfAccounts" units="accounts" expires_after="never"> <!-- expires-never: used to monitor the health of the signin feature and for troubleshooting. --> @@ -2253,41 +2268,6 @@ </summary> </histogram> -<histogram - name="Signin.OAuth2MintToken.BoundFetchAuthError{TokenBindingAssertionValue}" - enum="GoogleServiceAuthError" expires_after="2025-10-26"> - <owner>alexilin@chromium.org</owner> - <owner>droger@chromium.org</owner> - <owner>chrome-signin-team@google.com</owner> - <summary> - Records an error code (or none) for a single OAuth2MintToken API fetch with - a bound refresh token. Recorded for requests with - {TokenBindingAssertionValue}, after the response is processed. Desktop only. - </summary> - <token key="TokenBindingAssertionValue"> - <variant name="" summary="any token binding assertion value"/> - <variant name=".AssertionFailed" - summary="a "signature failed" placeholder in place of a - token binding assertion"/> - <variant name=".ChallengeSentinel" - summary="a challenge sentinel in place of a token binding assertion"/> - <variant name=".NoAssertion" summary="an empty token binding assertion"/> - <variant name=".SignedAssertion" summary="a successfully signed assertion"/> - </token> -</histogram> - -<histogram name="Signin.OAuth2MintToken.BoundFetchEncryptionError" - enum="TokenBindingResponseEncryptionError" expires_after="2025-10-26"> - <owner>alexilin@chromium.org</owner> - <owner>droger@chromium.org</owner> - <owner>chrome-signin-team@google.com</owner> - <summary> - Records an encryption failure (or lack of any) that occured during - OAuth2MintToken API fetch with a bound refresh token. Recorded after the - response is processed. Desktop only. - </summary> -</histogram> - <histogram name="Signin.OAuth2MintToken.Response" enum="OAuth2Response" expires_after="2025-10-26"> <owner>alexilin@chromium.org</owner> @@ -2304,6 +2284,49 @@ </summary> </histogram> +<histogram + name="Signin.OAuth2MintToken.{BindingState}FetchAuthError{TokenBindingAssertionValue}" + enum="GoogleServiceAuthError" expires_after="2025-10-26"> + <owner>alexilin@chromium.org</owner> + <owner>droger@chromium.org</owner> + <owner>chrome-signin-team@google.com</owner> + <summary> + Records an error code (or none) for a single OAuth2MintToken API fetch with + {BindingState} refresh token. Recorded for requests with + {TokenBindingAssertionValue}, after the response is processed. Desktop only. + </summary> + <token key="TokenBindingAssertionValue"> + <variant name="" summary="any token binding assertion value"/> + <variant name=".AssertionFailed" + summary="a "signature failed" placeholder in place of a + token binding assertion"/> + <variant name=".ChallengeSentinel" + summary="a challenge sentinel in place of a token binding assertion"/> + <variant name=".NoAssertion" summary="an empty token binding assertion"/> + <variant name=".SignedAssertion" summary="a successfully signed assertion"/> + </token> + <token key="BindingState"> + <variant name="Bound" summary="a bound"/> + <variant name="Unbound" summary="an unbound"/> + </token> +</histogram> + +<histogram name="Signin.OAuth2MintToken.{BindingState}FetchEncryptionError" + enum="TokenBindingResponseEncryptionError" expires_after="2025-10-26"> + <owner>alexilin@chromium.org</owner> + <owner>droger@chromium.org</owner> + <owner>chrome-signin-team@google.com</owner> + <summary> + Records an encryption failure (or lack of any) that occured during + OAuth2MintToken API fetch with {BindingState} refresh token. Recorded after + the response is processed. Desktop only. + </summary> + <token key="BindingState"> + <variant name="Bound" summary="a bound"/> + <variant name="Unbound" summary="an unbound"/> + </token> +</histogram> + <histogram name="Signin.OAuth2TokenGetResult" enum="GoogleServiceAuthError" expires_after="never"> <!-- expires-never: This is needed as long as Chrome is fetching access tokens
diff --git a/tools/metrics/histograms/metadata/ui/enums.xml b/tools/metrics/histograms/metadata/ui/enums.xml index d58bf24b..d4c79a1 100644 --- a/tools/metrics/histograms/metadata/ui/enums.xml +++ b/tools/metrics/histograms/metadata/ui/enums.xml
@@ -737,6 +737,7 @@ <int value="-377028639" label="chrome://copresence/"/> <int value="-359703631" label="chrome://slow_trace/"/> <int value="-336117866" label="chrome://focus-mode-media/"/> + <int value="-332876060" label="chrome://autofill-ml-internals/"/> <int value="-245337017" label="chrome://user-actions/"/> <int value="-233401490" label="chrome://accessory-update/"/> <int value="-222190329" label="chrome://dino/"/>
diff --git a/tools/perf/core/perfetto_binary_roller/binary_deps.json b/tools/perf/core/perfetto_binary_roller/binary_deps.json index 2579dfe..787c4d0 100644 --- a/tools/perf/core/perfetto_binary_roller/binary_deps.json +++ b/tools/perf/core/perfetto_binary_roller/binary_deps.json
@@ -1,24 +1,24 @@ { "trace_processor_shell": { "linux_arm64": { - "hash": "72572bff398b47ecc642c4b71a9af03f2d5528a5", - "full_remote_path": "perfetto-luci-artifacts/v51.0/linux-arm64/trace_processor_shell" + "hash": "527fa155fb7d4889d09c1c569ff1b4fefca6fd66", + "full_remote_path": "perfetto-luci-artifacts/v51.1/linux-arm64/trace_processor_shell" }, "win": { - "hash": "aa995cc279b94fdc5b558ee6240e6d1911d75bc9", - "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/win/32fc4f31c38c6598c34f7dca0dd6aad2a86f373d/trace_processor_shell.exe" + "hash": "62602b2f41aab510d3ba78b5f87ed4b9b71fda91", + "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/win/7b990aa93a0e658ef7d16e1d236142acd3ed3e46/trace_processor_shell.exe" }, "linux_arm": { - "hash": "247a7da473f8a92c9168f0ffdf73d2e0b9d46deb", - "full_remote_path": "perfetto-luci-artifacts/v51.0/linux-arm/trace_processor_shell" + "hash": "1fb099c947784ed2aa6e936668afa9d3ce4531b3", + "full_remote_path": "perfetto-luci-artifacts/v51.1/linux-arm/trace_processor_shell" }, "mac": { "hash": "f5d83eca972747f7c3db9f35c07ed57902418e8c", "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/mac/881ad50c05049ca13d4b34e4f92f4167de5ac52a/trace_processor_shell" }, "mac_arm64": { - "hash": "1f748fd4f2fcf26da46be119b60df0d295ddf9d9", - "full_remote_path": "perfetto-luci-artifacts/v51.0/mac-arm64/trace_processor_shell" + "hash": "a71372762e056a229bea2a215c74bbe3c1be3032", + "full_remote_path": "perfetto-luci-artifacts/v51.1/mac-arm64/trace_processor_shell" }, "linux": { "hash": "da59658d12c39e37a3ab55d5dfe1f8dd52f5d1e0",
diff --git a/ui/events/keycodes/keyboard_code_conversion_ios.mm b/ui/events/keycodes/keyboard_code_conversion_ios.mm index 9594a7d0..4597205 100644 --- a/ui/events/keycodes/keyboard_code_conversion_ios.mm +++ b/ui/events/keycodes/keyboard_code_conversion_ios.mm
@@ -192,14 +192,15 @@ if (characters.length == 0) { return 0; } - char16_t trail = [characters characterAtIndex:characters.length - 1]; + // Use uint16_t instead of char16_t to suppress a compiler warning. + uint16_t trail = [characters characterAtIndex:characters.length - 1]; if (CBU16_IS_SINGLE(trail)) { return trail; } if (characters.length == 1 || !CBU16_IS_TRAIL(trail)) { return 0; } - char16_t lead = [characters characterAtIndex:characters.length - 2]; + uint16_t lead = [characters characterAtIndex:characters.length - 2]; if (!CBU16_IS_LEAD(lead)) { return 0; }
diff --git a/ui/gfx/gpu_memory_buffer.h b/ui/gfx/gpu_memory_buffer.h index e392e53..d5cc2cb 100644 --- a/ui/gfx/gpu_memory_buffer.h +++ b/ui/gfx/gpu_memory_buffer.h
@@ -11,13 +11,6 @@ #include "ui/gfx/buffer_types.h" #include "ui/gfx/gpu_memory_buffer_handle.h" -namespace base { -namespace trace_event { -class ProcessMemoryDump; -class MemoryAllocatorDumpGuid; -} // namespace trace_event -} // namespace base - namespace gfx { // This interface typically correspond to a type of shared memory that is also @@ -67,9 +60,6 @@ // plane K is stored at index K-1 of the |stride| array. virtual int stride(size_t plane) const = 0; - // Returns a unique identifier associated with buffer. - virtual GpuMemoryBufferId GetId() const = 0; - // Returns the type of this buffer. virtual GpuMemoryBufferType GetType() const = 0; @@ -84,17 +74,6 @@ // MappableSI. See GpuMemoryBufferImplDXGI override for more details. virtual void SetUsePreMappedMemory(bool use_premapped_memory) {} #endif - - // Dumps information about the memory backing the GpuMemoryBuffer to |pmd|. - // The memory usage is attributed to |buffer_dump_guid|. - // |tracing_process_id| uniquely identifies the process owning the memory. - // |importance| is relevant only for the cases of co-ownership, the memory - // gets attributed to the owner with the highest importance. - virtual void OnMemoryDump( - base::trace_event::ProcessMemoryDump* pmd, - const base::trace_event::MemoryAllocatorDumpGuid& buffer_dump_guid, - uint64_t tracing_process_id, - int importance) const = 0; }; } // namespace gfx
diff --git a/ui/gfx/linux/client_native_pixmap_factory_dmabuf.cc b/ui/gfx/linux/client_native_pixmap_factory_dmabuf.cc index c65ab9b..b6278ce0 100644 --- a/ui/gfx/linux/client_native_pixmap_factory_dmabuf.cc +++ b/ui/gfx/linux/client_native_pixmap_factory_dmabuf.cc
@@ -37,7 +37,7 @@ CHECK_LT(plane, pixmap_handle_.planes.size()); // Even though a ClientNativePixmapOpaque should not be mapped, we may still // need to query the stride of each plane. See - // VideoFrame::WrapExternalGpuMemoryBuffer() for such a use case. + // VideoFrame::WrapMappableSharedImage() for such a use case. return base::checked_cast<int>(pixmap_handle_.planes[plane].stride); } NativePixmapHandle CloneHandleForIPC() const override {
diff --git a/v8 b/v8 index db56d8f..08af66d 160000 --- a/v8 +++ b/v8
@@ -1 +1 @@ -Subproject commit db56d8fbcf0e07da4c9ef31c5ae56739b7138fac +Subproject commit 08af66db4e0f2c52bf714c5f5f163d14f785d451