chromium/ui/base/ime/win/tsf_text_store_unittest.cc

// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/40285824): Remove this and convert code to safer constructs.
#pragma allow_unsafe_buffers
#endif

#include "ui/base/ime/win/tsf_text_store.h"

#include <initguid.h>  // for GUID_NULL and GUID_PROP_INPUTSCOPE

#include <InputScope.h>
#include <OleCtl.h>
#include <tsattrs.h>
#include <wrl/client.h>

#include <vector>

#include "base/memory/ref_counted.h"
#include "base/memory/weak_ptr.h"
#include "base/strings/utf_string_conversions.h"
#include "base/win/scoped_com_initializer.h"
#include "base/win/scoped_variant.h"
#include "build/build_config.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/base/ime/text_input_client.h"
#include "ui/base/ime/text_input_flags.h"
#include "ui/events/event.h"
#include "ui/events/event_dispatcher.h"
#include "ui/gfx/geometry/rect.h"

using testing::_;
using testing::Invoke;
using testing::Return;

namespace ui {
namespace {

class MockTextInputClient : public TextInputClient {
 public:
  ~MockTextInputClient() override {}
  base::WeakPtr<TextInputClient> AsWeakPtr() override {
    return weak_ptr_factory_.GetWeakPtr();
  }
  MOCK_METHOD1(SetCompositionText, void(const ui::CompositionText&));
  MOCK_METHOD1(ConfirmCompositionText, size_t(bool));
  MOCK_METHOD0(ClearCompositionText, void());
  MOCK_METHOD2(
      InsertText,
      void(const std::u16string&,
           ui::TextInputClient::InsertTextCursorBehavior cursor_behavior));
  MOCK_METHOD1(InsertChar, void(const ui::KeyEvent&));
  MOCK_CONST_METHOD0(GetTextInputType, ui::TextInputType());
  MOCK_CONST_METHOD0(GetTextInputMode, ui::TextInputMode());
  MOCK_CONST_METHOD0(GetTextDirection, base::i18n::TextDirection());
  MOCK_CONST_METHOD0(GetTextInputFlags, int());
  MOCK_CONST_METHOD0(CanComposeInline, bool());
  MOCK_CONST_METHOD0(GetCaretBounds, gfx::Rect());
  MOCK_CONST_METHOD0(GetSelectionBoundingBox, gfx::Rect());
  MOCK_CONST_METHOD2(GetCompositionCharacterBounds, bool(size_t, gfx::Rect*));
  MOCK_CONST_METHOD0(HasCompositionText, bool());
  MOCK_CONST_METHOD0(GetFocusReason, ui::TextInputClient::FocusReason());
  MOCK_METHOD0(ShouldDoLearning, bool());
  MOCK_CONST_METHOD1(GetTextRange, bool(gfx::Range*));
  MOCK_CONST_METHOD1(GetCompositionTextRange, bool(gfx::Range*));
  MOCK_CONST_METHOD1(GetEditableSelectionRange, bool(gfx::Range*));
  MOCK_METHOD1(SetEditableSelectionRange, bool(const gfx::Range&));
  MOCK_METHOD1(DeleteRange, bool(const gfx::Range&));
  MOCK_CONST_METHOD2(GetTextFromRange,
                     bool(const gfx::Range&, std::u16string*));
  MOCK_METHOD0(OnInputMethodChanged, void());
  MOCK_METHOD1(ChangeTextDirectionAndLayoutAlignment,
               bool(base::i18n::TextDirection));
  MOCK_METHOD2(ExtendSelectionAndDelete, void(size_t, size_t));
  MOCK_METHOD1(EnsureCaretNotInRect, void(const gfx::Rect&));
  MOCK_CONST_METHOD1(IsTextEditCommandEnabled, bool(TextEditCommand));
  MOCK_METHOD1(SetTextEditCommandForNextKeyEvent, void(TextEditCommand));
  MOCK_CONST_METHOD0(GetClientSourceForMetrics, ukm::SourceId());
  MOCK_METHOD2(SetCompositionFromExistingText,
               bool(const gfx::Range&, const std::vector<ui::ImeTextSpan>&));
  MOCK_METHOD3(SetActiveCompositionForAccessibility,
               void(const gfx::Range&, const std::u16string&, bool));
  MOCK_METHOD2(GetActiveTextInputControlLayoutBounds,
               void(std::optional<gfx::Rect>* control_bounds,
                    std::optional<gfx::Rect>* selection_bounds));
  MOCK_METHOD0(GetTextEditingContext, ui::TextInputClient::EditingContext());

 private:
  base::WeakPtrFactory<MockTextInputClient> weak_ptr_factory_{this};
};

class MockImeKeyEventDispatcher : public ImeKeyEventDispatcher {
 public:
  ~MockImeKeyEventDispatcher() override {}
  MOCK_METHOD1(DispatchKeyEventPostIME, EventDispatchDetails(KeyEvent*));
};

class MockStoreACPSink : public ITextStoreACPSink {
 public:
  MockStoreACPSink() : ref_count_(0) {}

  // IUnknown
  ULONG STDMETHODCALLTYPE AddRef() override {
    return InterlockedIncrement(&ref_count_);
  }
  ULONG STDMETHODCALLTYPE Release() override {
    const LONG count = InterlockedDecrement(&ref_count_);
    if (!count) {
      delete this;
      return 0;
    }
    return static_cast<ULONG>(count);
  }
  HRESULT STDMETHODCALLTYPE QueryInterface(REFIID iid, void** report) override {
    if (iid == IID_IUnknown || iid == IID_ITextStoreACPSink) {
      *report = static_cast<ITextStoreACPSink*>(this);
    } else {
      *report = nullptr;
      return E_NOINTERFACE;
    }
    AddRef();
    return S_OK;
  }

  // ITextStoreACPSink
  MOCK_METHOD2_WITH_CALLTYPE(STDMETHODCALLTYPE,
                             OnTextChange,
                             HRESULT(DWORD, const TS_TEXTCHANGE*));
  MOCK_METHOD0_WITH_CALLTYPE(STDMETHODCALLTYPE, OnSelectionChange, HRESULT());
  MOCK_METHOD2_WITH_CALLTYPE(STDMETHODCALLTYPE,
                             OnLayoutChange,
                             HRESULT(TsLayoutCode, TsViewCookie));
  MOCK_METHOD1_WITH_CALLTYPE(STDMETHODCALLTYPE, OnStatusChange, HRESULT(DWORD));
  MOCK_METHOD4_WITH_CALLTYPE(STDMETHODCALLTYPE,
                             OnAttrsChange,
                             HRESULT(LONG, LONG, ULONG, const TS_ATTRID*));
  MOCK_METHOD1_WITH_CALLTYPE(STDMETHODCALLTYPE, OnLockGranted, HRESULT(DWORD));
  MOCK_METHOD0_WITH_CALLTYPE(STDMETHODCALLTYPE,
                             OnStartEditTransaction,
                             HRESULT());
  MOCK_METHOD0_WITH_CALLTYPE(STDMETHODCALLTYPE,
                             OnEndEditTransaction,
                             HRESULT());

 private:
  virtual ~MockStoreACPSink() {}

  volatile LONG ref_count_;
};

const HWND kWindowHandle = reinterpret_cast<HWND>(1);

}  // namespace

class TSFTextStoreTest : public testing::Test {
 protected:
  void SetUp() override {
    text_store_ = new TSFTextStore();
    EXPECT_EQ(S_OK, text_store_->Initialize());
    sink_ = new MockStoreACPSink();
    EXPECT_EQ(S_OK, text_store_->AdviseSink(IID_ITextStoreACPSink, sink_.get(),
                                            TS_AS_ALL_SINKS));
    text_store_->SetFocusedTextInputClient(kWindowHandle, &text_input_client_);
    text_store_->SetImeKeyEventDispatcher(&ime_key_event_dispatcher_);
  }

  void TearDown() override {
    EXPECT_EQ(S_OK, text_store_->UnadviseSink(sink_.get()));
    sink_ = nullptr;
    text_store_ = nullptr;
  }

  // Accessors to the internal state of TSFTextStore.
  std::u16string* string_buffer() {
    return &text_store_->string_buffer_document_;
  }
  size_t* composition_start() { return &text_store_->composition_start_; }

  base::win::ScopedCOMInitializer com_initializer_;
  MockTextInputClient text_input_client_;
  MockImeKeyEventDispatcher ime_key_event_dispatcher_;
  scoped_refptr<TSFTextStore> text_store_;
  scoped_refptr<MockStoreACPSink> sink_;
};

class TSFTextStoreTestCallback {
 public:
  explicit TSFTextStoreTestCallback(TSFTextStore* text_store)
      : text_store_(text_store) {
    CHECK(text_store_);
  }

  TSFTextStoreTestCallback(const TSFTextStoreTestCallback&) = delete;
  TSFTextStoreTestCallback& operator=(const TSFTextStoreTestCallback&) = delete;

  virtual ~TSFTextStoreTestCallback() {}

  bool HasCompositionText() { return has_composition_text_; }
  bool GetTextRange(gfx::Range* range) {
    range->set_start(text_range_.start());
    range->set_end(text_range_.end());
    return true;
  }
  bool GetTextFromRange(const gfx::Range& range, std::u16string* text) {
    *text = text_buffer_.substr(range.GetMin(), range.length());
    return true;
  }
  bool GetEditableSelectionRange(gfx::Range* range) {
    range->set_start(selection_range_.start());
    range->set_end(selection_range_.end());
    return true;
  }

  bool GetCompositionTextRange(gfx::Range* range) {
    range->set_start(composition_range_.start());
    range->set_end(composition_range_.end());
    return true;
  }

 protected:
  // Accessors to the internal state of TSFTextStore.
  bool* edit_flag() { return &text_store_->edit_flag_; }
  bool* new_text_inserted() { return &text_store_->new_text_inserted_; }
  std::u16string* string_buffer() {
    return &text_store_->string_buffer_document_;
  }
  std::u16string* string_pending_insertion() {
    return &text_store_->string_pending_insertion_;
  }
  size_t* composition_start() { return &text_store_->composition_start_; }
  gfx::Range* selection() { return &text_store_->selection_; }
  ImeTextSpans* text_spans() { return &text_store_->text_spans_; }
  gfx::Range* composition_range() { return &text_store_->composition_range_; }
  bool* has_composition_range() { return &text_store_->has_composition_range_; }

  void SetInternalState(const std::u16string& new_string_buffer,
                        LONG new_composition_start,
                        LONG new_selection_start,
                        LONG new_selection_end) {
    ASSERT_LE(0, new_composition_start);
    ASSERT_LE(new_composition_start, new_selection_start);
    ASSERT_LE(new_selection_start, new_selection_end);
    ASSERT_LE(new_selection_end, static_cast<LONG>(new_string_buffer.size()));
    *string_buffer() = new_string_buffer;
    *string_pending_insertion() = new_string_buffer;
    *composition_start() = new_composition_start;
    selection()->set_start(new_selection_start);
    selection()->set_end(new_selection_end);
  }

  bool HasReadLock() const { return text_store_->HasReadLock(); }
  bool HasReadWriteLock() const { return text_store_->HasReadWriteLock(); }

  void GetSelectionTest(LONG expected_acp_start, LONG expected_acp_end) {
    TS_SELECTION_ACP selection = {};
    ULONG fetched = 0;
    EXPECT_EQ(S_OK, text_store_->GetSelection(0, 1, &selection, &fetched));
    EXPECT_EQ(1u, fetched);
    EXPECT_EQ(expected_acp_start, selection.acpStart);
    EXPECT_EQ(expected_acp_end, selection.acpEnd);
  }

  void SetSelectionTest(LONG acp_start, LONG acp_end, HRESULT expected_result) {
    TS_SELECTION_ACP selection = {};
    selection.acpStart = acp_start;
    selection.acpEnd = acp_end;
    selection.style.ase = TS_AE_NONE;
    selection.style.fInterimChar = 0;
    EXPECT_EQ(expected_result, text_store_->SetSelection(1, &selection));
    if (expected_result == S_OK) {
      GetSelectionTest(acp_start, acp_end);
    }
  }

  void SetTextTest(LONG acp_start,
                   LONG acp_end,
                   const std::wstring& text,
                   HRESULT error_code) {
    TS_TEXTCHANGE change = {};
    ASSERT_EQ(error_code,
              text_store_->SetText(0, acp_start, acp_end, text.c_str(),
                                   text.size(), &change));
    if (error_code == S_OK) {
      EXPECT_EQ(acp_start, change.acpStart);
      EXPECT_EQ(acp_end, change.acpOldEnd);
      EXPECT_EQ(acp_start + text.size(), (size_t)change.acpNewEnd);
    }
  }

  void GetTextTest(LONG acp_start,
                   LONG acp_end,
                   const std::wstring& expected_string,
                   LONG expected_next_acp) {
    wchar_t buffer[1024] = {};
    ULONG text_buffer_copied = 0;
    TS_RUNINFO run_info = {};
    ULONG run_info_buffer_copied = 0;
    LONG next_acp = 0;
    ASSERT_EQ(S_OK, text_store_->GetText(acp_start, acp_end, buffer, 1024,
                                         &text_buffer_copied, &run_info, 1,
                                         &run_info_buffer_copied, &next_acp));
    ASSERT_EQ(expected_string.size(), text_buffer_copied);
    EXPECT_EQ(expected_string,
              std::wstring(buffer, buffer + text_buffer_copied));
    if (text_buffer_copied > 0) {
      EXPECT_EQ(1u, run_info_buffer_copied);
      EXPECT_EQ(expected_string.size(), run_info.uCount);
      EXPECT_EQ(TS_RT_PLAIN, run_info.type);
      EXPECT_EQ(expected_next_acp, next_acp);
    } else {
      EXPECT_EQ(0u, run_info_buffer_copied);
    }
  }

  void GetTextErrorTest(LONG acp_start, LONG acp_end, HRESULT error_code) {
    wchar_t buffer[1024] = {};
    ULONG text_buffer_copied = 0;
    TS_RUNINFO run_info = {};
    ULONG run_info_buffer_copied = 0;
    LONG next_acp = 0;
    EXPECT_EQ(error_code,
              text_store_->GetText(acp_start, acp_end, buffer, 1024,
                                   &text_buffer_copied, &run_info, 1,
                                   &run_info_buffer_copied, &next_acp));
  }

  void InsertTextAtSelectionTest(const wchar_t* buffer,
                                 ULONG buffer_size,
                                 LONG expected_start,
                                 LONG expected_end,
                                 LONG expected_change_start,
                                 LONG expected_change_old_end,
                                 LONG expected_change_new_end) {
    LONG start = 0;
    LONG end = 0;
    TS_TEXTCHANGE change = {};
    EXPECT_EQ(S_OK, text_store_->InsertTextAtSelection(0, buffer, buffer_size,
                                                       &start, &end, &change));
    EXPECT_EQ(expected_start, start);
    EXPECT_EQ(expected_end, end);
    EXPECT_EQ(expected_change_start, change.acpStart);
    EXPECT_EQ(expected_change_old_end, change.acpOldEnd);
    EXPECT_EQ(expected_change_new_end, change.acpNewEnd);
  }

  void InsertTextAtSelectionQueryOnlyTest(const wchar_t* buffer,
                                          ULONG buffer_size,
                                          LONG expected_start,
                                          LONG expected_end) {
    LONG start = 0;
    LONG end = 0;
    EXPECT_EQ(S_OK, text_store_->InsertTextAtSelection(TS_IAS_QUERYONLY, buffer,
                                                       buffer_size, &start,
                                                       &end, nullptr));
    EXPECT_EQ(expected_start, start);
    EXPECT_EQ(expected_end, end);
  }

  void GetTextExtTest(TsViewCookie view_cookie,
                      LONG acp_start,
                      LONG acp_end,
                      LONG expected_left,
                      LONG expected_top,
                      LONG expected_right,
                      LONG expected_bottom) {
    RECT rect = {};
    BOOL clipped = FALSE;
    EXPECT_EQ(S_OK, text_store_->GetTextExt(view_cookie, acp_start, acp_end,
                                            &rect, &clipped));
    EXPECT_EQ(expected_left, rect.left);
    EXPECT_EQ(expected_top, rect.top);
    EXPECT_EQ(expected_right, rect.right);
    EXPECT_EQ(expected_bottom, rect.bottom);
    EXPECT_EQ(FALSE, clipped);
  }

  void GetTextExtNoLayoutTest(TsViewCookie view_cookie,
                              LONG acp_start,
                              LONG acp_end) {
    RECT rect = {};
    BOOL clipped = FALSE;
    EXPECT_EQ(TS_E_NOLAYOUT, text_store_->GetTextExt(view_cookie, acp_start,
                                                     acp_end, &rect, &clipped));
  }

  void ResetCompositionStateTest() {
    EXPECT_TRUE(text_store_->previous_composition_string_.empty());
    EXPECT_EQ(0u, text_store_->previous_composition_start_);
    EXPECT_EQ(gfx::Range::InvalidRange(),
              text_store_->previous_composition_selection_range_);
    EXPECT_TRUE(text_store_->previous_text_spans_.empty());

    EXPECT_TRUE(text_store_->string_pending_insertion_.empty());
    EXPECT_TRUE(text_store_->composition_range_.is_empty());
    EXPECT_EQ(text_store_->composition_from_client_.end(),
              text_store_->selection_.start());
    EXPECT_EQ(text_store_->composition_from_client_.end(),
              text_store_->selection_.end());
    EXPECT_EQ(text_store_->selection_.end(), text_store_->composition_start_);
  }

  void SetHasCompositionText(bool compText) {
    has_composition_text_ = compText;
  }

  void SetTextRange(uint32_t start, uint32_t end) {
    text_range_.set_start(start);
    text_range_.set_end(end);
  }

  void SetSelectionRange(uint32_t start, uint32_t end) {
    selection_range_.set_start(start);
    selection_range_.set_end(end);
  }

  void SetCompositionTextRange(uint32_t start, uint32_t end) {
    composition_range_.set_start(start);
    composition_range_.set_end(end);
  }

  void SetTextBuffer(const char16_t* buffer) {
    text_buffer_.clear();
    text_buffer_.assign(buffer);
  }

  bool has_composition_text_ = false;
  gfx::Range text_range_;
  gfx::Range selection_range_;
  gfx::Range composition_range_;
  std::u16string text_buffer_;
  scoped_refptr<TSFTextStore> text_store_;
};

namespace {

const HRESULT kInvalidResult = 0x12345678;

TEST_F(TSFTextStoreTest, GetStatusTest) {
  TS_STATUS status = {};
  EXPECT_EQ(S_OK, text_store_->GetStatus(&status));
  EXPECT_EQ((ULONG)TS_SD_INPUTPANEMANUALDISPLAYENABLE, status.dwDynamicFlags);
  EXPECT_EQ((ULONG)(TS_SS_TRANSITORY | TS_SS_NOHIDDENTEXT),
            status.dwStaticFlags);

  text_store_->UseEmptyTextStore(true);
  status = {};
  EXPECT_CALL(text_input_client_, GetTextInputType())
      .WillRepeatedly(Return(TEXT_INPUT_TYPE_NONE));
  EXPECT_EQ(S_OK, text_store_->GetStatus(&status));
  EXPECT_EQ((ULONG)TS_SD_READONLY, status.dwDynamicFlags & TS_SD_READONLY);

  status = {};
  text_store_->UseEmptyTextStore(false);
  EXPECT_CALL(text_input_client_, GetTextInputType())
      .WillRepeatedly(Return(TEXT_INPUT_TYPE_TEXT));
  EXPECT_EQ(S_OK, text_store_->GetStatus(&status));
  EXPECT_EQ((ULONG)0, status.dwDynamicFlags & TS_SD_READONLY);
}

TEST_F(TSFTextStoreTest, DummyLockTest) {
  HRESULT result = kInvalidResult;
  text_store_->UseEmptyTextStore(false);
  EXPECT_CALL(text_input_client_, GetTextInputType())
      .WillRepeatedly(Return(TEXT_INPUT_TYPE_TEXT));
  EXPECT_EQ(S_OK,
            text_store_->RequestLock(TS_LF_READWRITE | TS_LF_SYNC, &result));

  text_store_->UseEmptyTextStore(true);
  EXPECT_EQ(E_FAIL,
            text_store_->RequestLock(TS_LF_READWRITE | TS_LF_SYNC, &result));
  EXPECT_CALL(text_input_client_, GetTextInputType())
      .WillRepeatedly(Return(TEXT_INPUT_TYPE_NONE));
  EXPECT_EQ(E_FAIL,
            text_store_->RequestLock(TS_LF_READWRITE | TS_LF_SYNC, &result));
}

TEST_F(TSFTextStoreTest, QueryInsertTest) {
  LONG result_start = 0;
  LONG result_end = 0;
  *string_buffer() = std::u16string();
  *composition_start() = 0;
  EXPECT_EQ(E_INVALIDARG,
            text_store_->QueryInsert(0, 0, 0, nullptr, &result_end));
  EXPECT_EQ(E_INVALIDARG,
            text_store_->QueryInsert(0, 0, 0, &result_start, nullptr));
  EXPECT_EQ(S_OK,
            text_store_->QueryInsert(0, 0, 0, &result_start, &result_end));
  EXPECT_EQ(0, result_start);
  EXPECT_EQ(0, result_end);
  *string_buffer() = u"1234";
  *composition_start() = 1;
  EXPECT_EQ(S_OK,
            text_store_->QueryInsert(0, 1, 0, &result_start, &result_end));
  EXPECT_EQ(1, result_start);
  EXPECT_EQ(1, result_end);
  EXPECT_EQ(E_INVALIDARG,
            text_store_->QueryInsert(1, 0, 0, &result_start, &result_end));
  EXPECT_EQ(S_OK,
            text_store_->QueryInsert(2, 2, 0, &result_start, &result_end));
  EXPECT_EQ(2, result_start);
  EXPECT_EQ(2, result_end);
  EXPECT_EQ(S_OK,
            text_store_->QueryInsert(2, 3, 0, &result_start, &result_end));
  EXPECT_EQ(2, result_start);
  EXPECT_EQ(3, result_end);
  EXPECT_EQ(E_INVALIDARG,
            text_store_->QueryInsert(3, 2, 0, &result_start, &result_end));
  EXPECT_EQ(S_OK,
            text_store_->QueryInsert(3, 4, 0, &result_start, &result_end));
  EXPECT_EQ(3, result_start);
  EXPECT_EQ(4, result_end);
  EXPECT_EQ(S_OK,
            text_store_->QueryInsert(3, 5, 0, &result_start, &result_end));
  EXPECT_EQ(3, result_start);
  EXPECT_EQ(4, result_end);

  *string_buffer() = u"";
  *composition_start() = 2;
  EXPECT_EQ(S_OK,
            text_store_->QueryInsert(0, 2, 5, &result_start, &result_end));
  EXPECT_EQ(0, result_start);
  EXPECT_EQ(5, result_end);
}

class SyncRequestLockTestCallback : public TSFTextStoreTestCallback {
 public:
  explicit SyncRequestLockTestCallback(TSFTextStore* text_store)
      : TSFTextStoreTestCallback(text_store) {}

  SyncRequestLockTestCallback(const SyncRequestLockTestCallback&) = delete;
  SyncRequestLockTestCallback& operator=(const SyncRequestLockTestCallback&) =
      delete;

  HRESULT LockGranted1(DWORD flags) {
    EXPECT_TRUE(HasReadLock());
    EXPECT_FALSE(HasReadWriteLock());
    return S_OK;
  }

  HRESULT LockGranted2(DWORD flags) {
    EXPECT_TRUE(HasReadLock());
    EXPECT_TRUE(HasReadWriteLock());
    return S_OK;
  }

  HRESULT LockGranted3(DWORD flags) {
    EXPECT_TRUE(HasReadLock());
    EXPECT_FALSE(HasReadWriteLock());
    HRESULT result = kInvalidResult;
    EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READ | TS_LF_SYNC, &result));
    EXPECT_EQ(TS_E_SYNCHRONOUS, result);
    return S_OK;
  }

  HRESULT LockGranted4(DWORD flags) {
    EXPECT_TRUE(HasReadLock());
    EXPECT_FALSE(HasReadWriteLock());
    HRESULT result = kInvalidResult;
    EXPECT_EQ(S_OK,
              text_store_->RequestLock(TS_LF_READWRITE | TS_LF_SYNC, &result));
    EXPECT_EQ(TS_E_SYNCHRONOUS, result);
    return S_OK;
  }

