| Index: mash/browser/browser.cc
 | 
| diff --git a/mash/browser/browser.cc b/mash/browser/browser.cc
 | 
| index 5094674b278692b92b142b20bfdfcc038bd56f69..c746ef0e5a76a0620c74c29df4ad8780c6ca063a 100644
 | 
| --- a/mash/browser/browser.cc
 | 
| +++ b/mash/browser/browser.cc
 | 
| @@ -5,11 +5,13 @@
 | 
|  #include "mash/browser/browser.h"
 | 
|  
 | 
|  #include "base/macros.h"
 | 
| +#include "base/memory/ptr_util.h"
 | 
|  #include "base/memory/weak_ptr.h"
 | 
|  #include "base/message_loop/message_loop.h"
 | 
|  #include "base/strings/string16.h"
 | 
|  #include "base/strings/string_util.h"
 | 
|  #include "base/strings/utf_string_conversions.h"
 | 
| +#include "base/threading/thread_task_runner_handle.h"
 | 
|  #include "base/timer/timer.h"
 | 
|  #include "components/mus/public/cpp/window.h"
 | 
|  #include "components/mus/public/cpp/window_tree_client.h"
 | 
| @@ -22,12 +24,15 @@
 | 
|  #include "services/shell/public/cpp/shell_client.h"
 | 
|  #include "services/tracing/public/cpp/tracing_impl.h"
 | 
|  #include "ui/aura/mus/mus_util.h"
 | 
| +#include "ui/base/models/menu_model.h"
 | 
|  #include "ui/gfx/canvas.h"
 | 
|  #include "ui/gfx/paint_throbber.h"
 | 
|  #include "ui/gfx/text_constants.h"
 | 
|  #include "ui/native_theme/native_theme.h"
 | 
|  #include "ui/views/background.h"
 | 
|  #include "ui/views/controls/button/label_button.h"
 | 
| +#include "ui/views/controls/menu/menu_model_adapter.h"
 | 
| +#include "ui/views/controls/menu/menu_runner.h"
 | 
|  #include "ui/views/controls/textfield/textfield.h"
 | 
|  #include "ui/views/controls/textfield/textfield_controller.h"
 | 
|  #include "ui/views/mus/aura_init.h"
 | 
| @@ -43,6 +48,175 @@ void EnableButton(views::CustomButton* button, bool enabled) {
 | 
|                             : views::Button::STATE_DISABLED);
 | 
|  }
 | 
|  
 | 
