| Index: chrome/browser/renderer_context_menu/open_with_menu_factory_ash.cc
 | 
| diff --git a/chrome/browser/renderer_context_menu/open_with_menu_factory_ash.cc b/chrome/browser/renderer_context_menu/open_with_menu_factory_ash.cc
 | 
| new file mode 100644
 | 
| index 0000000000000000000000000000000000000000..1e2886cb117c6f01440f536b4667e4c0c664f64b
 | 
| --- /dev/null
 | 
| +++ b/chrome/browser/renderer_context_menu/open_with_menu_factory_ash.cc
 | 
| @@ -0,0 +1,222 @@
 | 
| +// Copyright 2016 The Chromium Authors. All rights reserved.
 | 
| +// Use of this source code is governed by a BSD-style license that can be
 | 
| +// found in the LICENSE file.
 | 
| +
 | 
| +#include "chrome/browser/renderer_context_menu/open_with_menu_factory.h"
 | 
| +
 | 
| +#include <memory>
 | 
| +#include <unordered_map>
 | 
| +#include <utility>
 | 
| +#include <vector>
 | 
| +
 | 
| +#include "ash/link_handler_model.h"
 | 
| +#include "ash/link_handler_model_factory.h"
 | 
| +#include "ash/shell.h"
 | 
| +#include "base/strings/string_util.h"
 | 
| +#include "base/strings/utf_string_conversions.h"
 | 
| +#include "chrome/app/chrome_command_ids.h"
 | 
| +#include "chrome/grit/generated_resources.h"
 | 
| +#include "components/renderer_context_menu/render_view_context_menu_observer.h"
 | 
| +#include "components/renderer_context_menu/render_view_context_menu_proxy.h"
 | 
| +#include "content/public/common/context_menu_params.h"
 | 
| +#include "ui/base/l10n/l10n_util.h"
 | 
| +#include "ui/base/models/simple_menu_model.h"
 | 
| +
 | 