  HRESULT LockGranted5(DWORD flags) {
    EXPECT_TRUE(HasReadLock());
    EXPECT_TRUE(HasReadWriteLock());
    HRESULT result = kInvalidResult;
    EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READ | TS_LF_SYNC, &result));
    EXPECT_EQ(TS_E_SYNCHRONOUS, result);
    return S_OK;
  }

  HRESULT LockGranted6(DWORD flags) {
    EXPECT_TRUE(HasReadLock());
    EXPECT_TRUE(HasReadWriteLock());
    HRESULT result = kInvalidResult;
    EXPECT_EQ(S_OK,
              text_store_->RequestLock(TS_LF_READWRITE | TS_LF_SYNC, &result));
    EXPECT_EQ(TS_E_SYNCHRONOUS, result);
    return S_OK;
  }
};

TEST_F(TSFTextStoreTest, SynchronousRequestLockTest) {
  EXPECT_CALL(text_input_client_, GetTextInputType())
      .WillRepeatedly(Return(TEXT_INPUT_TYPE_TEXT));
  SyncRequestLockTestCallback callback(text_store_.get());
  EXPECT_CALL(*sink_, OnLockGranted(_))
      .WillOnce(Invoke(&callback, &SyncRequestLockTestCallback::LockGranted1))
      .WillOnce(Invoke(&callback, &SyncRequestLockTestCallback::LockGranted2))
      .WillOnce(Invoke(&callback, &SyncRequestLockTestCallback::LockGranted3))
      .WillOnce(Invoke(&callback, &SyncRequestLockTestCallback::LockGranted4))
      .WillOnce(Invoke(&callback, &SyncRequestLockTestCallback::LockGranted5))
      .WillOnce(Invoke(&callback, &SyncRequestLockTestCallback::LockGranted6));

  HRESULT result = kInvalidResult;
  EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READ | TS_LF_SYNC, &result));
  EXPECT_EQ(S_OK, result);
  result = kInvalidResult;
  EXPECT_EQ(S_OK,
            text_store_->RequestLock(TS_LF_READWRITE | TS_LF_SYNC, &result));
  EXPECT_EQ(S_OK, result);

  EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READ | TS_LF_SYNC, &result));
  EXPECT_EQ(S_OK, result);
  result = kInvalidResult;
  EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READ | TS_LF_SYNC, &result));
  EXPECT_EQ(S_OK, result);

  result = kInvalidResult;
  EXPECT_EQ(S_OK,
            text_store_->RequestLock(TS_LF_READWRITE | TS_LF_SYNC, &result));
  EXPECT_EQ(S_OK, result);
  result = kInvalidResult;
  EXPECT_EQ(S_OK,
            text_store_->RequestLock(TS_LF_READWRITE | TS_LF_SYNC, &result));
  EXPECT_EQ(S_OK, result);
}

class AsyncRequestLockTestCallback : public TSFTextStoreTestCallback {
 public:
  explicit AsyncRequestLockTestCallback(TSFTextStore* text_store)
      : TSFTextStoreTestCallback(text_store), state_(0) {}

  AsyncRequestLockTestCallback(const AsyncRequestLockTestCallback&) = delete;
  AsyncRequestLockTestCallback& operator=(const AsyncRequestLockTestCallback&) =
      delete;

  HRESULT LockGranted1(DWORD flags) {
    EXPECT_EQ(0, state_);
    state_ = 1;
    EXPECT_TRUE(HasReadLock());
    EXPECT_FALSE(HasReadWriteLock());
    HRESULT result = kInvalidResult;
    EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READ, &result));
    EXPECT_EQ(TS_S_ASYNC, result);
    EXPECT_EQ(1, state_);
    state_ = 2;
    return S_OK;
  }

  HRESULT LockGranted2(DWORD flags) {
    EXPECT_EQ(2, state_);
    EXPECT_TRUE(HasReadLock());
    EXPECT_FALSE(HasReadWriteLock());
    HRESULT result = kInvalidResult;
    EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READWRITE, &result));
    EXPECT_EQ(TS_S_ASYNC, result);
    EXPECT_EQ(2, state_);
    state_ = 3;
    return S_OK;
  }

  HRESULT LockGranted3(DWORD flags) {
    EXPECT_EQ(3, state_);
    EXPECT_TRUE(HasReadLock());
    EXPECT_TRUE(HasReadWriteLock());
    HRESULT result = kInvalidResult;
    EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READWRITE, &result));
    EXPECT_EQ(TS_S_ASYNC, result);
    EXPECT_EQ(3, state_);
    state_ = 4;
    return S_OK;
  }

  HRESULT LockGranted4(DWORD flags) {
    EXPECT_EQ(4, state_);
    EXPECT_TRUE(HasReadLock());
    EXPECT_TRUE(HasReadWriteLock());
    HRESULT result = kInvalidResult;
    EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READ, &result));
    EXPECT_EQ(TS_S_ASYNC, result);
    EXPECT_EQ(4, state_);
    state_ = 5;
    return S_OK;
  }

  HRESULT LockGranted5(DWORD flags) {
    EXPECT_EQ(5, state_);
    EXPECT_TRUE(HasReadLock());
    EXPECT_FALSE(HasReadWriteLock());
    state_ = 6;
    return S_OK;
  }

 private:
  int state_;
};

TEST_F(TSFTextStoreTest, AsynchronousRequestLockTest) {
  EXPECT_CALL(text_input_client_, GetTextInputType())
      .WillRepeatedly(Return(TEXT_INPUT_TYPE_TEXT));
  AsyncRequestLockTestCallback callback(text_store_.get());
  EXPECT_CALL(*sink_, OnLockGranted(_))
      .WillOnce(Invoke(&callback, &AsyncRequestLockTestCallback::LockGranted1))
      .WillOnce(Invoke(&callback, &AsyncRequestLockTestCallback::LockGranted2))
      .WillOnce(Invoke(&callback, &AsyncRequestLockTestCallback::LockGranted3))
      .WillOnce(Invoke(&callback, &AsyncRequestLockTestCallback::LockGranted4))
      .WillOnce(Invoke(&callback, &AsyncRequestLockTestCallback::LockGranted5));

  HRESULT result = kInvalidResult;
  EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READ, &result));
  EXPECT_EQ(S_OK, result);
}

class RequestLockTextChangeTestCallback : public TSFTextStoreTestCallback {
 public:
  explicit RequestLockTextChangeTestCallback(TSFTextStore* text_store)
      : TSFTextStoreTestCallback(text_store), state_(0) {}

  RequestLockTextChangeTestCallback(const RequestLockTextChangeTestCallback&) =
      delete;
  RequestLockTextChangeTestCallback& operator=(
      const RequestLockTextChangeTestCallback&) = delete;

  HRESULT LockGranted1(DWORD flags) {
    EXPECT_EQ(0, state_);
    state_ = 1;
    EXPECT_TRUE(HasReadLock());
    EXPECT_TRUE(HasReadWriteLock());

    *edit_flag() = true;
    SetInternalState(u"012345", 6, 6, 6);
    text_spans()->clear();

    state_ = 2;
    return S_OK;
  }

  void InsertText(
      const std::u16string& text,
      ui::TextInputClient::InsertTextCursorBehavior cursor_behavior) {
    EXPECT_EQ(2, state_);
    EXPECT_EQ(u"012345", text);
    state_ = 3;
  }

  bool GetTextRange(gfx::Range* range) const {
    range->set_start(0);
    range->set_end(6);
    return true;
  }

  bool GetTextFromRange(const gfx::Range& range, std::u16string* text) const {
    std::u16string string_buffer = u"012345";
    *text = string_buffer.substr(range.GetMin(), range.length());
    return true;
  }

  bool GetEditableSelectionRange(gfx::Range* range) const {
    range->set_start(0);
    range->set_end(0);
    return true;
  }

  HRESULT OnSelectionChange() {
    EXPECT_EQ(3, state_);
    HRESULT result = kInvalidResult;
    state_ = 4;
    EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READWRITE, &result));
    EXPECT_EQ(S_OK, result);
    EXPECT_EQ(5, state_);
    state_ = 6;
    return S_OK;
  }

  HRESULT LockGranted2(DWORD flags) {
    EXPECT_EQ(4, state_);
    EXPECT_TRUE(HasReadLock());
    EXPECT_TRUE(HasReadWriteLock());
    state_ = 5;
    return S_OK;
  }

 private:
  int state_;
};

TEST_F(TSFTextStoreTest, RequestLockOnTextChangeTest) {
  EXPECT_CALL(text_input_client_, GetTextInputType())
      .WillRepeatedly(Return(TEXT_INPUT_TYPE_TEXT));
  RequestLockTextChangeTestCallback callback(text_store_.get());
  EXPECT_CALL(*sink_, OnLockGranted(_))
      .WillOnce(
          Invoke(&callback, &RequestLockTextChangeTestCallback::LockGranted1))
      .WillOnce(
          Invoke(&callback, &RequestLockTextChangeTestCallback::LockGranted2));

  EXPECT_CALL(*sink_, OnSelectionChange())
      .WillOnce(Invoke(&callback,
                       &RequestLockTextChangeTestCallback::OnSelectionChange));
  EXPECT_CALL(text_input_client_, InsertText(_, _))
      .WillOnce(
          Invoke(&callback, &RequestLockTextChangeTestCallback::InsertText));
  EXPECT_CALL(text_input_client_, GetEditableSelectionRange(_))
      .WillOnce(Invoke(
          &callback,
          &RequestLockTextChangeTestCallback::GetEditableSelectionRange));
  EXPECT_CALL(text_input_client_, GetTextFromRange(_, _))
      .WillOnce(Invoke(&callback,
                       &RequestLockTextChangeTestCallback::GetTextFromRange));
  EXPECT_CALL(text_input_client_, GetTextRange(_))
      .WillOnce(
          Invoke(&callback, &RequestLockTextChangeTestCallback::GetTextRange));

  ON_CALL(text_input_client_, GetCompositionTextRange(_))
      .WillByDefault(Invoke(
          &callback, &TSFTextStoreTestCallback::GetCompositionTextRange));

  HRESULT result = kInvalidResult;
  EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READWRITE, &result));
  EXPECT_EQ(S_OK, result);
}

class SelectionTestCallback : public TSFTextStoreTestCallback {
 public:
  explicit SelectionTestCallback(TSFTextStore* text_store)
      : TSFTextStoreTestCallback(text_store) {}

  HRESULT ReadLockGranted(DWORD flags) {
    SetInternalState(std::u16string(), 0, 0, 0);

    GetSelectionTest(0, 0);
    SetSelectionTest(0, 0, TF_E_NOLOCK);

    SetInternalState(u"012345", 0, 0, 3);

    GetSelectionTest(0, 3);
    SetSelectionTest(0, 0, TF_E_NOLOCK);

    return S_OK;
  }

  HRESULT ReadWriteLockGranted(DWORD flags) {
    SetInternalState(std::u16string(), 0, 0, 0);

    SetSelectionTest(0, 0, S_OK);
    GetSelectionTest(0, 0);
    SetSelectionTest(0, 1, TF_E_INVALIDPOS);
    SetSelectionTest(1, 0, TF_E_INVALIDPOS);
    SetSelectionTest(1, 1, TF_E_INVALIDPOS);

    SetInternalState(u"0123456", 3, 3, 3);

    SetSelectionTest(0, 0, S_OK);
    SetSelectionTest(0, 1, S_OK);
    SetSelectionTest(0, 3, S_OK);
    SetSelectionTest(0, 6, S_OK);
    SetSelectionTest(0, 7, S_OK);
    SetSelectionTest(0, 8, TF_E_INVALIDPOS);

    SetSelectionTest(1, 0, TF_E_INVALIDPOS);
    SetSelectionTest(1, 1, S_OK);
    SetSelectionTest(1, 3, S_OK);
    SetSelectionTest(1, 6, S_OK);
    SetSelectionTest(1, 7, S_OK);
    SetSelectionTest(1, 8, TF_E_INVALIDPOS);

    SetSelectionTest(3, 0, TF_E_INVALIDPOS);
    SetSelectionTest(3, 1, TF_E_INVALIDPOS);
    SetSelectionTest(3, 3, S_OK);
    SetSelectionTest(3, 6, S_OK);
    SetSelectionTest(3, 7, S_OK);
    SetSelectionTest(3, 8, TF_E_INVALIDPOS);

    SetSelectionTest(6, 0, TF_E_INVALIDPOS);
    SetSelectionTest(6, 1, TF_E_INVALIDPOS);
    SetSelectionTest(6, 3, TF_E_INVALIDPOS);
    SetSelectionTest(6, 6, S_OK);
    SetSelectionTest(6, 7, S_OK);
    SetSelectionTest(6, 8, TF_E_INVALIDPOS);

    SetSelectionTest(7, 0, TF_E_INVALIDPOS);
    SetSelectionTest(7, 1, TF_E_INVALIDPOS);
    SetSelectionTest(7, 3, TF_E_INVALIDPOS);
    SetSelectionTest(7, 6, TF_E_INVALIDPOS);
    SetSelectionTest(7, 7, S_OK);
    SetSelectionTest(7, 8, TF_E_INVALIDPOS);

    SetSelectionTest(8, 0, TF_E_INVALIDPOS);
    SetSelectionTest(8, 1, TF_E_INVALIDPOS);
    SetSelectionTest(8, 3, TF_E_INVALIDPOS);
    SetSelectionTest(8, 6, TF_E_INVALIDPOS);
    SetSelectionTest(8, 7, TF_E_INVALIDPOS);
    SetSelectionTest(8, 8, TF_E_INVALIDPOS);

    return S_OK;
  }
};

TEST_F(TSFTextStoreTest, SetGetSelectionTest) {
  EXPECT_CALL(text_input_client_, GetTextInputType())
      .WillRepeatedly(Return(TEXT_INPUT_TYPE_TEXT));
  SelectionTestCallback callback(text_store_.get());
  EXPECT_CALL(*sink_, OnLockGranted(_))
      .WillOnce(Invoke(&callback, &SelectionTestCallback::ReadLockGranted))
      .WillOnce(
          Invoke(&callback, &SelectionTestCallback::ReadWriteLockGranted));

  TS_SELECTION_ACP selection_buffer = {};
  ULONG fetched_count = 0;
  EXPECT_EQ(TS_E_NOLOCK,
            text_store_->GetSelection(0, 1, &selection_buffer, &fetched_count));

  HRESULT result = kInvalidResult;
  EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READ, &result));
  EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READWRITE, &result));
}

class SetGetTextTestCallback : public TSFTextStoreTestCallback {
 public:
  explicit SetGetTextTestCallback(TSFTextStore* text_store)
      : TSFTextStoreTestCallback(text_store) {}

  SetGetTextTestCallback(const SetGetTextTestCallback&) = delete;
  SetGetTextTestCallback& operator=(const SetGetTextTestCallback&) = delete;

  HRESULT ReadLockGranted(DWORD flags) {
    SetTextTest(0, 0, L"", TF_E_NOLOCK);

    GetTextTest(0, -1, L"", 0);
    GetTextTest(0, 0, L"", 0);
    GetTextErrorTest(0, 1, TF_E_INVALIDPOS);

    SetInternalState(u"0123456", 3, 3, 3);

    GetTextErrorTest(-1, -1, TF_E_INVALIDPOS);
    GetTextErrorTest(-1, 0, TF_E_INVALIDPOS);
    GetTextErrorTest(-1, 1, TF_E_INVALIDPOS);
    GetTextErrorTest(-1, 3, TF_E_INVALIDPOS);
    GetTextErrorTest(-1, 6, TF_E_INVALIDPOS);
    GetTextErrorTest(-1, 7, TF_E_INVALIDPOS);
    GetTextErrorTest(-1, 8, TF_E_INVALIDPOS);

    GetTextTest(0, -1, L"0123456", 7);
    GetTextTest(0, 0, L"", 0);
    GetTextTest(0, 1, L"0", 1);
    GetTextTest(0, 3, L"012", 3);
    GetTextTest(0, 6, L"012345", 6);
    GetTextTest(0, 7, L"0123456", 7);
    GetTextErrorTest(0, 8, TF_E_INVALIDPOS);

    GetTextTest(1, -1, L"123456", 7);
    GetTextErrorTest(1, 0, TF_E_INVALIDPOS);
    GetTextTest(1, 1, L"", 1);
    GetTextTest(1, 3, L"12", 3);
    GetTextTest(1, 6, L"12345", 6);
    GetTextTest(1, 7, L"123456", 7);
    GetTextErrorTest(1, 8, TF_E_INVALIDPOS);

    GetTextTest(3, -1, L"3456", 7);
    GetTextErrorTest(3, 0, TF_E_INVALIDPOS);
    GetTextErrorTest(3, 1, TF_E_INVALIDPOS);
    GetTextTest(3, 3, L"", 3);
    GetTextTest(3, 6, L"345", 6);
    GetTextTest(3, 7, L"3456", 7);
    GetTextErrorTest(3, 8, TF_E_INVALIDPOS);

    GetTextTest(6, -1, L"6", 7);
    GetTextErrorTest(6, 0, TF_E_INVALIDPOS);
    GetTextErrorTest(6, 1, TF_E_INVALIDPOS);
    GetTextErrorTest(6, 3, TF_E_INVALIDPOS);
    GetTextTest(6, 6, L"", 6);
    GetTextTest(6, 7, L"6", 7);
    GetTextErrorTest(6, 8, TF_E_INVALIDPOS);

    GetTextTest(7, -1, L"", 7);
    GetTextErrorTest(7, 0, TF_E_INVALIDPOS);
    GetTextErrorTest(7, 1, TF_E_INVALIDPOS);
    GetTextErrorTest(7, 3, TF_E_INVALIDPOS);
    GetTextErrorTest(7, 6, TF_E_INVALIDPOS);
    GetTextTest(7, 7, L"", 7);
    GetTextErrorTest(7, 8, TF_E_INVALIDPOS);

    GetTextErrorTest(8, -1, TF_E_INVALIDPOS);
    GetTextErrorTest(8, 0, TF_E_INVALIDPOS);
    GetTextErrorTest(8, 1, TF_E_INVALIDPOS);
    GetTextErrorTest(8, 3, TF_E_INVALIDPOS);
    GetTextErrorTest(8, 6, TF_E_INVALIDPOS);
    GetTextErrorTest(8, 7, TF_E_INVALIDPOS);
    GetTextErrorTest(8, 8, TF_E_INVALIDPOS);

    return S_OK;
  }

  HRESULT ReadWriteLockGranted(DWORD flags) {
    SetInternalState(std::u16string(), 0, 0, 0);
    SetTextTest(0, 0, L"", S_OK);

    SetInternalState(std::u16string(), 0, 0, 0);
    SetTextTest(0, 1, L"", TS_E_INVALIDPOS);

    SetInternalState(u"0123456", 3, 3, 3);

    SetTextTest(0, 0, L"", S_OK);
    SetTextTest(0, 1, L"", S_OK);
    SetTextTest(0, 3, L"", S_OK);
    SetTextTest(0, 6, L"", TS_E_INVALIDPOS);
    SetTextTest(0, 7, L"", TS_E_INVALIDPOS);
    SetTextTest(0, 8, L"", TS_E_INVALIDPOS);

    SetTextTest(1, 0, L"", TS_E_INVALIDPOS);
    SetTextTest(1, 1, L"", S_OK);
    SetTextTest(1, 3, L"", S_OK);
    SetTextTest(1, 6, L"", TS_E_INVALIDPOS);
    SetTextTest(1, 7, L"", TS_E_INVALIDPOS);
    SetTextTest(1, 8, L"", TS_E_INVALIDPOS);

    SetTextTest(3, 0, L"", TS_E_INVALIDPOS);
    SetTextTest(3, 1, L"", TS_E_INVALIDPOS);

    SetTextTest(3, 3, L"", TS_E_INVALIDPOS);
    GetTextTest(0, -1, L"4", 1);
    GetSelectionTest(1, 1);
    SetInternalState(u"0123456", 3, 3, 3);

    SetTextTest(3, 6, L"", S_OK);
    GetTextTest(0, -1, L"0126", 4);
    GetSelectionTest(3, 3);
    SetInternalState(u"0123456", 3, 3, 3);

    SetTextTest(3, 7, L"", S_OK);
    GetTextTest(0, -1, L"012", 3);
    GetSelectionTest(3, 3);
    SetInternalState(u"0123456", 3, 3, 3);

    SetTextTest(3, 8, L"", TS_E_INVALIDPOS);

    SetTextTest(6, 0, L"", TS_E_INVALIDPOS);
    SetTextTest(6, 1, L"", TS_E_INVALIDPOS);
    SetTextTest(6, 3, L"", TS_E_INVALIDPOS);

    SetTextTest(6, 6, L"", S_OK);
    GetTextTest(0, -1, L"0123456", 7);
    GetSelectionTest(6, 6);
    SetInternalState(u"0123456", 3, 3, 3);

    SetTextTest(6, 7, L"", S_OK);
    GetTextTest(0, -1, L"012345", 6);
    GetSelectionTest(6, 6);
    SetInternalState(u"0123456", 3, 3, 3);

    SetTextTest(6, 8, L"", TS_E_INVALIDPOS);

    SetTextTest(7, 0, L"", TS_E_INVALIDPOS);
    SetTextTest(7, 1, L"", TS_E_INVALIDPOS);
    SetTextTest(7, 3, L"", TS_E_INVALIDPOS);
    SetTextTest(7, 6, L"", TS_E_INVALIDPOS);

    SetTextTest(7, 7, L"", S_OK);
    GetTextTest(0, -1, L"0123456", 7);
    GetSelectionTest(7, 7);
    SetInternalState(u"0123456", 3, 3, 3);

    SetTextTest(7, 8, L"", TS_E_INVALIDPOS);

    SetInternalState(u"0123456", 3, 3, 3);
    SetTextTest(3, 3, L"abc", S_OK);
    GetTextTest(0, -1, L"012abc3456", 10);
    GetSelectionTest(3, 6);

    SetInternalState(u"0123456", 3, 3, 3);
    SetTextTest(3, 6, L"abc", S_OK);
    GetTextTest(0, -1, L"012abc6", 7);
    GetSelectionTest(3, 6);

    SetInternalState(u"0123456", 3, 3, 3);
    SetTextTest(3, 7, L"abc", S_OK);
    GetTextTest(0, -1, L"012abc", 6);
    GetSelectionTest(3, 6);

    SetInternalState(u"0123456", 3, 3, 3);
    SetTextTest(6, 6, L"abc", S_OK);
    GetTextTest(0, -1, L"012345abc6", 10);
    GetSelectionTest(6, 9);

    SetInternalState(u"0123456", 3, 3, 3);
    SetTextTest(6, 7, L"abc", S_OK);
    GetTextTest(0, -1, L"012345abc", 9);
    GetSelectionTest(6, 9);

    SetInternalState(u"0123456", 3, 3, 3);
    SetTextTest(7, 7, L"abc", S_OK);
    GetTextTest(0, -1, L"0123456abc", 10);
    GetSelectionTest(7, 10);

    return S_OK;
  }
};

TEST_F(TSFTextStoreTest, SetGetTextTest) {
  EXPECT_CALL(text_input_client_, GetTextInputType())
      .WillRepeatedly(Return(TEXT_INPUT_TYPE_TEXT));
  SetGetTextTestCallback callback(text_store_.get());
  EXPECT_CALL(*sink_, OnLockGranted(_))
      .WillOnce(Invoke(&callback, &SetGetTextTestCallback::ReadLockGranted))
      .WillOnce(
          Invoke(&callback, &SetGetTextTestCallback::ReadWriteLockGranted));

  wchar_t buffer[1024] = {};
  ULONG text_buffer_copied = 0;
  TS_RUNINFO run_info = {};
  ULONG run_info_buffer_copied = 0;
  LONG next_acp = 0;
  EXPECT_EQ(TF_E_NOLOCK, text_store_->GetText(
                             0, -1, buffer, 1024, &text_buffer_copied,
                             &run_info, 1, &run_info_buffer_copied, &next_acp));
  TS_TEXTCHANGE change = {};
  EXPECT_EQ(TF_E_NOLOCK, text_store_->SetText(0, 0, 0, L"abc", 3, &change));

  HRESULT result = kInvalidResult;
  EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READ, &result));
  EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READWRITE, &result));
}

class InsertTextAtSelectionTestCallback : public TSFTextStoreTestCallback {
 public:
  explicit InsertTextAtSelectionTestCallback(TSFTextStore* text_store)
      : TSFTextStoreTestCallback(text_store) {}

