// 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 "base/barrier_closure.h" #include "base/threading/sequenced_task_runner_handle.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" #if defined(OS_CHROMEOS) #include "ash/drag_drop/drag_drop_controller.h" #endif // defined(OS_CHROMEOS) namespace exo { namespace { uint32_t DndActionsToDragOperations(const base::flat_set& 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::Create( DataSource* source, Surface* origin, Surface* icon, const gfx::Point& drag_start_point, ui::DragDropTypes::DragEventSource event_source) { auto* dnd_op = new DragDropOperation(source, origin, icon, drag_start_point, event_source); return dnd_op->weak_ptr_factory_.GetWeakPtr(); } DragDropOperation::DragDropOperation( DataSource* source, Surface* origin, Surface* icon, const gfx::Point& drag_start_point, ui::DragDropTypes::DragEventSource event_source) : SurfaceTreeHost("ExoDragDropOperation"), source_(std::make_unique(source, this)), origin_(std::make_unique(origin, this)), drag_start_point_(drag_start_point), os_exchange_data_(std::make_unique()), 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( 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(icon, this); auto start_op_callback = base::BindOnce(&DragDropOperation::ScheduleStartDragDropOperation, weak_ptr_factory_.GetWeakPtr()); // When the icon is present, make the count kMaxClipboardDataTypes + 1 so we // can wait for the icon to be captured as well. counter_ = base::BarrierClosure(kMaxClipboardDataTypes + (icon ? 1 : 0), 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 request = std::make_unique( viz::CopyOutputRequest::ResultFormat::RGBA_BITMAP, base::BindOnce(&DragDropOperation::OnDragIconCaptured, weak_ptr_factory_.GetWeakPtr())); request->set_result_task_runner(base::SequencedTaskRunnerHandle::Get()); host_window()->layer()->RequestCopyOfOutput(std::move(request)); } void DragDropOperation::OnDragIconCaptured( std::unique_ptr 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()); base::WeakPtr weak_ptr = weak_ptr_factory_.GetWeakPtr(); 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_); // The instance deleted during StartDragAndDrop's nested RunLoop. if (!weak_ptr) return; 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