| +class NavMenuModel : public ui::MenuModel {
 | 
| + public:
 | 
| +  class Delegate {
 | 
| +   public:
 | 
| +    virtual void NavigateToOffset(int offset) = 0;
 | 
| +  };
 | 
| +
 | 
| +  struct Entry {
 | 
| +    Entry(const base::string16& title, int offset)
 | 
| +        : title(title), offset(offset) {}
 | 
| +    ~Entry() {}
 | 
| +
 | 
| +    // Title of the entry in the menu.
 | 
| +    base::string16 title;
 | 
| +    // Offset from the currently visible page to navigate to this item.
 | 
| +    int offset;
 | 
| +  };
 | 
| +
 | 
| +  NavMenuModel(const std::vector<Entry>& entries, Delegate* delegate)
 | 
| +      : navigation_delegate_(delegate), entries_(entries) {}
 | 
| +  ~NavMenuModel() override {}
 | 
| +
 | 
| + private:
 | 
| +  bool HasIcons() const override { return false; }
 | 
| +  int GetItemCount() const override {
 | 
| +    return static_cast<int>(entries_.size());
 | 
| +  }
 | 
| +  ui::MenuModel::ItemType GetTypeAt(int index) const override {
 | 
| +    return ui::MenuModel::TYPE_COMMAND;
 | 
| +  }
 | 
| +  ui::MenuSeparatorType GetSeparatorTypeAt(int index) const override {
 | 
| +    return ui::NORMAL_SEPARATOR;
 | 
| +  }
 | 
| +  int GetCommandIdAt(int index) const override {
 | 
| +    return index;
 | 
| +  }
 | 
| +  base::string16 GetLabelAt(int index) const override {
 | 
| +    return entries_[index].title;
 | 
| +  }
 | 
| +  base::string16 GetSublabelAt(int index) const override {
 | 
| +    return base::string16();
 | 
| +  }
 | 
| +  base::string16 GetMinorTextAt(int index) const override {
 | 
| +    return base::string16();
 | 
| +  }
 | 
| +  bool IsItemDynamicAt(int index) const override { return false; }
 | 
| +  bool GetAcceleratorAt(int index,
 | 
| +                        ui::Accelerator* accelerator) const override {
 | 
| +    return false;
 | 
| +  }
 | 
| +  bool IsItemCheckedAt(int index) const override { return false; }
 | 
| +  int GetGroupIdAt(int index) const override { return -1; }
 | 
| +  bool GetIconAt(int index, gfx::Image* icon) override { return false; }
 | 
| +  ui::ButtonMenuItemModel* GetButtonMenuItemAt(int index) const override {
 | 
| +    return nullptr;
 | 
| +  }
 | 
| +  bool IsEnabledAt(int index) const override { return true; }
 | 
| +  bool IsVisibleAt(int index) const override { return true; }
 | 
| +  ui::MenuModel* GetSubmenuModelAt(int index) const override { return nullptr; }
 | 
| +  void HighlightChangedTo(int index) override {}
 | 
| +  void ActivatedAt(int index) override {
 | 
| +    ActivatedAt(index, 0);
 | 
| +  }
 | 
| +  void ActivatedAt(int index, int event_flags) override {
 | 
| +    navigation_delegate_->NavigateToOffset(entries_[index].offset);
 | 
| +  }
 | 
| +  void SetMenuModelDelegate(ui::MenuModelDelegate* delegate) override {
 | 
| +    delegate_ = delegate;
 | 
| +  }
 | 
| +  ui::MenuModelDelegate* GetMenuModelDelegate() const override {
 | 
| +    return delegate_;
 | 
| +  }
 | 
| +
 | 
| +  ui::MenuModelDelegate* delegate_ = nullptr;
 | 
| +  Delegate* navigation_delegate_;
 | 
| +  std::vector<Entry> entries_;
 | 
| +
 | 
| +  DISALLOW_COPY_AND_ASSIGN(NavMenuModel);
 | 
| +};
 | 
| +
 | 
| +class NavButton : public views::LabelButton {
 | 
| + public:
 | 
| +  enum class Type {
 | 
| +    BACK,
 | 
| +    FORWARD
 | 
| +  };
 | 
| +
 | 
| +  class ModelProvider {
 | 
| +   public:
 | 
| +    virtual std::unique_ptr<ui::MenuModel> CreateMenuModel(Type type) = 0;
 | 
| +  };
 | 
| +
 | 
| +  NavButton(Type type,
 | 
| +            ModelProvider* model_provider,
 | 
| +            views::ButtonListener* listener,
 | 
| +            const base::string16& label)
 | 
| +      : views::LabelButton(listener, label),
 | 
| +        type_(type),
 | 
| +        model_provider_(model_provider),
 | 
| +        show_menu_factory_(this) {}
 | 
| +  ~NavButton() override {}
 | 
| +
 | 
| + private:
 | 
| +  // views::LabelButton overrides:
 | 
| +  bool OnMousePressed(const ui::MouseEvent& event) override {
 | 
| +    if (IsTriggerableEvent(event) && enabled() &&
 | 
| +        HitTestPoint(event.location())) {
 | 
| +      y_pos_on_lbuttondown_ = event.y();
 | 
| +      base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
 | 
| +          FROM_HERE,
 | 
| +          base::Bind(&NavButton::ShowMenu, show_menu_factory_.GetWeakPtr(),
 | 
| +                     ui::GetMenuSourceTypeForEvent(event)),
 | 
| +          base::TimeDelta::FromMilliseconds(500));
 | 
| +    }
 | 
| +    return LabelButton::OnMousePressed(event);
 | 
| +  }
 | 
| +  bool OnMouseDragged(const ui::MouseEvent& event) override {
 | 
| +    bool result = LabelButton::OnMouseDragged(event);
 | 
| +    if (show_menu_factory_.HasWeakPtrs()) {
 | 
| +      if (event.y() > y_pos_on_lbuttondown_ + GetHorizontalDragThreshold()) {
 | 
| +        show_menu_factory_.InvalidateWeakPtrs();
 | 
| +        ShowMenu(ui::GetMenuSourceTypeForEvent(event));
 | 
| +      }
 | 
| +    }
 | 
| +    return result;
 | 
| +  }
 | 