  InsertTextAtSelectionTestCallback(const InsertTextAtSelectionTestCallback&) =
      delete;
  InsertTextAtSelectionTestCallback& operator=(
      const InsertTextAtSelectionTestCallback&) = delete;

  HRESULT ReadLockGranted(DWORD flags) {
    const wchar_t kBuffer[] = L"0123456789";

    SetInternalState(u"abcedfg", 0, 0, 0);
    InsertTextAtSelectionQueryOnlyTest(kBuffer, 10, 0, 0);
    GetSelectionTest(0, 0);
    InsertTextAtSelectionQueryOnlyTest(kBuffer, 0, 0, 0);

    SetInternalState(u"abcedfg", 0, 2, 5);
    InsertTextAtSelectionQueryOnlyTest(kBuffer, 10, 2, 5);
    GetSelectionTest(2, 5);
    InsertTextAtSelectionQueryOnlyTest(kBuffer, 0, 2, 5);

    LONG start = 0;
    LONG end = 0;
    TS_TEXTCHANGE change = {};
    EXPECT_EQ(TS_E_NOLOCK, text_store_->InsertTextAtSelection(
                               0, kBuffer, 10, &start, &end, &change));
    return S_OK;
  }

  HRESULT ReadWriteLockGranted(DWORD flags) {
    SetInternalState(u"abcedfg", 0, 0, 0);

    const wchar_t kBuffer[] = L"0123456789";
    InsertTextAtSelectionQueryOnlyTest(kBuffer, 10, 0, 0);
    GetSelectionTest(0, 0);
    InsertTextAtSelectionQueryOnlyTest(kBuffer, 0, 0, 0);

    SetInternalState(std::u16string(), 0, 0, 0);
    InsertTextAtSelectionTest(kBuffer, 10, 0, 10, 0, 0, 10);
    GetSelectionTest(0, 10);
    GetTextTest(0, -1, L"0123456789", 10);

    SetInternalState(u"abcedfg", 0, 0, 0);
    InsertTextAtSelectionTest(kBuffer, 10, 0, 10, 0, 0, 10);
    GetSelectionTest(0, 10);
    GetTextTest(0, -1, L"0123456789abcedfg", 17);

    SetInternalState(u"abcedfg", 0, 0, 3);
    InsertTextAtSelectionTest(kBuffer, 0, 0, 0, 0, 3, 0);
    GetSelectionTest(0, 0);
    GetTextTest(0, -1, L"edfg", 4);

    SetInternalState(u"abcedfg", 0, 3, 7);
    InsertTextAtSelectionTest(kBuffer, 10, 3, 13, 3, 7, 13);
    GetSelectionTest(3, 13);
    GetTextTest(0, -1, L"abc0123456789", 13);

    SetInternalState(u"abcedfg", 0, 7, 7);
    InsertTextAtSelectionTest(kBuffer, 10, 7, 17, 7, 7, 17);
    GetSelectionTest(7, 17);
    GetTextTest(0, -1, L"abcedfg0123456789", 17);

    return S_OK;
  }
};

TEST_F(TSFTextStoreTest, InsertTextAtSelectionTest) {
  EXPECT_CALL(text_input_client_, GetTextInputType())
      .WillRepeatedly(Return(TEXT_INPUT_TYPE_TEXT));
  InsertTextAtSelectionTestCallback callback(text_store_.get());
  EXPECT_CALL(*sink_, OnLockGranted(_))
      .WillOnce(Invoke(&callback,
                       &InsertTextAtSelectionTestCallback::ReadLockGranted))
      .WillOnce(Invoke(
          &callback, &InsertTextAtSelectionTestCallback::ReadWriteLockGranted));

  HRESULT result = kInvalidResult;
  EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READ, &result));
  EXPECT_EQ(S_OK, result);
  result = kInvalidResult;
  EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READWRITE, &result));
  EXPECT_EQ(S_OK, result);
}

class ScenarioTestCallback : public TSFTextStoreTestCallback {
 public:
  explicit ScenarioTestCallback(TSFTextStore* text_store)
      : TSFTextStoreTestCallback(text_store) {}

  ScenarioTestCallback(const ScenarioTestCallback&) = delete;
  ScenarioTestCallback& operator=(const ScenarioTestCallback&) = delete;

  HRESULT LockGranted1(DWORD flags) {
    SetTextTest(0, 0, L"abc", S_OK);
    SetTextTest(1, 2, L"xyz", S_OK);

    GetTextTest(0, -1, L"axyzc", 5);

    SetSelectionTest(0, 5, S_OK);

    text_spans()->clear();
    ImeTextSpan text_span;
    text_span.start_offset = 0;
    text_span.end_offset = 5;
    text_span.underline_color = SK_ColorBLACK;
    text_span.thickness = ImeTextSpan::Thickness::kThin;
    text_span.background_color = SK_ColorTRANSPARENT;
    text_spans()->push_back(text_span);
    *edit_flag() = true;
    *composition_start() = 0;
    composition_range()->set_start(0);
    composition_range()->set_end(5);

    // Need to set |has_composition_range_| to indicate composition scenario.
    *has_composition_range() = true;
    text_store_->OnKeyTraceDown(66u, 3145729u);
    return S_OK;
  }

  void SetCompositionText1(const ui::CompositionText& composition) {
    EXPECT_EQ(u"axyzc", composition.text);
    EXPECT_EQ(0u, composition.selection.start());
    EXPECT_EQ(5u, composition.selection.end());
    ASSERT_EQ(1u, composition.ime_text_spans.size());
    EXPECT_EQ(SK_ColorBLACK, composition.ime_text_spans[0].underline_color);
    EXPECT_EQ(SK_ColorTRANSPARENT,
              composition.ime_text_spans[0].background_color);
    EXPECT_EQ(0u, composition.ime_text_spans[0].start_offset);
    EXPECT_EQ(5u, composition.ime_text_spans[0].end_offset);
    EXPECT_EQ(ImeTextSpan::Thickness::kThin,
              composition.ime_text_spans[0].thickness);
    has_composition_text_ = true;
  }

  HRESULT LockGranted2(DWORD flags) {
    SetTextTest(0, 5, L"axyZCPc", S_OK);
    GetTextTest(0, -1, L"axyZCPc", 7);

    text_spans()->clear();
    ImeTextSpan text_span;
    text_span.start_offset = 0;
    text_span.end_offset = 5;
    text_span.underline_color = SK_ColorBLACK;
    text_span.thickness = ImeTextSpan::Thickness::kThick;
    text_spans()->push_back(text_span);

    *edit_flag() = true;
    *composition_start() = 3;
    composition_range()->set_start(3);
    composition_range()->set_end(7);

    return S_OK;
  }

  void InsertText2(
      const std::u16string& text,
      ui::TextInputClient::InsertTextCursorBehavior cursor_behavior) {
    EXPECT_EQ(u"axy", text);
  }

  void SetCompositionText2(const ui::CompositionText& composition) {
    EXPECT_EQ(u"ZCPc", composition.text);
    EXPECT_EQ(0u, composition.selection.start());
    EXPECT_EQ(4u, composition.selection.end());
    ASSERT_EQ(1u, composition.ime_text_spans.size());
    // There is no styling applied from TSF in English typing
  }

  HRESULT LockGranted3(DWORD flags) {
    GetTextTest(0, -1, L"axyZCPc", 7);
    SetSelectionTest(7, 7, S_OK);

    text_spans()->clear();
    *edit_flag() = true;
    *composition_start() = 7;
    composition_range()->set_start(0);
    composition_range()->set_end(0);

    *has_composition_range() = false;
    return S_OK;
  }

  void InsertText3(
      const std::u16string& text,
      ui::TextInputClient::InsertTextCursorBehavior cursor_behavior) {
    EXPECT_EQ(u"ZCPc", text);
    has_composition_text_ = false;
  }

  HRESULT LockGranted4(DWORD flags) {
    GetTextTest(0, -1, L"axyZCPc", 7);
    SetTextTest(7, 7, L"EFGH", S_OK);
    GetTextTest(0, -1, L"axyZCPcEFGH", 11);
    SetSelectionTest(11, 11, S_OK);

    text_spans()->clear();
    ImeTextSpan text_span;
    text_span.start_offset = 0;
    text_span.end_offset = 4;
    text_span.underline_color = SK_ColorBLACK;
    text_span.thickness = ImeTextSpan::Thickness::kThick;
    text_spans()->push_back(text_span);

    *edit_flag() = true;
    *composition_start() = 7;
    composition_range()->set_start(7);
    composition_range()->set_end(11);

    *has_composition_range() = true;
    return S_OK;
  }

  void SetCompositionText4(const ui::CompositionText& composition) {
    EXPECT_EQ(u"EFGH", composition.text);
    EXPECT_EQ(4u, composition.selection.start());
    EXPECT_EQ(4u, composition.selection.end());
    ASSERT_EQ(1u, composition.ime_text_spans.size());
    *has_composition_range() = true;
    has_composition_text_ = true;
  }

  HRESULT LockGranted5(DWORD flags) {
    GetTextTest(0, -1, L"axyZCPcEFGH", 11);
    SetSelectionTest(9, 9, S_OK);

    text_spans()->clear();
    ImeTextSpan text_span;
    text_span.start_offset = 0;
    text_span.end_offset = 4;
    text_span.underline_color = SK_ColorBLACK;
    text_span.thickness = ImeTextSpan::Thickness::kThick;
    text_spans()->push_back(text_span);

    *edit_flag() = true;
    *composition_start() = 7;
    composition_range()->set_start(7);
    composition_range()->set_end(11);

    return S_OK;
  }

  // still need to call into TextInputClient to set composition text
  // to update selection range even though composition text is unchanged.
  void SetCompositionText5(const ui::CompositionText& composition) {
    EXPECT_EQ(u"EFGH", composition.text);
    EXPECT_EQ(2u, composition.selection.start());
    EXPECT_EQ(2u, composition.selection.end());
    ASSERT_EQ(1u, composition.ime_text_spans.size());
  }

  bool ClientHasCompositionText() { return has_composition_text_; }

 private:
  bool has_composition_text_;
};

TEST_F(TSFTextStoreTest, ScenarioTest) {
  EXPECT_CALL(text_input_client_, GetTextInputType())
      .WillRepeatedly(Return(TEXT_INPUT_TYPE_TEXT));
  ScenarioTestCallback callback(text_store_.get());
  EXPECT_CALL(text_input_client_, SetCompositionText(_))
      .WillOnce(Invoke(&callback, &ScenarioTestCallback::SetCompositionText1))
      .WillOnce(Invoke(&callback, &ScenarioTestCallback::SetCompositionText2))
      .WillOnce(Invoke(&callback, &ScenarioTestCallback::SetCompositionText4))
      .WillOnce(Invoke(&callback, &ScenarioTestCallback::SetCompositionText5));

  EXPECT_CALL(text_input_client_, InsertText(_, _))
      .WillOnce(Invoke(&callback, &ScenarioTestCallback::InsertText2))
      .WillOnce(Invoke(&callback, &ScenarioTestCallback::InsertText3));

  EXPECT_CALL(*sink_, OnLockGranted(_))
      .WillOnce(Invoke(&callback, &ScenarioTestCallback::LockGranted1))
      .WillOnce(Invoke(&callback, &ScenarioTestCallback::LockGranted2))
      .WillOnce(Invoke(&callback, &ScenarioTestCallback::LockGranted3))
      .WillOnce(Invoke(&callback, &ScenarioTestCallback::LockGranted4))
      .WillOnce(Invoke(&callback, &ScenarioTestCallback::LockGranted5));

  EXPECT_CALL(text_input_client_, HasCompositionText())
      .WillRepeatedly(
          Invoke(&callback, &ScenarioTestCallback::ClientHasCompositionText));

  HRESULT result = kInvalidResult;
  EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READWRITE, &result));
  EXPECT_EQ(S_OK, result);
  result = kInvalidResult;
  EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READWRITE, &result));
  EXPECT_EQ(S_OK, result);
  result = kInvalidResult;
  EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READWRITE, &result));
  EXPECT_EQ(S_OK, result);
  result = kInvalidResult;
  EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READWRITE, &result));
  EXPECT_EQ(S_OK, result);
  result = kInvalidResult;
  EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READWRITE, &result));
  EXPECT_EQ(S_OK, result);
}

class GetTextExtTestCallback : public TSFTextStoreTestCallback {
 public:
  explicit GetTextExtTestCallback(TSFTextStore* text_store)
      : TSFTextStoreTestCallback(text_store),
        layout_prepared_character_num_(0) {}

  GetTextExtTestCallback(const GetTextExtTestCallback&) = delete;
  GetTextExtTestCallback& operator=(const GetTextExtTestCallback&) = delete;

  HRESULT LockGranted(DWORD flags) {
    SetInternalState(u"0123456789012", 0, 0, 0);
    layout_prepared_character_num_ = 13;
    has_composition_text_ = true;

    TsViewCookie view_cookie = 0;
    EXPECT_EQ(S_OK, text_store_->GetActiveView(&view_cookie));
    GetTextExtTest(view_cookie, 0, 0, 11, 12, 11, 20);
    GetTextExtTest(view_cookie, 0, 1, 11, 12, 20, 20);
    GetTextExtTest(view_cookie, 0, 2, 11, 12, 30, 20);
    GetTextExtTest(view_cookie, 9, 9, 100, 12, 100, 20);
    GetTextExtTest(view_cookie, 9, 10, 101, 12, 110, 20);
    GetTextExtTest(view_cookie, 10, 10, 110, 12, 110, 20);
    GetTextExtTest(view_cookie, 11, 11, 20, 112, 20, 120);
    GetTextExtTest(view_cookie, 11, 12, 21, 112, 30, 120);
    GetTextExtTest(view_cookie, 9, 12, 101, 12, 101, 120);
    GetTextExtTest(view_cookie, 9, 13, 101, 12, 101, 120);
    GetTextExtTest(view_cookie, 0, 13, 11, 12, 40, 120);
    GetTextExtTest(view_cookie, 13, 13, 40, 112, 40, 120);

    layout_prepared_character_num_ = 12;
    GetTextExtNoLayoutTest(view_cookie, 13, 13);

    layout_prepared_character_num_ = 0;
    has_composition_text_ = false;
    GetTextExtTest(view_cookie, 0, 0, 1, 2, 4, 6);

    SetInternalState(std::u16string(), 0, 0, 0);
    GetTextExtTest(view_cookie, 0, 0, 1, 2, 4, 6);

    // Last character is not available due to timing issue of async API.
    // In this case, we will get first character bounds instead of whole text
    // bounds.
    SetInternalState(u"abc", 0, 0, 3);
    layout_prepared_character_num_ = 2;
    has_composition_text_ = true;
    GetTextExtTest(view_cookie, 0, 0, 11, 12, 11, 20);

    return S_OK;
  }

  bool GetCompositionCharacterBounds(uint32_t index, gfx::Rect* rect) {
    if (index >= layout_prepared_character_num_)
      return false;
    rect->set_x((index % 10) * 10 + 11);
    rect->set_y((index / 10) * 100 + 12);
    rect->set_width(9);
    rect->set_height(8);
    return true;
  }

  gfx::Rect GetCaretBounds() { return gfx::Rect(1, 2, 3, 4); }

  bool ClientHasCompositionText() { return has_composition_text_; }

 private:
  uint32_t layout_prepared_character_num_;
  bool has_composition_text_;
};

TEST_F(TSFTextStoreTest, GetTextExtTest) {
  EXPECT_CALL(text_input_client_, GetTextInputType())
      .WillRepeatedly(Return(TEXT_INPUT_TYPE_TEXT));
  GetTextExtTestCallback callback(text_store_.get());
  EXPECT_CALL(text_input_client_, GetCaretBounds())
      .WillRepeatedly(
          Invoke(&callback, &GetTextExtTestCallback::GetCaretBounds));

  EXPECT_CALL(text_input_client_, GetCompositionCharacterBounds(_, _))
      .WillRepeatedly(Invoke(
          &callback, &GetTextExtTestCallback::GetCompositionCharacterBounds));

  EXPECT_CALL(text_input_client_, HasCompositionText())
      .WillRepeatedly(
          Invoke(&callback, &GetTextExtTestCallback::ClientHasCompositionText));

  EXPECT_CALL(*sink_, OnLockGranted(_))
      .WillOnce(Invoke(&callback, &GetTextExtTestCallback::LockGranted));

  HRESULT result = kInvalidResult;
  EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READ, &result));
  EXPECT_EQ(S_OK, result);
}

TEST_F(TSFTextStoreTest, RequestSupportedAttrs) {
  ui::TextInputClient::EditingContext expected_editing_context;
  expected_editing_context.page_url = GURL("http://example.com");
  EXPECT_CALL(text_input_client_, GetTextInputType())
      .WillRepeatedly(Return(TEXT_INPUT_TYPE_TEXT));
  EXPECT_CALL(text_input_client_, GetTextInputMode())
      .WillRepeatedly(Return(TEXT_INPUT_MODE_DEFAULT));
  EXPECT_CALL(text_input_client_, GetTextEditingContext())
      .WillOnce(Return(ui::TextInputClient::EditingContext()))
      .WillOnce(Return(expected_editing_context));

  EXPECT_HRESULT_FAILED(text_store_->RequestSupportedAttrs(0, 1, nullptr));

  const TS_ATTRID kUnknownAttributes[] = {GUID_NULL};
  EXPECT_HRESULT_SUCCEEDED(text_store_->RequestSupportedAttrs(
      0, std::size(kUnknownAttributes), kUnknownAttributes))
      << "Mustn't fail for unknown attributes";

  const TS_ATTRID kAttributes[] = {GUID_NULL, GUID_PROP_INPUTSCOPE, GUID_NULL};
  EXPECT_EQ(S_OK, text_store_->RequestSupportedAttrs(0, std::size(kAttributes),
                                                     kAttributes))
      << "InputScope must be supported";
  const TS_ATTRID urlAttributes[] = {GUID_PROP_URL};
  ui::TextInputClient::EditingContext actual_editing_context =
      text_input_client_.GetTextEditingContext();
  EXPECT_TRUE(actual_editing_context.page_url.is_empty());
  EXPECT_EQ(S_OK, text_store_->RequestSupportedAttrs(
                      0, std::size(urlAttributes), urlAttributes))
      << "Should return S_OK even if URL not supported";

  actual_editing_context = text_input_client_.GetTextEditingContext();
  EXPECT_TRUE(!actual_editing_context.page_url.is_empty());
  EXPECT_TRUE(actual_editing_context.page_url.spec().compare(
                  expected_editing_context.page_url.spec()) == 0);
  EXPECT_EQ(S_OK, text_store_->RequestSupportedAttrs(
                      0, std::size(urlAttributes), urlAttributes))
      << "Expect URL to be supported";

  {
    SCOPED_TRACE("Check if RequestSupportedAttrs fails while focus is lost");
    // Emulate focus lost
    text_store_->SetFocusedTextInputClient(nullptr, nullptr);
    EXPECT_HRESULT_FAILED(text_store_->RequestSupportedAttrs(0, 0, nullptr));
    EXPECT_HRESULT_FAILED(text_store_->RequestSupportedAttrs(
        0, std::size(kAttributes), kAttributes));
  }
}

TEST_F(TSFTextStoreTest, RetrieveRequestedAttrs) {
  ui::TextInputClient::EditingContext expected_editing_context;
  expected_editing_context.page_url = GURL("http://example.com");
  EXPECT_CALL(text_input_client_, GetTextInputType())
      .WillRepeatedly(Return(TEXT_INPUT_TYPE_TEXT));
  EXPECT_CALL(text_input_client_, GetTextInputMode())
      .WillRepeatedly(Return(TEXT_INPUT_MODE_DEFAULT));
  EXPECT_CALL(text_input_client_, GetTextEditingContext())
      .WillRepeatedly(Return(expected_editing_context));

  ULONG num_copied = 0xfffffff;
  EXPECT_HRESULT_FAILED(
      text_store_->RetrieveRequestedAttrs(1, nullptr, &num_copied));

  {
    SCOPED_TRACE("Make sure that InputScope is supported");
    TS_ATTRVAL buffer[2] = {};
    num_copied = 0xfffffff;
    const TS_ATTRID kAttributes[] = {GUID_PROP_INPUTSCOPE};
    ASSERT_EQ(S_OK, text_store_->RequestSupportedAttrs(
                        0, std::size(kAttributes), kAttributes));
    ASSERT_EQ(S_OK, text_store_->RetrieveRequestedAttrs(std::size(buffer),
                                                        buffer, &num_copied));
    bool input_scope_found = false;
    for (size_t i = 0; i < num_copied; ++i) {
      base::win::ScopedVariant variant;
      // Move ownership from |buffer[i].varValue| to |variant|.
      std::swap(*variant.Receive(), buffer[i].varValue);
      if (IsEqualGUID(buffer[i].idAttr, GUID_PROP_INPUTSCOPE)) {
        EXPECT_EQ(VT_UNKNOWN, variant.type());
        Microsoft::WRL::ComPtr<ITfInputScope> input_scope;
        EXPECT_HRESULT_SUCCEEDED(variant.AsInput()->punkVal->QueryInterface(
            IID_PPV_ARGS(&input_scope)));
        input_scope_found = true;
        // we do not break here to clean up all the retrieved VARIANTs.
      }
    }
    EXPECT_TRUE(input_scope_found);
  }
  {
    SCOPED_TRACE("Verify URL support");
    TS_ATTRVAL buffer[2] = {};
    num_copied = 0xfffffff;
    base::win::ScopedVariant variant;
    const TS_ATTRID urlAttributes[] = {GUID_PROP_URL};

    // This call should have a valid URL so the URL property should be returned
    // and is expected to match the test_url value set above.
    ASSERT_EQ(S_OK, text_store_->RequestSupportedAttrs(
                        0, std::size(urlAttributes), urlAttributes));
    ASSERT_EQ(S_OK, text_store_->RetrieveRequestedAttrs(std::size(buffer),
                                                        buffer, &num_copied));
    EXPECT_EQ(num_copied, 1U) << "Expect only URL property to be supported";
    EXPECT_TRUE(IsEqualGUID(buffer[0].idAttr, GUID_PROP_URL));
    std::swap(*variant.Receive(), buffer[0].varValue);
    EXPECT_EQ(VT_BSTR, variant.type());
    std::string url_string = base::WideToUTF8(std::wstring(
        variant.ptr()->bstrVal, SysStringLen(variant.ptr()->bstrVal)));
    EXPECT_EQ(expected_editing_context.page_url.spec(), url_string)
        << "Expected url strings to match";
  }
  {
    SCOPED_TRACE("Verify URL and InputScope support");
    TS_ATTRVAL buffer[2] = {};
    num_copied = 0xfffffff;
    const TS_ATTRID inputScopeAndUrlAttributes[] = {GUID_PROP_INPUTSCOPE,
                                                    GUID_PROP_URL};

    // This call should have a valid URL so the URL property should be returned
    // and is expected to match the test_url value set above.
    ASSERT_EQ(S_OK, text_store_->RequestSupportedAttrs(
                        0, std::size(inputScopeAndUrlAttributes),
                        inputScopeAndUrlAttributes));
    ASSERT_EQ(S_OK, text_store_->RetrieveRequestedAttrs(std::size(buffer),
                                                        buffer, &num_copied));
    EXPECT_EQ(num_copied, 2U)
        << "Expect both URL & InputScope properties to be supported";
    for (size_t i = 0; i < num_copied; ++i) {
      base::win::ScopedVariant variant;
      // Move ownership from |buffer[i].varValue| to |variant|.
      std::swap(*variant.Receive(), buffer[i].varValue);
      if (IsEqualGUID(buffer[i].idAttr, GUID_PROP_INPUTSCOPE)) {
        EXPECT_EQ(VT_UNKNOWN, variant.type());
        Microsoft::WRL::ComPtr<ITfInputScope> input_scope;
        EXPECT_HRESULT_SUCCEEDED(variant.AsInput()->punkVal->QueryInterface(
            IID_PPV_ARGS(&input_scope)));
      }
      if (IsEqualGUID(buffer[i].idAttr, GUID_PROP_URL)) {
        EXPECT_EQ(VT_BSTR, variant.type());
        std::string url_string = base::WideToUTF8(std::wstring(
            variant.ptr()->bstrVal, SysStringLen(variant.ptr()->bstrVal)));
        EXPECT_EQ(expected_editing_context.page_url.spec(), url_string)
            << "Expected url strings to match";
      }
      // we do not break here to clean up all the retrieved VARIANTs.
    }
  }

  {
    SCOPED_TRACE("Verify TSATTRID_Text_VerticalWriting support");
    TS_ATTRVAL buffer[2] = {};
    num_copied = 0xfffffff;
    const TS_ATTRID attributes[] = {TSATTRID_Text_VerticalWriting};

    ASSERT_EQ(S_OK, text_store_->RequestSupportedAttrs(0, std::size(attributes),
                                                       attributes));

    EXPECT_CALL(text_input_client_, GetTextInputFlags()).WillOnce(Return(0));
    ASSERT_EQ(S_OK, text_store_->RetrieveRequestedAttrs(std::size(buffer),
                                                        buffer, &num_copied));
    EXPECT_EQ(num_copied, 1U);
    EXPECT_TRUE(IsEqualGUID(buffer[0].idAttr, TSATTRID_Text_VerticalWriting));
    EXPECT_EQ(VT_BOOL, buffer[0].varValue.vt);
    EXPECT_FALSE(buffer[0].varValue.boolVal);

    EXPECT_CALL(text_input_client_, GetTextInputFlags())
        .WillOnce(Return(ui::TEXT_INPUT_FLAG_VERTICAL));
    ASSERT_EQ(S_OK, text_store_->RetrieveRequestedAttrs(std::size(buffer),
                                                        buffer, &num_copied));
    EXPECT_EQ(num_copied, 1U);
    EXPECT_TRUE(IsEqualGUID(buffer[0].idAttr, TSATTRID_Text_VerticalWriting));
    EXPECT_EQ(VT_BOOL, buffer[0].varValue.vt);
    EXPECT_TRUE(buffer[0].varValue.boolVal);
  }

  {
    SCOPED_TRACE("Check if RetrieveRequestedAttrs fails while focus is lost");
    // Emulate focus lost
    text_store_->SetFocusedTextInputClient(nullptr, nullptr);
    num_copied = 0xfffffff;
    TS_ATTRVAL buffer[2] = {};
    EXPECT_HRESULT_FAILED(text_store_->RetrieveRequestedAttrs(
        std::size(buffer), buffer, &num_copied));
  }
}

