Home | History | Annotate | Download | only in omnibox
      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 "chrome/browser/ui/cocoa/omnibox/omnibox_view_mac.h"
      6 
      7 #include <Carbon/Carbon.h>  // kVK_Return
      8 
      9 #include "base/mac/foundation_util.h"
     10 #include "base/metrics/histogram.h"
     11 #include "base/strings/string_util.h"
     12 #include "base/strings/sys_string_conversions.h"
     13 #include "base/strings/utf_string_conversions.h"
     14 #include "chrome/browser/autocomplete/autocomplete_input.h"
     15 #include "chrome/browser/autocomplete/autocomplete_match.h"
     16 #include "chrome/browser/browser_process.h"
     17 #include "chrome/browser/search/search.h"
     18 #include "chrome/browser/ui/cocoa/location_bar/autocomplete_text_field_cell.h"
     19 #import "chrome/browser/ui/cocoa/location_bar/autocomplete_text_field_editor.h"
     20 #include "chrome/browser/ui/cocoa/omnibox/omnibox_popup_view_mac.h"
     21 #include "chrome/browser/ui/omnibox/omnibox_edit_controller.h"
     22 #include "chrome/browser/ui/omnibox/omnibox_popup_model.h"
     23 #include "chrome/browser/ui/toolbar/toolbar_model.h"
     24 #include "content/public/browser/web_contents.h"
     25 #include "extensions/common/constants.h"
     26 #include "grit/generated_resources.h"
     27 #include "grit/theme_resources.h"
     28 #import "third_party/mozilla/NSPasteboard+Utils.h"
     29 #import "ui/base/cocoa/cocoa_base_utils.h"
     30 #include "ui/base/clipboard/clipboard.h"
     31 #include "ui/base/resource/resource_bundle.h"
     32 #include "ui/gfx/font.h"
     33 #include "ui/gfx/font_list.h"
     34 #include "ui/gfx/geometry/rect.h"
     35 
     36 using content::WebContents;
     37 
     38 // Focus-handling between |field_| and model() is a bit subtle.
     39 // Other platforms detect change of focus, which is inconvenient
     40 // without subclassing NSTextField (even with a subclass, the use of a
     41 // field editor may complicate things).
     42 //
     43 // model() doesn't actually do anything when it gains focus, it just
     44 // initializes.  Visible activity happens only after the user edits.
     45 // NSTextField delegate receives messages around starting and ending
     46 // edits, so that suffices to catch focus changes.  Since all calls
     47 // into model() start from OmniboxViewMac, in the worst case
     48 // we can add code to sync up the sense of focus as needed.
     49 //
     50 // I've added DCHECK(IsFirstResponder()) in the places which I believe
     51 // should only be reachable when |field_| is being edited.  If these
     52 // fire, it probably means someone unexpected is calling into
     53 // model().
     54 //
     55 // Other platforms don't appear to have the sense of "key window" that
     56 // Mac does (I believe their fields lose focus when the window loses
     57 // focus).  Rather than modifying focus outside the control's edit
     58 // scope, when the window resigns key the autocomplete popup is
     59 // closed.  model() still believes it has focus, and the popup will
     60 // be regenerated on the user's next edit.  That seems to match how
     61 // things work on other platforms.
     62 
     63 namespace {
     64 
     65 // TODO(shess): This is ugly, find a better way.  Using it right now
     66 // so that I can crib from gtk and still be able to see that I'm using
     67 // the same values easily.
     68 NSColor* ColorWithRGBBytes(int rr, int gg, int bb) {
     69   DCHECK_LE(rr, 255);
     70   DCHECK_LE(bb, 255);
     71   DCHECK_LE(gg, 255);
     72   return [NSColor colorWithCalibratedRed:static_cast<float>(rr)/255.0
     73                                    green:static_cast<float>(gg)/255.0
     74                                     blue:static_cast<float>(bb)/255.0
     75                                    alpha:1.0];
     76 }
     77 
     78 NSColor* HostTextColor() {
     79   return [NSColor blackColor];
     80 }
     81 NSColor* BaseTextColor() {
     82   return [NSColor darkGrayColor];
     83 }
     84 NSColor* SecureSchemeColor() {
     85   return ColorWithRGBBytes(0x07, 0x95, 0x00);
     86 }
     87 NSColor* SecurityErrorSchemeColor() {
     88   return ColorWithRGBBytes(0xa2, 0x00, 0x00);
     89 }
     90 
     91 const char kOmniboxViewMacStateKey[] = "OmniboxViewMacState";
     92 
     93 // Store's the model and view state across tab switches.
     94 struct OmniboxViewMacState : public base::SupportsUserData::Data {
     95   OmniboxViewMacState(const OmniboxEditModel::State model_state,
     96                       const bool has_focus,
     97                       const NSRange& selection)
     98       : model_state(model_state),
     99         has_focus(has_focus),
    100         selection(selection) {
    101   }
    102   virtual ~OmniboxViewMacState() {}
    103 
    104   const OmniboxEditModel::State model_state;
    105   const bool has_focus;
    106   const NSRange selection;
    107 };
    108 
    109 // Accessors for storing and getting the state from the tab.
    110 void StoreStateToTab(WebContents* tab,
    111                      OmniboxViewMacState* state) {
    112   tab->SetUserData(kOmniboxViewMacStateKey, state);
    113 }
    114 const OmniboxViewMacState* GetStateFromTab(const WebContents* tab) {
    115   return static_cast<OmniboxViewMacState*>(
    116       tab->GetUserData(&kOmniboxViewMacStateKey));
    117 }
    118 
    119 // Helper to make converting url ranges to NSRange easier to
    120 // read.
    121 NSRange ComponentToNSRange(const url::Component& component) {
    122   return NSMakeRange(static_cast<NSInteger>(component.begin),
    123                      static_cast<NSInteger>(component.len));
    124 }
    125 
    126 }  // namespace
    127 
    128 // static
    129 NSImage* OmniboxViewMac::ImageForResource(int resource_id) {
    130   ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
    131   return rb.GetNativeImageNamed(resource_id).ToNSImage();
    132 }
    133 
    134 // static
    135 NSColor* OmniboxViewMac::SuggestTextColor() {
    136   return [NSColor colorWithCalibratedWhite:0.0 alpha:0.5];
    137 }
    138 
    139 OmniboxViewMac::OmniboxViewMac(OmniboxEditController* controller,
    140                                Profile* profile,
    141                                CommandUpdater* command_updater,
    142                                AutocompleteTextField* field)
    143     : OmniboxView(profile, controller, command_updater),
    144       popup_view_(new OmniboxPopupViewMac(this, model(), field)),
    145       field_(field),
    146       saved_temporary_selection_(NSMakeRange(0, 0)),
    147       selection_before_change_(NSMakeRange(0, 0)),
    148       marked_range_before_change_(NSMakeRange(0, 0)),
    149       delete_was_pressed_(false),
    150       delete_at_end_pressed_(false),
    151       in_coalesced_update_block_(false),
    152       do_coalesced_text_update_(false),
    153       do_coalesced_range_update_(false) {
    154   [field_ setObserver:this];
    155 
    156   // Needed so that editing doesn't lose the styling.
    157   [field_ setAllowsEditingTextAttributes:YES];
    158 
    159   // Get the appropriate line height for the font that we use.
    160   base::scoped_nsobject<NSLayoutManager> layoutManager(
    161       [[NSLayoutManager alloc] init]);
    162   [layoutManager setUsesScreenFonts:YES];
    163 }
    164 
    165 OmniboxViewMac::~OmniboxViewMac() {
    166   // Destroy popup view before this object in case it tries to call us
    167   // back in the destructor.
    168   popup_view_.reset();
    169 
    170   // Disconnect from |field_|, it outlives this object.
    171   [field_ setObserver:NULL];
    172 }
    173 
    174 void OmniboxViewMac::SaveStateToTab(WebContents* tab) {
    175   DCHECK(tab);
    176 
    177   const bool hasFocus = [field_ currentEditor] ? true : false;
    178 
    179   NSRange range;
    180   if (hasFocus) {
    181     range = GetSelectedRange();
    182   } else {
    183     // If we are not focussed, there is no selection.  Manufacture
    184     // something reasonable in case it starts to matter in the future.
    185     range = NSMakeRange(0, GetTextLength());
    186   }
    187 
    188   OmniboxViewMacState* state =
    189       new OmniboxViewMacState(model()->GetStateForTabSwitch(), hasFocus, range);
    190   StoreStateToTab(tab, state);
    191 }
    192 
    193 void OmniboxViewMac::OnTabChanged(const WebContents* web_contents) {
    194   const OmniboxViewMacState* state = GetStateFromTab(web_contents);
    195   model()->RestoreState(state ? &state->model_state : NULL);
    196   // Restore focus and selection if they were present when the tab
    197   // was switched away.
    198   if (state && state->has_focus) {
    199     // TODO(shess): Unfortunately, there is no safe way to update
    200     // this because TabStripController -selectTabWithContents:* is
    201     // also messing with focus.  Both parties need to agree to
    202     // store existing state before anyone tries to setup the new
    203     // state.  Anyhow, it would look something like this.
    204 #if 0
    205     [[field_ window] makeFirstResponder:field_];
    206     [[field_ currentEditor] setSelectedRange:state->selection];
    207 #endif
    208   }
    209 }
    210 
    211 void OmniboxViewMac::Update() {
    212   if (chrome::ShouldDisplayOriginChip()) {
    213     NSDictionary* placeholder_attributes = @{
    214       NSFontAttributeName : GetFieldFont(gfx::Font::NORMAL),
    215       NSForegroundColorAttributeName : [NSColor disabledControlTextColor]
    216     };
    217     base::scoped_nsobject<NSMutableAttributedString> placeholder_text(
    218         [[NSMutableAttributedString alloc]
    219             initWithString:base::SysUTF16ToNSString(GetHintText())
    220                 attributes:placeholder_attributes]);
    221     [[field_ cell] setPlaceholderAttributedString:placeholder_text];
    222   }
    223   if (model()->UpdatePermanentText()) {
    224     // Something visibly changed.  Re-enable URL replacement.
    225     controller()->GetToolbarModel()->set_url_replacement_enabled(true);
    226     model()->UpdatePermanentText();
    227 
    228     // Restore everything to the baseline look.
    229     RevertAll();
    230 
    231     // TODO(shess): Figure out how this case is used, to make sure
    232     // we're getting the selection and popup right.
    233   } else {
    234     // TODO(shess): This corresponds to _win and _gtk, except those
    235     // guard it with a test for whether the security level changed.
    236     // But AFAICT, that can only change if the text changed, and that
    237     // code compares the toolbar model security level with the local
    238     // security level.  Dig in and figure out why this isn't a no-op
    239     // that should go away.
    240     EmphasizeURLComponents();
    241   }
    242 }
    243 
    244 void OmniboxViewMac::OpenMatch(const AutocompleteMatch& match,
    245                                WindowOpenDisposition disposition,
    246                                const GURL& alternate_nav_url,
    247                                const base::string16& pasted_text,
    248                                size_t selected_line) {
    249   // Coalesce text and selection updates from the following function. If we
    250   // don't do this, the user may see intermediate states as brief flickers.
    251   in_coalesced_update_block_ = true;
    252   OmniboxView::OpenMatch(
    253       match, disposition, alternate_nav_url, pasted_text, selected_line);
    254   in_coalesced_update_block_ = false;
    255   if (do_coalesced_text_update_)
    256     SetText(coalesced_text_update_);
    257   do_coalesced_text_update_ = false;
    258   if (do_coalesced_range_update_)
    259     SetSelectedRange(coalesced_range_update_);
    260   do_coalesced_range_update_ = false;
    261 }
    262 
    263 base::string16 OmniboxViewMac::GetText() const {
    264   return base::SysNSStringToUTF16([field_ stringValue]);
    265 }
    266 
    267 NSRange OmniboxViewMac::GetSelectedRange() const {
    268   return [[field_ currentEditor] selectedRange];
    269 }
    270 
    271 NSRange OmniboxViewMac::GetMarkedRange() const {
    272   DCHECK([field_ currentEditor]);
    273   return [(NSTextView*)[field_ currentEditor] markedRange];
    274 }
    275 
    276 void OmniboxViewMac::SetSelectedRange(const NSRange range) {
    277   if (in_coalesced_update_block_) {
    278     do_coalesced_range_update_ = true;
    279     coalesced_range_update_ = range;
    280     return;
    281   }
    282 
    283   // This can be called when we don't have focus.  For instance, when
    284   // the user clicks the "Go" button.
    285   if (model()->has_focus()) {
    286     // TODO(shess): If model() thinks we have focus, this should not
    287     // be necessary.  Try to convert to DCHECK(IsFirstResponder()).
    288     if (![field_ currentEditor]) {
    289       [[field_ window] makeFirstResponder:field_];
    290     }
    291 
    292     // TODO(shess): What if it didn't get first responder, and there is
    293     // no field editor?  This will do nothing.  Well, at least it won't
    294     // crash.  Think of something more productive to do, or prove that
    295     // it cannot occur and DCHECK appropriately.
    296     [[field_ currentEditor] setSelectedRange:range];
    297   }
    298 }
    299 
    300 void OmniboxViewMac::SetWindowTextAndCaretPos(const base::string16& text,
    301                                               size_t caret_pos,
    302                                               bool update_popup,
    303                                               bool notify_text_changed) {
    304   DCHECK_LE(caret_pos, text.size());
    305   SetTextAndSelectedRange(text, NSMakeRange(caret_pos, 0));
    306 
    307   if (update_popup)
    308     UpdatePopup();
    309 
    310   if (notify_text_changed)
    311     TextChanged();
    312 }
    313 
    314 void OmniboxViewMac::SetForcedQuery() {
    315   // We need to do this first, else |SetSelectedRange()| won't work.
    316   FocusLocation(true);
    317 
    318   const base::string16 current_text(GetText());
    319   const size_t start = current_text.find_first_not_of(base::kWhitespaceUTF16);
    320   if (start == base::string16::npos || (current_text[start] != '?')) {
    321     SetUserText(base::ASCIIToUTF16("?"));
    322   } else {
    323     NSRange range = NSMakeRange(start + 1, current_text.size() - start - 1);
    324     [[field_ currentEditor] setSelectedRange:range];
    325   }
    326 }
    327 
    328 bool OmniboxViewMac::IsSelectAll() const {
    329   if (![field_ currentEditor])
    330     return true;
    331   const NSRange all_range = NSMakeRange(0, GetTextLength());
    332   return NSEqualRanges(all_range, GetSelectedRange());
    333 }
    334 
    335 bool OmniboxViewMac::DeleteAtEndPressed() {
    336   return delete_at_end_pressed_;
    337 }
    338 
    339 void OmniboxViewMac::GetSelectionBounds(base::string16::size_type* start,
    340                                         base::string16::size_type* end) const {
    341   if (![field_ currentEditor]) {
    342     *start = *end = 0;
    343     return;
    344   }
    345 
    346   const NSRange selected_range = GetSelectedRange();
    347   *start = static_cast<size_t>(selected_range.location);
    348   *end = static_cast<size_t>(NSMaxRange(selected_range));
    349 }
    350 
    351 void OmniboxViewMac::SelectAll(bool reversed) {
    352   // TODO(shess): Figure out what |reversed| implies.  The gtk version
    353   // has it imply inverting the selection front to back, but I don't
    354   // even know if that makes sense for Mac.
    355 
    356   // TODO(shess): Verify that we should be stealing focus at this
    357   // point.
    358   SetSelectedRange(NSMakeRange(0, GetTextLength()));
    359 }
    360 
    361 void OmniboxViewMac::RevertAll() {
    362   OmniboxView::RevertAll();
    363   [field_ clearUndoChain];
    364 }
    365 
    366 void OmniboxViewMac::UpdatePopup() {
    367   model()->SetInputInProgress(true);
    368   if (!model()->has_focus())
    369     return;
    370 
    371   // Comment copied from OmniboxViewWin::UpdatePopup():
    372   // Don't inline autocomplete when:
    373   //   * The user is deleting text
    374   //   * The caret/selection isn't at the end of the text
    375   //   * The user has just pasted in something that replaced all the text
    376   //   * The user is trying to compose something in an IME
    377   bool prevent_inline_autocomplete = IsImeComposing();
    378   NSTextView* editor = (NSTextView*)[field_ currentEditor];
    379   if (editor) {
    380     if (NSMaxRange([editor selectedRange]) < [[editor textStorage] length])
    381       prevent_inline_autocomplete = true;
    382   }
    383 
    384   model()->StartAutocomplete([editor selectedRange].length != 0,
    385                             prevent_inline_autocomplete);
    386 }
    387 
    388 void OmniboxViewMac::CloseOmniboxPopup() {
    389   // Call both base class methods.
    390   ClosePopup();
    391   OmniboxView::CloseOmniboxPopup();
    392 }
    393 
    394 void OmniboxViewMac::SetFocus() {
    395   FocusLocation(false);
    396   model()->SetCaretVisibility(true);
    397 }
    398 
    399 void OmniboxViewMac::ApplyCaretVisibility() {
    400   [[field_ cell] setHideFocusState:!model()->is_caret_visible()
    401                             ofView:field_];
    402 }
    403 
    404 void OmniboxViewMac::SetText(const base::string16& display_text) {
    405   SetTextInternal(display_text);
    406 }
    407 
    408 void OmniboxViewMac::SetTextInternal(const base::string16& display_text) {
    409   if (in_coalesced_update_block_) {
    410     do_coalesced_text_update_ = true;
    411     coalesced_text_update_ = display_text;
    412     // Don't do any selection changes, since they apply to the previous text.
    413     do_coalesced_range_update_ = false;
    414     return;
    415   }
    416 
    417   NSString* ss = base::SysUTF16ToNSString(display_text);
    418   NSMutableAttributedString* as =
    419       [[[NSMutableAttributedString alloc] initWithString:ss] autorelease];
    420 
    421   ApplyTextAttributes(display_text, as);
    422   [field_ setAttributedStringValue:as];
    423 
    424   // TODO(shess): This may be an appropriate place to call:
    425   //   model()->OnChanged();
    426   // In the current implementation, this tells LocationBarViewMac to
    427   // mess around with model() and update |field_|.  Unfortunately,
    428   // when I look at our peer implementations, it's not entirely clear
    429   // to me if this is safe.  SetTextInternal() is sort of an utility method,
    430   // and different callers sometimes have different needs.  Research
    431   // this issue so that it can be added safely.
    432 
    433   // TODO(shess): Also, consider whether this code couldn't just
    434   // manage things directly.  Windows uses a series of overlaid view
    435   // objects to accomplish the hinting stuff that OnChanged() does, so
    436   // it makes sense to have it in the controller that lays those
    437   // things out.  Mac instead pushes the support into a custom
    438   // text-field implementation.
    439 }
    440 
    441 void OmniboxViewMac::SetTextAndSelectedRange(const base::string16& display_text,
    442                                              const NSRange range) {
    443   SetText(display_text);
    444   SetSelectedRange(range);
    445 }
    446 
    447 void OmniboxViewMac::EmphasizeURLComponents() {
    448   NSTextView* editor = (NSTextView*)[field_ currentEditor];
    449   // If the autocomplete text field is in editing mode, then we can just change
    450   // its attributes through its editor. Otherwise, we simply reset its content.
    451   if (editor) {
    452     NSTextStorage* storage = [editor textStorage];
    453     [storage beginEditing];
    454 
    455     // Clear the existing attributes from the text storage, then
    456     // overlay the appropriate Omnibox attributes.
    457     [storage setAttributes:[NSDictionary dictionary]
    458                      range:NSMakeRange(0, [storage length])];
    459     ApplyTextAttributes(GetText(), storage);
    460 
    461     [storage endEditing];
    462 
    463     // This function can be called during the editor's -resignFirstResponder. If
    464     // that happens, |storage| and |field_| will not be synced automatically any
    465     // more. Calling -stringValue ensures that |field_| reflects the changes to
    466     // |storage|.
    467     [field_ stringValue];
    468   } else {
    469     SetText(GetText());
    470   }
    471 }
    472 
    473 void OmniboxViewMac::ApplyTextAttributes(const base::string16& display_text,
    474                                          NSMutableAttributedString* as) {
    475   NSUInteger as_length = [as length];
    476   NSRange as_entire_string = NSMakeRange(0, as_length);
    477 
    478   [as addAttribute:NSFontAttributeName value:GetFieldFont(gfx::Font::NORMAL)
    479              range:as_entire_string];
    480 
    481   // A kinda hacky way to add breaking at periods. This is what Safari does.
    482   // This works for IDNs too, despite the "en_US".
    483   [as addAttribute:@"NSLanguage" value:@"en_US_POSIX"
    484              range:as_entire_string];
    485 
    486   // Make a paragraph style locking in the standard line height as the maximum,
    487   // otherwise the baseline may shift "downwards".
    488   base::scoped_nsobject<NSMutableParagraphStyle> paragraph_style(
    489       [[NSMutableParagraphStyle alloc] init]);
    490   CGFloat line_height = [[field_ cell] lineHeight];
    491   [paragraph_style setMaximumLineHeight:line_height];
    492   [paragraph_style setMinimumLineHeight:line_height];
    493   [paragraph_style setLineBreakMode:NSLineBreakByTruncatingTail];
    494   [as addAttribute:NSParagraphStyleAttributeName value:paragraph_style
    495              range:as_entire_string];
    496 
    497   url::Component scheme, host;
    498   AutocompleteInput::ParseForEmphasizeComponents(
    499       display_text, &scheme, &host);
    500   bool grey_out_url = display_text.substr(scheme.begin, scheme.len) ==
    501       base::UTF8ToUTF16(extensions::kExtensionScheme);
    502   if (model()->CurrentTextIsURL() &&
    503       (host.is_nonempty() || grey_out_url)) {
    504     [as addAttribute:NSForegroundColorAttributeName value:BaseTextColor()
    505                range:as_entire_string];
    506 
    507     if (!grey_out_url) {
    508       [as addAttribute:NSForegroundColorAttributeName value:HostTextColor()
    509                range:ComponentToNSRange(host)];
    510     }
    511   }
    512 
    513   // TODO(shess): GTK has this as a member var, figure out why.
    514   // [Could it be to not change if no change?  If so, I'm guessing
    515   // AppKit may already handle that.]
    516   const ToolbarModel::SecurityLevel security_level =
    517       controller()->GetToolbarModel()->GetSecurityLevel(false);
    518 
    519   // Emphasize the scheme for security UI display purposes (if necessary).
    520   if (!model()->user_input_in_progress() && model()->CurrentTextIsURL() &&
    521       scheme.is_nonempty() && (security_level != ToolbarModel::NONE)) {
    522     NSColor* color;
    523     if (security_level == ToolbarModel::EV_SECURE ||
    524         security_level == ToolbarModel::SECURE) {
    525       color = SecureSchemeColor();
    526     } else if (security_level == ToolbarModel::SECURITY_ERROR) {
    527       color = SecurityErrorSchemeColor();
    528       // Add a strikethrough through the scheme.
    529       [as addAttribute:NSStrikethroughStyleAttributeName
    530                  value:[NSNumber numberWithInt:NSUnderlineStyleSingle]
    531                  range:ComponentToNSRange(scheme)];
    532     } else if (security_level == ToolbarModel::SECURITY_WARNING) {
    533       color = BaseTextColor();
    534     } else {
    535       NOTREACHED();
    536       color = BaseTextColor();
    537     }
    538     [as addAttribute:NSForegroundColorAttributeName value:color
    539                range:ComponentToNSRange(scheme)];
    540   }
    541 }
    542 
    543 void OmniboxViewMac::OnTemporaryTextMaybeChanged(
    544     const base::string16& display_text,
    545     bool save_original_selection,
    546     bool notify_text_changed) {
    547   if (save_original_selection)
    548     saved_temporary_selection_ = GetSelectedRange();
    549 
    550   SetWindowTextAndCaretPos(display_text, display_text.size(), false, false);
    551   if (notify_text_changed)
    552     model()->OnChanged();
    553   [field_ clearUndoChain];
    554 }
    555 
    556 bool OmniboxViewMac::OnInlineAutocompleteTextMaybeChanged(
    557     const base::string16& display_text,
    558     size_t user_text_length) {
    559   // TODO(shess): Make sure that this actually works.  The round trip
    560   // to native form and back may mean that it's the same but not the
    561   // same.
    562   if (display_text == GetText())
    563     return false;
    564 
    565   DCHECK_LE(user_text_length, display_text.size());
    566   const NSRange range =
    567       NSMakeRange(user_text_length, display_text.size() - user_text_length);
    568   SetTextAndSelectedRange(display_text, range);
    569   model()->OnChanged();
    570   [field_ clearUndoChain];
    571 
    572   return true;
    573 }
    574 
    575 void OmniboxViewMac::OnInlineAutocompleteTextCleared() {
    576 }
    577 
    578 void OmniboxViewMac::OnRevertTemporaryText() {
    579   SetSelectedRange(saved_temporary_selection_);
    580   // We got here because the user hit the Escape key. We explicitly don't call
    581   // TextChanged(), since OmniboxPopupModel::ResetToDefaultMatch() has already
    582   // been called by now, and it would've called TextChanged() if it was
    583   // warranted.
    584 }
    585 
    586 bool OmniboxViewMac::IsFirstResponder() const {
    587   return [field_ currentEditor] != nil ? true : false;
    588 }
    589 
    590 void OmniboxViewMac::OnBeforePossibleChange() {
    591   // We should only arrive here when the field is focussed.
    592   DCHECK(IsFirstResponder());
    593 
    594   selection_before_change_ = GetSelectedRange();
    595   text_before_change_ = GetText();
    596   marked_range_before_change_ = GetMarkedRange();
    597 }
    598 
    599 bool OmniboxViewMac::OnAfterPossibleChange() {
    600   // We should only arrive here when the field is focussed.
    601   DCHECK(IsFirstResponder());
    602 
    603   const NSRange new_selection(GetSelectedRange());
    604   const base::string16 new_text(GetText());
    605   const size_t length = new_text.length();
    606 
    607   const bool selection_differs =
    608       (new_selection.length || selection_before_change_.length) &&
    609       !NSEqualRanges(new_selection, selection_before_change_);
    610   const bool at_end_of_edit = (length == new_selection.location);
    611   const bool text_differs = (new_text != text_before_change_) ||
    612       !NSEqualRanges(marked_range_before_change_, GetMarkedRange());
    613 
    614   // When the user has deleted text, we don't allow inline
    615   // autocomplete.  This is assumed if the text has gotten shorter AND
    616   // the selection has shifted towards the front of the text.  During
    617   // normal typing the text will almost always be shorter (as the new
    618   // input replaces the autocomplete suggestion), but in that case the
    619   // selection point will have moved towards the end of the text.
    620   // TODO(shess): In our implementation, we can catch -deleteBackward:
    621   // and other methods to provide positive knowledge that a delete
    622   // occured, rather than intuiting it from context.  Consider whether
    623   // that would be a stronger approach.
    624   const bool just_deleted_text =
    625       (length < text_before_change_.length() &&
    626        new_selection.location <= selection_before_change_.location);
    627 
    628   delete_at_end_pressed_ = false;
    629 
    630   const bool something_changed = model()->OnAfterPossibleChange(
    631       text_before_change_, new_text, new_selection.location,
    632       NSMaxRange(new_selection), selection_differs, text_differs,
    633       just_deleted_text, !IsImeComposing());
    634 
    635   if (delete_was_pressed_ && at_end_of_edit)
    636     delete_at_end_pressed_ = true;
    637 
    638   // Restyle in case the user changed something.
    639   // TODO(shess): I believe there are multiple-redraw cases, here.
    640   // Linux watches for something_changed && text_differs, but that
    641   // fails for us in case you copy the URL and paste the identical URL
    642   // back (we'll lose the styling).
    643   TextChanged();
    644 
    645   delete_was_pressed_ = false;
    646 
    647   return something_changed;
    648 }
    649 
    650 gfx::NativeView OmniboxViewMac::GetNativeView() const {
    651   return field_;
    652 }
    653 
    654 gfx::NativeView OmniboxViewMac::GetRelativeWindowForPopup() const {
    655   // Not used on mac.
    656   NOTREACHED();
    657   return NULL;
    658 }
    659 
    660 void OmniboxViewMac::SetGrayTextAutocompletion(
    661     const base::string16& suggest_text) {
    662   if (suggest_text == suggest_text_)
    663     return;
    664   suggest_text_ = suggest_text;
    665   [field_ setGrayTextAutocompletion:base::SysUTF16ToNSString(suggest_text)
    666                           textColor:SuggestTextColor()];
    667 }
    668 
    669 base::string16 OmniboxViewMac::GetGrayTextAutocompletion() const {
    670   return suggest_text_;
    671 }
    672 
    673 int OmniboxViewMac::GetTextWidth() const {
    674   // Not used on mac.
    675   NOTREACHED();
    676   return 0;
    677 }
    678 
    679 int OmniboxViewMac::GetWidth() const {
    680   return ceil([field_ bounds].size.width);
    681 }
    682 
    683 bool OmniboxViewMac::IsImeComposing() const {
    684   return [(NSTextView*)[field_ currentEditor] hasMarkedText];
    685 }
    686 
    687 void OmniboxViewMac::OnDidBeginEditing() {
    688   // We should only arrive here when the field is focussed.
    689   DCHECK([field_ currentEditor]);
    690 }
    691 
    692 void OmniboxViewMac::OnBeforeChange() {
    693   // Capture the current state.
    694   OnBeforePossibleChange();
    695 }
    696 
    697 void OmniboxViewMac::OnDidChange() {
    698   // Figure out what changed and notify the model.
    699   OnAfterPossibleChange();
    700 }
    701 
    702 void OmniboxViewMac::OnDidEndEditing() {
    703   ClosePopup();
    704 }
    705 
    706 bool OmniboxViewMac::OnDoCommandBySelector(SEL cmd) {
    707   if (cmd == @selector(deleteForward:))
    708     delete_was_pressed_ = true;
    709 
    710   if (cmd == @selector(moveDown:)) {
    711     model()->OnUpOrDownKeyPressed(1);
    712     return true;
    713   }
    714 
    715   if (cmd == @selector(moveUp:)) {
    716     model()->OnUpOrDownKeyPressed(-1);
    717     return true;
    718   }
    719 
    720   if (model()->popup_model()->IsOpen()) {
    721     if (cmd == @selector(insertBacktab:)) {
    722       if (model()->popup_model()->selected_line_state() ==
    723             OmniboxPopupModel::KEYWORD) {
    724         model()->ClearKeyword(GetText());
    725         return true;
    726       } else {
    727         model()->OnUpOrDownKeyPressed(-1);
    728         return true;
    729       }
    730     }
    731 
    732     if ((cmd == @selector(insertTab:) ||
    733         cmd == @selector(insertTabIgnoringFieldEditor:)) &&
    734         !model()->is_keyword_hint()) {
    735       model()->OnUpOrDownKeyPressed(1);
    736       return true;
    737     }
    738   }
    739 
    740   if (cmd == @selector(moveRight:)) {
    741     // Only commit suggested text if the cursor is all the way to the right and
    742     // there is no selection.
    743     if (suggest_text_.length() > 0 && IsCaretAtEnd()) {
    744       model()->CommitSuggestedText();
    745       return true;
    746     }
    747   }
    748 
    749   if (cmd == @selector(scrollPageDown:)) {
    750     model()->OnUpOrDownKeyPressed(model()->result().size());
    751     return true;
    752   }
    753 
    754   if (cmd == @selector(scrollPageUp:)) {
    755     model()->OnUpOrDownKeyPressed(-model()->result().size());
    756     return true;
    757   }
    758 
    759   if (cmd == @selector(cancelOperation:)) {
    760     return model()->OnEscapeKeyPressed();
    761   }
    762 
    763   if ((cmd == @selector(insertTab:) ||
    764       cmd == @selector(insertTabIgnoringFieldEditor:)) &&
    765       model()->is_keyword_hint()) {
    766     return model()->AcceptKeyword(ENTERED_KEYWORD_MODE_VIA_TAB);
    767   }
    768 
    769   // |-noop:| is sent when the user presses Cmd+Return. Override the no-op
    770   // behavior with the proper WindowOpenDisposition.
    771   NSEvent* event = [NSApp currentEvent];
    772   if (cmd == @selector(insertNewline:) ||
    773      (cmd == @selector(noop:) &&
    774       ([event type] == NSKeyDown || [event type] == NSKeyUp) &&
    775       [event keyCode] == kVK_Return)) {
    776     WindowOpenDisposition disposition =
    777         ui::WindowOpenDispositionFromNSEvent(event);
    778     model()->AcceptInput(disposition, false);
    779     // Opening a URL in a background tab should also revert the omnibox contents
    780     // to their original state.  We cannot do a blanket revert in OpenURL()
    781     // because middle-clicks also open in a new background tab, but those should
    782     // not revert the omnibox text.
    783     RevertAll();
    784     return true;
    785   }
    786 
    787   // Option-Return
    788   if (cmd == @selector(insertNewlineIgnoringFieldEditor:)) {
    789     model()->AcceptInput(NEW_FOREGROUND_TAB, false);
    790     return true;
    791   }
    792 
    793   // When the user does Control-Enter, the existing content has "www."
    794   // prepended and ".com" appended.  model() should already have
    795   // received notification when the Control key was depressed, but it
    796   // is safe to tell it twice.
    797   if (cmd == @selector(insertLineBreak:)) {
    798     OnControlKeyChanged(true);
    799     WindowOpenDisposition disposition =
    800         ui::WindowOpenDispositionFromNSEvent([NSApp currentEvent]);
    801     model()->AcceptInput(disposition, false);
    802     return true;
    803   }
    804 
    805   if (cmd == @selector(deleteBackward:)) {
    806     if (OnBackspacePressed()) {
    807       return true;
    808     }
    809   }
    810 
    811   if (cmd == @selector(deleteForward:)) {
    812     const NSUInteger modifiers = [[NSApp currentEvent] modifierFlags];
    813     if ((modifiers & NSShiftKeyMask) != 0) {
    814       if (model()->popup_model()->IsOpen()) {
    815         model()->popup_model()->TryDeletingCurrentItem();
    816         return true;
    817       }
    818     }
    819   }
    820 
    821   return false;
    822 }
    823 
    824 void OmniboxViewMac::OnSetFocus(bool control_down) {
    825   model()->OnSetFocus(control_down);
    826   controller()->OnSetFocus();
    827 
    828   HandleOriginChipMouseRelease();
    829 }
    830 
    831 void OmniboxViewMac::OnKillFocus() {
    832   // Tell the model to reset itself.
    833   model()->OnWillKillFocus(NULL);
    834   model()->OnKillFocus();
    835 
    836   OnDidKillFocus();
    837 }
    838 
    839 void OmniboxViewMac::OnMouseDown(NSInteger button_number) {
    840   // Restore caret visibility whenever the user clicks in the the omnibox. This
    841   // is not always covered by OnSetFocus() because when clicking while the
    842   // omnibox has invisible focus does not trigger a new OnSetFocus() call.
    843   if (button_number == 0 || button_number == 1)
    844     model()->SetCaretVisibility(true);
    845 }
    846 
    847 bool OmniboxViewMac::ShouldSelectAllOnMouseDown() {
    848   return !controller()->GetToolbarModel()->WouldPerformSearchTermReplacement(
    849       false);
    850 }
    851 
    852 bool OmniboxViewMac::CanCopy() {
    853   const NSRange selection = GetSelectedRange();
    854   return selection.length > 0;
    855 }
    856 
    857 void OmniboxViewMac::CopyToPasteboard(NSPasteboard* pb) {
    858   DCHECK(CanCopy());
    859 
    860   const NSRange selection = GetSelectedRange();
    861   base::string16 text = base::SysNSStringToUTF16(
    862       [[field_ stringValue] substringWithRange:selection]);
    863 
    864   // Copy the URL unless this is the search URL and it's being replaced by the
    865   // Extended Instant API.
    866   GURL url;
    867   bool write_url = false;
    868   if (!controller()->GetToolbarModel()->WouldPerformSearchTermReplacement(
    869       false)) {
    870     model()->AdjustTextForCopy(selection.location, IsSelectAll(), &text, &url,
    871                                &write_url);
    872   }
    873 
    874   if (IsSelectAll())
    875     UMA_HISTOGRAM_COUNTS(OmniboxEditModel::kCutOrCopyAllTextHistogram, 1);
    876 
    877   NSString* nstext = base::SysUTF16ToNSString(text);
    878   [pb declareTypes:[NSArray arrayWithObject:NSStringPboardType] owner:nil];
    879   [pb setString:nstext forType:NSStringPboardType];
    880 
    881   if (write_url) {
    882     [pb declareURLPasteboardWithAdditionalTypes:[NSArray array] owner:nil];
    883     [pb setDataForURL:base::SysUTF8ToNSString(url.spec()) title:nstext];
    884   }
    885 }
    886 
    887 void OmniboxViewMac::ShowURL() {
    888   DCHECK(ShouldEnableShowURL());
    889   OmniboxView::ShowURL();
    890 }
    891 
    892 void OmniboxViewMac::OnPaste() {
    893   // This code currently expects |field_| to be focussed.
    894   DCHECK([field_ currentEditor]);
    895 
    896   base::string16 text = GetClipboardText();
    897   if (text.empty()) {
    898     return;
    899   }
    900   NSString* s = base::SysUTF16ToNSString(text);
    901 
    902   // -shouldChangeTextInRange:* and -didChangeText are documented in
    903   // NSTextView as things you need to do if you write additional
    904   // user-initiated editing functions.  They cause the appropriate
    905   // delegate methods to be called.
    906   // TODO(shess): It would be nice to separate the Cocoa-specific code
    907   // from the Chrome-specific code.
    908   NSTextView* editor = static_cast<NSTextView*>([field_ currentEditor]);
    909   const NSRange selectedRange = GetSelectedRange();
    910   if ([editor shouldChangeTextInRange:selectedRange replacementString:s]) {
    911     // Record this paste, so we can do different behavior.
    912     model()->OnPaste();
    913 
    914     // Force a Paste operation to trigger the text_changed code in
    915     // OnAfterPossibleChange(), even if identical contents are pasted
    916     // into the text box.
    917     text_before_change_.clear();
    918 
    919     [editor replaceCharactersInRange:selectedRange withString:s];
    920     [editor didChangeText];
    921   }
    922 }
    923 
    924 // TODO(dominich): Move to OmniboxView base class? Currently this is defined on
    925 // the AutocompleteTextFieldObserver but the logic is shared between all
    926 // platforms. Some refactor might be necessary to simplify this. Or at least
    927 // this method could call the OmniboxView version.
    928 bool OmniboxViewMac::ShouldEnableShowURL() {
    929   return controller()->GetToolbarModel()->WouldReplaceURL();
    930 }
    931 
    932 bool OmniboxViewMac::CanPasteAndGo() {
    933   return model()->CanPasteAndGo(GetClipboardText());
    934 }
    935 
    936 int OmniboxViewMac::GetPasteActionStringId() {
    937   base::string16 text(GetClipboardText());
    938   DCHECK(model()->CanPasteAndGo(text));
    939   return model()->IsPasteAndSearch(text) ?
    940       IDS_PASTE_AND_SEARCH : IDS_PASTE_AND_GO;
    941 }
    942 
    943 void OmniboxViewMac::OnPasteAndGo() {
    944   base::string16 text(GetClipboardText());
    945   if (model()->CanPasteAndGo(text))
    946     model()->PasteAndGo(text);
    947 }
    948 
    949 void OmniboxViewMac::OnFrameChanged() {
    950   // TODO(shess): UpdatePopupAppearance() is called frequently, so it
    951   // should be really cheap, but in this case we could probably make
    952   // things even cheaper by refactoring between the popup-placement
    953   // code and the matrix-population code.
    954   popup_view_->UpdatePopupAppearance();
    955 
    956   // Give controller a chance to rearrange decorations.
    957   model()->OnChanged();
    958 }
    959 
    960 void OmniboxViewMac::ClosePopup() {
    961   OmniboxView::CloseOmniboxPopup();
    962 }
    963 
    964 bool OmniboxViewMac::OnBackspacePressed() {
    965   // Don't intercept if not in keyword search mode.
    966   if (model()->is_keyword_hint() || model()->keyword().empty()) {
    967     return false;
    968   }
    969 
    970   // Don't intercept if there is a selection, or the cursor isn't at
    971   // the leftmost position.
    972   const NSRange selection = GetSelectedRange();
    973   if (selection.length > 0 || selection.location > 0) {
    974     return false;
    975   }
    976 
    977   // We're showing a keyword and the user pressed backspace at the
    978   // beginning of the text.  Delete the selected keyword.
    979   model()->ClearKeyword(GetText());
    980   return true;
    981 }
    982 
    983 NSRange OmniboxViewMac::SelectionRangeForProposedRange(NSRange proposed_range) {
    984   return proposed_range;
    985 }
    986 
    987 void OmniboxViewMac::OnControlKeyChanged(bool pressed) {
    988   model()->OnControlKeyChanged(pressed);
    989 }
    990 
    991 void OmniboxViewMac::FocusLocation(bool select_all) {
    992   if ([field_ isEditable]) {
    993     // If the text field has a field editor, it's the first responder, meaning
    994     // that it's already focused. makeFirstResponder: will select all, so only
    995     // call it if this behavior is desired.
    996     if (select_all || ![field_ currentEditor])
    997       [[field_ window] makeFirstResponder:field_];
    998     DCHECK_EQ([field_ currentEditor], [[field_ window] firstResponder]);
    999   }
   1000 }
   1001 
   1002 // static
   1003 NSFont* OmniboxViewMac::GetFieldFont(int style) {
   1004   // This value should be kept in sync with InstantPage::InitializeFonts.
   1005   ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
   1006   return rb.GetFontList(ui::ResourceBundle::BaseFont).Derive(1, style)
   1007       .GetPrimaryFont().GetNativeFont();
   1008 }
   1009 
   1010 int OmniboxViewMac::GetOmniboxTextLength() const {
   1011   return static_cast<int>(GetTextLength());
   1012 }
   1013 
   1014 NSUInteger OmniboxViewMac::GetTextLength() const {
   1015   return [field_ currentEditor] ?  [[[field_ currentEditor] string] length] :
   1016                                    [[field_ stringValue] length];
   1017 }
   1018 
   1019 bool OmniboxViewMac::IsCaretAtEnd() const {
   1020   const NSRange selection = GetSelectedRange();
   1021   return NSMaxRange(selection) == GetTextLength();
   1022 }
   1023