| +namespace {
 | 
| +
 | 
| +using HandlerMap = std::unordered_map<int, ash::LinkHandlerInfo>;
 | 
| +
 | 
| +// An observer class which populates the "Open with <app>" menu items either
 | 
| +// synchronously or asynchronously.
 | 
| +class OpenWithMenuObserver : public RenderViewContextMenuObserver,
 | 
| +                             public ash::LinkHandlerModel::Observer {
 | 
| + public:
 | 
| +  class SubMenuDelegate : public ui::SimpleMenuModel::Delegate {
 | 
| +   public:
 | 
| +    explicit SubMenuDelegate(OpenWithMenuObserver* parent) : parent_(parent) {}
 | 
| +    ~SubMenuDelegate() override {}
 | 
| +
 | 
| +    bool IsCommandIdChecked(int command_id) const override { return false; }
 | 
| +    bool IsCommandIdEnabled(int command_id) const override { return true; }
 | 
| +
 | 
| +    bool GetAcceleratorForCommandId(int command_id,
 | 
| +                                    ui::Accelerator* accelerator) override {
 | 
| +      return false;
 | 
| +    }
 | 
| +
 | 
| +    void ExecuteCommand(int command_id, int event_flags) override {
 | 
| +      parent_->ExecuteCommand(command_id);
 | 
| +    }
 | 
| +
 | 
| +   private:
 | 
| +    OpenWithMenuObserver* const parent_;
 | 
| +
 | 
| +    DISALLOW_COPY_AND_ASSIGN(SubMenuDelegate);
 | 
| +  };
 | 
| +
 | 
| +  explicit OpenWithMenuObserver(RenderViewContextMenuProxy* proxy)
 | 
| +      : proxy_(proxy),
 | 
| +        submenu_delegate_(this),
 | 
| +        more_apps_label_(
 | 
| +            l10n_util::GetStringUTF16(IDS_CONTENT_CONTEXT_MORE_APPS)) {}
 | 
| +
 | 
| +  ~OpenWithMenuObserver() override {}
 | 
| +
 | 
| +  // RenderViewContextMenuObserver overrides:
 | 
| +  void InitMenu(const content::ContextMenuParams& params) override {
 | 
| +    if (!ash::Shell::HasInstance())
 | 
| +      return;
 | 
| +    ash::LinkHandlerModelFactory* factory =
 | 
| +        ash::Shell::GetInstance()->link_handler_model_factory();
 | 
| +    if (!factory)
 | 
| +      return;
 | 
| +
 | 
| +    link_url_ = params.link_url;
 | 
| +    menu_model_ = factory->CreateModel(link_url_);
 | 
| +    if (!menu_model_)
 | 
| +      return;
 | 
| +
 | 
| +    // Add placeholder items.
 | 
| +    std::unique_ptr<ui::SimpleMenuModel> submenu(
 | 
| +        new ui::SimpleMenuModel(&submenu_delegate_));
 | 
| +    for (int i = 0; i < kNumSubMenuCommands; ++i) {
 | 
| +      const int command_id =
 | 
| +          IDC_CONTENT_CONTEXT_OPEN_WITH1 + kNumMainMenuCommands + i;
 | 
| +      submenu->AddItem(command_id, base::EmptyString16());
 | 
| +    }
 | 
| +    submenu_ = std::move(submenu);
 | 
| +
 | 
| +    int command_id;
 | 
| +    for (int i = 0; i < kNumMainMenuCommands - 1; ++i) {
 | 
| +      command_id = IDC_CONTENT_CONTEXT_OPEN_WITH1 + i;
 | 
| +      proxy_->AddMenuItem(command_id, base::EmptyString16());
 | 
| +    }
 | 
| +    proxy_->AddSubMenu(++command_id, more_apps_label_, submenu_.get());
 | 
| +
 | 
| +    menu_model_->AddObserver(this);
 | 
| +  }
 | 
| +
 | 
| +  bool IsCommandIdSupported(int command_id) override {
 | 
| +    return command_id >= IDC_CONTENT_CONTEXT_OPEN_WITH1 &&
 | 
| +           command_id <= IDC_CONTENT_CONTEXT_OPEN_WITH_LAST;
 | 
| +  }
 | 
| +
 | 
| +  bool IsCommandIdChecked(int command_id) override { return false; }
 | 
| +  bool IsCommandIdEnabled(int command_id) override { return true; }
 | 
| +
 | 
| +  void ExecuteCommand(int command_id) override {
 | 
| +    // Note: SubmenuDelegate also calls this method with a command_id for the
 | 
| +    // submenu.
 | 
| +    const auto it = handlers_.find(command_id);
 | 
| +    if (it == handlers_.end())
 | 
| +      return;
 | 
| +    menu_model_->OpenLinkWithHandler(link_url_, it->second.id);
 | 
| +  }
 | 
| +
 | 
| +  void OnMenuCancel() override {}
 | 
| +
 | 
| +  // ash::OpenWithItems::Delegate overrides:
 | 
| +  void ModelChanged(
 | 
| +      const std::vector<ash::LinkHandlerInfo>& handlers) override {
 | 
| +    auto result = BuildHandlersMap(handlers);
 | 
| +    handlers_ = std::move(result.first);
 | 
| +    const int submenu_parent_id = result.second;
 | 
| +    for (int command_id = IDC_CONTENT_CONTEXT_OPEN_WITH1;
 | 
| +         command_id <= IDC_CONTENT_CONTEXT_OPEN_WITH_LAST; ++command_id) {
 | 
| +      const auto it = handlers_.find(command_id);
 | 
| +      if (command_id == submenu_parent_id) {
 | 
| +        // Show the submenu parent.
 | 
| +        proxy_->UpdateMenuItem(command_id, true, false, more_apps_label_);
 | 
| +      } else if (it == handlers_.end()) {
 | 
| +        // Hide the menu or submenu parent.
 | 
| +        proxy_->UpdateMenuItem(command_id, false, true, base::EmptyString16());
 | 
| +      } else {
 | 
| +        // Update the menu with the new model.
 | 
| +        const base::string16 label =
 | 
| +            l10n_util::GetStringFUTF16(IDS_CONTENT_CONTEXT_OPEN_WITH_APP,
 | 
| +                                       base::UTF8ToUTF16(it->second.name));
 | 
| +        proxy_->UpdateMenuItem(command_id, true, false, label);
 | 
| +      }
 | 
| +    }
 | 
| +  }
 | 
| +
 | 
| +  static std::pair<HandlerMap, int> BuildHandlersMapForTesting(
 | 
| +      const std::vector<ash::LinkHandlerInfo>& handlers) {
 | 
| +    return BuildHandlersMap(handlers);
 | 
| +  }
 | 
| +
 | 
| + private:
 | 
| +  // Converts |handlers| into HandlerMap which is a map from a command ID to a
 | 
| +  // LinkHandlerInfo and returns the map. Also returns a command id for the
 | 
| +  // parent of the submenu. When the submenu is not needed, the function
 | 
| +  // returns |kInvalidCommandId|.
 | 
| +  static std::pair<HandlerMap, int> BuildHandlersMap(
 | 
| +      const std::vector<ash::LinkHandlerInfo>& handlers) {
 | 
| +    const int kInvalidCommandId = -1;
 | 
| +    const int submenu_id_start =
 | 
| +        IDC_CONTENT_CONTEXT_OPEN_WITH1 + kNumMainMenuCommands;
 | 
| +
 | 
| +    HandlerMap handler_map;
 | 
| +    int submenu_parent_command_id = kInvalidCommandId;
 | 
| +
 | 
| +    const int num_apps = handlers.size();
 | 
| +    size_t handlers_index = 0;
 | 
| +    // We use the last item in the main menu (IDC_CONTENT_CONTEXT_OPEN_WITH1 +
 | 
| +    // kNumMainMenuCommands- 1) as a parent of a submenu, and others as regular
 | 
| +    // menu items.
 | 
| +    if (num_apps < kNumMainMenuCommands) {
 | 
| +      // All apps can be shown with the regular main menu items.
 | 
| +      for (int i = 0; i < num_apps; ++i) {
 | 
| +        handler_map[IDC_CONTENT_CONTEXT_OPEN_WITH1 + i] =
 | 
| +            handlers[handlers_index++];
 | 
| +      }
 | 
| +    } else {
 | 
| +      // Otherwise, use the submenu too. In this case, disable the last item of
 | 
| +      // the regular main menu (hence '-2').
 | 
| +      for (int i = 0; i < kNumMainMenuCommands - 2; ++i) {
 | 
| +        handler_map[IDC_CONTENT_CONTEXT_OPEN_WITH1 + i] =
 | 
| +            handlers[handlers_index++];
 | 
| +      }
 | 
| +      submenu_parent_command_id =
 | 
| +          IDC_CONTENT_CONTEXT_OPEN_WITH1 + kNumMainMenuCommands - 1;
 | 
| +      const int sub_items =
 | 
| +          std::min(num_apps - (kNumMainMenuCommands - 2), kNumSubMenuCommands);
 | 
| +      for (int i = 0; i < sub_items; ++i) {
 | 
| +        handler_map[submenu_id_start + i] = handlers[handlers_index++];
 | 
| +      }
 | 
| +    }
 | 
| +
 | 
| +    return std::make_pair(std::move(handler_map), submenu_parent_command_id);
 | 
| +  }
 | 
| +
 | 
| +  static const int kNumMainMenuCommands;
 | 
| +  static const int kNumSubMenuCommands;
 | 
| +
 | 
| +  RenderViewContextMenuProxy* const proxy_;
 | 
| +  SubMenuDelegate submenu_delegate_;
 | 
| +  const base::string16 more_apps_label_;
 | 
| +  GURL link_url_;
 | 
| +
 | 
| +  // A menu model received from Ash side.
 | 
| +  std::unique_ptr<ash::LinkHandlerModel> menu_model_;
 | 
| +  HandlerMap handlers_;
 | 
| +  // A submenu passed to Chrome side.
 | 
| +  std::unique_ptr<ui::MenuModel> submenu_;
 | 
| +
 | 
| +  DISALLOW_COPY_AND_ASSIGN(OpenWithMenuObserver);
 | 
| +};
 | 
| +
 | 
| +const int OpenWithMenuObserver::kNumMainMenuCommands = 4;
 | 
| +const int OpenWithMenuObserver::kNumSubMenuCommands = 10;
 | 
| +
 | 
| +}  // namespace
 | 
| +
 | 
| +std::pair<HandlerMap, int> BuildHandlersMapForTesting(
 | 
| +    const std::vector<ash::LinkHandlerInfo>& handlers) {
 | 
| +  return OpenWithMenuObserver::BuildHandlersMapForTesting(handlers);
 | 
| +}
 | 
| +
 | 
| +RenderViewContextMenuObserver* OpenWithMenuFactory::CreateMenu(
 | 
| +    RenderViewContextMenuProxy* proxy) {
 | 
| +  return new OpenWithMenuObserver(proxy);
 | 
| +}
 | 
| 
 |