TEST_F(TSFTextStoreTest, SendOnUrlChanged) {
  text_store_->UseEmptyTextStore(true);
  EXPECT_TRUE(text_store_->MaybeSendOnUrlChanged());

  text_store_->UseEmptyTextStore(false);
  EXPECT_FALSE(text_store_->MaybeSendOnUrlChanged());
}

class KeyEventTestCallback : public TSFTextStoreTestCallback {
 public:
  explicit KeyEventTestCallback(TSFTextStore* text_store)
      : TSFTextStoreTestCallback(text_store) {}

  KeyEventTestCallback(const KeyEventTestCallback&) = delete;
  KeyEventTestCallback& operator=(const KeyEventTestCallback&) = delete;

  HRESULT LockGranted1(DWORD flags) {
    SetTextTest(0, 0, L"a", S_OK);

    GetTextTest(0, -1, L"a", 1);

    SetSelectionTest(0, 1, S_OK);

    text_spans()->clear();
    ImeTextSpan text_span;
    text_span.start_offset = 0;
    text_span.end_offset = 1;
    text_span.underline_color = SK_ColorBLACK;
    text_span.thickness = ImeTextSpan::Thickness::kThin;
    text_span.background_color = SK_ColorTRANSPARENT;
    text_spans()->push_back(text_span);
    *edit_flag() = true;
    *composition_start() = 0;
    composition_range()->set_start(0);
    composition_range()->set_end(1);
    text_store_->OnKeyTraceDown(65u, 1966081u);
    *has_composition_range() = true;

    return S_OK;
  }

  void SetCompositionText1(const ui::CompositionText& composition) {
    EXPECT_EQ(u"a", composition.text);
    EXPECT_EQ(0u, composition.selection.start());
    EXPECT_EQ(1u, composition.selection.end());
    ASSERT_EQ(1u, composition.ime_text_spans.size());
    EXPECT_EQ(SK_ColorBLACK, composition.ime_text_spans[0].underline_color);
    EXPECT_EQ(SK_ColorTRANSPARENT,
              composition.ime_text_spans[0].background_color);
    EXPECT_EQ(0u, composition.ime_text_spans[0].start_offset);
    EXPECT_EQ(1u, composition.ime_text_spans[0].end_offset);
    EXPECT_EQ(ImeTextSpan::Thickness::kThin,
              composition.ime_text_spans[0].thickness);
    SetHasCompositionText(true);
  }

  ui::EventDispatchDetails DispatchKeyEventPostIME1(KeyEvent* key) {
    EXPECT_EQ(ui::EventType::kKeyPressed, key->type());
    EXPECT_EQ(VKEY_PROCESSKEY, key->key_code());
    return ui::EventDispatchDetails();
  }

  HRESULT LockGranted2(DWORD flags) {
    SetSelectionTest(1, 1, S_OK);
    InsertTextAtSelectionTest(L"B", 1, 1, 2, 1, 1, 2);
    GetTextTest(0, -1, L"aB", 2);

    text_spans()->clear();
    ImeTextSpan text_span;
    text_span.start_offset = 1;
    text_span.end_offset = 2;
    text_span.underline_color = SK_ColorBLACK;
    text_span.thickness = ImeTextSpan::Thickness::kThick;
    text_spans()->push_back(text_span);

    *edit_flag() = true;
    *composition_start() = 1;
    composition_range()->set_start(1);
    composition_range()->set_end(2);

    text_store_->OnKeyTraceUp(65u, 1966081u);
    text_store_->OnKeyTraceDown(66u, 3145729u);
    return S_OK;
  }

  void InsertText2(
      const std::u16string& text,
      ui::TextInputClient::InsertTextCursorBehavior cursor_behavior) {
    EXPECT_EQ(u"a", text);
    SetHasCompositionText(false);
  }

  void SetCompositionText2(const ui::CompositionText& composition) {
    EXPECT_EQ(u"B", composition.text);
    EXPECT_EQ(0u, composition.selection.start());
    EXPECT_EQ(1u, composition.selection.end());
    ASSERT_EQ(1u, composition.ime_text_spans.size());
    EXPECT_EQ(SK_ColorBLACK, composition.ime_text_spans[0].underline_color);
    EXPECT_EQ(0u, composition.ime_text_spans[0].start_offset);
    EXPECT_EQ(1u, composition.ime_text_spans[0].end_offset);
    EXPECT_EQ(ImeTextSpan::Thickness::kThick,
              composition.ime_text_spans[0].thickness);
    SetHasCompositionText(true);
  }

  ui::EventDispatchDetails DispatchKeyEventPostIME2(KeyEvent* key) {
    EXPECT_EQ(ui::EventType::kKeyReleased, key->type());
    EXPECT_EQ(VKEY_PROCESSKEY, key->key_code());
    return ui::EventDispatchDetails();
  }

  ui::EventDispatchDetails DispatchKeyEventPostIME3a(KeyEvent* key) {
    EXPECT_EQ(ui::EventType::kKeyPressed, key->type());
    EXPECT_EQ(VKEY_PROCESSKEY, key->key_code());
    return ui::EventDispatchDetails();
  }

  HRESULT LockGranted3(DWORD flags) {
    GetTextTest(0, -1, L"aB", 2);

    text_spans()->clear();
    *edit_flag() = true;
    *composition_start() = 2;
    composition_range()->set_start(0);
    composition_range()->set_end(0);

    *has_composition_range() = false;
    return S_OK;
  }

  void InsertText3(
      const std::u16string& text,
      ui::TextInputClient::InsertTextCursorBehavior cursor_behavior) {
    EXPECT_EQ(u"B", text);
    SetHasCompositionText(false);
  }

  ui::EventDispatchDetails DispatchKeyEventPostIME3b(KeyEvent* key) {
    EXPECT_EQ(ui::EventType::kKeyReleased, key->type());
    EXPECT_EQ(VKEY_PROCESSKEY, key->key_code());
    return ui::EventDispatchDetails();
  }

  HRESULT LockGranted4(DWORD flags) {
    text_store_->OnKeyTraceUp(66u, 3145729u);
    return S_OK;
  }
};

TEST_F(TSFTextStoreTest, KeyEventTest) {
  EXPECT_CALL(text_input_client_, GetTextInputType())
      .WillRepeatedly(Return(TEXT_INPUT_TYPE_TEXT));
  KeyEventTestCallback callback(text_store_.get());
  EXPECT_CALL(text_input_client_, SetCompositionText(_))
      .WillOnce(Invoke(&callback, &KeyEventTestCallback::SetCompositionText1))
      .WillOnce(Invoke(&callback, &KeyEventTestCallback::SetCompositionText2));

  EXPECT_CALL(text_input_client_, InsertText(_, _))
      .WillOnce(Invoke(&callback, &KeyEventTestCallback::InsertText2))
      .WillOnce(Invoke(&callback, &KeyEventTestCallback::InsertText3));

  EXPECT_CALL(ime_key_event_dispatcher_, DispatchKeyEventPostIME(_))
      .WillOnce(
          Invoke(&callback, &KeyEventTestCallback::DispatchKeyEventPostIME1))
      .WillOnce(
          Invoke(&callback, &KeyEventTestCallback::DispatchKeyEventPostIME2))
      .WillOnce(
          Invoke(&callback, &KeyEventTestCallback::DispatchKeyEventPostIME3a))
      .WillOnce(
          Invoke(&callback, &KeyEventTestCallback::DispatchKeyEventPostIME3b));

  EXPECT_CALL(*sink_, OnLockGranted(_))
      .WillOnce(Invoke(&callback, &KeyEventTestCallback::LockGranted1))
      .WillOnce(Invoke(&callback, &KeyEventTestCallback::LockGranted2))
      .WillOnce(Invoke(&callback, &KeyEventTestCallback::LockGranted3))
      .WillOnce(Invoke(&callback, &KeyEventTestCallback::LockGranted4));

  ON_CALL(text_input_client_, HasCompositionText())
      .WillByDefault(
          Invoke(&callback, &TSFTextStoreTestCallback::HasCompositionText));

  HRESULT result = kInvalidResult;
  EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READWRITE, &result));
  EXPECT_EQ(S_OK, result);
  result = kInvalidResult;
  EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READWRITE, &result));
  EXPECT_EQ(S_OK, result);
  result = kInvalidResult;
  EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READWRITE, &result));
  EXPECT_EQ(S_OK, result);
  EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READWRITE, &result));
  EXPECT_EQ(S_OK, result);
}

// Below test covers the notification sent to accessibility about the
// composition
class AccessibilityEventTestCallback : public TSFTextStoreTestCallback {
 public:
  explicit AccessibilityEventTestCallback(TSFTextStore* text_store)
      : TSFTextStoreTestCallback(text_store) {}

  AccessibilityEventTestCallback(const AccessibilityEventTestCallback&) =
      delete;
  AccessibilityEventTestCallback& operator=(
      const AccessibilityEventTestCallback&) = delete;

  HRESULT LockGranted1(DWORD flags) {
    SetTextTest(0, 0, L"a", S_OK);

    GetTextTest(0, -1, L"a", 1);

    SetSelectionTest(0, 1, S_OK);

    text_spans()->clear();
    ImeTextSpan text_span;
    text_span.start_offset = 0;
    text_span.end_offset = 1;
    text_span.underline_color = SK_ColorBLACK;
    text_span.thickness = ImeTextSpan::Thickness::kThin;
    text_span.background_color = SK_ColorTRANSPARENT;
    text_spans()->push_back(text_span);
    *edit_flag() = true;
    *composition_start() = 0;
    composition_range()->set_start(0);
    composition_range()->set_end(1);
    *has_composition_range() = true;

    return S_OK;
  }

  void SetCompositionText1(const ui::CompositionText& composition) {
    EXPECT_EQ(u"a", composition.text);
    EXPECT_EQ(0u, composition.selection.start());
    EXPECT_EQ(1u, composition.selection.end());
    ASSERT_EQ(1u, composition.ime_text_spans.size());
    EXPECT_EQ(SK_ColorBLACK, composition.ime_text_spans[0].underline_color);
    EXPECT_EQ(SK_ColorTRANSPARENT,
              composition.ime_text_spans[0].background_color);
    EXPECT_EQ(0u, composition.ime_text_spans[0].start_offset);
    EXPECT_EQ(1u, composition.ime_text_spans[0].end_offset);
    EXPECT_EQ(ImeTextSpan::Thickness::kThin,
              composition.ime_text_spans[0].thickness);
    SetHasCompositionText(true);
  }

  void SetActiveCompositionForAccessibility1(
      const gfx::Range& range,
      const std::u16string& active_composition_text,
      bool committed_composition) {
    EXPECT_EQ(u"a", active_composition_text);
    EXPECT_EQ(0u, range.start());
    EXPECT_EQ(1u, range.end());
  }
};

TEST_F(TSFTextStoreTest, AccessibilityEventTest) {
  EXPECT_CALL(text_input_client_, GetTextInputType())
      .WillRepeatedly(Return(TEXT_INPUT_TYPE_TEXT));
  AccessibilityEventTestCallback callback(text_store_.get());
  EXPECT_CALL(text_input_client_, SetCompositionText(_))
      .WillOnce(Invoke(&callback,
                       &AccessibilityEventTestCallback::SetCompositionText1));

  EXPECT_CALL(text_input_client_, SetActiveCompositionForAccessibility(_, _, _))
      .WillOnce(Invoke(&callback, &AccessibilityEventTestCallback::
                                      SetActiveCompositionForAccessibility1));

  EXPECT_CALL(*sink_, OnLockGranted(_))
      .WillOnce(
          Invoke(&callback, &AccessibilityEventTestCallback::LockGranted1));

  ON_CALL(text_input_client_, HasCompositionText())
      .WillByDefault(
          Invoke(&callback, &TSFTextStoreTestCallback::HasCompositionText));

  HRESULT result = kInvalidResult;
  EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READWRITE, &result));
  EXPECT_EQ(S_OK, result);
}

// Summary of test scenarios:
// 1.  renderer proc changes buffer from "" to "a".
// 2.  input service changes buffer from "a" to "abcde".
// 3.  renderer proc changes buffer from "abcde" to "about".
// 4.  renderer proc changes buffer from "about" to "abFGt".
// 5.  renderer proc changes buffer from "abFGt" to "aHIGt".
// 6.  renderer proc changes buffer from "aHIGt" to "JKLMN".
// 7.  renderer proc changes buffer from "JKLMN" to "".
// 8.  renderer proc changes buffer from "" to "OPQ".
// 9.  renderer proc changes buffer from "OPQ" to "OPR".
// 10. renderer proc changes buffer from "OPR" to "SPR".
// 11. renderer proc changes buffer from "SPR" to "STPR".
// 12. renderer proc changes buffer from "STPR" to "PR".
// 13. renderer proc changes buffer from "PR" to "UPR".
// 14. renderer proc changes buffer from "UPR" to "UPVWR".
class DiffingAlgorithmTestCallback : public TSFTextStoreTestCallback {
 public:
  explicit DiffingAlgorithmTestCallback(TSFTextStore* text_store)
      : TSFTextStoreTestCallback(text_store) {}

  DiffingAlgorithmTestCallback(const DiffingAlgorithmTestCallback&) = delete;
  DiffingAlgorithmTestCallback& operator=(const DiffingAlgorithmTestCallback&) =
      delete;

  HRESULT LockGranted1(DWORD flags) {
    SetTextTest(0, 0, L"", S_OK);
    GetTextTest(0, -1, L"", 0);

    SetTextRange(0, 1);
    SetTextBuffer(u"a");
    SetSelectionRange(1, 1);
    *composition_start() = 1;
    return S_OK;
  }

  HRESULT OnTextChange1(DWORD flag, const TS_TEXTCHANGE* pChange) {
    HRESULT result = S_OK;
    EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READWRITE, &result));
    EXPECT_EQ(0, pChange->acpStart);
    EXPECT_EQ(0, pChange->acpOldEnd);
    EXPECT_EQ(1, pChange->acpNewEnd);

    EXPECT_EQ(S_OK, result);
    return S_OK;
  }

  HRESULT LockGranted1a(DWORD flags) {
    GetTextTest(0, -1, L"a", 1);

    return S_OK;
  }

  HRESULT OnSelectionChange1() {
    HRESULT result = S_OK;
    EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READWRITE, &result));
    EXPECT_EQ(S_OK, result);
    return S_OK;
  }

  HRESULT LockGranted1b(DWORD flags) {
    GetSelectionTest(1, 1);
    return S_OK;
  }

  HRESULT LockGranted2(DWORD flags) {
    SetTextTest(1, 1, L"bcde", S_OK);
    GetTextTest(0, -1, L"abcde", 5);
    SetSelectionTest(5, 5, S_OK);

    *edit_flag() = true;
    *composition_start() = 5;
    return S_OK;
  }

  void InsertText2(
      const std::u16string& text,
      ui::TextInputClient::InsertTextCursorBehavior cursor_behavior) {
    EXPECT_EQ(u"bcde", text);
    SetTextRange(0, 5);
    SetSelectionRange(5, 5);
    SetTextBuffer(u"abcde");
  }

  HRESULT LockGranted3(DWORD flags) {
    SetTextRange(0, 5);
    SetTextBuffer(u"about");
    SetSelectionRange(0, 5);
    return S_OK;
  }

  HRESULT OnTextChange3(DWORD flag, const TS_TEXTCHANGE* pChange) {
    HRESULT result = S_OK;
    EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READWRITE, &result));
    EXPECT_EQ(2, pChange->acpStart);
    EXPECT_EQ(5, pChange->acpOldEnd);
    EXPECT_EQ(5, pChange->acpNewEnd);

    EXPECT_EQ(S_OK, result);
    return S_OK;
  }

  HRESULT LockGranted3a(DWORD flags) {
    GetTextTest(1, 5, L"bout", 5);

    return S_OK;
  }

  HRESULT OnSelectionChange3() {
    HRESULT result = S_OK;
    EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READWRITE, &result));
    EXPECT_EQ(S_OK, result);
    return S_OK;
  }

  HRESULT LockGranted3b(DWORD flags) {
    GetSelectionTest(0, 5);
    return S_OK;
  }

  HRESULT LockGranted4(DWORD flags) {
    SetTextRange(0, 5);
    SetTextBuffer(u"abFGt");
    SetSelectionRange(3, 4);
    return S_OK;
  }

  HRESULT OnTextChange4(DWORD flag, const TS_TEXTCHANGE* pChange) {
    HRESULT result = S_OK;
    EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READWRITE, &result));
    EXPECT_EQ(2, pChange->acpStart);
    EXPECT_EQ(4, pChange->acpOldEnd);
    EXPECT_EQ(4, pChange->acpNewEnd);

    EXPECT_EQ(S_OK, result);
    return S_OK;
  }

  HRESULT LockGranted4a(DWORD flags) {
    GetTextTest(2, 4, L"FG", 4);

    return S_OK;
  }

  HRESULT OnSelectionChange4() {
    HRESULT result = S_OK;
    EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READWRITE, &result));
    EXPECT_EQ(S_OK, result);
    return S_OK;
  }

  HRESULT LockGranted4b(DWORD flags) {
    GetSelectionTest(3, 4);
    return S_OK;
  }

  HRESULT LockGranted5(DWORD flags) {
    SetTextRange(0, 3);
    SetTextBuffer(u"aHI");
    SetSelectionRange(3, 3);
    return S_OK;
  }

  HRESULT OnTextChange5(DWORD flag, const TS_TEXTCHANGE* pChange) {
    HRESULT result = S_OK;
    EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READWRITE, &result));
    EXPECT_EQ(1, pChange->acpStart);
    EXPECT_EQ(5, pChange->acpOldEnd);
    EXPECT_EQ(3, pChange->acpNewEnd);

    EXPECT_EQ(S_OK, result);
    return S_OK;
  }

  HRESULT LockGranted5a(DWORD flags) {
    GetTextTest(1, 3, L"HI", 3);

    return S_OK;
  }

  HRESULT OnSelectionChange5() {
    HRESULT result = S_OK;
    EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READWRITE, &result));
    EXPECT_EQ(S_OK, result);
    return S_OK;
  }

  HRESULT LockGranted5b(DWORD flags) {
    GetSelectionTest(3, 3);
    return S_OK;
  }

  HRESULT LockGranted6(DWORD flags) {
    SetTextRange(0, 5);
    SetTextBuffer(u"JKLMN");
    SetSelectionRange(2, 5);
    return S_OK;
  }

  HRESULT OnTextChange6(DWORD flag, const TS_TEXTCHANGE* pChange) {
    HRESULT result = S_OK;
    EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READWRITE, &result));
    EXPECT_EQ(0, pChange->acpStart);
    EXPECT_EQ(3, pChange->acpOldEnd);
    EXPECT_EQ(5, pChange->acpNewEnd);

    EXPECT_EQ(S_OK, result);
    return S_OK;
  }

  HRESULT LockGranted6a(DWORD flags) {
    GetTextTest(3, 5, L"MN", 5);

    return S_OK;
  }

  HRESULT OnSelectionChange6() {
    HRESULT result = S_OK;
    EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READWRITE, &result));
    EXPECT_EQ(S_OK, result);
    return S_OK;
  }

  HRESULT LockGranted6b(DWORD flags) {
    GetSelectionTest(2, 5);
    return S_OK;
  }

  HRESULT LockGranted7(DWORD flags) {
    SetTextRange(0, 0);
    SetTextBuffer(u"");
    SetSelectionRange(0, 0);
    return S_OK;
  }

  HRESULT OnTextChange7(DWORD flag, const TS_TEXTCHANGE* pChange) {
    HRESULT result = S_OK;
    EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READWRITE, &result));
    EXPECT_EQ(0, pChange->acpStart);
    EXPECT_EQ(5, pChange->acpOldEnd);
    EXPECT_EQ(0, pChange->acpNewEnd);

    EXPECT_EQ(S_OK, result);
    return S_OK;
  }

  HRESULT LockGranted7a(DWORD flags) {
    GetTextTest(0, -1, L"", 0);

    return S_OK;
  }

  HRESULT OnSelectionChange7() {
    HRESULT result = S_OK;
    EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READWRITE, &result));
    EXPECT_EQ(S_OK, result);
    return S_OK;
  }

  HRESULT LockGranted7b(DWORD flags) {
    GetSelectionTest(0, 0);
    return S_OK;
  }

  HRESULT LockGranted8(DWORD flags) {
    SetTextRange(0, 3);
    SetTextBuffer(u"OPQ");
    SetSelectionRange(0, 2);
    return S_OK;
  }

  HRESULT OnTextChange8(DWORD flag, const TS_TEXTCHANGE* pChange) {
    HRESULT result = S_OK;
    EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READWRITE, &result));
    EXPECT_EQ(0, pChange->acpStart);
    EXPECT_EQ(0, pChange->acpOldEnd);
    EXPECT_EQ(3, pChange->acpNewEnd);

    EXPECT_EQ(S_OK, result);
    return S_OK;
  }

  HRESULT LockGranted8a(DWORD flags) {
    GetTextTest(0, -1, L"OPQ", 3);

    return S_OK;
  }

  HRESULT OnSelectionChange8() {
    HRESULT result = S_OK;
    EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READWRITE, &result));
    EXPECT_EQ(S_OK, result);
    return S_OK;
  }

  HRESULT LockGranted8b(DWORD flags) {
    GetSelectionTest(0, 2);
    return S_OK;
  }

  HRESULT LockGranted9(DWORD flags) {
    SetTextRange(0, 3);
    SetTextBuffer(u"OPR");
    SetSelectionRange(2, 3);
    return S_OK;
  }

  HRESULT OnTextChange9(DWORD flag, const TS_TEXTCHANGE* pChange) {
    HRESULT result = S_OK;
    EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READWRITE, &result));
    EXPECT_EQ(2, pChange->acpStart);
    EXPECT_EQ(3, pChange->acpOldEnd);
    EXPECT_EQ(3, pChange->acpNewEnd);

    EXPECT_EQ(S_OK, result);
    return S_OK;
  }

  HRESULT LockGranted9a(DWORD flags) {
    GetTextTest(2, 3, L"R", 3);

    return S_OK;
  }

  HRESULT OnSelectionChange9() {
    HRESULT result = S_OK;
    EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READWRITE, &result));
    EXPECT_EQ(S_OK, result);
    return S_OK;
  }

  HRESULT LockGranted9b(DWORD flags) {
    GetSelectionTest(2, 3);
    return S_OK;
  }

  HRESULT LockGranted10(DWORD flags) {
    SetTextRange(0, 3);
    SetTextBuffer(u"SPR");
    SetSelectionRange(0, 1);
    return S_OK;
  }

  HRESULT OnTextChange10(DWORD flag, const TS_TEXTCHANGE* pChange) {
    HRESULT result = S_OK;
    EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READWRITE, &result));
    EXPECT_EQ(0, pChange->acpStart);
    EXPECT_EQ(1, pChange->acpOldEnd);
    EXPECT_EQ(1, pChange->acpNewEnd);

    EXPECT_EQ(S_OK, result);
    return S_OK;
  }

  HRESULT LockGranted10a(DWORD flags) {
    GetTextTest(0, 1, L"S", 1);

    return S_OK;
  }

  HRESULT OnSelectionChange10() {
    HRESULT result = S_OK;
    EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READWRITE, &result));
    EXPECT_EQ(S_OK, result);
    return S_OK;
  }

  HRESULT LockGranted10b(DWORD flags) {
    GetSelectionTest(0, 1);
    return S_OK;
  }
  // 11. renderer proc changes buffer from "SPR" to "STPR".
  HRESULT LockGranted11(DWORD flags) {
    SetTextRange(0, 4);
    SetTextBuffer(u"STPR");
    SetSelectionRange(2, 2);
    return S_OK;
  }

  HRESULT OnTextChange11(DWORD flag, const TS_TEXTCHANGE* pChange) {
    HRESULT result = S_OK;
    EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READWRITE, &result));
    EXPECT_EQ(1, pChange->acpStart);
    EXPECT_EQ(1, pChange->acpOldEnd);
    EXPECT_EQ(2, pChange->acpNewEnd);

    EXPECT_EQ(S_OK, result);
    return S_OK;
  }

  HRESULT LockGranted11a(DWORD flags) {
    GetTextTest(1, 2, L"T", 2);

    return S_OK;
  }

  HRESULT OnSelectionChange11() {
    HRESULT result = S_OK;
    EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READWRITE, &result));
    EXPECT_EQ(S_OK, result);
    return S_OK;
  }

  HRESULT LockGranted11b(DWORD flags) {
    GetSelectionTest(2, 2);
    return S_OK;
  }

  // 12. renderer proc changes buffer from "STPR" to "PR".
  HRESULT LockGranted12(DWORD flags) {
    SetTextRange(0, 2);
    SetTextBuffer(u"PR");
    SetSelectionRange(0, 0);
    return S_OK;
  }

  HRESULT OnTextChange12(DWORD flag, const TS_TEXTCHANGE* pChange) {
    HRESULT result = S_OK;
    EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READWRITE, &result));
    EXPECT_EQ(0, pChange->acpStart);
    EXPECT_EQ(2, pChange->acpOldEnd);
    EXPECT_EQ(0, pChange->acpNewEnd);

    EXPECT_EQ(S_OK, result);
    return S_OK;
  }

  HRESULT LockGranted12a(DWORD flags) {
    GetTextTest(0, 2, L"PR", 2);

    return S_OK;
  }

  HRESULT OnSelectionChange12() {
    HRESULT result = S_OK;
    EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READWRITE, &result));
    EXPECT_EQ(S_OK, result);
    return S_OK;
  }

  HRESULT LockGranted12b(DWORD flags) {
    GetSelectionTest(0, 0);
    return S_OK;
  }

  // 13. renderer proc changes buffer from "PR" to "UPR".
  HRESULT LockGranted13(DWORD flags) {
    SetTextRange(0, 3);
    SetTextBuffer(u"UPR");
    SetSelectionRange(1, 1);
    return S_OK;
  }

  HRESULT OnTextChange13(DWORD flag, const TS_TEXTCHANGE* pChange) {
    HRESULT result = S_OK;
    EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READWRITE, &result));
    EXPECT_EQ(0, pChange->acpStart);
    EXPECT_EQ(0, pChange->acpOldEnd);
    EXPECT_EQ(1, pChange->acpNewEnd);

    EXPECT_EQ(S_OK, result);
    return S_OK;
  }

  HRESULT LockGranted13a(DWORD flags) {
    GetTextTest(0, 1, L"U", 1);

    return S_OK;
  }

  HRESULT OnSelectionChange13() {
    HRESULT result = S_OK;
    EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READWRITE, &result));
    EXPECT_EQ(S_OK, result);
    return S_OK;
  }

  HRESULT LockGranted13b(DWORD flags) {
    GetSelectionTest(1, 1);
    return S_OK;
  }

  // 14. renderer proc changes buffer from "UPR" to "UPVWR".
  HRESULT LockGranted14(DWORD flags) {
    SetTextRange(0, 5);
    SetTextBuffer(u"UPVWR");
    SetSelectionRange(4, 4);
    return S_OK;
  }

  HRESULT OnTextChange14(DWORD flag, const TS_TEXTCHANGE* pChange) {
    HRESULT result = S_OK;
    EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READWRITE, &result));
    EXPECT_EQ(2, pChange->acpStart);
    EXPECT_EQ(2, pChange->acpOldEnd);
    EXPECT_EQ(4, pChange->acpNewEnd);

    EXPECT_EQ(S_OK, result);
    return S_OK;
  }

  HRESULT LockGranted14a(DWORD flags) {
    GetTextTest(2, 4, L"VW", 4);

    return S_OK;
  }

  HRESULT OnSelectionChange14() {
    HRESULT result = S_OK;
    EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READWRITE, &result));
    EXPECT_EQ(S_OK, result);
    return S_OK;
  }

  HRESULT LockGranted14b(DWORD flags) {
    GetSelectionTest(4, 4);
    return S_OK;
  }
};

