1// Copyright (c) 2012 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#import "content/browser/renderer_host/text_input_client_mac.h"
6
7#include "base/memory/singleton.h"
8#include "base/metrics/histogram.h"
9#include "base/threading/thread_restrictions.h"
10#include "base/time/time.h"
11#include "content/browser/renderer_host/render_widget_host_impl.h"
12#include "content/common/text_input_client_messages.h"
13
14namespace content {
15
16// The amount of time in milliseconds that the browser process will wait for a
17// response from the renderer.
18// TODO(rsesek): Using the histogram data, find the best upper-bound for this
19// value.
20const float kWaitTimeout = 1500;
21
22TextInputClientMac::TextInputClientMac()
23    : character_index_(NSNotFound),
24      lock_(),
25      condition_(&lock_) {
26}
27
28TextInputClientMac::~TextInputClientMac() {
29}
30
31// static
32TextInputClientMac* TextInputClientMac::GetInstance() {
33  return Singleton<TextInputClientMac>::get();
34}
35
36void TextInputClientMac::GetStringAtPoint(
37    RenderWidgetHost* rwh,
38    gfx::Point point,
39    void (^replyHandler)(NSAttributedString*, NSPoint)) {
40  DCHECK(replyHandler_.get() == nil);
41  replyHandler_.reset(replyHandler, base::scoped_policy::RETAIN);
42  RenderWidgetHostImpl* rwhi = RenderWidgetHostImpl::From(rwh);
43  rwhi->Send(new TextInputClientMsg_StringAtPoint(rwhi->GetRoutingID(), point));
44}
45
46void TextInputClientMac::GetStringAtPointReply(NSAttributedString* string,
47                                               NSPoint point) {
48  if (replyHandler_.get()) {
49    replyHandler_.get()(string, point);
50    replyHandler_.reset();
51  }
52}
53
54NSUInteger TextInputClientMac::GetCharacterIndexAtPoint(RenderWidgetHost* rwh,
55    gfx::Point point) {
56  base::TimeTicks start = base::TimeTicks::Now();
57
58  BeforeRequest();
59  RenderWidgetHostImpl* rwhi = RenderWidgetHostImpl::From(rwh);
60  rwhi->Send(new TextInputClientMsg_CharacterIndexForPoint(rwhi->GetRoutingID(),
61                                                          point));
62  // http://crbug.com/121917
63  base::ThreadRestrictions::ScopedAllowWait allow_wait;
64  condition_.TimedWait(base::TimeDelta::FromMilliseconds(kWaitTimeout));
65  AfterRequest();
66
67  base::TimeDelta delta(base::TimeTicks::Now() - start);
68  UMA_HISTOGRAM_LONG_TIMES("TextInputClient.CharacterIndex",
69                           delta * base::Time::kMicrosecondsPerMillisecond);
70
71  return character_index_;
72}
73
74NSRect TextInputClientMac::GetFirstRectForRange(RenderWidgetHost* rwh,
75    NSRange range) {
76  base::TimeTicks start = base::TimeTicks::Now();
77
78  BeforeRequest();
79  RenderWidgetHostImpl* rwhi = RenderWidgetHostImpl::From(rwh);
80  rwhi->Send(
81      new TextInputClientMsg_FirstRectForCharacterRange(rwhi->GetRoutingID(),
82                                                        gfx::Range(range)));
83  // http://crbug.com/121917
84  base::ThreadRestrictions::ScopedAllowWait allow_wait;
85  condition_.TimedWait(base::TimeDelta::FromMilliseconds(kWaitTimeout));
86  AfterRequest();
87
88  base::TimeDelta delta(base::TimeTicks::Now() - start);
89  UMA_HISTOGRAM_LONG_TIMES("TextInputClient.FirstRect",
90                           delta * base::Time::kMicrosecondsPerMillisecond);
91
92  return first_rect_;
93}
94
95NSAttributedString* TextInputClientMac::GetAttributedSubstringFromRange(
96    RenderWidgetHost* rwh,
97    NSRange range) {
98  base::TimeTicks start = base::TimeTicks::Now();
99
100  BeforeRequest();
101  RenderWidgetHostImpl* rwhi = RenderWidgetHostImpl::From(rwh);
102  rwhi->Send(new TextInputClientMsg_StringForRange(rwhi->GetRoutingID(),
103                                                   gfx::Range(range)));
104  // http://crbug.com/121917
105  base::ThreadRestrictions::ScopedAllowWait allow_wait;
106  condition_.TimedWait(base::TimeDelta::FromMilliseconds(kWaitTimeout));
107  AfterRequest();
108
109  base::TimeDelta delta(base::TimeTicks::Now() - start);
110  UMA_HISTOGRAM_LONG_TIMES("TextInputClient.Substring",
111                           delta * base::Time::kMicrosecondsPerMillisecond);
112
113  // Lookup.framework calls this method repeatedly and expects that repeated
114  // calls don't deallocate previous results immediately. Returning an
115  // autoreleased string is better convention anyway.
116  return [[substring_.get() retain] autorelease];
117}
118
119void TextInputClientMac::SetCharacterIndexAndSignal(NSUInteger index) {
120  lock_.Acquire();
121  character_index_ = index;
122  lock_.Release();
123  condition_.Signal();
124}
125
126void TextInputClientMac::SetFirstRectAndSignal(NSRect first_rect) {
127  lock_.Acquire();
128  first_rect_ = first_rect;
129  lock_.Release();
130  condition_.Signal();
131}
132
133void TextInputClientMac::SetSubstringAndSignal(NSAttributedString* string) {
134  lock_.Acquire();
135  substring_.reset([string copy]);
136  lock_.Release();
137  condition_.Signal();
138}
139
140void TextInputClientMac::BeforeRequest() {
141  base::TimeTicks start = base::TimeTicks::Now();
142
143  lock_.Acquire();
144
145  base::TimeDelta delta(base::TimeTicks::Now() - start);
146  UMA_HISTOGRAM_LONG_TIMES("TextInputClient.LockWait",
147                           delta * base::Time::kMicrosecondsPerMillisecond);
148
149  character_index_ = NSNotFound;
150  first_rect_ = NSZeroRect;
151  substring_.reset();
152}
153
154void TextInputClientMac::AfterRequest() {
155  lock_.Release();
156}
157
158}  // namespace content
159