diff --git a/.vpython3 b/.vpython3 index 76fc87e..a701358 100644 --- a/.vpython3 +++ b/.vpython3
@@ -531,8 +531,16 @@ > # Used by: +# tools/perf/crossbench +wheel: < + name: "infra/python/wheels/hjson-py2_py3" + version: "version:3.1.0" +> + +# Used by: # chrome/test/mini_installer/test_chrome_with_chromedriver.py # testing/script/run_variations_smoke_tests.py +# tools/perf/crossbench # Keep this to the same revision as third_party/webdriver/pylib, which is pulled # in via DEPS. wheel: <
diff --git a/DEPS b/DEPS index bfc665b..96d9f2b 100644 --- a/DEPS +++ b/DEPS
@@ -299,7 +299,7 @@ # Three lines of non-changing comments so that # the commit queue can handle CLs rolling Skia # and whatever else without interference from each other. - 'skia_revision': '59dcffca90618578a6f9522dbad7c500a98884a1', + 'skia_revision': 'e08e5c9058a09e1ba7a971b70d46abf566c65be0', # Three lines of non-changing comments so that # the commit queue can handle CLs rolling V8 # and whatever else without interference from each other. @@ -326,7 +326,7 @@ # Three lines of non-changing comments so that # the commit queue can handle CLs rolling Fuchsia sdk # and whatever else without interference from each other. - 'fuchsia_version': 'version:11.20230129.1.1', + 'fuchsia_version': 'version:11.20230129.3.1', # Three lines of non-changing comments so that # the commit queue can handle CLs rolling google-toolbox-for-mac # and whatever else without interference from each other. @@ -372,6 +372,10 @@ # and whatever else without interference from each other. 'catapult_revision': '35d06490ad8ba91feb48301edd8f842046ce7fd9', # Three lines of non-changing comments so that + # the commit queue can handle CLs rolling CrossBench + # and whatever else without interference from each other. + 'crossbench_revision': '146c4da6088475e6e34aa4f3c30150d53f2c15a9', + # Three lines of non-changing comments so that # the commit queue can handle CLs rolling libFuzzer # and whatever else without interference from each other. 'libfuzzer_revision': 'debe7d2d1982e540fbd6bd78604bf001753f9e74', @@ -414,11 +418,11 @@ # Three lines of non-changing comments so that # the commit queue can handle CLs rolling feed # and whatever else without interference from each other. - 'dawn_revision': 'e8168a177ab220bbb4be23e1e78afe72730b1811', + 'dawn_revision': 'a12ff9abd17832664dc64f0a31dae08d0aa09a7e', # Three lines of non-changing comments so that # the commit queue can handle CLs rolling feed # and whatever else without interference from each other. - 'quiche_revision': '18631e198ebd9f3c4984b76dcd52258b5ddf4a93', + 'quiche_revision': '1fdac9bdbf85d27537e8ce47ca4f1eaa034f1ff5', # Three lines of non-changing comments so that # the commit queue can handle CLs rolling ios_webkit # and whatever else without interference from each other. @@ -752,7 +756,7 @@ 'src/clank': { 'url': 'https://chrome-internal.googlesource.com/clank/internal/apps.git' + '@' + - 'a2c86438eb101876f70bacba5adec8b2c3716b19', + 'a3955fc5f2aa8351f6c539ee20fd110f1a61e64c', 'condition': 'checkout_android and checkout_src_internal', }, @@ -941,7 +945,7 @@ 'packages': [ { 'package': 'chromium/third_party/androidx', - 'version': 'Aw6kA-NpPjzWNknKBiY0EyY5bNO-dUuS9QdZHdjLzX8C', + 'version': 'Hdb7ZPqGV3lLyY7geGwmoelVab7mxM0oA0jtglEVp2MC', }, ], 'condition': 'checkout_android', @@ -1180,6 +1184,10 @@ 'condition': 'checkout_linux', }, + 'src/third_party/crossbench': + Var('chromium_git') + '/crossbench.git' + '@' + Var('crossbench_revision'), + + 'src/third_party/crubit/src': { 'url': Var('chromium_git') + '/external/github.com/google/crubit.git' + '@' + Var('crubit_revision'), 'condition': 'checkout_crubit', @@ -1650,7 +1658,7 @@ }, 'src/third_party/perfetto': - Var('android_git') + '/platform/external/perfetto.git' + '@' + '45275bed1173d8a0cd0995560ca2882eb7d45e19', + Var('android_git') + '/platform/external/perfetto.git' + '@' + '81c39bac7b68480ad61e2e71d41625acd1d729cc', 'src/third_party/perl': { 'url': Var('chromium_git') + '/chromium/deps/perl.git' + '@' + '6f3e5028eb65d0b4c5fdd792106ac4c84eee1eb3', @@ -1690,7 +1698,7 @@ 'packages': [ { 'package': 'chromium/third_party/r8', - 'version': 'l0C1ThOaE6rumuAbPi3UJ4mbRYtfEz2MYsw8oNHsehYC', + 'version': 'kTwoRbYJ0cNEX_B1XARsNkSFKf4bOHgQCEiP4afsmBUC', }, ], 'condition': 'checkout_android', @@ -1788,14 +1796,14 @@ 'packages': [ { 'package': 'chromium/third_party/turbine', - 'version': 't0TeGgk2CZr3B0HtEqBb60BSRwVPRJm9066izhJwzz0C', + 'version': 'YQC-btuan_DTe9V9dv_e1LxgYSWeOoDfrd-VSqzIvHkC', }, ], 'condition': 'checkout_android', 'dep_type': 'cipd', }, - 'src/third_party/vulkan-deps': '{chromium_git}/vulkan-deps@9988b37a33510f8ed6975544a3fb4228f1ca879b', + 'src/third_party/vulkan-deps': '{chromium_git}/vulkan-deps@be674ea25e15b319352b8b908604efa77c2a9edb', 'src/third_party/vulkan_memory_allocator': Var('chromium_git') + '/external/github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator.git' + '@' + 'ebe84bec02c041d28f902da0214bf442743fc907', @@ -1835,7 +1843,7 @@ Var('chromium_git') + '/external/github.com/gpuweb/cts.git' + '@' + '7324baba2b12a71cecc155d72680478ec75b6591', 'src/third_party/webrtc': - Var('webrtc_git') + '/src.git' + '@' + 'dc7a14bb669eb770908db0748995d9238b705235', + Var('webrtc_git') + '/src.git' + '@' + '68564bb59cf3792ea9009be0e124e6c36d4488dc', # Wuffs' canonical repository is at github.com/google/wuffs, but we use # Skia's mirror of Wuffs, the same as in upstream Skia's DEPS file. @@ -1905,7 +1913,7 @@ Var('chromium_git') + '/v8/v8.git' + '@' + Var('v8_revision'), 'src-internal': { - 'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@d19a58e3d0df3ee6b2e2889b7779ba4cf44c2e83', + 'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@f120cb807531a0513a94ab14e64c4a426126d290', 'condition': 'checkout_src_internal', }, @@ -1946,7 +1954,7 @@ 'packages': [ { 'package': 'chromeos_internal/apps/media_app/app', - 'version': 'zGZ3VTATJuOZ71PnzXR3M-WDFHg7Dx16r7L4hF_CO68C', + 'version': '-RdtXluBu-dcSG5Gw1K8IuC_0VZgiXRqD01UX27egB8C', }, ], 'condition': 'checkout_chromeos and checkout_src_internal', @@ -1957,7 +1965,7 @@ 'packages': [ { 'package': 'chromeos_internal/apps/projector_app/app', - 'version': 'aBpoCHEPfMKLdl7Tez28r4Vk4n52JH2cSQVC-VJ4Mc0C', + 'version': 'lnCJuzNKn7hl8VCKC7RD6UfB1nt-ADxMvaQlx6rzmxsC', }, ], 'condition': 'checkout_chromeos and checkout_src_internal',
diff --git a/android_webview/browser/aw_autofill_client.cc b/android_webview/browser/aw_autofill_client.cc index 99c0881..c58feb2 100644 --- a/android_webview/browser/aw_autofill_client.cc +++ b/android_webview/browser/aw_autofill_client.cc
@@ -213,7 +213,7 @@ bool AwAutofillClient::TryToShowFastCheckout( const autofill::FormData& form, const autofill::FormFieldData& field, - autofill::AutofillDriver* driver) { + base::WeakPtr<autofill::AutofillManager> autofill_manager) { return false; }
diff --git a/android_webview/browser/aw_autofill_client.h b/android_webview/browser/aw_autofill_client.h index 1169ae62..a6a9c68 100644 --- a/android_webview/browser/aw_autofill_client.h +++ b/android_webview/browser/aw_autofill_client.h
@@ -115,9 +115,10 @@ AddressProfileSavePromptCallback callback) override; bool HasCreditCardScanFeature() override; void ScanCreditCard(CreditCardScanCallback callback) override; - bool TryToShowFastCheckout(const autofill::FormData& form, - const autofill::FormFieldData& field, - autofill::AutofillDriver* driver) override; + bool TryToShowFastCheckout( + const autofill::FormData& form, + const autofill::FormFieldData& field, + base::WeakPtr<autofill::AutofillManager> autofill_manager) override; void HideFastCheckout(bool allow_further_runs) override; bool IsFastCheckoutSupported() override; bool IsShowingFastCheckoutUI() override;
diff --git a/android_webview/javatests/src/org/chromium/android_webview/test/services/JsSandboxServiceTest.java b/android_webview/javatests/src/org/chromium/android_webview/test/services/JsSandboxServiceTest.java index 704a5f50..235bfd4f 100644 --- a/android_webview/javatests/src/org/chromium/android_webview/test/services/JsSandboxServiceTest.java +++ b/android_webview/javatests/src/org/chromium/android_webview/test/services/JsSandboxServiceTest.java
@@ -44,6 +44,7 @@ // larger in future. However, we don't want it too large as that will make the tests slower and // require more memory. private static final long REASONABLE_HEAP_SIZE = 100 * 1024 * 1024; + private static final int LARGE_NAMED_DATA_SIZE = 2 * 1024 * 1024; @Test @MediumTest @@ -801,4 +802,343 @@ } } } + + @Test + @LargeTest + public void testArrayBufferSizeEnforced() throws Throwable { + final long maxHeapSize = REASONABLE_HEAP_SIZE; + // V8 cannot sparsely allocate array buffers, so no fill required. + final String oomingCode = "" + + "const bigArray = new Float32Array(new ArrayBuffer(" + (maxHeapSize + 1) + "));" + + "'Unreachable'"; + final String stableCode = "" + + "const smallArray = new Float32Array(new ArrayBuffer(100));" + + "'PASS'"; + final String stableExpected = "PASS"; + Context context = ContextUtils.getApplicationContext(); + ListenableFuture<JavaScriptSandbox> jsSandboxFuture = + JavaScriptSandbox.createConnectedInstanceForTestingAsync(context); + try (JavaScriptSandbox jsSandbox = jsSandboxFuture.get(5, TimeUnit.SECONDS)) { + Assume.assumeTrue(jsSandbox.isFeatureSupported( + JavaScriptSandbox.JS_FEATURE_ISOLATE_MAX_HEAP_SIZE)); + IsolateStartupParameters isolateStartupParameters = new IsolateStartupParameters(); + isolateStartupParameters.setMaxHeapSizeBytes(maxHeapSize); + try (JavaScriptIsolate jsIsolate = jsSandbox.createIsolate(isolateStartupParameters)) { + // Check that unserviceable large allocations fail. + ListenableFuture<String> resultFuture1 = + jsIsolate.evaluateJavaScriptAsync(oomingCode); + try { + resultFuture1.get(5, TimeUnit.SECONDS); + Assert.fail("Should have thrown."); + } catch (ExecutionException e) { + if (!(e.getCause() instanceof EvaluationFailedException)) { + throw e; + } + EvaluationFailedException cause = (EvaluationFailedException) e.getCause(); + if (!cause.getMessage().startsWith( + "Uncaught RangeError: Array buffer allocation failed")) { + throw e; + } + } + + // Check that the same isolate can be used to perform a smaller allocation. + ListenableFuture<String> resultFuture2 = + jsIsolate.evaluateJavaScriptAsync(stableCode); + String result = resultFuture2.get(5, TimeUnit.SECONDS); + Assert.assertEquals(stableExpected, result); + } + } + } + + @Test + @LargeTest + public void testGarbageCollection() throws Throwable { + final long maxHeapSize = REASONABLE_HEAP_SIZE; + Context context = ContextUtils.getApplicationContext(); + ListenableFuture<JavaScriptSandbox> jsSandboxFuture = + JavaScriptSandbox.createConnectedInstanceForTestingAsync(context); + try (JavaScriptSandbox jsSandbox = jsSandboxFuture.get(5, TimeUnit.SECONDS)) { + Assume.assumeTrue(jsSandbox.isFeatureSupported( + JavaScriptSandbox.JS_FEATURE_ISOLATE_MAX_HEAP_SIZE)); + IsolateStartupParameters isolateStartupParameters = new IsolateStartupParameters(); + isolateStartupParameters.setMaxHeapSizeBytes(maxHeapSize); + final long num_doubles = 1024 * 1024; + // There may be additional allocation overhead beyond this value. + final long allocation_size = 8 * num_doubles; + final long memoryUseFactor = 2; + final long allocationsToTry = memoryUseFactor * maxHeapSize / allocation_size; + // This test will exercise both the V8 heap and ArrayBuffer-allocated memory. Each will + // have allocations totalling approximately memoryUseFactor times the available memory. + // + // Note that the configured heap limit is not precisely enforced by V8, so we need to go + // comfortably over our specified limit (and not just by an extra allocation). + // + // We use doubles (rather than bytes) to reduce (heap) Array overheads. + try (JavaScriptIsolate jsIsolate = jsSandbox.createIsolate(isolateStartupParameters)) { + final String code = "" + + "this.arrayLength = " + num_doubles + ";" + + "this.obj = {" + + " array: Array(this.arrayLength).fill(Math.random(), 0)," + + " arraybuffer: new Float64Array(new ArrayBuffer(8 * this.arrayLength))," + + "};" + + "'PASS'"; + final String expected = "PASS"; + for (int i = 0; i < allocationsToTry; i++) { + ListenableFuture<String> resultFuture = jsIsolate.evaluateJavaScriptAsync(code); + // Execution time will be unstable when GC kicks in, so go with 60 seconds. + String result = resultFuture.get(60, TimeUnit.SECONDS); + Assert.assertEquals(expected, result); + } + } + } + } + + @Test + @LargeTest + public void testNamedDataCanBeFreed() throws Throwable { + final long maxHeapSize = REASONABLE_HEAP_SIZE; + Context context = ContextUtils.getApplicationContext(); + ListenableFuture<JavaScriptSandbox> jsSandboxFuture = + JavaScriptSandbox.createConnectedInstanceForTestingAsync(context); + try (JavaScriptSandbox jsSandbox = jsSandboxFuture.get(5, TimeUnit.SECONDS)) { + Assume.assumeTrue(jsSandbox.isFeatureSupported( + JavaScriptSandbox.JS_FEATURE_ISOLATE_MAX_HEAP_SIZE)); + Assume.assumeTrue( + jsSandbox.isFeatureSupported(JavaScriptSandbox.JS_FEATURE_PROMISE_RETURN)); + Assume.assumeTrue(jsSandbox.isFeatureSupported( + JavaScriptSandbox.JS_FEATURE_PROVIDE_CONSUME_ARRAY_BUFFER)); + IsolateStartupParameters isolateStartupParameters = new IsolateStartupParameters(); + isolateStartupParameters.setMaxHeapSizeBytes(maxHeapSize); + // There will be named data allocations of approximately memoryUseFactor times the + // available memory. Note that the memory usage in the Java side is constant with + // respect to number of allocations as we reuse the same input bytes. + try (JavaScriptIsolate jsIsolate = jsSandbox.createIsolate(isolateStartupParameters)) { + final byte[] bytes = new byte[LARGE_NAMED_DATA_SIZE]; + final long memoryUseFactor = 2; + final long allocationsToTry = memoryUseFactor * maxHeapSize / LARGE_NAMED_DATA_SIZE; + for (int i = 0; i < allocationsToTry; i++) { + boolean provideNamedDataReturn = jsIsolate.provideNamedData("id-" + i, bytes); + Assert.assertTrue(provideNamedDataReturn); + final String code = "" + + "android.consumeNamedDataAsArrayBuffer('id-' + " + i + ")" + + " .then((arrayBuffer) => {" + + " const len = arrayBuffer.byteLength;" + + " if (len != " + LARGE_NAMED_DATA_SIZE + ") {" + + " throw new Error('Bad length ' + len);" + + " }" + + " return 'PASS';" + + " })"; + final String expected = "PASS"; + ListenableFuture<String> resultFuture = jsIsolate.evaluateJavaScriptAsync(code); + // Execution time may be unstable if the GC kicks in, so go with 60 seconds. + String result = resultFuture.get(60, TimeUnit.SECONDS); + Assert.assertEquals(expected, result); + } + } + } + } + + @Test + @LargeTest + public void testNamedDataCanTriggerGarbageCollection() throws Throwable { + // Array buffers for named data are created differently to ordinary array buffers (see + // native service code android_webview::JsSandboxIsolate::tryAllocateArrayBuffer). The + // special allocation code needs to run the garbage collector if we've run out of external + // memory budget. We test this by using up the budget with array buffers (such that there + // would not be enough space to consume the named data), and then forget about (turn into + // garbage) enough buffer memory for the allocation to succeed. This means that when we run + // our own allocation code, there is only enough memory to proceed after a garbage + // collection (but not without one). + final long maxHeapSize = REASONABLE_HEAP_SIZE; + final byte[] bytes = new byte[LARGE_NAMED_DATA_SIZE]; + final long allocationsToTry = (maxHeapSize / LARGE_NAMED_DATA_SIZE) + 1; + final String code = "" + + "const allocation_size = " + LARGE_NAMED_DATA_SIZE + ";" + + "this.array_buffers = new Array(" + allocationsToTry + ");" + + "let i;" + + "for (i = 0; i < this.array_buffers.length; i++) {" + + " try {" + + " this.array_buffers[i] = new ArrayBuffer(allocation_size);" + + " } catch (e) {" + + " if (e instanceof RangeError) {" + + " break;" + + " }" + + " }" + + "}" + + "if (i == this.array_buffers.length) {" + + " throw new Error('Expected to run out of memory, but did not');" + + "} else if (i == 0) {" + + " throw new Error('Could not achieve at least one allocation');" + + "}" + + "this.array_buffers[0] = null;" + + "android.consumeNamedDataAsArrayBuffer('test')" + + " .then((arrayBuffer) => {" + + " const len = arrayBuffer.byteLength;" + + " if (len != " + LARGE_NAMED_DATA_SIZE + ") {" + + " throw new Error('Bad length ' + len);" + + " }" + + " return 'PASS';" + + " })"; + final String expected = "PASS"; + Context context = ContextUtils.getApplicationContext(); + ListenableFuture<JavaScriptSandbox> jsSandboxFuture = + JavaScriptSandbox.createConnectedInstanceForTestingAsync(context); + try (JavaScriptSandbox jsSandbox = jsSandboxFuture.get(5, TimeUnit.SECONDS)) { + Assume.assumeTrue(jsSandbox.isFeatureSupported( + JavaScriptSandbox.JS_FEATURE_ISOLATE_MAX_HEAP_SIZE)); + Assume.assumeTrue( + jsSandbox.isFeatureSupported(JavaScriptSandbox.JS_FEATURE_PROMISE_RETURN)); + Assume.assumeTrue(jsSandbox.isFeatureSupported( + JavaScriptSandbox.JS_FEATURE_PROVIDE_CONSUME_ARRAY_BUFFER)); + IsolateStartupParameters isolateStartupParameters = new IsolateStartupParameters(); + isolateStartupParameters.setMaxHeapSizeBytes(maxHeapSize); + try (JavaScriptIsolate jsIsolate = jsSandbox.createIsolate(isolateStartupParameters)) { + boolean provideNamedDataReturn = jsIsolate.provideNamedData("test", bytes); + Assert.assertTrue(provideNamedDataReturn); + ListenableFuture<String> resultFuture = jsIsolate.evaluateJavaScriptAsync(code); + // Execution time may be unstable if the GC kicks in, so go with 60 seconds. + String result = resultFuture.get(60, TimeUnit.SECONDS); + Assert.assertEquals(expected, result); + } + } + } + + @Test + @LargeTest + public void testArrayBuffersAllocatedInPages() throws Throwable { + final long maxHeapSize = REASONABLE_HEAP_SIZE; + Context context = ContextUtils.getApplicationContext(); + ListenableFuture<JavaScriptSandbox> jsSandboxFuture = + JavaScriptSandbox.createConnectedInstanceForTestingAsync(context); + try (JavaScriptSandbox jsSandbox = jsSandboxFuture.get(5, TimeUnit.SECONDS)) { + Assume.assumeTrue(jsSandbox.isFeatureSupported( + JavaScriptSandbox.JS_FEATURE_ISOLATE_MAX_HEAP_SIZE)); + IsolateStartupParameters isolateStartupParameters = new IsolateStartupParameters(); + isolateStartupParameters.setMaxHeapSizeBytes(maxHeapSize); + // The service code should assume that small allocations have overhead and will cost at + // least a 4096 byte page size. (Even if the page size was larger, that shouldn't + // interfere with the accuracy of the test.) + try (JavaScriptIsolate jsIsolate = jsSandbox.createIsolate(isolateStartupParameters)) { + final long allocations = maxHeapSize / 4096 + 1; + // We need to use `new Uint8Array(new ArrayBuffer(1))` instead of `new + // Uint8Array(1)` because V8 appears to instead internalize (onto the V8 heap) + // smaller directly constructed typed arrays. + final String code = "" + + "(function(){" + + " const arrayLength = " + allocations + ";" + + " const buffers = Array(arrayLength);" + + " for (let i = 0; i < arrayLength; i++) {" + + " try {" + + " buffers[i] = new Uint8Array(new ArrayBuffer(1));" + + " } catch (e) {" + + " if (e instanceof RangeError) {" + + " return i;" + + " } else {" + + " throw e;" + + " }" + + " }" + + " }" + + " return 'FAIL';" + + "})()"; + final String notExpected = "FAIL"; + ListenableFuture<String> resultFuture = jsIsolate.evaluateJavaScriptAsync(code); + String result = resultFuture.get(60, TimeUnit.SECONDS); + Assert.assertNotEquals(notExpected, result); + } + // Allocating one larger contiguous array buffer should not incur significant overhead. + try (JavaScriptIsolate jsIsolate = jsSandbox.createIsolate(isolateStartupParameters)) { + // At most maxHeapSize is available to array buffers, but maybe less. Go with + // something less than the full limit, but which is still much more than what was + // logically requested by the many smaller buffers. + final long size = maxHeapSize / 8; + final String code = "" + + "const buffer = new Uint8Array(new ArrayBuffer(" + size + "));" + + "'PASS'"; + final String expected = "PASS"; + ListenableFuture<String> resultFuture = jsIsolate.evaluateJavaScriptAsync(code); + String result = resultFuture.get(10, TimeUnit.SECONDS); + Assert.assertEquals(expected, result); + } + } + } + + @Test + @LargeTest + public void testOversizedNamedData() throws Throwable { + final long maxHeapSize = REASONABLE_HEAP_SIZE; + final long largeSize = (maxHeapSize + 1L); + Assert.assertTrue(largeSize <= Integer.MAX_VALUE); + final byte[] largeBytes = new byte[(int) largeSize]; + final String provideString = "Hello World"; + final byte[] smallBytes = provideString.getBytes(StandardCharsets.US_ASCII); + // Test that attempting to consume an oversized named data into a new array buffer fails + // with a RangeError, and a subsequent smaller request succeeds. + final String code = "" + + "function ab2str(buf) {" + + " return String.fromCharCode.apply(null, new Uint8Array(buf));" + + "}" + + "async function test() {" + + " try {" + + " await android.consumeNamedDataAsArrayBuffer('large');" + + " throw new Error('consumption of large named data should not have succeeded');" + + " } catch (e) {" + + " if (!(e instanceof RangeError)) {" + + " throw e;" + + " }" + + " }" + + " const buffer = await android.consumeNamedDataAsArrayBuffer('small');" + + " return await ab2str(buffer);" + + "}" + + "test()"; + Context context = ContextUtils.getApplicationContext(); + ListenableFuture<JavaScriptSandbox> jsSandboxFuture = + JavaScriptSandbox.createConnectedInstanceForTestingAsync(context); + try (JavaScriptSandbox jsSandbox = jsSandboxFuture.get(5, TimeUnit.SECONDS)) { + Assume.assumeTrue(jsSandbox.isFeatureSupported( + JavaScriptSandbox.JS_FEATURE_ISOLATE_MAX_HEAP_SIZE)); + Assume.assumeTrue( + jsSandbox.isFeatureSupported(JavaScriptSandbox.JS_FEATURE_PROMISE_RETURN)); + Assume.assumeTrue(jsSandbox.isFeatureSupported( + JavaScriptSandbox.JS_FEATURE_PROVIDE_CONSUME_ARRAY_BUFFER)); + IsolateStartupParameters isolateStartupParameters = new IsolateStartupParameters(); + isolateStartupParameters.setMaxHeapSizeBytes(maxHeapSize); + try (JavaScriptIsolate jsIsolate = jsSandbox.createIsolate(isolateStartupParameters)) { + boolean provideNamedDataLargeReturn = + jsIsolate.provideNamedData("large", largeBytes); + Assert.assertTrue(provideNamedDataLargeReturn); + boolean provideNamedDataSmallReturn = + jsIsolate.provideNamedData("small", smallBytes); + Assert.assertTrue(provideNamedDataSmallReturn); + ListenableFuture<String> resultFuture = jsIsolate.evaluateJavaScriptAsync(code); + String result = resultFuture.get(5, TimeUnit.SECONDS); + Assert.assertEquals(provideString, result); + } + } + } + + @Test + @LargeTest + public void testUnconsumedNamedData() throws Throwable { + // Ensure that creating and discarding loads of separate unconsumed named data do not result + // in leaks (particularly memory, file descriptors, and threads). + final byte[] bytes = new byte[LARGE_NAMED_DATA_SIZE]; + final int numIsolates = 100; + final int numNames = 100; + Context context = ContextUtils.getApplicationContext(); + ListenableFuture<JavaScriptSandbox> jsSandboxFuture = + JavaScriptSandbox.createConnectedInstanceForTestingAsync(context); + try (JavaScriptSandbox jsSandbox = jsSandboxFuture.get(5, TimeUnit.SECONDS)) { + Assume.assumeTrue(jsSandbox.isFeatureSupported( + JavaScriptSandbox.JS_FEATURE_PROVIDE_CONSUME_ARRAY_BUFFER)); + for (int i = 0; i < numIsolates; i++) { + try (JavaScriptIsolate jsIsolate = jsSandbox.createIsolate()) { + for (int j = 0; j < numNames; j++) { + boolean provideNamedDataReturn = + jsIsolate.provideNamedData("id-" + j, bytes); + Assert.assertTrue(provideNamedDataReturn); + } + } + } + } + } }
diff --git a/android_webview/js_sandbox/service/BUILD.gn b/android_webview/js_sandbox/service/BUILD.gn index 339293d..e887105b 100644 --- a/android_webview/js_sandbox/service/BUILD.gn +++ b/android_webview/js_sandbox/service/BUILD.gn
@@ -4,6 +4,8 @@ source_set("js_sandbox") { sources = [ + "js_sandbox_array_buffer_allocator.cc", + "js_sandbox_array_buffer_allocator.h", "js_sandbox_isolate.cc", "js_sandbox_isolate.h", "js_sandbox_isolate_callback.cc",
diff --git a/android_webview/js_sandbox/service/js_sandbox_array_buffer_allocator.cc b/android_webview/js_sandbox/service/js_sandbox_array_buffer_allocator.cc new file mode 100644 index 0000000..3328ee1 --- /dev/null +++ b/android_webview/js_sandbox/service/js_sandbox_array_buffer_allocator.cc
@@ -0,0 +1,118 @@ +// Copyright 2023 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "android_webview/js_sandbox/service/js_sandbox_array_buffer_allocator.h" + +#include <cstddef> +#include <memory> + +#include "base/check.h" +#include "base/check_op.h" +#include "base/logging.h" +#include "base/memory/raw_ref.h" +#include "v8/include/v8-array-buffer.h" + +namespace { + +size_t RoundUpToPage(const size_t amount, const size_t page_size) { + CHECK_LE(amount, SIZE_MAX / page_size * page_size); + return ((amount + page_size - 1) / page_size) * page_size; +} + +} // namespace + +namespace android_webview { + +JsSandboxArrayBufferAllocator::JsSandboxArrayBufferAllocator( + v8::ArrayBuffer::Allocator& inner, + const size_t budget, + const size_t page_size) + : inner_allocator_(inner), + remaining_(budget), + budget_(budget), + page_size_(page_size) { + DCHECK_GT(page_size, size_t{0}); +} + +JsSandboxArrayBufferAllocator::~JsSandboxArrayBufferAllocator() { + // Note, remaining_ <= budget is an invariant maintained by CHECKs, so this + // should only ever fail if remaining_ < budget_. + DCHECK_EQ(remaining_, budget_) << "Memory leaked: " << (budget_ - remaining_) + << " bytes of array buffers not freed before " + "array buffer allocator destruction"; +} + +void* JsSandboxArrayBufferAllocator::Allocate(const size_t length) { + if (!AllocateBudget(length)) { + return nullptr; + } + void* const buffer = inner_allocator_->Allocate(length); + if (!buffer) { + FreeBudget(length); + return nullptr; + } + return buffer; +} + +void* JsSandboxArrayBufferAllocator::AllocateUninitialized( + const size_t length) { + if (!AllocateBudget(length)) { + return nullptr; + } + void* const buffer = inner_allocator_->AllocateUninitialized(length); + if (!buffer) { + FreeBudget(length); + return nullptr; + } + return buffer; +} + +void JsSandboxArrayBufferAllocator::Free(void* const data, + const size_t length) { + inner_allocator_->Free(data, length); + FreeBudget(length); +} + +void* JsSandboxArrayBufferAllocator::Reallocate(void* const data, + const size_t old_length, + const size_t new_length) { + // Don't assume we will only need new_length minus old_length extra bytes + // during the operation, or that if the new_length is less than old_length + // that everything will just happen in place. In fact, the V8 docs stipulate + // that the default implementation will always allocate a new block and copy + // the old data over. + if (!AllocateBudget(new_length)) { + return nullptr; + } + void* const new_data = + inner_allocator_->Reallocate(data, old_length, new_length); + if (!new_data) { + FreeBudget(new_length); + return nullptr; + } + FreeBudget(old_length); + return new_data; +} + +bool JsSandboxArrayBufferAllocator::AllocateBudget(const size_t amount) { + const size_t rounded_amount = RoundUpToPage(amount, page_size_); + if (remaining_ < rounded_amount) { + return false; + } + remaining_ -= rounded_amount; + return true; +} + +void JsSandboxArrayBufferAllocator::FreeBudget(const size_t amount) { + const size_t rounded_amount = RoundUpToPage(amount, page_size_); + CHECK_LE(amount, GetUsage()) + << "attempted to free more array buffer memory than is allocated"; + remaining_ += rounded_amount; +} + +size_t JsSandboxArrayBufferAllocator::GetUsage() const { + return budget_ - remaining_; +} + +} // namespace android_webview
diff --git a/android_webview/js_sandbox/service/js_sandbox_array_buffer_allocator.h b/android_webview/js_sandbox/service/js_sandbox_array_buffer_allocator.h new file mode 100644 index 0000000..551f4e2 --- /dev/null +++ b/android_webview/js_sandbox/service/js_sandbox_array_buffer_allocator.h
@@ -0,0 +1,93 @@ +// Copyright 2023 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef ANDROID_WEBVIEW_JS_SANDBOX_SERVICE_JS_SANDBOX_ARRAY_BUFFER_ALLOCATOR_H_ +#define ANDROID_WEBVIEW_JS_SANDBOX_SERVICE_JS_SANDBOX_ARRAY_BUFFER_ALLOCATOR_H_ + +#include <cstddef> + +#include "base/memory/raw_ref.h" +#include "v8/include/v8-array-buffer.h" + +namespace android_webview { + +// A v8::ArrayBuffer::Allocator which imposes a limit on the amount of +// simultaneously allocated memory. See also the V8 documentation for +// v8::ArrayBuffer::Allocator. +// +// This allocator must only be used from one thread at a time. Allocation and +// deallocation methods should only happen on the isolate thread, but +// construction and destruction will typically happen outside of the isolate +// thread. +class JsSandboxArrayBufferAllocator final : public v8::ArrayBuffer::Allocator { + public: + // Value to supply as a budget to indicate an unlimited budget. + static constexpr size_t kUnlimitedBudget = SIZE_MAX; + + // Wrap the given inner allocator and impose a maximum allocation budget. + // + // The JsSandboxArrayBufferAllocator allocator does NOT assume ownership of or + // copy the inner allocator, so the caller must ensure that it outlives the + // JsSandboxArrayBufferAllocator. + // + // The memory budget specifies the maximum amount of memory which can be + // allocated at any given time. Note that a value of 0 will disallow all + // allocations. Use kUnlimitedBudget if you want no effective limit. + // + // The page size should desribe the page size of the allocator used (this is + // not necessarily the OS page size), and is used to (pessimistically) account + // for possible internal memory fragmentation in the budget. + explicit JsSandboxArrayBufferAllocator(v8::ArrayBuffer::Allocator& inner, + size_t memory_budget, + size_t page_size); + JsSandboxArrayBufferAllocator(const JsSandboxArrayBufferAllocator&) = delete; + JsSandboxArrayBufferAllocator& operator=( + const JsSandboxArrayBufferAllocator&) = delete; + // All prior allocations must be freed before the allocator is destructed. + ~JsSandboxArrayBufferAllocator() override; + // Attempt to allocate length bytes (zero-initialized). + // + // The tracked memory usage may be rounded up to a multiple of the page size. + // + // nullptr will be returned if the request exceeds the memory budget or the + // inner allocator returns nullptr. + void* Allocate(size_t length) override; + // Similar to Allocate(), but does not explicitly zero-(re)initialize memory + // in userspace. + void* AllocateUninitialized(size_t length) override; + // Deallocate a currently allocated memory region that was previously returned + // by Allocate (or Reallocate). + // + // The length must match the current length associated with the region. + void Free(void* data, size_t length) override; + // Attempt to reallocate a region of memory (usually to expand or contract + // it). + // + // In order for a reallocation to work, there must be at least new_length + // remaining bytes left in the memory budget up-front, and the inner + // allocator's reallocate operation must also succeed. + void* Reallocate(void* data, size_t old_length, size_t new_length) override; + + // Return the amount of allocated (used) memory budget. + size_t GetUsage() const; + + private: + // Try to allocate some budget and return true iff successful + bool AllocateBudget(size_t amount); + // Deallocate some budget + void FreeBudget(size_t amount); + + // The underlying allocator which does the actual memory management. + const raw_ref<v8::ArrayBuffer::Allocator> inner_allocator_; + // The amount of unused budget + size_t remaining_; + // The overall memory budget this allocator has to stay within + const size_t budget_; + // Page size for rounding up allocation amounts. + const size_t page_size_; +}; + +} // namespace android_webview + +#endif
diff --git a/android_webview/js_sandbox/service/js_sandbox_isolate.cc b/android_webview/js_sandbox/service/js_sandbox_isolate.cc index cea8ff8..6925e165 100644 --- a/android_webview/js_sandbox/service/js_sandbox_isolate.cc +++ b/android_webview/js_sandbox/service/js_sandbox_isolate.cc
@@ -10,17 +10,21 @@ #include <string> #include "android_webview/js_sandbox/js_sandbox_jni_headers/JsSandboxIsolate_jni.h" +#include "android_webview/js_sandbox/service/js_sandbox_array_buffer_allocator.h" #include "android_webview/js_sandbox/service/js_sandbox_isolate_callback.h" #include "base/android/callback_android.h" #include "base/android/jni_android.h" #include "base/android/jni_string.h" #include "base/auto_reset.h" +#include "base/check.h" +#include "base/check_op.h" #include "base/files/file_util.h" #include "base/functional/bind.h" #include "base/immediate_crash.h" #include "base/logging.h" #include "base/memory/raw_ptr.h" #include "base/memory/scoped_refptr.h" +#include "base/numerics/safe_math.h" #include "base/strings/string_piece.h" #include "base/synchronization/waitable_event.h" #include "base/system/sys_info.h" @@ -37,6 +41,7 @@ #include "gin/try_catch.h" #include "gin/v8_initializer.h" #include "js_sandbox_isolate.h" +#include "v8/include/v8-array-buffer.h" #include "v8/include/v8-function.h" #include "v8/include/v8-microtask-queue.h" #include "v8/include/v8-statistics.h" @@ -52,19 +57,21 @@ // this once error handling is in place. constexpr base::StringPiece resource_name = "<expression>"; +size_t GetAllocatePageSize() { + return gin::V8Platform::Get()->GetPageAllocator()->AllocatePageSize(); +} + // AdjustToValidHeapSize will either round the provided heap size up to a valid // allocation page size or clip the value to the maximum supported heap size. -size_t AdjustToValidHeapSize(const uint64_t heap_size_bytes) { - // The value of 64K should just work on all platforms. Smaller page sizes - // might work in practice, although we currently don't have long-term - // guarantees. This value is not necessarily the same as the system's memory - // page size. https://bugs.chromium.org/p/v8/issues/detail?id=13172#c6 - constexpr size_t page_size = 65536; - constexpr size_t max_supported_heap_size = - (size_t)UINT_MAX / page_size * page_size; +size_t AdjustToValidHeapSize(const size_t heap_size_bytes) { + // This value is not necessarily the same as the system's memory page + // size. https://bugs.chromium.org/p/v8/issues/detail?id=13172#c6 + const size_t page_size = GetAllocatePageSize(); + const size_t max_supported_heap_size = + size_t{UINT_MAX} / page_size * page_size; - if (heap_size_bytes < (uint64_t)max_supported_heap_size) { - return ((size_t)heap_size_bytes + (page_size - 1)) / page_size * page_size; + if (heap_size_bytes < max_supported_heap_size) { + return (heap_size_bytes + (page_size - 1)) / page_size * page_size; } else { return max_supported_heap_size; } @@ -140,14 +147,21 @@ length = len; } -JsSandboxIsolate::JsSandboxIsolate(jlong max_heap_size_bytes) - : isolate_max_heap_size_bytes_(max_heap_size_bytes) { - CHECK_GE(isolate_max_heap_size_bytes_, 0); - control_task_runner_ = base::ThreadPool::CreateSequencedTaskRunner({}); - isolate_task_runner_ = base::ThreadPool::CreateSingleThreadTaskRunner( - {base::TaskPriority::USER_BLOCKING, - base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN, base::MayBlock()}, - base::SingleThreadTaskRunnerThreadMode::DEDICATED); +JsSandboxIsolate::JsSandboxIsolate(const size_t max_heap_size_bytes) + : isolate_max_heap_size_bytes_(max_heap_size_bytes), + array_buffer_allocator_(std::make_unique<JsSandboxArrayBufferAllocator>( + *gin::ArrayBufferAllocator::SharedInstance(), + max_heap_size_bytes > 0 + ? max_heap_size_bytes + : JsSandboxArrayBufferAllocator::kUnlimitedBudget, + // This is a bit of an implementation detail - gin uses the same + // underlying allocator for pages and array buffers. + GetAllocatePageSize())), + control_task_runner_(base::ThreadPool::CreateSequencedTaskRunner({})), + isolate_task_runner_(base::ThreadPool::CreateSingleThreadTaskRunner( + {base::TaskPriority::USER_BLOCKING, + base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN, base::MayBlock()}, + base::SingleThreadTaskRunnerThreadMode::DEDICATED)) { control_task_runner_->PostTask( FROM_HERE, base::BindOnce(&JsSandboxIsolate::CreateCancelableTaskTracker, base::Unretained(this))); @@ -253,53 +267,59 @@ // Called from control sequence. void JsSandboxIsolate::ConvertPromiseToArrayBufferInControlSequence( std::string name, - std::unique_ptr<v8::BackingStore> backing_store) { + std::unique_ptr<v8::Global<v8::ArrayBuffer>> array_buffer, + std::unique_ptr<v8::Global<v8::Promise::Resolver>> resolver) { cancelable_task_tracker_->PostTask( isolate_task_runner_.get(), FROM_HERE, base::BindOnce( &JsSandboxIsolate::ConvertPromiseToArrayBufferInIsolateSequence, - base::Unretained(this), std::move(name), std::move(backing_store))); + base::Unretained(this), std::move(name), std::move(array_buffer), + std::move(resolver))); } // Called from control sequence. +// +// The array_buffer's API must only be used from the isolate thread. void JsSandboxIsolate::ConvertPromiseToFailureInControlSequence( std::string name, + std::unique_ptr<v8::Global<v8::ArrayBuffer>> array_buffer, + std::unique_ptr<v8::Global<v8::Promise::Resolver>> resolver, std::string reason) { cancelable_task_tracker_->PostTask( isolate_task_runner_.get(), FROM_HERE, base::BindOnce( &JsSandboxIsolate::ConvertPromiseToFailureInIsolateSequence, - base::Unretained(this), std::move(name), std::move(reason))); + base::Unretained(this), std::move(name), std::move(array_buffer), + std::move(resolver), std::move(reason))); } // Called from Thread pool. +// +// The array_buffer's API must only be used from the isolate thread, but the +// internal data (inner_buffer) may be accessed in whatever thread is currently +// processing the task, so long as array_buffer remains alive. void JsSandboxIsolate::ConvertPromiseToArrayBufferInThreadPool( base::ScopedFD fd, ssize_t length, - std::string name) { - char* buffer = - (char*)gin::ArrayBufferAllocator::SharedInstance()->Allocate(length); - if (base::ReadFromFD(fd.get(), buffer, length)) { - auto deleter = [](void* buffer, size_t length, void* data) { - gin::ArrayBufferAllocator::SharedInstance()->Free(buffer, length); - }; - - std::unique_ptr<v8::BackingStore> backing_store = - v8::ArrayBuffer::NewBackingStore(buffer, length, deleter, nullptr); + std::string name, + std::unique_ptr<v8::Global<v8::ArrayBuffer>> array_buffer, + std::unique_ptr<v8::Global<v8::Promise::Resolver>> resolver, + void* inner_buffer) { + if (base::ReadFromFD(fd.get(), static_cast<char*>(inner_buffer), length)) { control_task_runner_->PostTask( FROM_HERE, base::BindOnce( &JsSandboxIsolate::ConvertPromiseToArrayBufferInControlSequence, - base::Unretained(this), std::move(name), std::move(backing_store))); + base::Unretained(this), std::move(name), std::move(array_buffer), + std::move(resolver))); } else { - gin::ArrayBufferAllocator::SharedInstance()->Free(buffer, length); std::string failure_reason = "Reading data failed."; control_task_runner_->PostTask( FROM_HERE, base::BindOnce( &JsSandboxIsolate::ConvertPromiseToFailureInControlSequence, - base::Unretained(this), std::move(name), - std::move(failure_reason))); + base::Unretained(this), std::move(name), std::move(array_buffer), + std::move(resolver), std::move(failure_reason))); } } @@ -332,6 +352,7 @@ void JsSandboxIsolate::InitializeIsolateOnThread() { std::unique_ptr<v8::Isolate::CreateParams> params = gin::IsolateHolder::getDefaultIsolateParams(); + params->array_buffer_allocator = array_buffer_allocator_.get(); if (isolate_max_heap_size_bytes_ > 0) { params->constraints.ConfigureDefaultsFromHeapSize( 0, AdjustToValidHeapSize(isolate_max_heap_size_bytes_)); @@ -466,30 +487,35 @@ // Called from isolate thread. void JsSandboxIsolate::ConvertPromiseToArrayBufferInIsolateSequence( std::string name, - std::unique_ptr<v8::BackingStore> backing_store) { + std::unique_ptr<v8::Global<v8::ArrayBuffer>> array_buffer, + std::unique_ptr<v8::Global<v8::Promise::Resolver>> resolver) { v8::Isolate::Scope isolate_scope(isolate_holder_->isolate()); v8::HandleScope handle_scope(isolate_holder_->isolate()); v8::Context::Scope scope(context_holder_->context()); - v8::Local<v8::ArrayBuffer> array_buffer = v8::ArrayBuffer::New( - isolate_holder_->isolate(), std::move(backing_store)); - auto it = named_resolver_.find(name); - it->second.Get(isolate_holder_->isolate()) - ->Resolve(context_holder_->context(), array_buffer) + resolver->Get(isolate_holder_->isolate()) + ->Resolve(context_holder_->context(), + array_buffer->Get(isolate_holder_->isolate())) .ToChecked(); - named_resolver_.erase(it); } // Called from isolate thread. +// +// We pass the array_buffer to the isolate thread so that it (or the handle) +// only gets destructed from the isolate thread. void JsSandboxIsolate::ConvertPromiseToFailureInIsolateSequence( std::string name, + std::unique_ptr<v8::Global<v8::ArrayBuffer>> array_buffer, + std::unique_ptr<v8::Global<v8::Promise::Resolver>> resolver, std::string reason) { v8::Isolate::Scope isolate_scope(isolate_holder_->isolate()); v8::HandleScope handle_scope(isolate_holder_->isolate()); v8::Context::Scope scope(context_holder_->context()); - named_resolver_[name] - .Get(isolate_holder_->isolate()) + // Allow array buffer to be garbage collectable before further V8 calls. + array_buffer = nullptr; + + resolver->Get(isolate_holder_->isolate()) ->Reject(context_holder_->context(), v8::Exception::Error( gin::StringToV8(isolate_holder_->isolate(), reason))) @@ -520,25 +546,61 @@ { base::AutoLock lock(named_fd_lock_); auto entry = named_fd_.find(name); - if (entry == named_fd_.end()) { - std::string reason = "No NamedData available with the given name"; - global_resolver.Get(isolate_holder_->isolate()) - ->Reject(context_holder_->context(), - v8::Exception::Error( - gin::StringToV8(isolate_holder_->isolate(), reason))) - .ToChecked(); - args->Return(promise); - return; + if (entry != named_fd_.end()) { + // When we move the fd, we invalidate the entry in the map such that it + // cannot be used again, even if the operation fails before we read any + // data from the pipe. + fd = std::move(entry->second.fd); + length = entry->second.length; } - fd = std::move(entry->second.fd); - length = entry->second.length; } - named_resolver_.insert({name, std::move(global_resolver)}); + if (!fd.is_valid()) { + std::string reason = "No NamedData available with the given name"; + global_resolver.Get(isolate_holder_->isolate()) + ->Reject(context_holder_->context(), + v8::Exception::Error( + gin::StringToV8(isolate_holder_->isolate(), reason))) + .ToChecked(); + args->Return(promise); + return; + } + + // V8 only accounts for the external memory used by backing stores once they + // are bound to an array buffer. So we set up the whole array buffer up-front + // on the isolate thread. (This will prevent V8's view of external memory + // usage getting out of sync with our own.) + v8::MaybeLocal<v8::ArrayBuffer> maybe_array_buffer = + tryAllocateArrayBuffer(length); + if (maybe_array_buffer.IsEmpty()) { + const std::string reason = + "Array buffer allocation failed for consumeNamedDataAsArrayBuffer"; + global_resolver.Get(isolate_holder_->isolate()) + ->Reject(context_holder_->context(), + v8::Exception::RangeError( + gin::StringToV8(isolate_holder_->isolate(), reason))) + .ToChecked(); + args->Return(promise); + return; + } + + v8::Local<v8::ArrayBuffer> local_array_buffer = + maybe_array_buffer.ToLocalChecked(); + void* const inner_buffer = local_array_buffer->Data(); + // V8 documentation provides no guarantees about the thread-safety of Globals + // - even move construction/destruction. Wrap it in a unique_ptr so that it + // can be treated as an opaque pointer until it's handed back to the isolate + // thread. + std::unique_ptr<v8::Global<v8::ArrayBuffer>> global_array_buffer( + std::make_unique<v8::Global<v8::ArrayBuffer>>( + isolate, std::move(local_array_buffer))); base::ThreadPool::PostTask( FROM_HERE, {base::MayBlock()}, base::BindOnce(&JsSandboxIsolate::ConvertPromiseToArrayBufferInThreadPool, base::Unretained(this), std::move(fd), length, - std::move(name))); + std::move(name), std::move(global_array_buffer), + std::make_unique<v8::Global<v8::Promise::Resolver>>( + std::move(global_resolver)), + inner_buffer)); args->Return(promise); } @@ -561,11 +623,16 @@ // currently running evaluation. CHECK(current_callback_) << "Isolate ran out of memory outside of an evaluation."; - uint64_t memory_limit = static_cast<uint64_t>(isolate_max_heap_size_bytes_); + uint64_t memory_limit = uint64_t{isolate_max_heap_size_bytes_}; v8::HeapStatistics heap_statistics; isolate_holder_->isolate()->GetHeapStatistics(&heap_statistics); - uint64_t heap_usage = heap_statistics.used_heap_size(); - current_callback_->ReportMemoryLimitExceededError(memory_limit, heap_usage); + uint64_t v8_heap_usage = heap_statistics.used_heap_size(); + // Note that we use our own memory accounting, and not V8's external memory + // accounting, for non-heap usage. These numbers can differ, particularly as + // our own memory accounting considers whole pages rather than just bytes. + uint64_t non_v8_heap_usage = uint64_t{array_buffer_allocator_->GetUsage()}; + current_callback_->ReportMemoryLimitExceededError(memory_limit, v8_heap_usage, + non_v8_heap_usage); FreezeThread(); } @@ -586,6 +653,52 @@ base::ImmediateCrash(); } +// Called from isolate thread. +// +// Attempts to allocate an array buffer of given size. If unsuccessful, no array +// is returned, instead of an OOM crash. +// +// The public V8 APIs don't expose native methods for trying to allocate an +// array buffer without the risk of an OOM crash. +// +// The returned buffer will not be resizable. +v8::MaybeLocal<v8::ArrayBuffer> JsSandboxIsolate::tryAllocateArrayBuffer( + const size_t length) { + void* buffer = array_buffer_allocator_->Allocate(length); + if (!buffer) { + // Encourage V8 to perform some garbage collection, which might result in + // previous array buffers getting deallocated. Note this won't free memory + // from the heap itself, but it will clean up any garbage which is keeping + // otherwise disused array buffers alive. + // + // Note that this may cause overly aggressive garbage collection, but is the + // only sensible API provided. + isolate_holder_->isolate()->LowMemoryNotification(); + // Try again after GC. + buffer = array_buffer_allocator_->Allocate(length); + if (!buffer) { + return v8::MaybeLocal<v8::ArrayBuffer>(); + } + } + + std::unique_ptr<v8::BackingStore> backing_store = + v8::ArrayBuffer::NewBackingStore( + buffer, length, + [](void* buffer_to_delete, size_t length, void* allocator) { + static_cast<v8::ArrayBuffer::Allocator*>(allocator)->Free( + buffer_to_delete, length); + }, + array_buffer_allocator_.get()); + + // We do not need to call AdjustAmountOfExternalAllocatedMemory ourselves. V8 + // will automatically call AdjustAmountOfExternalAllocatedMemory with the size + // of the backing store involved, which may further trigger garbage + // collections if memory usage is being unreasonable. This is done deep within + // the call to v8::ArrayBuffer::New(). + return v8::MaybeLocal<v8::ArrayBuffer>(v8::ArrayBuffer::New( + isolate_holder_->isolate(), std::move(backing_store))); +} + static void JNI_JsSandboxIsolate_InitializeEnvironment(JNIEnv* env) { base::ThreadPoolInstance::CreateAndStartWithDefaultParams("JsSandboxIsolate"); #ifdef V8_USE_EXTERNAL_STARTUP_DATA @@ -598,7 +711,9 @@ static jlong JNI_JsSandboxIsolate_CreateNativeJsSandboxIsolateWrapper( JNIEnv* env, jlong max_heap_size_bytes) { - JsSandboxIsolate* processor = new JsSandboxIsolate(max_heap_size_bytes); + CHECK_GE(max_heap_size_bytes, 0); + JsSandboxIsolate* processor = + new JsSandboxIsolate(base::saturated_cast<size_t>(max_heap_size_bytes)); return reinterpret_cast<intptr_t>(processor); }
diff --git a/android_webview/js_sandbox/service/js_sandbox_isolate.h b/android_webview/js_sandbox/service/js_sandbox_isolate.h index 59b69e4..12af034 100644 --- a/android_webview/js_sandbox/service/js_sandbox_isolate.h +++ b/android_webview/js_sandbox/service/js_sandbox_isolate.h
@@ -16,6 +16,7 @@ #include "base/memory/scoped_refptr.h" #include "base/synchronization/lock.h" #include "base/thread_annotations.h" +#include "v8/include/v8-array-buffer.h" #include "v8/include/v8-promise.h" namespace base { @@ -31,18 +32,18 @@ } // namespace gin namespace v8 { -class BackingStore; class ObjectTemplate; } // namespace v8 namespace android_webview { class FdWithLength; +class JsSandboxArrayBufferAllocator; class JsSandboxIsolateCallback; class JsSandboxIsolate { public: - explicit JsSandboxIsolate(jlong max_heap_size_bytes = 0); + explicit JsSandboxIsolate(size_t max_heap_size_bytes = 0); ~JsSandboxIsolate(); jboolean EvaluateJavascript( @@ -74,19 +75,31 @@ void PostEvaluationToIsolateThread( const std::string code, scoped_refptr<JsSandboxIsolateCallback> callback); - void ConvertPromiseToArrayBufferInThreadPool(base::ScopedFD fd, - ssize_t length, - std::string name); + void ConvertPromiseToArrayBufferInThreadPool( + base::ScopedFD fd, + ssize_t length, + std::string name, + std::unique_ptr<v8::Global<v8::ArrayBuffer>> array_buffer, + std::unique_ptr<v8::Global<v8::Promise::Resolver>> resolver, + void* inner_buffer); void ConvertPromiseToArrayBufferInControlSequence( std::string name, - std::unique_ptr<v8::BackingStore> backing_store); - void ConvertPromiseToFailureInControlSequence(std::string name, - std::string reason); - void ConvertPromiseToFailureInIsolateSequence(std::string name, - std::string reason); + std::unique_ptr<v8::Global<v8::ArrayBuffer>> array_buffer, + std::unique_ptr<v8::Global<v8::Promise::Resolver>> resolver); + void ConvertPromiseToFailureInControlSequence( + std::string name, + std::unique_ptr<v8::Global<v8::ArrayBuffer>> array_buffer, + std::unique_ptr<v8::Global<v8::Promise::Resolver>> resolver, + std::string reason); + void ConvertPromiseToFailureInIsolateSequence( + std::string name, + std::unique_ptr<v8::Global<v8::ArrayBuffer>> array_buffer, + std::unique_ptr<v8::Global<v8::Promise::Resolver>> resolver, + std::string reason); void ConvertPromiseToArrayBufferInIsolateSequence( std::string name, - std::unique_ptr<v8::BackingStore>); + std::unique_ptr<v8::Global<v8::ArrayBuffer>> array_buffer, + std::unique_ptr<v8::Global<v8::Promise::Resolver>> resolver); void ConsumeNamedDataAsArrayBuffer(gin::Arguments* args); v8::Local<v8::ObjectTemplate> CreateAndroidNamespaceTemplate( @@ -96,10 +109,20 @@ [[noreturn]] static size_t NearHeapLimitCallback(void* data, size_t current_heap_limit, size_t initial_heap_limit); + v8::MaybeLocal<v8::ArrayBuffer> tryAllocateArrayBuffer(size_t length); + // Must only be used from isolate thread [[noreturn]] void MemoryLimitExceeded(); [[noreturn]] void FreezeThread(); + // V8 heap size limit. Must be non-negative. + // + // 0 indicates no explicit limit (but use the default V8 limits). + const size_t isolate_max_heap_size_bytes_; + // Apart from construction/destruction, must only be used from the isolate + // thread. + std::unique_ptr<JsSandboxArrayBufferAllocator> array_buffer_allocator_; + // Used as a control sequence to add ordering to binder threadpool requests. scoped_refptr<base::SequencedTaskRunner> control_task_runner_; // Should be used from control_task_runner_. @@ -115,17 +138,10 @@ std::unique_ptr<gin::IsolateHolder> isolate_holder_; // Should be used from isolate_task_runner_. std::unique_ptr<gin::ContextHolder> context_holder_; - // Should be used from isolate_task_runner_. - std::unordered_map<std::string, v8::Global<v8::Promise::Resolver>> - named_resolver_; base::Lock named_fd_lock_; std::unordered_map<std::string, FdWithLength> named_fd_ GUARDED_BY(named_fd_lock_); - // V8 heap size limit. Must be non-negative. - // - // 0 indicates no explicit limit (but use the default V8 limits). - const jlong isolate_max_heap_size_bytes_; // The callback associated with the current evaluation, if any. Used for // signaling errors from V8 callbacks.
diff --git a/android_webview/js_sandbox/service/js_sandbox_isolate_callback.cc b/android_webview/js_sandbox/service/js_sandbox_isolate_callback.cc index d596c51..38ef6a5 100644 --- a/android_webview/js_sandbox/service/js_sandbox_isolate_callback.cc +++ b/android_webview/js_sandbox/service/js_sandbox_isolate_callback.cc
@@ -37,7 +37,8 @@ void JsSandboxIsolateCallback::ReportMemoryLimitExceededError( const uint64_t memory_limit, - const uint64_t heap_usage) { + const uint64_t v8_heap_usage, + const uint64_t non_v8_heap_usage) { std::ostringstream details; details << "Memory limit exceeded.\n"; if (memory_limit > 0) { @@ -45,7 +46,8 @@ } else { details << "Memory limit not explicitly configured\n"; } - details << "Heap usage: " << heap_usage << " bytes\n"; + details << "V8 heap usage: " << v8_heap_usage << " bytes\n"; + details << "Non-V8 heap usage: " << non_v8_heap_usage << " bytes\n"; ReportError(ErrorType::kMemoryLimitExceeded, details.str()); }
diff --git a/android_webview/js_sandbox/service/js_sandbox_isolate_callback.h b/android_webview/js_sandbox/service/js_sandbox_isolate_callback.h index b3e8b06..b5f9b0d 100644 --- a/android_webview/js_sandbox/service/js_sandbox_isolate_callback.h +++ b/android_webview/js_sandbox/service/js_sandbox_isolate_callback.h
@@ -32,9 +32,12 @@ // // memory_limit == 0 indicates that no explicit limit was configured. // - // heap_usage describes memory usage before the failed allocation. + // v8_heap_usage describes V8-internal (V8 heap) memory usage before the + // failed allocation. non_v8_heap_usage describes memory usage external to the + // V8 heap. void ReportMemoryLimitExceededError(uint64_t memory_limit, - uint64_t heap_usage); + uint64_t v8_heap_usage, + uint64_t non_v8_heap_usage); private: friend class base::RefCounted<JsSandboxIsolateCallback>;
diff --git a/ash/constants/ash_switches.cc b/ash/constants/ash_switches.cc index 69d41178..365307c 100644 --- a/ash/constants/ash_switches.cc +++ b/ash/constants/ash_switches.cc
@@ -697,6 +697,9 @@ // 2. A terminal to start lacros-chrome with a debugger. const char kLacrosMojoSocketForTesting[] = "lacros-mojo-socket-for-testing"; +// When this flag is set, the lacros-selection policy is ignored. +const char kLacrosSelectionPolicyIgnore[] = "lacros-selection-policy-ignore"; + // Start Chrome in RMA mode. Launches RMA app automatically. // kRmaNotAllowed switch takes priority over this one. const char kLaunchRma[] = "launch-rma";
diff --git a/ash/constants/ash_switches.h b/ash/constants/ash_switches.h index cb06ce94..31e144f1 100644 --- a/ash/constants/ash_switches.h +++ b/ash/constants/ash_switches.h
@@ -221,6 +221,8 @@ COMPONENT_EXPORT(ASH_CONSTANTS) extern const char kLacrosChromeAdditionalEnv[]; COMPONENT_EXPORT(ASH_CONSTANTS) extern const char kLacrosChromePath[]; COMPONENT_EXPORT(ASH_CONSTANTS) extern const char kLacrosMojoSocketForTesting[]; +COMPONENT_EXPORT(ASH_CONSTANTS) +extern const char kLacrosSelectionPolicyIgnore[]; COMPONENT_EXPORT(ASH_CONSTANTS) extern const char kLaunchRma[]; COMPONENT_EXPORT(ASH_CONSTANTS) extern const char kLoginManager[]; COMPONENT_EXPORT(ASH_CONSTANTS) extern const char kLoginProfile[];
diff --git a/ash/login/ui/login_auth_user_view.cc b/ash/login/ui/login_auth_user_view.cc index 9b910eb2..4d9ac0c4 100644 --- a/ash/login/ui/login_auth_user_view.cc +++ b/ash/login/ui/login_auth_user_view.cc
@@ -1109,7 +1109,7 @@ gfx::Size(/*ignored*/ 0, kPinPasswordToggleButtonHeight)); auto pin_view = std::make_unique<LoginPinView>( - LoginPinView::Style::kAlphanumeric, + LoginPinView::Style::kNumeric, base::BindRepeating(&LoginAuthUserView::OnPinPadInsertDigit, base::Unretained(this)), base::BindRepeating(&LoginAuthUserView::OnPinPadBackspace,
diff --git a/build/config/compiler/BUILD.gn b/build/config/compiler/BUILD.gn index ef3285a..1b1a3cb 100644 --- a/build/config/compiler/BUILD.gn +++ b/build/config/compiler/BUILD.gn
@@ -169,6 +169,11 @@ # For use by tools/clang/scripts/analyze_includes.py show_includes = false + # Enable Profi algorithm. Profi can infer block and edge counts. + # https://clang.llvm.org/docs/UsersManual.html#using-sampling-profilers + # TODO(crbug.com/1375958i:) Possibly enable this for Android too. + use_profi = is_chromeos + # If true, linker crashes will be rerun with `--reproduce` which causes # a reproducer file to be saved. save_reproducers_on_lld_crash = false @@ -2344,6 +2349,9 @@ rebased_clang_sample_profile = rebase_path(_clang_sample_profile, root_build_dir) cflags += [ "-fprofile-sample-use=${rebased_clang_sample_profile}" ] + if (use_profi) { + cflags += [ "-fsample-profile-use-profi" ] + } inputs = [ _clang_sample_profile ] } } else if (auto_profile_path != "" && is_a_target_toolchain) {
diff --git a/chrome/app/password_manager_ui_strings.grdp b/chrome/app/password_manager_ui_strings.grdp index c4098a7..a97c31b 100644 --- a/chrome/app/password_manager_ui_strings.grdp +++ b/chrome/app/password_manager_ui_strings.grdp
@@ -256,4 +256,16 @@ <message name="IDS_PASSWORD_MANAGER_UI_HELP" desc="Aria label for the help button in the toolbar."> Help </message> + +<if expr="is_macosx"> + <message name="IDS_PASSWORD_MANAGER_UI_BIOMETRIC_AUTHENTICATION_FOR_FILLING_TOGGLE_LABEL_MAC" desc="Label for a toggle that allows users to be authenticate using their TouchID or any other means of authentication when logging into webpages."> + Use your screen lock when filling passwords + </message> +</if> + +<if expr="is_win"> + <message name="IDS_PASSWORD_MANAGER_UI_BIOMETRIC_AUTHENTICATION_FOR_FILLING_TOGGLE_LABEL_WIN" desc="Label for a toggle that allows users to be authenticate using their Windows Hello when logging into webpages."> + Use Windows Hello when filling passwords + </message> +</if> </grit-part>
diff --git a/chrome/app/password_manager_ui_strings_grdp/IDS_PASSWORD_MANAGER_UI_BIOMETRIC_AUTHENTICATION_FOR_FILLING_TOGGLE_LABEL_MAC.png.sha1 b/chrome/app/password_manager_ui_strings_grdp/IDS_PASSWORD_MANAGER_UI_BIOMETRIC_AUTHENTICATION_FOR_FILLING_TOGGLE_LABEL_MAC.png.sha1 new file mode 100644 index 0000000..29ebb03 --- /dev/null +++ b/chrome/app/password_manager_ui_strings_grdp/IDS_PASSWORD_MANAGER_UI_BIOMETRIC_AUTHENTICATION_FOR_FILLING_TOGGLE_LABEL_MAC.png.sha1
@@ -0,0 +1 @@ +fe2659f18f8a6273e533c22be381355ce82393b9 \ No newline at end of file
diff --git a/chrome/app/password_manager_ui_strings_grdp/IDS_PASSWORD_MANAGER_UI_BIOMETRIC_AUTHENTICATION_FOR_FILLING_TOGGLE_LABEL_WIN.png.sha1 b/chrome/app/password_manager_ui_strings_grdp/IDS_PASSWORD_MANAGER_UI_BIOMETRIC_AUTHENTICATION_FOR_FILLING_TOGGLE_LABEL_WIN.png.sha1 new file mode 100644 index 0000000..86e5e6f8 --- /dev/null +++ b/chrome/app/password_manager_ui_strings_grdp/IDS_PASSWORD_MANAGER_UI_BIOMETRIC_AUTHENTICATION_FOR_FILLING_TOGGLE_LABEL_WIN.png.sha1
@@ -0,0 +1 @@ +cc46aa505cc4af0cda1b51980e504b3879820817 \ No newline at end of file
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc index c779716e..ac12452 100644 --- a/chrome/browser/about_flags.cc +++ b/chrome/browser/about_flags.cc
@@ -955,6 +955,9 @@ crosapi::browser_util::kLacrosSelectionRootfs}, }; +const char kLacrosSelectionPolicyIgnoreInternalName[] = + "lacros-selection-ignore"; + const FeatureEntry::Choice kLacrosAvailabilityPolicyChoices[] = { {flags_ui::kGenericExperimentChoiceDefault, "", ""}, {crosapi::browser_util::kLacrosAvailabilityPolicyUserChoice, @@ -3634,6 +3637,10 @@ {kLacrosSelectionInternalName, flag_descriptions::kLacrosSelectionName, flag_descriptions::kLacrosSelectionDescription, kOsCrOS, MULTI_VALUE_TYPE(kLacrosSelectionChoices)}, + {kLacrosSelectionPolicyIgnoreInternalName, + flag_descriptions::kLacrosSelectionPolicyIgnoreName, + flag_descriptions::kLacrosSelectionPolicyIgnoreDescription, kOsCrOS, + SINGLE_VALUE_TYPE(ash::switches::kLacrosSelectionPolicyIgnore)}, {kWebAppsCrosapiInternalName, flag_descriptions::kWebAppsCrosapiName, flag_descriptions::kWebAppsCrosapiDescription, kOsCrOS, FEATURE_VALUE_TYPE(features::kWebAppsCrosapi)}, @@ -9389,6 +9396,11 @@ entry.internal_name)) { return true; } + // Skip lacros-selection if it is controlled by LacrosSelection policy. + if (!strcmp(kLacrosSelectionInternalName, entry.internal_name)) { + return crosapi::browser_util::GetCachedLacrosSelectionPolicy() != + crosapi::browser_util::LacrosSelectionPolicy::kUserChoice; + } if (!strcmp(kPreferDcheckInternalName, entry.internal_name)) { return !crosapi::browser_util::IsLacrosAllowedToBeEnabled();
diff --git a/chrome/browser/ash/crosapi/browser_loader.cc b/chrome/browser/ash/crosapi/browser_loader.cc index 71055bc..01e29da 100644 --- a/chrome/browser/ash/crosapi/browser_loader.cc +++ b/chrome/browser/ash/crosapi/browser_loader.cc
@@ -206,21 +206,20 @@ return; } - // If the user has specified to force using stateful or rootfs lacros-chrome - // binary, force the selection. - const base::CommandLine* cmdline = base::CommandLine::ForCurrentProcess(); - if (cmdline->HasSwitch(browser_util::kLacrosSelectionSwitch)) { + // If the LacrosSelection policy or the user have specified to force using + // stateful or rootfs lacros-chrome binary, force the selection. Otherwise, + // load the newest available binary. + if (absl::optional<browser_util::LacrosSelection> lacros_selection = + browser_util::DetermineLacrosSelection()) { // TODO(crbug.com/1293250): We should check the version compatibility here, // too. - auto value = - cmdline->GetSwitchValueASCII(browser_util::kLacrosSelectionSwitch); - if (value == browser_util::kLacrosSelectionRootfs) { - LoadRootfsLacros(std::move(callback)); - return; - } - if (value == browser_util::kLacrosSelectionStateful) { - LoadStatefulLacros(std::move(callback)); - return; + switch (lacros_selection.value()) { + case browser_util::LacrosSelection::kRootfs: + LoadRootfsLacros(std::move(callback)); + return; + case browser_util::LacrosSelection::kStateful: + LoadStatefulLacros(std::move(callback)); + return; } }
diff --git a/chrome/browser/ash/crosapi/browser_loader_unittest.cc b/chrome/browser/ash/crosapi/browser_loader_unittest.cc index 5e9882b0..bcee937 100644 --- a/chrome/browser/ash/crosapi/browser_loader_unittest.cc +++ b/chrome/browser/ash/crosapi/browser_loader_unittest.cc
@@ -10,13 +10,17 @@ #include "base/run_loop.h" #include "base/strings/utf_string_conversions.h" #include "base/test/bind.h" +#include "base/test/scoped_command_line.h" #include "base/test/scoped_feature_list.h" +#include "base/test/test_future.h" #include "chrome/browser/ash/crosapi/browser_util.h" #include "chrome/browser/browser_process.h" #include "chrome/browser/component_updater/fake_cros_component_manager.h" #include "chrome/test/base/browser_process_platform_part_test_api_chromeos.h" #include "chromeos/ash/components/dbus/upstart/fake_upstart_client.h" #include "components/component_updater/mock_component_updater_service.h" +#include "components/policy/core/common/policy_map.h" +#include "components/policy/policy_constants.h" #include "components/update_client/update_client.h" #include "content/public/test/browser_task_environment.h" #include "testing/gtest/include/gtest/gtest.h" @@ -32,6 +36,33 @@ constexpr char kLacrosMounterUpstartJob[] = "lacros_2dmounter"; constexpr char kLacrosUnmounterUpstartJob[] = "lacros_2dunmounter"; +// This implementation of RAII for LacrosSelection is to make it easy reset +// the state between runs. +class ScopedLacrosSelectionCache { + public: + explicit ScopedLacrosSelectionCache( + browser_util::LacrosSelectionPolicy lacros_selection) { + SetLacrosSelection(lacros_selection); + } + ScopedLacrosSelectionCache(const ScopedLacrosSelectionCache&) = delete; + ScopedLacrosSelectionCache& operator=(const ScopedLacrosSelectionCache&) = + delete; + ~ScopedLacrosSelectionCache() { + browser_util::ClearLacrosSelectionCacheForTest(); + } + + private: + void SetLacrosSelection( + browser_util::LacrosSelectionPolicy lacros_selection) { + policy::PolicyMap policy; + policy.Set(policy::key::kLacrosSelection, policy::POLICY_LEVEL_MANDATORY, + policy::POLICY_SCOPE_USER, policy::POLICY_SOURCE_CLOUD, + base::Value(GetLacrosSelectionPolicyName(lacros_selection)), + /*external_data_fetcher=*/nullptr); + browser_util::CacheLacrosSelection(policy); + } +}; + } // namespace class BrowserLoaderTest : public testing::Test { @@ -229,4 +260,70 @@ EXPECT_TRUE(callback_called); } +TEST_F(BrowserLoaderTest, OnLoadSelectionPolicyIsRootfs) { + ScopedLacrosSelectionCache cache( + browser_util::LacrosSelectionPolicy::kRootfs); + base::test::ScopedCommandLine command_line; + command_line.GetProcessCommandLine()->AppendSwitchASCII( + browser_util::kLacrosSelectionSwitch, + browser_util::kLacrosSelectionStateful); + + base::test::TestFuture<base::FilePath, LacrosSelection, base::Version> future; + browser_loader_->Load(future.GetCallback<const base::FilePath&, + LacrosSelection, base::Version>()); + + const LacrosSelection selection = future.Get<1>(); + EXPECT_EQ(selection, LacrosSelection::kRootfs); +} + +TEST_F(BrowserLoaderTest, OnLoadSelectionPolicyIsStateful) { + ScopedLacrosSelectionCache cache( + browser_util::LacrosSelectionPolicy::kStateful); + base::test::ScopedCommandLine command_line; + command_line.GetProcessCommandLine()->AppendSwitchASCII( + browser_util::kLacrosSelectionSwitch, + browser_util::kLacrosSelectionRootfs); + + base::test::TestFuture<base::FilePath, LacrosSelection, base::Version> future; + browser_loader_->Load(future.GetCallback<const base::FilePath&, + LacrosSelection, base::Version>()); + + const LacrosSelection selection = future.Get<1>(); + EXPECT_EQ(selection, LacrosSelection::kStateful); +} + +TEST_F(BrowserLoaderTest, + OnLoadSelectionPolicyIsUserChoiceAndCommandLineIsRootfs) { + ScopedLacrosSelectionCache cache( + browser_util::LacrosSelectionPolicy::kUserChoice); + base::test::ScopedCommandLine command_line; + command_line.GetProcessCommandLine()->AppendSwitchASCII( + browser_util::kLacrosSelectionSwitch, + browser_util::kLacrosSelectionRootfs); + + base::test::TestFuture<base::FilePath, LacrosSelection, base::Version> future; + browser_loader_->Load(future.GetCallback<const base::FilePath&, + LacrosSelection, base::Version>()); + + const LacrosSelection selection = future.Get<1>(); + EXPECT_EQ(selection, LacrosSelection::kRootfs); +} + +TEST_F(BrowserLoaderTest, + OnLoadSelectionPolicyIsUserChoiceAndCommandLineIsStateful) { + ScopedLacrosSelectionCache cache( + browser_util::LacrosSelectionPolicy::kUserChoice); + base::test::ScopedCommandLine command_line; + command_line.GetProcessCommandLine()->AppendSwitchASCII( + browser_util::kLacrosSelectionSwitch, + browser_util::kLacrosSelectionStateful); + + base::test::TestFuture<base::FilePath, LacrosSelection, base::Version> future; + browser_loader_->Load(future.GetCallback<const base::FilePath&, + LacrosSelection, base::Version>()); + + const LacrosSelection selection = future.Get<1>(); + EXPECT_EQ(selection, LacrosSelection::kStateful); +} + } // namespace crosapi
diff --git a/chrome/browser/ash/crosapi/browser_util.cc b/chrome/browser/ash/crosapi/browser_util.cc index 718c350..8852866 100644 --- a/chrome/browser/ash/crosapi/browser_util.cc +++ b/chrome/browser/ash/crosapi/browser_util.cc
@@ -64,6 +64,10 @@ absl::optional<LacrosDataBackwardMigrationMode> g_lacros_data_backward_migration_mode; +// At session start the value for LacrosSelection logic is applied and the +// result is stored in this variable which is used after that as a cache. +absl::optional<LacrosSelectionPolicy> g_lacros_selection_cache; + // The rootfs lacros-chrome metadata keys. constexpr char kLacrosMetadataContentKey[] = "content"; constexpr char kLacrosMetadataVersionKey[] = "version"; @@ -906,6 +910,60 @@ value ? value->GetString() : base::StringPiece()); } +void CacheLacrosSelection(const policy::PolicyMap& map) { + if (g_lacros_selection_cache.has_value()) { + // Some browser tests might call this multiple times. + LOG(ERROR) << "Trying to cache LacrosSelection and the value was set"; + return; + } + + // Users can set this switch in chrome://flags to disable the effect of the + // lacros-selection policy. This should only be allows for googlers. + const base::CommandLine* cmdline = base::CommandLine::ForCurrentProcess(); + if (cmdline->HasSwitch(ash::switches::kLacrosSelectionPolicyIgnore) && + IsGoogleInternal()) { + LOG(WARNING) << "LacrosSelection policy is ignored due to the ignore flag"; + return; + } + + const base::Value* value = + map.GetValue(policy::key::kLacrosSelection, base::Value::Type::STRING); + g_lacros_selection_cache = ParseLacrosSelectionPolicy( + value ? value->GetString() : base::StringPiece()); +} + +LacrosSelectionPolicy GetCachedLacrosSelectionPolicy() { + return g_lacros_selection_cache.value_or(LacrosSelectionPolicy::kUserChoice); +} + +absl::optional<LacrosSelection> DetermineLacrosSelection() { + switch (GetCachedLacrosSelectionPolicy()) { + case LacrosSelectionPolicy::kRootfs: + return LacrosSelection::kRootfs; + case LacrosSelectionPolicy::kStateful: + return LacrosSelection::kStateful; + case LacrosSelectionPolicy::kUserChoice: + break; + } + + const base::CommandLine* cmdline = base::CommandLine::ForCurrentProcess(); + + if (!cmdline->HasSwitch(browser_util::kLacrosSelectionSwitch)) { + return absl::nullopt; + } + + auto value = + cmdline->GetSwitchValueASCII(browser_util::kLacrosSelectionSwitch); + if (value == browser_util::kLacrosSelectionRootfs) { + return LacrosSelection::kRootfs; + } + if (value == browser_util::kLacrosSelectionStateful) { + return LacrosSelection::kStateful; + } + + return absl::nullopt; +} + ComponentInfo GetLacrosComponentInfoForChannel(version_info::Channel channel) { // We default to the Dev component for UNKNOWN channels. static const auto kChannelToComponentInfoMap = @@ -980,6 +1038,10 @@ g_lacros_data_backward_migration_mode.reset(); } +void ClearLacrosSelectionCacheForTest() { + g_lacros_selection_cache.reset(); +} + MigrationMode GetMigrationMode(const user_manager::User* user, PolicyInitState policy_init_state) { if (base::FeatureList::IsEnabled( @@ -1134,6 +1196,17 @@ return base::StringPiece(); } +base::StringPiece GetLacrosSelectionPolicyName(LacrosSelectionPolicy value) { + for (const auto& entry : kLacrosSelectionPolicyMap) { + if (entry.second == value) { + return entry.first; + } + } + + NOTREACHED(); + return base::StringPiece(); +} + bool IsAshBrowserSyncEnabled() { // Turn off sync from Ash if Lacros is enabled and Ash web browser is // disabled.
diff --git a/chrome/browser/ash/crosapi/browser_util.h b/chrome/browser/ash/crosapi/browser_util.h index 530ff03e..f8fad6ed 100644 --- a/chrome/browser/ash/crosapi/browser_util.h +++ b/chrome/browser/ash/crosapi/browser_util.h
@@ -348,6 +348,20 @@ // LacrosDataBackwardMigrationMode policy. void CacheLacrosDataBackwardMigrationMode(const policy::PolicyMap& map); +// To be called at primary user login, to cache the policy value for +// LacrosSelection policy. The effective value of the policy does not +// change for the duration of the user session, so cached value shall be +// checked. +void CacheLacrosSelection(const policy::PolicyMap& map); + +// Returns cached value of LacrosSelection policy. See `CacheLacrosSelection` +// for details. +LacrosSelectionPolicy GetCachedLacrosSelectionPolicy(); + +// Returns lacros selection option according to LarcrosSelectionPolicy and +// lacros-selection flag. Returns nullopt if there is no preference. +absl::optional<LacrosSelection> DetermineLacrosSelection(); + // Returns the lacros ComponentInfo for a given channel. ComponentInfo GetLacrosComponentInfoForChannel(version_info::Channel channel); @@ -383,6 +397,9 @@ // Clears the cached value for LacrosDataBackwardMigrationMode. void ClearLacrosDataBackwardMigrationModeCacheForTest(); +// Clears the cached value for LacrosSelection policy. +void ClearLacrosSelectionCacheForTest(); + bool IsProfileMigrationEnabled(const AccountId& account_id); // Returns true if the profile migration can run, but not yet completed. @@ -466,6 +483,10 @@ base::StringPiece GetLacrosDataBackwardMigrationModeName( LacrosDataBackwardMigrationMode value); +// Returns the LacrosSelection policy value name from the given value. Returned +// StringPiece is guaranteed to never be invalidated. +base::StringPiece GetLacrosSelectionPolicyName(LacrosSelectionPolicy value); + // Stores that "Go to files button" on the migration error screen is clicked. void SetGotoFilesClicked(PrefService* local_state, const std::string& user_id_hash);
diff --git a/chrome/browser/ash/crosapi/browser_util_unittest.cc b/chrome/browser/ash/crosapi/browser_util_unittest.cc index e98e03497..4d4ce85 100644 --- a/chrome/browser/ash/crosapi/browser_util_unittest.cc +++ b/chrome/browser/ash/crosapi/browser_util_unittest.cc
@@ -69,6 +69,33 @@ } }; +// This implementation of RAII for LacrosSelection is to make it easy reset +// the state between runs. +class ScopedLacrosSelectionCache { + public: + explicit ScopedLacrosSelectionCache( + browser_util::LacrosSelectionPolicy lacros_selection) { + SetLacrosSelection(lacros_selection); + } + ScopedLacrosSelectionCache(const ScopedLacrosSelectionCache&) = delete; + ScopedLacrosSelectionCache& operator=(const ScopedLacrosSelectionCache&) = + delete; + ~ScopedLacrosSelectionCache() { + browser_util::ClearLacrosSelectionCacheForTest(); + } + + private: + void SetLacrosSelection( + browser_util::LacrosSelectionPolicy lacros_selection) { + policy::PolicyMap policy; + policy.Set(policy::key::kLacrosSelection, policy::POLICY_LEVEL_MANDATORY, + policy::POLICY_SCOPE_USER, policy::POLICY_SOURCE_CLOUD, + base::Value(GetLacrosSelectionPolicyName(lacros_selection)), + /*external_data_fetcher=*/nullptr); + browser_util::CacheLacrosSelection(policy); + } +}; + } // namespace class BrowserUtilTest : public testing::Test { @@ -1118,4 +1145,116 @@ EXPECT_EQ(serial_number.value(), expected_serial_number); } +TEST_F(BrowserUtilTest, LacrosSelection) { + // Neither policy nor command line have any preference on Lacros selection. + EXPECT_FALSE(browser_util::DetermineLacrosSelection()); + + { + // LacrosSelection policy has precedence over command line. + ScopedLacrosSelectionCache cache( + browser_util::LacrosSelectionPolicy::kRootfs); + base::test::ScopedCommandLine cmd_line; + cmd_line.GetProcessCommandLine()->AppendSwitchASCII( + browser_util::kLacrosSelectionSwitch, + browser_util::kLacrosSelectionStateful); + EXPECT_EQ(browser_util::DetermineLacrosSelection(), + LacrosSelection::kRootfs); + } + + { + // LacrosSelection policy has precedence over command line. + ScopedLacrosSelectionCache cache( + browser_util::LacrosSelectionPolicy::kStateful); + base::test::ScopedCommandLine cmd_line; + cmd_line.GetProcessCommandLine()->AppendSwitchASCII( + browser_util::kLacrosSelectionSwitch, + browser_util::kLacrosSelectionRootfs); + EXPECT_EQ(browser_util::DetermineLacrosSelection(), + LacrosSelection::kStateful); + } + + { + // LacrosSelection allows command line check, but command line is not set. + ScopedLacrosSelectionCache cache( + browser_util::LacrosSelectionPolicy::kUserChoice); + EXPECT_FALSE(browser_util::DetermineLacrosSelection()); + } + + { + // LacrosSelection allows command line check. + ScopedLacrosSelectionCache cache( + browser_util::LacrosSelectionPolicy::kUserChoice); + base::test::ScopedCommandLine cmd_line; + cmd_line.GetProcessCommandLine()->AppendSwitchASCII( + browser_util::kLacrosSelectionSwitch, + browser_util::kLacrosSelectionRootfs); + EXPECT_EQ(browser_util::DetermineLacrosSelection(), + LacrosSelection::kRootfs); + } + + { + // LacrosSelection allows command line check. + ScopedLacrosSelectionCache cache( + browser_util::LacrosSelectionPolicy::kUserChoice); + base::test::ScopedCommandLine cmd_line; + cmd_line.GetProcessCommandLine()->AppendSwitchASCII( + browser_util::kLacrosSelectionSwitch, + browser_util::kLacrosSelectionStateful); + EXPECT_EQ(browser_util::DetermineLacrosSelection(), + LacrosSelection::kStateful); + } +} + +// LacrosSelection has no effect on non-googlers. +TEST_F(BrowserUtilTest, LacrosSelectionPolicyIgnoreNonGoogle) { + AddRegularUser("user@random.com"); + + base::test::ScopedCommandLine cmd_line; + cmd_line.GetProcessCommandLine()->AppendSwitch( + ash::switches::kLacrosSelectionPolicyIgnore); + + { + ScopedLacrosSelectionCache cache( + browser_util::LacrosSelectionPolicy::kRootfs); + EXPECT_EQ(browser_util::GetCachedLacrosSelectionPolicy(), + browser_util::LacrosSelectionPolicy::kRootfs); + EXPECT_EQ(browser_util::DetermineLacrosSelection(), + LacrosSelection::kRootfs); + } + + { + ScopedLacrosSelectionCache cache( + browser_util::LacrosSelectionPolicy::kStateful); + EXPECT_EQ(browser_util::GetCachedLacrosSelectionPolicy(), + browser_util::LacrosSelectionPolicy::kStateful); + EXPECT_EQ(browser_util::DetermineLacrosSelection(), + LacrosSelection::kStateful); + } +} + +// LacrosSelection has an effect on googlers. +TEST_F(BrowserUtilTest, LacrosSelectionPolicyIgnoreGoogleDisableToUserChoice) { + AddRegularUser("user@google.com"); + + base::test::ScopedCommandLine cmd_line; + cmd_line.GetProcessCommandLine()->AppendSwitch( + ash::switches::kLacrosSelectionPolicyIgnore); + + { + ScopedLacrosSelectionCache cache( + browser_util::LacrosSelectionPolicy::kRootfs); + EXPECT_EQ(browser_util::GetCachedLacrosSelectionPolicy(), + browser_util::LacrosSelectionPolicy::kUserChoice); + EXPECT_FALSE(browser_util::DetermineLacrosSelection()); + } + + { + ScopedLacrosSelectionCache cache( + browser_util::LacrosSelectionPolicy::kStateful); + EXPECT_EQ(browser_util::GetCachedLacrosSelectionPolicy(), + browser_util::LacrosSelectionPolicy::kUserChoice); + EXPECT_FALSE(browser_util::DetermineLacrosSelection()); + } +} + } // namespace crosapi
diff --git a/chrome/browser/ash/extensions/file_manager/private_api_sharesheet.cc b/chrome/browser/ash/extensions/file_manager/private_api_sharesheet.cc index e8c30e9..2c00f45 100644 --- a/chrome/browser/ash/extensions/file_manager/private_api_sharesheet.cc +++ b/chrome/browser/ash/extensions/file_manager/private_api_sharesheet.cc
@@ -190,6 +190,10 @@ if (params->urls.empty()) return RespondNow(Error("No URLs provided")); + if (params->dlp_source_urls.size() != params->urls.size()) { + return RespondNow(Error("Mismatching URLs and DLP source URLs provided")); + } + profile_ = Profile::FromBrowserContext(browser_context()); const scoped_refptr<storage::FileSystemContext> file_system_context = @@ -210,6 +214,8 @@ file_system_urls_.push_back(file_system_url); } + dlp_source_urls_ = std::move(params->dlp_source_urls); + mime_type_collector_ = std::make_unique<app_file_handler_util::MimeTypeCollector>(profile_); mime_type_collector_->CollectForURLs( @@ -249,7 +255,8 @@ } sharesheet_service->ShowBubble( - GetSenderWebContents(), apps_util::MakeShareIntent(urls_, *mime_types), + GetSenderWebContents(), + apps_util::MakeShareIntent(urls_, *mime_types, dlp_source_urls_), contains_hosted_document_, launch_source, base::NullCallback()); Respond(NoArguments()); }
diff --git a/chrome/browser/ash/extensions/file_manager/private_api_sharesheet.h b/chrome/browser/ash/extensions/file_manager/private_api_sharesheet.h index 480f321..c188e5bf 100644 --- a/chrome/browser/ash/extensions/file_manager/private_api_sharesheet.h +++ b/chrome/browser/ash/extensions/file_manager/private_api_sharesheet.h
@@ -112,6 +112,7 @@ std::vector<GURL> urls_; Profile* profile_ = nullptr; std::vector<storage::FileSystemURL> file_system_urls_; + std::vector<std::string> dlp_source_urls_; bool contains_hosted_document_ = false; };
diff --git a/chrome/browser/ash/file_manager/file_manager_browsertest.cc b/chrome/browser/ash/file_manager/file_manager_browsertest.cc index b035d38..ada597f 100644 --- a/chrome/browser/ash/file_manager/file_manager_browsertest.cc +++ b/chrome/browser/ash/file_manager/file_manager_browsertest.cc
@@ -1466,7 +1466,8 @@ TestCase("driveDeleteDialogDoesntMentionPermanentDelete"), TestCase("driveInlineSyncStatusSingleFile").EnableInlineStatusSync(), TestCase("driveInlineSyncStatusParentFolder").EnableInlineStatusSync(), - TestCase("driveLocalDeleteUnpinsItem").EnableBulkPinning() + TestCase("driveLocalDeleteUnpinsItem").EnableBulkPinning(), + TestCase("driveCloudDeleteUnpinsItem").EnableBulkPinning() // TODO(b/189173190): Enable // TestCase("driveEnableDocsOfflineDialog"), // TODO(b/189173190): Enable @@ -1546,13 +1547,14 @@ TestCase("transferShowDlpToast").EnableDlp(), TestCase("dlpShowManagedIcon").EnableDlp(), TestCase("dlpContextMenuRestrictionDetails").EnableDlp(), - TestCase("saveAsDlpRestrictedDirectory").EnableDlp(), + TestCase("saveAsDlpRestrictedAndroid").EnableArcVm().EnableDlp(), TestCase("saveAsDlpRestrictedCrostini").EnableDlp(), TestCase("saveAsNonDlpRestricted").EnableDlp(), TestCase("saveAsDlpRestrictedRedirectsToMyFiles").EnableDlp(), TestCase("saveAsDlpRestrictedVm").EnableDlp(), TestCase("openDlpRestrictedFile").EnableDlp(), - TestCase("openFolderDlpRestricted").EnableDlp())); + TestCase("openFolderDlpRestricted").EnableDlp(), + TestCase("fileTasksDlpRestricted").EnableDlp())); WRAPPED_INSTANTIATE_TEST_SUITE_P( DriveSpecific, /* drive_specific.js */
diff --git a/chrome/browser/ash/file_manager/file_manager_browsertest_base.cc b/chrome/browser/ash/file_manager/file_manager_browsertest_base.cc index cab9633..d1381166 100644 --- a/chrome/browser/ash/file_manager/file_manager_browsertest_base.cc +++ b/chrome/browser/ash/file_manager/file_manager_browsertest_base.cc
@@ -1321,6 +1321,21 @@ drivefs_delegate.FlushForTesting(); } + void SendCloudDeleteEvent(const std::string& path) { + const base::FilePath file_path(path); + absl::optional<drivefs::FakeDriveFs::FileMetadata> metadata = + fake_drivefs_helper_->fake_drivefs().GetItemMetadata(file_path); + ASSERT_TRUE(metadata.has_value()) << "No file metadata with path: " << path; + + std::vector<drivefs::mojom::FileChangePtr> file_changes; + file_changes.emplace_back(absl::in_place, file_path, + drivefs::mojom::FileChange::Type::kDelete, + metadata.value().stable_id); + auto& drivefs_delegate = fake_drivefs_helper_->fake_drivefs().delegate(); + drivefs_delegate->OnFilesChanged(std::move(file_changes)); + drivefs_delegate.FlushForTesting(); + } + absl::optional<drivefs::mojom::DialogResult> last_dialog_result() { return last_dialog_result_; } @@ -3266,6 +3281,13 @@ return; } + if (name == "sendDriveCloudDeleteEvent") { + const std::string* path = value.FindString("path"); + ASSERT_TRUE(path) << "No supplied path to sendDriveFilesChangedEvent"; + drive_volume_->SendCloudDeleteEvent(*path); + return; + } + if (HandleGuestOsCommands(name, value, output)) { return; }
diff --git a/chrome/browser/ash/file_manager/file_manager_string_util.cc b/chrome/browser/ash/file_manager/file_manager_string_util.cc index 85e1934..d8f37de 100644 --- a/chrome/browser/ash/file_manager/file_manager_string_util.cc +++ b/chrome/browser/ash/file_manager/file_manager_string_util.cc
@@ -972,6 +972,7 @@ IDS_FILE_BROWSER_MOVE_TO_TRASH_ITEMS_REMAINING); SET_STRING("UNKNOWN_FILESYSTEM_WARNING", IDS_FILE_BROWSER_UNKNOWN_FILESYSTEM_WARNING); + SET_STRING("UNMOUNT_BUTTON_LABEL", IDS_FILE_BROWSER_UNMOUNT_BUTTON_LABEL); SET_STRING("UNMOUNT_DEVICE_BUTTON_LABEL", IDS_FILE_BROWSER_UNMOUNT_DEVICE_BUTTON_LABEL); SET_STRING("UNMOUNT_FAILED", IDS_FILE_BROWSER_UNMOUNT_FAILED);
diff --git a/chrome/browser/ash/login/screens/gaia_screen.cc b/chrome/browser/ash/login/screens/gaia_screen.cc index 536e59b..26ce976b 100644 --- a/chrome/browser/ash/login/screens/gaia_screen.cc +++ b/chrome/browser/ash/login/screens/gaia_screen.cc
@@ -34,14 +34,6 @@ return false; } - // Always return `true` if the testing switch is set. It will allow to test - // the recovery without triggering the real recovery conditions which may be - // difficult as of now. - if (base::CommandLine::ForCurrentProcess()->HasSwitch( - switches::kForceCryptohomeRecoveryForTesting)) { - return true; - } - // Cryptohome recovery is probably needed when password is entered incorrectly // for many times or password changed. // TODO(b/197615068): Add metric to record the number of times we prepared for @@ -99,6 +91,16 @@ view_->SetGaiaPath(gaia_path); view_->SetReauthRequestToken(std::string()); + // Always fetch Gaia reauth request token if the testing switch is set. It + // will allow to test the recovery without triggering the real recovery + // conditions which may be difficult as of now. + if (base::CommandLine::ForCurrentProcess()->HasSwitch( + switches::kForceCryptohomeRecoveryForTesting)) { + DCHECK(features::IsCryptohomeRecoveryFlowEnabled()); + FetchGaiaReauthToken(account); + return; + } + if (ShouldPrepareForRecovery(account)) { auto user_context = std::make_unique<UserContext>(); user_context->SetAccountId(account); @@ -216,16 +218,19 @@ bool is_configured = config.HasConfiguredFactor(cryptohome::AuthFactorType::kRecovery); if (is_configured) { - gaia_reauth_token_fetcher_ = - std::make_unique<GaiaReauthTokenFetcher>(base::BindOnce( - &GaiaScreen::OnGaiaReauthTokenFetched, - weak_ptr_factory_.GetWeakPtr(), user_context->GetAccountId())); - gaia_reauth_token_fetcher_->Fetch(); + FetchGaiaReauthToken(user_context->GetAccountId()); } else { view_->LoadGaiaAsync(user_context->GetAccountId()); } } +void GaiaScreen::FetchGaiaReauthToken(const AccountId& account) { + gaia_reauth_token_fetcher_ = std::make_unique<GaiaReauthTokenFetcher>( + base::BindOnce(&GaiaScreen::OnGaiaReauthTokenFetched, + weak_ptr_factory_.GetWeakPtr(), account)); + gaia_reauth_token_fetcher_->Fetch(); +} + void GaiaScreen::OnGaiaReauthTokenFetched(const AccountId& account, const std::string& token) { gaia_reauth_token_fetcher_.reset();
diff --git a/chrome/browser/ash/login/screens/gaia_screen.h b/chrome/browser/ash/login/screens/gaia_screen.h index cf03551..9a2c509 100644 --- a/chrome/browser/ash/login/screens/gaia_screen.h +++ b/chrome/browser/ash/login/screens/gaia_screen.h
@@ -75,6 +75,8 @@ void OnGetAuthFactorsConfiguration(std::unique_ptr<UserContext> user_context, absl::optional<AuthenticationError> error); + // Fetch Gaia reauth request token from the recovery service. + void FetchGaiaReauthToken(const AccountId& account); void OnGaiaReauthTokenFetched(const AccountId& account, const std::string& token);
diff --git a/chrome/browser/chromeos/extensions/telemetry/api/base_telemetry_extension_api_guard_function_browsertest.cc b/chrome/browser/chromeos/extensions/telemetry/api/base_telemetry_extension_api_guard_function_browsertest.cc index ccf84687..dc393d14 100644 --- a/chrome/browser/chromeos/extensions/telemetry/api/base_telemetry_extension_api_guard_function_browsertest.cc +++ b/chrome/browser/chromeos/extensions/telemetry/api/base_telemetry_extension_api_guard_function_browsertest.cc
@@ -136,6 +136,15 @@ ); chrome.test.succeed(); }, + async function getUsbBusInfo() { + await chrome.test.assertPromiseRejects( + chrome.os.telemetry.getUsbBusInfo(), + 'Error: Unauthorized access to ' + + 'chrome.os.telemetry.getUsbBusInfo. ' + + '%s' + ); + chrome.test.succeed(); + }, async function getVpdInfo() { await chrome.test.assertPromiseRejects( chrome.os.telemetry.getVpdInfo(),
diff --git a/chrome/browser/chromeos/extensions/telemetry/api/telemetry_api.cc b/chrome/browser/chromeos/extensions/telemetry/api/telemetry_api.cc index c4e7b72..7538038 100644 --- a/chrome/browser/chromeos/extensions/telemetry/api/telemetry_api.cc +++ b/chrome/browser/chromeos/extensions/telemetry/api/telemetry_api.cc
@@ -386,6 +386,34 @@ Respond(ArgumentList(telemetry::GetTpmInfo::Results::Create(result))); } +// OsTelemetryGetUsbBusInfoFunction -------------------------------------------- + +void OsTelemetryGetUsbBusInfoFunction::RunIfAllowed() { + auto cb = base::BindOnce(&OsTelemetryGetUsbBusInfoFunction::OnResult, this); + + GetRemoteService()->ProbeTelemetryInfo( + {crosapi::mojom::ProbeCategoryEnum::kBus}, std::move(cb)); +} + +void OsTelemetryGetUsbBusInfoFunction::OnResult( + crosapi::mojom::ProbeTelemetryInfoPtr ptr) { + if (!ptr || !ptr->bus_result || !ptr->bus_result->is_bus_devices_info()) { + Respond(Error("API internal error")); + return; + } + + telemetry::UsbBusDevices result; + auto bus_infos = std::move(ptr->bus_result->get_bus_devices_info()); + for (auto& info : bus_infos) { + if (info->is_usb_bus_info()) { + result.devices.push_back(converters::ConvertPtr<telemetry::UsbBusInfo>( + std::move(info->get_usb_bus_info()))); + } + } + + Respond(ArgumentList(telemetry::GetUsbBusInfo::Results::Create(result))); +} + // OsTelemetryGetVpdInfoFunction ----------------------------------------------- void OsTelemetryGetVpdInfoFunction::RunIfAllowed() {
diff --git a/chrome/browser/chromeos/extensions/telemetry/api/telemetry_api.h b/chrome/browser/chromeos/extensions/telemetry/api/telemetry_api.h index 3e6111b..08f70ab0 100644 --- a/chrome/browser/chromeos/extensions/telemetry/api/telemetry_api.h +++ b/chrome/browser/chromeos/extensions/telemetry/api/telemetry_api.h
@@ -152,18 +152,6 @@ void OnResult(crosapi::mojom::ProbeTelemetryInfoPtr ptr); }; -class OsTelemetryGetVpdInfoFunction : public TelemetryApiFunctionBase { - DECLARE_EXTENSION_FUNCTION("os.telemetry.getVpdInfo", OS_TELEMETRY_GETVPDINFO) - - private: - ~OsTelemetryGetVpdInfoFunction() override = default; - - // BaseTelemetryExtensionApiGuardFunction: - void RunIfAllowed() override; - - void OnResult(crosapi::mojom::ProbeTelemetryInfoPtr ptr); -}; - class OsTelemetryGetStatefulPartitionInfoFunction : public TelemetryApiFunctionBase { DECLARE_EXTENSION_FUNCTION("os.telemetry.getStatefulPartitionInfo", @@ -190,6 +178,31 @@ void OnResult(crosapi::mojom::ProbeTelemetryInfoPtr ptr); }; +class OsTelemetryGetUsbBusInfoFunction : public TelemetryApiFunctionBase { + DECLARE_EXTENSION_FUNCTION("os.telemetry.getUsbBusInfo", + OS_TELEMETRY_GETUSBBUSINFO) + + private: + ~OsTelemetryGetUsbBusInfoFunction() override = default; + + // BaseTelemetryExtensionApiGuardFunction: + void RunIfAllowed() override; + + void OnResult(crosapi::mojom::ProbeTelemetryInfoPtr ptr); +}; + +class OsTelemetryGetVpdInfoFunction : public TelemetryApiFunctionBase { + DECLARE_EXTENSION_FUNCTION("os.telemetry.getVpdInfo", OS_TELEMETRY_GETVPDINFO) + + private: + ~OsTelemetryGetVpdInfoFunction() override = default; + + // BaseTelemetryExtensionApiGuardFunction: + void RunIfAllowed() override; + + void OnResult(crosapi::mojom::ProbeTelemetryInfoPtr ptr); +}; + } // namespace chromeos #endif // CHROME_BROWSER_CHROMEOS_EXTENSIONS_TELEMETRY_API_TELEMETRY_API_H_
diff --git a/chrome/browser/chromeos/extensions/telemetry/api/telemetry_api_browsertest.cc b/chrome/browser/chromeos/extensions/telemetry/api/telemetry_api_browsertest.cc index 06358b9..8cc87026 100644 --- a/chrome/browser/chromeos/extensions/telemetry/api/telemetry_api_browsertest.cc +++ b/chrome/browser/chromeos/extensions/telemetry/api/telemetry_api_browsertest.cc
@@ -1411,6 +1411,30 @@ )"); } +IN_PROC_BROWSER_TEST_F(TelemetryExtensionTelemetryApiBrowserTest, + GetUsbBusInfo_NoFeatureFlagEnabledError) { +#if BUILDFLAG(IS_CHROMEOS_LACROS) + // If Probe interface is not available on this version of ash-chrome, this + // test suite will no-op. + if (!IsServiceAvailable()) { + return; + } +#endif // BUILDFLAG(IS_CHROMEOS_LACROS) + + // If the permission is not enabled, the method isn't defined + // on `chrome.os.telemetry`. + CreateExtensionAndRunServiceWorker(R"( + chrome.test.runTests([ + function getUsbBusInfo() { + chrome.test.assertThrows(() => { chrome.os.telemetry.getUsbBusInfo(); }, + [], "chrome.os.telemetry.getUsbBusInfo is not a function"); + + chrome.test.succeed(); + } + ]); + )"); +} + class TelemetryExtensionTelemetryApiWithoutAdditionalPermissionsBrowserTest : public TelemetryExtensionTelemetryApiBrowserTest { public: @@ -1673,4 +1697,140 @@ )"); } +class PendingApprovalTelemetryExtensionTelemetryApiBrowserTest + : public TelemetryExtensionTelemetryApiBrowserTest { + public: + PendingApprovalTelemetryExtensionTelemetryApiBrowserTest() { + feature_list_.InitAndEnableFeature( + extensions_features::kTelemetryExtensionPendingApprovalApi); + } + + private: + base::test::ScopedFeatureList feature_list_; +}; + +IN_PROC_BROWSER_TEST_F(PendingApprovalTelemetryExtensionTelemetryApiBrowserTest, + GetUsbBusInfo_Error) { +#if BUILDFLAG(IS_CHROMEOS_LACROS) + // If Probe interface is not available on this version of ash-chrome, this + // test suite will no-op. + if (!IsServiceAvailable()) { + return; + } +#endif // BUILDFLAG(IS_CHROMEOS_LACROS) + + // Configure FakeProbeService. + { + auto fake_service_impl = std::make_unique<FakeProbeService>(); + fake_service_impl->SetExpectedLastRequestedCategories( + {crosapi::mojom::ProbeCategoryEnum::kBus}); + + SetServiceForTesting(std::move(fake_service_impl)); + } + + CreateExtensionAndRunServiceWorker(R"( + chrome.test.runTests([ + async function getUsbBusInfo() { + await chrome.test.assertPromiseRejects( + chrome.os.telemetry.getUsbBusInfo(), + 'Error: API internal error' + ); + chrome.test.succeed(); + } + ]); + )"); +} + +IN_PROC_BROWSER_TEST_F(PendingApprovalTelemetryExtensionTelemetryApiBrowserTest, + GetUsbBusInfo_Success) { +#if BUILDFLAG(IS_CHROMEOS_LACROS) + // If Probe interface is not available on this version of ash-chrome, this + // test suite will no-op. + if (!IsServiceAvailable()) { + return; + } +#endif // BUILDFLAG(IS_CHROMEOS_LACROS) + + // Configure FakeProbeService. + { + auto telemetry_info = crosapi::mojom::ProbeTelemetryInfo::New(); + { + std::vector<crosapi::mojom::ProbeUsbBusInterfaceInfoPtr> interfaces; + interfaces.push_back(crosapi::mojom::ProbeUsbBusInterfaceInfo::New( + crosapi::mojom::UInt8Value::New(42), + crosapi::mojom::UInt8Value::New(41), + crosapi::mojom::UInt8Value::New(43), + crosapi::mojom::UInt8Value::New(44), "MyDriver")); + + auto fwupd_version = crosapi::mojom::ProbeFwupdFirmwareVersionInfo::New( + "MyVersion", crosapi::mojom::ProbeFwupdVersionFormat::kPair); + + auto usb_bus_info = crosapi::mojom::ProbeUsbBusInfo::New(); + usb_bus_info->class_id = crosapi::mojom::UInt8Value::New(45); + usb_bus_info->subclass_id = crosapi::mojom::UInt8Value::New(46); + usb_bus_info->protocol_id = crosapi::mojom::UInt8Value::New(47); + usb_bus_info->vendor_id = crosapi::mojom::UInt16Value::New(48); + usb_bus_info->product_id = crosapi::mojom::UInt16Value::New(49); + usb_bus_info->interfaces = std::move(interfaces); + usb_bus_info->fwupd_firmware_version_info = std::move(fwupd_version); + usb_bus_info->version = crosapi::mojom::ProbeUsbVersion::kUsb3; + usb_bus_info->spec_speed = crosapi::mojom::ProbeUsbSpecSpeed::k20Gbps; + + auto bus_info = + crosapi::mojom::ProbeBusInfo::NewUsbBusInfo(std::move(usb_bus_info)); + + std::vector<crosapi::mojom::ProbeBusInfoPtr> input; + input.push_back(std::move(bus_info)); + + telemetry_info->bus_result = + crosapi::mojom::ProbeBusResult::NewBusDevicesInfo(std::move(input)); + } + + auto fake_service_impl = std::make_unique<FakeProbeService>(); + fake_service_impl->SetProbeTelemetryInfoResponse(std::move(telemetry_info)); + fake_service_impl->SetExpectedLastRequestedCategories( + {crosapi::mojom::ProbeCategoryEnum::kBus}); + + SetServiceForTesting(std::move(fake_service_impl)); + } + + CreateExtensionAndRunServiceWorker(R"( + chrome.test.runTests([ + async function getUsbBusInfo() { + const result = await chrome.os.telemetry.getUsbBusInfo(); + chrome.test.assertEq( + // The dictionary members are ordered lexicographically by the Unicode + // codepoints that comprise their identifiers. + { + "devices": [ + { + "classId": 45, + "fwupdFirmwareVersionInfo": { + "version": "MyVersion", + "version_format":"pair" + }, + "interfaces": [ + { + "classId": 41, + "driver": "MyDriver", + "interfaceNumber": 42, + "protocolId": 44, + "subclassId": 43 + } + ], + "productId": 49, + "protocolId": 47, + "spec_speed": "n20Gbps", + "subclassId": 46, + "vendorId": 48, + "version": "usb3" + } + ] + }, result); + chrome.test.succeed(); + } + ]); + )"); +} + } // namespace chromeos
diff --git a/chrome/browser/chromeos/extensions/telemetry/api/telemetry_api_converters.cc b/chrome/browser/chromeos/extensions/telemetry/api/telemetry_api_converters.cc index 6002e3fc..ae6f015a 100644 --- a/chrome/browser/chromeos/extensions/telemetry/api/telemetry_api_converters.cc +++ b/chrome/browser/chromeos/extensions/telemetry/api/telemetry_api_converters.cc
@@ -310,6 +310,69 @@ return result; } +telemetry_api::UsbBusInterfaceInfo UncheckedConvertPtr( + telemetry_service::ProbeUsbBusInterfaceInfoPtr input) { + telemetry_api::UsbBusInterfaceInfo result; + + if (input->interface_number) { + result.interface_number = input->interface_number->value; + } + if (input->class_id) { + result.class_id = input->class_id->value; + } + if (input->subclass_id) { + result.subclass_id = input->subclass_id->value; + } + if (input->protocol_id) { + result.protocol_id = input->protocol_id->value; + } + result.driver = input->driver; + + return result; +} + +telemetry_api::FwupdFirmwareVersionInfo UncheckedConvertPtr( + telemetry_service::ProbeFwupdFirmwareVersionInfoPtr input) { + telemetry_api::FwupdFirmwareVersionInfo result; + + result.version = input->version; + result.version_format = Convert(input->version_format); + + return result; +} + +telemetry_api::UsbBusInfo UncheckedConvertPtr( + telemetry_service::ProbeUsbBusInfoPtr input) { + telemetry_api::UsbBusInfo result; + + if (input->class_id) { + result.class_id = input->class_id->value; + } + if (input->subclass_id) { + result.subclass_id = input->subclass_id->value; + } + if (input->protocol_id) { + result.protocol_id = input->protocol_id->value; + } + if (input->vendor_id) { + result.vendor_id = input->vendor_id->value; + } + if (input->product_id) { + result.product_id = input->product_id->value; + } + if (input->interfaces) { + result.interfaces = ConvertPtrVector<telemetry_api::UsbBusInterfaceInfo>( + std::move(input->interfaces.value())); + } + result.fwupd_firmware_version_info = + ConvertPtr<telemetry_api::FwupdFirmwareVersionInfo>( + std::move(input->fwupd_firmware_version_info)); + result.version = Convert(input->version); + result.spec_speed = Convert(input->spec_speed); + + return result; +} + } // namespace unchecked telemetry_api::CpuArchitectureEnum Convert( @@ -390,5 +453,74 @@ NOTREACHED(); } +telemetry_api::FwupdVersionFormat Convert( + telemetry_service::ProbeFwupdVersionFormat input) { + switch (input) { + case crosapi::mojom::ProbeFwupdVersionFormat::kUnknown: + return telemetry_api::FwupdVersionFormat::FWUPD_VERSION_FORMAT_PLAIN; + case crosapi::mojom::ProbeFwupdVersionFormat::kPlain: + return telemetry_api::FwupdVersionFormat::FWUPD_VERSION_FORMAT_PLAIN; + case crosapi::mojom::ProbeFwupdVersionFormat::kNumber: + return telemetry_api::FwupdVersionFormat::FWUPD_VERSION_FORMAT_NUMBER; + case crosapi::mojom::ProbeFwupdVersionFormat::kPair: + return telemetry_api::FwupdVersionFormat::FWUPD_VERSION_FORMAT_PAIR; + case crosapi::mojom::ProbeFwupdVersionFormat::kTriplet: + return telemetry_api::FwupdVersionFormat::FWUPD_VERSION_FORMAT_TRIPLET; + case crosapi::mojom::ProbeFwupdVersionFormat::kQuad: + return telemetry_api::FwupdVersionFormat::FWUPD_VERSION_FORMAT_QUAD; + case crosapi::mojom::ProbeFwupdVersionFormat::kBcd: + return telemetry_api::FwupdVersionFormat::FWUPD_VERSION_FORMAT_BCD; + case crosapi::mojom::ProbeFwupdVersionFormat::kIntelMe: + return telemetry_api::FwupdVersionFormat::FWUPD_VERSION_FORMAT_INTELME; + case crosapi::mojom::ProbeFwupdVersionFormat::kIntelMe2: + return telemetry_api::FwupdVersionFormat::FWUPD_VERSION_FORMAT_INTELME2; + case crosapi::mojom::ProbeFwupdVersionFormat::kSurfaceLegacy: + return telemetry_api::FwupdVersionFormat:: + FWUPD_VERSION_FORMAT_SURFACELEGACY; + case crosapi::mojom::ProbeFwupdVersionFormat::kSurface: + return telemetry_api::FwupdVersionFormat::FWUPD_VERSION_FORMAT_SURFACE; + case crosapi::mojom::ProbeFwupdVersionFormat::kDellBios: + return telemetry_api::FwupdVersionFormat::FWUPD_VERSION_FORMAT_DELLBIOS; + case crosapi::mojom::ProbeFwupdVersionFormat::kHex: + return telemetry_api::FwupdVersionFormat::FWUPD_VERSION_FORMAT_HEX; + } + NOTREACHED(); +} + +telemetry_api::UsbVersion Convert(telemetry_service::ProbeUsbVersion input) { + switch (input) { + case crosapi::mojom::ProbeUsbVersion::kUnknown: + return telemetry_api::UsbVersion::USB_VERSION_UNKNOWN; + case crosapi::mojom::ProbeUsbVersion::kUsb1: + return telemetry_api::UsbVersion::USB_VERSION_USB1; + case crosapi::mojom::ProbeUsbVersion::kUsb2: + return telemetry_api::UsbVersion::USB_VERSION_USB2; + case crosapi::mojom::ProbeUsbVersion::kUsb3: + return telemetry_api::UsbVersion::USB_VERSION_USB3; + } + NOTREACHED(); +} + +telemetry_api::UsbSpecSpeed Convert( + telemetry_service::ProbeUsbSpecSpeed input) { + switch (input) { + case crosapi::mojom::ProbeUsbSpecSpeed::kUnknown: + return telemetry_api::UsbSpecSpeed::USB_SPEC_SPEED_UNKNOWN; + case crosapi::mojom::ProbeUsbSpecSpeed::k1_5Mbps: + return telemetry_api::UsbSpecSpeed::USB_SPEC_SPEED_N1_5MBPS; + case crosapi::mojom::ProbeUsbSpecSpeed::k12Mbps: + return telemetry_api::UsbSpecSpeed::USB_SPEC_SPEED_N12MBPS; + case crosapi::mojom::ProbeUsbSpecSpeed::k480Mbps: + return telemetry_api::UsbSpecSpeed::USB_SPEC_SPEED_N480MBPS; + case crosapi::mojom::ProbeUsbSpecSpeed::k5Gbps: + return telemetry_api::UsbSpecSpeed::USB_SPEC_SPEED_N5GBPS; + case crosapi::mojom::ProbeUsbSpecSpeed::k10Gbps: + return telemetry_api::UsbSpecSpeed::USB_SPEC_SPEED_N10GBPS; + case crosapi::mojom::ProbeUsbSpecSpeed::k20Gbps: + return telemetry_api::UsbSpecSpeed::USB_SPEC_SPEED_N20GBPS; + } + NOTREACHED(); +} + } // namespace converters } // namespace chromeos
diff --git a/chrome/browser/chromeos/extensions/telemetry/api/telemetry_api_converters.h b/chrome/browser/chromeos/extensions/telemetry/api/telemetry_api_converters.h index bcce467..c3f1289 100644 --- a/chrome/browser/chromeos/extensions/telemetry/api/telemetry_api_converters.h +++ b/chrome/browser/chromeos/extensions/telemetry/api/telemetry_api_converters.h
@@ -74,6 +74,15 @@ chromeos::api::os_telemetry::TpmInfo UncheckedConvertPtr( crosapi::mojom::ProbeTpmInfoPtr input); +chromeos::api::os_telemetry::UsbBusInterfaceInfo UncheckedConvertPtr( + crosapi::mojom::ProbeUsbBusInterfaceInfoPtr input); + +chromeos::api::os_telemetry::FwupdFirmwareVersionInfo UncheckedConvertPtr( + crosapi::mojom::ProbeFwupdFirmwareVersionInfoPtr input); + +chromeos::api::os_telemetry::UsbBusInfo UncheckedConvertPtr( + crosapi::mojom::ProbeUsbBusInfoPtr input); + } // namespace unchecked chromeos::api::os_telemetry::CpuArchitectureEnum Convert( @@ -88,6 +97,15 @@ chromeos::api::os_telemetry::TpmGSCVersion Convert( crosapi::mojom::ProbeTpmGSCVersion input); +chromeos::api::os_telemetry::FwupdVersionFormat Convert( + crosapi::mojom::ProbeFwupdVersionFormat input); + +chromeos::api::os_telemetry::UsbVersion Convert( + crosapi::mojom::ProbeUsbVersion input); + +chromeos::api::os_telemetry::UsbSpecSpeed Convert( + crosapi::mojom::ProbeUsbSpecSpeed input); + template <class OutputT, class InputT> std::vector<OutputT> ConvertPtrVector(std::vector<InputT> input) { std::vector<OutputT> output;
diff --git a/chrome/browser/chromeos/extensions/telemetry/api/telemetry_api_converters_unittest.cc b/chrome/browser/chromeos/extensions/telemetry/api/telemetry_api_converters_unittest.cc index c19f9cf9..b269c86 100644 --- a/chrome/browser/chromeos/extensions/telemetry/api/telemetry_api_converters_unittest.cc +++ b/chrome/browser/chromeos/extensions/telemetry/api/telemetry_api_converters_unittest.cc
@@ -742,5 +742,198 @@ *dictionary_attack_result.lockout_seconds_remaining)); } +TEST(TelemetryApiConverters, UsbVersion) { + EXPECT_EQ(Convert(crosapi::mojom::ProbeUsbVersion::kUnknown), + telemetry_api::UsbVersion::USB_VERSION_UNKNOWN); + + EXPECT_EQ(Convert(crosapi::mojom::ProbeUsbVersion::kUsb1), + telemetry_api::UsbVersion::USB_VERSION_USB1); + + EXPECT_EQ(Convert(crosapi::mojom::ProbeUsbVersion::kUsb2), + telemetry_api::UsbVersion::USB_VERSION_USB2); + + EXPECT_EQ(Convert(crosapi::mojom::ProbeUsbVersion::kUsb3), + telemetry_api::UsbVersion::USB_VERSION_USB3); +} + +TEST(TelemetryApiConverters, UsbSpecSpeed) { + EXPECT_EQ(Convert(crosapi::mojom::ProbeUsbSpecSpeed::kUnknown), + telemetry_api::UsbSpecSpeed::USB_SPEC_SPEED_UNKNOWN); + + EXPECT_EQ(Convert(crosapi::mojom::ProbeUsbSpecSpeed::k1_5Mbps), + telemetry_api::UsbSpecSpeed::USB_SPEC_SPEED_N1_5MBPS); + + EXPECT_EQ(Convert(crosapi::mojom::ProbeUsbSpecSpeed::k12Mbps), + telemetry_api::UsbSpecSpeed::USB_SPEC_SPEED_N12MBPS); + + EXPECT_EQ(Convert(crosapi::mojom::ProbeUsbSpecSpeed::k480Mbps), + telemetry_api::UsbSpecSpeed::USB_SPEC_SPEED_N480MBPS); + + EXPECT_EQ(Convert(crosapi::mojom::ProbeUsbSpecSpeed::k5Gbps), + telemetry_api::UsbSpecSpeed::USB_SPEC_SPEED_N5GBPS); + + EXPECT_EQ(Convert(crosapi::mojom::ProbeUsbSpecSpeed::k10Gbps), + telemetry_api::UsbSpecSpeed::USB_SPEC_SPEED_N10GBPS); + + EXPECT_EQ(Convert(crosapi::mojom::ProbeUsbSpecSpeed::k20Gbps), + telemetry_api::UsbSpecSpeed::USB_SPEC_SPEED_N20GBPS); +} + +TEST(TelemetryApiConverters, FwupdVersionFormat) { + EXPECT_EQ(Convert(crosapi::mojom::ProbeFwupdVersionFormat::kUnknown), + telemetry_api::FwupdVersionFormat::FWUPD_VERSION_FORMAT_PLAIN); + + EXPECT_EQ(Convert(crosapi::mojom::ProbeFwupdVersionFormat::kPlain), + telemetry_api::FwupdVersionFormat::FWUPD_VERSION_FORMAT_PLAIN); + + EXPECT_EQ(Convert(crosapi::mojom::ProbeFwupdVersionFormat::kNumber), + telemetry_api::FwupdVersionFormat::FWUPD_VERSION_FORMAT_NUMBER); + + EXPECT_EQ(Convert(crosapi::mojom::ProbeFwupdVersionFormat::kPair), + telemetry_api::FwupdVersionFormat::FWUPD_VERSION_FORMAT_PAIR); + + EXPECT_EQ(Convert(crosapi::mojom::ProbeFwupdVersionFormat::kTriplet), + telemetry_api::FwupdVersionFormat::FWUPD_VERSION_FORMAT_TRIPLET); + + EXPECT_EQ(Convert(crosapi::mojom::ProbeFwupdVersionFormat::kBcd), + telemetry_api::FwupdVersionFormat::FWUPD_VERSION_FORMAT_BCD); + + EXPECT_EQ(Convert(crosapi::mojom::ProbeFwupdVersionFormat::kIntelMe), + telemetry_api::FwupdVersionFormat::FWUPD_VERSION_FORMAT_INTELME); + + EXPECT_EQ(Convert(crosapi::mojom::ProbeFwupdVersionFormat::kIntelMe2), + telemetry_api::FwupdVersionFormat::FWUPD_VERSION_FORMAT_INTELME2); + + EXPECT_EQ( + Convert(crosapi::mojom::ProbeFwupdVersionFormat::kSurfaceLegacy), + telemetry_api::FwupdVersionFormat::FWUPD_VERSION_FORMAT_SURFACELEGACY); + + EXPECT_EQ(Convert(crosapi::mojom::ProbeFwupdVersionFormat::kSurface), + telemetry_api::FwupdVersionFormat::FWUPD_VERSION_FORMAT_SURFACE); + + EXPECT_EQ(Convert(crosapi::mojom::ProbeFwupdVersionFormat::kDellBios), + telemetry_api::FwupdVersionFormat::FWUPD_VERSION_FORMAT_DELLBIOS); + + EXPECT_EQ(Convert(crosapi::mojom::ProbeFwupdVersionFormat::kHex), + telemetry_api::FwupdVersionFormat::FWUPD_VERSION_FORMAT_HEX); +} + +TEST(TelemetryApiConverters, FwupdFirmwareVersionInfo) { + constexpr char kVersion[] = "MyVersion"; + + auto input = crosapi::mojom::ProbeFwupdFirmwareVersionInfo::New( + kVersion, crosapi::mojom::ProbeFwupdVersionFormat::kHex); + + auto result = + ConvertPtr<telemetry_api::FwupdFirmwareVersionInfo>(std::move(input)); + + EXPECT_EQ(result.version, kVersion); + EXPECT_EQ(result.version_format, + telemetry_api::FwupdVersionFormat::FWUPD_VERSION_FORMAT_HEX); +} + +TEST(TelemetryApiConverters, UsbBusInterfaceInfo) { + constexpr uint8_t kInterfaceNumber = 41; + constexpr uint8_t kClassId = 42; + constexpr uint8_t kSubclassId = 43; + constexpr uint8_t kProtocolId = 44; + constexpr char kDriver[] = "MyDriver"; + + auto input = crosapi::mojom::ProbeUsbBusInterfaceInfo::New( + crosapi::mojom::UInt8Value::New(kInterfaceNumber), + crosapi::mojom::UInt8Value::New(kClassId), + crosapi::mojom::UInt8Value::New(kSubclassId), + crosapi::mojom::UInt8Value::New(kProtocolId), kDriver); + + auto result = + ConvertPtr<telemetry_api::UsbBusInterfaceInfo>(std::move(input)); + + ASSERT_TRUE(result.interface_number); + EXPECT_EQ(static_cast<uint8_t>(*result.interface_number), kInterfaceNumber); + ASSERT_TRUE(result.class_id); + EXPECT_EQ(static_cast<uint8_t>(*result.class_id), kClassId); + ASSERT_TRUE(result.subclass_id); + EXPECT_EQ(static_cast<uint8_t>(*result.subclass_id), kSubclassId); + ASSERT_TRUE(result.protocol_id); + EXPECT_EQ(static_cast<uint8_t>(*result.protocol_id), kProtocolId); + ASSERT_TRUE(result.driver); + EXPECT_EQ(result.driver, kDriver); +} + +TEST(TelemetryApiConverters, UsbBusInfo) { + constexpr uint8_t kInterfaceNumberInterface = 41; + constexpr uint8_t kClassIdInterface = 42; + constexpr uint8_t kSubclassIdInterface = 43; + constexpr uint8_t kProtocolIdInterface = 44; + constexpr char kDriverInterface[] = "MyDriver"; + + std::vector<crosapi::mojom::ProbeUsbBusInterfaceInfoPtr> interfaces; + interfaces.push_back(crosapi::mojom::ProbeUsbBusInterfaceInfo::New( + crosapi::mojom::UInt8Value::New(kInterfaceNumberInterface), + crosapi::mojom::UInt8Value::New(kClassIdInterface), + crosapi::mojom::UInt8Value::New(kSubclassIdInterface), + crosapi::mojom::UInt8Value::New(kProtocolIdInterface), kDriverInterface)); + + constexpr uint8_t kClassId = 45; + constexpr uint8_t kSubclassId = 46; + constexpr uint8_t kProtocolId = 47; + constexpr uint16_t kVendor = 48; + constexpr uint16_t kProductId = 49; + + constexpr char kVersion[] = "MyVersion"; + + auto fwupd_version = crosapi::mojom::ProbeFwupdFirmwareVersionInfo::New( + kVersion, crosapi::mojom::ProbeFwupdVersionFormat::kPair); + + auto input = crosapi::mojom::ProbeUsbBusInfo::New(); + input->class_id = crosapi::mojom::UInt8Value::New(kClassId); + input->subclass_id = crosapi::mojom::UInt8Value::New(kSubclassId); + input->protocol_id = crosapi::mojom::UInt8Value::New(kProtocolId); + input->vendor_id = crosapi::mojom::UInt16Value::New(kVendor); + input->product_id = crosapi::mojom::UInt16Value::New(kProductId); + input->interfaces = std::move(interfaces); + input->fwupd_firmware_version_info = std::move(fwupd_version); + input->version = crosapi::mojom::ProbeUsbVersion::kUsb3; + input->spec_speed = crosapi::mojom::ProbeUsbSpecSpeed::k20Gbps; + + auto result = ConvertPtr<telemetry_api::UsbBusInfo>(std::move(input)); + + ASSERT_TRUE(result.class_id); + EXPECT_EQ(static_cast<uint8_t>(*result.class_id), kClassId); + ASSERT_TRUE(result.subclass_id); + EXPECT_EQ(static_cast<uint8_t>(*result.subclass_id), kSubclassId); + ASSERT_TRUE(result.protocol_id); + EXPECT_EQ(static_cast<uint8_t>(*result.protocol_id), kProtocolId); + ASSERT_TRUE(result.product_id); + EXPECT_EQ(static_cast<uint16_t>(*result.product_id), kProductId); + ASSERT_TRUE(result.vendor_id); + EXPECT_EQ(static_cast<uint16_t>(*result.vendor_id), kVendor); + + ASSERT_EQ(result.interfaces.size(), 1UL); + ASSERT_TRUE(result.interfaces[0].interface_number); + EXPECT_EQ(static_cast<uint8_t>(*result.interfaces[0].interface_number), + kInterfaceNumberInterface); + ASSERT_TRUE(result.interfaces[0].class_id); + EXPECT_EQ(static_cast<uint8_t>(*result.interfaces[0].class_id), + kClassIdInterface); + ASSERT_TRUE(result.interfaces[0].subclass_id); + EXPECT_EQ(static_cast<uint8_t>(*result.interfaces[0].subclass_id), + kSubclassIdInterface); + ASSERT_TRUE(result.interfaces[0].protocol_id); + EXPECT_EQ(static_cast<uint8_t>(*result.interfaces[0].protocol_id), + kProtocolIdInterface); + ASSERT_TRUE(result.interfaces[0].driver); + EXPECT_EQ(result.interfaces[0].driver, kDriverInterface); + + ASSERT_TRUE(result.fwupd_firmware_version_info); + EXPECT_EQ(result.fwupd_firmware_version_info->version, kVersion); + EXPECT_EQ(result.fwupd_firmware_version_info->version_format, + telemetry_api::FwupdVersionFormat::FWUPD_VERSION_FORMAT_PAIR); + + EXPECT_EQ(result.version, telemetry_api::UsbVersion::USB_VERSION_USB3); + EXPECT_EQ(result.spec_speed, + telemetry_api::UsbSpecSpeed::USB_SPEC_SPEED_N20GBPS); +} + } // namespace converters } // namespace chromeos
diff --git a/chrome/browser/fast_checkout/fast_checkout_capabilities_fetcher.h b/chrome/browser/fast_checkout/fast_checkout_capabilities_fetcher.h index c4e24346..0799386c 100644 --- a/chrome/browser/fast_checkout/fast_checkout_capabilities_fetcher.h +++ b/chrome/browser/fast_checkout/fast_checkout_capabilities_fetcher.h
@@ -5,6 +5,7 @@ #ifndef CHROME_BROWSER_FAST_CHECKOUT_FAST_CHECKOUT_CAPABILITIES_FETCHER_H_ #define CHROME_BROWSER_FAST_CHECKOUT_FAST_CHECKOUT_CAPABILITIES_FETCHER_H_ +#include "base/containers/flat_set.h" #include "components/keyed_service/core/keyed_service.h" namespace autofill { @@ -29,6 +30,10 @@ virtual bool IsTriggerFormSupported( const url::Origin& origin, autofill::FormSignature form_signature) = 0; + // Returns form signatures cached for `origin` if present. Otherwise returns + // an empty set. + virtual base::flat_set<autofill::FormSignature> GetFormsToFill( + const url::Origin& origin) = 0; }; #endif // CHROME_BROWSER_FAST_CHECKOUT_FAST_CHECKOUT_CAPABILITIES_FETCHER_H_
diff --git a/chrome/browser/fast_checkout/fast_checkout_capabilities_fetcher_impl.cc b/chrome/browser/fast_checkout/fast_checkout_capabilities_fetcher_impl.cc index 27d9e57..8d09773 100644 --- a/chrome/browser/fast_checkout/fast_checkout_capabilities_fetcher_impl.cc +++ b/chrome/browser/fast_checkout/fast_checkout_capabilities_fetcher_impl.cc
@@ -8,7 +8,6 @@ #include "base/metrics/histogram_functions.h" #include "chrome/browser/fast_checkout/fast_checkout_features.h" -#include "chrome/browser/fast_checkout/fast_checkout_funnels.pb.h" #include "services/network/public/cpp/resource_request.h" #include "services/network/public/cpp/shared_url_loader_factory.h" #include "services/network/public/mojom/url_response_head.mojom.h" @@ -150,18 +149,31 @@ for (const ::fast_checkout::FastCheckoutFunnels_FastCheckoutFunnel& funnel_proto : funnels.funnels()) { - FastCheckoutFunnel funnel; - for (uint64_t form_signature : funnel_proto.trigger()) { - funnel.trigger.emplace(form_signature); - } - for (uint64_t form_signature : funnel_proto.fill()) { - funnel.fill.emplace(form_signature); - } - for (const std::string& domain : funnel_proto.domains()) { - GURL url = GURL(domain); - if (url.is_valid() && url.SchemeIsHTTPOrHTTPS()) { - cache_.emplace(url::Origin::Create(url), funnel); - } + AddFunnelToCache(funnel_proto); + } +} + +void FastCheckoutCapabilitiesFetcherImpl::AddFunnelToCache( + const ::fast_checkout::FastCheckoutFunnels_FastCheckoutFunnel& + funnel_proto) { + // There has to be at least one trigger form signature for a funnel. Otherwise + // a run could never be triggered successfully. + if (funnel_proto.trigger().empty()) { + return; + } + + FastCheckoutFunnel funnel; + for (uint64_t form_signature : funnel_proto.trigger()) { + funnel.trigger.emplace(form_signature); + } + for (uint64_t form_signature : funnel_proto.fill()) { + funnel.fill.emplace(form_signature); + } + + for (const std::string& domain : funnel_proto.domains()) { + GURL url = GURL(domain); + if (url.is_valid() && url.SchemeIsHTTPOrHTTPS()) { + cache_.emplace(url::Origin::Create(url), funnel); } } } @@ -191,3 +203,17 @@ kEntryAvailableAndFormNotSupported); return is_supported; } + +base::flat_set<autofill::FormSignature> +FastCheckoutCapabilitiesFetcherImpl::GetFormsToFill(const url::Origin& origin) { + if (!cache_.contains(origin)) { + return {}; + } + const FastCheckoutFunnel& funnel = cache_.at(origin); + // All `FastCheckoutFunnel::trigger` and `FastCheckoutFunnel::fill` forms + // should be attempted to be filled, in any order. For that reason, merge the + // two sets into one set (`forms_to_fill`) and return it. + base::flat_set<autofill::FormSignature> forms_to_fill = funnel.trigger; + forms_to_fill.insert(funnel.fill.begin(), funnel.fill.end()); + return forms_to_fill; +}
diff --git a/chrome/browser/fast_checkout/fast_checkout_capabilities_fetcher_impl.h b/chrome/browser/fast_checkout/fast_checkout_capabilities_fetcher_impl.h index 69f8f7e..c76ecd2b 100644 --- a/chrome/browser/fast_checkout/fast_checkout_capabilities_fetcher_impl.h +++ b/chrome/browser/fast_checkout/fast_checkout_capabilities_fetcher_impl.h
@@ -7,6 +7,7 @@ #include "chrome/browser/fast_checkout/fast_checkout_capabilities_fetcher.h" +#include "chrome/browser/fast_checkout/fast_checkout_funnels.pb.h" #include "components/autofill/core/common/signatures.h" #include "services/network/public/cpp/shared_url_loader_factory.h" #include "services/network/public/cpp/simple_url_loader.h" @@ -79,13 +80,21 @@ void FetchCapabilities() override; bool IsTriggerFormSupported(const url::Origin& origin, autofill::FormSignature form_signature) override; + base::flat_set<autofill::FormSignature> GetFormsToFill( + const url::Origin& origin) override; private: struct FastCheckoutFunnel { FastCheckoutFunnel(); ~FastCheckoutFunnel(); FastCheckoutFunnel(const FastCheckoutFunnel&); + // `trigger` form signatures allow a fast checkout run to start by showing + // the bottomsheet if an input field of their forms got focused by the user. + // They will also be attempted to be autofilled, just like `fill` form + // signatures. base::flat_set<autofill::FormSignature> trigger; + // `fill` form signatures don't trigger a fast checkout run but are + // attempted to be autofilled. base::flat_set<autofill::FormSignature> fill; }; // Called when the request's response arrives. @@ -94,6 +103,16 @@ // Returns if the cache is stale, i.e. if `kCacheTimeout` minutes since the // last successful request have passed or if no request was done yet. bool IsCacheStale() const; + // Converts funnel proto message to `FastCheckoutFunnel` and adds it to + // `cache_`. + void AddFunnelToCache( + const ::fast_checkout::FastCheckoutFunnels_FastCheckoutFunnel& + funnel_proto); + // Converts `trigger` and `fill` fields from the funnel proto message to + // `FastCheckoutFunnel` + FastCheckoutFunnel ConvertToFunnel( + const ::google::protobuf::RepeatedField<uint64_t>& trigger, + const ::google::protobuf::RepeatedField<uint64_t>& fill) const; // URL loader object for the gstatic request. If `url_loader_` is not null, a // request is currently ongoing. std::unique_ptr<network::SimpleURLLoader> url_loader_ = nullptr;
diff --git a/chrome/browser/fast_checkout/fast_checkout_capabilities_fetcher_impl_unittest.cc b/chrome/browser/fast_checkout/fast_checkout_capabilities_fetcher_impl_unittest.cc index f558427b..8586cecd 100644 --- a/chrome/browser/fast_checkout/fast_checkout_capabilities_fetcher_impl_unittest.cc +++ b/chrome/browser/fast_checkout/fast_checkout_capabilities_fetcher_impl_unittest.cc
@@ -10,12 +10,14 @@ #include "chrome/test/base/chrome_render_view_host_test_harness.h" #include "services/network/public/cpp/weak_wrapper_shared_url_loader_factory.h" #include "services/network/test/test_url_loader_factory.h" +#include "testing/gmock/include/gmock/gmock.h" namespace { constexpr char kFastCheckoutFunnelsUrl[] = "https://www.gstatic.com/autofill/fast_checkout/funnels.binarypb"; constexpr char kInvalidResponseBody[] = "invalid response body"; constexpr char kDomain[] = "https://www.example.com"; +constexpr char kDomainWithoutTriggerForm[] = "https://www.example3.com"; constexpr char kUnsupportedDomain[] = "https://www.example2.com"; constexpr char kInvalidDomain[] = "invaliddomain"; constexpr char kNonHttpSDomain[] = "file://path/to/a/file"; @@ -44,6 +46,11 @@ funnel->add_trigger(kTriggerFormSignature.value()); funnel->add_fill(kFillFormSignature.value()); } + // Add one additional funnel with empty `trigger` field. + ::fast_checkout::FastCheckoutFunnels_FastCheckoutFunnel* funnel = + funnels.add_funnels(); + funnel->add_domains(kDomainWithoutTriggerForm); + funnel->add_fill(kFillFormSignature.value()); return funnels.SerializeAsString(); } } // namespace @@ -58,6 +65,8 @@ unsupported_origin_ = url::Origin::Create(GURL(kUnsupportedDomain)); invalid_origin_ = url::Origin::Create(GURL(kInvalidDomain)); non_http_s_origin_ = url::Origin::Create(GURL(kNonHttpSDomain)); + origin_without_trigger_form_ = + url::Origin::Create(GURL(kDomainWithoutTriggerForm)); } protected: @@ -90,6 +99,9 @@ const url::Origin& GetUnsupportedOrigin() { return unsupported_origin_; } const url::Origin& GetInvalidOrigin() { return invalid_origin_; } const url::Origin& GetNonHttpSOrigin() { return non_http_s_origin_; } + const url::Origin& GetOriginWithoutTriggerForm() { + return origin_without_trigger_form_; + } bool FetchCapabilitiesAndSimulateResponse( net::HttpStatusCode status = net::HTTP_OK) { @@ -113,6 +125,7 @@ url::Origin unsupported_origin_; url::Origin invalid_origin_; url::Origin non_http_s_origin_; + url::Origin origin_without_trigger_form_; }; TEST_F(FastCheckoutCapabilitiesFetcherImplTest, @@ -273,3 +286,34 @@ kEntryNotAvailable, 1u); } + +TEST_F(FastCheckoutCapabilitiesFetcherImplTest, + GetFormsToFill_InvalidDomain_ReturnsEmptySet) { + EXPECT_TRUE(FetchCapabilitiesAndSimulateResponse()); + EXPECT_THAT(fetcher()->GetFormsToFill(GetInvalidOrigin()), + testing::IsEmpty()); +} + +TEST_F(FastCheckoutCapabilitiesFetcherImplTest, + GetFormsToFill_ValidDomain_ReturnsTriggerAndFillForms) { + EXPECT_TRUE(FetchCapabilitiesAndSimulateResponse()); + + base::flat_set<autofill::FormSignature> forms_to_fill = + fetcher()->GetFormsToFill(GetOrigin()); + EXPECT_THAT(forms_to_fill, testing::UnorderedElementsAre( + kTriggerFormSignature, kFillFormSignature)); +} + +TEST_F(FastCheckoutCapabilitiesFetcherImplTest, NoTriggerForm) { + EXPECT_TRUE(FetchCapabilitiesAndSimulateResponse()); + + EXPECT_FALSE(fetcher()->IsTriggerFormSupported(GetOriginWithoutTriggerForm(), + kTriggerFormSignature)); + EXPECT_THAT(fetcher()->GetFormsToFill(GetOriginWithoutTriggerForm()), + testing::IsEmpty()); + histogram_tester().ExpectUniqueSample( + kUmaKeyCacheStateIsTriggerFormSupported, + FastCheckoutCapabilitiesFetcherImpl::CacheStateForIsTriggerFormSupported:: + kEntryNotAvailable, + 1u); +}
diff --git a/chrome/browser/fast_checkout/fast_checkout_client.h b/chrome/browser/fast_checkout/fast_checkout_client.h index 2762ea2..c43b0848 100644 --- a/chrome/browser/fast_checkout/fast_checkout_client.h +++ b/chrome/browser/fast_checkout/fast_checkout_client.h
@@ -5,10 +5,12 @@ #ifndef CHROME_BROWSER_FAST_CHECKOUT_FAST_CHECKOUT_CLIENT_H_ #define CHROME_BROWSER_FAST_CHECKOUT_FAST_CHECKOUT_CLIENT_H_ +#include "base/memory/weak_ptr.h" + class GURL; namespace autofill { -class AutofillDriver; +class AutofillManager; struct FormData; struct FormFieldData; } // namespace autofill @@ -28,10 +30,11 @@ content::WebContents* web_contents); // Starts the fast checkout run. Returns true if the run was successful. - virtual bool TryToStart(const GURL& url, - const autofill::FormData& form, - const autofill::FormFieldData& field, - autofill::AutofillDriver* autofill_driver) = 0; + virtual bool TryToStart( + const GURL& url, + const autofill::FormData& form, + const autofill::FormFieldData& field, + base::WeakPtr<autofill::AutofillManager> autofill_manager) = 0; // Stops the fast checkout run. Resets internal UI state to `kNotShownYet` if // `allow_further_runs == true`.
diff --git a/chrome/browser/fast_checkout/fast_checkout_client_impl.cc b/chrome/browser/fast_checkout/fast_checkout_client_impl.cc index 737f063..a220000 100644 --- a/chrome/browser/fast_checkout/fast_checkout_client_impl.cc +++ b/chrome/browser/fast_checkout/fast_checkout_client_impl.cc
@@ -10,7 +10,7 @@ #include "chrome/browser/fast_checkout/fast_checkout_personal_data_helper_impl.h" #include "chrome/browser/fast_checkout/fast_checkout_trigger_validator_impl.h" #include "chrome/browser/ui/autofill/chrome_autofill_client.h" -#include "components/autofill/content/browser/content_autofill_driver.h" +#include "components/autofill/core/browser/browser_autofill_manager.h" #include "components/autofill/core/browser/data_model/autofill_profile.h" #include "components/autofill/core/browser/data_model/credit_card.h" #include "content/public/browser/web_contents_user_data.h" @@ -21,12 +21,13 @@ : content::WebContentsUserData<FastCheckoutClientImpl>(*web_contents), autofill_client_( autofill::ChromeAutofillClient::FromWebContents(web_contents)), + fetcher_(FastCheckoutCapabilitiesFetcherFactory::GetForBrowserContext( + web_contents->GetBrowserContext())), personal_data_helper_( std::make_unique<FastCheckoutPersonalDataHelperImpl>(web_contents)), trigger_validator_(std::make_unique<FastCheckoutTriggerValidatorImpl>( autofill_client_, - FastCheckoutCapabilitiesFetcherFactory::GetForBrowserContext( - web_contents->GetBrowserContext()), + fetcher_, personal_data_helper_.get())) {} FastCheckoutClientImpl::~FastCheckoutClientImpl() { @@ -40,25 +41,23 @@ const GURL& url, const autofill::FormData& form, const autofill::FormFieldData& field, - autofill::AutofillDriver* autofill_driver) { - autofill::ContentAutofillDriver* content_autofill_driver = - static_cast<autofill::ContentAutofillDriver*>(autofill_driver); - - if (!content_autofill_driver) { + base::WeakPtr<autofill::AutofillManager> autofill_manager) { + if (!autofill_manager) { return false; } if (!trigger_validator_->ShouldRun(form, field, fast_checkout_ui_state_, - is_running_, content_autofill_driver)) { + is_running_, autofill_manager)) { return false; } - autofill_driver_ = content_autofill_driver; - url_ = url; + autofill_manager_ = autofill_manager; + origin_ = url::Origin::Create(url); is_running_ = true; personal_data_manager_observation_.Observe( personal_data_helper_->GetPersonalDataManager()); + SetFormsToFill(); SetShouldSuppressKeyboard(true); fast_checkout_controller_ = CreateFastCheckoutController(); @@ -78,8 +77,8 @@ } void FastCheckoutClientImpl::SetShouldSuppressKeyboard(bool suppress) { - if (autofill_driver_) { - autofill_driver_->SetShouldSuppressKeyboard(suppress); + if (autofill_manager_) { + autofill_manager_->SetShouldSuppressKeyboard(suppress); } } @@ -91,6 +90,9 @@ void FastCheckoutClientImpl::Stop(bool allow_further_runs) { if (allow_further_runs) { + forms_to_fill_.clear(); + selected_autofill_profile_.reset(); + selected_credit_card_.reset(); fast_checkout_ui_state_ = FastCheckoutUIState::kNotShownYet; } else if (IsShowing()) { fast_checkout_ui_state_ = FastCheckoutUIState::kWasShown; @@ -104,12 +106,7 @@ // stops. SetShouldSuppressKeyboard(false); - // There is one `ContentAutofillDriver` instance per frame but only one - // instance of this class per `WebContents`. Reset `autofill_driver_` here to - // avoid the issue of having a non-null, invalid pointer. This method is - // (also) called from `~BrowserAutofillManager()` which is owned by - // `ContentAutofillDriver`. - autofill_driver_ = nullptr; + autofill_manager_ = nullptr; } bool FastCheckoutClientImpl::IsShowing() const { @@ -133,13 +130,26 @@ void FastCheckoutClientImpl::OnOptionsSelected( std::unique_ptr<autofill::AutofillProfile> selected_profile, std::unique_ptr<autofill::CreditCard> selected_credit_card) { - // TODO(crbug.com/1334642): Signal that FC options have been selected. OnHidden(); + selected_autofill_profile_ = std::move(selected_profile); + selected_credit_card_ = std::move(selected_credit_card); +} + +void FastCheckoutClientImpl::SetFormsToFill() { + if (!fetcher_) { + return; + } + DCHECK(forms_to_fill_.empty()); + for (autofill::FormSignature form_signature : + fetcher_->GetFormsToFill(origin_)) { + forms_to_fill_.emplace(form_signature, FillingState::kNotFilled); + } } void FastCheckoutClientImpl::OnDismiss() { OnHidden(); Stop(/*allow_further_runs=*/false); + forms_to_fill_.clear(); } void FastCheckoutClientImpl::OnPersonalDataChanged() {
diff --git a/chrome/browser/fast_checkout/fast_checkout_client_impl.h b/chrome/browser/fast_checkout/fast_checkout_client_impl.h index ed0d56ce..b6e82ee61 100644 --- a/chrome/browser/fast_checkout/fast_checkout_client_impl.h +++ b/chrome/browser/fast_checkout/fast_checkout_client_impl.h
@@ -6,12 +6,12 @@ #define CHROME_BROWSER_FAST_CHECKOUT_FAST_CHECKOUT_CLIENT_IMPL_H_ #include "base/scoped_observation.h" +#include "chrome/browser/fast_checkout/fast_checkout_capabilities_fetcher.h" #include "chrome/browser/fast_checkout/fast_checkout_client.h" #include "chrome/browser/fast_checkout/fast_checkout_enums.h" #include "chrome/browser/fast_checkout/fast_checkout_personal_data_helper.h" #include "chrome/browser/fast_checkout/fast_checkout_trigger_validator.h" #include "chrome/browser/ui/fast_checkout/fast_checkout_controller_impl.h" -#include "components/autofill/content/browser/content_autofill_driver.h" #include "components/autofill/core/browser/personal_data_manager.h" #include "content/public/browser/web_contents.h" #include "content/public/browser/web_contents_user_data.h" @@ -36,10 +36,11 @@ FastCheckoutClientImpl& operator=(const FastCheckoutClientImpl&) = delete; // FastCheckoutClient: - bool TryToStart(const GURL& url, - const autofill::FormData& form, - const autofill::FormFieldData& field, - autofill::AutofillDriver* autofill_driver) override; + bool TryToStart( + const GURL& url, + const autofill::FormData& form, + const autofill::FormFieldData& field, + base::WeakPtr<autofill::AutofillManager> autofill_manager) override; void Stop(bool allow_further_runs) override; bool IsRunning() const override; bool IsShowing() const override; @@ -50,6 +51,19 @@ std::unique_ptr<autofill::CreditCard> selected_credit_card) override; void OnDismiss() override; + // Filling state of a form during a run. + enum class FillingState { + // Form was not attempted to be filled. + kNotFilled = 0, + + // Autofill was invoked on the form but this clas was not notified about the + // form being filled. + kFilling = 1, + + // This form has been filled. + kFilled = 2 + }; + #if defined(UNIT_TEST) void set_trigger_validator_for_test( std::unique_ptr<FastCheckoutTriggerValidator> trigger_validator) { @@ -60,8 +74,21 @@ autofill_client_ = autofill_client; } - autofill::ContentAutofillDriver* get_autofill_driver_for_test() { - return autofill_driver_; + base::WeakPtr<autofill::AutofillManager> get_autofill_manager_for_test() { + return autofill_manager_; + } + + autofill::AutofillProfile* get_autofill_profile_for_test() { + return selected_autofill_profile_.get(); + } + + autofill::CreditCard* get_credit_card_for_test() { + return selected_credit_card_.get(); + } + + const base::flat_map<autofill::FormSignature, FillingState>& + get_forms_to_fill_for_test() { + return forms_to_fill_; } #endif @@ -98,15 +125,19 @@ // Logs `message` to chrome://autofill-internals. void LogAutofillInternals(std::string message) const; + // Populates map with forms to fill at the beginning of the run. + void SetFormsToFill(); + // The `ChromeAutofillClient` instanced attached to the same `WebContents`. raw_ptr<autofill::AutofillClient> autofill_client_ = nullptr; - // The `ContentAutofillDriver` instance invoking the fast checkout run. This - // class generally outlives `autofill_driver_` so extra care needs to be taken - // with this pointer. It gets reset in `Stop(..)` which is (also) called from - // `~BrowserAutofillManager()` when the `ContentAutofillDriver` instance gets - // destroyed. - raw_ptr<autofill::ContentAutofillDriver> autofill_driver_ = nullptr; + // The `AutofillManager` instance invoking the fast checkout run. Note that + // `this` class generally outlives `AutofillManager`. + base::WeakPtr<autofill::AutofillManager> autofill_manager_ = nullptr; + + // Weak reference to the `FastCheckoutCapabilitiesFetcher` instance attached + // to `this` web content's browser context. + raw_ptr<FastCheckoutCapabilitiesFetcher> fetcher_ = nullptr; // Fast Checkout UI Controller. Responsible for showing the bottomsheet and // handling user selections. @@ -121,8 +152,17 @@ // True if a run is ongoing; used to avoid multiple runs in parallel. bool is_running_ = false; - // The url for which `Start()` was triggered. - GURL url_; + // Autofill profile selected by the user in the bottomsheet. + std::unique_ptr<autofill::AutofillProfile> selected_autofill_profile_; + + // Credit card selected by the user in the bottomsheet. + std::unique_ptr<autofill::CreditCard> selected_credit_card_; + + // The origin for which `TryToStart()` was triggered. + url::Origin origin_; + + // Maps forms to fill during the run to their filling state. + base::flat_map<autofill::FormSignature, FillingState> forms_to_fill_; // The current state of the bottomsheet. FastCheckoutUIState fast_checkout_ui_state_ =
diff --git a/chrome/browser/fast_checkout/fast_checkout_client_impl_unittest.cc b/chrome/browser/fast_checkout/fast_checkout_client_impl_unittest.cc index bffc1e1..eb79952 100644 --- a/chrome/browser/fast_checkout/fast_checkout_client_impl_unittest.cc +++ b/chrome/browser/fast_checkout/fast_checkout_client_impl_unittest.cc
@@ -15,8 +15,10 @@ #include "base/test/metrics/histogram_tester.h" #include "base/test/scoped_feature_list.h" #include "chrome/browser/autofill/personal_data_manager_factory.h" +#include "chrome/browser/fast_checkout/fast_checkout_capabilities_fetcher_factory.h" #include "chrome/browser/fast_checkout/fast_checkout_features.h" #include "chrome/browser/fast_checkout/fast_checkout_trigger_validator.h" +#include "chrome/browser/fast_checkout/mock_fast_checkout_capabilities_fetcher.h" #include "chrome/browser/ui/autofill/chrome_autofill_client.h" #include "chrome/browser/ui/fast_checkout/fast_checkout_controller.h" #include "chrome/test/base/chrome_render_view_host_test_harness.h" @@ -102,13 +104,13 @@ MOCK_METHOD(gfx::NativeView, GetNativeView, (), (override)); }; -class MockAutofillDriver : public autofill::TestAutofillDriver { +class MockBrowserAutofillManager : public autofill::TestBrowserAutofillManager { public: - MockAutofillDriver() = default; - MockAutofillDriver(const MockAutofillDriver&) = delete; - MockAutofillDriver& operator=(const MockAutofillDriver&) = delete; + MockBrowserAutofillManager(autofill::TestAutofillDriver* driver, + autofill::TestAutofillClient* client) + : autofill::TestBrowserAutofillManager(driver, client) {} + ~MockBrowserAutofillManager() override = default; - // Mock methods to enable testability. MOCK_METHOD(void, SetShouldSuppressKeyboard, (bool), (override)); }; @@ -145,7 +147,7 @@ const autofill::FormFieldData&, const FastCheckoutUIState, const bool, - const autofill::ContentAutofillDriver*), + const base::WeakPtr<autofill::AutofillManager>), (const)); MOCK_METHOD(bool, HasValidPersonalData, (), (const)); }; @@ -178,6 +180,11 @@ autofill::PersonalDataManagerFactory::GetInstance()->SetTestingFactory( GetBrowserContext(), base::BindRepeating(&BuildTestPersonalDataManager)); + FastCheckoutCapabilitiesFetcherFactory::GetInstance() + ->SetTestingSubclassFactoryAndUse( + profile(), base::BindRepeating([](content::BrowserContext*) { + return std::make_unique<MockFastCheckoutCapabilitiesFetcher>(); + })); test_client_ = TestFastCheckoutClientImpl::CreateForWebContents(web_contents()); @@ -190,13 +197,14 @@ std::move(fast_checkout_controller)); // Prepare the AutofillDriver. - autofill_driver_ = std::make_unique<MockAutofillDriver>(); + autofill_driver_ = std::make_unique<autofill::TestAutofillDriver>(); // Set AutofillManager on AutofillDriver. autofill_client_ = std::make_unique<MockAutofillClient>(); auto test_browser_autofill_manager = - std::make_unique<autofill::TestBrowserAutofillManager>( - autofill_driver_.get(), autofill_client_.get()); + std::make_unique<MockBrowserAutofillManager>(autofill_driver_.get(), + autofill_client_.get()); + autofill_manager_ = test_browser_autofill_manager.get(); autofill_driver_->set_autofill_manager( std::move(test_browser_autofill_manager)); @@ -220,21 +228,22 @@ return fast_checkout_controller_; } - MockAutofillDriver* autofill_driver() { return autofill_driver_.get(); } - MockFastCheckoutTriggerValidator* validator() { return validator_.get(); } MockAutofillClient* autofill_client() { return autofill_client_.get(); } + MockBrowserAutofillManager* autofill_manager() { return autofill_manager_; } + base::test::ScopedFeatureList feature_list_; base::HistogramTester histogram_tester_; private: std::unique_ptr<MockAutofillClient> autofill_client_; raw_ptr<MockFastCheckoutController> fast_checkout_controller_; - std::unique_ptr<MockAutofillDriver> autofill_driver_; + std::unique_ptr<autofill::TestAutofillDriver> autofill_driver_; raw_ptr<TestFastCheckoutClientImpl> test_client_; raw_ptr<MockFastCheckoutTriggerValidator> validator_; + raw_ptr<MockBrowserAutofillManager> autofill_manager_; }; TEST_F( @@ -247,14 +256,14 @@ EXPECT_EQ(client, fast_checkout_client()); } -TEST_F(FastCheckoutClientImplTest, Start_InvalidAutofillDriver_NoRun) { +TEST_F(FastCheckoutClientImplTest, Start_InvalidAutofillManager_NoRun) { // `FastCheckoutClient` is not running initially. EXPECT_FALSE(fast_checkout_client()->IsRunning()); // Do not expect bottomsheet to show up. EXPECT_CALL(*fast_checkout_controller(), Show).Times(0); // Do not expect keyboard to be suppressed. - EXPECT_CALL(*autofill_driver(), SetShouldSuppressKeyboard).Times(0); + EXPECT_CALL(*autofill_manager(), SetShouldSuppressKeyboard).Times(0); // Do not expect Autofill popups to be hidden. EXPECT_CALL(*autofill_client(), HideAutofillPopup).Times(0); @@ -271,13 +280,13 @@ // Do not expect bottomsheet to show up. EXPECT_CALL(*fast_checkout_controller(), Show).Times(0); // Do not expect keyboard to be suppressed. - EXPECT_CALL(*autofill_driver(), SetShouldSuppressKeyboard).Times(0); + EXPECT_CALL(*autofill_manager(), SetShouldSuppressKeyboard).Times(0); // Do not expect Autofill popups to be hidden. EXPECT_CALL(*autofill_client(), HideAutofillPopup).Times(0); EXPECT_FALSE(fast_checkout_client()->TryToStart( GURL(kUrl), autofill::FormData(), autofill::FormFieldData(), - autofill_driver())); + autofill_manager()->GetWeakPtr())); } TEST_F(FastCheckoutClientImplTest, Start_ShouldRunReturnsTrue_Run) { @@ -291,7 +300,7 @@ Pointee(kIncompleteProfile)), UnorderedElementsAre(Pointee(kCreditCard1), Pointee(kCreditCard2)))); // Expect keyboard suppression. - EXPECT_CALL(*autofill_driver(), SetShouldSuppressKeyboard(true)); + EXPECT_CALL(*autofill_manager(), SetShouldSuppressKeyboard(true)); // Expect call to `HideAutofillPopup`. EXPECT_CALL( *autofill_client(), @@ -300,7 +309,7 @@ EXPECT_TRUE(fast_checkout_client()->TryToStart( GURL(kUrl), autofill::FormData(), autofill::FormFieldData(), - autofill_driver())); + autofill_manager()->GetWeakPtr())); EXPECT_TRUE(fast_checkout_client()->IsRunning()); EXPECT_TRUE(fast_checkout_client()->IsShowing()); @@ -319,7 +328,7 @@ // Starting the run successfully. EXPECT_TRUE(fast_checkout_client()->TryToStart( GURL(kUrl), autofill::FormData(), autofill::FormFieldData(), - autofill_driver())); + autofill_manager()->GetWeakPtr())); // `FastCheckoutClient` is running. EXPECT_TRUE(fast_checkout_client()->IsRunning()); @@ -349,7 +358,7 @@ // Starting the run successfully. EXPECT_TRUE(fast_checkout_client()->TryToStart( GURL(kUrl), autofill::FormData(), autofill::FormFieldData(), - autofill_driver())); + autofill_manager()->GetWeakPtr())); // `FastCheckoutClient` is running. EXPECT_TRUE(fast_checkout_client()->IsRunning()); @@ -376,7 +385,7 @@ // Starting the run successfully. EXPECT_TRUE(fast_checkout_client()->TryToStart( GURL(kUrl), autofill::FormData(), autofill::FormFieldData(), - autofill_driver())); + autofill_manager()->GetWeakPtr())); // Fast Checkout is running and showing the bottomsheet. EXPECT_TRUE(fast_checkout_client()->IsRunning()); @@ -397,7 +406,7 @@ // Starting the run successfully. EXPECT_TRUE(fast_checkout_client()->TryToStart( GURL(kUrl), autofill::FormData(), autofill::FormFieldData(), - autofill_driver())); + autofill_manager()->GetWeakPtr())); fast_checkout_client()->OnDismiss(); @@ -406,15 +415,10 @@ } TEST_F(FastCheckoutClientImplTest, - DestroyingAutofillDriver_ResetsAutofillDriverPointer) { + DestroyingAutofillDriver_ResetsAutofillManagerPointer) { // Set up Autofill instances so that `FastCheckoutClient::Stop(..)` will be // called when `autofill_driver` is destroyed below. `Stop(..)` is supposed to - // reset `FastCheckoutClientImpl::autofill_driver_`. - // The expected stack trace is: - // `FastCheckoutClientImpl::Stop(/*allow_further_runs=*/true)` - // `ChromeAutofillClient::HideFastCheckout(/*allow_further_runs=*/true)` - // `~BrowserAutofillManager()` - // `autofill_driver.reset()` + // reset `FastCheckoutClientImpl::autofill_manager_`. autofill::ChromeAutofillClient::CreateForWebContents(web_contents()); auto autofill_router = std::make_unique<autofill::ContentAutofillRouter>(); auto autofill_driver = std::make_unique<autofill::ContentAutofillDriver>( @@ -424,26 +428,69 @@ autofill_driver.get(), autofill::ChromeAutofillClient::FromWebContents(web_contents()), "en-US", autofill::AutofillManager::EnableDownloadManager(false)); + autofill::BrowserAutofillManager* autofill_manager = + browser_autofill_manager.get(); autofill_driver->set_autofill_manager(std::move(browser_autofill_manager)); - // `FastCheckoutClientImpl::autofill_driver_` is `nullptr` initially. - EXPECT_FALSE(fast_checkout_client()->get_autofill_driver_for_test()); + // `FastCheckoutClientImpl::autofill_manager_` is `nullptr` initially. + EXPECT_FALSE(fast_checkout_client()->get_autofill_manager_for_test()); // Starting the run successfully. EXPECT_TRUE(fast_checkout_client()->TryToStart( GURL(kUrl), autofill::FormData(), autofill::FormFieldData(), - autofill_driver.get())); + autofill_manager->GetWeakPtr())); - // `FastCheckoutClientImpl::autofill_driver_` is not `nullptr` anymore. - EXPECT_TRUE(fast_checkout_client()->get_autofill_driver_for_test()); + // `FastCheckoutClientImpl::autofill_manager_` is not `nullptr` anymore. + EXPECT_TRUE(fast_checkout_client()->get_autofill_manager_for_test()); // Destroy `ContentAutofillDriver` instance, invoking // `~BrowserAutofillManager()` and thus `FastCheckoutClient::Stop(..)`. autofill_driver.reset(); - // `FastCheckoutClientImpl::autofill_driver_` is `nullptr` again. - EXPECT_FALSE(fast_checkout_client()->get_autofill_driver_for_test()); + // `FastCheckoutClientImpl::autofill_manager_` is `nullptr` again. + EXPECT_FALSE(fast_checkout_client()->get_autofill_manager_for_test()); // Expect this `Stop(..)` call to not crash the test. fast_checkout_client()->Stop(/*allow_further_runs=*/true); } + +TEST_F(FastCheckoutClientImplTest, + OnOptionsSelected_SavesFormsAndAutofillDataSelections) { + auto autofill_profile = std::make_unique<autofill::AutofillProfile>( + autofill::test::GetFullProfile()); + auto credit_card = + std::make_unique<autofill::CreditCard>(autofill::test::GetCreditCard()); + autofill::AutofillProfile* autofill_profile_ptr = autofill_profile.get(); + autofill::CreditCard* credit_card_ptr = credit_card.get(); + autofill::FormSignature signature_1 = autofill::FormSignature(72322UL); + autofill::FormSignature signature_2 = autofill::FormSignature(1154UL); + base::flat_set<autofill::FormSignature> forms_to_fill{signature_1, + signature_2}; + const base::flat_map<autofill::FormSignature, + FastCheckoutClientImpl::FillingState> + expected_forms_to_fill_map{ + {signature_1, FastCheckoutClientImpl::FillingState::kNotFilled}, + {signature_2, FastCheckoutClientImpl::FillingState::kNotFilled}}; + + MockFastCheckoutCapabilitiesFetcher* fetcher = + static_cast<MockFastCheckoutCapabilitiesFetcher*>( + FastCheckoutCapabilitiesFetcherFactory::GetForBrowserContext( + profile())); + EXPECT_TRUE(fetcher); + + EXPECT_CALL(*fetcher, GetFormsToFill(url::Origin::Create(GURL(kUrl)))) + .WillOnce(Return(forms_to_fill)); + EXPECT_TRUE(fast_checkout_client()->TryToStart( + GURL(kUrl), autofill::FormData(), autofill::FormFieldData(), + autofill_manager()->GetWeakPtr())); + + fast_checkout_client()->OnOptionsSelected(std::move(autofill_profile), + std::move(credit_card)); + + EXPECT_EQ(*fast_checkout_client()->get_autofill_profile_for_test(), + *autofill_profile_ptr); + EXPECT_EQ(*fast_checkout_client()->get_credit_card_for_test(), + *credit_card_ptr); + EXPECT_EQ(fast_checkout_client()->get_forms_to_fill_for_test(), + expected_forms_to_fill_map); +}
diff --git a/chrome/browser/fast_checkout/fast_checkout_trigger_validator.h b/chrome/browser/fast_checkout/fast_checkout_trigger_validator.h index 51dabc8..e0ff305 100644 --- a/chrome/browser/fast_checkout/fast_checkout_trigger_validator.h +++ b/chrome/browser/fast_checkout/fast_checkout_trigger_validator.h
@@ -6,7 +6,7 @@ #define CHROME_BROWSER_FAST_CHECKOUT_FAST_CHECKOUT_TRIGGER_VALIDATOR_H_ #include "chrome/browser/fast_checkout/fast_checkout_enums.h" -#include "components/autofill/content/browser/content_autofill_driver.h" +#include "components/autofill/core/browser/browser_autofill_manager.h" constexpr char kUmaKeyFastCheckoutTriggerOutcome[] = "Autofill.FastCheckout.TriggerOutcome"; @@ -22,12 +22,12 @@ // Returns `true` if a Fast Checkout run should be permitted, `false` // otherwise. Logs outcome to UMA and chrome://autofill-internals. - virtual bool ShouldRun( - const autofill::FormData& form, - const autofill::FormFieldData& field, - const FastCheckoutUIState ui_state, - const bool is_running, - const autofill::ContentAutofillDriver* autofill_driver) const = 0; + virtual bool ShouldRun(const autofill::FormData& form, + const autofill::FormFieldData& field, + const FastCheckoutUIState ui_state, + const bool is_running, + const base::WeakPtr<autofill::AutofillManager> + autofill_manager) const = 0; // Returns `true` if the current profile has Autofill data enabled and at // least one valid Autofill profile and credit card stored, `false` otherwise.
diff --git a/chrome/browser/fast_checkout/fast_checkout_trigger_validator_impl.cc b/chrome/browser/fast_checkout/fast_checkout_trigger_validator_impl.cc index 6b43a0d..d91600b7 100644 --- a/chrome/browser/fast_checkout/fast_checkout_trigger_validator_impl.cc +++ b/chrome/browser/fast_checkout/fast_checkout_trigger_validator_impl.cc
@@ -33,7 +33,7 @@ const autofill::FormFieldData& field, const FastCheckoutUIState ui_state, const bool is_running, - const autofill::ContentAutofillDriver* autofill_driver) const { + const base::WeakPtr<autofill::AutofillManager> autofill_manager) const { LogAutofillInternals( "Start of checking whether a Fast Checkout run should be permitted."); @@ -89,7 +89,7 @@ } // Trigger only if the UI is available. - if (!autofill_driver->CanShowAutofillUi()) { + if (!autofill_manager->CanShowAutofillUi()) { LogUmaTriggerOutcome( FastCheckoutTriggerOutcome::kFailureCannotShowAutofillUi); LogAutofillInternals("not triggered because Autofill UI cannot be shown.");
diff --git a/chrome/browser/fast_checkout/fast_checkout_trigger_validator_impl.h b/chrome/browser/fast_checkout/fast_checkout_trigger_validator_impl.h index ae2c23fe..3b08230 100644 --- a/chrome/browser/fast_checkout/fast_checkout_trigger_validator_impl.h +++ b/chrome/browser/fast_checkout/fast_checkout_trigger_validator_impl.h
@@ -9,7 +9,6 @@ #include "chrome/browser/fast_checkout/fast_checkout_enums.h" #include "chrome/browser/fast_checkout/fast_checkout_personal_data_helper.h" #include "chrome/browser/fast_checkout/fast_checkout_trigger_validator.h" -#include "components/autofill/content/browser/content_autofill_driver.h" class FastCheckoutTriggerValidatorImpl : public FastCheckoutTriggerValidator { public: @@ -25,12 +24,12 @@ const FastCheckoutTriggerValidatorImpl&) = delete; // FastCheckoutTriggerValidator: - bool ShouldRun( - const autofill::FormData& form, - const autofill::FormFieldData& field, - const FastCheckoutUIState ui_state, - const bool is_running, - const autofill::ContentAutofillDriver* autofill_driver) const override; + bool ShouldRun(const autofill::FormData& form, + const autofill::FormFieldData& field, + const FastCheckoutUIState ui_state, + const bool is_running, + const base::WeakPtr<autofill::AutofillManager> + autofill_manager) const override; bool HasValidPersonalData() const override; private:
diff --git a/chrome/browser/fast_checkout/fast_checkout_trigger_validator_impl_unittest.cc b/chrome/browser/fast_checkout/fast_checkout_trigger_validator_impl_unittest.cc index 0ceee02..5a1fec42 100644 --- a/chrome/browser/fast_checkout/fast_checkout_trigger_validator_impl_unittest.cc +++ b/chrome/browser/fast_checkout/fast_checkout_trigger_validator_impl_unittest.cc
@@ -6,13 +6,14 @@ #include "base/test/metrics/histogram_tester.h" #include "base/test/scoped_feature_list.h" -#include "chrome/browser/fast_checkout/fast_checkout_capabilities_fetcher.h" #include "chrome/browser/fast_checkout/fast_checkout_features.h" #include "chrome/browser/fast_checkout/fast_checkout_personal_data_helper.h" +#include "chrome/browser/fast_checkout/mock_fast_checkout_capabilities_fetcher.h" #include "chrome/test/base/chrome_render_view_host_test_harness.h" -#include "components/autofill/content/browser/content_autofill_driver.h" #include "components/autofill/core/browser/autofill_test_utils.h" #include "components/autofill/core/browser/test_autofill_client.h" +#include "components/autofill/core/browser/test_autofill_driver.h" +#include "components/autofill/core/browser/test_browser_autofill_manager.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" @@ -20,11 +21,12 @@ using ::testing::Return; -class MockContentAutofillDriver : public autofill::ContentAutofillDriver { +class MockBrowserAutofillManager : public autofill::TestBrowserAutofillManager { public: - MockContentAutofillDriver() - : autofill::ContentAutofillDriver(nullptr, nullptr) {} - ~MockContentAutofillDriver() override = default; + MockBrowserAutofillManager(autofill::TestAutofillDriver* driver, + autofill::TestAutofillClient* client) + : autofill::TestBrowserAutofillManager(driver, client) {} + ~MockBrowserAutofillManager() override = default; MOCK_METHOD(bool, CanShowAutofillUi, (), (const override)); }; @@ -35,16 +37,6 @@ MOCK_METHOD(bool, IsContextSecure, (), (const override)); }; -class MockCapabilitiesFetcher : public FastCheckoutCapabilitiesFetcher { - public: - MOCK_METHOD(void, FetchCapabilities, (), ()); - MOCK_METHOD(bool, - IsTriggerFormSupported, - (const url::Origin& origin, - autofill::FormSignature form_signature), - ()); -}; - class MockPersonalDataHelper : public FastCheckoutPersonalDataHelper { public: MockPersonalDataHelper() = default; @@ -53,23 +45,23 @@ MOCK_METHOD(std::vector<autofill::CreditCard*>, GetValidCreditCards, (), - (const)); + (const override)); MOCK_METHOD(std::vector<autofill::AutofillProfile*>, GetValidAddressProfiles, (), - (const)); + (const override)); MOCK_METHOD(autofill::PersonalDataManager*, GetPersonalDataManager, (), - (const)); + (const override)); MOCK_METHOD(std::vector<autofill::AutofillProfile*>, GetProfilesToSuggest, (), - (const)); + (const override)); MOCK_METHOD(std::vector<autofill::CreditCard*>, GetCreditCardsToSuggest, (), - (const)); + (const override)); }; class MockPersonalDataManager : public autofill::PersonalDataManager { @@ -77,8 +69,8 @@ MockPersonalDataManager() : PersonalDataManager("en-US") {} ~MockPersonalDataManager() override = default; - MOCK_METHOD(bool, IsAutofillProfileEnabled, (), (const)); - MOCK_METHOD(bool, IsAutofillCreditCardEnabled, (), (const)); + MOCK_METHOD(bool, IsAutofillProfileEnabled, (), (const override)); + MOCK_METHOD(bool, IsAutofillCreditCardEnabled, (), (const override)); }; class FastCheckoutTriggerValidatorTest @@ -96,15 +88,18 @@ pdm_ = std::make_unique<MockPersonalDataManager>(); autofill_client_ = std::make_unique<MockAutofillClient>(); - capabilities_fetcher_ = std::make_unique<MockCapabilitiesFetcher>(); + capabilities_fetcher_ = + std::make_unique<MockFastCheckoutCapabilitiesFetcher>(); personal_data_helper_ = std::make_unique<MockPersonalDataHelper>(); - autofill_driver_ = std::make_unique<MockContentAutofillDriver>(); + autofill_driver_ = std::make_unique<autofill::TestAutofillDriver>(); + autofill_manager_ = std::make_unique<MockBrowserAutofillManager>( + autofill_driver_.get(), autofill_client_.get()); validator_ = std::make_unique<FastCheckoutTriggerValidatorImpl>( autofill_client(), capabilities_fetcher(), personal_data_helper()); credit_card_ = autofill::test::GetCreditCard(); profile_ = autofill::test::GetFullProfile(); - ON_CALL(*autofill_driver(), CanShowAutofillUi).WillByDefault(Return(true)); + ON_CALL(*autofill_manager(), CanShowAutofillUi).WillByDefault(Return(true)); ON_CALL(*capabilities_fetcher(), IsTriggerFormSupported) .WillByDefault(Return(true)); ON_CALL(*personal_data_helper(), GetValidCreditCards) @@ -122,20 +117,20 @@ MockPersonalDataManager* pdm() { return pdm_.get(); } MockAutofillClient* autofill_client() { return autofill_client_.get(); } - MockCapabilitiesFetcher* capabilities_fetcher() { + MockFastCheckoutCapabilitiesFetcher* capabilities_fetcher() { return capabilities_fetcher_.get(); } MockPersonalDataHelper* personal_data_helper() { return personal_data_helper_.get(); } - MockContentAutofillDriver* autofill_driver() { - return autofill_driver_.get(); + MockBrowserAutofillManager* autofill_manager() { + return autofill_manager_.get(); } FastCheckoutTriggerValidatorImpl* validator() { return validator_.get(); } bool ShouldRun() { return validator()->ShouldRun(form_, field_, ui_state_, is_running_, - autofill_driver()); + autofill_manager()->GetWeakPtr()); } // Protected for access in tests below. @@ -149,10 +144,11 @@ autofill::CreditCard credit_card_; autofill::FormData form_; base::test::ScopedFeatureList feature_list_; + std::unique_ptr<autofill::TestAutofillDriver> autofill_driver_; std::unique_ptr<FastCheckoutTriggerValidatorImpl> validator_; std::unique_ptr<MockAutofillClient> autofill_client_; - std::unique_ptr<MockCapabilitiesFetcher> capabilities_fetcher_; - std::unique_ptr<MockContentAutofillDriver> autofill_driver_; + std::unique_ptr<MockFastCheckoutCapabilitiesFetcher> capabilities_fetcher_; + std::unique_ptr<MockBrowserAutofillManager> autofill_manager_; std::unique_ptr<MockPersonalDataHelper> personal_data_helper_; std::unique_ptr<MockPersonalDataManager> pdm_; }; @@ -240,7 +236,7 @@ TEST_F(FastCheckoutTriggerValidatorTest, ShouldRun_CannotShowAutofillUi_ReturnsFalse) { - ON_CALL(*autofill_driver(), CanShowAutofillUi).WillByDefault(Return(false)); + ON_CALL(*autofill_manager(), CanShowAutofillUi).WillByDefault(Return(false)); EXPECT_FALSE(ShouldRun()); histogram_tester_.ExpectUniqueSample(
diff --git a/chrome/browser/fast_checkout/mock_fast_checkout_capabilities_fetcher.h b/chrome/browser/fast_checkout/mock_fast_checkout_capabilities_fetcher.h index 5899613..319f8c0f 100644 --- a/chrome/browser/fast_checkout/mock_fast_checkout_capabilities_fetcher.h +++ b/chrome/browser/fast_checkout/mock_fast_checkout_capabilities_fetcher.h
@@ -22,6 +22,10 @@ IsTriggerFormSupported, (const url::Origin&, autofill::FormSignature), (override)); + MOCK_METHOD(base::flat_set<autofill::FormSignature>, + GetFormsToFill, + (const url::Origin& origin), + (override)); }; #endif // CHROME_BROWSER_FAST_CHECKOUT_MOCK_FAST_CHECKOUT_CAPABILITIES_FETCHER_H_
diff --git a/chrome/browser/flag-metadata.json b/chrome/browser/flag-metadata.json index e40c270..67bd430 100644 --- a/chrome/browser/flag-metadata.json +++ b/chrome/browser/flag-metadata.json
@@ -1357,7 +1357,7 @@ { "name": "destroy-profile-on-browser-close", "owners": [ "nicolaso", "droger" ], - "expiry_milestone": 112 + "expiry_milestone": 115 }, { "name": "destroy-system-profiles", @@ -2444,7 +2444,7 @@ { "name": "enable-generic-sensor-extra-classes", "owners": [ "reillyg@chromium.org", "raphael.kubo.da.costa@intel.com" ], - "expiry_milestone": 112 + "expiry_milestone": 116 }, { "name": "enable-get-display-media-set", @@ -3603,7 +3603,7 @@ { "name": "expanded-tab-strip", "owners": [ "rkgibson@google.com", "gambard", "bling-flags@google.com" ], - "expiry_milestone": 105 + "expiry_milestone": 116 }, { "name": "experience-kit-apple-calendar", @@ -3667,7 +3667,7 @@ { "name": "fast-checkout", "owners": [ "vizcay@google.com", "bwolfgang@google.com", "jkeitel@google.com" ], - "expiry_milestone": 112 + "expiry_milestone": 118 }, { "name": "fast-pair", @@ -3901,7 +3901,7 @@ { "name": "force-enable-fast-checkout-capabilities", "owners": [ "vizcay@google.com", "bwolfgang@google.com", "jkeitel@google.com" ], - "expiry_milestone": 115 + "expiry_milestone": 118 }, { "name": "force-gpu-main-thread-to-normal-priority-drdc", @@ -4567,6 +4567,11 @@ "expiry_milestone": 130 }, { + "name": "lacros-selection-ignore", + "owners": [ "asumaneev", "lacros-team@google.com" ], + "expiry_milestone": 130 + }, + { "name": "lacros-stability", "owners": [ "jamescook", "erikchen", "lacros-team@google.com" ], // Once Lacros is launched, this flag can be removed. Until then, this @@ -6996,7 +7001,7 @@ { "name": "use-sf-symbols", "owners": [ "ewannpv", "gambard", "bling-flags@google.com" ], - "expiry_milestone": 112 + "expiry_milestone": 113 }, { "name": "use-sf-symbols-omnibox",
diff --git a/chrome/browser/flag_descriptions.cc b/chrome/browser/flag_descriptions.cc index 1f8acfa..6b66061 100644 --- a/chrome/browser/flag_descriptions.cc +++ b/chrome/browser/flag_descriptions.cc
@@ -5603,6 +5603,12 @@ const char kLacrosSelectionRootfsDescription[] = "Rootfs"; const char kLacrosSelectionStatefulDescription[] = "Stateful"; +const char kLacrosSelectionPolicyIgnoreName[] = + "Ignore lacros-selection policy"; +const char kLacrosSelectionPolicyIgnoreDescription[] = + "Makes the lacros-selection policy have no effect. Instead Lacros " + "selection will be controlled by experiment and/or user flags."; + const char kLacrosSupportName[] = "Lacros support"; const char kLacrosSupportDescription[] = "Support for the experimental lacros-chrome browser. Please note that the "
diff --git a/chrome/browser/flag_descriptions.h b/chrome/browser/flag_descriptions.h index 1abee7b..b68a7d4 100644 --- a/chrome/browser/flag_descriptions.h +++ b/chrome/browser/flag_descriptions.h
@@ -3233,6 +3233,9 @@ extern const char kLacrosSelectionRootfsDescription[]; extern const char kLacrosSelectionStatefulDescription[]; +extern const char kLacrosSelectionPolicyIgnoreName[]; +extern const char kLacrosSelectionPolicyIgnoreDescription[]; + extern const char kLacrosSupportName[]; extern const char kLacrosSupportDescription[];
diff --git a/chrome/browser/lacros/browser_launcher_browsertest.cc b/chrome/browser/lacros/browser_launcher_browsertest.cc index 132583c..8e0ceb3 100644 --- a/chrome/browser/lacros/browser_launcher_browsertest.cc +++ b/chrome/browser/lacros/browser_launcher_browsertest.cc
@@ -3,9 +3,9 @@ // found in the LICENSE file. #include "base/containers/contains.h" -#include "base/run_loop.h" #include "base/test/bind.h" #include "base/test/scoped_feature_list.h" +#include "base/test/test_future.h" #include "chrome/browser/lacros/browser_service_lacros.h" #include "chrome/browser/lifetime/application_lifetime.h" #include "chrome/browser/prefs/session_startup_pref.h" @@ -85,12 +85,13 @@ } void NewWindowSync(bool incognito, bool should_trigger_session_restore) { - base::RunLoop run_loop; + base::test::TestFuture<void> new_window_future; browser_service()->NewWindow( incognito, should_trigger_session_restore, display::Screen::GetScreen()->GetDisplayForNewWindows().id(), - run_loop.QuitClosure()); - run_loop.Run(); + new_window_future.GetCallback()); + ASSERT_TRUE(new_window_future.Wait()) + << "NewWindow did not trigger the callback."; } void DisableWelcomePages(const std::vector<Profile*>& profiles) { @@ -281,10 +282,12 @@ EXPECT_TRUE(base::Contains(last_opened_profiles, profile2)); // Trigger Lacros full restore. - base::RunLoop run_loop; - testing::SessionsRestoredWaiter restore_waiter(run_loop.QuitClosure(), 2); + base::test::TestFuture<void> restore_waiter_future; + testing::SessionsRestoredWaiter restore_waiter( + restore_waiter_future.GetCallback(), 2); browser_service()->OpenForFullRestore(/*skip_crash_restore=*/true); - run_loop.Run(); + ASSERT_TRUE(restore_waiter_future.Wait()) + << "restore_waiter did not trigger the callback."; // The startup URLs are ignored, and instead the last open sessions are // restored. @@ -375,10 +378,12 @@ // Trigger Lacros full restore. EXPECT_FALSE(profile->restored_last_session()); - base::RunLoop run_loop; - testing::SessionsRestoredWaiter restore_waiter(run_loop.QuitClosure(), 1); + base::test::TestFuture<void> restore_waiter_future; + testing::SessionsRestoredWaiter restore_waiter( + restore_waiter_future.GetCallback(), 1); browser_service()->OpenForFullRestore(/*skip_crash_restore=*/true); - run_loop.Run(); + ASSERT_TRUE(restore_waiter_future.Wait()) + << "restore_waiter did not trigger the callback."; // The last session should be logged as restored. EXPECT_TRUE(profile->restored_last_session()); @@ -452,10 +457,12 @@ ASSERT_EQ(0u, BrowserList::GetInstance()->size()); // Trigger a new window with session restore. - base::RunLoop run_loop; - testing::SessionsRestoredWaiter restore_waiter(run_loop.QuitClosure(), 1); + base::test::TestFuture<void> restore_waiter_future; + testing::SessionsRestoredWaiter restore_waiter( + restore_waiter_future.GetCallback(), 1); NewWindowSync(/*incognito=*/false, /*should_trigger_session_restore=*/true); - run_loop.Run(); + ASSERT_TRUE(restore_waiter_future.Wait()) + << "restore_waiter did not trigger the callback."; // The browser window should be restored but app browser should not. ASSERT_EQ(1u, chrome::GetBrowserCount(profile)); @@ -557,10 +564,12 @@ ->SetLastSessionExitTypeForTest(ExitType::kCrashed); // Trigger Lacros full restore and skip crash restore prompts. - base::RunLoop run_loop; - testing::SessionsRestoredWaiter restore_waiter(run_loop.QuitClosure(), 1); + base::test::TestFuture<void> restore_waiter_future; + testing::SessionsRestoredWaiter restore_waiter( + restore_waiter_future.GetCallback(), 1); browser_service()->OpenForFullRestore(/*skip_crash_restore=*/true); - run_loop.Run(); + ASSERT_TRUE(restore_waiter_future.Wait()) + << "restore_waiter did not trigger the callback."; // The browser should be restored (ignoring startup preference). Browser* new_browser = nullptr;
diff --git a/chrome/browser/page_load_metrics/observers/prerender_page_load_metrics_observer_browsertest.cc b/chrome/browser/page_load_metrics/observers/prerender_page_load_metrics_observer_browsertest.cc index 6752c1e..6593738 100644 --- a/chrome/browser/page_load_metrics/observers/prerender_page_load_metrics_observer_browsertest.cc +++ b/chrome/browser/page_load_metrics/observers/prerender_page_load_metrics_observer_browsertest.cc
@@ -348,8 +348,7 @@ histogram_tester().ExpectTotalCount( prerender_helper_.GenerateHistogramName( internal::kHistogramPrerenderActivationToLargestContentfulPaint2, - content::PrerenderTriggerType::kEmbedder, - prerender_utils::kDirectUrlInputMetricSuffix), + content::PrerenderTriggerType::kSpeculationRule, ""), 0); }
diff --git a/chrome/browser/policy/configuration_policy_handler_list_factory.cc b/chrome/browser/policy/configuration_policy_handler_list_factory.cc index 58d8831..f9ab8c5 100644 --- a/chrome/browser/policy/configuration_policy_handler_list_factory.cc +++ b/chrome/browser/policy/configuration_policy_handler_list_factory.cc
@@ -2521,6 +2521,12 @@ handlers->AddHandler( std::make_unique<extensions::ExtensionSettingsPolicyHandler>( chrome_schema)); +#if !BUILDFLAG(IS_FUCHSIA) + handlers->AddHandler(std::make_unique<IntRangePolicyHandler>( + key::kExtensionUnpublishedAvailability, + extensions::pref_names::kExtensionUnpublishedAvailability, + /*min=*/0, /*max=*/2, /*clamp=*/false)); +#endif // !BUILDFLAG(IS_FUCHSIA) handlers->AddHandler(std::make_unique<IntRangePolicyHandler>( key::kExtensionManifestV2Availability, extensions::pref_names::kManifestV2Availability, /*min=*/0, /*max=*/3,
diff --git a/chrome/browser/profiles/profile_impl.cc b/chrome/browser/profiles/profile_impl.cc index 1340e75..4855da5 100644 --- a/chrome/browser/profiles/profile_impl.cc +++ b/chrome/browser/profiles/profile_impl.cc
@@ -657,6 +657,7 @@ policy::PolicyNamespace(policy::POLICY_DOMAIN_CHROME, std::string())); crosapi::browser_util::CacheLacrosAvailability(map); crosapi::browser_util::CacheLacrosDataBackwardMigrationMode(map); + crosapi::browser_util::CacheLacrosSelection(map); } #endif } @@ -1185,6 +1186,7 @@ policy::PolicyNamespace(policy::POLICY_DOMAIN_CHROME, std::string())); crosapi::browser_util::CacheLacrosAvailability(map); crosapi::browser_util::CacheLacrosDataBackwardMigrationMode(map); + crosapi::browser_util::CacheLacrosSelection(map); } ash::UserSessionManager::GetInstance()->RespectLocalePreferenceWrapper(
diff --git a/chrome/browser/resources/chromeos/emoji_picker/constants.ts b/chrome/browser/resources/chromeos/emoji_picker/constants.ts index 0febc15..f2ee677 100644 --- a/chrome/browser/resources/chromeos/emoji_picker/constants.ts +++ b/chrome/browser/resources/chromeos/emoji_picker/constants.ts
@@ -49,7 +49,7 @@ `${EMOJI_PICKER_TOTAL_EMOJI_WIDTH}px`; export const TAB_BUTTON_MARGIN_PX = `${TAB_BUTTON_MARGIN}px`; export const TEXT_GROUP_BUTTON_PADDING_PX = `${TEXT_GROUP_BUTTON_PADDING}px`; -export const TRENDING = '#trending'; +export const TRENDING = 'Trending'; // 24 hours is equivalent to 86400000 milliseconds. export const TWENTY_FOUR_HOURS = 86400000; export const GIF_VALIDATION_DATE = 'gifValidationDate';
diff --git a/chrome/browser/resources/chromeos/emoji_picker/emoji_picker.html b/chrome/browser/resources/chromeos/emoji_picker/emoji_picker.html index 73c791a..ba633a2 100644 --- a/chrome/browser/resources/chromeos/emoji_picker/emoji_picker.html +++ b/chrome/browser/resources/chromeos/emoji_picker/emoji_picker.html
@@ -347,29 +347,16 @@ </div> <div class="divider"></div> <div class="side-padding" id="groups" on-scroll="onEmojiScroll"> - <template is="dom-if" if="[[!isInfinite(category)]]"> - <template is="dom-repeat" items="[[categoriesGroupElements]]"> - <div data-group$="[[item.groupId]]"> - <emoji-group data="[[item.emoji]]" - group="[[item.name]]" - preferred="[[item.preferences]]" - clearable$="[[item.isHistory]]" - category$="[[item.category]]" - class$="[[getEmojiGroupClassNames(item.isHistory,item.emoji)]]"> - </emoji-group> - </div> - </template> - </template> - <template is="dom-if" if="[[isInfinite(category)]]"> - <div data-group$="[[activeInfiniteGroupElements]]"> - <emoji-group data="[[activeInfiniteGroupElements.emoji]]" - group="[[activeInfiniteGroupElements.name]]" - preferred="[[activeInfiniteGroupElements.preferences]]" - clearable$="[[activeInfiniteGroupElements.isHistory]]" - category$="[[activeInfiniteGroupElements.category]]" - class$="[[getEmojiGroupClassNames(activeInfiniteGroupElements.isHistory,activeInfiniteGroupElements.emoji)]]"> - </emoji-group> - </div> + <template is="dom-repeat" items="[[categoriesGroupElements]]"> + <div data-group$="[[item.groupId]]"> + <emoji-group data="[[item.emoji]]" + group="[[item.name]]" + preferred="[[item.preferences]]" + clearable$="[[item.isHistory]]" + category$="[[item.category]]" + class$="[[getEmojiGroupClassNames(item.emoji,item,category,activeInfiniteGroupId)]]"> + </emoji-group> + </div> </template> </div> <!-- Render invisible dummy tab to temporarily calculate width of a tab. -->
diff --git a/chrome/browser/resources/chromeos/emoji_picker/emoji_picker.ts b/chrome/browser/resources/chromeos/emoji_picker/emoji_picker.ts index d290d41..ea6e27f 100644 --- a/chrome/browser/resources/chromeos/emoji_picker/emoji_picker.ts +++ b/chrome/browser/resources/chromeos/emoji_picker/emoji_picker.ts
@@ -56,7 +56,7 @@ dummyTab: {type: Object, value: () => ({})}, categoriesData: {type: Array, value: () => ([])}, categoriesGroupElements: {type: Array, value: () => ([])}, - activeInfiniteGroupElements: {type: Object, value: () => ({})}, + activeInfiniteGroupId: {type: String, value: null}, categoriesHistory: {type: Object, value: () => ({})}, pagination: {type: Number, value: 1, observer: 'onPaginationChanged'}, searchLazyIndexing: {type: Boolean, value: true}, @@ -78,7 +78,7 @@ private allCategoryTabs: SubcategoryData[]; categoriesData: EmojiGroupData; categoriesGroupElements: EmojiGroupElement[]; - activeInfiniteGroupElements: EmojiGroupElement; + activeInfiniteGroupId: string|null; // null before Trending GIFs are fetched private categoriesHistory: {[index in CategoryEnum]: RecentlyUsedStore|null}; private pagination: number; private searchLazyIndexing: boolean; @@ -339,15 +339,33 @@ this.gifDataInitialised = true; this.updateCategoryData(trendingGifs, CategoryEnum.GIF); - const trendingCategoryIndex = this.allCategoryTabs.findIndex( - tab => tab.name === constants.TRENDING); - this.activeInfiniteGroupElements = this.createEmojiGroupElement( - trendingGifsElement, {}, false, trendingCategoryIndex); + this.activeInfiniteGroupId = + this.allCategoryTabs + .find(tab => tab.name === constants.TRENDING) + ?.groupId as string; }); } } - isInfinite(category: CategoryEnum) { + private canScrollToGroup(category: CategoryEnum, groupId: string): boolean { + if (!this.isInfiniteCategory(category)) { + return true; + } + + // There will always be a trending GIF group (initialised at beginning), + // hence it is safe to cast as SubcategoryData + const trendingGifGroup = + this.allCategoryTabs.find( + tab => + (tab.category === CategoryEnum.GIF && + tab.name === constants.TRENDING)) as SubcategoryData; + const historyGifGroup = this.categoriesGroupElements.find( + group => group.category === CategoryEnum.GIF && group.isHistory); + return [trendingGifGroup.groupId, historyGifGroup?.groupId].includes( + groupId); + } + + private isInfiniteCategory(category: CategoryEnum) { return category === CategoryEnum.GIF; } @@ -532,20 +550,6 @@ }); } - selectTextEmojiGroup(newGroup: string) { - // focus and scroll to selected group's first emoji. - const group = - this.shadowRoot?.querySelector(`div[data-group="${newGroup}"]`); - - if (group) { - const target = (group.querySelector('.group')?.shadowRoot?.querySelector( - '#fake-focus-target')) as HTMLElement | - null; - target?.focus(); - group.scrollIntoView(); - } - } - async setGifGroupElements(activeGroupId: string) { // Check if GIFs have already been previously fetched and cached let gifGroupElements = this.categoriesGroupElements.find( @@ -570,24 +574,33 @@ gifElements, {}, false, activeCategoryIndex); } - this.setActiveInfiniteGroupElements(activeGroupId, gifGroupElements); + this.setActiveInfiniteGroup(activeGroupId); if (!isCached) { this.categoriesGroupElements = [...this.categoriesGroupElements, gifGroupElements]; } } - setActiveInfiniteGroupElements( - activeGroupId: string, gifGroupElements: EmojiGroupElement) { + setActiveInfiniteGroup(activeGroupId: string) { this.updateActiveGroup(activeGroupId); - this.activeInfiniteGroupElements = gifGroupElements; + this.activeInfiniteGroupId = activeGroupId; } async selectGroup(newGroup: string) { if (this.category === CategoryEnum.GIF) { this.setGifGroupElements(newGroup); - } else { - this.selectTextEmojiGroup(newGroup); + } + + // focus and scroll to selected group's first emoji. + const group = + this.shadowRoot?.querySelector(`div[data-group="${newGroup}"]`); + + if (group) { + const target = (group.querySelector('.group')?.shadowRoot?.querySelector( + '#fake-focus-target')) as HTMLElement | + null; + target?.focus(); + group.scrollIntoView(); } } @@ -599,12 +612,8 @@ clearTimeout(this.scrollTimeout); } this.scrollTimeout = setTimeout(() => { - // Active category and group will not change via scrolling - // for infinite emoji types e.g. GIFs - if (!this.isInfinite(this.category)) { - this.updateActiveCategory(); - this.updateActiveGroup(); - } + this.updateActiveCategory(); + this.updateActiveGroup(); }, 100); } @@ -642,8 +651,6 @@ // GIF group tabs movement isn't affected by scrolling this.groupTabsMoving = false; - // TODO(b/265731647) Set the GIF elements for the first GIF tab group - // Awaiting (b/263920562) to be merged in } else { this.scrollToGroup(nextTab?.groupId); this.groupTabsMoving = true; @@ -740,18 +747,8 @@ */ updateActiveGroup(groupId?: string) { let activeGroupId = groupId; - if (activeGroupId == null && this.category !== CategoryEnum.GIF) { + if (groupId == null) { activeGroupId = this.getActiveGroupIdFromScrollPosition(); - } else if (activeGroupId == null && this.category === CategoryEnum.GIF) { - // Set the default active group to be trending - // TODO(b/265594436): Display correct first group - // (account for recently used if there are history items) - activeGroupId = - this.categoriesGroupElements - .find( - groupElement => groupElement.category === CategoryEnum.GIF && - groupElement.name === constants.TRENDING) - ?.groupId; } this.set( 'pagination', this.getPaginationFromGroupId(activeGroupId as string)); @@ -1014,10 +1011,16 @@ /** * Gets HTML classes for an emoji group element. * + * The emojis need to be passed in directly so Polymer registers changes + * e.g. when clearing history emojis. + * The currEmojiGroup is passed in for additional attribute info. + * * @returns {string} HTML element class attribute. */ - getEmojiGroupClassNames(isHistory: boolean, emojis: EmojiVariants[]) { - const baseClassNames = isHistory ? 'group history' : 'group'; + getEmojiGroupClassNames( + emojis: EmojiVariants[], currEmojiGroup: EmojiGroupElement, + activeCategory: CategoryEnum, activeInfiniteGroupId: string) { + const baseClassNames = currEmojiGroup.isHistory ? 'group history' : 'group'; // Make emoji hidden if it is empty. // Note: Filtering empty groups in dom-repeat is expensive due to @@ -1025,6 +1028,33 @@ if (!emojis || emojis.length === 0) { return baseClassNames + ' hidden'; } + + if (!this.gifSupport) { + return baseClassNames; + } + + // If the active group can be reached via scrolling (i.e. emojis, emoticons, + // symbols, recently used GIFs, trending GIFs), whereas the current group + // being rendered cannot, the current group should be hidden, and vice + // versa. i.e. When on an emoji group, you can scroll down to other emoticon + // or symbol groups, or recently used or trending GIFs, but cannot scroll + // past trending GIFs to view #tag GIF groups. + const canScrollToActiveGroup = + this.canScrollToGroup(activeCategory, activeInfiniteGroupId); + const canScrollToCurrGroup = + this.canScrollToGroup(currEmojiGroup.category, currEmojiGroup.groupId); + const canScrollToCurrGroupFromActive = + (canScrollToCurrGroup && canScrollToActiveGroup); + + // For GIF category groups (not including history or recently used), only + // the currently active group should be displayed and all other groups + // should be hidden. + const currGroupIsActiveInfiniteGroup = + currEmojiGroup.groupId === activeInfiniteGroupId; + + if (!canScrollToCurrGroupFromActive && !currGroupIsActiveInfiniteGroup) { + return baseClassNames + ' hidden'; + } return baseClassNames; } @@ -1033,7 +1063,7 @@ * * @returns {EmojiGroupElement} Instance of emoji group element. */ - createEmojiGroupElement( + private createEmojiGroupElement( emoji: EmojiVariants[], preferences: {[index: string]: string}, isHistory: boolean, subcategoryIndex: number): EmojiGroupElement { const baseDetails = { @@ -1147,28 +1177,13 @@ this.set('category', newCategory); this.set('pagination', 1); - // Jump to the GIF section instead of scrolling down - if (newCategory === CategoryEnum.GIF) { - // Currently displays trending items when first clicking on GIF category - // TODO(b/265594436): Display correct first group - // (account for recently used if there are history items) - const trendingGifElements = this.categoriesGroupElements.find( - groupElement => groupElement.category === CategoryEnum.GIF && - groupElement.name === constants.TRENDING); - if (trendingGifElements) { - this.updateActiveGroup(trendingGifElements.groupId); - this.activeInfiniteGroupElements = trendingGifElements; - } - } else { - // Scroll to relevant category elements if emojis, emoticons or symbols - if (this.getSearchContainer()?.searchNotEmpty()) { - this.getSearchContainer()?.setSearchQuery(''); - afterNextRender(this, () => { - this.scrollToGroup(this.emojiGroupTabs[0]?.groupId); - }); - } else { + if (this.getSearchContainer()?.searchNotEmpty()) { + this.getSearchContainer()?.setSearchQuery(''); + afterNextRender(this, () => { this.scrollToGroup(this.emojiGroupTabs[0]?.groupId); - } + }); + } else { + this.scrollToGroup(this.emojiGroupTabs[0]?.groupId); } }
diff --git a/chrome/browser/resources/chromeos/emoji_picker/types.ts b/chrome/browser/resources/chromeos/emoji_picker/types.ts index 70b014bf..a6e29a5e 100644 --- a/chrome/browser/resources/chromeos/emoji_picker/types.ts +++ b/chrome/browser/resources/chromeos/emoji_picker/types.ts
@@ -55,7 +55,7 @@ export interface EmojiGroupElement { name: string; - category: string; + category: CategoryEnum; emoji: EmojiVariants[]; groupId: string; active: boolean;
diff --git a/chrome/browser/resources/password_manager/password_manager_proxy.ts b/chrome/browser/resources/password_manager/password_manager_proxy.ts index a7861fc..441f0cf 100644 --- a/chrome/browser/resources/password_manager/password_manager_proxy.ts +++ b/chrome/browser/resources/password_manager/password_manager_proxy.ts
@@ -203,6 +203,12 @@ * Cancels the export in progress. */ cancelExportPasswords(): void; + + /** + * Switches Biometric authentication before filling state after + * successful authentication. + */ + switchBiometricAuthBeforeFillingState(): void; } /** @@ -333,6 +339,10 @@ chrome.passwordsPrivate.cancelExportPasswords(); } + switchBiometricAuthBeforeFillingState() { + chrome.passwordsPrivate.switchBiometricAuthBeforeFillingState(); + } + static getInstance(): PasswordManagerProxy { return instance || (instance = new PasswordManagerImpl()); }
diff --git a/chrome/browser/resources/password_manager/prefs/pref_toggle_button.ts b/chrome/browser/resources/password_manager/prefs/pref_toggle_button.ts index 50c0564..d69b21d 100644 --- a/chrome/browser/resources/password_manager/prefs/pref_toggle_button.ts +++ b/chrome/browser/resources/password_manager/prefs/pref_toggle_button.ts
@@ -49,6 +49,16 @@ notify: true, reflectToAttribute: true, }, + + /** + * If true, do not automatically set the preference value on user click. + * Confirm the change first then call either sendPrefChange or + * resetToPrefValue accordingly. + */ + changeRequiresValidation: { + type: Boolean, + value: false, + }, }; } @@ -59,6 +69,7 @@ label: string; subLabel: string; checked: boolean; + changeRequiresValidation: boolean; override ready() { super.ready(); @@ -78,6 +89,12 @@ private onHostClick_(e: Event) { e.stopPropagation(); + if (this.changeRequiresValidation) { + this.dispatchEvent(new CustomEvent( + 'validate-and-change-pref', {bubbles: true, composed: true})); + return; + } + this.checked = !this.checked; this.updatePrefValue_(); }
diff --git a/chrome/browser/resources/password_manager/settings_section.html b/chrome/browser/resources/password_manager/settings_section.html index 623d3b5..3f9f5f6 100644 --- a/chrome/browser/resources/password_manager/settings_section.html +++ b/chrome/browser/resources/password_manager/settings_section.html
@@ -41,6 +41,17 @@ sub-label="$i18n{autosigninDescription}" pref-key="credentials_enable_autosignin"> </pref-toggle-button> + <if expr="is_win or is_macosx"> + <template is="dom-if" + if="[[isBiometricAuthenticationForFillingToggleVisible_]]"> + <pref-toggle-button id="biometricAuthenticationToggle" class="hr" + label="$i18n{biometricAuthenticaionForFillingLabel}" + pref-key="biometric_authentication_filling" + change-requires-validation + on-validate-and-change-pref="switchBiometricAuthBeforeFillingState_"> + </pref-toggle-button> + </template> + </if> <cr-link-row id="trustedVaultBanner" class="cr-row settings-cr-link-row" label="$i18n{trustedVaultBannerLabelOfferOptIn}" sub-label="$i18n{trustedVaultBannerSubLabelOfferOptIn}" external>
diff --git a/chrome/browser/resources/password_manager/settings_section.ts b/chrome/browser/resources/password_manager/settings_section.ts index 04f7c38..7d64ea12 100644 --- a/chrome/browser/resources/password_manager/settings_section.ts +++ b/chrome/browser/resources/password_manager/settings_section.ts
@@ -48,6 +48,16 @@ return loadTimeData.getBoolean('isPasswordManagerShortcutInstalled'); }, }, + + // <if expr="is_win or is_macosx"> + isBiometricAuthenticationForFillingToggleVisible_: { + type: Boolean, + value() { + return loadTimeData.getBoolean( + 'biometricAuthenticationForFillingToggleVisible'); + }, + }, + // </if> }; } @@ -96,6 +106,15 @@ event: DomRepeatEvent<chrome.passwordsPrivate.ExceptionEntry>) { PasswordManagerImpl.getInstance().removeBlockedSite(event.model.item.id); } + + // <if expr="is_win or is_macosx"> + private switchBiometricAuthBeforeFillingState_(e: Event) { + const biometricAuthenticationForFillingToggle = + e!.target as PrefToggleButtonElement; + assert(biometricAuthenticationForFillingToggle); + PasswordManagerImpl.getInstance().switchBiometricAuthBeforeFillingState(); + } + // </if> } declare global {
diff --git a/chrome/browser/sharesheet/sharesheet_service.cc b/chrome/browser/sharesheet/sharesheet_service.cc index 3c4f12ed..043c7a0 100644 --- a/chrome/browser/sharesheet/sharesheet_service.cc +++ b/chrome/browser/sharesheet/sharesheet_service.cc
@@ -11,15 +11,17 @@ #include "base/ranges/algorithm.h" #include "base/strings/string_util.h" #include "base/strings/utf_string_conversions.h" +#include "chrome/browser/apps/app_service/app_icon/app_icon_util.h" #include "chrome/browser/apps/app_service/app_service_proxy.h" #include "chrome/browser/apps/app_service/app_service_proxy_factory.h" #include "chrome/browser/apps/app_service/launch_utils.h" #include "chrome/browser/sharesheet/share_action/share_action.h" #include "chrome/browser/sharesheet/sharesheet_service_delegator.h" +#include "chrome/browser/sharesheet/sharesheet_types.h" #include "chrome/grit/generated_resources.h" #include "components/services/app_service/public/cpp/app_launch_util.h" #include "components/services/app_service/public/cpp/app_types.h" -#include "components/services/app_service/public/cpp/intent_util.h" +#include "components/services/app_service/public/cpp/intent.h" #include "content/public/browser/web_contents.h" #include "third_party/abseil-cpp/absl/types/optional.h" #include "ui/base/l10n/l10n_util.h" @@ -278,7 +280,7 @@ if ((*iter)->ShouldShowAction(intent, contains_hosted_document)) { targets.emplace_back(TargetType::kAction, absl::nullopt, (*iter)->GetActionName(), (*iter)->GetActionName(), - absl::nullopt, absl::nullopt); + absl::nullopt, absl::nullopt, false); } ++iter; } @@ -297,13 +299,24 @@ // Making a copy because we move |intent_launch_info| out below. auto app_id = intent_launch_info[index].app_id; - app_service_proxy_->LoadIcon( - app_service_proxy_->AppRegistryCache().GetAppType(app_id), app_id, - apps::IconType::kStandard, kIconSize, - /*allow_placeholder_icon=*/false, - base::BindOnce(&SharesheetService::OnIconLoaded, - weak_factory_.GetWeakPtr(), std::move(intent_launch_info), - std::move(targets), index, std::move(callback))); + absl::optional<apps::IconKey> icon_key = + app_service_proxy_->GetIconKey(app_id); + if (icon_key.has_value()) { + if (intent_launch_info[index].is_dlp_blocked) { + icon_key->icon_effects |= apps::IconEffects::kBlocked; + } + app_service_proxy_->LoadIconFromIconKey( + app_service_proxy_->AppRegistryCache().GetAppType(app_id), app_id, + icon_key.value(), apps::IconType::kStandard, kIconSize, + /*allow_placeholder_icon=*/false, + base::BindOnce(&SharesheetService::OnIconLoaded, + weak_factory_.GetWeakPtr(), + std::move(intent_launch_info), std::move(targets), index, + std::move(callback))); + } else { + OnIconLoaded(std::move(intent_launch_info), std::move(targets), index, + std::move(callback), std::make_unique<apps::IconValue>()); + } } void SharesheetService::OnIconLoaded( @@ -330,11 +343,11 @@ (icon_value && icon_value->icon_type == apps::IconType::kStandard) ? icon_value->uncompressed : gfx::ImageSkia(); - targets.emplace_back(target_type, image_skia, - base::UTF8ToUTF16(launch_entry.app_id), - base::UTF8ToUTF16(update.Name()), - base::UTF8ToUTF16(launch_entry.activity_label), - launch_entry.activity_name); + targets.emplace_back( + target_type, image_skia, base::UTF8ToUTF16(launch_entry.app_id), + base::UTF8ToUTF16(update.Name()), + base::UTF8ToUTF16(launch_entry.activity_label), + launch_entry.activity_name, launch_entry.is_dlp_blocked); }); LoadAppIcons(std::move(intent_launch_info), std::move(targets), index + 1,
diff --git a/chrome/browser/sharesheet/sharesheet_types.cc b/chrome/browser/sharesheet/sharesheet_types.cc index 35a3386..1622dc89 100644 --- a/chrome/browser/sharesheet/sharesheet_types.cc +++ b/chrome/browser/sharesheet/sharesheet_types.cc
@@ -12,13 +12,15 @@ const std::u16string& launch_name, const std::u16string& display_name, const absl::optional<std::u16string>& secondary_display_name, - const absl::optional<std::string>& activity_name) + const absl::optional<std::string>& activity_name, + bool is_dlp_blocked) : type(type), icon(icon), launch_name(launch_name), display_name(display_name), secondary_display_name(secondary_display_name), - activity_name(activity_name) {} + activity_name(activity_name), + is_dlp_blocked(is_dlp_blocked) {} TargetInfo::~TargetInfo() = default;
diff --git a/chrome/browser/sharesheet/sharesheet_types.h b/chrome/browser/sharesheet/sharesheet_types.h index 86379fd..31c5b4cb 100644 --- a/chrome/browser/sharesheet/sharesheet_types.h +++ b/chrome/browser/sharesheet/sharesheet_types.h
@@ -32,7 +32,8 @@ const std::u16string& launch_name, const std::u16string& display_name, const absl::optional<std::u16string>& secondary_display_name, - const absl::optional<std::string>& activity_name); + const absl::optional<std::string>& activity_name, + bool is_dlp_blocked); ~TargetInfo(); // Allow move. @@ -67,6 +68,9 @@ // The activity of the app for the target. This only applies when the app type // is kArc. absl::optional<std::string> activity_name; + + // Whether the target is blocked by Data Leak Prevention (DLP). + bool is_dlp_blocked; }; using DeliveredCallback = base::OnceCallback<void(SharesheetResult success)>;
diff --git a/chrome/browser/signin/chrome_signin_helper.cc b/chrome/browser/signin/chrome_signin_helper.cc index 2d0e3e25..83b5c8d 100644 --- a/chrome/browser/signin/chrome_signin_helper.cc +++ b/chrome/browser/signin/chrome_signin_helper.cc
@@ -48,6 +48,12 @@ #include "chrome/browser/profiles/profile_manager.h" #include "chrome/browser/ui/browser_window.h" #include "components/account_manager_core/chromeos/account_manager_facade_factory.h" + +#if BUILDFLAG(ENABLE_EXTENSIONS) +#include "content/public/browser/render_process_host.h" +#include "extensions/browser/guest_view/web_view/web_view_renderer_state.h" +#endif // BUILDFLAG(ENABLE_EXTENSIONS) + #endif // BUILDFLAG(IS_CHROMEOS) #if BUILDFLAG(IS_CHROMEOS_ASH) @@ -203,18 +209,21 @@ signin_metrics::LogAccountReconcilorStateOnGaiaResponse( account_reconcilor->GetState()); - bool should_ignore_guest_webview = true; + bool should_process_guest_webview_request = false; #if BUILDFLAG(ENABLE_EXTENSIONS) // The mirror headers from some guest web views need to be processed. - should_ignore_guest_webview = - HeaderModificationDelegateImpl::ShouldIgnoreGuestWebViewRequest( + bool is_guest = extensions::WebViewRendererState::GetInstance()->IsGuest( + web_contents->GetPrimaryMainFrame()->GetProcess()->GetID()); + should_process_guest_webview_request = + is_guest && + !HeaderModificationDelegateImpl::ShouldIgnoreGuestWebViewRequest( web_contents); #endif // BUILDFLAG(ENABLE_EXTENSIONS) Browser* browser = chrome::FindBrowserWithWebContents(web_contents); // Do not do anything if the navigation happened in the "background". if ((!browser || !browser->window()->IsActive()) && - should_ignore_guest_webview) { + !should_process_guest_webview_request) { return; }
diff --git a/chrome/browser/signin/mirror_browsertest.cc b/chrome/browser/signin/mirror_browsertest.cc index 7b4501e..ef0211e 100644 --- a/chrome/browser/signin/mirror_browsertest.cc +++ b/chrome/browser/signin/mirror_browsertest.cc
@@ -9,16 +9,13 @@ #include "base/base_switches.h" #include "base/command_line.h" -#include "base/containers/flat_map.h" #include "base/functional/bind.h" #include "base/functional/callback.h" #include "base/functional/callback_helpers.h" #include "base/path_service.h" #include "base/run_loop.h" -#include "base/strings/string_util.h" #include "base/test/bind.h" #include "build/build_config.h" -#include "build/chromeos_buildflags.h" #include "chrome/browser/browser_process.h" #include "chrome/browser/chrome_content_browser_client.h" #include "chrome/browser/extensions/api/identity/web_auth_flow.h" @@ -36,7 +33,6 @@ #include "components/signin/public/base/signin_pref_names.h" #include "content/public/common/content_client.h" #include "content/public/test/browser_test.h" -#include "google_apis/gaia/gaia_switches.h" #include "google_apis/gaia/gaia_urls.h" #include "net/dns/mock_host_resolver.h" #include "net/test/embedded_test_server/embedded_test_server.h" @@ -45,10 +41,6 @@ #include "third_party/blink/public/common/loader/url_loader_throttle.h" #include "url/gurl.h" -#if BUILDFLAG(IS_CHROMEOS_LACROS) -#include "chrome/browser/lacros/account_manager/fake_account_manager_ui_dialog_waiter.h" -#endif - namespace { // A delegate to insert a user generated X-Chrome-Connected header to a specific @@ -283,109 +275,4 @@ RunExtensionConsentTest(extensions::WebAuthFlow::GET_AUTH_TOKEN, true); } -#if BUILDFLAG(IS_CHROMEOS_LACROS) - -// Tests the behavior of Chrome when it receives a Mirror response from Gaia: -// - listens to all network responses coming from Gaia with -// `signin::HeaderModificationDelegate`. -// - parses the Mirror response header with -// `signin::BuildManageAccountsParams()` -// - triggers dialogs based on the action specified in the header, with -// `ProcessMirrorHeader` -// The tests don't display real dialogs. Instead they use the -// `FakeAccountManagerUI` and only check that the dialogs were triggered. -class MirrorResponseBrowserTest : public InProcessBrowserTest { - public: - MirrorResponseBrowserTest(const MirrorResponseBrowserTest&) = delete; - MirrorResponseBrowserTest& operator=(const MirrorResponseBrowserTest&) = - delete; - - protected: - ~MirrorResponseBrowserTest() override = default; - - MirrorResponseBrowserTest() - : https_server_(net::EmbeddedTestServer::TYPE_HTTPS) {} - - // Navigates to Gaia and receives a response with the specified - // "X-Chrome-Manage-Accounts" header. - void ReceiveManageAccountsHeader( - const base::flat_map<std::string, std::string>& header_params) { - std::vector<std::string> parts; - for (const auto& [key, value] : header_params) { - // "=" must be escaped as "%3D" for the embedded server. - const char kEscapedEquals[] = "%3D"; - parts.push_back(key + kEscapedEquals + value); - } - std::string path = std::string("/set-header?X-Chrome-Manage-Accounts: ") + - base::JoinString(parts, ","); - ASSERT_TRUE( - ui_test_utils::NavigateToURL(browser(), https_server_.GetURL(path))); - } - - // InProcessBrowserTest: - void SetUp() override { - https_server_.AddDefaultHandlers(GetChromeTestDataDir()); - ASSERT_TRUE(https_server_.InitializeAndListen()); - InProcessBrowserTest::SetUp(); - } - - void SetUpCommandLine(base::CommandLine* command_line) override { - InProcessBrowserTest::SetUpCommandLine(command_line); - const GURL& base_url = https_server_.base_url(); - command_line->AppendSwitchASCII(switches::kGaiaUrl, base_url.spec()); - command_line->AppendSwitchASCII(switches::kGoogleApisUrl, base_url.spec()); - command_line->AppendSwitchASCII(switches::kLsoUrl, base_url.spec()); - } - - void SetUpOnMainThread() override { - https_server_.StartAcceptingConnections(); - InProcessBrowserTest::SetUpOnMainThread(); - } - - net::EmbeddedTestServer https_server_; - net::test_server::EmbeddedTestServerHandle https_server_handle_; -}; - -// Tests that the "Add Account" dialog is shown when receiving "ADDSESSION" from -// Gaia. -IN_PROC_BROWSER_TEST_F(MirrorResponseBrowserTest, AddSession) { - FakeAccountManagerUIDialogWaiter dialog_waiter( - GetFakeAccountManagerUI(), - FakeAccountManagerUIDialogWaiter::Event::kAddAccount); - ReceiveManageAccountsHeader({{"action", "ADDSESSION"}}); - dialog_waiter.Wait(); -} - -// Tests that the "Settings"" dialog is shown when receiving "DEFAULT" from -// Gaia. -IN_PROC_BROWSER_TEST_F(MirrorResponseBrowserTest, Settings) { - FakeAccountManagerUIDialogWaiter dialog_waiter( - GetFakeAccountManagerUI(), - FakeAccountManagerUIDialogWaiter::Event::kSettings); - ReceiveManageAccountsHeader({{"action", "DEFAULT"}}); - dialog_waiter.Wait(); -} - -// Tests that the "Reauth" dialog is shown when receiving an email from Gaia. -IN_PROC_BROWSER_TEST_F(MirrorResponseBrowserTest, Reauth) { - FakeAccountManagerUIDialogWaiter dialog_waiter( - GetFakeAccountManagerUI(), - FakeAccountManagerUIDialogWaiter::Event::kReauth); - ReceiveManageAccountsHeader( - {{"action", "ADDSESSION"}, {"email", "user@example.com"}}); - dialog_waiter.Wait(); -} - -// Tests that incognito browser is opened when receiving "INCOGNITO" from Gaia. -IN_PROC_BROWSER_TEST_F(MirrorResponseBrowserTest, Incognito) { - ui_test_utils::BrowserChangeObserver browser_change_observer( - /*browser=*/nullptr, - ui_test_utils::BrowserChangeObserver::ChangeType::kAdded); - ReceiveManageAccountsHeader({{"action", "INCOGNITO"}}); - Browser* incognito_browser = browser_change_observer.Wait(); - EXPECT_TRUE(incognito_browser->profile()->IsIncognitoProfile()); -} - -#endif // BUILDFLAG(IS_CHROMEOS_LACROS) - } // namespace
diff --git a/chrome/browser/signin/mirror_interactive_uitest.cc b/chrome/browser/signin/mirror_interactive_uitest.cc new file mode 100644 index 0000000..f6d6e620 --- /dev/null +++ b/chrome/browser/signin/mirror_interactive_uitest.cc
@@ -0,0 +1,169 @@ +// Copyright 2023 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <map> +#include <memory> +#include <string> +#include <utility> + +#include "base/command_line.h" +#include "base/containers/flat_map.h" +#include "base/functional/callback.h" +#include "base/strings/string_util.h" +#include "build/build_config.h" +#include "build/chromeos_buildflags.h" +#include "chrome/browser/chrome_content_browser_client.h" +#include "chrome/browser/profiles/profile.h" +#include "chrome/browser/signin/signin_util.h" +#include "chrome/browser/ui/browser.h" +#include "chrome/browser/ui/browser_finder.h" +#include "chrome/browser/ui/browser_navigator.h" +#include "chrome/browser/ui/browser_navigator_params.h" +#include "chrome/browser/ui/browser_window.h" +#include "chrome/test/base/in_process_browser_test.h" +#include "chrome/test/base/ui_test_utils.h" +#include "content/public/test/browser_test.h" +#include "google_apis/gaia/gaia_switches.h" +#include "net/test/embedded_test_server/embedded_test_server.h" +#include "net/test/embedded_test_server/request_handler_util.h" +#include "third_party/blink/public/common/loader/url_loader_throttle.h" +#include "ui/base/window_open_disposition.h" +#include "url/gurl.h" + +#if BUILDFLAG(IS_CHROMEOS_LACROS) +#include "chrome/browser/lacros/account_manager/fake_account_manager_ui_dialog_waiter.h" +#endif + +// Tests the behavior of Chrome when it receives a Mirror response from Gaia: +// - listens to all network responses coming from Gaia with +// `signin::HeaderModificationDelegate`. +// - parses the Mirror response header with +// `signin::BuildManageAccountsParams()` +// - triggers dialogs based on the action specified in the header, with +// `ProcessMirrorHeader` +// The tests don't display real dialogs. Instead they use the +// `FakeAccountManagerUI` and only check that the dialogs were triggered. +// The tests are interactive_ui_tests because they depend on browser's window +// activation state. +class MirrorResponseBrowserTest : public InProcessBrowserTest { + public: + MirrorResponseBrowserTest(const MirrorResponseBrowserTest&) = delete; + MirrorResponseBrowserTest& operator=(const MirrorResponseBrowserTest&) = + delete; + + protected: + ~MirrorResponseBrowserTest() override = default; + + MirrorResponseBrowserTest() + : https_server_(net::EmbeddedTestServer::TYPE_HTTPS) {} + + // Navigates to Gaia and receives a response with the specified + // "X-Chrome-Manage-Accounts" header. + void ReceiveManageAccountsHeader( + const base::flat_map<std::string, std::string>& header_params) { + ASSERT_TRUE(ui_test_utils::NavigateToURL( + browser(), GetUrlWithManageAccountsHeader(header_params))); + } + + GURL GetUrlWithManageAccountsHeader( + const base::flat_map<std::string, std::string>& header_params) { + std::vector<std::string> parts; + for (const auto& [key, value] : header_params) { + // "=" must be escaped as "%3D" for the embedded server. + const char kEscapedEquals[] = "%3D"; + parts.push_back(key + kEscapedEquals + value); + } + std::string path = std::string("/set-header?X-Chrome-Manage-Accounts: ") + + base::JoinString(parts, ","); + return https_server_.GetURL(path); + } + + // InProcessBrowserTest: + void SetUp() override { + https_server_.AddDefaultHandlers(GetChromeTestDataDir()); + ASSERT_TRUE(https_server_.InitializeAndListen()); + InProcessBrowserTest::SetUp(); + } + + void SetUpCommandLine(base::CommandLine* command_line) override { + InProcessBrowserTest::SetUpCommandLine(command_line); + const GURL& base_url = https_server_.base_url(); + command_line->AppendSwitchASCII(switches::kGaiaUrl, base_url.spec()); + command_line->AppendSwitchASCII(switches::kGoogleApisUrl, base_url.spec()); + command_line->AppendSwitchASCII(switches::kLsoUrl, base_url.spec()); + } + + void SetUpOnMainThread() override { + https_server_.StartAcceptingConnections(); + InProcessBrowserTest::SetUpOnMainThread(); + } + + net::EmbeddedTestServer https_server_; + net::test_server::EmbeddedTestServerHandle https_server_handle_; +}; + +// Following tests try to display the ChromeOS account manager dialogs. They can +// currently be tested only on Lacros which injects a `FakeAccountManagerUI`. +#if BUILDFLAG(IS_CHROMEOS_LACROS) + +// Tests that the "Add Account" dialog is shown when receiving "ADDSESSION" from +// Gaia. +IN_PROC_BROWSER_TEST_F(MirrorResponseBrowserTest, AddSession) { + FakeAccountManagerUIDialogWaiter dialog_waiter( + GetFakeAccountManagerUI(), + FakeAccountManagerUIDialogWaiter::Event::kAddAccount); + ReceiveManageAccountsHeader({{"action", "ADDSESSION"}}); + dialog_waiter.Wait(); +} + +// Tests that the "Settings"" dialog is shown when receiving "DEFAULT" from +// Gaia. +IN_PROC_BROWSER_TEST_F(MirrorResponseBrowserTest, Settings) { + FakeAccountManagerUIDialogWaiter dialog_waiter( + GetFakeAccountManagerUI(), + FakeAccountManagerUIDialogWaiter::Event::kSettings); + ReceiveManageAccountsHeader({{"action", "DEFAULT"}}); + dialog_waiter.Wait(); +} + +// Tests that the "Reauth" dialog is shown when receiving an email from Gaia. +IN_PROC_BROWSER_TEST_F(MirrorResponseBrowserTest, Reauth) { + FakeAccountManagerUIDialogWaiter dialog_waiter( + GetFakeAccountManagerUI(), + FakeAccountManagerUIDialogWaiter::Event::kReauth); + ReceiveManageAccountsHeader( + {{"action", "ADDSESSION"}, {"email", "user@example.com"}}); + dialog_waiter.Wait(); +} + +#endif // BUILDFLAG(IS_CHROMEOS_LACROS) + +// Tests that incognito browser is opened when receiving "INCOGNITO" from Gaia. +IN_PROC_BROWSER_TEST_F(MirrorResponseBrowserTest, Incognito) { + ui_test_utils::BrowserChangeObserver browser_change_observer( + /*browser=*/nullptr, + ui_test_utils::BrowserChangeObserver::ChangeType::kAdded); + ReceiveManageAccountsHeader({{"action", "INCOGNITO"}}); + Browser* incognito_browser = browser_change_observer.Wait(); + EXPECT_TRUE(incognito_browser->profile()->IsIncognitoProfile()); +} + +// Tests that the response is coming from a background browser is ignored. +IN_PROC_BROWSER_TEST_F(MirrorResponseBrowserTest, BackgroundResponseIgnored) { + // Minimize the browser window to disactivate it. + browser()->window()->Minimize(); + EXPECT_FALSE(browser()->window()->IsActive()); + + size_t browser_count = chrome::GetTotalBrowserCount(); + GURL url = GetUrlWithManageAccountsHeader({{"action", "INCOGNITO"}}); + NavigateParams params(browser(), url, ui::PAGE_TRANSITION_FROM_API); + // Use `NEW_BACKGROUND_TAB` to avoid activating `browser()`. + params.disposition = WindowOpenDisposition::NEW_BACKGROUND_TAB; + Navigate(¶ms); + EXPECT_TRUE(content::WaitForLoadStop(params.navigated_or_inserted_contents)); + + // Incognito window should not have been displayed, the browser count stays + // the same. + EXPECT_EQ(chrome::GetTotalBrowserCount(), browser_count); +}
diff --git a/chrome/browser/support_tool/data_collection_module.proto b/chrome/browser/support_tool/data_collection_module.proto index ed97da40..00680ab 100644 --- a/chrome/browser/support_tool/data_collection_module.proto +++ b/chrome/browser/support_tool/data_collection_module.proto
@@ -37,6 +37,27 @@ CHROMEOS_NETWORK_HEALTH = 23; } +// PiiType represent the different known categories of PII that can exist in +// logs. The types here are based on PII types listed in +// components/feedback/pii_types.h. +enum PiiType { + PII_TYPE_UNSPECIFIED = 0; + ANDROID_APP_STORAGE_PATH = 1; + EMAIL = 2; + GAIA_ID = 3; + IPP_ADDRESS = 4; + IP_ADDRESS = 5; + LOCATION_INFO = 6; + MAC_ADDRESS = 7; + UI_HIEARCHY_WINDOW_TITLE = 8; + URL = 9; + SERIAL = 10; + SSID = 11; + STABLE_IDENTIFIER = 12; + VOLUME_LABEL = 13; + EAP = 14; +} + // Used for storing the information about which data colllectors will be used // when Support Tool creates the support packet. message DataCollectionModule {
diff --git a/chrome/browser/sync/sync_startup_tracker.cc b/chrome/browser/sync/sync_startup_tracker.cc index b97ecab..15179cd 100644 --- a/chrome/browser/sync/sync_startup_tracker.cc +++ b/chrome/browser/sync/sync_startup_tracker.cc
@@ -5,8 +5,8 @@ #include "chrome/browser/sync/sync_startup_tracker.h" #include "base/functional/bind.h" +#include "base/notreached.h" #include "components/sync/driver/sync_service.h" -#include "google_apis/gaia/google_service_auth_error.h" #include "third_party/abseil-cpp/absl/types/optional.h" namespace { @@ -69,24 +69,30 @@ return ServiceStartupState::kError; } - // If the sync engine has started up, notify the callback. - if (sync_service->IsEngineInitialized()) { - return ServiceStartupState::kComplete; + // Unrecoverable errors return false for CanSyncFeatureStart(), handled above. + DCHECK(!sync_service->HasUnrecoverableError()); + + switch (sync_service->GetTransportState()) { + case syncer::SyncService::TransportState::DISABLED: + NOTREACHED(); + break; + case syncer::SyncService::TransportState::START_DEFERRED: + case syncer::SyncService::TransportState::INITIALIZING: + // No error detected yet, but the sync engine hasn't started up yet, so + // we're in the pending state. + return ServiceStartupState::kPending; + case syncer::SyncService::TransportState::PAUSED: + // Persistent auth errors lead to sync pausing. + return ServiceStartupState::kError; + case syncer::SyncService::TransportState::PENDING_DESIRED_CONFIGURATION: + case syncer::SyncService::TransportState::CONFIGURING: + case syncer::SyncService::TransportState::ACTIVE: + DCHECK(sync_service->IsEngineInitialized()); + return ServiceStartupState::kComplete; } - // If the sync service has some kind of error, report to the user. - if (sync_service->HasUnrecoverableError()) { - return ServiceStartupState::kError; - } - - // If we have an auth error, exit. - if (sync_service->GetAuthError().state() != GoogleServiceAuthError::NONE) { - return ServiceStartupState::kError; - } - - // No error detected yet, but the sync engine hasn't started up yet, so - // we're in the pending state. - return ServiceStartupState::kPending; + NOTREACHED(); + return ServiceStartupState::kError; } namespace testing {
diff --git a/chrome/browser/sync/test/integration/single_client_contact_info_sync_test.cc b/chrome/browser/sync/test/integration/single_client_contact_info_sync_test.cc index c1326c9..6f69759 100644 --- a/chrome/browser/sync/test/integration/single_client_contact_info_sync_test.cc +++ b/chrome/browser/sync/test/integration/single_client_contact_info_sync_test.cc
@@ -6,9 +6,11 @@ #include "base/test/scoped_feature_list.h" #include "chrome/browser/sync/test/integration/contact_info_helper.h" +#include "chrome/browser/sync/test/integration/encryption_helper.h" #include "chrome/browser/sync/test/integration/fake_server_match_status_checker.h" #include "chrome/browser/sync/test/integration/sync_service_impl_harness.h" #include "chrome/browser/sync/test/integration/sync_test.h" +#include "chrome/browser/sync/test/integration/updated_progress_marker_checker.h" #include "components/autofill/core/browser/contact_info_sync_util.h" #include "components/autofill/core/browser/personal_data_manager.h" #include "components/autofill/core/common/autofill_features.h" @@ -133,6 +135,41 @@ .Wait()); } +// Specialized fixture to test the behavior for custom passphrase users with and +// without kSyncEnableContactInfoDataTypeForCustomPassphraseUsers enabled. +class SingleClientContactInfoPassphraseSyncTest + : public SingleClientContactInfoSyncTest, + public testing::WithParamInterface<bool> { + public: + SingleClientContactInfoPassphraseSyncTest() { + feature_.InitWithFeatureState( + syncer::kSyncEnableContactInfoDataTypeForCustomPassphraseUsers, + EnabledForPassphraseUsersTestParam()); + } + + bool EnabledForPassphraseUsersTestParam() const { return GetParam(); } + + private: + base::test::ScopedFeatureList feature_; +}; + +INSTANTIATE_TEST_SUITE_P(, + SingleClientContactInfoPassphraseSyncTest, + testing::Bool()); + +IN_PROC_BROWSER_TEST_P(SingleClientContactInfoPassphraseSyncTest, Passphrase) { + ASSERT_TRUE(SetupSync()); + ASSERT_TRUE( + GetSyncService(0)->GetActiveDataTypes().Has(syncer::CONTACT_INFO)); + GetSyncService(0)->GetUserSettings()->SetEncryptionPassphrase("123456"); + ASSERT_TRUE( + ServerPassphraseTypeChecker(syncer::PassphraseType::kCustomPassphrase) + .Wait()); + ASSERT_TRUE(UpdatedProgressMarkerChecker(GetSyncService(0)).Wait()); + EXPECT_EQ(GetSyncService(0)->GetActiveDataTypes().Has(syncer::CONTACT_INFO), + EnabledForPassphraseUsersTestParam()); +} + // Specialized fixture that enables AutofillAccountProfilesOnSignIn. class SingleClientContactInfoTransportSyncTest : public SingleClientContactInfoSyncTest {
diff --git a/chrome/browser/touch_to_fill/android/javatests/src/org/chromium/chrome/browser/touch_to_fill/TouchToFillIntegrationTest.java b/chrome/browser/touch_to_fill/android/javatests/src/org/chromium/chrome/browser/touch_to_fill/TouchToFillIntegrationTest.java index 51ce2fb3..a46dae5 100644 --- a/chrome/browser/touch_to_fill/android/javatests/src/org/chromium/chrome/browser/touch_to_fill/TouchToFillIntegrationTest.java +++ b/chrome/browser/touch_to_fill/android/javatests/src/org/chromium/chrome/browser/touch_to_fill/TouchToFillIntegrationTest.java
@@ -35,7 +35,6 @@ import org.chromium.base.test.util.Batch; import org.chromium.base.test.util.CommandLineFlags; import org.chromium.base.test.util.CriteriaHelper; -import org.chromium.base.test.util.DisabledTest; import org.chromium.base.test.util.ScalableTimeout; import org.chromium.chrome.browser.flags.ChromeSwitches; import org.chromium.chrome.browser.touch_to_fill.data.Credential; @@ -149,7 +148,6 @@ @Test @MediumTest - @DisabledTest(message = "crbug.com/1348345") public void testBackDismissesAndCallsCallback() { runOnUiThreadBlocking(() -> { mTouchToFill.showCredentials(
diff --git a/chrome/browser/ui/ash/media_client_impl.cc b/chrome/browser/ui/ash/media_client_impl.cc index 26ceccb7..1a603ae2 100644 --- a/chrome/browser/ui/ash/media_client_impl.cc +++ b/chrome/browser/ui/ash/media_client_impl.cc
@@ -263,11 +263,10 @@ } // Small helper to make sure that `kCameraPrivacySwitchOnNotificationIdPrefix` -// combined with `device_name` always produce the same identifier. +// combined with `device_id` always produce the same identifier. std::string PrivacySwitchOnNotificationIdForDevice( - const std::string& device_name) { - return base::StrCat( - {kCameraPrivacySwitchOnNotificationIdPrefix, device_name}); + const std::string& device_id) { + return base::StrCat({kCameraPrivacySwitchOnNotificationIdPrefix, device_id}); } } // namespace @@ -477,10 +476,10 @@ weak_ptr_factory_.GetWeakPtr())); } else if (state == cros::mojom::CameraPrivacySwitchState::ON) { // The software switch is ON. Clear all hardware switch notifications. - for (const auto& notification_id : existing_notifications_) { - SystemNotificationHelper::GetInstance()->Close(notification_id); + for (auto it = devices_having_visible_notification_.begin(); + it != devices_having_visible_notification_.end();) { + it = RemoveCameraOffNotificationForDevice(*it); } - existing_notifications_.clear(); } } @@ -541,8 +540,7 @@ if (active_camera_client_count_ > 0) { ShowCameraOffNotification(device_id, device_name, /*resurface=*/false); } else { - RemoveCameraOffNotificationByID( - PrivacySwitchOnNotificationIdForDevice(device_name)); + RemoveCameraOffNotificationForDevice(device_id); } } @@ -699,28 +697,31 @@ app_name, std::make_pair(device_id, device_name)); } - const std::string notification_id = - PrivacySwitchOnNotificationIdForDevice(device_name); - if (resurface) { - RemoveCameraOffNotificationByID(notification_id); + RemoveCameraOffNotificationForDevice(device_id); } SystemNotificationHelper::GetInstance()->Display( notification_.builder() - .SetId(notification_id) + .SetId(PrivacySwitchOnNotificationIdForDevice(device_id)) .SetTitleWithArgs(IDS_CAMERA_PRIVACY_SWITCH_ON_NOTIFICATION_TITLE, {device_name_u16}) .SetMessageWithArgs(IDS_CAMERA_PRIVACY_SWITCH_ON_NOTIFICATION_MESSAGE, {device_name_u16}) .Build()); - existing_notifications_.insert(notification_id); + devices_having_visible_notification_.insert(device_id); } -void MediaClientImpl::RemoveCameraOffNotificationByID( - const std::string& notification_id) { - SystemNotificationHelper::GetInstance()->Close(notification_id); - existing_notifications_.erase(notification_id); +base::flat_set<std::string>::iterator +MediaClientImpl::RemoveCameraOffNotificationForDevice( + const std::string& device_id) { + auto it = devices_having_visible_notification_.find(device_id); + if (it != devices_having_visible_notification_.end()) { + SystemNotificationHelper::GetInstance()->Close( + PrivacySwitchOnNotificationIdForDevice(device_id)); + return devices_having_visible_notification_.erase(it); + } + return it; } void MediaClientImpl::OnGetSourceInfosByCameraHWPrivacySwitchStateChanged( @@ -810,8 +811,7 @@ } if (state == cros::mojom::CameraPrivacySwitchState::OFF) { - RemoveCameraOffNotificationByID( - PrivacySwitchOnNotificationIdForDevice(device_name)); + RemoveCameraOffNotificationForDevice(device_id); } } @@ -827,11 +827,20 @@ // As the device is being actively used by the client, display a // notification. ShowCameraOffNotification(device_id, device_name); - } else if (active_camera_client_count_ == 0) { - // Clear the notification for this device as no client is trying to use - // this camera anymore. - RemoveCameraOffNotificationByID( - PrivacySwitchOnNotificationIdForDevice(device_name)); + } else if (!IsDeviceActive(device_id)) { + // No application is actively using this camera. Remove the notification + // for the device if exists. + RemoveCameraOffNotificationForDevice(device_id); + } + } + + // Remove notifications for detached devices if any. + for (auto it = devices_having_visible_notification_.begin(); + it != devices_having_visible_notification_.end();) { + if (IsDeviceActive(*it)) { + ++it; + } else { + it = RemoveCameraOffNotificationForDevice(*it); } } }
diff --git a/chrome/browser/ui/ash/media_client_impl.h b/chrome/browser/ui/ash/media_client_impl.h index 63bc745d..27c3ea25 100644 --- a/chrome/browser/ui/ash/media_client_impl.h +++ b/chrome/browser/ui/ash/media_client_impl.h
@@ -144,9 +144,10 @@ const std::string& device_name, bool resurface = true); - // Removes the camera notification with the give id `notification_id` and - // updates the internal data structures of the class accordingly. - void RemoveCameraOffNotificationByID(const std::string& notification_id); + // Removes the camera notification for device with id `device_id` and returns + // iterator to the next device id in `devices_having_visible_notification_`. + base::flat_set<std::string>::iterator RemoveCameraOffNotificationForDevice( + const std::string& device_id); void OnGetSourceInfosByCameraHWPrivacySwitchStateChanged( const std::string& device_id, @@ -204,9 +205,9 @@ base::flat_map<cros::mojom::CameraClientType, base::flat_set<std::string>> devices_used_by_client_; - // IDs of the existing camera hardware switch notifications in the message - // center. - base::flat_set<std::string> existing_notifications_; + // Set of IDs of the camera devices having a visible notification in the + // message center. + base::flat_set<std::string> devices_having_visible_notification_; // Stores the state of the camera software privacy switch state locally. cros::mojom::CameraPrivacySwitchState camera_sw_privacy_switch_state_ =
diff --git a/chrome/browser/ui/ash/media_client_impl_unittest.cc b/chrome/browser/ui/ash/media_client_impl_unittest.cc index 30637c0..8a08d21 100644 --- a/chrome/browser/ui/ash/media_client_impl_unittest.cc +++ b/chrome/browser/ui/ash/media_client_impl_unittest.cc
@@ -5,6 +5,8 @@ #include "chrome/browser/ui/ash/media_client_impl.h" #include <memory> +#include <string> +#include <vector> #include "ash/public/cpp/media_controller.h" #include "ash/public/cpp/test/test_new_window_delegate.h" @@ -24,6 +26,7 @@ #include "components/services/app_service/public/cpp/capability_access.h" #include "components/services/app_service/public/cpp/capability_access_update.h" #include "components/user_manager/fake_user_manager.h" +#include "media/capture/video/video_capture_device_info.h" #include "testing/gmock/include/gmock/gmock.h" #include "third_party/abseil-cpp/absl/types/optional.h" #include "ui/base/accelerators/media_keys_listener.h" @@ -107,11 +110,14 @@ void RemoveObserver(NotificationDisplayService::Observer* observer) override { } - bool HasNotificationMessageContaining(const std::string& app_name) const { - const std::u16string app_name_u16 = base::UTF8ToUTF16(app_name); + // Returns true if any existing notification contains `keywords` as a + // substring. + bool HasNotificationMessageContaining(const std::string& keywords) const { + const std::u16string keywords_u16 = base::UTF8ToUTF16(keywords); for (const auto& [notification_id, notification] : active_notifications_) { - if (notification.message().find(app_name_u16) != std::u16string::npos) + if (notification.message().find(keywords_u16) != std::u16string::npos) { return true; + } } return false; } @@ -318,6 +324,29 @@ device_id}; } + void OnActiveClientChange( + cros::mojom::CameraClientType type, + const base::flat_set<std::string>& active_device_ids, + int active_client_count) { + media_client_.devices_used_by_client_.insert_or_assign(type, + active_device_ids); + media_client_.active_camera_client_count_ = active_client_count; + + media_client_.OnGetSourceInfosByActiveClientChanged(active_device_ids, + video_capture_devices_); + } + + void AttachCamera(const std::string& device_id, + const std::string& device_name) { + media::VideoCaptureDeviceInfo device_info; + device_info.descriptor.device_id = device_id; + device_info.descriptor.set_display_name(device_name); + video_capture_devices_.push_back(device_info); + } + + // Detaches the most recently attached camera. + void DetachCamera() { video_capture_devices_.pop_back(); } + void ShowCameraOffNotification(const std::string& device_id, const std::string& device_name) { media_client_.ShowCameraOffNotification(device_id, device_name); @@ -355,6 +384,7 @@ user_manager::FakeUserManager user_manager_; base::raw_ptr<MockNewWindowDelegate> new_window_delegate_ = nullptr; std::unique_ptr<ash::TestNewWindowDelegateProvider> window_delegate_provider_; + std::vector<media::VideoCaptureDeviceInfo> video_capture_devices_; }; TEST_F(MediaClientTest, HandleMediaAccelerators) { @@ -595,7 +625,62 @@ EXPECT_CALL(*new_window_delegate_, OpenUrl).Times(1); notification_display_service->SimulateClick( - "ash.media.camera.activity_with_privacy_switch_on.device_name", 0); + "ash.media.camera.activity_with_privacy_switch_on.device_id", 0); EXPECT_EQ(notification_display_service->NumberOfActiveNotifications(), 0u); } + +TEST_F(MediaClientAppUsingCameraInBrowserEnvironmentTest, + NotificationRemovedWhenCameraDetachedOrInactive) { + FakeNotificationDisplayService* notification_display_service = + SetSystemNotificationService(); + + // No notification initially. + EXPECT_EQ(0u, notification_display_service->NumberOfActiveNotifications()); + + const std::string camera1 = "camera1"; + const std::string camera1_name = "Fake camera 1"; + const std::string camera2 = "camera2"; + const std::string camera2_name = "Fake camera 2"; + + // Attach two cameras to the device. Both of the cameras have HW switch. Turn + // the HW switch ON for both of the devices. + AttachCamera(camera1, camera1_name); + SetCameraHWPrivacySwitchState(camera1, + cros::mojom::CameraPrivacySwitchState::ON); + AttachCamera(camera2, camera2_name); + SetCameraHWPrivacySwitchState(camera2, + cros::mojom::CameraPrivacySwitchState::ON); + + // Still no notification. + EXPECT_EQ(notification_display_service->NumberOfActiveNotifications(), 0u); + + // `CHROME` client starts accessing camera1. A hardware switch notification + // for camera1 should be displayed. + OnActiveClientChange(cros::mojom::CameraClientType::CHROME, {camera1}, 1); + EXPECT_EQ(1u, notification_display_service->NumberOfActiveNotifications()); + EXPECT_TRUE(notification_display_service->HasNotificationMessageContaining( + camera1_name)); + + // `CHROME` client starts accessing camera2 as well. A hardware switch + // notification for camera2 should be displayed. + OnActiveClientChange(cros::mojom::CameraClientType::CHROME, + {camera1, camera2}, 1); + EXPECT_EQ(2u, notification_display_service->NumberOfActiveNotifications()); + EXPECT_TRUE(notification_display_service->HasNotificationMessageContaining( + camera2_name)); + + // `CHROME` client stops accessing camera1. The respective notification should + // be removed. + OnActiveClientChange(cros::mojom::CameraClientType::CHROME, {camera2}, 1); + EXPECT_EQ(1u, notification_display_service->NumberOfActiveNotifications()); + EXPECT_FALSE(notification_display_service->HasNotificationMessageContaining( + camera1_name)); + + // Detach camera2. + DetachCamera(); + // `CHROME` client stops accessing camera2 as the camera is detached. The + // respective notification should be removed. + OnActiveClientChange(cros::mojom::CameraClientType::CHROME, {}, 0); + EXPECT_EQ(0u, notification_display_service->NumberOfActiveNotifications()); +}
diff --git a/chrome/browser/ui/ash/sharesheet/sharesheet_bubble_view.cc b/chrome/browser/ui/ash/sharesheet/sharesheet_bubble_view.cc index 3341c10..20b076a 100644 --- a/chrome/browser/ui/ash/sharesheet/sharesheet_bubble_view.cc +++ b/chrome/browser/ui/ash/sharesheet/sharesheet_bubble_view.cc
@@ -377,7 +377,7 @@ base::BindRepeating(&SharesheetBubbleView::TargetButtonPressed, base::Unretained(this), target), display_name, secondary_display_name, icon, - delegator_->GetVectorIcon(display_name))); + delegator_->GetVectorIcon(display_name), target.is_dlp_blocked)); } }
diff --git a/chrome/browser/ui/ash/sharesheet/sharesheet_bubble_view_browsertest.cc b/chrome/browser/ui/ash/sharesheet/sharesheet_bubble_view_browsertest.cc index 3c8075f..4c93e420 100644 --- a/chrome/browser/ui/ash/sharesheet/sharesheet_bubble_view_browsertest.cc +++ b/chrome/browser/ui/ash/sharesheet/sharesheet_bubble_view_browsertest.cc
@@ -9,21 +9,31 @@ #include "ash/shell.h" #include "base/functional/callback_helpers.h" #include "base/test/scoped_feature_list.h" +#include "chrome/browser/apps/app_service/app_service_proxy.h" +#include "chrome/browser/apps/app_service/app_service_proxy_factory.h" +#include "chrome/browser/apps/app_service/app_service_test.h" +#include "chrome/browser/ash/policy/dlp/dlp_files_controller.h" +#include "chrome/browser/chromeos/policy/dlp/dlp_rules_manager.h" +#include "chrome/browser/chromeos/policy/dlp/dlp_rules_manager_factory.h" +#include "chrome/browser/chromeos/policy/dlp/mock_dlp_rules_manager.h" #include "chrome/browser/nearby_sharing/common/nearby_share_features.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/sharesheet/sharesheet_metrics.h" #include "chrome/browser/sharesheet/sharesheet_service.h" #include "chrome/browser/sharesheet/sharesheet_service_factory.h" #include "chrome/browser/sharesheet/sharesheet_types.h" +#include "chrome/browser/ui/ash/sharesheet/sharesheet_target_button.h" +#include "chrome/browser/ui/ash/sharesheet/sharesheet_util.h" #include "chrome/browser/ui/browser.h" #include "chrome/browser/ui/views/frame/browser_view.h" #include "chrome/test/base/in_process_browser_test.h" #include "chromeos/components/sharesheet/constants.h" -#include "components/services/app_service/public/cpp/intent_filter_util.h" #include "components/services/app_service/public/cpp/intent_test_util.h" #include "components/services/app_service/public/cpp/intent_util.h" #include "content/public/browser/web_contents.h" #include "content/public/test/browser_test.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" #include "ui/aura/window.h" #include "ui/views/bubble/bubble_dialog_delegate_view.h" @@ -84,9 +94,11 @@ ASSERT_FALSE(sharesheet_widget_->IsVisible()); } + protected: + views::Widget* sharesheet_widget_; + private: base::test::ScopedFeatureList scoped_feature_list_; - views::Widget* sharesheet_widget_; }; INSTANTIATE_TEST_SUITE_P(All, @@ -99,5 +111,131 @@ DismissUi(); } +class SharesheetBubbleViewPolicyBrowserTest + : public SharesheetBubbleViewBrowserTest { + public: + class MockFilesController : public policy::DlpFilesController { + public: + explicit MockFilesController(const policy::DlpRulesManager& rules_manager) + : DlpFilesController(rules_manager) {} + ~MockFilesController() override = default; + + MOCK_METHOD(bool, + IsLaunchBlocked, + (const apps::AppUpdate&, const apps::IntentPtr&), + (override)); + }; + + void SetupRulesManager(bool is_dlp_blocked) { + policy::DlpRulesManagerFactory::GetInstance()->SetTestingFactory( + browser()->profile(), + base::BindRepeating( + &SharesheetBubbleViewPolicyBrowserTest::SetDlpRulesManager, + base::Unretained(this))); + ASSERT_TRUE(policy::DlpRulesManagerFactory::GetForPrimaryProfile()); + + ON_CALL(*rules_manager_, IsFilesPolicyEnabled) + .WillByDefault(testing::Return(true)); + mock_files_controller_ = + std::make_unique<MockFilesController>(*rules_manager_); + ON_CALL(*rules_manager_, GetDlpFilesController) + .WillByDefault(testing::Return(mock_files_controller_.get())); + + EXPECT_CALL(*mock_files_controller_.get(), IsLaunchBlocked) + .WillOnce(testing::Return(is_dlp_blocked)); + } + + void SetupAppService() { + app_service_test_.SetUp(browser()->profile()); + + AddAppServiceAppsForTesting("arcAppId", apps::AppType::kArc, "text/plain", + "https://example.com"); + } + + void AddAppServiceAppsForTesting(std::string app_id, + apps::AppType app_type, + std::string mime_type, + absl::optional<std::string> publisher_id) { + apps::AppServiceProxy* app_service_proxy = + apps::AppServiceProxyFactory::GetForProfile(browser()->profile()); + + std::vector<apps::AppPtr> fake_apps; + apps::AppPtr fake_app = + std::make_unique<apps::App>(apps::AppType::kArc, app_id); + fake_app->name = "xyz"; + fake_app->show_in_management = true; + fake_app->readiness = apps::Readiness::kReady; + if (publisher_id.has_value()) { + fake_app->publisher_id = publisher_id.value(); + } + std::vector<apps::PermissionPtr> fake_permissions; + fake_app->permissions = std::move(fake_permissions); + fake_app->handles_intents = true; + apps::IntentFilterPtr filter = + apps_util::MakeIntentFilterForMimeType(mime_type); + fake_app->intent_filters.push_back(std::move(filter)); + + fake_apps.push_back(std::move(fake_app)); + + app_service_proxy->AppRegistryCache().OnApps( + std::move(fake_apps), app_type, + /*should_notify_initialized=*/false); + } + + bool VerifyDlp(bool is_dlp_blocked) { + if (!sharesheet_widget_) { + return false; + } + SharesheetBubbleView* sharesheet_bubble_view_ = + static_cast<SharesheetBubbleView*>( + sharesheet_widget_->GetContentsView()); + views::View* targets = sharesheet_bubble_view_->GetViewByID( + SharesheetViewID::TARGETS_DEFAULT_VIEW_ID); + SharesheetTargetButton* button = static_cast<SharesheetTargetButton*>( + targets->children()[targets->children().size() - 1]); + return is_dlp_blocked == + (button->GetState() == SharesheetTargetButton::STATE_DISABLED); + } + + MockFilesController* mock_files_controller() { + return mock_files_controller_.get(); + } + + private: + std::unique_ptr<KeyedService> SetDlpRulesManager( + content::BrowserContext* context) { + auto dlp_rules_manager = + std::make_unique<testing::NiceMock<policy::MockDlpRulesManager>>(); + rules_manager_ = dlp_rules_manager.get(); + return dlp_rules_manager; + } + + apps::AppServiceTest app_service_test_; + policy::MockDlpRulesManager* rules_manager_ = nullptr; + std::unique_ptr<MockFilesController> mock_files_controller_ = nullptr; +}; + +INSTANTIATE_TEST_SUITE_P(All, + SharesheetBubbleViewPolicyBrowserTest, + ::testing::Bool()); + +IN_PROC_BROWSER_TEST_P(SharesheetBubbleViewPolicyBrowserTest, + InvokeUi_DlpAllowed) { + SetupRulesManager(/*is_dlp_blocked*/ false); + SetupAppService(); + ShowUi(); + ASSERT_TRUE(VerifyDlp(/*is_dlp_blocked*/ false)); + DismissUi(); +} + +IN_PROC_BROWSER_TEST_P(SharesheetBubbleViewPolicyBrowserTest, + InvokeUi_DlpBlocked) { + SetupRulesManager(/*is_dlp_blocked*/ true); + SetupAppService(); + ShowUi(); + ASSERT_TRUE(VerifyDlp(/*is_dlp_blocked*/ true)); + DismissUi(); +} + } // namespace sharesheet } // namespace ash
diff --git a/chrome/browser/ui/ash/sharesheet/sharesheet_target_button.cc b/chrome/browser/ui/ash/sharesheet/sharesheet_target_button.cc index 016ca08..3b37abd 100644 --- a/chrome/browser/ui/ash/sharesheet/sharesheet_target_button.cc +++ b/chrome/browser/ui/ash/sharesheet/sharesheet_target_button.cc
@@ -50,7 +50,8 @@ const std::u16string& display_name, const std::u16string& secondary_display_name, const absl::optional<gfx::ImageSkia> icon, - const gfx::VectorIcon* vector_icon) + const gfx::VectorIcon* vector_icon, + bool is_dlp_blocked) : Button(std::move(callback)), vector_icon_(vector_icon) { SetFocusBehavior(View::FocusBehavior::ALWAYS); // TODO(crbug.com/1097623) Margins shouldn't be within @@ -102,6 +103,10 @@ AddChildView(std::move(label_view)); SetAccessibleName(accessible_name); + + if (is_dlp_blocked) { + SetState(ButtonState::STATE_DISABLED); + } } void SharesheetTargetButton::OnThemeChanged() {
diff --git a/chrome/browser/ui/ash/sharesheet/sharesheet_target_button.h b/chrome/browser/ui/ash/sharesheet/sharesheet_target_button.h index b87b1bb..b043c25 100644 --- a/chrome/browser/ui/ash/sharesheet/sharesheet_target_button.h +++ b/chrome/browser/ui/ash/sharesheet/sharesheet_target_button.h
@@ -23,7 +23,8 @@ // a single target (either app or action) in the |sharesheet_bubble_view|. The // target is comprised of an image (made from |icon| for apps or from // |vector_icon| for actions), a |display_name| and an optional -// |secondary_display_name| below it. When pressed this button launches the +// |secondary_display_name| below it. If |is_dlp_blocked| is set to true, the +// button is disabled. Otherwise, when pressed this button launches the // associated target. class SharesheetTargetButton : public views::Button { public: @@ -33,7 +34,8 @@ const std::u16string& display_name, const std::u16string& secondary_display_name, const absl::optional<gfx::ImageSkia> icon, - const gfx::VectorIcon* vector_icon); + const gfx::VectorIcon* vector_icon, + bool is_dlp_blocked); SharesheetTargetButton(const SharesheetTargetButton&) = delete; SharesheetTargetButton& operator=(const SharesheetTargetButton&) = delete;
diff --git a/chrome/browser/ui/autofill/chrome_autofill_client.cc b/chrome/browser/ui/autofill/chrome_autofill_client.cc index 58cf6244..c7fcc6a 100644 --- a/chrome/browser/ui/autofill/chrome_autofill_client.cc +++ b/chrome/browser/ui/autofill/chrome_autofill_client.cc
@@ -711,13 +711,14 @@ std::move(callback)); } -bool ChromeAutofillClient::TryToShowFastCheckout(const FormData& form, - const FormFieldData& field, - AutofillDriver* driver) { +bool ChromeAutofillClient::TryToShowFastCheckout( + const FormData& form, + const FormFieldData& field, + base::WeakPtr<AutofillManager> autofill_manager) { #if BUILDFLAG(IS_ANDROID) const GURL& url = web_contents()->GetLastCommittedURL(); return FastCheckoutClient::GetOrCreateForWebContents(web_contents()) - ->TryToStart(url, form, field, driver); + ->TryToStart(url, form, field, autofill_manager); #else return false; #endif
diff --git a/chrome/browser/ui/autofill/chrome_autofill_client.h b/chrome/browser/ui/autofill/chrome_autofill_client.h index 4d274bf..1ff6f910 100644 --- a/chrome/browser/ui/autofill/chrome_autofill_client.h +++ b/chrome/browser/ui/autofill/chrome_autofill_client.h
@@ -182,9 +182,10 @@ AddressProfileSavePromptCallback callback) override; bool HasCreditCardScanFeature() override; void ScanCreditCard(CreditCardScanCallback callback) override; - bool TryToShowFastCheckout(const FormData& form, - const FormFieldData& field, - AutofillDriver* driver) override; + bool TryToShowFastCheckout( + const FormData& form, + const FormFieldData& field, + base::WeakPtr<AutofillManager> autofill_manager) override; void HideFastCheckout(bool allow_further_runs) override; bool IsFastCheckoutSupported() override; bool IsShowingFastCheckoutUI() override;
diff --git a/chrome/browser/ui/autofill/chrome_autofill_client_unittest.cc b/chrome/browser/ui/autofill/chrome_autofill_client_unittest.cc index f2187e0..a1b942f 100644 --- a/chrome/browser/ui/autofill/chrome_autofill_client_unittest.cc +++ b/chrome/browser/ui/autofill/chrome_autofill_client_unittest.cc
@@ -11,6 +11,7 @@ #include "chrome/browser/fast_checkout/fast_checkout_client_impl.h" #include "chrome/browser/fast_checkout/fast_checkout_features.h" #include "chrome/test/base/chrome_render_view_host_test_harness.h" +#include "components/autofill/content/browser/content_autofill_router.h" #include "components/autofill/core/browser/test_autofill_clock.h" #include "components/autofill/core/browser/test_autofill_driver.h" #include "components/autofill/core/browser/test_personal_data_manager.h" @@ -31,11 +32,13 @@ : FastCheckoutClientImpl(web_contents) {} ~MockFastCheckoutClient() override = default; - MOCK_METHOD( - bool, - TryToStart, - (const GURL&, const FormData&, const FormFieldData&, AutofillDriver*), - (override)); + MOCK_METHOD(bool, + TryToStart, + (const GURL&, + const FormData&, + const FormFieldData&, + base::WeakPtr<AutofillManager>), + (override)); MOCK_METHOD(void, Stop, (bool reset), (override)); MOCK_METHOD(bool, IsRunning, (), (const override)); MOCK_METHOD(bool, IsShowing, (), (const override)); @@ -87,6 +90,7 @@ personal_data_manager_->SetAutofillProfileEnabled(true); personal_data_manager_->SetAutofillCreditCardEnabled(true); + personal_data_manager_->SetAutofillWalletImportEnabled(false); // Enable MSBB by default. If MSBB has been explicitly turned off, Fast // Checkout is not supported. @@ -177,10 +181,19 @@ } TEST_F(ChromeAutofillClientTest, TryToShowFastCheckout) { + auto router = std::make_unique<autofill::ContentAutofillRouter>(); + auto driver = std::make_unique<autofill::ContentAutofillDriver>( + web_contents()->GetPrimaryMainFrame(), router.get()); + auto manager = std::make_unique<BrowserAutofillManager>( + driver.get(), client(), "en-US", + AutofillManager::EnableDownloadManager(false)); + BrowserAutofillManager* manager_ptr = manager.get(); + driver->set_autofill_manager(std::move(manager)); + EXPECT_CALL(*fast_checkout_client(), TryToStart) .WillOnce(testing::Return(true)); EXPECT_TRUE(client()->TryToShowFastCheckout(FormData(), FormFieldData(), - autofill_driver())); + manager_ptr->GetWeakPtr())); } #endif // BUILDFLAG(IS_ANDROID)
diff --git a/chrome/browser/ui/views/autofill/autofill_popup_base_view.cc b/chrome/browser/ui/views/autofill/autofill_popup_base_view.cc index 0aa2370..790d91a2 100644 --- a/chrome/browser/ui/views/autofill/autofill_popup_base_view.cc +++ b/chrome/browser/ui/views/autofill/autofill_popup_base_view.cc
@@ -109,10 +109,6 @@ return GetHorizontalMargin(); } -SkColor AutofillPopupBaseView::GetBackgroundColor() const { - return GetColorProvider()->GetColor(ui::kColorDropdownBackground); -} - SkColor AutofillPopupBaseView::GetForegroundColor() const { return GetColorProvider()->GetColor(ui::kColorDropdownForeground); } @@ -478,7 +474,6 @@ } BEGIN_METADATA(AutofillPopupBaseView, views::WidgetDelegateView) -ADD_READONLY_PROPERTY_METADATA(SkColor, BackgroundColor) ADD_READONLY_PROPERTY_METADATA(SkColor, ForegroundColor) ADD_READONLY_PROPERTY_METADATA(SkColor, SelectedBackgroundColor) ADD_READONLY_PROPERTY_METADATA(SkColor, SelectedForegroundColor)
diff --git a/chrome/browser/ui/views/autofill/autofill_popup_base_view.h b/chrome/browser/ui/views/autofill/autofill_popup_base_view.h index 78b76cd..21a7620 100644 --- a/chrome/browser/ui/views/autofill/autofill_popup_base_view.h +++ b/chrome/browser/ui/views/autofill/autofill_popup_base_view.h
@@ -66,7 +66,6 @@ // Get colors used throughout various popup UIs, based on the current native // theme. - SkColor GetBackgroundColor() const; SkColor GetForegroundColor() const; SkColor GetSelectedBackgroundColor() const; SkColor GetSelectedForegroundColor() const;
diff --git a/chrome/browser/ui/views/autofill/autofill_popup_view_native_views.cc b/chrome/browser/ui/views/autofill/autofill_popup_view_native_views.cc index 39bc82f..8bbd234 100644 --- a/chrome/browser/ui/views/autofill/autofill_popup_view_native_views.cc +++ b/chrome/browser/ui/views/autofill/autofill_popup_view_native_views.cc
@@ -372,7 +372,6 @@ // AutofillPopupRowView: void CreateContent() override; void RefreshStyle() override; - std::unique_ptr<views::Background> CreateBackground() final; int GetFrontendId() const; @@ -554,8 +553,6 @@ protected: // AutofillPopupRowView: void CreateContent() override; - void RefreshStyle() override; - std::unique_ptr<views::Background> CreateBackground() override; private: AutofillPopupSeparatorView(AutofillPopupViewNativeViews* popup_view, @@ -586,7 +583,6 @@ // AutofillPopupRowView: void CreateContent() override; void RefreshStyle() override {} - std::unique_ptr<views::Background> CreateBackground() override; private: AutofillPopupWarningView(AutofillPopupViewNativeViews* popup_view, @@ -783,15 +779,13 @@ } void AutofillPopupItemView::RefreshStyle() { - SetBackground(CreateBackground()); - SkColor bk_color = GetSelected() ? popup_view()->GetSelectedBackgroundColor() - : popup_view()->GetBackgroundColor(); + SetBackground(views::CreateThemedSolidBackground(GetBackgroundColorId())); // Set style for each label in this view depending on current state since the // style isn't automatically adjusted after creation of the label. for (views::Label* label : inner_labels_) { label->SetAutoColorReadabilityEnabled(false); - label->SetBackgroundColor(bk_color); + label->SetBackgroundColorId(GetBackgroundColorId()); if (!label->GetEnabled()) { label->SetEnabledColor(views::style::GetColor( @@ -809,12 +803,6 @@ SchedulePaint(); } -std::unique_ptr<views::Background> AutofillPopupItemView::CreateBackground() { - return views::CreateSolidBackground( - GetSelected() ? popup_view()->GetSelectedBackgroundColor() - : popup_view()->GetBackgroundColor()); -} - std::unique_ptr<views::Label> AutofillPopupItemView::CreateMainTextView() { // TODO(crbug.com/831603): Remove elision responsibilities from controller. const Suggestion::Text& main_text = @@ -1218,16 +1206,7 @@ void AutofillPopupSeparatorView::CreateContent() { SetUseDefaultFillLayout(true); AddChildView(std::make_unique<PopupSeparator>(popup_view())); -} - -void AutofillPopupSeparatorView::RefreshStyle() { - SetBackground(CreateBackground()); - SchedulePaint(); -} - -std::unique_ptr<views::Background> -AutofillPopupSeparatorView::CreateBackground() { - return views::CreateSolidBackground(popup_view()->GetBackgroundColor()); + SetBackground(views::CreateThemedSolidBackground(GetBackgroundColorId())); } AutofillPopupSeparatorView::AutofillPopupSeparatorView( @@ -1276,11 +1255,6 @@ controller->GetSuggestionMainTextAt(GetLineNumber()), popup_view())); } -std::unique_ptr<views::Background> -AutofillPopupWarningView::CreateBackground() { - return nullptr; -} - } // namespace /************** AutofillPopupRowView **************/ @@ -1345,6 +1319,11 @@ return selected_; } +ui::ColorId AutofillPopupRowView::GetBackgroundColorId() const { + return GetSelected() ? ui::kColorDropdownBackgroundSelected + : ui::kColorDropdownBackground; +} + bool AutofillPopupRowView::HandleAccessibleAction( const ui::AXActionData& action_data) { base::WeakPtr<AutofillPopupController> controller = @@ -1391,11 +1370,12 @@ void AutofillPopupViewNativeViews::OnThemeChanged() { AutofillPopupBaseView::OnThemeChanged(); - SetBackground(views::CreateSolidBackground(GetBackgroundColor())); - // |scroll_view_| and |footer_container_| will be null if there is no body + SetBackground( + views::CreateThemedSolidBackground(ui::kColorDropdownBackground)); + // `scroll_view_` and `footer_container_` will be null if there is no body // or footer content, respectively. if (scroll_view_) { - scroll_view_->SetBackgroundColor(GetBackgroundColor()); + scroll_view_->SetBackgroundThemeColorId(ui::kColorDropdownBackground); } if (footer_container_) { footer_container_->SetBackground(
diff --git a/chrome/browser/ui/views/autofill/autofill_popup_view_native_views.h b/chrome/browser/ui/views/autofill/autofill_popup_view_native_views.h index 931f6bf..308e86f 100644 --- a/chrome/browser/ui/views/autofill/autofill_popup_view_native_views.h +++ b/chrome/browser/ui/views/autofill/autofill_popup_view_native_views.h
@@ -16,6 +16,7 @@ #include "third_party/abseil-cpp/absl/types/optional.h" #include "ui/accessibility/ax_action_data.h" #include "ui/base/metadata/metadata_header_macros.h" +#include "ui/color/color_id.h" #include "ui/gfx/color_palette.h" #include "ui/gfx/font_list.h" #include "ui/views/bubble/bubble_border.h" @@ -67,10 +68,10 @@ AutofillPopupViewNativeViews* popup_view() { return popup_view_; } int GetLineNumber() const; bool GetSelected() const; + ui::ColorId GetBackgroundColorId() const; virtual void CreateContent() = 0; - virtual void RefreshStyle() = 0; - virtual std::unique_ptr<views::Background> CreateBackground() = 0; + virtual void RefreshStyle() {} private: raw_ptr<AutofillPopupViewNativeViews> popup_view_;
diff --git a/chrome/browser/ui/views/passwords/password_generation_popup_view_views.cc b/chrome/browser/ui/views/passwords/password_generation_popup_view_views.cc index d68bcd1..054262e 100644 --- a/chrome/browser/ui/views/passwords/password_generation_popup_view_views.cc +++ b/chrome/browser/ui/views/passwords/password_generation_popup_view_views.cc
@@ -25,6 +25,7 @@ #include "ui/base/metadata/metadata_header_macros.h" #include "ui/base/metadata/metadata_impl_macros.h" #include "ui/base/resource/resource_bundle.h" +#include "ui/color/color_id.h" #include "ui/gfx/canvas.h" #include "ui/gfx/color_palette.h" #include "ui/gfx/geometry/insets.h" @@ -135,12 +136,12 @@ password_label_->SetText(password); } - void UpdateBackground(SkColor color) { - SetBackground(views::CreateSolidBackground(color)); + void UpdateBackground(ui::ColorId color) { + SetBackground(views::CreateThemedSolidBackground(color)); // Setting a background color on the labels may change the text color to // improve contrast. - password_label_->SetBackgroundColor(color); - suggestion_label_->SetBackgroundColor(color); + password_label_->SetBackgroundColorId(color); + suggestion_label_->SetBackgroundColorId(color); } void reset_controller() { controller_ = nullptr; } @@ -296,12 +297,14 @@ return; password_view_->UpdateBackground(controller_->password_selected() - ? GetSelectedBackgroundColor() - : GetBackgroundColor()); + ? ui::kColorDropdownBackgroundSelected + : ui::kColorDropdownBackground); SchedulePaint(); } void PasswordGenerationPopupViewViews::CreateLayoutAndChildren() { + SetBackground( + views::CreateThemedSolidBackground(ui::kColorDropdownBackground)); if (controller_->IsStateMinimized()) { SetLayoutManager(std::make_unique<views::FillLayout>()); auto warning_icon = std::make_unique<views::ImageView>(); @@ -367,12 +370,6 @@ void PasswordGenerationPopupViewViews::OnThemeChanged() { autofill::AutofillPopupBaseView::OnThemeChanged(); - SetBackground(views::CreateSolidBackground(GetBackgroundColor())); - if (FullPopupVisible()) { - password_view_->UpdateBackground(controller_->password_selected() - ? GetSelectedBackgroundColor() - : GetBackgroundColor()); - } if (help_styled_label_) { help_styled_label_->SetDisplayedOnBackgroundColor( GetFooterBackgroundColor());
diff --git a/chrome/browser/ui/webui/ash/emoji/gif_tenor_api_fetcher.cc b/chrome/browser/ui/webui/ash/emoji/gif_tenor_api_fetcher.cc index bdac7cc..2e794adb 100644 --- a/chrome/browser/ui/webui/ash/emoji/gif_tenor_api_fetcher.cc +++ b/chrome/browser/ui/webui/ash/emoji/gif_tenor_api_fetcher.cc
@@ -183,6 +183,7 @@ void GifTenorApiFetcher::TenorGifsApiResponseHandler( TenorGifsApiCallback callback, + std::unique_ptr<EndpointFetcher> endpoint_fetcher, std::unique_ptr<EndpointResponse> response) { data_decoder::DataDecoder::ParseJsonIsolated( response->response, @@ -194,6 +195,11 @@ TenorGifsApiCallback callback, data_decoder::DataDecoder::ValueOrError result) { const auto* gifs = FindList(result, "results"); + if (!gifs) { + std::move(callback).Run(emoji_picker::mojom::TenorGifResponse::New( + "", std::vector<emoji_picker::mojom::GifResponsePtr>{})); + return; + } const auto* next = result->FindStringKey("next"); std::move(callback).Run(emoji_picker::mojom::TenorGifResponse::New( next ? *next : "", ParseGifs(gifs))); @@ -229,17 +235,20 @@ } )"); - endpoint_fetcher_ = endpoint_fetcher_creator_.Run( + auto endpoint_fetcher = endpoint_fetcher_creator_.Run( url_loader_factory, GURL(kTenorBaseUrl).Resolve(kCategoriesApi), kTrafficAnnotation); - endpoint_fetcher_->PerformRequest( + auto* const endpoint_fetcher_ptr = endpoint_fetcher.get(); + endpoint_fetcher_ptr->PerformRequest( base::BindOnce(&GifTenorApiFetcher::FetchCategoriesResponseHandler, - weak_ptr_factory_.GetWeakPtr(), std::move(callback)), + weak_ptr_factory_.GetWeakPtr(), std::move(callback), + std::move(endpoint_fetcher)), nullptr); } void GifTenorApiFetcher::FetchCategoriesResponseHandler( PageHandler::GetCategoriesCallback callback, + std::unique_ptr<EndpointFetcher> endpoint_fetcher, std::unique_ptr<EndpointResponse> response) { data_decoder::DataDecoder::ParseJsonIsolated( response->response, @@ -252,6 +261,7 @@ data_decoder::DataDecoder::ValueOrError result) { const auto* tags = FindList(result, "tags"); if (!tags) { + std::move(callback).Run({}); return; } @@ -306,11 +316,13 @@ } )"); - endpoint_fetcher_ = endpoint_fetcher_creator_.Run( + auto endpoint_fetcher = endpoint_fetcher_creator_.Run( url_loader_factory, GetUrl(kFeaturedApi, pos), kTrafficAnnotation); - endpoint_fetcher_->PerformRequest( + auto* const endpoint_fetcher_ptr = endpoint_fetcher.get(); + endpoint_fetcher_ptr->PerformRequest( base::BindOnce(&GifTenorApiFetcher::TenorGifsApiResponseHandler, - weak_ptr_factory_.GetWeakPtr(), std::move(callback)), + weak_ptr_factory_.GetWeakPtr(), std::move(callback), + std::move(endpoint_fetcher)), nullptr); } @@ -352,11 +364,13 @@ GURL url = GetUrl(kSearchApi, pos); url = net::AppendQueryParameter(url, "q", query); - endpoint_fetcher_ = endpoint_fetcher_creator_.Run(url_loader_factory, url, - kTrafficAnnotation); - endpoint_fetcher_->PerformRequest( + auto endpoint_fetcher = endpoint_fetcher_creator_.Run(url_loader_factory, url, + kTrafficAnnotation); + auto* const endpoint_fetcher_ptr = endpoint_fetcher.get(); + endpoint_fetcher_ptr->PerformRequest( base::BindOnce(&GifTenorApiFetcher::TenorGifsApiResponseHandler, - weak_ptr_factory_.GetWeakPtr(), std::move(callback)), + weak_ptr_factory_.GetWeakPtr(), std::move(callback), + std::move(endpoint_fetcher)), nullptr); } @@ -392,19 +406,22 @@ } )"); - endpoint_fetcher_ = endpoint_fetcher_creator_.Run( + auto endpoint_fetcher = endpoint_fetcher_creator_.Run( url_loader_factory, net::AppendQueryParameter(GURL(kTenorBaseUrl).Resolve(kPostsApi), "ids", base::JoinString(ids, ",")), kTrafficAnnotation); - endpoint_fetcher_->PerformRequest( + auto* const endpoint_fetcher_ptr = endpoint_fetcher.get(); + endpoint_fetcher_ptr->PerformRequest( base::BindOnce(&GifTenorApiFetcher::FetchGifsByIdsResponseHandler, - weak_ptr_factory_.GetWeakPtr(), std::move(callback)), + weak_ptr_factory_.GetWeakPtr(), std::move(callback), + std::move(endpoint_fetcher)), nullptr); } void GifTenorApiFetcher::FetchGifsByIdsResponseHandler( emoji_picker::mojom::PageHandler::GetGifsByIdsCallback callback, + std::unique_ptr<EndpointFetcher> endpoint_fetcher, std::unique_ptr<EndpointResponse> response) { data_decoder::DataDecoder::ParseJsonIsolated( response->response,
diff --git a/chrome/browser/ui/webui/ash/emoji/gif_tenor_api_fetcher.h b/chrome/browser/ui/webui/ash/emoji/gif_tenor_api_fetcher.h index 49e64c9..3ec8f16 100644 --- a/chrome/browser/ui/webui/ash/emoji/gif_tenor_api_fetcher.h +++ b/chrome/browser/ui/webui/ash/emoji/gif_tenor_api_fetcher.h
@@ -58,26 +58,29 @@ const std::vector<std::string>& ids); private: - std::unique_ptr<EndpointFetcher> endpoint_fetcher_; const EndpointFetcherCreator endpoint_fetcher_creator_; base::WeakPtrFactory<GifTenorApiFetcher> weak_ptr_factory_{this}; void FetchCategoriesResponseHandler( emoji_picker::mojom::PageHandler::GetCategoriesCallback callback, + std::unique_ptr<EndpointFetcher> endpoint_fetcher, std::unique_ptr<EndpointResponse> response); void OnCategoriesJsonParsed( emoji_picker::mojom::PageHandler::GetCategoriesCallback callback, data_decoder::DataDecoder::ValueOrError result); - void TenorGifsApiResponseHandler(TenorGifsApiCallback callback, - std::unique_ptr<EndpointResponse> response); + void TenorGifsApiResponseHandler( + TenorGifsApiCallback callback, + std::unique_ptr<EndpointFetcher> endpoint_fetcher, + std::unique_ptr<EndpointResponse> response); void OnGifsJsonParsed(TenorGifsApiCallback callback, data_decoder::DataDecoder::ValueOrError result); void FetchGifsByIdsResponseHandler( emoji_picker::mojom::PageHandler::GetGifsByIdsCallback callback, + std::unique_ptr<EndpointFetcher> endpoint_fetcher, std::unique_ptr<EndpointResponse> response); void OnGifsByIdsJsonParsed(
diff --git a/chrome/browser/ui/webui/ash/login/gaia_screen_handler.cc b/chrome/browser/ui/webui/ash/login/gaia_screen_handler.cc index 43245da..cac1a31 100644 --- a/chrome/browser/ui/webui/ash/login/gaia_screen_handler.cc +++ b/chrome/browser/ui/webui/ash/login/gaia_screen_handler.cc
@@ -559,7 +559,7 @@ } } - if (is_reauth && !gaia_reauth_request_token_.empty()) { + if (!gaia_reauth_request_token_.empty()) { params.Set("rart", gaia_reauth_request_token_); }
diff --git a/chrome/browser/ui/webui/password_manager/password_manager_ui.cc b/chrome/browser/ui/webui/password_manager/password_manager_ui.cc index 96df881..b87b2b3 100644 --- a/chrome/browser/ui/webui/password_manager/password_manager_ui.cc +++ b/chrome/browser/ui/webui/password_manager/password_manager_ui.cc
@@ -29,6 +29,12 @@ #include "ui/base/resource/resource_bundle.h" #include "ui/base/webui/web_ui_util.h" +#if BUILDFLAG(IS_MAC) || BUILDFLAG(IS_WIN) +#include "chrome/browser/browser_process.h" +#include "chrome/browser/browser_process_platform_part.h" +#include "components/password_manager/core/browser/password_manager_util.h" +#endif + #if BUILDFLAG(GOOGLE_CHROME_BRANDING) #include "chrome/grit/chrome_unscaled_resources.h" #endif @@ -47,110 +53,115 @@ IDR_PASSWORD_MANAGER_PASSWORD_MANAGER_HTML); static constexpr webui::LocalizedString kStrings[] = { - {"addPassword", IDS_PASSWORD_MANAGER_UI_ADD_PASSWORD_BUTTON}, - {"addPasswordFooter", IDS_PASSWORD_MANAGER_UI_ADD_PASSWORD_FOOTNOTE}, - {"addPasswordTitle", IDS_PASSWORD_MANAGER_UI_ADD_PASSWORD}, - {"addShortcut", IDS_PASSWORD_MANAGER_UI_ADD_SHORTCUT_TITLE}, - {"addShortcutDescription", - IDS_PASSWORD_MANAGER_UI_ADD_SHORTCUT_DESCRIPTION}, - {"autosigninDescription", IDS_PASSWORD_MANAGER_UI_AUTOSIGNIN_TOGGLE_DESC}, - {"autosigninLabel", IDS_PASSWORD_MANAGER_UI_AUTOSIGNIN_TOGGLE_LABEL}, - {"blockedSitesDescription", - IDS_PASSWORD_MANAGER_UI_BLOCKED_SITES_DESCRIPTION}, - {"blockedSitesEmptyDescription", - IDS_PASSWORD_MANAGER_UI_NO_BLOCKED_SITES_DESCRIPTION}, - {"blockedSitesTitle", IDS_PASSWORD_MANAGER_UI_BLOCKED_SITES_TITLE}, - {"cancel", IDS_CANCEL}, - {"changePassword", IDS_PASSWORD_MANAGER_UI_CHANGE_PASSWORD_BUTTON}, - {"checkup", IDS_PASSWORD_MANAGER_UI_CHECKUP}, - {"checkupCanceled", IDS_PASSWORD_MANAGER_UI_CHECKUP_CANCELED}, - {"checkupErrorGeneric", IDS_PASSWORD_MANAGER_UI_CHECKUP_OTHER_ERROR}, - {"checkupErrorNoPasswords", IDS_PASSWORD_MANAGER_UI_CHECKUP_NO_PASSWORDS}, - {"checkupErrorOffline", IDS_PASSWORD_MANAGER_UI_CHECKUP_OFFLINE}, - {"checkupErrorQuota", IDS_PASSWORD_MANAGER_UI_CHECKUP_QUOTA_LIMIT}, - {"checkupErrorSignedOut", IDS_PASSWORD_MANAGER_UI_CHECKUP_SIGNED_OUT}, - {"compromisedRowWithError", - IDS_PASSWORD_MANAGER_UI_CHECKUP_COMPROMISED_SECTION}, - {"checkupProgress", IDS_PASSWORD_MANAGER_UI_CHECKUP_PROGRESS}, - {"checkupTitle", IDS_PASSWORD_MANAGER_UI_CHECKUP_TITLE}, - {"clearSearch", IDS_CLEAR_SEARCH}, - {"close", IDS_CLOSE}, - {"compromisedPasswordsDescription", - IDS_PASSWORD_MANAGER_UI_COMPROMISED_PASSWORDS_DESCRIPTION}, - {"compromisedPasswordsEmpty", - IDS_PASSWORD_MANAGER_UI_NO_COMPROMISED_PASSWORDS}, - {"compromisedPasswordsTitle", - IDS_PASSWORD_MANAGER_UI_HAS_COMPROMISED_PASSWORDS}, - {"copyPassword", IDS_PASSWORD_MANAGER_UI_COPY_PASSWORD}, - {"copyUsername", IDS_PASSWORD_MANAGER_UI_COPY_USERNAME}, - {"deletePassword", IDS_DELETE}, - {"downloadFile", IDS_PASSWORD_MANAGER_UI_DOWNLOAD_FILE}, - {"editPassword", IDS_EDIT}, - {"emptyNote", IDS_PASSWORD_MANAGER_UI_NO_NOTE_SAVED}, - {"exportPasswords", IDS_PASSWORD_MANAGER_UI_EXPORT_TITLE}, - {"exportPasswordsDescription", - IDS_PASSWORD_MANAGER_UI_EXPORT_BANNER_DESCRIPTION}, - {"exportPasswordsFailTips", - IDS_PASSWORD_MANAGER_UI_EXPORTING_FAILURE_TIPS}, - {"exportPasswordsFailTipsAnotherFolder", - IDS_PASSWORD_MANAGER_UI_EXPORTING_FAILURE_TIP_ANOTHER_FOLDER}, - {"exportPasswordsFailTipsEnoughSpace", - IDS_PASSWORD_MANAGER_UI_EXPORTING_FAILURE_TIP_ENOUGH_SPACE}, - {"exportPasswordsFailTitle", - IDS_PASSWORD_MANAGER_UI_EXPORTING_FAILURE_TITLE}, - {"exportPasswordsTryAgain", IDS_PASSWORD_MANAGER_UI_EXPORT_TRY_AGAIN}, - {"exportingPasswordsTitle", IDS_PASSWORD_MANAGER_UI_EXPORTING_TITLE}, - {"federationLabel", IDS_PASSWORD_MANAGER_UI_FEDERATION_LABEL}, - {"help", IDS_PASSWORD_MANAGER_UI_HELP}, - {"hidePassword", IDS_PASSWORD_MANAGER_UI_HIDE_PASSWORD}, - {"importPasswords", IDS_PASSWORD_MANAGER_UI_IMPORT_BANNER_TITLE}, - {"importPasswordsDescription", - IDS_PASSWORD_MANAGER_UI_IMPORT_BANNER_DESCRIPTION}, - {"justNow", IDS_PASSWORD_MANAGER_UI_JUST_NOW}, - {"leakedPassword", IDS_PASSWORD_MANAGER_UI_PASSWORD_LEAKED}, - {"localPasswordManager", - IDS_PASSWORD_BUBBLES_PASSWORD_MANAGER_LINK_TEXT_SAVING_ON_DEVICE}, - {"menu", IDS_MENU}, - {"moreActions", IDS_PASSWORD_MANAGER_UI_MORE_ACTIONS}, - {"muteCompromisedPassword", IDS_PASSWORD_MANAGER_UI_MUTE_ISSUE}, - {"mutedCompromisedCredentials", - IDS_PASSWORD_MANAGER_UI_MUTED_COMPROMISED_PASSWORDS}, - {"notesLabel", IDS_PASSWORD_MANAGER_UI_NOTES_LABEL}, - {"passwordCopiedToClipboard", - IDS_PASSWORD_MANAGER_UI_PASSWORD_COPIED_TO_CLIPBOARD}, - {"passwordLabel", IDS_PASSWORD_MANAGER_UI_PASSWORD_LABEL}, - {"passwordManager", - IDS_PASSWORD_BUBBLES_PASSWORD_MANAGER_LINK_TEXT_SYNCED_TO_ACCOUNT}, - {"passwords", IDS_PASSWORD_MANAGER_UI_PASSWORDS}, - {"phishedAndLeakedPassword", - IDS_PASSWORD_MANAGER_UI_PASSWORD_PHISHED_AND_LEAKED}, - {"phishedPassword", IDS_PASSWORD_MANAGER_UI_PASSWORD_PHISHED}, - {"reusedPasswordsDescription", - IDS_PASSWORD_MANAGER_UI_REUSED_PASSWORDS_DESCRIPTION}, - {"reusedPasswordsEmpty", IDS_PASSWORD_MANAGER_UI_NO_REUSED_PASSWORDS}, - {"reusedPasswordsTitle", IDS_PASSWORD_MANAGER_UI_HAS_REUSED_PASSWORDS}, - {"save", IDS_SAVE}, - {"savePasswordsLabel", - IDS_PASSWORD_MANAGER_UI_SAVE_PASSWORDS_TOGGLE_LABEL}, - {"searchPrompt", IDS_PASSWORD_MANAGER_UI_SEARCH_PROMPT}, - {"settings", IDS_PASSWORD_MANAGER_UI_SETTINGS}, - {"showPassword", IDS_PASSWORD_MANAGER_UI_SHOW_PASSWORD}, - {"sitesLabel", IDS_PASSWORD_MANAGER_UI_SITES_LABEL}, - {"title", IDS_PASSWORD_MANAGER_UI_TITLE}, - {"trustedVaultBannerLabelOfferOptIn", - IDS_PASSWORD_MANAGER_UI_TRUSTED_VAULT_OPT_IN_TITLE}, - {"trustedVaultBannerSubLabelOfferOptIn", - IDS_PASSWORD_MANAGER_UI_RUSTED_VAULT_OPT_IN_DESCRIPTION}, - {"tryAgain", IDS_PASSWORD_MANAGER_UI_CHECK_PASSWORDS_AFTER_ERROR}, - {"unmuteCompromisedPassword", IDS_PASSWORD_MANAGER_UI_UNMUTE_ISSUE}, - {"usernameCopiedToClipboard", - IDS_PASSWORD_MANAGER_UI_USERNAME_COPIED_TO_CLIPBOARD}, - {"usernameLabel", IDS_PASSWORD_MANAGER_UI_USERNAME_LABEL}, - {"weakPasswordsDescription", - IDS_PASSWORD_MANAGER_UI_WEAK_PASSWORDS_DESCRIPTION}, - {"weakPasswordsEmpty", IDS_PASSWORD_MANAGER_UI_NO_WEAK_PASSWORDS}, - {"weakPasswordsTitle", IDS_PASSWORD_MANAGER_UI_HAS_WEAK_PASSWORDS}, - {"websiteLabel", IDS_PASSWORD_MANAGER_UI_WEBSITE_LABEL}, + {"addPassword", IDS_PASSWORD_MANAGER_UI_ADD_PASSWORD_BUTTON}, + {"addPasswordFooter", IDS_PASSWORD_MANAGER_UI_ADD_PASSWORD_FOOTNOTE}, + {"addPasswordTitle", IDS_PASSWORD_MANAGER_UI_ADD_PASSWORD}, + {"addShortcut", IDS_PASSWORD_MANAGER_UI_ADD_SHORTCUT_TITLE}, + {"addShortcutDescription", + IDS_PASSWORD_MANAGER_UI_ADD_SHORTCUT_DESCRIPTION}, + {"autosigninDescription", IDS_PASSWORD_MANAGER_UI_AUTOSIGNIN_TOGGLE_DESC}, + {"autosigninLabel", IDS_PASSWORD_MANAGER_UI_AUTOSIGNIN_TOGGLE_LABEL}, + {"blockedSitesDescription", + IDS_PASSWORD_MANAGER_UI_BLOCKED_SITES_DESCRIPTION}, + {"blockedSitesEmptyDescription", + IDS_PASSWORD_MANAGER_UI_NO_BLOCKED_SITES_DESCRIPTION}, + {"blockedSitesTitle", IDS_PASSWORD_MANAGER_UI_BLOCKED_SITES_TITLE}, + {"cancel", IDS_CANCEL}, + {"changePassword", IDS_PASSWORD_MANAGER_UI_CHANGE_PASSWORD_BUTTON}, + {"checkup", IDS_PASSWORD_MANAGER_UI_CHECKUP}, + {"checkupCanceled", IDS_PASSWORD_MANAGER_UI_CHECKUP_CANCELED}, + {"checkupErrorGeneric", IDS_PASSWORD_MANAGER_UI_CHECKUP_OTHER_ERROR}, + {"checkupErrorNoPasswords", IDS_PASSWORD_MANAGER_UI_CHECKUP_NO_PASSWORDS}, + {"checkupErrorOffline", IDS_PASSWORD_MANAGER_UI_CHECKUP_OFFLINE}, + {"checkupErrorQuota", IDS_PASSWORD_MANAGER_UI_CHECKUP_QUOTA_LIMIT}, + {"checkupErrorSignedOut", IDS_PASSWORD_MANAGER_UI_CHECKUP_SIGNED_OUT}, + {"compromisedRowWithError", + IDS_PASSWORD_MANAGER_UI_CHECKUP_COMPROMISED_SECTION}, + {"checkupProgress", IDS_PASSWORD_MANAGER_UI_CHECKUP_PROGRESS}, + {"checkupTitle", IDS_PASSWORD_MANAGER_UI_CHECKUP_TITLE}, + {"clearSearch", IDS_CLEAR_SEARCH}, + {"close", IDS_CLOSE}, + {"compromisedPasswordsDescription", + IDS_PASSWORD_MANAGER_UI_COMPROMISED_PASSWORDS_DESCRIPTION}, + {"compromisedPasswordsEmpty", + IDS_PASSWORD_MANAGER_UI_NO_COMPROMISED_PASSWORDS}, + {"compromisedPasswordsTitle", + IDS_PASSWORD_MANAGER_UI_HAS_COMPROMISED_PASSWORDS}, + {"copyPassword", IDS_PASSWORD_MANAGER_UI_COPY_PASSWORD}, + {"copyUsername", IDS_PASSWORD_MANAGER_UI_COPY_USERNAME}, + {"deletePassword", IDS_DELETE}, + {"downloadFile", IDS_PASSWORD_MANAGER_UI_DOWNLOAD_FILE}, + {"editPassword", IDS_EDIT}, + {"emptyNote", IDS_PASSWORD_MANAGER_UI_NO_NOTE_SAVED}, + {"exportPasswords", IDS_PASSWORD_MANAGER_UI_EXPORT_TITLE}, + {"exportPasswordsDescription", + IDS_PASSWORD_MANAGER_UI_EXPORT_BANNER_DESCRIPTION}, + {"exportPasswordsFailTips", IDS_PASSWORD_MANAGER_UI_EXPORTING_FAILURE_TIPS}, + {"exportPasswordsFailTipsAnotherFolder", + IDS_PASSWORD_MANAGER_UI_EXPORTING_FAILURE_TIP_ANOTHER_FOLDER}, + {"exportPasswordsFailTipsEnoughSpace", + IDS_PASSWORD_MANAGER_UI_EXPORTING_FAILURE_TIP_ENOUGH_SPACE}, + {"exportPasswordsFailTitle", + IDS_PASSWORD_MANAGER_UI_EXPORTING_FAILURE_TITLE}, + {"exportPasswordsTryAgain", IDS_PASSWORD_MANAGER_UI_EXPORT_TRY_AGAIN}, + {"exportingPasswordsTitle", IDS_PASSWORD_MANAGER_UI_EXPORTING_TITLE}, + {"federationLabel", IDS_PASSWORD_MANAGER_UI_FEDERATION_LABEL}, + {"help", IDS_PASSWORD_MANAGER_UI_HELP}, + {"hidePassword", IDS_PASSWORD_MANAGER_UI_HIDE_PASSWORD}, + {"importPasswords", IDS_PASSWORD_MANAGER_UI_IMPORT_BANNER_TITLE}, + {"importPasswordsDescription", + IDS_PASSWORD_MANAGER_UI_IMPORT_BANNER_DESCRIPTION}, + {"justNow", IDS_PASSWORD_MANAGER_UI_JUST_NOW}, + {"leakedPassword", IDS_PASSWORD_MANAGER_UI_PASSWORD_LEAKED}, + {"localPasswordManager", + IDS_PASSWORD_BUBBLES_PASSWORD_MANAGER_LINK_TEXT_SAVING_ON_DEVICE}, + {"menu", IDS_MENU}, + {"moreActions", IDS_PASSWORD_MANAGER_UI_MORE_ACTIONS}, + {"muteCompromisedPassword", IDS_PASSWORD_MANAGER_UI_MUTE_ISSUE}, + {"mutedCompromisedCredentials", + IDS_PASSWORD_MANAGER_UI_MUTED_COMPROMISED_PASSWORDS}, + {"notesLabel", IDS_PASSWORD_MANAGER_UI_NOTES_LABEL}, + {"passwordCopiedToClipboard", + IDS_PASSWORD_MANAGER_UI_PASSWORD_COPIED_TO_CLIPBOARD}, + {"passwordLabel", IDS_PASSWORD_MANAGER_UI_PASSWORD_LABEL}, + {"passwordManager", + IDS_PASSWORD_BUBBLES_PASSWORD_MANAGER_LINK_TEXT_SYNCED_TO_ACCOUNT}, + {"passwords", IDS_PASSWORD_MANAGER_UI_PASSWORDS}, + {"phishedAndLeakedPassword", + IDS_PASSWORD_MANAGER_UI_PASSWORD_PHISHED_AND_LEAKED}, + {"phishedPassword", IDS_PASSWORD_MANAGER_UI_PASSWORD_PHISHED}, + {"reusedPasswordsDescription", + IDS_PASSWORD_MANAGER_UI_REUSED_PASSWORDS_DESCRIPTION}, + {"reusedPasswordsEmpty", IDS_PASSWORD_MANAGER_UI_NO_REUSED_PASSWORDS}, + {"reusedPasswordsTitle", IDS_PASSWORD_MANAGER_UI_HAS_REUSED_PASSWORDS}, + {"save", IDS_SAVE}, + {"savePasswordsLabel", IDS_PASSWORD_MANAGER_UI_SAVE_PASSWORDS_TOGGLE_LABEL}, + {"searchPrompt", IDS_PASSWORD_MANAGER_UI_SEARCH_PROMPT}, + {"settings", IDS_PASSWORD_MANAGER_UI_SETTINGS}, + {"showPassword", IDS_PASSWORD_MANAGER_UI_SHOW_PASSWORD}, + {"sitesLabel", IDS_PASSWORD_MANAGER_UI_SITES_LABEL}, + {"title", IDS_PASSWORD_MANAGER_UI_TITLE}, + {"trustedVaultBannerLabelOfferOptIn", + IDS_PASSWORD_MANAGER_UI_TRUSTED_VAULT_OPT_IN_TITLE}, + {"trustedVaultBannerSubLabelOfferOptIn", + IDS_PASSWORD_MANAGER_UI_RUSTED_VAULT_OPT_IN_DESCRIPTION}, + {"tryAgain", IDS_PASSWORD_MANAGER_UI_CHECK_PASSWORDS_AFTER_ERROR}, + {"unmuteCompromisedPassword", IDS_PASSWORD_MANAGER_UI_UNMUTE_ISSUE}, + {"usernameCopiedToClipboard", + IDS_PASSWORD_MANAGER_UI_USERNAME_COPIED_TO_CLIPBOARD}, + {"usernameLabel", IDS_PASSWORD_MANAGER_UI_USERNAME_LABEL}, + {"weakPasswordsDescription", + IDS_PASSWORD_MANAGER_UI_WEAK_PASSWORDS_DESCRIPTION}, + {"weakPasswordsEmpty", IDS_PASSWORD_MANAGER_UI_NO_WEAK_PASSWORDS}, + {"weakPasswordsTitle", IDS_PASSWORD_MANAGER_UI_HAS_WEAK_PASSWORDS}, + {"websiteLabel", IDS_PASSWORD_MANAGER_UI_WEBSITE_LABEL}, +#if BUILDFLAG(IS_MAC) + {"biometricAuthenticaionForFillingLabel", + IDS_PASSWORD_MANAGER_UI_BIOMETRIC_AUTHENTICATION_FOR_FILLING_TOGGLE_LABEL_MAC}, +#elif BUILDFLAG(IS_WIN) + {"biometricAuthenticaionForFillingLabel", + IDS_PASSWORD_MANAGER_UI_BIOMETRIC_AUTHENTICATION_FOR_FILLING_TOGGLE_LABEL_WIN}, +#endif }; for (const auto& str : kStrings) webui::AddLocalizedString(source, str.name, str.id); @@ -173,6 +184,13 @@ password_manager::PasswordCheckupReferrer::kPasswordCheck) .spec())); +#if BUILDFLAG(IS_MAC) || BUILDFLAG(IS_WIN) + source->AddBoolean("biometricAuthenticationForFillingToggleVisible", + password_manager_util:: + ShouldBiometricAuthenticationForFillingToggleBeVisible( + g_browser_process->local_state())); +#endif + source->AddString("passwordManagerLearnMoreURL", chrome::kPasswordManagerLearnMoreURL);
diff --git a/chrome/browser/ui/webui/settings/site_settings_permissions_handler.cc b/chrome/browser/ui/webui/settings/site_settings_permissions_handler.cc index 97d7640..aa31ce5d 100644 --- a/chrome/browser/ui/webui/settings/site_settings_permissions_handler.cc +++ b/chrome/browser/ui/webui/settings/site_settings_permissions_handler.cc
@@ -18,12 +18,10 @@ #include "components/content_settings/core/browser/host_content_settings_map.h" #include "components/content_settings/core/common/content_settings.h" #include "components/content_settings/core/common/content_settings_pattern.h" -#include "components/content_settings/core/common/content_settings_types.h" #include "components/content_settings/core/common/features.h" #include "components/permissions/constants.h" #include "components/permissions/unused_site_permissions_service.h" #include "url/gurl.h" -#include "url/origin.h" namespace { // Reflects the maximum number of days between a permissions being revoked and @@ -59,6 +57,7 @@ void SiteSettingsPermissionsHandler::HandleAllowPermissionsAgainForUnusedSite( const base::Value::List& args) { CHECK_EQ(1U, args.size()); + CHECK(args[0].is_string()); const std::string& origin_str = args[0].GetString(); permissions::UnusedSitePermissionsService* service = @@ -84,6 +83,22 @@ } void SiteSettingsPermissionsHandler:: + HandleUndoAllowPermissionsAgainForUnusedSite( + const base::Value::List& args) { + CHECK_EQ(1U, args.size()); + CHECK(args[0].is_dict()); + + auto [origin, permissions, constraints] = + GetUnusedSitePermissionsFromDict(args[0].GetDict()); + permissions::UnusedSitePermissionsService* service = + UnusedSitePermissionsServiceFactory::GetForProfile(profile_); + + service->UndoRegrantPermissionsForOrigin(permissions, constraints, origin); + + SendUnusedSitePermissionsReviewList(); +} + +void SiteSettingsPermissionsHandler:: HandleAcknowledgeRevokedUnusedSitePermissionsList( const base::Value::List& args) { permissions::UnusedSitePermissionsService* service = @@ -103,35 +118,13 @@ permissions::UnusedSitePermissionsService* service = UnusedSitePermissionsServiceFactory::GetForProfile(profile_); - for (const auto& unused_site_permissions : unused_site_permissions_list) { - CHECK(unused_site_permissions.is_dict()); - const std::string* origin_str = - unused_site_permissions.GetDict().FindString(site_settings::kOrigin); - CHECK(origin_str); - const auto url = GURL(*origin_str); - CHECK(url.is_valid()); + for (const auto& unused_site_permissions_js : unused_site_permissions_list) { + CHECK(unused_site_permissions_js.is_dict()); + auto [origin, permissions, constraints] = + GetUnusedSitePermissionsFromDict(unused_site_permissions_js.GetDict()); - const base::Value::List* permissions = - unused_site_permissions.GetDict().FindList(site_settings::kPermissions); - CHECK(permissions); - std::list<ContentSettingsType> permission_types; - for (const auto& permission : *permissions) { - CHECK(permission.is_string()); - const std::string& type = permission.GetString(); - permission_types.push_back( - site_settings::ContentSettingsTypeFromGroupName(type)); - } - - const base::Value* js_expiration = - unused_site_permissions.GetDict().Find(kExpirationKey); - CHECK(js_expiration); - auto expiration = base::ValueToTime(js_expiration); - - const content_settings::ContentSettingConstraints constraint{ - .expiration = *expiration}; - - service->StorePermissionInRevokedPermissionSetting( - permission_types, constraint, url::Origin::Create(url)); + service->StorePermissionInRevokedPermissionSetting(permissions, constraints, + origin); } SendUnusedSitePermissionsReviewList(); @@ -184,6 +177,40 @@ return result; } +std::tuple<url::Origin, + std::set<ContentSettingsType>, + content_settings::ContentSettingConstraints> +SiteSettingsPermissionsHandler::GetUnusedSitePermissionsFromDict( + const base::Value::Dict& unused_site_permissions) { + const std::string* origin_str = + unused_site_permissions.FindString(site_settings::kOrigin); + CHECK(origin_str); + const auto url = GURL(*origin_str); + CHECK(url.is_valid()); + const url::Origin origin = url::Origin::Create(url); + + const base::Value::List* permissions = + unused_site_permissions.FindList(site_settings::kPermissions); + CHECK(permissions); + std::set<ContentSettingsType> permission_types; + for (const auto& permission : *permissions) { + CHECK(permission.is_string()); + const std::string& type = permission.GetString(); + permission_types.insert( + site_settings::ContentSettingsTypeFromGroupName(type)); + } + + const base::Value* js_expiration = + unused_site_permissions.Find(kExpirationKey); + CHECK(js_expiration); + auto expiration = base::ValueToTime(js_expiration); + + const content_settings::ContentSettingConstraints constraints{ + .expiration = *expiration}; + + return std::make_tuple(origin, permission_types, constraints); +} + void SiteSettingsPermissionsHandler::RegisterMessages() { // Usage of base::Unretained(this) is safe, because web_ui() owns `this` and // won't release ownership until destruction. @@ -198,6 +225,11 @@ HandleAllowPermissionsAgainForUnusedSite, base::Unretained(this))); web_ui()->RegisterMessageCallback( + "undoAllowPermissionsAgainForUnusedSite", + base::BindRepeating(&SiteSettingsPermissionsHandler:: + HandleUndoAllowPermissionsAgainForUnusedSite, + base::Unretained(this))); + web_ui()->RegisterMessageCallback( "acknowledgeRevokedUnusedSitePermissionsList", base::BindRepeating(&SiteSettingsPermissionsHandler:: HandleAcknowledgeRevokedUnusedSitePermissionsList,
diff --git a/chrome/browser/ui/webui/settings/site_settings_permissions_handler.h b/chrome/browser/ui/webui/settings/site_settings_permissions_handler.h index 6f122dcc..d9b5711 100644 --- a/chrome/browser/ui/webui/settings/site_settings_permissions_handler.h +++ b/chrome/browser/ui/webui/settings/site_settings_permissions_handler.h
@@ -9,6 +9,9 @@ #include "base/time/clock.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/ui/webui/settings/settings_page_ui_handler.h" +#include "components/content_settings/core/common/content_settings_constraints.h" +#include "components/content_settings/core/common/content_settings_types.h" +#include "url/origin.h" /** * This handler deals with the permission-related operations on the site @@ -41,17 +44,23 @@ // "Unused site permissions" module. void HandleGetRevokedUnusedSitePermissionsList(const base::Value::List& args); - // Re-grant the revoked permissions and remove the origin from the revoked - // permissions list. + // Re-grant the revoked permissions and remove the given origin from the + // revoked permissions list. void HandleAllowPermissionsAgainForUnusedSite(const base::Value::List& args); + // Reverse the changes made by |HandleAllowPermissionsAgainForUnusedSite| for + // the given |UnusedSitePermission| object. + void HandleUndoAllowPermissionsAgainForUnusedSite( + const base::Value::List& args); + // Clear the list of revoked permissions so they are not shown again. // Permission settings themselves are not affected by this. void HandleAcknowledgeRevokedUnusedSitePermissionsList( const base::Value::List& args); // Reverse the changes made by - // |HandleAcknowledgeRevokedUnusedSitePermissionsList|. List of revoked + // |HandleAcknowledgeRevokedUnusedSitePermissionsList| for the given list of + // |UnusedSitePermission| objects. List of revoked // permissions is repopulated. Permission settings are not changed. void HandleUndoAcknowledgeRevokedUnusedSitePermissionsList( const base::Value::List& args); @@ -63,6 +72,14 @@ // Sends the list of unused site permissions to review to the WebUI. void SendUnusedSitePermissionsReviewList(); + // Get values from |UnusedSitePermission| object in + // site_settings_permissions_browser_proxy.ts. + std::tuple<url::Origin, + std::set<ContentSettingsType>, + content_settings::ContentSettingConstraints> + GetUnusedSitePermissionsFromDict( + const base::Value::Dict& unused_site_permissions); + const raw_ptr<Profile> profile_; base::Clock* clock_;
diff --git a/chrome/browser/ui/webui/settings/site_settings_permissions_handler_unittest.cc b/chrome/browser/ui/webui/settings/site_settings_permissions_handler_unittest.cc index f0a1ebff..3fbd56e 100644 --- a/chrome/browser/ui/webui/settings/site_settings_permissions_handler_unittest.cc +++ b/chrome/browser/ui/webui/settings/site_settings_permissions_handler_unittest.cc
@@ -30,6 +30,8 @@ constexpr char kUnusedTestSite[] = "https://example1.com"; constexpr char kUsedTestSite[] = "https://example2.com"; +constexpr ContentSettingsType kUnusedPermission = + ContentSettingsType::GEOLOCATION; class SiteSettingsPermissionsHandlerTest : public testing::Test { public: @@ -93,6 +95,18 @@ } } + void ExpectRevokedPermission() { + ContentSettingsForOneType revoked_permissions_list; + hcsm()->GetSettingsForOneType( + ContentSettingsType::REVOKED_UNUSED_SITE_PERMISSIONS, + &revoked_permissions_list); + EXPECT_EQ(1U, revoked_permissions_list.size()); + EXPECT_EQ( + ContentSetting::CONTENT_SETTING_ASK, + hcsm()->GetContentSetting(GURL(kUnusedTestSite), GURL(kUnusedTestSite), + kUnusedPermission)); + } + TestingProfile* profile() { return profile_.get(); } content::TestWebUI* web_ui() { return &web_ui_; } SiteSettingsPermissionsHandler* handler() { return handler_.get(); } @@ -133,6 +147,9 @@ // Advance 14 days; this will be the expected histogram sample. clock()->Advance(base::Days(14)); base::HistogramTester histogram_tester; + base::Value::List initial_unused_site_permissions = + handler()->PopulateUnusedSitePermissionsData(); + ExpectRevokedPermission(); // Allow the revoked permission for the unused site again. base::Value::List args; @@ -157,7 +174,12 @@ EXPECT_EQ( ContentSetting::CONTENT_SETTING_ALLOW, hcsm()->GetContentSetting(GURL(kUnusedTestSite), GURL(kUnusedTestSite), - ContentSettingsType::GEOLOCATION)); + kUnusedPermission)); + + // Undoing restores the initial state. + handler()->HandleUndoAllowPermissionsAgainForUnusedSite( + std::move(initial_unused_site_permissions)); + ExpectRevokedPermission(); } TEST_F(SiteSettingsPermissionsHandlerTest,
diff --git a/chrome/browser/web_applications/policy/web_app_policy_manager_browsertest.cc b/chrome/browser/web_applications/policy/web_app_policy_manager_browsertest.cc index b382bd43..c3b1e723 100644 --- a/chrome/browser/web_applications/policy/web_app_policy_manager_browsertest.cc +++ b/chrome/browser/web_applications/policy/web_app_policy_manager_browsertest.cc
@@ -48,32 +48,31 @@ constexpr char kStartUrl[] = "https://example.com/start/?u=1"; constexpr char kManifestUrl[] = "https://example.com/install/manifest.json"; -base::Value GetForceInstalledAppItem() { - base::Value item(base::Value::Type::DICT); - item.SetKey(kUrlKey, base::Value(kInstallUrl)); - item.SetKey(kDefaultLaunchContainerKey, - base::Value(kDefaultLaunchContainerWindowValue)); +base::Value::Dict GetForceInstalledAppItem() { + base::Value::Dict item; + item.Set(kUrlKey, kInstallUrl); + item.Set(kDefaultLaunchContainerKey, kDefaultLaunchContainerWindowValue); return item; } -base::Value GetCustomAppNameItem() { - base::Value item = GetForceInstalledAppItem(); - item.SetKey(kCustomNameKey, base::Value(kDefaultCustomName)); +base::Value::Dict GetCustomAppNameItem() { + base::Value::Dict item = GetForceInstalledAppItem(); + item.Set(kCustomNameKey, kDefaultCustomName); return item; } -base::Value GetCustomAppIconItem() { - base::Value item = GetForceInstalledAppItem(); - base::Value sub_item(base::Value::Type::DICT); - sub_item.SetKey(kCustomIconURLKey, base::Value(kDefaultCustomIconUrl)); - sub_item.SetKey(kCustomIconHashKey, base::Value(kDefaultCustomIconHash)); - item.SetKey(kCustomIconKey, std::move(sub_item)); +base::Value::Dict GetCustomAppIconItem() { + base::Value::Dict item = GetForceInstalledAppItem(); + base::Value::Dict sub_item; + sub_item.Set(kCustomIconURLKey, kDefaultCustomIconUrl); + sub_item.Set(kCustomIconHashKey, kDefaultCustomIconHash); + item.Set(kCustomIconKey, std::move(sub_item)); return item; } -base::Value GetCustomAppIconAndNameItem() { - base::Value item = GetCustomAppIconItem(); - item.SetKey(kCustomNameKey, base::Value(kDefaultCustomName)); +base::Value::Dict GetCustomAppIconAndNameItem() { + base::Value::Dict item = GetCustomAppIconItem(); + item.Set(kCustomNameKey, kDefaultCustomName); return item; }
diff --git a/chrome/build/linux.pgo.txt b/chrome/build/linux.pgo.txt index 3edbdb2..a6ff40a 100644 --- a/chrome/build/linux.pgo.txt +++ b/chrome/build/linux.pgo.txt
@@ -1 +1 @@ -chrome-linux-main-1675036157-2a352d548f125d4ddfa254d9ff226e80d9f9799f.profdata +chrome-linux-main-1675057898-7fe3eba43fcaaecd815af7fc771049ceac93c406.profdata
diff --git a/chrome/build/mac-arm.pgo.txt b/chrome/build/mac-arm.pgo.txt index af91563..b68117d 100644 --- a/chrome/build/mac-arm.pgo.txt +++ b/chrome/build/mac-arm.pgo.txt
@@ -1 +1 @@ -chrome-mac-arm-main-1675036157-de5577c6796a1bae18ce319db757618a629d2e71.profdata +chrome-mac-arm-main-1675079896-7024c936ef87f65ff9d525b0e7866c3d936a0791.profdata
diff --git a/chrome/build/mac.pgo.txt b/chrome/build/mac.pgo.txt index 1584026..c3af4ab 100644 --- a/chrome/build/mac.pgo.txt +++ b/chrome/build/mac.pgo.txt
@@ -1 +1 @@ -chrome-mac-main-1675036157-e555aeb936bea0d1b9f27d276ecf828dcb9f34b8.profdata +chrome-mac-main-1675057898-0ce58d35aa925a78369db708279eb3b0b31be706.profdata
diff --git a/chrome/build/win32.pgo.txt b/chrome/build/win32.pgo.txt index 2c68003..0f62bb0 100644 --- a/chrome/build/win32.pgo.txt +++ b/chrome/build/win32.pgo.txt
@@ -1 +1 @@ -chrome-win32-main-1675036157-9739a839008de7437c454eb8e749518c73a3ba0f.profdata +chrome-win32-main-1675069017-7ea10c02f2205e73a5db0d2ea573fb614129421e.profdata
diff --git a/chrome/build/win64.pgo.txt b/chrome/build/win64.pgo.txt index d52e1a6..f580431 100644 --- a/chrome/build/win64.pgo.txt +++ b/chrome/build/win64.pgo.txt
@@ -1 +1 @@ -chrome-win64-main-1675036157-176866fa78a28cac409a46b4c32fbd9099017b45.profdata +chrome-win64-main-1675069017-00a4a655ec9ddeddfdde1dbc7a93aa14f660cb02.profdata
diff --git a/chrome/common/chrome_features.cc b/chrome/common/chrome_features.cc index 22653f14..8f15ebe 100644 --- a/chrome/common/chrome_features.cc +++ b/chrome/common/chrome_features.cc
@@ -650,7 +650,7 @@ // When enabled, users will see a warning when downloading from Incognito. BASE_FEATURE(kIncognitoDownloadsWarning, "IncognitoDownloadsWarning", - base::FEATURE_DISABLED_BY_DEFAULT); + base::FEATURE_ENABLED_BY_DEFAULT); #endif // When enabled, users will see updated UI in Incognito NTP
diff --git a/chrome/common/chromeos/extensions/api/_api_features.json b/chrome/common/chromeos/extensions/api/_api_features.json index b0bebb5..d48745c 100644 --- a/chrome/common/chromeos/extensions/api/_api_features.json +++ b/chrome/common/chromeos/extensions/api/_api_features.json
@@ -29,5 +29,17 @@ "lacros" ], "channel": "stable" + }, + "os.telemetry.getUsbBusInfo": { + "dependencies": [ "permission:os.telemetry" ], + "contexts": [ + "blessed_extension" + ], + "platforms": [ + "chromeos", + "lacros" + ], + "channel": "stable", + "feature_flag": "TelemetryExtensionPendingApprovalApi" } }
diff --git a/chrome/common/chromeos/extensions/api/telemetry.idl b/chrome/common/chromeos/extensions/api/telemetry.idl index 4823150f..e91c7b1 100644 --- a/chrome/common/chromeos/extensions/api/telemetry.idl +++ b/chrome/common/chromeos/extensions/api/telemetry.idl
@@ -178,6 +178,121 @@ callback OsVersionInfoCallback = void (OsVersionInfo osVersionInfo); + // The info related to usb interfaces. + dictionary UsbBusInterfaceInfo { + // The zero-based number (index) of the interface. + double? interfaceNumber; + // These fields can be used to classify / identify the usb interfaces. See the + // usb.ids database for the values. (https://github.com/gentoo/hwids) + double? classId; + double? subclassId; + double? protocolId; + // The driver used by the device. This is the name of the matched driver which + // is registered in the kernel. See "{kernel root}/drivers/" for the list of + // the built in drivers. + DOMString? driver; + }; + + // An enumeration of the formats of firmware version in fwpud. See the fwupd + // repo for the values. (https://github.com/fwupd/fwupd) + enum FwupdVersionFormat { + // An unidentified format text string. + plain, + // A single integer version number. + number, + // Two AABB.CCDD version numbers. + pair, + // Microsoft-style AA.BB.CCDD version numbers. + triplet, + // UEFI-style AA.BB.CC.DD version numbers. + quad, + // Binary coded decimal notation. + bcd, + // Intel ME-style bitshifted notation. + intelMe, + // Intel ME-style A.B.CC.DDDD notation. + intelMe2, + // Legacy Microsoft Surface 10b.12b.10b. + surfaceLegacy, + // Microsoft Surface 8b.16b.8b. + surface, + // Dell BIOS BB.CC.DD style. + dellBios, + // Hexadecimal 0xAABCCDD style. + hex + }; + + // The info related to a firmware version obtained from fwupd. + dictionary FwupdFirmwareVersionInfo { + // The string form of the firmware version. + DOMString? version; + // The format for parsing the version string. + FwupdVersionFormat? version_format; + }; + + enum UsbVersion { + // Can't determine the usb version. + unknown, + // Usb 1. + usb1, + // Usb 2. + usb2, + // Usb 3. + usb3 + }; + + // An enumeration of the usb spec speed in Mbps. + // Source: + // - https://www.kernel.org/doc/Documentation/ABI/testing/sysfs-bus-usb + // - https://www.kernel.org/doc/Documentation/ABI/stable/sysfs-bus-usb + // - https://en.wikipedia.org/wiki/USB + // + enum UsbSpecSpeed { + // Unknown speed. + unknown, + // Low speed. + n1_5Mbps, + // Full speed. + n12Mbps, + // High Speed. + n480Mbps, + // Super Speed. + n5Gbps, + // Super Speed+. + n10Gbps, + // Super Speed+ Gen 2x2. + n20Gbps + }; + + // The info related to usb. + dictionary UsbBusInfo { + // These fields can be used to classify / identify the usb devices. See the + // usb.ids database for the values. (https://github.com/gentoo/hwids) + double? classId; + double? subclassId; + double? protocolId; + double? vendorId; + double? productId; + // The usb interfaces under the device. A usb device has at least one + // interface. Each interface may or may not work independently, based on each + // device. This allows a usb device to provide multiple features. + // The interfaces are sorted by the `interface_number` field. + UsbBusInterfaceInfo[] interfaces; + // The firmware version obtained from fwupd. + FwupdFirmwareVersionInfo? fwupdFirmwareVersionInfo; + // The recognized usb version. It may not be the highest USB version supported + // by the hardware. + UsbVersion? version; + // The spec usb speed. + UsbSpecSpeed? spec_speed; + }; + + dictionary UsbBusDevices { + UsbBusInfo[] devices; + }; + + callback UsbBusDevicesCallback = void (UsbBusDevices UsbBusDevices); + dictionary VpdInfo { // Device activate date. Format: YYYY-WW. DOMString? activateDate; @@ -276,6 +391,8 @@ [supportsPromises] static void getOsVersionInfo(OsVersionInfoCallback callback); + [supportsPromises] static void getUsbBusInfo(UsbBusDevicesCallback callback); + [supportsPromises] static void getVpdInfo(VpdInfoCallback callback); [supportsPromises] static void getStatefulPartitionInfo(StatefulPartitionInfoCallback callback);
diff --git a/chrome/common/extensions/api/file_manager_private.idl b/chrome/common/extensions/api/file_manager_private.idl index b32e8cd..215adb9 100644 --- a/chrome/common/extensions/api/file_manager_private.idl +++ b/chrome/common/extensions/api/file_manager_private.idl
@@ -1333,7 +1333,8 @@ // Gets the list of tasks that can be performed over selected files. // |entries| Array of selected entries - // |dlpSourceUrls| Array of source URLs corresponding to the entries, used to check Data Leak Prevention (DLP) restrictions + // |dlpSourceUrls| Array of source URLs corresponding to the entries, used to + // check Data Leak Prevention (DLP) restrictions // |callback| [nocompile] static void getFileTasks([instanceof=Entry] object[] entries, @@ -1782,10 +1783,13 @@ // Invoke Sharesheet for selected files. // |entries| Array of selected entries. // |launchSource| Source from which sharesheet was invoked. + // |dlpSourceUrls| Array of source URLs corresponding to the entries, used to + // check Data Leak Prevention (DLP) restrictions // |callback| [nocompile] static void invokeSharesheet([instanceof=Entry] object[] entries, SharesheetLaunchSource launchSource, + DOMString[] dlpSourceUrls, SimpleCallback callback); // Adds or removes a list of entries to temporary holding space. Any entries
diff --git a/chrome/common/extensions/api/file_manager_private_internal.idl b/chrome/common/extensions/api/file_manager_private_internal.idl index 40cc397..70be908 100644 --- a/chrome/common/extensions/api/file_manager_private_internal.idl +++ b/chrome/common/extensions/api/file_manager_private_internal.idl
@@ -161,6 +161,7 @@ static void invokeSharesheet( DOMString[] urls, fileManagerPrivate.SharesheetLaunchSource launchSource, + DOMString[] dlpSourceUrls, SimpleCallback callback); static void toggleAddedToHoldingSpace(DOMString[] urls, boolean add, optional SimpleCallback callback);
diff --git a/chrome/renderer/resources/extensions/file_manager_private_custom_bindings.js b/chrome/renderer/resources/extensions/file_manager_private_custom_bindings.js index f764374..6b80b8a 100644 --- a/chrome/renderer/resources/extensions/file_manager_private_custom_bindings.js +++ b/chrome/renderer/resources/extensions/file_manager_private_custom_bindings.js
@@ -368,12 +368,13 @@ }); apiFunctions.setHandleRequest( - 'invokeSharesheet', function(entries, launchSource, callback) { + 'invokeSharesheet', + function(entries, launchSource, dlpSourceUrls, callback) { var urls = entries.map(function(entry) { return getEntryURL(entry); }); fileManagerPrivateInternal.invokeSharesheet( - urls, launchSource, callback); + urls, launchSource, dlpSourceUrls, callback); }); apiFunctions.setHandleRequest(
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn index ada7e67..683e620 100644 --- a/chrome/test/BUILD.gn +++ b/chrome/test/BUILD.gn
@@ -9754,7 +9754,10 @@ deps += [ "../browser/pdf:pdf_extension_test_utils" ] } if (is_chromeos) { - sources += [ "../browser/ui/views/toolbar/browser_app_menu_button_interactive_uitest_chromeos.cc" ] + sources += [ + "../browser/signin/mirror_interactive_uitest.cc", + "../browser/ui/views/toolbar/browser_app_menu_button_interactive_uitest_chromeos.cc", + ] } if (is_linux || is_chromeos_lacros) { # Desktop linux. @@ -9877,6 +9880,8 @@ # These tests require a MessageCenter which isn't available on Lacros. sources -= [ "../browser/notifications/notification_interactive_uitest.cc" ] + + deps += [ ":lacros_test_support_ui" ] } if (is_win) {
diff --git a/chrome/test/data/policy/policy_test_cases.json b/chrome/test/data/policy/policy_test_cases.json index 996e49fd..86195d6 100644 --- a/chrome/test/data/policy/policy_test_cases.json +++ b/chrome/test/data/policy/policy_test_cases.json
@@ -3935,6 +3935,55 @@ } ] }, + "ExtensionUnpublishedAvailability": { + "os": [ + "win", + "linux", + "mac", + "chromeos_ash", + "crhomeos_lacros" + ], + "policy_pref_mapping_tests": [ + { + "policies": { + "ExtensionUnpublishedAvailability": 0 + }, + "prefs": { + "extensions.unpublished_availability": { + "value": 0 + } + } + }, + { + "policies": { + "ExtensionUnpublishedAvailability": 1 + }, + "prefs": { + "extensions.unpublished_availability": { + "value": 1 + } + } + }, + { + "policies": { + "ExtensionUnpublishedAvailability": 2 + }, + "prefs": { + "extensions.unpublished_availability": { + "value": 2 + } + } + }, + { + "policies": {}, + "prefs": { + "extensions.unpublished_availability": { + "default_value": 0 + } + } + } + ] + }, "BlockExternalExtensions": { "os": [ "win",
diff --git a/chrome/test/data/webui/password_manager/settings_section_test.ts b/chrome/test/data/webui/password_manager/settings_section_test.ts index e540bc8..de2df2fc 100644 --- a/chrome/test/data/webui/password_manager/settings_section_test.ts +++ b/chrome/test/data/webui/password_manager/settings_section_test.ts
@@ -14,6 +14,13 @@ import {TestPrefsBrowserProxy} from './test_prefs_browser_proxy.js'; import {createBlockedSiteEntry, makePasswordManagerPrefs} from './test_util.js'; +// Disable clang format to keep OS-specific includes. +// clang-format off +// <if expr="is_win or is_macosx"> +import {PrefToggleButtonElement} from 'chrome://password-manager/password_manager.js'; + // </if> +// clang-format on + /** * Helper method that validates a that elements in the exception list match * the expected data. @@ -81,6 +88,50 @@ assertFalse(settings.$.autosigninToggle.checked); }); + // <if expr="is_win or is_macosx"> + // Tests that biometric auth pref is visible, and clicking on it triggers + // biometric auth validation instead of directly updating the pref value. + test('biometric auth prefs when feature is available', async function() { + await prefsProxy.setPref('biometric_authentication_filling', false); + loadTimeData.overrideValues( + {biometricAuthenticationForFillingToggleVisible: true}); + + const settings = document.createElement('settings-section'); + document.body.appendChild(settings); + await prefsProxy.whenCalled('getPref'); + await flushTasks(); + + const biometricAuthenticationToggle = + settings.shadowRoot!.querySelector<PrefToggleButtonElement>( + '#biometricAuthenticationToggle') as PrefToggleButtonElement; + assertTrue(!!biometricAuthenticationToggle); + assertFalse(biometricAuthenticationToggle.checked); + biometricAuthenticationToggle.click(); + + // Pref settings should not change until authentication succeeds. + await passwordManager.whenCalled('switchBiometricAuthBeforeFillingState'); + assertFalse(biometricAuthenticationToggle.checked); + + // Imitate prefs changing after successful identification. + prefsProxy.prefs = makePasswordManagerPrefs(); + await prefsProxy.setPref('biometric_authentication_filling', true); + assertTrue(biometricAuthenticationToggle.checked); + }); + + // Tests that biometric auth pref is not shown, if biometric auth is + // unavailable. + test('biometric auth prefs when feature is unavailable', async function() { + loadTimeData.overrideValues( + {biometricAuthenticationForFillingToggleVisible: false}); + const settings = document.createElement('settings-section'); + document.body.appendChild(settings); + await prefsProxy.whenCalled('getPref'); + await flushTasks(); + assertFalse(!!settings.shadowRoot!.querySelector<PrefToggleButtonElement>( + '#biometricAuthenticationToggle')); + }); + // </if> + test('settings section shows blockedSites', async function() { passwordManager.data.blockedSites = [ createBlockedSiteEntry('test.com', 0),
diff --git a/chrome/test/data/webui/password_manager/test_password_manager_proxy.ts b/chrome/test/data/webui/password_manager/test_password_manager_proxy.ts index b134f0e..d83055f 100644 --- a/chrome/test/data/webui/password_manager/test_password_manager_proxy.ts +++ b/chrome/test/data/webui/password_manager/test_password_manager_proxy.ts
@@ -53,6 +53,7 @@ 'requestPlaintextPassword', 'showAddShortcutDialog', 'startBulkPasswordCheck', + 'switchBiometricAuthBeforeFillingState', 'unmuteInsecureCredential', ]); @@ -216,4 +217,8 @@ cancelExportPasswords() { this.methodCalled('cancelExportPasswords'); } + + switchBiometricAuthBeforeFillingState() { + this.methodCalled('switchBiometricAuthBeforeFillingState'); + } }
diff --git a/chrome/test/data/webui/password_manager/test_util.ts b/chrome/test/data/webui/password_manager/test_util.ts index 650860f..2675256 100644 --- a/chrome/test/data/webui/password_manager/test_util.ts +++ b/chrome/test/data/webui/password_manager/test_util.ts
@@ -129,6 +129,13 @@ type: chrome.settingsPrivate.PrefType.BOOLEAN, value: true, }, + // <if expr="is_win or is_macosx"> + { + key: 'biometric_authentication_filling', + type: chrome.settingsPrivate.PrefType.BOOLEAN, + value: true, + }, + // </if> ]; }
diff --git a/chromeos/CHROMEOS_LKGM b/chromeos/CHROMEOS_LKGM index 1f37f7a..b711bcd 100644 --- a/chromeos/CHROMEOS_LKGM +++ b/chromeos/CHROMEOS_LKGM
@@ -1 +1 @@ -15332.0.0 \ No newline at end of file +15333.0.0 \ No newline at end of file
diff --git a/chromeos/ash/components/dbus/authpolicy/fake_authpolicy_client_unittest.cc b/chromeos/ash/components/dbus/authpolicy/fake_authpolicy_client_unittest.cc index 3b9ce67..707b97f 100644 --- a/chromeos/ash/components/dbus/authpolicy/fake_authpolicy_client_unittest.cc +++ b/chromeos/ash/components/dbus/authpolicy/fake_authpolicy_client_unittest.cc
@@ -5,8 +5,9 @@ #include "chromeos/ash/components/dbus/authpolicy/fake_authpolicy_client.h" #include "base/functional/bind.h" -#include "base/run_loop.h" +#include "base/test/repeating_test_future.h" #include "base/test/task_environment.h" +#include "base/test/test_future.h" #include "chromeos/ash/components/dbus/session_manager/session_manager_client.h" #include "chromeos/ash/components/install_attributes/stub_install_attributes.h" #include "components/account_id/account_id.h" @@ -88,8 +89,7 @@ } void OnWaitForServiceToBeAvailableCalled(bool is_service_available) { - EXPECT_TRUE(is_service_available); - service_is_available_called_num_++; + service_available_future_.AddValue(is_service_available); } void LockDevice() { @@ -97,7 +97,7 @@ "device_id"); } - int service_is_available_called_num_ = 0; + base::test::RepeatingTestFuture<bool> service_available_future_; private: ScopedStubInstallAttributes install_attributes_; @@ -131,17 +131,11 @@ EXPECT_EQ(authpolicy::ERROR_INVALID_MACHINE_NAME, error); EXPECT_TRUE(domain.empty()); })); - base::RunLoop loop; - JoinAdDomain(">nvalidname", kCorrectUserName, - base::BindOnce( - [](base::OnceClosure closure, authpolicy::ErrorType error, - const std::string& domain) { - EXPECT_EQ(authpolicy::ERROR_INVALID_MACHINE_NAME, error); - EXPECT_TRUE(domain.empty()); - std::move(closure).Run(); - }, - loop.QuitClosure())); - loop.Run(); + + base::test::TestFuture<authpolicy::ErrorType, const std::string&> future; + JoinAdDomain(">nvalidname", kCorrectUserName, future.GetCallback()); + EXPECT_EQ(authpolicy::ERROR_INVALID_MACHINE_NAME, future.Get<0>()); + EXPECT_TRUE(future.Get<1>().empty()); } // Tests join to a different machine domain. @@ -154,18 +148,13 @@ EXPECT_EQ(authpolicy::ERROR_NONE, error); EXPECT_EQ(kMachineDomain, domain); })); - base::RunLoop loop; - JoinAdDomainWithMachineDomain( - kCorrectMachineName, "", kCorrectUserName, - base::BindOnce( - [](base::OnceClosure closure, authpolicy::ErrorType error, - const std::string& domain) { - EXPECT_EQ(authpolicy::ERROR_NONE, error); - EXPECT_EQ(kCorrectUserDomain, domain); - std::move(closure).Run(); - }, - loop.QuitClosure())); - loop.Run(); + + base::test::TestFuture<authpolicy::ErrorType, const std::string&> future; + JoinAdDomainWithMachineDomain(kCorrectMachineName, "", kCorrectUserName, + future.GetCallback()); + + EXPECT_EQ(authpolicy::ERROR_NONE, future.Get<0>()); + EXPECT_EQ(kCorrectUserDomain, future.Get<1>()); } // Tests parsing user name. @@ -201,40 +190,28 @@ EXPECT_EQ(authpolicy::ERROR_PARSE_UPN_FAILED, error); EXPECT_TRUE(domain.empty()); })); - base::RunLoop loop; - JoinAdDomain(kCorrectMachineName, "user@realm@com", - base::BindOnce( - [](base::OnceClosure closure, authpolicy::ErrorType error, - const std::string& domain) { - EXPECT_EQ(authpolicy::ERROR_PARSE_UPN_FAILED, error); - EXPECT_TRUE(domain.empty()); - std::move(closure).Run(); - }, - loop.QuitClosure())); - loop.Run(); + + base::test::TestFuture<authpolicy::ErrorType, const std::string&> future; + JoinAdDomain(kCorrectMachineName, "user@realm@com", future.GetCallback()); + EXPECT_EQ(authpolicy::ERROR_PARSE_UPN_FAILED, future.Get<0>()); + EXPECT_TRUE(future.Get<1>().empty()); } // Tests that fake server does not support legacy encryption types. TEST_F(FakeAuthPolicyClientTest, JoinAdDomain_NotSupportedEncType) { authpolicy_client()->SetStarted(true); - base::RunLoop loop; authpolicy::JoinDomainRequest request; request.set_machine_name(kCorrectMachineName); request.set_user_principal_name(kCorrectUserName); request.set_kerberos_encryption_types( authpolicy::KerberosEncryptionTypes::ENC_TYPES_LEGACY); - authpolicy_client()->JoinAdDomain( - request, /* password_fd */ -1, - base::BindOnce( - [](base::OnceClosure closure, authpolicy::ErrorType error, - const std::string& domain) { - EXPECT_EQ(authpolicy::ERROR_KDC_DOES_NOT_SUPPORT_ENCRYPTION_TYPE, - error); - EXPECT_TRUE(domain.empty()); - std::move(closure).Run(); - }, - loop.QuitClosure())); - loop.Run(); + + base::test::TestFuture<authpolicy::ErrorType, const std::string&> future; + authpolicy_client()->JoinAdDomain(request, /* password_fd */ -1, + future.GetCallback()); + EXPECT_EQ(authpolicy::ERROR_KDC_DOES_NOT_SUPPORT_ENCRYPTION_TYPE, + future.Get<0>()); + EXPECT_TRUE(future.Get<1>().empty()); } // Test AuthenticateUser. @@ -271,16 +248,10 @@ base::BindOnce([](authpolicy::ErrorType error) { EXPECT_EQ(authpolicy::ERROR_DBUS_FAILURE, error); })); - base::RunLoop loop; + base::test::TestFuture<authpolicy::ErrorType> future; authpolicy_client()->RefreshUserPolicy( - AccountId::FromUserEmail(kCorrectUserName), - base::BindOnce( - [](base::OnceClosure closure, authpolicy::ErrorType error) { - EXPECT_EQ(authpolicy::ERROR_DBUS_FAILURE, error); - std::move(closure).Run(); - }, - loop.QuitClosure())); - loop.Run(); + AccountId::FromUserEmail(kCorrectUserName), future.GetCallback()); + EXPECT_EQ(authpolicy::ERROR_DBUS_FAILURE, future.Get()); } // Tests RefreshDevicePolicy. On a not locked device it should cache policy. On @@ -292,14 +263,9 @@ EXPECT_EQ(authpolicy::ERROR_DEVICE_POLICY_CACHED_BUT_NOT_SENT, error); })); LockDevice(); - base::RunLoop loop; - authpolicy_client()->RefreshDevicePolicy(base::BindOnce( - [](base::OnceClosure closure, authpolicy::ErrorType error) { - EXPECT_EQ(authpolicy::ERROR_NONE, error); - std::move(closure).Run(); - }, - loop.QuitClosure())); - loop.Run(); + base::test::TestFuture<authpolicy::ErrorType> future; + authpolicy_client()->RefreshDevicePolicy(future.GetCallback()); + EXPECT_EQ(authpolicy::ERROR_NONE, future.Get()); } // Tests that RefreshDevicePolicy stores device policy in the session manager. @@ -309,17 +275,12 @@ { // Call RefreshDevicePolicy. - base::RunLoop loop; + base::test::TestFuture<authpolicy::ErrorType> future; em::ChromeDeviceSettingsProto policy; policy.mutable_allow_new_users()->set_allow_new_users(true); authpolicy_client()->set_device_policy(policy); - authpolicy_client()->RefreshDevicePolicy(base::BindOnce( - [](base::OnceClosure closure, authpolicy::ErrorType error) { - EXPECT_EQ(authpolicy::ERROR_NONE, error); - std::move(closure).Run(); - }, - loop.QuitClosure())); - loop.Run(); + authpolicy_client()->RefreshDevicePolicy(future.GetCallback()); + EXPECT_EQ(authpolicy::ERROR_NONE, future.Get()); } { @@ -343,16 +304,16 @@ } TEST_F(FakeAuthPolicyClientTest, WaitForServiceToBeAvailableCalled) { + // Start waiting for service before starting the client. WaitForServiceToBeAvailable(); WaitForServiceToBeAvailable(); - base::RunLoop().RunUntilIdle(); - EXPECT_EQ(0, service_is_available_called_num_); authpolicy_client()->SetStarted(true); - base::RunLoop().RunUntilIdle(); - EXPECT_EQ(2, service_is_available_called_num_); WaitForServiceToBeAvailable(); - base::RunLoop().RunUntilIdle(); - EXPECT_EQ(3, service_is_available_called_num_); + + // Wait for the future to catch all three callbacks. + EXPECT_TRUE(service_available_future_.Take()); + EXPECT_TRUE(service_available_future_.Take()); + EXPECT_TRUE(service_available_future_.Take()); } } // namespace ash
diff --git a/chromeos/ash/components/drivefs/drivefs_pin_manager.cc b/chromeos/ash/components/drivefs/drivefs_pin_manager.cc index adc0296..0b3030b 100644 --- a/chromeos/ash/components/drivefs/drivefs_pin_manager.cc +++ b/chromeos/ash/components/drivefs/drivefs_pin_manager.cc
@@ -6,8 +6,6 @@ #include <locale> #include <type_traits> -#include <utility> -#include <vector> #include "base/files/file_path.h" #include "base/functional/bind.h" @@ -301,28 +299,41 @@ // queue. constexpr base::TimeDelta kPeriodicRemovalInterval = base::Seconds(10); -bool PinManager::Add(const Id id, const std::string& path, const int64_t size) { +bool PinManager::Add(const Id id, + const std::string& path, + const int64_t size, + const bool pinned) { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); DCHECK_GE(size, 0) << " for " << id << " " << Quote(path); - const auto [it, ok] = - files_to_pin_.try_emplace(id, File{.path = path, .total = size}); + const auto [it, ok] = files_to_track_.try_emplace( + id, File{.path = path, .total = size, .pinned = pinned}); DCHECK_EQ(id, it->first); if (!ok) { LOG_IF(ERROR, !ok) << "Cannot add " << id << " " << Quote(path) << " with size " << HumanReadableSize(size) - << " to the files to pin: Conflicting entry " + << " to the files to track: Conflicting entry " << it->second; return false; } VLOG(3) << "Added " << id << " " << Quote(path) << " with size " - << HumanReadableSize(size) << " to the files to pin"; + << HumanReadableSize(size) << " to the files to track"; progress_.bytes_to_pin += size; progress_.required_space += RoundToBlockSize(size); progress_.files_to_pin++; DCHECK_EQ(static_cast<size_t>(progress_.files_to_pin), - files_to_pin_.size() + files_to_track_.size()); + files_to_track_.size()); + + if (pinned) { + progress_.syncing_files++; + DCHECK_EQ(progress_.syncing_files, CountPinnedFiles()); + } else { + files_to_pin_.push_back(id); + DCHECK_LE(files_to_pin_.size(), + static_cast<size_t>(progress_.files_to_pin)); + } + return true; } @@ -343,10 +354,12 @@ Update(*it, path, transferred, transferred); } + if (it->second.pinned) { + progress_.syncing_files--; + } + files_to_track_.erase(it); - progress_.syncing_files--; - DCHECK_EQ(static_cast<size_t>(progress_.syncing_files), - files_to_track_.size()); + DCHECK_EQ(progress_.syncing_files, CountPinnedFiles()); VLOG(3) << "Stopped tracking " << id << " " << Quote(path); return true; } @@ -541,26 +554,13 @@ VLOG(1) << "Already pinned but not available offline yet: " << id << " " << Quote(path); - const auto [it, ok] = files_to_track_.try_emplace( - id, File{.path = path.value(), .total = size, .pinned = true}); - DCHECK(ok); - DCHECK_EQ(it->first, id); - progress_.syncing_files++; - DCHECK_EQ(static_cast<size_t>(progress_.syncing_files), - files_to_track_.size()); - progress_.bytes_to_pin += size; - progress_.required_space += RoundToBlockSize(size); - progress_.files_to_pin++; - DCHECK_EQ(static_cast<size_t>(progress_.files_to_pin), - files_to_pin_.size() + files_to_track_.size()); - continue; + } else { + VLOG_IF(1, md.available_offline) + << "Not pinned yet but already available offline: " << id << " " + << Quote(path) << ": " << Quote(md); } - VLOG_IF(1, md.available_offline) - << "Not pinned yet but already available offline: " << id << " " - << Quote(path) << ": " << Quote(md); - - Add(id, path.value(), size); + Add(id, path.value(), size, md.pinned); } NotifyProgress(); @@ -637,13 +637,14 @@ return Complete(Stage::kSuccess); } - if (files_to_track_.empty() && files_to_pin_.empty()) { + if (files_to_track_.empty()) { VLOG(1) << "Nothing to pin or track"; + DCHECK(files_to_pin_.empty()); + DCHECK_EQ(progress_.files_to_pin, 0); + DCHECK_EQ(progress_.syncing_files, 0); return Complete(Stage::kSuccess); } - VLOG(1) << "Pinning and tracking " - << (files_to_pin_.size() + files_to_track_.size()) << " files..."; timer_ = base::ElapsedTimer(); progress_.stage = Stage::kSyncing; NotifyProgress(); @@ -665,27 +666,33 @@ return Complete(Stage::kSuccess); } - while (files_to_track_.size() < 50 && !files_to_pin_.empty()) { - Files::node_type node = files_to_pin_.extract(files_to_pin_.begin()); - DCHECK(node); - const Id id = node.key(); - File& file = node.mapped(); + while (progress_.syncing_files < 50 && !files_to_pin_.empty()) { + const Id id = files_to_pin_.back(); + files_to_pin_.pop_back(); + + const Files::iterator it = files_to_track_.find(id); + if (it == files_to_track_.end()) { + VLOG(2) << "Not tracked: " << id; + continue; + } + + DCHECK_EQ(it->first, id); + File& file = it->second; const std::string& path = file.path; + if (file.pinned) { + VLOG(2) << "Already pinned: " << id << " " << Quote(path); + continue; + } + VLOG(2) << "Pinning " << id << " " << Quote(path); drivefs_->SetPinnedByStableId( static_cast<int64_t>(id), true, base::BindOnce(&PinManager::OnFilePinned, GetWeakPtr(), id, path)); - DCHECK(!file.pinned); - DCHECK(!file.in_progress); file.pinned = true; - const Files::insert_return_type ir = - files_to_track_.insert(std::move(node)); - DCHECK(ir.inserted) << " for " << id << " " << path; progress_.syncing_files++; - DCHECK_EQ(static_cast<size_t>(progress_.syncing_files), - files_to_track_.size()); + DCHECK_EQ(progress_.syncing_files, CountPinnedFiles()); } VLOG(1) << "Progress " @@ -794,7 +801,21 @@ if (progress_.stage != Stage::kSyncing) { for (const mojom::FileChange& change : changes) { - VLOG(1) << "Ignored FileChange " << Quote(change); + switch (change.type) { + case mojom::FileChange::Type::kDelete: + VLOG(1) << "Got FileChange " << Quote(change); + drivefs_->SetPinnedByStableId( + change.stable_id, /*pinned=*/false, + base::BindOnce([](drive::FileError status) { + LOG_IF(ERROR, status != drive::FILE_ERROR_OK) + << "Failed to unpin file: " + << drive::FileErrorToString(status); + })); + break; + default: + VLOG(1) << "Ignored FileChange " << Quote(change); + break; + } } return; }
diff --git a/chromeos/ash/components/drivefs/drivefs_pin_manager.h b/chromeos/ash/components/drivefs/drivefs_pin_manager.h index effd7243..ce52530 100644 --- a/chromeos/ash/components/drivefs/drivefs_pin_manager.h +++ b/chromeos/ash/components/drivefs/drivefs_pin_manager.h
@@ -5,6 +5,7 @@ #ifndef CHROMEOS_ASH_COMPONENTS_DRIVEFS_DRIVEFS_PIN_MANAGER_H_ #define CHROMEOS_ASH_COMPONENTS_DRIVEFS_DRIVEFS_PIN_MANAGER_H_ +#include <algorithm> #include <ostream> #include <utility> #include <vector> @@ -203,7 +204,7 @@ } private: - // Struct keeping track of the progress of a file being synced. + // Progress of a file being synced or to be synced. struct File { // Path inside the Drive folder. // TODO(b/265209836) Remove this field when not needed anymore. @@ -230,22 +231,21 @@ using Files = std::map<Id, File>; - // Adds an item to the files to pin. Does nothing if an item with the same ID - // already exists in files_to_pin_. Updates the total number of bytes to - // transfer and the required space. Returns whether an item was actually - // added. - bool Add(Id id, const std::string& path, int64_t size); + // Adds an item to the files to track. Does nothing if an item with the same + // ID already exists in the map. Updates the total number of bytes to transfer + // and the required space. Returns whether an item was actually added. + bool Add(Id id, const std::string& path, int64_t size, bool pinned); - // Removes an item from the map. Does nothing if the item is not in the map. - // Updates the total number of bytes transferred so far. If `transferred` is - // negative, use the total expected size. Returns whether an item was actually - // removed. + // Removes an item from the files to track. Does nothing if the item is not in + // the map. Updates the total number of bytes transferred so far. If + // `transferred` is negative, use the total expected size. Returns whether an + // item was actually removed. bool Remove(Id id, const std::string& path, int64_t transferred = -1); - // Updates an item in the map. Does nothing if the item is not in the map. - // Updates the total number of bytes transferred so far. Updates the required - // space. If `transferred` or `total` is negative, then the matching argument - // is ignored. Returns whether anything has actually been updated. + // Updates an item in the files to track. Does nothing if the item is not in + // the map. Updates the total number of bytes transferred so far. Updates the + // required space. If `transferred` or `total` is negative, then the matching + // argument is ignored. Returns whether anything has actually been updated. bool Update(Id id, const std::string& path, int64_t transferred, @@ -300,6 +300,16 @@ // Report progress to all the observers. void NotifyProgress(); + // Counts the files that have been marked as pinned and that are still being + // tracked. Should always be equal to progress_.syncing_files. For debugging + // only. + int CountPinnedFiles() const { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + return std::count_if( + files_to_track_.cbegin(), files_to_track_.cend(), + [](const Files::value_type& entry) { return entry.second.pinned; }); + } + SEQUENCE_CHECKER(sequence_checker_); // Should the feature actually pin files, or should it stop after checking the @@ -321,8 +331,12 @@ mojo::Remote<mojom::SearchQuery> search_query_; base::ElapsedTimer timer_; - // Map that tracks the in-progress files indexed by their stable ID. - Files files_to_pin_ GUARDED_BY_CONTEXT(sequence_checker_); + // Stable IDs of the files to pin, and which are not already marked as pinned. + std::vector<Id> files_to_pin_ GUARDED_BY_CONTEXT(sequence_checker_); + + // Map that tracks the in-progress files indexed by their stable ID. This + // contains all the files, either pinned or not, that are not completely + // cached yet. Files files_to_track_ GUARDED_BY_CONTEXT(sequence_checker_); base::WeakPtrFactory<PinManager> weak_ptr_factory_{this};
diff --git a/chromeos/ash/components/drivefs/drivefs_pin_manager_unittest.cc b/chromeos/ash/components/drivefs/drivefs_pin_manager_unittest.cc index c851ace2..7ba3e4b 100644 --- a/chromeos/ash/components/drivefs/drivefs_pin_manager_unittest.cc +++ b/chromeos/ash/components/drivefs/drivefs_pin_manager_unittest.cc
@@ -48,6 +48,7 @@ using testing::_; using testing::AnyNumber; using testing::DoAll; +using testing::ElementsAre; using testing::Field; using testing::IsEmpty; using testing::Return; @@ -265,24 +266,25 @@ EXPECT_THAT(manager.files_to_track_, IsEmpty()); // Add an item. - EXPECT_TRUE(manager.Add(id1, path1, size1)); - EXPECT_THAT(manager.files_to_pin_, SizeIs(1)); - EXPECT_THAT(manager.files_to_track_, IsEmpty()); + EXPECT_TRUE(manager.Add(id1, path1, size1, false)); + EXPECT_THAT(manager.files_to_pin_, ElementsAre(id1)); + EXPECT_THAT(manager.files_to_track_, SizeIs(1)); // Try to add a conflicting item with the same ID, but different path and // size. - EXPECT_FALSE(manager.Add(id1, path2, size2)); - EXPECT_THAT(manager.files_to_pin_, SizeIs(1)); - EXPECT_THAT(manager.files_to_track_, IsEmpty()); + EXPECT_FALSE(manager.Add(id1, path2, size2, true)); + EXPECT_THAT(manager.files_to_pin_, ElementsAre(id1)); + EXPECT_THAT(manager.files_to_track_, SizeIs(1)); { - const auto it = manager.files_to_pin_.find(id1); - ASSERT_NE(it, manager.files_to_pin_.end()); + const auto it = manager.files_to_track_.find(id1); + ASSERT_NE(it, manager.files_to_track_.end()); const auto& [id, file] = *it; EXPECT_EQ(id, id1); EXPECT_EQ(file.path, path1); EXPECT_EQ(file.total, size1); EXPECT_EQ(file.transferred, 0); + EXPECT_FALSE(file.pinned); EXPECT_FALSE(file.in_progress); } @@ -292,22 +294,25 @@ EXPECT_EQ(progress.pinned_bytes, 0); EXPECT_EQ(progress.bytes_to_pin, size1); EXPECT_EQ(progress.required_space, 698249216); + EXPECT_EQ(progress.syncing_files, 0); + EXPECT_EQ(progress.files_to_pin, 1); } - // Add a second item. - EXPECT_TRUE(manager.Add(id2, path2, size2)); - EXPECT_THAT(manager.files_to_pin_, SizeIs(2)); - EXPECT_THAT(manager.files_to_track_, IsEmpty()); + // Add a second item, but which is already pinned this time. + EXPECT_TRUE(manager.Add(id2, path2, size2, true)); + EXPECT_THAT(manager.files_to_pin_, ElementsAre(id1)); + EXPECT_THAT(manager.files_to_track_, SizeIs(2)); { - const auto it = manager.files_to_pin_.find(id2); - ASSERT_NE(it, manager.files_to_pin_.end()); + const auto it = manager.files_to_track_.find(id2); + ASSERT_NE(it, manager.files_to_track_.end()); const auto& [id, file] = *it; EXPECT_EQ(id, id2); EXPECT_EQ(file.path, path2); EXPECT_EQ(file.total, size2); EXPECT_EQ(file.transferred, 0); EXPECT_FALSE(file.in_progress); + EXPECT_TRUE(file.pinned); } { @@ -316,6 +321,8 @@ EXPECT_EQ(progress.pinned_bytes, 0); EXPECT_EQ(progress.bytes_to_pin, size1 + size2); EXPECT_EQ(progress.required_space, 777216000); + EXPECT_EQ(progress.syncing_files, 1); + EXPECT_EQ(progress.files_to_pin, 2); } } @@ -548,6 +555,7 @@ id1, PinManager::File{.path = path1, .transferred = 1200, .total = 3000, + .pinned = true, .in_progress = true}); ASSERT_TRUE(ok); manager.progress_.syncing_files++; @@ -600,9 +608,9 @@ id1, PinManager::File{.path = path1, .transferred = 1200, .total = 3000, + .pinned = false, .in_progress = true}); ASSERT_TRUE(ok); - manager.progress_.syncing_files++; } EXPECT_THAT(manager.files_to_track_, SizeIs(1)); @@ -626,6 +634,7 @@ id1, PinManager::File{.path = path1, .transferred = 5000, .total = 6000, + .pinned = true, .in_progress = true}); ASSERT_TRUE(ok); manager.progress_.syncing_files++; @@ -673,13 +682,13 @@ // Put in place a couple of files to track. { const auto [it, ok] = manager.files_to_track_.try_emplace( - id1, PinManager::File{.path = path1, .total = 10000}); + id1, PinManager::File{.path = path1, .total = 10000, .pinned = true}); ASSERT_TRUE(ok); manager.progress_.syncing_files++; } { const auto [it, ok] = manager.files_to_track_.try_emplace( - id2, PinManager::File{.path = path2, .total = 20000}); + id2, PinManager::File{.path = path2, .total = 20000, .pinned = true}); ASSERT_TRUE(ok); manager.progress_.syncing_files++; } @@ -740,6 +749,7 @@ EXPECT_EQ(file.path, path1); EXPECT_EQ(file.total, 10000); EXPECT_EQ(file.transferred, 0); + EXPECT_TRUE(file.pinned); EXPECT_TRUE(file.in_progress); } @@ -775,6 +785,7 @@ EXPECT_EQ(file.path, path1); EXPECT_EQ(file.total, 10000); EXPECT_EQ(file.transferred, 5000); + EXPECT_TRUE(file.pinned); EXPECT_TRUE(file.in_progress); }
diff --git a/chromeos/ash/components/drivefs/fake_drivefs.cc b/chromeos/ash/components/drivefs/fake_drivefs.cc index 1ae5657..dc00f4c1 100644 --- a/chromeos/ash/components/drivefs/fake_drivefs.cc +++ b/chromeos/ash/components/drivefs/fake_drivefs.cc
@@ -90,20 +90,6 @@ return std::move(bootstrap_); } -struct FakeDriveFs::FileMetadata { - std::string mime_type; - bool pinned = false; - bool hosted = false; - bool shared = false; - bool available_offline = false; - std::string original_name; - mojom::Capabilities capabilities; - mojom::FolderFeature folder_feature; - std::string doc_id; - int64_t stable_id = 0; - std::string alternate_url; -}; - class FakeDriveFs::SearchQuery : public mojom::SearchQuery { public: SearchQuery(base::WeakPtr<FakeDriveFs> drive_fs, @@ -258,6 +244,13 @@ base::WeakPtrFactory<SearchQuery> weak_ptr_factory_{this}; }; +FakeDriveFs::FileMetadata::FileMetadata() = default; +FakeDriveFs::FileMetadata::FileMetadata(const FakeDriveFs::FileMetadata&) = + default; +FakeDriveFs::FileMetadata& FakeDriveFs::FileMetadata::operator=( + const FakeDriveFs::FileMetadata&) = default; +FakeDriveFs::FileMetadata::~FileMetadata() = default; + FakeDriveFs::FakeDriveFs(const base::FilePath& mount_path) : mount_path_(mount_path) { CHECK(mount_path.IsAbsolute()); @@ -331,6 +324,15 @@ return absl::nullopt; } +absl::optional<FakeDriveFs::FileMetadata> FakeDriveFs::GetItemMetadata( + const base::FilePath& path) { + const auto& metadata = metadata_.find(path); + if (metadata == metadata_.end()) { + return absl::nullopt; + } + return metadata->second; +} + void FakeDriveFs::Init( drivefs::mojom::DriveFsConfigurationPtr config, mojo::PendingReceiver<drivefs::mojom::DriveFs> receiver,
diff --git a/chromeos/ash/components/drivefs/fake_drivefs.h b/chromeos/ash/components/drivefs/fake_drivefs.h index 680e555..050d7ff 100644 --- a/chromeos/ash/components/drivefs/fake_drivefs.h +++ b/chromeos/ash/components/drivefs/fake_drivefs.h
@@ -84,8 +84,29 @@ absl::optional<bool> IsItemPinned(const std::string& path); + struct FileMetadata { + FileMetadata(); + FileMetadata(const FileMetadata&); + FileMetadata& operator=(const FileMetadata&); + ~FileMetadata(); + + std::string mime_type; + bool pinned = false; + bool hosted = false; + bool shared = false; + bool available_offline = false; + std::string original_name; + mojom::Capabilities capabilities; + mojom::FolderFeature folder_feature; + std::string doc_id; + int64_t stable_id = 0; + std::string alternate_url; + }; + + absl::optional<FakeDriveFs::FileMetadata> GetItemMetadata( + const base::FilePath& path); + private: - struct FileMetadata; class SearchQuery; // drivefs::mojom::DriveFsBootstrap:
diff --git a/components/autofill/core/browser/autofill_client.h b/components/autofill/core/browser/autofill_client.h index 81b5d91f..52b741815 100644 --- a/components/autofill/core/browser/autofill_client.h +++ b/components/autofill/core/browser/autofill_client.h
@@ -65,6 +65,7 @@ class AutofillAblationStudy; class AutofillDriver; struct AutofillErrorDialogContext; +class AutofillManager; class AutofillOfferData; class AutofillOfferManager; class AutofillPopupDelegate; @@ -638,9 +639,10 @@ // extensive than `IsFastCheckoutSupported()`. // If it is, shows the FastCheckout surface (for autofilling information // during the checkout flow) and returns `true` on success. - virtual bool TryToShowFastCheckout(const FormData& form, - const FormFieldData& field, - AutofillDriver* driver) = 0; + virtual bool TryToShowFastCheckout( + const FormData& form, + const FormFieldData& field, + base::WeakPtr<AutofillManager> autofill_manager) = 0; // Hides the Fast Checkout surface (for autofilling information during the // checkout flow) if one is currently shown.
diff --git a/components/autofill/core/browser/autofill_experiments.cc b/components/autofill/core/browser/autofill_experiments.cc index e9ae5a1..e21558be 100644 --- a/components/autofill/core/browser/autofill_experiments.cc +++ b/components/autofill/core/browser/autofill_experiments.cc
@@ -35,7 +35,6 @@ #include "components/sync/driver/sync_user_settings.h" #include "components/variations/variations_associated_data.h" #include "google_apis/gaia/gaia_auth_util.h" -#include "google_apis/gaia/google_service_auth_error.h" #include "ui/base/l10n/l10n_util.h" #include "ui/base/ui_base_features.h" @@ -116,13 +115,11 @@ return false; } - // TODO(crbug.com/1156584): This should simply check SyncService:: - // GetTransportState() is PAUSED. - if (sync_service->GetAuthError().IsPersistentError()) { + if (sync_service->GetTransportState() == + syncer::SyncService::TransportState::PAUSED) { autofill_metrics::LogCardUploadEnabledMetric( - autofill_metrics::CardUploadEnabled::kSyncServicePersistentAuthError, - sync_state); - LogCardUploadDisabled(log_manager, "SYNC_SERVICE_PERSISTENT_ERROR"); + autofill_metrics::CardUploadEnabled::kSyncServicePaused, sync_state); + LogCardUploadDisabled(log_manager, "SYNC_SERVICE_PAUSED"); return false; }
diff --git a/components/autofill/core/browser/autofill_experiments_unittest.cc b/components/autofill/core/browser/autofill_experiments_unittest.cc index 9b789cd..9b70ac6 100644 --- a/components/autofill/core/browser/autofill_experiments_unittest.cc +++ b/components/autofill/core/browser/autofill_experiments_unittest.cc
@@ -109,10 +109,10 @@ EXPECT_FALSE(IsCreditCardUploadEnabled(AutofillSyncSigninState::kSyncPaused)); histogram_tester.ExpectUniqueSample( "Autofill.CardUploadEnabled", - autofill_metrics::CardUploadEnabled::kSyncServicePersistentAuthError, 1); + autofill_metrics::CardUploadEnabled::kSyncServicePaused, 1); histogram_tester.ExpectUniqueSample( "Autofill.CardUploadEnabled.SyncPaused", - autofill_metrics::CardUploadEnabled::kSyncServicePersistentAuthError, 1); + autofill_metrics::CardUploadEnabled::kSyncServicePaused, 1); } TEST_F(AutofillExperimentsTest,
diff --git a/components/autofill/core/browser/autofill_manager.cc b/components/autofill/core/browser/autofill_manager.cc index f6e937a..c8c1168 100644 --- a/components/autofill/core/browser/autofill_manager.cc +++ b/components/autofill/core/browser/autofill_manager.cc
@@ -645,6 +645,18 @@ return it != form_structures_.end() ? it->second.get() : nullptr; } +void AutofillManager::SetShouldSuppressKeyboard(bool suppress) { + driver_->SetShouldSuppressKeyboard(suppress); +} + +bool AutofillManager::CanShowAutofillUi() const { + return driver_->CanShowAutofillUi(); +} + +void AutofillManager::TriggerReparseInAllFrames() { + driver_->TriggerReparseInAllFrames(); +} + void AutofillManager::ParseFormsAsync( const std::vector<FormData>& forms, base::OnceCallback<void(AutofillManager&, const std::vector<FormData>&)>
diff --git a/components/autofill/core/browser/autofill_manager.h b/components/autofill/core/browser/autofill_manager.h index 5a2a51f2..c8ce26f 100644 --- a/components/autofill/core/browser/autofill_manager.h +++ b/components/autofill/core/browser/autofill_manager.h
@@ -287,6 +287,15 @@ // Returns the number of forms this Autofill handler is aware of. size_t NumFormsDetected() const { return form_structures_.size(); } + // Forwards call to the same-named `AutofillDriver` function. + virtual void SetShouldSuppressKeyboard(bool suppress); + + // Forwards call to the same-named `AutofillDriver` function. + virtual bool CanShowAutofillUi() const; + + // Forwards call to the same-named `AutofillDriver` function. + virtual void TriggerReparseInAllFrames(); + void AddObserver(Observer* observer) { observers_.AddObserver(observer); } void RemoveObserver(Observer* observer) {
diff --git a/components/autofill/core/browser/autofill_manager_unittest.cc b/components/autofill/core/browser/autofill_manager_unittest.cc index 559670f..7c27f3f 100644 --- a/components/autofill/core/browser/autofill_manager_unittest.cc +++ b/components/autofill/core/browser/autofill_manager_unittest.cc
@@ -47,6 +47,10 @@ MockAutofillDriver(const MockAutofillDriver&) = delete; MockAutofillDriver& operator=(const MockAutofillDriver&) = delete; ~MockAutofillDriver() override = default; + + MOCK_METHOD(void, SetShouldSuppressKeyboard, (bool), ()); + MOCK_METHOD(bool, CanShowAutofillUi, (), (const)); + MOCK_METHOD(void, TriggerReparseInAllFrames, (), ()); }; class MockAutofillManager : public AutofillManager { @@ -359,4 +363,19 @@ manager_->OnTextFieldDidChange(form, field, bounds, time); } +TEST_F(AutofillManagerTest, SetShouldSuppressKeyboard) { + EXPECT_CALL(*driver_, SetShouldSuppressKeyboard(true)); + manager_->SetShouldSuppressKeyboard(true); +} + +TEST_F(AutofillManagerTest, CanShowAutofillUi) { + EXPECT_CALL(*driver_, CanShowAutofillUi).WillOnce(Return(true)); + EXPECT_TRUE(manager_->CanShowAutofillUi()); +} + +TEST_F(AutofillManagerTest, TriggerReparseInAllFrames) { + EXPECT_CALL(*driver_, TriggerReparseInAllFrames); + manager_->TriggerReparseInAllFrames(); +} + } // namespace autofill
diff --git a/components/autofill/core/browser/browser_autofill_manager.cc b/components/autofill/core/browser/browser_autofill_manager.cc index 3ff24b0..2291ec2 100644 --- a/components/autofill/core/browser/browser_autofill_manager.cc +++ b/components/autofill/core/browser/browser_autofill_manager.cc
@@ -1162,7 +1162,7 @@ auto ShouldShowSuggestion = [&] { if (client()->IsShowingFastCheckoutUI() || (form_element_was_clicked && - client()->TryToShowFastCheckout(form, field, driver()))) { + client()->TryToShowFastCheckout(form, field, GetWeakPtr()))) { // The Fast Checkout surface is shown, so abort showing regular Autofill // UI. Now the flow is controlled by the `FastCheckoutClient` instead of // `external_delegate_`. @@ -3328,18 +3328,6 @@ credit_card_form_event_logger_->set_autofill_suggestion_method(method); } -void BrowserAutofillManager::SetShouldSuppressKeyboard(bool suppress) { - driver()->SetShouldSuppressKeyboard(suppress); -} - -bool BrowserAutofillManager::CanShowAutofillUi() const { - return driver()->CanShowAutofillUi(); -} - -void BrowserAutofillManager::TriggerReparseInAllFrames() { - driver()->TriggerReparseInAllFrames(); -} - void BrowserAutofillManager::ProcessFieldLogEventsInForm( const FormStructure& form_structure) { // TODO(crbug.com/1325851): Log metrics if at least one field in the form was
diff --git a/components/autofill/core/browser/browser_autofill_manager.h b/components/autofill/core/browser/browser_autofill_manager.h index 4b5555b..d266080e 100644 --- a/components/autofill/core/browser/browser_autofill_manager.h +++ b/components/autofill/core/browser/browser_autofill_manager.h
@@ -312,15 +312,6 @@ // keyboard accessory, etc. virtual void SetAutofillSuggestionMethod(AutofillSuggestionMethod state); - // Forwards call to the same-named `AutofillDriver` function. - virtual void SetShouldSuppressKeyboard(bool suppress); - - // Forwards call to the same-named `AutofillDriver` function. - virtual bool CanShowAutofillUi() const; - - // Forwards call to the same-named `AutofillDriver` function. - virtual void TriggerReparseInAllFrames(); - void SetExternalDelegateForTest( std::unique_ptr<AutofillExternalDelegate> external_delegate) { external_delegate_ = std::move(external_delegate);
diff --git a/components/autofill/core/browser/browser_autofill_manager_unittest.cc b/components/autofill/core/browser/browser_autofill_manager_unittest.cc index 8cbb611..e1f820d 100644 --- a/components/autofill/core/browser/browser_autofill_manager_unittest.cc +++ b/components/autofill/core/browser/browser_autofill_manager_unittest.cc
@@ -381,9 +381,6 @@ SendFieldsEligibleForManualFillingToRenderer, (const std::vector<FieldGlobalId>& fields), (override)); - MOCK_METHOD(void, SetShouldSuppressKeyboard, (bool), ()); - MOCK_METHOD(bool, CanShowAutofillUi, (), (const)); - MOCK_METHOD(void, TriggerReparseInAllFrames, (), ()); }; } // namespace @@ -9817,21 +9814,6 @@ EXPECT_FALSE(external_delegate_->on_suggestions_returned_seen()); } -TEST_F(BrowserAutofillManagerTest, SetShouldSuppressKeyboard) { - EXPECT_CALL(*autofill_driver_, SetShouldSuppressKeyboard(true)); - browser_autofill_manager_->SetShouldSuppressKeyboard(true); -} - -TEST_F(BrowserAutofillManagerTest, CanShowAutofillUi) { - EXPECT_CALL(*autofill_driver_, CanShowAutofillUi).WillOnce(Return(true)); - EXPECT_TRUE(browser_autofill_manager_->CanShowAutofillUi()); -} - -TEST_F(BrowserAutofillManagerTest, TriggerReparseInAllFrames) { - EXPECT_CALL(*autofill_driver_, TriggerReparseInAllFrames); - browser_autofill_manager_->TriggerReparseInAllFrames(); -} - // Desktop only tests. #if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS) class BrowserAutofillManagerTestForVirtualCardOption
diff --git a/components/autofill/core/browser/metrics/autofill_metrics.cc b/components/autofill/core/browser/metrics/autofill_metrics.cc index 34fccd7..4f46e58 100644 --- a/components/autofill/core/browser/metrics/autofill_metrics.cc +++ b/components/autofill/core/browser/metrics/autofill_metrics.cc
@@ -2576,12 +2576,19 @@ had_value_before_filling |= event->had_value_before_filling; autofill_skipped_status.insert(event->autofill_skipped_status); had_value_after_filling = event->had_value_after_filling; + if (was_autofilled == OptionalBoolean::kTrue && + filled_value_was_modified == OptionalBoolean::kUndefined) { + // Only switch from unknown to false on the first filling. + filled_value_was_modified = OptionalBoolean::kFalse; + } ++autofill_count; } if (auto* event = absl::get_if<TypingFieldLogEvent>(&log_event)) { user_typed_into_field = OptionalBoolean::kTrue; - filled_value_was_modified |= was_autofilled; + if (was_autofilled == OptionalBoolean::kTrue) { + filled_value_was_modified = OptionalBoolean::kTrue; + } has_value_after_typing = event->has_value_after_typing; } @@ -2674,9 +2681,12 @@ } if (user_typed_into_field == OptionalBoolean::kTrue) { - builder.SetUserTypedIntoField(OptionalBooleanToBool(user_typed_into_field)) - .SetFilledValueWasModified( - OptionalBooleanToBool(filled_value_was_modified)); + builder.SetUserTypedIntoField(OptionalBooleanToBool(user_typed_into_field)); + } + + if (filled_value_was_modified != OptionalBoolean::kUndefined) { + builder.SetFilledValueWasModified( + OptionalBooleanToBool(filled_value_was_modified)); } if (had_typed_or_filled_value_at_submission != OptionalBoolean::kUndefined) {
diff --git a/components/autofill/core/browser/metrics/autofill_metrics_unittest.cc b/components/autofill/core/browser/metrics/autofill_metrics_unittest.cc index b94cfb67..7fc309b 100644 --- a/components/autofill/core/browser/metrics/autofill_metrics_unittest.cc +++ b/components/autofill/core/browser/metrics/autofill_metrics_unittest.cc
@@ -10599,6 +10599,7 @@ DenseSet<SkipStatus>{SkipStatus::kNotSkipped}.to_uint64()}, {UFIT::kWasRefillName, false}, {UFIT::kHadValueBeforeFillingName, false}, + {UFIT::kFilledValueWasModifiedName, false}, {UFIT::kHadTypedOrFilledValueAtSubmissionName, true}, }; if (i == 0) { @@ -10619,7 +10620,7 @@ // Test if we have recorded UKM metrics correctly about field types after // parsing the form by the local heuristic prediction. -TEST_F(AutofillMetricsFromLogEventsTest, AutofillFieldInfoMetrics_FieldType) { +TEST_F(AutofillMetricsFromLogEventsTest, AutofillFieldInfoMetricsFieldType) { FormData form = CreateForm( {// Heuristic value will match with Autocomplete attribute. CreateField("Last Name", "lastname", "", "text", "family-name"), @@ -10729,6 +10730,64 @@ } } +// Test if we have recorded FieldInfo UKM metrics correctly after typing in +// fields without autofilling first. +TEST_F(AutofillMetricsFromLogEventsTest, + AutofillFieldInfoMetricsEditedFieldWithoutFill) { + test::FormDescription form_description = { + .description_for_logging = "NumberOfAutofilledFields", + .fields = {{.role = NAME_FULL, + .value = u"Elvis Aaron Presley", + .is_autofilled = false}, + {.role = EMAIL_ADDRESS, + .value = u"buddy@gmail.com", + .is_autofilled = false}, + {.role = PHONE_HOME_CITY_AND_NUMBER, .is_autofilled = true}}, + .unique_renderer_id = test::MakeFormRendererId(), + .main_frame_origin = + url::Origin::Create(autofill_client_->form_origin())}; + + FormData form = GetAndAddSeenForm(form_description); + + base::HistogramTester histogram_tester; + // Simulate text input in the first and second fields. + SimulateUserChangedTextField(form, form.fields[0]); + SimulateUserChangedTextField(form, form.fields[1]); + + SubmitForm(form); + + // Record Autofill2.FieldInfo UKM event at autofill manager reset. + autofill_manager().Reset(); + + auto entries = + test_ukm_recorder_->GetEntriesByName(UkmFieldInfoType::kEntryName); + ASSERT_EQ(2u, entries.size()); + + for (size_t i = 0; i < entries.size(); ++i) { + SCOPED_TRACE(testing::Message() << i); + using UFIT = UkmFieldInfoType; + const auto* const entry = entries[i]; + + std::map<std::string, int64_t> expected = { + {UFIT::kFormSessionIdentifierName, + AutofillMetrics::FormGlobalIdToHash64Bit(form.global_id())}, + {UFIT::kFieldSessionIdentifierName, + AutofillMetrics::FieldGlobalIdToHash64Bit(form.fields[i].global_id())}, + {UFIT::kFieldSignatureName, + Collapse(CalculateFieldSignatureForField(form.fields[i])).value()}, + {UFIT::kWasFocusedName, false}, + {UFIT::kIsFocusableName, true}, + {UFIT::kUserTypedIntoFieldName, true}, + {UFIT::kHadTypedOrFilledValueAtSubmissionName, true}, + }; + + EXPECT_EQ(expected.size(), entry->metrics.size()); + for (const auto& [metric, value] : expected) { + test_ukm_recorder_->ExpectEntryMetric(entry, metric, value); + } + } +} + // TODO(crbug.com/1352826) Delete this after collecting the metrics. struct LaxLocalHeuristicsTestCase { test::FormDescription form;
diff --git a/components/autofill/core/browser/metrics/payments/credit_card_save_metrics.h b/components/autofill/core/browser/metrics/payments/credit_card_save_metrics.h index c4c6062..f11cf47 100644 --- a/components/autofill/core/browser/metrics/payments/credit_card_save_metrics.h +++ b/components/autofill/core/browser/metrics/payments/credit_card_save_metrics.h
@@ -89,7 +89,7 @@ // numeric values should never be reused. kSyncServiceNull = 0, - kSyncServicePersistentAuthError = 1, + kSyncServicePaused = 1, kSyncServiceMissingAutofillWalletDataActiveType = 2, kSyncServiceMissingAutofillProfileActiveType = 3, // Deprecated: kAccountWalletStorageUploadDisabled = 4,
diff --git a/components/autofill/core/browser/test_autofill_client.cc b/components/autofill/core/browser/test_autofill_client.cc index c1527d4..2ff4581 100644 --- a/components/autofill/core/browser/test_autofill_client.cc +++ b/components/autofill/core/browser/test_autofill_client.cc
@@ -302,9 +302,10 @@ void TestAutofillClient::ScanCreditCard(CreditCardScanCallback callback) {} -bool TestAutofillClient::TryToShowFastCheckout(const FormData& form, - const FormFieldData& field, - AutofillDriver* driver) { +bool TestAutofillClient::TryToShowFastCheckout( + const FormData& form, + const FormFieldData& field, + base::WeakPtr<AutofillManager> autofill_manager) { return false; }
diff --git a/components/autofill/core/browser/test_autofill_client.h b/components/autofill/core/browser/test_autofill_client.h index ba7c8e1..0a1b2eb 100644 --- a/components/autofill/core/browser/test_autofill_client.h +++ b/components/autofill/core/browser/test_autofill_client.h
@@ -164,9 +164,10 @@ AddressProfileSavePromptCallback callback) override; bool HasCreditCardScanFeature() override; void ScanCreditCard(CreditCardScanCallback callback) override; - bool TryToShowFastCheckout(const FormData& form, - const FormFieldData& field, - AutofillDriver* driver) override; + bool TryToShowFastCheckout( + const FormData& form, + const FormFieldData& field, + base::WeakPtr<AutofillManager> autofill_manager) override; void HideFastCheckout(bool allow_further_runs) override; bool IsFastCheckoutSupported() override; bool IsShowingFastCheckoutUI() override;
diff --git a/components/autofill/core/browser/webdata/contact_info_model_type_controller.cc b/components/autofill/core/browser/webdata/contact_info_model_type_controller.cc index 7c971c7..9866ad1 100644 --- a/components/autofill/core/browser/webdata/contact_info_model_type_controller.cc +++ b/components/autofill/core/browser/webdata/contact_info_model_type_controller.cc
@@ -4,9 +4,12 @@ #include "components/autofill/core/browser/webdata/contact_info_model_type_controller.h" +#include "base/check.h" #include "base/feature_list.h" #include "components/autofill/core/common/autofill_features.h" +#include "components/sync/base/features.h" #include "components/sync/base/model_type.h" +#include "components/sync/driver/sync_user_settings.h" namespace autofill { @@ -14,14 +17,35 @@ std::unique_ptr<syncer::ModelTypeControllerDelegate> delegate_for_full_sync_mode, std::unique_ptr<syncer::ModelTypeControllerDelegate> - delegate_for_transport_mode) + delegate_for_transport_mode, + syncer::SyncService* sync_service) : ModelTypeController(syncer::CONTACT_INFO, std::move(delegate_for_full_sync_mode), - std::move(delegate_for_transport_mode)) {} + std::move(delegate_for_transport_mode)), + sync_service_(sync_service) { + sync_service_observation_.Observe(sync_service_); +} + +ContactInfoModelTypeController::~ContactInfoModelTypeController() = default; bool ContactInfoModelTypeController::ShouldRunInTransportOnlyMode() const { return base::FeatureList::IsEnabled( autofill::features::kAutofillAccountProfilesOnSignIn); } +syncer::DataTypeController::PreconditionState +ContactInfoModelTypeController::GetPreconditionState() const { + return !sync_service_->GetUserSettings()->IsUsingExplicitPassphrase() || + base::FeatureList::IsEnabled( + syncer:: + kSyncEnableContactInfoDataTypeForCustomPassphraseUsers) + ? PreconditionState::kPreconditionsMet + : PreconditionState::kMustStopAndClearData; +} + +void ContactInfoModelTypeController::OnStateChanged(syncer::SyncService* sync) { + DCHECK(CalledOnValidThread()); + sync_service_->DataTypePreconditionChanged(type()); +} + } // namespace autofill
diff --git a/components/autofill/core/browser/webdata/contact_info_model_type_controller.h b/components/autofill/core/browser/webdata/contact_info_model_type_controller.h index 5c3bf18..95c84fd 100644 --- a/components/autofill/core/browser/webdata/contact_info_model_type_controller.h +++ b/components/autofill/core/browser/webdata/contact_info_model_type_controller.h
@@ -5,17 +5,24 @@ #ifndef COMPONENTS_AUTOFILL_CORE_BROWSER_WEBDATA_CONTACT_INFO_MODEL_TYPE_CONTROLLER_H_ #define COMPONENTS_AUTOFILL_CORE_BROWSER_WEBDATA_CONTACT_INFO_MODEL_TYPE_CONTROLLER_H_ +#include "base/memory/raw_ptr.h" +#include "base/scoped_observation.h" #include "components/sync/driver/model_type_controller.h" +#include "components/sync/driver/sync_service.h" +#include "components/sync/driver/sync_service_observer.h" namespace autofill { -class ContactInfoModelTypeController : public syncer::ModelTypeController { +class ContactInfoModelTypeController : public syncer::ModelTypeController, + public syncer::SyncServiceObserver { public: ContactInfoModelTypeController( std::unique_ptr<syncer::ModelTypeControllerDelegate> delegate_for_full_sync_mode, std::unique_ptr<syncer::ModelTypeControllerDelegate> - delegate_for_transport_mode); + delegate_for_transport_mode, + syncer::SyncService* sync_service); + ~ContactInfoModelTypeController() override; ContactInfoModelTypeController(const ContactInfoModelTypeController&) = delete; @@ -24,6 +31,15 @@ // ModelTypeController overrides. bool ShouldRunInTransportOnlyMode() const override; + PreconditionState GetPreconditionState() const override; + + // SyncServiceObserver overrides. + void OnStateChanged(syncer::SyncService* sync) override; + + private: + const raw_ptr<syncer::SyncService> sync_service_; + base::ScopedObservation<syncer::SyncService, syncer::SyncServiceObserver> + sync_service_observation_{this}; }; } // namespace autofill
diff --git a/components/browser_sync/sync_api_component_factory_impl.cc b/components/browser_sync/sync_api_component_factory_impl.cc index 83fe0dd..ce8da7c 100644 --- a/components/browser_sync/sync_api_component_factory_impl.cc +++ b/components/browser_sync/sync_api_component_factory_impl.cc
@@ -245,7 +245,8 @@ db_thread_, base::BindRepeating( &ContactInfoDelegateFromDataService, - base::RetainedRef(web_data_service_on_disk_))))); + base::RetainedRef(web_data_service_on_disk_))), + sync_service)); } // Wallet data sync is enabled by default. Register unless explicitly
diff --git a/components/browsing_data/content/service_worker_helper.cc b/components/browsing_data/content/service_worker_helper.cc index 9b8382c..68e794f 100644 --- a/components/browsing_data/content/service_worker_helper.cc +++ b/components/browsing_data/content/service_worker_helper.cc
@@ -25,7 +25,7 @@ namespace browsing_data { namespace { -void GetAllOriginsInfoForServiceWorkerCallback( +void OnGotAllStorageKeysInfoForServiceWorker( ServiceWorkerHelper::FetchCallback callback, const std::vector<StorageUsageInfo>& infos) { DCHECK_CURRENTLY_ON(BrowserThread::UI); @@ -55,7 +55,7 @@ DCHECK_CURRENTLY_ON(BrowserThread::UI); DCHECK(!callback.is_null()); service_worker_context_->GetAllStorageKeysInfo(base::BindOnce( - &GetAllOriginsInfoForServiceWorkerCallback, std::move(callback))); + &OnGotAllStorageKeysInfoForServiceWorker, std::move(callback))); } void ServiceWorkerHelper::DeleteServiceWorkers(const url::Origin& origin) {
diff --git a/components/password_manager/core/browser/password_manager_util_unittest.cc b/components/password_manager/core/browser/password_manager_util_unittest.cc index 8ecb6b7..8e82d66 100644 --- a/components/password_manager/core/browser/password_manager_util_unittest.cc +++ b/components/password_manager/core/browser/password_manager_util_unittest.cc
@@ -236,7 +236,7 @@ TryToShowFastCheckout, (const autofill::FormData&, const autofill::FormFieldData&, - autofill::AutofillDriver*), + base::WeakPtr<autofill::AutofillManager>), (override)); MOCK_METHOD(void, HideFastCheckout, (bool), (override)); MOCK_METHOD(bool, IsShowingFastCheckoutUI, (), (override));
diff --git a/components/permissions/unused_site_permissions_service.cc b/components/permissions/unused_site_permissions_service.cc index c57a533..0bdcefc 100644 --- a/components/permissions/unused_site_permissions_service.cc +++ b/components/permissions/unused_site_permissions_service.cc
@@ -191,6 +191,21 @@ ContentSettingsType::REVOKED_UNUSED_SITE_PERMISSIONS, {}); } +void UnusedSitePermissionsService::UndoRegrantPermissionsForOrigin( + const std::set<ContentSettingsType> permissions, + const absl::optional<content_settings::ContentSettingConstraints> + constraint, + const url::Origin origin) { + for (const auto& permission : permissions) { + hcsm_->SetContentSettingCustomScope( + ContentSettingsPattern::FromURLNoWildcard(origin.GetURL()), + ContentSettingsPattern::Wildcard(), permission, + ContentSetting::CONTENT_SETTING_DEFAULT); + } + + StorePermissionInRevokedPermissionSetting(permissions, constraint, origin); +} + void UnusedSitePermissionsService::ClearRevokedPermissionsList() { ContentSettingsForOneType settings; hcsm_->GetSettingsForOneType( @@ -293,7 +308,7 @@ ContentSettingsPattern secondary_pattern = unused_site_permissions.front().source.secondary_pattern; - std::list<ContentSettingsType> revoked_permissions; + std::set<ContentSettingsType> revoked_permissions; for (auto permission_itr = unused_site_permissions.begin(); permission_itr != unused_site_permissions.end();) { const ContentSettingEntry& entry = *permission_itr; @@ -315,7 +330,7 @@ if (entry.source.metadata.last_visited < threshold && entry.source.secondary_pattern == ContentSettingsPattern::Wildcard()) { - revoked_permissions.push_back(entry.type); + revoked_permissions.insert(entry.type); hcsm_->SetContentSettingCustomScope( entry.source.primary_pattern, entry.source.secondary_pattern, entry.type, ContentSetting::CONTENT_SETTING_DEFAULT); @@ -351,7 +366,7 @@ } void UnusedSitePermissionsService::StorePermissionInRevokedPermissionSetting( - const std::list<ContentSettingsType> permissions, + const std::set<ContentSettingsType> permissions, const absl::optional<content_settings::ContentSettingConstraints> constraint, const url::Origin origin) { @@ -364,7 +379,7 @@ } void UnusedSitePermissionsService::StorePermissionInRevokedPermissionSetting( - const std::list<ContentSettingsType> permissions, + const std::set<ContentSettingsType> permissions, const absl::optional<content_settings::ContentSettingConstraints> constraint, const ContentSettingsPattern& primary_pattern,
diff --git a/components/permissions/unused_site_permissions_service.h b/components/permissions/unused_site_permissions_service.h index dadd2a45..ec5a691 100644 --- a/components/permissions/unused_site_permissions_service.h +++ b/components/permissions/unused_site_permissions_service.h
@@ -87,13 +87,21 @@ // from revoked permissions list. void RegrantPermissionsForOrigin(const url::Origin& origin); + // Reverse changes made by |RegrantPermissionsForOrigin|. Adds this origin to + // the removed permissions list and resets its permissions. + void UndoRegrantPermissionsForOrigin( + const std::set<ContentSettingsType> permissions, + const absl::optional<content_settings::ContentSettingConstraints> + constraint, + const url::Origin origin); + // Clear the list of revoked permissions so they will no longer be shown to // the user. Does not change permissions themselves. void ClearRevokedPermissionsList(); // Stores revoked permissions data on HCSM. void StorePermissionInRevokedPermissionSetting( - const std::list<ContentSettingsType> permissions, + const std::set<ContentSettingsType> permissions, const absl::optional<content_settings::ContentSettingConstraints> constraint, const url::Origin origin); @@ -123,7 +131,7 @@ // Stores revoked permissions data on HCSM. void StorePermissionInRevokedPermissionSetting( - const std::list<ContentSettingsType> permissions, + const std::set<ContentSettingsType> permissions, const absl::optional<content_settings::ContentSettingConstraints> constraint, const ContentSettingsPattern& primary_pattern,
diff --git a/components/permissions/unused_site_permissions_service_unittest.cc b/components/permissions/unused_site_permissions_service_unittest.cc index 85ec54e..d89b16e 100644 --- a/components/permissions/unused_site_permissions_service_unittest.cc +++ b/components/permissions/unused_site_permissions_service_unittest.cc
@@ -372,6 +372,89 @@ // Check if the permissions of url1 is regranted. EXPECT_EQ(ContentSetting::CONTENT_SETTING_ALLOW, hcsm()->GetContentSetting(GURL(url1), GURL(url1), type)); + + // Undoing the changes should add url1 back to the list of revoked permissions + // and reset its permissions. + service()->UndoRegrantPermissionsForOrigin({type}, absl::nullopt, + url::Origin::Create(GURL(url1))); + + hcsm()->GetSettingsForOneType( + ContentSettingsType::REVOKED_UNUSED_SITE_PERMISSIONS, + &revoked_permissions_list); + EXPECT_EQ(2U, revoked_permissions_list.size()); + EXPECT_EQ(ContentSetting::CONTENT_SETTING_ASK, + hcsm()->GetContentSetting(GURL(url1), GURL(url1), type)); +} + +TEST_F(UnusedSitePermissionsServiceTest, RegrantPreventsAutorevoke) { + base::test::ScopedFeatureList scoped_feature; + scoped_feature.InitAndEnableFeature( + content_settings::features::kSafetyCheckUnusedSitePermissions); + + const GURL url1 = GURL("https://example1.com:443"); + const content_settings::ContentSettingConstraints constraint{ + .track_last_visit_for_autoexpiration = true}; + + hcsm()->SetContentSettingDefaultScope( + url1, url1, ContentSettingsType::GEOLOCATION, + ContentSetting::CONTENT_SETTING_ALLOW, constraint); + EXPECT_EQ(GetRevokedUnusedPermissions(hcsm()).size(), 0u); + + // Travel 70 days through time so that the granted permission is revoked. + clock()->Advance(base::Days(70)); + service()->UpdateUnusedPermissionsForTesting(); + EXPECT_EQ(GetRevokedUnusedPermissions(hcsm()).size(), 1u); + + // After regranting permissions they are not revoked again even after >60 days + // pass. + service()->RegrantPermissionsForOrigin(url::Origin::Create(url1)); + EXPECT_EQ(GetRevokedUnusedPermissions(hcsm()).size(), 0u); + + clock()->Advance(base::Days(70)); + service()->UpdateUnusedPermissionsForTesting(); + EXPECT_EQ(GetRevokedUnusedPermissions(hcsm()).size(), 0u); +} + +TEST_F(UnusedSitePermissionsServiceTest, UndoRegrantPermissionsForOrigin) { + base::test::ScopedFeatureList scoped_feature; + scoped_feature.InitAndEnableFeature( + content_settings::features::kSafetyCheckUnusedSitePermissions); + + const GURL url1 = GURL("https://example1.com:443"); + const ContentSettingsType type = ContentSettingsType::GEOLOCATION; + const content_settings::ContentSettingConstraints constraint{ + .track_last_visit_for_autoexpiration = true}; + + hcsm()->SetContentSettingDefaultScope( + url1, url1, type, ContentSetting::CONTENT_SETTING_ALLOW, constraint); + EXPECT_EQ(GetRevokedUnusedPermissions(hcsm()).size(), 0u); + + // Travel 70 days through time so that the granted permission is revoked. + clock()->Advance(base::Days(70)); + service()->UpdateUnusedPermissionsForTesting(); + EXPECT_EQ(GetRevokedUnusedPermissions(hcsm()).size(), 1u); + const ContentSettingPatternSource revoked_permission = + GetRevokedUnusedPermissions(hcsm())[0]; + + // Permission remains revoked after regrant and undo. + content_settings::ContentSettingConstraints expiration_constraint = { + .expiration = revoked_permission.metadata.expiration}; + service()->RegrantPermissionsForOrigin(url::Origin::Create(url1)); + service()->UndoRegrantPermissionsForOrigin({type}, expiration_constraint, + url::Origin::Create(url1)); + EXPECT_EQ(GetRevokedUnusedPermissions(hcsm()).size(), 1u); + + // Revoked permission is cleaned up after >30 days. + clock()->Advance(base::Days(40)); + service()->UpdateUnusedPermissionsForTesting(); + EXPECT_EQ(GetRevokedUnusedPermissions(hcsm()).size(), 0u); + + // If that permission is granted again, it will still be autorevoked. + hcsm()->SetContentSettingDefaultScope( + url1, url1, type, ContentSetting::CONTENT_SETTING_ALLOW, constraint); + clock()->Advance(base::Days(70)); + service()->UpdateUnusedPermissionsForTesting(); + EXPECT_EQ(GetRevokedUnusedPermissions(hcsm()).size(), 1u); } TEST_F(UnusedSitePermissionsServiceTest, NotRevokeNotificationPermission) {
diff --git a/components/policy/resources/templates/policies.yaml b/components/policy/resources/templates/policies.yaml index b99a981..e7bcb11 100644 --- a/components/policy/resources/templates/policies.yaml +++ b/components/policy/resources/templates/policies.yaml
@@ -1069,6 +1069,7 @@ 1068: WindowManagementAllowedForUrls 1069: WindowManagementBlockedForUrls 1070: OutOfProcessSystemDnsResolutionEnabled + 1071: ExtensionUnpublishedAvailability atomic_groups: 1: Homepage 2: RemoteAccess
diff --git a/components/policy/resources/templates/policy_definitions/Extensions/ExtensionUnpublishedAvailability.yaml b/components/policy/resources/templates/policy_definitions/Extensions/ExtensionUnpublishedAvailability.yaml new file mode 100644 index 0000000..4c9a84a --- /dev/null +++ b/components/policy/resources/templates/policy_definitions/Extensions/ExtensionUnpublishedAvailability.yaml
@@ -0,0 +1,43 @@ +owners: +- anunoy@chromium.org +- file://components/policy/OWNERS +caption: Control availability of extensions unpublished on the Chrome Web Store. +desc: |- + If this policy is enabled, extensions that are unpublished on the Chrome Web + Store will be disabled on <ph name="PRODUCT_NAME">$1<ex>Google Chrome</ex></ph>. + This policy will ignore developer-mode, command-line installed and other + extensions that are not installed from Chrome Web Store (eg. self-hosting). + + If the policy is set to <ph name="ALLOW_UNPUBLISHED">AllowUnpublished</ph> (0) or not set, unpublished extensions are allowed. + If the policy is set to <ph name="DISABLE_UNPUBLISHED_EXCEPT_FORCED_OR_VERSION_PINNED">DisableUnpublishedExceptForcedOrVersionPinned</ph> (1), extensions that are unpublished on the Chrome Wesb Store will be disabled, but not if the extension is force-installed or version-pinned by enterprise policy (See Note). + If the policy is set to <ph name="DISABLE_UNPUBLISHED">DisableUnpublished</ph> (2), extensions that are unpublished on the Chrome Web Store will be disabled, even if the extension is force-installed or version-pinned by enterprise policy (See Note). + + Note: Force-installation and version-pinning are configured using the <ph name="EXTENSION_INSTALL_FORCELIST_POLICY_NAME">ExtensionInstallForcelist</ph> and + <ph name="EXTENSION_SETTINGS_POLICY_NAME">ExtensionSettings</ph> policies. + +future_on: +- chrome.* +- chrome_os +features: + dynamic_refresh: true + per_profile: true +type: int-enum +schema: + type: integer + enum: + - 0 + - 1 + - 2 +items: +- caption: Allow unpublished extensions + name: AllowUnpublished + value: 0 +- caption: Disable unpublished extensions except force-installed or version-pinned + name: DisableUnpublishedExceptForcedOrVersionPinned + value: 1 +- caption: Disable unpublished extensions including force-installed or version-pinned + name: DisableUnpublished + value: 2 +default: 0 +example_value: 1 +tags: []
diff --git a/components/policy/resources/templates/policy_definitions/Extensions/policy_atomic_groups.yaml b/components/policy/resources/templates/policy_definitions/Extensions/policy_atomic_groups.yaml index 7246e0fc..67980cb 100644 --- a/components/policy/resources/templates/policy_definitions/Extensions/policy_atomic_groups.yaml +++ b/components/policy/resources/templates/policy_definitions/Extensions/policy_atomic_groups.yaml
@@ -9,3 +9,4 @@ - ExtensionAllowInsecureUpdates - ExtensionSettings - ExtensionManifestV2Availability + - ExtensionUnpublishedAvailability
diff --git a/components/policy/resources/templates/policy_definitions/Miscellaneous/LacrosSelection.yaml b/components/policy/resources/templates/policy_definitions/Miscellaneous/LacrosSelection.yaml index 0850325..69bbebbd 100644 --- a/components/policy/resources/templates/policy_definitions/Miscellaneous/LacrosSelection.yaml +++ b/components/policy/resources/templates/policy_definitions/Miscellaneous/LacrosSelection.yaml
@@ -61,6 +61,6 @@ - stateful type: string supported_on: -- chrome_os:110- +- chrome_os:112- tags: [] type: string-enum
diff --git a/components/services/app_service/public/cpp/intent_util.cc b/components/services/app_service/public/cpp/intent_util.cc index f47a62f..8be4dc8 100644 --- a/components/services/app_service/public/cpp/intent_util.cc +++ b/components/services/app_service/public/cpp/intent_util.cc
@@ -122,6 +122,19 @@ return intent; } +apps::IntentPtr MakeShareIntent( + const std::vector<GURL>& filesystem_urls, + const std::vector<std::string>& mime_types, + const std::vector<std::string>& dlp_source_urls) { + auto intent = MakeShareIntent(filesystem_urls, mime_types); + + DCHECK_EQ(filesystem_urls.size(), dlp_source_urls.size()); + for (size_t i = 0; i < filesystem_urls.size(); i++) { + intent->files[i]->dlp_source_url = dlp_source_urls[i]; + } + return intent; +} + apps::IntentPtr MakeEditIntent(const GURL& filesystem_url, const std::string& mime_type) { auto intent = std::make_unique<apps::Intent>(kIntentActionEdit);
diff --git a/components/services/app_service/public/cpp/intent_util.h b/components/services/app_service/public/cpp/intent_util.h index 8d36e52..0fcf4cf7 100644 --- a/components/services/app_service/public/cpp/intent_util.h +++ b/components/services/app_service/public/cpp/intent_util.h
@@ -63,6 +63,12 @@ apps::IntentPtr MakeShareIntent(const std::string& text, const std::string& title); +// Creates an intent for sharing |filesystem_urls|, with |dlpSourceUrls|. +apps::IntentPtr MakeShareIntent( + const std::vector<GURL>& filesystem_urls, + const std::vector<std::string>& mime_types, + const std::vector<std::string>& dlp_source_urls); + // Create an edit intent for the file with a given |filesystem_url| and // |mime_type|. apps::IntentPtr MakeEditIntent(const GURL& filesystem_url,
diff --git a/components/services/app_service/public/cpp/intent_util_unittest.cc b/components/services/app_service/public/cpp/intent_util_unittest.cc index 687b4e8..17079dfb 100644 --- a/components/services/app_service/public/cpp/intent_util_unittest.cc +++ b/components/services/app_service/public/cpp/intent_util_unittest.cc
@@ -704,6 +704,23 @@ EXPECT_TRUE(intent->MatchFilter(filter)); } +TEST_F(IntentUtilTest, FileWithDlpSourceUrls) { + const std::string mime_type = "image/jpeg"; + const GURL file_url = GURL("https://www.google.com/"); + const std::string dlp_source_url = "https://www.example.com/"; + + auto filter = apps_util::MakeIntentFilterForSend(mime_type); + const std::vector<GURL> urls{file_url}; + const std::vector<std::string> mime_types{mime_type}; + const std::vector<std::string> dlp_source_urls{dlp_source_url}; + + auto intent = apps_util::MakeShareIntent(urls, mime_types, dlp_source_urls); + ASSERT_EQ(1u, intent->files.size()); + EXPECT_EQ(file_url, intent->files[0]->url); + EXPECT_EQ(dlp_source_url, intent->files[0]->dlp_source_url); + EXPECT_TRUE(intent->MatchFilter(filter)); +} + TEST_F(IntentUtilTest, TextMatch) { std::string mime_type1 = "text/plain"; std::string mime_type2 = "image/jpeg";
diff --git a/components/sync/base/features.cc b/components/sync/base/features.cc index f9eac4e..396fb04 100644 --- a/components/sync/base/features.cc +++ b/components/sync/base/features.cc
@@ -118,6 +118,10 @@ "SyncEnableContactInfoDataType", base::FEATURE_DISABLED_BY_DEFAULT); +BASE_FEATURE(kSyncEnableContactInfoDataTypeForCustomPassphraseUsers, + "kSyncEnableContactInfoDataTypeForCustomPassphraseUsers", + base::FEATURE_DISABLED_BY_DEFAULT); + BASE_FEATURE(kSyncEnforceBookmarksCountLimit, "SyncEnforceBookmarksCountLimit", base::FEATURE_DISABLED_BY_DEFAULT);
diff --git a/components/sync/base/features.h b/components/sync/base/features.h index 509c47e..ddbdc7ec 100644 --- a/components/sync/base/features.h +++ b/components/sync/base/features.h
@@ -155,6 +155,8 @@ BASE_DECLARE_FEATURE(kSyncEnableContactInfoDataType); +BASE_DECLARE_FEATURE(kSyncEnableContactInfoDataTypeForCustomPassphraseUsers); + // If enabled, issues error and disables bookmarks sync when limit is crossed. BASE_DECLARE_FEATURE(kSyncEnforceBookmarksCountLimit);
diff --git a/components/viz/service/display_embedder/image_context_impl.cc b/components/viz/service/display_embedder/image_context_impl.cc index cc077bc..ac9e782 100644 --- a/components/viz/service/display_embedder/image_context_impl.cc +++ b/components/viz/service/display_embedder/image_context_impl.cc
@@ -325,9 +325,8 @@ // we need check the texture image state, and bind the image to the // texture if necessary. auto* texture = gpu::gles2::Texture::CheckedCast(texture_base); - gpu::gles2::Texture::ImageState image_state; - auto* image = texture->GetLevelImage(GL_TEXTURE_2D, 0, &image_state); - if (image && image_state == gpu::gles2::Texture::UNBOUND) { + if (texture->HasUnboundLevelImage(GL_TEXTURE_2D, 0)) { + auto* image = texture->GetLevelImage(GL_TEXTURE_2D, 0); glBindTexture(texture_base->target(), texture_base->service_id()); if (!image->BindTexImage(texture_base->target())) { LOG(ERROR) << "Failed to bind a gl image to texture.";
diff --git a/content/browser/renderer_host/media/media_stream_manager.cc b/content/browser/renderer_host/media/media_stream_manager.cc index a0d021a..2d6ac5d 100644 --- a/content/browser/renderer_host/media/media_stream_manager.cc +++ b/content/browser/renderer_host/media/media_stream_manager.cc
@@ -3094,10 +3094,8 @@ // while the current task was hopping between threads/queues, an event will // be fired by the CaptureHandleManager. if (video_device.has_value()) { - MaybeStartTrackingCaptureHandleConfig( - label, video_device.value(), - GlobalRenderFrameHostId(request->requesting_process_id, - request->requesting_frame_id)); + MaybeStartTrackingCaptureHandleConfig(label, video_device.value(), + *request); } } @@ -3156,13 +3154,12 @@ void MediaStreamManager::FinalizeChangeDevice(const std::string& label, DeviceRequest* request) { DCHECK_CURRENTLY_ON(BrowserThread::IO); + DCHECK(request); request->FinalizeChangeDevice(label); - MaybeUpdateTrackedCaptureHandleConfigs( - label, request->stream_devices_set, - GlobalRenderFrameHostId(request->requesting_process_id, - request->requesting_frame_id)); + MaybeUpdateTrackedCaptureHandleConfigs(label, request->stream_devices_set, + *request); } void MediaStreamManager::FinalizeMediaAccessRequest( @@ -4376,7 +4373,7 @@ void MediaStreamManager::MaybeStartTrackingCaptureHandleConfig( const std::string& label, const MediaStreamDevice& captured_device, - GlobalRenderFrameHostId capturer) { + DeviceRequest& request) { DCHECK_CURRENTLY_ON(BrowserThread::IO); if (!blink::IsVideoInputMediaType(captured_device.type) || @@ -4384,10 +4381,8 @@ return; } - DeviceRequest* request = FindRequest(label); - if (!request) { - return; - } + const GlobalRenderFrameHostId capturer(request.requesting_process_id, + request.requesting_frame_id); // It is safe to bind base::Unretained(this) because MediaStreamManager is // owned by BrowserMainLoop. @@ -4399,7 +4394,7 @@ base::Unretained(&capture_handle_manager_), label, captured_device, capturer, base::BindPostTask(GetIOThreadTaskRunner({}), - request->OnCaptureHandleChangeCb()))); + request.OnCaptureHandleChangeCb()))); } void MediaStreamManager::MaybeStopTrackingCaptureHandleConfig( @@ -4424,14 +4419,12 @@ void MediaStreamManager::MaybeUpdateTrackedCaptureHandleConfigs( const std::string& label, const blink::mojom::StreamDevicesSet& new_devices_set, - GlobalRenderFrameHostId capturer) { + DeviceRequest& request) { DCHECK_CURRENTLY_ON(BrowserThread::IO); DCHECK_EQ(1u, new_devices_set.stream_devices.size()); - DeviceRequest* request = FindRequest(label); - if (!request) { - return; - } + const GlobalRenderFrameHostId capturer(request.requesting_process_id, + request.requesting_frame_id); const blink::mojom::StreamDevices& new_devices = *new_devices_set.stream_devices[0]; @@ -4455,7 +4448,7 @@ base::Unretained(&capture_handle_manager_), label, std::move(filtered_new_devices_set), capturer, base::BindPostTask(GetIOThreadTaskRunner({}), - request->OnCaptureHandleChangeCb()))); + request.OnCaptureHandleChangeCb()))); } bool MediaStreamManager::ShouldUseFakeUIProxy(
diff --git a/content/browser/renderer_host/media/media_stream_manager.h b/content/browser/renderer_host/media/media_stream_manager.h index 0aaecc2c..fc2407b 100644 --- a/content/browser/renderer_host/media/media_stream_manager.h +++ b/content/browser/renderer_host/media/media_stream_manager.h
@@ -738,7 +738,7 @@ void MaybeStartTrackingCaptureHandleConfig( const std::string& label, const blink::MediaStreamDevice& captured_device, - GlobalRenderFrameHostId capturer); + DeviceRequest& request); // Stop tracking capture-handle changes for tab-capture. void MaybeStopTrackingCaptureHandleConfig( @@ -749,7 +749,7 @@ void MaybeUpdateTrackedCaptureHandleConfigs( const std::string& label, const blink::mojom::StreamDevicesSet& new_stream_devices_set, - GlobalRenderFrameHostId capturer); + DeviceRequest& request); bool ShouldUseFakeUIProxy(blink::mojom::MediaStreamType stream_type) const;
diff --git a/content/browser/renderer_host/page_lifecycle_state_manager.cc b/content/browser/renderer_host/page_lifecycle_state_manager.cc index 612aff3..9b24c54 100644 --- a/content/browser/renderer_host/page_lifecycle_state_manager.cc +++ b/content/browser/renderer_host/page_lifecycle_state_manager.cc
@@ -15,10 +15,12 @@ #include "services/service_manager/public/cpp/interface_provider.h" namespace { -// ASAN builds are slow and we see flakes caused by reaching this timeout. +// ASAN builds are slow and we see flakes caused by reaching this timeout. 6s +// was not enough to stop the flakes. Trying 12s just to ensure that there isn't +// something else going on that we don't understand. // See https://crbug.com/1224355. #if defined(ADDRESS_SANITIZER) -constexpr base::TimeDelta kBackForwardCacheTimeout = base::Seconds(6); +constexpr base::TimeDelta kBackForwardCacheTimeout = base::Seconds(12); #else constexpr base::TimeDelta kBackForwardCacheTimeout = base::Seconds(3); #endif
diff --git a/content/browser/utility_process_host.h b/content/browser/utility_process_host.h index c0abba24..64ca6bc 100644 --- a/content/browser/utility_process_host.h +++ b/content/browser/utility_process_host.h
@@ -77,6 +77,11 @@ virtual void OnProcessCrashed() {} }; + // This class is self-owned. It must be instantiated using new, and shouldn't + // be deleted manually. + // TODO(https://crbug.com/1411101): Make it clearer the caller of the + // constructor do not own memory. A static method to create them + private + // constructor could be better. UtilityProcessHost(); explicit UtilityProcessHost(std::unique_ptr<Client> client);
diff --git a/content/browser/utility_process_host_browsertest.cc b/content/browser/utility_process_host_browsertest.cc index 96acba8c..b5d7376 100644 --- a/content/browser/utility_process_host_browsertest.cc +++ b/content/browser/utility_process_host_browsertest.cc
@@ -65,11 +65,16 @@ DCHECK_CURRENTLY_ON(BrowserThread::UI); BrowserChildProcessObserver::Add(this); - host_ = new UtilityProcessHost(); + host_ = new UtilityProcessHost(); // Owned by a global list. host_->SetName(u"TestProcess"); host_->SetMetricsName(kTestProcessName); } + void TearDownOnMainThread() override { + // `host_` is about to be deleted during BrowserMainRunnerImpl::Shutdown(). + host_ = nullptr; + } + void SetExpectFailLaunch() { DCHECK_CURRENTLY_ON(BrowserThread::UI); expect_failed_launch_ = true;
diff --git a/docs/enterprise/add_new_policy.md b/docs/enterprise/add_new_policy.md index 133d13c..5698f3c 100644 --- a/docs/enterprise/add_new_policy.md +++ b/docs/enterprise/add_new_policy.md
@@ -306,10 +306,15 @@ - Make sure the policy metadata is up-to-date, in particular `supported_on`, and the feature flags. - In general, don't change policy semantics in a way that is incompatible -(as determined by user/admin-visible behavior) with previous semantics. **In -particular, consider that existing policy deployments may affect both old and -new browser versions, and both should behave according to the admin's -intentions**. +(as determined by user/admin-visible behavior) with previous semantics. + - **In particular, consider that existing policy deployments may affect** + **both old and new browser versions, and both should behave according to** + **the admin's intentions.** + - **Do not modify the behavior when the policy is not set**. To be more + specific: values `default`, `default_for_enterprise_users` and + `default_policy_level` must (likely) never change after the launch. Contact + cros-policy-muc-eng@google.com for guidance if you need to make such + changes. - **An important pitfall is that adding an additional allowed value to an enum policy may cause compatibility issues.** Specifically, an administrator may use the new policy value, which makes older Chrome versions
diff --git a/extensions/browser/extension_function_histogram_value.h b/extensions/browser/extension_function_histogram_value.h index 749360df..cb09570 100644 --- a/extensions/browser/extension_function_histogram_value.h +++ b/extensions/browser/extension_function_histogram_value.h
@@ -1819,6 +1819,7 @@ PASSWORDSPRIVATE_GETCREDENTIALSWITHREUSEDPASSWORD = 1756, OS_TELEMETRY_GETAUDIOINFO = 1757, OS_TELEMETRY_GETMARKETINGINFO = 1758, + OS_TELEMETRY_GETUSBBUSINFO = 1759, // Last entry: Add new entries above, then run: // tools/metrics/histograms/update_extension_histograms.py ENUM_BOUNDARY
diff --git a/extensions/browser/extension_prefs.cc b/extensions/browser/extension_prefs.cc index 9724736..d9aaa7b 100644 --- a/extensions/browser/extension_prefs.cc +++ b/extensions/browser/extension_prefs.cc
@@ -2227,6 +2227,8 @@ #endif registry->RegisterBooleanPref(pref_names::kBlockExternalExtensions, false); + registry->RegisterIntegerPref(pref_names::kExtensionUnpublishedAvailability, + 0); } template <class ExtensionIdContainer>
diff --git a/extensions/browser/pref_names.cc b/extensions/browser/pref_names.cc index 59c7f809..d0b2a43 100644 --- a/extensions/browser/pref_names.cc +++ b/extensions/browser/pref_names.cc
@@ -35,6 +35,8 @@ const char kBlockExternalExtensions[] = "extensions.block_external_extensions"; const char kExtensions[] = "extensions.settings"; const char kExtensionManagement[] = "extensions.management"; +const char kExtensionUnpublishedAvailability[] = + "extensions.unpublished_availability"; const char kInstallAllowList[] = "extensions.install.allowlist"; const char kInstallDenyList[] = "extensions.install.denylist"; const char kInstallForceList[] = "extensions.install.forcelist";
diff --git a/extensions/browser/pref_names.h b/extensions/browser/pref_names.h index 2ea0bec..7aa07123 100644 --- a/extensions/browser/pref_names.h +++ b/extensions/browser/pref_names.h
@@ -52,6 +52,11 @@ // policy. extern const char kExtensionManagement[]; +// An integer that indicates the availability of extensions that are unpublished +// on the Chrome Web Store. More details can be found at +// ExtensionUnpublishedAvailability.yaml +extern const char kExtensionUnpublishedAvailability[]; + // Policy that indicates whether CRX2 extension updates are allowed. extern const char kInsecureExtensionUpdatesEnabled[];
diff --git a/gpu/command_buffer/service/gles2_cmd_decoder.cc b/gpu/command_buffer/service/gles2_cmd_decoder.cc index 07d6a4d..21e8ae0 100644 --- a/gpu/command_buffer/service/gles2_cmd_decoder.cc +++ b/gpu/command_buffer/service/gles2_cmd_decoder.cc
@@ -10494,9 +10494,8 @@ GLuint texture_unit) { // Image is already in use if texture is attached to a framebuffer. if (texture && !texture->IsAttachedToFramebuffer()) { - Texture::ImageState old_image_state; - gl::GLImage* image = texture->GetLevelImage(textarget, 0, &old_image_state); - if (image && old_image_state == Texture::UNBOUND) { + if (texture->HasUnboundLevelImage(textarget, 0)) { + gl::GLImage* image = texture->GetLevelImage(textarget, 0); UMA_HISTOGRAM_BOOLEAN( "GPU.GLES2DecoderImplLazyBindingCheck.WasBindNecessary", true); @@ -10507,7 +10506,7 @@ api()->glBindTextureFn(textarget, texture->service_id()); bool rv = image->BindTexImage(textarget); DCHECK(rv) << "BindTexImage() failed"; - texture->SetLevelImageState(textarget, 0, Texture::BOUND); + texture->MarkLevelImageBound(textarget, 0); if (!texture_unit) { RestoreCurrentTextureBindings(&state_, textarget, state_.active_texture_unit);
diff --git a/gpu/command_buffer/service/gles2_cmd_decoder_unittest.cc b/gpu/command_buffer/service/gles2_cmd_decoder_unittest.cc index 2cbce8e..ca6a302 100644 --- a/gpu/command_buffer/service/gles2_cmd_decoder_unittest.cc +++ b/gpu/command_buffer/service/gles2_cmd_decoder_unittest.cc
@@ -282,14 +282,12 @@ auto* validating_texture = static_cast<ValidatingAbstractTextureImpl*>(abstract_texture.get()); TextureRef* texture_ref = validating_texture->GetTextureRefForTesting(); - Texture::ImageState state; - EXPECT_EQ(texture_ref->texture()->GetLevelImage(target, 0, &state), - image.get()); + EXPECT_EQ(texture_ref->texture()->GetLevelImage(target, 0), image.get()); #if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) - EXPECT_EQ(state, Texture::ImageState::UNBOUND); + EXPECT_TRUE(texture_ref->texture()->HasUnboundLevelImage(target, 0)); #else - EXPECT_EQ(state, Texture::ImageState::BOUND); + EXPECT_FALSE(texture_ref->texture()->HasUnboundLevelImage(target, 0)); #endif EXPECT_CALL(*gl_, DeleteTextures(1, _)).Times(1).RetiresOnSaturation();
diff --git a/gpu/command_buffer/service/shared_image/wrapped_sk_image_backing_factory_unittest.cc b/gpu/command_buffer/service/shared_image/wrapped_sk_image_backing_factory_unittest.cc index ab1fb6e..b3015feb 100644 --- a/gpu/command_buffer/service/shared_image/wrapped_sk_image_backing_factory_unittest.cc +++ b/gpu/command_buffer/service/shared_image/wrapped_sk_image_backing_factory_unittest.cc
@@ -5,6 +5,7 @@ #include "gpu/command_buffer/service/shared_image/wrapped_sk_image_backing_factory.h" #include "base/functional/callback_helpers.h" +#include "build/build_config.h" #include "cc/test/pixel_comparator.h" #include "cc/test/pixel_test_utils.h" #include "components/viz/common/resources/resource_format_utils.h" @@ -59,11 +60,19 @@ return pixmaps; } -class WrappedSkImageBackingFactoryTest +// WrappedSkImageBackingFactoryTest is failing on Android emulator bots: +// https://crbug.com/1411266 +#if BUILDFLAG(IS_ANDROID) && defined(ARCH_CPU_X86_FAMILY) +#define MAYBE_WrappedSkImageBackingFactoryTest \ + DISABLED_WrappedSkImageBackingFactoryTest +#else +#define MAYBE_WrappedSkImageBackingFactoryTest WrappedSkImageBackingFactoryTest +#endif // BUILDFLAG(IS_ANDROID) && defined(ARCH_CPU_X86_FAMILY) +class MAYBE_WrappedSkImageBackingFactoryTest : public testing::TestWithParam<viz::SharedImageFormat> { public: - WrappedSkImageBackingFactoryTest() = default; - ~WrappedSkImageBackingFactoryTest() override { + MAYBE_WrappedSkImageBackingFactoryTest() = default; + ~MAYBE_WrappedSkImageBackingFactoryTest() override { // |context_state_| must be destroyed while current. context_state_->MakeCurrent(surface_.get(), /*needs_gl=*/true); } @@ -111,7 +120,7 @@ }; // Verify creation and Skia access works as expected. -TEST_P(WrappedSkImageBackingFactoryTest, Basic) { +TEST_P(MAYBE_WrappedSkImageBackingFactoryTest, Basic) { auto format = GetFormat(); auto mailbox = Mailbox::GenerateForSharedImage(); gfx::Size size(100, 100); @@ -172,7 +181,7 @@ } // Verify that pixel upload works as expected. -TEST_P(WrappedSkImageBackingFactoryTest, Upload) { +TEST_P(MAYBE_WrappedSkImageBackingFactoryTest, Upload) { auto format = GetFormat(); auto mailbox = Mailbox::GenerateForSharedImage(); gfx::Size size(100, 100); @@ -254,7 +263,7 @@ viz::MultiPlaneFormat::kYVU_420); INSTANTIATE_TEST_SUITE_P(, - WrappedSkImageBackingFactoryTest, + MAYBE_WrappedSkImageBackingFactoryTest, kFormats, TestParamToString);
diff --git a/gpu/command_buffer/service/texture_manager.cc b/gpu/command_buffer/service/texture_manager.cc index f54f4764..75d86b9b 100644 --- a/gpu/command_buffer/service/texture_manager.cc +++ b/gpu/command_buffer/service/texture_manager.cc
@@ -735,19 +735,7 @@ } } -Texture::LevelInfo::LevelInfo() - : target(0), - level(-1), - internal_format(0), - width(0), - height(0), - depth(0), - border(0), - format(0), - type(0), - image_state(UNBOUND), - estimated_size(0), - internal_workaround(false) {} +Texture::LevelInfo::LevelInfo() = default; Texture::LevelInfo::LevelInfo(const LevelInfo& rhs) : cleared_rect(rhs.cleared_rect), @@ -761,9 +749,9 @@ format(rhs.format), type(rhs.type), image(rhs.image), - image_state(rhs.image_state), estimated_size(rhs.estimated_size), - internal_workaround(rhs.internal_workaround) {} + internal_workaround(rhs.internal_workaround), + image_state(rhs.image_state) {} Texture::LevelInfo::~LevelInfo() = default; @@ -1924,7 +1912,7 @@ } #endif -void Texture::SetLevelImageState(GLenum target, GLint level, ImageState state) { +void Texture::MarkLevelImageBound(GLenum target, GLint level) { DCHECK_GE(level, 0); size_t face_index = GLES2Util::GLTargetToFaceIndex(target); DCHECK_LT(face_index, face_infos_.size()); @@ -1933,7 +1921,7 @@ Texture::LevelInfo& info = face_infos_[face_index].level_infos[level]; DCHECK_EQ(info.target, target); DCHECK_EQ(info.level, level); - info.image_state = state; + info.image_state = ImageState::BOUND; } const Texture::LevelInfo* Texture::GetLevelInfo(GLint target, @@ -1953,20 +1941,25 @@ return nullptr; } -gl::GLImage* Texture::GetLevelImage(GLint target, - GLint level, - ImageState* state) const { +gl::GLImage* Texture::GetLevelImage(GLint target, GLint level) const { const LevelInfo* info = GetLevelInfo(target, level); if (!info) return nullptr; - if (state) - *state = info->image_state; return info->image.get(); } -gl::GLImage* Texture::GetLevelImage(GLint target, GLint level) const { - return GetLevelImage(target, level, nullptr); +bool Texture::HasUnboundLevelImage(GLint target, GLint level) const { + const LevelInfo* info = GetLevelInfo(target, level); + if (!info) { + return false; + } + + if (!info->image.get()) { + return false; + } + + return info->image_state == ImageState::UNBOUND; } void Texture::DumpLevelMemory(base::trace_event::ProcessMemoryDump* pmd,
diff --git a/gpu/command_buffer/service/texture_manager.h b/gpu/command_buffer/service/texture_manager.h index c7c7b6a2..4557611 100644 --- a/gpu/command_buffer/service/texture_manager.h +++ b/gpu/command_buffer/service/texture_manager.h
@@ -191,9 +191,15 @@ GLenum format = 0; GLenum type = 0; scoped_refptr<gl::GLImage> image; - ImageState image_state = UNBOUND; uint32_t estimated_size = 0; bool internal_workaround = false; + + private: + friend class Texture; + + // Nothing outside of Texture should directly access the binding state of + // the image; clients can use Texture::HasUnboundLevelImage(). + ImageState image_state = UNBOUND; }; explicit Texture(GLuint service_id); @@ -332,8 +338,8 @@ void BindToServiceId(GLuint service_id); #endif - // Set the ImageState for the image bound to the given level. - void SetLevelImageState(GLenum target, GLint level, ImageState state); + // Marks the image for the given level as bound. + void MarkLevelImageBound(GLenum target, GLint level); bool CompatibleWithSamplerUniformType( GLenum type, @@ -341,11 +347,12 @@ // Get the image associated with a particular level. Returns NULL if level // does not exist. - gl::GLImage* GetLevelImage(GLint target, - GLint level, - ImageState* state) const; gl::GLImage* GetLevelImage(GLint target, GLint level) const; + // Returns true iff (a) there is an image associated with the particular + // level, and (b) the image is unbound. + bool HasUnboundLevelImage(GLint target, GLint level) const; + bool HasImages() const { return has_images_; }
diff --git a/gpu/command_buffer/service/texture_manager_unittest.cc b/gpu/command_buffer/service/texture_manager_unittest.cc index b5caa32..ba7700a 100644 --- a/gpu/command_buffer/service/texture_manager_unittest.cc +++ b/gpu/command_buffer/service/texture_manager_unittest.cc
@@ -1691,25 +1691,25 @@ EXPECT_TRUE(texture->GetLevelImage(GL_TEXTURE_2D, 1) == nullptr); } -TEST_F(TextureTest, SetLevelImageState) { +#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) +TEST_F(TextureTest, MarkLevelImageBound) { manager_->SetTarget(texture_ref_.get(), GL_TEXTURE_2D); manager_->SetLevelInfo(texture_ref_.get(), GL_TEXTURE_2D, 0, GL_RGBA, 2, 2, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, gfx::Rect(2, 2)); Texture* texture = texture_ref_->texture(); - // Set image, initially BOUND. + // Set image, initially unbound. scoped_refptr<gl::GLImage> image(new gl::GLImageStub); - manager_->SetBoundLevelImage(texture_ref_.get(), GL_TEXTURE_2D, 0, - image.get()); - Texture::ImageState state; - texture->GetLevelImage(GL_TEXTURE_2D, 0, &state); - EXPECT_EQ(state, Texture::BOUND); - // Change the state. - texture->SetLevelImageState(GL_TEXTURE_2D, 0, Texture::COPIED); - texture->GetLevelImage(GL_TEXTURE_2D, 0, &state); - EXPECT_EQ(state, Texture::COPIED); + manager_->SetUnboundLevelImage(texture_ref_.get(), GL_TEXTURE_2D, 0, + image.get()); + EXPECT_TRUE(texture->HasUnboundLevelImage(GL_TEXTURE_2D, 0)); + // Mark the image as bound and verify that the state updates. + texture->MarkLevelImageBound(GL_TEXTURE_2D, 0); + EXPECT_FALSE(texture->HasUnboundLevelImage(GL_TEXTURE_2D, 0)); } #endif +#endif + #if BUILDFLAG(IS_ANDROID) TEST_F(TextureTest, SetStreamTextureImageServiceID) { manager_->SetTarget(texture_ref_.get(), GL_TEXTURE_EXTERNAL_OES);
diff --git a/gpu/command_buffer/service/validating_abstract_texture_impl.cc b/gpu/command_buffer/service/validating_abstract_texture_impl.cc index 62f8f17..4fd32a2 100644 --- a/gpu/command_buffer/service/validating_abstract_texture_impl.cc +++ b/gpu/command_buffer/service/validating_abstract_texture_impl.cc
@@ -99,7 +99,7 @@ const GLuint target = texture_ref_->texture()->target(); const GLint level = 0; - return texture_ref_->texture()->GetLevelImage(target, level, nullptr); + return texture_ref_->texture()->GetLevelImage(target, level); } void ValidatingAbstractTextureImpl::SetCleared() {
diff --git a/ios/chrome/browser/ui/autofill/chrome_autofill_client_ios.h b/ios/chrome/browser/ui/autofill/chrome_autofill_client_ios.h index 2ae29a1d..56451f5b 100644 --- a/ios/chrome/browser/ui/autofill/chrome_autofill_client_ios.h +++ b/ios/chrome/browser/ui/autofill/chrome_autofill_client_ios.h
@@ -108,9 +108,10 @@ AddressProfileSavePromptCallback callback) override; bool HasCreditCardScanFeature() override; void ScanCreditCard(CreditCardScanCallback callback) override; - bool TryToShowFastCheckout(const FormData& form, - const FormFieldData& field, - AutofillDriver* driver) override; + bool TryToShowFastCheckout( + const FormData& form, + const FormFieldData& field, + base::WeakPtr<AutofillManager> autofill_manager) override; void HideFastCheckout(bool allow_further_runs) override; bool IsFastCheckoutSupported() override; bool IsShowingFastCheckoutUI() override;
diff --git a/ios/chrome/browser/ui/autofill/chrome_autofill_client_ios.mm b/ios/chrome/browser/ui/autofill/chrome_autofill_client_ios.mm index e6bbdff..cf6c1f4 100644 --- a/ios/chrome/browser/ui/autofill/chrome_autofill_client_ios.mm +++ b/ios/chrome/browser/ui/autofill/chrome_autofill_client_ios.mm
@@ -391,9 +391,10 @@ return false; } -bool ChromeAutofillClientIOS::TryToShowFastCheckout(const FormData& form, - const FormFieldData& field, - AutofillDriver* driver) { +bool ChromeAutofillClientIOS::TryToShowFastCheckout( + const FormData& form, + const FormFieldData& field, + base::WeakPtr<AutofillManager> autofill_manager) { return false; }
diff --git a/ios/chrome/browser/ui/bookmarks/bookmarks_coordinator.mm b/ios/chrome/browser/ui/bookmarks/bookmarks_coordinator.mm index 15f8b918..bdd3f3ff 100644 --- a/ios/chrome/browser/ui/bookmarks/bookmarks_coordinator.mm +++ b/ios/chrome/browser/ui/bookmarks/bookmarks_coordinator.mm
@@ -465,11 +465,6 @@ #pragma mark - BookmarksEditorViewControllerDelegate -- (BOOL)bookmarkEditor:(BookmarksEditorViewController*)controller - shoudDeleteAllOccurencesOfBookmark:(const BookmarkNode*)bookmark { - return YES; -} - - (void)bookmarkEditorWantsDismissal: (BookmarksEditorViewController*)controller { [self dismissBookmarkEditorAnimated:YES];
diff --git a/ios/chrome/browser/ui/bookmarks/editor/bookmarks_editor_view_controller.h b/ios/chrome/browser/ui/bookmarks/editor/bookmarks_editor_view_controller.h index ae746fa..46cad06 100644 --- a/ios/chrome/browser/ui/bookmarks/editor/bookmarks_editor_view_controller.h +++ b/ios/chrome/browser/ui/bookmarks/editor/bookmarks_editor_view_controller.h
@@ -19,15 +19,6 @@ @protocol BookmarksEditorViewControllerDelegate -// Called when the edited bookmark is set for deletion. -// If the delegate returns `YES`, all nodes matching the URL of `bookmark` will -// be deleted. -// If the delegate returns `NO`, only `bookmark` will be deleted. -// If the delegate doesn't implement this method, the default behavior is to -// delete all nodes matching the URL of `bookmark`. -- (BOOL)bookmarkEditor:(BookmarksEditorViewController*)controller - shoudDeleteAllOccurencesOfBookmark:(const bookmarks::BookmarkNode*)bookmark; - // Called when the controller should be dismissed. - (void)bookmarkEditorWantsDismissal:(BookmarksEditorViewController*)controller;
diff --git a/ios/chrome/browser/ui/bookmarks/editor/bookmarks_editor_view_controller.mm b/ios/chrome/browser/ui/bookmarks/editor/bookmarks_editor_view_controller.mm index 547c2bae..b471a1d 100644 --- a/ios/chrome/browser/ui/bookmarks/editor/bookmarks_editor_view_controller.mm +++ b/ios/chrome/browser/ui/bookmarks/editor/bookmarks_editor_view_controller.mm
@@ -420,21 +420,11 @@ // ignore bookmark model updates notifications. base::AutoReset<BOOL> autoReset(&_ignoresBookmarkModelChanges, YES); - std::set<const BookmarkNode*> nodes; - if ([self.delegate bookmarkEditor:self - shoudDeleteAllOccurencesOfBookmark:self.bookmark]) { - // When launched from the star button, removing the current bookmark - // removes all matching nodes. - std::vector<const BookmarkNode*> nodesVector; - self.bookmarkModel->GetNodesByURL(self.bookmark->url(), &nodesVector); - for (const BookmarkNode* node : nodesVector) { - nodes.insert(node); - } - } else { - // When launched from the info button, removing the current bookmark only - // removes the current node. - nodes.insert(self.bookmark); - } + // When launched from the star button, removing the current bookmark + // removes all matching nodes. + std::vector<const BookmarkNode*> nodesVector; + self.bookmarkModel->GetNodesByURL(self.bookmark->url(), &nodesVector); + std::set<const BookmarkNode*> nodes(nodesVector.begin(), nodesVector.end()); [self.snackbarCommandsHandler showSnackbarMessage:bookmark_utils_ios::DeleteBookmarksWithUndoToast(
diff --git a/ios/chrome/browser/ui/browser_view/browser_coordinator.mm b/ios/chrome/browser/ui/browser_view/browser_coordinator.mm index 6bd4508..3af17838 100644 --- a/ios/chrome/browser/ui/browser_view/browser_coordinator.mm +++ b/ios/chrome/browser/ui/browser_view/browser_coordinator.mm
@@ -831,6 +831,7 @@ _primaryToolbarCoordinator.popupPresenterDelegate = self.viewController; [_primaryToolbarCoordinator start]; + _legacyTabStripCoordinator.baseViewController = self.viewController; _NTPCoordinator.baseViewController = self.viewController; [_dispatcher startDispatchingToTarget:self.viewController
diff --git a/ios/chrome/browser/ui/menu/menu_histograms.h b/ios/chrome/browser/ui/menu/menu_histograms.h index 5dabbc1..a00a91c 100644 --- a/ios/chrome/browser/ui/menu/menu_histograms.h +++ b/ios/chrome/browser/ui/menu/menu_histograms.h
@@ -27,7 +27,8 @@ kThumbStrip = 15, kOmniboxMostVisitedEntry = 16, kPinnedTabsEntry = 17, - kMaxValue = kPinnedTabsEntry, + kTabStripEntry = 18, + kMaxValue = kTabStripEntry, }; // Records a menu shown histogram metric for the `scenario`.
diff --git a/ios/chrome/browser/ui/menu/menu_histograms.mm b/ios/chrome/browser/ui/menu/menu_histograms.mm index 74ba1d61..a1d67a8 100644 --- a/ios/chrome/browser/ui/menu/menu_histograms.mm +++ b/ios/chrome/browser/ui/menu/menu_histograms.mm
@@ -48,6 +48,8 @@ "Mobile.ContextMenu.OmniboxMostVisitedEntry.Actions"; const char kPinnedTabsEntryActionsHistogram[] = "Mobile.ContextMenu.PinnedTabsEntry.Actions"; +const char kTabStripEntryActionsHistogram[] = + "Mobile.ContextMenu.TabStrip.Actions"; } // namespace void RecordMenuShown(MenuScenarioHistogram scenario) { @@ -91,5 +93,7 @@ return kOmniboxMostVisitedEntryActionsHistogram; case MenuScenarioHistogram::kPinnedTabsEntry: return kPinnedTabsEntryActionsHistogram; + case MenuScenarioHistogram::kTabStripEntry: + return kTabStripEntryActionsHistogram; } }
diff --git a/ios/chrome/browser/ui/omnibox/omnibox_constants.h b/ios/chrome/browser/ui/omnibox/omnibox_constants.h index a5aed66..f73c145 100644 --- a/ios/chrome/browser/ui/omnibox/omnibox_constants.h +++ b/ios/chrome/browser/ui/omnibox/omnibox_constants.h
@@ -9,4 +9,11 @@ extern const CGFloat kOmniboxPlaceholderAlpha; +extern NSString* const kOmniboxLeadingImageDefaultAccessibilityIdentifier; + +extern NSString* const kOmniboxLeadingImageEmptyTextAccessibilityIdentifier; + +extern NSString* const + kOmniboxLeadingImageSuggestionImageAccessibilityIdentifier; + #endif // IOS_CHROME_BROWSER_UI_OMNIBOX_OMNIBOX_CONSTANTS_H_
diff --git a/ios/chrome/browser/ui/omnibox/omnibox_constants.mm b/ios/chrome/browser/ui/omnibox/omnibox_constants.mm index b686238..8ba7137 100644 --- a/ios/chrome/browser/ui/omnibox/omnibox_constants.mm +++ b/ios/chrome/browser/ui/omnibox/omnibox_constants.mm
@@ -9,3 +9,12 @@ #endif const CGFloat kOmniboxPlaceholderAlpha = 0.3; + +NSString* const kOmniboxLeadingImageDefaultAccessibilityIdentifier = + @"OmniboxLeadingImageDefaultAccessibilityIdentifier"; + +NSString* const kOmniboxLeadingImageEmptyTextAccessibilityIdentifier = + @"OmniboxLeadingImageEmptyTextAccessibilityIdentifier"; + +NSString* const kOmniboxLeadingImageSuggestionImageAccessibilityIdentifier = + @"OmniboxLeadingImageSuggestionImageAccessibilityIdentifier";
diff --git a/ios/chrome/browser/ui/omnibox/omnibox_consumer.h b/ios/chrome/browser/ui/omnibox/omnibox_consumer.h index a782b2d..c3a8ff2 100644 --- a/ios/chrome/browser/ui/omnibox/omnibox_consumer.h +++ b/ios/chrome/browser/ui/omnibox/omnibox_consumer.h
@@ -11,7 +11,12 @@ // Notifies the consumer to update the autocomplete icon for the currently // highlighted autocomplete result. -- (void)updateAutocompleteIcon:(UIImage*)icon; +- (void)updateAutoCompleteIconWithoutAccessibilityIdentifier:(UIImage*)icon; + +// Notifies the consumer to update the autocomplete icon for the currently +// highlighted autocomplete result with given accessibility identifier. +- (void)updateAutocompleteIcon:(UIImage*)icon + withAccessibilityIdentifier:(NSString*)accessibilityIdentifier; // Notifies the consumer to update after the search-by-image support status // changes. (This is usually when the default search engine changes).
diff --git a/ios/chrome/browser/ui/omnibox/omnibox_container_view.h b/ios/chrome/browser/ui/omnibox/omnibox_container_view.h index 752eaef..d914247 100644 --- a/ios/chrome/browser/ui/omnibox/omnibox_container_view.h +++ b/ios/chrome/browser/ui/omnibox/omnibox_container_view.h
@@ -37,8 +37,12 @@ // The layout guide center to use to refer to the omnibox leading image. @property(nonatomic, strong) LayoutGuideCenter* layoutGuideCenter; +// Sets the leading button's image and sets its accessibility identifier. +- (void)setLeadingImage:(UIImage*)image + withAccessibilityIdentifier:(NSString*)accessibilityIdentifier; + // Sets the leading button's image. -- (void)setLeadingImage:(UIImage*)image; +- (void)setLeadingImageWithoutAccessibilityIdentifier:(UIImage*)image; // Sets the alpha level of the leading image view. - (void)setLeadingImageAlpha:(CGFloat)alpha;
diff --git a/ios/chrome/browser/ui/omnibox/omnibox_container_view.mm b/ios/chrome/browser/ui/omnibox/omnibox_container_view.mm index d0e2ebfb..17cbfcb 100644 --- a/ios/chrome/browser/ui/omnibox/omnibox_container_view.mm +++ b/ios/chrome/browser/ui/omnibox/omnibox_container_view.mm
@@ -102,7 +102,17 @@ return self; } -- (void)setLeadingImage:(UIImage*)image { +- (void)setLeadingImage:(UIImage*)image + withAccessibilityIdentifier:(NSString*)accessibilityIdentifier { + DCHECK(base::FeatureList::IsEnabled( + kEnableAccessibilityIdentifierToOmniboxLeadingImage)); + [self.leadingImageView setImage:image]; + [self.leadingImageView setAccessibilityIdentifier:accessibilityIdentifier]; +} + +- (void)setLeadingImageWithoutAccessibilityIdentifier:(UIImage*)image { + DCHECK(!base::FeatureList::IsEnabled( + kEnableAccessibilityIdentifierToOmniboxLeadingImage)); [self.leadingImageView setImage:image]; }
diff --git a/ios/chrome/browser/ui/omnibox/omnibox_mediator.mm b/ios/chrome/browser/ui/omnibox/omnibox_mediator.mm index b7961b9..37fc1366 100644 --- a/ios/chrome/browser/ui/omnibox/omnibox_mediator.mm +++ b/ios/chrome/browser/ui/omnibox/omnibox_mediator.mm
@@ -23,6 +23,7 @@ #import "ios/chrome/browser/ui/lens/lens_entrypoint.h" #import "ios/chrome/browser/ui/main/default_browser_scene_agent.h" #import "ios/chrome/browser/ui/main/scene_state_browser_agent.h" +#import "ios/chrome/browser/ui/omnibox/omnibox_constants.h" #import "ios/chrome/browser/ui/omnibox/omnibox_consumer.h" #import "ios/chrome/browser/ui/omnibox/omnibox_suggestion_icon_util.h" #import "ios/chrome/browser/ui/omnibox/omnibox_util.h" @@ -152,38 +153,89 @@ } // Set the suggestion image, or load it if necessary. - [self.consumer updateAutocompleteIcon:suggestion.matchTypeIcon]; + if (base::FeatureList::IsEnabled( + kEnableAccessibilityIdentifierToOmniboxLeadingImage)) { + [self.consumer updateAutocompleteIcon:suggestion.matchTypeIcon + withAccessibilityIdentifier: + kOmniboxLeadingImageSuggestionImageAccessibilityIdentifier]; + } else { + [self.consumer updateAutoCompleteIconWithoutAccessibilityIdentifier: + suggestion.matchTypeIcon]; + } + __weak OmniboxMediator* weakSelf = self; if ([suggestion isMatchTypeSearch]) { // Show Default Search Engine favicon. [self loadDefaultSearchEngineFaviconWithCompletion:^(UIImage* image) { - [weakSelf.consumer updateAutocompleteIcon:image]; + if (base::FeatureList::IsEnabled( + kEnableAccessibilityIdentifierToOmniboxLeadingImage)) { + [weakSelf.consumer updateAutocompleteIcon:image + withAccessibilityIdentifier: + kOmniboxLeadingImageDefaultAccessibilityIdentifier]; + } else { + [weakSelf.consumer + updateAutoCompleteIconWithoutAccessibilityIdentifier:image]; + } }]; } else if (suggestion.destinationUrl.gurl.is_valid()) { // Show url favicon when it's valid. - [self loadFaviconByPageURL:suggestion.destinationUrl.gurl - completion:^(UIImage* image) { - [weakSelf.consumer updateAutocompleteIcon:image]; - }]; + [self + loadFaviconByPageURL:suggestion.destinationUrl.gurl + completion:^(UIImage* image) { + if (base::FeatureList::IsEnabled( + kEnableAccessibilityIdentifierToOmniboxLeadingImage)) { + NSString* webPageUrl = base::SysUTF8ToNSString( + suggestion.destinationUrl.gurl.spec()); + [weakSelf.consumer updateAutocompleteIcon:image + withAccessibilityIdentifier:webPageUrl]; + } else { + [weakSelf.consumer + updateAutoCompleteIconWithoutAccessibilityIdentifier: + image]; + } + }]; } else if (isFirstUpdate) { // When no suggestion is highlighted (aka. isFirstUpdate) show the default // browser icon. [self setDefaultLeftImage]; } else { // When a suggestion is highlighted, show the same icon as in the popup. - [self.consumer updateAutocompleteIcon:suggestion.matchTypeIcon]; + if (base::FeatureList::IsEnabled( + kEnableAccessibilityIdentifierToOmniboxLeadingImage)) { + [self.consumer updateAutocompleteIcon:suggestion.matchTypeIcon + withAccessibilityIdentifier: + suggestion.matchTypeIconAccessibilityIdentifier]; + } else { + [weakSelf.consumer updateAutoCompleteIconWithoutAccessibilityIdentifier: + suggestion.matchTypeIcon]; + } } } - (void)setDefaultLeftImage { UIImage* image = GetOmniboxSuggestionIconForAutocompleteMatchType( AutocompleteMatchType::SEARCH_WHAT_YOU_TYPED, /* is_starred */ false); - [self.consumer updateAutocompleteIcon:image]; + if (base::FeatureList::IsEnabled( + kEnableAccessibilityIdentifierToOmniboxLeadingImage)) { + [self.consumer updateAutocompleteIcon:image + withAccessibilityIdentifier: + kOmniboxLeadingImageDefaultAccessibilityIdentifier]; + } else { + [self.consumer updateAutoCompleteIconWithoutAccessibilityIdentifier:image]; + } __weak OmniboxMediator* weakSelf = self; // Show Default Search Engine favicon. [self loadDefaultSearchEngineFaviconWithCompletion:^(UIImage* icon) { - [weakSelf.consumer updateAutocompleteIcon:icon]; + if (base::FeatureList::IsEnabled( + kEnableAccessibilityIdentifierToOmniboxLeadingImage)) { + [weakSelf.consumer updateAutocompleteIcon:icon + withAccessibilityIdentifier: + kOmniboxLeadingImageDefaultAccessibilityIdentifier]; + } else { + [weakSelf.consumer + updateAutoCompleteIconWithoutAccessibilityIdentifier:icon]; + } }]; }
diff --git a/ios/chrome/browser/ui/omnibox/omnibox_view_controller.mm b/ios/chrome/browser/ui/omnibox/omnibox_view_controller.mm index 29436b6..f24e457d 100644 --- a/ios/chrome/browser/ui/omnibox/omnibox_view_controller.mm +++ b/ios/chrome/browser/ui/omnibox/omnibox_view_controller.mm
@@ -256,9 +256,18 @@ } - (void)textFieldDidChange:(id)sender { + BOOL accessibilityIdentifierFlagEnabled = base::FeatureList::IsEnabled( + kEnableAccessibilityIdentifierToOmniboxLeadingImage); // If the text is empty, update the leading image. if (self.textField.text.length == 0) { - [self.view setLeadingImage:self.emptyTextLeadingImage]; + if (accessibilityIdentifierFlagEnabled) { + [self.view setLeadingImage:self.emptyTextLeadingImage + withAccessibilityIdentifier: + kOmniboxLeadingImageEmptyTextAccessibilityIdentifier]; + } else { + [self.view setLeadingImageWithoutAccessibilityIdentifier: + self.emptyTextLeadingImage]; + } } [self updateClearButtonVisibility]; @@ -303,9 +312,21 @@ // Update the clear button state. [self updateClearButtonVisibility]; - [self.view setLeadingImage:self.textField.text.length - ? self.defaultLeadingImage - : self.emptyTextLeadingImage]; + UIImage* image = self.textField.text.length ? self.defaultLeadingImage + : self.emptyTextLeadingImage; + + NSString* accessibilityID = + self.textField.text.length + ? kOmniboxLeadingImageDefaultAccessibilityIdentifier + : kOmniboxLeadingImageEmptyTextAccessibilityIdentifier; + + if (base::FeatureList::IsEnabled( + kEnableAccessibilityIdentifierToOmniboxLeadingImage)) { + [self.view setLeadingImage:image + withAccessibilityIdentifier:accessibilityID]; + } else { + [self.view setLeadingImageWithoutAccessibilityIdentifier:image]; + } self.semanticContentAttribute = [self.textField bestSemanticContentAttribute]; self.isTextfieldEditing = YES; @@ -420,10 +441,18 @@ #pragma mark - OmniboxConsumer -- (void)updateAutocompleteIcon:(UIImage*)icon { - [self.view setLeadingImage:icon]; +- (void)updateAutoCompleteIconWithoutAccessibilityIdentifier:(UIImage*)icon { + DCHECK(!base::FeatureList::IsEnabled( + kEnableAccessibilityIdentifierToOmniboxLeadingImage)); + [self.view setLeadingImageWithoutAccessibilityIdentifier:icon]; } - +- (void)updateAutocompleteIcon:(UIImage*)icon + withAccessibilityIdentifier:(NSString*)accessibilityIdentifier { + DCHECK(base::FeatureList::IsEnabled( + kEnableAccessibilityIdentifierToOmniboxLeadingImage)); + [self.view setLeadingImage:icon + withAccessibilityIdentifier:accessibilityIdentifier]; +} - (void)updateSearchByImageSupported:(BOOL)searchByImageSupported { self.searchByImageEnabled = searchByImageSupported; }
diff --git a/ios/chrome/browser/ui/omnibox/popup/autocomplete_match_formatter.mm b/ios/chrome/browser/ui/omnibox/popup/autocomplete_match_formatter.mm index 4a8804e..1e1f9b6 100644 --- a/ios/chrome/browser/ui/omnibox/popup/autocomplete_match_formatter.mm +++ b/ios/chrome/browser/ui/omnibox/popup/autocomplete_match_formatter.mm
@@ -247,6 +247,10 @@ _match.type, /* is_starred */ false); } +- (NSString*)matchTypeIconAccessibilityIdentifier { + return base::SysUTF8ToNSString(AutocompleteMatchType::ToString(_match.type)); +} + - (BOOL)isMatchTypeSearch { return AutocompleteMatch::IsSearchType(_match.type); }
diff --git a/ios/chrome/browser/ui/omnibox/popup/autocomplete_suggestion.h b/ios/chrome/browser/ui/omnibox/popup/autocomplete_suggestion.h index dc84cb2..5d95f72a 100644 --- a/ios/chrome/browser/ui/omnibox/popup/autocomplete_suggestion.h +++ b/ios/chrome/browser/ui/omnibox/popup/autocomplete_suggestion.h
@@ -57,6 +57,9 @@ /// History, Search, or Stock. /// Ignores `starred` status of the suggestion. @property(nonatomic, readonly) UIImage* matchTypeIcon; +/// Accessibility identifier of the icon corresponding to the suggestion's +/// autocomplete match type. +@property(nonatomic, readonly) NSString* matchTypeIconAccessibilityIdentifier; /// Whether this is a search suggestion (as opposed to URL suggestion) @property(nonatomic, readonly, getter=isMatchTypeSearch) BOOL matchTypeSearch; /// For URL suggestions, the URL that the match represents.
diff --git a/ios/chrome/browser/ui/omnibox/popup/omnibox_popup_row_cell_unittest.mm b/ios/chrome/browser/ui/omnibox/popup/omnibox_popup_row_cell_unittest.mm index 6edcc18c..020d6fd 100644 --- a/ios/chrome/browser/ui/omnibox/popup/omnibox_popup_row_cell_unittest.mm +++ b/ios/chrome/browser/ui/omnibox/popup/omnibox_popup_row_cell_unittest.mm
@@ -30,6 +30,7 @@ @property(nonatomic, weak) id<OmniboxPedal, OmniboxIcon> pedal; @property(nonatomic, strong) NSAttributedString* omniboxPreviewText; @property(nonatomic, strong) UIImage* matchTypeIcon; +@property(nonatomic, strong) NSString* matchTypeIconAccessibilityIdentifier; @property(nonatomic, getter=isMatchTypeSearch) BOOL matchTypeSearch; @property(nonatomic, strong) CrURL* destinationUrl; @property(nonatomic, strong) NSNumber* suggestionGroupId;
diff --git a/ios/chrome/browser/ui/omnibox/popup/pedal_suggestion_wrapper.mm b/ios/chrome/browser/ui/omnibox/popup/pedal_suggestion_wrapper.mm index bce6c019..ac20abf 100644 --- a/ios/chrome/browser/ui/omnibox/popup/pedal_suggestion_wrapper.mm +++ b/ios/chrome/browser/ui/omnibox/popup/pedal_suggestion_wrapper.mm
@@ -3,6 +3,7 @@ // found in the LICENSE file. #import "ios/chrome/browser/ui/omnibox/popup/pedal_suggestion_wrapper.h" +#import "base/notreached.h" #import "ios/chrome/common/ui/colors/semantic_color_names.h" #if !defined(__has_feature) || !__has_feature(objc_arc) @@ -90,6 +91,11 @@ return nil; } +- (NSString*)matchTypeIconAccessibilityIdentifier { + NOTREACHED(); + return nil; +} + - (BOOL)isMatchTypeSearch { return true; }
diff --git a/ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_view_controller.mm b/ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_view_controller.mm index 8dfe6bf7..4f39efe 100644 --- a/ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_view_controller.mm +++ b/ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_view_controller.mm
@@ -169,8 +169,6 @@ @property(nonatomic, strong) UIControl* scrimView; @property(nonatomic, weak) TabGridTopToolbar* topToolbar; @property(nonatomic, weak) TabGridBottomToolbar* bottomToolbar; -// Bool informing if the confirmation action sheet is displayed. -@property(nonatomic, assign) BOOL closeAllConfirmationDisplayed; @property(nonatomic, assign) TabGridConfiguration configuration; // Setting the current page doesn't scroll the scroll view; use // -scrollToPage:animated: for that. @@ -226,7 +224,6 @@ self = [super initWithNibName:nil bundle:nil]; if (self) { _pageConfiguration = tabGridPageConfiguration; - _closeAllConfirmationDisplayed = NO; _dragSeesionInProgress = NO; switch (_pageConfiguration) { @@ -1645,17 +1642,14 @@ - (void)configureDoneButtonBasedOnPage:(TabGridPage)page { const BOOL tabsPresent = [self tabsPresentForPage:page]; - if (!self.closeAllConfirmationDisplayed) - self.topToolbar.pageControl.userInteractionEnabled = YES; + self.topToolbar.pageControl.userInteractionEnabled = YES; // The Done button should have the same behavior as the other buttons on the // top Toolbar. BOOL incognitoTabsNeedsAuth = (self.currentPage == TabGridPageIncognitoTabs && self.incognitoTabsViewController.contentNeedsAuthentication); - BOOL doneEnabled = tabsPresent && - self.topToolbar.pageControl.userInteractionEnabled && - !incognitoTabsNeedsAuth; + BOOL doneEnabled = tabsPresent && !incognitoTabsNeedsAuth; [self.topToolbar setDoneButtonEnabled:doneEnabled]; [self.bottomToolbar setDoneButtonEnabled:doneEnabled]; } @@ -1675,8 +1669,7 @@ // Disables the done button on bottom toolbar if a disabled tab view is // presented. - (void)configureDoneButtonOnDisabledPage { - if (!self.closeAllConfirmationDisplayed) - self.topToolbar.pageControl.userInteractionEnabled = YES; + self.topToolbar.pageControl.userInteractionEnabled = YES; [self.bottomToolbar setDoneButtonEnabled:NO]; [self.topToolbar setDoneButtonEnabled:NO]; }
diff --git a/ios/chrome/browser/ui/tabs/BUILD.gn b/ios/chrome/browser/ui/tabs/BUILD.gn index 7a101ee..c0afaaa 100644 --- a/ios/chrome/browser/ui/tabs/BUILD.gn +++ b/ios/chrome/browser/ui/tabs/BUILD.gn
@@ -14,6 +14,10 @@ "tab_strip_container_view.h", "tab_strip_container_view.mm", "tab_strip_containing.h", + "tab_strip_context_menu_delegate.h", + "tab_strip_context_menu_helper.h", + "tab_strip_context_menu_helper.mm", + "tab_strip_context_menu_provider.h", "tab_strip_controller.h", "tab_strip_controller.mm", "tab_strip_view.h", @@ -38,14 +42,19 @@ "resources:tabstrip_toggle_button_gradient", "//base", "//base:i18n", + "//components/bookmarks/browser", + "//components/bookmarks/common", "//components/favicon/ios", + "//components/prefs", "//ios/chrome/app/strings", + "//ios/chrome/browser/bookmarks", "//ios/chrome/browser/browser_state", "//ios/chrome/browser/drag_and_drop", "//ios/chrome/browser/flags:system_flags", "//ios/chrome/browser/main:public", "//ios/chrome/browser/snapshots", "//ios/chrome/browser/ui:feature_flags", + "//ios/chrome/browser/ui/bookmarks", "//ios/chrome/browser/ui/bubble", "//ios/chrome/browser/ui/commands", "//ios/chrome/browser/ui/elements", @@ -54,11 +63,12 @@ "//ios/chrome/browser/ui/icons:symbols", "//ios/chrome/browser/ui/image_util", "//ios/chrome/browser/ui/main:scene_state_header", - "//ios/chrome/browser/ui/ntp:logo", + "//ios/chrome/browser/ui/menu", "//ios/chrome/browser/ui/ntp:util", - "//ios/chrome/browser/ui/open_in", "//ios/chrome/browser/ui/popup_menu/public", - "//ios/chrome/browser/ui/tab_switcher/tab_grid/grid/resources:grid_cell_close_button", + "//ios/chrome/browser/ui/tab_switcher:tab_utils", + "//ios/chrome/browser/ui/tab_switcher/tab_grid/pinned_tabs:features", + "//ios/chrome/browser/ui/tab_switcher/tab_grid/tab_context_menu:tab_item", "//ios/chrome/browser/ui/tabs/requirements", "//ios/chrome/browser/ui/util", "//ios/chrome/browser/url_loading",
diff --git a/ios/chrome/browser/ui/tabs/tab_strip_context_menu_delegate.h b/ios/chrome/browser/ui/tabs/tab_strip_context_menu_delegate.h new file mode 100644 index 0000000..4a17910 --- /dev/null +++ b/ios/chrome/browser/ui/tabs/tab_strip_context_menu_delegate.h
@@ -0,0 +1,35 @@ +// Copyright 2023 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef IOS_CHROME_BROWSER_UI_TABS_TAB_STRIP_CONTEXT_MENU_DELEGATE_H_ +#define IOS_CHROME_BROWSER_UI_TABS_TAB_STRIP_CONTEXT_MENU_DELEGATE_H_ + +#import <Foundation/Foundation.h> + +class GURL; + +// Methods used to create context menu actions for tab strip items. +@protocol TabStripContextMenuDelegate <NSObject> + +// Tells the delegate to add `URL` and `title` to the reading list. +- (void)addToReadingListURL:(const GURL&)URL title:(NSString*)title; + +// Tells the delegate to create a bookmark for `URL` with `title`. +- (void)bookmarkURL:(const GURL&)URL title:(NSString*)title; + +// Tells the delegate to edit the bookmark for `URL`. +- (void)editBookmarkWithURL:(const GURL&)URL; + +// Tells the delegate to pin a tab with the item identifier `identifier`. +- (void)pinTabWithIdentifier:(NSString*)identifier; + +// Tells the delegate to unpin a tab with the item identifier `identifier`. +- (void)unpinTabWithIdentifier:(NSString*)identifier; + +// Tells the delegate to close the tab with the item identifier `identifier`. +- (void)closeTabWithIdentifier:(NSString*)identifier; + +@end + +#endif // IOS_CHROME_BROWSER_UI_TABS_TAB_STRIP_CONTEXT_MENU_DELEGATE_H_
diff --git a/ios/chrome/browser/ui/tabs/tab_strip_context_menu_helper.h b/ios/chrome/browser/ui/tabs/tab_strip_context_menu_helper.h new file mode 100644 index 0000000..a92063f69 --- /dev/null +++ b/ios/chrome/browser/ui/tabs/tab_strip_context_menu_helper.h
@@ -0,0 +1,27 @@ +// Copyright 2023 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef IOS_CHROME_BROWSER_UI_TABS_TAB_STRIP_CONTEXT_MENU_HELPER_H_ +#define IOS_CHROME_BROWSER_UI_TABS_TAB_STRIP_CONTEXT_MENU_HELPER_H_ + +#import <UIKit/UIKit.h> + +#import "ios/chrome/browser/ui/tabs/tab_strip_context_menu_provider.h" + +class Browser; +@protocol TabStripContextMenuDelegate; + +// TabStripContextMenuHelper controls the creation of context menus for tab +// strip items. +@interface TabStripContextMenuHelper : NSObject <TabStripContextMenuProvider> +- (instancetype)initWithBrowser:(Browser*)browser + tabStripContextMenuDelegate: + (id<TabStripContextMenuDelegate>)tabStripContextMenuDelegate + NS_DESIGNATED_INITIALIZER; + +- (instancetype)init NS_UNAVAILABLE; + +@end + +#endif // IOS_CHROME_BROWSER_UI_TABS_TAB_STRIP_CONTEXT_MENU_HELPER_H_
diff --git a/ios/chrome/browser/ui/tabs/tab_strip_context_menu_helper.mm b/ios/chrome/browser/ui/tabs/tab_strip_context_menu_helper.mm new file mode 100644 index 0000000..825cce5 --- /dev/null +++ b/ios/chrome/browser/ui/tabs/tab_strip_context_menu_helper.mm
@@ -0,0 +1,208 @@ +// Copyright 2023 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#import "ios/chrome/browser/ui/tabs/tab_strip_context_menu_helper.h" + +#import "base/metrics/histogram_functions.h" +#import "components/bookmarks/browser/bookmark_model.h" +#import "components/bookmarks/common/bookmark_pref_names.h" +#import "components/prefs/pref_service.h" +#import "ios/chrome/browser/bookmarks/bookmark_model_factory.h" +#import "ios/chrome/browser/browser_state/chrome_browser_state.h" +#import "ios/chrome/browser/main/browser.h" +#import "ios/chrome/browser/main/browser_list.h" +#import "ios/chrome/browser/main/browser_list_factory.h" +#import "ios/chrome/browser/main/browser_observer_bridge.h" +#import "ios/chrome/browser/ui/menu/action_factory.h" +#import "ios/chrome/browser/ui/menu/menu_histograms.h" +#import "ios/chrome/browser/ui/ntp/new_tab_page_util.h" +#import "ios/chrome/browser/ui/tab_switcher/tab_grid/pinned_tabs/features.h" +#import "ios/chrome/browser/ui/tab_switcher/tab_grid/tab_context_menu/tab_item.h" +#import "ios/chrome/browser/ui/tab_switcher/tab_utils.h" +#import "ios/chrome/browser/ui/tabs/tab_strip_context_menu_delegate.h" +#import "ios/web/public/web_state.h" + +#if !defined(__has_feature) || !__has_feature(objc_arc) +#error "This file requires ARC support." +#endif + +@interface TabStripContextMenuHelper () <BrowserObserving, + TabStripContextMenuProvider> { + // Observe BrowserObserver to prevent any access to Browser before its + // destroyed. + std::unique_ptr<BrowserObserverBridge> _browserObserver; +} + +@property(nonatomic, assign) Browser* browser; +@property(nonatomic, weak) id<TabStripContextMenuDelegate> delegate; + +@end + +@implementation TabStripContextMenuHelper + +#pragma mark - TabStripContextMenuProvider + +- (instancetype)initWithBrowser:(Browser*)browser + tabStripContextMenuDelegate: + (id<TabStripContextMenuDelegate>)tabStripContextMenuDelegate { + self = [super init]; + if (self) { + _browser = browser; + _browserObserver = std::make_unique<BrowserObserverBridge>(_browser, self); + _delegate = tabStripContextMenuDelegate; + } + return self; +} + +- (void)dealloc { + _browserObserver.reset(); + _browser = nullptr; +} + +#pragma mark - TabStripContextMenuProvider + +- (UIContextMenuConfiguration*) + contextMenuConfigurationForWebStateIdentifier:(NSString*)identifier + pinnedState:(BOOL)pinnedState { + __weak __typeof(self) weakSelf = self; + + UIContextMenuActionProvider actionProvider = + ^(NSArray<UIMenuElement*>* suggestedActions) { + TabStripContextMenuHelper* strongSelf = weakSelf; + if (!strongSelf) { + // Return an empty menu. + return [UIMenu menuWithTitle:@"" children:@[]]; + } + + NSArray<UIMenuElement*>* menuElements = + [strongSelf menuElementsForWebstateIdentifier:identifier + pinnedState:pinnedState]; + return [UIMenu menuWithTitle:@"" children:menuElements]; + }; + + return + [UIContextMenuConfiguration configurationWithIdentifier:nil + previewProvider:nil + actionProvider:actionProvider]; +} + +#pragma mark - Private + +// Returns context menu actions for the given `webStateIndex` and `pinnedState`. +- (NSArray<UIMenuElement*>*) + menuElementsForWebstateIdentifier:(NSString*)identifier + pinnedState:(BOOL)pinnedState { + // Record that this context menu was shown to the user. + MenuScenarioHistogram scenario = MenuScenarioHistogram::kTabStripEntry; + RecordMenuShown(scenario); + + ActionFactory* actionFactory = + [[ActionFactory alloc] initWithScenario:scenario]; + + TabItem* item = [self tabItemForIdentifier:identifier + pinnedState:pinnedState]; + + if (!item) { + return @[]; + } + + NSMutableArray<UIMenuElement*>* menuElements = [[NSMutableArray alloc] init]; + + BOOL pinnedActionsAvailable = + IsPinnedTabsEnabled() && !_browser->GetBrowserState()->IsOffTheRecord(); + if (pinnedActionsAvailable) { + if (pinnedState) { + [menuElements addObject:[actionFactory actionToUnpinTabWithBlock:^{ + [self.delegate unpinTabWithIdentifier:identifier]; + }]]; + } else { + [menuElements addObject:[actionFactory actionToPinTabWithBlock:^{ + [self.delegate pinTabWithIdentifier:identifier]; + }]]; + } + } + + BOOL addToReadingListActionAvailable = + !IsURLNewTabPage(item.URL) && item.URL.SchemeIsHTTPOrHTTPS(); + if (addToReadingListActionAvailable) { + [menuElements addObject:[actionFactory actionToAddToReadingListWithBlock:^{ + [self.delegate addToReadingListURL:item.URL + title:item.title]; + }]]; + } + + BOOL bookmarksActionsAvailable = + !IsURLNewTabPage(item.URL) && item.URL.SchemeIsHTTPOrHTTPS(); + if (bookmarksActionsAvailable) { + UIAction* bookmarkAction; + const BOOL currentlyBookmarked = [self isTabItemBookmarked:item]; + if (currentlyBookmarked) { + bookmarkAction = [actionFactory actionToEditBookmarkWithBlock:^{ + [self.delegate editBookmarkWithURL:item.URL]; + }]; + } else { + bookmarkAction = [actionFactory actionToBookmarkWithBlock:^{ + [self.delegate bookmarkURL:item.URL title:item.title]; + }]; + } + // Bookmarking can be disabled from prefs (from an enterprise policy), + // if that's the case grey out the option in the menu. + if (self.browser) { + BOOL isEditBookmarksEnabled = + self.browser->GetBrowserState()->GetPrefs()->GetBoolean( + bookmarks::prefs::kEditBookmarksEnabled); + if (!isEditBookmarksEnabled && bookmarkAction) { + bookmarkAction.attributes = UIMenuElementAttributesDisabled; + } + if (bookmarkAction) { + [menuElements addObject:bookmarkAction]; + } + } + } + + [menuElements addObject:[actionFactory actionToCloseTabWithBlock:^{ + [self.delegate closeTabWithIdentifier:identifier]; + }]]; + return menuElements; +} + +#pragma mark - BrowserObserving + +- (void)browserDestroyed:(Browser*)browser { + DCHECK_EQ(browser, self.browser); + _browserObserver.reset(); + self.browser = nullptr; +} + +#pragma mark - Private + +// Returns `YES` if the tab `item` is already bookmarked. +- (BOOL)isTabItemBookmarked:(TabItem*)item { + bookmarks::BookmarkModel* bookmarkModel = + ios::BookmarkModelFactory::GetForBrowserState( + _browser->GetBrowserState()); + return item && bookmarkModel && + bookmarkModel->GetMostRecentlyAddedUserNodeForURL(item.URL); +} + +// Returns the TabItem object representing the tab with `identifier`. +// `pinnedState` tracks the pinned state of the tab we are looking for. +- (TabItem*)tabItemForIdentifier:(NSString*)identifier + pinnedState:(BOOL)pinnedState { + BrowserList* browserList = + BrowserListFactory::GetForBrowserState(_browser->GetBrowserState()); + std::set<Browser*> browsers = _browser->GetBrowserState()->IsOffTheRecord() + ? browserList->AllIncognitoBrowsers() + : browserList->AllRegularBrowsers(); + for (Browser* browser : browsers) { + WebStateList* webStateList = browser->GetWebStateList(); + TabItem* item = GetTabItem(webStateList, identifier, pinnedState); + if (item) { + return item; + } + } + return nil; +} + +@end
diff --git a/ios/chrome/browser/ui/tabs/tab_strip_context_menu_provider.h b/ios/chrome/browser/ui/tabs/tab_strip_context_menu_provider.h new file mode 100644 index 0000000..338427b --- /dev/null +++ b/ios/chrome/browser/ui/tabs/tab_strip_context_menu_provider.h
@@ -0,0 +1,21 @@ +// Copyright 2023 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef IOS_CHROME_BROWSER_UI_TABS_TAB_STRIP_CONTEXT_MENU_PROVIDER_H_ +#define IOS_CHROME_BROWSER_UI_TABS_TAB_STRIP_CONTEXT_MENU_PROVIDER_H_ + +#import <UIKit/UIKit.h> + +// Protocol that provides tab strip context menus. +@protocol TabStripContextMenuProvider + +// Returns a context menu configuration instance for the given +// `identifier` and `pinnedState`. +- (UIContextMenuConfiguration*) + contextMenuConfigurationForWebStateIdentifier:(NSString*)identifier + pinnedState:(BOOL)pinnedState; + +@end + +#endif // IOS_CHROME_BROWSER_UI_TABS_TAB_STRIP_CONTEXT_MENU_PROVIDER_H_
diff --git a/ios/chrome/browser/ui/tabs/tab_strip_controller.h b/ios/chrome/browser/ui/tabs/tab_strip_controller.h index 0ca47d1..2be9f86 100644 --- a/ios/chrome/browser/ui/tabs/tab_strip_controller.h +++ b/ios/chrome/browser/ui/tabs/tab_strip_controller.h
@@ -42,9 +42,11 @@ // it needs to be run in sync with BVC. @property(nonatomic, readonly, strong) id<ViewRevealingAnimatee> animatee; -// Designated initializer, `dispatcher` is not retained. -- (instancetype)initWithBrowser:(Browser*)browser - style:(TabStripStyle)style NS_DESIGNATED_INITIALIZER; +// Designated initializer. +- (instancetype)initWithBaseViewController:(UIViewController*)baseViewController + browser:(Browser*)browser + style:(TabStripStyle)style + NS_DESIGNATED_INITIALIZER; - (instancetype)init NS_UNAVAILABLE;
diff --git a/ios/chrome/browser/ui/tabs/tab_strip_controller.mm b/ios/chrome/browser/ui/tabs/tab_strip_controller.mm index c2b3993..eb345aee 100644 --- a/ios/chrome/browser/ui/tabs/tab_strip_controller.mm +++ b/ios/chrome/browser/ui/tabs/tab_strip_controller.mm
@@ -8,6 +8,7 @@ #import <memory> #import <vector> +#import "base/feature_list.h" #import "base/i18n/rtl.h" #import "base/mac/bundle_locations.h" #import "base/mac/foundation_util.h" @@ -15,7 +16,9 @@ #import "base/metrics/user_metrics_action.h" #import "base/numerics/safe_conversions.h" #import "base/strings/sys_string_conversions.h" +#import "components/bookmarks/browser/bookmark_model.h" #import "components/favicon/ios/web_favicon_driver.h" +#import "ios/chrome/browser/bookmarks/bookmark_model_factory.h" #import "ios/chrome/browser/browser_state/chrome_browser_state.h" #import "ios/chrome/browser/drag_and_drop/drag_item_util.h" #import "ios/chrome/browser/drag_and_drop/url_drag_drop_handler.h" @@ -23,12 +26,16 @@ #import "ios/chrome/browser/main/browser.h" #import "ios/chrome/browser/snapshots/snapshot_tab_helper.h" #import "ios/chrome/browser/tabs/tab_title_util.h" +#import "ios/chrome/browser/ui/bookmarks/bookmarks_coordinator.h" #import "ios/chrome/browser/ui/bubble/bubble_util.h" #import "ios/chrome/browser/ui/bubble/bubble_view.h" #import "ios/chrome/browser/ui/commands/application_commands.h" +#import "ios/chrome/browser/ui/commands/bookmarks_commands.h" +#import "ios/chrome/browser/ui/commands/browser_commands.h" #import "ios/chrome/browser/ui/commands/command_dispatcher.h" #import "ios/chrome/browser/ui/commands/open_new_tab_command.h" #import "ios/chrome/browser/ui/commands/popup_menu_commands.h" +#import "ios/chrome/browser/ui/commands/reading_list_add_command.h" #import "ios/chrome/browser/ui/fullscreen/fullscreen_controller.h" #import "ios/chrome/browser/ui/fullscreen/scoped_fullscreen_disabler.h" #import "ios/chrome/browser/ui/gestures/view_revealing_vertical_pan_handler.h" @@ -37,10 +44,14 @@ #import "ios/chrome/browser/ui/main/scene_state_browser_agent.h" #import "ios/chrome/browser/ui/ntp/new_tab_page_util.h" #import "ios/chrome/browser/ui/popup_menu/public/popup_menu_long_press_delegate.h" +#import "ios/chrome/browser/ui/tab_switcher/tab_utils.h" #import "ios/chrome/browser/ui/tabs/requirements/tab_strip_constants.h" #import "ios/chrome/browser/ui/tabs/requirements/tab_strip_presentation.h" #import "ios/chrome/browser/ui/tabs/tab_strip_constants.h" #import "ios/chrome/browser/ui/tabs/tab_strip_container_view.h" +#import "ios/chrome/browser/ui/tabs/tab_strip_context_menu_delegate.h" +#import "ios/chrome/browser/ui/tabs/tab_strip_context_menu_helper.h" +#import "ios/chrome/browser/ui/tabs/tab_strip_context_menu_provider.h" #import "ios/chrome/browser/ui/tabs/tab_strip_view.h" #import "ios/chrome/browser/ui/tabs/tab_view.h" #import "ios/chrome/browser/ui/tabs/target_frame_cache.h" @@ -80,7 +91,8 @@ const NSTimeInterval kTabStripFadeAnimationDuration = 0.15; // Amount of time needed to trigger drag and drop mode when long pressing. -const NSTimeInterval kDragAndDropLongPressDuration = 0.4; +const NSTimeInterval kDragAndDropLongPressDuration = 0.1; +const NSTimeInterval kDragAndDropLongPressLegacyDuration = 0.4; // Tab dimensions. const CGFloat kTabOverlapStacked = 32.0; @@ -130,6 +142,12 @@ return UIColor.blackColor; } +// Convenience method for determining if the TabStripContextMenu feature is +// enabled. +bool IsTabStripContextMenuEnabled() { + return base::FeatureList::IsEnabled(kTabStripContextMenu); +} + const CGFloat kSymbolSize = 18; } // namespace @@ -169,11 +187,13 @@ @end @interface TabStripController () <CRWWebStateObserver, + TabStripContextMenuDelegate, TabStripViewLayoutDelegate, TabViewDelegate, ViewRevealingAnimatee, - WebStateListObserving, WebStateFaviconDriverObserver, + WebStateListObserving, + UIContextMenuInteractionDelegate, UIGestureRecognizerDelegate, UIScrollViewDelegate, URLDropDelegate> { @@ -271,6 +291,17 @@ // YES if the controller has been disconnected. @property(nonatomic) BOOL disconnected; +// The base view controller from which to present UI. +@property(nonatomic, readwrite, weak) UIViewController* baseViewController; + +// Provider of context menu configurations. +@property(nonatomic, strong) id<TabStripContextMenuProvider> + contextMenuProvider; + +// Coordinator that manages the various pieces of UI used to create, remove and +// edit a bookmark. +@property(nonatomic, strong) BookmarksCoordinator* bookmarksCoordinator; + // If set to `YES`, tabs at either end of the tabstrip are "collapsed" into a // stack, such that the visible width of the tabstrip is constant. If set to // `NO`, tabs are never collapsed and the tabstrip scrolls horizontally as a @@ -431,12 +462,14 @@ @synthesize animationWaitDuration = _animationWaitDuration; @synthesize panGestureHandler = _panGestureHandler; -- (instancetype)initWithBrowser:(Browser*)browser style:(TabStripStyle)style { +- (instancetype)initWithBaseViewController:(UIViewController*)baseViewController + browser:(Browser*)browser + style:(TabStripStyle)style { if ((self = [super init])) { _tabArray = [[NSMutableArray alloc] initWithCapacity:10]; _closingTabs = [[NSMutableSet alloc] initWithCapacity:5]; DCHECK(browser); - + _baseViewController = baseViewController; _browser = browser; _webStateList = _browser->GetWebStateList(); _webStateListObserver = std::make_unique<WebStateListObserverBridge>(self); @@ -477,6 +510,15 @@ [_view addSubview:_tabStripView]; _view.tabStripView = _tabStripView; + if (IsTabStripContextMenuEnabled()) { + _contextMenuProvider = + [[TabStripContextMenuHelper alloc] initWithBrowser:_browser + tabStripContextMenuDelegate:self]; + _bookmarksCoordinator = + [[BookmarksCoordinator alloc] initWithBrowser:_browser]; + _bookmarksCoordinator.baseViewController = _baseViewController; + } + // `self.buttonNewTab` setup. CGRect buttonNewTabFrame = tabStripFrame; buttonNewTabFrame.size.width = kNewTabButtonWidth; @@ -550,10 +592,16 @@ - (void)disconnect { [_tabStripView setDelegate:nil]; [_tabStripView setLayoutDelegate:nil]; + + self.presentationProvider = nil; + self.baseViewController = nil; + self.bookmarksCoordinator = nil; + _allWebStateObservationForwarder.reset(); _webStateListFaviconObserver.reset(); _webStateList->RemoveObserver(_webStateListObserver.get()); [[NSNotificationCenter defaultCenter] removeObserver:self]; + self.disconnected = YES; } @@ -628,7 +676,15 @@ [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(handleLongPress:)]; - [longPress setMinimumPressDuration:kDragAndDropLongPressDuration]; + + if (IsTabStripContextMenuEnabled()) { + [view addInteraction:[[UIContextMenuInteraction alloc] + initWithDelegate:self]]; + [longPress setMinimumPressDuration:kDragAndDropLongPressDuration]; + + } else { + [longPress setMinimumPressDuration:kDragAndDropLongPressLegacyDuration]; + } [longPress setDelegate:self]; [view addGestureRecognizer:longPress]; @@ -831,8 +887,84 @@ UrlLoadingBrowserAgent::FromBrowser(_browser)->Load(params); } -#pragma mark - -#pragma mark Tab Drag and Drop methods +#pragma mark - UIContextMenuInteractionDelegate + +- (UIContextMenuConfiguration*)contextMenuInteraction: + (UIContextMenuInteraction*)interaction + configurationForMenuAtLocation:(CGPoint)location { + DCHECK(IsTabStripContextMenuEnabled()); + + int webStateIndex = [self webStateListIndexForTabView:interaction.view]; + NSString* identifier = + _webStateList->GetWebStateAt(webStateIndex)->GetStableIdentifier(); + BOOL pinnedState = _webStateList->IsWebStatePinnedAt(webStateIndex); + + return [self.contextMenuProvider + contextMenuConfigurationForWebStateIdentifier:identifier + pinnedState:pinnedState]; +} + +- (UITargetedPreview*)contextMenuInteraction: + (UIContextMenuInteraction*)interaction + configuration: + (UIContextMenuConfiguration*)configuration + highlightPreviewForItemWithIdentifier:(id<NSCopying>)identifier { + // TODO(crbug.com/1409893): Update the targeted preview. + UIPreviewParameters* previewParameters = [[UIPreviewParameters alloc] init]; + previewParameters.backgroundColor = UIColor.clearColor; + return [[UITargetedPreview alloc] initWithView:interaction.view + parameters:previewParameters]; +} + +#pragma mark - TabStripContextMenuDelegate + +- (void)addToReadingListURL:(const GURL&)URL title:(NSString*)title { + ReadingListAddCommand* command = + [[ReadingListAddCommand alloc] initWithURL:URL title:title]; + // TODO(crbug.com/1045047): Use HandlerForProtocol after commands + // protocol clean up. + id<BrowserCommands> readingListAdder = + static_cast<id<BrowserCommands>>(_browser->GetCommandDispatcher()); + [readingListAdder addToReadingList:command]; +} + +- (void)bookmarkURL:(const GURL&)URL title:(NSString*)title { + bookmarks::BookmarkModel* bookmarkModel = + ios::BookmarkModelFactory::GetForBrowserState( + _browser->GetBrowserState()); + bool currentlyBookmarked = + bookmarkModel && bookmarkModel->GetMostRecentlyAddedUserNodeForURL(URL); + + if (currentlyBookmarked) { + [self editBookmarkWithURL:URL]; + } else { + [self.bookmarksCoordinator bookmarkURL:URL title:title]; + } +} + +- (void)editBookmarkWithURL:(const GURL&)URL { + [self.bookmarksCoordinator presentBookmarkEditorForURL:URL]; +} + +- (void)pinTabWithIdentifier:(NSString*)identifier { + SetWebStatePinnedState(_webStateList, identifier, YES); +} + +- (void)unpinTabWithIdentifier:(NSString*)identifier { + SetWebStatePinnedState(_webStateList, identifier, NO); +} + +- (void)closeTabWithIdentifier:(NSString*)identifier { + for (int index = 0; index < static_cast<int>(_tabArray.count); ++index) { + web::WebState* web_state = _webStateList->GetWebStateAt(index); + if ([identifier isEqualToString:web_state->GetStableIdentifier()]) { + _webStateList->CloseWebStateAt(index, WebStateList::CLOSE_USER_ACTION); + return; + } + } +} + +#pragma mark - Tab Drag and Drop methods - (void)beginDrag:(UILongPressGestureRecognizer*)gesture { DCHECK([[gesture view] isKindOfClass:[TabView class]]); @@ -978,8 +1110,7 @@ [self insertNewItemAtIndex:_webStateList->count() withURL:URL]; } -#pragma mark - -#pragma mark Autoscroll methods +#pragma mark - Autoscroll methods - (void)installAutoscrollTimerIfNeeded { if (_autoscrollTimer) @@ -1069,7 +1200,6 @@ _autoscrollDistance = -offset.x; } -#pragma mark - #pragma mark - CRWWebStateObserver methods - (void)webStateDidStartLoading:(web::WebState*)webState { @@ -1118,8 +1248,7 @@ [view setNeedsDisplay]; } -#pragma mark - -#pragma mark WebStateListObserving methods +#pragma mark - WebStateListObserving methods // Observer method, active WebState changed. - (void)webStateList:(WebStateList*)webStateList @@ -1234,8 +1363,7 @@ [self layoutTabStripSubviews]; } -#pragma mark - -#pragma mark WebStateFaviconDriverObserver +#pragma mark - WebStateFaviconDriverObserver // Observer method. `webState` got a favicon update. - (void)faviconDriver:(favicon::FaviconDriver*)driver @@ -1261,8 +1389,7 @@ } } -#pragma mark - -#pragma mark Views and Layout +#pragma mark - Views and Layout - (TabView*)tabViewForWebState:(web::WebState*)webState { int listIndex = _webStateList->GetIndexOfWebState(webState); @@ -1361,7 +1488,6 @@ return frame; } -#pragma mark - #pragma mark - Unstacked layout - (int)maxNumCollapsedTabs {
diff --git a/ios/chrome/browser/ui/tabs/tab_strip_controller_unittest.mm b/ios/chrome/browser/ui/tabs/tab_strip_controller_unittest.mm index 3e0261c..db9607a 100644 --- a/ios/chrome/browser/ui/tabs/tab_strip_controller_unittest.mm +++ b/ios/chrome/browser/ui/tabs/tab_strip_controller_unittest.mm
@@ -69,8 +69,10 @@ SceneStateBrowserAgent::CreateForBrowser(browser_.get(), scene_state_); - controller_ = [[TabStripController alloc] initWithBrowser:browser_.get() - style:NORMAL]; + controller_ = + [[TabStripController alloc] initWithBaseViewController:nil + browser:browser_.get() + style:NORMAL]; } void TearDown() override {
diff --git a/ios/chrome/browser/ui/tabs/tab_strip_legacy_coordinator.h b/ios/chrome/browser/ui/tabs/tab_strip_legacy_coordinator.h index da0daa6..2a67d7e1 100644 --- a/ios/chrome/browser/ui/tabs/tab_strip_legacy_coordinator.h +++ b/ios/chrome/browser/ui/tabs/tab_strip_legacy_coordinator.h
@@ -26,6 +26,10 @@ - (instancetype)initWithBaseViewController:(UIViewController*)viewController browser:(Browser*)browser NS_UNAVAILABLE; +// The base view controller for this coordinator. This is required because the +// TabStripLegacyCoordinator is instantiated before the BrowserViewController. +@property(nonatomic, weak, readwrite) UIViewController* baseViewController; + // Delegate for the long press gesture recognizer triggering popup menu. @property(nonatomic, weak) id<PopupMenuLongPressDelegate> longPressDelegate;
diff --git a/ios/chrome/browser/ui/tabs/tab_strip_legacy_coordinator.mm b/ios/chrome/browser/ui/tabs/tab_strip_legacy_coordinator.mm index 0e89c3b..8808ec95 100644 --- a/ios/chrome/browser/ui/tabs/tab_strip_legacy_coordinator.mm +++ b/ios/chrome/browser/ui/tabs/tab_strip_legacy_coordinator.mm
@@ -26,6 +26,7 @@ @synthesize started = _started; @synthesize tabStripController = _tabStripController; @synthesize animationWaitDuration = _animationWaitDuration; +@synthesize baseViewController = _baseViewController; - (instancetype)initWithBrowser:(Browser*)browser { DCHECK(browser); @@ -81,8 +82,10 @@ DCHECK(self.presentationProvider); TabStripStyle style = self.browser->GetBrowserState()->IsOffTheRecord() ? INCOGNITO : NORMAL; - self.tabStripController = - [[TabStripController alloc] initWithBrowser:self.browser style:style]; + self.tabStripController = [[TabStripController alloc] + initWithBaseViewController:self.baseViewController + browser:self.browser + style:style]; self.tabStripController.presentationProvider = self.presentationProvider; self.tabStripController.animationWaitDuration = self.animationWaitDuration; self.tabStripController.longPressDelegate = self.longPressDelegate;
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 3bd57ae..255a9c0 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 @@ -aaa617af2315b22a11ec7b5159225b7f14a66f0f \ No newline at end of file +d894d40effa7748a1de9e1d1ec9cebf5b03c22dd \ 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 a4ec38dc..9878abfc 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 @@ -d686f9789f916117ea8260147305816099dc179d \ No newline at end of file +b75de431ad6973e7a0384d11dd35b51ed42e7c74 \ 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 5cb8c00..2b02dc1c 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 @@ -507fc52fed5af89dd8420a9f95c7231ffff873b5 \ No newline at end of file +0c4fb9c620b74fc961cf7409cf10b4423af79e31 \ 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 29783da..f50b81b8 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 @@ -77ae8aa36c7ef47ed238a7de580069eb70da14e2 \ No newline at end of file +66e4cf96d69f2015c698309e082cba0c9fe80b28 \ 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 cb79a2d..63d105b 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 @@ -ca29b5f1fad1e839c576563a4b5f452731fd55f2 \ No newline at end of file +3633971426c95914abdb888879b02980dee987e8 \ 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 ea95448..a5acaec 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 @@ -a107e498fe69969c35eddb5709866fd3a22eaa0e \ No newline at end of file +942903c48e04de927169db3378e1c71112f39b18 \ 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 7a3091c..ac758c550 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 @@ -ae8261e31c286e9a590a7cdf2b6313467bd93eff \ No newline at end of file +c0330d1834e15ae64395c5233526cd8dc2b91298 \ 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 036b934..8ae26e5 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 @@ -f9b11ace8a8cef1033dc240658ef61131ac46056 \ No newline at end of file +4bf2fb24ad97da6078bac249e8dd236c28d0ea14 \ 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 7fc628d2..d8561ad 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 @@ -2ec45101e881357c6c80ffd79b78d1793adac7e7 \ No newline at end of file +251681ecc02ab3c87cea4a47e78311bc0d630d05 \ 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 a810dcf2..47c5475 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 @@ -2dfb4a3792d08e09aaec815bbd6d95b06570d08b \ No newline at end of file +cb037170f23a783c287d6495e2ab71746553113c \ 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 252818895..3aec8a6 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 @@ -0257b25830500946a5135486adc73e2d5dc744fc \ No newline at end of file +cbc62028e7099c3b3b756877f678ca209917900c \ 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 02b18d1..49f3f8d 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 @@ -0729d392259fd40afd4565841016ba863917267f \ No newline at end of file +105f0d8c1cc774e5eb18b2f68d2220eb699445e4 \ No newline at end of file
diff --git a/ios/showcase/omnibox_popup/fake_autocomplete_suggestion.h b/ios/showcase/omnibox_popup/fake_autocomplete_suggestion.h index 09d90c0..f1382c3 100644 --- a/ios/showcase/omnibox_popup/fake_autocomplete_suggestion.h +++ b/ios/showcase/omnibox_popup/fake_autocomplete_suggestion.h
@@ -32,6 +32,7 @@ @property(nonatomic) NSAttributedString* omniboxPreviewText; @property(nonatomic) UIImage* matchTypeIcon; +@property(nonatomic) NSString* matchTypeIconAccessibilityIdentifier; @property(nonatomic, getter=isMatchTypeSearch) BOOL matchTypeSearch; @property(nonatomic) CrURL* destinationUrl;
diff --git a/ios/web_view/internal/autofill/web_view_autofill_client_ios.h b/ios/web_view/internal/autofill/web_view_autofill_client_ios.h index a0285c45..cbdef07 100644 --- a/ios/web_view/internal/autofill/web_view_autofill_client_ios.h +++ b/ios/web_view/internal/autofill/web_view_autofill_client_ios.h
@@ -101,9 +101,10 @@ AddressProfileSavePromptCallback callback) override; bool HasCreditCardScanFeature() override; void ScanCreditCard(CreditCardScanCallback callback) override; - bool TryToShowFastCheckout(const FormData& form, - const FormFieldData& field, - AutofillDriver* driver) override; + bool TryToShowFastCheckout( + const FormData& form, + const FormFieldData& field, + base::WeakPtr<AutofillManager> autofill_manager) override; void HideFastCheckout(bool allow_further_runs) override; bool IsFastCheckoutSupported() override; bool IsShowingFastCheckoutUI() override;
diff --git a/ios/web_view/internal/autofill/web_view_autofill_client_ios.mm b/ios/web_view/internal/autofill/web_view_autofill_client_ios.mm index 3b37fcc..21e6ebc 100644 --- a/ios/web_view/internal/autofill/web_view_autofill_client_ios.mm +++ b/ios/web_view/internal/autofill/web_view_autofill_client_ios.mm
@@ -271,9 +271,10 @@ return false; } -bool WebViewAutofillClientIOS::TryToShowFastCheckout(const FormData& form, - const FormFieldData& field, - AutofillDriver* driver) { +bool WebViewAutofillClientIOS::TryToShowFastCheckout( + const FormData& form, + const FormFieldData& field, + base::WeakPtr<AutofillManager> autofill_manager) { return false; }
diff --git a/testing/variations/fieldtrial_testing_config.json b/testing/variations/fieldtrial_testing_config.json index 8fa91e1..fb07b47 100644 --- a/testing/variations/fieldtrial_testing_config.json +++ b/testing/variations/fieldtrial_testing_config.json
@@ -6315,21 +6315,6 @@ ] } ], - "IncognitoDownloadsWarning": [ - { - "platforms": [ - "android" - ], - "experiments": [ - { - "name": "Enabled", - "enable_features": [ - "IncognitoDownloadsWarning" - ] - } - ] - } - ], "IncognitoNtpRevamp": [ { "platforms": [ @@ -13479,6 +13464,28 @@ ] } ], + "WebRtcEncoderAsyncEncode": [ + { + "platforms": [ + "android", + "android_webview", + "chromeos", + "chromeos_lacros", + "ios", + "linux", + "mac", + "windows" + ], + "experiments": [ + { + "name": "Enabled", + "enable_features": [ + "WebRtcEncoderAsyncEncode" + ] + } + ] + } + ], "WebRtcMetronome": [ { "platforms": [
diff --git a/third_party/.gitignore b/third_party/.gitignore index dd0dd2f8..93ef0a6 100644 --- a/third_party/.gitignore +++ b/third_party/.gitignore
@@ -68,6 +68,7 @@ /chromeos_text_input /chromevox/third_party/sre/src /chromite +/crossbench /cld_2/src /cld_3/src /colorama/src
diff --git a/third_party/blink/renderer/core/css/parser/css_selector_parser.cc b/third_party/blink/renderer/core/css/parser/css_selector_parser.cc index 86c3bf8c..bfee4e1 100644 --- a/third_party/blink/renderer/core/css/parser/css_selector_parser.cc +++ b/third_party/blink/renderer/core/css/parser/css_selector_parser.cc
@@ -413,6 +413,12 @@ ConsumeComplexSelector(argument, /*in_nested_style_rule=*/false, /*first_in_complex_selector_list=*/false); if (selector.empty() || failed_parsing_ || !argument.AtEnd()) { + for (const CSSParserToken& token : argument) { + if (token.GetType() == kDelimiterToken && token.Delimiter() == '&') { + dropped_nest_token_during_forgiving_parsing_ = true; + break; + } + } if (in_supports_parsing_) { at_supports_drop_invalid_counter.Count(); } @@ -766,7 +772,8 @@ // of SelectorListIsNestContaining(). wtf_size_t last_index = output_.size() - 1; output_[last_index].SetLastInSelectorList(true); - if (!SelectorListIsNestContaining(reset_vector.AddedElements().data())) { + if (!dropped_nest_token_during_forgiving_parsing_ && + !SelectorListIsNestContaining(reset_vector.AddedElements().data())) { output_.back().SetRelation(CSSSelector::kDescendant); output_.push_back( CSSSelector(parent_rule_for_nesting_, /*is_implicit=*/true));
diff --git a/third_party/blink/renderer/core/css/parser/css_selector_parser.h b/third_party/blink/renderer/core/css/parser/css_selector_parser.h index 9365fcb..3214cc5 100644 --- a/third_party/blink/renderer/core/css/parser/css_selector_parser.h +++ b/third_party/blink/renderer/core/css/parser/css_selector_parser.h
@@ -209,6 +209,11 @@ // PseudoType of the pseudo-element causing the restriction. CSSSelector::PseudoType restricting_pseudo_element_ = CSSSelector::kPseudoUnknown; + // Whether at any point we dropped an & token during forgiving parsing + // (e.g. in :is(!&, .foo)); such selectors are still considered as + // nest-containing. Since every top-level selector gets a new CSSParser + // instance, we don't need to worry about resetting it. + bool dropped_nest_token_during_forgiving_parsing_ = false; // If we're _resisting_ the default namespace, it means that we are inside // a nested selector (:is(), :where(), etc) where we should _consider_ // ignoring the default namespace (depending on circumstance). See the
diff --git a/third_party/blink/renderer/core/css/rule_set.cc b/third_party/blink/renderer/core/css/rule_set.cc index 6afba63..1e1fd36 100644 --- a/third_party/blink/renderer/core/css/rule_set.cc +++ b/third_party/blink/renderer/core/css/rule_set.cc
@@ -45,6 +45,7 @@ #include "third_party/blink/renderer/core/html/shadow/shadow_element_names.h" #include "third_party/blink/renderer/core/html/track/text_track_cue.h" #include "third_party/blink/renderer/core/html_names.h" +#include "third_party/blink/renderer/core/style/computed_style_constants.h" #include "third_party/blink/renderer/platform/instrumentation/tracing/trace_event.h" #include "third_party/blink/renderer/platform/weborigin/security_origin.h" @@ -80,13 +81,13 @@ case CSSSelector::kPseudoTargetText: case CSSSelector::kPseudoGrammarError: case CSSSelector::kPseudoSpellingError: - if (RuntimeEnabledFeatures::HighlightInheritanceEnabled()) { + case CSSSelector::kPseudoHighlight: + if (UsesHighlightPseudoInheritance( + component->GetPseudoId(component->GetPseudoType()))) { return ValidPropertyFilter::kHighlight; } else { return ValidPropertyFilter::kHighlightLegacy; } - case CSSSelector::kPseudoHighlight: - return ValidPropertyFilter::kHighlight; default: break; }
diff --git a/third_party/blink/renderer/core/css/style_engine_test.cc b/third_party/blink/renderer/core/css/style_engine_test.cc index 4ac0f72d..59759861 100644 --- a/third_party/blink/renderer/core/css/style_engine_test.cc +++ b/third_party/blink/renderer/core/css/style_engine_test.cc
@@ -5357,7 +5357,7 @@ UpdateAllLifecyclePhases(); unsigned element_count = GetStyleEngine().StyleForElementCount() - start_count; - ASSERT_EQ(2U, element_count); + ASSERT_EQ(1U, element_count); start_count = GetStyleEngine().StyleForElementCount(); GetDocument().getElementById("div1")->RemoveChild(
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 7bfbdddb..6904487c 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
@@ -647,8 +647,12 @@ bool HTMLMediaElement::ShouldReusePlayer(Document& old_document, Document& new_document) const { + // Don't reuse player if the Document Picture-in-Picture API is disabled for + // both documents. if (!RuntimeEnabledFeatures::DocumentPictureInPictureAPIEnabled( - GetExecutionContext())) { + old_document.domWindow()->GetExecutionContext()) && + !RuntimeEnabledFeatures::DocumentPictureInPictureAPIEnabled( + new_document.domWindow()->GetExecutionContext())) { return false; }
diff --git a/third_party/blink/renderer/core/html/resources/html.css b/third_party/blink/renderer/core/html/resources/html.css index a2102e7..f0fb658 100644 --- a/third_party/blink/renderer/core/html/resources/html.css +++ b/third_party/blink/renderer/core/html/resources/html.css
@@ -1295,12 +1295,6 @@ outline-offset: 1px; } -/* ensure that selected links are underlined.*/ -a:-webkit-any-link::target-text { - text-decoration:underline; - background-color:#E9D2FD; -} - /* HTML5 ruby elements */ ruby, rt {
diff --git a/third_party/blink/renderer/core/layout/layout_object.cc b/third_party/blink/renderer/core/layout/layout_object.cc index f706c4b..39c6e0f 100644 --- a/third_party/blink/renderer/core/layout/layout_object.cc +++ b/third_party/blink/renderer/core/layout/layout_object.cc
@@ -113,6 +113,7 @@ #include "third_party/blink/renderer/core/paint/timing/image_element_timing.h" #include "third_party/blink/renderer/core/paint/timing/paint_timing_detector.h" #include "third_party/blink/renderer/core/scroll/scroll_into_view_util.h" +#include "third_party/blink/renderer/core/style/computed_style_constants.h" #include "third_party/blink/renderer/core/style/content_data.h" #include "third_party/blink/renderer/core/style/cursor_data.h" #include "third_party/blink/renderer/platform/graphics/paint/geometry_mapper.h" @@ -2569,41 +2570,12 @@ if (style_->HasPseudoElementStyle(pseudo) || style->HasPseudoElementStyle(pseudo)) { - const ComputedStyle* pseudo_old_style = nullptr; - const ComputedStyle* pseudo_new_style = nullptr; - - // TODO(rego): Refactor this code so we can call something like - // HighlightData()->PseudoStyle(pseudo) and avoid the switch (we could - // also avoid the switch in - // HighlightPaintingUtils::HighlightPseudoStyle(). - switch (pseudo) { - case kPseudoIdTargetText: - pseudo_old_style = style_->HighlightData() - ? style_->HighlightData()->TargetText() + const ComputedStyle* pseudo_old_style = + style_->HighlightData() ? style_->HighlightData()->Style(pseudo) + : nullptr; + const ComputedStyle* pseudo_new_style = + style->HighlightData() ? style->HighlightData()->Style(pseudo) : nullptr; - pseudo_new_style = style->HighlightData() - ? style->HighlightData()->TargetText() - : nullptr; - break; - case kPseudoIdSpellingError: - pseudo_old_style = style_->HighlightData() - ? style_->HighlightData()->SpellingError() - : nullptr; - pseudo_new_style = style->HighlightData() - ? style->HighlightData()->SpellingError() - : nullptr; - break; - case kPseudoIdGrammarError: - pseudo_old_style = style_->HighlightData() - ? style_->HighlightData()->GrammarError() - : nullptr; - pseudo_new_style = style->HighlightData() - ? style->HighlightData()->GrammarError() - : nullptr; - break; - default: - NOTREACHED(); - } if (pseudo_old_style && pseudo_new_style) { diff.Merge(pseudo_old_style->VisualInvalidationDiff( @@ -2614,16 +2586,19 @@ } }; - if (RuntimeEnabledFeatures::HighlightInheritanceEnabled()) { - // TODO(rego): We don't do anything regarding ::selection, as ::selection - // uses its own mechanism for this (see - // LayoutObject::InvalidateSelectedChildrenOnStyleChange()). Maybe in the - // future we could detect changes here for ::selection too. + // See HighlightRegistry for ::highlight() paint invalidation. + // TODO(rego): We don't do anything regarding ::selection, as ::selection + // uses its own mechanism for this (see + // LayoutObject::InvalidateSelectedChildrenOnStyleChange()). Maybe in the + // future we could detect changes here for ::selection too. + if (UsesHighlightPseudoInheritance(kPseudoIdTargetText)) { HighlightPseudoUpdateDiff(kPseudoIdTargetText); - if (RuntimeEnabledFeatures::CSSSpellingGrammarErrorsEnabled()) { - HighlightPseudoUpdateDiff(kPseudoIdSpellingError); - HighlightPseudoUpdateDiff(kPseudoIdGrammarError); - } + } + if (UsesHighlightPseudoInheritance(kPseudoIdSpellingError)) { + HighlightPseudoUpdateDiff(kPseudoIdSpellingError); + } + if (UsesHighlightPseudoInheritance(kPseudoIdGrammarError)) { + HighlightPseudoUpdateDiff(kPseudoIdGrammarError); } }
diff --git a/third_party/blink/renderer/modules/mediastream/media_constraints_impl.cc b/third_party/blink/renderer/modules/mediastream/media_constraints_impl.cc index 26493647..f70677f 100644 --- a/third_party/blink/renderer/modules/mediastream/media_constraints_impl.cc +++ b/third_party/blink/renderer/modules/mediastream/media_constraints_impl.cc
@@ -110,10 +110,6 @@ // Google-specific constraint keys for a local video source (getUserMedia). const char kNoiseReduction[] = "googNoiseReduction"; -// Names used for testing. -const char kTestConstraint1[] = "valid_and_supported_1"; -const char kTestConstraint2[] = "valid_and_supported_2"; - static bool ParseMandatoryConstraintsDictionary( const Dictionary& mandatory_constraints_dictionary, Vector<NameValueStringConstraint>& mandatory) { @@ -179,8 +175,7 @@ static void ParseOldStyleNames( ExecutionContext* context, const Vector<NameValueStringConstraint>& old_names, - MediaTrackConstraintSetPlatform& result, - MediaErrorState& error_state) { + MediaTrackConstraintSetPlatform& result) { if (old_names.size() > 0) { UseCounter::Count(context, WebFeature::kOldConstraintsParsed); } @@ -240,14 +235,6 @@ result.goog_da_echo_cancellation.SetExact(ToBoolean(constraint.value_)); } else if (constraint.name_.Equals(kNoiseReduction)) { result.goog_noise_reduction.SetExact(ToBoolean(constraint.value_)); - } else if (constraint.name_.Equals(kTestConstraint1) || - constraint.name_.Equals(kTestConstraint2)) { - // These constraints are only for testing parsing. - // Values 0 and 1 are legal, all others are a ConstraintError. - if (!constraint.value_.Equals("0") && !constraint.value_.Equals("1")) { - error_state.ThrowConstraintError("Illegal value for constraint", - constraint.name_); - } } // else: Nothing. Unrecognized constraints are simply ignored. } @@ -256,22 +243,17 @@ static MediaConstraints CreateFromNamedConstraints( ExecutionContext* context, Vector<NameValueStringConstraint>& mandatory, - const Vector<NameValueStringConstraint>& optional, - MediaErrorState& error_state) { + const Vector<NameValueStringConstraint>& optional) { MediaTrackConstraintSetPlatform basic; MediaTrackConstraintSetPlatform advanced; MediaConstraints constraints; - ParseOldStyleNames(context, mandatory, basic, error_state); - if (error_state.HadException()) - return constraints; - // We ignore unknow names and syntax errors in optional constraints. - MediaErrorState ignored_error_state; + ParseOldStyleNames(context, mandatory, basic); + // We ignore unknown names and syntax errors in optional constraints. Vector<MediaTrackConstraintSetPlatform> advanced_vector; for (const auto& optional_constraint : optional) { MediaTrackConstraintSetPlatform advanced_element; Vector<NameValueStringConstraint> element_as_list(1, optional_constraint); - ParseOldStyleNames(context, element_as_list, advanced_element, - ignored_error_state); + ParseOldStyleNames(context, element_as_list, advanced_element); if (!advanced_element.IsUnconstrained()) advanced_vector.push_back(advanced_element); } @@ -367,7 +349,7 @@ DCHECK(!error_state.HadException()); if (str.length() > kMaxConstraintStringLength) { - error_state.ThrowTypeError("Constraint string too long."); + error_state.MarkTypeError("Constraint string too long."); return false; } return true; @@ -378,7 +360,7 @@ DCHECK(!error_state.HadException()); if (strs.size() > kMaxConstraintStringSeqLength) { - error_state.ThrowTypeError("Constraint string sequence too long."); + error_state.MarkTypeError("Constraint string sequence too long."); return false; } @@ -699,7 +681,7 @@ if (constraints_in->hasOptional() || constraints_in->hasMandatory()) { if (!standard_form.IsUnconstrained()) { UseCounter::Count(context, WebFeature::kMediaStreamConstraintsOldAndNew); - error_state.ThrowTypeError( + error_state.MarkTypeError( "Malformed constraint: Cannot use both optional/mandatory and " "specific or advanced constraints."); return MediaConstraints(); @@ -707,12 +689,11 @@ Vector<NameValueStringConstraint> optional; Vector<NameValueStringConstraint> mandatory; if (!Parse(constraints_in, optional, mandatory)) { - error_state.ThrowTypeError("Malformed constraints object."); + error_state.MarkTypeError("Malformed constraints object."); return MediaConstraints(); } UseCounter::Count(context, WebFeature::kMediaStreamConstraintsNameValue); - return CreateFromNamedConstraints(context, mandatory, optional, - error_state); + return CreateFromNamedConstraints(context, mandatory, optional); } UseCounter::Count(context, WebFeature::kMediaStreamConstraintsConformant); return standard_form;
diff --git a/third_party/blink/renderer/modules/mediastream/media_devices.cc b/third_party/blink/renderer/modules/mediastream/media_devices.cc index 5efec04..584586c95 100644 --- a/third_party/blink/renderer/modules/mediastream/media_devices.cc +++ b/third_party/blink/renderer/modules/mediastream/media_devices.cc
@@ -42,7 +42,6 @@ #include "third_party/blink/renderer/modules/mediastream/crop_target.h" #include "third_party/blink/renderer/modules/mediastream/identifiability_metrics.h" #include "third_party/blink/renderer/modules/mediastream/input_device_info.h" -#include "third_party/blink/renderer/modules/mediastream/media_error_state.h" #include "third_party/blink/renderer/modules/mediastream/media_stream.h" #include "third_party/blink/renderer/modules/mediastream/navigator_media_stream.h" #include "third_party/blink/renderer/modules/mediastream/user_media_client.h" @@ -429,24 +428,17 @@ surface_type, TokenFromConstraints(options)); } ScriptPromise promise = resolver->Promise(); - MediaErrorState error_state; UserMediaRequest* request = UserMediaRequest::Create(window, user_media_client, media_type, options, - callbacks, error_state, surface); + callbacks, exception_state, surface); if (!request) { - DCHECK(error_state.HadException()); - if (error_state.CanGenerateException()) { - // TODO(crbug.com/1373398): Change this to use - // ScriptPromiseResolverWithTracker. - error_state.RaiseException(exception_state); - return ScriptPromise(); - } + DCHECK(exception_state.HadException()); + resolver->RecordResultAndLatency( + UserMediaRequestResult::kInvalidConstraints); RecordIdentifiabilityMetric( surface, GetExecutionContext(), - IdentifiabilityBenignStringToken(error_state.GetErrorMessage())); - resolver->Reject(error_state.CreateError(), - UserMediaRequestResult::kInvalidConstraints); - return promise; + IdentifiabilityBenignStringToken(exception_state.Message())); + return ScriptPromise(); } String error_message;
diff --git a/third_party/blink/renderer/modules/mediastream/media_error_state.cc b/third_party/blink/renderer/modules/mediastream/media_error_state.cc index a3a0167..f316dc42 100644 --- a/third_party/blink/renderer/modules/mediastream/media_error_state.cc +++ b/third_party/blink/renderer/modules/mediastream/media_error_state.cc
@@ -30,35 +30,18 @@ #include "third_party/blink/renderer/modules/mediastream/media_error_state.h" -#include "third_party/blink/renderer/bindings/modules/v8/v8_union_domexception_overconstrainederror.h" -#include "third_party/blink/renderer/modules/mediastream/overconstrained_error.h" #include "third_party/blink/renderer/platform/bindings/exception_state.h" #include "third_party/blink/renderer/platform/heap/garbage_collected.h" namespace blink { -MediaErrorState::MediaErrorState() - : error_type_(kNoError), code_(DOMExceptionCode::kNoError) {} +MediaErrorState::MediaErrorState() = default; -void MediaErrorState::ThrowTypeError(const String& message) { +void MediaErrorState::MarkTypeError(const String& message) { error_type_ = kTypeError; message_ = message; } -void MediaErrorState::ThrowDOMException(DOMExceptionCode code, - const String& message) { - error_type_ = kDOMException; - code_ = code; - message_ = message; -} - -void MediaErrorState::ThrowConstraintError(const String& message, - const String& constraint) { - error_type_ = kConstraintError; - message_ = message; - constraint_ = constraint; -} - void MediaErrorState::Reset() { error_type_ = kNoError; } @@ -67,61 +50,14 @@ return error_type_ != kNoError; } -bool MediaErrorState::CanGenerateException() { - return error_type_ == kTypeError || error_type_ == kDOMException; -} - -void MediaErrorState::RaiseException(ExceptionState& target) { - switch (error_type_) { - case kNoError: - NOTREACHED(); - break; - case kTypeError: - target.ThrowTypeError(message_); - break; - case kDOMException: - target.ThrowDOMException(code_, message_); - break; - case kConstraintError: - // This is for the cases where we can't pass back a - // NavigatorUserMediaError. - // So far, we have this in the constructor of RTCPeerConnection, - // which is due to be deprecated. - // TODO(hta): Remove this code. https://crbug.com/576581 - target.ThrowDOMException(DOMExceptionCode::kNotSupportedError, - "Unsatisfiable constraint " + constraint_); - break; - default: - NOTREACHED(); - } +void MediaErrorState::Throw(ExceptionState& target) { + DCHECK_EQ(error_type_, kTypeError); + target.ThrowTypeError(message_); } String MediaErrorState::GetErrorMessage() { - switch (error_type_) { - case kNoError: - NOTREACHED(); - break; - case kTypeError: - case kDOMException: - return message_; - case kConstraintError: - // This is for the cases where we can't pass back a - // NavigatorUserMediaError. - // So far, we have this in the constructor of RTCPeerConnection, - // which is due to be deprecated. - // TODO(hta): Remove this code. https://crbug.com/576581 - return "Unsatisfiable constraint " + constraint_; - default: - NOTREACHED(); - } - - return String(); -} - -V8UnionDOMExceptionOrOverconstrainedError* MediaErrorState::CreateError() { - DCHECK_EQ(error_type_, kConstraintError); - return MakeGarbageCollected<V8UnionDOMExceptionOrOverconstrainedError>( - MakeGarbageCollected<OverconstrainedError>(constraint_, message_)); + DCHECK_EQ(error_type_, kTypeError); + return message_; } } // namespace blink
diff --git a/third_party/blink/renderer/modules/mediastream/media_error_state.h b/third_party/blink/renderer/modules/mediastream/media_error_state.h index 0325944..297323d 100644 --- a/third_party/blink/renderer/modules/mediastream/media_error_state.h +++ b/third_party/blink/renderer/modules/mediastream/media_error_state.h
@@ -32,15 +32,12 @@ #define THIRD_PARTY_BLINK_RENDERER_MODULES_MEDIASTREAM_MEDIA_ERROR_STATE_H_ #include "third_party/blink/renderer/modules/modules_export.h" -#include "third_party/blink/renderer/platform/bindings/exception_code.h" #include "third_party/blink/renderer/platform/wtf/allocator/allocator.h" #include "third_party/blink/renderer/platform/wtf/text/wtf_string.h" namespace blink { class ExceptionState; -class OverconstrainedError; -class V8UnionDOMExceptionOrOverconstrainedError; // A class that is able to be used like ExceptionState for carrying // information about an error up the stack, but it is up to the higher @@ -50,24 +47,18 @@ public: MediaErrorState(); - void ThrowTypeError(const String& message); - void ThrowDOMException(DOMExceptionCode, const String& message); - void ThrowConstraintError(const String& message, const String& constraint); + void MarkTypeError(const String& message); void Reset(); bool HadException(); - bool CanGenerateException(); - void RaiseException(ExceptionState&); + void Throw(ExceptionState&); String GetErrorMessage(); - V8UnionDOMExceptionOrOverconstrainedError* CreateError(); private: - enum ErrorType { kNoError, kTypeError, kDOMException, kConstraintError }; - ErrorType error_type_; + enum ErrorType { kNoError, kTypeError }; + ErrorType error_type_ = kNoError; String name_; - DOMExceptionCode code_; String message_; - String constraint_; }; } // namespace blink
diff --git a/third_party/blink/renderer/modules/mediastream/navigator_media_stream.cc b/third_party/blink/renderer/modules/mediastream/navigator_media_stream.cc index feb2f2f..f81568eb 100644 --- a/third_party/blink/renderer/modules/mediastream/navigator_media_stream.cc +++ b/third_party/blink/renderer/modules/mediastream/navigator_media_stream.cc
@@ -35,7 +35,6 @@ #include "third_party/blink/renderer/core/frame/settings.h" #include "third_party/blink/renderer/core/page/page.h" #include "third_party/blink/renderer/modules/mediastream/identifiability_metrics.h" -#include "third_party/blink/renderer/modules/mediastream/media_error_state.h" #include "third_party/blink/renderer/modules/mediastream/user_media_client.h" #include "third_party/blink/renderer/modules/mediastream/user_media_request.h" #include "third_party/blink/renderer/platform/bindings/exception_state.h" @@ -105,23 +104,17 @@ surface = IdentifiableSurface::FromTypeAndToken( surface_type, TokenFromConstraints(options)); } - MediaErrorState error_state; + UserMediaRequest* request = UserMediaRequest::Create( navigator.DomWindow(), user_media, UserMediaRequestType::kUserMedia, options, MakeGarbageCollected<V8Callbacks>(success_callback, error_callback), - error_state, surface); + exception_state, surface); if (!request) { - DCHECK(error_state.HadException()); - if (error_state.CanGenerateException()) { - error_state.RaiseException(exception_state); - } else { - error_callback->InvokeAndReportException(nullptr, - error_state.CreateError()); - } + DCHECK(exception_state.HadException()); RecordIdentifiabilityMetric( surface, navigator.GetExecutionContext(), - IdentifiabilityBenignStringToken(error_state.GetErrorMessage())); + IdentifiabilityBenignStringToken(exception_state.Message())); return; }
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 fd73b86..ceb32c1 100644 --- a/third_party/blink/renderer/modules/mediastream/user_media_request.cc +++ b/third_party/blink/renderer/modules/mediastream/user_media_request.cc
@@ -385,7 +385,7 @@ MediaConstraints ParseOptions( ExecutionContext* execution_context, const V8UnionBooleanOrMediaTrackConstraints* options, - MediaErrorState& error_state) { + ExceptionState& exception_state) { if (!options) return MediaConstraints(); switch (options->GetContentType()) { @@ -396,9 +396,14 @@ return MediaConstraints(); case V8UnionBooleanOrMediaTrackConstraints::ContentType:: kMediaTrackConstraints: - return media_constraints_impl::Create( + MediaErrorState error_state; + auto constraints = media_constraints_impl::Create( execution_context, options->GetAsMediaTrackConstraints(), error_state); + if (error_state.HadException()) { + error_state.Throw(exception_state); + } + return constraints; } NOTREACHED(); return MediaConstraints(); @@ -412,29 +417,33 @@ UserMediaRequestType media_type, const MediaStreamConstraints* options, Callbacks* callbacks, - MediaErrorState& error_state, + ExceptionState& exception_state, IdentifiableSurface surface) { - MediaConstraints audio = ParseOptions(context, options->audio(), error_state); - if (error_state.HadException()) + MediaConstraints audio = + ParseOptions(context, options->audio(), exception_state); + if (exception_state.HadException()) { return nullptr; + } - MediaConstraints video = ParseOptions(context, options->video(), error_state); - if (error_state.HadException()) + MediaConstraints video = + ParseOptions(context, options->video(), exception_state); + if (exception_state.HadException()) { return nullptr; + } std::string display_surface_constraint; absl::optional<bool> suppress_local_audio_playback; if (media_type == UserMediaRequestType::kUserMedia) { if (audio.IsNull() && video.IsNull()) { - error_state.ThrowTypeError( + exception_state.ThrowTypeError( "At least one of audio and video must be requested"); return nullptr; } else if (!video.IsNull()) { if (video.Basic().pan.HasMandatory() || video.Basic().tilt.HasMandatory() || video.Basic().zoom.HasMandatory()) { - error_state.ThrowTypeError( + exception_state.ThrowTypeError( "Mandatory pan-tilt-zoom constraints are not supported"); return nullptr; } @@ -456,33 +465,33 @@ // either a dictionary value or a value of true. if (media_type == UserMediaRequestType::kDisplayMediaSet) { if (!audio.IsNull()) { - error_state.ThrowTypeError("Audio requests are not supported"); + exception_state.ThrowTypeError("Audio requests are not supported"); return nullptr; } else if (options->preferCurrentTab()) { - error_state.ThrowTypeError("preferCurrentTab is not supported"); + exception_state.ThrowTypeError("preferCurrentTab is not supported"); return nullptr; } } if (video.IsNull()) { - error_state.ThrowTypeError("video must be requested"); + exception_state.ThrowTypeError("video must be requested"); return nullptr; } if ((!audio.IsNull() && !audio.Advanced().empty()) || !video.Advanced().empty()) { - error_state.ThrowTypeError("Advanced constraints are not supported"); + exception_state.ThrowTypeError("Advanced constraints are not supported"); return nullptr; } if ((!audio.IsNull() && audio.Basic().HasMin()) || video.Basic().HasMin()) { - error_state.ThrowTypeError("min constraints are not supported"); + exception_state.ThrowTypeError("min constraints are not supported"); return nullptr; } if ((!audio.IsNull() && audio.Basic().HasExact()) || video.Basic().HasExact()) { - error_state.ThrowTypeError("exact constraints are not supported"); + exception_state.ThrowTypeError("exact constraints are not supported"); return nullptr; } @@ -524,7 +533,7 @@ options->selfBrowserSurface().AsEnum() == V8SelfCapturePreferenceEnum::Enum::kExclude; if (exclude_self_browser_surface && options->preferCurrentTab()) { - error_state.ThrowTypeError( + exception_state.ThrowTypeError( "Self-contradictory configuration (preferCurrentTab and " "selfBrowserSurface=exclude)."); return nullptr;
diff --git a/third_party/blink/renderer/modules/mediastream/user_media_request.h b/third_party/blink/renderer/modules/mediastream/user_media_request.h index 6adde6a7..8bff462 100644 --- a/third_party/blink/renderer/modules/mediastream/user_media_request.h +++ b/third_party/blink/renderer/modules/mediastream/user_media_request.h
@@ -45,7 +45,6 @@ namespace blink { class LocalDOMWindow; -class MediaErrorState; class MediaStreamConstraints; class ScriptWrappable; class TransferredMediaStreamTrack; @@ -98,7 +97,7 @@ UserMediaRequestType media_type, const MediaStreamConstraints* options, Callbacks*, - MediaErrorState&, + ExceptionState&, IdentifiableSurface surface); static UserMediaRequest* CreateForTesting(const MediaConstraints& audio, const MediaConstraints& video);
diff --git a/third_party/blink/renderer/modules/mediastream/webmediaplayer_ms.cc b/third_party/blink/renderer/modules/mediastream/webmediaplayer_ms.cc index f78877e4..de62aaa 100644 --- a/third_party/blink/renderer/modules/mediastream/webmediaplayer_ms.cc +++ b/third_party/blink/renderer/modules/mediastream/webmediaplayer_ms.cc
@@ -414,9 +414,6 @@ if (frame_deliverer_) video_task_runner_->DeleteSoon(FROM_HERE, frame_deliverer_.release()); - if (compositor_) - compositor_->StopUsingProvider(); - if (video_frame_provider_) video_frame_provider_->Stop();
diff --git a/third_party/blink/renderer/modules/mediastream/webmediaplayer_ms_compositor.cc b/third_party/blink/renderer/modules/mediastream/webmediaplayer_ms_compositor.cc index 08cf1df..cbde42a 100644 --- a/third_party/blink/renderer/modules/mediastream/webmediaplayer_ms_compositor.cc +++ b/third_party/blink/renderer/modules/mediastream/webmediaplayer_ms_compositor.cc
@@ -239,13 +239,8 @@ WebMediaPlayerMSCompositor::~WebMediaPlayerMSCompositor() { // Ensured by destructor traits. DCHECK(video_frame_compositor_task_runner_->BelongsToCurrentThread()); - - if (submitter_) { - video_frame_compositor_task_runner_->DeleteSoon(FROM_HERE, - std::move(submitter_)); - } else { - DCHECK(!video_frame_provider_client_) - << "Must call StopUsingProvider() before dtor!"; + if (video_frame_provider_client_) { + video_frame_provider_client_->StopUsingProvider(); } } @@ -265,7 +260,7 @@ void WebMediaPlayerMSCompositor::InitializeSubmitter() { DCHECK(video_frame_compositor_task_runner_->BelongsToCurrentThread()); - submitter_->Initialize(this, /* is_media_stream = */ true); + submitter_->Initialize(this, /*is_media_stream=*/true); } void WebMediaPlayerMSCompositor::SetIsSurfaceVisible( @@ -632,15 +627,6 @@ WrapRefCounted(this)))); } -void WebMediaPlayerMSCompositor::StopUsingProvider() { - DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); - PostCrossThreadTask( - *video_frame_compositor_task_runner_, FROM_HERE, - CrossThreadBindOnce( - &WebMediaPlayerMSCompositor::StopUsingProviderInternal, - WrapRefCounted(this))); -} - bool WebMediaPlayerMSCompositor::MapTimestampsToRenderTimeTicks( const std::vector<base::TimeDelta>& timestamps, std::vector<base::TimeTicks>* wall_clock_times) { @@ -874,13 +860,6 @@ video_frame_provider_client_->StopRendering(); } -void WebMediaPlayerMSCompositor::StopUsingProviderInternal() { - DCHECK(video_frame_compositor_task_runner_->BelongsToCurrentThread()); - if (video_frame_provider_client_) - video_frame_provider_client_->StopUsingProvider(); - video_frame_provider_client_ = nullptr; -} - void WebMediaPlayerMSCompositor::ReplaceCurrentFrameWithACopyInternal() { DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); scoped_refptr<media::VideoFrame> current_frame_ref;
diff --git a/third_party/blink/renderer/modules/mediastream/webmediaplayer_ms_compositor.h b/third_party/blink/renderer/modules/mediastream/webmediaplayer_ms_compositor.h index 5b75c4aa..735de30 100644 --- a/third_party/blink/renderer/modules/mediastream/webmediaplayer_ms_compositor.h +++ b/third_party/blink/renderer/modules/mediastream/webmediaplayer_ms_compositor.h
@@ -121,10 +121,6 @@ void StopRendering(); void ReplaceCurrentFrameWithACopy(); - // Tell |video_frame_provider_client_| to stop using this instance in - // preparation for dtor. - void StopUsingProvider(); - // Sets a hook to be notified when a new frame is presented, to fulfill a // prending video.requestAnimationFrame() request. // Can be called from any thread. @@ -210,7 +206,6 @@ void StartRenderingInternal(); void StopRenderingInternal(); - void StopUsingProviderInternal(); void ReplaceCurrentFrameWithACopyInternal(); void SetAlgorithmEnabledForTesting(bool algorithm_enabled);
diff --git a/third_party/blink/web_tests/TestExpectations b/third_party/blink/web_tests/TestExpectations index 93008247..7d4cd2d 100644 --- a/third_party/blink/web_tests/TestExpectations +++ b/third_party/blink/web_tests/TestExpectations
@@ -4417,6 +4417,7 @@ # DevTools roll crbug.com/1006759 http/tests/devtools/elements/styles-1/edit-value-url-with-color.js [ Crash Failure Pass Timeout ] +crbug.com/1400243 http/tests/devtools/extensions/extensions-api.js [ Failure Pass ] #Mixed content autoupgrades make these tests not applicable, since they check for mixed content audio/video crbug.com/1025274 external/wpt/mixed-content/gen/top.meta/unset/audio-tag.https.html [ Failure ] @@ -5266,9 +5267,6 @@ crbug.com/1237640 http/tests/inspector-protocol/network/disabled-cache-navigation.js [ Pass Timeout ] crbug.com/1239175 http/tests/navigation/same-and-different-back.html [ Failure Pass ] crbug.com/1239164 http/tests/inspector-protocol/network/navigate-iframe-in2in.js [ Failure Pass ] -crbug.com/1237909 external/wpt/webrtc-svc/RTCRtpParameters-scalability.html [ Crash Failure Pass Timeout ] -crbug.com/1237909 [ Mac11-arm64 Release ] external/wpt/webrtc-svc/RTCRtpParameters-scalability-h264.html [ Crash Pass Timeout ] -crbug.com/1237909 [ Mac12-arm64 Release ] external/wpt/webrtc-svc/RTCRtpParameters-scalability-h264.html [ Crash Pass Timeout ] # Sheriff 2021-08-19 crbug.com/1234315 [ Mac ] virtual/threaded/external/wpt/animation-worklet/worklet-animation-with-scroll-timeline-and-overflow-hidden.https.html [ Failure Timeout ]
diff --git a/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_8.json b/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_8.json index 3441b7b..0e391e3 100644 --- a/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_8.json +++ b/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_8.json
@@ -121933,7 +121933,7 @@ ] ], "font-synthesis-style-first-letter.html": [ - "32f037bdb721dc3315787d37dce5c66b7cf1e37c", + "1296baf05f9c430cda6c52e5a63e813c79862b9a", [ null, [ @@ -121985,7 +121985,7 @@ ] ], "font-synthesis-weight-first-letter.html": [ - "52865540a791189e0adf89fb17f8b43fb830334b", + "3d60b2a22847e1b99f1ee53d27c231a41bbc60da", [ null, [ @@ -263212,16 +263212,6 @@ } }, "support": { - ".cache": { - "gitignore2.json": [ - "0049f3e5247e64b948c41f5edbd906d61b954feb", - [] - ], - "mtime.json": [ - "0c7d29e6a9cdda61de450dcb3990409922abc5b9", - [] - ] - }, ".gitignore": [ "d93e645d547894b50149d3726de2654957b6e06f", [] @@ -292001,7 +291991,7 @@ [] ], "font-synthesis-style-first-letter-ref.html": [ - "f1abcb7bb3f4681ecfc2b135dc5ca6dd6b6b9896", + "c4f9b175de79052ad0f29df3be07a33c1072f245", [] ], "font-synthesis-style-first-line-ref.html": [ @@ -292017,7 +292007,7 @@ [] ], "font-synthesis-weight-first-letter-ref.html": [ - "7ec42f07b7c2bfbd329534bc8401acb5ee33d5f2", + "fd29f4134d67d22b6e344dcc7ec8315a32dac2e5", [] ], "font-synthesis-weight-first-line-ref.html": [ @@ -347844,7 +347834,7 @@ [] ], "rc-helper.js": [ - "ef0da2da1ed8f9684637d48991b2ea9abfb986c9", + "7b9e83a1f42afeadf1705607465b8c65aaaeb721", [] ], "service-worker.js": [ @@ -370766,11 +370756,11 @@ [] ], "early-hints-helpers.sub.js": [ - "3991e8fe9da479dd0f5b4cea537542ba948a8540", + "faf6119cf1edd5454aaabd643ffe40d33c589463", [] ], "early-hints-test-loader.h2.py": [ - "aa9188c6dbc3920055095421bc880ec5595b0bbb", + "bb987209c50f41f260cc7e5d43901c8777ee8779", [] ], "empty-corp-absent.js": [ @@ -370789,6 +370779,14 @@ "1738466bcb3225df2ec2dc7f58459784ce426a84", [] ], + "empty.json": [ + "0967ef424bce6791893e9a57bb952f80fd536e93", + [] + ], + "empty.json.headers": [ + "1738466bcb3225df2ec2dc7f58459784ce426a84", + [] + ], "example.pdf": [ "7bad251ba7e08efc9c383d88518367a6f204cdd6", [] @@ -370849,6 +370847,10 @@ "daea33160ae4e7b7bd074fb1849f1ba40ce8e715", [] ], + "preload-fetch.html": [ + "2e90f76af143fce7f05eb77bb71f00e0d880ff88", + [] + ], "preload-finished-before-final-response.h2.py": [ "d0b12408d9cff01352380cec5727145dbed6dded", [] @@ -402873,7 +402875,7 @@ ] ], "back-forward-cache-open-connection.window.js": [ - "397eadab8763fe884f877da63b75a06c8dcc023a", + "10c8482230938d4e4e917d43b924710bd913e574", [ "IndexedDB/back-forward-cache-open-connection.window.html", { @@ -562980,6 +562982,20 @@ } ] ], + "preload-fetch.h2.window.js": [ + "121913ed3175d552acf83ba3cb4b19e54b51d75a", + [ + "loading/early-hints/preload-fetch.h2.window.html", + { + "script_metadata": [ + [ + "script", + "resources/early-hints-helpers.sub.js" + ] + ] + } + ] + ], "preload-finished-before-final-response.h2.window.js": [ "c63239be1f59426b35760c26ef47453c9248c59b", [
diff --git a/third_party/blink/web_tests/external/wpt/css/css-fonts/font-synthesis-style-first-letter-ref.html b/third_party/blink/web_tests/external/wpt/css/css-fonts/font-synthesis-style-first-letter-ref.html index f1abcb7..c4f9b17 100644 --- a/third_party/blink/web_tests/external/wpt/css/css-fonts/font-synthesis-style-first-letter-ref.html +++ b/third_party/blink/web_tests/external/wpt/css/css-fonts/font-synthesis-style-first-letter-ref.html
@@ -10,6 +10,7 @@ .test { font-family: "Lato-Medium"; font-size: 3em; + font-kerning: none; } </style>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-fonts/font-synthesis-style-first-letter.html b/third_party/blink/web_tests/external/wpt/css/css-fonts/font-synthesis-style-first-letter.html index 32f037bd..1296baf 100644 --- a/third_party/blink/web_tests/external/wpt/css/css-fonts/font-synthesis-style-first-letter.html +++ b/third_party/blink/web_tests/external/wpt/css/css-fonts/font-synthesis-style-first-letter.html
@@ -16,6 +16,7 @@ .test { font-family: "Lato-Medium"; font-size: 3em; + font-kerning: none; } p.nosynth::first-letter { font-style: italic;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-fonts/font-synthesis-weight-first-letter-ref.html b/third_party/blink/web_tests/external/wpt/css/css-fonts/font-synthesis-weight-first-letter-ref.html index 7ec42f0..fd29f413 100644 --- a/third_party/blink/web_tests/external/wpt/css/css-fonts/font-synthesis-weight-first-letter-ref.html +++ b/third_party/blink/web_tests/external/wpt/css/css-fonts/font-synthesis-weight-first-letter-ref.html
@@ -10,6 +10,7 @@ .test { font-family: "Lato-Medium"; font-size: 3em; + font-kerning: none; } </style>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-fonts/font-synthesis-weight-first-letter.html b/third_party/blink/web_tests/external/wpt/css/css-fonts/font-synthesis-weight-first-letter.html index 5286554..3d60b2a2 100644 --- a/third_party/blink/web_tests/external/wpt/css/css-fonts/font-synthesis-weight-first-letter.html +++ b/third_party/blink/web_tests/external/wpt/css/css-fonts/font-synthesis-weight-first-letter.html
@@ -16,6 +16,7 @@ .test { font-family: "Lato-Medium"; font-size: 3em; + font-kerning: none; } p.nosynth::first-letter { font-weight: bold;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-nesting/nest-containing-forgiving-ref.html b/third_party/blink/web_tests/external/wpt/css/css-nesting/nest-containing-forgiving-ref.html new file mode 100644 index 0000000..36b07c92 --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/css/css-nesting/nest-containing-forgiving-ref.html
@@ -0,0 +1,18 @@ +<!DOCTYPE html> +<title>Nest-containing in forgiving parsing</title> +<style> + .test { + background-color: green; + width: 100px; + height: 100px; + display: grid; + } + + body * + * { + margin-top: 8px; + } +</style> +<body> + <p>Tests pass if <strong>block is green</strong></p> + <div class="test"></div> +</body>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-nesting/nest-containing-forgiving.html b/third_party/blink/web_tests/external/wpt/css/css-nesting/nest-containing-forgiving.html new file mode 100644 index 0000000..d399142 --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/css/css-nesting/nest-containing-forgiving.html
@@ -0,0 +1,27 @@ +<!DOCTYPE html> +<title>Nest-containing in forgiving parsing</title> +<link rel="author" title="Steinar H. Gunderson" href="mailto:sesse@chromium.org"> +<link rel="help" href="https://drafts.csswg.org/css-nesting-1/"> +<link rel="match" href="nest-containing-forgiving-ref.html"> +<style> + .test { + background-color: red; + width: 100px; + height: 100px; + display: grid; + } + + .does-not-exist { + :is(.test-1, !&) { + background-color: green; + } + } + + body * + * { + margin-top: 8px; + } +</style> +<body> + <p>Tests pass if <strong>block is green</strong></p> + <div class="test test-1"></div> +</body>
diff --git a/third_party/blink/web_tests/external/wpt/webrtc-extensions/RTCRtpParameters-adaptivePtime.html b/third_party/blink/web_tests/external/wpt/webrtc-extensions/RTCRtpParameters-adaptivePtime.html index a0cc989..8a7a8b6b 100644 --- a/third_party/blink/web_tests/external/wpt/webrtc-extensions/RTCRtpParameters-adaptivePtime.html +++ b/third_party/blink/web_tests/external/wpt/webrtc-extensions/RTCRtpParameters-adaptivePtime.html
@@ -6,12 +6,6 @@ <script> 'use strict'; - function getFirstEncoding(param) { - const { encodings } = param; - assert_equals(encodings.length, 1); - return encodings[0]; - } - promise_test(async t => { const pc = new RTCPeerConnection(); t.add_cleanup(() => pc.close()); @@ -20,14 +14,14 @@ }); let param = sender.getParameters(); - let encoding = getFirstEncoding(param); + let encoding = param.encodings[0]; assert_true(encoding.adaptivePtime); encoding.adaptivePtime = false; await sender.setParameters(param); param = sender.getParameters(); - encoding = getFirstEncoding(param); + encoding = param.encodings[0]; assert_false(encoding.adaptivePtime); @@ -39,7 +33,7 @@ const { sender } = pc.addTransceiver('audio', { sendEncodings: [{}] }); const param = sender.getParameters(); - const encoding = getFirstEncoding(param); + const encoding = param.encodings[0]; assert_false(encoding.adaptivePtime);
diff --git a/third_party/blink/web_tests/external/wpt/webrtc-priority/RTCRtpParameters-encodings-expected.txt b/third_party/blink/web_tests/external/wpt/webrtc-priority/RTCRtpParameters-encodings-expected.txt deleted file mode 100644 index 0d37e5e..0000000 --- a/third_party/blink/web_tests/external/wpt/webrtc-priority/RTCRtpParameters-encodings-expected.txt +++ /dev/null
@@ -1,10 +0,0 @@ -This is a testharness.js-based test. -FAIL sender.getParameters() should return sendEncodings set by addTransceiver() promise_test: Unhandled rejection with value: object "ReferenceError: getFirstEncoding is not defined" -PASS setParameters() with modified encoding.active should succeed with RTCRtpTransceiverInit -PASS setParameters() with modified encoding.active should succeed without RTCRtpTransceiverInit -PASS setParameters() with modified encoding.priority should succeed with RTCRtpTransceiverInit -PASS setParameters() with modified encoding.priority should succeed without RTCRtpTransceiverInit -PASS setParameters() with modified encoding.networkPriority should succeed with RTCRtpTransceiverInit -PASS setParameters() with modified encoding.networkPriority should succeed without RTCRtpTransceiverInit -Harness: the test ran to completion. -
diff --git a/third_party/blink/web_tests/external/wpt/webrtc-priority/RTCRtpParameters-encodings.html b/third_party/blink/web_tests/external/wpt/webrtc-priority/RTCRtpParameters-encodings.html index 37c1b91..1519ee8 100644 --- a/third_party/blink/web_tests/external/wpt/webrtc-priority/RTCRtpParameters-encodings.html +++ b/third_party/blink/web_tests/external/wpt/webrtc-priority/RTCRtpParameters-encodings.html
@@ -25,7 +25,7 @@ const param = sender.getParameters(); validateSenderRtpParameters(param); - const encoding = getFirstEncoding(param); + const encoding = param.encodings[0]; assert_equals(encoding.active, false); assert_equals(encoding.priority, 'low');
diff --git a/third_party/blink/web_tests/external/wpt/webrtc-svc/RTCRtpParameters-scalability-expected.txt b/third_party/blink/web_tests/external/wpt/webrtc-svc/RTCRtpParameters-scalability-expected.txt deleted file mode 100644 index 816a2534..0000000 --- a/third_party/blink/web_tests/external/wpt/webrtc-svc/RTCRtpParameters-scalability-expected.txt +++ /dev/null
@@ -1,10 +0,0 @@ -This is a testharness.js-based test. -PASS Setting and updating scalabilityMode to a legal value should be accepted -PASS Sender capabilities should include at least some scalability modes -PASS Not setting sendEncodings results in no mode info before negotiation -PASS Not setting a scalability mode results in no mode set before negotiation -FAIL Not setting a scalability mode results in some mode set after negotiation assert_true: expected true got false -PASS Setting a scalability mode to nonsense throws an exception -FAIL L3T3 on VP8 should return something other than L3T3 assert_not_equals: got disallowed value "L3T3" -Harness: the test ran to completion. -
diff --git a/third_party/blink/web_tests/external/wpt/webrtc-svc/RTCRtpParameters-scalability.html b/third_party/blink/web_tests/external/wpt/webrtc-svc/RTCRtpParameters-scalability.html index d8b1c6e..ff28c2b5e 100644 --- a/third_party/blink/web_tests/external/wpt/webrtc-svc/RTCRtpParameters-scalability.html +++ b/third_party/blink/web_tests/external/wpt/webrtc-svc/RTCRtpParameters-scalability.html
@@ -21,7 +21,7 @@ }); const param = sender.getParameters(); - const encoding = getFirstEncoding(param); + const encoding = param.encodings[0]; assert_equals(encoding.scalabilityMode, 'L1T3'); @@ -29,28 +29,18 @@ await sender.setParameters(param); const updatedParam = sender.getParameters(); - const updatedEncoding = getFirstEncoding(updatedParam); + const updatedEncoding = updatedParam.encodings[0]; assert_equals(updatedEncoding.scalabilityMode, 'L1T2'); }, `Setting and updating scalabilityMode to a legal value should be accepted`); promise_test(async t => { - const capabilities = RTCRtpSender.getCapabilities('video'); - var svcSupported = false; - for (const codec of capabilities.codecs) { - if ('scalabilityModes' in codec && codec.scalabilityModes.length > 0) { - svcSupported = true; - } - } - assert_true(svcSupported); - }, `Sender capabilities should include at least some scalability modes`); - - promise_test(async t => { const pc = new RTCPeerConnection(); t.add_cleanup(() => pc.close()); const { sender } = pc.addTransceiver('video'); const param = sender.getParameters(); - assert_equals(param.encodings.length, 0); + const encoding = param.encodings[0]; + assert_true(!('scalabilityMode' in encoding)); }, 'Not setting sendEncodings results in no mode info before negotiation'); promise_test(async t => { @@ -60,30 +50,11 @@ sendEncodings: [{}], }); const param = sender.getParameters(); - const encoding = getFirstEncoding(param); - + const encoding = param.encodings[0]; assert_true(!('scalabilityMode' in encoding)); }, 'Not setting a scalability mode results in no mode set before negotiation'); promise_test(async t => { - const pc1 = new RTCPeerConnection(); - const pc2 = new RTCPeerConnection(); - t.add_cleanup(() => pc1.close()); - t.add_cleanup(() => pc2.close()); - const { sender } = pc1.addTransceiver('video', { - sendEncodings: [{}], - }); - const param = sender.getParameters(); - const encoding = getFirstEncoding(param); - - exchangeIceCandidates(pc1, pc2); - await exchangeOfferAnswer(pc1, pc2); - const param2 = sender.getParameters(); - const encoding2 = getFirstEncoding(param); - assert_true('scalabilityMode' in encoding2); - }, 'Not setting a scalability mode results in some mode set after negotiation'); - - promise_test(async t => { const pc = new RTCPeerConnection(); t.add_cleanup(() => pc.close()); assert_throws_dom('OperationError', () => { @@ -105,7 +76,7 @@ }); // Before negotiation, the mode should be preserved. const param = transceiver.sender.getParameters(); - const encoding = getFirstEncoding(param); + const encoding = param.encodings[0]; assert_true('scalabilityMode' in encoding); // If L3T3 is not supported at all, abort test. assert_implements_optional(encoding.scalabilityMode === 'L3T3');
diff --git a/third_party/blink/web_tests/fast/css/invalidation/any-link-pseudo-expected.txt b/third_party/blink/web_tests/fast/css/invalidation/any-link-pseudo-expected.txt index 1db80ae..d721354 100644 --- a/third_party/blink/web_tests/fast/css/invalidation/any-link-pseudo-expected.txt +++ b/third_party/blink/web_tests/fast/css/invalidation/any-link-pseudo-expected.txt
@@ -3,7 +3,7 @@ On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE". PASS getComputedStyle(link).backgroundColor is red -PASS internals.updateStyleAndReturnAffectedElementCount() is 2 +PASS internals.updateStyleAndReturnAffectedElementCount() is 1 PASS getComputedStyle(link).backgroundColor is green PASS successfullyParsed is true
diff --git a/third_party/blink/web_tests/fast/css/invalidation/any-link-pseudo.html b/third_party/blink/web_tests/fast/css/invalidation/any-link-pseudo.html index 615de079..5abc1668 100644 --- a/third_party/blink/web_tests/fast/css/invalidation/any-link-pseudo.html +++ b/third_party/blink/web_tests/fast/css/invalidation/any-link-pseudo.html
@@ -24,7 +24,7 @@ link.href = "not-visited.html"; if (window.internals) - shouldBe("internals.updateStyleAndReturnAffectedElementCount()", "2"); + shouldBe("internals.updateStyleAndReturnAffectedElementCount()", "1"); shouldBe("getComputedStyle(link).backgroundColor", "green"); </script>
diff --git a/third_party/blink/web_tests/fast/css/invalidation/link-pseudo-expected.txt b/third_party/blink/web_tests/fast/css/invalidation/link-pseudo-expected.txt index 2686b7a..80aa8db 100644 --- a/third_party/blink/web_tests/fast/css/invalidation/link-pseudo-expected.txt +++ b/third_party/blink/web_tests/fast/css/invalidation/link-pseudo-expected.txt
@@ -3,7 +3,7 @@ On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE". PASS internals.computedStyleIncludingVisitedInfo(match).backgroundColor is red -PASS internals.updateStyleAndReturnAffectedElementCount() is 6 +PASS internals.updateStyleAndReturnAffectedElementCount() is 5 PASS internals.computedStyleIncludingVisitedInfo(match).backgroundColor is green PASS successfullyParsed is true
diff --git a/third_party/blink/web_tests/fast/css/invalidation/link-pseudo.html b/third_party/blink/web_tests/fast/css/invalidation/link-pseudo.html index 5919793..c2f609fb 100644 --- a/third_party/blink/web_tests/fast/css/invalidation/link-pseudo.html +++ b/third_party/blink/web_tests/fast/css/invalidation/link-pseudo.html
@@ -38,7 +38,7 @@ if (window.internals) { // Ideally, the affected element count should be 2, but there is a _insideLink that // causes inheritance to recalc all elements inside <a>. - shouldBe("internals.updateStyleAndReturnAffectedElementCount()", "6"); + shouldBe("internals.updateStyleAndReturnAffectedElementCount()", "5"); shouldBe("internals.computedStyleIncludingVisitedInfo(match).backgroundColor", "green"); }
diff --git a/third_party/blink/web_tests/fast/css/invalidation/visited-pseudo-expected.txt b/third_party/blink/web_tests/fast/css/invalidation/visited-pseudo-expected.txt index 5b7de406..8b9254c 100644 --- a/third_party/blink/web_tests/fast/css/invalidation/visited-pseudo-expected.txt +++ b/third_party/blink/web_tests/fast/css/invalidation/visited-pseudo-expected.txt
@@ -3,7 +3,7 @@ On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE". PASS internals.computedStyleIncludingVisitedInfo(match).backgroundColor is red -PASS internals.updateStyleAndReturnAffectedElementCount() is 6 +PASS internals.updateStyleAndReturnAffectedElementCount() is 5 PASS internals.computedStyleIncludingVisitedInfo(match).backgroundColor is green PASS successfullyParsed is true
diff --git a/third_party/blink/web_tests/fast/css/invalidation/visited-pseudo.html b/third_party/blink/web_tests/fast/css/invalidation/visited-pseudo.html index b0b9b9a..8c3af56 100644 --- a/third_party/blink/web_tests/fast/css/invalidation/visited-pseudo.html +++ b/third_party/blink/web_tests/fast/css/invalidation/visited-pseudo.html
@@ -38,7 +38,7 @@ if (window.internals) { // Ideally, the affected element count should be 2, but there is a _insideLink that // causes inheritance to recalc all elements inside <a>. - shouldBe("internals.updateStyleAndReturnAffectedElementCount()", "6"); + shouldBe("internals.updateStyleAndReturnAffectedElementCount()", "5"); shouldBe("internals.computedStyleIncludingVisitedInfo(match).backgroundColor", "green"); }
diff --git a/third_party/blink/web_tests/fast/css/invalidation/webkit-any-link-pseudo-expected.txt b/third_party/blink/web_tests/fast/css/invalidation/webkit-any-link-pseudo-expected.txt index c8584e1..51ba955 100644 --- a/third_party/blink/web_tests/fast/css/invalidation/webkit-any-link-pseudo-expected.txt +++ b/third_party/blink/web_tests/fast/css/invalidation/webkit-any-link-pseudo-expected.txt
@@ -3,7 +3,7 @@ On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE". PASS getComputedStyle(link).backgroundColor is red -PASS internals.updateStyleAndReturnAffectedElementCount() is 2 +PASS internals.updateStyleAndReturnAffectedElementCount() is 1 PASS getComputedStyle(link).backgroundColor is green PASS successfullyParsed is true
diff --git a/third_party/blink/web_tests/fast/css/invalidation/webkit-any-link-pseudo.html b/third_party/blink/web_tests/fast/css/invalidation/webkit-any-link-pseudo.html index 1dad263..b9daa2e 100644 --- a/third_party/blink/web_tests/fast/css/invalidation/webkit-any-link-pseudo.html +++ b/third_party/blink/web_tests/fast/css/invalidation/webkit-any-link-pseudo.html
@@ -24,7 +24,7 @@ link.href = "not-visited.html"; if (window.internals) - shouldBe("internals.updateStyleAndReturnAffectedElementCount()", "2"); + shouldBe("internals.updateStyleAndReturnAffectedElementCount()", "1"); shouldBe("getComputedStyle(link).backgroundColor", "green"); </script>
diff --git a/third_party/blink/web_tests/http/tests/devtools/extensions/extensions-api-expected.txt b/third_party/blink/web_tests/http/tests/devtools/extensions/extensions-api-expected.txt index 974a354..e3a66ff 100644 --- a/third_party/blink/web_tests/http/tests/devtools/extensions/extensions-api-expected.txt +++ b/third_party/blink/web_tests/http/tests/devtools/extensions/extensions-api-expected.txt
@@ -70,6 +70,7 @@ themeName : "themeNameForTest" } recorder : { + createView : <function> registerRecorderExtensionPlugin : <function> unregisterRecorderExtensionPlugin : <function> }
diff --git a/third_party/blink/web_tests/wpt_internal/css/css-pseudo/target-text-001.html b/third_party/blink/web_tests/wpt_internal/css/css-pseudo/target-text-001.html deleted file mode 100644 index 7f7bda7..0000000 --- a/third_party/blink/web_tests/wpt_internal/css/css-pseudo/target-text-001.html +++ /dev/null
@@ -1,22 +0,0 @@ -<!doctype html> -<meta charset="utf-8"> -<title>CSS Pseudo-Elements Test: ::target-text - ensure links are distinct when highlighted.</title> -<link rel="help" href="https://drafts.csswg.org/css-pseudo/#selectordef-target-text"> -<link rel="match" href="target-text-lime-green-link-ref.html"> -<link rel="stylesheet" type="text/css" href="/fonts/ahem.css"> -<style> - .ahem { font-family: Ahem; } - :any-link { - /* The default ::target-text should add back in underlines for links */ - text-decoration: none; - } - :any-link::target-text { - color: lime; - background-color: green; - } -</style> -<p>PASS if there are two segments of lime squares with an underlined green square between below.</p> -<a class="ahem" href="example.html"> match me </a> -<script> - window.location.hash = "#:~:text=match%20me"; -</script>
diff --git a/third_party/blink/web_tests/wpt_internal/css/css-pseudo/target-text-001.html.ini b/third_party/blink/web_tests/wpt_internal/css/css-pseudo/target-text-001.html.ini deleted file mode 100644 index 8254e87..0000000 --- a/third_party/blink/web_tests/wpt_internal/css/css-pseudo/target-text-001.html.ini +++ /dev/null
@@ -1,3 +0,0 @@ -[target-text-001.html] - expected: - if flag_specific == "disable-layout-ng": FAIL
diff --git a/third_party/blink/web_tests/wpt_internal/css/css-pseudo/target-text-002.html b/third_party/blink/web_tests/wpt_internal/css/css-pseudo/target-text-002.html deleted file mode 100644 index 36c3f17..0000000 --- a/third_party/blink/web_tests/wpt_internal/css/css-pseudo/target-text-002.html +++ /dev/null
@@ -1,23 +0,0 @@ -<!doctype html> -<meta charset="utf-8"> -<title>CSS Pseudo-Elements Test: ::target-text - This test ensures link style can be controlled by author.</title> -<link rel="help" href="https://drafts.csswg.org/css-pseudo/#selectordef-target-text"> -<link rel="match" href="target-text-lime-green-link-no-underline-ref.html"> -<link rel="stylesheet" type="text/css" href="/fonts/ahem.css"> -<style> - .ahem { font-family: Ahem; } - :any-link::target-text { - /* The default ::target-text should add back in underlines for links, but author style should be able to remove it" */ - color: lime; - background-color: green; - text-decoration: none; - } - a:any-link{ - text-decoration: none; - } -</style> -<p>PASS if there are two segments of lime squares with a green square, without an underline, between them below.</p> -<a class="ahem" href="example.html"> match me </a> -<script> - window.location.hash = "#:~:text=match%20me"; -</script>
diff --git a/third_party/blink/web_tests/wpt_internal/css/css-pseudo/target-text-002.html.ini b/third_party/blink/web_tests/wpt_internal/css/css-pseudo/target-text-002.html.ini deleted file mode 100644 index 03989af..0000000 --- a/third_party/blink/web_tests/wpt_internal/css/css-pseudo/target-text-002.html.ini +++ /dev/null
@@ -1,3 +0,0 @@ -[target-text-002.html] - expected: - if flag_specific == "disable-layout-ng": FAIL
diff --git a/third_party/blink/web_tests/wpt_internal/css/css-pseudo/target-text-lime-green-link-no-underline-ref.html b/third_party/blink/web_tests/wpt_internal/css/css-pseudo/target-text-lime-green-link-no-underline-ref.html deleted file mode 100644 index bf57235..0000000 --- a/third_party/blink/web_tests/wpt_internal/css/css-pseudo/target-text-lime-green-link-no-underline-ref.html +++ /dev/null
@@ -1,4 +0,0 @@ -<!doctype html> -<link rel="stylesheet" type="text/css" href="/fonts/ahem.css"> -<p>PASS if there are two segments of lime squares with a green square, without an underline, between them below.</p> -<a style="font-family:Ahem"><span style="color:lime;background:green">match me</span></a>
diff --git a/third_party/blink/web_tests/wpt_internal/css/css-pseudo/target-text-lime-green-link-ref.html b/third_party/blink/web_tests/wpt_internal/css/css-pseudo/target-text-lime-green-link-ref.html deleted file mode 100644 index f9eb405..0000000 --- a/third_party/blink/web_tests/wpt_internal/css/css-pseudo/target-text-lime-green-link-ref.html +++ /dev/null
@@ -1,4 +0,0 @@ -<!doctype html> -<link rel="stylesheet" type="text/css" href="/fonts/ahem.css"> -<p>PASS if there are two segments of lime squares with an underlined green square between below.</p> -<a style="font-family:Ahem; color:lime;background:green;text-decoration: underline;" href="example.html" >match me</span>
diff --git a/third_party/closure_compiler/externs/file_manager_private.js b/third_party/closure_compiler/externs/file_manager_private.js index 58d6bf00..a19fddc5 100644 --- a/third_party/closure_compiler/externs/file_manager_private.js +++ b/third_party/closure_compiler/externs/file_manager_private.js
@@ -859,15 +859,16 @@ /** * Gets the list of tasks that can be performed over selected files. |entries| - * Array of selected entries. |sourceUrls| Array of source URLs corresponding to - * the entries |callback| + * Array of selected entries. |dlpSourceUrls| Array of source URLs corresponding + * to the entries, used to check Data Leak Prevention (DLP) restrictions + * |callback| * @param {!Array<!Entry>} entries - * @param {!Array<string>} sourceUrls + * @param {!Array<string>} dlpSourceUrls * @param {function((!chrome.fileManagerPrivate.ResultingTasks|undefined))} * callback The list of matched file tasks for the entries. */ chrome.fileManagerPrivate.getFileTasks = function( - entries, sourceUrls, callback) {}; + entries, dlpSourceUrls, callback) {}; /** * Gets the MIME type of an entry. @@ -1490,12 +1491,16 @@ /** * Invoke Sharesheet for selected files. If not possible, then returns * an error via chrome.runtime.lastError. |entries| Array of selected entries. + * |launchSource| Source from which sharesheet was invoked. |dlpSourceUrls| + * Array of source URLs corresponding to the entries, used to check Data Leak + * Prevention (DLP) restrictions. * @param {!Array<!Entry>} entries * @param {chrome.fileManagerPrivate.SharesheetLaunchSource} launchSource + * @param {!Array<string>} dlpSourceUrls * @param {function()} callback */ chrome.fileManagerPrivate.invokeSharesheet = function( - entries, launchSource, callback) {}; + entries, launchSource, dlpSourceUrls, callback) {}; /** * Adds or removes a list of entries to temporary holding space. Any entries
diff --git a/tools/clang/plugins/RawPtrHelpers.h b/tools/clang/plugins/RawPtrHelpers.h index 7536268..e56a2872 100644 --- a/tools/clang/plugins/RawPtrHelpers.h +++ b/tools/clang/plugins/RawPtrHelpers.h
@@ -5,6 +5,8 @@ #ifndef TOOLS_CLANG_PLUGINS_RAWPTRHELPERS_H_ #define TOOLS_CLANG_PLUGINS_RAWPTRHELPERS_H_ +#include <optional> + #include "Util.h" #include "clang/ASTMatchers/ASTMatchers.h" #include "clang/ASTMatchers/ASTMatchersMacros.h" @@ -51,11 +53,11 @@ // be matched by the filter. The exclusion lines specify what to force exclude // from the filter. Lazily-constructed regex that matches strings that contain // any of the inclusion lines in |file_lines_|. - mutable llvm::Optional<llvm::Regex> inclusion_substring_regex_; + mutable std::optional<llvm::Regex> inclusion_substring_regex_; // Lazily-constructed regex that matches strings that contain any of the // exclusion lines in |file_lines_|. - mutable llvm::Optional<llvm::Regex> exclusion_substring_regex_; + mutable std::optional<llvm::Regex> exclusion_substring_regex_; }; AST_MATCHER(clang::Type, anyCharType) {
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml index 12e3f6f..7d28710 100644 --- a/tools/metrics/histograms/enums.xml +++ b/tools/metrics/histograms/enums.xml
@@ -32508,6 +32508,7 @@ <int value="1068" label="WindowManagementAllowedForUrls"/> <int value="1069" label="WindowManagementBlockedForUrls"/> <int value="1070" label="OutOfProcessSystemDnsResolutionEnabled"/> + <int value="1071" label="ExtensionUnpublishedAvailability"/> </enum> <enum name="EnterprisePoliciesSources"> @@ -35728,6 +35729,7 @@ <int value="1756" label="PASSWORDSPRIVATE_GETCREDENTIALSWITHREUSEDPASSWORD"/> <int value="1757" label="OS_TELEMETRY_GETAUDIOINFO"/> <int value="1758" label="OS_TELEMETRY_GETMARKETINGINFO"/> + <int value="1759" label="OS_TELEMETRY_GETUSBBUSINFO"/> </enum> <enum name="ExtensionIconState"> @@ -53306,6 +53308,7 @@ <int value="15" label="Thumb Strip"/> <int value="16" label="Omnibox Most Visited Entry"/> <int value="17" label="Pinned Tabs entry"/> + <int value="18" label="Tab Strip entry"/> </enum> <enum name="IOSMultiWindowConfiguration"> @@ -64815,6 +64818,7 @@ label="RemoveUsageOfDeprecatedGaiaSigninEndpoint:enabled"/> <int value="2032880973" label="U2FSecurityKeyAPI:disabled"/> <int value="2034198538" label="TabHoverCards:enabled"/> + <int value="2035351945" label="lacros-selection-policy-ignore"/> <int value="2035415906" label="Win11StyleMenus:enabled"/> <int value="2035524582" label="WebViewRecordAppDataDirectorySize:disabled"/> <int value="2037562553" label="OmniboxBubbleUrlSuggestions:disabled"/>
diff --git a/tools/metrics/histograms/metadata/media/histograms.xml b/tools/metrics/histograms/metadata/media/histograms.xml index 7925e7db..bf7bc99 100644 --- a/tools/metrics/histograms/metadata/media/histograms.xml +++ b/tools/metrics/histograms/metadata/media/histograms.xml
@@ -263,10 +263,9 @@ <histogram name="Media.Audio.Capture.Mac.MicSystemPermission.FixedTime.SinceFirstFailure" - units="ms" expires_after="2023-07-16"> - <owner>grunell@chromium.org</owner> - <owner>engedy@chromium.org</owner> - <owner>hkamila@chromium.org</owner> + units="ms" expires_after="2023-07-01"> + <owner>toprice@chromium.org</owner> + <owner>video-cmi-apis@google.com</owner> <owner>webrtc-audio-uma@google.com</owner> <summary> The time from the first microphone access failure due to blocked system @@ -279,10 +278,9 @@ <histogram name="Media.Audio.Capture.Mac.MicSystemPermission.FixedTime.SinceLastFailure" - units="ms" expires_after="2023-03-01"> - <owner>grunell@chromium.org</owner> - <owner>engedy@chromium.org</owner> - <owner>hkamila@chromium.org</owner> + units="ms" expires_after="2023-07-01"> + <owner>toprice@chromium.org</owner> + <owner>video-cmi-apis@google.com</owner> <owner>webrtc-audio-uma@google.com</owner> <summary> The time from the last microphone access failure due to blocked system @@ -294,10 +292,9 @@ </histogram> <histogram name="Media.Audio.Capture.Mac.MicSystemPermission.Startup" - enum="SystemMediaCapturePermission" expires_after="2023-07-02"> - <owner>grunell@chromium.org</owner> - <owner>engedy@chromium.org</owner> - <owner>hkamila@chromium.org</owner> + enum="SystemMediaCapturePermission" expires_after="2023-07-01"> + <owner>toprice@chromium.org</owner> + <owner>video-cmi-apis@google.com</owner> <owner>webrtc-audio-uma@google.com</owner> <summary> The Mac system permission state for microphone. Logged once at browser @@ -310,10 +307,9 @@ <histogram name="Media.Audio.Capture.Mac.MicSystemPermission.StartupAfterFailure" - enum="SystemMediaCapturePermission" expires_after="2023-07-16"> - <owner>grunell@chromium.org</owner> - <owner>engedy@chromium.org</owner> - <owner>hkamila@chromium.org</owner> + enum="SystemMediaCapturePermission" expires_after="2023-07-01"> + <owner>toprice@chromium.org</owner> + <owner>video-cmi-apis@google.com</owner> <owner>webrtc-audio-uma@google.com</owner> <summary> The Mac system permission state for microphone. Logged once at browser @@ -327,10 +323,9 @@ </histogram> <histogram name="Media.Audio.Capture.Mac.MicSystemPermission.UserMedia" - enum="SystemMediaCapturePermission" expires_after="2023-07-02"> - <owner>grunell@chromium.org</owner> - <owner>engedy@chromium.org</owner> - <owner>hkamila@chromium.org</owner> + enum="SystemMediaCapturePermission" expires_after="2023-07-01"> + <owner>toprice@chromium.org</owner> + <owner>video-cmi-apis@google.com</owner> <owner>webrtc-audio-uma@google.com</owner> <summary> The Mac system permission state for microphone at the time of a user media @@ -4749,10 +4744,9 @@ <histogram name="Media.Video.Capture.Mac.CameraSystemPermission.FixedTime.SinceFirstFailure" - units="ms" expires_after="2023-03-01"> - <owner>grunell@chromium.org</owner> - <owner>engedy@chromium.org</owner> - <owner>hkamila@chromium.org</owner> + units="ms" expires_after="2023-07-01"> + <owner>toprice@chromium.org</owner> + <owner>video-cmi-apis@google.com</owner> <summary> The time from the first camera access failure due to blocked system permission before the last browser restart to startup after browser restart. @@ -4764,10 +4758,9 @@ <histogram name="Media.Video.Capture.Mac.CameraSystemPermission.FixedTime.SinceLastFailure" - units="ms" expires_after="2023-03-01"> - <owner>grunell@chromium.org</owner> - <owner>engedy@chromium.org</owner> - <owner>hkamila@chromium.org</owner> + units="ms" expires_after="2023-07-01"> + <owner>toprice@chromium.org</owner> + <owner>video-cmi-apis@google.com</owner> <summary> The time from the last camera access failure due to blocked system permission before the last browser restart to startup after browser restart. @@ -4779,9 +4772,8 @@ <histogram name="Media.Video.Capture.Mac.CameraSystemPermission.Startup" enum="SystemMediaCapturePermission" expires_after="2023-07-23"> - <owner>grunell@chromium.org</owner> - <owner>engedy@chromium.org</owner> - <owner>hkamila@chromium.org</owner> + <owner>toprice@chromium.org</owner> + <owner>video-cmi-apis@google.com</owner> <summary> The Mac system permission state for camera. Logged once at browser startup. For more information on the values, see @@ -4793,10 +4785,9 @@ <histogram name="Media.Video.Capture.Mac.CameraSystemPermission.StartupAfterFailure" - enum="SystemMediaCapturePermission" expires_after="2023-03-01"> - <owner>grunell@chromium.org</owner> - <owner>engedy@chromium.org</owner> - <owner>hkamila@chromium.org</owner> + enum="SystemMediaCapturePermission" expires_after="2023-07-01"> + <owner>toprice@chromium.org</owner> + <owner>video-cmi-apis@google.com</owner> <summary> The Mac system permission state for camera. Logged once at browser startup if there was a failure accessing the camera due to blocked system permission @@ -4808,10 +4799,9 @@ </histogram> <histogram name="Media.Video.Capture.Mac.CameraSystemPermission.UserMedia" - enum="SystemMediaCapturePermission" expires_after="2023-07-16"> - <owner>grunell@chromium.org</owner> - <owner>engedy@chromium.org</owner> - <owner>hkamila@chromium.org</owner> + enum="SystemMediaCapturePermission" expires_after="2023-07-01"> + <owner>toprice@chromium.org</owner> + <owner>video-cmi-apis@google.com</owner> <summary> The Mac system permission state for camera at the time of a user media request. Logged when the system permission is checked. If the permission is
diff --git a/tools/perf/core/path_util.py b/tools/perf/core/path_util.py index c4b009e..37fbcb6 100644 --- a/tools/perf/core/path_util.py +++ b/tools/perf/core/path_util.py
@@ -46,6 +46,10 @@ GetChromiumSrcDir(), 'third_party', 'catapult', 'common', 'py_utils') +def GetCrossBenchDir(): + return os.path.join(GetChromiumSrcDir(), 'third_party', 'crossbench') + + def GetPerfDir(): return os.path.join(GetChromiumSrcDir(), 'tools', 'perf') @@ -100,5 +104,11 @@ sys.path.insert(1, android_pylib_path) +def AddCrossBenchToPath(): + crossbench_path = GetCrossBenchDir() + if crossbench_path not in sys.path: + sys.path.insert(1, crossbench_path) + + def GetExpectationsPath(): return os.path.join(GetPerfDir(), 'expectations.config')
diff --git a/tools/perf/core/perfetto_binary_roller/binary_deps.json b/tools/perf/core/perfetto_binary_roller/binary_deps.json index d6c82b9..efaa873 100644 --- a/tools/perf/core/perfetto_binary_roller/binary_deps.json +++ b/tools/perf/core/perfetto_binary_roller/binary_deps.json
@@ -5,24 +5,24 @@ "full_remote_path": "perfetto-luci-artifacts/v31.0/linux-arm64/trace_processor_shell" }, "win": { - "hash": "4ce6d14e7b3c5e7dbde313802b17a5f317072a83", - "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/win/07e128e9a2a37507a7db1de743b7b1b3e27cc930/trace_processor_shell.exe" + "hash": "b52651258872fb9c264701e9bf7529166c5764b3", + "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/win/45275bed1173d8a0cd0995560ca2882eb7d45e19/trace_processor_shell.exe" }, "linux_arm": { "hash": "6373f26144aad58f230d11d6a91efda5a09c9873", "full_remote_path": "perfetto-luci-artifacts/v31.0/linux-arm/trace_processor_shell" }, "mac": { - "hash": "420c16164a428421562ecd28f7333a9412670bca", - "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/mac/07e128e9a2a37507a7db1de743b7b1b3e27cc930/trace_processor_shell" + "hash": "970b1569ef0444a994cf2e26196d918ce827f65b", + "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/mac/446be1d134d3b94b33a8f7039fbff1177388da4e/trace_processor_shell" }, "mac_arm64": { "hash": "5f47ee79e59d00bf3889d30ca52315522c158040", "full_remote_path": "perfetto-luci-artifacts/v31.0/mac-arm64/trace_processor_shell" }, "linux": { - "hash": "2d33a469e9e944fe61887e391ed91fd684034d37", - "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/linux/07e128e9a2a37507a7db1de743b7b1b3e27cc930/trace_processor_shell" + "hash": "ff61c5dcaf3f861e7848c098d91e69d95651b234", + "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/linux/446be1d134d3b94b33a8f7039fbff1177388da4e/trace_processor_shell" } }, "power_profile.sql": {
diff --git a/tools/perf/crossbench b/tools/perf/crossbench new file mode 100755 index 0000000..98c79c8 --- /dev/null +++ b/tools/perf/crossbench
@@ -0,0 +1,15 @@ +#!/usr/bin/env vpython3 +# Copyright 2023 The Chromium Authors +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import sys + +from core import path_util +path_util.AddCrossBenchToPath() + +from crossbench import cli + +if __name__ == '__main__': + argv = sys.argv + cli.CrossBenchCLI().run(argv[1:])
diff --git a/tools/perf/expectations.config b/tools/perf/expectations.config index 90473a4..e4475f6 100644 --- a/tools/perf/expectations.config +++ b/tools/perf/expectations.config
@@ -374,13 +374,9 @@ crbug.com/1297360 [ fuchsia-chrome ] system_health.common_desktop/browse:tools:sheets:2019 [ Skip ] crbug.com/1306274 [ fuchsia-chrome ] system_health.common_desktop/browse:media:imgur [ Skip ] crbug.com/1306280 [ fuchsia-chrome ] system_health.common_desktop/load:media:google_images:2018 [ Skip ] -crbug.com/1302694 [ mac ] system_health.common_desktop/browse:tools:photoshop:2021 [ Skip ] -crbug.com/1302694 [ mac ] system_health.common_desktop/browse:tools:photoshop_warm:2021 [ Skip ] crbug.com/1348057 [ mac ] system_health.common_desktop/browse:tools:docs_scrolling [ Skip ] crbug.com/1360312 [ win-laptop ] system_health.common_desktop/browse:tools:photoshop:2021 [ Skip ] crbug.com/1368494 [ chromeos ] system_health.common_desktop/browse:tools:photoshop:2021 [ Skip ] -crbug.com/1360314 [ win ] system_health.common_desktop/browse:tools:photoshop_warm:2021 [ Skip ] -crbug.com/1360314 [ linux ] system_health.common_desktop/browse:tools:photoshop_warm:2021 [ Skip ] crbug.com/1362017 system_health.common_desktop/browse:tech:discourse_infinite_scroll:2018 [ Skip ] crbug.com/1393414 [ linux ] system_health.common_desktop/browse:tools:autocad:2021 [ Skip ] @@ -625,11 +621,7 @@ crbug.com/1211795 [ mac ] v8.browsing_desktop/browse:media:pinterest:2018 [ Skip ] crbug.com/1211795 [ mac ] v8.browsing_desktop/browse:social:twitter_infinite_scroll:2018 [ Skip ] crbug.com/1211795 [ mac ] v8.browsing_desktop/browse:social:facebook_infinite_scroll:2018 [ Skip ] -crbug.com/1302694 [ mac ] v8.browsing_desktop/browse:tools:photoshop:2021 [ Skip ] -crbug.com/1302694 [ mac ] v8.browsing_desktop/browse:tools:photoshop_warm:2021 [ Skip ] crbug.com/1360312 [ win-laptop ] v8.browsing_desktop/browse:tools:photoshop:2021 [ Skip ] -crbug.com/1360314 [ win ] v8.browsing_desktop/browse:tools:photoshop_warm:2021 [ Skip ] -crbug.com/1360314 [ linux ] v8.browsing_desktop/browse:tools:photoshop_warm:2021 [ Skip ] crbug.com/1362017 v8.browsing_desktop/browse:tech:discourse_infinite_scroll:2018 [ Skip ] crbug.com/1393414 [ linux ] v8.browsing_desktop/browse:tools:autocad:2021 [ Skip ] @@ -663,12 +655,8 @@ crbug.com/1211795 [ mac ] v8.browsing_desktop-future/browse:media:pinterest:2018 [ Skip ] crbug.com/1211795 [ mac ] v8.browsing_desktop-future/browse:social:twitter_infinite_scroll:2018 [ Skip ] crbug.com/1211795 [ mac ] v8.browsing_desktop-future/browse:social:facebook_infinite_scroll:2018 [ Skip ] -crbug.com/1302694 [ mac ] v8.browsing_desktop-future/browse:tools:photoshop:2021 [ Skip ] crbug.com/1380407 [ chromeos ] v8.browsing_desktop-future/browse:tools:photoshop:2021 [ Skip ] -crbug.com/1302694 [ mac ] v8.browsing_desktop-future/browse:tools:photoshop_warm:2021 [ Skip ] crbug.com/1360312 [ win-laptop ] v8.browsing_desktop-future/browse:tools:photoshop:2021 [ Skip ] -crbug.com/1360314 [ win ] v8.browsing_desktop-future/browse:tools:photoshop_warm:2021 [ Skip ] -crbug.com/1360314 [ linux ] v8.browsing_desktop-future/browse:tools:photoshop_warm:2021 [ Skip ] crbug.com/1362017 v8.browsing_desktop-future/browse:tech:discourse_infinite_scroll:2018 [ Skip ] crbug.com/1393414 [ linux ] v8.browsing_desktop-future/browse:tools:autocad:2021 [ Skip ]
diff --git a/ui/chromeos/file_manager_strings.grdp b/ui/chromeos/file_manager_strings.grdp index 967c5dab..cd1ff8d 100644 --- a/ui/chromeos/file_manager_strings.grdp +++ b/ui/chromeos/file_manager_strings.grdp
@@ -354,6 +354,9 @@ <message name="IDS_FILE_BROWSER_CONFIGURE_VOLUME_BUTTON_LABEL" desc="Title of the action for configuring the selected volume."> Configure </message> + <message name="IDS_FILE_BROWSER_UNMOUNT_BUTTON_LABEL" desc="Title of the action for unmounting a given filesystem."> + Eject <ph name="TARGET_NAME">$1<ex>zipfile.zip</ex></ph> + </message> <message name="IDS_FILE_BROWSER_UNMOUNT_DEVICE_BUTTON_LABEL" desc="Title of the action for unmounting removable device."> Eject device </message>
diff --git a/ui/chromeos/file_manager_strings_grdp/IDS_FILE_BROWSER_UNMOUNT_BUTTON_LABEL.png.sha1 b/ui/chromeos/file_manager_strings_grdp/IDS_FILE_BROWSER_UNMOUNT_BUTTON_LABEL.png.sha1 new file mode 100644 index 0000000..1e5407a --- /dev/null +++ b/ui/chromeos/file_manager_strings_grdp/IDS_FILE_BROWSER_UNMOUNT_BUTTON_LABEL.png.sha1
@@ -0,0 +1 @@ +db01c284f5acb6629f37cab811e366b25ab3f434 \ No newline at end of file
diff --git a/ui/file_manager/file_manager/foreground/js/BUILD.gn b/ui/file_manager/file_manager/foreground/js/BUILD.gn index 83a2dfd..677e1f2 100644 --- a/ui/file_manager/file_manager/foreground/js/BUILD.gn +++ b/ui/file_manager/file_manager/foreground/js/BUILD.gn
@@ -845,6 +845,8 @@ "//ui/file_manager/file_manager/common/js:api", "//ui/file_manager/file_manager/common/js:files_app_entry_types", "//ui/file_manager/file_manager/common/js:util", + "//ui/file_manager/file_manager/common/js:volume_manager_types", + "//ui/file_manager/file_manager/externs:volume_manager", ] }
diff --git a/ui/file_manager/file_manager/foreground/js/file_manager.js b/ui/file_manager/file_manager/foreground/js/file_manager.js index 76f3719..4a3d4375 100644 --- a/ui/file_manager/file_manager/foreground/js/file_manager.js +++ b/ui/file_manager/file_manager/foreground/js/file_manager.js
@@ -1273,8 +1273,7 @@ if (util.isGuestOsEnabled()) { this.guestOsController_ = new GuestOsController( this.directoryModel_, assert(this.directoryTree), - this.volumeManager_.isDisabled( - VolumeManagerCommon.VolumeType.GUEST_OS)); + this.volumeManager_); await this.guestOsController_.refresh(); } }
diff --git a/ui/file_manager/file_manager/foreground/js/file_manager_commands.js b/ui/file_manager/file_manager/foreground/js/file_manager_commands.js index 7bcf54c..1d1d11e 100644 --- a/ui/file_manager/file_manager/foreground/js/file_manager_commands.js +++ b/ui/file_manager/file_manager/foreground/js/file_manager_commands.js
@@ -1897,8 +1897,11 @@ execute(event, fileManager) { const entries = fileManager.selectionHandler.selection.entries; const launchSource = CommandUtil.getSharesheetLaunchSource(event); + const dlpSourceUrls = + fileManager.metadataModel.getCache(entries, ['sourceUrl']) + .map(m => m.sourceUrl || ''); chrome.fileManagerPrivate.invokeSharesheet( - entries, launchSource, () => { + entries, launchSource, dlpSourceUrls, () => { if (chrome.runtime.lastError) { console.warn(chrome.runtime.lastError.message); return;
diff --git a/ui/file_manager/file_manager/foreground/js/guest_os_controller.js b/ui/file_manager/file_manager/foreground/js/guest_os_controller.js index d5f5118..4e130427 100644 --- a/ui/file_manager/file_manager/foreground/js/guest_os_controller.js +++ b/ui/file_manager/file_manager/foreground/js/guest_os_controller.js
@@ -5,6 +5,8 @@ import {listMountableGuests} from '../../common/js/api.js'; import {GuestOsPlaceholder} from '../../common/js/files_app_entry_types.js'; import {util} from '../../common/js/util.js'; +import {VolumeManagerCommon} from '../../common/js/volume_manager_types.js'; +import {VolumeManager} from '../../externs/volume_manager.js'; import {DirectoryModel} from './directory_model.js'; import {NavigationModelFakeItem, NavigationModelItemType} from './navigation_list_model.js'; @@ -18,10 +20,9 @@ /** * @param {!DirectoryModel} directoryModel DirectoryModel. * @param {!DirectoryTree} directoryTree DirectoryTree. - * @param {boolean} disabled Whether the Guest OS item should be disabled. - * Defaults to false. + * @param {!VolumeManager} volumeManager VolumeManager. */ - constructor(directoryModel, directoryTree, disabled = false) { + constructor(directoryModel, directoryTree, volumeManager) { if (!util.isGuestOsEnabled()) { console.warn('Created a guest os controller when it\'s not enabled'); } @@ -31,8 +32,8 @@ /** @private @const */ this.directoryTree_ = directoryTree; - /** @private @const {boolean} */ - this.disabled_ = disabled; + /** @private @const {!VolumeManager} */ + this.volumeManager_ = volumeManager; chrome.fileManagerPrivate.onMountableGuestsChanged.addListener( this.onMountableGuestsChanged.bind(this)); @@ -58,7 +59,13 @@ const navigationModelItem = new NavigationModelFakeItem( guest.displayName, NavigationModelItemType.GUEST_OS, new GuestOsPlaceholder(guest.displayName, guest.id, guest.vmType)); - navigationModelItem.disabled = this.disabled_; + if (guest.vmType == chrome.fileManagerPrivate.VmType.ARCVM) { + navigationModelItem.disabled = this.volumeManager_.isDisabled( + VolumeManagerCommon.VolumeType.ANDROID_FILES); + } else { + navigationModelItem.disabled = this.volumeManager_.isDisabled( + VolumeManagerCommon.VolumeType.GUEST_OS); + } return navigationModelItem; });
diff --git a/ui/file_manager/file_manager/foreground/js/navigation_list_model.js b/ui/file_manager/file_manager/foreground/js/navigation_list_model.js index d3d6b56..c628789 100644 --- a/ui/file_manager/file_manager/foreground/js/navigation_list_model.js +++ b/ui/file_manager/file_manager/foreground/js/navigation_list_model.js
@@ -701,6 +701,8 @@ const androidVolume = getSingleVolume(VolumeManagerCommon.VolumeType.ANDROID_FILES); if (androidVolume) { + androidVolume.disabled = this.volumeManager_.isDisabled( + VolumeManagerCommon.VolumeType.ANDROID_FILES); guestOsVolumes = guestOsVolumes.concat(androidVolume); } }
diff --git a/ui/file_manager/file_manager/foreground/js/ui/directory_tree.js b/ui/file_manager/file_manager/foreground/js/ui/directory_tree.js index ada33cd..8d35a9e 100644 --- a/ui/file_manager/file_manager/foreground/js/ui/directory_tree.js +++ b/ui/file_manager/file_manager/foreground/js/ui/directory_tree.js
@@ -2,15 +2,15 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import {dispatchSimpleEvent, getPropertyDescriptor, PropertyKind} from 'chrome://resources/ash/common/cr_deprecated.js'; import {assert, assertNotReached} from 'chrome://resources/ash/common/assert.js'; +import {dispatchSimpleEvent, getPropertyDescriptor, PropertyKind} from 'chrome://resources/ash/common/cr_deprecated.js'; import {maybeShowTooltip} from '../../../common/js/dom_utils.js'; import {FileType} from '../../../common/js/file_type.js'; import {VolumeEntry} from '../../../common/js/files_app_entry_types.js'; import {vmTypeToIconName} from '../../../common/js/icon_util.js'; import {metrics} from '../../../common/js/metrics.js'; -import {str, util} from '../../../common/js/util.js'; +import {strf, util} from '../../../common/js/util.js'; import {VolumeManagerCommon} from '../../../common/js/volume_manager_types.js'; import {FileOperationManager} from '../../../externs/background/file_operation_manager.js'; import {FilesAppDirEntry} from '../../../externs/files_app_entry_interfaces.js'; @@ -790,13 +790,15 @@ * Set up eject button. It is placed as the last element of the elements that * compose the tree row content. * @param {!HTMLElement} rowElement Tree row element. + * @param {string} targetLabel Label for the ejectable target. * @private */ - setupEjectButton_(rowElement) { + setupEjectButton_(rowElement, targetLabel) { const ejectButton = document.createElement('cr-button'); ejectButton.className = 'root-eject align-right-icon'; - ejectButton.setAttribute('aria-label', str('UNMOUNT_DEVICE_BUTTON_LABEL')); + ejectButton.setAttribute( + 'aria-label', strf('UNMOUNT_BUTTON_LABEL', targetLabel)); ejectButton.setAttribute('tabindex', '0'); // Block mouse handlers, handle click. @@ -987,7 +989,7 @@ this.disabled = modelItem.disabled; if (rootType === VolumeManagerCommon.RootType.REMOVABLE) { - this.setupEjectButton_(this.rowElement); + this.setupEjectButton_(this.rowElement, modelItem.label); // For removable add menus for roots to be able to unmount, format, etc. if (tree.contextMenuForRootItems) { @@ -1151,7 +1153,7 @@ // This placeholder is added to allow to put textbox before eject button // while executing renaming action on external drive. this.setupRenamePlaceholder_(this.rowElement); - this.setupEjectButton_(this.rowElement); + this.setupEjectButton_(this.rowElement, modelItem.label); } // Sets up context menu of the item.
diff --git a/ui/file_manager/integration_tests/file_manager/dlp.js b/ui/file_manager/integration_tests/file_manager/dlp.js index b6b6ea9..1cbafcf 100644 --- a/ui/file_manager/integration_tests/file_manager/dlp.js +++ b/ui/file_manager/integration_tests/file_manager/dlp.js
@@ -7,6 +7,7 @@ import {testcase} from '../testcase.js'; import {navigateWithDirectoryTree, openAndWaitForClosingDialog, remoteCall, setupAndWaitUntilReady} from './background.js'; +import {FakeTask} from './tasks.js'; import {BASIC_ANDROID_ENTRY_SET, BASIC_LOCAL_ENTRY_SET} from './test_data.js'; /** @@ -141,56 +142,70 @@ ':not([hidden]):not([disabled])'); }; +// Filters used for the following save-as and file-open tests. +// Rows in `My files` +const downloadsRow = ['Downloads', '--', 'Folder']; +const playFilesRow = ['Play files', '--', 'Folder']; +const linuxFilesRow = ['Linux files', '--', 'Folder']; +// Dialog buttons +const okButton = '.button-panel button.ok:enabled'; +const disabledOkButton = '.button-panel button.ok:disabled'; +const cancelButton = '.button-panel button.cancel'; + /** - * Tests the save dialogs properly show DLP blocked volumes/directories. If a - * volume is blocked by DLP, it should be marked as disabled both in the - * navigation list and in the details list. If such a volume is selected, the - * "Open" dialog button should be disabled, i.e. changing the directory is - * prevented. + * Tests the save dialogs properly show DLP blocked Play files, before + * and after being mounted, both in the navigation list and in + * the details list. */ -testcase.saveAsDlpRestrictedDirectory = async () => { +testcase.saveAsDlpRestrictedAndroid = async () => { // Setup the restrictions. await sendTestMessage({name: 'setBlockedArc'}); - const okButton = '.button-panel button.ok:enabled'; - const disabledOkButton = '.button-panel button.ok:disabled'; - const cancelButton = '.button-panel button.cancel'; - - // Add entries to Downloads. - await addEntries(['local'], [ENTRIES.hello]); - const closer = async (dialog) => { - // Verify that the button is enabled when a file is selected. - await remoteCall.waitUntilSelected(dialog, ENTRIES.hello.targetPath); - await remoteCall.waitForElement(dialog, okButton); - // Select My Files folder and wait for file list to display Downloads, Play // files, and Linux files. await navigateWithDirectoryTree(dialog, '/My files'); - const downloadsRow = ['Downloads', '--', 'Folder']; - const playFilesRow = ['Play files', '--', 'Folder']; - const linuxFilesRow = ['Linux files', '--', 'Folder']; + await remoteCall.waitForFiles( dialog, [downloadsRow, playFilesRow, linuxFilesRow], {ignoreFileSize: true, ignoreLastModifiedTime: true}); // Only one directory, Android files, should be disabled, both as the tree // item and the directory in the main list. - const playFilesInFileList = '.directory[disabled][file-name="Play files"]'; - const playFilesInDirectoryTree = '#directory-tree .tree-item[disabled] ' + + const guestName = 'Play files'; + const disabledDirectory = `.directory[disabled][file-name="${guestName}"]`; + const disabledRealTreeItem = '#directory-tree .tree-item[disabled] ' + '.icon[volume-type-icon="android_files"]'; - await remoteCall.waitForElementsCount(dialog, [playFilesInFileList], 1); - await remoteCall.waitForElementsCount( - dialog, [playFilesInDirectoryTree], 1); + const disabledFakeTreeItem = '#directory-tree .tree-item[disabled] ' + + '[root-type-icon=android_files]'; + await remoteCall.waitForElement(dialog, disabledDirectory); + await remoteCall.waitForElement(dialog, disabledRealTreeItem); // Verify that the button is enabled when a non-blocked volume is selected. await remoteCall.waitUntilSelected(dialog, 'Downloads'); await remoteCall.waitForElement(dialog, okButton); // Verify that the button is disabled when a blocked volume is selected. - await remoteCall.waitUntilSelected(dialog, 'Play files'); + await remoteCall.waitUntilSelected(dialog, guestName); await remoteCall.waitForElement(dialog, disabledOkButton); + // Unmount Play files and mount ARCVM. + await sendTestMessage({name: 'unmountPlayFiles'}); + const guestId = await sendTestMessage({ + name: 'registerMountableGuest', + displayName: guestName, + canMount: true, + vmType: 'arcvm', + }); + + // Wait for the placeholder to appear, which should be disabled. The + // directory shouldn't appear in the details list. + // TODO(b/267133288): Check why the dir doesn't appear and fix if necessary. + await remoteCall.waitAndClickElement(dialog, disabledFakeTreeItem); + await remoteCall.waitForFiles( + dialog, [downloadsRow, linuxFilesRow], + {ignoreFileSize: true, ignoreLastModifiedTime: true}); + // Click the close button to dismiss the dialog. await remoteCall.waitAndClickElement(dialog, [cancelButton]); }; @@ -198,7 +213,7 @@ chrome.test.assertEq( undefined, await openAndWaitForClosingDialog( - {type: 'saveFile'}, 'downloads', [ENTRIES.hello], closer)); + {type: 'saveFile'}, 'downloads', [], closer)); }; /** @@ -211,10 +226,6 @@ // Setup the restrictions. await sendTestMessage({name: 'setBlockedPluginVM'}); - const okButton = '.button-panel button.ok:enabled'; - const disabledOkButton = '.button-panel button.ok:disabled'; - const cancelButton = '.button-panel button.cancel'; - const guestName = 'JennyAnyDots'; const guestId = await sendTestMessage({ name: 'registerMountableGuest', @@ -226,9 +237,6 @@ const closer = async (dialog) => { // Select My Files folder and wait for file list. await navigateWithDirectoryTree(dialog, '/My files'); - const downloadsRow = ['Downloads', '--', 'Folder']; - const playFilesRow = ['Play files', '--', 'Folder']; - const linuxFilesRow = ['Linux files', '--', 'Folder']; const guestFilesRow = [guestName, '--', 'Folder']; await remoteCall.waitForFiles( dialog, [downloadsRow, playFilesRow, linuxFilesRow, guestFilesRow], @@ -293,10 +301,6 @@ // Setup the restrictions. await sendTestMessage({name: 'setBlockedCrostini'}); - const okButton = '.button-panel button.ok:enabled'; - const disabledOkButton = '.button-panel button.ok:disabled'; - const cancelButton = '.button-panel button.cancel'; - // Add entries to Downloads. await addEntries(['local'], [ENTRIES.hello]); @@ -308,9 +312,6 @@ // Select My Files folder and wait for file list to display Downloads, Play // files, and Linux files. await navigateWithDirectoryTree(dialog, '/My files'); - const downloadsRow = ['Downloads', '--', 'Folder']; - const playFilesRow = ['Play files', '--', 'Folder']; - const linuxFilesRow = ['Linux files', '--', 'Folder']; await remoteCall.waitForFiles( dialog, [downloadsRow, playFilesRow, linuxFilesRow], {ignoreFileSize: true, ignoreLastModifiedTime: true}); @@ -359,8 +360,6 @@ * opened in the requested path. */ testcase.saveAsNonDlpRestricted = async () => { - const cancelButton = '.button-panel button.cancel'; - // Add entries to Play files. await addEntries(['android_files'], BASIC_ANDROID_ENTRY_SET); @@ -386,8 +385,6 @@ * but rather in the default display root. */ testcase.saveAsDlpRestrictedRedirectsToMyFiles = async () => { - const cancelButton = '.button-panel button.cancel'; - // Add entries to Downloads and Play files. await addEntries(['local'], [ENTRIES.hello]); await addEntries(['android_files'], BASIC_ANDROID_ENTRY_SET); @@ -438,10 +435,6 @@ await sendTestMessage({name: 'setIsRestrictedByAnyRuleRestrictions'}); await sendTestMessage({name: 'setIsRestrictedDestinationRestriction'}); - const okButton = '.button-panel button.ok:enabled'; - const disabledOkButton = '.button-panel button.ok:disabled'; - const cancelButton = '.button-panel button.cancel'; - const closer = async (dialog) => { // Wait for the file list to appear. await remoteCall.waitForElement(dialog, '#file-list'); @@ -507,10 +500,6 @@ await sendTestMessage({name: 'setIsRestrictedByAnyRuleRestrictions'}); await sendTestMessage({name: 'setIsRestrictedDestinationRestriction'}); - const enabledOkButton = '.button-panel button.ok:enabled'; - const disabledOkButton = '.button-panel button.ok:disabled'; - const cancelButton = '.button-panel button.cancel'; - const closer = async (dialog) => { // Wait for directoryA to appear. await remoteCall.waitForElement( @@ -564,5 +553,47 @@ fileNames: [ENTRIES.directoryA.targetPath], openType: 'open', }); - await remoteCall.waitAndClickElement(dialog, enabledOkButton); -}; \ No newline at end of file + await remoteCall.waitAndClickElement(dialog, okButton); +}; + +/** + * Tests that DLP disabled file tasks are shown as disabled in the menu. + */ +testcase.fileTasksDlpRestricted = async () => { + const entry = ENTRIES.hello; + // Open Files app. + const appId = await setupAndWaitUntilReady(RootPath.DOWNLOADS, [entry], []); + // Override file tasks so that some are DLP disabled. + const fakeTasks = [ + new FakeTask( + true, {appId: 'dummyId1', taskType: 'file', actionId: 'open-with'}, + 'DummyTask1', false, true), + new FakeTask( + false, {appId: 'dummyId2', taskType: 'file', actionId: 'open-with'}, + 'DummyTask2', false, false), + new FakeTask( + false, {appId: 'dummyId3', taskType: 'file', actionId: 'open-with'}, + 'DummyTask3', false, true), + ]; + await remoteCall.callRemoteTestUtil('overrideTasks', appId, [fakeTasks]); + + // Select file. + await remoteCall.waitUntilSelected(appId, entry.nameText); + + // Check that multiple tasks are available and then click on "Open ▼" button. + await remoteCall.waitAndClickElement(appId, '#tasks[multiple]'); + + // Wait for dropdown menu to show. + await remoteCall.waitForElement( + appId, '#tasks-menu:not([hidden]) cr-menu-item'); + + // Verify that the first and third tasks are disabled, and the second one is + // not. + await remoteCall.waitForElement( + appId, ['#tasks-menu:not([hidden]) cr-menu-item[disabled]:nth-child(1)']); + await remoteCall.waitForElement( + appId, + ['#tasks-menu:not([hidden]) cr-menu-item:not([disabled]):nth-child(2)']); + await remoteCall.waitForElement( + appId, ['#tasks-menu:not([hidden]) cr-menu-item[disabled]:nth-child(3)']); +};
diff --git a/ui/file_manager/integration_tests/file_manager/drive_specific.js b/ui/file_manager/integration_tests/file_manager/drive_specific.js index bac8e36..5bf6b66 100644 --- a/ui/file_manager/integration_tests/file_manager/drive_specific.js +++ b/ui/file_manager/integration_tests/file_manager/drive_specific.js
@@ -1298,3 +1298,27 @@ // Ensure the file was unpinned prior to deleting. await remoteCall.expectDriveItemPinnedStatus(appId, '/root/test.txt', false); }; + +/** + * Test that when files get deleted in the cloud, they get unpinned after being + * deleted. + */ +testcase.driveCloudDeleteUnpinsItem = async () => { + const appId = await setupAndWaitUntilReady(RootPath.DRIVE); + + // Select test.txt which is already pinned. + await remoteCall.waitAndClickElement( + appId, '#file-list [file-name="test.txt"]'); + await remoteCall.waitForElement( + appId, '[file-name="test.txt"][selected] xf-icon[type=offline]'); + + // Ensure the metadata for the file is set to pinned. + await remoteCall.expectDriveItemPinnedStatus(appId, '/root/test.txt', true); + + await remoteCall.sendDriveCloudDeleteEvent(appId, '/root/test.txt'); + await remoteCall.waitForElementLost( + appId, '#file-list [file-name="test.txt"]'); + + // Ensure the file was unpinned prior to deleting. + await remoteCall.expectDriveItemPinnedStatus(appId, '/root/test.txt', false); +};
diff --git a/ui/file_manager/integration_tests/file_manager/tasks.js b/ui/file_manager/integration_tests/file_manager/tasks.js index 3e730dd..dddb9c3 100644 --- a/ui/file_manager/integration_tests/file_manager/tasks.js +++ b/ui/file_manager/integration_tests/file_manager/tasks.js
@@ -19,14 +19,18 @@ * @param {string=} opt_title Title of the task. * @param {boolean=} opt_isGenericFileHandler Whether the task is a generic * file handler. + * @param {boolean=} opt_isDlpBlocked Whether the task is blocked by DLP. */ - constructor(isDefault, descriptor, opt_title, opt_isGenericFileHandler) { + constructor( + isDefault, descriptor, opt_title, opt_isGenericFileHandler, + opt_isDlpBlocked) { this.driveApp = false; this.iconUrl = 'chrome://theme/IDR_DEFAULT_FAVICON'; // Dummy icon this.isDefault = isDefault; this.descriptor = descriptor; this.title = opt_title; this.isGenericFileHandler = opt_isGenericFileHandler || false; + this.isDlpBlocked = opt_isDlpBlocked || false; Object.freeze(this); } }
diff --git a/ui/file_manager/integration_tests/remote_call.js b/ui/file_manager/integration_tests/remote_call.js index 4199ec8..1ea7f3d 100644 --- a/ui/file_manager/integration_tests/remote_call.js +++ b/ui/file_manager/integration_tests/remote_call.js
@@ -998,4 +998,18 @@ }), String(status)); } + + /** + * Send a delete event via the `OnFilesChanged` drivefs delegate method. + * @param {string} appId app window ID. + * @param {string} path Path from the drive mount point, e.g. /root/test.txt + */ + async sendDriveCloudDeleteEvent(appId, path) { + await this.waitFor('isFileManagerLoaded', appId, true); + await sendTestMessage({ + appId, + name: 'sendDriveCloudDeleteEvent', + path, + }); + } }
diff --git a/ui/ozone/platform/wayland/OWNERS b/ui/ozone/platform/wayland/OWNERS index 72721318..6700cfc 100644 --- a/ui/ozone/platform/wayland/OWNERS +++ b/ui/ozone/platform/wayland/OWNERS
@@ -3,3 +3,9 @@ tonikitoo@igalia.com adunaev@igalia.com fangzhoug@chromium.org + +# For Keyboard / TextInput (IME) +hidehiko@chromium.org + +# For Events / WindowManagement / Screen Management +oshima@chromium.org
diff --git a/weblayer/browser/autofill_client_impl.cc b/weblayer/browser/autofill_client_impl.cc index 665dcd3f..55f1ac3 100644 --- a/weblayer/browser/autofill_client_impl.cc +++ b/weblayer/browser/autofill_client_impl.cc
@@ -265,7 +265,7 @@ bool AutofillClientImpl::TryToShowFastCheckout( const autofill::FormData& form, const autofill::FormFieldData& field, - autofill::AutofillDriver* driver) { + base::WeakPtr<autofill::AutofillManager> autofill_manager) { return false; }
diff --git a/weblayer/browser/autofill_client_impl.h b/weblayer/browser/autofill_client_impl.h index 08f5467..dc4abee 100644 --- a/weblayer/browser/autofill_client_impl.h +++ b/weblayer/browser/autofill_client_impl.h
@@ -112,9 +112,10 @@ AddressProfileSavePromptCallback callback) override; bool HasCreditCardScanFeature() override; void ScanCreditCard(CreditCardScanCallback callback) override; - bool TryToShowFastCheckout(const autofill::FormData& form, - const autofill::FormFieldData& field, - autofill::AutofillDriver* driver) override; + bool TryToShowFastCheckout( + const autofill::FormData& form, + const autofill::FormFieldData& field, + base::WeakPtr<autofill::AutofillManager> autofill_manager) override; void HideFastCheckout(bool allow_further_runs) override; bool IsFastCheckoutSupported() override; bool IsShowingFastCheckoutUI() override;