TEST_F(TSFTextStoreTest, DiffingAlgorithmTest) {
  EXPECT_CALL(text_input_client_, GetTextInputType())
      .WillRepeatedly(Return(TEXT_INPUT_TYPE_TEXT));
  DiffingAlgorithmTestCallback callback(text_store_.get());

  EXPECT_CALL(*sink_, OnTextChange(_, _))
      .WillOnce(Invoke(&callback, &DiffingAlgorithmTestCallback::OnTextChange1))
      .WillOnce(Invoke(&callback, &DiffingAlgorithmTestCallback::OnTextChange3))
      .WillOnce(Invoke(&callback, &DiffingAlgorithmTestCallback::OnTextChange4))
      .WillOnce(Invoke(&callback, &DiffingAlgorithmTestCallback::OnTextChange5))
      .WillOnce(Invoke(&callback, &DiffingAlgorithmTestCallback::OnTextChange6))
      .WillOnce(Invoke(&callback, &DiffingAlgorithmTestCallback::OnTextChange7))
      .WillOnce(Invoke(&callback, &DiffingAlgorithmTestCallback::OnTextChange8))
      .WillOnce(Invoke(&callback, &DiffingAlgorithmTestCallback::OnTextChange9))
      .WillOnce(
          Invoke(&callback, &DiffingAlgorithmTestCallback::OnTextChange10))
      .WillOnce(
          Invoke(&callback, &DiffingAlgorithmTestCallback::OnTextChange11))
      .WillOnce(
          Invoke(&callback, &DiffingAlgorithmTestCallback::OnTextChange12))
      .WillOnce(
          Invoke(&callback, &DiffingAlgorithmTestCallback::OnTextChange13))
      .WillOnce(
          Invoke(&callback, &DiffingAlgorithmTestCallback::OnTextChange14));

  EXPECT_CALL(*sink_, OnSelectionChange())
      .WillOnce(
          Invoke(&callback, &DiffingAlgorithmTestCallback::OnSelectionChange1))
      .WillOnce(
          Invoke(&callback, &DiffingAlgorithmTestCallback::OnSelectionChange3))
      .WillOnce(
          Invoke(&callback, &DiffingAlgorithmTestCallback::OnSelectionChange4))
      .WillOnce(
          Invoke(&callback, &DiffingAlgorithmTestCallback::OnSelectionChange5))
      .WillOnce(
          Invoke(&callback, &DiffingAlgorithmTestCallback::OnSelectionChange6))
      .WillOnce(
          Invoke(&callback, &DiffingAlgorithmTestCallback::OnSelectionChange7))
      .WillOnce(
          Invoke(&callback, &DiffingAlgorithmTestCallback::OnSelectionChange8))
      .WillOnce(
          Invoke(&callback, &DiffingAlgorithmTestCallback::OnSelectionChange9))
      .WillOnce(
          Invoke(&callback, &DiffingAlgorithmTestCallback::OnSelectionChange10))
      .WillOnce(
          Invoke(&callback, &DiffingAlgorithmTestCallback::OnSelectionChange11))
      .WillOnce(
          Invoke(&callback, &DiffingAlgorithmTestCallback::OnSelectionChange12))
      .WillOnce(
          Invoke(&callback, &DiffingAlgorithmTestCallback::OnSelectionChange13))
      .WillOnce(Invoke(&callback,
                       &DiffingAlgorithmTestCallback::OnSelectionChange14));

  EXPECT_CALL(text_input_client_, InsertText(_, _))
      .WillOnce(Invoke(&callback, &DiffingAlgorithmTestCallback::InsertText2));

  EXPECT_CALL(*sink_, OnLockGranted(_))
      .WillOnce(Invoke(&callback, &DiffingAlgorithmTestCallback::LockGranted1))
      .WillOnce(Invoke(&callback, &DiffingAlgorithmTestCallback::LockGranted1a))
      .WillOnce(Invoke(&callback, &DiffingAlgorithmTestCallback::LockGranted1b))
      .WillOnce(Invoke(&callback, &DiffingAlgorithmTestCallback::LockGranted2))
      .WillOnce(Invoke(&callback, &DiffingAlgorithmTestCallback::LockGranted3))
      .WillOnce(Invoke(&callback, &DiffingAlgorithmTestCallback::LockGranted3a))
      .WillOnce(Invoke(&callback, &DiffingAlgorithmTestCallback::LockGranted3b))
      .WillOnce(Invoke(&callback, &DiffingAlgorithmTestCallback::LockGranted4))
      .WillOnce(Invoke(&callback, &DiffingAlgorithmTestCallback::LockGranted4a))
      .WillOnce(Invoke(&callback, &DiffingAlgorithmTestCallback::LockGranted4b))
      .WillOnce(Invoke(&callback, &DiffingAlgorithmTestCallback::LockGranted5))
      .WillOnce(Invoke(&callback, &DiffingAlgorithmTestCallback::LockGranted5a))
      .WillOnce(Invoke(&callback, &DiffingAlgorithmTestCallback::LockGranted5b))
      .WillOnce(Invoke(&callback, &DiffingAlgorithmTestCallback::LockGranted6))
      .WillOnce(Invoke(&callback, &DiffingAlgorithmTestCallback::LockGranted6a))
      .WillOnce(Invoke(&callback, &DiffingAlgorithmTestCallback::LockGranted6b))
      .WillOnce(Invoke(&callback, &DiffingAlgorithmTestCallback::LockGranted7))
      .WillOnce(Invoke(&callback, &DiffingAlgorithmTestCallback::LockGranted7a))
      .WillOnce(Invoke(&callback, &DiffingAlgorithmTestCallback::LockGranted7b))
      .WillOnce(Invoke(&callback, &DiffingAlgorithmTestCallback::LockGranted8))
      .WillOnce(Invoke(&callback, &DiffingAlgorithmTestCallback::LockGranted8a))
      .WillOnce(Invoke(&callback, &DiffingAlgorithmTestCallback::LockGranted8b))
      .WillOnce(Invoke(&callback, &DiffingAlgorithmTestCallback::LockGranted9))
      .WillOnce(Invoke(&callback, &DiffingAlgorithmTestCallback::LockGranted9a))
      .WillOnce(Invoke(&callback, &DiffingAlgorithmTestCallback::LockGranted9b))
      .WillOnce(Invoke(&callback, &DiffingAlgorithmTestCallback::LockGranted10))
      .WillOnce(
          Invoke(&callback, &DiffingAlgorithmTestCallback::LockGranted10a))
      .WillOnce(
          Invoke(&callback, &DiffingAlgorithmTestCallback::LockGranted10b))
      .WillOnce(Invoke(&callback, &DiffingAlgorithmTestCallback::LockGranted11))
      .WillOnce(
          Invoke(&callback, &DiffingAlgorithmTestCallback::LockGranted11a))
      .WillOnce(
          Invoke(&callback, &DiffingAlgorithmTestCallback::LockGranted11b))
      .WillOnce(Invoke(&callback, &DiffingAlgorithmTestCallback::LockGranted12))
      .WillOnce(
          Invoke(&callback, &DiffingAlgorithmTestCallback::LockGranted12a))
      .WillOnce(
          Invoke(&callback, &DiffingAlgorithmTestCallback::LockGranted12b))
      .WillOnce(Invoke(&callback, &DiffingAlgorithmTestCallback::LockGranted13))
      .WillOnce(
          Invoke(&callback, &DiffingAlgorithmTestCallback::LockGranted13a))
      .WillOnce(
          Invoke(&callback, &DiffingAlgorithmTestCallback::LockGranted13b))
      .WillOnce(Invoke(&callback, &DiffingAlgorithmTestCallback::LockGranted14))
      .WillOnce(
          Invoke(&callback, &DiffingAlgorithmTestCallback::LockGranted14a))
      .WillOnce(
          Invoke(&callback, &DiffingAlgorithmTestCallback::LockGranted14b));

  ON_CALL(text_input_client_, GetCompositionTextRange(_))
      .WillByDefault(Invoke(
          &callback, &TSFTextStoreTestCallback::GetCompositionTextRange));

  ON_CALL(text_input_client_, GetTextRange(_))
      .WillByDefault(
          Invoke(&callback, &TSFTextStoreTestCallback::GetTextRange));

  ON_CALL(text_input_client_, GetTextFromRange(_, _))
      .WillByDefault(
          Invoke(&callback, &TSFTextStoreTestCallback::GetTextFromRange));

  ON_CALL(text_input_client_, GetEditableSelectionRange(_))
      .WillByDefault(Invoke(
          &callback, &TSFTextStoreTestCallback::GetEditableSelectionRange));

  HRESULT result = kInvalidResult;
  EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READWRITE, &result));
  EXPECT_EQ(S_OK, result);
  result = kInvalidResult;
  EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READWRITE, &result));
  EXPECT_EQ(S_OK, result);
  result = kInvalidResult;
  EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READWRITE, &result));
  EXPECT_EQ(S_OK, result);
  result = kInvalidResult;
  EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READWRITE, &result));
  EXPECT_EQ(S_OK, result);
  result = kInvalidResult;
  EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READWRITE, &result));
  EXPECT_EQ(S_OK, result);
  result = kInvalidResult;
  EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READWRITE, &result));
  EXPECT_EQ(S_OK, result);
  result = kInvalidResult;
  EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READWRITE, &result));
  EXPECT_EQ(S_OK, result);
  result = kInvalidResult;
  EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READWRITE, &result));
  EXPECT_EQ(S_OK, result);
  result = kInvalidResult;
  EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READWRITE, &result));
  EXPECT_EQ(S_OK, result);
  result = kInvalidResult;
  EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READWRITE, &result));
  EXPECT_EQ(S_OK, result);
  result = kInvalidResult;
  EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READWRITE, &result));
  EXPECT_EQ(S_OK, result);
  result = kInvalidResult;
  EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READWRITE, &result));
  EXPECT_EQ(S_OK, result);
  result = kInvalidResult;
  EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READWRITE, &result));
  EXPECT_EQ(S_OK, result);
  result = kInvalidResult;
  EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READWRITE, &result));
  EXPECT_EQ(S_OK, result);
}

// regression tests for crbug.com/944452.
// This test covers several corner cases:
// 1. User may commit existing composition and start new composition in the same
//    edit session with same composition text.
// 2. some third-party IMEs use SetText() API instead of InsertTextAtSelection()
//    API to insert new composition text. We should allow IMEs to use both
//    APIs to insert new text.
// 3. Some Japanese IMEs such as CorvusSKK can start and end composition with
//    single key stroke. We should still fire keydown/keyup event for such case.
class RegressionTestCallback : public TSFTextStoreTestCallback {
 public:
  explicit RegressionTestCallback(TSFTextStore* text_store)
      : TSFTextStoreTestCallback(text_store) {}

  RegressionTestCallback(const RegressionTestCallback&) = delete;
  RegressionTestCallback& operator=(const RegressionTestCallback&) = delete;

  HRESULT LockGranted1(DWORD flags) {
    SetTextTest(0, 0, L"a", S_OK);
    GetTextTest(0, -1, L"a", 1);
    SetSelectionTest(0, 1, S_OK);

    text_spans()->clear();
    ImeTextSpan text_span;
    text_span.start_offset = 0;
    text_span.end_offset = 1;
    text_span.underline_color = SK_ColorBLACK;
    text_span.thickness = ImeTextSpan::Thickness::kThin;
    text_span.background_color = SK_ColorTRANSPARENT;
    text_spans()->push_back(text_span);
    *edit_flag() = true;
    *composition_start() = 0;
    composition_range()->set_start(0);
    composition_range()->set_end(1);
    text_store_->OnKeyTraceDown(65u, 1966081u);
    *has_composition_range() = true;

    return S_OK;
  }

  ui::EventDispatchDetails DispatchKeyEventPostIME1(KeyEvent* key) {
    EXPECT_EQ(ui::EventType::kKeyPressed, key->type());
    EXPECT_EQ(VKEY_PROCESSKEY, key->key_code());
    return ui::EventDispatchDetails();
  }

  void SetCompositionText1(const ui::CompositionText& composition) {
    EXPECT_EQ(u"a", composition.text);
    EXPECT_EQ(0u, composition.selection.start());
    EXPECT_EQ(1u, composition.selection.end());
    ASSERT_EQ(1u, composition.ime_text_spans.size());
    EXPECT_EQ(SK_ColorBLACK, composition.ime_text_spans[0].underline_color);
    EXPECT_EQ(SK_ColorTRANSPARENT,
              composition.ime_text_spans[0].background_color);
    EXPECT_EQ(0u, composition.ime_text_spans[0].start_offset);
    EXPECT_EQ(1u, composition.ime_text_spans[0].end_offset);
    EXPECT_EQ(ImeTextSpan::Thickness::kThin,
              composition.ime_text_spans[0].thickness);
    SetHasCompositionText(true);
  }

  // commit existing composition from [0,1] and start new composition at [1,2]
  HRESULT LockGranted2(DWORD flags) {
    SetSelectionTest(1, 1, S_OK);
    InsertTextAtSelectionTest(L"a", 1, 1, 2, 1, 1, 2);
    GetTextTest(0, -1, L"aa", 2);

    text_spans()->clear();
    ImeTextSpan text_span;
    text_span.start_offset = 1;
    text_span.end_offset = 2;
    text_span.underline_color = SK_ColorBLACK;
    text_span.thickness = ImeTextSpan::Thickness::kThick;
    text_spans()->push_back(text_span);

    *edit_flag() = true;
    *composition_start() = 1;
    composition_range()->set_start(1);
    composition_range()->set_end(2);

    text_store_->OnKeyTraceUp(65u, 1966081u);
    text_store_->OnKeyTraceDown(65u, 1966081u);
    return S_OK;
  }

  ui::EventDispatchDetails DispatchKeyEventPostIME2a(KeyEvent* key) {
    EXPECT_EQ(ui::EventType::kKeyReleased, key->type());
    EXPECT_EQ(VKEY_PROCESSKEY, key->key_code());
    return ui::EventDispatchDetails();
  }

  ui::EventDispatchDetails DispatchKeyEventPostIME2b(KeyEvent* key) {
    EXPECT_EQ(ui::EventType::kKeyPressed, key->type());
    EXPECT_EQ(VKEY_PROCESSKEY, key->key_code());
    return ui::EventDispatchDetails();
  }

  void InsertText2(
      const std::u16string& text,
      ui::TextInputClient::InsertTextCursorBehavior cursor_behavior) {
    EXPECT_EQ(u"a", text);
    SetHasCompositionText(false);
  }

  void SetCompositionText2(const ui::CompositionText& composition) {
    EXPECT_EQ(u"a", composition.text);
    EXPECT_EQ(0u, composition.selection.start());
    EXPECT_EQ(1u, composition.selection.end());
    ASSERT_EQ(1u, composition.ime_text_spans.size());
    EXPECT_EQ(SK_ColorBLACK, composition.ime_text_spans[0].underline_color);
    EXPECT_EQ(0u, composition.ime_text_spans[0].start_offset);
    EXPECT_EQ(1u, composition.ime_text_spans[0].end_offset);
    EXPECT_EQ(ImeTextSpan::Thickness::kThick,
              composition.ime_text_spans[0].thickness);
    SetHasCompositionText(true);
  }

  HRESULT LockGranted3(DWORD flags) {
    GetTextTest(0, -1, L"aa", 2);
    SetSelectionTest(2, 2, S_OK);

    text_spans()->clear();
    *edit_flag() = true;
    *composition_start() = 2;
    composition_range()->set_start(0);
    composition_range()->set_end(0);

    *has_composition_range() = false;
    return S_OK;
  }

  void InsertText3(
      const std::u16string& text,
      ui::TextInputClient::InsertTextCursorBehavior cursor_behavior) {
    EXPECT_EQ(u"a", text);
    SetHasCompositionText(false);
  }

  // Insert new composition text using SetText().
  // We also fire key events here.
  HRESULT LockGranted4(DWORD flags) {
    SetTextTest(2, 2, L"b", S_OK);
    GetTextTest(0, -1, L"aab", 3);
    SetTextTest(2, 3, L"c", S_OK);
    SetSelectionTest(3, 3, S_OK);

    *edit_flag() = true;
    *composition_start() = 3;

    text_store_->OnKeyTraceUp(65u, 1966081u);
    text_store_->OnStartComposition(nullptr, nullptr);
    text_store_->OnEndComposition(nullptr);
    return S_OK;
  }

  ui::EventDispatchDetails DispatchKeyEventPostIME4(KeyEvent* key) {
    EXPECT_EQ(ui::EventType::kKeyReleased, key->type());
    EXPECT_EQ(VKEY_PROCESSKEY, key->key_code());
    return ui::EventDispatchDetails();
  }

  // We expect this call since the composition was started and committed during
  // same edit session.
  void SetCompositionText4(const ui::CompositionText& composition) {
    EXPECT_EQ(u"c", composition.text);
    ASSERT_EQ(1u, composition.ime_text_spans.size());
    ASSERT_EQ(gfx::Range(1, 1), composition.selection);
  }

  void InsertText4(
      const std::u16string& text,
      ui::TextInputClient::InsertTextCursorBehavior cursor_behavior) {
    EXPECT_EQ(u"c", text);
  }

  HRESULT LockGranted5(DWORD flags) {
    GetTextTest(0, -1, L"aac", 3);
    SetTextTest(3, 3, L"d", S_OK);
    GetTextTest(0, -1, L"aacd", 4);
    SetSelectionTest(3, 4, S_OK);

    text_spans()->clear();
    ImeTextSpan text_span;
    text_span.start_offset = 3;
    text_span.end_offset = 4;
    text_span.underline_color = SK_ColorBLACK;
    text_span.thickness = ImeTextSpan::Thickness::kThin;
    text_span.background_color = SK_ColorTRANSPARENT;
    text_spans()->push_back(text_span);
    *edit_flag() = true;
    *composition_start() = 3;
    composition_range()->set_start(3);
    composition_range()->set_end(4);

    text_store_->OnKeyTraceDown(65u, 1966081u);

    *has_composition_range() = true;

    return S_OK;
  }

  ui::EventDispatchDetails DispatchKeyEventPostIME5(KeyEvent* key) {
    EXPECT_EQ(ui::EventType::kKeyPressed, key->type());
    EXPECT_EQ(VKEY_PROCESSKEY, key->key_code());
    return ui::EventDispatchDetails();
  }

  void SetCompositionText5(const ui::CompositionText& composition) {
    EXPECT_EQ(u"d", composition.text);
    EXPECT_EQ(0u, composition.selection.start());
    EXPECT_EQ(1u, composition.selection.end());
    ASSERT_EQ(1u, composition.ime_text_spans.size());
    EXPECT_EQ(SK_ColorBLACK, composition.ime_text_spans[0].underline_color);
    EXPECT_EQ(SK_ColorTRANSPARENT,
              composition.ime_text_spans[0].background_color);
    EXPECT_EQ(0u, composition.ime_text_spans[0].start_offset);
    EXPECT_EQ(1u, composition.ime_text_spans[0].end_offset);
    EXPECT_EQ(ImeTextSpan::Thickness::kThin,
              composition.ime_text_spans[0].thickness);
    SetHasCompositionText(true);
  }

