diff options
author | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2020-10-12 14:27:29 +0200 |
---|---|---|
committer | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2020-10-13 09:35:20 +0000 |
commit | c30a6232df03e1efbd9f3b226777b07e087a1122 (patch) | |
tree | e992f45784689f373bcc38d1b79a239ebe17ee23 /chromium/ui/gfx | |
parent | 7b5b123ac58f58ffde0f4f6e488bcd09aa4decd3 (diff) | |
download | qtwebengine-chromium-c30a6232df03e1efbd9f3b226777b07e087a1122.tar.gz |
BASELINE: Update Chromium to 85.0.4183.14085-based
Change-Id: Iaa42f4680837c57725b1344f108c0196741f6057
Reviewed-by: Allan Sandfeld Jensen <allan.jensen@qt.io>
Diffstat (limited to 'chromium/ui/gfx')
91 files changed, 3067 insertions, 1449 deletions
diff --git a/chromium/ui/gfx/BUILD.gn b/chromium/ui/gfx/BUILD.gn index 31f3b93e51b..86e50da4cb0 100644 --- a/chromium/ui/gfx/BUILD.gn +++ b/chromium/ui/gfx/BUILD.gn @@ -127,6 +127,7 @@ jumbo_component("gfx") { "shadow_value.h", "skbitmap_operations.cc", "skbitmap_operations.h", + "swap_result.cc", "sys_color_change_listener.cc", "sys_color_change_listener.h", "text_constants.h", @@ -170,8 +171,6 @@ jumbo_component("gfx") { "image/image_skia_util_mac.h", "image/image_skia_util_mac.mm", "image/image_util_mac.mm", - "mac/cocoa_scrollbar_painter.cc", - "mac/cocoa_scrollbar_painter.h", "mac/coordinate_conversion.h", "mac/coordinate_conversion.mm", "mac/nswindow_frame_controls.h", diff --git a/chromium/ui/gfx/animation/animation.cc b/chromium/ui/gfx/animation/animation.cc index b216afb5cf2..0b1d4f22cc5 100644 --- a/chromium/ui/gfx/animation/animation.cc +++ b/chromium/ui/gfx/animation/animation.cc @@ -26,8 +26,7 @@ base::Optional<bool> Animation::prefers_reduced_motion_; Animation::Animation(base::TimeDelta timer_interval) : timer_interval_(timer_interval), is_animating_(false), - delegate_(NULL) { -} + delegate_(nullptr) {} Animation::~Animation() { // Don't send out notification from the destructor. Chances are the delegate diff --git a/chromium/ui/gfx/animation/animation_delegate_notifier.h b/chromium/ui/gfx/animation/animation_delegate_notifier.h index 6121c7c8d58..7983a70fc39 100644 --- a/chromium/ui/gfx/animation/animation_delegate_notifier.h +++ b/chromium/ui/gfx/animation/animation_delegate_notifier.h @@ -5,7 +5,7 @@ #ifndef UI_GFX_ANIMATION_ANIMATION_DELEGATE_NOTIFIER_H_ #define UI_GFX_ANIMATION_ANIMATION_DELEGATE_NOTIFIER_H_ -#include "base/logging.h" +#include "base/check.h" #include "ui/gfx/animation/animation_delegate.h" namespace gfx { diff --git a/chromium/ui/gfx/break_list.h b/chromium/ui/gfx/break_list.h index cb3de2b76de..19c947f5afa 100644 --- a/chromium/ui/gfx/break_list.h +++ b/chromium/ui/gfx/break_list.h @@ -10,7 +10,7 @@ #include <utility> #include <vector> -#include "base/logging.h" +#include "base/check_op.h" #include "ui/gfx/range/range.h" namespace gfx { diff --git a/chromium/ui/gfx/codec/jpeg_codec.cc b/chromium/ui/gfx/codec/jpeg_codec.cc index ea7c0dcab69..d1fbdd7f562 100644 --- a/chromium/ui/gfx/codec/jpeg_codec.cc +++ b/chromium/ui/gfx/codec/jpeg_codec.cc @@ -7,6 +7,7 @@ #include <setjmp.h> #include <memory> +#include <ostream> #include "base/notreached.h" #include "third_party/skia/include/core/SkBitmap.h" diff --git a/chromium/ui/gfx/codec/png_codec.cc b/chromium/ui/gfx/codec/png_codec.cc index f2176e92024..bcaaaa38f36 100644 --- a/chromium/ui/gfx/codec/png_codec.cc +++ b/chromium/ui/gfx/codec/png_codec.cc @@ -8,6 +8,7 @@ #include "base/logging.h" #include "base/macros.h" +#include "base/notreached.h" #include "base/strings/string_util.h" #include "third_party/libpng/png.h" #include "third_party/skia/include/core/SkBitmap.h" @@ -39,13 +40,12 @@ class PngDecoderState { PngDecoderState(PNGCodec::ColorFormat ofmt, std::vector<unsigned char>* o) : output_format(ofmt), output_channels(0), - bitmap(NULL), + bitmap(nullptr), is_opaque(true), output(o), width(0), height(0), - done(false) { - } + done(false) {} // Output is an SkBitmap. explicit PngDecoderState(SkBitmap* skbitmap) @@ -53,11 +53,10 @@ class PngDecoderState { output_channels(0), bitmap(skbitmap), is_opaque(true), - output(NULL), + output(nullptr), width(0), height(0), - done(false) { - } + done(false) {} PNGCodec::ColorFormat output_format; int output_channels; diff --git a/chromium/ui/gfx/codec/vector_wstream.h b/chromium/ui/gfx/codec/vector_wstream.h index 5a20c470e1a..56f9712a6d6 100644 --- a/chromium/ui/gfx/codec/vector_wstream.h +++ b/chromium/ui/gfx/codec/vector_wstream.h @@ -9,7 +9,7 @@ #include <vector> -#include "base/logging.h" +#include "base/check_op.h" #include "third_party/skia/include/core/SkStream.h" namespace gfx { diff --git a/chromium/ui/gfx/color_palette.h b/chromium/ui/gfx/color_palette.h index 127ae855d5c..8cdcba3187c 100644 --- a/chromium/ui/gfx/color_palette.h +++ b/chromium/ui/gfx/color_palette.h @@ -29,7 +29,7 @@ constexpr SkColor kGoogleBlue900 = SkColorSetRGB(0x17, 0x4E, 0xA6); constexpr SkColor kGoogleBlueDark400 = SkColorSetRGB(0x6B, 0xA5, 0xED); constexpr SkColor kGoogleBlueDark600 = SkColorSetRGB(0x25, 0x81, 0xDF); -constexpr SkColor kGoogleRed050 = SkColorSetRGB(0xFC, 0x8E, 0xE6); +constexpr SkColor kGoogleRed050 = SkColorSetRGB(0xFC, 0xE8, 0xE6); constexpr SkColor kGoogleRed100 = SkColorSetRGB(0xFA, 0xD2, 0xCF); constexpr SkColor kGoogleRed200 = SkColorSetRGB(0xF6, 0xAE, 0xA9); constexpr SkColor kGoogleRed300 = SkColorSetRGB(0xF2, 0x8B, 0x82); diff --git a/chromium/ui/gfx/color_space.cc b/chromium/ui/gfx/color_space.cc index dab693d3d0a..1a4c33cd1b9 100644 --- a/chromium/ui/gfx/color_space.cc +++ b/chromium/ui/gfx/color_space.cc @@ -630,7 +630,7 @@ sk_sp<SkColorSpace> ColorSpace::ToSkColorSpace() const { gamut = SkNamedGamut::kAdobeRGB; break; case PrimaryID::SMPTEST432_1: - gamut = SkNamedGamut::kDCIP3; + gamut = SkNamedGamut::kDisplayP3; break; case PrimaryID::BT2020: gamut = SkNamedGamut::kRec2020; @@ -893,7 +893,7 @@ bool ColorSpace::GetTransferFunction(TransferID transfer, // software uses the sRGB transfer function. // * User studies shows that users don't really care. // * Apple's CoreVideo uses gamma=1.961. - // Bearing all of that in mind, use the same transfer funciton as sRGB, + // Bearing all of that in mind, use the same transfer function as sRGB, // which will allow more optimization, and will more closely match other // media players. case ColorSpace::TransferID::IEC61966_2_1: @@ -908,8 +908,7 @@ bool ColorSpace::GetTransferFunction(TransferID transfer, fn->g = 1.961000000000f; return true; case ColorSpace::TransferID::SMPTEST428_1: - fn->a = 0.225615407568f; - fn->e = -1.091041666667f; + fn->a = 1.034080527699f; // (52.37 / 48.0) ^ (1.0 / 2.6) per ITU-T H.273. fn->g = 2.600000000000f; return true; case ColorSpace::TransferID::IEC61966_2_4: @@ -1021,9 +1020,9 @@ void ColorSpace::GetTransferMatrix(SkMatrix44* matrix) const { case ColorSpace::MatrixID::YCOCG: { float data[16] = { - 0.25f, 0.5f, 0.25f, 0.5f, // Y + 0.25f, 0.5f, 0.25f, 0.0f, // Y -0.25f, 0.5f, -0.25f, 0.5f, // Cg - 0.5f, 0.0f, -0.5f, 0.0f, // Co + 0.5f, 0.0f, -0.5f, 0.5f, // Co 0.0f, 0.0f, 0.0f, 1.0f }; matrix->setRowMajorf(data); @@ -1081,7 +1080,8 @@ void ColorSpace::GetTransferMatrix(SkMatrix44* matrix) const { matrix->setRowMajorf(data); } -void ColorSpace::GetRangeAdjustMatrix(SkMatrix44* matrix) const { +void ColorSpace::GetRangeAdjustMatrix(int bit_depth, SkMatrix44* matrix) const { + DCHECK_GE(bit_depth, 8); switch (range_) { case RangeID::FULL: case RangeID::INVALID: @@ -1093,33 +1093,21 @@ void ColorSpace::GetRangeAdjustMatrix(SkMatrix44* matrix) const { break; } - // Note: The values below assume an 8-bit range and aren't entirely correct - // for higher bit depths. They are close enough though (with a relative error - // of ~2.9% for 10-bit and ~3.7% for 12-bit) that it's not worth adding a - // |bit_depth| field to gfx::ColorSpace yet. - // - // The limited ranges are [64,940] and [256, 3760] for 10 and 12 bit content - // respectively. So the final values end up being: - // - // 16 / 255 = 0.06274509803921569 - // 64 / 1023 = 0.06256109481915934 - // 256 / 4095 = 0.06251526251526252 - // - // 235 / 255 = 0.9215686274509803 - // 940 / 1023 = 0.9188660801564027 - // 3760 / 4095 = 0.9181929181929182 - // - // Relative error (same for min/max): - // 10 bit: abs(16/235 - 64/1023)/(64/1023) = 0.0029411764705882222 - // 12 bit: abs(16/235 - 256/4095)/(256/4095) = 0.003676470588235281 + // See ITU-T H.273 (2016), Section 8.3. The following is derived from + // Equations 20-31. + const int shift = bit_depth - 8; + const float a_y = 219 << shift; + const float c = (1 << bit_depth) - 1; + const float scale_y = c / a_y; switch (matrix_) { case MatrixID::RGB: case MatrixID::GBR: case MatrixID::INVALID: - case MatrixID::YCOCG: - matrix->setScale(255.0f/219.0f, 255.0f/219.0f, 255.0f/219.0f); - matrix->postTranslate(-16.0f/219.0f, -16.0f/219.0f, -16.0f/219.0f); + case MatrixID::YCOCG: { + matrix->setScale(scale_y, scale_y, scale_y); + matrix->postTranslate(-16.0f / 219.0f, -16.0f / 219.0f, -16.0f / 219.0f); break; + } case MatrixID::BT709: case MatrixID::FCC: @@ -1128,18 +1116,23 @@ void ColorSpace::GetRangeAdjustMatrix(SkMatrix44* matrix) const { case MatrixID::SMPTE240M: case MatrixID::BT2020_NCL: case MatrixID::BT2020_CL: - case MatrixID::YDZDX: - matrix->setScale(255.0f/219.0f, 255.0f/224.0f, 255.0f/224.0f); - matrix->postTranslate(-16.0f/219.0f, -15.5f/224.0f, -15.5f/224.0f); + case MatrixID::YDZDX: { + const float a_uv = 224 << shift; + const float scale_uv = c / a_uv; + const float translate_uv = (a_uv - c) / (2.0f * a_uv); + matrix->setScale(scale_y, scale_uv, scale_uv); + matrix->postTranslate(-16.0f / 219.0f, translate_uv, translate_uv); break; + } } } bool ColorSpace::ToSkYUVColorSpace(SkYUVColorSpace* out) const { if (range_ == RangeID::FULL) { - // TODO(dalecurtis): This is probably not right for BT.2020. - *out = kJPEG_SkYUVColorSpace; - return true; + if (matrix_ == MatrixID::BT470BG || matrix_ == MatrixID::SMPTE170M) { + *out = kJPEG_SkYUVColorSpace; + return true; + } } switch (matrix_) { case MatrixID::BT709: @@ -1148,7 +1141,6 @@ bool ColorSpace::ToSkYUVColorSpace(SkYUVColorSpace* out) const { case MatrixID::BT470BG: case MatrixID::SMPTE170M: - case MatrixID::SMPTE240M: *out = kRec601_SkYUVColorSpace; return true; diff --git a/chromium/ui/gfx/color_space.h b/chromium/ui/gfx/color_space.h index 6c123f494e8..425f88769c2 100644 --- a/chromium/ui/gfx/color_space.h +++ b/chromium/ui/gfx/color_space.h @@ -305,7 +305,35 @@ class COLOR_SPACE_EXPORT ColorSpace { // For most formats, this is the RGB to YUV matrix. void GetTransferMatrix(SkMatrix44* matrix) const; - void GetRangeAdjustMatrix(SkMatrix44* matrix) const; + + // Returns the range adjust matrix that converts from |range_| to full range + // for |bit_depth|. + void GetRangeAdjustMatrix(int bit_depth, SkMatrix44* matrix) const; + + // Returns the range adjust matrix that converts from |range_| to full range + // for bit depth 8. + // + // WARNING: The returned matrix assumes an 8-bit range and isn't entirely + // correct for higher bit depths, with a relative error of ~2.9% for 10-bit + // and ~3.7% for 12-bit. Use the above GetRangeAdjustMatrix() method instead. + // + // The limited ranges are [64,940] and [256, 3760] for 10 and 12 bit content + // respectively. So the final values end up being: + // + // 16 / 255 = 0.06274509803921569 + // 64 / 1023 = 0.06256109481915934 + // 256 / 4095 = 0.06251526251526252 + // + // 235 / 255 = 0.9215686274509803 + // 940 / 1023 = 0.9188660801564027 + // 3760 / 4095 = 0.9181929181929182 + // + // Relative error (same for min/max): + // 10 bit: abs(16/235 - 64/1023)/(64/1023) = 0.0029411764705882222 + // 12 bit: abs(16/235 - 256/4095)/(256/4095) = 0.003676470588235281 + void GetRangeAdjustMatrix(SkMatrix44* matrix) const { + GetRangeAdjustMatrix(kDefaultBitDepth, matrix); + } // Returns the current primary ID. // Note: if SetCustomPrimaries() has been used, the primary ID returned @@ -327,6 +355,9 @@ class COLOR_SPACE_EXPORT ColorSpace { bool HasExtendedSkTransferFn() const; private: + // The default bit depth assumed by GetRangeAdjustMatrix(). + static constexpr int kDefaultBitDepth = 8; + static void GetPrimaryMatrix(PrimaryID, skcms_Matrix3x3* to_XYZD50); static bool GetTransferFunction(TransferID, skcms_TransferFunction* fn); static size_t TransferParamCount(TransferID); diff --git a/chromium/ui/gfx/color_space_unittest.cc b/chromium/ui/gfx/color_space_unittest.cc index b73880861bc..b790f8f834d 100644 --- a/chromium/ui/gfx/color_space_unittest.cc +++ b/chromium/ui/gfx/color_space_unittest.cc @@ -81,6 +81,104 @@ TEST(ColorSpace, RGBToYUV) { } } +TEST(ColorSpace, RangeAdjust) { + const size_t kNumTestYUVs = 2; + SkVector4 test_yuvs[kNumTestYUVs] = { + SkVector4(1.f, 1.f, 1.f, 1.f), + SkVector4(0.f, 0.f, 0.f, 1.f), + }; + + const size_t kNumBitDepths = 3; + int bit_depths[kNumBitDepths] = {8, 10, 12}; + + const size_t kNumColorSpaces = 3; + ColorSpace color_spaces[kNumColorSpaces] = { + ColorSpace::CreateREC601(), + ColorSpace::CreateJpeg(), + ColorSpace(ColorSpace::PrimaryID::INVALID, + ColorSpace::TransferID::INVALID, ColorSpace::MatrixID::YCOCG, + ColorSpace::RangeID::LIMITED), + }; + + SkVector4 expected_yuvs[kNumColorSpaces][kNumBitDepths][kNumTestYUVs] = { + // REC601 + { + // 8bpc + { + SkVector4(235.f / 255.f, 239.5f / 255.f, 239.5f / 255.f, 1.0000f), + SkVector4(16.f / 255.f, 15.5f / 255.f, 15.5f / 255.f, 1.0000f), + }, + // 10bpc + { + SkVector4(940.f / 1023.f, 959.5f / 1023.f, 959.5f / 1023.f, + 1.0000f), + SkVector4(64.f / 1023.f, 63.5f / 1023.f, 63.5f / 1023.f, 1.0000f), + }, + // 12bpc + { + SkVector4(3760.f / 4095.f, 3839.5f / 4095.f, 3839.5f / 4095.f, + 1.0000f), + SkVector4(256.f / 4095.f, 255.5f / 4095.f, 255.5f / 4095.f, + 1.0000f), + }, + }, + // Jpeg + { + // 8bpc + { + SkVector4(1.0000f, 1.0000f, 1.0000f, 1.0000f), + SkVector4(0.0000f, 0.0000f, 0.0000f, 1.0000f), + }, + // 10bpc + { + SkVector4(1.0000f, 1.0000f, 1.0000f, 1.0000f), + SkVector4(0.0000f, 0.0000f, 0.0000f, 1.0000f), + }, + // 12bpc + { + SkVector4(1.0000f, 1.0000f, 1.0000f, 1.0000f), + SkVector4(0.0000f, 0.0000f, 0.0000f, 1.0000f), + }, + }, + // YCoCg + { + // 8bpc + { + SkVector4(235.f / 255.f, 235.f / 255.f, 235.f / 255.f, 1.0000f), + SkVector4(16.f / 255.f, 16.f / 255.f, 16.f / 255.f, 1.0000f), + }, + // 10bpc + { + SkVector4(940.f / 1023.f, 940.f / 1023.f, 940.f / 1023.f, + 1.0000f), + SkVector4(64.f / 1023.f, 64.f / 1023.f, 64.f / 1023.f, 1.0000f), + }, + // 12bpc + { + SkVector4(3760.f / 4095.f, 3760.f / 4095.f, 3760.f / 4095.f, + 1.0000f), + SkVector4(256.f / 4095.f, 256.f / 4095.f, 256.f / 4095.f, + 1.0000f), + }, + }, + }; + + for (size_t i = 0; i < kNumColorSpaces; ++i) { + for (size_t j = 0; j < kNumBitDepths; ++j) { + SkMatrix44 range_adjust; + color_spaces[i].GetRangeAdjustMatrix(bit_depths[j], &range_adjust); + + SkMatrix44 range_adjust_inv; + range_adjust.invert(&range_adjust_inv); + + for (size_t k = 0; k < kNumTestYUVs; ++k) { + SkVector4 yuv = range_adjust_inv * test_yuvs[k]; + EXPECT_LT(Diff(yuv, expected_yuvs[i][j][k]), kEpsilon); + } + } + } +} + TEST(ColorSpace, RasterAndBlend) { ColorSpace display_color_space; @@ -120,7 +218,8 @@ TEST(ColorSpace, ConversionToAndFromSkColorSpace) { sk_sp<SkColorSpace> sk_color_spaces[kNumTests] = { SkColorSpace::MakeSRGB(), SkColorSpace::MakeRGB(SkNamedTransferFn::kSRGB, SkNamedGamut::kAdobeRGB), - SkColorSpace::MakeRGB(SkNamedTransferFn::kLinear, SkNamedGamut::kDCIP3), + SkColorSpace::MakeRGB(SkNamedTransferFn::kLinear, + SkNamedGamut::kDisplayP3), SkColorSpace::MakeRGB(SkNamedTransferFn::kSRGB, SkNamedGamut::kRec2020), SkColorSpace::MakeRGB(transfer_fn, primary_matrix), }; diff --git a/chromium/ui/gfx/color_transform.cc b/chromium/ui/gfx/color_transform.cc index c99c4b506cb..981d2f8386b 100644 --- a/chromium/ui/gfx/color_transform.cc +++ b/chromium/ui/gfx/color_transform.cc @@ -12,6 +12,7 @@ #include <utility> #include "base/logging.h" +#include "base/notreached.h" #include "third_party/skia/include/core/SkColor.h" #include "third_party/skia/include/third_party/skcms/skcms.h" #include "ui/gfx/color_space.h" @@ -170,9 +171,10 @@ Transform GetTransferMatrix(const gfx::ColorSpace& color_space) { return Transform(transfer_matrix); } -Transform GetRangeAdjustMatrix(const gfx::ColorSpace& color_space) { +Transform GetRangeAdjustMatrix(const gfx::ColorSpace& color_space, + int bit_depth) { SkMatrix44 range_adjust_matrix; - color_space.GetRangeAdjustMatrix(&range_adjust_matrix); + color_space.GetRangeAdjustMatrix(bit_depth, &range_adjust_matrix); return Transform(range_adjust_matrix); } @@ -224,7 +226,9 @@ class ColorTransformStep { class ColorTransformInternal : public ColorTransform { public: ColorTransformInternal(const ColorSpace& src, + int src_bit_depth, const ColorSpace& dst, + int dst_bit_depth, Intent intent); ~ColorTransformInternal() override; @@ -243,7 +247,9 @@ class ColorTransformInternal : public ColorTransform { private: void AppendColorSpaceToColorSpaceTransform(const ColorSpace& src, - const ColorSpace& dst); + int src_bit_depth, + const ColorSpace& dst, + int dst_bit_depth); void Simplify(); std::list<std::unique_ptr<ColorTransformStep>> steps_; @@ -891,9 +897,11 @@ class ColorTransformFromBT2020CL : public ColorTransformStep { void ColorTransformInternal::AppendColorSpaceToColorSpaceTransform( const ColorSpace& src, - const ColorSpace& dst) { - steps_.push_back( - std::make_unique<ColorTransformMatrix>(GetRangeAdjustMatrix(src))); + int src_bit_depth, + const ColorSpace& dst, + int dst_bit_depth) { + steps_.push_back(std::make_unique<ColorTransformMatrix>( + GetRangeAdjustMatrix(src, src_bit_depth))); if (src.GetMatrixID() == ColorSpace::MatrixID::BT2020_CL) { // BT2020 CL is a special case. @@ -972,18 +980,21 @@ void ColorTransformInternal::AppendColorSpaceToColorSpaceTransform( } steps_.push_back(std::make_unique<ColorTransformMatrix>( - Invert(GetRangeAdjustMatrix(dst)))); + Invert(GetRangeAdjustMatrix(dst, dst_bit_depth)))); } ColorTransformInternal::ColorTransformInternal(const ColorSpace& src, + int src_bit_depth, const ColorSpace& dst, + int dst_bit_depth, Intent intent) : src_(src), dst_(dst) { // If no source color space is specified, do no transformation. // TODO(ccameron): We may want dst assume sRGB at some point in the future. if (!src_.IsValid()) return; - AppendColorSpaceToColorSpaceTransform(src_, dst_); + AppendColorSpaceToColorSpaceTransform(src_, src_bit_depth, dst_, + dst_bit_depth); if (intent != Intent::TEST_NO_OPT) Simplify(); } @@ -1046,10 +1057,12 @@ void ColorTransformInternal::Simplify() { // static std::unique_ptr<ColorTransform> ColorTransform::NewColorTransform( const ColorSpace& src, + int src_bit_depth, const ColorSpace& dst, + int dst_bit_depth, Intent intent) { - return std::unique_ptr<ColorTransform>( - new ColorTransformInternal(src, dst, intent)); + return std::make_unique<ColorTransformInternal>(src, src_bit_depth, dst, + dst_bit_depth, intent); } ColorTransform::ColorTransform() {} diff --git a/chromium/ui/gfx/color_transform.h b/chromium/ui/gfx/color_transform.h index db7d75d8335..2ed1da72d41 100644 --- a/chromium/ui/gfx/color_transform.h +++ b/chromium/ui/gfx/color_transform.h @@ -44,12 +44,33 @@ class GFX_EXPORT ColorTransform { virtual size_t NumberOfStepsForTesting() const = 0; + // Two special cases: + // 1. If no source color space is specified (i.e., src.IsValid() is false), do + // no transformation. + // 2. If the target color space is not defined (i.e., dst.IsValid() is false), + // just apply the range adjust and inverse transfer matrices. This can be used + // for YUV to RGB color conversion. static std::unique_ptr<ColorTransform> NewColorTransform( - const ColorSpace& from, - const ColorSpace& to, + const ColorSpace& src, + int src_bit_depth, + const ColorSpace& dst, + int dst_bit_depth, Intent intent); + // Assumes bit depth 8. For higher bit depths, use above NewColorTransform() + // method instead. + static std::unique_ptr<ColorTransform> NewColorTransform( + const ColorSpace& src, + const ColorSpace& dst, + Intent intent) { + return NewColorTransform(src, kDefaultBitDepth, dst, kDefaultBitDepth, + intent); + } + private: + // The default bit depth assumed by NewColorTransform(). + static constexpr int kDefaultBitDepth = 8; + DISALLOW_COPY_AND_ASSIGN(ColorTransform); }; diff --git a/chromium/ui/gfx/color_transform_fuzzer.cc b/chromium/ui/gfx/color_transform_fuzzer.cc index aa26e79d2a0..8f4011804b4 100644 --- a/chromium/ui/gfx/color_transform_fuzzer.cc +++ b/chromium/ui/gfx/color_transform_fuzzer.cc @@ -7,6 +7,7 @@ #include <random> #include "base/at_exit.h" +#include "base/logging.h" #include "ui/gfx/color_space.h" #include "ui/gfx/color_transform.h" #include "ui/gfx/icc_profile.h" diff --git a/chromium/ui/gfx/color_transform_unittest.cc b/chromium/ui/gfx/color_transform_unittest.cc index 314a8768709..372c3ca1c03 100644 --- a/chromium/ui/gfx/color_transform_unittest.cc +++ b/chromium/ui/gfx/color_transform_unittest.cc @@ -40,6 +40,7 @@ ColorSpace::TransferID simple_transfers[] = { ColorSpace::TransferID::GAMMA28, ColorSpace::TransferID::SMPTE170M, ColorSpace::TransferID::SMPTE240M, + ColorSpace::TransferID::SMPTEST428_1, ColorSpace::TransferID::LINEAR, ColorSpace::TransferID::LOG, ColorSpace::TransferID::LOG_SQRT, @@ -53,29 +54,16 @@ ColorSpace::TransferID simple_transfers[] = { ColorSpace::TransferID::IEC61966_2_1_HDR, }; -// This one is weird as the non-linear numbers are not between 0 and 1. -ColorSpace::TransferID noninvertible_transfers[] = { - ColorSpace::TransferID::SMPTEST428_1, -}; - ColorSpace::TransferID extended_transfers[] = { ColorSpace::TransferID::LINEAR_HDR, ColorSpace::TransferID::IEC61966_2_1_HDR, }; ColorSpace::MatrixID all_matrices[] = { - ColorSpace::MatrixID::RGB, - ColorSpace::MatrixID::BT709, - ColorSpace::MatrixID::FCC, - ColorSpace::MatrixID::BT470BG, - ColorSpace::MatrixID::SMPTE170M, - ColorSpace::MatrixID::SMPTE240M, - - // YCOCG produces lots of negative values which isn't compatible with many - // transfer functions. - // TODO(hubbe): Test this separately. - // ColorSpace::MatrixID::YCOCG, - ColorSpace::MatrixID::BT2020_NCL, + ColorSpace::MatrixID::RGB, ColorSpace::MatrixID::BT709, + ColorSpace::MatrixID::FCC, ColorSpace::MatrixID::BT470BG, + ColorSpace::MatrixID::SMPTE170M, ColorSpace::MatrixID::SMPTE240M, + ColorSpace::MatrixID::YCOCG, ColorSpace::MatrixID::BT2020_NCL, ColorSpace::MatrixID::YDZDX, }; @@ -388,6 +376,12 @@ TEST(SimpleColorSpace, ToUndefined) { ColorTransform::NewColorTransform( video, null, ColorTransform::Intent::INTENT_PERCEPTUAL)); EXPECT_EQ(video_to_null->NumberOfStepsForTesting(), 1u); + // Without optimization, video should have 2 steps: limited range to full + // range, and YUV to RGB. + std::unique_ptr<ColorTransform> video_to_null_no_opt( + ColorTransform::NewColorTransform(video, null, + ColorTransform::Intent::TEST_NO_OPT)); + EXPECT_EQ(video_to_null_no_opt->NumberOfStepsForTesting(), 2u); // Test with an ICC profile that can't be represented as matrix+transfer. ColorSpace luttrcicc = ICCProfileForTestingNoAnalyticTrFn().GetColorSpace(); @@ -412,15 +406,15 @@ TEST(SimpleColorSpace, ToUndefined) { EXPECT_GT(adobeicc_to_nonnull->NumberOfStepsForTesting(), 0u); // And with something analytic. - ColorSpace srgb = gfx::ColorSpace::CreateXYZD50(); - std::unique_ptr<ColorTransform> srgb_to_null( + ColorSpace xyzd50 = gfx::ColorSpace::CreateXYZD50(); + std::unique_ptr<ColorTransform> xyzd50_to_null( ColorTransform::NewColorTransform( - srgb, null, ColorTransform::Intent::INTENT_PERCEPTUAL)); - EXPECT_EQ(srgb_to_null->NumberOfStepsForTesting(), 0u); - std::unique_ptr<ColorTransform> srgb_to_nonnull( + xyzd50, null, ColorTransform::Intent::INTENT_PERCEPTUAL)); + EXPECT_EQ(xyzd50_to_null->NumberOfStepsForTesting(), 0u); + std::unique_ptr<ColorTransform> xyzd50_to_nonnull( ColorTransform::NewColorTransform( - srgb, nonnull, ColorTransform::Intent::INTENT_PERCEPTUAL)); - EXPECT_GT(srgb_to_nonnull->NumberOfStepsForTesting(), 0u); + xyzd50, nonnull, ColorTransform::Intent::INTENT_PERCEPTUAL)); + EXPECT_GT(xyzd50_to_nonnull->NumberOfStepsForTesting(), 0u); } TEST(SimpleColorSpace, DefaultToSRGB) { @@ -572,35 +566,6 @@ INSTANTIATE_TEST_SUITE_P(ColorSpace, TransferTest, testing::ValuesIn(simple_transfers)); -class NonInvertibleTransferTest - : public testing::TestWithParam<ColorSpace::TransferID> {}; - -TEST_P(NonInvertibleTransferTest, basicTest) { - gfx::ColorSpace space_with_transfer(ColorSpace::PrimaryID::BT709, GetParam(), - ColorSpace::MatrixID::RGB, - ColorSpace::RangeID::FULL); - gfx::ColorSpace space_linear( - ColorSpace::PrimaryID::BT709, ColorSpace::TransferID::LINEAR, - ColorSpace::MatrixID::RGB, ColorSpace::RangeID::FULL); - - std::unique_ptr<ColorTransform> to_linear(ColorTransform::NewColorTransform( - space_with_transfer, space_linear, - ColorTransform::Intent::INTENT_ABSOLUTE)); - - std::unique_ptr<ColorTransform> from_linear(ColorTransform::NewColorTransform( - space_linear, space_with_transfer, - ColorTransform::Intent::INTENT_ABSOLUTE)); - - // These transforms should not crash when created or applied. - float x = 0.5; - ColorTransform::TriStim tristim(x, x, x); - to_linear->Transform(&tristim, 1); - from_linear->Transform(&tristim, 1); -} - -INSTANTIATE_TEST_SUITE_P(ColorSpace, - NonInvertibleTransferTest, - testing::ValuesIn(noninvertible_transfers)); class ExtendedTransferTest : public testing::TestWithParam<ColorSpace::TransferID> {}; diff --git a/chromium/ui/gfx/geometry/matrix3_f.h b/chromium/ui/gfx/geometry/matrix3_f.h index fea4852cf74..0b5cc128466 100644 --- a/chromium/ui/gfx/geometry/matrix3_f.h +++ b/chromium/ui/gfx/geometry/matrix3_f.h @@ -5,7 +5,7 @@ #ifndef UI_GFX_GEOMETRY_MATRIX3_F_H_ #define UI_GFX_GEOMETRY_MATRIX3_F_H_ -#include "base/logging.h" +#include "base/check.h" #include "ui/gfx/geometry/vector3d_f.h" namespace gfx { diff --git a/chromium/ui/gfx/geometry/mojom/geometry.mojom b/chromium/ui/gfx/geometry/mojom/geometry.mojom index 78d28f07773..82218aef7f6 100644 --- a/chromium/ui/gfx/geometry/mojom/geometry.mojom +++ b/chromium/ui/gfx/geometry/mojom/geometry.mojom @@ -4,18 +4,13 @@ module gfx.mojom; -// Don't make backwards-incompatible changes to this definition! -// It's used in PageState serialization, so backwards incompatible changes -// would cause stored PageState objects to be un-parseable. +[Stable] struct Point { int32 x; int32 y; }; -// Don't make backwards-incompatible changes to this definition! -// It's used in PageState serialization, so backwards incompatible changes -// would cause stored PageState objects to be un-parseable. Please contact the -// page state serialization owners before making such a change. +[Stable] struct PointF { float x; float y; diff --git a/chromium/ui/gfx/geometry/quad_f.h b/chromium/ui/gfx/geometry/quad_f.h index 00ade0e3749..3fe8cf10d5d 100644 --- a/chromium/ui/gfx/geometry/quad_f.h +++ b/chromium/ui/gfx/geometry/quad_f.h @@ -12,7 +12,7 @@ #include <iosfwd> #include <string> -#include "base/logging.h" +#include "base/check_op.h" #include "ui/gfx/geometry/geometry_export.h" #include "ui/gfx/geometry/point_f.h" #include "ui/gfx/geometry/rect_f.h" diff --git a/chromium/ui/gfx/geometry/rect.h b/chromium/ui/gfx/geometry/rect.h index 7fa7045f6f4..3ec89df0d47 100644 --- a/chromium/ui/gfx/geometry/rect.h +++ b/chromium/ui/gfx/geometry/rect.h @@ -16,7 +16,7 @@ #include <iosfwd> #include <string> -#include "base/logging.h" +#include "base/check.h" #include "build/build_config.h" #include "ui/gfx/geometry/point.h" #include "ui/gfx/geometry/safe_integer_conversions.h" diff --git a/chromium/ui/gfx/gpu_fence.cc b/chromium/ui/gfx/gpu_fence.cc index 7d38c0eb906..87fc6d10761 100644 --- a/chromium/ui/gfx/gpu_fence.cc +++ b/chromium/ui/gfx/gpu_fence.cc @@ -5,6 +5,8 @@ #include "ui/gfx/gpu_fence.h" #include "base/logging.h" +#include "base/notreached.h" +#include "base/time/time.h" #if defined(OS_LINUX) || defined(OS_ANDROID) #include <sync/sync.h> @@ -73,4 +75,49 @@ void GpuFence::Wait() { } } +// static +GpuFence::FenceStatus GpuFence::GetStatusChangeTime(int fd, + base::TimeTicks* time) { + DCHECK_NE(fd, -1); +#if defined(OS_LINUX) || defined(OS_ANDROID) + auto info = + std::unique_ptr<sync_fence_info_data, void (*)(sync_fence_info_data*)>{ + sync_fence_info(fd), sync_fence_info_free}; + if (!info) { + LOG(ERROR) << "sync_fence_info returned null for fd : " << fd; + return FenceStatus::kInvalid; + } + + // Not signalled yet. + if (info->status != 1) { + return FenceStatus::kNotSignaled; + } + + uint64_t timestamp_ns = 0u; + struct sync_pt_info* pt_info = nullptr; + while ((pt_info = sync_pt_info(info.get(), pt_info))) + timestamp_ns = std::max(timestamp_ns, pt_info->timestamp_ns); + + if (timestamp_ns == 0u) { + LOG(ERROR) << "No timestamp provided from sync_pt_info for fd : " << fd; + return FenceStatus::kInvalid; + } + *time = base::TimeTicks() + base::TimeDelta::FromNanoseconds(timestamp_ns); + return FenceStatus::kSignaled; +#endif + NOTREACHED(); + return FenceStatus::kInvalid; +} + +base::TimeTicks GpuFence::GetMaxTimestamp() const { + base::TimeTicks timestamp; +#if defined(OS_LINUX) || defined(OS_ANDROID) + FenceStatus status = GetStatusChangeTime(owned_fd_.get(), ×tamp); + DCHECK_EQ(status, FenceStatus::kSignaled); + return timestamp; +#endif + NOTREACHED(); + return timestamp; +} + } // namespace gfx diff --git a/chromium/ui/gfx/gpu_fence.h b/chromium/ui/gfx/gpu_fence.h index de4e81fea29..2d2a24be612 100644 --- a/chromium/ui/gfx/gpu_fence.h +++ b/chromium/ui/gfx/gpu_fence.h @@ -12,6 +12,10 @@ extern "C" typedef struct _ClientGpuFence* ClientGpuFence; +namespace base { +class TimeTicks; +} // namespace base + namespace gfx { // GpuFence objects own a GpuFenceHandle and release the resources in it when @@ -35,6 +39,11 @@ class GFX_EXPORT GpuFence { // Wait for the GpuFence to become ready. void Wait(); + enum FenceStatus { kSignaled, kNotSignaled, kInvalid }; + static FenceStatus GetStatusChangeTime(int fd, base::TimeTicks* time); + + base::TimeTicks GetMaxTimestamp() const; + private: gfx::GpuFenceHandleType type_; #if defined(OS_POSIX) diff --git a/chromium/ui/gfx/image/image_generic.cc b/chromium/ui/gfx/image/image_generic.cc index d7606e1d8cd..3115fecfb8d 100644 --- a/chromium/ui/gfx/image/image_generic.cc +++ b/chromium/ui/gfx/image/image_generic.cc @@ -8,6 +8,7 @@ #include <set> #include <utility> +#include "base/logging.h" #include "ui/gfx/codec/png_codec.h" #include "ui/gfx/image/image_skia_source.h" diff --git a/chromium/ui/gfx/image/image_util.cc b/chromium/ui/gfx/image/image_util.cc index d21fb445716..0b6cacfca49 100644 --- a/chromium/ui/gfx/image/image_util.cc +++ b/chromium/ui/gfx/image/image_util.cc @@ -85,9 +85,15 @@ Image ResizedImageForSearchByImageSkiaRepresentation(const Image& image) { if (bitmap.height() * bitmap.width() > kSearchByImageMaxImageArea && (bitmap.width() > kSearchByImageMaxImageWidth || bitmap.height() > kSearchByImageMaxImageHeight)) { + double scale = std::min( + static_cast<double>(kSearchByImageMaxImageWidth) / bitmap.width(), + static_cast<double>(kSearchByImageMaxImageHeight) / bitmap.height()); + int width = base::ClampToRange<int>(scale * bitmap.width(), 1, + kSearchByImageMaxImageWidth); + int height = base::ClampToRange<int>(scale * bitmap.height(), 1, + kSearchByImageMaxImageHeight); SkBitmap new_bitmap = skia::ImageOperations::Resize( - bitmap, skia::ImageOperations::RESIZE_GOOD, kSearchByImageMaxImageWidth, - kSearchByImageMaxImageHeight); + bitmap, skia::ImageOperations::RESIZE_GOOD, width, height); return Image(ImageSkia(ImageSkiaRep(new_bitmap, 0.0f))); } diff --git a/chromium/ui/gfx/image/image_util_unittest.cc b/chromium/ui/gfx/image/image_util_unittest.cc index 40efde273aa..0ef65fdbb81 100644 --- a/chromium/ui/gfx/image/image_util_unittest.cc +++ b/chromium/ui/gfx/image/image_util_unittest.cc @@ -139,10 +139,21 @@ TEST(ImageUtilTest, ResizedImageForSearchByImage) { // Make sure the image large enough to let ResizedImageForSearchByImage to // resize the image. gfx::Image original_image = - gfx::test::CreateImage(gfx::kSearchByImageMaxImageHeight + 10, - gfx::kSearchByImageMaxImageWidth + 10); + gfx::test::CreateImage(gfx::kSearchByImageMaxImageWidth * 2, + gfx::kSearchByImageMaxImageHeight * 2); gfx::Image resized_image = gfx::ResizedImageForSearchByImage(original_image); - EXPECT_NE(original_image.Size(), resized_image.Size()); EXPECT_FALSE(resized_image.IsEmpty()); + EXPECT_EQ(resized_image.Width(), gfx::kSearchByImageMaxImageWidth); + EXPECT_EQ(resized_image.Height(), gfx::kSearchByImageMaxImageHeight); +} + +TEST(ImageUtilTest, ResizedImageForSearchByImageShouldKeepRatio) { + // Make sure the image large enough to let ResizedImageForSearchByImage to + // resize the image. + gfx::Image original_image = gfx::test::CreateImage(600, 600); + + gfx::Image resized_image = gfx::ResizedImageForSearchByImage(original_image); + EXPECT_EQ(resized_image.Width(), 400); + EXPECT_EQ(resized_image.Height(), 400); } diff --git a/chromium/ui/gfx/linux/client_native_pixmap_dmabuf.cc b/chromium/ui/gfx/linux/client_native_pixmap_dmabuf.cc index 88c16776abd..ed404cd52f8 100644 --- a/chromium/ui/gfx/linux/client_native_pixmap_dmabuf.cc +++ b/chromium/ui/gfx/linux/client_native_pixmap_dmabuf.cc @@ -14,6 +14,7 @@ #include <utility> #include "base/command_line.h" +#include "base/logging.h" #include "base/memory/ptr_util.h" #include "base/numerics/safe_conversions.h" #include "base/posix/eintr_wrapper.h" @@ -102,6 +103,20 @@ bool ClientNativePixmapDmaBuf::IsConfigurationSupported( } #endif + bool disable_yuv_biplanar = true; +#if defined(OS_CHROMEOS) + // IsConfigurationSupported(SCANOUT_CPU_READ_WRITE) is used by the renderer + // to tell whether the platform supports sampling a given format. Zero-copy + // video capture and encoding requires gfx::BufferFormat::YUV_420_BIPLANAR to + // be supported by the renderer. Most of Chrome OS platforms support it, so + // enable it by default, with a switch that allows an explicit disable on + // platforms known to have problems, e.g. the Tegra-based nyan." + // TODO(crbug.com/982201): move gfx::BufferFormat::YUV_420_BIPLANAR out + // of if defined(ARCH_CPU_X86_FAMLIY) when Tegra is no longer supported. + disable_yuv_biplanar = base::CommandLine::ForCurrentProcess()->HasSwitch( + switches::kDisableYuv420Biplanar); +#endif + switch (usage) { case gfx::BufferUsage::GPU_READ: return format == gfx::BufferFormat::BGR_565 || @@ -124,10 +139,15 @@ bool ClientNativePixmapDmaBuf::IsConfigurationSupported( if (format == gfx::BufferFormat::RG_88 && !AllowCpuMappableBuffers()) return false; + if (!disable_yuv_biplanar && + format == gfx::BufferFormat::YUV_420_BIPLANAR) { + return true; + } + return #if defined(ARCH_CPU_X86_FAMILY) - // Currently only Intel driver (i.e. minigbm and Mesa) supports - // R_8 RG_88, NV12 and XB30/XR30. + // The minigbm backends and Mesa drivers commonly used on x86 systems + // support the following formats. format == gfx::BufferFormat::R_8 || format == gfx::BufferFormat::RG_88 || format == gfx::BufferFormat::YUV_420_BIPLANAR || @@ -145,10 +165,16 @@ bool ClientNativePixmapDmaBuf::IsConfigurationSupported( case gfx::BufferUsage::GPU_READ_CPU_READ_WRITE: if (!AllowCpuMappableBuffers()) return false; + + if (!disable_yuv_biplanar && + format == gfx::BufferFormat::YUV_420_BIPLANAR) { + return true; + } + return #if defined(ARCH_CPU_X86_FAMILY) - // Only the Intel stack (i.e. minigbm and Mesa) supports the formats - // below. + // The minigbm backends and Mesa drivers commonly used on x86 systems + // support the following formats. format == gfx::BufferFormat::R_8 || format == gfx::BufferFormat::RG_88 || format == gfx::BufferFormat::YUV_420_BIPLANAR || diff --git a/chromium/ui/gfx/linux/drm_util_linux.cc b/chromium/ui/gfx/linux/drm_util_linux.cc index 7482722b128..e1372b68092 100644 --- a/chromium/ui/gfx/linux/drm_util_linux.cc +++ b/chromium/ui/gfx/linux/drm_util_linux.cc @@ -8,15 +8,6 @@ #include "base/notreached.h" -#ifndef DRM_FORMAT_INVALID -// TODO(mcasas): Remove when uprevving //third_party/libdrm. -#define DRM_FORMAT_INVALID 0 -#endif - -#ifndef DRM_FORMAT_P010 -#define DRM_FORMAT_P010 fourcc_code('P', '0', '1', '0') -#endif - namespace ui { int GetFourCCFormatFromBufferFormat(gfx::BufferFormat format) { diff --git a/chromium/ui/gfx/linux/gbm_util.cc b/chromium/ui/gfx/linux/gbm_util.cc index cd9d13b55df..c516c13fafa 100644 --- a/chromium/ui/gfx/linux/gbm_util.cc +++ b/chromium/ui/gfx/linux/gbm_util.cc @@ -28,7 +28,8 @@ uint32_t BufferUsageToGbmFlags(gfx::BufferUsage usage) { case gfx::BufferUsage::GPU_READ_CPU_READ_WRITE: return GBM_BO_USE_LINEAR | GBM_BO_USE_TEXTURING; case gfx::BufferUsage::SCANOUT_VEA_READ_CAMERA_AND_CPU_READ_WRITE: - return GBM_BO_USE_TEXTURING | GBM_BO_USE_HW_VIDEO_ENCODER; + return GBM_BO_USE_LINEAR | GBM_BO_USE_CAMERA_WRITE | GBM_BO_USE_SCANOUT | + GBM_BO_USE_TEXTURING | GBM_BO_USE_HW_VIDEO_ENCODER; default: NOTREACHED(); return 0; diff --git a/chromium/ui/gfx/mac/cocoa_scrollbar_painter.cc b/chromium/ui/gfx/mac/cocoa_scrollbar_painter.cc deleted file mode 100644 index 34218498007..00000000000 --- a/chromium/ui/gfx/mac/cocoa_scrollbar_painter.cc +++ /dev/null @@ -1,334 +0,0 @@ -// Copyright 2019 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 "ui/gfx/mac/cocoa_scrollbar_painter.h" - -#include "ui/gfx/canvas.h" -#include "ui/gfx/skia_util.h" - -namespace gfx { - -using Params = CocoaScrollbarPainter::Params; -using Orientation = CocoaScrollbarPainter::Orientation; - -namespace { - -// The width of the scroller track border. -constexpr int kTrackBorderWidth = 1; - -// The amount the thumb is inset from the ends and the inside edge of track -// border. -constexpr int kThumbInset = 3; -constexpr int kThumbInsetOverlay = 2; - -// The minimum sizes for the thumb. We will not inset the thumb if it will -// be smaller than this size. -constexpr int kThumbMinGirth = 6; -constexpr int kThumbMinLength = 18; - -// Scrollbar thumb colors. -constexpr SkColor kThumbColorDefault = SkColorSetARGB(0x3A, 0, 0, 0); -constexpr SkColor kThumbColorHover = SkColorSetARGB(0x80, 0, 0, 0); -constexpr SkColor kThumbColorDarkMode = SkColorSetRGB(0x6B, 0x6B, 0x6B); -constexpr SkColor kThumbColorDarkModeHover = SkColorSetRGB(0x93, 0x93, 0x93); -constexpr SkColor kThumbColorOverlay = SkColorSetARGB(0x80, 0, 0, 0); -constexpr SkColor kThumbColorOverlayDarkMode = - SkColorSetARGB(0x80, 0xFF, 0xFF, 0xFF); - -// Non-overlay scroller track colors are not transparent. On Safari, they are, -// but on all other macOS applications they are not. -constexpr SkColor kTrackGradientColors[] = { - SkColorSetRGB(0xFA, 0xFA, 0xFA), - SkColorSetRGB(0xFA, 0xFA, 0xFA), -}; -constexpr SkColor kTrackInnerBorderColor = SkColorSetRGB(0xE8, 0xE8, 0xE8); -constexpr SkColor kTrackOuterBorderColor = SkColorSetRGB(0xED, 0xED, 0xED); - -// Non-overlay dark mode scroller track colors. -constexpr SkColor kTrackGradientColorsDarkMode[] = { - SkColorSetRGB(0x2D, 0x2D, 0x2D), - SkColorSetRGB(0x2B, 0x2B, 0x2B), -}; -constexpr SkColor kTrackInnerBorderColorDarkMode = - SkColorSetRGB(0x3D, 0x3D, 0x3D); -constexpr SkColor kTrackOuterBorderColorDarkMode = - SkColorSetRGB(0x51, 0x51, 0x51); - -// Overlay scroller track colors are transparent. -constexpr SkColor kTrackGradientColorsOverlay[] = { - SkColorSetARGB(0xC6, 0xF8, 0xF8, 0xF8), - SkColorSetARGB(0xC2, 0xF8, 0xF8, 0xF8), - SkColorSetARGB(0xC2, 0xF8, 0xF8, 0xF8), - SkColorSetARGB(0xC2, 0xF8, 0xF8, 0xF8), -}; -constexpr SkColor kTrackInnerBorderColorOverlay = - SkColorSetARGB(0xF9, 0xDF, 0xDF, 0xDF); -constexpr SkColor kTrackOuterBorderColorOverlay = - SkColorSetARGB(0xC6, 0xE8, 0xE8, 0xE8); - -// Dark mode overlay scroller track colors. -constexpr SkColor kTrackGradientColorsOverlayDarkMode[] = { - SkColorSetARGB(0x28, 0xD8, 0xD8, 0xD8), - SkColorSetARGB(0x26, 0xCC, 0xCC, 0xCC), - SkColorSetARGB(0x26, 0xCC, 0xCC, 0xCC), - SkColorSetARGB(0x26, 0xCC, 0xCC, 0xCC), -}; -constexpr SkColor kTrackInnerBorderColorOverlayDarkMode = - SkColorSetARGB(0x33, 0xE5, 0xE5, 0xE5); -constexpr SkColor kTrackOuterBorderColorOverlayDarkMode = - SkColorSetARGB(0x28, 0xD8, 0xD8, 0xD8); - -void ConstrainInsets(int old_width, int min_width, int* left, int* right) { - int requested_total_inset = *left + *right; - if (requested_total_inset == 0) - return; - int max_total_inset = old_width - min_width; - if (requested_total_inset < max_total_inset) - return; - if (max_total_inset < 0) { - *left = *right = 0; - return; - } - // Multiply the right/bottom inset by the ratio by which we need to shrink the - // total inset. This has the effect of rounding down the right/bottom inset, - // if the two sides are to be affected unevenly. - *right *= max_total_inset * 1.f / requested_total_inset; - *left = max_total_inset - *right; -} - -void ConstrainedInset(gfx::Rect* rect, - int min_width, - int min_height, - int inset_left, - int inset_top, - int inset_right, - int inset_bottom) { - ConstrainInsets(rect->width(), min_width, &inset_left, &inset_right); - ConstrainInsets(rect->height(), min_height, &inset_top, &inset_bottom); - rect->Inset(inset_left, inset_top, inset_right, inset_bottom); -} - -void PaintTrackGradient(gfx::Canvas* canvas, - const gfx::Rect& rect, - const Params& params, - bool is_corner) { - // Select colors. - const SkColor* gradient_colors = nullptr; - size_t gradient_stops = 0; - if (params.overlay) { - if (params.dark_mode) { - gradient_colors = kTrackGradientColorsOverlayDarkMode; - gradient_stops = base::size(kTrackGradientColorsOverlayDarkMode); - } else { - gradient_colors = kTrackGradientColorsOverlay; - gradient_stops = base::size(kTrackGradientColorsOverlay); - } - } else { - if (params.dark_mode) { - gradient_colors = kTrackGradientColorsDarkMode; - gradient_stops = base::size(kTrackGradientColorsDarkMode); - } else { - gradient_colors = kTrackGradientColors; - gradient_stops = base::size(kTrackGradientColors); - } - } - - // Set the gradient direction. - const SkPoint gradient_bounds_vertical[] = { - gfx::PointToSkPoint(rect.origin()), - gfx::PointToSkPoint(rect.bottom_left()), - }; - const SkPoint gradient_bounds_horizontal[] = { - gfx::PointToSkPoint(rect.origin()), - gfx::PointToSkPoint(rect.top_right()), - }; - const SkPoint gradient_bounds_corner_right[] = { - gfx::PointToSkPoint(rect.origin()), - gfx::PointToSkPoint(rect.bottom_right()), - }; - const SkPoint gradient_bounds_corner_left[] = { - gfx::PointToSkPoint(rect.top_right()), - gfx::PointToSkPoint(rect.bottom_left()), - }; - const SkPoint* gradient_bounds = nullptr; - if (is_corner) { - if (params.orientation == Orientation::kVerticalOnRight) - gradient_bounds = gradient_bounds_corner_right; - else - gradient_bounds = gradient_bounds_corner_left; - } else { - if (params.orientation == Orientation::kHorizontal) - gradient_bounds = gradient_bounds_horizontal; - else - gradient_bounds = gradient_bounds_vertical; - } - - // And draw. - cc::PaintFlags gradient; - gradient.setShader(cc::PaintShader::MakeLinearGradient( - gradient_bounds, gradient_colors, nullptr, gradient_stops, - SkTileMode::kClamp)); - canvas->DrawRect(rect, gradient); -} - -void PaintTrackInnerBorder(gfx::Canvas* canvas, - const gfx::Rect& rect, - const Params& params, - bool is_corner) { - // Select the color. - SkColor inner_border_color = 0; - if (params.overlay) { - if (params.dark_mode) - inner_border_color = kTrackInnerBorderColorOverlayDarkMode; - else - inner_border_color = kTrackInnerBorderColorOverlay; - } else { - if (params.dark_mode) - inner_border_color = kTrackInnerBorderColorDarkMode; - else - inner_border_color = kTrackInnerBorderColor; - } - - // Compute the rect for the border. - gfx::Rect inner_border(rect); - if (params.orientation == Orientation::kVerticalOnLeft) - inner_border.set_x(rect.right() - kTrackBorderWidth); - if (is_corner || params.orientation == Orientation::kHorizontal) - inner_border.set_height(kTrackBorderWidth); - if (is_corner || params.orientation != Orientation::kHorizontal) - inner_border.set_width(kTrackBorderWidth); - - // And draw. - cc::PaintFlags flags; - flags.setColor(inner_border_color); - canvas->DrawRect(inner_border, flags); -} - -void PaintTrackOuterBorder(gfx::Canvas* canvas, - const gfx::Rect& rect, - const Params& params, - bool is_corner) { - // Select the color. - SkColor outer_border_color = 0; - if (params.overlay) { - if (params.dark_mode) - outer_border_color = kTrackOuterBorderColorOverlayDarkMode; - else - outer_border_color = kTrackOuterBorderColorOverlay; - } else { - if (params.dark_mode) - outer_border_color = kTrackOuterBorderColorDarkMode; - else - outer_border_color = kTrackOuterBorderColor; - } - cc::PaintFlags flags; - flags.setColor(outer_border_color); - - // Draw the horizontal outer border. - if (is_corner || params.orientation == Orientation::kHorizontal) { - gfx::Rect outer_border(rect); - outer_border.set_height(kTrackBorderWidth); - outer_border.set_y(rect.bottom() - kTrackBorderWidth); - canvas->DrawRect(outer_border, flags); - } - - // Draw the vertial outer border. - if (is_corner || params.orientation != Orientation::kHorizontal) { - gfx::Rect outer_border(rect); - outer_border.set_width(kTrackBorderWidth); - if (params.orientation == Orientation::kVerticalOnRight) - outer_border.set_x(rect.right() - kTrackBorderWidth); - canvas->DrawRect(outer_border, flags); - } -} - -} // namespace - -// static -void CocoaScrollbarPainter::PaintTrack(cc::PaintCanvas* cc_canvas, - const SkIRect& sk_track_rect, - const Params& params) { - gfx::Canvas canvas(cc_canvas, 1.f); - const gfx::Rect track_rect(SkIRectToRect(sk_track_rect)); - constexpr bool is_corner = false; - PaintTrackGradient(&canvas, track_rect, params, is_corner); - PaintTrackInnerBorder(&canvas, track_rect, params, is_corner); - PaintTrackOuterBorder(&canvas, track_rect, params, is_corner); -} - -// static -void CocoaScrollbarPainter::PaintCorner(cc::PaintCanvas* cc_canvas, - const SkIRect& sk_corner_rect, - const Params& params) { - // Overlay scrollbars don't have a corner. - if (params.overlay) - return; - gfx::Canvas canvas(cc_canvas, 1.f); - const gfx::Rect corner_rect(SkIRectToRect(sk_corner_rect)); - constexpr bool is_corner = true; - PaintTrackGradient(&canvas, corner_rect, params, is_corner); - PaintTrackInnerBorder(&canvas, corner_rect, params, is_corner); - PaintTrackOuterBorder(&canvas, corner_rect, params, is_corner); -} - -// static -void CocoaScrollbarPainter::PaintThumb(cc::PaintCanvas* cc_canvas, - const SkIRect& sk_bounds, - const Params& params) { - gfx::Canvas canvas(cc_canvas, 1.f); - - // Select the color. - SkColor thumb_color = 0; - if (params.overlay) { - if (params.dark_mode) - thumb_color = kThumbColorOverlayDarkMode; - else - thumb_color = kThumbColorOverlay; - } else { - if (params.dark_mode) { - if (params.hovered) - thumb_color = kThumbColorDarkModeHover; - else - thumb_color = kThumbColorDarkMode; - } else { - if (params.hovered) - thumb_color = kThumbColorHover; - else - thumb_color = kThumbColorDefault; - } - } - - // Compute the bounds for the rounded rect for the thumb from the bounds of - // the thumb. - gfx::Rect bounds(SkIRectToRect(sk_bounds)); - { - // Shrink the thumb evenly in length and girth to fit within the track. - const int thumb_inset = params.overlay ? kThumbInsetOverlay : kThumbInset; - int inset_left = thumb_inset; - int inset_top = thumb_inset; - int inset_right = thumb_inset; - int inset_bottom = thumb_inset; - - // Also shrink the thumb in girth to not touch the border. - if (params.orientation == Orientation::kHorizontal) { - inset_top += kTrackBorderWidth; - ConstrainedInset(&bounds, kThumbMinLength, kThumbMinGirth, inset_left, - inset_top, inset_right, inset_bottom); - } else { - inset_left += kTrackBorderWidth; - ConstrainedInset(&bounds, kThumbMinGirth, kThumbMinLength, inset_left, - inset_top, inset_right, inset_bottom); - } - } - - // Draw. - cc::PaintFlags flags; - flags.setAntiAlias(true); - flags.setStyle(cc::PaintFlags::kFill_Style); - flags.setColor(thumb_color); - const SkScalar radius = std::min(bounds.width(), bounds.height()); - canvas.DrawRoundRect(bounds, radius, flags); -} - -} // namespace gfx diff --git a/chromium/ui/gfx/mac/cocoa_scrollbar_painter.h b/chromium/ui/gfx/mac/cocoa_scrollbar_painter.h deleted file mode 100644 index ea4c5232732..00000000000 --- a/chromium/ui/gfx/mac/cocoa_scrollbar_painter.h +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright 2019 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. - -#ifndef UI_GFX_MAC_COCOA_SCROLLBAR_PAINTER_H_ -#define UI_GFX_MAC_COCOA_SCROLLBAR_PAINTER_H_ - -#include "cc/paint/paint_canvas.h" -#include "third_party/skia/include/core/SkRect.h" -#include "ui/gfx/gfx_export.h" - -namespace gfx { - -class GFX_EXPORT CocoaScrollbarPainter { - public: - enum class Orientation { - // Vertical scrollbar on the right side of content. - kVerticalOnRight, - // Vertical scrollbar on the left side of content. - kVerticalOnLeft, - // Horizontal scrollbar (on the bottom of content). - kHorizontal, - }; - - struct Params { - // The orientation of the scrollbar. - Orientation orientation = Orientation::kVerticalOnRight; - - // Whether or not this is an overlay scrollbar. - bool overlay = false; - - // Scrollbars change color in dark mode. - bool dark_mode = false; - - // Non-overlay scrollbars change thumb color when they are hovered (or - // pressed). - bool hovered = false; - }; - - // Paint the thumb. The |thumb_bounds| changes over time when the thumb - // engorges during hover. - static void PaintThumb(cc::PaintCanvas* canvas, - const SkIRect& thumb_bounds, - const Params& params); - // Paint the track. |track_bounds| is the bounds for the track. - static void PaintTrack(cc::PaintCanvas* canvas, - const SkIRect& track_bounds, - const Params& params); - // Paint the corner. |corner_bounds| is the bounds for the corner. - static void PaintCorner(cc::PaintCanvas* canvas, - const SkIRect& corner_bounds, - const Params& params); -}; - -} // namespace gfx - -#endif // UI_GFX_MAC_COCOA_SCROLLBAR_PAINTER_H_ diff --git a/chromium/ui/gfx/mac/display_icc_profiles.cc b/chromium/ui/gfx/mac/display_icc_profiles.cc index 822924229f2..119b652bb87 100644 --- a/chromium/ui/gfx/mac/display_icc_profiles.cc +++ b/chromium/ui/gfx/mac/display_icc_profiles.cc @@ -4,6 +4,7 @@ #include "ui/gfx/mac/display_icc_profiles.h" +#include "base/notreached.h" #include "ui/gfx/icc_profile.h" namespace gfx { diff --git a/chromium/ui/gfx/mojom/BUILD.gn b/chromium/ui/gfx/mojom/BUILD.gn index eec21020c9c..f8d73762c53 100644 --- a/chromium/ui/gfx/mojom/BUILD.gn +++ b/chromium/ui/gfx/mojom/BUILD.gn @@ -87,6 +87,26 @@ mojom("mojom") { { types = [ { + mojom = "gfx.mojom.PresentationFeedback" + cpp = "::gfx::PresentationFeedback" + }, + ] + traits_headers = [ "presentation_feedback_mojom_traits.h" ] + traits_public_deps = [ "//ui/gfx" ] + }, + { + types = [ + { + mojom = "gfx.mojom.SwapTimings" + cpp = "::gfx::SwapTimings" + }, + ] + traits_headers = [ "swap_timings_mojom_traits.h" ] + traits_public_deps = [ "//ui/gfx" ] + }, + { + types = [ + { mojom = "gfx.mojom.Transform" cpp = "::gfx::Transform" }, @@ -157,16 +177,6 @@ mojom("mojom") { { types = [ { - mojom = "gfx.mojom.PresentationFeedback" - cpp = "::gfx::PresentationFeedback" - }, - ] - traits_headers = [ "presentation_feedback_mojom_traits.h" ] - traits_public_deps = [ "//ui/gfx" ] - }, - { - types = [ - { mojom = "gfx.mojom.RRectF" cpp = "::gfx::RRectF" }, @@ -194,16 +204,6 @@ mojom("mojom") { traits_headers = [ "swap_result_mojom_traits.h" ] traits_public_deps = [ "//ui/gfx" ] }, - { - types = [ - { - mojom = "gfx.mojom.SwapTimings" - cpp = "::gfx::SwapTimings" - }, - ] - traits_headers = [ "swap_timings_mojom_traits.h" ] - traits_public_deps = [ "//ui/gfx" ] - }, ] cpp_typemaps += shared_cpp_typemaps diff --git a/chromium/ui/gfx/mojom/mojom_traits_unittest.cc b/chromium/ui/gfx/mojom/mojom_traits_unittest.cc index dfe551e3061..4bd41e851d7 100644 --- a/chromium/ui/gfx/mojom/mojom_traits_unittest.cc +++ b/chromium/ui/gfx/mojom/mojom_traits_unittest.cc @@ -245,12 +245,21 @@ TEST_F(StructTraitsTest, PresentationFeedback) { uint32_t flags = PresentationFeedback::kVSync | PresentationFeedback::kZeroCopy; PresentationFeedback input{timestamp, interval, flags}; + input.available_timestamp = + base::TimeTicks() + base::TimeDelta::FromMilliseconds(20); + input.ready_timestamp = + base::TimeTicks() + base::TimeDelta::FromMilliseconds(21); + input.latch_timestamp = + base::TimeTicks() + base::TimeDelta::FromMilliseconds(22); PresentationFeedback output; mojo::test::SerializeAndDeserialize<gfx::mojom::PresentationFeedback>( &input, &output); EXPECT_EQ(timestamp, output.timestamp); EXPECT_EQ(interval, output.interval); EXPECT_EQ(flags, output.flags); + EXPECT_EQ(input.available_timestamp, output.available_timestamp); + EXPECT_EQ(input.ready_timestamp, output.ready_timestamp); + EXPECT_EQ(input.latch_timestamp, output.latch_timestamp); } TEST_F(StructTraitsTest, RRectF) { diff --git a/chromium/ui/gfx/mojom/presentation_feedback.mojom b/chromium/ui/gfx/mojom/presentation_feedback.mojom index 49233c72bd1..d91fe6a4be6 100644 --- a/chromium/ui/gfx/mojom/presentation_feedback.mojom +++ b/chromium/ui/gfx/mojom/presentation_feedback.mojom @@ -11,4 +11,8 @@ struct PresentationFeedback { mojo_base.mojom.TimeTicks timestamp; mojo_base.mojom.TimeDelta interval; uint32 flags; + + mojo_base.mojom.TimeTicks available_timestamp; + mojo_base.mojom.TimeTicks ready_timestamp; + mojo_base.mojom.TimeTicks latch_timestamp; }; diff --git a/chromium/ui/gfx/mojom/presentation_feedback_mojom_traits.h b/chromium/ui/gfx/mojom/presentation_feedback_mojom_traits.h index 14b726b4090..26b0f2e7fe9 100644 --- a/chromium/ui/gfx/mojom/presentation_feedback_mojom_traits.h +++ b/chromium/ui/gfx/mojom/presentation_feedback_mojom_traits.h @@ -27,11 +27,29 @@ struct StructTraits<gfx::mojom::PresentationFeedbackDataView, return input.flags; } + static base::TimeTicks available_timestamp( + const gfx::PresentationFeedback& input) { + return input.available_timestamp; + } + + static base::TimeTicks ready_timestamp( + const gfx::PresentationFeedback& input) { + return input.ready_timestamp; + } + + static base::TimeTicks latch_timestamp( + const gfx::PresentationFeedback& input) { + return input.latch_timestamp; + } + static bool Read(gfx::mojom::PresentationFeedbackDataView data, gfx::PresentationFeedback* out) { out->flags = data.flags(); return data.ReadTimestamp(&out->timestamp) && - data.ReadInterval(&out->interval); + data.ReadInterval(&out->interval) && + data.ReadAvailableTimestamp(&out->available_timestamp) && + data.ReadReadyTimestamp(&out->ready_timestamp) && + data.ReadLatchTimestamp(&out->latch_timestamp); } }; diff --git a/chromium/ui/gfx/native_widget_types.h b/chromium/ui/gfx/native_widget_types.h index 32929a7124c..eb5af9df8ee 100644 --- a/chromium/ui/gfx/native_widget_types.h +++ b/chromium/ui/gfx/native_widget_types.h @@ -7,7 +7,6 @@ #include <stdint.h> -#include "base/logging.h" #include "build/build_config.h" #include "ui/gfx/gfx_export.h" @@ -100,7 +99,7 @@ struct ANativeWindow; namespace ui { class WindowAndroid; class ViewAndroid; -} +} // namespace ui #endif class SkBitmap; @@ -111,6 +110,12 @@ typedef struct _AtkObject AtkObject; } #endif +#if defined(USE_X11) +namespace x11 { +enum class Window : uint32_t; +} +#endif + namespace gfx { #if defined(USE_AURA) @@ -220,7 +225,7 @@ typedef UnimplementedNativeViewAccessible* NativeViewAccessible; const ui::mojom::CursorType kNullCursor = static_cast<ui::mojom::CursorType>(-1); #else -const gfx::NativeCursor kNullCursor = static_cast<gfx::NativeCursor>(NULL); +const gfx::NativeCursor kNullCursor = static_cast<gfx::NativeCursor>(nullptr); #endif // Note: for test_shell we're packing a pointer into the NativeViewId. So, if @@ -233,10 +238,11 @@ typedef intptr_t NativeViewId; // AcceleratedWidget provides a surface to compositors to paint pixels. #if defined(OS_WIN) typedef HWND AcceleratedWidget; -constexpr AcceleratedWidget kNullAcceleratedWidget = NULL; +constexpr AcceleratedWidget kNullAcceleratedWidget = nullptr; #elif defined(USE_X11) -typedef unsigned long AcceleratedWidget; -constexpr AcceleratedWidget kNullAcceleratedWidget = 0; +typedef x11::Window AcceleratedWidget; +constexpr AcceleratedWidget kNullAcceleratedWidget = + static_cast<x11::Window>(0); #elif defined(OS_IOS) typedef UIView* AcceleratedWidget; constexpr AcceleratedWidget kNullAcceleratedWidget = 0; diff --git a/chromium/ui/gfx/nine_image_painter.h b/chromium/ui/gfx/nine_image_painter.h index 9a579edb8ff..36321dc40d7 100644 --- a/chromium/ui/gfx/nine_image_painter.h +++ b/chromium/ui/gfx/nine_image_painter.h @@ -10,7 +10,6 @@ #include <vector> #include "base/gtest_prod_util.h" -#include "base/logging.h" #include "base/macros.h" #include "ui/gfx/gfx_export.h" #include "ui/gfx/image/image_skia.h" diff --git a/chromium/ui/gfx/paint_throbber.cc b/chromium/ui/gfx/paint_throbber.cc index 83a31693365..e59b25bf820 100644 --- a/chromium/ui/gfx/paint_throbber.cc +++ b/chromium/ui/gfx/paint_throbber.cc @@ -4,6 +4,8 @@ #include "ui/gfx/paint_throbber.h" +#include <algorithm> + #include "base/time/time.h" #include "cc/paint/paint_flags.h" #include "third_party/skia/include/core/SkPath.h" @@ -18,14 +20,14 @@ namespace gfx { namespace { // The maximum size of the "spinning" state arc, in degrees. -const int64_t kMaxArcSize = 270; +constexpr int64_t kMaxArcSize = 270; // The amount of time it takes to grow the "spinning" arc from 0 to 270 degrees. -const int64_t kArcTimeMs = 666; +constexpr auto kArcTime = base::TimeDelta::FromSecondsD(2.0 / 3.0); // The amount of time it takes for the "spinning" throbber to make a full // rotation. -const int64_t kRotationTimeMs = 1568; +constexpr auto kRotationTime = base::TimeDelta::FromMilliseconds(1568); void PaintArc(Canvas* canvas, const Rect& bounds, @@ -44,7 +46,7 @@ void PaintArc(Canvas* canvas, Rect oval = bounds; // Inset by half the stroke width to make sure the whole arc is inside // the visible rect. - int inset = SkScalarCeilToInt(*stroke_width / 2.0); + const int inset = SkScalarCeilToInt(*stroke_width / 2.0); oval.Inset(inset, inset); SkPath path; @@ -66,10 +68,10 @@ void CalculateWaitingAngles(const base::TimeDelta& elapsed_time, // the throbber spins counter-clockwise. The finish angle starts at 12 o'clock // (90 degrees) and rotates steadily. The start angle trails 180 degrees // behind, except for the first half revolution, when it stays at 12 o'clock. - base::TimeDelta revolution_time = base::TimeDelta::FromMilliseconds(1320); + constexpr auto kRevolutionTime = base::TimeDelta::FromMilliseconds(1320); int64_t twelve_oclock = 90; int64_t finish_angle_cc = - twelve_oclock + 360 * elapsed_time / revolution_time; + twelve_oclock + 360 * elapsed_time / kRevolutionTime; int64_t start_angle_cc = std::max(finish_angle_cc - 180, twelve_oclock); // Negate the angles to convert to the clockwise numbers Skia expects. @@ -91,31 +93,29 @@ void PaintThrobberSpinningWithStartAngle( // The sweep angle ranges from -270 to 270 over 1333ms. CSS // animation timing functions apply in between key frames, so we have to // break up the 1333ms into two keyframes (-270 to 0, then 0 to 270). - base::TimeDelta arc_time = base::TimeDelta::FromMilliseconds(kArcTimeMs); - double arc_size_progress = static_cast<double>(elapsed_time.InMicroseconds() % - arc_time.InMicroseconds()) / - arc_time.InMicroseconds(); + const double arc_progress = + (elapsed_time % kArcTime).InMicrosecondsF() / kArcTime.InMicrosecondsF(); // This tween is equivalent to cubic-bezier(0.4, 0.0, 0.2, 1). - double sweep = kMaxArcSize * Tween::CalculateValue(Tween::FAST_OUT_SLOW_IN, - arc_size_progress); - int64_t sweep_keyframe = (elapsed_time / arc_time) % 2; + double sweep = kMaxArcSize * + Tween::CalculateValue(Tween::FAST_OUT_SLOW_IN, arc_progress); + const int64_t sweep_keyframe = (elapsed_time / kArcTime) % 2; if (sweep_keyframe == 0) sweep -= kMaxArcSize; // This part makes sure the sweep is at least 5 degrees long. Roughly // equivalent to the "magic constants" in SVG's fillunfill animation. - const double min_sweep_length = 5.0; - if (sweep >= 0.0 && sweep < min_sweep_length) { - start_angle -= (min_sweep_length - sweep); - sweep = min_sweep_length; - } else if (sweep <= 0.0 && sweep > -min_sweep_length) { - start_angle += (-min_sweep_length - sweep); - sweep = -min_sweep_length; + constexpr double kMinSweepLength = 5.0; + if (sweep >= 0.0 && sweep < kMinSweepLength) { + start_angle -= (kMinSweepLength - sweep); + sweep = kMinSweepLength; + } else if (sweep <= 0.0 && sweep > -kMinSweepLength) { + start_angle += (-kMinSweepLength - sweep); + sweep = -kMinSweepLength; } // To keep the sweep smooth, we have an additional rotation after each - // |arc_time| period has elapsed. See SVG's 'rot' animation. - int64_t rot_keyframe = (elapsed_time / (arc_time * 2)) % 4; + // arc period has elapsed. See SVG's 'rot' animation. + const int64_t rot_keyframe = (elapsed_time / (kArcTime * 2)) % 4; PaintArc(canvas, bounds, color, start_angle + rot_keyframe * kMaxArcSize, sweep, stroke_width); } @@ -127,9 +127,7 @@ void PaintThrobberSpinning(Canvas* canvas, SkColor color, const base::TimeDelta& elapsed_time, base::Optional<SkScalar> stroke_width) { - base::TimeDelta rotation_time = - base::TimeDelta::FromMilliseconds(kRotationTimeMs); - int64_t start_angle = 270 + 360 * elapsed_time / rotation_time; + const int64_t start_angle = 270 + 360 * elapsed_time / kRotationTime; PaintThrobberSpinningWithStartAngle(canvas, bounds, color, elapsed_time, start_angle, stroke_width); } @@ -157,35 +155,34 @@ void PaintThrobberSpinningAfterWaiting(Canvas* canvas, // |arc_time_offset| is the effective amount of time one would have to wait // for the "spinning" sweep to match |waiting_sweep|. Brute force calculation. if (waiting_state->arc_time_offset.is_zero()) { - for (int64_t arc_time_it = 0; arc_time_it <= kArcTimeMs; ++arc_time_it) { - double arc_size_progress = static_cast<double>(arc_time_it) / kArcTimeMs; + for (int64_t arc_ms = 0; arc_ms <= kArcTime.InMillisecondsRoundedUp(); + ++arc_ms) { + double arc_size_progress = + std::min(1.0, arc_ms / kArcTime.InMillisecondsF()); if (kMaxArcSize * Tween::CalculateValue(Tween::FAST_OUT_SLOW_IN, arc_size_progress) >= waiting_sweep) { - // Add kArcTimeMs to sidestep the |sweep_keyframe == 0| offset below. - waiting_state->arc_time_offset = - base::TimeDelta::FromMilliseconds(arc_time_it + kArcTimeMs); + // Add kArcTime to sidestep the |sweep_keyframe == 0| offset below. + waiting_state->arc_time_offset = kArcTime * (arc_size_progress + 1); break; } } } // Blend the color between "waiting" and "spinning" states. - base::TimeDelta color_fade_time = base::TimeDelta::FromMilliseconds(900); + constexpr auto kColorFadeTime = base::TimeDelta::FromMilliseconds(900); float color_progress = 1.0f; - if (elapsed_time < color_fade_time) { - color_progress = static_cast<float>(Tween::CalculateValue( + if (elapsed_time < kColorFadeTime) { + color_progress = float{Tween::CalculateValue( Tween::LINEAR_OUT_SLOW_IN, - static_cast<double>(elapsed_time.InMicroseconds()) / - color_fade_time.InMicroseconds())); + elapsed_time.InMicrosecondsF() / kColorFadeTime.InMicrosecondsF())}; } - SkColor blend_color = + const SkColor blend_color = color_utils::AlphaBlend(color, waiting_state->color, color_progress); - int64_t start_angle = - waiting_start_angle + - 360 * elapsed_time / base::TimeDelta::FromMilliseconds(kRotationTimeMs); - base::TimeDelta effective_elapsed_time = + const int64_t start_angle = + waiting_start_angle + 360 * elapsed_time / kRotationTime; + const base::TimeDelta effective_elapsed_time = elapsed_time + waiting_state->arc_time_offset; PaintThrobberSpinningWithStartAngle(canvas, bounds, blend_color, @@ -197,13 +194,15 @@ GFX_EXPORT void PaintNewThrobberWaiting(Canvas* canvas, const RectF& throbber_container_bounds, SkColor color, const base::TimeDelta& elapsed_time) { + // Cycle time for the waiting throbber. + constexpr auto kNewThrobberWaitingCycleTime = base::TimeDelta::FromSeconds(1); + // The throbber bounces back and forth. We map the elapsed time to 0->2. Time // 0->1 represents when the throbber moves left to right, time 1->2 represents // right to left. - float time = - 2.0f * - (elapsed_time.InMilliseconds() % kNewThrobberWaitingAnimationCycleMs) / - kNewThrobberWaitingAnimationCycleMs; + float time = 2.0f * + (elapsed_time % kNewThrobberWaitingCycleTime).InMicrosecondsF() / + kNewThrobberWaitingCycleTime.InMicrosecondsF(); // 1 -> 2 values mirror back to 1 -> 0 values to represent right-to-left. const bool going_back = time > 1.0f; if (going_back) diff --git a/chromium/ui/gfx/paint_throbber.h b/chromium/ui/gfx/paint_throbber.h index a6603757b5b..c5370c40452 100644 --- a/chromium/ui/gfx/paint_throbber.h +++ b/chromium/ui/gfx/paint_throbber.h @@ -68,10 +68,6 @@ GFX_EXPORT void PaintNewThrobberWaiting(Canvas* canvas, SkColor color, const base::TimeDelta& elapsed_time); -// Cycle time for the throbber above. Used to be able to smoothly transition -// between the throbber and the determinite progress-bar animation. -constexpr int kNewThrobberWaitingAnimationCycleMs = 1000; - } // namespace gfx #endif // UI_GFX_PAINT_THROBBER_H_ diff --git a/chromium/ui/gfx/path_mac.mm b/chromium/ui/gfx/path_mac.mm index e74aa2906a2..ae108175d70 100644 --- a/chromium/ui/gfx/path_mac.mm +++ b/chromium/ui/gfx/path_mac.mm @@ -4,8 +4,11 @@ #import "ui/gfx/path_mac.h" +#include <ostream> + #import <Cocoa/Cocoa.h> +#include "base/notreached.h" #include "base/stl_util.h" #include "third_party/skia/include/core/SkPath.h" #include "third_party/skia/include/core/SkRegion.h" diff --git a/chromium/ui/gfx/path_mac_unittest.mm b/chromium/ui/gfx/path_mac_unittest.mm index 0953cc82d68..1f2dff5696d 100644 --- a/chromium/ui/gfx/path_mac_unittest.mm +++ b/chromium/ui/gfx/path_mac_unittest.mm @@ -9,6 +9,7 @@ #import <Cocoa/Cocoa.h> +#include "base/check_op.h" #include "base/stl_util.h" #include "testing/gtest/include/gtest/gtest.h" #include "third_party/skia/include/core/SkPath.h" diff --git a/chromium/ui/gfx/platform_font_ios.mm b/chromium/ui/gfx/platform_font_ios.mm index 018899651bb..5a59235b5d9 100644 --- a/chromium/ui/gfx/platform_font_ios.mm +++ b/chromium/ui/gfx/platform_font_ios.mm @@ -9,6 +9,7 @@ #include <cmath> #import "base/mac/foundation_util.h" +#include "base/notreached.h" #include "base/strings/sys_string_conversions.h" #include "base/strings/utf_string_conversions.h" #include "third_party/skia/include/ports/SkTypeface_mac.h" diff --git a/chromium/ui/gfx/presentation_feedback.h b/chromium/ui/gfx/presentation_feedback.h index f4a6cdde750..8e6e2ad03d2 100644 --- a/chromium/ui/gfx/presentation_feedback.h +++ b/chromium/ui/gfx/presentation_feedback.h @@ -48,6 +48,8 @@ struct PresentationFeedback { return {base::TimeTicks::Now(), base::TimeDelta(), Flags::kFailure}; } + bool failed() const { return !!(flags & Flags::kFailure); } + // The time when a buffer begins scan-out. If a buffer is never presented on // a screen, the |timestamp| will be set to the time of the failure. base::TimeTicks timestamp; @@ -57,12 +59,38 @@ struct PresentationFeedback { // A combination of Flags. It indicates the kind of the |timestamp|. uint32_t flags = 0; + + // The following are additional timestamps that are reported if available on + // the underlying platform. If not available, the timestamp is set to 0. + + // A buffer sent to the system compositor or display controller for + // presentation is returned to chromium's compositor with an out fence for + // synchronization. This fence indicates when reads from this buffer for + // presentation (on the GPU or display controller) have been finished and it + // is safe to write new data to this buffer. Since this fence may not have + // been signalled when the swap for a new frame is issued, this timestamp is + // meant to track the latency from when a swap is issued on the GPU thread to + // when the GPU can start rendering to this buffer. + base::TimeTicks available_timestamp; + + // The time when the GPU has finished completing all the drawing commands on + // the primary plane. On Android, SurfaceFlinger does not latch to a buffer + // until this fence has been signalled. + base::TimeTicks ready_timestamp; + + // The time when the primary plane is latched by the system compositor for its + // next rendering update. On Android this corresponds to the SurfaceFlinger + // latch time. + base::TimeTicks latch_timestamp; }; inline bool operator==(const PresentationFeedback& lhs, const PresentationFeedback& rhs) { return lhs.timestamp == rhs.timestamp && lhs.interval == rhs.interval && - lhs.flags == rhs.flags; + lhs.flags == rhs.flags && + lhs.available_timestamp == rhs.available_timestamp && + lhs.ready_timestamp == rhs.ready_timestamp && + lhs.latch_timestamp == rhs.latch_timestamp; } inline bool operator!=(const PresentationFeedback& lhs, diff --git a/chromium/ui/gfx/range/mojom/BUILD.gn b/chromium/ui/gfx/range/mojom/BUILD.gn index 497acbc59cb..8f58cfd9e09 100644 --- a/chromium/ui/gfx/range/mojom/BUILD.gn +++ b/chromium/ui/gfx/range/mojom/BUILD.gn @@ -7,6 +7,7 @@ import("//mojo/public/tools/bindings/mojom.gni") # This target does NOT depend on skia. One can depend on this target to avoid # picking up a dependency on skia. mojom("mojom") { + generate_java = true sources = [ "range.mojom" ] shared_cpp_typemap = { diff --git a/chromium/ui/gfx/render_text.cc b/chromium/ui/gfx/render_text.cc index ddbad437878..38394380939 100644 --- a/chromium/ui/gfx/render_text.cc +++ b/chromium/ui/gfx/render_text.cc @@ -48,23 +48,11 @@ namespace { // Replacement codepoint for elided text. constexpr base::char16 kEllipsisCodepoint = 0x2026; -// Default color used for the text and cursor. -const SkColor kDefaultColor = SK_ColorBLACK; - -// Default color used for drawing selection background. -const SkColor kDefaultSelectionBackgroundColor = SK_ColorGRAY; - // Fraction of the text size to raise the center of a strike-through line above // the baseline. const SkScalar kStrikeThroughOffset = (SK_Scalar1 * 65 / 252); // Fraction of the text size to lower an underline below the baseline. const SkScalar kUnderlineOffset = (SK_Scalar1 / 9); -// Default fraction of the text size to use for a strike-through or underline. -const SkScalar kLineThicknessFactor = (SK_Scalar1 / 18); - -// Invalid value of baseline. Assigning this value to |baseline_| causes -// re-calculation of baseline. -const int kInvalidBaseline = INT_MAX; // Float comparison needs epsilon to consider rounding errors in float // arithmetic. Epsilon should be dependent on the context and here, we are @@ -338,7 +326,8 @@ void SkiaTextRenderer::DrawUnderline(int x, SkRect r = SkRect::MakeLTRB( x_scalar, y + text_size * kUnderlineOffset, x_scalar + width, y + (text_size * - (kUnderlineOffset + (thickness_factor * kLineThicknessFactor)))); + (kUnderlineOffset + + (thickness_factor * RenderText::kLineThicknessFactor)))); canvas_skia_->drawRect(r, flags_); } @@ -359,7 +348,7 @@ StyleIterator::StyleIterator(const BreakList<SkColor>* colors, const BreakList<BaselineStyle>* baselines, const BreakList<int>* font_size_overrides, const BreakList<Font::Weight>* weights, - const std::vector<BreakList<bool>>* styles) + const StyleArray* styles) : colors_(colors), baselines_(baselines), font_size_overrides_(font_size_overrides), @@ -370,7 +359,7 @@ StyleIterator::StyleIterator(const BreakList<SkColor>* colors, font_size_override_ = font_size_overrides_->breaks().begin(); weight_ = weights_->breaks().begin(); for (size_t i = 0; i < styles_->size(); ++i) - style_.push_back((*styles_)[i].breaks().begin()); + style_[i] = (*styles_)[i].breaks().begin(); } StyleIterator::StyleIterator(const StyleIterator& style) = default; @@ -385,7 +374,7 @@ Range StyleIterator::GetTextBreakingRange() const { Range range = baselines_->GetRange(baseline_); range = range.Intersect(font_size_overrides_->GetRange(font_size_override_)); range = range.Intersect(weights_->GetRange(weight_)); - for (size_t i = 0; i < TEXT_STYLE_COUNT; ++i) + for (size_t i = 0; i < styles_->size(); ++i) range = range.Intersect((*styles_)[i].GetRange(style_[i])); return range; } @@ -397,7 +386,7 @@ void StyleIterator::IncrementToPosition(size_t position) { font_size_override_ = IncrementBreakListIteratorToPosition( *font_size_overrides_, font_size_override_, position); weight_ = IncrementBreakListIteratorToPosition(*weights_, weight_, position); - for (size_t i = 0; i < TEXT_STYLE_COUNT; ++i) { + for (size_t i = 0; i < styles_->size(); ++i) { style_[i] = IncrementBreakListIteratorToPosition((*styles_)[i], style_[i], position); } @@ -439,9 +428,12 @@ void ApplyRenderParams(const FontRenderParams& params, // static constexpr base::char16 RenderText::kPasswordReplacementChar; constexpr bool RenderText::kDragToEndIfOutsideVerticalBounds; +constexpr SkColor RenderText::kDefaultColor; +constexpr SkColor RenderText::kDefaultSelectionBackgroundColor; +constexpr int RenderText::kInvalidBaseline; +constexpr SkScalar RenderText::kLineThicknessFactor; -RenderText::~RenderText() { -} +RenderText::~RenderText() = default; // static std::unique_ptr<RenderText> RenderText::CreateRenderText() { @@ -475,12 +467,12 @@ void RenderText::SetText(const base::string16& text) { // Clear style ranges as they might break new text graphemes and apply // the first style to the whole text instead. - colors_.SetValue(colors_.breaks().begin()->second); - baselines_.SetValue(baselines_.breaks().begin()->second); - font_size_overrides_.SetValue(font_size_overrides_.breaks().begin()->second); - weights_.SetValue(weights_.breaks().begin()->second); - for (size_t style = 0; style < TEXT_STYLE_COUNT; ++style) - styles_[style].SetValue(styles_[style].breaks().begin()->second); + colors_.SetValue(colors_.breaks().front().second); + baselines_.SetValue(baselines_.breaks().front().second); + font_size_overrides_.SetValue(font_size_overrides_.breaks().front().second); + weights_.SetValue(weights_.breaks().front().second); + for (auto& style : styles_) + style.SetValue(style.breaks().front().second); cached_bounds_and_offset_valid_ = false; // Reset selection model. SetText should always followed by SetSelectionModel @@ -500,6 +492,11 @@ void RenderText::AppendText(const base::string16& text) { UpdateStyleLengths(); cached_bounds_and_offset_valid_ = false; obscured_reveal_index_ = -1; + + // Invalidate the cached text direction if it depends on the text contents. + if (directionality_mode_ == DIRECTIONALITY_FROM_TEXT) + text_direction_ = base::i18n::UNKNOWN_DIRECTION; + OnTextAttributeChanged(); } @@ -577,6 +574,13 @@ size_t RenderText::GetNumLines() { return GetShapedText()->lines().size(); } +size_t RenderText::GetTextIndexOfLine(size_t line) { + const std::vector<internal::Line>& lines = GetShapedText()->lines(); + if (line >= lines.size()) + return text_.size(); + return DisplayIndexToTextIndex(lines[line].display_text_index); +} + void RenderText::SetWordWrapBehavior(WordWrapBehavior behavior) { if (word_wrap_behavior_ != behavior) { word_wrap_behavior_ = behavior; @@ -876,8 +880,17 @@ void RenderText::SetDirectionalityMode(DirectionalityMode mode) { } } +base::i18n::TextDirection RenderText::GetTextDirection() const { + if (text_direction_ == base::i18n::UNKNOWN_DIRECTION) + text_direction_ = GetTextDirectionForGivenText(text_); + return text_direction_; +} + base::i18n::TextDirection RenderText::GetDisplayTextDirection() { - return GetTextDirection(GetDisplayText()); + EnsureLayout(); + if (display_text_direction_ == base::i18n::UNKNOWN_DIRECTION) + display_text_direction_ = GetTextDirectionForGivenText(GetDisplayText()); + return display_text_direction_; } VisualCursorDirection RenderText::GetVisualDirectionOfLogicalEnd() { @@ -1325,37 +1338,7 @@ Range RenderText::GetLineRange(const base::string16& text, return Range(min_index, max_index); } -RenderText::RenderText() - : horizontal_alignment_(base::i18n::IsRTL() ? ALIGN_RIGHT : ALIGN_LEFT), - vertical_alignment_(ALIGN_MIDDLE), - directionality_mode_(DIRECTIONALITY_FROM_TEXT), - text_direction_(base::i18n::UNKNOWN_DIRECTION), - cursor_enabled_(true), - has_directed_selection_(kSelectionIsAlwaysDirected), - selection_color_(kDefaultColor), - selection_background_focused_color_(kDefaultSelectionBackgroundColor), - focused_(false), - composition_range_(Range::InvalidRange()), - colors_(kDefaultColor), - baselines_(NORMAL_BASELINE), - font_size_overrides_(0), - weights_(Font::Weight::NORMAL), - styles_(TEXT_STYLE_COUNT), - layout_styles_(TEXT_STYLE_COUNT), - obscured_(false), - obscured_reveal_index_(-1), - truncate_length_(0), - elide_behavior_(NO_ELIDE), - text_elided_(false), - min_line_height_(0), - multiline_(false), - max_lines_(0), - word_wrap_behavior_(IGNORE_LONG_WORDS), - subpixel_rendering_suppressed_(false), - clip_to_display_rect_(true), - baseline_(kInvalidBaseline), - cached_bounds_and_offset_valid_(false), - strike_thickness_factor_(kLineThicknessFactor) {} +RenderText::RenderText() = default; internal::StyleIterator RenderText::GetTextStyleIterator() const { return internal::StyleIterator(&colors_, &baselines_, &font_size_overrides_, @@ -1372,13 +1355,13 @@ internal::StyleIterator RenderText::GetLayoutTextStyleIterator() const { bool RenderText::IsHomogeneous() const { if (colors().breaks().size() > 1 || baselines().breaks().size() > 1 || font_size_overrides().breaks().size() > 1 || - weights().breaks().size() > 1) + weights().breaks().size() > 1) { return false; - for (size_t style = 0; style < TEXT_STYLE_COUNT; ++style) { - if (styles()[style].breaks().size() > 1) - return false; } - return true; + + return std::none_of( + styles().cbegin(), styles().cend(), + [](const auto& style) { return style.breaks().size() > 1; }); } internal::ShapedText* RenderText::GetShapedText() { @@ -1474,6 +1457,8 @@ void RenderText::EnsureLayoutTextUpdated() const { layout_text_.clear(); text_to_display_indices_.clear(); + display_text_direction_ = base::i18n::UNKNOWN_DIRECTION; + // Reset the previous layout text attributes. Allocate enough space for // layout text attributes (upper limit to 2x characters per codepoint). The // actual size will be updated at the end of the function. @@ -1577,7 +1562,7 @@ void RenderText::EnsureLayoutTextUpdated() const { layout_font_size_overrides_.ApplyValue(styles.font_size_override(), range); layout_weights_.ApplyValue(styles.weight(), range); - for (size_t i = 0; i < TEXT_STYLE_COUNT; ++i) { + for (size_t i = 0; i < layout_styles_.size(); ++i) { layout_styles_[i].ApplyValue(styles.style(static_cast<TextStyle>(i)), range); } @@ -1850,53 +1835,44 @@ void RenderText::ApplyTextShadows(internal::SkiaTextRenderer* renderer) { renderer->SetDrawLooper(CreateShadowDrawLooper(shadows_)); } -base::i18n::TextDirection RenderText::GetTextDirection( - const base::string16& text) { - if (text_direction_ == base::i18n::UNKNOWN_DIRECTION) { - switch (directionality_mode_) { - case DIRECTIONALITY_FROM_TEXT: - // Derive the direction from the display text, which differs from text() - // in the case of obscured (password) textfields. - text_direction_ = - base::i18n::GetFirstStrongCharacterDirection(text); - break; - case DIRECTIONALITY_FROM_UI: - text_direction_ = base::i18n::IsRTL() ? base::i18n::RIGHT_TO_LEFT : - base::i18n::LEFT_TO_RIGHT; - break; - case DIRECTIONALITY_FORCE_LTR: - text_direction_ = base::i18n::LEFT_TO_RIGHT; - break; - case DIRECTIONALITY_FORCE_RTL: - text_direction_ = base::i18n::RIGHT_TO_LEFT; - break; - case DIRECTIONALITY_AS_URL: - // Rendering as a URL implies left-to-right paragraph direction. - // URL Standard specifies that a URL "should be rendered as if it were - // in a left-to-right embedding". - // https://url.spec.whatwg.org/#url-rendering - // - // Consider logical string for domain "ABC.com/hello" (where ABC are - // Hebrew (RTL) characters). The normal Bidi algorithm renders this as - // "com/hello.CBA"; by forcing LTR, it is rendered as "CBA.com/hello". - // - // Note that this only applies a LTR embedding at the top level; it - // doesn't change the Bidi algorithm, so there are still some URLs that - // will render in a confusing order. Consider the logical string - // "abc.COM/HELLO/world", which will render as "abc.OLLEH/MOC/world". - // See https://crbug.com/351639. - // - // Note that the LeftToRightUrls feature flag enables additional - // behaviour for DIRECTIONALITY_AS_URL, but the left-to-right embedding - // behaviour is always enabled, regardless of the flag. - text_direction_ = base::i18n::LEFT_TO_RIGHT; - break; - default: - NOTREACHED(); - } +base::i18n::TextDirection RenderText::GetTextDirectionForGivenText( + const base::string16& text) const { + switch (directionality_mode_) { + case DIRECTIONALITY_FROM_TEXT: + // Derive the direction from the display text, which differs from text() + // in the case of obscured (password) textfields. + return base::i18n::GetFirstStrongCharacterDirection(text); + case DIRECTIONALITY_FROM_UI: + return base::i18n::IsRTL() ? base::i18n::RIGHT_TO_LEFT + : base::i18n::LEFT_TO_RIGHT; + case DIRECTIONALITY_FORCE_LTR: + return base::i18n::LEFT_TO_RIGHT; + case DIRECTIONALITY_FORCE_RTL: + return base::i18n::RIGHT_TO_LEFT; + case DIRECTIONALITY_AS_URL: + // Rendering as a URL implies left-to-right paragraph direction. + // URL Standard specifies that a URL "should be rendered as if it were + // in a left-to-right embedding". + // https://url.spec.whatwg.org/#url-rendering + // + // Consider logical string for domain "ABC.com/hello" (where ABC are + // Hebrew (RTL) characters). The normal Bidi algorithm renders this as + // "com/hello.CBA"; by forcing LTR, it is rendered as "CBA.com/hello". + // + // Note that this only applies a LTR embedding at the top level; it + // doesn't change the Bidi algorithm, so there are still some URLs that + // will render in a confusing order. Consider the logical string + // "abc.COM/HELLO/world", which will render as "abc.OLLEH/MOC/world". + // See https://crbug.com/351639. + // + // Note that the LeftToRightUrls feature flag enables additional + // behaviour for DIRECTIONALITY_AS_URL, but the left-to-right embedding + // behaviour is always enabled, regardless of the flag. + return base::i18n::LEFT_TO_RIGHT; + default: + NOTREACHED(); + return base::i18n::UNKNOWN_DIRECTION; } - - return text_direction_; } void RenderText::UpdateStyleLengths() { @@ -2041,7 +2017,7 @@ base::string16 RenderText::Elide(const base::string16& text, // length of text.length(), not text.length()-1. float lo_width = 0; float hi_width = text_width; - const base::i18n::TextDirection text_direction = GetTextDirection(text); + const base::i18n::TextDirection text_direction = GetTextDirection(); while (lo <= hi) { // Linearly interpolate between |lo| and |hi|, which correspond to widths // of |lo_width| and |hi_width| to estimate at what position @@ -2100,8 +2076,8 @@ base::string16 RenderText::Elide(const base::string16& text, // Restore styles and baselines without breaking multi-character graphemes. render_text->styles_ = styles_; - for (size_t style = 0; style < TEXT_STYLE_COUNT; ++style) - RestoreBreakList(render_text.get(), &render_text->styles_[style]); + for (auto& style : render_text->styles_) + RestoreBreakList(render_text.get(), &style); RestoreBreakList(render_text.get(), &render_text->baselines_); RestoreBreakList(render_text.get(), &render_text->font_size_overrides_); render_text->weights_ = weights_; @@ -2237,7 +2213,7 @@ internal::GraphemeIterator RenderText::GetGraphemeIteratorAtIndex( } void RenderText::DrawSelections(Canvas* canvas, - const std::vector<Range> selections) { + const std::vector<Range>& selections) { for (auto selection : selections) { if (!selection.is_empty()) { for (Rect s : GetSubstringBounds(selection)) { diff --git a/chromium/ui/gfx/render_text.h b/chromium/ui/gfx/render_text.h index 8cfbd5c5c4a..9ef49c1dfda 100644 --- a/chromium/ui/gfx/render_text.h +++ b/chromium/ui/gfx/render_text.h @@ -9,6 +9,7 @@ #include <stdint.h> #include <algorithm> +#include <array> #include <cstring> #include <memory> #include <string> @@ -91,6 +92,7 @@ struct TextToDisplayIndex { }; using TextToDisplaySequence = std::vector<TextToDisplayIndex>; using GraphemeIterator = TextToDisplaySequence::const_iterator; +using StyleArray = std::array<BreakList<bool>, TEXT_STYLE_COUNT>; // Internal helper class used to iterate colors, baselines, and styles. class StyleIterator { @@ -99,7 +101,7 @@ class StyleIterator { const BreakList<BaselineStyle>* baselines, const BreakList<int>* font_size_overrides, const BreakList<Font::Weight>* weights, - const std::vector<BreakList<bool>>* styles); + const StyleArray* styles); StyleIterator(const StyleIterator& style); ~StyleIterator(); StyleIterator& operator=(const StyleIterator& style); @@ -128,13 +130,13 @@ class StyleIterator { const BreakList<BaselineStyle>* baselines_; const BreakList<int>* font_size_overrides_; const BreakList<Font::Weight>* weights_; - const std::vector<BreakList<bool>>* styles_; + const StyleArray* styles_; BreakList<SkColor>::const_iterator color_; BreakList<BaselineStyle>::const_iterator baseline_; BreakList<int>::const_iterator font_size_override_; BreakList<Font::Weight>::const_iterator weight_; - std::vector<BreakList<bool>::const_iterator> style_; + std::array<BreakList<bool>::const_iterator, TEXT_STYLE_COUNT> style_; }; // Line segments are slices of the display text to be rendered on a single line. @@ -172,6 +174,9 @@ struct Line { // Maximum baseline of all segments on this line. int baseline; + + // The text index of this line in |text_|. + int display_text_index = 0; }; // Internal class that contains the results of the text layout and shaping. @@ -217,6 +222,19 @@ class GFX_EXPORT RenderText { static constexpr bool kSelectionIsAlwaysDirected = true; #endif + // Default color used for the text and cursor. + static constexpr SkColor kDefaultColor = SK_ColorBLACK; + + // Default color used for drawing selection background. + static constexpr SkColor kDefaultSelectionBackgroundColor = SK_ColorGRAY; + + // Invalid value of baseline. Assigning this value to |baseline_| causes + // re-calculation of baseline. + static constexpr int kInvalidBaseline = INT_MAX; + + // Default fraction of the text size to use for a strike-through or underline. + static constexpr SkScalar kLineThicknessFactor = (SK_Scalar1 / 18); + // The character used for displaying obscured text. Use a bullet character. // TODO(pbos): This is highly font dependent, consider replacing the character // with a vector glyph. @@ -298,6 +316,10 @@ class GFX_EXPORT RenderText { // Returns the actual number of lines, broken by |lines_|. size_t GetNumLines(); + // Returns the text index of the given line |line|. Returns the text length + // for any |line| above the number of lines. + size_t GetTextIndexOfLine(size_t line); + // TODO(mukai): ELIDE_LONG_WORDS is not supported. WordWrapBehavior word_wrap_behavior() const { return word_wrap_behavior_; } void SetWordWrapBehavior(WordWrapBehavior behavior); @@ -434,6 +456,8 @@ class GFX_EXPORT RenderText { DirectionalityMode directionality_mode() const { return directionality_mode_; } + + base::i18n::TextDirection GetTextDirection() const; base::i18n::TextDirection GetDisplayTextDirection(); // Returns the visual movement direction corresponding to the logical @@ -632,7 +656,7 @@ class GFX_EXPORT RenderText { return font_size_overrides_; } const BreakList<Font::Weight>& weights() const { return weights_; } - const std::vector<BreakList<bool>>& styles() const { return styles_; } + const internal::StyleArray& styles() const { return styles_; } SkScalar strike_thickness_factor() const { return strike_thickness_factor_; } const BreakList<SkColor>& layout_colors() const { return layout_colors_; } @@ -730,7 +754,7 @@ class GFX_EXPORT RenderText { // Draw all text and make the given ranges appear selected. virtual void DrawVisualText(internal::SkiaTextRenderer* renderer, - const std::vector<Range> selections) = 0; + const std::vector<Range>& selections) = 0; // Update the display text. void UpdateDisplayText(float text_width); @@ -756,7 +780,8 @@ class GFX_EXPORT RenderText { // Get the text direction for the current directionality mode and given // |text|. - base::i18n::TextDirection GetTextDirection(const base::string16& text); + base::i18n::TextDirection GetTextDirectionForGivenText( + const base::string16& text) const; // Adjust ranged styles to accommodate a new |text_| length. void UpdateStyleLengths(); @@ -821,7 +846,7 @@ class GFX_EXPORT RenderText { void UpdateCachedBoundsAndOffset(); // Draws the specified ranges of text with a selected appearance. - void DrawSelections(Canvas* canvas, const std::vector<Range> selections); + void DrawSelections(Canvas* canvas, const std::vector<Range>& selections); // Returns a grapheme iterator that contains the codepoint at |index|. internal::GraphemeIterator GetGraphemeIteratorAtIndex( @@ -857,18 +882,22 @@ class GFX_EXPORT RenderText { // Horizontal alignment of the text with respect to |display_rect_|. The // default is to align left if the application UI is LTR and right if RTL. - HorizontalAlignment horizontal_alignment_; + HorizontalAlignment horizontal_alignment_{base::i18n::IsRTL() ? ALIGN_RIGHT + : ALIGN_LEFT}; // Vertical alignment of the text with respect to |display_rect_|. Only // applicable when |multiline_| is true. The default is to align center. - VerticalAlignment vertical_alignment_; + VerticalAlignment vertical_alignment_ = ALIGN_MIDDLE; // The text directionality mode, defaults to DIRECTIONALITY_FROM_TEXT. - DirectionalityMode directionality_mode_; + DirectionalityMode directionality_mode_ = DIRECTIONALITY_FROM_TEXT; // The cached text direction, potentially computed from the text or UI locale. // Use GetTextDirection(), do not use this potentially invalid value directly! - base::i18n::TextDirection text_direction_; + mutable base::i18n::TextDirection text_direction_ = + base::i18n::UNKNOWN_DIRECTION; + mutable base::i18n::TextDirection display_text_direction_ = + base::i18n::UNKNOWN_DIRECTION; // A list of fonts used to render |text_|. FontList font_list_; @@ -881,19 +910,20 @@ class GFX_EXPORT RenderText { // Specifies whether the cursor is enabled. If disabled, no space is reserved // for the cursor when positioning text. - bool cursor_enabled_; + bool cursor_enabled_ = true; // Whether the current selection has a known direction. That is, whether a // directional input (e.g. arrow key) has been received for the current // selection to indicate which end of the selection has the caret. When true, // directed inputs preserve (rather than replace) the selection affinity. - bool has_directed_selection_; + bool has_directed_selection_ = kSelectionIsAlwaysDirected; // The color used for drawing selected text. - SkColor selection_color_; + SkColor selection_color_ = kDefaultColor; // The background color used for drawing the selection when focused. - SkColor selection_background_focused_color_; + SkColor selection_background_focused_color_ = + kDefaultSelectionBackgroundColor; // Whether the selection visual bounds should be expanded vertically to be // vertically symmetric with respect to the display rect. Note this flag has @@ -901,25 +931,25 @@ class GFX_EXPORT RenderText { bool symmetric_selection_visual_bounds_ = false; // The focus state of the text. - bool focused_; + bool focused_ = false; // Composition text range. - Range composition_range_; + Range composition_range_ = Range::InvalidRange(); // Color, baseline, and style breaks, used to modify ranges of text. // BreakList positions are stored with text indices, not display indices. // TODO(msw): Expand to support cursor, selection, background, etc. colors. - BreakList<SkColor> colors_; - BreakList<BaselineStyle> baselines_; - BreakList<int> font_size_overrides_; - BreakList<Font::Weight> weights_; - std::vector<BreakList<bool>> styles_; + BreakList<SkColor> colors_{kDefaultColor}; + BreakList<BaselineStyle> baselines_{NORMAL_BASELINE}; + BreakList<int> font_size_overrides_{0}; + BreakList<Font::Weight> weights_{Font::Weight::NORMAL}; + internal::StyleArray styles_; mutable BreakList<SkColor> layout_colors_; mutable BreakList<BaselineStyle> layout_baselines_; mutable BreakList<int> layout_font_size_overrides_; mutable BreakList<Font::Weight> layout_weights_; - mutable std::vector<BreakList<bool>> layout_styles_; + mutable internal::StyleArray layout_styles_; // A mapping from text to display text indices for each grapheme. The vector // contains an ordered sequence of indice pairs. Both sequence |text_index| @@ -927,12 +957,12 @@ class GFX_EXPORT RenderText { mutable internal::TextToDisplaySequence text_to_display_indices_; // A flag to obscure actual text with asterisks for password fields. - bool obscured_; + bool obscured_ = false; // The index at which the char should be revealed in the obscured text. - int obscured_reveal_index_; + int obscured_reveal_index_ = -1; // The maximum length of text to display, 0 forgoes a hard limit. - size_t truncate_length_; + size_t truncate_length_ = 0; // The obscured and/or truncated text used to layout the text to display. mutable base::string16 layout_text_; @@ -945,31 +975,31 @@ class GFX_EXPORT RenderText { mutable base::string16 display_text_; // The behavior for eliding, fading, or truncating. - ElideBehavior elide_behavior_; + ElideBehavior elide_behavior_ = NO_ELIDE; // The behavior for eliding whitespace when eliding or truncating. - base::Optional<bool> whitespace_elision_ = base::nullopt; + base::Optional<bool> whitespace_elision_; // True if the text is elided given the current behavior and display area. - bool text_elided_; + bool text_elided_ = false; // The minimum height a line should have. - int min_line_height_; + int min_line_height_ = 0; // Whether the text should be broken into multiple lines. Uses the width of // |display_rect_| as the width cap. - bool multiline_; + bool multiline_ = false; // If multiple lines, the maximum number of lines to render, or 0. - size_t max_lines_; + size_t max_lines_ = 0; // The wrap behavior when the text is broken into lines. Do nothing unless // |multiline_| is set. The default value is IGNORE_LONG_WORDS. - WordWrapBehavior word_wrap_behavior_; + WordWrapBehavior word_wrap_behavior_ = IGNORE_LONG_WORDS; // Set to true to suppress subpixel rendering due to non-font reasons (eg. // if the background is transparent). The default value is false. - bool subpixel_rendering_suppressed_; + bool subpixel_rendering_suppressed_ = false; // The local display area for rendering the text. Rect display_rect_; @@ -978,7 +1008,7 @@ class GFX_EXPORT RenderText { // that results in incorrect clipping when drawing to the document margins. // This field allows disabling clipping to work around the issue. // TODO(asvitkine): Remove this when the underlying Skia bug is fixed. - bool clip_to_display_rect_; + bool clip_to_display_rect_ = true; // The offset for the text to be drawn, relative to the display area. // Get this point with GetUpdatedDisplayOffset (or risk using a stale value). @@ -987,11 +1017,11 @@ class GFX_EXPORT RenderText { // The baseline of the text. This is determined from the height of the // display area and the cap height of the font list so the text is vertically // centered. - int baseline_; + int baseline_ = kInvalidBaseline; // The cached bounds and offset are invalidated by changes to the cursor, // selection, font, and other operations that adjust the visible text bounds. - bool cached_bounds_and_offset_valid_; + bool cached_bounds_and_offset_valid_ = false; // Text shadows to be drawn. ShadowValues shadows_; @@ -1004,7 +1034,7 @@ class GFX_EXPORT RenderText { std::unique_ptr<internal::ShapedText> shaped_text_; // The ratio of strike-through line thickness to text height. - SkScalar strike_thickness_factor_; + SkScalar strike_thickness_factor_ = kLineThicknessFactor; // Extra spacing placed between glyphs; used only for obscured text styling. int obscured_glyph_spacing_ = 0; diff --git a/chromium/ui/gfx/render_text_api_fuzzer.cc b/chromium/ui/gfx/render_text_api_fuzzer.cc index 834a6fc8092..fc642e160bf 100644 --- a/chromium/ui/gfx/render_text_api_fuzzer.cc +++ b/chromium/ui/gfx/render_text_api_fuzzer.cc @@ -9,6 +9,7 @@ #include "base/at_exit.h" #include "base/command_line.h" #include "base/i18n/icu_util.h" +#include "base/logging.h" #include "base/strings/utf_string_conversions.h" #include "base/test/task_environment.h" #include "base/test/test_timeouts.h" diff --git a/chromium/ui/gfx/render_text_fuzzer.cc b/chromium/ui/gfx/render_text_fuzzer.cc index a4b430d3aa7..9ce8bc6680d 100644 --- a/chromium/ui/gfx/render_text_fuzzer.cc +++ b/chromium/ui/gfx/render_text_fuzzer.cc @@ -5,6 +5,7 @@ #include "base/at_exit.h" #include "base/command_line.h" #include "base/i18n/icu_util.h" +#include "base/logging.h" #include "base/strings/utf_string_conversions.h" #include "base/test/task_environment.h" #include "base/test/test_timeouts.h" diff --git a/chromium/ui/gfx/render_text_harfbuzz.cc b/chromium/ui/gfx/render_text_harfbuzz.cc index 540c1fc200f..64d00af2c83 100644 --- a/chromium/ui/gfx/render_text_harfbuzz.cc +++ b/chromium/ui/gfx/render_text_harfbuzz.cc @@ -485,6 +485,14 @@ class HarfBuzzLineBreaker { // Finishes line breaking and outputs the results. Can be called at most once. void FinalizeLines(std::vector<internal::Line>* lines, SizeF* size) { DCHECK(!lines_.empty()); + // If the last character of the text is a new line character, then the last + // line is any empty string, which contains no segments. This means that the + // display_text_index will not have been set in AdvanceLine. So here, set + // display_text_index to the text length, which is the true text index of + // the final line. + internal::Line* line = &lines_.back(); + if (line->display_text_index == 0) + line->display_text_index = text_.size(); // Add an empty line to finish the line size calculation and remove it. AdvanceLine(); lines_.pop_back(); @@ -505,6 +513,11 @@ class HarfBuzzLineBreaker { void AdvanceLine() { if (!lines_.empty()) { internal::Line* line = &lines_.back(); + // Compute the line start while the line segments are in the logical order + // so that the start of the line is the start of the char range, + // regardless of i18n. + if (!line->segments.empty()) + line->display_text_index = line->segments[0].char_range.start(); std::sort(line->segments.begin(), line->segments.end(), [this](const internal::LineSegment& s1, const internal::LineSegment& s2) -> bool { @@ -1685,7 +1698,7 @@ void RenderTextHarfBuzz::EnsureLayout() { } void RenderTextHarfBuzz::DrawVisualText(internal::SkiaTextRenderer* renderer, - const std::vector<Range> selections) { + const std::vector<Range>& selections) { DCHECK(!update_layout_run_list_); DCHECK(!update_display_run_list_); DCHECK(!update_display_text_); @@ -1834,7 +1847,7 @@ void RenderTextHarfBuzz::ItemizeTextToRuns( // to misbehave since they expect non-zero text metrics from a non-empty text. ui::gfx::BiDiLineIterator bidi_iterator; - if (!bidi_iterator.Open(text, GetTextDirection(text))) { + if (!bidi_iterator.Open(text, GetTextDirectionForGivenText(text))) { auto run = std::make_unique<internal::TextRunHarfBuzz>( font_list().GetPrimaryFont()); run->range = Range(0, text.length()); diff --git a/chromium/ui/gfx/render_text_harfbuzz.h b/chromium/ui/gfx/render_text_harfbuzz.h index d6ae49ec29b..00e51764481 100644 --- a/chromium/ui/gfx/render_text_harfbuzz.h +++ b/chromium/ui/gfx/render_text_harfbuzz.h @@ -229,7 +229,7 @@ class GFX_EXPORT RenderTextHarfBuzz : public RenderText { void OnDisplayTextAttributeChanged() override; void EnsureLayout() override; void DrawVisualText(internal::SkiaTextRenderer* renderer, - const std::vector<Range> selections) override; + const std::vector<Range>& selections) override; private: friend class test::RenderTextTestApi; diff --git a/chromium/ui/gfx/render_text_test_api.h b/chromium/ui/gfx/render_text_test_api.h index 1bffccea4a9..3111f1d494c 100644 --- a/chromium/ui/gfx/render_text_test_api.h +++ b/chromium/ui/gfx/render_text_test_api.h @@ -60,9 +60,7 @@ class RenderTextTestApi { return render_text_->weights(); } - const std::vector<BreakList<bool>>& styles() const { - return render_text_->styles(); - } + const internal::StyleArray& styles() const { return render_text_->styles(); } const std::vector<internal::Line>& lines() const { return render_text_->GetShapedText()->lines(); diff --git a/chromium/ui/gfx/render_text_unittest.cc b/chromium/ui/gfx/render_text_unittest.cc index 092e40ae6e9..2fd63b1b75f 100644 --- a/chromium/ui/gfx/render_text_unittest.cc +++ b/chromium/ui/gfx/render_text_unittest.cc @@ -72,6 +72,9 @@ const char kLtrRtlLtr[] = "a\u05d1b"; const char kRtlLtr[] = "\u05d0\u05d1a"; const char kRtlLtrRtl[] = "\u05d0a\u05d1"; +constexpr bool kUseWordWrap = true; +constexpr bool kUseObscuredText = true; + // Bitmasks based on gfx::TextStyle. enum { ITALIC_MASK = 1 << TEXT_STYLE_ITALIC, @@ -2194,7 +2197,7 @@ TEST_F(RenderTextTest, MultilineElideBiDi) { render_text->SetMaxLines(2); render_text->SetElideBehavior(ELIDE_TAIL); render_text->SetDisplayRect(Rect(30, 0)); - render_text->GetStringSize(); + test_api()->EnsureLayout(); EXPECT_EQ(render_text->GetDisplayText(), UTF8ToUTF16("אa\nbcdבג") + base::string16(kEllipsisUTF16)); @@ -2890,6 +2893,94 @@ TEST_F(RenderTextTest, MoveCursor_UpDown_Cache) { &expected_range); } +TEST_F(RenderTextTest, GetTextDirectionInvalidation) { + RenderText* render_text = GetRenderText(); + ASSERT_EQ(render_text->directionality_mode(), DIRECTIONALITY_FROM_TEXT); + + const base::i18n::TextDirection original_text_direction = + render_text->GetTextDirection(); + + render_text->SetText(ASCIIToUTF16("a")); + EXPECT_EQ(base::i18n::LEFT_TO_RIGHT, render_text->GetTextDirection()); + + render_text->SetText(WideToUTF16(L"\u05d0")); + EXPECT_EQ(base::i18n::RIGHT_TO_LEFT, render_text->GetTextDirection()); + + // The codepoints u+2026 (ellipsis) has no strong direction. + render_text->SetText(WideToUTF16(L"\u2026")); + EXPECT_EQ(original_text_direction, render_text->GetTextDirection()); + render_text->AppendText(ASCIIToUTF16("a")); + EXPECT_EQ(base::i18n::LEFT_TO_RIGHT, render_text->GetTextDirection()); + + render_text->SetText(WideToUTF16(L"\u2026")); + EXPECT_EQ(original_text_direction, render_text->GetTextDirection()); + render_text->AppendText(WideToUTF16(L"\u05d0")); + EXPECT_EQ(base::i18n::RIGHT_TO_LEFT, render_text->GetTextDirection()); +} + +TEST_F(RenderTextTest, GetDisplayTextDirectionInvalidation) { + RenderText* render_text = GetRenderText(); + ASSERT_EQ(render_text->directionality_mode(), DIRECTIONALITY_FROM_TEXT); + + const base::i18n::TextDirection original_text_direction = + render_text->GetDisplayTextDirection(); + + render_text->SetText(ASCIIToUTF16("a")); + EXPECT_EQ(base::i18n::LEFT_TO_RIGHT, render_text->GetDisplayTextDirection()); + + render_text->SetText(WideToUTF16(L"\u05d0")); + EXPECT_EQ(base::i18n::RIGHT_TO_LEFT, render_text->GetDisplayTextDirection()); + + // The codepoints u+2026 (ellipsis) has no strong direction. + render_text->SetText(WideToUTF16(L"\u2026")); + EXPECT_EQ(original_text_direction, render_text->GetDisplayTextDirection()); + render_text->AppendText(ASCIIToUTF16("a")); + EXPECT_EQ(base::i18n::LEFT_TO_RIGHT, render_text->GetDisplayTextDirection()); + + render_text->SetText(WideToUTF16(L"\u2026")); + EXPECT_EQ(original_text_direction, render_text->GetDisplayTextDirection()); + render_text->AppendText(WideToUTF16(L"\u05d0")); + EXPECT_EQ(base::i18n::RIGHT_TO_LEFT, render_text->GetDisplayTextDirection()); +} + +TEST_F(RenderTextTest, GetTextDirectionWithDifferentDirection) { + SetGlyphWidth(10); + RenderText* render_text = GetRenderText(); + ASSERT_EQ(render_text->directionality_mode(), DIRECTIONALITY_FROM_TEXT); + render_text->SetWhitespaceElision(false); + render_text->SetText(WideToUTF16(L"123\u0638xyz")); + render_text->SetElideBehavior(ELIDE_HEAD); + render_text->SetDisplayRect(Rect(25, 100)); + + // The elided text is an ellipsis with neutral directionality, and a 'z' with + // a strong LTR directionality. + EXPECT_EQ(WideToUTF16(L"\u2026z"), render_text->GetDisplayText()); + EXPECT_EQ(base::i18n::RIGHT_TO_LEFT, render_text->GetTextDirection()); + EXPECT_EQ(base::i18n::LEFT_TO_RIGHT, render_text->GetDisplayTextDirection()); +} + +TEST_F(RenderTextTest, DirectionalityInvalidation) { + RenderText* render_text = GetRenderText(); + ASSERT_EQ(render_text->directionality_mode(), DIRECTIONALITY_FROM_TEXT); + + // The codepoints u+2026 (ellipsis) has weak directionality. + render_text->SetText(WideToUTF16(L"\u2026")); + const base::i18n::TextDirection original_text_direction = + render_text->GetTextDirection(); + + render_text->SetDirectionalityMode(DIRECTIONALITY_FORCE_LTR); + EXPECT_EQ(base::i18n::LEFT_TO_RIGHT, render_text->GetTextDirection()); + EXPECT_EQ(base::i18n::LEFT_TO_RIGHT, render_text->GetDisplayTextDirection()); + + render_text->SetDirectionalityMode(DIRECTIONALITY_FORCE_RTL); + EXPECT_EQ(base::i18n::RIGHT_TO_LEFT, render_text->GetTextDirection()); + EXPECT_EQ(base::i18n::RIGHT_TO_LEFT, render_text->GetDisplayTextDirection()); + + render_text->SetDirectionalityMode(DIRECTIONALITY_FROM_TEXT); + EXPECT_EQ(original_text_direction, render_text->GetTextDirection()); + EXPECT_EQ(original_text_direction, render_text->GetDisplayTextDirection()); +} + TEST_F(RenderTextTest, GetDisplayTextDirection) { struct { const char* text; @@ -2946,6 +3037,96 @@ TEST_F(RenderTextTest, GetDisplayTextDirection) { EXPECT_EQ(render_text->GetDisplayTextDirection(), base::i18n::RIGHT_TO_LEFT); } +struct GetTextIndexOfLineCase { + const char* test_name; + const wchar_t* const text; + const std::vector<size_t> line_breaks; + const bool set_word_wrap = false; + const bool set_obscured = false; +}; + +class RenderTextTestWithGetTextIndexOfLineCase + : public RenderTextTest, + public ::testing::WithParamInterface<GetTextIndexOfLineCase> { + public: + static std::string ParamInfoToString( + ::testing::TestParamInfo<GetTextIndexOfLineCase> param_info) { + return param_info.param.test_name; + } +}; + +TEST_P(RenderTextTestWithGetTextIndexOfLineCase, GetTextIndexOfLine) { + GetTextIndexOfLineCase param = GetParam(); + RenderText* render_text = GetRenderText(); + render_text->SetMultiline(true); + SetGlyphWidth(10); + if (param.set_word_wrap) { + render_text->SetDisplayRect(Rect(1, 1000)); + render_text->SetWordWrapBehavior(WRAP_LONG_WORDS); + } + render_text->SetObscured(param.set_obscured); + render_text->SetText(base::WideToUTF16(param.text)); + for (size_t i = 0; i < param.line_breaks.size(); ++i) { + EXPECT_EQ(param.line_breaks[i], render_text->GetTextIndexOfLine(i)); + } +} + +const GetTextIndexOfLineCase kGetTextIndexOfLineCases[] = { + {"emptyString", L"", {0}}, + // The following test strings are three character strings. + // The word wrap makes each character fall on a new line. + {"kWeak_minWidth", L" . ", {0, 1, 2}, kUseWordWrap}, + {"kLtr_minWidth", L"abc", {0, 1, 2}, kUseWordWrap}, + {"kLtrRtl_minWidth", L"a\u05d0\u05d1", {0, 1, 2}, kUseWordWrap}, + {"kLtrRtlLtr_minWidth", L"a\u05d1b", {0, 1, 2}, kUseWordWrap}, + {"kRtl_minWidth", L"\u05d0\u05d1\u05d2", {0, 1, 2}, kUseWordWrap}, + {"kRtlLtr_minWidth", L"\u05d0\u05d1a", {0, 1, 2}, kUseWordWrap}, + {"kRtlLtrRtl_minWidth", L"\u05d0a\u05d1", {0, 1, 2}, kUseWordWrap}, + // The following test strings have 2 graphemes separated by a newline. + // The obscured text replace each grapheme by a single codepoint. + {"grapheme_unobscured", + L"\U0001F601\n\U0001F468\u200D\u2708\uFE0F\nx", + {0, 3, 9}}, + {"grapheme_obscured", + L"\U0001F601\n\U0001F468\u200D\u2708\uFE0F\nx", + {0, 3, 9}, + !kUseWordWrap, + kUseObscuredText}, + // The following test strings have a new line character. + {"basic_newLine", L"abc\ndef", {0, 4}}, + {"basic_newLineWindows", L"abc\r\ndef", {0, 5}}, + {"spaces_newLine", L"a \n b ", {0, 3}}, + {"spaces_newLineWindows", L"a \r\n b ", {0, 4}}, + {"double_newLine", L"a\n\nb", {0, 2, 3}}, + {"double_newLineWindows", L"a\r\n\r\nb", {0, 3, 5}}, + {"start_newLine", L"\nab", {0, 1}}, + {"start_newLineWindows", L"\r\nab", {0, 2}}, + {"end_newLine", L"ab\n", {0}}, + {"end_newLineWindows", L"ab\r\n", {0}}, + {"isolated_newLine", L"\n", {0}}, + {"isolated_newLineWindows", L"\r\n", {0}}, + {"isolatedDouble_newLine", L"\n\n", {0, 1}}, + {"isolatedDouble_newLineWindows", L"\r\n\r\n", {0, 2}}, + // The following test strings have unicode characters. + {"playSymbol_unicode", L"x\n\u25B6\ny", {0, 2, 4}}, + {"emoji_unicode", L"x\n\U0001F601\ny\n\u2728\nz", {0, 2, 5, 7, 9}}, + {"flag_unicode", L"🇬🇧\n🇯🇵", {0, 5}, false, false}, + // The following cases test that GetTextIndexOfLine returns the length of + // the text when passed a line index larger than the number of lines. + {"basic_outsideRange", L"abc", {0, 1, 2, 3, 3}, kUseWordWrap}, + {"emptyString_outsideRange", L"", {0, 0, 0}}, + {"newLine_outsideRange", L"\n", {0, 1, 1}}, + {"newLineWindows_outsideRange", L"\r\n", {0, 2, 2, 2}}, + {"doubleNewLine_outsideRange", L"\n\n", {0, 1, 2, 2}}, + {"doubleNewLineWindows_outsideRange", L"\r\n\r\n", {0, 2, 4, 4}}, +}; + +INSTANTIATE_TEST_SUITE_P( + GetTextIndexOfLine, + RenderTextTestWithGetTextIndexOfLineCase, + ::testing::ValuesIn(kGetTextIndexOfLineCases), + RenderTextTestWithGetTextIndexOfLineCase::ParamInfoToString); + TEST_F(RenderTextTest, MoveCursorLeftRightInLtr) { RenderText* render_text = GetRenderText(); // Pure LTR. @@ -5963,8 +6144,8 @@ TEST_F(RenderTextTest, EmojiFlagGlyphCount) { const internal::TextRunList* run_list = GetHarfBuzzRunList(); ASSERT_EQ(1U, run_list->runs().size()); -#if defined(OS_MACOSX) - // On Mac, the flags should be found, so two glyphs result. +#if defined(OS_LINUX) || defined(OS_MACOSX) + // On Linux and macOS, the flags should be found, so two glyphs result. EXPECT_EQ(2u, run_list->runs()[0]->shape.glyph_count); #elif defined(OS_ANDROID) // It seems that some versions of android support the flags. Older versions diff --git a/chromium/ui/gfx/rrect_f.cc b/chromium/ui/gfx/rrect_f.cc index c9b643ca2f9..c58e0cfdad4 100644 --- a/chromium/ui/gfx/rrect_f.cc +++ b/chromium/ui/gfx/rrect_f.cc @@ -128,7 +128,7 @@ void RRectF::Scale(float x_scale, float y_scale) { skrrect_ = SkRRect::MakeEmpty(); return; } - SkMatrix scale = SkMatrix::MakeScale(x_scale, y_scale); + SkMatrix scale = SkMatrix::Scale(x_scale, y_scale); SkRRect result; bool success = skrrect_.transform(scale, &result); DCHECK(success); diff --git a/chromium/ui/gfx/selection_model.cc b/chromium/ui/gfx/selection_model.cc index e91342bda1e..a792731f0fc 100644 --- a/chromium/ui/gfx/selection_model.cc +++ b/chromium/ui/gfx/selection_model.cc @@ -6,8 +6,8 @@ #include <ostream> +#include "base/check.h" #include "base/format_macros.h" -#include "base/logging.h" #include "base/strings/stringprintf.h" namespace gfx { diff --git a/chromium/ui/gfx/swap_result.cc b/chromium/ui/gfx/swap_result.cc new file mode 100644 index 00000000000..f5c91451939 --- /dev/null +++ b/chromium/ui/gfx/swap_result.cc @@ -0,0 +1,29 @@ +// 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 "ui/gfx/swap_result.h" + +#include "ui/gfx/ca_layer_params.h" +#include "ui/gfx/gpu_fence.h" + +namespace gfx { + +SwapCompletionResult::SwapCompletionResult(gfx::SwapResult swap_result) + : swap_result(swap_result) {} + +SwapCompletionResult::SwapCompletionResult( + gfx::SwapResult swap_result, + std::unique_ptr<gfx::GpuFence> gpu_fence) + : swap_result(swap_result), gpu_fence(std::move(gpu_fence)) {} + +SwapCompletionResult::SwapCompletionResult( + gfx::SwapResult swap_result, + std::unique_ptr<gfx::CALayerParams> ca_layer_params) + : swap_result(swap_result), ca_layer_params(std::move(ca_layer_params)) {} + +SwapCompletionResult::SwapCompletionResult(SwapCompletionResult&& other) = + default; +SwapCompletionResult::~SwapCompletionResult() = default; + +} // namespace gfx diff --git a/chromium/ui/gfx/swap_result.h b/chromium/ui/gfx/swap_result.h index 027a1914ae7..1a71e4ec174 100644 --- a/chromium/ui/gfx/swap_result.h +++ b/chromium/ui/gfx/swap_result.h @@ -5,10 +5,16 @@ #ifndef UI_GFX_SWAP_RESULT_H_ #define UI_GFX_SWAP_RESULT_H_ +#include <memory> + #include "base/time/time.h" +#include "ui/gfx/gfx_export.h" namespace gfx { +struct CALayerParams; +class GpuFence; + enum class SwapResult { SWAP_ACK, SWAP_FAILED, @@ -38,12 +44,32 @@ struct SwapResponse { uint64_t swap_id; // Indicates whether the swap succeeded or not. + // TODO(https://crbug.com/894929): It may be more reasonable to add + // a full SwapCompletionResult as a member. SwapResult result; // Timing information about the given swap. SwapTimings timings; }; +// Sent by GLImages to their GLImage::SwapCompletionCallbacks. +struct GFX_EXPORT SwapCompletionResult { + explicit SwapCompletionResult(gfx::SwapResult swap_result); + SwapCompletionResult(gfx::SwapResult swap_result, + std::unique_ptr<gfx::GpuFence> gpu_fence); + SwapCompletionResult(gfx::SwapResult swap_result, + std::unique_ptr<gfx::CALayerParams> ca_layer_params); + SwapCompletionResult(SwapCompletionResult&& other); + ~SwapCompletionResult(); + + SwapCompletionResult(const SwapCompletionResult& other) = delete; + SwapCompletionResult& operator=(const SwapCompletionResult other) = delete; + + gfx::SwapResult swap_result = SwapResult::SWAP_FAILED; + std::unique_ptr<GpuFence> gpu_fence; + std::unique_ptr<CALayerParams> ca_layer_params; +}; + } // namespace gfx #endif // UI_GFX_SWAP_RESULT_H_ diff --git a/chromium/ui/gfx/switches.cc b/chromium/ui/gfx/switches.cc index 0f746ead72a..16eeda0d910 100644 --- a/chromium/ui/gfx/switches.cc +++ b/chromium/ui/gfx/switches.cc @@ -16,6 +16,10 @@ const char kAnimationDurationScale[] = "animation-duration-scale"; const char kDisableFontSubpixelPositioning[] = "disable-font-subpixel-positioning"; +// Disable a NV12 format buffer allocation with +// gfx::BufferUsage::SCANOUT_CPU_READ_WRITE usage. +const char kDisableYuv420Biplanar[] = "disable-yuv420-biplanar"; + // Enable native CPU-mappable GPU memory buffer support on Linux. const char kEnableNativeGpuMemoryBuffers[] = "enable-native-gpu-memory-buffers"; diff --git a/chromium/ui/gfx/switches.h b/chromium/ui/gfx/switches.h index abe0a58a9cc..fa86fc52621 100644 --- a/chromium/ui/gfx/switches.h +++ b/chromium/ui/gfx/switches.h @@ -12,10 +12,10 @@ namespace switches { GFX_SWITCHES_EXPORT extern const char kAnimationDurationScale[]; GFX_SWITCHES_EXPORT extern const char kDisableFontSubpixelPositioning[]; +GFX_SWITCHES_EXPORT extern const char kDisableYuv420Biplanar[]; GFX_SWITCHES_EXPORT extern const char kEnableNativeGpuMemoryBuffers[]; GFX_SWITCHES_EXPORT extern const char kForcePrefersReducedMotion[]; GFX_SWITCHES_EXPORT extern const char kHeadless[]; - } // namespace switches #endif // UI_GFX_SWITCHES_H_ diff --git a/chromium/ui/gfx/system_fonts_win.cc b/chromium/ui/gfx/system_fonts_win.cc index 5454e39befe..58b498b8144 100644 --- a/chromium/ui/gfx/system_fonts_win.cc +++ b/chromium/ui/gfx/system_fonts_win.cc @@ -7,6 +7,7 @@ #include <windows.h> #include "base/containers/flat_map.h" +#include "base/logging.h" #include "base/no_destructor.h" #include "base/strings/sys_string_conversions.h" #include "base/trace_event/trace_event.h" diff --git a/chromium/ui/gfx/text_utils.cc b/chromium/ui/gfx/text_utils.cc index fa062ea8972..dba839e87eb 100644 --- a/chromium/ui/gfx/text_utils.cc +++ b/chromium/ui/gfx/text_utils.cc @@ -11,6 +11,10 @@ #include "base/numerics/safe_conversions.h" #include "third_party/icu/source/common/unicode/uchar.h" #include "third_party/icu/source/common/unicode/utf16.h" +#include "ui/gfx/font_list.h" +#include "ui/gfx/geometry/insets.h" +#include "ui/gfx/geometry/rect.h" +#include "ui/gfx/geometry/size.h" namespace gfx { @@ -126,4 +130,36 @@ HorizontalAlignment MaybeFlipForRTL(HorizontalAlignment alignment) { return alignment; } +Size GetStringSize(const base::string16& text, const FontList& font_list) { + return Size(GetStringWidth(text, font_list), font_list.GetHeight()); +} + +Insets AdjustVisualBorderForFont(const FontList& font_list, + const Insets& desired_visual_padding) { + Insets result = desired_visual_padding; + const int baseline = font_list.GetBaseline(); + const int leading_space = baseline - font_list.GetCapHeight(); + const int descender = font_list.GetHeight() - baseline; + result.set_top(std::max(0, result.top() - leading_space)); + result.set_bottom(std::max(0, result.bottom() - descender)); + return result; +} + +int GetFontCapHeightCenterOffset(const gfx::FontList& original_font, + const gfx::FontList& to_center) { + const int original_cap_height = original_font.GetCapHeight(); + const int original_cap_leading = + original_font.GetBaseline() - original_cap_height; + const int to_center_cap_height = to_center.GetCapHeight(); + const int to_center_leading = to_center.GetBaseline() - to_center_cap_height; + + const int cap_height_diff = original_cap_height - to_center_cap_height; + const int new_cap_top = + original_cap_leading + std::lround(cap_height_diff / 2.0f); + const int new_top = new_cap_top - to_center_leading; + + // Since we assume the old font starts at zero, the new top is the adjustment. + return new_top; +} + } // namespace gfx diff --git a/chromium/ui/gfx/text_utils.h b/chromium/ui/gfx/text_utils.h index 4b6b5cae13e..ea342ee5a8c 100644 --- a/chromium/ui/gfx/text_utils.h +++ b/chromium/ui/gfx/text_utils.h @@ -14,6 +14,8 @@ namespace gfx { class FontList; +class Insets; +class Size; // Strip the accelerator char (typically '&') from a menu string. A double // accelerator char ('&&') will be converted to a single char. The out params @@ -31,6 +33,12 @@ GFX_EXPORT base::string16 RemoveAcceleratorChar(const base::string16& s, GFX_EXPORT int GetStringWidth(const base::string16& text, const FontList& font_list); +// Returns the size required to render |text| in |font_list|. This includes all +// leading space, descender area, etc. even if the text to render does not +// contain characters with ascenders or descenders. +GFX_EXPORT Size GetStringSize(const base::string16& text, + const FontList& font_list); + // This is same as GetStringWidth except that fractional width is returned. GFX_EXPORT float GetStringWidthF(const base::string16& text, const FontList& font_list); @@ -50,6 +58,72 @@ GFX_EXPORT size_t FindValidBoundaryAfter(const base::string16& text, // If the UI layout is right-to-left, flip the alignment direction. GFX_EXPORT HorizontalAlignment MaybeFlipForRTL(HorizontalAlignment alignment); +// Returns insets that can be used to draw a highlight or border that appears to +// be distance |desired_visual_padding| from the body of a string of text +// rendered using |font_list|. The insets are adjusted based on the box used to +// render capital letters (or the bodies of most letters in non-capital fonts +// like Hebrew and Devanagari), in order to give the best visual appearance. +// +// That is, any portion of |desired_visual_padding| overlapping the font's +// leading space or descender area are truncated, to a minimum of zero. +// +// In this example, the text is rendered in a highlight that stretches above and +// below the height of the H as well as to the left and right of the text +// (|desired_visual_padding| = {2, 2, 2, 2}). Note that the descender of the 'y' +// overlaps with the padding, as it is outside the capital letter box. +// +// The resulting padding is {1, 2, 1, 2}. +// +// . . . . . . . . . . | actual top +// . . | | leading space +// . | | _ . | font | capital +// . |--| /_\ \ / . | height | height +// . | | \_ \/ . | | +// . / . | | descender +// . . . . . . . . . . | actual bottom +// ___ ___ +// actual actual +// left right +// +GFX_EXPORT Insets +AdjustVisualBorderForFont(const FontList& font_list, + const Insets& desired_visual_padding); + +// Returns the y adjustment necessary to align the center of the "cap size" box +// - the space between a capital letter's top and bottom - between two fonts. +// For non-capital scripts (e.g. Hebrew, Devanagari) the box containing the body +// of most letters is used. +// +// A positive return value means the font |to_center| needs to be moved down +// relative to the font |original_font|, while a negative value means it needs +// to be moved up. +// +// Illustration: +// +// original_font to_center +// ---------- ] - return value (+1) +// leading ---------- +// ---------- leading +// ---------- +// +// cap-height cap-height +// +// ---------- +// ---------- descent +// descent ---------- +// ---------- +// +// Visual result: Non-Latin example (Devanagari ऐ "ai"): +// \ +// |\ | ------ \ +// | \ | |\ | | | ---- +// | \ | | \| \ / \| +// | \| \ / +// / +// +GFX_EXPORT int GetFontCapHeightCenterOffset(const gfx::FontList& original_font, + const gfx::FontList& to_center); + } // namespace gfx #endif // UI_GFX_TEXT_UTILS_H_ diff --git a/chromium/ui/gfx/text_utils_unittest.cc b/chromium/ui/gfx/text_utils_unittest.cc index 564ec40bd0f..ed1eb98a45c 100644 --- a/chromium/ui/gfx/text_utils_unittest.cc +++ b/chromium/ui/gfx/text_utils_unittest.cc @@ -6,12 +6,17 @@ #include <stddef.h> +#include <vector> + #include "base/logging.h" #include "base/stl_util.h" #include "base/strings/utf_string_conversions.h" #include "build/build_config.h" #include "testing/gtest/include/gtest/gtest.h" #include "ui/gfx/font_list.h" +#include "ui/gfx/geometry/insets.h" +#include "ui/gfx/geometry/point.h" +#include "ui/gfx/geometry/rect.h" namespace gfx { namespace { @@ -37,6 +42,81 @@ TEST(TextUtilsTest, GetStringWidth) { GetStringWidth(base::ASCIIToUTF16("ab"), font_list)); } +TEST(TextUtilsTest, GetStringSize) { + std::vector<base::string16> strings{ + base::string16(), + base::ASCIIToUTF16("a"), + base::ASCIIToUTF16("abc"), + }; + + FontList font_list; + for (base::string16 string : strings) { + gfx::Size size = GetStringSize(string, font_list); + EXPECT_EQ(GetStringWidth(string, font_list), size.width()) + << " input string is \"" << string << "\""; + EXPECT_EQ(font_list.GetHeight(), size.height()) + << " input string is \"" << string << "\""; + } +} + +TEST(TextUtilsTest, AdjustVisualBorderForFont_BorderLargerThanFont) { + FontList font_list; + + // We will make some assumptions about the default font - specifically that it + // has leading space and space for the descender. + DCHECK_GT(font_list.GetBaseline(), font_list.GetCapHeight()); + DCHECK_LT(font_list.GetBaseline(), font_list.GetHeight()); + + // Adjust a large border for the default font. Using a large number means that + // the border will extend outside the leading and descender area of the font. + constexpr gfx::Insets kOriginalBorder(20); + const gfx::Insets result = + AdjustVisualBorderForFont(font_list, kOriginalBorder); + EXPECT_EQ(result.left(), kOriginalBorder.left()); + EXPECT_EQ(result.right(), kOriginalBorder.right()); + EXPECT_LT(result.top(), kOriginalBorder.top()); + EXPECT_LT(result.bottom(), kOriginalBorder.bottom()); +} + +TEST(TextUtilsTest, AdjustVisualBorderForFont_BorderSmallerThanFont) { + FontList font_list; + + // We will make some assumptions about the default font - specifically that it + // has leading space and space for the descender. + DCHECK_GT(font_list.GetBaseline(), font_list.GetCapHeight()); + DCHECK_LT(font_list.GetBaseline(), font_list.GetHeight()); + + // Adjust a border with a small vertical component. The vertical component + // should go to zero because it overlaps the leading and descender areas of + // the font. + constexpr gfx::Insets kSmallVerticalInsets(1, 20); + const gfx::Insets result = + AdjustVisualBorderForFont(font_list, kSmallVerticalInsets); + EXPECT_EQ(result.left(), kSmallVerticalInsets.left()); + EXPECT_EQ(result.right(), kSmallVerticalInsets.right()); + EXPECT_EQ(result.top(), 0); + EXPECT_EQ(result.bottom(), 0); +} + +TEST(TextUtilsTest, GetFontCapHeightCenterOffset_SecondFontIsSmaller) { + FontList original_font; + FontList smaller_font = original_font.DeriveWithSizeDelta(-3); + DCHECK_LT(smaller_font.GetCapHeight(), original_font.GetCapHeight()); + EXPECT_GT(GetFontCapHeightCenterOffset(original_font, smaller_font), 0); +} + +TEST(TextUtilsTest, GetFontCapHeightCenterOffset_SecondFontIsLarger) { + FontList original_font; + FontList larger_font = original_font.DeriveWithSizeDelta(3); + DCHECK_GT(larger_font.GetCapHeight(), original_font.GetCapHeight()); + EXPECT_LT(GetFontCapHeightCenterOffset(original_font, larger_font), 0); +} + +TEST(TextUtilsTest, GetFontCapHeightCenterOffset_SameSize) { + FontList original_font; + EXPECT_EQ(0, GetFontCapHeightCenterOffset(original_font, original_font)); +} + class RemoveAcceleratorCharTest : public testing::TestWithParam<RemoveAcceleratorCharData> { public: diff --git a/chromium/ui/gfx/transform.cc b/chromium/ui/gfx/transform.cc index e0f0b4a3925..58b662123d6 100644 --- a/chromium/ui/gfx/transform.cc +++ b/chromium/ui/gfx/transform.cc @@ -245,19 +245,28 @@ bool Transform::IsApproximatelyIdentityOrTranslation(SkScalar tolerance) const { matrix_.get(3, 3) == 1; } +bool Transform::IsApproximatelyIdentityOrIntegerTranslation( + SkScalar tolerance) const { + if (!IsApproximatelyIdentityOrTranslation(tolerance)) + return false; + + for (float t : {matrix_.get(0, 3), matrix_.get(1, 3), matrix_.get(2, 3)}) { + if (!base::IsValueInRangeForNumericType<int>(t) || + std::abs(std::round(t) - t) > tolerance) + return false; + } + return true; +} + bool Transform::IsIdentityOrIntegerTranslation() const { if (!IsIdentityOrTranslation()) return false; - float t[] = {matrix_.get(0, 3), matrix_.get(1, 3), matrix_.get(2, 3)}; - bool no_fractional_translation = - base::IsValueInRangeForNumericType<int>(t[0]) && - base::IsValueInRangeForNumericType<int>(t[1]) && - base::IsValueInRangeForNumericType<int>(t[2]) && - static_cast<int>(t[0]) == t[0] && static_cast<int>(t[1]) == t[1] && - static_cast<int>(t[2]) == t[2]; - - return no_fractional_translation; + for (float t : {matrix_.get(0, 3), matrix_.get(1, 3), matrix_.get(2, 3)}) { + if (!base::IsValueInRangeForNumericType<int>(t) || static_cast<int>(t) != t) + return false; + } + return true; } bool Transform::IsBackFaceVisible() const { diff --git a/chromium/ui/gfx/transform.h b/chromium/ui/gfx/transform.h index 94fe601155b..c056ab0f854 100644 --- a/chromium/ui/gfx/transform.h +++ b/chromium/ui/gfx/transform.h @@ -147,6 +147,7 @@ class GEOMETRY_SKIA_EXPORT Transform { // Returns true if the matrix is either identity or pure translation, // allowing for an amount of inaccuracy as specified by the parameter. bool IsApproximatelyIdentityOrTranslation(SkScalar tolerance) const; + bool IsApproximatelyIdentityOrIntegerTranslation(SkScalar tolerance) const; // Returns true if the matrix is either a positive scale and/or a translation. bool IsPositiveScaleOrTranslation() const { diff --git a/chromium/ui/gfx/transform_unittest.cc b/chromium/ui/gfx/transform_unittest.cc index ee8406500b6..35a3fca60b0 100644 --- a/chromium/ui/gfx/transform_unittest.cc +++ b/chromium/ui/gfx/transform_unittest.cc @@ -2220,6 +2220,21 @@ TEST(XFormTest, verifyIsApproximatelyIdentityOrTranslation) { // Exact pure translation. A.MakeIdentity(); + EXPECT_TRUE(A.IsApproximatelyIdentityOrTranslation(0)); + EXPECT_TRUE(A.IsApproximatelyIdentityOrTranslation(kApproxZero)); + EXPECT_TRUE(A.IsApproximatelyIdentityOrIntegerTranslation(0)); + EXPECT_TRUE(A.IsApproximatelyIdentityOrIntegerTranslation(kApproxZero)); + + // Set translate values to integer values other than 0 or 1. + matrix.set(0, 3, 3); + matrix.set(1, 3, 4); + matrix.set(2, 3, 5); + + EXPECT_TRUE(A.IsApproximatelyIdentityOrTranslation(0)); + EXPECT_TRUE(A.IsApproximatelyIdentityOrTranslation(kApproxZero)); + EXPECT_TRUE(A.IsApproximatelyIdentityOrIntegerTranslation(0)); + EXPECT_TRUE(A.IsApproximatelyIdentityOrIntegerTranslation(kApproxZero)); + // Set translate values to values other than 0 or 1. matrix.set(0, 3, 3.4f); matrix.set(1, 3, 4.4f); @@ -2227,16 +2242,38 @@ TEST(XFormTest, verifyIsApproximatelyIdentityOrTranslation) { EXPECT_TRUE(A.IsApproximatelyIdentityOrTranslation(0)); EXPECT_TRUE(A.IsApproximatelyIdentityOrTranslation(kApproxZero)); + EXPECT_FALSE(A.IsApproximatelyIdentityOrIntegerTranslation(0)); + EXPECT_FALSE(A.IsApproximatelyIdentityOrIntegerTranslation(kApproxZero)); // Approximately pure translation. InitializeApproxIdentityMatrix(&A); + EXPECT_FALSE(A.IsApproximatelyIdentityOrTranslation(0)); + EXPECT_FALSE(A.IsApproximatelyIdentityOrTranslation(kApproxZero)); + EXPECT_FALSE(A.IsApproximatelyIdentityOrIntegerTranslation(0)); + EXPECT_FALSE(A.IsApproximatelyIdentityOrIntegerTranslation(kApproxZero)); + // Some values must be exact. matrix.set(3, 0, 0); matrix.set(3, 1, 0); matrix.set(3, 2, 0); matrix.set(3, 3, 1); + EXPECT_FALSE(A.IsApproximatelyIdentityOrTranslation(0)); + EXPECT_TRUE(A.IsApproximatelyIdentityOrTranslation(kApproxZero)); + EXPECT_FALSE(A.IsApproximatelyIdentityOrIntegerTranslation(0)); + EXPECT_TRUE(A.IsApproximatelyIdentityOrIntegerTranslation(kApproxZero)); + + // Set translate values to values other than 0 or 1. + matrix.set(0, 3, matrix.get(0, 3) + 3); + matrix.set(1, 3, matrix.get(1, 3) + 4); + matrix.set(2, 3, matrix.get(2, 3) + 5); + + EXPECT_FALSE(A.IsApproximatelyIdentityOrTranslation(0)); + EXPECT_TRUE(A.IsApproximatelyIdentityOrTranslation(kApproxZero)); + EXPECT_FALSE(A.IsApproximatelyIdentityOrIntegerTranslation(0)); + EXPECT_TRUE(A.IsApproximatelyIdentityOrIntegerTranslation(kApproxZero)); + // Set translate values to values other than 0 or 1. matrix.set(0, 3, 3.4f); matrix.set(1, 3, 4.4f); @@ -2244,6 +2281,8 @@ TEST(XFormTest, verifyIsApproximatelyIdentityOrTranslation) { EXPECT_FALSE(A.IsApproximatelyIdentityOrTranslation(0)); EXPECT_TRUE(A.IsApproximatelyIdentityOrTranslation(kApproxZero)); + EXPECT_FALSE(A.IsApproximatelyIdentityOrIntegerTranslation(0)); + EXPECT_FALSE(A.IsApproximatelyIdentityOrIntegerTranslation(kApproxZero)); // Not approximately pure translation. InitializeApproxIdentityMatrix(&A); @@ -2261,6 +2300,8 @@ TEST(XFormTest, verifyIsApproximatelyIdentityOrTranslation) { EXPECT_FALSE(A.IsApproximatelyIdentityOrTranslation(0)); EXPECT_FALSE(A.IsApproximatelyIdentityOrTranslation(kApproxZero)); + EXPECT_FALSE(A.IsApproximatelyIdentityOrIntegerTranslation(0)); + EXPECT_FALSE(A.IsApproximatelyIdentityOrIntegerTranslation(kApproxZero)); } TEST(XFormTest, verifyIsScaleOrTranslation) { diff --git a/chromium/ui/gfx/win/hwnd_util.cc b/chromium/ui/gfx/win/hwnd_util.cc index 069968c3b24..9730ad7cfa1 100644 --- a/chromium/ui/gfx/win/hwnd_util.cc +++ b/chromium/ui/gfx/win/hwnd_util.cc @@ -4,7 +4,11 @@ #include "ui/gfx/win/hwnd_util.h" +#include <windows.h> + +#include "base/debug/alias.h" #include "base/logging.h" +#include "base/notreached.h" #include "base/strings/string_util.h" #include "base/win/win_util.h" #include "ui/gfx/geometry/rect.h" @@ -50,17 +54,31 @@ void AdjustWindowToFit(HWND hwnd, const RECT& bounds, bool fit_to_monitor) { // Don't inline these functions so they show up in crash reports. -NOINLINE void CrashOutOfMemory() { - PLOG(FATAL); +NOINLINE void CrashOutOfMemory(DWORD last_error) { + // Record Graphics Device Interface (GDI) object counts so they are visible in + // the crash's minidump. By default, GDI and USER handles are limited to + // 10,000 each per process and 65,535 each globally, exceeding which typically + // indicates a leak of GDI resources. + const HANDLE process = ::GetCurrentProcess(); + DWORD num_process_gdi_handles = ::GetGuiResources(process, GR_GDIOBJECTS); + DWORD num_process_user_handles = ::GetGuiResources(process, GR_USEROBJECTS); + DWORD num_global_gdi_handles = ::GetGuiResources(GR_GLOBAL, GR_GDIOBJECTS); + DWORD num_global_user_handles = ::GetGuiResources(GR_GLOBAL, GR_USEROBJECTS); + base::debug::Alias(&num_process_gdi_handles); + base::debug::Alias(&num_process_user_handles); + base::debug::Alias(&num_global_gdi_handles); + base::debug::Alias(&num_global_user_handles); + + LOG(FATAL) << last_error; } -NOINLINE void CrashAccessDenied() { - PLOG(FATAL); +NOINLINE void CrashAccessDenied(DWORD last_error) { + LOG(FATAL) << last_error; } // Crash isn't one of the ones we commonly see. -NOINLINE void CrashOther() { - PLOG(FATAL); +NOINLINE void CrashOther(DWORD last_error) { + LOG(FATAL) << last_error; } } // namespace @@ -182,20 +200,20 @@ void CenterAndSizeWindow(HWND parent, AdjustWindowToFit(window, window_bounds, !parent); } -void CheckWindowCreated(HWND hwnd) { +void CheckWindowCreated(HWND hwnd, DWORD last_error) { if (!hwnd) { - switch (GetLastError()) { + switch (last_error) { case ERROR_NOT_ENOUGH_MEMORY: - CrashOutOfMemory(); + CrashOutOfMemory(last_error); break; case ERROR_ACCESS_DENIED: - CrashAccessDenied(); + CrashAccessDenied(last_error); break; default: - CrashOther(); + CrashOther(last_error); break; } - PLOG(FATAL); + LOG(FATAL) << last_error; } } diff --git a/chromium/ui/gfx/win/hwnd_util.h b/chromium/ui/gfx/win/hwnd_util.h index 5351d9467f0..18b386208a0 100644 --- a/chromium/ui/gfx/win/hwnd_util.h +++ b/chromium/ui/gfx/win/hwnd_util.h @@ -35,9 +35,9 @@ GFX_EXPORT void CenterAndSizeWindow(HWND parent, HWND window, const gfx::Size& pref); -// If |hwnd| is NULL logs various thing and CHECKs. Invoke right after calling -// CreateWindow. -GFX_EXPORT void CheckWindowCreated(HWND hwnd); +// If |hwnd| is nullptr logs various thing and CHECKs. |last_error| must contain +// the result of ::GetLastError(), called immediately after CreateWindow(). +GFX_EXPORT void CheckWindowCreated(HWND hwnd, DWORD last_error); // Returns the window you can use to parent a top level window. // Note that in some cases we create child windows not parented to its final diff --git a/chromium/ui/gfx/win/rendering_window_manager.cc b/chromium/ui/gfx/win/rendering_window_manager.cc index 1483e9883c3..85368828682 100644 --- a/chromium/ui/gfx/win/rendering_window_manager.cc +++ b/chromium/ui/gfx/win/rendering_window_manager.cc @@ -5,6 +5,7 @@ #include "ui/gfx/win/rendering_window_manager.h" #include "base/bind.h" +#include "base/logging.h" #include "base/no_destructor.h" #include "base/single_thread_task_runner.h" #include "base/threading/thread_task_runner_handle.h" diff --git a/chromium/ui/gfx/win/scoped_set_map_mode.h b/chromium/ui/gfx/win/scoped_set_map_mode.h index 253eca8a072..5ab40bc60c8 100644 --- a/chromium/ui/gfx/win/scoped_set_map_mode.h +++ b/chromium/ui/gfx/win/scoped_set_map_mode.h @@ -7,7 +7,7 @@ #include <windows.h> -#include "base/logging.h" +#include "base/check_op.h" #include "base/macros.h" namespace gfx { diff --git a/chromium/ui/gfx/win/window_impl.cc b/chromium/ui/gfx/win/window_impl.cc index 8df35cdc651..9c4de6c604d 100644 --- a/chromium/ui/gfx/win/window_impl.cc +++ b/chromium/ui/gfx/win/window_impl.cc @@ -217,6 +217,8 @@ void WindowImpl::Init(HWND parent, const Rect& bounds) { reinterpret_cast<wchar_t*>(atom), NULL, window_style_, x, y, width, height, parent, NULL, NULL, this); + const DWORD create_window_error = ::GetLastError(); + // First nccalcszie (during CreateWindow) for captioned windows is // deliberately ignored so force a second one here to get the right // non-client set up. @@ -226,7 +228,7 @@ void WindowImpl::Init(HWND parent, const Rect& bounds) { SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOREDRAW); } - if (!hwnd_ && GetLastError() == 0) { + if (!hwnd_ && create_window_error == 0) { base::debug::Alias(&destroyed); base::debug::Alias(&hwnd); bool got_create = got_create_; @@ -248,7 +250,7 @@ void WindowImpl::Init(HWND parent, const Rect& bounds) { if (!destroyed) destroyed_ = NULL; - CheckWindowCreated(hwnd_); + CheckWindowCreated(hwnd_, create_window_error); // The window procedure should have set the data for us. CHECK_EQ(this, GetWindowUserData(hwnd)); diff --git a/chromium/ui/gfx/win/window_impl.h b/chromium/ui/gfx/win/window_impl.h index 268ca081016..d34c649708a 100644 --- a/chromium/ui/gfx/win/window_impl.h +++ b/chromium/ui/gfx/win/window_impl.h @@ -7,7 +7,7 @@ #include <string> -#include "base/logging.h" +#include "base/check_op.h" #include "base/macros.h" #include "ui/gfx/geometry/rect.h" #include "ui/gfx/gfx_export.h" diff --git a/chromium/ui/gfx/x/BUILD.gn b/chromium/ui/gfx/x/BUILD.gn index 5369e38efed..24d36ecba8a 100644 --- a/chromium/ui/gfx/x/BUILD.gn +++ b/chromium/ui/gfx/x/BUILD.gn @@ -3,23 +3,12 @@ # found in the LICENSE file. import("//build/config/jumbo.gni") -import("//build/config/sysroot.gni") import("//build/config/ui.gni") import("//ui/ozone/ozone.gni") assert(use_x11 || ozone_platform_x11) -declare_args() { - xcbproto_path = "" -} - -if (xcbproto_path == "") { - if (use_sysroot) { - xcbproto_path = "$sysroot/usr/share/xcb" - } else { - xcbproto_path = "/usr/share/xcb" - } -} +xcbproto_path = "//third_party/xcbproto/src" config("x11_private_config") { cflags = [ @@ -32,54 +21,62 @@ config("x11_private_config") { defines = [ "IS_X11_IMPL" ] } -action_foreach("gen_xprotos") { +action("gen_xprotos") { visibility = [ ":xprotos" ] script = "gen_xproto.py" - sources = [ - "$xcbproto_path/bigreq.xml", - "$xcbproto_path/composite.xml", - "$xcbproto_path/damage.xml", - "$xcbproto_path/dpms.xml", - "$xcbproto_path/dri2.xml", - "$xcbproto_path/dri3.xml", - "$xcbproto_path/ge.xml", - "$xcbproto_path/glx.xml", - "$xcbproto_path/present.xml", - "$xcbproto_path/randr.xml", - "$xcbproto_path/record.xml", - "$xcbproto_path/render.xml", - "$xcbproto_path/res.xml", - "$xcbproto_path/screensaver.xml", - "$xcbproto_path/shape.xml", - "$xcbproto_path/shm.xml", - "$xcbproto_path/sync.xml", - "$xcbproto_path/xc_misc.xml", - "$xcbproto_path/xevie.xml", - "$xcbproto_path/xf86dri.xml", - "$xcbproto_path/xf86vidmode.xml", - "$xcbproto_path/xfixes.xml", - "$xcbproto_path/xinerama.xml", - "$xcbproto_path/xinput.xml", - "$xcbproto_path/xkb.xml", - "$xcbproto_path/xprint.xml", - "$xcbproto_path/xproto.xml", - "$xcbproto_path/xselinux.xml", - "$xcbproto_path/xtest.xml", - "$xcbproto_path/xv.xml", - "$xcbproto_path/xvmc.xml", + protos = [ + "bigreq", + "composite", + "damage", + "dpms", + "dri2", + "dri3", + "ge", + "glx", + "present", + "randr", + "record", + "render", + "res", + "screensaver", + "shape", + "shm", + "sync", + "xc_misc", + "xevie", + "xf86dri", + "xf86vidmode", + "xfixes", + "xinerama", + "xinput", + "xkb", + "xprint", + "xproto", + "xselinux", + "xtest", + "xv", + "xvmc", ] + sources = [] outputs = [ - "$target_gen_dir/{{source_name_part}}_undef.h", - "$target_gen_dir/{{source_name_part}}.h", - "$target_gen_dir/{{source_name_part}}.cc", + "$target_gen_dir/read_event.cc", + "$target_gen_dir/extension_manager.h", + "$target_gen_dir/extension_manager.cc", ] - args = [ "{{source}}" ] + rebase_path(outputs, root_build_dir) - if (use_sysroot) { - args += [ - "--sysroot", - rebase_path(sysroot, root_build_dir), + foreach(proto, protos) { + sources += [ "$xcbproto_path/src/${proto}.xml" ] + outputs += [ + "$target_gen_dir/${proto}_undef.h", + "$target_gen_dir/${proto}.h", + "$target_gen_dir/${proto}.cc", ] } + + args = rebase_path([ + xcbproto_path, + target_gen_dir, + ], + root_build_dir) + protos } component("xprotos") { @@ -90,12 +87,13 @@ component("xprotos") { sources = get_target_outputs(":gen_xprotos") + [ "xproto_internal.h", "xproto_types.h", - "request_queue.h", - "request_queue.cc", + "xproto_types.cc", "xproto_util.h", "xproto_util.cc", "connection.h", "connection.cc", + "event.h", + "event.cc", "x11_switches.cc", "x11_switches.h", ] @@ -131,3 +129,12 @@ jumbo_component("x") { ] public_deps = [ ":xprotos" ] } + +source_set("unit_test") { + testonly = true + sources = [ "connection_unittest.cc" ] + deps = [ + "//testing/gtest", + "//ui/gfx/x", + ] +} diff --git a/chromium/ui/gfx/x/connection.cc b/chromium/ui/gfx/x/connection.cc index 6dd336cac57..af5b6bfb1ae 100644 --- a/chromium/ui/gfx/x/connection.cc +++ b/chromium/ui/gfx/x/connection.cc @@ -4,13 +4,44 @@ #include "ui/gfx/x/connection.h" +#include <X11/Xlib-xcb.h> +#include <X11/Xlib.h> +#include <xcb/xcb.h> + +#include <algorithm> + #include "base/command_line.h" +#include "ui/gfx/x/bigreq.h" +#include "ui/gfx/x/event.h" +#include "ui/gfx/x/randr.h" #include "ui/gfx/x/x11_switches.h" +#include "ui/gfx/x/xproto_types.h" namespace x11 { namespace { +// On the wire, sequence IDs are 16 bits. In xcb, they're usually extended to +// 32 and sometimes 64 bits. In Xlib, they're extended to unsigned long, which +// may be 32 or 64 bits depending on the platform. This function is intended to +// prevent bugs caused by comparing two differently sized sequences. Also +// handles rollover. To use, compare the result of this function with 0. For +// example, to compare seq1 <= seq2, use CompareSequenceIds(seq1, seq2) <= 0. +template <typename T, typename U> +auto CompareSequenceIds(T t, U u) { + static_assert(std::is_unsigned<T>::value, ""); + static_assert(std::is_unsigned<U>::value, ""); + // Cast to the smaller of the two types so that comparisons will always work. + // If we casted to the larger type, then the smaller type will be zero-padded + // and may incorrectly compare less than the other value. + using SmallerType = + typename std::conditional<sizeof(T) <= sizeof(U), T, U>::type; + SmallerType t0 = static_cast<SmallerType>(t); + SmallerType u0 = static_cast<SmallerType>(u); + using SignedType = typename std::make_signed<SmallerType>::type; + return static_cast<SignedType>(t0 - u0); +} + XDisplay* OpenNewXDisplay() { if (!XInitThreads()) return nullptr; @@ -23,10 +54,197 @@ XDisplay* OpenNewXDisplay() { } // namespace Connection* Connection::Get() { - static Connection* instance = new Connection(OpenNewXDisplay()); + static Connection* instance = new Connection; return instance; } -Connection::Connection(XDisplay* display) : XProto(display) {} +Connection::Connection() : XProto(this), display_(OpenNewXDisplay()) { + if (display_) { + XSetEventQueueOwner(display_, XCBOwnsEventQueue); + + setup_ = Read<Setup>( + reinterpret_cast<const uint8_t*>(xcb_get_setup(XcbConnection()))); + default_screen_ = &setup_.roots[DefaultScreenId()]; + default_root_depth_ = &*std::find_if( + default_screen_->allowed_depths.begin(), + default_screen_->allowed_depths.end(), [&](const Depth& depth) { + return depth.depth == default_screen_->root_depth; + }); + default_root_visual_ = &*std::find_if( + default_root_depth_->visuals.begin(), + default_root_depth_->visuals.end(), [&](const VisualType visual) { + return visual.visual_id == default_screen_->root_visual; + }); + } else { + // Default-initialize the setup data so we always have something to return. + setup_.roots.emplace_back(); + default_screen_ = &setup_.roots[0]; + default_screen_->allowed_depths.emplace_back(); + default_root_depth_ = &default_screen_->allowed_depths[0]; + default_root_depth_->visuals.emplace_back(); + default_root_visual_ = &default_root_depth_->visuals[0]; + } + + ExtensionManager::Init(this); + if (auto response = bigreq().Enable({}).Sync()) + extended_max_request_length_ = response->maximum_request_length; +} + +Connection::~Connection() { + if (display_) + XCloseDisplay(display_); +} + +xcb_connection_t* Connection::XcbConnection() { + if (!display()) + return nullptr; + return XGetXCBConnection(display()); +} + +Connection::Request::Request(unsigned int sequence, + FutureBase::ResponseCallback callback) + : sequence(sequence), callback(std::move(callback)) {} + +Connection::Request::Request(Request&& other) + : sequence(other.sequence), callback(std::move(other.callback)) {} + +Connection::Request::~Request() = default; + +bool Connection::HasNextResponse() const { + return !requests_.empty() && + CompareSequenceIds(XLastKnownRequestProcessed(display_), + requests_.front().sequence) >= 0; +} + +int Connection::DefaultScreenId() const { + // This is not part of the setup data as the server has no concept of a + // default screen. Instead, it's part of the display name. Eg in + // "localhost:0.0", the screen ID is the second "0". + return DefaultScreen(display_); +} + +bool Connection::Ready() const { + return display_ && !xcb_connection_has_error(XGetXCBConnection(display_)); +} + +void Connection::Flush() { + XFlush(display_); +} + +void Connection::Sync() { + GetInputFocus({}).Sync(); +} + +void Connection::ReadResponses() { + while (auto* event = xcb_poll_for_event(XcbConnection())) { + events_.emplace_back(event, this); + free(event); + } +} + +bool Connection::HasPendingResponses() const { + return !events_.empty() || HasNextResponse(); +} + +void Connection::Dispatch(Delegate* delegate) { + DCHECK(display_); + + auto process_next_response = [&] { + xcb_connection_t* connection = XGetXCBConnection(display_); + auto request = std::move(requests_.front()); + requests_.pop(); + + void* raw_reply = nullptr; + xcb_generic_error_t* raw_error = nullptr; + xcb_poll_for_reply(connection, request.sequence, &raw_reply, &raw_error); + + std::move(request.callback) + .Run(FutureBase::RawReply{reinterpret_cast<uint8_t*>(raw_reply)}, + FutureBase::RawError{raw_error}); + }; + + auto process_next_event = [&] { + DCHECK(!events_.empty()); + + Event event = std::move(events_.front()); + events_.pop_front(); + PreDispatchEvent(event); + delegate->DispatchXEvent(&event); + }; + + // Handle all pending events. + while (delegate->ShouldContinueStream()) { + Flush(); + ReadResponses(); + + if (HasNextResponse() && !events_.empty()) { + if (!events_.front().sequence_valid()) { + process_next_event(); + continue; + } + + auto next_response_sequence = requests_.front().sequence; + auto next_event_sequence = events_.front().sequence(); + + // All events have the sequence number of the last processed request + // included in them. So if a reply and an event have the same sequence, + // the reply must have been received first. + if (CompareSequenceIds(next_event_sequence, next_response_sequence) <= 0) + process_next_response(); + else + process_next_event(); + } else if (HasNextResponse()) { + process_next_response(); + } else if (!events_.empty()) { + process_next_event(); + } else { + break; + } + } +} + +void Connection::AddRequest(unsigned int sequence, + FutureBase::ResponseCallback callback) { + DCHECK(requests_.empty() || + CompareSequenceIds(requests_.back().sequence, sequence) < 0); + + requests_.emplace(sequence, std::move(callback)); +} + +void Connection::PreDispatchEvent(const Event& event) { + // This is adapted from XRRUpdateConfiguration. + if (auto* configure = event.As<x11::ConfigureNotifyEvent>()) { + int index = ScreenIndexFromRootWindow(configure->window); + if (index != -1) { + setup_.roots[index].width_in_pixels = configure->width; + setup_.roots[index].height_in_pixels = configure->height; + } + } else if (auto* screen = event.As<x11::RandR::ScreenChangeNotifyEvent>()) { + int index = ScreenIndexFromRootWindow(screen->root); + DCHECK_GE(index, 0); + bool portrait = static_cast<bool>( + screen->rotation & + (x11::RandR::Rotation::Rotate_90 | x11::RandR::Rotation::Rotate_270)); + if (portrait) { + setup_.roots[index].width_in_pixels = screen->height; + setup_.roots[index].height_in_pixels = screen->width; + setup_.roots[index].width_in_millimeters = screen->mheight; + setup_.roots[index].height_in_millimeters = screen->mwidth; + } else { + setup_.roots[index].width_in_pixels = screen->width; + setup_.roots[index].height_in_pixels = screen->height; + setup_.roots[index].width_in_millimeters = screen->mwidth; + setup_.roots[index].height_in_millimeters = screen->mheight; + } + } +} + +int Connection::ScreenIndexFromRootWindow(x11::Window root) const { + for (size_t i = 0; i < setup_.roots.size(); i++) { + if (setup_.roots[i].root == root) + return i; + } + return -1; +} } // namespace x11 diff --git a/chromium/ui/gfx/x/connection.h b/chromium/ui/gfx/x/connection.h index 2e9baf64464..a103b431d71 100644 --- a/chromium/ui/gfx/x/connection.h +++ b/chromium/ui/gfx/x/connection.h @@ -5,25 +5,113 @@ #ifndef UI_GFX_X_CONNECTION_H_ #define UI_GFX_X_CONNECTION_H_ +#include <list> +#include <queue> + #include "base/component_export.h" +#include "ui/gfx/x/event.h" +#include "ui/gfx/x/extension_manager.h" #include "ui/gfx/x/xproto.h" namespace x11 { -using Atom = XProto::Atom; -using Window = XProto::Window; - // Represents a socket to the X11 server. -class COMPONENT_EXPORT(X11) Connection : public XProto { +class COMPONENT_EXPORT(X11) Connection : public XProto, + public ExtensionManager { public: - // Gets or creates the singeton connection. + class Delegate { + public: + virtual bool ShouldContinueStream() const = 0; + virtual void DispatchXEvent(x11::Event* event) = 0; + + protected: + virtual ~Delegate() = default; + }; + + // Gets or creates the singleton connection. static Connection* Get(); + explicit Connection(); + ~Connection(); + Connection(const Connection&) = delete; Connection(Connection&&) = delete; + XDisplay* display() const { return display_; } + xcb_connection_t* XcbConnection(); + + uint32_t extended_max_request_length() const { + return extended_max_request_length_; + } + + const Setup& setup() const { return setup_; } + const Screen& default_screen() const { return *default_screen_; } + x11::Window default_root() const { return default_screen().root; } + const Depth& default_root_depth() const { return *default_root_depth_; } + const VisualType& default_root_visual() const { + return *default_root_visual_; + } + + int DefaultScreenId() const; + + template <typename T> + T GenerateId() { + return static_cast<T>(xcb_generate_id(XcbConnection())); + } + + // Is the connection up and error-free? + bool Ready() const; + + // Write all requests to the socket. + void Flush(); + + // Flush and block until the server has responded to all requests. + void Sync(); + + // Read all responses from the socket without blocking. + void ReadResponses(); + + // Are there any events, errors, or replies already buffered? + bool HasPendingResponses() const; + + // Dispatch any buffered events, errors, or replies. + void Dispatch(Delegate* delegate); + + // Access the event buffer. Clients can add, delete, or modify events. + std::list<Event>& events() { return events_; } + private: - explicit Connection(XDisplay* display); + friend class FutureBase; + + struct Request { + Request(unsigned int sequence, FutureBase::ResponseCallback callback); + Request(Request&& other); + ~Request(); + + const unsigned int sequence; + FutureBase::ResponseCallback callback; + }; + + void AddRequest(unsigned int sequence, FutureBase::ResponseCallback callback); + + bool HasNextResponse() const; + + void PreDispatchEvent(const Event& event); + + int ScreenIndexFromRootWindow(x11::Window root) const; + + XDisplay* const display_; + + uint32_t extended_max_request_length_ = 0; + + Setup setup_; + Screen* default_screen_ = nullptr; + Depth* default_root_depth_ = nullptr; + VisualType* default_root_visual_ = nullptr; + + std::list<Event> events_; + + std::queue<Request> requests_; }; } // namespace x11 diff --git a/chromium/ui/gfx/x/connection_unittest.cc b/chromium/ui/gfx/x/connection_unittest.cc new file mode 100644 index 00000000000..de2285911a4 --- /dev/null +++ b/chromium/ui/gfx/x/connection_unittest.cc @@ -0,0 +1,108 @@ +// 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 "ui/gfx/x/connection.h" +#include "ui/gfx/x/xproto.h" + +#undef Bool + +#include <xcb/xcb.h> + +#include "testing/gtest/include/gtest/gtest.h" + +namespace x11 { + +namespace { + +Window CreateWindow(Connection* connection) { + Window window = connection->GenerateId<Window>(); + auto create_window_future = connection->CreateWindow({ + .depth = connection->default_root_depth().depth, + .wid = window, + .parent = connection->default_screen().root, + .width = 1, + .height = 1, + .override_redirect = Bool32(true), + }); + auto create_window_response = create_window_future.Sync(); + EXPECT_FALSE(create_window_response.error); + return window; +} + +} // namespace + +// Connection setup and teardown. +TEST(X11ConnectionTest, Basic) { + Connection connection; + ASSERT_TRUE(connection.XcbConnection()); + EXPECT_FALSE(xcb_connection_has_error(connection.XcbConnection())); +} + +TEST(X11ConnectionTest, Request) { + Connection connection; + ASSERT_TRUE(connection.XcbConnection()); + EXPECT_FALSE(xcb_connection_has_error(connection.XcbConnection())); + + Window window = CreateWindow(&connection); + + auto attributes = connection.GetWindowAttributes({window}).Sync(); + ASSERT_TRUE(attributes); + EXPECT_EQ(attributes->map_state, MapState::Unmapped); + EXPECT_TRUE(attributes->override_redirect); + + auto geometry = connection.GetGeometry({window}).Sync(); + ASSERT_TRUE(geometry); + EXPECT_EQ(geometry->x, 0); + EXPECT_EQ(geometry->y, 0); + EXPECT_EQ(geometry->width, 1u); + EXPECT_EQ(geometry->height, 1u); +} + +TEST(X11ConnectionTest, Event) { + Connection connection; + ASSERT_TRUE(connection.XcbConnection()); + EXPECT_FALSE(xcb_connection_has_error(connection.XcbConnection())); + + Window window = CreateWindow(&connection); + + auto cwa_future = connection.ChangeWindowAttributes({ + .window = window, + .event_mask = EventMask::PropertyChange, + }); + EXPECT_FALSE(cwa_future.Sync().error); + + auto prop_future = connection.ChangeProperty({ + .window = static_cast<x11::Window>(window), + .property = x11::Atom::WM_NAME, + .type = x11::Atom::STRING, + .format = CHAR_BIT, + .data_len = 1, + .data = std::vector<uint8_t>{0}, + }); + EXPECT_FALSE(prop_future.Sync().error); + + connection.ReadResponses(); + ASSERT_EQ(connection.events().size(), 1u); + XEvent& event = connection.events().front().xlib_event(); + auto property_notify_opcode = PropertyNotifyEvent::opcode; + EXPECT_EQ(event.type, property_notify_opcode); + EXPECT_EQ(event.xproperty.atom, static_cast<uint32_t>(x11::Atom::WM_NAME)); + EXPECT_EQ(event.xproperty.state, static_cast<int>(Property::NewValue)); +} + +TEST(X11ConnectionTest, Error) { + Connection connection; + ASSERT_TRUE(connection.XcbConnection()); + EXPECT_FALSE(xcb_connection_has_error(connection.XcbConnection())); + + Window invalid_window = connection.GenerateId<Window>(); + + auto geometry = connection.GetGeometry({invalid_window}).Sync(); + ASSERT_FALSE(geometry); + xcb_generic_error_t* error = geometry.error.get(); + EXPECT_EQ(error->error_code, XCB_DRAWABLE); + EXPECT_EQ(error->resource_id, static_cast<uint32_t>(invalid_window)); +} + +} // namespace x11 diff --git a/chromium/ui/gfx/x/event.cc b/chromium/ui/gfx/x/event.cc new file mode 100644 index 00000000000..238e06bb656 --- /dev/null +++ b/chromium/ui/gfx/x/event.cc @@ -0,0 +1,103 @@ +// 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 "ui/gfx/x/event.h" + +#include <X11/Xlibint.h> +#include <X11/extensions/XInput2.h> + +// Xlibint.h defines those as macros, which breaks the C++ versions in +// the std namespace. +#undef max +#undef min + +#include <cstring> + +#include "ui/gfx/x/connection.h" + +namespace x11 { + +Event::Event() = default; + +Event::Event(xcb_generic_event_t* xcb_event, + x11::Connection* connection, + bool sequence_valid) { + XDisplay* display = connection->display(); + + sequence_valid_ = sequence_valid; + sequence_ = xcb_event->full_sequence; + // KeymapNotify events are the only events that don't have a sequence. + if ((xcb_event->response_type & ~kSendEventMask) != + x11::KeymapNotifyEvent::opcode) { + // Rewrite the sequence to the last seen sequence so that Xlib doesn't + // think the sequence wrapped around. + xcb_event->sequence = XLastKnownRequestProcessed(display); + + // On the wire, events are 32 bytes except for generic events which are + // trailed by additional data. XCB inserts an extended 4-byte sequence + // between the 32-byte event and the additional data, so we need to shift + // the additional data over by 4 bytes so the event is back in its wire + // format, which is what Xlib and XProto are expecting. + if ((xcb_event->response_type & ~kSendEventMask) == + x11::GeGenericEvent::opcode) { + auto* ge = reinterpret_cast<xcb_ge_event_t*>(xcb_event); + memmove(&ge->full_sequence, &ge[1], ge->length * 4); + } + } + + // Xlib sometimes modifies |xcb_event|, so let it handle the event after + // we parse it with ReadEvent(). + ReadEvent(this, connection, reinterpret_cast<uint8_t*>(xcb_event)); + + _XEnq(display, reinterpret_cast<xEvent*>(xcb_event)); + if (!XEventsQueued(display, QueuedAlready)) { + // If Xlib gets an event it doesn't recognize (eg. from an + // extension it doesn't know about), it won't add the event to the + // queue. In this case, zero-out the event data. This will set + // the event type to 0, which does not correspond to any event. + // This is safe because event handlers should always check the + // event type before downcasting to a concrete event. + memset(&xlib_event_, 0, sizeof(xlib_event_)); + return; + } + XNextEvent(display, &xlib_event_); + if (xlib_event_.type == x11::GeGenericEvent::opcode) + XGetEventData(display, &xlib_event_.xcookie); +} + +Event::Event(Event&& event) { + memcpy(this, &event, sizeof(Event)); + memset(&event, 0, sizeof(Event)); +} + +Event& Event::operator=(Event&& event) { + Dealloc(); + memcpy(this, &event, sizeof(Event)); + memset(&event, 0, sizeof(Event)); + return *this; +} + +Event::~Event() { + Dealloc(); +} + +void Event::Dealloc() { + if (xlib_event_.type == x11::GeGenericEvent::opcode && + xlib_event_.xcookie.data) { + if (custom_allocated_xlib_event_) { + XIDeviceEvent* xiev = + static_cast<XIDeviceEvent*>(xlib_event_.xcookie.data); + delete[] xiev->valuators.mask; + delete[] xiev->valuators.values; + delete[] xiev->buttons.mask; + delete xiev; + } else { + XFreeEventData(xlib_event_.xcookie.display, &xlib_event_.xcookie); + } + } + if (deleter_) + deleter_(event_); +} + +} // namespace x11 diff --git a/chromium/ui/gfx/x/event.h b/chromium/ui/gfx/x/event.h new file mode 100644 index 00000000000..37073dbd584 --- /dev/null +++ b/chromium/ui/gfx/x/event.h @@ -0,0 +1,92 @@ +// 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. + +#ifndef UI_GFX_X_EVENT_H_ +#define UI_GFX_X_EVENT_H_ + +#include <X11/Xlib.h> +#include <xcb/xcb.h> + +#include <cstdint> +#include <utility> + +#include "base/component_export.h" + +namespace x11 { + +class Connection; +class Event; + +COMPONENT_EXPORT(X11) +void ReadEvent(Event* event, Connection* connection, const uint8_t* buffer); + +class COMPONENT_EXPORT(X11) Event { + public: + // Used to create events for testing. + template <typename T> + Event(XEvent* xlib_event, T&& xproto_event) { + sequence_valid_ = true; + sequence_ = xlib_event_.xany.serial; + custom_allocated_xlib_event_ = true; + xlib_event_ = *xlib_event; + type_id_ = T::type_id; + deleter_ = [](void* event) { delete reinterpret_cast<T*>(event); }; + event_ = new T(std::forward<T>(xproto_event)); + } + + Event(); + Event(xcb_generic_event_t* xcb_event, + Connection* connection, + bool sequence_valid = true); + + Event(const Event&) = delete; + Event& operator=(const Event&) = delete; + + Event(Event&& event); + Event& operator=(Event&& event); + + ~Event(); + + template <typename T> + T* As() { + if (type_id_ == T::type_id) + return reinterpret_cast<T*>(event_); + return nullptr; + } + + template <typename T> + const T* As() const { + return const_cast<Event*>(this)->As<T>(); + } + + bool sequence_valid() const { return sequence_valid_; } + uint32_t sequence() const { return sequence_; } + + const XEvent& xlib_event() const { return xlib_event_; } + XEvent& xlib_event() { return xlib_event_; } + + private: + friend void ReadEvent(Event* event, + Connection* connection, + const uint8_t* buffer); + + void Dealloc(); + + bool sequence_valid_ = false; + uint32_t sequence_ = 0; + + // Indicates if |xlib_event_| was allocated manually and therefore + // needs to be freed manually. + bool custom_allocated_xlib_event_ = false; + XEvent xlib_event_{}; + + // XProto event state. + int type_id_ = 0; + void (*deleter_)(void*) = nullptr; + void* event_ = nullptr; +}; + +} // namespace x11 + +#endif // UI_GFX_X_EVENT_H_ diff --git a/chromium/ui/gfx/x/gen_xproto.py b/chromium/ui/gfx/x/gen_xproto.py index 4a34901012d..24a2efcc245 100644 --- a/chromium/ui/gfx/x/gen_xproto.py +++ b/chromium/ui/gfx/x/gen_xproto.py @@ -23,15 +23,15 @@ # #include "base/component_export.h" # #include "ui/gfx/x/xproto_types.h" # -# typedef struct _XDisplay XDisplay; -# # namespace x11 { # +# class Connection; +# # class COMPONENT_EXPORT(X11) XProto { # public: -# explicit XProto(XDisplay* display); +# explicit XProto(Connection* connection); # -# XDisplay* display() { return display_; } +# Connection* connection() const { return connection_; } # # struct RGB { # uint16_t red{}; @@ -54,7 +54,7 @@ # Future<QueryColorsReply> QueryColors(const QueryColorsRequest& request); # # private: -# XDisplay* display_; +# Connection* const connection_; # }; # # } // namespace x11 @@ -66,12 +66,13 @@ # #include <xcb/xcb.h> # #include <xcb/xcbext.h> # -# #include "base/logging.h" +# #include "base/notreached.h" +# #include "base/check_op.h" # #include "ui/gfx/x/xproto_internal.h" # # namespace x11 { # -# XProto::XProto(XDisplay* display) : display_(display) {} +# XProto::XProto(Connection* connection) : connection_(connection) {} # # Future<XProto::QueryColorsReply> # XProto::QueryColors( @@ -102,7 +103,7 @@ # Write(&pixels_elem, &buf); # } # -# return x11::SendRequest<XProto::QueryColorsReply>(display_, &buf); +# return x11::SendRequest<XProto::QueryColorsReply>(connection_, &buf); # } # # template<> COMPONENT_EXPORT(X11) @@ -168,6 +169,8 @@ from __future__ import print_function import argparse import collections +import functools +import itertools import os import re import sys @@ -177,61 +180,106 @@ import types # so this global is unavoidable. output = collections.defaultdict(int) -UPPER_CASE_PATTERN = re.compile(r'^[A-Z0-9_]+$') - - -def adjust_type_case(name): - if UPPER_CASE_PATTERN.match(name): - SPECIAL = { - 'ANIMCURSORELT': 'AnimationCursorElement', - 'CA': 'ChangeAlarmAttribute', - 'CHAR2B': 'Char16', - 'CHARINFO': 'CharInfo', - 'COLORITEM': 'ColorItem', - 'COLORMAP': 'ColorMap', - 'CP': 'CreatePictureAttribute', - 'CW': 'CreateWindowAttribute', - 'DAMAGE': 'DamageId', - 'DIRECTFORMAT': 'DirectFormat', - 'DOTCLOCK': 'DotClock', - 'FBCONFIG': 'FbConfig', - 'FLOAT32': 'float', - 'FLOAT64': 'double', - 'FONTPROP': 'FontProperty', - 'GC': 'GraphicsContextAttribute', - 'GCONTEXT': 'GraphicsContext', - 'GLYPHINFO': 'GlyphInfo', - 'GLYPHSET': 'GlyphSet', - 'INDEXVALUE': 'IndexValue', - 'KB': 'Keyboard', - 'KEYCODE': 'KeyCode', - 'KEYCODE32': 'KeyCode32', - 'KEYSYM': 'KeySym', - 'LINEFIX': 'LineFix', - 'OP': 'Operation', - 'PBUFFER': 'PBuffer', - 'PCONTEXT': 'PContext', - 'PICTDEPTH': 'PictDepth', - 'PICTFORMAT': 'PictFormat', - 'PICTFORMINFO': 'PictFormInfo', - 'PICTSCREEN': 'PictScreen', - 'PICTVISUAL': 'PictVisual', - 'POINTFIX': 'PointFix', - 'SEGMENT': 'SEGMENT', - 'SPANFIX': 'SpanFix', - 'SUBPICTURE': 'SubPicture', - 'SYSTEMCOUNTER': 'SystemCounter', - 'TIMECOORD': 'TimeCoord', - 'TIMESTAMP': 'TimeStamp', - 'VISUALID': 'VisualId', - 'VISUALTYPE': 'VisualType', - 'WAITCONDITION': 'WaitCondition', - } - if name in SPECIAL: - return SPECIAL[name] +RENAME = { + 'ANIMCURSORELT': 'AnimationCursorElement', + 'CA': 'ChangeAlarmAttribute', + 'CHAR2B': 'Char16', + 'CHARINFO': 'CharInfo', + 'COLORITEM': 'ColorItem', + 'COLORMAP': 'ColorMap', + 'Connection': 'RandRConnection', + 'CP': 'CreatePictureAttribute', + 'CW': 'CreateWindowAttribute', + 'DAMAGE': 'DamageId', + 'DIRECTFORMAT': 'DirectFormat', + 'DOTCLOCK': 'DotClock', + 'FBCONFIG': 'FbConfig', + 'FLOAT32': 'float', + 'FLOAT64': 'double', + 'FONTPROP': 'FontProperty', + 'GC': 'GraphicsContextAttribute', + 'GCONTEXT': 'GraphicsContext', + 'GLYPHINFO': 'GlyphInfo', + 'GLYPHSET': 'GlyphSet', + 'INDEXVALUE': 'IndexValue', + 'KB': 'Keyboard', + 'KEYCODE': 'KeyCode', + 'KEYCODE32': 'KeyCode32', + 'KEYSYM': 'KeySym', + 'LINEFIX': 'LineFix', + 'OP': 'Operation', + 'PBUFFER': 'PBuffer', + 'PCONTEXT': 'PContext', + 'PICTDEPTH': 'PictDepth', + 'PICTFORMAT': 'PictFormat', + 'PICTFORMINFO': 'PictFormInfo', + 'PICTSCREEN': 'PictScreen', + 'PICTVISUAL': 'PictVisual', + 'POINTFIX': 'PointFix', + 'SPANFIX': 'SpanFix', + 'SUBPICTURE': 'SubPicture', + 'SYSTEMCOUNTER': 'SystemCounter', + 'TIMECOORD': 'TimeCoord', + 'TIMESTAMP': 'Time', + 'VISUALID': 'VisualId', + 'VISUALTYPE': 'VisualType', + 'WAITCONDITION': 'WaitCondition', +} + +READ_SPECIAL = set([ + ('xcb', 'Setup'), +]) + +WRITE_SPECIAL = set([ + ('xcb', 'ClientMessage'), + ('xcb', 'UnmapNotify'), + ('xcb', 'SelectionNotify'), +]) + + +def adjust_type_name(name): + if name in RENAME: + return RENAME[name] + # If there's an underscore, then this is either snake case or upper case. + if '_' in name: return ''.join([ token[0].upper() + token[1:].lower() for token in name.split('_') ]) + if name.isupper(): + name = name.lower() + # Now the only possibilities are caml case and pascal case. It could also + # be snake case with a single word, but that would be same as caml case. + # To convert all of these, just capitalize the first letter. + return name[0].upper() + name[1:] + + +# Given a list of event names like ["KeyPress", "KeyRelease"], returns a name +# suitable for use as a base event like "Key". +def event_base_name(names): + # If there's only one event in this group, the "common name" is just + # the event name. + if len(names) == 1: + return names[0] + + # Handle a few special cases where the longest common prefix is empty: eg. + # EnterNotify/LeaveNotify/FocusIn/FocusOut -> Crossing. + EVENT_NAMES = [ + ('TouchBegin', 'Device'), + ('RawTouchBegin', 'RawDevice'), + ('Enter', 'Crossing'), + ('EnterNotify', 'Crossing'), + ('DeviceButtonPress', 'LegacyDevice'), + ] + for name, rename in EVENT_NAMES: + if name in names: + return rename + + # Use the longest common prefix of the event names as the base name. + name = ''.join( + chars[0] + for chars in itertools.takewhile(lambda chars: len(set(chars)) == 1, + zip(*names))) + assert name return name @@ -251,17 +299,6 @@ class Indent: self.xproto.write(self.closing_line) -class NullContext: - def __init__(self): - pass - - def __enter__(self): - pass - - def __exit__(self, exc_type, exc_value, exc_traceback): - pass - - # Make all members of |obj|, given by |fields|, visible in # the local scope while this class is alive. class ScopedFields: @@ -302,20 +339,37 @@ def safe_name(name): return name -class GenXproto: - def __init__(self, args, xcbgen): +class FileWriter: + def __init__(self): + self.indent = 0 + + # Write a line to the current file. + def write(self, line=''): + indent = self.indent if line and not line.startswith('#') else 0 + print((' ' * indent) + line, file=self.file) + + +class GenXproto(FileWriter): + def __init__(self, proto, proto_dir, gen_dir, xcbgen, all_types): + FileWriter.__init__(self) + # Command line arguments - self.args = args + self.proto = proto + self.xml_filename = os.path.join(proto_dir, '%s.xml' % proto) + self.header_file = open(os.path.join(gen_dir, '%s.h' % proto), 'w') + self.source_file = open(os.path.join(gen_dir, '%s.cc' % proto), 'w') + self.undef_file = open(os.path.join(gen_dir, '%s_undef.h' % proto), + 'w') # Top-level xcbgen python module self.xcbgen = xcbgen + # Types for every module including this one + self.all_types = all_types + # The last used UID for making unique names self.prev_id = -1 - # Current indentation level - self.indent = 0 - # Current file to write to self.file = None @@ -331,7 +385,7 @@ class GenXproto: # Map from type names to a set of types. Certain types # like enums and simple types can alias each other. - self.types = collections.defaultdict(set) + self.types = collections.defaultdict(list) # Set of names of simple types to be replaced with enums self.replace_with_enum = set() @@ -339,10 +393,11 @@ class GenXproto: # Map of enums to their underlying types self.enum_types = collections.defaultdict(set) - # Write a line to the current file. - def write(self, line=''): - indent = self.indent if line and not line.startswith('#') else 0 - print((' ' * indent) + line, file=self.file) + # Map from (XML tag, XML name) to XML element + self.module_names = {} + + # Enums that represent bit masks. + self.bitenums = [] # Geenerate an ID suitable for use in temporary variable names. def new_uid(self, ): @@ -361,57 +416,70 @@ class GenXproto: return '' def rename_type(self, t, name): - name = list(name) - for i in range(1, len(name)): - name[i] = adjust_type_case(name[i]) - name[-1] += self.type_suffix(t) - return name - - # Given an xcbgen.xtypes.Type, returns a C++-namespace-qualified - # string that looks like Input::InputClass::Key. - def qualtype(self, t, name): # Work around a bug in xcbgen: ('int') should have been ('int',) if name == 'int': name = ('int', ) - name = self.rename_type(t, name) + name = list(name) if name[0] == 'xcb': # Use namespace x11 instead of xcb. name[0] = 'x11' - # We want the non-extension X11 structures to live in a class too. - if len(name) == 2: - name[1:1] = ['XProto'] + for i in range(1, len(name)): + name[i] = adjust_type_name(name[i]) + name[-1] += self.type_suffix(t) + return name + + # Given an unqualified |name| like ('Window') and a namespace like ['x11'], + # returns a fully qualified name like ('x11', 'Window'). + def qualify_type(self, name, namespace): + if tuple(namespace + name) in self.all_types: + return namespace + name + return self.qualify_type(name, namespace[:-1]) + + # Given an xcbgen.xtypes.Type, returns a C++-namespace-qualified + # string that looks like Input::InputClass::Key. + def qualtype(self, t, name): + name = self.rename_type(t, name) # Try to avoid adding namespace qualifiers if they're not necessary. chop = 0 for t1, t2 in zip(name, self.namespace): if t1 != t2: break + if self.qualify_type(name[chop + 1:], self.namespace) != name: + break chop += 1 return '::'.join(name[chop:]) def fieldtype(self, field): - return self.qualtype(field.type, field.field_type) + return self.qualtype(field.type, + field.enum if field.enum else field.field_type) + + def switch_fields(self, switch): + fields = [] + for case in switch.bitcases: + if case.field_name: + fields.append(case) + else: + fields.extend(case.type.fields) + return fields def add_field_to_scope(self, field, obj): - if not field.visible or not field.wire: + if not field.visible or (not field.wire and not field.isfd): + return 0 + + field_name = safe_name(field.field_name) + + if field.type.is_switch: + self.write('auto& %s = %s;' % (field_name, obj)) return 0 self.scope.append(field) - field_name = safe_name(field.field_name) - # There's one case where we would have generated: - # auto& enable = enable.enable; - # To prevent a compiler error from trying to use the variable - # in its own definition, save to a temporary variable first. - if field_name == obj: - tmp_id = self.new_uid() - self.write('auto& tmp%d = %s.%s;' % (tmp_id, obj, field_name)) - self.write('auto& %s = tmp%d;' % (field_name, tmp_id)) - elif field.for_list: - self.write('%s %s;' % (self.fieldtype(field), field_name)) + if field.for_list or field.for_switch: + self.write('%s %s{};' % (self.fieldtype(field), field_name)) else: self.write('auto& %s = %s.%s;' % (field_name, obj, field_name)) @@ -432,9 +500,9 @@ class GenXproto: # Work around conflicts caused by Xlib's liberal use of macros. def undef(self, name): - print('#ifdef %s' % name, file=self.args.undeffile) - print('#undef %s' % name, file=self.args.undeffile) - print('#endif', file=self.args.undeffile) + print('#ifdef %s' % name, file=self.undef_file) + print('#undef %s' % name, file=self.undef_file) + print('#endif', file=self.undef_file) def expr(self, expr): if expr.op == 'popcount': @@ -476,16 +544,53 @@ class GenXproto: assert expr.lenfield_name return expr.lenfield_name + def get_xidunion_element(self, name): + key = ('xidunion', name[-1]) + return self.module_names.get(key, None) + + def declare_xidunion(self, xidunion, xidname): + names = [type_element.text for type_element in xidunion] + types = list(set([self.module.get_type(name) for name in names])) + assert len(types) == 1 + value_type = types[0] + value_typename = self.qualtype(value_type, value_type.name) + with Indent(self, 'struct %s {' % xidname, '};'): + self.write('%s() : value{} {}' % xidname) + self.write() + for name in names: + cpp_name = self.module.get_type_name(name) + typename = self.qualtype(value_type, cpp_name) + self.write('%s(%s value) : value{static_cast<%s>(value)} {}' % + (xidname, typename, value_typename)) + self.write( + 'operator %s() const { return static_cast<%s>(value); }' % + (typename, typename)) + self.write() + self.write('%s value{};' % value_typename) + def declare_simple(self, item, name): # The underlying type of an enum must be integral, so avoid defining # FLOAT32 or FLOAT64. Usages are renamed to float and double instead. renamed = tuple(self.rename_type(item, name)) - if name[-1] not in ('FLOAT32', 'FLOAT64' - ) and renamed not in self.replace_with_enum: - self.write( - 'enum class %s : %s {};' % - (adjust_type_case(name[-1]), self.qualtype(item, item.name))) + if (name[-1] in ('FLOAT32', 'FLOAT64') + or renamed in self.replace_with_enum): + return + elif name[-1] == 'FP1616': + # Xcbproto defines FP1616 as uint32_t instead of a struct of + # two 16-bit ints, which is how it's intended to be used. + with Indent(self, 'struct Fp1616 {', '};'): + self.write('int16_t integral;') + self.write('uint16_t frac;') self.write() + return + + xidunion = self.get_xidunion_element(name) + if xidunion: + self.declare_xidunion(xidunion, renamed[-1]) + else: + self.write('enum class %s : %s {};' % + (renamed[-1], self.qualtype(item, item.name))) + self.write() def copy_primitive(self, name): self.write('%s(&%s, &buf);' % @@ -495,28 +600,36 @@ class GenXproto: type_name = self.fieldtype(field) name = safe_name(field.field_name) + def copy_basic(): + self.write('%s %s;' % (type_name, name)) + self.copy_primitive(name) + if name in ('major_opcode', 'minor_opcode'): assert not self.is_read - is_ext = any( - [f.field_name == 'minor_opcode' for f in field.parent.fields]) - if is_ext and name == 'major_opcode': - self.write('// Caller fills in extension major opcode.') - self.write('Pad(&buf, sizeof(%s));' % type_name) + is_ext = self.module.namespace.is_ext + self.write( + '%s %s = %s;' % + (type_name, name, 'info_.major_opcode' if is_ext + and name == 'major_opcode' else field.parent[0].opcode)) + self.copy_primitive(name) + elif name == 'response_type': + if self.is_read: + copy_basic() else: - self.write('%s %s = %s;' % - (type_name, name, field.parent.opcode)) + container_type, container_name = field.parent + assert container_type.is_event + opcode = container_type.opcodes[container_name] + self.write('%s %s = %s;' % (type_name, name, opcode)) self.copy_primitive(name) - elif name in ('response_type', 'sequence', 'extension'): + elif name in ('extension', 'error_code', 'event_type'): assert self.is_read - self.write('%s %s;' % (type_name, name)) - self.copy_primitive(name) + copy_basic() elif name == 'length': if not self.is_read: self.write('// Caller fills in length for writes.') self.write('Pad(&buf, sizeof(%s));' % type_name) else: - self.write('%s %s;' % (type_name, name)) - self.copy_primitive(name) + copy_basic() else: assert field.type.is_expr assert (not isinstance(field.type, self.xcbgen.xtypes.Enum)) @@ -527,48 +640,52 @@ class GenXproto: def declare_case(self, case): assert case.type.is_case != case.type.is_bitcase - with (Indent(self, 'struct {', '} %s;' % safe_name(case.field_name)) - if case.field_name else NullContext()): - for case_field in case.type.fields: - self.declare_field(case_field) - - def copy_case(self, case, switch_var): - op = 'CaseEq' if case.type.is_case else 'BitAnd' + fields = [ + field for case_field in case.type.fields + for field in self.declare_field(case_field) + ] + if not case.field_name: + return fields + name = safe_name(case.field_name) + with Indent(self, 'struct %s_t {' % name, '};'): + for field in fields: + self.write('%s %s{};' % field) + return [(name + '_t', name)] + + def copy_case(self, case, switch_name): + op = 'CaseEq' if case.type.is_case else 'CaseAnd' condition = ' || '.join([ - '%s(%s, %s)' % (op, switch_var, self.expr(expr)) + '%s(%s_expr, %s)' % (op, switch_name, self.expr(expr)) for expr in case.type.expr ]) with Indent(self, 'if (%s) {' % condition, '}'): - with (ScopedFields(self, case.field_name, case.type.fields) - if case.field_name else NullContext()): + if case.field_name: + fields = [case] + obj = '(*%s.%s)' % (switch_name, safe_name(case.field_name)) + else: + fields = case.type.fields + obj = '*' + switch_name + for case_field in fields: + name = safe_name(case_field.field_name) + if case_field.visible and self.is_read: + self.write('%s.%s.emplace();' % (switch_name, name)) + with ScopedFields(self, obj, case.type.fields): for case_field in case.type.fields: - assert case_field.wire self.copy_field(case_field) def declare_switch(self, field): - t = field.type - name = safe_name(field.field_name) - - with Indent(self, 'struct {', '} %s;' % name): - for case in t.bitcases: - self.declare_case(case) + return [('base::Optional<%s>' % field_type, field_name) + for case in field.type.bitcases + for field_type, field_name in self.declare_case(case)] def copy_switch(self, field): t = field.type name = safe_name(field.field_name) - scope_fields = [] + self.write('auto %s_expr = %s;' % (name, self.expr(t.expr))) for case in t.bitcases: - if case.field_name: - scope_fields.append(case) - else: - scope_fields.extend(case.type.fields) - with Indent(self, '{', '}'), ScopedFields(self, name, scope_fields): - switch_var = name + '_expr' - self.write('auto %s = %s;' % (switch_var, self.expr(t.expr))) - for case in t.bitcases: - self.copy_case(case, switch_var) + self.copy_case(case, name) def declare_list(self, field): t = field.type @@ -588,7 +705,7 @@ class GenXproto: type_name = 'std::string' else: type_name = 'std::vector<%s>' % type_name - self.write('%s %s{};' % (type_name, name)) + return [(type_name, name)] def copy_list(self, field): t = field.type @@ -604,35 +721,60 @@ class GenXproto: with Indent(self, 'for (auto& %s_elem : %s) {' % (name, name), '}'): elem_name = name + '_elem' elem_type = t.member - if elem_type.is_simple or elem_type.is_union: - assert (not isinstance(elem_type, self.xcbgen.xtypes.Enum)) - self.copy_primitive(elem_name) - else: - assert elem_type.is_container - self.copy_container(elem_type, elem_name) + elem_field = self.xcbgen.expr.Field(elem_type, field.field_type, + elem_name, field.visible, + field.wire, field.auto, + field.enum, field.isfd) + elem_field.for_list = None + elem_field.for_switch = None + self.copy_field(elem_field) + + def generate_switch_var(self, field): + name = safe_name(field.field_name) + for case in field.for_switch.type.bitcases: + case_field = case if case.field_name else case.type.fields[0] + self.write('SwitchVar(%s, %s.%s.has_value(), %s, &%s);' % + (self.expr(case.type.expr[0]), + safe_name(field.for_switch.field_name), + safe_name(case_field.field_name), + 'true' if case.type.is_bitcase else 'false', name)) def declare_field(self, field): t = field.type name = safe_name(field.field_name) - if not field.wire or not field.visible or field.for_list: - return + if not field.visible or field.for_list or field.for_switch: + return [] if t.is_switch: - self.declare_switch(field) - elif t.is_list: - self.declare_list(field) - else: - self.write( - '%s %s{};' % - (self.qualtype(field.type, field.enum - if field.enum else field.field_type), name)) + return self.declare_switch(field) + if t.is_list: + return self.declare_list(field) + return [(self.fieldtype(field), name)] def copy_field(self, field): + if not field.wire and not field.isfd: + return + t = field.type + renamed = tuple(self.rename_type(field.type, field.field_type)) + if t.is_list: + t.member = self.all_types.get(renamed, t.member) + else: + t = self.all_types.get(renamed, t) name = safe_name(field.field_name) self.write('// ' + name) + + # If this is a generated field, initialize the value of the field + # variable from the given context. + if not self.is_read: + if field.for_list: + self.write('%s = %s.size();' % + (name, safe_name(field.for_list.field_name))) + if field.for_switch: + self.generate_switch_var(field) + if t.is_pad: if t.align > 1: assert t.nmemb == 1 @@ -642,11 +784,6 @@ class GenXproto: self.write('Pad(&buf, %d);' % t.nmemb) elif not field.visible: self.copy_special_field(field) - elif field.for_list: - if not self.is_read: - self.write('%s = %s.size();' % - (name, safe_name(field.for_list.field_name))) - self.copy_primitive(name) elif t.is_switch: self.copy_switch(field) elif t.is_list: @@ -656,6 +793,9 @@ class GenXproto: elif t.is_container: with Indent(self, '{', '}'): self.copy_container(t, name) + elif t.is_fd: + # TODO(https://crbug.com/1066670): Copy FDs out of band. + self.write('NOTIMPLEMENTED();') else: assert t.is_simple if field.enum: @@ -663,6 +803,8 @@ class GenXproto: else: self.copy_primitive(name) + self.write() + def declare_enum(self, enum): def declare_enum_entry(name, value): name = safe_name(name) @@ -672,7 +814,7 @@ class GenXproto: self.undef(enum.name[-1]) with Indent( self, 'enum class %s : %s {' % - (adjust_type_case(enum.name[-1]), self.enum_types[enum.name][0] + (adjust_type_name(enum.name[-1]), self.enum_types[enum.name][0] if enum.name in self.enum_types else 'int'), '};'): bitnames = set([name for name, _ in enum.bits]) for name, value in enum.values: @@ -686,7 +828,7 @@ class GenXproto: # The size of enum types may be different depending on the # context, so they should always be casted to the contextual # underlying type before calling Read() or Write(). - underlying_type = self.fieldtype(field) + underlying_type = self.qualtype(field.type, field.type.name) tmp_name = 'tmp%d' % self.new_uid() real_name = safe_name(field.field_name) self.write('%s %s;' % (underlying_type, tmp_name)) @@ -699,30 +841,84 @@ class GenXproto: self.write('%s = static_cast<%s>(%s);' % (real_name, enum_type, tmp_name)) - def declare_container(self, struct): - name = struct.name[-1] + self.type_suffix(struct) + def declare_fields(self, fields): + for field in fields: + for field_type_name in self.declare_field(field): + self.write('%s %s{};' % field_type_name) + + def declare_event(self, event, name): + event_name = name[-1] + 'Event' + self.undef(event_name) + with Indent(self, 'struct %s {' % adjust_type_name(event_name), '};'): + self.write('static constexpr int type_id = %d;' % event.type_id) + if len(event.opcodes) == 1: + self.write('static constexpr uint8_t opcode = %s;' % + event.opcodes[name]) + else: + with Indent(self, 'enum Opcode {', '} opcode{};'): + items = [(int(x), y) + for (y, x) in event.enum_opcodes.items()] + for opcode, opname in sorted(items): + self.undef(opname) + self.write('%s = %s,' % (opname, opcode)) + self.write('bool send_event{};') + self.declare_fields(event.fields) + self.write() + + def declare_container(self, struct, struct_name): + name = struct_name[-1] + self.type_suffix(struct) self.undef(name) - with Indent(self, 'struct %s {' % adjust_type_case(name), '};'): - for field in struct.fields: - self.declare_field(field) + with Indent(self, 'struct %s {' % adjust_type_name(name), '};'): + self.declare_fields(struct.fields) self.write() def copy_container(self, struct, name): assert not struct.is_union with ScopedFields(self, name, struct.fields): for field in struct.fields: - if field.wire: - self.copy_field(field) - self.write() + self.copy_field(field) + + def read_special_container(self, struct, name): + self.namespace = ['x11'] + name = self.qualtype(struct, name) + self.write('template <> COMPONENT_EXPORT(X11)') + self.write('%s Read<%s>(' % (name, name)) + with Indent(self, ' const uint8_t* buffer) {', '}'): + self.write('ReadBuffer buf{buffer, 0UL};') + self.write('%s obj;' % name) + self.write() + self.is_read = True + self.copy_container(struct, 'obj') + self.write('return obj;') + self.write() + + def write_special_container(self, struct, name): + self.namespace = ['x11'] + name = self.qualtype(struct, name) + self.write('template <> COMPONENT_EXPORT(X11)') + self.write('std::vector<uint8_t> Write<%s>(' % name) + with Indent(self, ' const %s& obj) {' % name, '}'): + self.write('WriteBuffer buf;') + self.write() + self.is_read = False + self.copy_container(struct, 'obj') + self.write('return buf;') + self.write() def declare_union(self, union): name = union.name[-1] + if union.elt.tag == 'eventstruct': + # There's only one of these in all of the protocol descriptions. + # It's just used to represent any 32-byte event for XInput. + self.write('using %s = std::array<uint8_t, 32>;' % name) + return with Indent(self, 'union %s {' % name, '};'): self.write('%s() { memset(this, 0, sizeof(*this)); }' % name) self.write() for field in union.fields: - type_name = self.fieldtype(field) - self.write('%s %s;' % (type_name, safe_name(field.field_name))) + field_type_names = self.declare_field(field) + assert len(field_type_names) == 1 + self.write('%s %s;' % field_type_names[0]) self.write( 'static_assert(std::is_trivially_copyable<%s>::value, "");' % name) self.write() @@ -730,26 +926,30 @@ class GenXproto: def declare_request(self, request): method_name = request.name[-1] request_name = method_name + 'Request' - reply_name = method_name + 'Reply' + reply_name = method_name + 'Reply' if request.reply else 'void' - self.declare_container(request) - if request.reply: - self.declare_container(request.reply) - else: - reply_name = 'void' + in_class = self.namespace == ['x11', self.class_name] - self.write('using %sResponse = Response<%s>;' % - (method_name, reply_name)) - self.write() + if not in_class or self.module.namespace.is_ext: + self.declare_container(request, request.name) + if request.reply: + self.declare_container(request.reply, request.reply.name) - self.write('Future<%s> %s(' % (reply_name, method_name)) - self.write(' const %s& request);' % request_name) - self.write() + self.write('using %sResponse = Response<%s>;' % + (method_name, reply_name)) + self.write() + + if in_class: + self.write('Future<%s> %s(' % (reply_name, method_name)) + self.write(' const %s& request);' % request_name) + self.write() def define_request(self, request): method_name = '%s::%s' % (self.class_name, request.name[-1]) - request_name = method_name + 'Request' - reply_name = method_name + 'Reply' + prefix = (method_name + if self.module.namespace.is_ext else request.name[-1]) + request_name = prefix + 'Request' + reply_name = prefix + 'Reply' reply = request.reply if not reply: @@ -758,6 +958,12 @@ class GenXproto: self.write('Future<%s>' % reply_name) self.write('%s(' % method_name) with Indent(self, ' const %s& request) {' % request_name, '}'): + cond = '!connection_->Ready()' + if self.module.namespace.is_ext: + cond += ' || !present()' + self.write('if (%s)' % cond) + self.write(' return {};') + self.write() self.namespace = ['x11', self.class_name] self.write('WriteBuffer buf;') self.write() @@ -765,7 +971,7 @@ class GenXproto: self.copy_container(request, 'request') self.write('Align(&buf, 4);') self.write() - self.write('return x11::SendRequest<%s>(display_, &buf);' % + self.write('return x11::SendRequest<%s>(connection_, &buf);' % reply_name) self.write() @@ -789,14 +995,38 @@ class GenXproto: self.write('return reply;') self.write() + def define_event(self, event, name): + self.namespace = ['x11'] + name = self.qualtype(event, name) + self.write('template <> COMPONENT_EXPORT(X11)') + self.write('void ReadEvent<%s>(' % name) + with Indent(self, ' %s* event_, const uint8_t* buffer) {' % name, + '}'): + self.write('ReadBuffer buf{buffer, 0UL};') + self.write() + self.is_read = True + self.copy_container(event, '(*event_)') + self.write() + + def define_type(self, item, name): + if name in READ_SPECIAL: + self.read_special_container(item, name) + if name in WRITE_SPECIAL: + self.write_special_container(item, name) + if isinstance(item, self.xcbgen.xtypes.Request): + self.define_request(item) + elif item.is_event: + self.define_event(item, name) + def declare_type(self, item, name): if item.is_union: self.declare_union(item) elif isinstance(item, self.xcbgen.xtypes.Request): self.declare_request(item) + elif item.is_event: + self.declare_event(item, name) elif item.is_container: - item.name = name - self.declare_container(item) + self.declare_container(item, name) elif isinstance(item, self.xcbgen.xtypes.Enum): self.declare_enum(item) else: @@ -823,72 +1053,141 @@ class GenXproto: if enums: assert len(enums) == 1 enum = enums[0] - field.enum = self.module.get_type(enum).name if enums else None + field.enum = self.module.get_type(enum).name self.enum_types[enum].add(field.type.name) + else: + field.enum = None def resolve_type(self, t, name): renamed = tuple(self.rename_type(t, name)) - if t in self.types[renamed]: - return - self.types[renamed].add(t) + assert renamed[0] == 'x11' + assert t not in self.types[renamed] + self.types[renamed].append(t) + self.all_types[renamed] = t + + if isinstance(t, self.xcbgen.xtypes.Enum): + self.bitenums.append((t, name)) if not t.is_container: return - if t.is_switch: - fields = {} - for case in t.bitcases: - if case.field_name: - fields[case.field_name] = case - else: - for field in case.type.fields: - fields[field.field_name] = field - else: - fields = {field.field_name: field for field in t.fields} + fields = { + field.field_name: field + for field in (self.switch_fields(t) if t.is_switch else t.fields) + } self.resolve_element(t.elt, fields) for field in fields.values(): - field.parent = t - field.for_list = None + if field.field_name == 'sequence': + field.visible = True + field.parent = (t, name) + # |for_list| and |for_switch| may have already been set when + # processing other fields in this structure. + field.for_list = getattr(field, 'for_list', None) + field.for_switch = getattr(field, 'for_switch', None) + + for is_type, for_type in ((field.type.is_list, 'for_list'), + (field.type.is_switch, 'for_switch')): + if not is_type: + continue + expr = field.type.expr + field_name = expr.lenfield_name + if (expr.op in (None, 'calculate_len') + and field_name in fields): + setattr(fields[field_name], for_type, field) + if field.type.is_switch or field.type.is_case_or_bitcase: self.resolve_type(field.type, field.field_type) - elif field.type.is_list: - self.resolve_type(field.type.member, field.type.member.name) - expr = field.type.expr - if not expr.op and expr.lenfield_name in fields: - fields[expr.lenfield_name].for_list = field - else: - self.resolve_type(field.type, field.type.name) if isinstance(t, self.xcbgen.xtypes.Request) and t.reply: self.resolve_type(t.reply, t.reply.name) + # Multiple event names may map to the same underlying event. For these + # cases, we want to avoid duplicating the event structure. Instead, put + # all of these events under one structure with an additional opcode field + # to indicate the type of event. + def uniquify_events(self): + # Manually merge some events in XInput. These groups of 8 events have + # idential structure, and are merged as XIDeviceEvent in Xlib. To avoid + # duplication, and to ease the transition from Xlib to XProto, we merge + # the events here too. + # TODO(thomasanderson): We should avoid adding workarounds for xcbproto. + # Instead, the protocol files should be modified directly. However, + # some of the changes we want to make change the API, so the changes + # should be made in a fork in //third_party rather than upstreamed. + MERGE = [ + ([ + 'KeyPress', 'KeyRelease', 'ButtonPress', 'ButtonRelease', + 'Motion', 'TouchBegin', 'TouchUpdate', 'TouchEnd' + ], []), + ([ + 'RawKeyPress', 'RawKeyRelease', 'RawButtonPress', + 'RawButtonRelease', 'RawMotion', 'RawTouchBegin', + 'RawTouchUpdate', 'RawTouchEnd' + ], []), + ] + for i, (name, t) in enumerate(self.module.all): + if t.is_event and name[1] == 'Input': + for names, event in MERGE: + if name[-1] in names: + if event: + event[0].opcodes.update(t.opcodes) + self.module.all[i] = name, event[0] + else: + event.append(t) + + types = [] + events = set() + for name, t in self.module.all: + if not t.is_event or len(t.opcodes) == 1: + types.append((name, t)) + continue + + renamed = tuple(self.rename_type(t, name)) + self.all_types[renamed] = t + if t in events: + continue + events.add(t) + + names = [name[-1] for name in t.opcodes.keys()] + name = name[:-1] + (event_base_name(names), ) + types.append((name, t)) + + t.enum_opcodes = {} + for opname in t.opcodes: + opcode = t.opcodes[opname] + opname = opname[-1] + if opname.startswith(name[-1]): + opname = opname[len(name[-1]):] + t.enum_opcodes[opname] = opcode + self.module.all = types + # Perform preprocessing like renaming, reordering, and adding additional # data fields. def resolve(self): - for name, t in self.module.all: + self.class_name = (adjust_type_name(self.module.namespace.ext_name) + if self.module.namespace.is_ext else 'XProto') + + self.uniquify_events() + + for i, (name, t) in enumerate(self.module.all): + # Work around a name conflict: the type ScreenSaver has the same + # name as the extension, so rename the type. + if name == ('xcb', 'ScreenSaver'): + name = ('xcb', 'ScreenSaverMode') + t.name = name + self.module.all[i] = (name, t) self.resolve_type(t, name) - to_delete = [] - for enum in self.enum_types: - types = self.enum_types[enum] + for enum, types in list(self.enum_types.items()): if len(types) == 1: self.enum_types[enum] = list(types)[0] else: - to_delete.append(enum) - for x in to_delete: - del self.enum_types[x] + del self.enum_types[enum] for t in self.types: - # Lots of fields have types like uint8_t. Ignore these. - if len(t) == 1: - continue - l = list(self.types[t]) - # For some reason, FDs always have distint types so they appear - # duplicated in the set. If the set contains only FDs, then bail. - if all(x.is_fd for x in l): - continue + l = self.types[t] if len(l) == 1: continue @@ -908,26 +1207,34 @@ class GenXproto: self.replace_with_enum.add(t) self.enum_types[enum.name] = simple.name + for node in self.module.namespace.root: + if 'name' in node.attrib: + key = (node.tag, node.attrib['name']) + assert key not in self.module_names + self.module_names[key] = node + # The order of types in xcbproto's xml files are inconsistent, so sort - # them in the order {type aliases, enums, structs, requests/replies}. - def type_order_priority(item): + # them in the order {type aliases, enums, xidunions, structs, + # requests/replies}. + def type_order_priority(module_type): + name, item = module_type if item.is_simple: - return 0 + return 2 if self.get_xidunion_element(name) else 0 if isinstance(item, self.xcbgen.xtypes.Enum): return 1 if isinstance(item, self.xcbgen.xtypes.Request): - return 3 - return 2 + return 4 + return 3 - def cmp((_1, item1), (_2, item2)): - return type_order_priority(item1) - type_order_priority(item2) + def cmp(type1, type2): + return type_order_priority(type1) - type_order_priority(type2) # sort() is guaranteed to be stable. - self.module.all.sort(cmp=cmp) + self.module.all.sort(key=functools.cmp_to_key(cmp)) def gen_header(self): - self.file = self.args.headerfile - include_guard = self.args.headerfile.name.replace('/', '_').replace( + self.file = self.header_file + include_guard = self.header_file.name.replace('/', '_').replace( '.', '_').upper() + '_' self.write('#ifndef ' + include_guard) self.write('#define ' + include_guard) @@ -939,37 +1246,86 @@ class GenXproto: self.write('#include <vector>') self.write() self.write('#include "base/component_export.h"') + self.write('#include "base/optional.h"') self.write('#include "ui/gfx/x/xproto_types.h"') - for direct_import in self.module.direct_imports: + imports = set(self.module.direct_imports) + if self.module.namespace.is_ext: + imports.add(('xproto', 'xproto')) + for direct_import in sorted(list(imports)): self.write('#include "%s.h"' % direct_import[-1]) self.write('#include "%s_undef.h"' % self.module.namespace.header) self.write() - self.write('typedef struct _XDisplay XDisplay;') - self.write() self.write('namespace x11 {') self.write() + self.write('class Connection;') + self.write() + + self.namespace = ['x11'] + if not self.module.namespace.is_ext: + for (name, item) in self.module.all: + self.declare_type(item, name) name = self.class_name self.undef(name) with Indent(self, 'class COMPONENT_EXPORT(X11) %s {' % name, '};'): self.namespace = ['x11', self.class_name] self.write('public:') - self.write('explicit %s(XDisplay* display);' % name) + if self.module.namespace.is_ext: + self.write('static constexpr unsigned major_version = %s;' % + self.module.namespace.major_version) + self.write('static constexpr unsigned minor_version = %s;' % + self.module.namespace.minor_version) + self.write() + self.write(name + '(Connection* connection,') + self.write(' const x11::QueryExtensionReply& info);') + self.write() + with Indent(self, 'uint8_t present() const {', '}'): + self.write('return info_.present;') + with Indent(self, 'uint8_t major_opcode() const {', '}'): + self.write('return info_.major_opcode;') + with Indent(self, 'uint8_t first_event() const {', '}'): + self.write('return info_.first_event;') + with Indent(self, 'uint8_t first_error() const {', '}'): + self.write('return info_.first_error;') + else: + self.write('explicit %s(Connection* connection);' % name) self.write() - self.write('XDisplay* display() { return display_; }') + self.write( + 'Connection* connection() const { return connection_; }') self.write() for (name, item) in self.module.all: - self.declare_type(item, name) + if self.module.namespace.is_ext: + self.declare_type(item, name) + elif isinstance(item, self.xcbgen.xtypes.Request): + self.declare_request(item) self.write('private:') - self.write('XDisplay* const display_;') + self.write('x11::Connection* const connection_;') + if self.module.namespace.is_ext: + self.write('x11::QueryExtensionReply info_{};') self.write() self.write('} // namespace x11') self.write() + self.namespace = [] + + def binop(op, name): + self.write('inline constexpr %s operator%s(' % (name, op)) + with Indent(self, ' {0} l, {0} r)'.format(name) + ' {', '}'): + self.write('using T = std::underlying_type_t<%s>;' % name) + self.write('return static_cast<%s>(' % name) + self.write(' static_cast<T>(l) %s static_cast<T>(r));' % op) + self.write() + + for enum, name in self.bitenums: + name = self.qualtype(enum, name) + binop('|', name) + binop('&', name) + + self.write() self.write('#endif // ' + include_guard) def gen_source(self): - self.file = self.args.sourcefile + self.file = self.source_file self.write('#include "%s.h"' % self.module.namespace.header) self.write() self.write('#include <xcb/xcb.h>') @@ -980,46 +1336,246 @@ class GenXproto: self.write() self.write('namespace x11 {') self.write() - name = self.class_name - self.write('%s::%s(XDisplay* display) : display_(display) {}' % - (name, name)) + ctor = '%s::%s' % (self.class_name, self.class_name) + if self.module.namespace.is_ext: + self.write(ctor + '(x11::Connection* connection,') + self.write(' const x11::QueryExtensionReply& info)') + self.write(' : connection_(connection), info_(info) {}') + else: + self.write(ctor + + '(Connection* connection) : connection_(connection) {}') self.write() for (name, item) in self.module.all: - if isinstance(item, self.xcbgen.xtypes.Request): - self.define_request(item) + self.define_type(item, name) self.write('} // namespace x11') - def generate(self): - self.module = self.xcbgen.state.Module(self.args.xmlfile.name, None) + def parse(self): + self.module = self.xcbgen.state.Module(self.xml_filename, None) self.module.register() self.module.resolve() - self.resolve() - self.class_name = (adjust_type_case(self.module.namespace.ext_name) - if self.module.namespace.is_ext else 'XProto') + def generate(self): self.gen_header() self.gen_source() +class GenExtensionManager(FileWriter): + def __init__(self, gen_dir, genprotos): + FileWriter.__init__(self) + + self.gen_dir = gen_dir + self.genprotos = genprotos + self.extensions = [ + proto for proto in genprotos if proto.module.namespace.is_ext + ] + + def gen_header(self): + self.file = open(os.path.join(self.gen_dir, 'extension_manager.h'), + 'w') + self.write('#ifndef UI_GFX_X_EXTENSION_MANAGER_H_') + self.write('#define UI_GFX_X_EXTENSION_MANAGER_H_') + self.write() + self.write('#include <memory>') + self.write() + self.write('#include "base/component_export.h"') + self.write() + self.write('// Avoid conflicts caused by the GenericEvent macro.') + self.write('#include "ui/gfx/x/ge_undef.h"') + self.write() + self.write('namespace x11 {') + self.write() + self.write('class Connection;') + self.write() + for genproto in self.genprotos: + self.write('class %s;' % genproto.class_name) + self.write() + with Indent(self, 'class COMPONENT_EXPORT(X11) ExtensionManager {', + '};'): + self.write('public:') + self.write('ExtensionManager();') + self.write('~ExtensionManager();') + self.write() + for extension in self.extensions: + name = extension.proto + self.write('%s& %s() { return *%s_; }' % + (extension.class_name, name, name)) + self.write() + self.write('protected:') + self.write('void Init(Connection* conn);') + self.write() + self.write('private:') + for extension in self.extensions: + self.write('std::unique_ptr<%s> %s_;' % + (extension.class_name, extension.proto)) + self.write() + self.write('} // namespace x11') + self.write() + self.write('#endif // UI_GFX_X_EXTENSION_MANAGER_H_') + + def gen_source(self): + self.file = open(os.path.join(self.gen_dir, 'extension_manager.cc'), + 'w') + self.write('#include "ui/gfx/x/extension_manager.h"') + self.write() + self.write('#include "ui/gfx/x/connection.h"') + self.write('#include "ui/gfx/x/xproto_internal.h"') + for genproto in self.genprotos: + self.write('#include "ui/gfx/x/%s.h"' % genproto.proto) + self.write() + self.write('namespace x11 {') + self.write() + init = 'void ExtensionManager::Init' + with Indent(self, init + '(Connection* conn) {', '}'): + for extension in self.extensions: + self.write( + 'auto %s_future = conn->QueryExtension({"%s"});' % + (extension.proto, extension.module.namespace.ext_xname)) + self.write() + for extension in self.extensions: + name = extension.proto + self.write( + '%s_ = MakeExtension<%s>(conn, std::move(%s_future));' % + (name, extension.class_name, name)) + self.write() + self.write('ExtensionManager::ExtensionManager() = default;') + self.write('ExtensionManager::~ExtensionManager() = default;') + self.write() + self.write('} // namespace x11') + + +class GenReadEvent(FileWriter): + def __init__(self, gen_dir, genprotos): + FileWriter.__init__(self) + + self.gen_dir = gen_dir + self.genprotos = genprotos + + self.events = [] + for proto in self.genprotos: + for name, item in proto.module.all: + if item.is_event: + self.events.append((name, item, proto)) + + def event_condition(self, event, typename, proto): + ext = 'conn->%s()' % proto.proto + + conds = [] + if not proto.module.namespace.is_ext: + # Core protocol event + opcode = 'evtype' + elif event.is_ge_event: + # GenericEvent extension event + conds.extend([ + 'evtype == GeGenericEvent::opcode', + '%s.present()' % ext, + 'ge->extension == %s.major_opcode()' % ext, + ]) + opcode = 'ge->event_type' + else: + # Extension event + opcode = 'evtype - %s.first_event()' % ext + conds.append('%s.present()' % ext) + + if len(event.opcodes) == 1: + conds.append('%s == %s::opcode' % (opcode, typename)) + else: + conds.append('(%s)' % ' || '.join([ + '%s == %s::%s' % (opcode, typename, opname) + for opname in event.enum_opcodes.keys() + ])) + + return ' && '.join(conds), opcode + + def gen_event(self, name, event, proto): + # We can't ever have a plain generic event. It must be a concrete + # event provided by an extension. + if name == ('xcb', 'GeGeneric'): + return + + name = [adjust_type_name(part) for part in name[1:]] + typename = '::'.join(name) + 'Event' + + cond, opcode = self.event_condition(event, typename, proto) + with Indent(self, 'if (%s) {' % cond, '}'): + self.write('event->type_id_ = %d;' % event.type_id) + with Indent(self, 'event->deleter_ = [](void* event) {', '};'): + self.write('delete reinterpret_cast<%s*>(event);' % typename) + self.write('auto* event_ = new %s;' % typename) + self.write('ReadEvent(event_, buf);') + if len(event.opcodes) > 1: + self.write('{0} = static_cast<decltype({0})>({1});'.format( + 'event_->opcode', opcode)) + self.write('event_->send_event = send_event;') + self.write('event->event_ = event_;') + self.write('return;') + self.write() + + def gen_source(self): + self.file = open(os.path.join(self.gen_dir, 'read_event.cc'), 'w') + self.write('#include "ui/gfx/x/event.h"') + self.write() + self.write('#include "ui/gfx/x/connection.h"') + for genproto in self.genprotos: + self.write('#include "ui/gfx/x/%s.h"' % genproto.proto) + self.write() + self.write('namespace x11 {') + self.write() + self.write('void ReadEvent(') + args = 'Event* event, Connection* conn, const uint8_t* buf' + with Indent(self, ' %s) {' % args, '}'): + cast = 'auto* %s = reinterpret_cast<const %s*>(buf);' + self.write(cast % ('ev', 'xcb_generic_event_t')) + self.write(cast % ('ge', 'xcb_ge_generic_event_t')) + self.write('auto evtype = ev->response_type & ~kSendEventMask;') + self.write('bool send_event = ev->response_type & kSendEventMask;') + self.write() + for name, event, proto in self.events: + self.gen_event(name, event, proto) + self.write('NOTREACHED();') + self.write() + self.write('} // namespace x11') + + def main(): parser = argparse.ArgumentParser() - parser.add_argument('xmlfile', type=argparse.FileType('r')) - parser.add_argument('undeffile', type=argparse.FileType('w')) - parser.add_argument('headerfile', type=argparse.FileType('w')) - parser.add_argument('sourcefile', type=argparse.FileType('w')) - parser.add_argument('--sysroot') + parser.add_argument('xcbproto_dir', type=str) + parser.add_argument('gen_dir', type=str) + parser.add_argument('protos', type=str, nargs='*') args = parser.parse_args() - if args.sysroot: - path = os.path.join(args.sysroot, 'usr', 'lib', 'python2.7', - 'dist-packages') - sys.path.insert(1, path) - + sys.path.insert(1, args.xcbproto_dir) import xcbgen.xtypes import xcbgen.state - generator = GenXproto(args, xcbgen) - generator.generate() + all_types = {} + proto_src_dir = os.path.join(args.xcbproto_dir, 'src') + genprotos = [ + GenXproto(proto, proto_src_dir, args.gen_dir, xcbgen, all_types) + for proto in args.protos + ] + for genproto in genprotos: + genproto.parse() + for genproto in genprotos: + genproto.resolve() + + # Give each event a unique type ID. This is used by x11::Event to + # implement downcasting for events. + type_id = 1 + for proto in genprotos: + for _, item in proto.module.all: + if item.is_event: + item.type_id = type_id + type_id += 1 + + for genproto in genprotos: + genproto.generate() + + gen_extension_manager = GenExtensionManager(args.gen_dir, genprotos) + gen_extension_manager.gen_header() + gen_extension_manager.gen_source() + + gen_read_event = GenReadEvent(args.gen_dir, genprotos) + gen_read_event.gen_source() return 0 diff --git a/chromium/ui/gfx/x/request_queue.cc b/chromium/ui/gfx/x/request_queue.cc deleted file mode 100644 index 9bd3ab2f248..00000000000 --- a/chromium/ui/gfx/x/request_queue.cc +++ /dev/null @@ -1,29 +0,0 @@ -// 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 "ui/gfx/x/request_queue.h" - -#include "base/check_op.h" - -namespace x11 { - -// static -RequestQueue* RequestQueue::instance_ = nullptr; - -RequestQueue::RequestQueue() { - DCHECK(!instance_); - instance_ = this; -} - -RequestQueue::~RequestQueue() { - DCHECK_EQ(instance_, this); - instance_ = nullptr; -} - -// static -RequestQueue* RequestQueue::GetInstance() { - return instance_; -} - -} // namespace x11 diff --git a/chromium/ui/gfx/x/request_queue.h b/chromium/ui/gfx/x/request_queue.h deleted file mode 100644 index b48b1c031de..00000000000 --- a/chromium/ui/gfx/x/request_queue.h +++ /dev/null @@ -1,51 +0,0 @@ -// 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. - -#ifndef UI_GFX_X_REQUEST_QUEUE_H_ -#define UI_GFX_X_REQUEST_QUEUE_H_ - -#include <xcb/xcb.h> - -#include <memory> - -#include "base/callback_forward.h" -#include "base/component_export.h" -#include "base/memory/free_deleter.h" - -namespace ui { -class X11EventSource; -} - -namespace x11 { - -// This interface allows //ui/gfx/x to call into //ui/events/platform/x11 which -// is at a higher layer. It should not be used by client code. -class COMPONENT_EXPORT(X11) RequestQueue { - private: - friend class ui::X11EventSource; - template <typename T> - friend class Future; - - using Reply = std::unique_ptr<uint8_t, base::FreeDeleter>; - using Error = std::unique_ptr<xcb_generic_error_t, base::FreeDeleter>; - using ResponseCallback = base::OnceCallback<void(Reply reply, Error error)>; - - RequestQueue(); - virtual ~RequestQueue(); - - // Adds a request to the queue. |is_void| indicates if a reply is generated - // for this request. |sequence| is the ID of the request. |callback| will - // be called upon request completion (or failure). - virtual void AddRequest(bool is_void, - unsigned int sequence, - ResponseCallback callback) = 0; - - static RequestQueue* GetInstance(); - - static RequestQueue* instance_; -}; - -} // namespace x11 - -#endif // UI_GFX_X_REQUEST_QUEUE_H_ diff --git a/chromium/ui/gfx/x/x11.h b/chromium/ui/gfx/x/x11.h index b163f788d98..2cae7c5b3ee 100644 --- a/chromium/ui/gfx/x/x11.h +++ b/chromium/ui/gfx/x/x11.h @@ -31,11 +31,8 @@ extern "C" { #include <X11/extensions/XShm.h> #include <X11/extensions/XTest.h> #include <X11/extensions/Xfixes.h> -#include <X11/extensions/Xrandr.h> #include <X11/extensions/Xrender.h> #include <X11/extensions/record.h> -#include <X11/extensions/scrnsaver.h> -#include <X11/extensions/shape.h> #include <X11/extensions/sync.h> // Define XK_xxx before the #include of <X11/keysym.h> so that <X11/keysym.h> @@ -76,6 +73,9 @@ extern "C" { #include <X11/Sunkeysym.h> #include <X11/XF86keysym.h> #include <X11/keysym.h> +} + +#include "ui/gfx/x/connection.h" // These commonly used names are undefined and if necessary recreated // in the x11 namespace below. This is the main purpose of this header @@ -97,14 +97,13 @@ extern "C" { #undef DeviceAdded // Defined by X11/extensions/XI.h to 0 #undef DeviceMode // Defined by X11/extensions/XI.h to 1 #undef DeviceRemoved // Defined by X11/extensions/XI.h to 1 -#undef FocusIn // Defined by X.h to 9 -#undef FocusOut // Defined by X.h to 10 -#undef None // Defined by X11/X.h to 0L -#undef True // Defined by X11/Xlib.h to 1 -#undef False // Defined by X11/Xlib.h to 0 -#undef CurrentTime // Defined by X11/X.h to 0L -#undef Success // Defined by X11/X.h to 0 -} +#undef FocusIn // Defined by X.h to 9 +#undef FocusOut // Defined by X.h to 10 +#undef None // Defined by X11/X.h to 0L +#undef True // Defined by X11/Xlib.h to 1 +#undef False // Defined by X11/Xlib.h to 0 +#undef CurrentTime // Defined by X11/X.h to 0L +#undef Success // Defined by X11/X.h to 0 // The x11 namespace allows to scope X11 constants and types that // would be problematic at the default preprocessor level. diff --git a/chromium/ui/gfx/x/x11_atom_cache.cc b/chromium/ui/gfx/x/x11_atom_cache.cc index 75f18c1b57a..2e09c095eea 100644 --- a/chromium/ui/gfx/x/x11_atom_cache.cc +++ b/chromium/ui/gfx/x/x11_atom_cache.cc @@ -20,76 +20,76 @@ namespace { struct { const char* atom_name; - Atom atom_value; + x11::Atom atom_value; } const kPredefinedAtoms[] = { - // {"PRIMARY", XA_PRIMARY}, - // {"SECONDARY", XA_SECONDARY}, - // {"ARC", XA_ARC}, - {"ATOM", XA_ATOM}, - // {"BITMAP", XA_BITMAP}, - {"CARDINAL", XA_CARDINAL}, - // {"COLORMAP", XA_COLORMAP}, - // {"CURSOR", XA_CURSOR}, - // {"CUT_BUFFER0", XA_CUT_BUFFER0}, - // {"CUT_BUFFER1", XA_CUT_BUFFER1}, - // {"CUT_BUFFER2", XA_CUT_BUFFER2}, - // {"CUT_BUFFER3", XA_CUT_BUFFER3}, - // {"CUT_BUFFER4", XA_CUT_BUFFER4}, - // {"CUT_BUFFER5", XA_CUT_BUFFER5}, - // {"CUT_BUFFER6", XA_CUT_BUFFER6}, - // {"CUT_BUFFER7", XA_CUT_BUFFER7}, - // {"DRAWABLE", XA_DRAWABLE}, - // {"FONT", XA_FONT}, - // {"INTEGER", XA_INTEGER}, - // {"PIXMAP", XA_PIXMAP}, - // {"POINT", XA_POINT}, - // {"RECTANGLE", XA_RECTANGLE}, - // {"RESOURCE_MANAGER", XA_RESOURCE_MANAGER}, - // {"RGB_COLOR_MAP", XA_RGB_COLOR_MAP}, - // {"RGB_BEST_MAP", XA_RGB_BEST_MAP}, - // {"RGB_BLUE_MAP", XA_RGB_BLUE_MAP}, - // {"RGB_DEFAULT_MAP", XA_RGB_DEFAULT_MAP}, - // {"RGB_GRAY_MAP", XA_RGB_GRAY_MAP}, - // {"RGB_GREEN_MAP", XA_RGB_GREEN_MAP}, - // {"RGB_RED_MAP", XA_RGB_RED_MAP}, - {"STRING", XA_STRING}, - // {"VISUALID", XA_VISUALID}, - // {"WINDOW", XA_WINDOW}, - // {"WM_COMMAND", XA_WM_COMMAND}, - // {"WM_HINTS", XA_WM_HINTS}, - // {"WM_CLIENT_MACHINE", XA_WM_CLIENT_MACHINE}, - // {"WM_ICON_NAME", XA_WM_ICON_NAME}, - // {"WM_ICON_SIZE", XA_WM_ICON_SIZE}, - // {"WM_NAME", XA_WM_NAME}, - // {"WM_NORMAL_HINTS", XA_WM_NORMAL_HINTS}, - // {"WM_SIZE_HINTS", XA_WM_SIZE_HINTS}, - // {"WM_ZOOM_HINTS", XA_WM_ZOOM_HINTS}, - // {"MIN_SPACE", XA_MIN_SPACE}, - // {"NORM_SPACE", XA_NORM_SPACE}, - // {"MAX_SPACE", XA_MAX_SPACE}, - // {"END_SPACE", XA_END_SPACE}, - // {"SUPERSCRIPT_X", XA_SUPERSCRIPT_X}, - // {"SUPERSCRIPT_Y", XA_SUPERSCRIPT_Y}, - // {"SUBSCRIPT_X", XA_SUBSCRIPT_X}, - // {"SUBSCRIPT_Y", XA_SUBSCRIPT_Y}, - // {"UNDERLINE_POSITION", XA_UNDERLINE_POSITION}, - // {"UNDERLINE_THICKNESS", XA_UNDERLINE_THICKNESS}, - // {"STRIKEOUT_ASCENT", XA_STRIKEOUT_ASCENT}, - // {"STRIKEOUT_DESCENT", XA_STRIKEOUT_DESCENT}, - // {"ITALIC_ANGLE", XA_ITALIC_ANGLE}, - // {"X_HEIGHT", XA_X_HEIGHT}, - // {"QUAD_WIDTH", XA_QUAD_WIDTH}, - // {"WEIGHT", XA_WEIGHT}, - // {"POINT_SIZE", XA_POINT_SIZE}, - // {"RESOLUTION", XA_RESOLUTION}, - // {"COPYRIGHT", XA_COPYRIGHT}, - // {"NOTICE", XA_NOTICE}, - // {"FONT_NAME", XA_FONT_NAME}, - // {"FAMILY_NAME", XA_FAMILY_NAME}, - // {"FULL_NAME", XA_FULL_NAME}, - // {"CAP_HEIGHT", XA_CAP_HEIGHT}, - {"WM_CLASS", XA_WM_CLASS}, - // {"WM_TRANSIENT_FOR", XA_WM_TRANSIENT_FOR}, + // {"PRIMARY", x11::Atom::PRIMARY}, + // {"SECONDARY", x11::Atom::SECONDARY}, + // {"ARC", x11::Atom::ARC}, + {"ATOM", x11::Atom::ATOM}, + // {"BITMAP", x11::Atom::BITMAP}, + {"CARDINAL", x11::Atom::CARDINAL}, + // {"COLORMAP", x11::Atom::COLORMAP}, + // {"CURSOR", x11::Atom::CURSOR}, + // {"CUT_BUFFER0", x11::Atom::CUT_BUFFER0}, + // {"CUT_BUFFER1", x11::Atom::CUT_BUFFER1}, + // {"CUT_BUFFER2", x11::Atom::CUT_BUFFER2}, + // {"CUT_BUFFER3", x11::Atom::CUT_BUFFER3}, + // {"CUT_BUFFER4", x11::Atom::CUT_BUFFER4}, + // {"CUT_BUFFER5", x11::Atom::CUT_BUFFER5}, + // {"CUT_BUFFER6", x11::Atom::CUT_BUFFER6}, + // {"CUT_BUFFER7", x11::Atom::CUT_BUFFER7}, + // {"DRAWABLE", x11::Atom::DRAWABLE}, + // {"FONT", x11::Atom::FONT}, + // {"INTEGER", x11::Atom::INTEGER}, + // {"PIXMAP", x11::Atom::PIXMAP}, + // {"POINT", x11::Atom::POINT}, + // {"RECTANGLE", x11::Atom::RECTANGLE}, + // {"RESOURCE_MANAGER", x11::Atom::RESOURCE_MANAGER}, + // {"RGB_COLOR_MAP", x11::Atom::RGB_COLOR_MAP}, + // {"RGB_BEST_MAP", x11::Atom::RGB_BEST_MAP}, + // {"RGB_BLUE_MAP", x11::Atom::RGB_BLUE_MAP}, + // {"RGB_DEFAULT_MAP", x11::Atom::RGB_DEFAULT_MAP}, + // {"RGB_GRAY_MAP", x11::Atom::RGB_GRAY_MAP}, + // {"RGB_GREEN_MAP", x11::Atom::RGB_GREEN_MAP}, + // {"RGB_RED_MAP", x11::Atom::RGB_RED_MAP}, + {"STRING", x11::Atom::STRING}, + // {"VISUALID", x11::Atom::VISUALID}, + // {"WINDOW", x11::Atom::WINDOW}, + // {"WM_COMMAND", x11::Atom::WM_COMMAND}, + // {"WM_HINTS", x11::Atom::WM_HINTS}, + // {"WM_CLIENT_MACHINE", x11::Atom::WM_CLIENT_MACHINE}, + // {"WM_ICON_NAME", x11::Atom::WM_ICON_NAME}, + // {"WM_ICON_SIZE", x11::Atom::WM_ICON_SIZE}, + // {"WM_NAME", x11::Atom::WM_NAME}, + // {"WM_NORMAL_HINTS", x11::Atom::WM_NORMAL_HINTS}, + // {"WM_SIZE_HINTS", x11::Atom::WM_SIZE_HINTS}, + // {"WM_ZOOM_HINTS", x11::Atom::WM_ZOOM_HINTS}, + // {"MIN_SPACE", x11::Atom::MIN_SPACE}, + // {"NORM_SPACE", x11::Atom::NORM_SPACE}, + // {"MAX_SPACE", x11::Atom::MAX_SPACE}, + // {"END_SPACE", x11::Atom::END_SPACE}, + // {"SUPERSCRIPT_X", x11::Atom::SUPERSCRIPT_X}, + // {"SUPERSCRIPT_Y", x11::Atom::SUPERSCRIPT_Y}, + // {"SUBSCRIPT_X", x11::Atom::SUBSCRIPT_X}, + // {"SUBSCRIPT_Y", x11::Atom::SUBSCRIPT_Y}, + // {"UNDERLINE_POSITION", x11::Atom::UNDERLINE_POSITION}, + // {"UNDERLINE_THICKNESS", x11::Atom::UNDERLINE_THICKNESS}, + // {"STRIKEOUT_ASCENT", x11::Atom::STRIKEOUT_ASCENT}, + // {"STRIKEOUT_DESCENT", x11::Atom::STRIKEOUT_DESCENT}, + // {"ITALIC_ANGLE", x11::Atom::ITALIC_ANGLE}, + // {"X_HEIGHT", x11::Atom::X_HEIGHT}, + // {"QUAD_WIDTH", x11::Atom::QUAD_WIDTH}, + // {"WEIGHT", x11::Atom::WEIGHT}, + // {"POINT_SIZE", x11::Atom::POINT_SIZE}, + // {"RESOLUTION", x11::Atom::RESOLUTION}, + // {"COPYRIGHT", x11::Atom::COPYRIGHT}, + // {"NOTICE", x11::Atom::NOTICE}, + // {"FONT_NAME", x11::Atom::FONT_NAME}, + // {"FAMILY_NAME", x11::Atom::FAMILY_NAME}, + // {"FULL_NAME", x11::Atom::FULL_NAME}, + // {"CAP_HEIGHT", x11::Atom::CAP_HEIGHT}, + {"WM_CLASS", x11::Atom::WM_CLASS}, + // {"WM_TRANSIENT_FOR", x11::Atom::WM_TRANSIENT_FOR}, }; constexpr const char* kAtomsToCache[] = { @@ -244,7 +244,7 @@ constexpr int kCacheCount = base::size(kAtomsToCache); namespace gfx { -XAtom GetAtom(const char* name) { +x11::Atom GetAtom(const std::string& name) { return X11AtomCache::GetInstance()->GetAtom(name); } @@ -256,27 +256,26 @@ X11AtomCache::X11AtomCache() : connection_(x11::Connection::Get()) { for (const auto& predefined_atom : kPredefinedAtoms) cached_atoms_[predefined_atom.atom_name] = predefined_atom.atom_value; - std::vector<x11::Future<x11::XProto::InternAtomReply>> requests; + std::vector<x11::Future<x11::InternAtomReply>> requests; requests.reserve(kCacheCount); for (const char* name : kAtomsToCache) requests.push_back(connection_->InternAtom({.name = name})); for (size_t i = 0; i < kCacheCount; ++i) { if (auto response = requests[i].Sync()) - cached_atoms_[kAtomsToCache[i]] = static_cast<XAtom>(response->atom); + cached_atoms_[kAtomsToCache[i]] = static_cast<x11::Atom>(response->atom); } } X11AtomCache::~X11AtomCache() = default; -XAtom X11AtomCache::GetAtom(const char* name) const { - DCHECK(name); +x11::Atom X11AtomCache::GetAtom(const std::string& name) const { const auto it = cached_atoms_.find(name); if (it != cached_atoms_.end()) return it->second; - XAtom atom = 0; + x11::Atom atom = x11::Atom::None; if (auto response = connection_->InternAtom({.name = name}).Sync()) { - atom = static_cast<XAtom>(response->atom); + atom = static_cast<x11::Atom>(response->atom); cached_atoms_.emplace(name, atom); } else { static int error_count = 0; diff --git a/chromium/ui/gfx/x/x11_atom_cache.h b/chromium/ui/gfx/x/x11_atom_cache.h index b04608a783c..53fb799875f 100644 --- a/chromium/ui/gfx/x/x11_atom_cache.h +++ b/chromium/ui/gfx/x/x11_atom_cache.h @@ -24,7 +24,7 @@ class Connection; namespace gfx { // Gets the X atom for default display corresponding to atom_name. -GFX_EXPORT XAtom GetAtom(const char* atom_name); +GFX_EXPORT x11::Atom GetAtom(const std::string& atom_name); // Pre-caches all Atoms on first use to minimize roundtrips to the X11 // server. By default, GetAtom() will CHECK() that atoms accessed through @@ -35,7 +35,7 @@ class GFX_EXPORT X11AtomCache { static X11AtomCache* GetInstance(); private: - friend XAtom GetAtom(const char* atom_name); + friend x11::Atom GetAtom(const std::string& atom_name); friend struct base::DefaultSingletonTraits<X11AtomCache>; X11AtomCache(); @@ -43,12 +43,12 @@ class GFX_EXPORT X11AtomCache { // Returns the pre-interned Atom without having to go to the x server. // On failure, x11::None is returned. - XAtom GetAtom(const char*) const; + x11::Atom GetAtom(const std::string&) const; x11::Connection* connection_; // Using std::map, as it is possible for thousands of atoms to be registered. - mutable std::map<std::string, XAtom> cached_atoms_; + mutable std::map<std::string, x11::Atom> cached_atoms_; DISALLOW_COPY_AND_ASSIGN(X11AtomCache); }; diff --git a/chromium/ui/gfx/x/x11_error_tracker.cc b/chromium/ui/gfx/x/x11_error_tracker.cc index a6f054f5ba2..6592ae04838 100644 --- a/chromium/ui/gfx/x/x11_error_tracker.cc +++ b/chromium/ui/gfx/x/x11_error_tracker.cc @@ -10,7 +10,7 @@ namespace { unsigned char g_x11_error_code = 0; -static gfx::X11ErrorTracker* g_handler = NULL; +static gfx::X11ErrorTracker* g_handler = nullptr; int X11ErrorHandler(Display* display, XErrorEvent* error) { g_x11_error_code = error->error_code; @@ -24,7 +24,7 @@ namespace gfx { X11ErrorTracker::X11ErrorTracker() { // This is a non-exhaustive check for incorrect usage. It disallows nested // X11ErrorTracker instances on the same thread. - DCHECK(g_handler == NULL); + DCHECK(g_handler == nullptr); g_handler = this; XSync(GetXDisplay(), False); old_handler_ = XSetErrorHandler(X11ErrorHandler); @@ -32,7 +32,7 @@ X11ErrorTracker::X11ErrorTracker() { } X11ErrorTracker::~X11ErrorTracker() { - g_handler = NULL; + g_handler = nullptr; XSetErrorHandler(old_handler_); } diff --git a/chromium/ui/gfx/x/x11_path.cc b/chromium/ui/gfx/x/x11_path.cc index 4a171dbc564..937e206007e 100644 --- a/chromium/ui/gfx/x/x11_path.cc +++ b/chromium/ui/gfx/x/x11_path.cc @@ -12,8 +12,9 @@ namespace gfx { -Region CreateRegionFromSkRegion(const SkRegion& region) { - Region result = XCreateRegion(); +std::unique_ptr<std::vector<x11::Rectangle>> CreateRegionFromSkRegion( + const SkRegion& region) { + auto result = std::make_unique<std::vector<x11::Rectangle>>(); for (SkRegion::Iterator i(region); !i.done(); i.next()) { XRectangle rect; @@ -21,23 +22,23 @@ Region CreateRegionFromSkRegion(const SkRegion& region) { rect.y = i.rect().y(); rect.width = i.rect().width(); rect.height = i.rect().height(); - XUnionRectWithRegion(&rect, result, result); + result->push_back({ + .x = i.rect().x(), + .y = i.rect().y(), + .width = i.rect().width(), + .height = i.rect().height(), + }); } return result; } -Region CreateRegionFromSkPath(const SkPath& path) { - int point_count = path.getPoints(nullptr, 0); - std::unique_ptr<SkPoint[]> points(new SkPoint[point_count]); - path.getPoints(points.get(), point_count); - std::unique_ptr<XPoint[]> x11_points(new XPoint[point_count]); - for (int i = 0; i < point_count; ++i) { - x11_points[i].x = SkScalarRoundToInt(points[i].fX); - x11_points[i].y = SkScalarRoundToInt(points[i].fY); - } - - return XPolygonRegion(x11_points.get(), point_count, EvenOddRule); +std::unique_ptr<std::vector<x11::Rectangle>> CreateRegionFromSkPath( + const SkPath& path) { + SkRegion clip{path.getBounds().roundOut()}; + SkRegion region; + region.setPath(path, clip); + return CreateRegionFromSkRegion(region); } } // namespace gfx diff --git a/chromium/ui/gfx/x/x11_path.h b/chromium/ui/gfx/x/x11_path.h index a85e67a287e..9e5d4be9bae 100644 --- a/chromium/ui/gfx/x/x11_path.h +++ b/chromium/ui/gfx/x/x11_path.h @@ -6,7 +6,7 @@ #define UI_GFX_X_X11_PATH_H_ #include "ui/gfx/gfx_export.h" -#include "ui/gfx/x/x11_types.h" +#include "ui/gfx/x/xproto.h" class SkPath; class SkRegion; @@ -15,11 +15,13 @@ namespace gfx { // Creates a new XRegion given |region|. The caller is responsible for // destroying the returned region. -GFX_EXPORT XRegion* CreateRegionFromSkRegion(const SkRegion& region); +GFX_EXPORT std::unique_ptr<std::vector<x11::Rectangle>> +CreateRegionFromSkRegion(const SkRegion& region); // Creates a new XRegion given |path|. The caller is responsible for destroying // the returned region. -GFX_EXPORT XRegion* CreateRegionFromSkPath(const SkPath& path); +GFX_EXPORT std::unique_ptr<std::vector<x11::Rectangle>> CreateRegionFromSkPath( + const SkPath& path); } // namespace gfx diff --git a/chromium/ui/gfx/x/x11_types.cc b/chromium/ui/gfx/x/x11_types.cc index ebbe076ab6b..ad41a13dff3 100644 --- a/chromium/ui/gfx/x/x11_types.cc +++ b/chromium/ui/gfx/x/x11_types.cc @@ -77,10 +77,10 @@ void PutARGBImage(XDisplay* display, image.width = data_width; image.height = data_height; - image.format = static_cast<int>(x11::XProto::ImageFormat::ZPixmap); - image.byte_order = static_cast<int>(x11::XProto::ImageOrder::LSBFirst); + image.format = static_cast<int>(x11::ImageFormat::ZPixmap); + image.byte_order = static_cast<int>(x11::ImageOrder::LSBFirst); image.bitmap_unit = 8; - image.bitmap_bit_order = static_cast<int>(x11::XProto::ImageOrder::LSBFirst); + image.bitmap_bit_order = static_cast<int>(x11::ImageOrder::LSBFirst); image.depth = depth; image.bits_per_pixel = pixmap_bpp; image.bytes_per_line = data_width * pixmap_bpp / 8; diff --git a/chromium/ui/gfx/x/x11_types.h b/chromium/ui/gfx/x/x11_types.h index 8ea0ca4ca49..a1886764342 100644 --- a/chromium/ui/gfx/x/x11_types.h +++ b/chromium/ui/gfx/x/x11_types.h @@ -10,9 +10,8 @@ #include <memory> #include "ui/gfx/gfx_export.h" +#include "ui/gfx/x/connection.h" -typedef unsigned long XAtom; -typedef unsigned long XID; typedef unsigned long VisualID; typedef struct _XcursorImage XcursorImage; typedef union _XEvent XEvent; diff --git a/chromium/ui/gfx/x/xproto_internal.h b/chromium/ui/gfx/x/xproto_internal.h index f76ebe5ecac..32c68b4f801 100644 --- a/chromium/ui/gfx/x/xproto_internal.h +++ b/chromium/ui/gfx/x/xproto_internal.h @@ -20,7 +20,9 @@ #include <type_traits> #include "base/component_export.h" +#include "base/logging.h" #include "base/optional.h" +#include "ui/gfx/x/connection.h" #include "ui/gfx/x/xproto_types.h" namespace x11 { @@ -49,11 +51,17 @@ struct ReadBuffer { }; template <typename T> -void Write(const T* t, WriteBuffer* buf) { - static_assert(std::is_trivially_copyable<T>::value, ""); +void VerifyAlignment(T* t, size_t offset) { // On the wire, X11 types are always aligned to their size. This is a sanity // check to ensure padding etc are working properly. - DCHECK_EQ(buf->size() % sizeof(*t), 0UL); + if (sizeof(T) == 2 || sizeof(T) == 4 || sizeof(T) == 8) + DCHECK_EQ(offset % sizeof(*t), 0UL); +} + +template <typename T> +void Write(const T* t, WriteBuffer* buf) { + static_assert(std::is_trivially_copyable<T>::value, ""); + VerifyAlignment(t, buf->size()); const uint8_t* start = reinterpret_cast<const uint8_t*>(t); std::copy(start, start + sizeof(*t), std::back_inserter(*buf)); } @@ -61,9 +69,7 @@ void Write(const T* t, WriteBuffer* buf) { template <typename T> void Read(T* t, ReadBuffer* buf) { static_assert(std::is_trivially_copyable<T>::value, ""); - // On the wire, X11 types are always aligned to their size. This is a sanity - // check to ensure padding etc are working properly. - DCHECK_EQ(buf->offset % sizeof(*t), 0UL); + VerifyAlignment(t, buf->offset); memcpy(t, buf->data + buf->offset, sizeof(*t)); buf->offset += sizeof(*t); } @@ -85,12 +91,11 @@ inline void Align(ReadBuffer* buf, size_t align) { } template <typename Reply> -Future<Reply> SendRequest(XDisplay* display, WriteBuffer* buf) { +Future<Reply> SendRequest(x11::Connection* connection, WriteBuffer* buf) { // Clang crashes when the value of |is_void| is inlined below, // so keep this variable outside of |xpr|. constexpr bool is_void = std::is_void<Reply>::value; xcb_protocol_request_t xpr{ - .count = 1, .ext = nullptr, .isvoid = is_void, }; @@ -101,23 +106,46 @@ Future<Reply> SendRequest(XDisplay* display, WriteBuffer* buf) { uint16_t length; }; - auto* header = reinterpret_cast<RequestHeader*>(buf->data()); + struct ExtendedRequestHeader { + RequestHeader header; + uint32_t long_length; + }; + static_assert(sizeof(ExtendedRequestHeader) == 8, ""); + + auto* old_header = reinterpret_cast<RequestHeader*>(buf->data()); + ExtendedRequestHeader new_header{*old_header, 0}; + // Requests are always a multiple of 4 bytes on the wire. Because of this, // the length field represents the size in chunks of 4 bytes. DCHECK_EQ(buf->size() % 4, 0UL); - DCHECK_LE(buf->size() / 4, std::numeric_limits<uint16_t>::max()); - header->length = buf->size() / 4; + size_t size32 = buf->size() / 4; + + struct iovec io[4]; + memset(&io, 0, sizeof(io)); + if (size32 < connection->setup().maximum_request_length) { + xpr.count = 1; + old_header->length = size32; + io[2].iov_base = buf->data(); + io[2].iov_len = buf->size(); + } else if (size32 < connection->extended_max_request_length()) { + xpr.count = 2; + DCHECK_EQ(new_header.header.length, 0U); + new_header.long_length = size32 + 1; + io[2].iov_base = &new_header; + io[2].iov_len = sizeof(ExtendedRequestHeader); + io[3].iov_base = buf->data() + sizeof(RequestHeader); + io[3].iov_len = buf->size() - sizeof(RequestHeader); + } else { + LOG(ERROR) << "Cannot send request of length " << buf->size(); + return {nullptr, base::nullopt}; + } - struct iovec io[3]; - io[2].iov_base = buf->data(); - io[2].iov_len = buf->size(); + xcb_connection_t* conn = connection->XcbConnection(); auto flags = XCB_REQUEST_CHECKED | XCB_REQUEST_RAW; - - xcb_connection_t* conn = XGetXCBConnection(display); auto sequence = xcb_send_request(conn, flags, &io[2], &xpr); if (xcb_connection_has_error(conn)) return {nullptr, base::nullopt}; - return {display, sequence}; + return {connection, sequence}; } // Helper function for xcbproto popcount. Given an integral type, returns the @@ -143,19 +171,51 @@ bool CaseEq(T t, S s) { return t == static_cast<decltype(t)>(s); } -// Helper function for xcbproto bitcase and & expressions. Checks if the -// bitmasks |t| and |s| have any intersection. +// Helper function for xcbproto bitcase expressions. Checks if the bitmasks |t| +// and |s| have any intersection. +template <typename T, typename S> +bool CaseAnd(T t, S s) { + return static_cast<EnumBaseType<T>>(t) & static_cast<EnumBaseType<T>>(s); +} + +// Helper function for xcbproto & expressions. Computes |t| & |s|. template <typename T, typename S> -bool BitAnd(T t, S s) { +auto BitAnd(T t, S s) { return static_cast<EnumBaseType<T>>(t) & static_cast<EnumBaseType<T>>(s); } -// Helper function for ~ expressions. +// Helper function for xcbproto ~ expressions. template <typename T> -bool BitNot(T t) { +auto BitNot(T t) { return ~static_cast<EnumBaseType<T>>(t); } +// Helper function for generating switch values. |switch_var| is the value to +// modify. |enum_val| is the value to set |switch_var| to if this is a regular +// case, or the bit to be set in |switch_var| if this is a bit case. This +// function is a no-op when |condition| is false. +template <typename T> +auto SwitchVar(T enum_val, bool condition, bool is_bitcase, T* switch_var) { + using EnumInt = EnumBaseType<T>; + if (!condition) + return; + EnumInt switch_int = static_cast<EnumInt>(*switch_var); + if (is_bitcase) { + *switch_var = static_cast<T>(switch_int | static_cast<EnumInt>(enum_val)); + } else { + DCHECK(!switch_int); + *switch_var = enum_val; + } +} + +template <typename T> +std::unique_ptr<T> MakeExtension(Connection* connection, + Future<QueryExtensionReply> future) { + auto reply = future.Sync(); + return std::make_unique<T>(connection, + reply ? *reply.reply : QueryExtensionReply{}); +} + } // namespace x11 #endif // UI_GFX_X_XPROTO_INTERNAL_H_ diff --git a/chromium/ui/gfx/x/xproto_types.cc b/chromium/ui/gfx/x/xproto_types.cc new file mode 100644 index 00000000000..0fc634cecf3 --- /dev/null +++ b/chromium/ui/gfx/x/xproto_types.cc @@ -0,0 +1,76 @@ +// 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 "ui/gfx/x/xproto_types.h" + +#include "ui/gfx/x/connection.h" + +namespace x11 { + +FutureBase::FutureBase(Connection* connection, + base::Optional<unsigned int> sequence) + : connection_(connection), sequence_(sequence) {} + +// If a user-defined response-handler is not installed before this object goes +// out of scope, a default response handler will be installed. The default +// handler throws away the reply and prints the error if there is one. +FutureBase::~FutureBase() { + if (!sequence_) + return; + + OnResponseImpl(base::BindOnce( + [](Connection* connection, RawReply reply, RawError error) { + if (!error) + return; + + x11::LogErrorEventDescription(XErrorEvent({ + .type = error->response_type, + .display = connection->display(), + .resourceid = error->resource_id, + .serial = error->full_sequence, + .error_code = error->error_code, + .request_code = error->major_code, + .minor_code = error->minor_code, + })); + }, + connection_)); +} + +FutureBase::FutureBase(FutureBase&& future) + : connection_(future.connection_), sequence_(future.sequence_) { + future.connection_ = nullptr; + future.sequence_ = base::nullopt; +} + +FutureBase& FutureBase::operator=(FutureBase&& future) { + connection_ = future.connection_; + sequence_ = future.sequence_; + future.connection_ = nullptr; + future.sequence_ = base::nullopt; + return *this; +} + +void FutureBase::SyncImpl(Error** raw_error, uint8_t** raw_reply) { + if (!sequence_) + return; + *raw_reply = reinterpret_cast<uint8_t*>( + xcb_wait_for_reply(connection_->XcbConnection(), *sequence_, raw_error)); + sequence_ = base::nullopt; +} + +void FutureBase::SyncImpl(Error** raw_error) { + if (!sequence_) + return; + *raw_error = xcb_request_check(connection_->XcbConnection(), {*sequence_}); + sequence_ = base::nullopt; +} + +void FutureBase::OnResponseImpl(ResponseCallback callback) { + if (!sequence_) + return; + connection_->AddRequest(*sequence_, std::move(callback)); + sequence_ = base::nullopt; +} + +} // namespace x11 diff --git a/chromium/ui/gfx/x/xproto_types.h b/chromium/ui/gfx/x/xproto_types.h index 9e5dfe09534..415c2146481 100644 --- a/chromium/ui/gfx/x/xproto_types.h +++ b/chromium/ui/gfx/x/xproto_types.h @@ -16,25 +16,37 @@ #include "base/callback.h" #include "base/memory/free_deleter.h" #include "base/optional.h" -#include "ui/gfx/x/request_queue.h" #include "ui/gfx/x/xproto_util.h" typedef struct _XDisplay XDisplay; namespace x11 { +class Connection; + +constexpr uint8_t kSendEventMask = 0x80; + namespace detail { template <typename Reply> std::unique_ptr<Reply> ReadReply(const uint8_t* buffer); -} +} // namespace detail using Error = xcb_generic_error_t; template <class Reply> class Future; +template <typename T> +T Read(const uint8_t* buf); + +template <typename T> +std::vector<uint8_t> Write(const T& t); + +template <typename T> +void ReadEvent(T* event, const uint8_t* buf); + template <typename Reply> struct Response { operator bool() const { return reply.get(); } @@ -62,66 +74,48 @@ struct Response<void> { : error(std::move(error)) {} }; +class COMPONENT_EXPORT(X11) FutureBase { + public: + using RawReply = std::unique_ptr<uint8_t, base::FreeDeleter>; + using RawError = std::unique_ptr<xcb_generic_error_t, base::FreeDeleter>; + using ResponseCallback = + base::OnceCallback<void(RawReply reply, RawError error)>; + + FutureBase(const FutureBase&) = delete; + FutureBase& operator=(const FutureBase&) = delete; + + protected: + FutureBase(Connection* connection, base::Optional<unsigned int> sequence); + ~FutureBase(); + + FutureBase(FutureBase&& future); + FutureBase& operator=(FutureBase&& future); + + void SyncImpl(Error** raw_error, uint8_t** raw_reply); + void SyncImpl(Error** raw_error); + + void OnResponseImpl(ResponseCallback callback); + + private: + Connection* connection_; + base::Optional<unsigned int> sequence_; +}; + // An x11::Future wraps an asynchronous response from the X11 server. The // response may be waited-for with Sync(), or asynchronously handled by // installing a response handler using OnResponse(). template <typename Reply> -class Future { +class Future : public FutureBase { public: using Callback = base::OnceCallback<void(Response<Reply> response)>; - using RQ = RequestQueue; - - // If a user-defined response-handler is not installed before this object goes - // out of scope, a default response handler will be installed. The default - // handler throws away the reply and prints the error if there is one. - ~Future() { - if (!sequence_) - return; - - EnqueueRequest(base::BindOnce( - [](XDisplay* display, RQ::Reply reply, RQ::Error error) { - if (!error) - return; - - x11::LogErrorEventDescription(XErrorEvent({ - .type = error->response_type, - .display = display, - .resourceid = error->resource_id, - .serial = error->full_sequence, - .error_code = error->error_code, - .request_code = error->major_code, - .minor_code = error->minor_code, - })); - }, - display_)); - } - - Future(const Future&) = delete; - Future& operator=(const Future&) = delete; - - Future(Future&& future) - : display_(future.display_), sequence_(future.sequence_) { - future.display_ = nullptr; - future.sequence_ = base::nullopt; - } - Future& operator=(Future&& future) { - display_ = future.display_; - sequence_ = future.sequence_; - future.display_ = nullptr; - future.sequence_ = base::nullopt; - } - xcb_connection_t* connection() { return XGetXCBConnection(display_); } + Future() : FutureBase(nullptr, base::nullopt) {} // Blocks until we receive the response from the server. Returns the response. Response<Reply> Sync() { - if (!sequence_) - return {{}, {}}; - Error* raw_error = nullptr; - uint8_t* raw_reply = reinterpret_cast<uint8_t*>( - xcb_wait_for_reply(connection(), *sequence_, &raw_error)); - sequence_ = base::nullopt; + uint8_t* raw_reply = nullptr; + SyncImpl(&raw_error, &raw_reply); std::unique_ptr<Reply> reply; if (raw_reply) { @@ -138,47 +132,37 @@ class Future { // Installs |callback| to be run when the response is received. void OnResponse(Callback callback) { - if (!sequence_) - return; - // This intermediate callback handles the conversion from |raw_reply| to a // real Reply object before feeding the result to |callback|. This means // |callback| must be bound as the first argument of the intermediate // function. - auto wrapper = [](Callback callback, RQ::Reply raw_reply, RQ::Error error) { + auto wrapper = [](Callback callback, RawReply raw_reply, RawError error) { std::unique_ptr<Reply> reply = raw_reply ? detail::ReadReply<Reply>(raw_reply.get()) : nullptr; std::move(callback).Run({std::move(reply), std::move(error)}); }; - EnqueueRequest(base::BindOnce(wrapper, std::move(callback))); + OnResponseImpl(base::BindOnce(wrapper, std::move(callback))); + } - sequence_ = base::nullopt; + void IgnoreError() { + OnResponse(base::BindOnce([](Response<Reply>) {})); } private: template <typename R> - friend Future<R> SendRequest(XDisplay*, std::vector<uint8_t>*); - - Future(XDisplay* display, base::Optional<unsigned int> sequence) - : display_(display), sequence_(sequence) {} - - void EnqueueRequest(RQ::ResponseCallback callback) { - RQ::GetInstance()->AddRequest(std::is_void<Reply>::value, *sequence_, - std::move(callback)); - } + friend Future<R> SendRequest(Connection*, std::vector<uint8_t>*); - XDisplay* display_; - base::Optional<unsigned int> sequence_; + Future(Connection* connection, base::Optional<unsigned int> sequence) + : FutureBase(connection, sequence) {} }; // Sync() specialization for requests that don't generate replies. The returned // response will only contain an error if there was one. template <> inline Response<void> Future<void>::Sync() { - if (!sequence_) - return Response<void>(nullptr); + Error* raw_error = nullptr; + SyncImpl(&raw_error); - Error* raw_error = xcb_request_check(connection(), {*sequence_}); std::unique_ptr<Error, base::FreeDeleter> error; if (raw_error) error.reset(raw_error); @@ -190,18 +174,18 @@ inline Response<void> Future<void>::Sync() { // response argument to |callback| will only contain an error if there was one. template <> inline void Future<void>::OnResponse(Callback callback) { - if (!sequence_) - return; - // See Future<Reply>::OnResponse() for an explanation of why // this wrapper is necessary. - auto wrapper = [](Callback callback, RQ::Reply reply, RQ::Error error) { + auto wrapper = [](Callback callback, RawReply reply, RawError error) { DCHECK(!reply); std::move(callback).Run(Response<void>{std::move(error)}); }; - EnqueueRequest(base::BindOnce(wrapper, std::move(callback))); + OnResponseImpl(base::BindOnce(wrapper, std::move(callback))); +} - sequence_ = base::nullopt; +template <> +inline void Future<void>::IgnoreError() { + OnResponse(base::BindOnce([](Response<void>) {})); } } // namespace x11 diff --git a/chromium/ui/gfx/x/xproto_util.cc b/chromium/ui/gfx/x/xproto_util.cc index 495e4a64221..e08bf4f6b5a 100644 --- a/chromium/ui/gfx/x/xproto_util.cc +++ b/chromium/ui/gfx/x/xproto_util.cc @@ -4,8 +4,10 @@ #include "ui/gfx/x/xproto_util.h" +#include "base/logging.h" #include "base/strings/string_number_conversions.h" #include "base/strings/stringprintf.h" +#include "ui/gfx/x/connection.h" #include "ui/gfx/x/xproto.h" namespace x11 { @@ -21,7 +23,7 @@ void LogErrorEventDescription(const XErrorEvent& error_event) { char request_str[256]; XDisplay* dpy = error_event.display; - XProto conn{dpy}; + x11::Connection* conn = x11::Connection::Get(); XGetErrorText(dpy, error_event.error_code, error_str, sizeof(error_str)); strncpy(request_str, "Unknown", sizeof(request_str)); @@ -30,7 +32,7 @@ void LogErrorEventDescription(const XErrorEvent& error_event) { XGetErrorDatabaseText(dpy, "XRequest", num.c_str(), "Unknown", request_str, sizeof(request_str)); } else { - if (auto response = conn.ListExtensions({}).Sync()) { + if (auto response = conn->ListExtensions({}).Sync()) { for (const auto& str : response->names) { int ext_code, first_event, first_error; const char* name = str.name.c_str(); |