Commit 7362c644 authored by Leonard Grey's avatar Leonard Grey Committed by Commit Bot

Introduce Commander backend/coordination layer

This is the core of the Commander.

It:
- Receives input from the UI and responds with view models
- Handles coordinating multiple sources of commands
- Sorts and filters results
- Serves as an intermediary between the view and a secondary
  backend if a composite command is being entered

Bug: 1014639

Change-Id: Ife8022e5e5471cbe6dc794823f7acf1de7023d40
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2304970Reviewed-by: default avatarElly Fong-Jones <ellyjones@chromium.org>
Commit-Queue: Leonard Grey <lgrey@chromium.org>
Cr-Commit-Position: refs/heads/master@{#792860}
parent 861595cb
...@@ -917,6 +917,13 @@ static_library("ui") { ...@@ -917,6 +917,13 @@ static_library("ui") {
"chrome_web_modal_dialog_manager_delegate.h", "chrome_web_modal_dialog_manager_delegate.h",
"collected_cookies_infobar_delegate.cc", "collected_cookies_infobar_delegate.cc",
"collected_cookies_infobar_delegate.h", "collected_cookies_infobar_delegate.h",
"commander/command_source.cc",
"commander/command_source.h",
"commander/commander_backend.h",
"commander/commander_controller.cc",
"commander/commander_controller.h",
"commander/commander_view_model.cc",
"commander/commander_view_model.h",
"confirm_bubble_model.cc", "confirm_bubble_model.cc",
"confirm_bubble_model.h", "confirm_bubble_model.h",
"content_settings/content_setting_bubble_model.cc", "content_settings/content_setting_bubble_model.cc",
......
# Commander overview
This directory contains the bulk of the Commander, a new UI surface that allows
users to access most Chromium functionality via a keyboard interface.
## Components
The three main components of the commander are:
### View
The view is responsible for passing user input to the controller and displaying
the subsequent results, delivered in a `CommanderViewModel`. The implementation
will most likely be WebUI hosted in a Views toolkit widget.
### Command Sources
Command sources (`CommandSource`) are responsible for providing a list of
possible commands (`CommandItem`) for the user's current context, then
executing them if they are chosen.
### Controller
The controller (`CommanderController`) is responsible for maintaining a
list of command sources, passing user input to them, and sorting and ranking
the result.
If a command requires more input (for example, choosing a window to move the
current tab to), the command source will provide a delegate which implements
the same `CommanderBackend` interface as the controller. The controller then
mediates between the delegate and the view.
## Relationships
To sum up the relationships of the classes in this file:
`CommandController` (a singleton), owns multiple `CommandSource`s. On user
input, each `CommandSource` provides the controller with matching
`CommandItems`. The controller sorts, ranks and processes the `CommandItems`,
producing a `CommanderViewModel` and sending it to the view.
When the controller receives a message indicating that the user has chosen an
option, the `CommandItem` is executed if it's "one-shot". Otherwise, it's
a composite command and a delegate `CommanderBackend` is created. The controller
mediates between the delegate and view, passing input to the delegate and view
models to the view until the command is either completed or cancelled.
// Copyright 2020 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/commander/command_source.h"
namespace commander {
CommandItem::CommandItem() = default;
CommandItem::~CommandItem() = default;
CommandItem::CommandItem(CommandItem&& other) = default;
CommandItem& CommandItem::operator=(CommandItem&& other) = default;
CommandItem::Type CommandItem::GetType() {
DCHECK(!command || !delegate_factory);
if (delegate_factory)
return kComposite;
return kOneShot;
}
} // namespace commander
// Copyright 2020 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_COMMANDER_COMMAND_SOURCE_H_
#define CHROME_BROWSER_UI_COMMANDER_COMMAND_SOURCE_H_
#include "base/callback.h"
#include "base/optional.h"
#include "ui/gfx/range/range.h"
class Browser;
namespace commander {
class CommanderBackend;
// Represents a single option that can be presented in the command palette.
struct CommandItem {
public:
enum Type {
// On selection, the command is invoked and the UI should close.
kOneShot,
// On selection, the user is prompted for further information.
kComposite,
};
CommandItem();
virtual ~CommandItem();
CommandItem(const CommandItem& other) = delete;
CommandItem& operator=(const CommandItem& other) = delete;
CommandItem(CommandItem&& other);
CommandItem& operator=(CommandItem&& other);
Type GetType();
// The title to display to the user.
base::string16 title;
// The code to execute if the user selects this option, if it's a one-shot.
// Must be unset if |delegate_factory| is set.
base::Optional<base::OnceClosure> command;
// If the user selects this option and further input is required,
// this creates an item-specific backend to handle the rest of the command.
base::Optional<base::OnceCallback<std::unique_ptr<CommanderBackend>()>>
delegate_factory;
// How relevant the item is to user input. Expected range is (0,1], with 1
// indicating a perfect match (in the absence of other criteria, this boils
// down to an exact string match).
double score;
// Ranges of indices in |item|'s title that correspond to user input.
// For example, given user input "comitmlt" and a command called "Command Item
// Match Result", this would result in {(0, 3), (8, 10), (13,14), (23,25)},
// representing:
// [Com]mand [It]em [M]atch Resu[lt]
std::vector<gfx::Range> matched_ranges;
};
// Provides and ranks available commands in response to user input. The
// intention is for every system available through the commander to
// provide its own source, which is responsible for tracking the state and
// context necessary to provide appropriate commands from that system.
class CommandSource {
public:
using CommandResults = std::vector<std::unique_ptr<CommandItem>>;
CommandSource() = default;
virtual ~CommandSource() = default;
// Returns a list of scored commands to return for |input|, or an empty
// list if none are appropriate. The commands are not guaranteed to be in
// any particular order. |browser| is the browser the active commander
// is attached to.
virtual CommandResults GetCommands(const base::string16& input,
Browser* browser) const = 0;
};
} // namespace commander
#endif // CHROME_BROWSER_UI_COMMANDER_COMMAND_SOURCE_H_
// Copyright 2020 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_COMMANDER_COMMANDER_BACKEND_H_
#define CHROME_BROWSER_UI_COMMANDER_COMMANDER_BACKEND_H_
#include "base/callback.h"
#include "base/strings/string16.h"
class Browser;
namespace commander {
struct CommanderViewModel;
// An abstract interface for an object that responds to input from the UI layer
// and responds with a view model representing results or some action.
class CommanderBackend {
public:
using ViewModelUpdateCallback =
base::RepeatingCallback<void(CommanderViewModel view_model)>;
CommanderBackend() = default;
virtual ~CommanderBackend() = default;
// Called when user input changes. |text| is the full contents of
// the textfield. |browser| is the browser the commander UI is currently
// attached to.
virtual void OnTextChanged(const base::string16& text, Browser* browser) = 0;
// Called when the user has selected (clicked or pressed enter) the option at
// |command_index| from the result set identified by |result_set_id|.
// If |result_set_id| is stale due to race conditions, this is a no-op to
// ensure that we don't perform an action the user didn't intend.
virtual void OnCommandSelected(size_t command_index, int result_set_id) = 0;
// Sets the callback to be used when a fresh view model is ready to be
// displayed. Invocations of the callback are not necessarily 1:1 or
// synchronous with user input, since some command sources may be async or
// provide incremental results.
virtual void SetUpdateCallback(ViewModelUpdateCallback callback) = 0;
};
} // namespace commander
#endif // CHROME_BROWSER_UI_COMMANDER_COMMANDER_BACKEND_H_
// Copyright 2020 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/commander/commander_controller.h"
#include "base/memory/ptr_util.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/commander/commander_view_model.h"
namespace commander {
namespace {
CommanderController::CommandSources CreateDefaultSources() {
return {};
}
} // namespace
CommanderController::CommanderController()
: CommanderController(CreateDefaultSources()) {}
CommanderController::CommanderController(CommandSources sources)
: current_result_set_id_(0), sources_(std::move(sources)) {}
CommanderController::~CommanderController() = default;
void CommanderController::OnDelegateViewModelCallback(
CommanderViewModel view_model) {
view_model.result_set_id = ++current_result_set_id_;
callback_.Run(view_model);
}
void CommanderController::OnTextChanged(const base::string16& text,
Browser* browser) {
if (delegate_.get())
return delegate_->OnTextChanged(text, browser);
std::vector<std::unique_ptr<CommandItem>> items;
for (auto& source : sources_) {
auto commands = source->GetCommands(text, browser);
items.insert(items.end(), std::make_move_iterator(commands.begin()),
std::make_move_iterator(commands.end()));
}
// Just sort for now.
std::sort(std::begin(items), std::end(items),
[](const std::unique_ptr<CommandItem>& left,
const std::unique_ptr<CommandItem>& right) {
return left->score > right->score;
});
// TODO(lgrey): Threshold this at some kind of max items.
current_items_ = std::move(items);
CommanderViewModel vm;
vm.result_set_id = ++current_result_set_id_;
vm.action = CommanderViewModel::Action::kDisplayResults;
for (auto& item : current_items_) {
vm.items.emplace_back(item->title, item->matched_ranges);
}
callback_.Run(vm);
}
void CommanderController::OnCommandSelected(size_t command_index,
int result_set_id) {
if (delegate_.get())
return delegate_->OnCommandSelected(command_index, result_set_id);
if (command_index >= current_items_.size() ||
result_set_id != current_result_set_id_)
return;
CommandItem* item = current_items_[command_index].get();
if (item->GetType() == CommandItem::Type::kOneShot) {
// Dismiss the view.
CommanderViewModel vm;
vm.result_set_id = ++current_result_set_id_;
vm.action = CommanderViewModel::Action::kClose;
callback_.Run(vm);
std::move(*(item->command)).Run();
} else {
delegate_ = std::move(*(item->delegate_factory)).Run();
// base::Unretained is safe since we own |delegate_|.
delegate_->SetUpdateCallback(
base::BindRepeating(&CommanderController::OnDelegateViewModelCallback,
base::Unretained(this)));
// Tell the view to requery.
CommanderViewModel vm;
vm.result_set_id = ++current_result_set_id_;
vm.action = CommanderViewModel::Action::kPrompt;
callback_.Run(vm);
}
}
void CommanderController::SetUpdateCallback(ViewModelUpdateCallback callback) {
callback_ = std::move(callback);
}
// static
std::unique_ptr<CommanderController>
CommanderController::CreateWithSourcesForTesting(CommandSources sources) {
auto* instance = new CommanderController(std::move(sources));
return base::WrapUnique(instance);
}
} // namespace commander
// Copyright 2020 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_COMMANDER_COMMANDER_CONTROLLER_H_
#define CHROME_BROWSER_UI_COMMANDER_COMMANDER_CONTROLLER_H_
#include "chrome/browser/ui/commander/commander_backend.h"
#include "chrome/browser/ui/commander/command_source.h"
namespace commander {
// The primary CommanderBackend, responsible for aggregating results for
// multiple command sources. If the user selects a composite command (one
// that requires additional user input), this object delegates the
// CommandBackend implementation to |delegate_|.
class CommanderController : public CommanderBackend {
public:
using CommandSources = std::vector<std::unique_ptr<CommandSource>>;
CommanderController();
~CommanderController() override;
// Disallow copy and assign.
CommanderController(const CommanderController& other) = delete;
CommanderController& operator=(const CommanderController& other) = delete;
// CommandPaletteBackend overrides.
void OnTextChanged(const base::string16& text, Browser* browser) override;
void OnCommandSelected(size_t command_index, int result_set_id) override;
void SetUpdateCallback(ViewModelUpdateCallback callback) override;
static std::unique_ptr<CommanderController> CreateWithSourcesForTesting(
CommandSources sources);
private:
explicit CommanderController(CommandSources sources);
// If a delegate is installed, this serves as the delegate's view model
// callback, allowing the controller to decorate or mutate the view model
// before passing it back to the view, if necessary.
void OnDelegateViewModelCallback(CommanderViewModel view_model);
std::vector<std::unique_ptr<CommandItem>> current_items_;
int current_result_set_id_;
CommandSources sources_;
ViewModelUpdateCallback callback_;
std::unique_ptr<CommanderBackend> delegate_;
};
} // namespace commander
#endif // CHROME_BROWSER_UI_COMMANDER_COMMANDER_CONTROLLER_H_
This diff is collapsed.
// Copyright 2020 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/commander/commander_view_model.h"
namespace commander {
CommandItemViewModel::CommandItemViewModel(
base::string16& title,
std::vector<gfx::Range>& matched_ranges)
: title(title), matched_ranges(matched_ranges) {}
CommandItemViewModel::~CommandItemViewModel() = default;
CommandItemViewModel::CommandItemViewModel(const CommandItemViewModel& other) =
default;
CommanderViewModel::CommanderViewModel() = default;
CommanderViewModel::~CommanderViewModel() = default;
CommanderViewModel::CommanderViewModel(const CommanderViewModel& other) =
default;
} // namespace commander
// Copyright 2020 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_COMMANDER_COMMANDER_VIEW_MODEL_H_
#define CHROME_BROWSER_UI_COMMANDER_COMMANDER_VIEW_MODEL_H_
#include <vector>
#include "base/strings/string16.h"
#include "ui/gfx/range/range.h"
namespace commander {
// A view model for a single command to be presented by the commander UI.
struct CommandItemViewModel {
public:
CommandItemViewModel(base::string16& title,
std::vector<gfx::Range>& matched_ranges);
~CommandItemViewModel();
CommandItemViewModel(const CommandItemViewModel& other);
// The displayed title of the command.
base::string16 title;
// The locations of spans in |title| that should be emphasised to
// indicate to the user why the command was surfaced for their input.
std::vector<gfx::Range> matched_ranges;
};
// A view model for a set of results to be presented by the commander UI.
struct CommanderViewModel {
enum Action {
// Display the items in |items|.
kDisplayResults,
// Close the UI. Typically sent after a command has been executed.
kClose,
// Clear the input and requery. Sent when the user has selected a command
// that needs further user input.
kPrompt,
};
public:
CommanderViewModel();
~CommanderViewModel();
CommanderViewModel(const CommanderViewModel& other);
// An identifier for this result set. See discussion in
// commander_backend.h for more details.
int result_set_id;
// A pre-ranked list of items to display. Can be empty if there are
// no results, or |action| is not kDisplayResults.
std::vector<CommandItemViewModel> items;
// The action the view should take in response to receiving this view model.
Action action;
};
} // namespace commander
#endif // CHROME_BROWSER_UI_COMMANDER_COMMANDER_VIEW_MODEL_H_
...@@ -4244,6 +4244,7 @@ test("unit_tests") { ...@@ -4244,6 +4244,7 @@ test("unit_tests") {
"../browser/ui/browser_instant_controller_unittest.cc", "../browser/ui/browser_instant_controller_unittest.cc",
"../browser/ui/browser_unittest.cc", "../browser/ui/browser_unittest.cc",
"../browser/ui/browser_window_state_unittest.cc", "../browser/ui/browser_window_state_unittest.cc",
"../browser/ui/commander/commander_controller_unittest.cc",
"../browser/ui/content_settings/content_setting_bubble_model_unittest.cc", "../browser/ui/content_settings/content_setting_bubble_model_unittest.cc",
"../browser/ui/content_settings/content_setting_image_model_unittest.cc", "../browser/ui/content_settings/content_setting_image_model_unittest.cc",
"../browser/ui/content_settings/content_setting_media_image_model_unittest.mm", "../browser/ui/content_settings/content_setting_media_image_model_unittest.mm",
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment