Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(142)

Side by Side Diff: ios/chrome/browser/ui/omnibox/omnibox_view_ios.mm

Issue 2589803002: Upstream Chrome on iOS source code [6/11]. (Closed)
Patch Set: Created 4 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
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 #include "ios/chrome/browser/ui/omnibox/omnibox_view_ios.h"
6
7 #import <CoreText/CoreText.h>
8 #import <MobileCoreServices/MobileCoreServices.h>
9
10 #include "base/command_line.h"
11 #include "base/ios/device_util.h"
12 #include "base/memory/ptr_util.h"
13 #include "base/metrics/user_metrics.h"
14 #include "base/metrics/user_metrics_action.h"
15 #include "base/strings/string16.h"
16 #include "base/strings/sys_string_conversions.h"
17 #include "components/omnibox/browser/autocomplete_input.h"
18 #include "components/omnibox/browser/autocomplete_match.h"
19 #include "components/omnibox/browser/omnibox_edit_model.h"
20 #include "components/omnibox/browser/omnibox_popup_model.h"
21 #include "components/toolbar/toolbar_model.h"
22 #include "ios/chrome/browser/autocomplete/autocomplete_scheme_classifier_impl.h"
23 #include "ios/chrome/browser/browser_state/chrome_browser_state.h"
24 #include "ios/chrome/browser/ui/omnibox/chrome_omnibox_client_ios.h"
25 #include "ios/chrome/browser/ui/omnibox/omnibox_popup_view_ios.h"
26 #include "ios/chrome/browser/ui/omnibox/omnibox_util.h"
27 #include "ios/chrome/browser/ui/omnibox/preload_provider.h"
28 #include "ios/chrome/browser/ui/omnibox/web_omnibox_edit_controller.h"
29 #include "ios/chrome/browser/ui/ui_util.h"
30 #include "ios/chrome/grit/ios_theme_resources.h"
31 #include "ios/web/public/referrer.h"
32 #import "net/base/mac/url_conversions.h"
33 #include "skia/ext/skia_utils_ios.h"
34 #include "ui/base/page_transition_types.h"
35 #include "ui/base/resource/resource_bundle.h"
36 #include "ui/base/window_open_disposition.h"
37 #include "ui/gfx/color_palette.h"
38 #include "ui/gfx/image/image.h"
39
40 using base::UserMetricsAction;
41
42 namespace {
43 // The color of the rest of the URL (i.e. after the TLD) in the omnibox.
44 UIColor* BaseTextColor() {
45 return [UIColor colorWithWhite:(161 / 255.0) alpha:1.0];
46 }
47
48 // The color of the https when there is an error.
49 UIColor* ErrorTextColor() {
50 return skia::UIColorFromSkColor(gfx::kGoogleRed700);
51 }
52
53 // The color of the https when there is a warning.
54 UIColor* WarningTextColor() {
55 return skia::UIColorFromSkColor(gfx::kGoogleYellow700);
56 }
57
58 // The color of the https when there is not an error.
59 UIColor* SecureTextColor() {
60 return skia::UIColorFromSkColor(gfx::kGoogleGreen700);
61 }
62
63 // The color of the https when highlighted in incognito.
64 UIColor* IncognitoSecureTextColor() {
65 return [UIColor colorWithWhite:(255 / 255.0) alpha:1.0];
66 }
67
68 // Helper to make converting url_parse ranges to NSRange easier to
69 // read.
70 NSRange ComponentToNSRange(const url::Component& component) {
71 return NSMakeRange(static_cast<NSInteger>(component.begin),
72 static_cast<NSInteger>(component.len));
73 }
74
75 } // namespace
76
77 // Simple Obj-C object to forward UITextFieldDelegate method calls back to the
78 // OmniboxViewIOS.
79 @interface AutocompleteTextFieldDelegate : NSObject<OmniboxTextFieldDelegate> {
80 @private
81 OmniboxViewIOS* editView_; // weak, owns us
82
83 // YES if we are already forwarding an OnDidChange() message to the edit view.
84 // Needed to prevent infinite recursion.
85 // TODO(rohitrao): There must be a better way.
86 BOOL forwardingOnDidChange_;
87
88 // YES if this text field is currently processing a user-initiated event,
89 // such as typing in the omnibox or pressing the clear button. Used to
90 // distinguish between calls to textDidChange that are triggered by the user
91 // typing vs by calls to setText.
92 BOOL processingUserEvent_;
93 }
94 @end
95
96 @implementation AutocompleteTextFieldDelegate
97 - (id)initWithEditView:(OmniboxViewIOS*)editView {
98 if ((self = [super init])) {
99 editView_ = editView;
100 forwardingOnDidChange_ = NO;
101 processingUserEvent_ = NO;
102 }
103 return self;
104 }
105
106 - (BOOL)textField:(UITextField*)textField
107 shouldChangeCharactersInRange:(NSRange)range
108 replacementString:(NSString*)newText {
109 processingUserEvent_ = editView_->OnWillChange(range, newText);
110 return processingUserEvent_;
111 }
112
113 - (void)textFieldDidChange:(id)sender {
114 if (forwardingOnDidChange_)
115 return;
116
117 BOOL savedProcessingUserEvent = processingUserEvent_;
118 processingUserEvent_ = NO;
119 forwardingOnDidChange_ = YES;
120 editView_->OnDidChange(savedProcessingUserEvent);
121 forwardingOnDidChange_ = NO;
122 }
123
124 // Delegate method for UITextField, called when user presses the "go" button.
125 - (BOOL)textFieldShouldReturn:(UITextField*)textField {
126 editView_->OnAccept();
127 return NO;
128 }
129
130 // Always update the text field colors when we start editing. It's possible
131 // for this method to be called when we are already editing (popup focus
132 // change). In this case, OnDidBeginEditing will be called multiple times.
133 // If that becomes an issue a boolean should be added to track editing state.
134 - (void)textFieldDidBeginEditing:(UITextField*)textField {
135 editView_->OnDidBeginEditing();
136 }
137
138 // On phone, the omnibox may still be editing when the popup is open, so end
139 // editing is called directly in OnDidEndEditing.
140 - (void)textFieldDidEndEditing:(UITextField*)textField {
141 if (!IsIPadIdiom() && editView_->IsPopupOpen())
142 return;
143
144 editView_->OnDidEndEditing();
145 }
146
147 // When editing, forward the message on to |editView_|.
148 - (BOOL)textFieldShouldClear:(UITextField*)textField {
149 editView_->OnClear();
150 processingUserEvent_ = YES;
151 return YES;
152 }
153
154 - (BOOL)onCopy {
155 return editView_->OnCopy();
156 }
157
158 - (BOOL)onCopyURL {
159 return editView_->OnCopyURL();
160 }
161
162 - (BOOL)canCopyURL {
163 return editView_->CanCopyURL();
164 }
165
166 - (void)willPaste {
167 editView_->WillPaste();
168 }
169
170 - (void)onDeleteBackward {
171 editView_->OnDeleteBackward();
172 }
173
174 @end
175
176 OmniboxViewIOS::OmniboxViewIOS(OmniboxTextFieldIOS* field,
177 WebOmniboxEditController* controller,
178 ios::ChromeBrowserState* browser_state,
179 id<PreloadProvider> preloader,
180 id<OmniboxPopupPositioner> positioner)
181 : OmniboxView(
182 controller,
183 base::MakeUnique<ChromeOmniboxClientIOS>(controller, browser_state)),
184 browser_state_(browser_state),
185 field_([field retain]),
186 controller_(controller),
187 preloader_(preloader),
188 ignore_popup_updates_(false) {
189 popup_view_.reset(new OmniboxPopupViewIOS(this, model(), positioner));
190 field_delegate_.reset(
191 [[AutocompleteTextFieldDelegate alloc] initWithEditView:this]);
192 [field_ setDelegate:field_delegate_];
193 [field_ addTarget:field_delegate_
194 action:@selector(textFieldDidChange:)
195 forControlEvents:UIControlEventEditingChanged];
196 }
197
198 OmniboxViewIOS::~OmniboxViewIOS() {
199 // |field_| outlives this object.
200 [field_ setDelegate:nil];
201
202 [field_ removeTarget:field_delegate_
203 action:@selector(textFieldDidChange:)
204 forControlEvents:UIControlEventEditingChanged];
205
206 // Destroy the model, in case it tries to call back into us when destroyed.
207 popup_view_.reset();
208 }
209
210 void OmniboxViewIOS::OpenMatch(const AutocompleteMatch& match,
211 WindowOpenDisposition disposition,
212 const GURL& alternate_nav_url,
213 const base::string16& pasted_text,
214 size_t selected_line) {
215 // It may be unsafe to modify the contents of the field.
216 if (ShouldIgnoreUserInputDueToPendingVoiceSearch()) {
217 return;
218 }
219
220 OmniboxView::OpenMatch(match, disposition, alternate_nav_url, pasted_text,
221 selected_line);
222 }
223
224 base::string16 OmniboxViewIOS::GetText() const {
225 return [field_ displayedText];
226 }
227
228 void OmniboxViewIOS::SetWindowTextAndCaretPos(const base::string16& text,
229 size_t caret_pos,
230 bool update_popup,
231 bool notify_text_changed) {
232 // Do not call SetUserText() here, as the user has not triggered this change.
233 // Instead, set the field's text directly.
234 // TODO(justincohen): b/5244062 Temporary fix to set the text_field value
235 // before model()->CurrentTextIsUrl(), since that pulls from text_field.value.
236 [field_ setText:base::SysUTF16ToNSString(text)];
237
238 NSAttributedString* as = ApplyTextAttributes(text);
239 [field_ setText:as userTextLength:[as length]];
240
241 if (update_popup)
242 UpdatePopup();
243
244 if (notify_text_changed)
245 model()->OnChanged();
246 }
247
248 void OmniboxViewIOS::RevertAll() {
249 ignore_popup_updates_ = true;
250 OmniboxView::RevertAll();
251 ignore_popup_updates_ = false;
252 }
253
254 void OmniboxViewIOS::UpdatePopup() {
255 model()->SetInputInProgress(true);
256
257 if (!model()->has_focus())
258 return;
259
260 // Prevent inline-autocomplete if the IME is currently composing or if the
261 // cursor is not at the end of the text.
262 bool prevent_inline_autocomplete =
263 IsImeComposing() ||
264 NSMaxRange(current_selection_) != [[field_ text] length];
265 model()->StartAutocomplete(current_selection_.length != 0,
266 prevent_inline_autocomplete);
267 popup_view_->SetTextAlignment([field_ bestTextAlignment]);
268 }
269
270 void OmniboxViewIOS::OnTemporaryTextMaybeChanged(
271 const base::string16& display_text,
272 bool save_original_selection,
273 bool notify_text_changed) {
274 SetWindowTextAndCaretPos(display_text, display_text.size(), false, false);
275 model()->OnChanged();
276 }
277
278 bool OmniboxViewIOS::OnInlineAutocompleteTextMaybeChanged(
279 const base::string16& display_text,
280 size_t user_text_length) {
281 if (display_text == GetText())
282 return false;
283
284 NSAttributedString* as = ApplyTextAttributes(display_text);
285 [field_ setText:as userTextLength:user_text_length];
286 model()->OnChanged();
287 return true;
288 }
289
290 void OmniboxViewIOS::OnBeforePossibleChange() {
291 GetState(&state_before_change_);
292 marked_text_before_change_.reset([[field_ markedText] copy]);
293 }
294
295 bool OmniboxViewIOS::OnAfterPossibleChange(bool allow_keyword_ui_change) {
296 State new_state;
297 GetState(&new_state);
298 // Manually update the selection state after calling GetState().
299 new_state.sel_start = current_selection_.location;
300 new_state.sel_end = current_selection_.location + current_selection_.length;
301
302 OmniboxView::StateChanges state_changes =
303 GetStateChanges(state_before_change_, new_state);
304
305 // iOS does not supports KeywordProvider, so never allow keyword UI changes.
306 const bool something_changed =
307 model()->OnAfterPossibleChange(state_changes, allow_keyword_ui_change);
308
309 model()->OnChanged();
310
311 // TODO(justincohen): Find a different place to call this. Give the omnibox
312 // a chance to update the alignment for a text direction change.
313 [field_ updateTextDirection];
314 return something_changed;
315 }
316
317 bool OmniboxViewIOS::IsImeComposing() const {
318 return [field_ markedTextRange] != nil;
319 }
320
321 bool OmniboxViewIOS::IsIndicatingQueryRefinement() const {
322 return [field_ isShowingQueryRefinementChip];
323 }
324
325 bool OmniboxViewIOS::IsSelectAll() const {
326 return false;
327 }
328
329 bool OmniboxViewIOS::DeleteAtEndPressed() {
330 return false;
331 }
332
333 void OmniboxViewIOS::GetSelectionBounds(base::string16::size_type* start,
334 base::string16::size_type* end) const {
335 *start = *end = 0;
336 }
337
338 gfx::NativeView OmniboxViewIOS::GetNativeView() const {
339 return nullptr;
340 }
341
342 gfx::NativeView OmniboxViewIOS::GetRelativeWindowForPopup() const {
343 return nullptr;
344 }
345
346 int OmniboxViewIOS::GetTextWidth() const {
347 return 0;
348 }
349
350 // TODO(crbug.com/329527): [Merge r241107] implement OmniboxViewIOS::GetWidth().
351 int OmniboxViewIOS::GetWidth() const {
352 return 0;
353 }
354
355 void OmniboxViewIOS::OnDidBeginEditing() {
356 // If Open from Clipboard offers a suggestion, the popup may be opened when
357 // |OnSetFocus| is called on the model. The state of the popup is saved early
358 // to ignore that case.
359 bool popup_was_open_before_editing_began = popup_view_->IsOpen();
360
361 // Text attributes (e.g. text color) should not be shown while editing, so
362 // strip them out by calling setText (as opposed to setAttributedText).
363 [field_ setText:[field_ text]];
364 [field_ enableLeftViewButton:NO];
365 OnBeforePossibleChange();
366 // In the case where the user taps the fakebox on the Google landing page,
367 // the WebToolbarController invokes OnSetFocus before calling
368 // becomeFirstResponder on OmniboxTextFieldIOS (which leads to this method
369 // beting invoked) so there is no need to call OnSetFocus again. In fact,
370 // calling OnSetFocus again here would reset the caret visibility to true and
371 // it would be impossible to tell that the omnibox was focused by a tap in the
372 // fakebox instead of the omnibox.
373 if (!model()->has_focus()) {
374 model()->OnSetFocus(false);
375 }
376
377 // If the omnibox is displaying a URL and the popup is not showing, set the
378 // field into pre-editing state. If the omnibox is displaying search terms,
379 // leave the default behavior of positioning the cursor at the end of the
380 // text. If the popup is already open, that means that the omnibox is
381 // regaining focus after a popup scroll took focus away, so the pre-edit
382 // behavior should not be invoked.
383 if (!popup_was_open_before_editing_began)
384 [field_ enterPreEditState];
385
386 // The controller looks at the current pre-edit state, so the call to
387 // OnSetFocus() must come after entering pre-edit.
388 controller_->OnSetFocus();
389 }
390
391 void OmniboxViewIOS::OnDidEndEditing() {
392 CloseOmniboxPopup();
393 [field_ enableLeftViewButton:YES];
394 [field_ setChipText:@""];
395 model()->OnWillKillFocus();
396 model()->OnKillFocus();
397 if ([field_ isPreEditing])
398 [field_ exitPreEditState];
399
400 // The controller looks at the current pre-edit state, so the call to
401 // OnKillFocus() must come after exiting pre-edit.
402 controller_->OnKillFocus();
403
404 // Cancel any outstanding preload requests.
405 [preloader_ cancelPrerender];
406 [preloader_ cancelPrefetch];
407
408 // Blow away any in-progress edits.
409 RevertAll();
410 DCHECK(![field_ hasAutocompleteText]);
411 }
412
413 bool OmniboxViewIOS::OnWillChange(NSRange range, NSString* new_text) {
414 bool ok_to_change = true;
415
416 // It may be unsafe to modify the contents of the field.
417 if (ShouldIgnoreUserInputDueToPendingVoiceSearch()) {
418 return false;
419 }
420
421 if ([field_ isPreEditing]) {
422 [field_ setClearingPreEditText:YES];
423
424 // Exit the pre-editing state in OnWillChange() instead of OnDidChange(), as
425 // that allows IME to continue working. The following code clears the text
426 // field but continues the normal text editing flow, so UIKit behaves as
427 // though the user had typed into an empty field.
428 [field_ exitPreEditState];
429
430 // Clearing the text field will trigger a call to OnDidChange(). This is
431 // ok, because the autocomplete system will process it as if the user had
432 // deleted all the omnibox text.
433 [field_ setText:@""];
434
435 // Reset |range| to be of zero-length at location zero, as the field is now
436 // cleared.
437 range = NSMakeRange(0, 0);
438 }
439
440 // Figure out the old and current (new) selections. Assume the new selection
441 // will be of zero-length, located at the end of |new_text|.
442 NSRange old_range = range;
443 NSRange new_range = NSMakeRange(range.location + [new_text length], 0);
444
445 // We may need to fix up the old and new ranges in the case where autocomplete
446 // text was showing. If there is autocomplete text, assume it was selected.
447 // If the change is deleting one character from the end of the actual text,
448 // disallow the change, but clear the autocomplete text and call OnDidChange
449 // directly. If there is autocomplete text AND a text field selection, or if
450 // the user entered multiple characters, clear the autocomplete text and
451 // pretend it never existed.
452 if ([field_ hasAutocompleteText]) {
453 bool adding_text = (range.length < [new_text length]);
454 bool deleting_text = (range.length > [new_text length]);
455
456 if (adding_text) {
457 // TODO(rohitrao): What about cases where [new_text length] > 1? This
458 // could happen if an IME completion inserts multiple characters at once,
459 // or if the user pastes some text in. Let's loosen this test to allow
460 // multiple characters, as long as the "old range" ends at the end of the
461 // permanent text.
462 if ([new_text length] == 1 && range.location == [[field_ text] length]) {
463 old_range = NSMakeRange([[field_ text] length],
464 [field_ autocompleteText].length());
465 }
466 } else if (deleting_text) {
467 if ([new_text length] == 0 &&
468 range.location == [[field_ text] length] - 1) {
469 ok_to_change = false;
470 }
471 }
472 }
473
474 // Update variables needed by OnDidChange() and GetState().
475 old_selection_ = old_range;
476 current_selection_ = new_range;
477
478 // Store the displayed text. Older version of Chrome used to clear the
479 // autocomplete text here as well, but on iOS7 doing this causes the inline
480 // autocomplete text to flicker, so the call was moved to the start on
481 // OnDidChange().
482 GetState(&state_before_change_);
483 // Manually update the selection state after calling GetState().
484 state_before_change_.sel_start = old_selection_.location;
485 state_before_change_.sel_end =
486 old_selection_.location + old_selection_.length;
487
488 if (!ok_to_change) {
489 // Force a change in the autocomplete system, since we won't get an
490 // OnDidChange() message.
491 OnDidChange(true);
492 }
493
494 return ok_to_change;
495 }
496
497 void OmniboxViewIOS::OnDidChange(bool processing_user_event) {
498 // Sanitize pasted text.
499 if (model()->is_pasting()) {
500 base::string16 pastedText = base::SysNSStringToUTF16([field_ text]);
501 base::string16 newText = OmniboxView::SanitizeTextForPaste(pastedText);
502 if (pastedText != newText) {
503 [field_ setText:SysUTF16ToNSString(newText)];
504 }
505 }
506
507 // Clear the autocomplete text, since the omnibox model does not expect to see
508 // it in OnAfterPossibleChange(). Clearing the text here should not cause
509 // flicker as the UI will not get a chance to redraw before the new
510 // autocomplete text is set by the model.
511 [field_ clearAutocompleteText];
512 [field_ setClearingPreEditText:NO];
513
514 // Generally do not notify the autocomplete system of a text change unless the
515 // change was a direct result of a user event. One exception is if the marked
516 // text changed, which could happen through a delayed IME recognition
517 // callback. iOS4 does not provide API access to marked text, so use
518 // |IsImeComposing()| as a proxy.
519 bool proceed_without_user_event = false;
520
521 // The IME exception does not work for Korean text, because Korean does not
522 // seem to ever have marked text. It simply replaces or modifies previous
523 // characters as you type. Always proceed without user input on iOS7 if the
524 // Korean keyboard is currently active. (This Korean exception is not
525 // possible on iOS6 due to crbug.com/285294.)
526 NSString* current_language = [[field_ textInputMode] primaryLanguage];
527
528 if ([current_language hasPrefix:@"ko-"]) {
529 proceed_without_user_event = true;
530 } else {
531 NSString* current_marked_text = [field_ markedText];
532
533 // The IME exception kicks in if the current marked text is not equal to the
534 // previous marked text. Two nil strings should be considered equal, so
535 // There is logic to avoid calling into isEqual: in that case.
536 proceed_without_user_event =
537 (marked_text_before_change_ || current_marked_text) &&
538 ![current_marked_text isEqual:marked_text_before_change_];
539 }
540
541 if (!processing_user_event && !proceed_without_user_event)
542 return;
543
544 // TODO(crbug.com/564599): OnAfterPossibleChange() now takes an argument. It
545 // use to not take an argument and was defaulting to false, so as it is
546 // unclear what the correct value is, using what was that before seems
547 // consistent.
548 OnAfterPossibleChange(false);
549 OnBeforePossibleChange();
550 }
551
552 void OmniboxViewIOS::OnAccept() {
553 // It may be unsafe to modify the contents of the field.
554 // Note that by happy coincidence, the |textFieldDidReturn| delegate method
555 // always returns NO, which is the desired behavior in this situation.
556 if (ShouldIgnoreUserInputDueToPendingVoiceSearch()) {
557 return;
558 }
559
560 base::RecordAction(UserMetricsAction("MobileOmniboxUse"));
561
562 WindowOpenDisposition disposition = WindowOpenDisposition::CURRENT_TAB;
563 model()->AcceptInput(disposition, false);
564 RevertAll();
565 }
566
567 void OmniboxViewIOS::OnClear() {
568 [field_ clearAutocompleteText];
569 [field_ exitPreEditState];
570 }
571
572 bool OmniboxViewIOS::OnCopy() {
573 UIPasteboard* board = [UIPasteboard generalPasteboard];
574 UITextRange* selected_range = [field_ selectedTextRange];
575 base::string16 text =
576 base::SysNSStringToUTF16([field_ textInRange:selected_range]);
577
578 UITextPosition* start = [field_ beginningOfDocument];
579 UITextPosition* end = [field_ endOfDocument];
580 BOOL is_select_all = ([field_ comparePosition:[selected_range start]
581 toPosition:start] == NSOrderedSame) &&
582 ([field_ comparePosition:[selected_range end]
583 toPosition:end] == NSOrderedSame);
584
585 // The following call to |-offsetFromPosition:toPosition:| gives the offset in
586 // terms of the number of "visible characters." The documentation does not
587 // specify whether this means glyphs or UTF16 chars. This does not matter for
588 // the current implementation of AdjustTextForCopy(), but it may become an
589 // issue at some point.
590 NSInteger start_location =
591 [field_ offsetFromPosition:start toPosition:[selected_range start]];
592
593 GURL url;
594 bool write_url = false;
595 // Don't adjust the text (e.g. add http://) if the omnibox is currently
596 // showing non-URL text (e.g. search terms instead of the URL).
597 if (!CanCopyURL()) {
598 model()->AdjustTextForCopy(start_location, is_select_all, &text, &url,
599 &write_url);
600 }
601
602 // Create the pasteboard item manually because the pasteboard expects a single
603 // item with multiple representations. This is expressed as a single
604 // NSDictionary with multiple keys, one for each representation.
605 NSMutableDictionary* item = [NSMutableDictionary dictionaryWithCapacity:2];
606 [item setObject:base::SysUTF16ToNSString(text)
607 forKey:(NSString*)kUTTypePlainText];
608
609 if (write_url)
610 [item setObject:net::NSURLWithGURL(url) forKey:(NSString*)kUTTypeURL];
611
612 board.items = [NSArray arrayWithObject:item];
613 return true;
614 }
615
616 bool OmniboxViewIOS::OnCopyURL() {
617 // Create the pasteboard item manually because the pasteboard expects a single
618 // item with multiple representations. This is expressed as a single
619 // NSDictionary with multiple keys, one for each representation.
620 GURL url = controller_->GetToolbarModel()->GetURL();
621 NSDictionary* item = @{
622 (NSString*)kUTTypePlainText : base::SysUTF8ToNSString(url.spec()),
623 (NSString*)kUTTypeURL : net::NSURLWithGURL(url)
624 };
625 [UIPasteboard generalPasteboard].items = [NSArray arrayWithObject:item];
626 return true;
627 }
628
629 bool OmniboxViewIOS::CanCopyURL() {
630 return false;
631 }
632
633 void OmniboxViewIOS::WillPaste() {
634 model()->OnPaste();
635 }
636
637 NSAttributedString* OmniboxViewIOS::ApplyTextAttributes(
638 const base::string16& text) {
639 NSMutableAttributedString* as = [[[NSMutableAttributedString alloc]
640 initWithString:base::SysUTF16ToNSString(text)] autorelease];
641 url::Component scheme, host;
642 AutocompleteInput::ParseForEmphasizeComponents(
643 text, AutocompleteSchemeClassifierImpl(), &scheme, &host);
644
645 const bool emphasize = model()->CurrentTextIsURL() && (host.len > 0);
646 if (emphasize) {
647 [as addAttribute:NSForegroundColorAttributeName
648 value:BaseTextColor()
649 range:NSMakeRange(0, [as length])];
650
651 [as addAttribute:NSForegroundColorAttributeName
652 value:[field_ displayedTextColor]
653 range:ComponentToNSRange(host)];
654
655 if (scheme.len > 0) {
656 UIColor* color = nil;
657 switch (controller_->GetToolbarModel()->GetSecurityLevel(false)) {
658 case security_state::NONE:
659 break;
660 case security_state::SECURITY_WARNING:
661 // Don't color strikethrough schemes. See https://crbug.com/635004#c6
662 if (![field_ incognito])
663 color = WarningTextColor();
664 [as addAttribute:NSStrikethroughStyleAttributeName
665 value:[NSNumber numberWithInteger:NSUnderlineStyleSingle]
666 range:ComponentToNSRange(scheme)];
667 break;
668 case security_state::SECURE:
669 case security_state::EV_SECURE:
670 color = [field_ incognito] ? IncognitoSecureTextColor()
671 : SecureTextColor();
672 break;
673 case security_state::DANGEROUS:
674 // Don't color strikethrough schemes. See https://crbug.com/635004#c6
675 if (![field_ incognito])
676 color = ErrorTextColor();
677 [as addAttribute:NSStrikethroughStyleAttributeName
678 value:[NSNumber numberWithInteger:NSUnderlineStyleSingle]
679 range:ComponentToNSRange(scheme)];
680 break;
681 case security_state::HTTP_SHOW_WARNING:
682 case security_state::SECURE_WITH_POLICY_INSTALLED_CERT:
683 NOTREACHED();
684 }
685 if (color) {
686 [as addAttribute:NSForegroundColorAttributeName
687 value:color
688 range:ComponentToNSRange(scheme)];
689 }
690 }
691 }
692 return as;
693 }
694
695 void OmniboxViewIOS::UpdateAppearance() {
696 // If Siri is thinking, treat that as user input being in progress. It is
697 // unsafe to modify the text field while voice entry is pending.
698 if (model()->UpdatePermanentText()) {
699 // Revert everything to the baseline look.
700 RevertAll();
701 } else if (!model()->has_focus() &&
702 !ShouldIgnoreUserInputDueToPendingVoiceSearch()) {
703 // Only update the chip text if the omnibox is not currently focused.
704 [field_ setChipText:@""];
705
706 // Even if the change wasn't "user visible" to the model, it still may be
707 // necessary to re-color to the URL string. Only do this if the omnibox is
708 // not currently focused.
709 base::string16 text =
710 controller_->GetToolbarModel()->GetFormattedURL(nullptr);
711 NSAttributedString* as = ApplyTextAttributes(text);
712 [field_ setText:as userTextLength:[as length]];
713 }
714 }
715
716 void OmniboxViewIOS::OnDeleteBackward() {
717 if ([field_ text].length == 0) {
718 // If the user taps backspace while the pre-edit text is showing,
719 // OnWillChange is invoked before this method and sets the text to an empty
720 // string, so use the |clearingPreEditText| to determine if the chip should
721 // be cleared or not.
722 if ([field_ clearingPreEditText]) {
723 // In the case where backspace is tapped while in pre-edit mode,
724 // OnWillChange is called but OnDidChange is never called so ensure the
725 // clearingPreEditText flag is set to false again.
726 [field_ setClearingPreEditText:NO];
727 // Explicitly set the input-in-progress flag. Normally this is set via
728 // in model()->OnAfterPossibleChange, but in this case the text has been
729 // set to the empty string by OnWillChange so when OnAfterPossibleChange
730 // checks if the text has changed it does not see any difference so it
731 // never sets the input-in-progress flag.
732 model()->SetInputInProgress(YES);
733 } else {
734 RemoveQueryRefinementChip();
735 }
736 }
737 }
738
739 void OmniboxViewIOS::ClearText() {
740 // It may be unsafe to modify the contents of the field.
741 if (ShouldIgnoreUserInputDueToPendingVoiceSearch()) {
742 return;
743 }
744
745 // Ensure omnibox is first responder. This will bring up the keyboard so the
746 // user can start typing a new query.
747 if (![field_ isFirstResponder])
748 [field_ becomeFirstResponder];
749 if ([field_ text].length == 0) {
750 // If |field_| is empty, remove the query refinement chip.
751 RemoveQueryRefinementChip();
752 } else {
753 // Otherwise, just remove the text in the omnibox.
754 // Since iOS 6, calling |setText| does not trigger |textDidChange| so it
755 // must be called explicitly.
756 OnClear();
757 [field_ setText:@""];
758 OnDidChange(YES);
759 }
760 // Calling OnDidChange() can trigger a scroll event, which removes focus from
761 // the omnibox.
762 [field_ becomeFirstResponder];
763 }
764
765 void OmniboxViewIOS::RemoveQueryRefinementChip() {
766 [field_ setChipText:nil];
767 controller_->OnChanged();
768 }
769
770 bool OmniboxViewIOS::ShouldIgnoreUserInputDueToPendingVoiceSearch() {
771 // When the response of the iOS voice entry is pending a spinning wheel is
772 // visible. The spinner's location is marked in [self text] as a Unicode
773 // "Object Replacement Character".
774 // http://www.fileformat.info/info/unicode/char/fffc/index.htm
775 NSString* objectReplacementChar =
776 [NSString stringWithFormat:@"%C", (unichar)0xFFFC];
777 return [[field_ text] rangeOfString:objectReplacementChar].length > 0;
778 }
779
780 void OmniboxViewIOS::SetLeftImage(int imageId) {
781 [field_ setPlaceholderImage:imageId];
782 }
783
784 void OmniboxViewIOS::HideKeyboardAndEndEditing() {
785 [field_ resignFirstResponder];
786
787 // Handle the case where a phone-format ombniox has already resigned first
788 // responder because the popup was scrolled. If the model still has focus,
789 // dismiss again. This should only happen on iPhone.
790 if (model()->has_focus()) {
791 DCHECK(!IsIPadIdiom());
792 this->OnDidEndEditing();
793 }
794 }
795
796 void OmniboxViewIOS::HideKeyboard() {
797 [field_ resignFirstResponder];
798 }
799
800 void OmniboxViewIOS::FocusOmnibox() {
801 [field_ becomeFirstResponder];
802 }
803
804 // Called whenever the popup results change. May trigger the URLs of
805 // autocomplete results to be prerendered or prefetched.
806 void OmniboxViewIOS::OnPopupResultsChanged(const AutocompleteResult& result) {
807 if (!ignore_popup_updates_ && !result.empty()) {
808 const AutocompleteMatch& match = result.match_at(0);
809 bool is_inline_autocomplete = !match.inline_autocompletion.empty();
810
811 // TODO(rohitrao): When prerendering the result of a paste operation, we
812 // should change the transition to LINK instead of TYPED. b/6143631.
813
814 // Only prerender HISTORY_URL matches, which come from the history DB. Do
815 // not prerender other types of matches, including matches from the search
816 // provider.
817 if (is_inline_autocomplete &&
818 match.type == AutocompleteMatchType::HISTORY_URL) {
819 ui::PageTransition transition = ui::PageTransitionFromInt(
820 match.transition | ui::PAGE_TRANSITION_FROM_ADDRESS_BAR);
821 [preloader_ prerenderURL:match.destination_url
822 referrer:web::Referrer()
823 transition:transition
824 immediately:is_inline_autocomplete];
825 } else {
826 [preloader_ cancelPrerender];
827 }
828
829 // If the first autocomplete result is a search suggestion, prefetch the
830 // corresponding search result page.
831 if (match.type == AutocompleteMatchType::SEARCH_SUGGEST) {
832 ui::PageTransition transition = ui::PageTransitionFromInt(
833 match.transition | ui::PAGE_TRANSITION_FROM_ADDRESS_BAR);
834 [preloader_ prefetchURL:match.destination_url transition:transition];
835 } else {
836 [preloader_ cancelPrefetch];
837 }
838 }
839 }
840
841 BOOL OmniboxViewIOS::IsPopupOpen() {
842 return popup_view_->IsOpen();
843 }
844
845 int OmniboxViewIOS::GetIcon(bool offlinePage) const {
846 if (!IsEditingOrEmpty()) {
847 if (offlinePage) {
848 return IDR_IOS_OMNIBOX_OFFLINE;
849 }
850 return GetIconForSecurityState(
851 controller()->GetToolbarModel()->GetSecurityLevel(false));
852 }
853
854 return GetIconForAutocompleteMatchType(
855 model() ? model()->CurrentTextType()
856 : AutocompleteMatchType::URL_WHAT_YOU_TYPED,
857 /* is_starred */ false, /* is_incognito */ false);
858 }
859
860 int OmniboxViewIOS::GetOmniboxTextLength() const {
861 return [field_ displayedText].length();
862 }
863
864 void OmniboxViewIOS::EmphasizeURLComponents() {
865 // TODO(rohitrao): Implement this function using code like below. This code
866 // is being left out for now because it was not present before the OmniboxView
867 // rewrite.
868 #if 0
869 // When editing is in progress, the url text is not colored, so there is
870 // nothing to emphasize. (Calling SetText() in that situation would also be
871 // harmful, as it would reset the carat position to the end of the text.)
872 if (!IsEditingOrEmpty())
873 SetText(GetText());
874 #endif
875 }
OLDNEW
« no previous file with comments | « ios/chrome/browser/ui/omnibox/omnibox_view_ios.h ('k') | ios/chrome/browser/ui/omnibox/page_info_model.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698