blob: 7dd430f9a7e3d714d7f21604fd2b5b2f03026f4b [file] [log] [blame]
// Copyright 2019 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 "components/exo/drag_drop_operation.h"
#include "ash/drag_drop/drag_drop_controller.h"
#include "base/barrier_closure.h"
#include "components/exo/data_offer.h"
#include "components/exo/data_source.h"
#include "components/exo/seat.h"
#include "components/exo/surface.h"
#include "components/viz/common/frame_sinks/copy_output_request.h"
#include "components/viz/common/frame_sinks/copy_output_result.h"
#include "ui/aura/client/drag_drop_client.h"
#include "ui/base/dragdrop/os_exchange_data.h"
#include "ui/display/display.h"
#include "ui/display/screen.h"
#include "ui/gfx/transform_util.h"
namespace exo {
namespace {
uint32_t DndActionsToDragOperations(const base::flat_set<DndAction>& actions) {
uint32_t dnd_operations = 0;
for (const DndAction action : actions) {
switch (action) {
case DndAction::kNone:
FALLTHROUGH;
// We don't support the ask action
case DndAction::kAsk:
break;
case DndAction::kCopy:
dnd_operations |= ui::DragDropTypes::DragOperation::DRAG_COPY;
break;
case DndAction::kMove:
dnd_operations |= ui::DragDropTypes::DragOperation::DRAG_MOVE;
break;
}
}
return dnd_operations;
}
#if defined(OS_CHROMEOS)
DndAction DragOperationsToPreferredDndAction(int op) {
if (op & ui::DragDropTypes::DragOperation::DRAG_COPY)
return DndAction::kCopy;
if (op & ui::DragDropTypes::DragOperation::DRAG_MOVE)
return DndAction::kMove;
return DndAction::kNone;
}
#endif
DndAction DragOperationToDndAction(int op) {
switch (op) {
case ui::DragDropTypes::DragOperation::DRAG_NONE:
return DndAction::kNone;
case ui::DragDropTypes::DragOperation::DRAG_MOVE:
return DndAction::kMove;
case ui::DragDropTypes::DragOperation::DRAG_COPY:
return DndAction::kCopy;
default:
NOTREACHED();
return DndAction::kNone;
}
}
} // namespace
base::WeakPtr<DragDropOperation> DragDropOperation::Create(
DataSource* source,
Surface* origin,
Surface* icon,
ui::DragDropTypes::DragEventSource event_source) {
auto* dnd_op = new DragDropOperation(source, origin, icon, event_source);
return dnd_op->weak_ptr_factory_.GetWeakPtr();
}
DragDropOperation::DragDropOperation(
DataSource* source,
Surface* origin,
Surface* icon,
ui::DragDropTypes::DragEventSource event_source)
: SurfaceTreeHost("ExoDragDropOperation"),
source_(std::make_unique<ScopedDataSource>(source, this)),
origin_(std::make_unique<ScopedSurface>(origin, this)),
drag_start_point_(display::Screen::GetScreen()->GetCursorScreenPoint()),
os_exchange_data_(std::make_unique<ui::OSExchangeData>()),
event_source_(event_source),
weak_ptr_factory_(this) {
aura::Window* root_window = origin_->get()->window()->GetRootWindow();
DCHECK(root_window);
#if defined(OS_CHROMEOS)
drag_drop_controller_ = static_cast<ash::DragDropController*>(
aura::client::GetDragDropClient(root_window));
#else
drag_drop_controller_ = aura::client::GetDragDropClient(root_window);
#endif
DCHECK(drag_drop_controller_);
if (drag_drop_controller_->IsDragDropInProgress())
drag_drop_controller_->DragCancel();
drag_drop_controller_->AddObserver(this);
if (icon)
icon_ = std::make_unique<ScopedSurface>(icon, this);
auto start_op_callback =
base::BindOnce(&DragDropOperation::ScheduleStartDragDropOperation,
weak_ptr_factory_.GetWeakPtr());
// Make the count kMaxClipboardDataTypes + 1 so we can wait for the icon to be
// captured as well.
counter_ = base::BarrierClosure(kMaxClipboardDataTypes + 1,
std::move(start_op_callback));
source->GetDataForPreferredMimeTypes(
base::BindOnce(&DragDropOperation::OnTextRead,
weak_ptr_factory_.GetWeakPtr()),
DataSource::ReadDataCallback(),
base::BindOnce(&DragDropOperation::OnHTMLRead,
weak_ptr_factory_.GetWeakPtr()),
DataSource::ReadDataCallback(), counter_);
if (icon) {
origin_->get()->window()->AddChild(host_window());
SetRootSurface(icon);
}
}
DragDropOperation::~DragDropOperation() {
drag_drop_controller_->RemoveObserver(this);
if (source_)
source_->get()->Cancelled();
if (drag_drop_controller_->IsDragDropInProgress() && started_by_this_object_)
drag_drop_controller_->DragCancel();
}
void DragDropOperation::AbortIfPending() {
if (!started_by_this_object_)
delete this;
}
void DragDropOperation::OnTextRead(const std::string& mime_type,
base::string16 data) {
DCHECK(os_exchange_data_);
os_exchange_data_->SetString(std::move(data));
// Prefer to use the HTML MIME type if possible
if (mime_type_.empty())
mime_type_ = mime_type;
counter_.Run();
}
void DragDropOperation::OnHTMLRead(const std::string& mime_type,
base::string16 data) {
DCHECK(os_exchange_data_);
os_exchange_data_->SetHtml(std::move(data), GURL());
mime_type_ = mime_type;
counter_.Run();
}
void DragDropOperation::OnSurfaceCommit() {
SurfaceTreeHost::OnSurfaceCommit();
if (icon_)
CaptureDragIcon();
}
void DragDropOperation::CaptureDragIcon() {
SubmitCompositorFrame();
std::unique_ptr<viz::CopyOutputRequest> request =
std::make_unique<viz::CopyOutputRequest>(
viz::CopyOutputRequest::ResultFormat::RGBA_BITMAP,
base::BindOnce(&DragDropOperation::OnDragIconCaptured,
weak_ptr_factory_.GetWeakPtr()));
host_window()->layer()->RequestCopyOfOutput(std::move(request));
}
void DragDropOperation::OnDragIconCaptured(
std::unique_ptr<viz::CopyOutputResult> icon_result) {
gfx::ImageSkia icon_skia;
// An empty response means the request was deleted before it was completed. If
// this happens, and no operation has yet finished, restart the capture.
if (icon_result->IsEmpty()) {
CaptureDragIcon();
return;
}
float scale_factor = origin_->get()->window()->layer()->device_scale_factor();
icon_skia = gfx::ImageSkia(
gfx::ImageSkiaRep(icon_result->AsSkBitmap(), scale_factor));
if (os_exchange_data_) {
os_exchange_data_->provider().SetDragImage(
icon_skia, -icon_->get()->GetBufferOffset());
} else {
#if defined(OS_CHROMEOS)
drag_drop_controller_->SetDragImage(icon_skia,
-icon_->get()->GetBufferOffset());
#endif
}
if (!captured_icon_) {
captured_icon_ = true;
counter_.Run();
}
}
void DragDropOperation::ScheduleStartDragDropOperation() {
// StartDragAndDrop uses a nested run loop. When restarting, we a) don't want
// to interrupt the callers task for an arbitrary period of time and b) want
// to let any nested run loops that are currently running to have a chance to
// exit to avoid arbitrarily deep nesting. We can accomplish both of those
// things by posting a new task to actually start the drag and drop operation.
base::SequencedTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(&DragDropOperation::StartDragDropOperation,
weak_ptr_factory_.GetWeakPtr()));
}
void DragDropOperation::StartDragDropOperation() {
uint32_t dnd_operations =
DndActionsToDragOperations(source_->get()->GetActions());
started_by_this_object_ = true;
// This triggers a nested run loop that terminates when the drag and drop
// operation is completed.
int op = drag_drop_controller_->StartDragAndDrop(
std::move(os_exchange_data_), origin_->get()->window()->GetRootWindow(),
origin_->get()->window(), drag_start_point_, dnd_operations,
event_source_);
if (op) {
// Success
// TODO(crbug.com/994065) This is currently not the actual mime type used by
// the recipient, just an arbitrary one we pick out of the offered types so
// we can report back whether or not the drop can succeed. This may need to
// change in the future.
source_->get()->Target(mime_type_);
source_->get()->Action(DragOperationToDndAction(op));
source_->get()->DndDropPerformed();
source_->get()->DndFinished();
// Reset |source_| so it the destructor doesn't try to cancel it.
source_.reset();
}
// On failure the destructor will handle canceling the data source.
delete this;
}
void DragDropOperation::OnDragStarted() {
if (!started_by_this_object_)
delete this;
}
void DragDropOperation::OnDragEnded() {}
#if defined(OS_CHROMEOS)
void DragDropOperation::OnDragActionsChanged(int actions) {
if (!started_by_this_object_)
return;
DndAction dnd_action = DragOperationsToPreferredDndAction(actions);
// We send a mime type along with the action to indicate to the application
// that dropping is/is not currently possible. We do not currently know of
// any applications that care about the specific mime type until the drop is
// actually performed.
if (dnd_action != DndAction::kNone)
source_->get()->Target(mime_type_);
else
source_->get()->Target(base::nullopt);
source_->get()->Action(dnd_action);
}
#endif
void DragDropOperation::OnSurfaceDestroying(Surface* surface) {
if (surface == origin_->get() || surface == icon_->get()) {
delete this;
} else {
NOTREACHED();
}
}
void DragDropOperation::OnDataSourceDestroying(DataSource* source) {
if (source == source_->get()) {
source_.reset();
delete this;
} else {
NOTREACHED();
}
}
} // namespace exo