123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517 |
- // 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/check.h"
- #include "base/pickle.h"
- #include "base/strings/string_split.h"
- #include "base/threading/sequenced_task_runner_handle.h"
- #include "build/chromeos_buildflags.h"
- #include "components/exo/data_exchange_delegate.h"
- #include "components/exo/data_offer.h"
- #include "components/exo/data_source.h"
- #include "components/exo/seat.h"
- #include "components/exo/shell_surface_base.h"
- #include "components/exo/shell_surface_util.h"
- #include "components/exo/surface.h"
- #include "components/exo/surface_tree_host.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/aura/window_tracker.h"
- #include "ui/base/clipboard/file_info.h"
- #include "ui/base/data_transfer_policy/data_transfer_endpoint.h"
- #include "ui/base/dragdrop/drag_drop_types.h"
- #include "ui/base/dragdrop/mojom/drag_drop_types.mojom.h"
- #include "ui/base/dragdrop/os_exchange_data.h"
- #include "ui/compositor/layer.h"
- #include "ui/display/display.h"
- #include "ui/display/screen.h"
- #include "ui/gfx/geometry/point.h"
- #include "ui/gfx/geometry/point_conversions.h"
- #include "ui/gfx/geometry/point_f.h"
- #include "ui/gfx/geometry/transform_util.h"
- #include "ui/gfx/geometry/vector2d.h"
- #include "url/gurl.h"
- #if BUILDFLAG(IS_CHROMEOS_ASH)
- #include "ash/drag_drop/drag_drop_controller.h"
- #include "chromeos/ui/base/window_properties.h"
- #include "components/exo/extended_drag_source.h"
- #include "ui/base/data_transfer_policy/data_transfer_endpoint_serializer.h"
- #endif // BUILDFLAG(IS_CHROMEOS_ASH)
- namespace exo {
- namespace {
- using ::ui::mojom::DragOperation;
- 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 BUILDFLAG(IS_CHROMEOS_ASH)
- 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(DragOperation op) {
- switch (op) {
- case DragOperation::kNone:
- return DndAction::kNone;
- case DragOperation::kMove:
- return DndAction::kMove;
- case DragOperation::kCopy:
- return DndAction::kCopy;
- case DragOperation::kLink:
- return DndAction::kAsk;
- default:
- NOTREACHED() << op;
- return DndAction::kNone;
- }
- NOTREACHED();
- }
- } // namespace
- // Internal representation of a drag icon surface. Used when a non-null surface
- // is passed in wl_data_device::start_drag requests.
- // TODO(crbug.com/1119385): Rework icon implementation to avoid frame copies.
- class DragDropOperation::IconSurface final : public SurfaceTreeHost,
- public ScopedSurface {
- public:
- IconSurface(Surface* icon, DragDropOperation* operation)
- : SurfaceTreeHost("ExoDragIcon"),
- ScopedSurface(icon, operation),
- operation_(operation) {
- DCHECK(operation_);
- DCHECK(!icon->HasSurfaceDelegate());
- Surface* origin_surface = operation_->origin_->get();
- origin_surface->window()->AddChild(host_window());
- SetRootSurface(icon);
- }
- IconSurface(const IconSurface&) = delete;
- IconSurface& operator=(const IconSurface&) = delete;
- ~IconSurface() override = default;
- private:
- // SurfaceTreeHost:
- void OnSurfaceCommit() override {
- SurfaceTreeHost::OnSurfaceCommit();
- RequestCaptureIcon();
- }
- void RequestCaptureIcon() {
- SubmitCompositorFrame();
- std::unique_ptr<viz::CopyOutputRequest> request =
- std::make_unique<viz::CopyOutputRequest>(
- viz::CopyOutputRequest::ResultFormat::RGBA,
- viz::CopyOutputRequest::ResultDestination::kSystemMemory,
- base::BindOnce(&IconSurface::OnCaptured,
- weak_ptr_factory_.GetWeakPtr()));
- request->set_result_task_runner(base::SequencedTaskRunnerHandle::Get());
- host_window()->layer()->RequestCopyOfOutput(std::move(request));
- }
- void OnCaptured(std::unique_ptr<viz::CopyOutputResult> icon_result) {
- // 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()) {
- RequestCaptureIcon();
- return;
- }
- auto scoped_bitmap = icon_result->ScopedAccessSkBitmap();
- operation_->OnDragIconCaptured(scoped_bitmap.GetOutScopedBitmap());
- }
- DragDropOperation* const operation_;
- base::WeakPtrFactory<IconSurface> weak_ptr_factory_{this};
- };
- base::WeakPtr<DragDropOperation> DragDropOperation::Create(
- DataExchangeDelegate* data_exchange_delegate,
- DataSource* source,
- Surface* origin,
- Surface* icon,
- const gfx::PointF& drag_start_point,
- ui::mojom::DragEventSource event_source) {
- auto* dnd_op = new DragDropOperation(data_exchange_delegate, source, origin,
- icon, drag_start_point, event_source);
- return dnd_op->weak_ptr_factory_.GetWeakPtr();
- }
- DragDropOperation::DragDropOperation(
- DataExchangeDelegate* data_exchange_delegate,
- DataSource* source,
- Surface* origin,
- Surface* icon,
- const gfx::PointF& drag_start_point,
- ui::mojom::DragEventSource event_source)
- : source_(std::make_unique<ScopedDataSource>(source, this)),
- origin_(std::make_unique<ScopedSurface>(origin, this)),
- drag_start_point_(drag_start_point),
- os_exchange_data_(std::make_unique<ui::OSExchangeData>()),
- event_source_(event_source) {
- aura::Window* root_window = origin_->get()->window()->GetRootWindow();
- DCHECK(root_window);
- #if BUILDFLAG(IS_CHROMEOS_ASH)
- 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);
- ui::EndpointType endpoint_type =
- data_exchange_delegate->GetDataTransferEndpointType(
- origin_->get()->window());
- os_exchange_data_->SetSource(
- std::make_unique<ui::DataTransferEndpoint>(endpoint_type));
- #if BUILDFLAG(IS_CHROMEOS_ASH)
- extended_drag_source_ = ExtendedDragSource::Get();
- if (extended_drag_source_) {
- drag_drop_controller_->set_toplevel_window_drag_delegate(
- extended_drag_source_);
- extended_drag_source_->AddObserver(this);
- }
- #endif
- int num_additional_callbacks = 0;
- #if BUILDFLAG(IS_CHROMEOS_ASH)
- // TODO(crbug.com/1298033): Move DTE retrieval into
- // DataSource::GetDataForPreferredMimeTypes()
- // Lacros sends additional metadata, in a custom MIME type, to sync drag
- // source metadata. Hence, the number of callbacks is incremented by one.
- if (endpoint_type == ui::EndpointType::kLacros)
- ++num_additional_callbacks;
- #endif
- // When the icon is present, we increment the number of callbacks so we can
- // wait for the icon to be captured as well.
- if (icon) {
- icon_ = std::make_unique<IconSurface>(icon, this);
- ++num_additional_callbacks;
- }
- auto start_op_callback =
- base::BindOnce(&DragDropOperation::ScheduleStartDragDropOperation,
- weak_ptr_factory_.GetWeakPtr());
- counter_ =
- base::BarrierClosure(DataSource::kMaxDataTypes + num_additional_callbacks,
- std::move(start_op_callback));
- #if BUILDFLAG(IS_CHROMEOS_ASH)
- // TODO(crbug.com/1298033): Move DTE retrieval into
- // DataSource::GetDataForPreferredMimeTypes()
- if (endpoint_type == ui::EndpointType::kLacros) {
- source->ReadDataTransferEndpoint(
- base::BindOnce(&DragDropOperation::OnDataTransferEndpointRead,
- weak_ptr_factory_.GetWeakPtr()),
- counter_);
- }
- #endif
- source->GetDataForPreferredMimeTypes(
- base::BindOnce(&DragDropOperation::OnTextRead,
- weak_ptr_factory_.GetWeakPtr()),
- DataSource::ReadDataCallback(),
- base::BindOnce(&DragDropOperation::OnHTMLRead,
- weak_ptr_factory_.GetWeakPtr()),
- DataSource::ReadDataCallback(),
- base::BindOnce(&DragDropOperation::OnFilenamesRead,
- weak_ptr_factory_.GetWeakPtr(), data_exchange_delegate,
- origin->window()),
- base::BindOnce(&DragDropOperation::OnFileContentsRead,
- weak_ptr_factory_.GetWeakPtr()),
- base::BindOnce(&DragDropOperation::OnWebCustomDataRead,
- weak_ptr_factory_.GetWeakPtr()),
- counter_);
- }
- 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();
- #if BUILDFLAG(IS_CHROMEOS_ASH)
- if (extended_drag_source_)
- ResetExtendedDragSource();
- #endif
- }
- void DragDropOperation::AbortIfPending() {
- if (!started_by_this_object_)
- delete this;
- }
- #if BUILDFLAG(IS_CHROMEOS_ASH)
- void DragDropOperation::OnDataTransferEndpointRead(const std::string& mime_type,
- std::u16string data) {
- DCHECK(os_exchange_data_);
- std::string utf8_json = base::UTF16ToUTF8(data);
- auto drag_source_dte = ui::ConvertJsonToDataTransferEndpoint(utf8_json);
- os_exchange_data_->SetSource(std::move(drag_source_dte));
- counter_.Run();
- }
- #endif
- void DragDropOperation::OnTextRead(const std::string& mime_type,
- std::u16string 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,
- std::u16string data) {
- DCHECK(os_exchange_data_);
- os_exchange_data_->SetHtml(std::move(data), GURL());
- mime_type_ = mime_type;
- counter_.Run();
- }
- void DragDropOperation::OnFilenamesRead(
- DataExchangeDelegate* data_exchange_delegate,
- aura::Window* source,
- const std::string& mime_type,
- const std::vector<uint8_t>& data) {
- DCHECK(os_exchange_data_);
- os_exchange_data_->SetFilenames(data_exchange_delegate->GetFilenames(
- data_exchange_delegate->GetDataTransferEndpointType(source), data));
- mime_type_ = mime_type;
- counter_.Run();
- }
- void DragDropOperation::OnFileContentsRead(const std::string& mime_type,
- const base::FilePath& filename,
- const std::vector<uint8_t>& data) {
- DCHECK(os_exchange_data_);
- os_exchange_data_->SetFileContents(filename,
- std::string(data.begin(), data.end()));
- mime_type_ = mime_type;
- counter_.Run();
- }
- void DragDropOperation::OnWebCustomDataRead(const std::string& mime_type,
- const std::vector<uint8_t>& data) {
- DCHECK(os_exchange_data_);
- base::Pickle pickle(reinterpret_cast<const char*>(data.data()), data.size());
- os_exchange_data_->SetPickledData(
- ui::ClipboardFormatType::WebCustomDataType(), pickle);
- mime_type_ = mime_type;
- counter_.Run();
- }
- void DragDropOperation::OnDragIconCaptured(const SkBitmap& icon_bitmap) {
- DCHECK(icon_);
- float scale_factor = origin_->get()->window()->layer()->device_scale_factor();
- gfx::ImageSkia icon_skia =
- gfx::ImageSkia::CreateFromBitmap(icon_bitmap, scale_factor);
- gfx::Vector2d icon_offset = -icon_->get()->GetBufferOffset();
- if (os_exchange_data_) {
- os_exchange_data_->provider().SetDragImage(icon_skia, icon_offset);
- } else {
- #if BUILDFLAG(IS_CHROMEOS_ASH)
- drag_drop_controller_->SetDragImage(icon_skia, icon_offset);
- #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.
- #if BUILDFLAG(IS_CHROMEOS_ASH)
- if (extended_drag_source_) {
- ShellSurfaceBase* shell_surface = GetShellSurfaceBaseForWindow(
- origin_->get()->window()->GetToplevelWindow());
- if (shell_surface)
- shell_surface->set_in_extended_drag(true);
- }
- #endif
- 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<DragDropOperation> weak_ptr = weak_ptr_factory_.GetWeakPtr();
- started_by_this_object_ = true;
- gfx::Point drag_start_point = gfx::ToFlooredPoint(drag_start_point_);
- // This triggers a nested run loop that terminates when the drag and drop
- // operation is completed.
- DragOperation 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 BUILDFLAG(IS_CHROMEOS_ASH)
- // Always reset the in_extended_drag becacuse ExtendedDragSource may be
- // destroyed during nested loop.
- if (origin_->get()) {
- ShellSurfaceBase* shell_surface = GetShellSurfaceBaseForWindow(
- origin_->get()->window()->GetToplevelWindow());
- if (shell_surface)
- shell_surface->set_in_extended_drag(false);
- }
- #endif
- // In tests, drag_drop_controller_ does not create a nested message loop and
- // so StartDragAndDrop exits before the drag&drop session finishes. In that
- // case the cleanup process shouldn't be made.
- if (drag_drop_controller_->IsDragDropInProgress())
- return;
- if (op != DragOperation::kNone) {
- // It is possible that Ash flags the dragged tab to snap its origin, and
- // it uses the `chromeos::kIsDeferredTabDraggingTargetWindowKey` property to
- // control that. The snap back behavior works as if the drag was cancelled.
- bool force_tab_swallow = false;
- #if BUILDFLAG(IS_CHROMEOS_ASH)
- aura::Window* source_window = origin_->get()->window()->GetToplevelWindow();
- force_tab_swallow =
- (source_window && source_window->GetProperty(
- chromeos::kIsDeferredTabDraggingTargetWindowKey));
- source_window->ClearProperty(
- chromeos::kIsDeferredTabDraggingTargetWindowKey);
- #endif
- if (force_tab_swallow) {
- source_->get()->Cancelled();
- } else {
- // 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;
- }
- #if BUILDFLAG(IS_CHROMEOS_ASH)
- 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(absl::nullopt);
- source_->get()->Action(dnd_action);
- }
- void DragDropOperation::OnExtendedDragSourceDestroying(
- ExtendedDragSource* source) {
- ResetExtendedDragSource();
- }
- void DragDropOperation::ResetExtendedDragSource() {
- DCHECK(extended_drag_source_);
- extended_drag_source_->RemoveObserver(this);
- drag_drop_controller_->set_toplevel_window_drag_delegate(nullptr);
- extended_drag_source_ = nullptr;
- }
- #endif
- void DragDropOperation::OnSurfaceDestroying(Surface* surface) {
- DCHECK(surface == origin_->get() || (icon_ && surface == icon_->get()));
- delete this;
- }
- void DragDropOperation::OnDataSourceDestroying(DataSource* source) {
- DCHECK_EQ(source, source_->get());
- source_.reset();
- delete this;
- }
- } // namespace exo
|