summaryrefslogtreecommitdiffstats
path: root/content
diff options
context:
space:
mode:
authornona@chromium.org <nona@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-07-23 11:11:15 +0000
committernona@chromium.org <nona@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-07-23 11:11:15 +0000
commitc5130278777487597b1f61b429bd2b42e1d3e73d (patch)
tree5021cd35d052f02335d66deb40a214e9e7808bde /content
parent94354a8557587906b9601ca10fd514cd7fad2a8e (diff)
downloadchromium_src-c5130278777487597b1f61b429bd2b42e1d3e73d.zip
chromium_src-c5130278777487597b1f61b429bd2b42e1d3e73d.tar.gz
chromium_src-c5130278777487597b1f61b429bd2b42e1d3e73d.tar.bz2
Use cached composition bounds for firstRectForCharacterRange if possible.
This CL fixes(or reduces) mac dead-lock issue. Browser process uses synchronous IPC for firstRectForCharacterRange request, but also renderer process uses synchronous IPC for some reasons. This CL changes from using synchronous IPC to using cached composition character rectangle in browser process if possible. BUG=115920 TEST=try bot and manually done on Lion Review URL: https://chromiumcodereview.appspot.com/10656019 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@147843 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'content')
-rw-r--r--content/browser/renderer_host/render_widget_host_view_mac.h30
-rw-r--r--content/browser/renderer_host/render_widget_host_view_mac.mm140
-rw-r--r--content/browser/renderer_host/render_widget_host_view_mac_unittest.mm343
3 files changed, 508 insertions, 5 deletions
diff --git a/content/browser/renderer_host/render_widget_host_view_mac.h b/content/browser/renderer_host/render_widget_host_view_mac.h
index 700d576..699f6d8 100644
--- a/content/browser/renderer_host/render_widget_host_view_mac.h
+++ b/content/browser/renderer_host/render_widget_host_view_mac.h
@@ -333,6 +333,29 @@ class RenderWidgetHostViewMac : public RenderWidgetHostViewBase {
// no effect if there are no pending requests.
void AckPendingSwapBuffers();
+ // Returns true and stores first rectangle for character range if the
+ // requested |range| is already cached, otherwise returns false.
+ bool GetCachedFirstRectForCharacterRange(NSRange range, NSRect* rect,
+ NSRange* actual_range);
+
+ // Returns true if there is line break in |range| and stores line breaking
+ // point to |line_breaking_point|. The |line_break_point| is valid only if
+ // this function returns true.
+ bool GetLineBreakIndex(const std::vector<gfx::Rect>& bounds,
+ const ui::Range& range,
+ size_t* line_break_point);
+
+ // Returns composition character boundary rectangle. The |range| is
+ // composition based range. Also stores |actual_range| which is corresponding
+ // to actually used range for returned rectangle.
+ gfx::Rect GetFirstRectForCompositionRange(const ui::Range& range,
+ ui::Range* actual_range);
+
+ // Converts from given whole character range to composition oriented range. If
+ // the conversion failed, return ui::Range::InvalidRange.
+ ui::Range ConvertCharacterRangeToCompositionRange(
+ const ui::Range& request_range);
+
// These member variables should be private, but the associated ObjC class
// needs access to them and can't be made a friend.
@@ -429,6 +452,13 @@ class RenderWidgetHostViewMac : public RenderWidgetHostViewBase {
// pairs of (route_id, gpu_host_id).
std::list<std::pair<int32, int32> > pending_swap_buffers_acks_;
+ // The current composition character range and its bounds.
+ ui::Range composition_range_;
+ std::vector<gfx::Rect> composition_bounds_;
+
+ // The current caret bounds.
+ gfx::Rect caret_rect_;
+
DISALLOW_COPY_AND_ASSIGN(RenderWidgetHostViewMac);
};
diff --git a/content/browser/renderer_host/render_widget_host_view_mac.mm b/content/browser/renderer_host/render_widget_host_view_mac.mm
index 319fb3b..2a399b7 100644
--- a/content/browser/renderer_host/render_widget_host_view_mac.mm
+++ b/content/browser/renderer_host/render_widget_host_view_mac.mm
@@ -615,6 +615,8 @@ void RenderWidgetHostViewMac::TextInputStateChanged(
void RenderWidgetHostViewMac::SelectionBoundsChanged(
const gfx::Rect& start_rect,
const gfx::Rect& end_rect) {
+ if (start_rect == end_rect)
+ caret_rect_ = start_rect;
}
void RenderWidgetHostViewMac::ImeCancelComposition() {
@@ -627,6 +629,8 @@ void RenderWidgetHostViewMac::ImeCompositionRangeChanged(
// The RangeChanged message is only sent with valid values. The current
// caret position (start == end) will be sent if there is no IME range.
[cocoa_view_ setMarkedRange:range.ToNSRange()];
+ composition_range_ = range;
+ composition_bounds_ = character_bounds;
}
void RenderWidgetHostViewMac::DidUpdateBackingStore(
@@ -770,6 +774,8 @@ void RenderWidgetHostViewMac::SelectionChanged(const string16& text,
if (![cocoa_view_ hasMarkedText]) {
[cocoa_view_ setMarkedRange:range.ToNSRange()];
}
+
+ RenderWidgetHostViewBase::SelectionChanged(text, offset, range);
}
void RenderWidgetHostViewMac::SetShowingContextMenu(bool showing) {
@@ -1019,6 +1025,123 @@ void RenderWidgetHostViewMac::AckPendingSwapBuffers() {
}
}
+bool RenderWidgetHostViewMac::GetLineBreakIndex(
+ const std::vector<gfx::Rect>& bounds,
+ const ui::Range& range,
+ size_t* line_break_point) {
+ DCHECK(line_break_point);
+ if (range.start() >= bounds.size() || range.is_reversed() || range.is_empty())
+ return false;
+
+ // We can't check line breaking completely from only rectangle array. Thus we
+ // assume the line breaking as the next character's y offset is larger than
+ // a threshold. Currently the threshold is determined as minimum y offset plus
+ // 75% of maximum height.
+ // TODO(nona): Check the threshold is reliable or not.
+ // TODO(nona): Bidi support.
+ const size_t loop_end_idx = std::min(bounds.size(), range.end());
+ int max_height = 0;
+ int min_y_offset = kint32max;
+ for (size_t idx = range.start(); idx < loop_end_idx; ++idx) {
+ max_height = std::max(max_height, bounds[idx].height());
+ min_y_offset = std::min(min_y_offset, bounds[idx].y());
+ }
+ int line_break_threshold = min_y_offset + (max_height * 3 / 4);
+ for (size_t idx = range.start(); idx < loop_end_idx; ++idx) {
+ if (bounds[idx].y() > line_break_threshold) {
+ *line_break_point = idx;
+ return true;
+ }
+ }
+ return false;
+}
+
+gfx::Rect RenderWidgetHostViewMac::GetFirstRectForCompositionRange(
+ const ui::Range& range,
+ ui::Range* actual_range) {
+ DCHECK(actual_range);
+ DCHECK(!composition_bounds_.empty());
+ DCHECK(range.start() <= composition_bounds_.size());
+ DCHECK(range.end() <= composition_bounds_.size());
+
+ if (range.is_empty()) {
+ *actual_range = range;
+ if (range.start() == composition_bounds_.size()) {
+ return gfx::Rect(composition_bounds_[range.start() - 1].right(),
+ composition_bounds_[range.start() - 1].y(),
+ 0,
+ composition_bounds_[range.start() - 1].height());
+ } else {
+ return gfx::Rect(composition_bounds_[range.start()].x(),
+ composition_bounds_[range.start()].y(),
+ 0,
+ composition_bounds_[range.start()].height());
+ }
+ }
+
+ size_t end_idx;
+ if (!GetLineBreakIndex(composition_bounds_, range, &end_idx)) {
+ end_idx = range.end();
+ }
+ *actual_range = ui::Range(range.start(), end_idx);
+ gfx::Rect rect = composition_bounds_[range.start()];
+ for (size_t i = range.start() + 1; i < end_idx; ++i) {
+ rect = rect.Union(composition_bounds_[i]);
+ }
+ return rect;
+}
+
+ui::Range RenderWidgetHostViewMac::ConvertCharacterRangeToCompositionRange(
+ const ui::Range& request_range) {
+ if (composition_range_.is_empty())
+ return ui::Range::InvalidRange();
+
+ if (request_range.is_reversed())
+ return ui::Range::InvalidRange();
+
+ if (request_range.start() < composition_range_.start() ||
+ request_range.start() > composition_range_.end() ||
+ request_range.end() > composition_range_.end()) {
+ return ui::Range::InvalidRange();
+ }
+
+ return ui::Range(
+ request_range.start() - composition_range_.start(),
+ request_range.end() - composition_range_.start());
+}
+
+bool RenderWidgetHostViewMac::GetCachedFirstRectForCharacterRange(
+ NSRange range,
+ NSRect* rect,
+ NSRange* actual_range) {
+ // This exists to make IMEs more responsive, see http://crbug.com/115920
+ TRACE_EVENT0("browser",
+ "RenderWidgetHostViewMac::GetFirstRectForCharacterRange");
+
+ // If requested range is same as caret location, we can just return it.
+ if (selection_range_.is_empty() && ui::Range(range) == selection_range_) {
+ *actual_range = range;
+ *rect = NSRectFromCGRect(caret_rect_.ToCGRect());
+ return true;
+ }
+
+ const ui::Range request_range_in_composition =
+ ConvertCharacterRangeToCompositionRange(ui::Range(range));
+ if (request_range_in_composition == ui::Range::InvalidRange())
+ return false;
+
+ ui::Range ui_actual_range;
+ *rect = NSRectFromCGRect(GetFirstRectForCompositionRange(
+ request_range_in_composition,
+ &ui_actual_range).ToCGRect());
+ if (actual_range) {
+ *actual_range = ui::Range(
+ composition_range_.start() + ui_actual_range.start(),
+ composition_range_.start() + ui_actual_range.end()).ToNSRange();
+ }
+ return true;
+}
+
void RenderWidgetHostViewMac::AcceleratedSurfaceBuffersSwapped(
const GpuHostMsg_AcceleratedSurfaceBuffersSwapped_Params& params,
int gpu_host_id) {
@@ -2662,11 +2785,18 @@ extern NSString *NSTextInputReplacementRangeAttributeName;
- (NSRect)firstRectForCharacterRange:(NSRange)theRange
actualRange:(NSRangePointer)actualRange {
- // TODO(thakis): Pipe |actualRange| through TextInputClientMac machinery.
- if (actualRange)
- *actualRange = theRange;
- NSRect rect = TextInputClientMac::GetInstance()->GetFirstRectForRange(
- renderWidgetHostView_->render_widget_host_, theRange);
+ NSRect rect;
+ if (!renderWidgetHostView_->GetCachedFirstRectForCharacterRange(
+ theRange,
+ &rect,
+ actualRange)) {
+ rect = TextInputClientMac::GetInstance()->GetFirstRectForRange(
+ renderWidgetHostView_->render_widget_host_, theRange);
+
+ // TODO(thakis): Pipe |actualRange| through TextInputClientMac machinery.
+ if (actualRange)
+ *actualRange = theRange;
+ }
// The returned rectangle is in WebKit coordinates (upper left origin), so
// flip the coordinate system and then convert it into screen coordinates for
diff --git a/content/browser/renderer_host/render_widget_host_view_mac_unittest.mm b/content/browser/renderer_host/render_widget_host_view_mac_unittest.mm
index 8a89877..6f140ba 100644
--- a/content/browser/renderer_host/render_widget_host_view_mac_unittest.mm
+++ b/content/browser/renderer_host/render_widget_host_view_mac_unittest.mm
@@ -5,6 +5,7 @@
#include "content/browser/renderer_host/render_widget_host_view_mac.h"
#include "base/mac/scoped_nsautorelease_pool.h"
+#include "base/utf_string_conversions.h"
#include "content/browser/browser_thread_impl.h"
#include "content/browser/renderer_host/test_render_view_host.h"
#include "content/common/gpu/gpu_messages.h"
@@ -15,6 +16,51 @@
namespace content {
+namespace {
+
+// Generates the |length| of composition rectangle vector and save them to
+// |output|. It starts from |origin| and each rectangle contains |unit_size|.
+void GenerateCompositionRectArray(const gfx::Point& origin,
+ const gfx::Size& unit_size,
+ size_t length,
+ const std::vector<size_t>& break_points,
+ std::vector<gfx::Rect>* output) {
+ DCHECK(output);
+ output->clear();
+
+ std::queue<int> break_point_queue;
+ for (size_t i = 0; i < break_points.size(); ++i)
+ break_point_queue.push(break_points[i]);
+ break_point_queue.push(length);
+ size_t next_break_point = break_point_queue.front();
+ break_point_queue.pop();
+
+ gfx::Rect current_rect(origin, unit_size);
+ for (size_t i = 0; i < length; ++i) {
+ if (i == next_break_point) {
+ current_rect.set_x(origin.x());
+ current_rect.set_y(current_rect.y() + current_rect.height());
+ next_break_point = break_point_queue.front();
+ break_point_queue.pop();
+ }
+ output->push_back(current_rect);
+ current_rect.set_x(current_rect.right());
+ }
+}
+
+gfx::Rect GetExpectedRect(const gfx::Point& origin,
+ const gfx::Size& size,
+ const ui::Range& range,
+ int line_no) {
+ return gfx::Rect(
+ origin.x() + range.start() * size.width(),
+ origin.y() + line_no * size.height(),
+ range.length() * size.width(),
+ size.height());
+}
+
+} // namespace
+
class RenderWidgetHostViewMacTest : public RenderViewHostImplTestHarness {
public:
RenderWidgetHostViewMacTest() : old_rwhv_(NULL), rwhv_mac_(NULL) {}
@@ -205,4 +251,301 @@ TEST_F(RenderWidgetHostViewMacTest, Fullscreen) {
EXPECT_TRUE(rwhv_mac_->pepper_fullscreen_window());
}
+TEST_F(RenderWidgetHostViewMacTest, GetFirstRectForCharaacterRangeCaretCase) {
+ const string16 kDummyString = UTF8ToUTF16("hogehoge");
+ const size_t kDummyOffset = 0;
+
+ gfx::Rect caret_rect(10, 11, 0, 10);
+ ui::Range caret_range(0, 0);
+
+ NSRect rect;
+ NSRange actual_range;
+ rwhv_mac_->SelectionChanged(kDummyString, kDummyOffset, caret_range);
+ rwhv_mac_->SelectionBoundsChanged(caret_rect, caret_rect);
+ EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
+ caret_range.ToNSRange(),
+ &rect,
+ &actual_range));
+ EXPECT_EQ(caret_rect, gfx::Rect(NSRectToCGRect(rect)));
+ EXPECT_EQ(caret_range, ui::Range(actual_range));
+
+ EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
+ ui::Range(0, 1).ToNSRange(),
+ &rect,
+ &actual_range));
+ EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
+ ui::Range(1, 1).ToNSRange(),
+ &rect,
+ &actual_range));
+ EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
+ ui::Range(2, 3).ToNSRange(),
+ &rect,
+ &actual_range));
+
+ // Caret moved.
+ caret_rect = gfx::Rect(20, 11, 0, 10);
+ caret_range = ui::Range(1, 1);
+ rwhv_mac_->SelectionChanged(kDummyString, kDummyOffset, caret_range);
+ rwhv_mac_->SelectionBoundsChanged(caret_rect, caret_rect);
+ EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
+ caret_range.ToNSRange(),
+ &rect,
+ &actual_range));
+ EXPECT_EQ(caret_rect, gfx::Rect(NSRectToCGRect(rect)));
+ EXPECT_EQ(caret_range, ui::Range(actual_range));
+
+ EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
+ ui::Range(0, 0).ToNSRange(),
+ &rect,
+ &actual_range));
+ EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
+ ui::Range(1, 2).ToNSRange(),
+ &rect,
+ &actual_range));
+ EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
+ ui::Range(2, 3).ToNSRange(),
+ &rect,
+ &actual_range));
+
+ // No caret.
+ caret_range = ui::Range(1, 2);
+ rwhv_mac_->SelectionChanged(kDummyString, kDummyOffset, caret_range);
+ rwhv_mac_->SelectionBoundsChanged(caret_rect, gfx::Rect(30, 11, 0, 10));
+ EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
+ ui::Range(0, 0).ToNSRange(),
+ &rect,
+ &actual_range));
+ EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
+ ui::Range(0, 1).ToNSRange(),
+ &rect,
+ &actual_range));
+ EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
+ ui::Range(1, 1).ToNSRange(),
+ &rect,
+ &actual_range));
+ EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
+ ui::Range(1, 2).ToNSRange(),
+ &rect,
+ &actual_range));
+ EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
+ ui::Range(2, 2).ToNSRange(),
+ &rect,
+ &actual_range));
+}
+
+TEST_F(RenderWidgetHostViewMacTest, UpdateCompositionSinglelineCase) {
+ const gfx::Point kOrigin(10, 11);
+ const gfx::Size kBoundsUnit(10, 20);
+
+ // If there are no update from renderer, always returned caret position.
+ NSRect rect;
+ NSRange actual_range;
+ EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
+ ui::Range(0, 0).ToNSRange(),
+ &rect,
+ &actual_range));
+ EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
+ ui::Range(0, 1).ToNSRange(),
+ &rect,
+ &actual_range));
+ EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
+ ui::Range(1, 0).ToNSRange(),
+ &rect,
+ &actual_range));
+ EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
+ ui::Range(1, 1).ToNSRange(),
+ &rect,
+ &actual_range));
+ EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
+ ui::Range(1, 2).ToNSRange(),
+ &rect,
+ &actual_range));
+
+ const int kCompositionLength = 10;
+ std::vector<gfx::Rect> composition_bounds;
+ const int kCompositionStart = 3;
+ const ui::Range kCompositionRange(kCompositionStart,
+ kCompositionStart + kCompositionLength);
+ GenerateCompositionRectArray(kOrigin,
+ kBoundsUnit,
+ kCompositionLength,
+ std::vector<size_t>(),
+ &composition_bounds);
+ rwhv_mac_->ImeCompositionRangeChanged(kCompositionRange, composition_bounds);
+
+ // Out of range requests will return caret position.
+ EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
+ ui::Range(0, 0).ToNSRange(),
+ &rect,
+ &actual_range));
+ EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
+ ui::Range(1, 1).ToNSRange(),
+ &rect,
+ &actual_range));
+ EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
+ ui::Range(1, 2).ToNSRange(),
+ &rect,
+ &actual_range));
+ EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
+ ui::Range(2, 2).ToNSRange(),
+ &rect,
+ &actual_range));
+ EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
+ ui::Range(13, 14).ToNSRange(),
+ &rect,
+ &actual_range));
+ EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
+ ui::Range(14, 15).ToNSRange(),
+ &rect,
+ &actual_range));
+
+ for (int i = 0; i <= kCompositionLength; ++i) {
+ for (int j = 0; j <= kCompositionLength - i; ++j) {
+ const ui::Range range(i, i + j);
+ const gfx::Rect expected_rect = GetExpectedRect(kOrigin,
+ kBoundsUnit,
+ range,
+ 0);
+ const NSRange request_range = ui::Range(
+ kCompositionStart + range.start(),
+ kCompositionStart + range.end()).ToNSRange();
+ EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
+ request_range,
+ &rect,
+ &actual_range));
+ EXPECT_EQ(ui::Range(request_range), ui::Range(actual_range));
+ EXPECT_EQ(expected_rect, gfx::Rect(NSRectToCGRect(rect)));
+ }
+ }
+}
+
+TEST_F(RenderWidgetHostViewMacTest, UpdateCompositionMultilineCase) {
+ const gfx::Point kOrigin(10, 11);
+ const gfx::Size kBoundsUnit(10, 20);
+ NSRect rect;
+
+ const int kCompositionLength = 30;
+ std::vector<gfx::Rect> composition_bounds;
+ const ui::Range kCompositionRange(0, kCompositionLength);
+ // Set breaking point at 10 and 20.
+ std::vector<size_t> break_points;
+ break_points.push_back(10);
+ break_points.push_back(20);
+ GenerateCompositionRectArray(kOrigin,
+ kBoundsUnit,
+ kCompositionLength,
+ break_points,
+ &composition_bounds);
+ rwhv_mac_->ImeCompositionRangeChanged(kCompositionRange, composition_bounds);
+
+ // Range doesn't contain line breaking point.
+ ui::Range range;
+ range = ui::Range(5, 8);
+ NSRange actual_range;
+ EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(range.ToNSRange(),
+ &rect,
+ &actual_range));
+ EXPECT_EQ(range, ui::Range(actual_range));
+ EXPECT_EQ(
+ GetExpectedRect(kOrigin, kBoundsUnit, range, 0),
+ gfx::Rect(NSRectToCGRect(rect)));
+ range = ui::Range(15, 18);
+ EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(range.ToNSRange(),
+ &rect,
+ &actual_range));
+ EXPECT_EQ(range, ui::Range(actual_range));
+ EXPECT_EQ(
+ GetExpectedRect(kOrigin, kBoundsUnit, ui::Range(5, 8), 1),
+ gfx::Rect(NSRectToCGRect(rect)));
+ range = ui::Range(25, 28);
+ EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(range.ToNSRange(),
+ &rect,
+ &actual_range));
+ EXPECT_EQ(range, ui::Range(actual_range));
+ EXPECT_EQ(
+ GetExpectedRect(kOrigin, kBoundsUnit, ui::Range(5, 8), 2),
+ gfx::Rect(NSRectToCGRect(rect)));
+
+ // Range contains line breaking point.
+ range = ui::Range(8, 12);
+ EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(range.ToNSRange(),
+ &rect,
+ &actual_range));
+ EXPECT_EQ(ui::Range(8, 10), ui::Range(actual_range));
+ EXPECT_EQ(
+ GetExpectedRect(kOrigin, kBoundsUnit, ui::Range(8, 10), 0),
+ gfx::Rect(NSRectToCGRect(rect)));
+ range = ui::Range(18, 22);
+ EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(range.ToNSRange(),
+ &rect,
+ &actual_range));
+ EXPECT_EQ(ui::Range(18, 20), ui::Range(actual_range));
+ EXPECT_EQ(
+ GetExpectedRect(kOrigin, kBoundsUnit, ui::Range(8, 10), 1),
+ gfx::Rect(NSRectToCGRect(rect)));
+
+ // Start point is line breaking point.
+ range = ui::Range(10, 12);
+ EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(range.ToNSRange(),
+ &rect,
+ &actual_range));
+ EXPECT_EQ(ui::Range(10, 12), ui::Range(actual_range));
+ EXPECT_EQ(
+ GetExpectedRect(kOrigin, kBoundsUnit, ui::Range(0, 2), 1),
+ gfx::Rect(NSRectToCGRect(rect)));
+ range = ui::Range(20, 22);
+ EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(range.ToNSRange(),
+ &rect,
+ &actual_range));
+ EXPECT_EQ(ui::Range(20, 22), ui::Range(actual_range));
+ EXPECT_EQ(
+ GetExpectedRect(kOrigin, kBoundsUnit, ui::Range(0, 2), 2),
+ gfx::Rect(NSRectToCGRect(rect)));
+
+ // End point is line breaking point.
+ range = ui::Range(5, 10);
+ EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(range.ToNSRange(),
+ &rect,
+ &actual_range));
+ EXPECT_EQ(ui::Range(5, 10), ui::Range(actual_range));
+ EXPECT_EQ(
+ GetExpectedRect(kOrigin, kBoundsUnit, ui::Range(5, 10), 0),
+ gfx::Rect(NSRectToCGRect(rect)));
+ range = ui::Range(15, 20);
+ EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(range.ToNSRange(),
+ &rect,
+ &actual_range));
+ EXPECT_EQ(ui::Range(15, 20), ui::Range(actual_range));
+ EXPECT_EQ(
+ GetExpectedRect(kOrigin, kBoundsUnit, ui::Range(5, 10), 1),
+ gfx::Rect(NSRectToCGRect(rect)));
+
+ // Start and end point are same line breaking point.
+ range = ui::Range(10, 10);
+ EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(range.ToNSRange(),
+ &rect,
+ &actual_range));
+ EXPECT_EQ(ui::Range(10, 10), ui::Range(actual_range));
+ EXPECT_EQ(
+ GetExpectedRect(kOrigin, kBoundsUnit, ui::Range(0, 0), 1),
+ gfx::Rect(NSRectToCGRect(rect)));
+ range = ui::Range(20, 20);
+ EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(range.ToNSRange(),
+ &rect,
+ &actual_range));
+ EXPECT_EQ(ui::Range(20, 20), ui::Range(actual_range));
+ EXPECT_EQ(
+ GetExpectedRect(kOrigin, kBoundsUnit, ui::Range(0, 0), 2),
+ gfx::Rect(NSRectToCGRect(rect)));
+
+ // Start and end point are different line breaking point.
+ range = ui::Range(10, 20);
+ EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(range.ToNSRange(),
+ &rect,
+ &actual_range));
+ EXPECT_EQ(ui::Range(10, 20), ui::Range(actual_range));
+ EXPECT_EQ(
+ GetExpectedRect(kOrigin, kBoundsUnit, ui::Range(0, 10), 1),
+ gfx::Rect(NSRectToCGRect(rect)));
+}
} // namespace content