  // change existing composition text using SetText().
  HRESULT LockGranted6(DWORD flags) {
    SetTextTest(3, 4, L"e", S_OK);
    SetSelectionTest(4, 4, S_OK);

    text_spans()->clear();
    *edit_flag() = true;
    *composition_start() = 4;
    composition_range()->set_start(0);
    composition_range()->set_end(0);

    *has_composition_range() = false;
    return S_OK;
  }

  void InsertText6(
      const std::u16string& text,
      ui::TextInputClient::InsertTextCursorBehavior cursor_behavior) {
    EXPECT_EQ(u"e", text);
    SetHasCompositionText(false);
  }
};

TEST_F(TSFTextStoreTest, RegressionTest) {
  EXPECT_CALL(text_input_client_, GetTextInputType())
      .WillRepeatedly(Return(TEXT_INPUT_TYPE_TEXT));
  RegressionTestCallback callback(text_store_.get());
  EXPECT_CALL(text_input_client_, SetCompositionText(_))
      .WillOnce(Invoke(&callback, &RegressionTestCallback::SetCompositionText1))
      .WillOnce(Invoke(&callback, &RegressionTestCallback::SetCompositionText2))
      .WillOnce(Invoke(&callback, &RegressionTestCallback::SetCompositionText4))
      .WillOnce(
          Invoke(&callback, &RegressionTestCallback::SetCompositionText5));

  EXPECT_CALL(ime_key_event_dispatcher_, DispatchKeyEventPostIME(_))
      .WillOnce(
          Invoke(&callback, &RegressionTestCallback::DispatchKeyEventPostIME1))
      .WillOnce(
          Invoke(&callback, &RegressionTestCallback::DispatchKeyEventPostIME2a))
      .WillOnce(
          Invoke(&callback, &RegressionTestCallback::DispatchKeyEventPostIME2b))
      .WillOnce(
          Invoke(&callback, &RegressionTestCallback::DispatchKeyEventPostIME4))
      .WillOnce(
          Invoke(&callback, &RegressionTestCallback::DispatchKeyEventPostIME5));

  EXPECT_CALL(text_input_client_, InsertText(_, _))
      .WillOnce(Invoke(&callback, &RegressionTestCallback::InsertText2))
      .WillOnce(Invoke(&callback, &RegressionTestCallback::InsertText3))
      .WillOnce(Invoke(&callback, &RegressionTestCallback::InsertText4))
      .WillOnce(Invoke(&callback, &RegressionTestCallback::InsertText6));

  EXPECT_CALL(*sink_, OnLockGranted(_))
      .WillOnce(Invoke(&callback, &RegressionTestCallback::LockGranted1))
      .WillOnce(Invoke(&callback, &RegressionTestCallback::LockGranted2))
      .WillOnce(Invoke(&callback, &RegressionTestCallback::LockGranted3))
      .WillOnce(Invoke(&callback, &RegressionTestCallback::LockGranted4))
      .WillOnce(Invoke(&callback, &RegressionTestCallback::LockGranted5))
      .WillOnce(Invoke(&callback, &RegressionTestCallback::LockGranted6));

  ON_CALL(text_input_client_, HasCompositionText())
      .WillByDefault(
          Invoke(&callback, &TSFTextStoreTestCallback::HasCompositionText));

  HRESULT result = kInvalidResult;
  EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READWRITE, &result));
  EXPECT_EQ(S_OK, result);
  result = kInvalidResult;
  EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READWRITE, &result));
  EXPECT_EQ(S_OK, result);
  result = kInvalidResult;
  EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READWRITE, &result));
  EXPECT_EQ(S_OK, result);
  result = kInvalidResult;
  EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READWRITE, &result));
  EXPECT_EQ(S_OK, result);
  result = kInvalidResult;
  EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READWRITE, &result));
  EXPECT_EQ(S_OK, result);
  result = kInvalidResult;
  EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READWRITE, &result));
  EXPECT_EQ(S_OK, result);
}

// Some IMEs may replace existing text with new text and start new composition
// on the new text. We should replace old text with new text and start new
// composition. This test covers the above scenario.
class RegressionTest2Callback : public TSFTextStoreTestCallback {
 public:
  explicit RegressionTest2Callback(TSFTextStore* text_store)
      : TSFTextStoreTestCallback(text_store) {}

  RegressionTest2Callback(const RegressionTest2Callback&) = delete;
  RegressionTest2Callback& operator=(const RegressionTest2Callback&) = delete;

  HRESULT LockGranted1(DWORD flags) {
    SetTextTest(0, 0, L"abc", S_OK);
    SetSelectionTest(3, 3, S_OK);

    *composition_start() = 3;
    return S_OK;
  }

  HRESULT LockGranted2(DWORD flags) {
    GetTextTest(0, -1, L"abc", 3);
    SetTextTest(1, 3, L"DE", S_OK);
    GetTextTest(0, -1, L"aDE", 3);
    SetSelectionTest(3, 3, S_OK);

    text_spans()->clear();
    ImeTextSpan text_span;
    text_span.start_offset = 1;
    text_span.end_offset = 3;
    text_span.underline_color = SK_ColorBLACK;
    text_span.thickness = ImeTextSpan::Thickness::kThin;
    text_span.background_color = SK_ColorTRANSPARENT;
    text_spans()->push_back(text_span);
    *edit_flag() = true;
    *composition_start() = 1;
    composition_range()->set_start(1);
    composition_range()->set_end(3);
    text_store_->OnKeyTraceDown(65u, 1966081u);
    *has_composition_range() = true;

    return S_OK;
  }

  void SetCompositionText2(const ui::CompositionText& composition) {
    EXPECT_EQ(u"DE", composition.text);
    EXPECT_EQ(2u, composition.selection.start());
    EXPECT_EQ(2u, composition.selection.end());
    ASSERT_EQ(1u, composition.ime_text_spans.size());
    EXPECT_EQ(SK_ColorBLACK, composition.ime_text_spans[0].underline_color);
    EXPECT_EQ(SK_ColorTRANSPARENT,
              composition.ime_text_spans[0].background_color);
    EXPECT_EQ(0u, composition.ime_text_spans[0].start_offset);
    EXPECT_EQ(2u, composition.ime_text_spans[0].end_offset);
    EXPECT_EQ(ImeTextSpan::Thickness::kThin,
              composition.ime_text_spans[0].thickness);
    SetHasCompositionText(true);
  }

  HRESULT LockGranted3(DWORD flags) {
    GetTextTest(0, -1, L"aDE", 3);

    text_spans()->clear();
    *edit_flag() = true;
    *composition_start() = 3;
    composition_range()->set_start(0);
    composition_range()->set_end(0);

    *has_composition_range() = false;
    return S_OK;
  }

  void InsertText3(
      const std::u16string& text,
      ui::TextInputClient::InsertTextCursorBehavior cursor_behavior) {
    EXPECT_EQ(u"DE", text);
    SetHasCompositionText(false);
  }
};

TEST_F(TSFTextStoreTest, RegressionTest2) {
  EXPECT_CALL(text_input_client_, GetTextInputType())
      .WillRepeatedly(Return(TEXT_INPUT_TYPE_TEXT));
  RegressionTest2Callback callback(text_store_.get());
  EXPECT_CALL(text_input_client_, SetCompositionText(_))
      .WillOnce(
          Invoke(&callback, &RegressionTest2Callback::SetCompositionText2));

  EXPECT_CALL(text_input_client_, InsertText(_, _))
      .WillOnce(Invoke(&callback, &RegressionTest2Callback::InsertText3));

  EXPECT_CALL(*sink_, OnLockGranted(_))
      .WillOnce(Invoke(&callback, &RegressionTest2Callback::LockGranted1))
      .WillOnce(Invoke(&callback, &RegressionTest2Callback::LockGranted2))
      .WillOnce(Invoke(&callback, &RegressionTest2Callback::LockGranted3));

  ON_CALL(text_input_client_, HasCompositionText())
      .WillByDefault(
          Invoke(&callback, &TSFTextStoreTestCallback::HasCompositionText));

  HRESULT result = kInvalidResult;
  EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READWRITE, &result));
  EXPECT_EQ(S_OK, result);
  result = kInvalidResult;
  EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READWRITE, &result));
  EXPECT_EQ(S_OK, result);
  result = kInvalidResult;
  EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READWRITE, &result));
  EXPECT_EQ(S_OK, result);
}

// Due to crbug.com/978678, we should not call TextInputClient::InsertText if
// provided text is empty.
class RegressionTest3Callback : public TSFTextStoreTestCallback {
 public:
  explicit RegressionTest3Callback(TSFTextStore* text_store)
      : TSFTextStoreTestCallback(text_store) {}

  RegressionTest3Callback(const RegressionTest3Callback&) = delete;
  RegressionTest3Callback& operator=(const RegressionTest3Callback&) = delete;

  HRESULT LockGranted1(DWORD flags) {
    GetTextTest(0, -1, L"", 0);
    SetTextTest(0, 0, L"a", S_OK);
    GetTextTest(0, -1, L"a", 1);
    SetSelectionTest(0, 1, S_OK);

    text_spans()->clear();
    ImeTextSpan text_span;
    text_span.start_offset = 0;
    text_span.end_offset = 1;
    text_span.underline_color = SK_ColorBLACK;
    text_span.thickness = ImeTextSpan::Thickness::kThin;
    text_span.background_color = SK_ColorTRANSPARENT;
    text_spans()->push_back(text_span);
    *edit_flag() = true;
    *composition_start() = 0;
    composition_range()->set_start(0);
    composition_range()->set_end(1);
    text_store_->OnKeyTraceDown(65u, 1966081u);
    *has_composition_range() = true;

    return S_OK;
  }

  void SetCompositionText1(const ui::CompositionText& composition) {
    EXPECT_EQ(u"a", composition.text);
    EXPECT_EQ(0u, composition.selection.start());
    EXPECT_EQ(1u, composition.selection.end());
    ASSERT_EQ(1u, composition.ime_text_spans.size());
    EXPECT_EQ(SK_ColorBLACK, composition.ime_text_spans[0].underline_color);
    EXPECT_EQ(SK_ColorTRANSPARENT,
              composition.ime_text_spans[0].background_color);
    EXPECT_EQ(0u, composition.ime_text_spans[0].start_offset);
    EXPECT_EQ(1u, composition.ime_text_spans[0].end_offset);
    EXPECT_EQ(ImeTextSpan::Thickness::kThin,
              composition.ime_text_spans[0].thickness);
    SetHasCompositionText(true);
  }

  HRESULT LockGranted2(DWORD flags) {
    GetTextTest(0, -1, L"a", 1);
    SetTextTest(0, 1, L"", S_OK);
    GetTextTest(0, -1, L"", 0);
    SetSelectionTest(0, 0, S_OK);

    text_spans()->clear();
    *edit_flag() = true;
    *composition_start() = 0;
    composition_range()->set_start(0);
    composition_range()->set_end(0);

    *has_composition_range() = false;
    return S_OK;
  }

  void SetCompositionText2(const ui::CompositionText& composition) {
    EXPECT_EQ(std::u16string(), composition.text);
    EXPECT_EQ(0u, composition.selection.start());
    EXPECT_EQ(0u, composition.selection.end());
    ASSERT_EQ(0u, composition.ime_text_spans.size());
  }
};

TEST_F(TSFTextStoreTest, RegressionTest3) {
  EXPECT_CALL(text_input_client_, GetTextInputType())
      .WillRepeatedly(Return(TEXT_INPUT_TYPE_TEXT));
  RegressionTest3Callback callback(text_store_.get());
  EXPECT_CALL(text_input_client_, SetCompositionText(_))
      .WillOnce(
          Invoke(&callback, &RegressionTest3Callback::SetCompositionText1))
      .WillOnce(
          Invoke(&callback, &RegressionTest3Callback::SetCompositionText2));

  EXPECT_CALL(text_input_client_, InsertText(_, _)).Times(0);

  EXPECT_CALL(*sink_, OnLockGranted(_))
      .WillOnce(Invoke(&callback, &RegressionTest3Callback::LockGranted1))
      .WillOnce(Invoke(&callback, &RegressionTest3Callback::LockGranted2));

  ON_CALL(text_input_client_, HasCompositionText())
      .WillByDefault(
          Invoke(&callback, &TSFTextStoreTestCallback::HasCompositionText));

  HRESULT result = kInvalidResult;
  EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READWRITE, &result));
  EXPECT_EQ(S_OK, result);
  result = kInvalidResult;
  EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READWRITE, &result));
  EXPECT_EQ(S_OK, result);
}

// Due to crbug.com/978678, we should not call TextInputClient::InsertText if
// provided text is empty. In fact, we should call TextInputClient::InsertText
// with current composition text to commit composition without losing text.
class RegressionTest4Callback : public TSFTextStoreTestCallback {
 public:
  explicit RegressionTest4Callback(TSFTextStore* text_store)
      : TSFTextStoreTestCallback(text_store) {}

  RegressionTest4Callback(const RegressionTest4Callback&) = delete;
  RegressionTest4Callback& operator=(const RegressionTest4Callback&) = delete;

  HRESULT LockGranted1(DWORD flags) {
    GetTextTest(0, -1, L"", 0);
    SetTextTest(0, 0, L"a", S_OK);
    GetTextTest(0, -1, L"a", 1);
    SetSelectionTest(0, 1, S_OK);

    text_spans()->clear();
    ImeTextSpan text_span;
    text_span.start_offset = 0;
    text_span.end_offset = 1;
    text_span.underline_color = SK_ColorBLACK;
    text_span.thickness = ImeTextSpan::Thickness::kThin;
    text_span.background_color = SK_ColorTRANSPARENT;
    text_spans()->push_back(text_span);
    *edit_flag() = true;
    *composition_start() = 0;
    composition_range()->set_start(0);
    composition_range()->set_end(1);
    text_store_->OnKeyTraceDown(65u, 1966081u);
    *has_composition_range() = true;

    return S_OK;
  }

  void SetCompositionText1(const ui::CompositionText& composition) {
    EXPECT_EQ(u"a", composition.text);
    EXPECT_EQ(0u, composition.selection.start());
    EXPECT_EQ(1u, composition.selection.end());
    ASSERT_EQ(1u, composition.ime_text_spans.size());
    EXPECT_EQ(SK_ColorBLACK, composition.ime_text_spans[0].underline_color);
    EXPECT_EQ(SK_ColorTRANSPARENT,
              composition.ime_text_spans[0].background_color);
    EXPECT_EQ(0u, composition.ime_text_spans[0].start_offset);
    EXPECT_EQ(1u, composition.ime_text_spans[0].end_offset);
    EXPECT_EQ(ImeTextSpan::Thickness::kThin,
              composition.ime_text_spans[0].thickness);
    SetHasCompositionText(true);
  }

  HRESULT LockGranted2(DWORD flags) {
    GetTextTest(0, -1, L"a", 1);
    SetSelectionTest(1, 1, S_OK);

    text_spans()->clear();
    *edit_flag() = true;
    *composition_start() = 0;
    composition_range()->set_start(0);
    composition_range()->set_end(0);

    *has_composition_range() = false;
    return S_OK;
  }

  void InsertText2(
      const std::u16string& text,
      ui::TextInputClient::InsertTextCursorBehavior cursor_behavior) {
    EXPECT_EQ(u"a", text);
    SetHasCompositionText(false);
  }
};

TEST_F(TSFTextStoreTest, RegressionTest4) {
  EXPECT_CALL(text_input_client_, GetTextInputType())
      .WillRepeatedly(Return(TEXT_INPUT_TYPE_TEXT));
  RegressionTest4Callback callback(text_store_.get());
  EXPECT_CALL(text_input_client_, SetCompositionText(_))
      .WillOnce(
          Invoke(&callback, &RegressionTest4Callback::SetCompositionText1));

  EXPECT_CALL(text_input_client_, InsertText(_, _))
      .WillOnce(Invoke(&callback, &RegressionTest4Callback::InsertText2));

  EXPECT_CALL(*sink_, OnLockGranted(_))
      .WillOnce(Invoke(&callback, &RegressionTest4Callback::LockGranted1))
      .WillOnce(Invoke(&callback, &RegressionTest4Callback::LockGranted2));

  ON_CALL(text_input_client_, HasCompositionText())
      .WillByDefault(
          Invoke(&callback, &TSFTextStoreTestCallback::HasCompositionText));

  HRESULT result = kInvalidResult;
  EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READWRITE, &result));
  EXPECT_EQ(S_OK, result);
  result = kInvalidResult;
  EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READWRITE, &result));
  EXPECT_EQ(S_OK, result);
}

// regression tests for crbug.com/1006067.
// We should call |TextInputClient::SetCompositionText()| if ImeTextSpans are
// changed from previous edit session during same composition even though
// composition string and composition selection remain unchanged.
class RegressionTest5Callback : public TSFTextStoreTestCallback {
 public:
  explicit RegressionTest5Callback(TSFTextStore* text_store)
      : TSFTextStoreTestCallback(text_store) {}

  RegressionTest5Callback(const RegressionTest5Callback&) = delete;
  RegressionTest5Callback& operator=(const RegressionTest5Callback&) = delete;

  HRESULT LockGranted1(DWORD flags) {
    SetTextTest(0, 0, L"aa", S_OK);
    GetTextTest(0, -1, L"aa", 2);
    SetSelectionTest(2, 2, S_OK);

    text_spans()->clear();
    ImeTextSpan text_span_1;
    text_span_1.start_offset = 0;
    text_span_1.end_offset = 1;
    text_span_1.underline_color = SK_ColorBLACK;
    text_span_1.thickness = ImeTextSpan::Thickness::kThick;
    text_span_1.background_color = SK_ColorTRANSPARENT;
    text_spans()->push_back(text_span_1);
    ImeTextSpan text_span_2;
    text_span_2.start_offset = 1;
    text_span_2.end_offset = 2;
    text_span_2.underline_color = SK_ColorBLACK;
    text_span_2.thickness = ImeTextSpan::Thickness::kThin;
    text_span_2.background_color = SK_ColorTRANSPARENT;
    text_spans()->push_back(text_span_2);
    *edit_flag() = true;
    *composition_start() = 0;
    composition_range()->set_start(0);
    composition_range()->set_end(2);
    text_store_->OnKeyTraceDown(65u, 1966081u);
    *has_composition_range() = true;

    return S_OK;
  }

  void SetCompositionText1(const ui::CompositionText& composition) {
    EXPECT_EQ(u"aa", composition.text);
    EXPECT_EQ(2u, composition.selection.start());
    EXPECT_EQ(2u, composition.selection.end());
    ASSERT_EQ(2u, composition.ime_text_spans.size());
    EXPECT_EQ(ImeTextSpan::Thickness::kThick,
              composition.ime_text_spans[0].thickness);
    EXPECT_EQ(ImeTextSpan::Thickness::kThin,
              composition.ime_text_spans[1].thickness);
    SetHasCompositionText(true);
  }

  // Only change underline thickness in IME spans. Other states (composition
  // string, selection) remain unchanged.
  HRESULT LockGranted2(DWORD flags) {
    GetTextTest(0, -1, L"aa", 2);

    text_spans()->clear();
    ImeTextSpan text_span_1;
    text_span_1.start_offset = 0;
    text_span_1.end_offset = 1;
    text_span_1.underline_color = SK_ColorBLACK;
    text_span_1.thickness = ImeTextSpan::Thickness::kThin;
    text_span_1.background_color = SK_ColorTRANSPARENT;
    text_spans()->push_back(text_span_1);
    ImeTextSpan text_span_2;
    text_span_2.start_offset = 1;
    text_span_2.end_offset = 2;
    text_span_2.underline_color = SK_ColorBLACK;
    text_span_2.thickness = ImeTextSpan::Thickness::kThick;
    text_span_2.background_color = SK_ColorTRANSPARENT;
    text_spans()->push_back(text_span_2);

    *edit_flag() = true;
    *composition_start() = 0;
    composition_range()->set_start(0);
    composition_range()->set_end(2);

    text_store_->OnKeyTraceUp(65u, 1966081u);
    text_store_->OnKeyTraceDown(65u, 1966081u);
    return S_OK;
  }

  void SetCompositionText2(const ui::CompositionText& composition) {
    EXPECT_EQ(u"aa", composition.text);
    EXPECT_EQ(2u, composition.selection.start());
    EXPECT_EQ(2u, composition.selection.end());
    ASSERT_EQ(2u, composition.ime_text_spans.size());
    EXPECT_EQ(ImeTextSpan::Thickness::kThin,
              composition.ime_text_spans[0].thickness);
    EXPECT_EQ(ImeTextSpan::Thickness::kThick,
              composition.ime_text_spans[1].thickness);
    SetHasCompositionText(true);
  }

  HRESULT LockGranted3(DWORD flags) {
    GetTextTest(0, -1, L"aa", 2);

    text_spans()->clear();
    *edit_flag() = true;
    *composition_start() = 2;
    composition_range()->set_start(0);
    composition_range()->set_end(0);

    *has_composition_range() = false;
    text_store_->OnKeyTraceUp(65u, 1966081u);
    return S_OK;
  }

  void InsertText3(
      const std::u16string& text,
      ui::TextInputClient::InsertTextCursorBehavior cursor_behavior) {
    EXPECT_EQ(u"aa", text);
    SetHasCompositionText(false);
  }
};

TEST_F(TSFTextStoreTest, RegressionTest5) {
  EXPECT_CALL(text_input_client_, GetTextInputType())
      .WillRepeatedly(Return(TEXT_INPUT_TYPE_TEXT));
  RegressionTest5Callback callback(text_store_.get());
  EXPECT_CALL(text_input_client_, SetCompositionText(_))
      .WillOnce(
          Invoke(&callback, &RegressionTest5Callback::SetCompositionText1))
      .WillOnce(
          Invoke(&callback, &RegressionTest5Callback::SetCompositionText2));

  EXPECT_CALL(text_input_client_, InsertText(_, _))
      .WillOnce(Invoke(&callback, &RegressionTest5Callback::InsertText3));

  EXPECT_CALL(*sink_, OnLockGranted(_))
      .WillOnce(Invoke(&callback, &RegressionTest5Callback::LockGranted1))
      .WillOnce(Invoke(&callback, &RegressionTest5Callback::LockGranted2))
      .WillOnce(Invoke(&callback, &RegressionTest5Callback::LockGranted3));

  ON_CALL(text_input_client_, HasCompositionText())
      .WillByDefault(
          Invoke(&callback, &TSFTextStoreTestCallback::HasCompositionText));

  HRESULT result = kInvalidResult;
  EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READWRITE, &result));
  EXPECT_EQ(S_OK, result);
  result = kInvalidResult;
  EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READWRITE, &result));
  EXPECT_EQ(S_OK, result);
  result = kInvalidResult;
  EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READWRITE, &result));
  EXPECT_EQ(S_OK, result);
}

// regression tests for crbug.com/1013472.
// We should reset |new_text_inserted_| at the end of
// |TSFTextStore::RequestLock| since the text should have been already
// inserted/replaced.
class RegressionTest6Callback : public TSFTextStoreTestCallback {
 public:
  explicit RegressionTest6Callback(TSFTextStore* text_store)
      : TSFTextStoreTestCallback(text_store) {}

  RegressionTest6Callback(const RegressionTest6Callback&) = delete;
  RegressionTest6Callback& operator=(const RegressionTest6Callback&) = delete;

  HRESULT LockGranted1(DWORD flags) {
    EXPECT_EQ(false, *new_text_inserted());
    SetTextTest(0, 0, L"a", S_OK);
    GetTextTest(0, -1, L"a", 1);
    SetSelectionTest(1, 1, S_OK);

    text_spans()->clear();
    ImeTextSpan text_span_1;
    text_span_1.start_offset = 0;
    text_span_1.end_offset = 2;
    text_span_1.underline_color = SK_ColorBLACK;
    text_span_1.thickness = ImeTextSpan::Thickness::kThick;
    text_span_1.background_color = SK_ColorTRANSPARENT;
    text_spans()->push_back(text_span_1);
    *edit_flag() = true;
    *composition_start() = 0;
    composition_range()->set_start(0);
    composition_range()->set_end(2);
    text_store_->OnKeyTraceDown(65u, 1966081u);
    *has_composition_range() = true;

    return S_OK;
  }