| +  void OnMouseReleased(const ui::MouseEvent& event) override {
 | 
| +    if (IsTriggerableEvent(event))
 | 
| +      show_menu_factory_.InvalidateWeakPtrs();
 | 
| +    LabelButton::OnMouseReleased(event);
 | 
| +  }
 | 
| +
 | 
| +  void ShowMenu(ui::MenuSourceType source_type) {
 | 
| +    gfx::Rect local = GetLocalBounds();
 | 
| +    gfx::Point menu_position(local.origin());
 | 
| +    menu_position.Offset(0, local.height() - 1);
 | 
| +    View::ConvertPointToScreen(this, &menu_position);
 | 
| +
 | 
| +    model_ = model_provider_->CreateMenuModel(type_);
 | 
| +    menu_model_adapter_.reset(new views::MenuModelAdapter(
 | 
| +        model_.get(),
 | 
| +        base::Bind(&NavButton::OnMenuClosed, base::Unretained(this))));
 | 
| +    menu_model_adapter_->set_triggerable_event_flags(triggerable_event_flags());
 | 
| +    menu_runner_.reset(new views::MenuRunner(
 | 
| +        menu_model_adapter_->CreateMenu(),
 | 
| +        views::MenuRunner::HAS_MNEMONICS | views::MenuRunner::ASYNC));
 | 
| +    ignore_result(menu_runner_->RunMenuAt(
 | 
| +        GetWidget(), nullptr, gfx::Rect(menu_position, gfx::Size(0, 0)),
 | 
| +        views::MENU_ANCHOR_TOPLEFT, source_type));
 | 
| +  }
 | 
| +
 | 
| +  void OnMenuClosed() {
 | 
| +    SetMouseHandler(nullptr);
 | 
| +    model_.reset();
 | 
| +    menu_runner_.reset();
 | 
| +    menu_model_adapter_.reset();
 | 
| +  }
 | 
| +
 | 
| +  Type type_;
 | 
| +  ModelProvider* model_provider_;
 | 
| +  int y_pos_on_lbuttondown_ = 0;
 | 
| +  std::unique_ptr<ui::MenuModel> model_;
 | 
| +  std::unique_ptr<views::MenuModelAdapter> menu_model_adapter_;
 | 
| +  std::unique_ptr<views::MenuRunner> menu_runner_;
 | 
| +  base::WeakPtrFactory<NavButton> show_menu_factory_;
 | 
| +
 | 
| +  DISALLOW_COPY_AND_ASSIGN(NavButton);
 | 
| +};
 | 
| +
 | 
