diff options
author | juncai <juncai@chromium.org> | 2015-12-10 18:37:39 -0800 |
---|---|---|
committer | Commit bot <commit-bot@chromium.org> | 2015-12-11 02:38:30 +0000 |
commit | a7dd94484b64397611deaf2177f28dcb83a7903b (patch) | |
tree | d0e7d05da99fe815c5f823a19e544083883870ea /chrome/browser | |
parent | d454228bc08c2f2f7c6b430a5c39b571a530936c (diff) | |
download | chromium_src-a7dd94484b64397611deaf2177f28dcb83a7903b.zip chromium_src-a7dd94484b64397611deaf2177f28dcb83a7903b.tar.gz chromium_src-a7dd94484b64397611deaf2177f28dcb83a7903b.tar.bz2 |
Add chrome side webusb permission UI code
This patch added chrome side webusb permission UI code.
It displays a device chooser permission dialog box asking
permission from user so that website can access
the device.
This patch is for Linux, Windows, and ChromeOS. There will
be another patch for Mac.
BUG=492204
Review URL: https://codereview.chromium.org/1408193003
Cr-Commit-Position: refs/heads/master@{#364593}
Diffstat (limited to 'chrome/browser')
-rw-r--r-- | chrome/browser/chrome_content_browser_client.cc | 25 | ||||
-rw-r--r-- | chrome/browser/ui/chrome_bubble_manager.cc | 6 | ||||
-rw-r--r-- | chrome/browser/ui/views/website_settings/chooser_bubble_ui_view.cc | 406 | ||||
-rw-r--r-- | chrome/browser/ui/views/website_settings/chooser_bubble_ui_view.h | 43 | ||||
-rw-r--r-- | chrome/browser/ui/website_settings/chooser_bubble_delegate.cc | 26 | ||||
-rw-r--r-- | chrome/browser/ui/website_settings/chooser_bubble_delegate.h | 82 | ||||
-rw-r--r-- | chrome/browser/usb/DEPS | 2 | ||||
-rw-r--r-- | chrome/browser/usb/usb_chooser_bubble_delegate.cc | 152 | ||||
-rw-r--r-- | chrome/browser/usb/usb_chooser_bubble_delegate.h | 65 | ||||
-rw-r--r-- | chrome/browser/usb/usb_tab_helper.cc | 52 | ||||
-rw-r--r-- | chrome/browser/usb/usb_tab_helper.h | 23 | ||||
-rw-r--r-- | chrome/browser/usb/web_usb_permission_bubble.cc | 45 | ||||
-rw-r--r-- | chrome/browser/usb/web_usb_permission_bubble.h | 48 |
13 files changed, 964 insertions, 11 deletions
diff --git a/chrome/browser/chrome_content_browser_client.cc b/chrome/browser/chrome_content_browser_client.cc index f9f87bc..778bd5d 100644 --- a/chrome/browser/chrome_content_browser_client.cc +++ b/chrome/browser/chrome_content_browser_client.cc @@ -280,6 +280,10 @@ #include "chrome/browser/media/router/presentation_service_delegate_impl.h" #endif +#if !defined(OS_ANDROID) && !defined(OS_IOS) +#include "components/webusb/public/interfaces/webusb_permission_bubble.mojom.h" +#endif + #if defined(ENABLE_WAYLAND_SERVER) #include "chrome/browser/chrome_browser_main_extra_parts_exo.h" #endif @@ -659,6 +663,23 @@ void CreateUsbDeviceManager( tab_helper->CreateDeviceManager(render_frame_host, request.Pass()); } +#if !defined(OS_ANDROID) && !defined(OS_IOS) +void CreateWebUsbPermissionBubble( + RenderFrameHost* render_frame_host, + mojo::InterfaceRequest<webusb::WebUsbPermissionBubble> request) { + WebContents* web_contents = + WebContents::FromRenderFrameHost(render_frame_host); + if (!web_contents) { + NOTREACHED(); + return; + } + + UsbTabHelper* tab_helper = + UsbTabHelper::GetOrCreateForWebContents(web_contents); + tab_helper->CreatePermissionBubble(render_frame_host, request.Pass()); +} +#endif // !defined(OS_ANDROID) && !defined(OS_IOS) + } // namespace ChromeContentBrowserClient::ChromeContentBrowserClient() @@ -2614,6 +2635,10 @@ void ChromeContentBrowserClient::RegisterRenderFrameMojoServices( content::ServiceRegistry* registry, content::RenderFrameHost* render_frame_host) { registry->AddService(base::Bind(&CreateUsbDeviceManager, render_frame_host)); +#if !defined(OS_ANDROID) && !defined(OS_IOS) + registry->AddService( + base::Bind(&CreateWebUsbPermissionBubble, render_frame_host)); +#endif // !defined(OS_ANDROID) && !defined(OS_IOS) } void ChromeContentBrowserClient::RegisterOutOfProcessMojoApplications( diff --git a/chrome/browser/ui/chrome_bubble_manager.cc b/chrome/browser/ui/chrome_bubble_manager.cc index 33890dc..56b686e 100644 --- a/chrome/browser/ui/chrome_bubble_manager.cc +++ b/chrome/browser/ui/chrome_bubble_manager.cc @@ -31,11 +31,15 @@ enum BubbleType { // Permissions-related bubbles: BUBBLE_TYPE_PERMISSION = 30, // Displays a permission request to the user. + BUBBLE_TYPE_CHOOSER_PERMISSION = 31, // For chooser permissions. // Upper boundary for metrics. BUBBLE_TYPE_MAX, }; +// TODO(juncai): Since LogBubbleCloseReason function adds metrics for each +// close type, we can use only enum, and it may not be necessary to keep the +// bubble name. // Convert from bubble name to ID. The bubble ID will allow collecting the // close reason for each bubble type. static int GetBubbleId(BubbleReference bubble) { @@ -50,6 +54,8 @@ static int GetBubbleId(BubbleReference bubble) { bubble_type = BUBBLE_TYPE_TRANSLATE; else if (bubble->GetName().compare("PermissionBubble") == 0) bubble_type = BUBBLE_TYPE_PERMISSION; + else if (bubble->GetName().compare("ChooserBubble") == 0) + bubble_type = BUBBLE_TYPE_CHOOSER_PERMISSION; DCHECK_NE(bubble_type, BUBBLE_TYPE_UNKNOWN); DCHECK_NE(bubble_type, BUBBLE_TYPE_MAX); diff --git a/chrome/browser/ui/views/website_settings/chooser_bubble_ui_view.cc b/chrome/browser/ui/views/website_settings/chooser_bubble_ui_view.cc new file mode 100644 index 0000000..15851bf --- /dev/null +++ b/chrome/browser/ui/views/website_settings/chooser_bubble_ui_view.cc @@ -0,0 +1,406 @@ +// Copyright 2015 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/ui/views/website_settings/chooser_bubble_ui_view.h" + +#include <string> + +#include "base/prefs/pref_service.h" +#include "base/strings/string16.h" +#include "chrome/browser/profiles/profile.h" +#include "chrome/browser/ui/browser.h" +#include "chrome/browser/ui/views/exclusive_access_bubble_views.h" +#include "chrome/browser/ui/views/frame/browser_view.h" +#include "chrome/browser/ui/views/frame/top_container_view.h" +#include "chrome/browser/ui/views/location_bar/location_bar_view.h" +#include "chrome/browser/ui/views/location_bar/location_icon_view.h" +#include "chrome/browser/ui/website_settings/chooser_bubble_delegate.h" +#include "chrome/common/pref_names.h" +#include "chrome/grit/generated_resources.h" +#include "ui/accessibility/ax_view_state.h" +#include "ui/base/l10n/l10n_util.h" +#include "ui/base/resource/resource_bundle.h" +#include "ui/gfx/paint_vector_icon.h" +#include "ui/gfx/text_constants.h" +#include "ui/gfx/vector_icons_public.h" +#include "ui/views/bubble/bubble_delegate.h" +#include "ui/views/bubble/bubble_frame_view.h" +#include "ui/views/controls/button/label_button.h" +#include "ui/views/controls/button/label_button_border.h" +#include "ui/views/controls/table/table_view.h" +#include "ui/views/controls/table/table_view_observer.h" +#include "ui/views/layout/box_layout.h" +#include "ui/views/layout/grid_layout.h" + +namespace { + +// Chooser permission bubble width +const int kChooserPermissionBubbleWidth = 300; + +// Chooser permission bubble height +const int kChooserPermissionBubbleHeight = 200; + +// Spacing constant for outer margin. This is added to the +// bubble margin itself to equalize the margins at 13px. +const int kBubbleOuterMargin = 5; + +// Spacing between major items should be 9px. +const int kItemMajorSpacing = 9; + +// Button border size, draws inside the spacing distance. +const int kButtonBorderSize = 2; + +} // namespace + +scoped_ptr<BubbleUi> ChooserBubbleDelegate::BuildBubbleUi() { + return make_scoped_ptr(new ChooserBubbleUiView(browser_, this)); +} + +class ChooserTableModel; + +/////////////////////////////////////////////////////////////////////////////// +// View implementation for the chooser bubble. +class ChooserBubbleUiViewDelegate : public views::BubbleDelegateView, + public views::ButtonListener, + public views::TableViewObserver { + public: + ChooserBubbleUiViewDelegate(views::View* anchor_view, + views::BubbleBorder::Arrow anchor_arrow, + ChooserBubbleUiView* owner, + ChooserBubbleDelegate* chooser_bubble_delegate); + ~ChooserBubbleUiViewDelegate() override; + + void Close(); + + // BubbleDelegateView: + bool ShouldShowCloseButton() const override; + bool ShouldShowWindowTitle() const override; + base::string16 GetWindowTitle() const override; + void OnWidgetDestroying(views::Widget* widget) override; + + // ButtonListener: + void ButtonPressed(views::Button* button, const ui::Event& event) override; + + // views::TableViewObserver: + void OnSelectionChanged() override; + + // Updates the anchor's arrow and view. Also repositions the bubble so it's + // displayed in the correct location. + void UpdateAnchor(views::View* anchor_view, + views::BubbleBorder::Arrow anchor_arrow); + + private: + friend ChooserBubbleUiView; + + ChooserBubbleUiView* owner_; + ChooserBubbleDelegate* chooser_bubble_delegate_; + + views::LabelButton* connect_button_; + views::LabelButton* cancel_button_; + views::TableView* table_view_; + ChooserTableModel* chooser_table_model_; + bool button_pressed_; + + DISALLOW_COPY_AND_ASSIGN(ChooserBubbleUiViewDelegate); +}; + +ui::TableColumn ChooserTableColumn(int id, const std::string& title) { + ui::TableColumn column; + column.id = id; + column.title = base::ASCIIToUTF16(title.c_str()); + return column; +} + +class ChooserTableModel : public ui::TableModel, + public ChooserBubbleDelegate::Observer { + public: + explicit ChooserTableModel(ChooserBubbleDelegate* chooser_bubble_delegate) + : observer_(nullptr), chooser_bubble_delegate_(chooser_bubble_delegate) { + chooser_bubble_delegate_->set_observer(this); + } + + // ui::TableModel: + int RowCount() override { + const std::vector<base::string16>& device_names = + chooser_bubble_delegate_->GetOptions(); + if (device_names.empty()) { + // Here it returns 1 when there is no device. In this case, the + // table view still needs to display a text message saying no + // devices found, so the number of rows is 1. + return 1; + } else { + return static_cast<int>(device_names.size()); + } + } + + // ui::TableModel: + base::string16 GetText(int row, int column_id) override { + const std::vector<base::string16>& device_names = + chooser_bubble_delegate_->GetOptions(); + if (device_names.empty()) { + DCHECK(row == 0); + return l10n_util::GetStringUTF16( + IDS_CHOOSER_BUBBLE_NO_DEVICES_FOUND_PROMPT); + } else if (row >= 0 && row < static_cast<int>(device_names.size())) { + return device_names[row]; + } else { + NOTREACHED(); + return base::string16(); + } + } + + // ui::TableModel: + void SetObserver(ui::TableModelObserver* observer) override { + observer_ = observer; + } + + // ChooserOptions::Observer: + void OnOptionsInitialized() override { + if (observer_) { + observer_->OnModelChanged(); + Update(); + } + } + + // ChooserOptions::Observer: + void OnOptionAdded(int index) override { + if (observer_) { + observer_->OnItemsAdded(index, 1); + Update(); + } + } + + // ChooserOptions::Observer: + void OnOptionRemoved(int index) override { + if (observer_) { + observer_->OnItemsRemoved(index, 1); + Update(); + } + } + + void Update() { + views::TableView* table_view = static_cast<views::TableView*>(observer_); + + if (chooser_bubble_delegate_->GetOptions().empty()) { + observer_->OnModelChanged(); + table_view->SetEnabled(false); + } else { + table_view->SetEnabled(true); + } + } + + void SetConnectButton(views::LabelButton* connect_button) { + connect_button_ = connect_button; + } + + private: + ui::TableModelObserver* observer_; + ChooserBubbleDelegate* chooser_bubble_delegate_; + views::LabelButton* connect_button_; +}; + +ChooserBubbleUiViewDelegate::ChooserBubbleUiViewDelegate( + views::View* anchor_view, + views::BubbleBorder::Arrow anchor_arrow, + ChooserBubbleUiView* owner, + ChooserBubbleDelegate* chooser_bubble_delegate) + : views::BubbleDelegateView(anchor_view, anchor_arrow), + owner_(owner), + chooser_bubble_delegate_(chooser_bubble_delegate), + button_pressed_(false) { + views::GridLayout* layout = new views::GridLayout(this); + SetLayoutManager(layout); + + views::ColumnSet* column_set = layout->AddColumnSet(0); + column_set->AddColumn(views::GridLayout::FILL, views::GridLayout::FILL, 1, + views::GridLayout::USE_PREF, 0, 0); + + layout->StartRow(1, 0); + + // Create a table view + std::vector<ui::TableColumn> table_columns; + table_columns.push_back(ChooserTableColumn( + 0, "" /* Empty string makes the column title invisible */)); + chooser_table_model_ = new ChooserTableModel(chooser_bubble_delegate_); + table_view_ = new views::TableView(chooser_table_model_, table_columns, + views::TEXT_ONLY, true); + table_view_->set_select_on_remove(false); + chooser_table_model_->SetObserver(table_view_); + table_view_->SetObserver(this); + layout->AddView(table_view_->CreateParentIfNecessary(), 1, 1, + views::GridLayout::FILL, views::GridLayout::FILL, + kChooserPermissionBubbleWidth, + kChooserPermissionBubbleHeight); + if (chooser_bubble_delegate_->GetOptions().empty()) { + table_view_->SetEnabled(false); + } + + layout->AddPaddingRow(0, kItemMajorSpacing); + + views::View* button_row = new views::View(); + views::GridLayout* button_layout = new views::GridLayout(button_row); + views::ColumnSet* button_columns = button_layout->AddColumnSet(0); + button_row->SetLayoutManager(button_layout); + layout->StartRow(1, 0); + layout->AddView(button_row); + + // Lay out the Connect/Cancel buttons. + button_columns->AddColumn(views::GridLayout::TRAILING, + views::GridLayout::FILL, 100, + views::GridLayout::USE_PREF, 0, 0); + button_columns->AddPaddingColumn(0, + kItemMajorSpacing - (2 * kButtonBorderSize)); + button_columns->AddColumn(views::GridLayout::TRAILING, + views::GridLayout::FILL, 0, + views::GridLayout::USE_PREF, 0, 0); + button_layout->StartRow(0, 0); + + base::string16 connect_text = + l10n_util::GetStringUTF16(IDS_CHOOSER_BUBBLE_CONNECT_BUTTON_TEXT); + connect_button_ = new views::LabelButton(this, connect_text); + connect_button_->SetStyle(views::Button::STYLE_BUTTON); + // Disable the connect button at the beginning since no device selected yet. + connect_button_->SetEnabled(false); + button_layout->AddView(connect_button_); + chooser_table_model_->SetConnectButton(connect_button_); + + base::string16 cancel_text = + l10n_util::GetStringUTF16(IDS_CHOOSER_BUBBLE_CANCEL_BUTTON_TEXT); + cancel_button_ = new views::LabelButton(this, cancel_text); + cancel_button_->SetStyle(views::Button::STYLE_BUTTON); + button_layout->AddView(cancel_button_); + + button_layout->AddPaddingRow(0, kBubbleOuterMargin); +} + +ChooserBubbleUiViewDelegate::~ChooserBubbleUiViewDelegate() { + RemoveAllChildViews(true); + if (owner_) + owner_->Close(); + chooser_table_model_->SetObserver(nullptr); +} + +void ChooserBubbleUiViewDelegate::Close() { + if (!button_pressed_) + chooser_bubble_delegate_->Close(); + owner_ = nullptr; + GetWidget()->Close(); +} + +bool ChooserBubbleUiViewDelegate::ShouldShowCloseButton() const { + return true; +} + +bool ChooserBubbleUiViewDelegate::ShouldShowWindowTitle() const { + return true; +} + +base::string16 ChooserBubbleUiViewDelegate::GetWindowTitle() const { + return l10n_util::GetStringUTF16(IDS_CHOOSER_BUBBLE_PROMPT); +} + +void ChooserBubbleUiViewDelegate::OnWidgetDestroying(views::Widget* widget) { + views::BubbleDelegateView::OnWidgetDestroying(widget); + if (owner_) { + owner_->Close(); + owner_ = nullptr; + } +} + +void ChooserBubbleUiViewDelegate::ButtonPressed(views::Button* button, + const ui::Event& event) { + if (button == connect_button_) + chooser_bubble_delegate_->Select(table_view_->selection_model().active()); + else + chooser_bubble_delegate_->Cancel(); + button_pressed_ = true; + owner_->Close(); +} + +void ChooserBubbleUiViewDelegate::OnSelectionChanged() { + connect_button_->SetEnabled(!table_view_->selection_model().empty()); +} + +void ChooserBubbleUiViewDelegate::UpdateAnchor( + views::View* anchor_view, + views::BubbleBorder::Arrow anchor_arrow) { + if (GetAnchorView() == anchor_view && arrow() == anchor_arrow) + return; + + set_arrow(anchor_arrow); + + // Update the border in the bubble: will either add or remove the arrow. + views::BubbleFrameView* frame = + views::BubbleDelegateView::GetBubbleFrameView(); + views::BubbleBorder::Arrow adjusted_arrow = anchor_arrow; + if (base::i18n::IsRTL()) + adjusted_arrow = views::BubbleBorder::horizontal_mirror(adjusted_arrow); + frame->SetBubbleBorder(scoped_ptr<views::BubbleBorder>( + new views::BubbleBorder(adjusted_arrow, shadow(), color()))); + + // Reposition the bubble based on the updated arrow and view. + SetAnchorView(anchor_view); +} + +////////////////////////////////////////////////////////////////////////////// +// ChooserBubbleUiView + +ChooserBubbleUiView::ChooserBubbleUiView( + Browser* browser, + ChooserBubbleDelegate* chooser_bubble_delegate) + : browser_(browser), + chooser_bubble_delegate_(chooser_bubble_delegate), + chooser_bubble_ui_view_delegate_(nullptr) { + DCHECK(browser_); + DCHECK(chooser_bubble_delegate_); +} + +ChooserBubbleUiView::~ChooserBubbleUiView() {} + +void ChooserBubbleUiView::Show(BubbleReference bubble_reference) { + chooser_bubble_ui_view_delegate_ = new ChooserBubbleUiViewDelegate( + GetAnchorView(), GetAnchorArrow(), this, chooser_bubble_delegate_); + + // Set |parent_window| because some valid anchors can become hidden. + views::Widget* widget = views::Widget::GetWidgetForNativeWindow( + browser_->window()->GetNativeWindow()); + chooser_bubble_ui_view_delegate_->set_parent_window(widget->GetNativeView()); + + views::BubbleDelegateView::CreateBubble(chooser_bubble_ui_view_delegate_) + ->Show(); + + chooser_bubble_ui_view_delegate_->chooser_table_model_->Update(); +} + +void ChooserBubbleUiView::Close() { + if (chooser_bubble_ui_view_delegate_) { + chooser_bubble_ui_view_delegate_->Close(); + chooser_bubble_ui_view_delegate_ = nullptr; + } +} + +void ChooserBubbleUiView::UpdateAnchorPosition() { + if (chooser_bubble_ui_view_delegate_) { + chooser_bubble_ui_view_delegate_->UpdateAnchor(GetAnchorView(), + GetAnchorArrow()); + } +} + +views::View* ChooserBubbleUiView::GetAnchorView() { + BrowserView* browser_view = BrowserView::GetBrowserViewForBrowser(browser_); + + if (browser_->SupportsWindowFeature(Browser::FEATURE_LOCATIONBAR)) + return browser_view->GetLocationBarView()->location_icon_view(); + + if (browser_view->IsFullscreenBubbleVisible()) + return browser_view->exclusive_access_bubble()->GetView(); + + return browser_view->top_container(); +} + +views::BubbleBorder::Arrow ChooserBubbleUiView::GetAnchorArrow() { + if (browser_->SupportsWindowFeature(Browser::FEATURE_LOCATIONBAR)) + return views::BubbleBorder::TOP_LEFT; + return views::BubbleBorder::NONE; +} diff --git a/chrome/browser/ui/views/website_settings/chooser_bubble_ui_view.h b/chrome/browser/ui/views/website_settings/chooser_bubble_ui_view.h new file mode 100644 index 0000000..72a71b6 --- /dev/null +++ b/chrome/browser/ui/views/website_settings/chooser_bubble_ui_view.h @@ -0,0 +1,43 @@ +// Copyright 2015 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. + +#ifndef CHROME_BROWSER_UI_VIEWS_WEBSITE_SETTINGS_CHOOSER_BUBBLE_UI_VIEW_H_ +#define CHROME_BROWSER_UI_VIEWS_WEBSITE_SETTINGS_CHOOSER_BUBBLE_UI_VIEW_H_ + +#include "components/bubble/bubble_ui.h" +#include "ui/views/bubble/bubble_border.h" + +namespace views { +class View; +} + +class Browser; +class ChooserBubbleDelegate; +class ChooserBubbleUiViewDelegate; + +// ChooserBubbleUiView implements a chooser-based permission model, +// it uses table view to show a list of items (such as usb devices, etc.) +// for user to grant permission. It can be used by WebUsb, WebBluetooth. +class ChooserBubbleUiView : public BubbleUi { + public: + ChooserBubbleUiView(Browser* browser, + ChooserBubbleDelegate* chooser_bubble_delegate); + ~ChooserBubbleUiView() override; + + // BubbleUi: + void Show(BubbleReference bubble_reference) override; + void Close() override; + void UpdateAnchorPosition() override; + + private: + friend class ChooserBubbleUiViewDelegate; + views::View* GetAnchorView(); + views::BubbleBorder::Arrow GetAnchorArrow(); + + Browser* browser_; + ChooserBubbleDelegate* chooser_bubble_delegate_; + ChooserBubbleUiViewDelegate* chooser_bubble_ui_view_delegate_; +}; + +#endif // CHROME_BROWSER_UI_VIEWS_WEBSITE_SETTINGS_CHOOSER_BUBBLE_UI_VIEW_H_ diff --git a/chrome/browser/ui/website_settings/chooser_bubble_delegate.cc b/chrome/browser/ui/website_settings/chooser_bubble_delegate.cc new file mode 100644 index 0000000..b0c3ea5 --- /dev/null +++ b/chrome/browser/ui/website_settings/chooser_bubble_delegate.cc @@ -0,0 +1,26 @@ +// Copyright 2015 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/ui/website_settings/chooser_bubble_delegate.h" + +#if defined(OS_MACOSX) +#include "components/bubble/bubble_ui.h" +#endif + +ChooserBubbleDelegate::ChooserBubbleDelegate(Browser* browser) + : browser_(browser) {} + +ChooserBubbleDelegate::~ChooserBubbleDelegate() {} + +std::string ChooserBubbleDelegate::GetName() const { + return "ChooserBubble"; +} + +// TODO(juncai): Add chooser bubble ui cocoa code for Mac. +// Please refer to http://crbug.com/492204 for more information. +#if defined(OS_MACOSX) +scoped_ptr<BubbleUi> ChooserBubbleDelegate::BuildBubbleUi() { + return scoped_ptr<BubbleUi>(); +} +#endif diff --git a/chrome/browser/ui/website_settings/chooser_bubble_delegate.h b/chrome/browser/ui/website_settings/chooser_bubble_delegate.h new file mode 100644 index 0000000..6166e56 --- /dev/null +++ b/chrome/browser/ui/website_settings/chooser_bubble_delegate.h @@ -0,0 +1,82 @@ +// Copyright 2015 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. + +#ifndef CHROME_BROWSER_UI_WEBSITE_SETTINGS_CHOOSER_BUBBLE_DELEGATE_H_ +#define CHROME_BROWSER_UI_WEBSITE_SETTINGS_CHOOSER_BUBBLE_DELEGATE_H_ + +#include <vector> + +#include "base/macros.h" +#include "base/strings/string16.h" +#include "components/bubble/bubble_delegate.h" + +class Browser; + +// Subclass ChooserBubbleDelegate to implement a chooser bubble, which has +// some introductory text and a list of options that users can pick one of. +// Create an instance of your subclass and pass it to +// BubbleManager::ShowBubble() to show the bubble. Your subclass must define +// the set of options users can pick from; the actions taken after users +// select an item or press the 'Cancel' button or the bubble is closed. +// You can also override GetName() to identify the bubble you define for +// collecting metrics. +// After Select/Cancel/Close is called, this object is destroyed and call back +// into it is not allowed. +class ChooserBubbleDelegate : public BubbleDelegate { + public: + explicit ChooserBubbleDelegate(Browser* browser); + ~ChooserBubbleDelegate() override; + + // Since the set of options can change while the UI is visible an + // implementation should register an observer. + class Observer { + public: + // Called after the options list is initialized for the first time. + // OnOptionsInitialized should only be called once. + virtual void OnOptionsInitialized() = 0; + // Called after GetOptions()[index] has been added to the options and the + // newly added option is the last element in the options list. Calling + // GetOptions()[index] from inside a call to OnOptionAdded will see the + // added string since the options have already been updated. + virtual void OnOptionAdded(int index) = 0; + // Called when GetOptions()[index] is no longer present, and all later + // options have been moved earlier by 1 slot. Calling GetOptions()[index] + // from inside a call to OnOptionRemoved will NOT see the removed string + // since the options have already been updated. + virtual void OnOptionRemoved(int index) = 0; + + protected: + virtual ~Observer() {} + }; + + // BubbleDelegate: + std::string GetName() const override; + scoped_ptr<BubbleUi> BuildBubbleUi() override; + + // The set of options users can pick from. For example, it can be + // USB/Bluetooth devices names which are listed in the chooser bubble + // so that users can grant permission. + virtual const std::vector<base::string16>& GetOptions() const = 0; + + // These three functions are called just before this object is destroyed: + // Called when the user selects the |index|th element from the dialog. + virtual void Select(int index) = 0; + // Called when the user presses the 'Cancel' button in the dialog. + virtual void Cancel() = 0; + // Called when the user clicks outside the dialog or the dialog otherwise + // closes without the user taking an explicit action. + virtual void Close() = 0; + + // Only one observer may be registered at a time. + void set_observer(Observer* observer) { observer_ = observer; } + Observer* observer() const { return observer_; } + + private: + Browser* browser_; + Observer* observer_ = nullptr; + + DISALLOW_COPY_AND_ASSIGN(ChooserBubbleDelegate); +}; + +#endif // CHROME_BROWSER_UI_WEBSITE_SETTINGS_CHOOSER_BUBBLE_DELEGATE_H_ diff --git a/chrome/browser/usb/DEPS b/chrome/browser/usb/DEPS index 4e689e7..1e0f248 100644 --- a/chrome/browser/usb/DEPS +++ b/chrome/browser/usb/DEPS @@ -1,4 +1,6 @@ include_rules = [ + "+chrome/browser/ui", + "+components/bubble", "+device/core", "+device/devices_app/usb/public", "+device/usb", diff --git a/chrome/browser/usb/usb_chooser_bubble_delegate.cc b/chrome/browser/usb/usb_chooser_bubble_delegate.cc new file mode 100644 index 0000000..6815fab --- /dev/null +++ b/chrome/browser/usb/usb_chooser_bubble_delegate.cc @@ -0,0 +1,152 @@ +// Copyright 2015 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/usb/usb_chooser_bubble_delegate.h" + +#include "base/bind.h" +#include "base/stl_util.h" +#include "chrome/browser/profiles/profile.h" +#include "chrome/browser/usb/usb_chooser_context.h" +#include "chrome/browser/usb/usb_chooser_context_factory.h" +#include "content/public/browser/render_frame_host.h" +#include "content/public/browser/web_contents.h" +#include "device/core/device_client.h" +#include "device/devices_app/usb/type_converters.h" +#include "device/usb/usb_device.h" +#include "device/usb/usb_device_filter.h" +#include "url/gurl.h" + +namespace { + +// Check if the origin is in the description set. +bool FindOriginInDescriptorSet(const device::WebUsbDescriptorSet* set, + const GURL& origin) { + if (!set) + return false; + + if (ContainsValue(set->origins, origin)) + return true; + + for (const auto& config : set->configurations) { + if (ContainsValue(config.origins, origin)) + return true; + + for (const auto& function : config.functions) { + if (ContainsValue(function.origins, origin)) + return true; + } + } + + return false; +} + +} // namespace + +UsbChooserBubbleDelegate::UsbChooserBubbleDelegate( + Browser* browser, + mojo::Array<device::usb::DeviceFilterPtr> device_filters, + content::RenderFrameHost* render_frame_host, + const webusb::WebUsbPermissionBubble::GetPermissionCallback& callback) + : ChooserBubbleDelegate(browser), + render_frame_host_(render_frame_host), + callback_(callback), + usb_service_observer_(this), + weak_factory_(this) { + device::UsbService* usb_service = + device::DeviceClient::Get()->GetUsbService(); + if (!usb_service) + return; + + if (!usb_service_observer_.IsObserving(usb_service)) + usb_service_observer_.Add(usb_service); + + if (!device_filters.is_null()) + filters_ = device_filters.To<std::vector<device::UsbDeviceFilter>>(); + + usb_service->GetDevices(base::Bind( + &UsbChooserBubbleDelegate::GotUsbDeviceList, weak_factory_.GetWeakPtr())); +} + +UsbChooserBubbleDelegate::~UsbChooserBubbleDelegate() { + if (!callback_.is_null()) + callback_.Run(nullptr); +} + +const std::vector<base::string16>& UsbChooserBubbleDelegate::GetOptions() + const { + return devices_names_; +} + +void UsbChooserBubbleDelegate::Select(int index) { + size_t idx = static_cast<size_t>(index); + size_t num_options = devices_.size(); + DCHECK_LT(idx, num_options); + if (idx < num_options) { + content::WebContents* web_contents = + content::WebContents::FromRenderFrameHost(render_frame_host_); + GURL embedding_origin = + web_contents->GetMainFrame()->GetLastCommittedURL().GetOrigin(); + Profile* profile = + Profile::FromBrowserContext(web_contents->GetBrowserContext()); + UsbChooserContext* chooser_context = + UsbChooserContextFactory::GetForProfile(profile); + chooser_context->GrantDevicePermission( + render_frame_host_->GetLastCommittedURL().GetOrigin(), embedding_origin, + devices_[idx]->guid()); + + device::usb::DeviceInfoPtr device_info_ptr = + device::usb::DeviceInfo::From(*devices_[idx]); + callback_.Run(device_info_ptr.Pass()); + } else { + callback_.Run(nullptr); + } + callback_.reset(); // Reset |callback_| so that it is only run once. +} + +void UsbChooserBubbleDelegate::Cancel() {} + +void UsbChooserBubbleDelegate::Close() {} + +void UsbChooserBubbleDelegate::OnDeviceAdded( + scoped_refptr<device::UsbDevice> device) { + DCHECK(!ContainsValue(devices_, device)); + if (device::UsbDeviceFilter::MatchesAny(device, filters_) && + FindOriginInDescriptorSet( + device->webusb_allowed_origins(), + render_frame_host_->GetLastCommittedURL().GetOrigin())) { + devices_.push_back(device); + devices_names_.push_back(device->product_string()); + if (observer()) + observer()->OnOptionAdded(static_cast<int>(devices_names_.size()) - 1); + } +} + +void UsbChooserBubbleDelegate::OnDeviceRemoved( + scoped_refptr<device::UsbDevice> device) { + auto iter = std::find(devices_.begin(), devices_.end(), device); + if (iter != devices_.end()) { + int index = iter - devices_.begin(); + devices_.erase(iter); + devices_names_.erase(devices_names_.begin() + index); + if (observer()) + observer()->OnOptionRemoved(index); + } +} + +// Get a list of devices that can be shown in the chooser bubble UI for +// user to grant permsssion. +void UsbChooserBubbleDelegate::GotUsbDeviceList( + const std::vector<scoped_refptr<device::UsbDevice>>& devices) { + for (const auto& device : devices) { + if (device::UsbDeviceFilter::MatchesAny(device, filters_) && + FindOriginInDescriptorSet( + device->webusb_allowed_origins(), + render_frame_host_->GetLastCommittedURL().GetOrigin())) { + devices_.push_back(device); + devices_names_.push_back(device->product_string()); + } + } + if (observer()) + observer()->OnOptionsInitialized(); +} diff --git a/chrome/browser/usb/usb_chooser_bubble_delegate.h b/chrome/browser/usb/usb_chooser_bubble_delegate.h new file mode 100644 index 0000000..9d06131 --- /dev/null +++ b/chrome/browser/usb/usb_chooser_bubble_delegate.h @@ -0,0 +1,65 @@ +// Copyright 2015 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. + +#ifndef CHROME_BROWSER_USB_USB_CHOOSER_BUBBLE_DELEGATE_H_ +#define CHROME_BROWSER_USB_USB_CHOOSER_BUBBLE_DELEGATE_H_ + +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/memory/weak_ptr.h" +#include "base/scoped_observer.h" +#include "chrome/browser/ui/website_settings/chooser_bubble_delegate.h" +#include "components/webusb/public/interfaces/webusb_permission_bubble.mojom.h" +#include "device/usb/usb_service.h" +#include "mojo/public/cpp/bindings/array.h" + +namespace content { +class RenderFrameHost; +} + +namespace device { +class UsbDevice; +class UsbDeviceFilter; +} + +class Browser; + +// UsbChooserBubbleDelegate creates a chooser bubble for WebUsb. +class UsbChooserBubbleDelegate : public ChooserBubbleDelegate, + public device::UsbService::Observer { + public: + UsbChooserBubbleDelegate( + Browser* browser, + mojo::Array<device::usb::DeviceFilterPtr> device_filters, + content::RenderFrameHost* render_frame_host, + const webusb::WebUsbPermissionBubble::GetPermissionCallback& callback); + ~UsbChooserBubbleDelegate() override; + + // ChooserBubbleDelegate: + const std::vector<base::string16>& GetOptions() const override; + void Select(int index) override; + void Cancel() override; + void Close() override; + + // device::UsbService::Observer: + void OnDeviceAdded(scoped_refptr<device::UsbDevice> device) override; + void OnDeviceRemoved(scoped_refptr<device::UsbDevice> device) override; + + void GotUsbDeviceList( + const std::vector<scoped_refptr<device::UsbDevice>>& devices); + + private: + content::RenderFrameHost* const render_frame_host_; + webusb::WebUsbPermissionBubble::GetPermissionCallback callback_; + ScopedObserver<device::UsbService, device::UsbService::Observer> + usb_service_observer_; + std::vector<device::UsbDeviceFilter> filters_; + std::vector<scoped_refptr<device::UsbDevice>> devices_; + std::vector<base::string16> devices_names_; + base::WeakPtrFactory<UsbChooserBubbleDelegate> weak_factory_; + + DISALLOW_COPY_AND_ASSIGN(UsbChooserBubbleDelegate); +}; + +#endif // CHROME_BROWSER_USB_USB_CHOOSER_BUBBLE_DELEGATE_H_ diff --git a/chrome/browser/usb/usb_tab_helper.cc b/chrome/browser/usb/usb_tab_helper.cc index 29f875a..3eafaee 100644 --- a/chrome/browser/usb/usb_tab_helper.cc +++ b/chrome/browser/usb/usb_tab_helper.cc @@ -4,6 +4,10 @@ #include "chrome/browser/usb/usb_tab_helper.h" +#include <utility> + +#include "base/memory/scoped_ptr.h" +#include "chrome/browser/usb/web_usb_permission_bubble.h" #include "chrome/browser/usb/web_usb_permission_provider.h" #include "device/devices_app/usb/device_manager_impl.h" @@ -12,6 +16,11 @@ using content::WebContents; DEFINE_WEB_CONTENTS_USER_DATA_KEY(UsbTabHelper); +struct FrameUsbServices { + scoped_ptr<WebUSBPermissionProvider> permission_provider; + scoped_ptr<ChromeWebUsbPermissionBubble> permission_bubble; +}; + // static UsbTabHelper* UsbTabHelper::GetOrCreateForWebContents( WebContents* web_contents) { @@ -36,23 +45,50 @@ void UsbTabHelper::CreateDeviceManager( request.Pass()); } +void UsbTabHelper::CreatePermissionBubble( + content::RenderFrameHost* render_frame_host, + mojo::InterfaceRequest<webusb::WebUsbPermissionBubble> request) { + GetPermissionBubble(render_frame_host, request.Pass()); +} + UsbTabHelper::UsbTabHelper(WebContents* web_contents) : content::WebContentsObserver(web_contents) {} void UsbTabHelper::RenderFrameDeleted(RenderFrameHost* render_frame_host) { - permission_provider_.erase(render_frame_host); + frame_usb_services_.erase(render_frame_host); +} + +FrameUsbServices* UsbTabHelper::GetFrameUsbService( + content::RenderFrameHost* render_frame_host) { + FrameUsbServicesMap::const_iterator it = + frame_usb_services_.find(render_frame_host); + if (it == frame_usb_services_.end()) { + scoped_ptr<FrameUsbServices> frame_usb_services(new FrameUsbServices()); + it = (frame_usb_services_.insert( + std::make_pair(render_frame_host, frame_usb_services.Pass()))) + .first; + } + return it->second.get(); } void UsbTabHelper::GetPermissionProvider( RenderFrameHost* render_frame_host, mojo::InterfaceRequest<device::usb::PermissionProvider> request) { - const auto it = permission_provider_.find(render_frame_host); - if (it == permission_provider_.end()) { - scoped_ptr<WebUSBPermissionProvider> permission_provider( + FrameUsbServices* frame_usb_services = GetFrameUsbService(render_frame_host); + if (!frame_usb_services->permission_provider) { + frame_usb_services->permission_provider.reset( new WebUSBPermissionProvider(render_frame_host)); - permission_provider->Bind(request.Pass()); - permission_provider_[render_frame_host] = permission_provider.Pass(); - } else { - it->second->Bind(request.Pass()); } + frame_usb_services->permission_provider->Bind(request.Pass()); +} + +void UsbTabHelper::GetPermissionBubble( + content::RenderFrameHost* render_frame_host, + mojo::InterfaceRequest<webusb::WebUsbPermissionBubble> request) { + FrameUsbServices* frame_usb_services = GetFrameUsbService(render_frame_host); + if (!frame_usb_services->permission_bubble) { + frame_usb_services->permission_bubble.reset( + new ChromeWebUsbPermissionBubble(render_frame_host)); + } + frame_usb_services->permission_bubble->Bind(request.Pass()); } diff --git a/chrome/browser/usb/usb_tab_helper.h b/chrome/browser/usb/usb_tab_helper.h index 003f8dd..7d4cec5 100644 --- a/chrome/browser/usb/usb_tab_helper.h +++ b/chrome/browser/usb/usb_tab_helper.h @@ -19,7 +19,14 @@ class PermissionProvider; } } -class WebUSBPermissionProvider; +namespace webusb { +class WebUsbPermissionBubble; +} + +struct FrameUsbServices; + +typedef std::map<content::RenderFrameHost*, scoped_ptr<FrameUsbServices>> + FrameUsbServicesMap; // Per-tab owner of USB services provided to render frames within that tab. class UsbTabHelper : public content::WebContentsObserver, @@ -34,6 +41,10 @@ class UsbTabHelper : public content::WebContentsObserver, content::RenderFrameHost* render_frame_host, mojo::InterfaceRequest<device::usb::DeviceManager> request); + void CreatePermissionBubble( + content::RenderFrameHost* render_frame_host, + mojo::InterfaceRequest<webusb::WebUsbPermissionBubble> request); + private: explicit UsbTabHelper(content::WebContents* web_contents); friend class content::WebContentsUserData<UsbTabHelper>; @@ -41,12 +52,18 @@ class UsbTabHelper : public content::WebContentsObserver, // content::WebContentsObserver overrides: void RenderFrameDeleted(content::RenderFrameHost* render_frame_host) override; + FrameUsbServices* GetFrameUsbService( + content::RenderFrameHost* render_frame_host); + void GetPermissionProvider( content::RenderFrameHost* render_frame_host, mojo::InterfaceRequest<device::usb::PermissionProvider> request); - std::map<content::RenderFrameHost*, scoped_ptr<WebUSBPermissionProvider>> - permission_provider_; + void GetPermissionBubble( + content::RenderFrameHost* render_frame_host, + mojo::InterfaceRequest<webusb::WebUsbPermissionBubble> request); + + FrameUsbServicesMap frame_usb_services_; DISALLOW_COPY_AND_ASSIGN(UsbTabHelper); }; diff --git a/chrome/browser/usb/web_usb_permission_bubble.cc b/chrome/browser/usb/web_usb_permission_bubble.cc new file mode 100644 index 0000000..c7ca541 --- /dev/null +++ b/chrome/browser/usb/web_usb_permission_bubble.cc @@ -0,0 +1,45 @@ +// Copyright 2015 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/usb/web_usb_permission_bubble.h" + +#include "chrome/browser/ui/browser_finder.h" +#include "chrome/browser/ui/chrome_bubble_manager.h" +#include "chrome/browser/usb/usb_chooser_bubble_delegate.h" +#include "components/bubble/bubble_controller.h" +#include "content/public/browser/browser_thread.h" +#include "content/public/browser/render_frame_host.h" + +ChromeWebUsbPermissionBubble::ChromeWebUsbPermissionBubble( + content::RenderFrameHost* render_frame_host) + : render_frame_host_(render_frame_host) { + DCHECK_CURRENTLY_ON(content::BrowserThread::UI); + DCHECK(render_frame_host); +} + +ChromeWebUsbPermissionBubble::~ChromeWebUsbPermissionBubble() { + for (const auto& bubble : bubbles_) { + if (bubble) + bubble->CloseBubble(BUBBLE_CLOSE_FORCED); + } +} + +void ChromeWebUsbPermissionBubble::GetPermission( + mojo::Array<device::usb::DeviceFilterPtr> device_filters, + const GetPermissionCallback& callback) { + content::WebContents* web_contents = + content::WebContents::FromRenderFrameHost(render_frame_host_); + Browser* browser = chrome::FindBrowserWithWebContents(web_contents); + scoped_ptr<BubbleDelegate> bubble_delegate(new UsbChooserBubbleDelegate( + browser, device_filters.Pass(), render_frame_host_, callback)); + BubbleReference bubble_reference = + browser->GetBubbleManager()->ShowBubble(bubble_delegate.Pass()); + bubbles_.push_back(bubble_reference); +} + +void ChromeWebUsbPermissionBubble::Bind( + mojo::InterfaceRequest<webusb::WebUsbPermissionBubble> request) { + DCHECK_CURRENTLY_ON(content::BrowserThread::UI); + bindings_.AddBinding(this, request.Pass()); +} diff --git a/chrome/browser/usb/web_usb_permission_bubble.h b/chrome/browser/usb/web_usb_permission_bubble.h new file mode 100644 index 0000000..18247541 --- /dev/null +++ b/chrome/browser/usb/web_usb_permission_bubble.h @@ -0,0 +1,48 @@ +// Copyright 2015 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. + +#ifndef CHROME_BROWSER_USB_WEB_USB_PERMISSION_BUBBLE_H_ +#define CHROME_BROWSER_USB_WEB_USB_PERMISSION_BUBBLE_H_ + +#include <vector> + +#include "base/macros.h" +#include "components/bubble/bubble_reference.h" +#include "components/webusb/public/interfaces/webusb_permission_bubble.mojom.h" +#include "mojo/common/weak_binding_set.h" +#include "mojo/public/cpp/bindings/array.h" +#include "mojo/public/cpp/bindings/interface_request.h" + +namespace content { +class RenderFrameHost; +} + +namespace device { +class UsbDevice; +} + +// Implementation of the public webusb::WebUsbPermissionBubble interface. +// This interface can be used by a webpage to request permission from user +// to access a certain device. +class ChromeWebUsbPermissionBubble : public webusb::WebUsbPermissionBubble { + public: + explicit ChromeWebUsbPermissionBubble( + content::RenderFrameHost* render_frame_host); + + ~ChromeWebUsbPermissionBubble() override; + + // webusb::WebUsbPermissionBubble: + void GetPermission(mojo::Array<device::usb::DeviceFilterPtr> device_filters, + const GetPermissionCallback& callback) override; + void Bind(mojo::InterfaceRequest<webusb::WebUsbPermissionBubble> request); + + private: + content::RenderFrameHost* const render_frame_host_; + mojo::WeakBindingSet<webusb::WebUsbPermissionBubble> bindings_; + std::vector<BubbleReference> bubbles_; + + DISALLOW_COPY_AND_ASSIGN(ChromeWebUsbPermissionBubble); +}; + +#endif // CHROME_BROWSER_USB_WEB_USB_PERMISSION_BUBBLE_H_ |