diff --git a/DEPS b/DEPS index 5e9b4322..fbef1933 100644 --- a/DEPS +++ b/DEPS
@@ -40,11 +40,11 @@ # Three lines of non-changing comments so that # the commit queue can handle CLs rolling Skia # and whatever else without interference from each other. - 'skia_revision': '210e8551a30f6cb48894fd7c010d0a9be146ffa5', + 'skia_revision': '878df6dd03d1e5b8c018f803eff5b4736af399f8', # Three lines of non-changing comments so that # the commit queue can handle CLs rolling V8 # and whatever else without interference from each other. - 'v8_revision': '7b1dc4fedaee89c90dd085e9f2819adcd6c1f170', + 'v8_revision': 'b89390700f2ee1b26a5c4f38ff2b1c6fc46b23d4', # Three lines of non-changing comments so that # the commit queue can handle CLs rolling swarming_client # and whatever else without interference from each other. @@ -397,7 +397,7 @@ # For Linux and Chromium OS. 'src/third_party/cros_system_api': - Var('chromium_git') + '/chromiumos/platform/system_api.git' + '@' + '0995a6af616cd4ef0e94d278ecc92aa2537b16c3', + Var('chromium_git') + '/chromiumos/platform/system_api.git' + '@' + '05cee9da77790110786e6211f75c49d0c7e26ae8', # Note that this is different from Android's freetype repo. 'src/third_party/freetype2/src':
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/tabmodel/UndoTabModelTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/tabmodel/UndoTabModelTest.java index 9245627..d0a053f 100644 --- a/chrome/android/javatests/src/org/chromium/chrome/browser/tabmodel/UndoTabModelTest.java +++ b/chrome/android/javatests/src/org/chromium/chrome/browser/tabmodel/UndoTabModelTest.java
@@ -1390,7 +1390,6 @@ */ @MediumTest @RetryOnFailure - @DisabledTest(message = "crbug.com/674654") public void testUndoNotSupported() throws InterruptedException { TabModel model = getActivity().getTabModelSelector().getModel(true); ChromeTabCreator tabCreator = getActivity().getTabCreator(true);
diff --git a/chrome/browser/android/compositor/tab_content_manager.cc b/chrome/browser/android/compositor/tab_content_manager.cc index a516fdb..505c843 100644 --- a/chrome/browser/android/compositor/tab_content_manager.cc +++ b/chrome/browser/android/compositor/tab_content_manager.cc
@@ -20,7 +20,9 @@ #include "chrome/browser/android/compositor/layer/thumbnail_layer.h" #include "chrome/browser/android/tab_android.h" #include "chrome/browser/android/thumbnail/thumbnail.h" +#include "content/public/browser/interstitial_page.h" #include "content/public/browser/readback_types.h" +#include "content/public/browser/render_frame_host.h" #include "content/public/browser/render_view_host.h" #include "content/public/browser/render_widget_host.h" #include "content/public/browser/render_widget_host_view.h" @@ -45,22 +47,18 @@ class TabContentManager::TabReadbackRequest { public: - TabReadbackRequest(content::WebContents* web_contents, + TabReadbackRequest(content::RenderWidgetHost* rwh, float thumbnail_scale, const TabReadbackCallback& end_callback) : thumbnail_scale_(thumbnail_scale), end_callback_(end_callback), drop_after_readback_(false), weak_factory_(this) { - DCHECK(web_contents); + DCHECK(rwh); content::ReadbackRequestCallback result_callback = base::Bind(&TabReadbackRequest::OnFinishGetTabThumbnailBitmap, weak_factory_.GetWeakPtr()); - content::RenderWidgetHost* rwh = - web_contents->GetRenderViewHost()->GetWidget(); - DCHECK(rwh); - SkColorType color_type = kN32_SkColorType; gfx::Rect src_rect = rwh->GetView()->GetViewBounds(); gfx::Size dst_size( @@ -217,11 +215,17 @@ content::WebContents* web_contents = tab_android->web_contents(); DCHECK(web_contents); - if (!web_contents->GetRenderViewHost() || - !web_contents->GetRenderViewHost()->GetWidget() || - !web_contents->GetRenderViewHost() - ->GetWidget() - ->CanCopyFromBackingStore() || + content::RenderViewHost* rvh = web_contents->GetRenderViewHost(); + if (web_contents->ShowingInterstitialPage()) { + if (!web_contents->GetInterstitialPage()->GetMainFrame()) + return; + + rvh = web_contents->GetInterstitialPage()->GetMainFrame()-> + GetRenderViewHost(); + } + + if (!rvh || !rvh->GetWidget() || + !rvh->GetWidget()->CanCopyFromBackingStore() || pending_tab_readbacks_.find(tab_id) != pending_tab_readbacks_.end() || pending_tab_readbacks_.size() >= kMaxReadbacks) { return; @@ -232,7 +236,7 @@ base::Bind(&TabContentManager::PutThumbnailIntoCache, weak_factory_.GetWeakPtr(), tab_id); pending_tab_readbacks_[tab_id] = base::MakeUnique<TabReadbackRequest>( - web_contents, thumbnail_scale, readback_done_callback); + rvh->GetWidget(), thumbnail_scale, readback_done_callback); } }
diff --git a/chrome/browser/chromeos/policy/server_backed_state_keys_broker.h b/chrome/browser/chromeos/policy/server_backed_state_keys_broker.h index 6fb80e9..de4baf7 100644 --- a/chrome/browser/chromeos/policy/server_backed_state_keys_broker.h +++ b/chrome/browser/chromeos/policy/server_backed_state_keys_broker.h
@@ -46,10 +46,12 @@ // requested yet, calling this will also trigger their initial fetch. Subscription RegisterUpdateCallback(const base::Closure& callback); - // Requests state keys asynchronously. Invokes the passed callback exactly - // once (unless |this| gets destroyed before the callback happens), with the - // current state keys passed as a parameter to the callback. If there's a - // problem determining the state keys, the passed vector will be empty. + // Requests state keys asynchronously. Invokes the passed callback at most + // once, with the current state keys passed as a parameter to the callback. If + // there's a problem determining the state keys, the passed vector will be + // empty. If |this| gets destroyed before the callback happens or if the time + // sync fails / the network is not established, then the |callback| is never + // invoked. See http://crbug.com/649422 for more context. void RequestStateKeys(const StateKeysCallback& callback); // Get the set of current state keys. Empty if state keys are unavailable
diff --git a/chrome/browser/subresource_filter/subresource_filter_browsertest.cc b/chrome/browser/subresource_filter/subresource_filter_browsertest.cc index c1b2138..4e1cd2e8 100644 --- a/chrome/browser/subresource_filter/subresource_filter_browsertest.cc +++ b/chrome/browser/subresource_filter/subresource_filter_browsertest.cc
@@ -229,9 +229,13 @@ ASSERT_TRUE(frame); EXPECT_FALSE(WasParsedScriptElementLoaded(frame)); + // Support both pre-/post-PersistentHistograms worlds. The latter is enabled + // through field trials, so the former is still used on offical builders. + content::FetchHistogramsFromChildProcesses(); + SubprocessMetricsProvider::MergeHistogramDeltasForTesting(); + // The only frames where filtering was (even considered to be) activated // should be the main frame, and the child that was navigated to an HTTP URL. - SubprocessMetricsProvider::MergeHistogramDeltasForTesting(); histogram_tester.ExpectUniqueSample( "SubresourceFilter.DocumentLoad.ActivationState", static_cast<base::Histogram::Sample>(ActivationState::ENABLED), 2);
diff --git a/chromeos/dbus/OWNERS b/chromeos/dbus/OWNERS index 4c9ab1f..d33c0344 100644 --- a/chromeos/dbus/OWNERS +++ b/chromeos/dbus/OWNERS
@@ -1,7 +1,9 @@ stevenjb@chromium.org hashimoto@chromium.org -per-file *cryptohome*=dkrahn@chromium.org per-file *audio*=jennyz@chromium.org per-file *audio*=hychao@chromium.org +per-file *auth_policy*=rsorokin@chromium.org +per-file *auth_policy*=tnagel@chromium.org +per-file *cryptohome*=dkrahn@chromium.org per-file *power*=derat@chromium.org
diff --git a/chromeos/dbus/session_manager_client.h b/chromeos/dbus/session_manager_client.h index dc00918..527c92b 100644 --- a/chromeos/dbus/session_manager_client.h +++ b/chromeos/dbus/session_manager_client.h
@@ -211,7 +211,8 @@ // for the device to retrieve after a device factory reset. // // The state keys are returned asynchronously via |callback|. The callback - // will be invoked with an empty state key vector in case of errors. + // is invoked with an empty state key vector in case of errors. If the time + // sync fails or there's no network, the callback is never invoked. virtual void GetServerBackedStateKeys(const StateKeysCallback& callback) = 0; // Used for several ARC methods. Takes a boolean indicating whether the
diff --git a/components/cronet/android/BUILD.gn b/components/cronet/android/BUILD.gn index 24bd002..21eabaa 100644 --- a/components/cronet/android/BUILD.gn +++ b/components/cronet/android/BUILD.gn
@@ -553,7 +553,10 @@ android_resources("cronet_test_apk_resources") { testonly = true - resource_dirs = [ "test/res" ] + resource_dirs = [ + "test/res", + "test/smoketests/res/native", + ] android_manifest = "test/AndroidManifest.xml" } @@ -588,6 +591,35 @@ run_findbugs_override = true } +cronet_smoketests_platform_only_common_srcs = [ + "test/smoketests/src/org/chromium/net/smoke/ChromiumPlatformOnlyTestSupport.java", + "test/smoketests/src/org/chromium/net/smoke/CronetSmokeTestCase.java", + "test/smoketests/src/org/chromium/net/smoke/HttpTestServer.java", + "test/smoketests/src/org/chromium/net/smoke/SmokeTestRequestCallback.java", + "test/smoketests/src/org/chromium/net/smoke/TestSupport.java", +] + +cronet_smoketests_native_common_srcs = cronet_smoketests_platform_only_common_srcs + [ + "test/smoketests/src/org/chromium/net/smoke/ChromiumNativeTestSupport.java", + "test/smoketests/src/org/chromium/net/smoke/NativeCronetTestCase.java", + ] + +android_library("cronet_smoketests_native_java") { + testonly = true + java_files = [ + "test/smoketests/src/org/chromium/net/smoke/Http2Test.java", + "test/smoketests/src/org/chromium/net/smoke/QuicTest.java", + ] + cronet_smoketests_native_common_srcs + + deps = [ + ":cronet_api_java", + ":cronet_test_apk_java", + "//base:base_java", + "//third_party/android_support_test_runner:runner_java", + "//third_party/netty4:netty_all_java", + ] +} + android_assets("cronet_test_apk_assets") { testonly = true @@ -639,13 +671,21 @@ loadable_modules = [ "$root_out_dir/libnetty-tcnative.so" ] deps = [ + ":cronet_combine_proguard_flags", ":cronet_test_apk_assets", - ":cronet_test_apk_java", ":cronet_test_apk_resources", "//base:base_java", "//third_party/netty-tcnative:netty-tcnative-so", ] + proguard_enabled = true + + proguard_configs = [ + "$target_gen_dir/cronet_impl_native_proguard.cfg", + "cronet_impl_common_proguard.cfg", + "cronet_impl_platform_proguard.cfg", + ] + run_findbugs_override = true } @@ -710,6 +750,7 @@ ":cronet_api_java", ":cronet_impl_all_java", ":cronet_javatests", + ":cronet_smoketests_native_java", ":cronet_test_apk_java", "//base:base_java", "//base:base_java_test_support", @@ -723,6 +764,113 @@ "//net:test_support", ] + proguard_enabled = true + + proguard_configs = [ "test/proguard.cfg" ] + + run_findbugs_override = true +} + +android_resources("cronet_smoketests_platform_only_apk_resources") { + testonly = true + resource_dirs = [ "test/smoketests/res/platform_only" ] + android_manifest = "test/AndroidManifest.xml" +} + +android_library("cronet_smoketests_platform_only_java") { + testonly = true + java_files = [ "test/smoketests/src/org/chromium/net/smoke/PlatformOnlyEngineTest.java" ] + cronet_smoketests_platform_only_common_srcs + deps = [ + ":cronet_api_java", + "//third_party/android_support_test_runner:runner_java", + "//third_party/netty4:netty_all_java", + ] +} + +android_apk("cronet_smoketests_platform_only_apk") { + testonly = true + apk_name = "PlatformOnlyEngineSmokeTest" + android_manifest = "test/AndroidManifest.xml" + java_files = [ "test/src/org/chromium/net/CronetTestApplication.java" ] + + proguard_enabled = true + proguard_configs = [ + "cronet_impl_common_proguard.cfg", + "cronet_impl_platform_proguard.cfg", + ] + + deps = [ + ":cronet_api_java", + ":cronet_impl_common_java", + ":cronet_impl_platform_java", + ":cronet_smoketests_platform_only_apk_resources", + ] + run_findbugs_override = true +} + +instrumentation_test_apk( + "cronet_smoketests_platform_only_instrumentation_apk") { + apk_name = "PlatformOnlyEngineSmokeTestInstrumentation" + apk_under_test = ":cronet_smoketests_platform_only_apk" + android_manifest = "test/javatests/AndroidManifest.xml" + deps = [ + ":cronet_smoketests_platform_only_java", + ] + + proguard_enabled = true + + proguard_configs = [ "test/proguard.cfg" ] + run_findbugs_override = true +} + +android_library("cronet_smoketests_missing_native_library_java") { + testonly = true + java_files = [ "test/smoketests/src/org/chromium/net/smoke/MissingNativeLibraryTest.java" ] + cronet_smoketests_native_common_srcs + deps = [ + ":cronet_api_java", + ":cronet_test_apk_java", + "//base:base_java", + "//third_party/android_support_test_runner:runner_java", + "//third_party/netty4:netty_all_java", + ] +} + +android_apk("cronet_smoketests_missing_native_library_apk") { + testonly = true + apk_name = "MissingNativeLibrarySmokeTest" + android_manifest = "test/AndroidManifest.xml" + deps = [ + ":cronet_api_java", + ":cronet_combine_proguard_flags", + ":cronet_impl_common_java", + ":cronet_impl_platform_java", + ":cronet_test_apk_resources", + ] + + proguard_enabled = true + proguard_configs = [ + "$target_gen_dir/cronet_impl_native_proguard.cfg", + "cronet_impl_common_proguard.cfg", + "cronet_impl_platform_proguard.cfg", + ] + + run_findbugs_override = true +} + +instrumentation_test_apk( + "cronet_smoketests_missing_native_library_instrumentation_apk") { + apk_name = "MissingNativeLibrarySmokeTestInstrumentation" + apk_under_test = ":cronet_smoketests_missing_native_library_apk" + android_manifest = "test/javatests/AndroidManifest.xml" + + deps = [ + ":cronet_smoketests_missing_native_library_java", + ] + + proguard_enabled = true + + proguard_configs = [ "test/proguard.cfg" ] + run_findbugs_override = true } @@ -763,7 +911,7 @@ proguard_configs = [ "$target_gen_dir/cronet_impl_native_proguard.cfg", "cronet_impl_common_proguard.cfg", - "test/javaperftests/proguard.cfg", + "test/proguard.cfg", "//base/android/proguard/chromium_apk.flags", ] }
diff --git a/components/cronet/android/cronet_impl_common_proguard.cfg b/components/cronet/android/cronet_impl_common_proguard.cfg index 531394a..a4ecdcd 100644 --- a/components/cronet/android/cronet_impl_common_proguard.cfg +++ b/components/cronet/android/cronet_impl_common_proguard.cfg
@@ -3,4 +3,8 @@ # This constructor is called using the reflection from Cronet API (cronet_api.jar). -keep class org.chromium.net.impl.CronetEngineBuilderImpl { public <init>(android.content.Context); -} \ No newline at end of file +} + +# This class should be explicitly kept to avoid failure if +# class/merging/horizontal proguard optimization is enabled. +-keep class org.chromium.net.impl.ImplVersion \ No newline at end of file
diff --git a/components/cronet/android/cronet_impl_native_proguard.cfg b/components/cronet/android/cronet_impl_native_proguard.cfg index 9ee247f..c0493225 100644 --- a/components/cronet/android/cronet_impl_native_proguard.cfg +++ b/components/cronet/android/cronet_impl_native_proguard.cfg
@@ -23,3 +23,7 @@ # https://android.googlesource.com/platform/sdk/+/marshmallow-mr1-release/files/proguard-android.txt#54 -dontwarn android.support.** +# This class should be explicitly kept to avoid failure if +# class/merging/horizontal proguard optimization is enabled. +-keep class org.chromium.base.CollectionUtil +
diff --git a/components/cronet/android/test/javaperftests/proguard.cfg b/components/cronet/android/test/javaperftests/proguard.cfg deleted file mode 100644 index b89cdaf..0000000 --- a/components/cronet/android/test/javaperftests/proguard.cfg +++ /dev/null
@@ -1,4 +0,0 @@ -# TODO(pauljensen): Remove when crbug.com/488192 is fixed. --dontwarn org.apache.http.** -# Javaperftest doesn't use Netty so ignore warnings. --dontwarn io.netty.**
diff --git a/components/cronet/android/test/proguard.cfg b/components/cronet/android/test/proguard.cfg new file mode 100644 index 0000000..22b0db0 --- /dev/null +++ b/components/cronet/android/test/proguard.cfg
@@ -0,0 +1,19 @@ +# Proguard configuration that is common for all type of tests. + +-keepattributes Signature,InnerClasses,SourceFile,LineNumberTable +-dontwarn io.netty.** +-keep class io.netty.** { *; } +-keep class org.chromium.net.smoke.ChromiumNativeTestSupport +-keep class org.chromium.net.smoke.ChromiumPlatformOnlyTestSupport + +# TODO(jbudorick): Remove when crbug.com/488192 is fixed. +-dontwarn org.apache.http.** + +-dontwarn android.support.test.runner.MonitoringInstrumentation + +# These classes should be explicitly kept to avoid failure if +# class/merging/horizontal proguard optimization is enabled. +# NOTE: make sure that only test classes are added to this list. +-keep class org.chromium.base.test.util.** +-keep class org.chromium.net.TestFilesInstaller +-keep class org.chromium.net.MetricsTestUtil \ No newline at end of file
diff --git a/components/cronet/android/test/smoketests/res/native/values/strings.xml b/components/cronet/android/test/smoketests/res/native/values/strings.xml new file mode 100644 index 0000000..8538e13 --- /dev/null +++ b/components/cronet/android/test/smoketests/res/native/values/strings.xml
@@ -0,0 +1,11 @@ +<?xml version="1.0" encoding="utf-8"?> + +<!-- Copyright 2016 The Chromium Authors. All rights reserved. + + Use of this source code is governed by a BSD-style license that can be + found in the LICENSE file. + --> + +<resources> + <string name="TestSupportImplClass">org.chromium.net.smoke.ChromiumNativeTestSupport</string> +</resources>
diff --git a/components/cronet/android/test/smoketests/res/native/xml/network_security_config.xml b/components/cronet/android/test/smoketests/res/native/xml/network_security_config.xml new file mode 100644 index 0000000..8a0e91a9 --- /dev/null +++ b/components/cronet/android/test/smoketests/res/native/xml/network_security_config.xml
@@ -0,0 +1,9 @@ +<?xml version="1.0" encoding="utf-8"?> + +<!-- Copyright 2016 The Chromium Authors. All rights reserved. + + Use of this source code is governed by a BSD-style license that can be + found in the LICENSE file. +--> + +<network-security-config/>
diff --git a/components/cronet/android/test/smoketests/res/platform_only/values/strings.xml b/components/cronet/android/test/smoketests/res/platform_only/values/strings.xml new file mode 100644 index 0000000..bb27fea --- /dev/null +++ b/components/cronet/android/test/smoketests/res/platform_only/values/strings.xml
@@ -0,0 +1,11 @@ +<?xml version="1.0" encoding="utf-8"?> + +<!-- Copyright 2016 The Chromium Authors. All rights reserved. + + Use of this source code is governed by a BSD-style license that can be + found in the LICENSE file. + --> + +<resources> + <string name="TestSupportImplClass">org.chromium.net.smoke.ChromiumPlatformOnlyTestSupport</string> +</resources>
diff --git a/components/cronet/android/test/smoketests/res/platform_only/xml/network_security_config.xml b/components/cronet/android/test/smoketests/res/platform_only/xml/network_security_config.xml new file mode 100644 index 0000000..8a0e91a9 --- /dev/null +++ b/components/cronet/android/test/smoketests/res/platform_only/xml/network_security_config.xml
@@ -0,0 +1,9 @@ +<?xml version="1.0" encoding="utf-8"?> + +<!-- Copyright 2016 The Chromium Authors. All rights reserved. + + Use of this source code is governed by a BSD-style license that can be + found in the LICENSE file. +--> + +<network-security-config/>
diff --git a/components/cronet/android/test/smoketests/src/org/chromium/net/smoke/ChromiumNativeTestSupport.java b/components/cronet/android/test/smoketests/src/org/chromium/net/smoke/ChromiumNativeTestSupport.java new file mode 100644 index 0000000..c80bc93 --- /dev/null +++ b/components/cronet/android/test/smoketests/src/org/chromium/net/smoke/ChromiumNativeTestSupport.java
@@ -0,0 +1,113 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.net.smoke; + +import android.content.Context; + +import org.json.JSONObject; + +import org.chromium.base.Log; +import org.chromium.net.CronetTestUtil; +import org.chromium.net.ExperimentalCronetEngine; + +/** + * Provides support for tests that depend on QUIC and HTTP2 servers. + */ +class ChromiumNativeTestSupport extends ChromiumPlatformOnlyTestSupport { + private static final String TAG = "NativeTestSupport"; + + @Override + public TestServer createTestServer(Context context, Protocol protocol) { + switch (protocol) { + case QUIC: + return new QuicTestServer(context); + case HTTP2: + return new Http2TestServer(context); + case HTTP1: + return super.createTestServer(context, protocol); + default: + throw new RuntimeException("Unknown server protocol: " + protocol); + } + } + + @Override + public void addHostResolverRules(JSONObject experimentalOptionsJson) { + try { + JSONObject hostResolverParams = CronetTestUtil.generateHostResolverRules(); + experimentalOptionsJson.put("HostResolverRules", hostResolverParams); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + @Override + public void installMockCertVerifierForTesting(ExperimentalCronetEngine.Builder builder) { + CronetTestUtil.setMockCertVerifierForTesting( + builder, org.chromium.net.QuicTestServer.createMockCertVerifier()); + } + + @Override + public void loadTestNativeLibrary() { + System.loadLibrary("cronet_tests"); + } + + private static class QuicTestServer implements TestServer { + private final Context mContext; + + QuicTestServer(Context context) { + mContext = context; + } + + @Override + public boolean start() { + org.chromium.net.QuicTestServer.startQuicTestServer(mContext); + return true; + } + + @Override + public void shutdown() { + org.chromium.net.QuicTestServer.shutdownQuicTestServer(); + } + + @Override + public String getSuccessURL() { + return org.chromium.net.QuicTestServer.getServerURL() + "/simple.txt"; + } + } + + private static class Http2TestServer implements TestServer { + private final Context mContext; + + Http2TestServer(Context context) { + mContext = context; + } + + @Override + public boolean start() { + try { + return org.chromium.net.Http2TestServer.startHttp2TestServer(mContext, + org.chromium.net.QuicTestServer.getServerCert(), + org.chromium.net.QuicTestServer.getServerCertKey()); + } catch (Exception e) { + Log.e(TAG, "Exception during Http2TestServer start", e); + return false; + } + } + + @Override + public void shutdown() { + try { + org.chromium.net.Http2TestServer.shutdownHttp2TestServer(); + } catch (Exception e) { + Log.e(TAG, "Exception during Http2TestServer shutdown", e); + } + } + + @Override + public String getSuccessURL() { + return org.chromium.net.Http2TestServer.getEchoMethodUrl(); + } + } +}
diff --git a/components/cronet/android/test/smoketests/src/org/chromium/net/smoke/ChromiumPlatformOnlyTestSupport.java b/components/cronet/android/test/smoketests/src/org/chromium/net/smoke/ChromiumPlatformOnlyTestSupport.java new file mode 100644 index 0000000..0545f04 --- /dev/null +++ b/components/cronet/android/test/smoketests/src/org/chromium/net/smoke/ChromiumPlatformOnlyTestSupport.java
@@ -0,0 +1,53 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.net.smoke; + +import android.content.Context; + +import org.json.JSONObject; + +import org.chromium.net.ExperimentalCronetEngine; + +import java.io.File; + +/** + * Tests support for Java only Cronet engine tests. This class should not depend on + * Chromium 'base' or 'net'. + */ +public class ChromiumPlatformOnlyTestSupport implements TestSupport { + @Override + public TestServer createTestServer(Context context, Protocol protocol) { + switch (protocol) { + case QUIC: + throw new IllegalArgumentException("QUIC is not supported"); + case HTTP2: + throw new IllegalArgumentException("HTTP2 is not supported"); + case HTTP1: + return new HttpTestServer(); + default: + throw new IllegalArgumentException("Unknown server protocol: " + protocol); + } + } + + @Override + public void processNetLog(Context context, File file) { + // Do nothing + } + + @Override + public void addHostResolverRules(JSONObject experimentalOptionsJson) { + throw new UnsupportedOperationException("Unsupported by ChromiumPlatformOnlyTestSupport"); + } + + @Override + public void installMockCertVerifierForTesting(ExperimentalCronetEngine.Builder builder) { + throw new UnsupportedOperationException("Unsupported by ChromiumPlatformOnlyTestSupport"); + } + + @Override + public void loadTestNativeLibrary() { + throw new UnsupportedOperationException("Unsupported by ChromiumPlatformOnlyTestSupport"); + } +}
diff --git a/components/cronet/android/test/smoketests/src/org/chromium/net/smoke/CronetSmokeTestCase.java b/components/cronet/android/test/smoketests/src/org/chromium/net/smoke/CronetSmokeTestCase.java new file mode 100644 index 0000000..dd2f7ad --- /dev/null +++ b/components/cronet/android/test/smoketests/src/org/chromium/net/smoke/CronetSmokeTestCase.java
@@ -0,0 +1,90 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.net.smoke; + +import android.content.Context; +import android.test.AndroidTestCase; + +import org.chromium.net.CronetEngine; +import org.chromium.net.ExperimentalCronetEngine; +import org.chromium.net.UrlResponseInfo; + +/** + * Base test class. This class should not import any classes from the org.chromium.base package. + */ +public class CronetSmokeTestCase extends AndroidTestCase { + /** + * The key in the string resource file that specifies {@link TestSupport} that should + * be instantiated. + */ + private static final String SUPPORT_IMPL_RES_KEY = "TestSupportImplClass"; + + protected ExperimentalCronetEngine.Builder mCronetEngineBuilder; + protected CronetEngine mCronetEngine; + protected TestSupport mTestSupport; + + @Override + protected void setUp() throws Exception { + super.setUp(); + mCronetEngineBuilder = new ExperimentalCronetEngine.Builder(getContext()); + initTestSupport(); + } + + @Override + protected void tearDown() throws Exception { + if (mCronetEngine != null) { + mCronetEngine.shutdown(); + } + super.tearDown(); + } + + protected void initCronetEngine() { + mCronetEngine = mCronetEngineBuilder.build(); + } + + static void assertSuccessfulNonEmptyResponse(SmokeTestRequestCallback callback, String url) { + // Check the request state + if (callback.getFinalState() == SmokeTestRequestCallback.State.Failed) { + throw new RuntimeException( + "The request failed with an error", callback.getFailureError()); + } + assertEquals(SmokeTestRequestCallback.State.Succeeded, callback.getFinalState()); + + // Check the response info + UrlResponseInfo responseInfo = callback.getResponseInfo(); + assertNotNull(responseInfo); + assertFalse(responseInfo.wasCached()); + assertEquals(url, responseInfo.getUrl()); + assertEquals(url, responseInfo.getUrlChain().get(responseInfo.getUrlChain().size() - 1)); + assertEquals(200, responseInfo.getHttpStatusCode()); + assertTrue(responseInfo.toString().length() > 0); + } + + static void assertJavaEngine(CronetEngine engine) { + assertNotNull(engine); + assertEquals("org.chromium.net.impl.JavaCronetEngine", engine.getClass().getName()); + } + + static void assertNativeEngine(CronetEngine engine) { + assertNotNull(engine); + assertEquals("org.chromium.net.impl.CronetUrlRequestContext", engine.getClass().getName()); + } + + /** + * Instantiates a concrete implementation of {@link TestSupport} interface. + * The name of the implementation class is determined dynamically by reading + * the value of |TestSupportImplClass| from the Android string resource file. + * + * @throws Exception if the class cannot be instantiated. + */ + private void initTestSupport() throws Exception { + Context ctx = getContext(); + String packageName = ctx.getPackageName(); + int resId = ctx.getResources().getIdentifier(SUPPORT_IMPL_RES_KEY, "string", packageName); + String className = ctx.getResources().getString(resId); + Class<? extends TestSupport> cl = Class.forName(className).asSubclass(TestSupport.class); + mTestSupport = cl.newInstance(); + } +}
diff --git a/components/cronet/android/test/smoketests/src/org/chromium/net/smoke/Http2Test.java b/components/cronet/android/test/smoketests/src/org/chromium/net/smoke/Http2Test.java new file mode 100644 index 0000000..c1d680d --- /dev/null +++ b/components/cronet/android/test/smoketests/src/org/chromium/net/smoke/Http2Test.java
@@ -0,0 +1,44 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.net.smoke; + +import android.support.test.filters.SmallTest; + +import org.chromium.net.UrlRequest; + +/** + * HTTP2 Tests. + */ +public class Http2Test extends NativeCronetTestCase { + private TestSupport.TestServer mServer; + + @Override + protected void setUp() throws Exception { + super.setUp(); + mServer = mTestSupport.createTestServer(getContext(), TestSupport.Protocol.HTTP2); + } + + @Override + protected void tearDown() throws Exception { + mServer.shutdown(); + super.tearDown(); + } + + // Test that HTTP/2 is enabled by default but QUIC is not. + @SmallTest + public void testHttp2() throws Exception { + mTestSupport.installMockCertVerifierForTesting(mCronetEngineBuilder); + initCronetEngine(); + assertTrue(mServer.start()); + SmokeTestRequestCallback callback = new SmokeTestRequestCallback(); + UrlRequest.Builder requestBuilder = mCronetEngine.newUrlRequestBuilder( + mServer.getSuccessURL(), callback, callback.getExecutor()); + requestBuilder.build().start(); + callback.blockForDone(); + + assertSuccessfulNonEmptyResponse(callback, mServer.getSuccessURL()); + assertEquals("h2", callback.getResponseInfo().getNegotiatedProtocol()); + } +}
diff --git a/components/cronet/android/test/smoketests/src/org/chromium/net/smoke/HttpTestServer.java b/components/cronet/android/test/smoketests/src/org/chromium/net/smoke/HttpTestServer.java new file mode 100644 index 0000000..6b412316 --- /dev/null +++ b/components/cronet/android/test/smoketests/src/org/chromium/net/smoke/HttpTestServer.java
@@ -0,0 +1,121 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.net.smoke; + +import android.os.ConditionVariable; +import android.util.Log; + +import static io.netty.handler.codec.http.HttpResponseStatus.OK; +import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1; + +import io.netty.bootstrap.ServerBootstrap; +import io.netty.buffer.Unpooled; +import io.netty.channel.Channel; +import io.netty.channel.ChannelFutureListener; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.ChannelPipeline; +import io.netty.channel.EventLoopGroup; +import io.netty.channel.SimpleChannelInboundHandler; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.channel.socket.SocketChannel; +import io.netty.channel.socket.nio.NioServerSocketChannel; +import io.netty.handler.codec.http.DefaultFullHttpResponse; +import io.netty.handler.codec.http.FullHttpResponse; +import io.netty.handler.codec.http.HttpRequestDecoder; +import io.netty.handler.codec.http.HttpResponseEncoder; +import io.netty.handler.logging.LogLevel; +import io.netty.handler.logging.LoggingHandler; +import io.netty.util.CharsetUtil; + +/** + * A simple HTTP server for testing. + */ +public class HttpTestServer implements TestSupport.TestServer { + private static final String TAG = "HttpTestServer"; + private static final String HOST = "127.0.0.1"; + private static final int PORT = 8080; + + private Channel mServerChannel; + private ConditionVariable mStartBlock = new ConditionVariable(); + private ConditionVariable mShutdownBlock = new ConditionVariable(); + + @Override + public boolean start() { + new Thread(new Runnable() { + @Override + public void run() { + try { + HttpTestServer.this.run(); + } catch (Exception e) { + Log.e(TAG, "Unable to start HttpTestServer", e); + } + } + }).start(); + // Return false if the server cannot start within 5 seconds. + return mStartBlock.block(5000); + } + + @Override + public void shutdown() { + if (mServerChannel != null) { + mServerChannel.close(); + boolean success = mShutdownBlock.block(10000); + if (!success) { + Log.e(TAG, "Unable to shutdown the server. Is it already dead?"); + } + mServerChannel = null; + } + } + + @Override + public String getSuccessURL() { + return getServerUrl() + "/success"; + } + + private String getServerUrl() { + return "http://" + HOST + ":" + PORT; + } + + private void run() throws Exception { + EventLoopGroup bossGroup = new NioEventLoopGroup(1); + EventLoopGroup workerGroup = new NioEventLoopGroup(4); + try { + ServerBootstrap b = new ServerBootstrap(); + b.group(bossGroup, workerGroup) + .channel(NioServerSocketChannel.class) + .handler(new LoggingHandler(LogLevel.INFO)) + .childHandler(new ChannelInitializer<SocketChannel>() { + @Override + public void initChannel(SocketChannel ch) throws Exception { + ChannelPipeline p = ch.pipeline(); + p.addLast(new HttpRequestDecoder()); + p.addLast(new HttpResponseEncoder()); + p.addLast(new TestServerHandler()); + } + }); + + // Start listening fo incoming connections. + mServerChannel = b.bind(PORT).sync().channel(); + mStartBlock.open(); + // Block until the channel is closed. + mServerChannel.closeFuture().sync(); + mShutdownBlock.open(); + Log.i(TAG, "HttpServer stopped"); + } finally { + workerGroup.shutdownGracefully(); + bossGroup.shutdownGracefully(); + } + } + + private static class TestServerHandler extends SimpleChannelInboundHandler { + @Override + protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception { + FullHttpResponse response = new DefaultFullHttpResponse( + HTTP_1_1, OK, Unpooled.copiedBuffer("Hello!", CharsetUtil.UTF_8)); + ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE); + } + } +}
diff --git a/components/cronet/android/test/smoketests/src/org/chromium/net/smoke/MissingNativeLibraryTest.java b/components/cronet/android/test/smoketests/src/org/chromium/net/smoke/MissingNativeLibraryTest.java new file mode 100644 index 0000000..23803b7 --- /dev/null +++ b/components/cronet/android/test/smoketests/src/org/chromium/net/smoke/MissingNativeLibraryTest.java
@@ -0,0 +1,48 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.net.smoke; + +import android.support.test.filters.SmallTest; + +import org.chromium.net.CronetEngine; +import org.chromium.net.ExperimentalCronetEngine; + +/** + * Tests scenarios when the native shared library file is missing in the APK or was built for a + * wrong architecture. + */ +public class MissingNativeLibraryTest extends CronetSmokeTestCase { + /** + * If the ".so" file is missing, instantiating the Cronet engine should throw an exception. + */ + @SmallTest + public void testExceptionWhenSoFileIsAbsent() throws Exception { + ExperimentalCronetEngine.Builder builder = + new ExperimentalCronetEngine.Builder(getContext()); + try { + builder.build(); + fail("Expected exception since the shared library '.so' file is absent"); + } catch (Throwable t) { + // Find the root cause. + while (t.getCause() != null) { + t = t.getCause(); + } + assertEquals(UnsatisfiedLinkError.class, t.getClass()); + } + } + + /** + * Tests the enableLegacyMode API that allows the embedder to force JavaCronetEngine + * instantiation even when the native Cronet engine implementation is available. + */ + @SmallTest + public void testEnableLegacyMode() throws Exception { + ExperimentalCronetEngine.Builder builder = + new ExperimentalCronetEngine.Builder(getContext()); + builder.enableLegacyMode(true); + CronetEngine engine = builder.build(); + assertJavaEngine(engine); + } +}
diff --git a/components/cronet/android/test/smoketests/src/org/chromium/net/smoke/NativeCronetTestCase.java b/components/cronet/android/test/smoketests/src/org/chromium/net/smoke/NativeCronetTestCase.java new file mode 100644 index 0000000..6fcb18d --- /dev/null +++ b/components/cronet/android/test/smoketests/src/org/chromium/net/smoke/NativeCronetTestCase.java
@@ -0,0 +1,55 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.net.smoke; + +import org.chromium.base.ContextUtils; +import org.chromium.base.PathUtils; + +import java.io.File; + +/** + * Test base class for testing native Engine implementation. This class can import classes from the + * org.chromium.base package. + */ +public class NativeCronetTestCase extends CronetSmokeTestCase { + private static final String PRIVATE_DATA_DIRECTORY_SUFFIX = "cronet_test"; + private static final String LOGFILE_NAME = "cronet-netlog.json"; + + @Override + protected void setUp() throws Exception { + super.setUp(); + ContextUtils.initApplicationContext(getContext().getApplicationContext()); + PathUtils.setPrivateDataDirectorySuffix(PRIVATE_DATA_DIRECTORY_SUFFIX); + mTestSupport.loadTestNativeLibrary(); + } + + @Override + protected void tearDown() throws Exception { + stopAndSaveNetLog(); + super.tearDown(); + } + + @Override + protected void initCronetEngine() { + super.initCronetEngine(); + assertNativeEngine(mCronetEngine); + startNetLog(); + } + + private void startNetLog() { + if (mCronetEngine != null) { + mCronetEngine.startNetLogToFile( + PathUtils.getDataDirectory() + "/" + LOGFILE_NAME, false); + } + } + + private void stopAndSaveNetLog() { + if (mCronetEngine == null) return; + mCronetEngine.stopNetLog(); + File netLogFile = new File(PathUtils.getDataDirectory(), LOGFILE_NAME); + if (!netLogFile.exists()) return; + mTestSupport.processNetLog(getContext(), netLogFile); + } +}
diff --git a/components/cronet/android/test/smoketests/src/org/chromium/net/smoke/PlatformOnlyEngineTest.java b/components/cronet/android/test/smoketests/src/org/chromium/net/smoke/PlatformOnlyEngineTest.java new file mode 100644 index 0000000..71cd784 --- /dev/null +++ b/components/cronet/android/test/smoketests/src/org/chromium/net/smoke/PlatformOnlyEngineTest.java
@@ -0,0 +1,47 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.net.smoke; + +import android.support.test.filters.SmallTest; + +import org.chromium.net.UrlRequest; + +/** + * Tests scenario when an app doesn't contain the native Cronet implementation. + */ +public class PlatformOnlyEngineTest extends CronetSmokeTestCase { + private String mURL; + private TestSupport.TestServer mServer; + + @Override + protected void setUp() throws Exception { + super.setUp(); + // Java-only implementation of the Cronet engine only supports Http/1 protocol. + mServer = mTestSupport.createTestServer(getContext(), TestSupport.Protocol.HTTP1); + assertTrue(mServer.start()); + mURL = mServer.getSuccessURL(); + } + + @Override + protected void tearDown() throws Exception { + mServer.shutdown(); + super.tearDown(); + } + + /** + * Test a successful response when a request is sent by the Java Cronet Engine. + */ + @SmallTest + public void testSuccessfulResponse() { + initCronetEngine(); + assertJavaEngine(mCronetEngine); + SmokeTestRequestCallback callback = new SmokeTestRequestCallback(); + UrlRequest.Builder requestBuilder = + mCronetEngine.newUrlRequestBuilder(mURL, callback, callback.getExecutor()); + requestBuilder.build().start(); + callback.blockForDone(); + assertSuccessfulNonEmptyResponse(callback, mURL); + } +}
diff --git a/components/cronet/android/test/smoketests/src/org/chromium/net/smoke/QuicTest.java b/components/cronet/android/test/smoketests/src/org/chromium/net/smoke/QuicTest.java new file mode 100644 index 0000000..5719a93 --- /dev/null +++ b/components/cronet/android/test/smoketests/src/org/chromium/net/smoke/QuicTest.java
@@ -0,0 +1,72 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.net.smoke; + +import android.support.test.filters.SmallTest; + +import org.json.JSONObject; + +import static org.chromium.net.smoke.TestSupport.Protocol.QUIC; + +import org.chromium.net.UrlRequest; + +import java.net.URL; + +/** + * QUIC Tests. + */ +public class QuicTest extends NativeCronetTestCase { + private TestSupport.TestServer mServer; + + @Override + protected void setUp() throws Exception { + super.setUp(); + mServer = mTestSupport.createTestServer(getContext(), QUIC); + } + + @Override + protected void tearDown() throws Exception { + mServer.shutdown(); + super.tearDown(); + } + + @SmallTest + public void testQuic() throws Exception { + assertTrue(mServer.start()); + final String urlString = mServer.getSuccessURL(); + final URL url = new URL(urlString); + + mCronetEngineBuilder.enableQuic(true); + mCronetEngineBuilder.addQuicHint(url.getHost(), url.getPort(), url.getPort()); + mTestSupport.installMockCertVerifierForTesting(mCronetEngineBuilder); + + JSONObject quicParams = new JSONObject().put("delay_tcp_race", true); + JSONObject experimentalOptions = new JSONObject().put("QUIC", quicParams); + mTestSupport.addHostResolverRules(experimentalOptions); + experimentalOptions.put("host_whitelist", url.getHost()); + + mCronetEngineBuilder.setExperimentalOptions(experimentalOptions.toString()); + + initCronetEngine(); + + // QUIC is not guaranteed to win the race even with |delay_tcp_race| set, so try + // multiple times. + boolean quicNegotiated = false; + + for (int i = 0; i < 5; i++) { + SmokeTestRequestCallback callback = new SmokeTestRequestCallback(); + UrlRequest.Builder requestBuilder = + mCronetEngine.newUrlRequestBuilder(urlString, callback, callback.getExecutor()); + requestBuilder.build().start(); + callback.blockForDone(); + assertSuccessfulNonEmptyResponse(callback, urlString); + if (callback.getResponseInfo().getNegotiatedProtocol().startsWith("http/2+quic/")) { + quicNegotiated = true; + break; + } + } + assertTrue(quicNegotiated); + } +}
diff --git a/components/cronet/android/test/smoketests/src/org/chromium/net/smoke/SmokeTestRequestCallback.java b/components/cronet/android/test/smoketests/src/org/chromium/net/smoke/SmokeTestRequestCallback.java new file mode 100644 index 0000000..fa07bfe --- /dev/null +++ b/components/cronet/android/test/smoketests/src/org/chromium/net/smoke/SmokeTestRequestCallback.java
@@ -0,0 +1,126 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.net.smoke; + +import static junit.framework.Assert.assertTrue; + +import android.os.ConditionVariable; + +import org.chromium.net.CronetException; +import org.chromium.net.UrlRequest; +import org.chromium.net.UrlResponseInfo; + +import java.nio.ByteBuffer; +import java.util.concurrent.Executor; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +/** + * A simple boilerplate implementation of {@link UrlRequest.Callback} that is used by smoke tests. + */ +class SmokeTestRequestCallback extends UrlRequest.Callback { + private static final int READ_BUFFER_SIZE = 10000; + + // An executor that is used to execute {@link UrlRequest.Callback UrlRequest callbacks}. + private ExecutorService mExecutor = Executors.newSingleThreadExecutor(); + + // Signals when the request is done either successfully or not. + private final ConditionVariable mDone = new ConditionVariable(); + + // The state of the request. + public enum State { NotSet, Succeeded, Failed, Canceled } + + // The current state of the request. + private State mState = State.NotSet; + + // Response info of the finished request. + private UrlResponseInfo mResponseInfo; + + // Holds an error if the request failed. + private CronetException mError; + + @Override + public void onRedirectReceived(UrlRequest request, UrlResponseInfo info, String newLocationUrl) + throws Exception { + request.followRedirect(); + } + + @Override + public void onResponseStarted(UrlRequest request, UrlResponseInfo info) throws Exception { + request.read(ByteBuffer.allocateDirect(READ_BUFFER_SIZE)); + } + + @Override + public void onReadCompleted(UrlRequest request, UrlResponseInfo info, ByteBuffer byteBuffer) + throws Exception { + request.read(ByteBuffer.allocateDirect(READ_BUFFER_SIZE)); + } + + @Override + public void onSucceeded(UrlRequest request, UrlResponseInfo info) { + done(State.Succeeded, info); + } + + @Override + public void onFailed(UrlRequest request, UrlResponseInfo info, CronetException error) { + mError = error; + done(State.Failed, info); + } + + @Override + public void onCanceled(UrlRequest request, UrlResponseInfo info) { + done(State.Canceled, info); + } + + /** + * Returns the request executor. + * + * @return the executor. + */ + public Executor getExecutor() { + return mExecutor; + } + + /** + * Blocks until the request is either succeeded, failed or canceled. + */ + public void blockForDone() { + mDone.block(); + } + + /** + * Returns the final state of the request. + * + * @return the state. + */ + public State getFinalState() { + return mState; + } + + /** + * Returns an error that was passed to {@link #onFailed} when the request failed. + * + * @return the error if the request failed; {@code null} otherwise. + */ + public CronetException getFailureError() { + return mError; + } + + /** + * Returns {@link UrlResponseInfo} of the finished response. + * + * @return the response info. {@code null} if the request hasn't completed yet. + */ + public UrlResponseInfo getResponseInfo() { + return mResponseInfo; + } + + private void done(State finalState, UrlResponseInfo responseInfo) { + assertTrue(mState == State.NotSet); + mResponseInfo = responseInfo; + mState = finalState; + mDone.open(); + } +}
diff --git a/components/cronet/android/test/smoketests/src/org/chromium/net/smoke/TestSupport.java b/components/cronet/android/test/smoketests/src/org/chromium/net/smoke/TestSupport.java new file mode 100644 index 0000000..f205106 --- /dev/null +++ b/components/cronet/android/test/smoketests/src/org/chromium/net/smoke/TestSupport.java
@@ -0,0 +1,93 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.net.smoke; + +import android.content.Context; + +import org.json.JSONObject; + +import org.chromium.net.ExperimentalCronetEngine; + +import java.io.File; + +/** + * Provides support for tests, so they can be run in different environments against different + * servers. It contains methods, which behavior can be different in different testing environments. + * The concrete implementation of this interface is determined dynamically at runtime by reading + * the value of |TestSupportImplClass| from the Android string resource file. + */ +public interface TestSupport { + enum Protocol { + HTTP1, + HTTP2, + QUIC, + } + + /** + * Creates a new test server that supports a given {@code protocol}. + * + * @param context context. + * @param protocol protocol that should be supported by the server. + * @return an instance of the server. + * + * @throws UnsupportedOperationException if the implementation of this interface + * does not support a given {@code protocol}. + */ + TestServer createTestServer(Context context, Protocol protocol); + + /** + * This method is called at the end of a test run if the netlog is available. An implementer + * of {@link TestSupport} can use it to process the result netlog; e.g., to copy the netlog + * to a directory where all test logs are collected. This method is optional and can be no-op. + * + * @param file the netlog file. + */ + void processNetLog(Context context, File file); + + /** + * Adds host resolver rules to a given experimental option JSON file. + * This method is optional. + * + * @param experimentalOptionsJson experimental options. + */ + void addHostResolverRules(JSONObject experimentalOptionsJson); + + /** + * Installs mock certificate verifier for a given {@code builder}. + * This method is optional. + * + * @param builder that should have the verifier installed. + */ + void installMockCertVerifierForTesting(ExperimentalCronetEngine.Builder builder); + + /** + * Loads a native library that is required for testing if any required. + */ + void loadTestNativeLibrary(); + + /** + * A test server. + */ + interface TestServer { + /** + * Starts the server. + * + * @return true if the server started successfully. + */ + boolean start(); + + /** + * Shuts down the server. + */ + void shutdown(); + + /** + * Return a URL that can be used by the test code to receive a successful response. + * + * @return the URL as a string. + */ + String getSuccessURL(); + } +}
diff --git a/extensions/browser/event_listener_map.cc b/extensions/browser/event_listener_map.cc index bd2524c..12dd5b4 100644 --- a/extensions/browser/event_listener_map.cc +++ b/extensions/browser/event_listener_map.cc
@@ -14,6 +14,7 @@ #include "extensions/browser/event_router.h" #include "ipc/ipc_message.h" #include "url/gurl.h" +#include "url/origin.h" using base::DictionaryValue; @@ -37,7 +38,12 @@ const GURL& listener_url, content::RenderProcessHost* process, std::unique_ptr<base::DictionaryValue> filter) { - return base::WrapUnique(new EventListener(event_name, "", listener_url, + // Use only the origin to identify the event listener, e.g. chrome://settings + // for chrome://settings/accounts, to avoid multiple events being triggered + // for the same process. See crbug.com/536858 for details. // TODO(devlin): If + // we dispatched events to processes more intelligently this could be avoided. + return base::WrapUnique(new EventListener(event_name, "", + url::Origin(listener_url).GetURL(), process, std::move(filter))); }
diff --git a/ios/chrome/browser/ui/browser_view_controller.mm b/ios/chrome/browser/ui/browser_view_controller.mm index a612ccb..9263577d 100644 --- a/ios/chrome/browser/ui/browser_view_controller.mm +++ b/ios/chrome/browser/ui/browser_view_controller.mm
@@ -3872,7 +3872,10 @@ switch (command) { case IDC_BACK: - [[_model currentTab] goBack]; + // TODO(crbug.com.677160): Remove |canGoBack| check. + if ([_model currentTab].canGoBack) { + [[_model currentTab] goBack]; + } break; case IDC_BOOKMARK_PAGE: [self initializeBookmarkInteractionController]; @@ -3913,7 +3916,10 @@ [self searchFindInPage]; break; case IDC_FORWARD: - [[_model currentTab] goForward]; + // TODO(crbug.com.677160): Remove |canGoForward| check. + if ([_model currentTab].canGoForward) { + [[_model currentTab] goForward]; + } break; case IDC_FULLSCREEN: NOTIMPLEMENTED();
diff --git a/testing/buildbot/chromium.fyi.json b/testing/buildbot/chromium.fyi.json index 5cb805d..9eb52c84 100644 --- a/testing/buildbot/chromium.fyi.json +++ b/testing/buildbot/chromium.fyi.json
@@ -1694,7 +1694,6 @@ { "args": [ "--enable-browser-side-navigation", - "--test-launcher-filter-file=../../testing/buildbot/filters/browser-side-navigation.linux.browser_tests.filter", "--test-launcher-retry-limit=6" ], "swarming": { @@ -1735,14 +1734,6 @@ "scripts": [ { "args": [ - "browser_tests", - "../../testing/buildbot/filters/browser-side-navigation.linux.browser_tests.filter" - ], - "name": "count_filtered_tests_browser_tests", - "script": "count_filtered_tests.py" - }, - { - "args": [ "content_browsertests", "../../testing/buildbot/filters/browser-side-navigation.linux.content_browsertests.filter" ],
diff --git a/testing/buildbot/chromium.linux.json b/testing/buildbot/chromium.linux.json index 5dd210d..fa85b29 100644 --- a/testing/buildbot/chromium.linux.json +++ b/testing/buildbot/chromium.linux.json
@@ -2812,8 +2812,7 @@ }, { "args": [ - "--enable-browser-side-navigation", - "--test-launcher-filter-file=../../testing/buildbot/filters/browser-side-navigation.linux.browser_tests.filter" + "--enable-browser-side-navigation" ], "name": "browser_side_navigation_browser_tests", "swarming": { @@ -3482,8 +3481,7 @@ }, { "args": [ - "--enable-browser-side-navigation", - "--test-launcher-filter-file=../../testing/buildbot/filters/browser-side-navigation.linux.browser_tests.filter" + "--enable-browser-side-navigation" ], "name": "browser_side_navigation_browser_tests", "swarming": {
diff --git a/testing/buildbot/filters/BUILD.gn b/testing/buildbot/filters/BUILD.gn index 0fdb768..6804ad32 100644 --- a/testing/buildbot/filters/BUILD.gn +++ b/testing/buildbot/filters/BUILD.gn
@@ -20,7 +20,6 @@ testonly = true data = [ - "//testing/buildbot/filters/browser-side-navigation.linux.browser_tests.filter", "//testing/buildbot/filters/isolate-extensions.browser_tests.filter", "//testing/buildbot/filters/mash.browser_tests.filter", "//testing/buildbot/filters/mojo.fyi.browser_tests.filter",
diff --git a/testing/buildbot/filters/browser-side-navigation.linux.browser_tests.filter b/testing/buildbot/filters/browser-side-navigation.linux.browser_tests.filter deleted file mode 100644 index 6f3fc301..0000000 --- a/testing/buildbot/filters/browser-side-navigation.linux.browser_tests.filter +++ /dev/null
@@ -1,6 +0,0 @@ --ChromeServiceWorkerTest.FallbackMainResourceRequestWhenJSDisabled - -# https://crbug.com/652767: NavigationHandle::GetResponseHeaders sometimes -# returns null for browser-side navigations --PageLoadMetricsBrowserTest.Ignore204Pages --PageLoadMetricsBrowserTest.IgnoreDownloads
diff --git a/testing/buildbot/filters/browser-side-navigation.linux.content_browsertests.filter b/testing/buildbot/filters/browser-side-navigation.linux.content_browsertests.filter index 15285ed..730c7a8 100644 --- a/testing/buildbot/filters/browser-side-navigation.linux.content_browsertests.filter +++ b/testing/buildbot/filters/browser-side-navigation.linux.content_browsertests.filter
@@ -1,7 +1,2 @@ --NavigationControllerBrowserTest.EnsureSamePageNavigationUpdatesFrameNavigationEntry - # Browser-initiated fragment navigations are handled improperly. https://crbug.com/663777 -NavigationControllerBrowserTest.SamePageBrowserInitiated - -# Fail in Debug --NavigationControllerBrowserTest.RaceCrossOriginNavigationAndSamePageHistoryNavigation
diff --git a/third_party/WebKit/LayoutTests/FlagExpectations/enable-slimming-paint-v2 b/third_party/WebKit/LayoutTests/FlagExpectations/enable-slimming-paint-v2 index b7f2a11e..c24abe4 100644 --- a/third_party/WebKit/LayoutTests/FlagExpectations/enable-slimming-paint-v2 +++ b/third_party/WebKit/LayoutTests/FlagExpectations/enable-slimming-paint-v2
@@ -538,6 +538,7 @@ Bug(none) fast/canvas/canvas-composite-video.html [ Failure ] Bug(none) fast/canvas/canvas-css-clip-path.html [ Failure ] Bug(none) fast/canvas/webgl/pixelated.html [ Failure ] +crbug.com/668342 fast/clip/010.html [ Failure ] Bug(none) fast/clip/nestedTransparencyClip.html [ Failure ] Bug(none) fast/clip/outline-overflowClip.html [ Failure ] Bug(none) fast/clip/overflow-border-radius-clip.html [ Failure ] @@ -1247,6 +1248,7 @@ Bug(none) svg/custom/grayscale-gradient-mask.svg [ Failure ] Bug(none) svg/custom/group-opacity.svg [ Failure ] Bug(none) svg/custom/image-with-preserveAspectRatio-none.html [ Failure ] +Bug(none) svg/custom/image-with-transform-clip-filter.svg [ Failure ] Bug(none) svg/custom/inline-svg-in-xhtml.xml [ Failure ] Bug(none) svg/custom/invalid-css.svg [ Failure ] Bug(none) svg/custom/invalid-stroke-hex.svg [ Failure ] @@ -1335,6 +1337,7 @@ Bug(none) svg/custom/use-transform.svg [ Failure ] Bug(none) svg/custom/viewBox-hit.svg [ Failure ] Bug(none) svg/custom/viewbox-syntax.svg [ Failure ] +Bug(none) svg/custom/viewport-clippath-invalidation.html [ Failure ] Bug(none) svg/custom/visibility-override-clip.svg [ Failure ] Bug(none) svg/custom/visibility-override-mask.svg [ Failure ] Bug(none) svg/custom/visited-link-color.svg [ Failure ] @@ -2206,3 +2209,12 @@ # SPv1 does not understand how to apply transforms properly in PaintLayerClipper (the issue is # also tracked in crbug.com/548184). # transforms/transform-overflow.html + +# Subpixel adjustments due to differences in compositing +crbug.com/668342 fast/pagination/auto-height-with-break.html [ Failure ] +crbug.com/668342 svg/custom/non-scaling-stroke-update.svg [ Failure ] +crbug.com/668342 fast/css/transform-default-parameter.html [ Failure ] +crbug.com/668342 svg/custom/use-css-events.svg [ Failure ] +crbug.com/668342 svg/text/text-layout-crash.html [ Failure ] +crbug.com/668342 fast/multicol/span/invalid-spanner-in-transform.html [ Failure ] +crbug.com/668342 images/color-profile-iframe.html [ Failure ]
diff --git a/third_party/WebKit/LayoutTests/TestExpectations b/third_party/WebKit/LayoutTests/TestExpectations index 9726cded..e08886a 100644 --- a/third_party/WebKit/LayoutTests/TestExpectations +++ b/third_party/WebKit/LayoutTests/TestExpectations
@@ -43,7 +43,9 @@ crbug.com/596780 virtual/spv2/compositing/framesets/composited-frame-alignment.html [ Pass ] crbug.com/596780 virtual/spv2/compositing/geometry/outline-change.html [ Pass ] crbug.com/600618 virtual/spv2/svg/custom/object-current-scale.html [ Pass ] -crbug.com/600618 virtual/spv2/svg/custom/object-sizing-explicit-height.xhtml [ Pass ] + +# Re-add this once it rebaselines. +# crbug.com/600618 virtual/spv2/svg/custom/object-sizing-explicit-height.xhtml [ Pass ] # SkiaBitLocker should avoid allocating huge offscreen buffer crbug.com/605812 [ Mac ] virtual/spv2/fast/overflow/overflow-height-float-not-removed-crash.html [ Skip ] crbug.com/605812 [ Mac ] virtual/spv2/fast/overflow/overflow-height-float-not-removed-crash2.html [ Skip ] @@ -698,6 +700,8 @@ crbug.com/520194 http/tests/xmlhttprequest/timeout/xmlhttprequest-timeout-worker-overridesexpires.html [ Failure Pass ] crbug.com/520194 virtual/mojo-loading/http/tests/xmlhttprequest/timeout/xmlhttprequest-timeout-worker-overridesexpires.html [ Failure Pass ] +crbug.com/668342 virtual/spv2/svg/custom/object-sizing-explicit-height.xhtml [ NeedsRebaseline ] + crbug.com/410974 fast/scroll-behavior/scroll-customization/scrollstate-basic.html [ Pass Failure ] crbug.com/410974 fast/scroll-behavior/scroll-customization/scrollstate-consume-deltas.html [ Pass Failure ] crbug.com/410974 fast/scroll-behavior/scroll-customization/scrollstate-consume-deltas-throw.html [ Pass Failure ] @@ -2406,3 +2410,7 @@ # Temporarily disable sharedarraybuffer test to land API change in v8 crbug.com/676063 virtual/sharedarraybuffer/fast/workers/worker-sharedarraybuffer-transfer.html [ Pass Timeout ] + +# Flaky on Win7 (dbg) +crbug.com/677145 [ Win7 Debug ] inspector/tracing/timeline-js/timeline-script-tag-1.html [ Pass Failure ] +crbug.com/677145 [ Win7 Debug ] virtual/threaded/inspector/tracing/timeline-js/timeline-script-tag-1.html [ Pass Failure ]
diff --git a/third_party/WebKit/LayoutTests/paint/invalidation/overflow-hidden-yet-scrolled-with-custom-scrollbar-expected.txt b/third_party/WebKit/LayoutTests/paint/invalidation/overflow-hidden-yet-scrolled-with-custom-scrollbar-expected.txt index 20097e11..47d9d336 100644 --- a/third_party/WebKit/LayoutTests/paint/invalidation/overflow-hidden-yet-scrolled-with-custom-scrollbar-expected.txt +++ b/third_party/WebKit/LayoutTests/paint/invalidation/overflow-hidden-yet-scrolled-with-custom-scrollbar-expected.txt
@@ -30,6 +30,11 @@ }, { "object": "LayoutBlockFlow DIV id='target'", + "rect": [1, 601, 100, 100], + "reason": "subtree" + }, + { + "object": "LayoutBlockFlow DIV id='target'", "rect": [1, 201, 100, 100], "reason": "subtree" },
diff --git a/third_party/WebKit/Source/core/frame/FrameView.cpp b/third_party/WebKit/Source/core/frame/FrameView.cpp index be913bd..77f87433 100644 --- a/third_party/WebKit/Source/core/frame/FrameView.cpp +++ b/third_party/WebKit/Source/core/frame/FrameView.cpp
@@ -1911,8 +1911,7 @@ m_frame->document()->fetcher()->updateAllImageResourcePriorities(); } -void FrameView::updateLayersAndCompositingAfterScrollIfNeeded( - const ScrollOffset& scrollDelta) { +void FrameView::updateLayersAndCompositingAfterScrollIfNeeded() { // Nothing to do after scrolling if there are no fixed position elements. if (!hasViewportConstrainedObjects()) return; @@ -1925,7 +1924,8 @@ // TODO(skobes): Resolve circular dependency between scroll offset and // compositing state, and remove this disabler. https://crbug.com/420741 DisableCompositingQueryAsserts disabler; - layer->updateLayerPositionsAfterOverflowScroll(scrollDelta); + layer->updateLayerPositionsAfterOverflowScroll(); + layoutObject->setMayNeedPaintInvalidationSubtree(); } } @@ -3804,7 +3804,7 @@ if (scrollTypeClearsFragmentAnchor(scrollType)) clearFragmentAnchor(); - updateLayersAndCompositingAfterScrollIfNeeded(scrollDelta); + updateLayersAndCompositingAfterScrollIfNeeded(); Document* document = m_frame->document(); document->enqueueScrollEventForNode(document);
diff --git a/third_party/WebKit/Source/core/frame/FrameView.h b/third_party/WebKit/Source/core/frame/FrameView.h index 6f8b92d..47517031 100644 --- a/third_party/WebKit/Source/core/frame/FrameView.h +++ b/third_party/WebKit/Source/core/frame/FrameView.h
@@ -933,8 +933,7 @@ void scrollToFragmentAnchor(); void didScrollTimerFired(TimerBase*); - void updateLayersAndCompositingAfterScrollIfNeeded( - const ScrollOffset& scrollDelta); + void updateLayersAndCompositingAfterScrollIfNeeded(); static bool computeCompositedSelection(LocalFrame&, CompositedSelection&); void updateCompositedSelectionIfNeeded();
diff --git a/third_party/WebKit/Source/core/layout/LayoutBoxModelObject.cpp b/third_party/WebKit/Source/core/layout/LayoutBoxModelObject.cpp index bd0101a2..16faf3f 100644 --- a/third_party/WebKit/Source/core/layout/LayoutBoxModelObject.cpp +++ b/third_party/WebKit/Source/core/layout/LayoutBoxModelObject.cpp
@@ -306,17 +306,14 @@ } if (RuntimeEnabledFeatures::slimmingPaintInvalidationEnabled()) { - // hasLayer status will affect whether to create localBorderBoxProperties. - // hasTransformRelatedProperty will affect whether to create transform node. - if (hadLayer != hasLayer() || - hadTransformRelatedProperty != hasTransformRelatedProperty()) { - setNeedsPaintPropertyUpdate(); - } else if (oldStyle && oldStyle->position() != styleRef().position() && - (oldStyle->position() == FixedPosition || - styleRef().position() == FixedPosition)) { - // Fixed-position status affects whether to create paintOffsetTranslation. - // TODO(chrishtr): Update the condition here when changing the condition - // in PaintPropertyTreeBuilder::updatePaintOffsetTranslation(). + if ((oldStyle && oldStyle->position() != styleRef().position()) || + hadLayer != hasLayer()) { + // This may affect paint properties of the current object, and descendants + // even if paint properties of the current object won't change. E.g. the + // stacking context and/or containing block of descendants may change. + setSubtreeNeedsPaintPropertyUpdate(); + } else if (hadTransformRelatedProperty != hasTransformRelatedProperty()) { + // This affects whether to create transform node. setNeedsPaintPropertyUpdate(); } }
diff --git a/third_party/WebKit/Source/core/layout/LayoutObject.cpp b/third_party/WebKit/Source/core/layout/LayoutObject.cpp index 574b9fa..ec4269c 100644 --- a/third_party/WebKit/Source/core/layout/LayoutObject.cpp +++ b/third_party/WebKit/Source/core/layout/LayoutObject.cpp
@@ -1259,13 +1259,6 @@ return rect; } -void LayoutObject::adjustPreviousPaintInvalidationForScrollIfNeeded( - const DoubleSize& scrollDelta) { - if (containerForPaintInvalidation().usesCompositedScrolling()) - return; - m_previousVisualRect.move(LayoutSize(scrollDelta)); -} - void LayoutObject::clearPreviousVisualRects() { setPreviousVisualRect(LayoutRect()); ObjectPaintInvalidator(*this).setPreviousLocationInBacking(LayoutPoint());
diff --git a/third_party/WebKit/Source/core/layout/LayoutObject.h b/third_party/WebKit/Source/core/layout/LayoutObject.h index 6115e81..f0138f4 100644 --- a/third_party/WebKit/Source/core/layout/LayoutObject.h +++ b/third_party/WebKit/Source/core/layout/LayoutObject.h
@@ -1577,11 +1577,6 @@ return m_previousPaintOffset; } - // Only adjusts if the paint invalidation container is not a composited - // scroller. - void adjustPreviousPaintInvalidationForScrollIfNeeded( - const DoubleSize& scrollDelta); - PaintInvalidationReason fullPaintInvalidationReason() const { return m_bitfields.fullPaintInvalidationReason(); }
diff --git a/third_party/WebKit/Source/core/paint/PaintLayer.cpp b/third_party/WebKit/Source/core/paint/PaintLayer.cpp index 4f39b06..7a098e40 100644 --- a/third_party/WebKit/Source/core/paint/PaintLayer.cpp +++ b/third_party/WebKit/Source/core/paint/PaintLayer.cpp
@@ -295,15 +295,6 @@ void PaintLayer::updateLayerPositionRecursive() { updateLayerPosition(); - // FIXME(400589): We would like to do this in - // PaintLayerScrollableArea::updateAfterLayout, but it depends on the size - // computed by updateLayerPosition. - if (m_scrollableArea) { - if (ScrollAnimatorBase* scrollAnimator = - m_scrollableArea->existingScrollAnimator()) - scrollAnimator->updateAfterLayout(); - } - for (PaintLayer* child = firstChild(); child; child = child->nextSibling()) child->updateLayerPositionRecursive(); } @@ -378,35 +369,9 @@ return ancestorScrollingLayer() != other->ancestorScrollingLayer(); } -void PaintLayer::updateLayerPositionsAfterOverflowScroll( - const DoubleSize& scrollDelta) { +void PaintLayer::updateLayerPositionsAfterOverflowScroll() { clipper().clearClipRectsIncludingDescendants(); - updateLayerPositionsAfterScrollRecursive(scrollDelta, - isPaintInvalidationContainer()); -} - -void PaintLayer::updateLayerPositionsAfterScrollRecursive( - const DoubleSize& scrollDelta, - bool paintInvalidationContainerWasScrolled) { - updateLayerPosition(); - if (paintInvalidationContainerWasScrolled && - !isPaintInvalidationContainer()) { - // Paint invalidation rects are in the coordinate space of the paint - // invalidation container. If it has scrolled, the rect must be adjusted. - // Note that it is not safe to reset it to the current bounds rect, as the - // LayoutObject may have moved since the - // last invalidation. - // FIXME(416535): Ideally, pending invalidations of scrolling content should - // be stored in the coordinate space of the scrolling content layer, so that - // they need no adjustment. - m_layoutObject->adjustPreviousPaintInvalidationForScrollIfNeeded( - scrollDelta); - } - for (PaintLayer* child = firstChild(); child; child = child->nextSibling()) { - child->updateLayerPositionsAfterScrollRecursive( - scrollDelta, paintInvalidationContainerWasScrolled && - !child->isPaintInvalidationContainer()); - } + updateLayerPositionRecursive(); } void PaintLayer::updateTransformationMatrix() {
diff --git a/third_party/WebKit/Source/core/paint/PaintLayer.h b/third_party/WebKit/Source/core/paint/PaintLayer.h index 8f736a50..5ec98e3 100644 --- a/third_party/WebKit/Source/core/paint/PaintLayer.h +++ b/third_party/WebKit/Source/core/paint/PaintLayer.h
@@ -292,7 +292,7 @@ void updateLayerPosition(); void updateLayerPositionsAfterLayout(); - void updateLayerPositionsAfterOverflowScroll(const DoubleSize& scrollDelta); + void updateLayerPositionsAfterOverflowScroll(); PaintLayer* enclosingPaginationLayer() const { return m_rareData ? m_rareData->enclosingPaginationLayer : nullptr; @@ -990,9 +990,6 @@ void dirtyAncestorChainHasSelfPaintingLayerDescendantStatus(); void updateLayerPositionRecursive(); - void updateLayerPositionsAfterScrollRecursive( - const DoubleSize& scrollDelta, - bool paintInvalidationContainerWasScrolled); void setNextSibling(PaintLayer* next) { m_next = next; } void setPreviousSibling(PaintLayer* prev) { m_previous = prev; }
diff --git a/third_party/WebKit/Source/core/paint/PaintLayerClipper.h b/third_party/WebKit/Source/core/paint/PaintLayerClipper.h index 51232a6..df71e64 100644 --- a/third_party/WebKit/Source/core/paint/PaintLayerClipper.h +++ b/third_party/WebKit/Source/core/paint/PaintLayerClipper.h
@@ -182,7 +182,7 @@ LayoutRect localClipRect(const PaintLayer* ancestorLayer) const; // Computes the same thing as backgroundRect in calculateRects(), but skips - // apllying CSS clip and the visualOverflowRect() of |m_layer|. + // applying CSS clip and the visualOverflowRect() of |m_layer|. ClipRect backgroundClipRect(const ClipRectsContext&) const; // This method figures out our layerBounds in coordinates relative to
diff --git a/third_party/WebKit/Source/core/paint/PaintLayerScrollableArea.cpp b/third_party/WebKit/Source/core/paint/PaintLayerScrollableArea.cpp index af28b1f..02acdf8 100644 --- a/third_party/WebKit/Source/core/paint/PaintLayerScrollableArea.cpp +++ b/third_party/WebKit/Source/core/paint/PaintLayerScrollableArea.cpp
@@ -372,7 +372,6 @@ return; showOverlayScrollbars(); - ScrollOffset scrollDelta = getScrollOffset() - newOffset; m_scrollOffset = newOffset; LocalFrame* frame = box().frame(); @@ -393,7 +392,7 @@ if (!frameView->isInPerformLayout()) { // If we're in the middle of layout, we'll just update layers once layout // has finished. - layer()->updateLayerPositionsAfterOverflowScroll(scrollDelta); + layer()->updateLayerPositionsAfterOverflowScroll(); // Update regions, scrolling may change the clip of a particular region. frameView->updateDocumentAnnotatedRegions(); frameView->setNeedsUpdateWidgetGeometries();
diff --git a/third_party/WebKit/Source/core/paint/PaintLayerTest.cpp b/third_party/WebKit/Source/core/paint/PaintLayerTest.cpp index f137b1c..aa3d293a 100644 --- a/third_party/WebKit/Source/core/paint/PaintLayerTest.cpp +++ b/third_party/WebKit/Source/core/paint/PaintLayerTest.cpp
@@ -345,4 +345,59 @@ ->m_needsDescendantDependentFlagsUpdate); } +TEST_P(PaintLayerTest, PaintInvalidationOnNonCompositedScroll) { + if (RuntimeEnabledFeatures::slimmingPaintV2Enabled()) + return; + + setBodyInnerHTML( + "<style>* { margin: 0 } ::-webkit-scrollbar { display: none }</style>" + "<div id='scroller' style='overflow: scroll; width: 50px; height: 50px'>" + " <div style='height: 400px'>" + " <div id='content-layer' style='position: relative; height: 10px;" + " top: 30px; background: blue'>" + " <div id='content' style='height: 5px; background: yellow'></div>" + " </div>" + " </div>" + "</div>"); + + LayoutBox* scroller = toLayoutBox(getLayoutObjectByElementId("scroller")); + LayoutObject* contentLayer = getLayoutObjectByElementId("content-layer"); + LayoutObject* content = getLayoutObjectByElementId("content"); + EXPECT_EQ(LayoutRect(0, 30, 50, 10), contentLayer->visualRect()); + EXPECT_EQ(LayoutRect(0, 30, 50, 5), content->visualRect()); + + scroller->getScrollableArea()->setScrollOffset(ScrollOffset(0, 20), + ProgrammaticScroll); + document().view()->updateAllLifecyclePhases(); + EXPECT_EQ(LayoutRect(0, 10, 50, 10), contentLayer->visualRect()); + EXPECT_EQ(LayoutRect(0, 10, 50, 5), content->visualRect()); +} + +TEST_P(PaintLayerTest, PaintInvalidationOnCompositedScroll) { + enableCompositing(); + setBodyInnerHTML( + "<style>* { margin: 0 } ::-webkit-scrollbar { display: none }</style>" + "<div id='scroller' style='overflow: scroll; width: 50px; height: 50px;" + " will-change: transform'>" + " <div style='height: 400px'>" + " <div id='content-layer' style='position: relative; height: 10px;" + " top: 30px; background: blue'>" + " <div id='content' style='height: 5px; background: yellow'></div>" + " </div>" + " </div>" + "</div>"); + + LayoutBox* scroller = toLayoutBox(getLayoutObjectByElementId("scroller")); + LayoutObject* contentLayer = getLayoutObjectByElementId("content-layer"); + LayoutObject* content = getLayoutObjectByElementId("content"); + EXPECT_EQ(LayoutRect(0, 30, 50, 10), contentLayer->visualRect()); + EXPECT_EQ(LayoutRect(0, 30, 50, 5), content->visualRect()); + + scroller->getScrollableArea()->setScrollOffset(ScrollOffset(0, 20), + ProgrammaticScroll); + document().view()->updateAllLifecyclePhases(); + EXPECT_EQ(LayoutRect(0, 30, 50, 10), contentLayer->visualRect()); + EXPECT_EQ(LayoutRect(0, 30, 50, 5), content->visualRect()); +} + } // namespace blink
diff --git a/third_party/WebKit/Source/core/paint/PaintPropertyTreeBuilder.cpp b/third_party/WebKit/Source/core/paint/PaintPropertyTreeBuilder.cpp index f329b31..6baebf0 100644 --- a/third_party/WebKit/Source/core/paint/PaintPropertyTreeBuilder.cpp +++ b/third_party/WebKit/Source/core/paint/PaintPropertyTreeBuilder.cpp
@@ -319,6 +319,18 @@ !object.styleRef().subtreeWillChangeContents()) compositingReasons |= CompositingReasonWillChangeCompositingHint; + if (object.isBoxModelObject()) { + const LayoutBoxModelObject* box = toLayoutBoxModelObject(&object); + if (box->hasLayer()) { + // TODO(chrishtr): move this to the descendant-dependent flags recursion + // PaintLayer::updateDescendantDependentFlags. + box->layer()->update3DTransformedDescendantStatus(); + + if (box->layer()->has3DTransformedDescendant()) + compositingReasons |= CompositingReason3DTransform; + } + } + return compositingReasons; }
diff --git a/third_party/WebKit/Source/core/paint/PaintPropertyTreeBuilderTest.cpp b/third_party/WebKit/Source/core/paint/PaintPropertyTreeBuilderTest.cpp index 969f3fa..10952ba 100644 --- a/third_party/WebKit/Source/core/paint/PaintPropertyTreeBuilderTest.cpp +++ b/third_party/WebKit/Source/core/paint/PaintPropertyTreeBuilderTest.cpp
@@ -3156,4 +3156,31 @@ willChange->paintProperties()->transform()->origin()); } +TEST_P(PaintPropertyTreeBuilderTest, ChangePositionUpdateDescendantProperties) { + setBodyInnerHTML( + "<style>" + " * { margin: 0; }" + " #ancestor { position: absolute; overflow: hidden }" + " #descendant { position: absolute }" + "</style>" + "<div id='ancestor'>" + " <div id='descendant'></div>" + "</div>"); + + LayoutObject* ancestor = getLayoutObjectByElementId("ancestor"); + LayoutObject* descendant = getLayoutObjectByElementId("descendant"); + EXPECT_EQ(ancestor->paintProperties()->overflowClip(), + descendant->paintProperties() + ->localBorderBoxProperties() + ->propertyTreeState.clip()); + + toElement(ancestor->node()) + ->setAttribute(HTMLNames::styleAttr, "position: static"); + document().view()->updateAllLifecyclePhases(); + EXPECT_NE(ancestor->paintProperties()->overflowClip(), + descendant->paintProperties() + ->localBorderBoxProperties() + ->propertyTreeState.clip()); +} + } // namespace blink
diff --git a/third_party/WebKit/Source/core/paint/README.md b/third_party/WebKit/Source/core/paint/README.md index 1fdc4b1..34d5d57 100644 --- a/third_party/WebKit/Source/core/paint/README.md +++ b/third_party/WebKit/Source/core/paint/README.md
@@ -209,7 +209,8 @@ * Building paint property tree: creates paint property tree nodes for special things in the layout tree, including but not limit to: overflow clip, transform, - fixed-pos, animation, mask, filter, etc. + fixed-pos, animation, mask, filter, etc. Also sets direct compositing reasons to be + used later for compositing. * Paint invalidation: Not implemented yet. TODO(wangxianzhu): add details after it's implemented.
diff --git a/third_party/WebKit/Source/platform/BUILD.gn b/third_party/WebKit/Source/platform/BUILD.gn index 7f7fe873..5b3be6b 100644 --- a/third_party/WebKit/Source/platform/BUILD.gn +++ b/third_party/WebKit/Source/platform/BUILD.gn
@@ -1024,6 +1024,7 @@ "graphics/paint/PaintChunker.h", "graphics/paint/PaintController.cpp", "graphics/paint/PaintController.h", + "graphics/paint/PropertyTreeState.cpp", "graphics/paint/PropertyTreeState.h", "graphics/paint/RasterInvalidationTracking.cpp", "graphics/paint/RasterInvalidationTracking.h", @@ -1755,6 +1756,7 @@ "graphics/paint/PaintChunkTest.cpp", "graphics/paint/PaintChunkerTest.cpp", "graphics/paint/PaintControllerTest.cpp", + "graphics/paint/PropertyTreeStateTest.cpp", "image-decoders/FastSharedBufferReaderTest.cpp", "image-decoders/ImageDecoderTest.cpp", "image-decoders/ImageDecoderTestHelpers.cpp",
diff --git a/third_party/WebKit/Source/platform/graphics/compositing/PaintArtifactCompositor.cpp b/third_party/WebKit/Source/platform/graphics/compositing/PaintArtifactCompositor.cpp index a6ab798..00b42f49 100644 --- a/third_party/WebKit/Source/platform/graphics/compositing/PaintArtifactCompositor.cpp +++ b/third_party/WebKit/Source/platform/graphics/compositing/PaintArtifactCompositor.cpp
@@ -7,9 +7,12 @@ #include "cc/layers/content_layer_client.h" #include "cc/layers/layer.h" #include "cc/layers/picture_layer.h" +#include "cc/playback/compositing_display_item.h" #include "cc/playback/display_item_list.h" #include "cc/playback/display_item_list_settings.h" #include "cc/playback/drawing_display_item.h" +#include "cc/playback/filter_display_item.h" +#include "cc/playback/float_clip_display_item.h" #include "cc/playback/transform_display_item.h" #include "cc/trees/clip_node.h" #include "cc/trees/effect_node.h" @@ -22,7 +25,9 @@ #include "platform/graphics/paint/DisplayItem.h" #include "platform/graphics/paint/DrawingDisplayItem.h" #include "platform/graphics/paint/ForeignLayerDisplayItem.h" +#include "platform/graphics/paint/GeometryMapper.h" #include "platform/graphics/paint/PaintArtifact.h" +#include "platform/graphics/paint/PropertyTreeState.h" #include "platform/graphics/paint/RasterInvalidationTracking.h" #include "platform/graphics/paint/ScrollPaintPropertyNode.h" #include "platform/graphics/paint/TransformPaintPropertyNode.h" @@ -236,31 +241,6 @@ } } -static scoped_refptr<cc::DisplayItemList> recordPaintChunk( - const PaintArtifact& artifact, - const PaintChunk& chunk, - const gfx::Rect& combinedBounds) { - cc::DisplayItemListSettings settings; - scoped_refptr<cc::DisplayItemList> list = - cc::DisplayItemList::Create(settings); - - gfx::Transform translation; - translation.Translate(-combinedBounds.x(), -combinedBounds.y()); - // Passing combinedBounds as the visual rect for the begin/end transform item - // would normally be the sensible thing to do, but see comment above re: - // visual rects for drawing items and further rework in flight. - list->CreateAndAppendPairedBeginItem<cc::TransformDisplayItem>(translation); - - const DisplayItemList& displayItems = artifact.getDisplayItemList(); - for (const auto& displayItem : displayItems.itemsInPaintChunk(chunk)) - appendDisplayItemToCcDisplayItemList(displayItem, list.get()); - - list->CreateAndAppendPairedEndItem<cc::EndTransformDisplayItem>(); - - list->Finalize(); - return list; -} - scoped_refptr<cc::Layer> foreignLayerForPaintChunk( const PaintArtifact& paintArtifact, const PaintChunk& paintChunk, @@ -290,8 +270,220 @@ constexpr int kSecondaryRootNodeId = 1; constexpr int kPropertyTreeSequenceNumber = 1; +enum EndDisplayItemType { EndTransform, EndClip, EndEffect }; + +// Applies the clips between |localState| and |ancestorState| into a single +// combined cc::FloatClipDisplayItem on |ccList|. +static void applyClipsBetweenStates(const PropertyTreeState& localState, + const PropertyTreeState& ancestorState, + cc::DisplayItemList& ccList, + Vector<EndDisplayItemType>& endDisplayItems, + GeometryMapper& geometryMapper) { + DCHECK(localState.transform() == ancestorState.transform()); +#ifdef DCHECK_IS_ON + const TransformPaintPropertyNode* transformNode = + localState.clip()->localTransformSpace(); + if (transformNode != ancestorState.transform()) { + bool success = false; + const TransformationMatrix& localToAncestorMatrix = + geometryMapper.localToAncestorMatrix(transformNode, ancestorState, + success); + DCHECK(success); + // Clips are only in descendant spaces that are transformed by one + // or more scrolls. + DCHECK(localToAncestorMatrix.isIdentityOrTranslation()); + } +#endif + + FloatRect combinedClip; + bool success = false; + // TODO(chrishtr) :get rid of infiniteIntRect here. + combinedClip = geometryMapper.localToVisualRectInAncestorSpace( + FloatRect(LayoutRect::infiniteIntRect()), localState, ancestorState, + success); + + DCHECK(success); + + ccList.CreateAndAppendPairedBeginItem<cc::FloatClipDisplayItem>( + gfx::RectF(combinedClip)); + endDisplayItems.push_back(EndClip); +} + +static void recordPairedBeginDisplayItems( + const Vector<PropertyTreeState>& pairedStates, + const PropertyTreeState& pendingLayerState, + cc::DisplayItemList& ccList, + Vector<EndDisplayItemType>& endDisplayItems, + GeometryMapper& geometryMapper) { + PropertyTreeState mappedClipDestinationSpace = pendingLayerState; + PropertyTreeState clipSpace = pendingLayerState; + bool hasClip = false; + + for (Vector<PropertyTreeState>::const_reverse_iterator pairedState = + pairedStates.rbegin(); + pairedState != pairedStates.rend(); ++pairedState) { + switch (pairedState->innermostNode()) { + case PropertyTreeState::Transform: { + if (hasClip) { + applyClipsBetweenStates(clipSpace, mappedClipDestinationSpace, ccList, + endDisplayItems, geometryMapper); + hasClip = false; + } + mappedClipDestinationSpace = *pairedState; + clipSpace = *pairedState; + + TransformationMatrix matrix = pairedState->transform()->matrix(); + matrix.applyTransformOrigin(pairedState->transform()->origin()); + + gfx::Transform transform(gfx::Transform::kSkipInitialization); + transform.matrix() = TransformationMatrix::toSkMatrix44(matrix); + + ccList.CreateAndAppendPairedBeginItem<cc::TransformDisplayItem>( + transform); + endDisplayItems.push_back(EndTransform); + break; + } + case PropertyTreeState::Clip: { + // Clips are handled in |applyClips| when ending the iterator, or + // transitioning between transform spaces. Here we store off the + // PropertyTreeState of the first found clip, under the transform of + // pairedState->transform(). All subsequent clips before applying the + // transform will be applied in applyClips. + clipSpace = *pairedState; + hasClip = true; +#ifdef DCHECK_IS_ON + if (pairedState->clip()->localTransformSpace() != + pairedState->transform()) { + const TransformationMatrix& localTransformMatrix = + pairedState->effect()->localTransformSpace()->matrix(); + // Clips are only in descendant spaces that are transformed by scroll. + DCHECK(localTransformMatrix.isIdentityOrTranslation()); + } +#endif + break; + } + case PropertyTreeState::Effect: { + // TODO(chrishtr): skip effect and/or compositing display items if + // not necessary. + + FloatRect clipRect = + pairedState->effect()->outputClip()->clipRect().rect(); + // TODO(chrishtr): specify origin of the filter. + FloatPoint filterOrigin; + if (pairedState->effect()->localTransformSpace() != + pairedState->transform()) { + bool success = false; + const TransformPaintPropertyNode* transformNode = + pairedState->effect()->localTransformSpace(); + const TransformationMatrix& localToAncestorMatrix = + geometryMapper.localToAncestorMatrix(transformNode, *pairedState, + success); + DCHECK(success); + // Effects are only in descendant spaces that are transformed by one + // or more scrolls. + DCHECK(localToAncestorMatrix.isIdentityOrTranslation()); + + clipRect = localToAncestorMatrix.mapRect(clipRect); + filterOrigin = localToAncestorMatrix.mapPoint(filterOrigin); + } + + const bool kLcdTextRequiresOpaqueLayer = true; + ccList.CreateAndAppendPairedBeginItem<cc::CompositingDisplayItem>( + static_cast<uint8_t>( + gfx::ToFlooredInt(255 * pairedState->effect()->opacity())), + pairedState->effect()->blendMode(), + // TODO(chrishtr): compute bounds as necessary. + nullptr, nullptr, kLcdTextRequiresOpaqueLayer); + + ccList.CreateAndAppendPairedBeginItem<cc::FilterDisplayItem>( + pairedState->effect()->filter().asCcFilterOperations(), clipRect, + gfx::PointF(filterOrigin.x(), filterOrigin.y())); + + endDisplayItems.push_back(EndEffect); + break; + } + case PropertyTreeState::None: + break; + } + } + + if (hasClip) { + applyClipsBetweenStates(clipSpace, mappedClipDestinationSpace, ccList, + endDisplayItems, geometryMapper); + } +} + +static void recordPairedEndDisplayItems( + const Vector<EndDisplayItemType>& endDisplayItemTypes, + cc::DisplayItemList* ccList) { + for (Vector<EndDisplayItemType>::const_reverse_iterator endType = + endDisplayItemTypes.rbegin(); + endType != endDisplayItemTypes.rend(); ++endType) { + switch (*endType) { + case EndTransform: + ccList->CreateAndAppendPairedEndItem<cc::EndTransformDisplayItem>(); + break; + case EndClip: + ccList->CreateAndAppendPairedEndItem<cc::EndFloatClipDisplayItem>(); + break; + case EndEffect: + ccList->CreateAndAppendPairedEndItem<cc::EndFilterDisplayItem>(); + ccList->CreateAndAppendPairedEndItem<cc::EndCompositingDisplayItem>(); + break; + } + } +} + } // namespace +scoped_refptr<cc::DisplayItemList> PaintArtifactCompositor::recordPendingLayer( + const PaintArtifact& artifact, + const PendingLayer& pendingLayer, + const gfx::Rect& combinedBounds, + GeometryMapper& geometryMapper) { + cc::DisplayItemListSettings settings; + scoped_refptr<cc::DisplayItemList> ccList = + cc::DisplayItemList::Create(settings); + + gfx::Transform translation; + translation.Translate(-combinedBounds.x(), -combinedBounds.y()); + // Passing combinedBounds as the visual rect for the begin/end transform item + // would normally be the sensible thing to do, but see comment above re: + // visual rects for drawing items and further rework in flight. + ccList->CreateAndAppendPairedBeginItem<cc::TransformDisplayItem>(translation); + + const DisplayItemList& displayItems = artifact.getDisplayItemList(); + for (const auto& paintChunk : pendingLayer.paintChunks) { + const PropertyTreeState* state = &paintChunk->properties.propertyTreeState; + PropertyTreeStateIterator iterator(*state); + Vector<PropertyTreeState> pairedStates; + for (; state && *state != pendingLayer.propertyTreeState; + state = iterator.next()) { + if (state->innermostNode() != PropertyTreeState::None) + pairedStates.push_back(*state); + } + + // TODO(chrishtr): we can avoid some extra paired display items if + // multiple PaintChunks share them. We can also collapse clips between + // transforms into single clips in the same way that PaintLayerClipper does. + Vector<EndDisplayItemType> endDisplayItems; + + recordPairedBeginDisplayItems(pairedStates, pendingLayer.propertyTreeState, + *ccList.get(), endDisplayItems, + geometryMapper); + + for (const auto& displayItem : displayItems.itemsInPaintChunk(*paintChunk)) + appendDisplayItemToCcDisplayItemList(displayItem, ccList.get()); + + recordPairedEndDisplayItems(endDisplayItems, ccList.get()); + } + + ccList->CreateAndAppendPairedEndItem<cc::EndTransformDisplayItem>(); + + ccList->Finalize(); + return ccList; +} + std::unique_ptr<PaintArtifactCompositor::ContentLayerClientImpl> PaintArtifactCompositor::clientForPaintChunk( const PaintChunk& paintChunk, @@ -310,61 +502,83 @@ : paintArtifact.getDisplayItemList()[paintChunk.beginIndex].getId())); } -scoped_refptr<cc::Layer> PaintArtifactCompositor::layerForPaintChunk( +scoped_refptr<cc::Layer> +PaintArtifactCompositor::compositedLayerForPendingLayer( const PaintArtifact& paintArtifact, - const PaintChunk& paintChunk, + const PendingLayer& pendingLayer, gfx::Vector2dF& layerOffset, Vector<std::unique_ptr<ContentLayerClientImpl>>& newContentLayerClients, - RasterInvalidationTracking* tracking, - bool storeDebugInfo) { - DCHECK(paintChunk.size()); + RasterInvalidationTrackingMap<const PaintChunk>* trackingMap, + bool storeDebugInfo, + GeometryMapper& geometryMapper) { + DCHECK(pendingLayer.paintChunks.size()); + const PaintChunk& firstPaintChunk = *pendingLayer.paintChunks[0]; + DCHECK(firstPaintChunk.size()); +#if DCHECK_IS_ON + for (const auto& paintChunk : pendingLayer.paintChunks) { + DCHECK(paintChunk.properties == firstPaintChunk.properties); + } +#endif // If the paint chunk is a foreign layer, just return that layer. - if (scoped_refptr<cc::Layer> foreignLayer = - foreignLayerForPaintChunk(paintArtifact, paintChunk, layerOffset)) + if (scoped_refptr<cc::Layer> foreignLayer = foreignLayerForPaintChunk( + paintArtifact, firstPaintChunk, layerOffset)) { + DCHECK_EQ(pendingLayer.paintChunks.size(), 1u); return foreignLayer; + } // The common case: create or reuse a PictureLayer for painted content. std::unique_ptr<ContentLayerClientImpl> contentLayerClient = - clientForPaintChunk(paintChunk, paintArtifact); + clientForPaintChunk(firstPaintChunk, paintArtifact); - gfx::Rect combinedBounds = enclosingIntRect(paintChunk.bounds); - scoped_refptr<cc::DisplayItemList> displayList = - recordPaintChunk(paintArtifact, paintChunk, combinedBounds); + gfx::Rect ccCombinedBounds(enclosingIntRect(pendingLayer.bounds)); + + scoped_refptr<cc::DisplayItemList> displayList = recordPendingLayer( + paintArtifact, pendingLayer, ccCombinedBounds, geometryMapper); contentLayerClient->SetDisplayList(std::move(displayList)); - contentLayerClient->SetPaintableRegion(gfx::Rect(combinedBounds.size())); + contentLayerClient->SetPaintableRegion(gfx::Rect(ccCombinedBounds.size())); - layerOffset = combinedBounds.OffsetFromOrigin(); + layerOffset = ccCombinedBounds.OffsetFromOrigin(); + scoped_refptr<cc::PictureLayer> ccPictureLayer = contentLayerClient->ccPictureLayer(); - ccPictureLayer->SetBounds(combinedBounds.size()); + ccPictureLayer->SetBounds(ccCombinedBounds.size()); ccPictureLayer->SetIsDrawable(true); - if (paintChunk.knownToBeOpaque) - ccPictureLayer->SetContentsOpaque(true); - DCHECK(!tracking || - tracking->trackedRasterInvalidations.size() == - paintChunk.rasterInvalidationRects.size()); - + ccPictureLayer->SetContentsOpaque(pendingLayer.knownToBeOpaque); contentLayerClient->clearPaintChunkDebugData(); - if (storeDebugInfo) { - contentLayerClient->addPaintChunkDebugData( - paintArtifact.getDisplayItemList().subsequenceAsJSON( - paintChunk.beginIndex, paintChunk.endIndex, - DisplayItemList::SkipNonDrawings | - DisplayItemList::ShownOnlyDisplayItemTypes)); - } - for (unsigned index = 0; index < paintChunk.rasterInvalidationRects.size(); - ++index) { - IntRect rect(enclosingIntRect(paintChunk.rasterInvalidationRects[index])); - gfx::Rect ccInvalidationRect(rect.x(), rect.y(), std::max(0, rect.width()), - std::max(0, rect.height())); - // Raster paintChunk.rasterInvalidationRects is in the space of the - // containing transform node, so need to subtract off the layer offset. - ccInvalidationRect.Offset(-combinedBounds.OffsetFromOrigin()); - contentLayerClient->setNeedsDisplayRect( - ccInvalidationRect, - tracking ? &tracking->trackedRasterInvalidations[index] : nullptr); + for (const auto& paintChunk : pendingLayer.paintChunks) { + RasterInvalidationTracking* rasterTracking = + trackingMap ? trackingMap->find(paintChunk) : nullptr; + DCHECK(!rasterTracking || + rasterTracking->trackedRasterInvalidations.size() == + paintChunk->rasterInvalidationRects.size()); + + if (storeDebugInfo) { + contentLayerClient->addPaintChunkDebugData( + paintArtifact.getDisplayItemList().subsequenceAsJSON( + paintChunk->beginIndex, paintChunk->endIndex, + DisplayItemList::SkipNonDrawings | + DisplayItemList::ShownOnlyDisplayItemTypes)); + } + + for (unsigned index = 0; index < paintChunk->rasterInvalidationRects.size(); + ++index) { + IntRect rect( + enclosingIntRect(paintChunk->rasterInvalidationRects[index])); + gfx::Rect ccInvalidationRect(rect.x(), rect.y(), + std::max(0, rect.width()), + std::max(0, rect.height())); + if (ccInvalidationRect.IsEmpty()) + continue; + // Raster paintChunk.rasterInvalidationRects is in the space of the + // containing transform node, so need to subtract off the layer offset. + ccInvalidationRect.Offset(-ccCombinedBounds.OffsetFromOrigin()); + contentLayerClient->setNeedsDisplayRect( + ccInvalidationRect, + rasterTracking ? &rasterTracking->trackedRasterInvalidations[index] + : nullptr); + } } newContentLayerClients.append(std::move(contentLayerClient)); @@ -677,6 +891,8 @@ return result; } +// TODO(chrishtr): templatize this to avoid duplication of +// GeometryMapper::leastCommonAncestor. const EffectPaintPropertyNode* lowestCommonAncestor( const EffectPaintPropertyNode* nodeA, const EffectPaintPropertyNode* nodeB) { @@ -791,10 +1007,109 @@ } // namespace +bool PaintArtifactCompositor::canMergeInto( + const PaintArtifact& paintArtifact, + const PaintChunk& newChunk, + const PendingLayer& candidatePendingLayer) { + const PaintChunk& pendingLayerFirstChunk = + *candidatePendingLayer.paintChunks[0]; + if (paintArtifact.getDisplayItemList()[newChunk.beginIndex].isForeignLayer()) + return false; + + if (paintArtifact.getDisplayItemList()[pendingLayerFirstChunk.beginIndex] + .isForeignLayer()) + return false; + + if (newChunk.properties.backfaceHidden != + pendingLayerFirstChunk.properties.backfaceHidden) + return false; + + DCHECK_GE(candidatePendingLayer.paintChunks.size(), 1u); + PropertyTreeStateIterator iterator(newChunk.properties.propertyTreeState); + for (const PropertyTreeState* currentState = + &newChunk.properties.propertyTreeState; + currentState; currentState = iterator.next()) { + if (currentState->hasDirectCompositingReasons()) + return false; + if (*currentState == candidatePendingLayer.propertyTreeState) + return true; + } + return false; +} + +bool PaintArtifactCompositor::mightOverlap( + const PaintChunk& paintChunk, + const PendingLayer& candidatePendingLayer, + GeometryMapper& geometryMapper) { + // TODO(chrishtr): implement + return true; +} + +PaintArtifactCompositor::PendingLayer::PendingLayer( + const PaintChunk& firstPaintChunk) + : bounds(firstPaintChunk.bounds), + knownToBeOpaque(firstPaintChunk.knownToBeOpaque), + backfaceHidden(firstPaintChunk.properties.backfaceHidden), + propertyTreeState(firstPaintChunk.properties.propertyTreeState) { + paintChunks.append(&firstPaintChunk); +} + +void PaintArtifactCompositor::PendingLayer::add( + const PaintChunk& paintChunk, + GeometryMapper* geometryMapper) { + DCHECK(paintChunk.properties.backfaceHidden == backfaceHidden); + paintChunks.append(&paintChunk); + FloatRect mappedBounds = paintChunk.bounds; + if (geometryMapper) { + bool success = false; + mappedBounds = geometryMapper->localToAncestorRect( + mappedBounds, paintChunk.properties.propertyTreeState, + propertyTreeState, success); + DCHECK(success); + } + bounds.unite(mappedBounds); + if (bounds.size() != paintChunks[0]->bounds.size()) { + if (bounds.size() != paintChunk.bounds.size()) + knownToBeOpaque = false; + else + knownToBeOpaque = paintChunk.knownToBeOpaque; + } +} + +void PaintArtifactCompositor::collectPendingLayers( + const PaintArtifact& paintArtifact, + Vector<PendingLayer>& pendingLayers, + GeometryMapper& geometryMapper) { + // n = # of paint chunks. Memoizing canMergeInto() can get it to O(n^2), and + // other heuristics can make worst-case behavior better. + for (const PaintChunk& paintChunk : paintArtifact.paintChunks()) { + bool createNew = true; + for (Vector<PendingLayer>::reverse_iterator candidatePendingLayer = + pendingLayers.rbegin(); + candidatePendingLayer != pendingLayers.rend(); + ++candidatePendingLayer) { + if (canMergeInto(paintArtifact, paintChunk, *candidatePendingLayer)) { + candidatePendingLayer->add(paintChunk, &geometryMapper); + createNew = false; + break; + } + if (mightOverlap(paintChunk, *candidatePendingLayer, geometryMapper)) { + break; + } + } + if (createNew) + pendingLayers.append(PendingLayer(paintChunk)); + } +} + void PaintArtifactCompositor::update( const PaintArtifact& paintArtifact, RasterInvalidationTrackingMap<const PaintChunk>* rasterChunkInvalidations, bool storeDebugInfo) { +#ifndef NDEBUG + storeDebugInfo = true; +#endif + DCHECK(m_rootLayer); cc::LayerTree* layerTree = m_rootLayer->GetLayerTree(); @@ -813,24 +1128,26 @@ PropertyTreeManager propertyTreeManager(*layerTree->property_trees(), m_rootLayer.get()); + Vector<PendingLayer, 0> pendingLayers; + GeometryMapper geometryMapper; + collectPendingLayers(paintArtifact, pendingLayers, geometryMapper); + Vector<std::unique_ptr<ContentLayerClientImpl>> newContentLayerClients; newContentLayerClients.reserveCapacity(paintArtifact.paintChunks().size()); - for (const PaintChunk& paintChunk : paintArtifact.paintChunks()) { + for (const PendingLayer& pendingLayer : pendingLayers) { gfx::Vector2dF layerOffset; - scoped_refptr<cc::Layer> layer = layerForPaintChunk( - paintArtifact, paintChunk, layerOffset, newContentLayerClients, - rasterChunkInvalidations ? rasterChunkInvalidations->find(&paintChunk) - : nullptr, - storeDebugInfo); + scoped_refptr<cc::Layer> layer = compositedLayerForPendingLayer( + paintArtifact, pendingLayer, layerOffset, newContentLayerClients, + rasterChunkInvalidations, storeDebugInfo, geometryMapper); int transformId = propertyTreeManager.compositorIdForTransformNode( - paintChunk.properties.propertyTreeState.transform()); + pendingLayer.propertyTreeState.transform()); int scrollId = propertyTreeManager.compositorIdForScrollNode( - paintChunk.properties.propertyTreeState.scroll()); + pendingLayer.propertyTreeState.scroll()); int clipId = propertyTreeManager.compositorIdForClipNode( - paintChunk.properties.propertyTreeState.clip()); + pendingLayer.propertyTreeState.clip()); int effectId = propertyTreeManager.switchToEffectNode( - *paintChunk.properties.propertyTreeState.effect()); + *pendingLayer.propertyTreeState.effect()); propertyTreeManager.updateScrollOffset(layer->id(), scrollId); @@ -849,8 +1166,7 @@ ->transform_tree.Node(transformId) ->sorting_context_id); - layer->SetShouldCheckBackfaceVisibility( - paintChunk.properties.backfaceHidden); + layer->SetShouldCheckBackfaceVisibility(pendingLayer.backfaceHidden); if (m_extraDataForTestingEnabled) m_extraDataForTesting->contentLayers.append(layer); @@ -864,4 +1180,13 @@ layerTree->property_trees()->ResetCachedData(); } +#ifndef NDEBUG +void PaintArtifactCompositor::showDebugData() { + LOG(ERROR) << layersAsJSON(LayerTreeIncludesDebugInfo) + ->toPrettyJSONString() + .utf8() + .data(); +} +#endif + } // namespace blink
diff --git a/third_party/WebKit/Source/platform/graphics/compositing/PaintArtifactCompositor.h b/third_party/WebKit/Source/platform/graphics/compositing/PaintArtifactCompositor.h index bdb58952..38e955b 100644 --- a/third_party/WebKit/Source/platform/graphics/compositing/PaintArtifactCompositor.h +++ b/third_party/WebKit/Source/platform/graphics/compositing/PaintArtifactCompositor.h
@@ -16,6 +16,7 @@ #include <memory> namespace cc { +class DisplayItemList; class Layer; } @@ -25,6 +26,7 @@ namespace blink { +class GeometryMapper; class JSONObject; class PaintArtifact; class WebLayer; @@ -81,24 +83,50 @@ std::unique_ptr<JSONObject> layersAsJSON(LayerTreeFlags) const; +#ifndef NDEBUG + void showDebugData(); +#endif + private: + // A pending layer is a collection of paint chunks that will end up in + // the same cc::Layer. + struct PLATFORM_EXPORT PendingLayer { + PendingLayer(const PaintChunk& firstPaintChunk); + void add(const PaintChunk&, GeometryMapper*); + FloatRect bounds; + Vector<const PaintChunk*> paintChunks; + bool knownToBeOpaque; + bool backfaceHidden; + PropertyTreeState propertyTreeState; + }; + PaintArtifactCompositor(); class ContentLayerClientImpl; + // Collects the PaintChunks into groups which will end up in the same + // cc layer. This includes testing PaintChunks for "merge" compatibility (e.g. + // directly composited property tree states are separately composited) + // and overlap testing (PaintChunks that overlap existing PaintLayers they + // are not compatible with must be separately composited). + void collectPendingLayers(const PaintArtifact&, + Vector<PendingLayer>& pendingLayers, + GeometryMapper&); + // Builds a leaf layer that represents a single paint chunk. // Note: cc::Layer API assumes the layer bounds start at (0, 0), but the // bounding box of a paint chunk does not necessarily start at (0, 0) (and // could even be negative). Internally the generated layer translates the // paint chunk to align the bounding box to (0, 0) and return the actual // origin of the paint chunk in the |layerOffset| outparam. - scoped_refptr<cc::Layer> layerForPaintChunk( + scoped_refptr<cc::Layer> compositedLayerForPendingLayer( const PaintArtifact&, - const PaintChunk&, + const PendingLayer&, gfx::Vector2dF& layerOffset, Vector<std::unique_ptr<ContentLayerClientImpl>>& newContentLayerClients, - RasterInvalidationTracking*, - bool storeDebugInfo); + RasterInvalidationTrackingMap<const PaintChunk>*, + bool storeDebugInfo, + GeometryMapper&); // Finds a client among the current vector of clients that matches the paint // chunk's id, or otherwise allocates a new one. @@ -106,6 +134,26 @@ const PaintChunk&, const PaintArtifact&); + // This method is an implementation of Algorithm step 4 from goo.gl/6xP8Oe. + static scoped_refptr<cc::DisplayItemList> recordPendingLayer( + const PaintArtifact&, + const PendingLayer&, + const gfx::Rect& combinedBounds, + GeometryMapper&); + + static bool canMergeInto(const PaintArtifact&, + const PaintChunk& newChunk, + const PendingLayer& candidatePendingLayer); + + // Returns true if |newChunk| might overlap |candidatePendingLayer| in the + // root property tree space. If it does overlap, it will always return true. + // If it doesn't overlap, it might return true in cases were we can't + // efficiently determine a false value, or the truth depends on + // compositor animations. + static bool mightOverlap(const PaintChunk& newChunk, + const PendingLayer& candidatePendingLayer, + GeometryMapper&); + scoped_refptr<cc::Layer> m_rootLayer; std::unique_ptr<WebLayer> m_webLayer; Vector<std::unique_ptr<ContentLayerClientImpl>> m_contentLayerClients; @@ -115,6 +163,35 @@ friend class StubChromeClientForSPv2; bool m_isTrackingRasterInvalidations; + FRIEND_TEST_ALL_PREFIXES(PaintArtifactCompositorTestWithPropertyTrees, + ForeignLayerPassesThrough); + FRIEND_TEST_ALL_PREFIXES(PaintArtifactCompositorTestWithPropertyTrees, + MergeSimpleChunks); + FRIEND_TEST_ALL_PREFIXES(PaintArtifactCompositorTestWithPropertyTrees, + Merge2DTransform); + FRIEND_TEST_ALL_PREFIXES(PaintArtifactCompositorTestWithPropertyTrees, + MergeClip); + FRIEND_TEST_ALL_PREFIXES(PaintArtifactCompositorTestWithPropertyTrees, + MergeOpacity); + FRIEND_TEST_ALL_PREFIXES(PaintArtifactCompositorTestWithPropertyTrees, + MergeNested); + FRIEND_TEST_ALL_PREFIXES(PaintArtifactCompositorTestWithPropertyTrees, + ClipPushedUp); + FRIEND_TEST_ALL_PREFIXES(PaintArtifactCompositorTestWithPropertyTrees, + EffectPushedUp); + FRIEND_TEST_ALL_PREFIXES(PaintArtifactCompositorTestWithPropertyTrees, + EffectAndClipPushedUp); + FRIEND_TEST_ALL_PREFIXES(PaintArtifactCompositorTestWithPropertyTrees, + ClipAndEffectNoTransform); + FRIEND_TEST_ALL_PREFIXES(PaintArtifactCompositorTestWithPropertyTrees, + TwoClips); + FRIEND_TEST_ALL_PREFIXES(PaintArtifactCompositorTestWithPropertyTrees, + TwoEffects); + + FRIEND_TEST_ALL_PREFIXES(PaintArtifactCompositorTestWithPropertyTrees, + PendingLayer); + FRIEND_TEST_ALL_PREFIXES(PaintArtifactCompositorTestWithPropertyTrees, + PendingLayerWithGeometry); }; } // namespace blink
diff --git a/third_party/WebKit/Source/platform/graphics/compositing/PaintArtifactCompositorTest.cpp b/third_party/WebKit/Source/platform/graphics/compositing/PaintArtifactCompositorTest.cpp index 3f9df35..fb445b3 100644 --- a/third_party/WebKit/Source/platform/graphics/compositing/PaintArtifactCompositorTest.cpp +++ b/third_party/WebKit/Source/platform/graphics/compositing/PaintArtifactCompositorTest.cpp
@@ -16,6 +16,7 @@ #include "cc/trees/scroll_node.h" #include "cc/trees/transform_node.h" #include "platform/graphics/paint/EffectPaintPropertyNode.h" +#include "platform/graphics/paint/GeometryMapper.h" #include "platform/graphics/paint/PaintArtifact.h" #include "platform/graphics/paint/ScrollPaintPropertyNode.h" #include "platform/testing/PaintPropertyTestHelpers.h" @@ -28,7 +29,6 @@ #include <memory> namespace blink { -namespace { using ::blink::testing::createOpacityOnlyEffect; using ::testing::Pointee; @@ -140,9 +140,9 @@ TEST_F(PaintArtifactCompositorTestWithPropertyTrees, OneTransform) { // A 90 degree clockwise rotation about (100, 100). RefPtr<TransformPaintPropertyNode> transform = - TransformPaintPropertyNode::create(TransformPaintPropertyNode::root(), - TransformationMatrix().rotate(90), - FloatPoint3D(100, 100, 0)); + TransformPaintPropertyNode::create( + TransformPaintPropertyNode::root(), TransformationMatrix().rotate(90), + FloatPoint3D(100, 100, 0), false, 0, CompositingReason3DTransform); TestPaintArtifact artifact; artifact @@ -190,9 +190,9 @@ TEST_F(PaintArtifactCompositorTestWithPropertyTrees, TransformCombining) { // A translation by (5, 5) within a 2x scale about (10, 10). RefPtr<TransformPaintPropertyNode> transform1 = - TransformPaintPropertyNode::create(TransformPaintPropertyNode::root(), - TransformationMatrix().scale(2), - FloatPoint3D(10, 10, 0)); + TransformPaintPropertyNode::create( + TransformPaintPropertyNode::root(), TransformationMatrix().scale(2), + FloatPoint3D(10, 10, 0), false, 0, CompositingReason3DTransform); RefPtr<TransformPaintPropertyNode> transform2 = TransformPaintPropertyNode::create( transform1, TransformationMatrix().translate(5, 5), FloatPoint3D()); @@ -298,15 +298,18 @@ // Establishes a 3D rendering context. RefPtr<TransformPaintPropertyNode> transform2 = TransformPaintPropertyNode::create(transform1, TransformationMatrix(), - FloatPoint3D(), false, 1); + FloatPoint3D(), false, 1, + CompositingReason3DTransform); // Extends the 3D rendering context of transform2. RefPtr<TransformPaintPropertyNode> transform3 = TransformPaintPropertyNode::create(transform2, TransformationMatrix(), - FloatPoint3D(), false, 1); + FloatPoint3D(), false, 1, + CompositingReason3DTransform); // Establishes a 3D rendering context distinct from transform2. RefPtr<TransformPaintPropertyNode> transform4 = TransformPaintPropertyNode::create(transform2, TransformationMatrix(), - FloatPoint3D(), false, 2); + FloatPoint3D(), false, 2, + CompositingReason3DTransform); TestPaintArtifact artifact; artifact @@ -399,10 +402,12 @@ TEST_F(PaintArtifactCompositorTestWithPropertyTrees, NestedClips) { RefPtr<ClipPaintPropertyNode> clip1 = ClipPaintPropertyNode::create( ClipPaintPropertyNode::root(), TransformPaintPropertyNode::root(), - FloatRoundedRect(100, 100, 700, 700)); + FloatRoundedRect(100, 100, 700, 700), + CompositingReasonOverflowScrollingTouch); RefPtr<ClipPaintPropertyNode> clip2 = ClipPaintPropertyNode::create(clip1, TransformPaintPropertyNode::root(), - FloatRoundedRect(200, 200, 700, 100)); + FloatRoundedRect(200, 200, 700, 100), + CompositingReasonOverflowScrollingTouch); TestPaintArtifact artifact; artifact @@ -555,13 +560,35 @@ ForeignLayerPassesThrough) { scoped_refptr<cc::Layer> layer = cc::Layer::Create(); - TestPaintArtifact artifact; - artifact.chunk(defaultPaintChunkProperties()) + TestPaintArtifact testArtifact; + testArtifact + .chunk(TransformPaintPropertyNode::root(), ClipPaintPropertyNode::root(), + EffectPaintPropertyNode::root()) + .rectDrawing(FloatRect(0, 0, 100, 100), Color::white); + testArtifact.chunk(defaultPaintChunkProperties()) .foreignLayer(FloatPoint(50, 100), IntSize(400, 300), layer); - update(artifact.build()); + testArtifact + .chunk(TransformPaintPropertyNode::root(), ClipPaintPropertyNode::root(), + EffectPaintPropertyNode::root()) + .rectDrawing(FloatRect(0, 0, 100, 100), Color::gray); - ASSERT_EQ(1u, contentLayerCount()); - EXPECT_EQ(layer, contentLayerAt(0)); + const PaintArtifact& artifact = testArtifact.build(); + + ASSERT_EQ(3u, artifact.paintChunks().size()); + PaintArtifactCompositor::PendingLayer pendingLayer1( + artifact.paintChunks()[0]); + // Foreign layers can't merge. + EXPECT_FALSE(PaintArtifactCompositor::canMergeInto( + artifact, artifact.paintChunks()[1], pendingLayer1)); + PaintArtifactCompositor::PendingLayer pendingLayer2( + artifact.paintChunks()[1]); + EXPECT_FALSE(PaintArtifactCompositor::canMergeInto( + artifact, artifact.paintChunks()[2], pendingLayer2)); + + update(artifact); + + ASSERT_EQ(3u, contentLayerCount()); + EXPECT_EQ(layer, contentLayerAt(1)); EXPECT_EQ(gfx::Size(400, 300), layer->bounds()); EXPECT_EQ(translation(50, 100), layer->screen_space_transform()); } @@ -718,5 +745,606 @@ MainThreadScrollingReason::kHasBackgroundAttachmentFixedObjects); } -} // namespace +TEST_F(PaintArtifactCompositorTestWithPropertyTrees, MergeSimpleChunks) { + TestPaintArtifact testArtifact; + testArtifact + .chunk(TransformPaintPropertyNode::root(), ClipPaintPropertyNode::root(), + EffectPaintPropertyNode::root()) + .rectDrawing(FloatRect(0, 0, 100, 100), Color::white); + testArtifact + .chunk(TransformPaintPropertyNode::root(), ClipPaintPropertyNode::root(), + EffectPaintPropertyNode::root()) + .rectDrawing(FloatRect(0, 0, 200, 300), Color::gray); + + const PaintArtifact& artifact = testArtifact.build(); + + ASSERT_EQ(2u, artifact.paintChunks().size()); + PaintArtifactCompositor::PendingLayer pendingLayer(artifact.paintChunks()[0]); + EXPECT_TRUE(PaintArtifactCompositor::canMergeInto( + artifact, artifact.paintChunks()[1], pendingLayer)); + + update(artifact); + + ASSERT_EQ(1u, contentLayerCount()); + { + Vector<RectWithColor> rectsWithColor; + rectsWithColor.push_back( + RectWithColor(FloatRect(0, 0, 100, 100), Color::white)); + rectsWithColor.push_back( + RectWithColor(FloatRect(0, 0, 200, 300), Color::gray)); + + const cc::Layer* layer = contentLayerAt(0); + EXPECT_THAT(layer->GetPicture(), Pointee(drawsRectangles(rectsWithColor))); + } +} + +TEST_F(PaintArtifactCompositorTestWithPropertyTrees, MergeClip) { + RefPtr<ClipPaintPropertyNode> clip = ClipPaintPropertyNode::create( + ClipPaintPropertyNode::root(), TransformPaintPropertyNode::root(), + FloatRoundedRect(10, 20, 50, 60)); + + TestPaintArtifact testArtifact; + testArtifact + .chunk(TransformPaintPropertyNode::root(), ClipPaintPropertyNode::root(), + EffectPaintPropertyNode::root()) + .rectDrawing(FloatRect(0, 0, 100, 100), Color::white); + testArtifact + .chunk(TransformPaintPropertyNode::root(), clip.get(), + EffectPaintPropertyNode::root()) + .rectDrawing(FloatRect(0, 0, 200, 300), Color::black); + testArtifact + .chunk(TransformPaintPropertyNode::root(), ClipPaintPropertyNode::root(), + EffectPaintPropertyNode::root()) + .rectDrawing(FloatRect(0, 0, 300, 400), Color::gray); + + const PaintArtifact& artifact = testArtifact.build(); + + ASSERT_EQ(3u, artifact.paintChunks().size()); + PaintArtifactCompositor::PendingLayer pendingLayer(artifact.paintChunks()[0]); + EXPECT_TRUE(PaintArtifactCompositor::canMergeInto( + artifact, artifact.paintChunks()[1], pendingLayer)); + pendingLayer.add(artifact.paintChunks()[1], nullptr); + EXPECT_TRUE(PaintArtifactCompositor::canMergeInto( + artifact, artifact.paintChunks()[2], pendingLayer)); + + update(artifact); + + ASSERT_EQ(1u, contentLayerCount()); + { + Vector<RectWithColor> rectsWithColor; + rectsWithColor.push_back( + RectWithColor(FloatRect(0, 0, 100, 100), Color::white)); + // Clip is applied to this PaintChunk. + rectsWithColor.push_back( + RectWithColor(FloatRect(10, 20, 50, 60), Color::black)); + rectsWithColor.push_back( + RectWithColor(FloatRect(0, 0, 300, 400), Color::gray)); + + const cc::Layer* layer = contentLayerAt(0); + EXPECT_THAT(layer->GetPicture(), Pointee(drawsRectangles(rectsWithColor))); + } +} + +TEST_F(PaintArtifactCompositorTestWithPropertyTrees, Merge2DTransform) { + RefPtr<TransformPaintPropertyNode> transform = + TransformPaintPropertyNode::create( + TransformPaintPropertyNode::root(), + TransformationMatrix().translate(50, 50), FloatPoint3D(100, 100, 0), + false, 0); + + TestPaintArtifact testArtifact; + testArtifact + .chunk(TransformPaintPropertyNode::root(), ClipPaintPropertyNode::root(), + EffectPaintPropertyNode::root()) + .rectDrawing(FloatRect(0, 0, 100, 100), Color::white); + testArtifact + .chunk(transform.get(), ClipPaintPropertyNode::root(), + EffectPaintPropertyNode::root()) + .rectDrawing(FloatRect(0, 0, 100, 100), Color::black); + testArtifact + .chunk(TransformPaintPropertyNode::root(), ClipPaintPropertyNode::root(), + EffectPaintPropertyNode::root()) + .rectDrawing(FloatRect(0, 0, 200, 300), Color::gray); + + const PaintArtifact& artifact = testArtifact.build(); + + ASSERT_EQ(3u, artifact.paintChunks().size()); + PaintArtifactCompositor::PendingLayer pendingLayer(artifact.paintChunks()[0]); + EXPECT_TRUE(PaintArtifactCompositor::canMergeInto( + artifact, artifact.paintChunks()[1], pendingLayer)); + pendingLayer.add(artifact.paintChunks()[1], nullptr); + EXPECT_TRUE(PaintArtifactCompositor::canMergeInto( + artifact, artifact.paintChunks()[2], pendingLayer)); + + update(artifact); + + ASSERT_EQ(1u, contentLayerCount()); + { + Vector<RectWithColor> rectsWithColor; + rectsWithColor.push_back( + RectWithColor(FloatRect(0, 0, 100, 100), Color::white)); + // Transform is applied to this PaintChunk. + rectsWithColor.push_back( + RectWithColor(FloatRect(50, 50, 100, 100), Color::black)); + rectsWithColor.push_back( + RectWithColor(FloatRect(0, 0, 200, 300), Color::gray)); + + const cc::Layer* layer = contentLayerAt(0); + EXPECT_THAT(layer->GetPicture(), Pointee(drawsRectangles(rectsWithColor))); + } +} + +TEST_F(PaintArtifactCompositorTestWithPropertyTrees, MergeOpacity) { + float opacity = 2.0 / 255.0; + RefPtr<EffectPaintPropertyNode> effect = EffectPaintPropertyNode::create( + EffectPaintPropertyNode::root(), TransformPaintPropertyNode::root(), + ClipPaintPropertyNode::root(), CompositorFilterOperations(), opacity, + SkBlendMode::kSrcOver); + + TestPaintArtifact testArtifact; + testArtifact + .chunk(TransformPaintPropertyNode::root(), ClipPaintPropertyNode::root(), + EffectPaintPropertyNode::root()) + .rectDrawing(FloatRect(0, 0, 100, 100), Color::white); + testArtifact + .chunk(TransformPaintPropertyNode::root(), ClipPaintPropertyNode::root(), + effect.get()) + .rectDrawing(FloatRect(0, 0, 100, 100), Color::black); + testArtifact + .chunk(TransformPaintPropertyNode::root(), ClipPaintPropertyNode::root(), + EffectPaintPropertyNode::root()) + .rectDrawing(FloatRect(0, 0, 200, 300), Color::gray); + + const PaintArtifact& artifact = testArtifact.build(); + + ASSERT_EQ(3u, artifact.paintChunks().size()); + PaintArtifactCompositor::PendingLayer pendingLayer(artifact.paintChunks()[0]); + EXPECT_TRUE(PaintArtifactCompositor::canMergeInto( + artifact, artifact.paintChunks()[1], pendingLayer)); + pendingLayer.add(artifact.paintChunks()[1], nullptr); + EXPECT_TRUE(PaintArtifactCompositor::canMergeInto( + artifact, artifact.paintChunks()[2], pendingLayer)); + + update(artifact); + + ASSERT_EQ(1u, contentLayerCount()); + { + Vector<RectWithColor> rectsWithColor; + rectsWithColor.push_back( + RectWithColor(FloatRect(0, 0, 100, 100), Color::white)); + // Transform is applied to this PaintChunk. + rectsWithColor.push_back( + RectWithColor(FloatRect(0, 0, 100, 100), + Color(Color::black).combineWithAlpha(opacity).rgb())); + rectsWithColor.push_back( + RectWithColor(FloatRect(0, 0, 200, 300), Color::gray)); + + const cc::Layer* layer = contentLayerAt(0); + EXPECT_THAT(layer->GetPicture(), Pointee(drawsRectangles(rectsWithColor))); + } +} + +TEST_F(PaintArtifactCompositorTestWithPropertyTrees, MergeNested) { + // Tests merging of an opacity effect, inside of a clip, inside of a + // transform. + + RefPtr<TransformPaintPropertyNode> transform = + TransformPaintPropertyNode::create( + TransformPaintPropertyNode::root(), + TransformationMatrix().translate(50, 50), FloatPoint3D(100, 100, 0), + false, 0); + + RefPtr<ClipPaintPropertyNode> clip = ClipPaintPropertyNode::create( + ClipPaintPropertyNode::root(), transform.get(), + FloatRoundedRect(10, 20, 50, 60)); + + float opacity = 2.0 / 255.0; + RefPtr<EffectPaintPropertyNode> effect = EffectPaintPropertyNode::create( + EffectPaintPropertyNode::root(), transform.get(), clip.get(), + CompositorFilterOperations(), opacity, SkBlendMode::kSrcOver); + + TestPaintArtifact testArtifact; + testArtifact + .chunk(TransformPaintPropertyNode::root(), ClipPaintPropertyNode::root(), + EffectPaintPropertyNode::root()) + .rectDrawing(FloatRect(0, 0, 100, 100), Color::white); + testArtifact.chunk(transform.get(), clip.get(), effect.get()) + .rectDrawing(FloatRect(0, 0, 100, 100), Color::black); + testArtifact + .chunk(TransformPaintPropertyNode::root(), ClipPaintPropertyNode::root(), + EffectPaintPropertyNode::root()) + .rectDrawing(FloatRect(0, 0, 200, 300), Color::gray); + + const PaintArtifact& artifact = testArtifact.build(); + + ASSERT_EQ(3u, artifact.paintChunks().size()); + PaintArtifactCompositor::PendingLayer pendingLayer(artifact.paintChunks()[0]); + EXPECT_TRUE(PaintArtifactCompositor::canMergeInto( + artifact, artifact.paintChunks()[1], pendingLayer)); + pendingLayer.add(artifact.paintChunks()[1], nullptr); + EXPECT_TRUE(PaintArtifactCompositor::canMergeInto( + artifact, artifact.paintChunks()[2], pendingLayer)); + update(artifact); + + ASSERT_EQ(1u, contentLayerCount()); + { + Vector<RectWithColor> rectsWithColor; + rectsWithColor.push_back( + RectWithColor(FloatRect(0, 0, 100, 100), Color::white)); + // Transform is applied to this PaintChunk. + rectsWithColor.push_back( + RectWithColor(FloatRect(60, 70, 50, 60), + Color(Color::black).combineWithAlpha(opacity).rgb())); + rectsWithColor.push_back( + RectWithColor(FloatRect(0, 0, 200, 300), Color::gray)); + + const cc::Layer* layer = contentLayerAt(0); + EXPECT_THAT(layer->GetPicture(), Pointee(drawsRectangles(rectsWithColor))); + } +} + +TEST_F(PaintArtifactCompositorTestWithPropertyTrees, ClipPushedUp) { + // Tests merging of an element which has a clipapplied to it, + // but has an ancestor transform of them. This can happen for fixed- + // or absolute-position elements which escape scroll transforms. + + RefPtr<TransformPaintPropertyNode> transform = + TransformPaintPropertyNode::create( + TransformPaintPropertyNode::root(), + TransformationMatrix().translate(20, 25), FloatPoint3D(100, 100, 0), + false, 0); + + RefPtr<TransformPaintPropertyNode> transform2 = + TransformPaintPropertyNode::create( + transform.get(), TransformationMatrix().translate(20, 25), + FloatPoint3D(100, 100, 0), false, 0); + + RefPtr<ClipPaintPropertyNode> clip = ClipPaintPropertyNode::create( + ClipPaintPropertyNode::root(), transform2.get(), + FloatRoundedRect(10, 20, 50, 60)); + + TestPaintArtifact testArtifact; + testArtifact + .chunk(TransformPaintPropertyNode::root(), ClipPaintPropertyNode::root(), + EffectPaintPropertyNode::root()) + .rectDrawing(FloatRect(0, 0, 100, 100), Color::white); + testArtifact + .chunk(TransformPaintPropertyNode::root(), clip.get(), + EffectPaintPropertyNode::root()) + .rectDrawing(FloatRect(0, 0, 300, 400), Color::black); + testArtifact + .chunk(TransformPaintPropertyNode::root(), ClipPaintPropertyNode::root(), + EffectPaintPropertyNode::root()) + .rectDrawing(FloatRect(0, 0, 200, 300), Color::gray); + + const PaintArtifact& artifact = testArtifact.build(); + + ASSERT_EQ(3u, artifact.paintChunks().size()); + PaintArtifactCompositor::PendingLayer pendingLayer(artifact.paintChunks()[0]); + EXPECT_TRUE(PaintArtifactCompositor::canMergeInto( + artifact, artifact.paintChunks()[1], pendingLayer)); + pendingLayer.add(artifact.paintChunks()[1], nullptr); + EXPECT_TRUE(PaintArtifactCompositor::canMergeInto( + artifact, artifact.paintChunks()[2], pendingLayer)); + update(artifact); + + ASSERT_EQ(1u, contentLayerCount()); + { + Vector<RectWithColor> rectsWithColor; + rectsWithColor.push_back( + RectWithColor(FloatRect(0, 0, 100, 100), Color::white)); + // The two transforms (combined translation of (40, 50)) are applied here, + // before clipping. + rectsWithColor.push_back( + RectWithColor(FloatRect(50, 70, 50, 60), Color(Color::black))); + rectsWithColor.push_back( + RectWithColor(FloatRect(0, 0, 200, 300), Color::gray)); + + const cc::Layer* layer = contentLayerAt(0); + EXPECT_THAT(layer->GetPicture(), Pointee(drawsRectangles(rectsWithColor))); + } +} + +TEST_F(PaintArtifactCompositorTestWithPropertyTrees, EffectPushedUp) { + // Tests merging of an element which has an effect applied to it, + // but has an ancestor transform of them. This can happen for fixed- + // or absolute-position elements which escape scroll transforms. + + RefPtr<TransformPaintPropertyNode> transform = + TransformPaintPropertyNode::create( + TransformPaintPropertyNode::root(), + TransformationMatrix().translate(20, 25), FloatPoint3D(100, 100, 0), + false, 0); + + RefPtr<TransformPaintPropertyNode> transform2 = + TransformPaintPropertyNode::create( + transform.get(), TransformationMatrix().translate(20, 25), + FloatPoint3D(100, 100, 0), false, 0); + + float opacity = 2.0 / 255.0; + RefPtr<EffectPaintPropertyNode> effect = EffectPaintPropertyNode::create( + EffectPaintPropertyNode::root(), transform2.get(), + ClipPaintPropertyNode::root(), CompositorFilterOperations(), opacity, + SkBlendMode::kSrcOver); + + TestPaintArtifact testArtifact; + testArtifact + .chunk(TransformPaintPropertyNode::root(), ClipPaintPropertyNode::root(), + EffectPaintPropertyNode::root()) + .rectDrawing(FloatRect(0, 0, 100, 100), Color::white); + testArtifact + .chunk(TransformPaintPropertyNode::root(), ClipPaintPropertyNode::root(), + effect.get()) + .rectDrawing(FloatRect(0, 0, 300, 400), Color::black); + testArtifact + .chunk(TransformPaintPropertyNode::root(), ClipPaintPropertyNode::root(), + EffectPaintPropertyNode::root()) + .rectDrawing(FloatRect(0, 0, 200, 300), Color::gray); + + const PaintArtifact& artifact = testArtifact.build(); + + ASSERT_EQ(3u, artifact.paintChunks().size()); + PaintArtifactCompositor::PendingLayer pendingLayer(artifact.paintChunks()[0]); + EXPECT_TRUE(PaintArtifactCompositor::canMergeInto( + artifact, artifact.paintChunks()[1], pendingLayer)); + pendingLayer.add(artifact.paintChunks()[1], nullptr); + EXPECT_TRUE(PaintArtifactCompositor::canMergeInto( + artifact, artifact.paintChunks()[2], pendingLayer)); + update(artifact); + + ASSERT_EQ(1u, contentLayerCount()); + { + Vector<RectWithColor> rectsWithColor; + rectsWithColor.push_back( + RectWithColor(FloatRect(0, 0, 100, 100), Color::white)); + rectsWithColor.push_back( + RectWithColor(FloatRect(0, 0, 300, 400), + Color(Color::black).combineWithAlpha(opacity).rgb())); + rectsWithColor.push_back( + RectWithColor(FloatRect(0, 0, 200, 300), Color::gray)); + + const cc::Layer* layer = contentLayerAt(0); + EXPECT_THAT(layer->GetPicture(), Pointee(drawsRectangles(rectsWithColor))); + } +} + +TEST_F(PaintArtifactCompositorTestWithPropertyTrees, EffectAndClipPushedUp) { + // Tests merging of an element which has an effect applied to it, + // but has an ancestor transform of them. This can happen for fixed- + // or absolute-position elements which escape scroll transforms. + + RefPtr<TransformPaintPropertyNode> transform = + TransformPaintPropertyNode::create( + TransformPaintPropertyNode::root(), + TransformationMatrix().translate(20, 25), FloatPoint3D(100, 100, 0), + false, 0); + + RefPtr<TransformPaintPropertyNode> transform2 = + TransformPaintPropertyNode::create( + transform.get(), TransformationMatrix().translate(20, 25), + FloatPoint3D(100, 100, 0), false, 0); + + RefPtr<ClipPaintPropertyNode> clip = ClipPaintPropertyNode::create( + ClipPaintPropertyNode::root(), transform.get(), + FloatRoundedRect(10, 20, 50, 60)); + + float opacity = 2.0 / 255.0; + RefPtr<EffectPaintPropertyNode> effect = EffectPaintPropertyNode::create( + EffectPaintPropertyNode::root(), transform2.get(), clip.get(), + CompositorFilterOperations(), opacity, SkBlendMode::kSrcOver); + + TestPaintArtifact testArtifact; + testArtifact + .chunk(TransformPaintPropertyNode::root(), ClipPaintPropertyNode::root(), + EffectPaintPropertyNode::root()) + .rectDrawing(FloatRect(0, 0, 100, 100), Color::white); + testArtifact + .chunk(TransformPaintPropertyNode::root(), clip.get(), effect.get()) + .rectDrawing(FloatRect(0, 0, 300, 400), Color::black); + testArtifact + .chunk(TransformPaintPropertyNode::root(), ClipPaintPropertyNode::root(), + EffectPaintPropertyNode::root()) + .rectDrawing(FloatRect(0, 0, 200, 300), Color::gray); + + const PaintArtifact& artifact = testArtifact.build(); + + ASSERT_EQ(3u, artifact.paintChunks().size()); + PaintArtifactCompositor::PendingLayer pendingLayer(artifact.paintChunks()[0]); + EXPECT_TRUE(PaintArtifactCompositor::canMergeInto( + artifact, artifact.paintChunks()[1], pendingLayer)); + pendingLayer.add(artifact.paintChunks()[1], nullptr); + EXPECT_TRUE(PaintArtifactCompositor::canMergeInto( + artifact, artifact.paintChunks()[2], pendingLayer)); + update(artifact); + + ASSERT_EQ(1u, contentLayerCount()); + { + Vector<RectWithColor> rectsWithColor; + rectsWithColor.push_back( + RectWithColor(FloatRect(0, 0, 100, 100), Color::white)); + // The clip is under |transform| but not |transform2|, so only an adjustment + // of (20, 25) occurs. + rectsWithColor.push_back( + RectWithColor(FloatRect(30, 45, 50, 60), + Color(Color::black).combineWithAlpha(opacity).rgb())); + rectsWithColor.push_back( + RectWithColor(FloatRect(0, 0, 200, 300), Color::gray)); + + const cc::Layer* layer = contentLayerAt(0); + EXPECT_THAT(layer->GetPicture(), Pointee(drawsRectangles(rectsWithColor))); + } +} + +TEST_F(PaintArtifactCompositorTestWithPropertyTrees, ClipAndEffectNoTransform) { + // Tests merging of an element which has a clip and effect in the root + // transform space. + + RefPtr<ClipPaintPropertyNode> clip = ClipPaintPropertyNode::create( + ClipPaintPropertyNode::root(), TransformPaintPropertyNode::root(), + FloatRoundedRect(10, 20, 50, 60)); + + float opacity = 2.0 / 255.0; + RefPtr<EffectPaintPropertyNode> effect = EffectPaintPropertyNode::create( + EffectPaintPropertyNode::root(), TransformPaintPropertyNode::root(), + clip.get(), CompositorFilterOperations(), opacity, SkBlendMode::kSrcOver); + + TestPaintArtifact testArtifact; + testArtifact + .chunk(TransformPaintPropertyNode::root(), ClipPaintPropertyNode::root(), + EffectPaintPropertyNode::root()) + .rectDrawing(FloatRect(0, 0, 100, 100), Color::white); + testArtifact + .chunk(TransformPaintPropertyNode::root(), clip.get(), effect.get()) + .rectDrawing(FloatRect(0, 0, 300, 400), Color::black); + testArtifact + .chunk(TransformPaintPropertyNode::root(), ClipPaintPropertyNode::root(), + EffectPaintPropertyNode::root()) + .rectDrawing(FloatRect(0, 0, 200, 300), Color::gray); + + const PaintArtifact& artifact = testArtifact.build(); + + ASSERT_EQ(3u, artifact.paintChunks().size()); + PaintArtifactCompositor::PendingLayer pendingLayer(artifact.paintChunks()[0]); + EXPECT_TRUE(PaintArtifactCompositor::canMergeInto( + artifact, artifact.paintChunks()[1], pendingLayer)); + pendingLayer.add(artifact.paintChunks()[1], nullptr); + EXPECT_TRUE(PaintArtifactCompositor::canMergeInto( + artifact, artifact.paintChunks()[2], pendingLayer)); + update(artifact); + + ASSERT_EQ(1u, contentLayerCount()); + { + Vector<RectWithColor> rectsWithColor; + rectsWithColor.push_back( + RectWithColor(FloatRect(0, 0, 100, 100), Color::white)); + rectsWithColor.push_back( + RectWithColor(FloatRect(10, 20, 50, 60), + Color(Color::black).combineWithAlpha(opacity).rgb())); + rectsWithColor.push_back( + RectWithColor(FloatRect(0, 0, 200, 300), Color::gray)); + + const cc::Layer* layer = contentLayerAt(0); + EXPECT_THAT(layer->GetPicture(), Pointee(drawsRectangles(rectsWithColor))); + } +} + +TEST_F(PaintArtifactCompositorTestWithPropertyTrees, TwoClips) { + // Tests merging of an element which has two clips in the root + // transform space. + + RefPtr<ClipPaintPropertyNode> clip = ClipPaintPropertyNode::create( + ClipPaintPropertyNode::root(), TransformPaintPropertyNode::root(), + FloatRoundedRect(20, 30, 10, 20)); + + RefPtr<ClipPaintPropertyNode> clip2 = ClipPaintPropertyNode::create( + clip.get(), TransformPaintPropertyNode::root(), + FloatRoundedRect(10, 20, 50, 60)); + + TestPaintArtifact testArtifact; + testArtifact + .chunk(TransformPaintPropertyNode::root(), ClipPaintPropertyNode::root(), + EffectPaintPropertyNode::root()) + .rectDrawing(FloatRect(0, 0, 100, 100), Color::white); + testArtifact + .chunk(TransformPaintPropertyNode::root(), clip2.get(), + EffectPaintPropertyNode::root()) + .rectDrawing(FloatRect(0, 0, 300, 400), Color::black); + testArtifact + .chunk(TransformPaintPropertyNode::root(), ClipPaintPropertyNode::root(), + EffectPaintPropertyNode::root()) + .rectDrawing(FloatRect(0, 0, 200, 300), Color::gray); + + const PaintArtifact& artifact = testArtifact.build(); + + ASSERT_EQ(3u, artifact.paintChunks().size()); + PaintArtifactCompositor::PendingLayer pendingLayer(artifact.paintChunks()[0]); + EXPECT_TRUE(PaintArtifactCompositor::canMergeInto( + artifact, artifact.paintChunks()[1], pendingLayer)); + pendingLayer.add(artifact.paintChunks()[1], nullptr); + EXPECT_TRUE(PaintArtifactCompositor::canMergeInto( + artifact, artifact.paintChunks()[2], pendingLayer)); + update(artifact); + + ASSERT_EQ(1u, contentLayerCount()); + { + Vector<RectWithColor> rectsWithColor; + rectsWithColor.push_back( + RectWithColor(FloatRect(0, 0, 100, 100), Color::white)); + // The interesction of the two clips is (20, 30, 10, 20). + rectsWithColor.push_back( + RectWithColor(FloatRect(20, 30, 10, 20), Color(Color::black))); + rectsWithColor.push_back( + RectWithColor(FloatRect(0, 0, 200, 300), Color::gray)); + + const cc::Layer* layer = contentLayerAt(0); + EXPECT_THAT(layer->GetPicture(), Pointee(drawsRectangles(rectsWithColor))); + } +} + +TEST_F(PaintArtifactCompositorTestWithPropertyTrees, PendingLayer) { + PaintChunk chunk1; + chunk1.properties.propertyTreeState = PropertyTreeState( + TransformPaintPropertyNode::root(), ClipPaintPropertyNode::root(), + EffectPaintPropertyNode::root(), ScrollPaintPropertyNode::root()); + chunk1.properties.backfaceHidden = true; + chunk1.knownToBeOpaque = true; + chunk1.bounds = FloatRect(0, 0, 30, 40); + + PaintArtifactCompositor::PendingLayer pendingLayer(chunk1); + + EXPECT_TRUE(pendingLayer.backfaceHidden); + EXPECT_TRUE(pendingLayer.knownToBeOpaque); + EXPECT_FLOAT_RECT_EQ(FloatRect(0, 0, 30, 40), pendingLayer.bounds); + + PaintChunk chunk2; + chunk2.properties.propertyTreeState = chunk1.properties.propertyTreeState; + chunk2.properties.backfaceHidden = true; + chunk2.knownToBeOpaque = true; + chunk2.bounds = FloatRect(10, 20, 30, 40); + pendingLayer.add(chunk2, nullptr); + + EXPECT_TRUE(pendingLayer.backfaceHidden); + // Bounds not equal to one PaintChunk. + EXPECT_FALSE(pendingLayer.knownToBeOpaque); + EXPECT_FLOAT_RECT_EQ(FloatRect(0, 0, 40, 60), pendingLayer.bounds); + + PaintChunk chunk3; + chunk3.properties.propertyTreeState = chunk1.properties.propertyTreeState; + chunk3.properties.backfaceHidden = true; + chunk3.knownToBeOpaque = true; + chunk3.bounds = FloatRect(-5, -25, 20, 20); + pendingLayer.add(chunk3, nullptr); + + EXPECT_TRUE(pendingLayer.backfaceHidden); + EXPECT_FALSE(pendingLayer.knownToBeOpaque); + EXPECT_FLOAT_RECT_EQ(FloatRect(-5, -25, 45, 85), pendingLayer.bounds); +} + +TEST_F(PaintArtifactCompositorTestWithPropertyTrees, PendingLayerWithGeometry) { + RefPtr<TransformPaintPropertyNode> transform = + TransformPaintPropertyNode::create( + TransformPaintPropertyNode::root(), + TransformationMatrix().translate(20, 25), FloatPoint3D(100, 100, 0), + false, 0); + + PaintChunk chunk1; + chunk1.properties.propertyTreeState = PropertyTreeState( + TransformPaintPropertyNode::root(), ClipPaintPropertyNode::root(), + EffectPaintPropertyNode::root(), ScrollPaintPropertyNode::root()); + chunk1.bounds = FloatRect(0, 0, 30, 40); + + PaintArtifactCompositor::PendingLayer pendingLayer(chunk1); + + EXPECT_FLOAT_RECT_EQ(FloatRect(0, 0, 30, 40), pendingLayer.bounds); + + PaintChunk chunk2; + chunk2.properties.propertyTreeState = chunk1.properties.propertyTreeState; + chunk2.properties.propertyTreeState.setTransform(transform); + chunk2.bounds = FloatRect(0, 0, 50, 60); + GeometryMapper geometryMapper; + pendingLayer.add(chunk2, &geometryMapper); + + EXPECT_FLOAT_RECT_EQ(FloatRect(0, 0, 70, 85), pendingLayer.bounds); +} + } // namespace blink
diff --git a/third_party/WebKit/Source/platform/graphics/compositing/README.md b/third_party/WebKit/Source/platform/graphics/compositing/README.md new file mode 100644 index 0000000..29eb505 --- /dev/null +++ b/third_party/WebKit/Source/platform/graphics/compositing/README.md
@@ -0,0 +1,59 @@ +# `Source/platform/graphics/compositing` + +This directory contains the implementation of the "Blink compositing algorithm". + +This code is owned by the [paint team][paint-team-site]. +[paint-team-site]: https://www.chromium.org/developers/paint-team + +This document explains the SPv2 world as it develops, not the SPv1 world it +replaces. + +## Blink compositing algorithm + +Design document: goo.gl/6xP8Oe + +Inputs: `PaintArtifact` +Outputs: List of `cc::Layer` objects and `cc::PropertyTree`'s. + +The algorithm walks through the list of `PaintChunks` in the `PaintArtifact`, +allocating new `c::Layers` if the `PaintChunk` cannot merge into an existing +`cc::Layer`. The reasons why it would not be able to do so are: + +1. The `PaintChunk` requires a foreign layer (see below) + +2. The `PaintChunk` cannot merge with any existing layer, due incompatible +direct compositing reasons on its `PropertyTreeState`. + +3. The `PaintChunk` overlaps with an earlier `cc::Layer` that it can't merge with +due to reason 2, and there is no later-drawn `cc::Layer` for which reasons 1 and +2 do not apply. + +In the worst case, this algorithm has an O(n^2) running time, where n is the +number of `PaintChunks`. + +All property tree nodes referred to by any `PaintChunk` are currently copied +into their equivalent `cc::PropertyTree` node, regardless of whether they are +required by the above. + +### Flattening property tree nodes + +When `PaintChunks` can merge into an existing `cc::Layer`, they may have +different `PropertyTreeState`s than the `PropertyTreeState` of the `cc::Layer`. +If so, we need to *flatten* down the nodes that are different between the +`PropertyTreeState` of the `PaintChunk` and the `cc::Layer`. This is done +by iterating over the "innermostNode" of the `PropertyTreeState`, finding +the sequence of transform, clip and effect nodes that need to be flattened, +and turning them into a sequence of paired display items representing the +transforms, clips and effects. The algorithm for this is spelled out in +the designdoc at http://goo.gl/6xP8Oe. + +### Foreign layers + +Some `PaintChunk` content requires a foreign layer, meaning a layer that is +managed outside of the scope of this code. Examples are composited video, a +and 2D/3D (WebGL) canvas. + +### Raster invalidations + +Any raster invalidates on a `PaintChunk` are also mapped to the space of the +backing `cc::Layer`.
diff --git a/third_party/WebKit/Source/platform/graphics/paint/ClipPaintPropertyNode.h b/third_party/WebKit/Source/platform/graphics/paint/ClipPaintPropertyNode.h index e86f72bc..80ec78512 100644 --- a/third_party/WebKit/Source/platform/graphics/paint/ClipPaintPropertyNode.h +++ b/third_party/WebKit/Source/platform/graphics/paint/ClipPaintPropertyNode.h
@@ -31,9 +31,11 @@ static PassRefPtr<ClipPaintPropertyNode> create( PassRefPtr<const ClipPaintPropertyNode> parent, PassRefPtr<const TransformPaintPropertyNode> localTransformSpace, - const FloatRoundedRect& clipRect) { + const FloatRoundedRect& clipRect, + CompositingReasons directCompositingReasons = CompositingReasonNone) { return adoptRef(new ClipPaintPropertyNode( - std::move(parent), std::move(localTransformSpace), clipRect)); + std::move(parent), std::move(localTransformSpace), clipRect, + directCompositingReasons)); } void update(PassRefPtr<const ClipPaintPropertyNode> parent, @@ -59,8 +61,9 @@ // The clone function is used by FindPropertiesNeedingUpdate.h for recording // a clip node before it has been updated, to later detect changes. PassRefPtr<ClipPaintPropertyNode> clone() const { - return adoptRef( - new ClipPaintPropertyNode(m_parent, m_localTransformSpace, m_clipRect)); + return adoptRef(new ClipPaintPropertyNode(m_parent, m_localTransformSpace, + m_clipRect, + m_directCompositingReasons)); } // The equality operator is used by FindPropertiesNeedingUpdate.h for checking @@ -74,18 +77,25 @@ String toString() const; + bool hasDirectCompositingReasons() const { + return m_directCompositingReasons != CompositingReasonNone; + } + private: ClipPaintPropertyNode( PassRefPtr<const ClipPaintPropertyNode> parent, PassRefPtr<const TransformPaintPropertyNode> localTransformSpace, - const FloatRoundedRect& clipRect) + const FloatRoundedRect& clipRect, + CompositingReasons directCompositingReasons) : m_parent(parent), m_localTransformSpace(localTransformSpace), - m_clipRect(clipRect) {} + m_clipRect(clipRect), + m_directCompositingReasons(directCompositingReasons) {} RefPtr<const ClipPaintPropertyNode> m_parent; RefPtr<const TransformPaintPropertyNode> m_localTransformSpace; FloatRoundedRect m_clipRect; + CompositingReasons m_directCompositingReasons; }; // Redeclared here to avoid ODR issues.
diff --git a/third_party/WebKit/Source/platform/graphics/paint/GeometryMapper.h b/third_party/WebKit/Source/platform/graphics/paint/GeometryMapper.h index 7df60f2..b12dcffd 100644 --- a/third_party/WebKit/Source/platform/graphics/paint/GeometryMapper.h +++ b/third_party/WebKit/Source/platform/graphics/paint/GeometryMapper.h
@@ -117,6 +117,14 @@ const PropertyTreeState& ancestorState, bool& success); + // Returns the matrix used in |LocalToAncestorRect|. Sets |success| to false + // iff |localTransformNode| is not equal to or a descendant of + // |ancestorState.transform|. + const TransformationMatrix& localToAncestorMatrix( + const TransformPaintPropertyNode* localTransformNode, + const PropertyTreeState& ancestorState, + bool& success); + private: // Used by mapToVisualRectInDestinationSpace() after fast mapping (assuming // destination is an ancestor of source) failed. @@ -134,14 +142,6 @@ const PropertyTreeState& destinationState, bool& success); - // Returns the matrix used in |LocalToAncestorRect|. Sets |success| to false - // iff |localTransformNode| is not equal to or a descendant of - // |ancestorState.transform|. - const TransformationMatrix& localToAncestorMatrix( - const TransformPaintPropertyNode* localTransformNode, - const PropertyTreeState& ancestorState, - bool& success); - // Returns the "clip visual rect" between |localTransformState| and // |ancestorState|. See above for the definition of "clip visual rect". FloatRect localToAncestorClipRect(
diff --git a/third_party/WebKit/Source/platform/graphics/paint/PropertyTreeState.cpp b/third_party/WebKit/Source/platform/graphics/paint/PropertyTreeState.cpp new file mode 100644 index 0000000..8f2317a --- /dev/null +++ b/third_party/WebKit/Source/platform/graphics/paint/PropertyTreeState.cpp
@@ -0,0 +1,76 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "platform/graphics/paint/PropertyTreeState.h" + +#include "platform/graphics/paint/GeometryMapper.h" + +namespace blink { + +bool PropertyTreeState::hasDirectCompositingReasons() const { + switch (innermostNode()) { + case Transform: + return transform()->hasDirectCompositingReasons(); + case Clip: + return clip()->hasDirectCompositingReasons(); + case Effect: + return effect()->hasDirectCompositingReasons(); + default: + return false; + } +} + +template <typename PropertyNode> +bool isAncestorOf(const PropertyNode* ancestor, const PropertyNode* child) { + while (child && child != ancestor) { + child = child->parent(); + } + return child == ancestor; +} + +PropertyTreeState::InnermostNode PropertyTreeState::innermostNode() const { + // TODO(chrishtr): this is very inefficient when innermostNode() is called + // repeatedly. + bool clipTransformStrictAncestorOfTransform = + m_clip->localTransformSpace() != m_transform.get() && + isAncestorOf<TransformPaintPropertyNode>(m_clip->localTransformSpace(), + m_transform.get()); + bool effectTransformStrictAncestorOfTransform = + m_effect->localTransformSpace() != m_transform.get() && + isAncestorOf<TransformPaintPropertyNode>(m_effect->localTransformSpace(), + m_transform.get()); + + if (!m_transform->isRoot() && clipTransformStrictAncestorOfTransform && + effectTransformStrictAncestorOfTransform) + return Transform; + + bool clipAncestorOfEffect = + isAncestorOf<ClipPaintPropertyNode>(m_clip.get(), m_effect->outputClip()); + + if (!m_effect->isRoot() && clipAncestorOfEffect) { + return Effect; + } + if (!m_clip->isRoot()) + return Clip; + return None; +} + +const PropertyTreeState* PropertyTreeStateIterator::next() { + switch (m_properties.innermostNode()) { + case PropertyTreeState::Transform: + m_properties.setTransform(m_properties.transform()->parent()); + return &m_properties; + case PropertyTreeState::Clip: + m_properties.setClip(m_properties.clip()->parent()); + return &m_properties; + case PropertyTreeState::Effect: + m_properties.setEffect(m_properties.effect()->parent()); + return &m_properties; + case PropertyTreeState::None: + return nullptr; + } + return nullptr; +} + +} // namespace blink
diff --git a/third_party/WebKit/Source/platform/graphics/paint/PropertyTreeState.h b/third_party/WebKit/Source/platform/graphics/paint/PropertyTreeState.h index c59fc27..1955559 100644 --- a/third_party/WebKit/Source/platform/graphics/paint/PropertyTreeState.h +++ b/third_party/WebKit/Source/platform/graphics/paint/PropertyTreeState.h
@@ -18,7 +18,7 @@ // other objects. RefPtrs are used to guard against use-after-free bugs and // DCHECKs ensure PropertyTreeState never retains the last reference to a // property tree node. -class PropertyTreeState { +class PLATFORM_EXPORT PropertyTreeState { public: PropertyTreeState(const TransformPaintPropertyNode* transform, const ClipPaintPropertyNode* clip, @@ -34,6 +34,8 @@ DCHECK(!m_scroll || !m_scroll->hasOneRef()); } + bool hasDirectCompositingReasons() const; + const TransformPaintPropertyNode* transform() const { DCHECK(!m_transform || !m_transform->hasOneRef()); return m_transform.get(); @@ -66,6 +68,50 @@ m_scroll = std::move(node); } + enum InnermostNode { + None, // None means that all nodes are their root values + Transform, + Clip, + Effect, + }; + + // There is always a well-defined order in which the transform, clip + // and effects of a PropertyTreeState apply. This method returns which + // of them applies first to content drawn with this PropertyTreeState. + // Note that it may be the case that multiple nodes from the same tree apply + // before any from another tree. This can happen, for example, if multiple + // effects or clips apply to a descendant transform state from the transform + // node. + // + // This method is meant to be used in concert with + // |PropertyTreeStateIterator|, which allows one to iterate over the nodes in + // the order in which they apply. + // + // Example: + // + // Transform tree Clip tree Effect tree + // ~~~~~~~~~~~~~~ ~~~~~~~~~ ~~~~~~~~~~~ + // Root Root Root + // | | | + // T C E + // + // Suppose that PropertyTreeState(T, C, E).innerMostNode() is E, and + // PropertytreeState(T, C, Root).innermostNode() is C. Then a PaintChunk + // that has propertyTreeState = PropertyTreeState(T, C, E) can be painted + // with the following display list structure: + // + // [BeginTransform] [BeginClip] [BeginEffect] PaintChunk drawings + // [EndEffect] [EndClip] [EndTransform] + // + // The PropertyTreeStateIterator will behave like this: + // + // PropertyTreeStateIterator iterator(PropertyTreeState(T, C, E)); + // DCHECK(iterator.innermostNode() == Effect); + // DCHECK(iterator.next()->innermostNode() == Clip); + // DCHECK(iterator.next()->innermostNode() == Transform); + // DCHECK(iterator.next()->innermostNode() == None); + InnermostNode innermostNode() const; + private: RefPtr<const TransformPaintPropertyNode> m_transform; RefPtr<const ClipPaintPropertyNode> m_clip; @@ -78,6 +124,21 @@ a.effect() == b.effect() && a.scroll() == b.scroll(); } +// Iterates over the sequence transforms, clips and effects for a +// PropertyTreeState between that state and the "root" state (all nodes equal +// to *::Root()), in the order that they apply. +// +// See also PropertyTreeState::innermostNode for a more detailed example. +class PLATFORM_EXPORT PropertyTreeStateIterator { + public: + PropertyTreeStateIterator(const PropertyTreeState& properties) + : m_properties(properties) {} + const PropertyTreeState* next(); + + private: + PropertyTreeState m_properties; +}; + } // namespace blink #endif // PropertyTreeState_h
diff --git a/third_party/WebKit/Source/platform/graphics/paint/PropertyTreeStateTest.cpp b/third_party/WebKit/Source/platform/graphics/paint/PropertyTreeStateTest.cpp new file mode 100644 index 0000000..d211d86 --- /dev/null +++ b/third_party/WebKit/Source/platform/graphics/paint/PropertyTreeStateTest.cpp
@@ -0,0 +1,156 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "platform/graphics/paint/PropertyTreeState.h" + +#include "testing/gtest/include/gtest/gtest.h" + +namespace blink { + +class PropertyTreeStateTest : public ::testing::Test {}; + +TEST_F(PropertyTreeStateTest, TrasformOnEffectOnClip) { + RefPtr<TransformPaintPropertyNode> transform = + TransformPaintPropertyNode::create(TransformPaintPropertyNode::root(), + TransformationMatrix(), + FloatPoint3D()); + + RefPtr<ClipPaintPropertyNode> clip = ClipPaintPropertyNode::create( + ClipPaintPropertyNode::root(), TransformPaintPropertyNode::root(), + FloatRoundedRect()); + + RefPtr<EffectPaintPropertyNode> effect = EffectPaintPropertyNode::create( + EffectPaintPropertyNode::root(), TransformPaintPropertyNode::root(), + clip.get(), CompositorFilterOperations(), 1.0, SkBlendMode::kSrcOver); + + PropertyTreeState state(transform.get(), clip.get(), effect.get(), + ScrollPaintPropertyNode::root()); + EXPECT_EQ(PropertyTreeState::Transform, state.innermostNode()); + + PropertyTreeStateIterator iterator(state); + EXPECT_EQ(PropertyTreeState::Effect, iterator.next()->innermostNode()); + EXPECT_EQ(PropertyTreeState::Clip, iterator.next()->innermostNode()); + EXPECT_EQ(PropertyTreeState::None, iterator.next()->innermostNode()); +} + +TEST_F(PropertyTreeStateTest, RootState) { + PropertyTreeState state( + TransformPaintPropertyNode::root(), ClipPaintPropertyNode::root(), + EffectPaintPropertyNode::root(), ScrollPaintPropertyNode::root()); + EXPECT_EQ(PropertyTreeState::None, state.innermostNode()); +} + +TEST_F(PropertyTreeStateTest, EffectOnClipOnTransform) { + RefPtr<TransformPaintPropertyNode> transform = + TransformPaintPropertyNode::create(TransformPaintPropertyNode::root(), + TransformationMatrix(), + FloatPoint3D()); + + RefPtr<ClipPaintPropertyNode> clip = ClipPaintPropertyNode::create( + ClipPaintPropertyNode::root(), transform.get(), FloatRoundedRect()); + + RefPtr<EffectPaintPropertyNode> effect = EffectPaintPropertyNode::create( + EffectPaintPropertyNode::root(), transform.get(), clip.get(), + CompositorFilterOperations(), 1.0, SkBlendMode::kSrcOver); + + PropertyTreeState state(transform.get(), clip.get(), effect.get(), + ScrollPaintPropertyNode::root()); + EXPECT_EQ(PropertyTreeState::Effect, state.innermostNode()); + + PropertyTreeStateIterator iterator(state); + EXPECT_EQ(PropertyTreeState::Clip, iterator.next()->innermostNode()); + EXPECT_EQ(PropertyTreeState::Transform, iterator.next()->innermostNode()); + EXPECT_EQ(PropertyTreeState::None, iterator.next()->innermostNode()); +} + +TEST_F(PropertyTreeStateTest, ClipOnEffectOnTransform) { + RefPtr<TransformPaintPropertyNode> transform = + TransformPaintPropertyNode::create(TransformPaintPropertyNode::root(), + TransformationMatrix(), + FloatPoint3D()); + + RefPtr<ClipPaintPropertyNode> clip = ClipPaintPropertyNode::create( + ClipPaintPropertyNode::root(), transform.get(), FloatRoundedRect()); + + RefPtr<EffectPaintPropertyNode> effect = EffectPaintPropertyNode::create( + EffectPaintPropertyNode::root(), transform.get(), + ClipPaintPropertyNode::root(), CompositorFilterOperations(), 1.0, + SkBlendMode::kSrcOver); + + PropertyTreeState state(transform.get(), clip.get(), effect.get(), + ScrollPaintPropertyNode::root()); + EXPECT_EQ(PropertyTreeState::Clip, state.innermostNode()); + + PropertyTreeStateIterator iterator(state); + EXPECT_EQ(PropertyTreeState::Effect, iterator.next()->innermostNode()); + EXPECT_EQ(PropertyTreeState::Transform, iterator.next()->innermostNode()); + EXPECT_EQ(PropertyTreeState::None, iterator.next()->innermostNode()); +} + +TEST_F(PropertyTreeStateTest, ClipDescendantOfTransform) { + RefPtr<TransformPaintPropertyNode> transform = + TransformPaintPropertyNode::create(TransformPaintPropertyNode::root(), + TransformationMatrix(), + FloatPoint3D()); + + RefPtr<TransformPaintPropertyNode> transform2 = + TransformPaintPropertyNode::create( + transform.get(), TransformationMatrix(), FloatPoint3D()); + + RefPtr<ClipPaintPropertyNode> clip = ClipPaintPropertyNode::create( + ClipPaintPropertyNode::root(), transform2.get(), FloatRoundedRect()); + + RefPtr<EffectPaintPropertyNode> effect = EffectPaintPropertyNode::create( + EffectPaintPropertyNode::root(), TransformPaintPropertyNode::root(), + ClipPaintPropertyNode::root(), CompositorFilterOperations(), 1.0, + SkBlendMode::kSrcOver); + + // Here the clip is inside of its own transform, but the transform is an + // ancestor of the clip's transform. This models situations such as + // a clip inside a scroller that applies to an absolute-positioned element + // which escapes the scroll transform but not the clip. + PropertyTreeState state(transform.get(), clip.get(), effect.get(), + ScrollPaintPropertyNode::root()); + EXPECT_EQ(PropertyTreeState::Clip, state.innermostNode()); + + PropertyTreeStateIterator iterator(state); + EXPECT_EQ(PropertyTreeState::Transform, iterator.next()->innermostNode()); + EXPECT_EQ(PropertyTreeState::Effect, iterator.next()->innermostNode()); + EXPECT_EQ(PropertyTreeState::None, iterator.next()->innermostNode()); +} + +TEST_F(PropertyTreeStateTest, EffectDescendantOfTransform) { + RefPtr<TransformPaintPropertyNode> transform = + TransformPaintPropertyNode::create(TransformPaintPropertyNode::root(), + TransformationMatrix(), + FloatPoint3D()); + + RefPtr<ClipPaintPropertyNode> clip = ClipPaintPropertyNode::create( + ClipPaintPropertyNode::root(), TransformPaintPropertyNode::root(), + FloatRoundedRect()); + + RefPtr<TransformPaintPropertyNode> transform2 = + TransformPaintPropertyNode::create(TransformPaintPropertyNode::root(), + TransformationMatrix(), + FloatPoint3D()); + + RefPtr<EffectPaintPropertyNode> effect = EffectPaintPropertyNode::create( + EffectPaintPropertyNode::root(), transform2.get(), clip.get(), + CompositorFilterOperations(), 1.0, SkBlendMode::kSrcOver); + + // Here the clip is inside of its own transform, but the transform is an + // ancestor of the clip's transform. This models situations such as + // a clip inside a scroller that applies to an absolute-positioned element + // which escapes the scroll transform but not the clip. + PropertyTreeState state(transform.get(), clip.get(), effect.get(), + ScrollPaintPropertyNode::root()); + EXPECT_EQ(PropertyTreeState::Effect, state.innermostNode()); + + PropertyTreeStateIterator iterator(state); + EXPECT_EQ(PropertyTreeState::Transform, iterator.next()->innermostNode()); + EXPECT_EQ(PropertyTreeState::Clip, iterator.next()->innermostNode()); + EXPECT_EQ(PropertyTreeState::None, iterator.next()->innermostNode()); +} + +} // namespace blink
diff --git a/third_party/WebKit/Source/platform/scroll/ScrollAnimatorBase.h b/third_party/WebKit/Source/platform/scroll/ScrollAnimatorBase.h index 6a81c42b..758431ce 100644 --- a/third_party/WebKit/Source/platform/scroll/ScrollAnimatorBase.h +++ b/third_party/WebKit/Source/platform/scroll/ScrollAnimatorBase.h
@@ -90,7 +90,6 @@ virtual void mouseMovedInContentArea() const {} virtual void mouseEnteredScrollbar(Scrollbar&) const {} virtual void mouseExitedScrollbar(Scrollbar&) const {} - virtual void updateAfterLayout() {} virtual void contentsResized() const {} virtual void contentAreaDidShow() const {} virtual void contentAreaDidHide() const {}
diff --git a/third_party/WebKit/Source/platform/testing/PictureMatchers.cpp b/third_party/WebKit/Source/platform/testing/PictureMatchers.cpp index 1324f7b..5f04765a 100644 --- a/third_party/WebKit/Source/platform/testing/PictureMatchers.cpp +++ b/third_party/WebKit/Source/platform/testing/PictureMatchers.cpp
@@ -16,81 +16,165 @@ namespace { -class DrawsRectangleCanvas : public SkCanvas { - public: - DrawsRectangleCanvas() : SkCanvas(800, 600) {} - const Vector<std::pair<FloatQuad, Color>>& quads() const { return m_quads; } - void onDrawRect(const SkRect& rect, const SkPaint& paint) override { - SkPoint quad[4]; - getTotalMatrix().mapRectToQuad(quad, rect); - FloatQuad floatQuad(quad); - m_quads.append(std::make_pair(floatQuad, Color(paint.getColor()))); - SkCanvas::onDrawRect(rect, paint); - } - - private: - Vector<std::pair<FloatQuad, Color>> m_quads; +struct QuadWithColor { + FloatQuad quad; + Color color; }; -class DrawsRectangleMatcher +class DrawsRectangleCanvas : public SkCanvas { + public: + DrawsRectangleCanvas() + : SkCanvas(800, 600), + m_saveCount(0), + m_alpha(255), + m_alphaSaveLayerCount(-1) {} + const Vector<QuadWithColor>& quadsWithColor() const { return m_quads; } + + void onDrawRect(const SkRect& rect, const SkPaint& paint) override { + SkRect clippedRect(rect); + for (Vector<ClipAndIndex>::const_reverse_iterator clip = m_clips.rbegin(); + clip != m_clips.rend(); clip++) { + if (SkRect::Intersects(rect, clip->rect)) + CHECK(clippedRect.intersect(clip->rect)); + } + SkPoint quad[4]; + getTotalMatrix().mapRectToQuad(quad, clippedRect); + QuadWithColor quadWithColor; + quadWithColor.quad = FloatQuad(quad); + + unsigned paintAlpha = static_cast<unsigned>(paint.getAlpha()); + SkPaint paintWithAlpha(paint); + paintWithAlpha.setAlpha(static_cast<U8CPU>(m_alpha * paintAlpha / 255)); + quadWithColor.color = Color(paintWithAlpha.getColor()); + m_quads.append(quadWithColor); + SkCanvas::onDrawRect(clippedRect, paint); + } + + SkCanvas::SaveLayerStrategy getSaveLayerStrategy( + const SaveLayerRec& rec) override { + m_saveCount++; + unsigned layerAlpha = static_cast<unsigned>(rec.fPaint->getAlpha()); + if (layerAlpha < 255) { + DCHECK_EQ(m_alphaSaveLayerCount, -1); + m_alphaSaveLayerCount = m_saveCount; + m_alpha = layerAlpha; + } + return SkCanvas::getSaveLayerStrategy(rec); + } + + void willSave() override { + m_saveCount++; + SkCanvas::willSave(); + } + + void willRestore() override { + DCHECK_GT(m_saveCount, 0); + if (m_clips.size() && m_saveCount == m_clips.back().saveCount) + m_clips.pop_back(); + if (m_alphaSaveLayerCount == m_saveCount) { + m_alpha = 255; + m_alphaSaveLayerCount = -1; + } + m_saveCount--; + SkCanvas::willRestore(); + } + + void onClipRect(const SkRect& rect, + SkClipOp op, + ClipEdgeStyle style) override { + ClipAndIndex clipStruct; + clipStruct.rect = rect; + clipStruct.saveCount = m_saveCount; + m_clips.push_back(clipStruct); + SkCanvas::onClipRect(rect, op, style); + } + + struct ClipAndIndex { + SkRect rect; + int saveCount; + }; + + private: + Vector<QuadWithColor> m_quads; + Vector<ClipAndIndex> m_clips; + int m_saveCount; + unsigned m_alpha; + int m_alphaSaveLayerCount; +}; + +class DrawsRectanglesMatcher : public ::testing::MatcherInterface<const SkPicture&> { public: - DrawsRectangleMatcher(const FloatRect& rect, Color color) - : m_rect(rect), m_color(color) {} + DrawsRectanglesMatcher(const Vector<RectWithColor>& rectsWithColor) + : m_rectsWithColor(rectsWithColor) {} bool MatchAndExplain( const SkPicture& picture, ::testing::MatchResultListener* listener) const override { DrawsRectangleCanvas canvas; picture.playback(&canvas); - const auto& quads = canvas.quads(); - - if (quads.size() != 1) { + const auto& quads = canvas.quadsWithColor(); + if (quads.size() != m_rectsWithColor.size()) { *listener << "which draws " << quads.size() << " quads"; return false; } - const FloatQuad& quad = quads[0].first; - if (!quad.isRectilinear()) { - if (listener->IsInterested()) { - *listener << "which draws "; - PrintTo(quad, listener->stream()); - *listener << " with color " - << quads[0].second.serialized().ascii().data(); + for (unsigned index = 0; index < quads.size(); index++) { + const auto& quadWithColor = quads[index]; + const auto& rectWithColor = m_rectsWithColor[index]; + if (!quadWithColor.quad.isRectilinear()) { + if (listener->IsInterested()) { + *listener << "at index " << index << " which draws "; + PrintTo(quadWithColor.quad, listener->stream()); + *listener << " with color " + << quadWithColor.color.serialized().ascii().data() << "\n"; + } + return false; } - return false; - } - const FloatRect& rect = quad.boundingBox(); - if (rect != m_rect || quads[0].second != m_color) { - if (listener->IsInterested()) { - *listener << "which draws "; - PrintTo(rect, listener->stream()); - *listener << " with color " - << quads[0].second.serialized().ascii().data(); + const FloatRect& rect = quadWithColor.quad.boundingBox(); + if (rect != rectWithColor.rect || + quadWithColor.color != rectWithColor.color) { + if (listener->IsInterested()) { + *listener << "at index " << index << " which draws "; + PrintTo(rect, listener->stream()); + *listener << " with color " + << quadWithColor.color.serialized().ascii().data() << "\n"; + } + return false; } - return false; } return true; } void DescribeTo(::std::ostream* os) const override { - *os << "draws "; - PrintTo(m_rect, os); - *os << " with color " << m_color.serialized().ascii().data(); + *os << "\n"; + for (unsigned index = 0; index < m_rectsWithColor.size(); index++) { + const auto& rectWithColor = m_rectsWithColor[index]; + *os << "at index " << index << " rect draws "; + PrintTo(rectWithColor.rect, os); + *os << " with color " << rectWithColor.color.serialized().ascii().data() + << "\n"; + } } private: - const FloatRect m_rect; - const Color m_color; + const Vector<RectWithColor> m_rectsWithColor; }; } // namespace ::testing::Matcher<const SkPicture&> drawsRectangle(const FloatRect& rect, Color color) { - return ::testing::MakeMatcher(new DrawsRectangleMatcher(rect, color)); + Vector<RectWithColor> rectsWithColor; + rectsWithColor.push_back(RectWithColor(rect, color)); + return ::testing::MakeMatcher(new DrawsRectanglesMatcher(rectsWithColor)); +} + +::testing::Matcher<const SkPicture&> drawsRectangles( + const Vector<RectWithColor>& rectsWithColor) { + return ::testing::MakeMatcher(new DrawsRectanglesMatcher(rectsWithColor)); } } // namespace blink
diff --git a/third_party/WebKit/Source/platform/testing/PictureMatchers.h b/third_party/WebKit/Source/platform/testing/PictureMatchers.h index 01368f2..3f400a9e 100644 --- a/third_party/WebKit/Source/platform/testing/PictureMatchers.h +++ b/third_party/WebKit/Source/platform/testing/PictureMatchers.h
@@ -5,6 +5,7 @@ #ifndef PictureMatchers_h #define PictureMatchers_h +#include "platform/geometry/FloatRect.h" #include "platform/graphics/Color.h" #include "testing/gmock/include/gmock/gmock.h" @@ -15,10 +16,25 @@ class FloatRect; // Matches if the picture draws exactly one rectangle, which (after accounting -// for the total transformation matrix) matches the rect provided, and whose -// paint has the color requested. +// for the total transformation matrix and applying any clips inside that +// transform) matches the rect provided, and whose paint has the color +// requested. +// Note that clips which appear outside of a transform are not currently +// supported. ::testing::Matcher<const SkPicture&> drawsRectangle(const FloatRect&, Color); +struct RectWithColor { + RectWithColor(const FloatRect& rectArg, const Color& colorArg) + : rect(rectArg), color(colorArg) {} + FloatRect rect; + Color color; +}; + +// Same as above, but matches a number of rectangles equal to the size of the +// given vector. +::testing::Matcher<const SkPicture&> drawsRectangles( + const Vector<RectWithColor>&); + } // namespace blink #endif // PictureMatchers_h
diff --git a/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/controllers/test_result_writer.py b/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/controllers/test_result_writer.py index fece01a..fc70eec 100644 --- a/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/controllers/test_result_writer.py +++ b/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/controllers/test_result_writer.py
@@ -29,6 +29,7 @@ import logging +from webkitpy.common.html_diff import html_diff from webkitpy.layout_tests.controllers import repaint_overlay from webkitpy.layout_tests.models import test_failures @@ -113,7 +114,7 @@ FILENAME_SUFFIX_SAMPLE = "-sample" FILENAME_SUFFIX_LEAK_LOG = "-leak-log" FILENAME_SUFFIX_WDIFF = "-wdiff.html" - FILENAME_SUFFIX_PRETTY_PATCH = "-pretty-diff.html" + FILENAME_SUFFIX_HTML_DIFF = "-pretty-diff.html" FILENAME_SUFFIX_IMAGE_DIFF = "-diff.png" FILENAME_SUFFIX_IMAGE_DIFFS_HTML = "-diffs.html" FILENAME_SUFFIX_OVERLAY = "-overlay.html" @@ -199,11 +200,10 @@ if not actual_text or not expected_text: return + # Output a plain-text diff file. file_type = '.txt' actual_filename = self.output_filename(self.FILENAME_SUFFIX_ACTUAL + file_type) expected_filename = self.output_filename(self.FILENAME_SUFFIX_EXPECTED + file_type) - # We treat diff output as binary. Diff output may contain multiple files - # in conflicting encodings. diff = self._port.diff_text(expected_text, actual_text, expected_filename, actual_filename) diff_filename = self.output_filename(self.FILENAME_SUFFIX_DIFF + file_type) self._write_file(diff_filename, diff) @@ -214,11 +214,10 @@ wdiff_filename = self.output_filename(self.FILENAME_SUFFIX_WDIFF) self._write_file(wdiff_filename, wdiff) - # Use WebKit's PrettyPatch.rb to get an HTML diff. - if self._port.pretty_patch_available(): - pretty_patch = self._port.pretty_patch_text(diff_filename) - pretty_patch_filename = self.output_filename(self.FILENAME_SUFFIX_PRETTY_PATCH) - self._write_file(pretty_patch_filename, pretty_patch) + # Output a HTML diff file. + html_diff_filename = self.output_filename(self.FILENAME_SUFFIX_HTML_DIFF) + html_diff_contents = html_diff(expected_text, actual_text) + self._write_file(html_diff_filename, html_diff_contents) def create_repaint_overlay_result(self, actual_text, expected_text): html = repaint_overlay.generate_repaint_overlay_html(self._test_name, actual_text, expected_text)
diff --git a/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/models/test_run_results.py b/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/models/test_run_results.py index 35706f7a..e58a5de 100644 --- a/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/models/test_run_results.py +++ b/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/models/test_run_results.py
@@ -329,7 +329,7 @@ results['interrupted'] = initial_results.interrupted results['layout_tests_dir'] = port_obj.layout_tests_dir() results['has_wdiff'] = port_obj.wdiff_available() - results['has_pretty_patch'] = port_obj.pretty_patch_available() + results['has_pretty_patch'] = True results['pixel_tests_enabled'] = port_obj.get_option('pixel_tests') results['seconds_since_epoch'] = int(time.time()) results['build_number'] = port_obj.get_option('build_number') @@ -337,6 +337,9 @@ if port_obj.get_option('order') == 'random': results['random_order_seed'] = port_obj.get_option('seed') results['path_delimiter'] = '/' + # The pretty-diff.html files should always be available. + # TODO(qyearsley): Change this key since PrettyPatch.rb has been removed. + results['has_pretty_patch'] = True # Don't do this by default since it takes >100ms. # It's only used for rebaselining and uploading data to the flakiness dashboard.
diff --git a/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/run_webkit_tests_unittest.py b/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/run_webkit_tests_unittest.py index 589d20ea..2c60207f 100644 --- a/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/run_webkit_tests_unittest.py +++ b/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/run_webkit_tests_unittest.py
@@ -967,19 +967,19 @@ self.assertNotIn('platform/test-win-win7/http/test.html', tests_run) def test_output_diffs(self): - # Test to ensure that we don't generate -wdiff.html or -pretty.html if wdiff and PrettyPatch - # aren't available. + # Test to ensure that we don't generate -wdiff.html if wdiff isn't available, + # but we always generate -diff.txt an -pretty-diff.html. host = MockHost() logging_run(['--pixel-tests', 'failures/unexpected/text-image-checksum.html'], tests_included=True, host=host) written_files = host.filesystem.written_files - self.assertTrue(any(path.endswith('-diff.txt') for path in written_files.keys())) - self.assertFalse(any(path.endswith('-wdiff.html') for path in written_files.keys())) - self.assertFalse(any(path.endswith('-pretty-diff.html') for path in written_files.keys())) + self.assertTrue(any(path.endswith('-diff.txt') for path in written_files)) + self.assertTrue(any(path.endswith('-pretty-diff.html') for path in written_files)) + self.assertFalse(any(path.endswith('-wdiff.html') for path in written_files)) full_results_text = host.filesystem.read_text_file('/tmp/layout-test-results/full_results.json') full_results = json.loads(full_results_text.replace("ADD_RESULTS(", "").replace(");", "")) self.assertEqual(full_results['has_wdiff'], False) - self.assertEqual(full_results['has_pretty_patch'], False) + self.assertEqual(full_results['has_pretty_patch'], True) def test_unsupported_platform(self): stdout = StringIO.StringIO()
diff --git a/tools/win/pdb_compare_globals.py b/tools/win/pdb_compare_globals.py new file mode 100644 index 0000000..0455a5d2 --- /dev/null +++ b/tools/win/pdb_compare_globals.py
@@ -0,0 +1,92 @@ +# Copyright (c) 2016 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +""" +This script uses ShowGlobals.exe to compare two PDBs to see what interesting +globals are present in one but not the other. You can either pass in a .pdb file +or you can pass in a .txt file that contains the results of calling ShowGlobals. +This helps when investigating size regressions. Often code-size regressions are +associated with global variable changes, and those global variables can be +easier to track and investigate than the code. + +Typical output from ShowGlobals.exe is lines like these: + + #Dups DupSize Size Section Symbol name PDB name + + 0 0 122784 2 kBrotliDictionary chrome.dll.pdb + 1 1824 0 0 LcidToLocaleNameTable chrome.dll.pdb +""" + +import os +import subprocess +import sys + + +def LoadSymbols(pdb_name): + result = {} + extension = os.path.splitext(pdb_name)[1].lower() + if extension in ['.pdb']: + command = 'ShowGlobals.exe "%s"' % pdb_name + lines = subprocess.check_output(command).splitlines() + elif extension in ['.txt']: + lines = open(pdb_name).readlines() + else: + print 'Unrecognized extension in %s' % pdb_name + return result + for line in lines: + parts = line.split('\t') + if len(parts) >= 5 and not line.startswith('#'): + # Put the first four columns (the numerical data associated with a symbol) + # into a dictionary indexed by the fifth column, which is the symbol name. + symbol_name = parts[4] + result[symbol_name] = parts[:4] + return result + + +def ShowExtras(symbols_A, symbols_B, name_A, name_B): + print 'Symbols that are in %s but not in %s' % (name_A, name_B) + for key in symbols_A: + if not key in symbols_B: + # Print all the numerical data, followed by the symbol name, separated by + # tabs. + print '\t'.join(symbols_A[key] + [key]) + print + + +def ShowDifferences(symbols_A, symbols_B, name_A, name_B): + print 'Symbols that are changed from %s to %s' % (name_A, name_B) + for key in symbols_A: + if key in symbols_B: + value_a = symbols_A[key] + value_b = symbols_B[key] + if value_a != value_b: + # Print the symbol name and then the two versions of the numerical data, + # indented. + print '%s changed from/to:' % key + print '\t' + '\t'.join(value_a) + print '\t' + '\t'.join(value_b) + print + + +def main(): + symbols_1 = LoadSymbols(sys.argv[1]) + symbols_2 = LoadSymbols(sys.argv[2]) + + if len(symbols_1) == 0: + print 'No data found in %s - fastlink?' % sys.argv[1] + return + if len(symbols_2) == 0: + print 'No data found in %s - fastlink?' % sys.argv[2] + return + + print ('%d interesting globals in %s, %d interesting globals in %s' % + (len(symbols_1), sys.argv[1], len(symbols_2), sys.argv[2])) + + ShowExtras(symbols_1, symbols_2, sys.argv[1], sys.argv[2]) + ShowExtras(symbols_2, symbols_1, sys.argv[2], sys.argv[1]) + ShowDifferences(symbols_1, symbols_2, sys.argv[1], sys.argv[2]) + + +if __name__ == '__main__': + sys.exit(main())