  void SetCompositionText1(const ui::CompositionText& composition) {
    EXPECT_EQ(u"a", composition.text);
    EXPECT_EQ(1u, composition.selection.start());
    EXPECT_EQ(1u, composition.selection.end());
    ASSERT_EQ(1u, composition.ime_text_spans.size());
    EXPECT_EQ(true, *new_text_inserted());
    SetHasCompositionText(true);
  }

  HRESULT LockGranted2(DWORD flags) {
    EXPECT_EQ(false, *new_text_inserted());
    GetTextTest(0, -1, L"a", 1);

    text_spans()->clear();
    *edit_flag() = true;
    *composition_start() = 1;
    composition_range()->set_start(0);
    composition_range()->set_end(0);

    *has_composition_range() = false;
    text_store_->OnKeyTraceUp(65u, 1966081u);
    return S_OK;
  }

  void InsertText2(
      const std::u16string& text,
      ui::TextInputClient::InsertTextCursorBehavior cursor_behavior) {
    EXPECT_EQ(u"a", text);
    SetHasCompositionText(false);
  }
};

TEST_F(TSFTextStoreTest, RegressionTest6) {
  EXPECT_CALL(text_input_client_, GetTextInputType())
      .WillRepeatedly(Return(TEXT_INPUT_TYPE_TEXT));
  RegressionTest6Callback callback(text_store_.get());
  EXPECT_CALL(text_input_client_, SetCompositionText(_))
      .WillOnce(
          Invoke(&callback, &RegressionTest6Callback::SetCompositionText1));

  EXPECT_CALL(text_input_client_, InsertText(_, _))
      .WillOnce(Invoke(&callback, &RegressionTest6Callback::InsertText2));

  EXPECT_CALL(*sink_, OnLockGranted(_))
      .WillOnce(Invoke(&callback, &RegressionTest6Callback::LockGranted1))
      .WillOnce(Invoke(&callback, &RegressionTest6Callback::LockGranted2));

  ON_CALL(text_input_client_, HasCompositionText())
      .WillByDefault(
          Invoke(&callback, &TSFTextStoreTestCallback::HasCompositionText));

  HRESULT result = kInvalidResult;
  EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READWRITE, &result));
  EXPECT_EQ(S_OK, result);
  result = kInvalidResult;
  EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READWRITE, &result));
  EXPECT_EQ(S_OK, result);
}

class UnderlineStyleTestCallback : public TSFTextStoreTestCallback {
 public:
  explicit UnderlineStyleTestCallback(TSFTextStore* text_store)
      : TSFTextStoreTestCallback(text_store) {}

  UnderlineStyleTestCallback(const UnderlineStyleTestCallback&) = delete;
  UnderlineStyleTestCallback& operator=(const UnderlineStyleTestCallback&) =
      delete;

  HRESULT LockGranted1(DWORD flags) {
    SetTextTest(0, 0, L"a", S_OK);

    GetTextTest(0, -1, L"a", 1);

    SetSelectionTest(0, 1, S_OK);

    text_spans()->clear();
    ImeTextSpan text_span;
    text_span.start_offset = 0;
    text_span.end_offset = 1;
    text_span.underline_color = SK_ColorBLACK;
    text_span.thickness = ImeTextSpan::Thickness::kThin;
    text_span.underline_style = ImeTextSpan::UnderlineStyle::kSolid;
    text_span.background_color = SK_ColorTRANSPARENT;
    text_spans()->push_back(text_span);
    *edit_flag() = true;
    *composition_start() = 0;
    composition_range()->set_start(0);
    composition_range()->set_end(1);
    *has_composition_range() = true;

    return S_OK;
  }

  void SetCompositionText1(const ui::CompositionText& composition) {
    EXPECT_EQ(u"a", composition.text);
    EXPECT_EQ(0u, composition.selection.start());
    EXPECT_EQ(1u, composition.selection.end());
    ASSERT_EQ(1u, composition.ime_text_spans.size());
    EXPECT_EQ(SK_ColorBLACK, composition.ime_text_spans[0].underline_color);
    EXPECT_EQ(SK_ColorTRANSPARENT,
              composition.ime_text_spans[0].background_color);
    EXPECT_EQ(0u, composition.ime_text_spans[0].start_offset);
    EXPECT_EQ(1u, composition.ime_text_spans[0].end_offset);
    EXPECT_EQ(ImeTextSpan::Thickness::kThin,
              composition.ime_text_spans[0].thickness);
    EXPECT_EQ(ImeTextSpan::UnderlineStyle::kSolid,
              composition.ime_text_spans[0].underline_style);
    SetHasCompositionText(true);
  }
};

TEST_F(TSFTextStoreTest, UnderlineStyleTest) {
  EXPECT_CALL(text_input_client_, GetTextInputType())
      .WillRepeatedly(Return(TEXT_INPUT_TYPE_TEXT));
  UnderlineStyleTestCallback callback(text_store_.get());
  EXPECT_CALL(text_input_client_, SetCompositionText(_))
      .WillOnce(
          Invoke(&callback, &UnderlineStyleTestCallback::SetCompositionText1));

  EXPECT_CALL(*sink_, OnLockGranted(_))
      .WillOnce(Invoke(&callback, &UnderlineStyleTestCallback::LockGranted1));

  ON_CALL(text_input_client_, HasCompositionText())
      .WillByDefault(
          Invoke(&callback, &TSFTextStoreTestCallback::HasCompositionText));

  HRESULT result = kInvalidResult;
  EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READWRITE, &result));
  EXPECT_EQ(S_OK, result);
}

// regression tests for crbug.com/1013154.
// We should remove selected text before start composition on existing text.
class RegressionTest7Callback : public TSFTextStoreTestCallback {
 public:
  explicit RegressionTest7Callback(TSFTextStore* text_store)
      : TSFTextStoreTestCallback(text_store) {}

  RegressionTest7Callback(const RegressionTest7Callback&) = delete;
  RegressionTest7Callback& operator=(const RegressionTest7Callback&) = delete;

  HRESULT LockGranted1(DWORD flags) {
    SetTextTest(0, 0, L"aaaa", S_OK);
    SetSelectionTest(0, 4, S_OK);
    return S_OK;
  }

  HRESULT LockGranted2(DWORD flags) {
    GetTextTest(0, -1, L"aaaa", 4);
    SetTextTest(1, 4, L"", S_OK);
    SetSelectionTest(1, 1, S_OK);

    text_spans()->clear();
    ImeTextSpan text_span_1;
    text_span_1.start_offset = 0;
    text_span_1.end_offset = 1;
    text_span_1.underline_color = SK_ColorBLACK;
    text_span_1.thickness = ImeTextSpan::Thickness::kThin;
    text_span_1.background_color = SK_ColorTRANSPARENT;
    text_spans()->push_back(text_span_1);

    *edit_flag() = true;
    *composition_start() = 0;
    composition_range()->set_start(0);
    composition_range()->set_end(1);
    *has_composition_range() = true;

    text_store_->OnKeyTraceDown(65u, 1966081u);
    text_store_->OnStartComposition(nullptr, nullptr);
    return S_OK;
  }

  bool SetCompositionFromExistingText2(
      const gfx::Range& range,
      const std::vector<ui::ImeTextSpan>& text_spans) {
    EXPECT_EQ(0u, range.start());
    EXPECT_EQ(1u, range.end());
    EXPECT_EQ(1u, text_spans.size());
    SetHasCompositionText(true);
    return true;
  }

  HRESULT LockGranted3(DWORD flags) {
    GetTextTest(0, -1, L"a", 1);

    text_spans()->clear();
    *edit_flag() = true;
    *composition_start() = 1;
    composition_range()->set_start(0);
    composition_range()->set_end(0);

    *has_composition_range() = false;
    text_store_->OnKeyTraceUp(65u, 1966081u);
    return S_OK;
  }

  void InsertText3(
      const std::u16string& text,
      ui::TextInputClient::InsertTextCursorBehavior cursor_behavior) {
    EXPECT_EQ(u"a", text);
    SetHasCompositionText(false);
  }
};

TEST_F(TSFTextStoreTest, RegressionTest7) {
  EXPECT_CALL(text_input_client_, GetTextInputType())
      .WillRepeatedly(Return(TEXT_INPUT_TYPE_TEXT));
  RegressionTest7Callback callback(text_store_.get());
  EXPECT_CALL(text_input_client_, SetCompositionFromExistingText(_, _))
      .WillOnce(
          Invoke(&callback,
                 &RegressionTest7Callback::SetCompositionFromExistingText2));

  EXPECT_CALL(text_input_client_, InsertText(_, _))
      .WillOnce(Invoke(&callback, &RegressionTest7Callback::InsertText3));

  EXPECT_CALL(*sink_, OnLockGranted(_))
      .WillOnce(Invoke(&callback, &RegressionTest7Callback::LockGranted1))
      .WillOnce(Invoke(&callback, &RegressionTest7Callback::LockGranted2))
      .WillOnce(Invoke(&callback, &RegressionTest7Callback::LockGranted3));

  ON_CALL(text_input_client_, HasCompositionText())
      .WillByDefault(
          Invoke(&callback, &TSFTextStoreTestCallback::HasCompositionText));

  HRESULT result = kInvalidResult;
  EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READWRITE, &result));
  EXPECT_EQ(S_OK, result);
  result = kInvalidResult;
  EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READWRITE, &result));
  EXPECT_EQ(S_OK, result);
  result = kInvalidResult;
  EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READWRITE, &result));
  EXPECT_EQ(S_OK, result);
}

// regression tests for crbug.com/1091069.
// We should allow inserting empty compositon string to cancel composition.
class RegressionTest8Callback : public TSFTextStoreTestCallback {
 public:
  explicit RegressionTest8Callback(TSFTextStore* text_store)
      : TSFTextStoreTestCallback(text_store) {}

  RegressionTest8Callback(const RegressionTest8Callback&) = delete;
  RegressionTest8Callback& operator=(const RegressionTest8Callback&) = delete;

  HRESULT LockGranted1(DWORD flags) {
    SetTextTest(0, 0, L"bbbb", S_OK);
    SetSelectionTest(0, 4, S_OK);

    text_spans()->clear();
    ImeTextSpan text_span_1;
    text_span_1.start_offset = 0;
    text_span_1.end_offset = 4;
    text_span_1.underline_color = SK_ColorBLACK;
    text_span_1.thickness = ImeTextSpan::Thickness::kThin;
    text_span_1.background_color = SK_ColorTRANSPARENT;
    text_spans()->push_back(text_span_1);

    *edit_flag() = true;
    *composition_start() = 0;
    composition_range()->set_start(0);
    composition_range()->set_end(4);
    *has_composition_range() = true;

    text_store_->OnKeyTraceDown(65u, 1966081u);
    return S_OK;
  }

  void SetCompositionText1(const ui::CompositionText& composition) {
    EXPECT_EQ(u"bbbb", composition.text);
    EXPECT_EQ(0u, composition.selection.start());
    EXPECT_EQ(4u, composition.selection.end());
    ASSERT_EQ(1u, composition.ime_text_spans.size());
    EXPECT_EQ(0u, composition.ime_text_spans[0].start_offset);
    EXPECT_EQ(4u, composition.ime_text_spans[0].end_offset);
    SetHasCompositionText(true);
  }

  HRESULT LockGranted2(DWORD flags) {
    GetTextTest(0, -1, L"bbbb", 4);
    SetTextTest(0, 4, L"", S_OK);

    text_spans()->clear();
    *edit_flag() = true;
    *composition_start() = 0;
    composition_range()->set_start(0);
    composition_range()->set_end(0);

    *has_composition_range() = false;
    text_store_->OnKeyTraceUp(65u, 1966081u);
    return S_OK;
  }

  void ClearCompositionText2() { EXPECT_EQ(false, *has_composition_range()); }
};

TEST_F(TSFTextStoreTest, RegressionTest8) {
  EXPECT_CALL(text_input_client_, GetTextInputType())
      .WillRepeatedly(Return(TEXT_INPUT_TYPE_TEXT));
  RegressionTest8Callback callback(text_store_.get());
  EXPECT_CALL(text_input_client_, SetCompositionText(_))
      .WillOnce(
          Invoke(&callback, &RegressionTest8Callback::SetCompositionText1));

  EXPECT_CALL(text_input_client_, ClearCompositionText())
      .WillOnce(
          Invoke(&callback, &RegressionTest8Callback::ClearCompositionText2));

  EXPECT_CALL(*sink_, OnLockGranted(_))
      .WillOnce(Invoke(&callback, &RegressionTest8Callback::LockGranted1))
      .WillOnce(Invoke(&callback, &RegressionTest8Callback::LockGranted2));

  ON_CALL(text_input_client_, HasCompositionText())
      .WillByDefault(
          Invoke(&callback, &TSFTextStoreTestCallback::HasCompositionText));

  HRESULT result = kInvalidResult;
  EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READWRITE, &result));
  EXPECT_EQ(S_OK, result);
  result = kInvalidResult;
  EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READWRITE, &result));
  EXPECT_EQ(S_OK, result);
}

// We should use the last composition end pos instead of the cached
// |coposition_start_| to calculate last composition.
class RegressionTest9Callback : public TSFTextStoreTestCallback {
 public:
  explicit RegressionTest9Callback(TSFTextStore* text_store)
      : TSFTextStoreTestCallback(text_store) {}

  RegressionTest9Callback(const RegressionTest9Callback&) = delete;
  RegressionTest9Callback& operator=(const RegressionTest9Callback&) = delete;

  HRESULT LockGranted1(DWORD flags) {
    SetTextTest(0, 0, L"a", S_OK);
    SetSelectionTest(1, 1, S_OK);
    *composition_start() = 1;
    return S_OK;
  }

  HRESULT LockGranted2(DWORD flags) {
    GetTextTest(0, -1, L"a", 1);
    SetTextTest(1, 1, L"bbbb", S_OK);
    SetSelectionTest(1, 5, S_OK);

    text_spans()->clear();
    ImeTextSpan text_span_1;
    text_span_1.start_offset = 0;
    text_span_1.end_offset = 4;
    text_span_1.underline_color = SK_ColorBLACK;
    text_span_1.thickness = ImeTextSpan::Thickness::kThin;
    text_span_1.background_color = SK_ColorTRANSPARENT;
    text_spans()->push_back(text_span_1);

    *edit_flag() = true;
    composition_range()->set_start(1);
    composition_range()->set_end(5);
    *has_composition_range() = true;

    text_store_->OnKeyTraceDown(65u, 1966081u);
    return S_OK;
  }

  void SetCompositionText2(const ui::CompositionText& composition) {
    EXPECT_EQ(u"bbbb", composition.text);
    EXPECT_EQ(0u, composition.selection.start());
    EXPECT_EQ(4u, composition.selection.end());
    ASSERT_EQ(1u, composition.ime_text_spans.size());
    *has_composition_range() = true;
    has_composition_text_ = true;
  }

  HRESULT LockGranted3(DWORD flags) {
    GetTextTest(0, -1, L"abbbb", 5);
    SetTextTest(1, 3, L"bb", S_OK);
    SetTextTest(3, 5, L"cc", S_OK);

    text_spans()->clear();
    ImeTextSpan text_span_1;
    text_span_1.start_offset = 0;
    text_span_1.end_offset = 4;
    text_span_1.underline_color = SK_ColorBLACK;
    text_span_1.thickness = ImeTextSpan::Thickness::kThin;
    text_span_1.background_color = SK_ColorTRANSPARENT;
    text_spans()->push_back(text_span_1);

    *edit_flag() = true;
    composition_range()->set_start(1);
    composition_range()->set_end(5);
    *has_composition_range() = true;

    text_store_->OnKeyTraceDown(65u, 1966081u);
    return S_OK;
  }

  void SetCompositionText3(const ui::CompositionText& composition) {
    EXPECT_EQ(u"bbcc", composition.text);
    EXPECT_EQ(2u, composition.selection.start());
    EXPECT_EQ(4u, composition.selection.end());
    ASSERT_EQ(1u, composition.ime_text_spans.size());
    *has_composition_range() = true;
    has_composition_text_ = true;
  }

  HRESULT LockGranted4(DWORD flags) {
    GetTextTest(0, -1, L"abbcc", 5);
    SetTextTest(3, 5, L"cc", S_OK);
    SetSelectionTest(3, 5, S_OK);

    text_spans()->clear();
    *edit_flag() = true;
    composition_range()->set_start(0);
    composition_range()->set_end(0);

    *has_composition_range() = false;
    *composition_start() = 3;
    text_store_->OnKeyTraceUp(65u, 1966081u);
    return S_OK;
  }

  void InsertText4(
      const std::u16string& text,
      ui::TextInputClient::InsertTextCursorBehavior cursor_behavior) {
    EXPECT_EQ(u"bbcc", text);
    SetHasCompositionText(false);
  }

  HRESULT LockGranted5(DWORD flags) {
    GetTextTest(0, -1, L"abbcc", 5);
    return S_OK;
  }
};

TEST_F(TSFTextStoreTest, RegressionTest9) {
  EXPECT_CALL(text_input_client_, GetTextInputType())
      .WillRepeatedly(Return(TEXT_INPUT_TYPE_TEXT));
  RegressionTest9Callback callback(text_store_.get());
  EXPECT_CALL(text_input_client_, SetCompositionText(_))
      .WillOnce(
          Invoke(&callback, &RegressionTest9Callback::SetCompositionText2))
      .WillOnce(
          Invoke(&callback, &RegressionTest9Callback::SetCompositionText3));

  EXPECT_CALL(text_input_client_, InsertText(_, _))
      .WillOnce(Invoke(&callback, &RegressionTest9Callback::InsertText4));

  EXPECT_CALL(*sink_, OnLockGranted(_))
      .WillOnce(Invoke(&callback, &RegressionTest9Callback::LockGranted1))
      .WillOnce(Invoke(&callback, &RegressionTest9Callback::LockGranted2))
      .WillOnce(Invoke(&callback, &RegressionTest9Callback::LockGranted3))
      .WillOnce(Invoke(&callback, &RegressionTest9Callback::LockGranted4))
      .WillOnce(Invoke(&callback, &RegressionTest9Callback::LockGranted5));

  ON_CALL(text_input_client_, HasCompositionText())
      .WillByDefault(
          Invoke(&callback, &TSFTextStoreTestCallback::HasCompositionText));

  HRESULT result = kInvalidResult;
  EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READWRITE, &result));
  EXPECT_EQ(S_OK, result);
  result = kInvalidResult;
  EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READWRITE, &result));
  EXPECT_EQ(S_OK, result);
  result = kInvalidResult;
  EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READWRITE, &result));
  EXPECT_EQ(S_OK, result);
  result = kInvalidResult;
  EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READWRITE, &result));
  EXPECT_EQ(S_OK, result);
  result = kInvalidResult;
  EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READWRITE, &result));
  EXPECT_EQ(S_OK, result);
}

// |ConfirmComposition| should set selection to the end of compositiont text.
class RegressionTest10Callback : public TSFTextStoreTestCallback {
 public:
  explicit RegressionTest10Callback(TSFTextStore* text_store)
      : TSFTextStoreTestCallback(text_store) {}

  RegressionTest10Callback(const RegressionTest10Callback&) = delete;
  RegressionTest10Callback& operator=(const RegressionTest10Callback&) = delete;

  HRESULT LockGranted1(DWORD flags) {
    SetTextTest(0, 0, L"abcd", S_OK);
    SetSelectionTest(0, 4, S_OK);

    text_spans()->clear();
    ImeTextSpan text_span;
    text_span.start_offset = 0;
    text_span.end_offset = 4;
    text_span.underline_color = SK_ColorBLACK;
    text_span.thickness = ImeTextSpan::Thickness::kThin;
    text_span.background_color = SK_ColorTRANSPARENT;
    text_spans()->push_back(text_span);
    *edit_flag() = true;
    *composition_start() = 0;
    composition_range()->set_start(0);
    composition_range()->set_end(4);
    text_store_->OnKeyTraceDown(65u, 1966081u);
    *has_composition_range() = true;

    return S_OK;
  }

  void SetCompositionText1(const ui::CompositionText& composition) {
    EXPECT_EQ(u"abcd", composition.text);
    EXPECT_EQ(0u, composition.selection.start());
    EXPECT_EQ(4u, composition.selection.end());
    ASSERT_EQ(1u, composition.ime_text_spans.size());
    EXPECT_EQ(0u, composition.ime_text_spans[0].start_offset);
    EXPECT_EQ(4u, composition.ime_text_spans[0].end_offset);
    SetHasCompositionText(true);
    SetCompositionTextRange(0, 4);
  }

  HRESULT LockGranted2(DWORD flags) {
    SetSelectionTest(2, 2, S_OK);
    GetTextTest(0, -1, L"abcd", 4);

    text_spans()->clear();
    ImeTextSpan text_span_1;
    text_span_1.start_offset = 0;
    text_span_1.end_offset = 2;
    text_span_1.underline_color = SK_ColorBLACK;
    text_span_1.thickness = ImeTextSpan::Thickness::kThick;
    text_span_1.background_color = SK_ColorTRANSPARENT;
    text_spans()->push_back(text_span_1);
    ImeTextSpan text_span_2;
    text_span_2.start_offset = 2;
    text_span_2.end_offset = 4;
    text_span_2.underline_color = SK_ColorBLACK;
    text_span_2.thickness = ImeTextSpan::Thickness::kThin;
    text_span_2.background_color = SK_ColorTRANSPARENT;
    text_spans()->push_back(text_span_2);
    *edit_flag() = true;
    *composition_start() = 0;
    composition_range()->set_start(0);
    composition_range()->set_end(4);
    text_store_->OnKeyTraceDown(65u, 1966081u);
    *has_composition_range() = true;

    text_store_->OnKeyTraceUp(65u, 1966081u);
    text_store_->OnKeyTraceDown(65u, 1966081u);
    return S_OK;
  }

  void SetCompositionText2(const ui::CompositionText& composition) {
    EXPECT_EQ(u"abcd", composition.text);
    EXPECT_EQ(2u, composition.selection.start());
    EXPECT_EQ(2u, composition.selection.end());
    ASSERT_EQ(2u, composition.ime_text_spans.size());
    EXPECT_EQ(0u, composition.ime_text_spans[0].start_offset);
    EXPECT_EQ(2u, composition.ime_text_spans[0].end_offset);
    EXPECT_EQ(2u, composition.ime_text_spans[1].start_offset);
    EXPECT_EQ(4u, composition.ime_text_spans[1].end_offset);
    SetHasCompositionText(true);
  }

  HRESULT LockGranted3(DWORD flags) {
    GetTextTest(0, -1, L"abcd", 4);
    GetSelectionTest(2, 2);
    *edit_flag() = false;
    return S_OK;
  }

  HRESULT LockGranted4(DWORD flags) {
    GetTextTest(0, -1, L"abcd", 4);
    GetSelectionTest(4, 4);
    return S_OK;
  }
};

TEST_F(TSFTextStoreTest, RegressionTest10) {
  EXPECT_CALL(text_input_client_, GetTextInputType())
      .WillRepeatedly(Return(TEXT_INPUT_TYPE_TEXT));
  RegressionTest10Callback callback(text_store_.get());
  EXPECT_CALL(text_input_client_, SetCompositionText(_))
      .WillOnce(
          Invoke(&callback, &RegressionTest10Callback::SetCompositionText1))
      .WillOnce(
          Invoke(&callback, &RegressionTest10Callback::SetCompositionText2));

  EXPECT_CALL(*sink_, OnLockGranted(_))
      .WillOnce(Invoke(&callback, &RegressionTest10Callback::LockGranted1))
      .WillOnce(Invoke(&callback, &RegressionTest10Callback::LockGranted2))
      .WillOnce(Invoke(&callback, &RegressionTest10Callback::LockGranted3))
      .WillOnce(Invoke(&callback, &RegressionTest10Callback::LockGranted4));

  ON_CALL(text_input_client_, GetCompositionTextRange(_))
      .WillByDefault(Invoke(
          &callback, &TSFTextStoreTestCallback::GetCompositionTextRange));

  ON_CALL(text_input_client_, GetTextRange(_))
      .WillByDefault(
          Invoke(&callback, &TSFTextStoreTestCallback::GetTextRange));

  ON_CALL(text_input_client_, GetTextFromRange(_, _))
      .WillByDefault(
          Invoke(&callback, &TSFTextStoreTestCallback::GetTextFromRange));

  ON_CALL(text_input_client_, GetEditableSelectionRange(_))
      .WillByDefault(Invoke(
          &callback, &TSFTextStoreTestCallback::GetEditableSelectionRange));

  ON_CALL(text_input_client_, HasCompositionText())
      .WillByDefault(
          Invoke(&callback, &TSFTextStoreTestCallback::HasCompositionText));

  HRESULT result = kInvalidResult;
  EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READWRITE, &result));
  EXPECT_EQ(S_OK, result);
  result = kInvalidResult;
  EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READWRITE, &result));
  EXPECT_EQ(S_OK, result);
  result = kInvalidResult;
  EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READWRITE, &result));
  EXPECT_EQ(S_OK, result);

  text_store_->ConfirmComposition();

  result = kInvalidResult;
  EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READWRITE, &result));
  EXPECT_EQ(S_OK, result);
}

