text_input_host.mm 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411
  1. // Copyright 2018 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. #include "ui/views/cocoa/text_input_host.h"
  5. #include "components/remote_cocoa/app_shim/native_widget_ns_window_bridge.h"
  6. #include "ui/accelerated_widget_mac/window_resize_helper_mac.h"
  7. #include "ui/base/ime/text_input_client.h"
  8. #include "ui/events/keycodes/dom/dom_code.h"
  9. #include "ui/views/cocoa/native_widget_mac_ns_window_host.h"
  10. namespace {
  11. // Returns the boundary rectangle for composition characters in the
  12. // |requested_range|. Sets |actual_range| corresponding to the returned
  13. // rectangle. For cases, where there is no composition text or the
  14. // |requested_range| lies outside the composition range, a zero width rectangle
  15. // corresponding to the caret bounds is returned. Logic used is similar to
  16. // RenderWidgetHostViewMac::GetCachedFirstRectForCharacterRange(...).
  17. gfx::Rect GetFirstRectForRangeHelper(const ui::TextInputClient* client,
  18. const gfx::Range& requested_range,
  19. gfx::Range* actual_range) {
  20. // NSRange doesn't support reversed ranges.
  21. DCHECK(!requested_range.is_reversed());
  22. DCHECK(actual_range);
  23. // Set up default return values, to be returned in case of unusual cases.
  24. gfx::Rect default_rect;
  25. *actual_range = gfx::Range::InvalidRange();
  26. if (!client)
  27. return default_rect;
  28. default_rect = client->GetCaretBounds();
  29. default_rect.set_width(0);
  30. // If possible, modify actual_range to correspond to caret position.
  31. gfx::Range selection_range;
  32. if (client->GetEditableSelectionRange(&selection_range)) {
  33. // Caret bounds correspond to end index of selection_range.
  34. *actual_range = gfx::Range(selection_range.end());
  35. }
  36. gfx::Range composition_range;
  37. if (!client->HasCompositionText() ||
  38. !client->GetCompositionTextRange(&composition_range) ||
  39. !requested_range.IsBoundedBy(composition_range)) {
  40. return default_rect;
  41. }
  42. DCHECK(!composition_range.is_reversed());
  43. const size_t from = requested_range.start() - composition_range.start();
  44. const size_t to = requested_range.end() - composition_range.start();
  45. // Pick the first character's bounds as the initial rectangle, then grow it to
  46. // the full |requested_range| if possible.
  47. const bool request_is_composition_end = from == composition_range.length();
  48. const size_t first_index = request_is_composition_end ? from - 1 : from;
  49. gfx::Rect union_rect;
  50. if (!client->GetCompositionCharacterBounds(first_index, &union_rect))
  51. return default_rect;
  52. // If requested_range is empty, return a zero width rectangle corresponding to
  53. // it.
  54. if (from == to) {
  55. if (request_is_composition_end &&
  56. client->GetTextDirection() != base::i18n::RIGHT_TO_LEFT) {
  57. // In case of an empty requested range at end of composition, return the
  58. // rectangle to the right of the last compositioned character.
  59. union_rect.set_origin(union_rect.top_right());
  60. }
  61. union_rect.set_width(0);
  62. *actual_range = requested_range;
  63. return union_rect;
  64. }
  65. // Toolkit-views textfields are always single-line, so no need to check for
  66. // line breaks.
  67. for (size_t i = from + 1; i < to; i++) {
  68. gfx::Rect current_rect;
  69. if (client->GetCompositionCharacterBounds(i, &current_rect)) {
  70. union_rect.Union(current_rect);
  71. } else {
  72. *actual_range =
  73. gfx::Range(requested_range.start(), i + composition_range.start());
  74. return union_rect;
  75. }
  76. }
  77. *actual_range = requested_range;
  78. return union_rect;
  79. }
  80. // Returns the string corresponding to |requested_range| for the given |client|.
  81. // If a gfx::Range::InvalidRange() is passed, the full string stored by |client|
  82. // is returned. Sets |actual_range| corresponding to the returned string.
  83. std::u16string AttributedSubstringForRangeHelper(
  84. const ui::TextInputClient* client,
  85. const gfx::Range& requested_range,
  86. gfx::Range* actual_range) {
  87. // NSRange doesn't support reversed ranges.
  88. DCHECK(!requested_range.is_reversed());
  89. DCHECK(actual_range);
  90. std::u16string substring;
  91. gfx::Range text_range;
  92. *actual_range = gfx::Range::InvalidRange();
  93. if (!client || !client->GetTextRange(&text_range))
  94. return substring;
  95. // gfx::Range::Intersect() behaves a bit weirdly. If B is an empty range
  96. // contained inside a non-empty range A, B intersection A returns
  97. // gfx::Range::InvalidRange(), instead of returning B.
  98. *actual_range = text_range.Contains(requested_range)
  99. ? requested_range
  100. : text_range.Intersect(requested_range);
  101. // This is a special case for which the complete string should should be
  102. // returned. NSTextView also follows this, though the same is not mentioned in
  103. // NSTextInputClient documentation.
  104. if (!requested_range.IsValid())
  105. *actual_range = text_range;
  106. client->GetTextFromRange(*actual_range, &substring);
  107. return substring;
  108. }
  109. } // namespace
  110. namespace views {
  111. ////////////////////////////////////////////////////////////////////////////////
  112. // TextInputHost, public:
  113. TextInputHost::TextInputHost(NativeWidgetMacNSWindowHost* host_impl)
  114. : host_impl_(host_impl) {}
  115. TextInputHost::~TextInputHost() = default;
  116. void TextInputHost::BindReceiver(
  117. mojo::PendingAssociatedReceiver<remote_cocoa::mojom::TextInputHost>
  118. receiver) {
  119. mojo_receiver_.Bind(std::move(receiver),
  120. ui::WindowResizeHelperMac::Get()->task_runner());
  121. }
  122. ui::TextInputClient* TextInputHost::GetTextInputClient() const {
  123. return text_input_client_;
  124. }
  125. void TextInputHost::SetTextInputClient(
  126. ui::TextInputClient* new_text_input_client) {
  127. if (pending_text_input_client_ == new_text_input_client)
  128. return;
  129. // This method may cause the IME window to dismiss, which may cause it to
  130. // insert text (e.g. to replace marked text with "real" text). That should
  131. // happen in the old -inputContext (which AppKit stores a reference to).
  132. // Unfortunately, the only way to invalidate the the old -inputContext is to
  133. // invoke -[NSApp updateWindows], which also wants a reference to the _new_
  134. // -inputContext. So put the new inputContext in |pendingTextInputClient_| and
  135. // only use it for -inputContext.
  136. ui::TextInputClient* old_text_input_client = text_input_client_;
  137. // Since dismissing an IME may insert text, a misbehaving IME or a
  138. // ui::TextInputClient that acts on InsertChar() to change focus a second time
  139. // may invoke -setTextInputClient: recursively; with [NSApp updateWindows]
  140. // still on the stack. Calling [NSApp updateWindows] recursively may upset
  141. // an IME. Since the rest of this method is only to decide whether to call
  142. // updateWindows, and we're already calling it, just bail out.
  143. if (text_input_client_ != pending_text_input_client_) {
  144. pending_text_input_client_ = new_text_input_client;
  145. return;
  146. }
  147. // Start by assuming no need to invoke -updateWindows.
  148. text_input_client_ = new_text_input_client;
  149. pending_text_input_client_ = new_text_input_client;
  150. if (host_impl_->in_process_ns_window_bridge_ &&
  151. host_impl_->in_process_ns_window_bridge_->NeedsUpdateWindows()) {
  152. text_input_client_ = old_text_input_client;
  153. [NSApp updateWindows];
  154. // Note: |pending_text_input_client_| (and therefore +[NSTextInputContext
  155. // currentInputContext] may have changed if called recursively.
  156. text_input_client_ = pending_text_input_client_;
  157. }
  158. }
  159. ////////////////////////////////////////////////////////////////////////////////
  160. // TextInputHost, remote_cocoa::mojom::TextInputHost:
  161. bool TextInputHost::HasClient(bool* out_has_client) {
  162. *out_has_client = text_input_client_ != nullptr;
  163. return true;
  164. }
  165. bool TextInputHost::HasInputContext(bool* out_has_input_context) {
  166. *out_has_input_context = false;
  167. // If the textInputClient_ does not exist, return nil since this view does not
  168. // conform to NSTextInputClient protocol.
  169. if (!pending_text_input_client_)
  170. return true;
  171. // If a menu is active, and -[NSView interpretKeyEvents:] asks for the
  172. // input context, return nil. This ensures the action message is sent to
  173. // the view, rather than any NSTextInputClient a subview has installed.
  174. bool has_menu_controller = false;
  175. host_impl_->GetHasMenuController(&has_menu_controller);
  176. if (has_menu_controller)
  177. return true;
  178. // When not in an editable mode, or while entering passwords
  179. // (http://crbug.com/23219), we don't want to show IME candidate windows.
  180. // Returning nil prevents this view from getting messages defined as part of
  181. // the NSTextInputClient protocol.
  182. switch (pending_text_input_client_->GetTextInputType()) {
  183. case ui::TEXT_INPUT_TYPE_NONE:
  184. case ui::TEXT_INPUT_TYPE_PASSWORD:
  185. return true;
  186. default:
  187. *out_has_input_context = true;
  188. }
  189. return true;
  190. }
  191. bool TextInputHost::IsRTL(bool* out_is_rtl) {
  192. *out_is_rtl = text_input_client_ && text_input_client_->GetTextDirection() ==
  193. base::i18n::RIGHT_TO_LEFT;
  194. return true;
  195. }
  196. bool TextInputHost::GetSelectionRange(gfx::Range* out_range) {
  197. if (!text_input_client_ ||
  198. !text_input_client_->GetEditableSelectionRange(out_range)) {
  199. *out_range = gfx::Range::InvalidRange();
  200. }
  201. return true;
  202. }
  203. bool TextInputHost::GetSelectionText(bool* out_result,
  204. std::u16string* out_text) {
  205. *out_result = false;
  206. if (!text_input_client_)
  207. return true;
  208. gfx::Range selection_range;
  209. if (!text_input_client_->GetEditableSelectionRange(&selection_range))
  210. return true;
  211. *out_result = text_input_client_->GetTextFromRange(selection_range, out_text);
  212. return true;
  213. }
  214. void TextInputHost::InsertText(const std::u16string& text, bool as_character) {
  215. if (!text_input_client_)
  216. return;
  217. if (as_character) {
  218. // If a single character is inserted by keyDown's call to
  219. // interpretKeyEvents: then use InsertChar() to allow editing events to be
  220. // merged. We use ui::VKEY_UNKNOWN as the key code since it's not feasible
  221. // to determine the correct key code for each unicode character. Also a
  222. // correct keycode is not needed in the current context. Send ui::EF_NONE as
  223. // the key modifier since |text| already accounts for the pressed key
  224. // modifiers.
  225. text_input_client_->InsertChar(ui::KeyEvent(
  226. text[0], ui::VKEY_UNKNOWN, ui::DomCode::NONE, ui::EF_NONE));
  227. } else {
  228. text_input_client_->InsertText(
  229. text,
  230. ui::TextInputClient::InsertTextCursorBehavior::kMoveCursorAfterText);
  231. }
  232. }
  233. void TextInputHost::DeleteRange(const gfx::Range& range) {
  234. if (!text_input_client_)
  235. return;
  236. text_input_client_->DeleteRange(range);
  237. }
  238. void TextInputHost::SetCompositionText(const std::u16string& text,
  239. const gfx::Range& selected_range,
  240. const gfx::Range& replacement_range) {
  241. if (!text_input_client_)
  242. return;
  243. text_input_client_->DeleteRange(replacement_range);
  244. ui::CompositionText composition;
  245. composition.text = text;
  246. composition.selection = selected_range;
  247. // Add an underline with text color and a transparent background to the
  248. // composition text. TODO(karandeepb): On Cocoa textfields, the target clause
  249. // of the composition has a thick underlines. The composition text also has
  250. // discontinuous underlines for different clauses. This is also supported in
  251. // the Chrome renderer. Add code to extract underlines from |text| once our
  252. // render text implementation supports thick underlines and discontinuous
  253. // underlines for consecutive characters. See http://crbug.com/612675.
  254. composition.ime_text_spans.push_back(ui::ImeTextSpan(
  255. ui::ImeTextSpan::Type::kComposition, 0, text.length(),
  256. ui::ImeTextSpan::Thickness::kThin,
  257. ui::ImeTextSpan::UnderlineStyle::kSolid, SK_ColorTRANSPARENT));
  258. text_input_client_->SetCompositionText(composition);
  259. }
  260. void TextInputHost::ConfirmCompositionText() {
  261. if (!text_input_client_)
  262. return;
  263. text_input_client_->ConfirmCompositionText(/* keep_selection */ false);
  264. }
  265. bool TextInputHost::HasCompositionText(bool* out_has_composition_text) {
  266. *out_has_composition_text = false;
  267. if (!text_input_client_)
  268. return true;
  269. *out_has_composition_text = text_input_client_->HasCompositionText();
  270. return true;
  271. }
  272. bool TextInputHost::GetCompositionTextRange(gfx::Range* out_composition_range) {
  273. *out_composition_range = gfx::Range::InvalidRange();
  274. if (!text_input_client_)
  275. return true;
  276. if (!text_input_client_->HasCompositionText())
  277. return true;
  278. text_input_client_->GetCompositionTextRange(out_composition_range);
  279. return true;
  280. }
  281. bool TextInputHost::GetAttributedSubstringForRange(
  282. const gfx::Range& requested_range,
  283. std::u16string* out_text,
  284. gfx::Range* out_actual_range) {
  285. *out_text = AttributedSubstringForRangeHelper(
  286. text_input_client_, requested_range, out_actual_range);
  287. return true;
  288. }
  289. bool TextInputHost::GetFirstRectForRange(const gfx::Range& requested_range,
  290. gfx::Rect* out_rect,
  291. gfx::Range* out_actual_range) {
  292. *out_rect = GetFirstRectForRangeHelper(text_input_client_, requested_range,
  293. out_actual_range);
  294. return true;
  295. }
  296. ////////////////////////////////////////////////////////////////////////////////
  297. // TextInputHost, remote_cocoa::mojom::TextInputHost synchronous methods:
  298. void TextInputHost::HasClient(HasClientCallback callback) {
  299. bool has_client = false;
  300. HasClient(&has_client);
  301. std::move(callback).Run(has_client);
  302. }
  303. void TextInputHost::HasInputContext(HasInputContextCallback callback) {
  304. bool has_input_context = false;
  305. HasClient(&has_input_context);
  306. std::move(callback).Run(has_input_context);
  307. }
  308. void TextInputHost::IsRTL(IsRTLCallback callback) {
  309. bool is_rtl = false;
  310. IsRTL(&is_rtl);
  311. std::move(callback).Run(is_rtl);
  312. }
  313. void TextInputHost::GetSelectionRange(GetSelectionRangeCallback callback) {
  314. gfx::Range range = gfx::Range::InvalidRange();
  315. GetSelectionRange(&range);
  316. std::move(callback).Run(range);
  317. }
  318. void TextInputHost::GetSelectionText(GetSelectionTextCallback callback) {
  319. bool result = false;
  320. std::u16string text;
  321. GetSelectionText(&result, &text);
  322. std::move(callback).Run(result, text);
  323. }
  324. void TextInputHost::HasCompositionText(HasCompositionTextCallback callback) {
  325. bool has_composition_text = false;
  326. HasCompositionText(&has_composition_text);
  327. std::move(callback).Run(has_composition_text);
  328. }
  329. void TextInputHost::GetCompositionTextRange(
  330. GetCompositionTextRangeCallback callback) {
  331. gfx::Range range = gfx::Range::InvalidRange();
  332. GetCompositionTextRange(&range);
  333. std::move(callback).Run(range);
  334. }
  335. void TextInputHost::GetAttributedSubstringForRange(
  336. const gfx::Range& requested_range,
  337. GetAttributedSubstringForRangeCallback callback) {
  338. std::u16string text;
  339. gfx::Range actual_range = gfx::Range::InvalidRange();
  340. GetAttributedSubstringForRange(requested_range, &text, &actual_range);
  341. std::move(callback).Run(text, actual_range);
  342. }
  343. void TextInputHost::GetFirstRectForRange(
  344. const gfx::Range& requested_range,
  345. GetFirstRectForRangeCallback callback) {
  346. gfx::Rect rect;
  347. gfx::Range actual_range;
  348. GetFirstRectForRange(requested_range, &rect, &actual_range);
  349. std::move(callback).Run(rect, actual_range);
  350. }
  351. } // namespace views