 Chromium Code Reviews
 Chromium Code Reviews Issue 1760773004:
  Add "Open with <ARC-app-name>" items to the context menu  (Closed) 
  Base URL: https://chromium.googlesource.com/chromium/src.git@master
    
  
    Issue 1760773004:
  Add "Open with <ARC-app-name>" items to the context menu  (Closed) 
  Base URL: https://chromium.googlesource.com/chromium/src.git@master| 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..d55e7231c1c839efd5eab5ca5dc6754352fb41e3 | 
| --- /dev/null | 
| +++ b/chrome/browser/renderer_context_menu/open_with_menu_factory_ash.cc | 
| @@ -0,0 +1,220 @@ | 
| +// 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/renderer_context_menu/link_handler_model.h" | 
| +#include "ash/renderer_context_menu/open_with_menu_controller.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) {} | 
| + | 
| + ~OpenWithMenuObserver() override {} | 
| + | 
| + // RenderViewContextMenuObserver overrides: | 
| + void InitMenu(const content::ContextMenuParams& params) override { | 
| + if (!ash::Shell::HasInstance()) | 
| + return; | 
| + ash::OpenWithMenuController* controller = | 
| + ash::Shell::GetInstance()->open_with_menu_controller(); | 
| + if (!controller) | 
| + return; | 
| + | 
| + link_url_ = params.link_url; | 
| + menu_model_ = controller->CreateModel(link_url_); | 
| + if (!menu_model_) | 
| + return; | 
| + | 
| + // Add placeholder items. | 
| + ui::SimpleMenuModel* submenu = new ui::SimpleMenuModel(&submenu_delegate_); | 
| 
hidehiko
2016/04/20 04:40:18
I'd prefer
std::unique_ptr here, too. Instead, std
 
Yusuke Sato
2016/04/22 05:27:48
Done.
 | 
| + 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_.reset(submenu); | 
| + | 
| + for (int i = 0; i < kNumMainMenuCommands; ++i) { | 
| + const int command_id = IDC_CONTENT_CONTEXT_OPEN_WITH1 + i; | 
| + if (i == kNumMainMenuCommands - 1) { | 
| 
hidehiko
2016/04/20 04:40:18
How about iterating between [i, kNumMainMenuComman
 
Yusuke Sato
2016/04/22 05:27:48
Done.
 | 
| + base::string16 label = | 
| + l10n_util::GetStringUTF16(IDS_CONTENT_CONTEXT_MORE_APPS); | 
| + proxy_->AddSubMenu(command_id, label, submenu_.get()); | 
| + } else { | 
| + proxy_->AddMenuItem(command_id, base::EmptyString16()); | 
| + } | 
| + } | 
| + | 
| + 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; | 
| 
hidehiko
2016/04/20 04:40:18
indent?
 
Yusuke Sato
2016/04/22 05:27:48
This is by git cl format actually..
 | 
| + } | 
| + | 
| + 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( | 
| 
hidehiko
2016/04/20 18:10:18
As we discussed offline, if this is called twice o
 
Yusuke Sato
2016/04/22 05:27:48
Done.
 | 
| + 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) { | 
| + // Keep the submenu parent. | 
| + } else if (it == handlers_.end()) { | 
| + // Hide the menu. | 
| + 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_; | 
| + 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); | 
| +} |