|  class ProgressBar : public views::View {
 | 
|   public:
 | 
|    ProgressBar() {}
 | 
| @@ -115,7 +289,9 @@ class Throbber : public views::View {
 | 
|  class UI : public views::WidgetDelegateView,
 | 
|             public views::ButtonListener,
 | 
|             public views::TextfieldController,
 | 
| -           public navigation::mojom::ViewClient {
 | 
| +           public navigation::mojom::ViewClient,
 | 
| +           public NavButton::ModelProvider,
 | 
| +           public NavMenuModel::Delegate {
 | 
|   public:
 | 
|    enum class Type { WINDOW, POPUP };
 | 
|  
 | 
| @@ -125,9 +301,10 @@ class UI : public views::WidgetDelegateView,
 | 
|       navigation::mojom::ViewClientRequest request)
 | 
|        : browser_(browser),
 | 
|          type_(type),
 | 
| -        back_button_(new views::LabelButton(this, base::ASCIIToUTF16("Back"))),
 | 
| -        forward_button_(
 | 
| -            new views::LabelButton(this, base::ASCIIToUTF16("Forward"))),
 | 
| +        back_button_(new NavButton(NavButton::Type::BACK, this, this,
 | 
| +                                   base::ASCIIToUTF16("Back"))),
 | 
| +        forward_button_(new NavButton(NavButton::Type::FORWARD, this, this,
 | 
| +                                      base::ASCIIToUTF16("Forward"))),
 | 
|          reload_button_(
 | 
|              new views::LabelButton(this, base::ASCIIToUTF16("Reload"))),
 | 
|          prompt_(new views::Textfield),
 | 
| @@ -321,6 +498,66 @@ class UI : public views::WidgetDelegateView,
 | 
|      browser_->AddWindow(window);
 | 
|    }
 | 
|    void Close() override { GetWidget()->Close(); }
 | 
| +  void NavigationPending(navigation::mojom::NavigationEntryPtr entry) override {
 | 
| +    pending_nav_ = std::move(entry);
 | 
| +  }
 | 
| +  void NavigationCommitted(
 | 
| +      navigation::mojom::NavigationCommittedDetailsPtr details,
 | 
| +      int current_index) override {
 | 
| +    switch (details->type) {
 | 
| +      case navigation::mojom::NavigationType::NEW_PAGE: {
 | 
| +        navigation_list_.push_back(std::move(pending_nav_));
 | 
| +        navigation_list_position_ = current_index;
 | 
| +        break;
 | 
| +      }
 | 
| +      case navigation::mojom::NavigationType::EXISTING_PAGE:
 | 
| +        navigation_list_position_ = current_index;
 | 
| +        break;
 | 
| +      default:
 | 
| +        break;
 | 
| +    }
 | 
| +  }
 | 
| +  void NavigationEntryChanged(navigation::mojom::NavigationEntryPtr entry,
 | 
| +                              int entry_index) override {
 | 
| +    navigation_list_[entry_index] = std::move(entry);
 | 
| +  }
 | 
| +  void NavigationListPruned(bool from_front, int count) override {
 | 
| +    DCHECK(count < static_cast<int>(navigation_list_.size()));
 | 
| +    if (from_front) {
 | 
| +      auto it = navigation_list_.begin() + count;
 | 
| +      navigation_list_.erase(navigation_list_.begin(), it);
 | 
| +    } else {
 | 
| +      auto it = navigation_list_.end() - count;
 | 
| +      navigation_list_.erase(it, navigation_list_.end());
 | 
| +    }
 | 
| +  }
 | 
| +
 | 
| +  // NavButton::ModelProvider:
 | 
| +  std::unique_ptr<ui::MenuModel> CreateMenuModel(
 | 
| +      NavButton::Type type) override {
 | 
| +    std::vector<NavMenuModel::Entry> entries;
 | 
| +    if (type == NavButton::Type::BACK) {
 | 
| +      for (int i = navigation_list_position_ - 1, offset = -1;
 | 
| +           i >= 0; --i, --offset) {
 | 
| +        std::string title = navigation_list_[i]->title;
 | 
| +        entries.push_back(
 | 
| +            NavMenuModel::Entry(base::UTF8ToUTF16(title), offset));
 | 
| +      }
 | 
| +    } else {
 | 
| +      for (int i = navigation_list_position_ + 1, offset = 1;
 | 
| +           i < static_cast<int>(navigation_list_.size()); ++i, ++offset) {
 | 
| +        std::string title = navigation_list_[i]->title;
 | 
| +        entries.push_back(
 | 
| +            NavMenuModel::Entry(base::UTF8ToUTF16(title), offset));
 | 
| +      }
 | 
| +    }
 | 
| +    return base::WrapUnique(new NavMenuModel(entries, this));
 | 
| +  }
 | 
| +
 | 
| +  // NavMenuModel::Delegate:
 | 
| +  void NavigateToOffset(int offset) override {
 | 
| +    view_->NavigateToOffset(offset);
 | 
| +  }
 | 
|  
 | 
|    void ToggleDebugView() {
 | 
|      showing_debug_view_ = !showing_debug_view_;
 | 
| @@ -350,6 +587,10 @@ class UI : public views::WidgetDelegateView,
 | 
|    base::string16 current_title_;
 | 
|    GURL current_url_;
 | 
|  
 | 
| +  navigation::mojom::NavigationEntryPtr pending_nav_;
 | 
| +  std::vector<navigation::mojom::NavigationEntryPtr> navigation_list_;
 | 
| +  int navigation_list_position_ = 0;
 | 
| +
 | 
|    bool showing_debug_view_ = false;
 | 
|  
 | 
|    DISALLOW_COPY_AND_ASSIGN(UI);
 | 
| 
 |