// |CancelComposition| should reset all tracking composition state.
class RegressionTest11Callback : public TSFTextStoreTestCallback {
 public:
  explicit RegressionTest11Callback(TSFTextStore* text_store)
      : TSFTextStoreTestCallback(text_store) {}

  RegressionTest11Callback(const RegressionTest11Callback&) = delete;
  RegressionTest11Callback& operator=(const RegressionTest11Callback&) = delete;

  HRESULT LockGranted1(DWORD flags) {
    SetTextTest(0, 0, L"abcd", S_OK);
    SetSelectionTest(0, 4, S_OK);

    text_spans()->clear();
    ImeTextSpan text_span;
    text_span.start_offset = 0;
    text_span.end_offset = 4;
    text_span.underline_color = SK_ColorBLACK;
    text_span.thickness = ImeTextSpan::Thickness::kThin;
    text_span.background_color = SK_ColorTRANSPARENT;
    text_spans()->push_back(text_span);
    *edit_flag() = true;
    *composition_start() = 0;
    composition_range()->set_start(0);
    composition_range()->set_end(4);
    text_store_->OnKeyTraceDown(65u, 1966081u);
    *has_composition_range() = true;

    return S_OK;
  }

  void SetCompositionText(const ui::CompositionText& composition) {
    EXPECT_EQ(u"abcd", composition.text);
    EXPECT_EQ(0u, composition.selection.start());
    EXPECT_EQ(4u, composition.selection.end());
    ASSERT_EQ(1u, composition.ime_text_spans.size());
    EXPECT_EQ(0u, composition.ime_text_spans[0].start_offset);
    EXPECT_EQ(4u, composition.ime_text_spans[0].end_offset);
    SetHasCompositionText(true);
    SetCompositionTextRange(0, 4);
  }

  HRESULT LockGranted2(DWORD flags) {
    *edit_flag() = false;
    return S_OK;
  }

  HRESULT LockGranted3(DWORD flags) {
    ResetCompositionStateTest();
    return S_OK;
  }
};

TEST_F(TSFTextStoreTest, RegressionTest11) {
  EXPECT_CALL(text_input_client_, GetTextInputType())
      .WillRepeatedly(Return(TEXT_INPUT_TYPE_TEXT));
  RegressionTest11Callback callback(text_store_.get());
  EXPECT_CALL(text_input_client_, SetCompositionText(_))
      .WillOnce(
          Invoke(&callback, &RegressionTest11Callback::SetCompositionText));

  EXPECT_CALL(*sink_, OnLockGranted(_))
      .WillOnce(Invoke(&callback, &RegressionTest11Callback::LockGranted1))
      .WillOnce(Invoke(&callback, &RegressionTest11Callback::LockGranted2))
      .WillOnce(Invoke(&callback, &RegressionTest11Callback::LockGranted3));

  ON_CALL(text_input_client_, GetCompositionTextRange(_))
      .WillByDefault(Invoke(
          &callback, &TSFTextStoreTestCallback::GetCompositionTextRange));

  ON_CALL(text_input_client_, GetTextRange(_))
      .WillByDefault(
          Invoke(&callback, &TSFTextStoreTestCallback::GetTextRange));

  ON_CALL(text_input_client_, GetTextFromRange(_, _))
      .WillByDefault(
          Invoke(&callback, &TSFTextStoreTestCallback::GetTextFromRange));

  ON_CALL(text_input_client_, GetEditableSelectionRange(_))
      .WillByDefault(Invoke(
          &callback, &TSFTextStoreTestCallback::GetEditableSelectionRange));

  ON_CALL(text_input_client_, HasCompositionText())
      .WillByDefault(
          Invoke(&callback, &TSFTextStoreTestCallback::HasCompositionText));

  HRESULT result = kInvalidResult;
  EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READWRITE, &result));
  EXPECT_EQ(S_OK, result);

  result = kInvalidResult;
  EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READWRITE, &result));
  EXPECT_EQ(S_OK, result);

  text_store_->CancelComposition();

  result = kInvalidResult;
  EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READWRITE, &result));
  EXPECT_EQ(S_OK, result);
}

// regression tests for crbug.com/1156612.
// We should remove selected text even if there is no new composition and IME
// ask us to delete a previously inserted text.
class RegressionTest12Callback : public TSFTextStoreTestCallback {
 public:
  explicit RegressionTest12Callback(TSFTextStore* text_store)
      : TSFTextStoreTestCallback(text_store) {}

  RegressionTest12Callback(const RegressionTest12Callback&) = delete;
  RegressionTest12Callback& operator=(const RegressionTest12Callback&) = delete;

  HRESULT LockGranted1(DWORD flags) {
    SetTextTest(0, 0, L"a", S_OK);
    SetSelectionTest(1, 1, S_OK);
    return S_OK;
  }

  HRESULT LockGranted2(DWORD flags) {
    GetTextTest(0, -1, L"a", 1);
    SetTextTest(0, 1, L"", S_OK);

    text_spans()->clear();
    *edit_flag() = true;

    return S_OK;
  }

  HRESULT LockGranted3(DWORD flags) {
    GetTextTest(0, -1, L"", 0);

    return S_OK;
  }
};

TEST_F(TSFTextStoreTest, RegressionTest12) {
  EXPECT_CALL(text_input_client_, GetTextInputType())
      .WillRepeatedly(Return(TEXT_INPUT_TYPE_TEXT));
  RegressionTest12Callback callback(text_store_.get());

  EXPECT_CALL(text_input_client_, ExtendSelectionAndDelete(_, _)).Times(1);
  EXPECT_CALL(*sink_, OnLockGranted(_))
      .WillOnce(Invoke(&callback, &RegressionTest12Callback::LockGranted1))
      .WillOnce(Invoke(&callback, &RegressionTest12Callback::LockGranted2))
      .WillOnce(Invoke(&callback, &RegressionTest12Callback::LockGranted3));

  HRESULT result = kInvalidResult;
  EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READWRITE, &result));
  EXPECT_EQ(S_OK, result);
  result = kInvalidResult;
  EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READWRITE, &result));
  EXPECT_EQ(S_OK, result);
  result = kInvalidResult;
  EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READWRITE, &result));
  EXPECT_EQ(S_OK, result);
}

// regression tests for crbug.com/1225896.
// Some IMEs (e.g. voice typing panel) may remove text before active
// composition. We should delete text before inserting new text.
class RegressionTest13Callback : public TSFTextStoreTestCallback {
 public:
  explicit RegressionTest13Callback(TSFTextStore* text_store)
      : TSFTextStoreTestCallback(text_store) {}

  RegressionTest13Callback(const RegressionTest13Callback&) = delete;
  RegressionTest13Callback& operator=(const RegressionTest13Callback&) = delete;

  HRESULT LockGranted1(DWORD flags) {
    SetTextTest(0, 0, L"text ", S_OK);
    SetTextTest(4, 5, L"", S_OK);
    SetTextTest(4, 4, L" delete that ", S_OK);
    SetSelectionTest(17, 17, S_OK);

    text_spans()->clear();
    ImeTextSpan text_span;
    text_span.start_offset = 4;
    text_span.end_offset = 17;
    text_span.underline_color = SK_ColorBLACK;
    text_span.thickness = ImeTextSpan::Thickness::kThin;
    text_span.background_color = SK_ColorTRANSPARENT;
    text_spans()->push_back(text_span);
    *edit_flag() = true;
    *composition_start() = 4;
    composition_range()->set_start(4);
    composition_range()->set_end(17);
    // text_store_->OnKeyTraceDown(65u, 1966081u);
    *has_composition_range() = true;

    return S_OK;
  }

  void SetCompositionText1(const ui::CompositionText& composition) {
    EXPECT_EQ(u" delete that ", composition.text);
    EXPECT_EQ(13u, composition.selection.start());
    EXPECT_EQ(13u, composition.selection.end());
    ASSERT_EQ(1u, composition.ime_text_spans.size());
    EXPECT_EQ(0u, composition.ime_text_spans[0].start_offset);
    EXPECT_EQ(13u, composition.ime_text_spans[0].end_offset);
    SetHasCompositionText(true);
    SetTextRange(0, 17);
    SetTextBuffer(u"text delete that ");
    SetSelectionRange(17, 17);
  }

  HRESULT LockGranted2(DWORD flags) {
    GetTextTest(0, -1, L"text delete that ", 17);
    SetTextTest(4, 17, L"", S_OK);
    GetTextTest(0, -1, L"text", 4);
    SetTextTest(0, 4, L"", S_OK);
    GetTextTest(0, -1, L"", 0);
    SetSelectionTest(0, 0, S_OK);

    text_spans()->clear();
    *edit_flag() = true;
    *composition_start() = 3;
    composition_range()->set_start(0);
    composition_range()->set_end(0);
    *has_composition_range() = false;

    return S_OK;
  }

  HRESULT LockGranted3(DWORD flags) {
    GetTextTest(0, -1, L"", 0);

    return S_OK;
  }
};

TEST_F(TSFTextStoreTest, RegressionTest13) {
  EXPECT_CALL(text_input_client_, GetTextInputType())
      .WillRepeatedly(Return(TEXT_INPUT_TYPE_TEXT));
  RegressionTest13Callback callback(text_store_.get());
  EXPECT_CALL(text_input_client_, ExtendSelectionAndDelete(_, _)).Times(1);
  EXPECT_CALL(text_input_client_, InsertText(_, _)).Times(0);
  EXPECT_CALL(text_input_client_, SetCompositionText(_))
      .WillOnce(
          Invoke(&callback, &RegressionTest13Callback::SetCompositionText1));

  EXPECT_CALL(*sink_, OnLockGranted(_))
      .WillOnce(Invoke(&callback, &RegressionTest13Callback::LockGranted1))
      .WillOnce(Invoke(&callback, &RegressionTest13Callback::LockGranted2))
      .WillOnce(Invoke(&callback, &RegressionTest13Callback::LockGranted3));

  ON_CALL(text_input_client_, HasCompositionText())
      .WillByDefault(
          Invoke(&callback, &TSFTextStoreTestCallback::HasCompositionText));

  HRESULT result = kInvalidResult;
  EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READWRITE, &result));
  EXPECT_EQ(S_OK, result);
  result = kInvalidResult;
  EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READWRITE, &result));
  EXPECT_EQ(S_OK, result);
  result = kInvalidResult;
  EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READWRITE, &result));
  EXPECT_EQ(S_OK, result);
}

// regression tests for crbug.com/1295578.
// Some IMEs (e.g. voice typing panel) may select text before active
// composition. We should select text after composition end.
class RegressionTest14Callback : public TSFTextStoreTestCallback {
 public:
  explicit RegressionTest14Callback(TSFTextStore* text_store)
      : TSFTextStoreTestCallback(text_store) {}

  RegressionTest14Callback(const RegressionTest14Callback&) = delete;
  RegressionTest14Callback& operator=(const RegressionTest14Callback&) = delete;

  HRESULT LockGranted1(DWORD flags) {
    SetTextTest(0, 0, L"text ", S_OK);
    SetTextTest(4, 5, L"", S_OK);
    SetTextTest(4, 4, L" select that ", S_OK);
    SetSelectionTest(17, 17, S_OK);

    text_spans()->clear();
    ImeTextSpan text_span;
    text_span.start_offset = 4;
    text_span.end_offset = 17;
    text_span.underline_color = SK_ColorBLACK;
    text_span.thickness = ImeTextSpan::Thickness::kThin;
    text_span.background_color = SK_ColorTRANSPARENT;
    text_spans()->push_back(text_span);
    *edit_flag() = true;
    *composition_start() = 4;
    composition_range()->set_start(4);
    composition_range()->set_end(17);
    *has_composition_range() = true;

    return S_OK;
  }

  void SetCompositionText1(const ui::CompositionText& composition) {
    EXPECT_EQ(u" select that ", composition.text);
    EXPECT_EQ(13u, composition.selection.start());
    EXPECT_EQ(13u, composition.selection.end());
    ASSERT_EQ(1u, composition.ime_text_spans.size());
    EXPECT_EQ(0u, composition.ime_text_spans[0].start_offset);
    EXPECT_EQ(13u, composition.ime_text_spans[0].end_offset);
    SetHasCompositionText(true);
    SetTextRange(0, 17);
    SetTextBuffer(u"text select that ");
    SetSelectionRange(17, 17);
  }

  HRESULT LockGranted2(DWORD flags) {
    GetTextTest(0, -1, L"text select that ", 17);
    SetTextTest(4, 17, L"", S_OK);
    GetTextTest(0, -1, L"text", 4);
    SetSelectionTest(0, 4, S_OK);

    text_spans()->clear();
    *edit_flag() = true;
    *composition_start() = 0;
    composition_range()->set_start(0);
    composition_range()->set_end(0);
    *has_composition_range() = false;

    return S_OK;
  }

  bool SetEditableSelectionRange2(const gfx::Range& range) {
    EXPECT_EQ(range.GetMin(), 0u);
    EXPECT_EQ(range.length(), 4u);
    return true;
  }

  HRESULT LockGranted3(DWORD flags) {
    GetTextTest(0, -1, L"text", 4);
    GetSelectionTest(0, 4);

    return S_OK;
  }
};

TEST_F(TSFTextStoreTest, RegressionTest14) {
  EXPECT_CALL(text_input_client_, GetTextInputType())
      .WillRepeatedly(Return(TEXT_INPUT_TYPE_TEXT));
  RegressionTest14Callback callback(text_store_.get());
  EXPECT_CALL(text_input_client_, InsertText(_, _)).Times(0);
  EXPECT_CALL(text_input_client_, SetCompositionText(_))
      .WillOnce(
          Invoke(&callback, &RegressionTest14Callback::SetCompositionText1));
  EXPECT_CALL(text_input_client_, SetEditableSelectionRange(_))
      .WillOnce(Invoke(&callback,
                       &RegressionTest14Callback::SetEditableSelectionRange2));

  EXPECT_CALL(*sink_, OnLockGranted(_))
      .WillOnce(Invoke(&callback, &RegressionTest14Callback::LockGranted1))
      .WillOnce(Invoke(&callback, &RegressionTest14Callback::LockGranted2))
      .WillOnce(Invoke(&callback, &RegressionTest14Callback::LockGranted3));

  ON_CALL(text_input_client_, HasCompositionText())
      .WillByDefault(
          Invoke(&callback, &TSFTextStoreTestCallback::HasCompositionText));

  HRESULT result = kInvalidResult;
  EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READWRITE, &result));
  EXPECT_EQ(S_OK, result);
  result = kInvalidResult;
  EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READWRITE, &result));
  EXPECT_EQ(S_OK, result);
  result = kInvalidResult;
  EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READWRITE, &result));
  EXPECT_EQ(S_OK, result);
}

// Test multiple |SetText| call in one edit session.
class MultipleSetTextCallback : public TSFTextStoreTestCallback {
 public:
  explicit MultipleSetTextCallback(TSFTextStore* text_store)
      : TSFTextStoreTestCallback(text_store) {}

  MultipleSetTextCallback(const MultipleSetTextCallback&) = delete;
  MultipleSetTextCallback& operator=(const MultipleSetTextCallback&) = delete;

  HRESULT LockGranted1(DWORD flags) {
    SetTextRange(0, 6);
    SetTextBuffer(u"123456");
    SetTextTest(0, 0, L"123456", S_OK);
    SetSelectionRange(6, 6);
    *composition_start() = 1;
    return S_OK;
  }

  HRESULT LockGranted2(DWORD flags) {
    SetTextTest(3, 3, L"a", S_OK);
    SetTextTest(4, 4, L"b", S_OK);
    GetTextTest(0, -1, L"123ab456", 8);
    SetSelectionTest(5, 5, S_OK);

    *edit_flag() = true;
    *composition_start() = 5;
    return S_OK;
  }

  HRESULT LockGranted3(DWORD flags) {
    SetTextTest(2, 6, L"cd", S_OK);
    SetTextTest(5, 6, L"e", S_OK);
    GetTextTest(0, -1, L"12cd5e", 6);
    SetSelectionRange(6, 6);

    *edit_flag() = true;
    *composition_start() = 6;
    return S_OK;
  }

  HRESULT LockGranted4(DWORD flags) {
    SetTextTest(5, 6, L"fg", S_OK);
    GetTextTest(0, -1, L"12cd5fg", 7);
    SetTextTest(1, 3, L"h", S_OK);
    GetTextTest(0, -1, L"1hd5fg", 6);
    SetSelectionRange(6, 6);

    *edit_flag() = true;
    *composition_start() = 5;
    return S_OK;
  }
};

TEST_F(TSFTextStoreTest, MultipleSetText) {
  EXPECT_CALL(text_input_client_, GetTextInputType())
      .WillRepeatedly(Return(TEXT_INPUT_TYPE_TEXT));
  MultipleSetTextCallback callback(text_store_.get());
  EXPECT_CALL(*sink_, OnLockGranted(_))
      .WillOnce(Invoke(&callback, &MultipleSetTextCallback::LockGranted1))
      .WillOnce(Invoke(&callback, &MultipleSetTextCallback::LockGranted2))
      .WillOnce(Invoke(&callback, &MultipleSetTextCallback::LockGranted3))
      .WillOnce(Invoke(&callback, &MultipleSetTextCallback::LockGranted4));

  ON_CALL(text_input_client_, GetTextRange(_))
      .WillByDefault(
          Invoke(&callback, &TSFTextStoreTestCallback::GetTextRange));

  ON_CALL(text_input_client_, GetTextFromRange(_, _))
      .WillByDefault(
          Invoke(&callback, &TSFTextStoreTestCallback::GetTextFromRange));

  ON_CALL(text_input_client_, GetEditableSelectionRange(_))
      .WillByDefault(Invoke(
          &callback, &TSFTextStoreTestCallback::GetEditableSelectionRange));

  HRESULT result = kInvalidResult;
  EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READWRITE, &result));
  EXPECT_EQ(S_OK, result);
  result = kInvalidResult;
  EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READWRITE, &result));
  EXPECT_EQ(S_OK, result);
  result = kInvalidResult;
  EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READWRITE, &result));
  EXPECT_EQ(S_OK, result);
  result = kInvalidResult;
  EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READWRITE, &result));
  EXPECT_EQ(S_OK, result);
}

// Test re-entrancy scanerio while writing to text input client.
class TextInputClientReentrancyTestCallback : public TSFTextStoreTestCallback {
 public:
  explicit TextInputClientReentrancyTestCallback(TSFTextStore* text_store)
      : TSFTextStoreTestCallback(text_store) {}

  TextInputClientReentrancyTestCallback(
      const TextInputClientReentrancyTestCallback&) = delete;
  TextInputClientReentrancyTestCallback& operator=(
      const TextInputClientReentrancyTestCallback&) = delete;

  HRESULT LockGranted1(DWORD flags) {
    SetTextTest(0, 0, L"a", S_OK);
    SetSelectionTest(0, 1, S_OK);

    text_spans()->clear();
    ImeTextSpan text_span;
    text_span.start_offset = 0;
    text_span.end_offset = 1;
    text_span.underline_color = SK_ColorBLACK;
    text_span.thickness = ImeTextSpan::Thickness::kThin;
    text_span.background_color = SK_ColorTRANSPARENT;
    text_spans()->push_back(text_span);
    *edit_flag() = true;
    *composition_start() = 0;
    composition_range()->set_start(0);
    composition_range()->set_end(1);
    text_store_->OnKeyTraceDown(65u, 1966081u);
    *has_composition_range() = true;

    return S_OK;
  }

  void SetCompositionText1(const ui::CompositionText& composition) {
    EXPECT_EQ(u"a", composition.text);
    EXPECT_EQ(0u, composition.selection.start());
    EXPECT_EQ(1u, composition.selection.end());
    ASSERT_EQ(1u, composition.ime_text_spans.size());
    EXPECT_EQ(0u, composition.ime_text_spans[0].start_offset);
    EXPECT_EQ(1u, composition.ime_text_spans[0].end_offset);
    SetHasCompositionText(true);
    SetTextRange(0, 1);
    SetTextBuffer(u"a");
    SetSelectionRange(0, 1);
  }

  HRESULT LockGranted2(DWORD flags) {
    GetTextTest(0, -1, L"a", 1);

    text_spans()->clear();
    *edit_flag() = true;
    *composition_start() = 1;
    composition_range()->set_start(0);
    composition_range()->set_end(0);

    *has_composition_range() = false;
    return S_OK;
  }

  void InsertText2(
      const std::u16string& text,
      ui::TextInputClient::InsertTextCursorBehavior cursor_behavior) {
    EXPECT_EQ(u"a", text);
    SetHasCompositionText(false);
    SetSelectionRange(1, 1);
    SetTextBuffer(u"b");
    HRESULT result = kInvalidResult;
    EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READWRITE, &result));
    EXPECT_EQ(S_OK, result);
  }

  HRESULT LockGranted3(DWORD flags) {
    GetTextTest(0, -1, L"a", 1);
    *edit_flag() = false;
    return S_OK;
  }

  HRESULT LockGranted4(DWORD flags) {
    GetTextTest(0, -1, L"b", 1);
    *edit_flag() = false;
    return S_OK;
  }
};

TEST_F(TSFTextStoreTest, TextInputClientReentrancTest) {
  EXPECT_CALL(text_input_client_, GetTextInputType())
      .WillRepeatedly(Return(TEXT_INPUT_TYPE_TEXT));
  TextInputClientReentrancyTestCallback callback(text_store_.get());
  EXPECT_CALL(*sink_, OnLockGranted(_))
      .WillOnce(Invoke(&callback,
                       &TextInputClientReentrancyTestCallback::LockGranted1))
      .WillOnce(Invoke(&callback,
                       &TextInputClientReentrancyTestCallback::LockGranted2))
      .WillOnce(Invoke(&callback,
                       &TextInputClientReentrancyTestCallback::LockGranted3))
      .WillOnce(Invoke(&callback,
                       &TextInputClientReentrancyTestCallback::LockGranted4));

  EXPECT_CALL(text_input_client_, InsertText(_, _))
      .WillOnce(Invoke(&callback,
                       &TextInputClientReentrancyTestCallback::InsertText2));

  EXPECT_CALL(text_input_client_, SetCompositionText(_))
      .WillOnce(
          Invoke(&callback,
                 &TextInputClientReentrancyTestCallback::SetCompositionText1));

  ON_CALL(text_input_client_, GetTextRange(_))
      .WillByDefault(
          Invoke(&callback, &TSFTextStoreTestCallback::GetTextRange));

  ON_CALL(text_input_client_, GetTextFromRange(_, _))
      .WillByDefault(
          Invoke(&callback, &TSFTextStoreTestCallback::GetTextFromRange));

  ON_CALL(text_input_client_, GetEditableSelectionRange(_))
      .WillByDefault(Invoke(
          &callback, &TSFTextStoreTestCallback::GetEditableSelectionRange));

  ON_CALL(text_input_client_, HasCompositionText())
      .WillByDefault(
          Invoke(&callback, &TSFTextStoreTestCallback::HasCompositionText));

  ON_CALL(text_input_client_, GetCompositionTextRange(_))
      .WillByDefault(Invoke(
          &callback, &TSFTextStoreTestCallback::GetCompositionTextRange));

  HRESULT result = kInvalidResult;
  EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READWRITE, &result));
  EXPECT_EQ(S_OK, result);
  result = kInvalidResult;
  EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READWRITE, &result));
  EXPECT_EQ(S_OK, result);
  result = kInvalidResult;
  EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READWRITE, &result));
  EXPECT_EQ(S_OK, result);
}

}  // namespace
}  // namespace ui