blob: e1af099212997f638f65f03d6067981f0f5c4560 [file] [log] [blame]
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "third_party/blink/renderer/platform/graphics/identifiability_paint_op_digest.h"
#include "cc/test/skia_common.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/common/privacy_budget/identifiability_study_settings.h"
#include "third_party/blink/public/common/privacy_budget/identifiability_study_settings_provider.h"
#include "third_party/skia/include/core/SkPath.h"
#include "third_party/skia/include/core/SkTileMode.h"
namespace blink {
namespace {
// A IdentifiabilityStudySettingsProvider implementation that opts-into study
// participation.
class ActiveSettingsProvider : public IdentifiabilityStudySettingsProvider {
public:
bool IsActive() const override { return true; }
bool IsAnyTypeOrSurfaceBlocked() const override { return false; }
bool IsSurfaceAllowed(IdentifiableSurface surface) const override {
return true;
}
bool IsTypeAllowed(IdentifiableSurface::Type type) const override {
return true;
}
int SampleRate(IdentifiableSurface surface) const override { return 1; }
int SampleRate(IdentifiableSurface::Type type) const override { return 1; }
};
// An RAII class that opts into study participation using
// ActiveSettingsProvider.
class StudyParticipationRaii {
public:
StudyParticipationRaii() {
IdentifiabilityStudySettings::SetGlobalProvider(
std::make_unique<ActiveSettingsProvider>());
}
~StudyParticipationRaii() {
IdentifiabilityStudySettings::ResetStateForTesting();
}
};
// Arbitrary non-zero size.
constexpr IntSize kSize(10, 10);
constexpr float kScaleX = 1.0f, kScaleY = 1.0f;
constexpr int64_t kScaleDigest = INT64_C(8258647715449129112);
constexpr int64_t kTokenBuilderInitialDigest = INT64_C(6544625333304541877);
TEST(IdentifiabilityPaintOpDigestTest, Construct) {
IdentifiabilityPaintOpDigest identifiability_paintop_digest(kSize);
}
TEST(IdentifiabilityPaintOpDigestTest, Construct_InStudy) {
StudyParticipationRaii study_participation_raii;
IdentifiabilityPaintOpDigest identifiability_paintop_digest(kSize);
}
TEST(IdentifiabilityPaintOpDigestTest, InitialDigest) {
StudyParticipationRaii study_participation_raii;
IdentifiabilityPaintOpDigest identifiability_paintop_digest(kSize);
auto paint_record = sk_make_sp<cc::PaintRecord>();
identifiability_paintop_digest.MaybeUpdateDigest(paint_record,
/*num_ops_to_visit=*/1);
EXPECT_EQ(kTokenBuilderInitialDigest,
identifiability_paintop_digest.GetToken().ToUkmMetricValue());
EXPECT_FALSE(identifiability_paintop_digest.encountered_skipped_ops());
EXPECT_FALSE(
identifiability_paintop_digest.encountered_partially_digested_image());
}
TEST(IdentifiabilityPaintOpDigestTest, SimpleDigest) {
StudyParticipationRaii study_participation_raii;
IdentifiabilityPaintOpDigest identifiability_paintop_digest(kSize);
auto paint_record = sk_make_sp<cc::PaintRecord>();
paint_record->push<cc::ScaleOp>(kScaleX, kScaleY);
identifiability_paintop_digest.MaybeUpdateDigest(paint_record,
/*num_ops_to_visit=*/1);
EXPECT_EQ(kScaleDigest,
identifiability_paintop_digest.GetToken().ToUkmMetricValue());
EXPECT_FALSE(identifiability_paintop_digest.encountered_skipped_ops());
EXPECT_FALSE(
identifiability_paintop_digest.encountered_partially_digested_image());
}
TEST(IdentifiabilityPaintOpDigestTest, DigestIsInitialIfNotInStudy) {
IdentifiabilityPaintOpDigest identifiability_paintop_digest(kSize);
auto paint_record = sk_make_sp<cc::PaintRecord>();
paint_record->push<cc::ScaleOp>(1.0f, 1.0f);
identifiability_paintop_digest.MaybeUpdateDigest(paint_record,
/*num_ops_to_visit=*/1);
EXPECT_EQ(kTokenBuilderInitialDigest,
identifiability_paintop_digest.GetToken().ToUkmMetricValue());
EXPECT_FALSE(identifiability_paintop_digest.encountered_skipped_ops());
EXPECT_FALSE(
identifiability_paintop_digest.encountered_partially_digested_image());
}
TEST(IdentifiabilityPaintOpDigestTest, IgnoresTextOpsContents) {
StudyParticipationRaii study_participation_raii;
IdentifiabilityPaintOpDigest identifiability_paintop_digest1(kSize);
IdentifiabilityPaintOpDigest identifiability_paintop_digest2(kSize);
auto paint_record1 = sk_make_sp<cc::PaintRecord>();
auto paint_record2 = sk_make_sp<cc::PaintRecord>();
paint_record1->push<cc::DrawTextBlobOp>(
SkTextBlob::MakeFromString("abc", SkFont(SkTypeface::MakeDefault())),
1.0f, 1.0f, cc::PaintFlags());
paint_record2->push<cc::DrawTextBlobOp>(
SkTextBlob::MakeFromString("def", SkFont(SkTypeface::MakeDefault())),
2.0f, 2.0f, cc::PaintFlags());
identifiability_paintop_digest1.MaybeUpdateDigest(paint_record1,
/*num_ops_to_visit=*/1);
identifiability_paintop_digest2.MaybeUpdateDigest(paint_record2,
/*num_ops_to_visit=*/1);
EXPECT_EQ(identifiability_paintop_digest1.GetToken().ToUkmMetricValue(),
identifiability_paintop_digest2.GetToken().ToUkmMetricValue());
EXPECT_NE(kTokenBuilderInitialDigest,
identifiability_paintop_digest1.GetToken().ToUkmMetricValue());
EXPECT_EQ(INT64_C(5364951310489041526),
identifiability_paintop_digest1.GetToken().ToUkmMetricValue());
EXPECT_FALSE(identifiability_paintop_digest1.encountered_skipped_ops());
EXPECT_FALSE(
identifiability_paintop_digest1.encountered_partially_digested_image());
EXPECT_FALSE(identifiability_paintop_digest2.encountered_skipped_ops());
EXPECT_FALSE(
identifiability_paintop_digest2.encountered_partially_digested_image());
}
TEST(IdentifiabilityPaintOpDigestTest, RelativeOrderingTextOpsAndOtherOps) {
StudyParticipationRaii study_participation_raii;
IdentifiabilityPaintOpDigest identifiability_paintop_digest1(kSize);
IdentifiabilityPaintOpDigest identifiability_paintop_digest2(kSize);
auto paint_record1 = sk_make_sp<cc::PaintRecord>();
auto paint_record2 = sk_make_sp<cc::PaintRecord>();
paint_record1->push<cc::DrawTextBlobOp>(
SkTextBlob::MakeFromString("abc", SkFont(SkTypeface::MakeDefault())),
1.0f, 1.0f, cc::PaintFlags());
paint_record1->push<cc::ScaleOp>(kScaleX, kScaleY);
paint_record2->push<cc::ScaleOp>(kScaleX, kScaleY);
paint_record2->push<cc::DrawTextBlobOp>(
SkTextBlob::MakeFromString("abc", SkFont(SkTypeface::MakeDefault())),
1.0f, 1.0f, cc::PaintFlags());
identifiability_paintop_digest1.MaybeUpdateDigest(paint_record1,
/*num_ops_to_visit=*/1);
identifiability_paintop_digest2.MaybeUpdateDigest(paint_record2,
/*num_ops_to_visit=*/1);
EXPECT_NE(identifiability_paintop_digest1.GetToken().ToUkmMetricValue(),
identifiability_paintop_digest2.GetToken().ToUkmMetricValue());
EXPECT_FALSE(identifiability_paintop_digest1.encountered_skipped_ops());
EXPECT_FALSE(
identifiability_paintop_digest1.encountered_partially_digested_image());
EXPECT_FALSE(identifiability_paintop_digest2.encountered_skipped_ops());
EXPECT_FALSE(
identifiability_paintop_digest2.encountered_partially_digested_image());
}
TEST(IdentifiabilityPaintOpDigestTest, SkPathDigestStability) {
StudyParticipationRaii study_participation_raii;
// These 2 SkPath objects will have different internal generation IDs. We
// can't use empty paths as in that case the internal SkPathRefs (which holds
// the ID) for each SkPath would be the same global "empty" SkPathRef, so we
// make the SkPaths non-empty.
SkPath path1;
path1.rLineTo(1.0f, 0.0f);
SkPath path2;
path2.rLineTo(1.0f, 0.0f);
IdentifiabilityPaintOpDigest identifiability_paintop_digest1(kSize);
IdentifiabilityPaintOpDigest identifiability_paintop_digest2(kSize);
auto paint_record1 = sk_make_sp<cc::PaintRecord>();
auto paint_record2 = sk_make_sp<cc::PaintRecord>();
paint_record1->push<cc::DrawPathOp>(path1, cc::PaintFlags());
paint_record2->push<cc::DrawPathOp>(path2, cc::PaintFlags());
identifiability_paintop_digest1.MaybeUpdateDigest(paint_record1,
/*num_ops_to_visit=*/1);
identifiability_paintop_digest2.MaybeUpdateDigest(paint_record2,
/*num_ops_to_visit=*/1);
EXPECT_EQ(identifiability_paintop_digest1.GetToken().ToUkmMetricValue(),
identifiability_paintop_digest2.GetToken().ToUkmMetricValue());
EXPECT_EQ(INT64_C(-1093634256342000670),
identifiability_paintop_digest1.GetToken().ToUkmMetricValue());
EXPECT_FALSE(identifiability_paintop_digest1.encountered_skipped_ops());
EXPECT_FALSE(
identifiability_paintop_digest1.encountered_partially_digested_image());
EXPECT_FALSE(identifiability_paintop_digest2.encountered_skipped_ops());
EXPECT_FALSE(
identifiability_paintop_digest2.encountered_partially_digested_image());
}
TEST(IdentifiabilityPaintOpDigestTest, PaintShaderStability) {
StudyParticipationRaii study_participation_raii;
SkPath path;
path.rLineTo(1.0f, 0.0f);
IdentifiabilityPaintOpDigest identifiability_paintop_digest1(kSize);
IdentifiabilityPaintOpDigest identifiability_paintop_digest2(kSize);
auto paint_record1 = sk_make_sp<cc::PaintRecord>();
auto paint_record2 = sk_make_sp<cc::PaintRecord>();
auto paint_record_shader = sk_make_sp<cc::PaintRecord>();
paint_record_shader->push<cc::ScaleOp>(2.0f, 2.0f);
const SkRect tile = SkRect::MakeWH(100, 100);
// These 2 shaders will have different internal generation IDs.
cc::PaintFlags paint_flags1;
paint_flags1.setShader(cc::PaintShader::MakePaintRecord(
paint_record_shader, tile, SkTileMode::kClamp, SkTileMode::kClamp,
nullptr));
cc::PaintFlags paint_flags2;
paint_flags2.setShader(cc::PaintShader::MakePaintRecord(
paint_record_shader, tile, SkTileMode::kClamp, SkTileMode::kClamp,
nullptr));
paint_record1->push<cc::DrawPathOp>(path, paint_flags1);
paint_record2->push<cc::DrawPathOp>(path, paint_flags2);
identifiability_paintop_digest1.MaybeUpdateDigest(paint_record1,
/*num_ops_to_visit=*/1);
identifiability_paintop_digest2.MaybeUpdateDigest(paint_record2,
/*num_ops_to_visit=*/1);
EXPECT_EQ(identifiability_paintop_digest1.GetToken().ToUkmMetricValue(),
identifiability_paintop_digest2.GetToken().ToUkmMetricValue());
EXPECT_EQ(INT64_C(6157094048912696853),
identifiability_paintop_digest1.GetToken().ToUkmMetricValue());
}
TEST(IdentifiabilityPaintOpDigestTest, BufferLeftoversDontAffectFutureDigests) {
StudyParticipationRaii study_participation_raii;
// Make a complex path to make a PaintOp with a large serialization.
SkPath path;
path.rLineTo(1.0f, 0.0f);
path.rLineTo(1.0f, 1.0f);
path.rLineTo(0.0f, 1.0f);
path.rLineTo(2.0f, 0.0f);
path.rLineTo(2.0f, 2.0f);
path.rLineTo(0.0f, 2.0f);
path.rLineTo(1.0f, 0.0f);
IdentifiabilityPaintOpDigest identifiability_paintop_digest1(kSize);
IdentifiabilityPaintOpDigest identifiability_paintop_digest2(kSize);
auto paint_record1 = sk_make_sp<cc::PaintRecord>();
auto paint_record2 = sk_make_sp<cc::PaintRecord>();
paint_record1->push<cc::DrawPathOp>(path, cc::PaintFlags());
paint_record2->push<cc::ScaleOp>(kScaleX, kScaleY);
identifiability_paintop_digest1.MaybeUpdateDigest(paint_record1,
/*num_ops_to_visit=*/1);
identifiability_paintop_digest2.MaybeUpdateDigest(paint_record2,
/*num_ops_to_visit=*/1);
EXPECT_EQ(INT64_C(-8958477480441775589),
identifiability_paintop_digest1.GetToken().ToUkmMetricValue());
EXPECT_EQ(kScaleDigest,
identifiability_paintop_digest2.GetToken().ToUkmMetricValue());
EXPECT_FALSE(identifiability_paintop_digest1.encountered_skipped_ops());
EXPECT_FALSE(
identifiability_paintop_digest1.encountered_partially_digested_image());
EXPECT_FALSE(identifiability_paintop_digest2.encountered_skipped_ops());
EXPECT_FALSE(
identifiability_paintop_digest2.encountered_partially_digested_image());
}
TEST(IdentifiabilityPaintOpDigestTest,
BufferLeftoversDontAffectFutureDigests_SameCanvas) {
StudyParticipationRaii study_participation_raii;
// Make a complex path to make a PaintOp with a large serialization.
SkPath path;
path.rLineTo(1.0f, 0.0f);
path.rLineTo(1.0f, 1.0f);
path.rLineTo(0.0f, 1.0f);
path.rLineTo(2.0f, 0.0f);
path.rLineTo(2.0f, 2.0f);
path.rLineTo(0.0f, 2.0f);
path.rLineTo(1.0f, 0.0f);
IdentifiabilityPaintOpDigest identifiability_paintop_digest(kSize);
auto paint_record = sk_make_sp<cc::PaintRecord>();
paint_record->push<cc::DrawPathOp>(path, cc::PaintFlags());
paint_record->push<cc::ScaleOp>(kScaleX, kScaleY);
identifiability_paintop_digest.MaybeUpdateDigest(paint_record,
/*num_ops_to_visit=*/2);
EXPECT_EQ(INT64_C(4130836803240885894),
identifiability_paintop_digest.GetToken().ToUkmMetricValue());
EXPECT_FALSE(identifiability_paintop_digest.encountered_skipped_ops());
EXPECT_FALSE(
identifiability_paintop_digest.encountered_partially_digested_image());
}
TEST(IdentifiabilityPaintOpDigestTest, IgnoresPrefixAndSuffix) {
StudyParticipationRaii study_participation_raii;
SkPath path1;
path1.rLineTo(1.0f, 0.0f);
SkPath path2;
path2.rLineTo(0.0f, 1.0f);
IdentifiabilityPaintOpDigest identifiability_paintop_digest(kSize);
auto paint_record = sk_make_sp<cc::PaintRecord>();
paint_record->push<cc::DrawPathOp>(path1, cc::PaintFlags());
paint_record->push<cc::ScaleOp>(kScaleX, kScaleY);
paint_record->push<cc::DrawPathOp>(path2, cc::PaintFlags());
identifiability_paintop_digest.SetPrefixSkipCount(1);
identifiability_paintop_digest.MaybeUpdateDigest(paint_record,
/*num_ops_to_visit=*/2);
EXPECT_EQ(kScaleDigest,
identifiability_paintop_digest.GetToken().ToUkmMetricValue());
EXPECT_FALSE(identifiability_paintop_digest.encountered_skipped_ops());
EXPECT_FALSE(
identifiability_paintop_digest.encountered_partially_digested_image());
}
TEST(IdentifiabilityPaintOpDigestTest, IgnoresPrefixAndSuffix_MultipleOps) {
StudyParticipationRaii study_participation_raii;
SkPath path1;
path1.rLineTo(1.0f, 0.0f);
SkPath path2;
path2.rLineTo(0.0f, 1.0f);
SkPath path3;
path3.rLineTo(1.0f, 1.0f);
IdentifiabilityPaintOpDigest identifiability_paintop_digest(kSize);
auto paint_record = sk_make_sp<cc::PaintRecord>();
paint_record->push<cc::DrawPathOp>(path1, cc::PaintFlags());
paint_record->push<cc::ScaleOp>(kScaleX, kScaleY);
paint_record->push<cc::DrawPathOp>(path2, cc::PaintFlags());
paint_record->push<cc::DrawPathOp>(path3, cc::PaintFlags());
identifiability_paintop_digest.SetPrefixSkipCount(1);
identifiability_paintop_digest.MaybeUpdateDigest(paint_record,
/*num_ops_to_visit=*/2);
EXPECT_EQ(INT64_C(8258647715449129112),
identifiability_paintop_digest.GetToken().ToUkmMetricValue());
EXPECT_FALSE(identifiability_paintop_digest.encountered_skipped_ops());
EXPECT_FALSE(
identifiability_paintop_digest.encountered_partially_digested_image());
}
TEST(IdentifiabilityPaintOpDigestTest, RecursesIntoDrawRecords) {
StudyParticipationRaii study_participation_raii;
IdentifiabilityPaintOpDigest identifiability_paintop_digest(kSize);
auto paint_record_inner = sk_make_sp<cc::PaintRecord>();
auto paint_record_outer = sk_make_sp<cc::PaintRecord>();
paint_record_inner->push<cc::ScaleOp>(kScaleX, kScaleY);
paint_record_outer->push<cc::DrawRecordOp>(paint_record_inner);
identifiability_paintop_digest.MaybeUpdateDigest(paint_record_outer,
/*num_ops_to_visit=*/1);
EXPECT_EQ(kScaleDigest,
identifiability_paintop_digest.GetToken().ToUkmMetricValue());
EXPECT_FALSE(identifiability_paintop_digest.encountered_skipped_ops());
EXPECT_FALSE(
identifiability_paintop_digest.encountered_partially_digested_image());
}
TEST(IdentifiabilityPaintOpDigestTest, RecursesIntoDrawRecords_TwoLevels) {
StudyParticipationRaii study_participation_raii;
IdentifiabilityPaintOpDigest identifiability_paintop_digest(kSize);
auto paint_record_inner = sk_make_sp<cc::PaintRecord>();
auto paint_record_middle = sk_make_sp<cc::PaintRecord>();
auto paint_record_outer = sk_make_sp<cc::PaintRecord>();
paint_record_inner->push<cc::ScaleOp>(kScaleX, kScaleY);
paint_record_middle->push<cc::DrawRecordOp>(paint_record_inner);
paint_record_outer->push<cc::DrawRecordOp>(paint_record_middle);
identifiability_paintop_digest.MaybeUpdateDigest(paint_record_outer,
/*num_ops_to_visit=*/1);
EXPECT_EQ(kScaleDigest,
identifiability_paintop_digest.GetToken().ToUkmMetricValue());
EXPECT_FALSE(identifiability_paintop_digest.encountered_skipped_ops());
EXPECT_FALSE(
identifiability_paintop_digest.encountered_partially_digested_image());
}
TEST(IdentifiabilityPaintOpDigestTest, StopsUpdatingDigestAfterThreshold) {
StudyParticipationRaii study_participation_raii;
constexpr int kMaxOperations = 5;
IdentifiabilityPaintOpDigest identifiability_paintop_digest(kSize,
kMaxOperations);
int64_t last_digest = INT64_C(0);
for (int i = 0; i < kMaxOperations; i++) {
auto paint_record = sk_make_sp<cc::PaintRecord>();
paint_record->push<cc::ScaleOp>(kScaleX, kScaleY);
identifiability_paintop_digest.MaybeUpdateDigest(paint_record,
/*num_ops_to_visit=*/1);
EXPECT_NE(last_digest,
identifiability_paintop_digest.GetToken().ToUkmMetricValue())
<< i;
last_digest = identifiability_paintop_digest.GetToken().ToUkmMetricValue();
}
auto paint_record = sk_make_sp<cc::PaintRecord>();
paint_record->push<cc::ScaleOp>(kScaleX, kScaleY);
identifiability_paintop_digest.MaybeUpdateDigest(paint_record,
/*num_ops_to_visit=*/1);
EXPECT_EQ(last_digest,
identifiability_paintop_digest.GetToken().ToUkmMetricValue());
EXPECT_TRUE(identifiability_paintop_digest.encountered_skipped_ops());
EXPECT_FALSE(
identifiability_paintop_digest.encountered_partially_digested_image());
}
TEST(IdentifiabilityPaintOpDigestTest, MassiveOpSkipped) {
StudyParticipationRaii study_participation_raii;
IdentifiabilityPaintOpDigest identifiability_paintop_digest(kSize);
SkPath path;
// Build a massive PaintOp.
constexpr size_t kMaxIterations = 1 << 22;
for (size_t i = 0; i < kMaxIterations; i++)
path.rLineTo(1.0f, 1.0f);
auto paint_record = sk_make_sp<cc::PaintRecord>();
paint_record->push<cc::DrawPathOp>(path, cc::PaintFlags());
identifiability_paintop_digest.MaybeUpdateDigest(paint_record,
/*num_ops_to_visit=*/1);
EXPECT_EQ(kTokenBuilderInitialDigest,
identifiability_paintop_digest.GetToken().ToUkmMetricValue());
EXPECT_TRUE(identifiability_paintop_digest.encountered_skipped_ops());
EXPECT_FALSE(
identifiability_paintop_digest.encountered_partially_digested_image());
}
TEST(IdentifiabilityPaintOpDigestTest, DigestImageOp) {
StudyParticipationRaii study_participation_raii;
IdentifiabilityPaintOpDigest identifiability_paintop_digest(kSize);
auto paint_record = sk_make_sp<cc::PaintRecord>();
paint_record->push<cc::DrawImageOp>(
cc::CreateDiscardablePaintImage(gfx::Size(10, 10)), 10.0f, 10.0f);
identifiability_paintop_digest.MaybeUpdateDigest(paint_record,
/*num_ops_to_visit=*/1);
EXPECT_EQ(INT64_C(-3447989221783743109),
identifiability_paintop_digest.GetToken().ToUkmMetricValue());
EXPECT_FALSE(identifiability_paintop_digest.encountered_skipped_ops());
EXPECT_TRUE(
identifiability_paintop_digest.encountered_partially_digested_image());
}
} // namespace
} // namespace blink