/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=4 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
// The maximum time to wait for a "drag_received" arrived in microseconds. #define NS_DND_TIMEOUT (1000000)
// The maximum time to wait before temporary files resulting // from drag'n'drop events will be removed in miliseconds. // It's set to 5 min as the file has to be present some time after drop // event to give target application time to get the data. // (A target application can throw a dialog to ask user what to do with // the data and will access the tmp file after user action.) #define NS_DND_TMP_CLEANUP_TIMEOUT (1000 * 60 * 5)
// _NETSCAPE_URL is similar to text/uri-list type. // Format is UTF8: URL + "\n" + title. // While text/uri-list tells target application to fetch, copy and store data // from URL, _NETSCAPE_URL suggest to create a link to the target. // Also _NETSCAPE_URL points to only one item while text/uri-list can point to // multiple ones. staticconstchar gMozUrlType[] = "_NETSCAPE_URL"; staticconstchar gMimeListType[] = "application/x-moz-internal-item-list"; staticconstchar gTextUriListType[] = "text/uri-list"; staticconstchar gTextPlainUTF8Type[] = "text/plain;charset=utf-8"; staticconstchar gXdndDirectSaveType[] = "XdndDirectSave0"; staticconstchar gTabDropType[] = "application/x-moz-tabbrowser-tab"; staticconstchar gPortalFile[] = "application/vnd.portal.files"; staticconstchar gPortalFileTransfer[] = "application/vnd.portal.filetransfer";
LOGDRAG("DragData::Export() MIME %s index %d", flavorName.get(), aItemIndex);
if (IsFileFlavor()) {
MOZ_ASSERT(mDragUris.get());
char** list = mDragUris.get(); if (aItemIndex >= g_strv_length(list)) {
NS_WARNING(
nsPrintfCString( "DragData::Export(): Index %d is overflow file list len %d",
aItemIndex, g_strv_length(list))
.get()); returnfalse;
} bool fileExists = false;
nsCOMPtr<nsIFile> file; if (GetFileFromUri(nsDependentCString(list[aItemIndex]), file)) {
file->Exists(&fileExists);
} if (!fileExists) {
LOGDRAG(" uri %s not reachable/not found\n", list[aItemIndex]); returnfalse;
}
LOGDRAG(" export file %s (flavor: %s) as %s", list[aItemIndex],
flavorName.get(), kFileMime);
aTransferable->SetTransferData(kFileMime, file); returntrue;
}
if (IsURIFlavor()) {
MOZ_ASSERT(mAsURIData); if (aItemIndex >= mUris.Length()) {
NS_WARNING(nsPrintfCString( "DragData::Export(): Index %d is overflow uri list len %d",
aItemIndex, (int)mUris.Length())
.get()); returnfalse;
}
// put it into the transferable.
nsCOMPtr<nsISupports> genericDataWrapper;
nsPrimitiveHelpers::CreatePrimitiveForData(
nsAutoCString(kURLMime), mUris[aItemIndex].get(),
mUris[aItemIndex].Length() * 2, getter_AddRefs(genericDataWrapper));
if (IsTextFlavor()) {
LOGDRAG(" export text %s", kTextMime);
// We get text flavors as UTF8 but we export them as UTF16. if (mData.IsEmpty() && mDragDataLen) {
mData = UTF8ToNewString(static_cast<constchar*>(mDragData.get()),
mDragDataLen);
}
// put it into the transferable.
nsCOMPtr<nsISupports> genericDataWrapper;
nsPrimitiveHelpers::CreatePrimitiveForData(
nsAutoCString(kTextMime), mData.get(), mData.Length() * 2,
getter_AddRefs(genericDataWrapper));
// We export obtained data directly from Gtk. In such case only // update line endings to DOM format. if (!mDragDataDOMEndings &&
mDataFlavor != nsDragSession::sCustomTypesMimeAtom) {
mDragDataDOMEndings = true; void* tmpData = mDragData.release();
nsLinebreakHelpers::ConvertPlatformToDOMLinebreaks(
mDataFlavor == nsDragSession::sRTFMimeAtom, &tmpData,
(int32_t*)&mDragDataLen);
mDragData.reset(tmpData);
}
// put it into the transferable.
nsCOMPtr<nsISupports> genericDataWrapper;
nsPrimitiveHelpers::CreatePrimitiveForData(
nsDependentCString(flavorName.get()), mDragData.get(), mDragDataLen,
getter_AddRefs(genericDataWrapper));
RefPtr<DragData> DragData::ConvertToMozURL() const { // "text/uri-list" is exported as URI mime type by Gtk, perhaps in UTF8. // We convert it to "text/x-moz-url" which is UTF16 with line breaks. if (mDataFlavor == nsDragSession::sTextUriListTypeAtom) {
MOZ_ASSERT(mAsURIData && mDragUris);
LOGDRAG("ConvertToMozURL(): text/uri-list => text/x-moz-url");
RefPtr<DragData> data = new DragData(nsDragSession::sURLMimeAtom);
data->mAsURIData = true;
int len = g_strv_length(mDragUris.get()); for (int i = 0; i < len; i++) {
data->mUris.AppendElement(UTF8ToNewString(mDragUris.get()[i]));
} return data;
}
// MozUrlType (_NETSCAPE_URL) MIME is not registered as URI MIME byt Gtk // is it exports it as plain data. We convert it to "text/x-moz-url" // which is UTF16 with line breaks. if (mDataFlavor == nsDragSession::sMozUrlTypeAtom) {
MOZ_ASSERT(mDragData);
LOGDRAG("ConvertToMozURL(): _NETSCAPE_URL => text/x-moz-url");
RefPtr<DragData> data = new DragData(nsDragSession::sURLMimeAtom);
data->mAsURIData = true;
data->mUris.AppendElement(
UTF8ToNewString((constchar*)mDragData.get(), mDragDataLen)); return data;
}
RefPtr<DragData> DragData::ConvertToFile() const { // "text/uri-list" is exported as URI mime type by Gtk, perhaps in UTF8. // We convert it to application/x-moz-file. if (mDataFlavor != nsDragSession::sTextUriListTypeAtom) { return nullptr;
}
MOZ_ASSERT(mAsURIData && mDragUris);
// We can use text/uri-list directly as application/x-moz-file returnnew DragData(nsDragSession::sFileMimeAtom, g_strdupv(mDragUris.get()));
}
staticint CopyURI(const nsAString& aSourceURL, nsAString& aTargetURL, int aOffset, bool aRequestNewLine) {
int32_t uriEnd = aSourceURL.FindChar(u'\n', aOffset); if (uriEnd == aOffset) { return aOffset + 1;
} if (uriEnd < 0) { if (aRequestNewLine) { return uriEnd;
} // We may miss newline ending on URL title which is correct
uriEnd = aSourceURL.Length();
}
int32_t newOffset = uriEnd + 1;
if (aSourceURL[uriEnd - 1] == u'\r') {
uriEnd--;
}
// It holds the URLs of links followed by their titles, // separated by a linebreak. void DragData::ConvertToMozURIList() { if (mDataFlavor != nsDragSession::sURLMimeAtom) { return;
}
mAsURIData = true;
LOGDRAG("DragData::ConvertToMozURIList(), data %s",
NS_ConvertUTF16toUTF8(uris).get());
int32_t uriBegin = 0; do {
nsAutoString uri; // First line contains URL and is terminated by newline if ((uriBegin = CopyURI(uris, uri, uriBegin, /* aRequestNewLine */ true)) <
0) { break;
} // Second line is URL title and may be terminated by newline if ((uriBegin = CopyURI(uris, uri, uriBegin, /* aRequestNewLine */ false)) <
0) { break;
}
LOGDRAG(" URI: %s", NS_ConvertUTF16toUTF8(uri).get());
mUris.AppendElement(uri);
} while (uriBegin < (int32_t)uris.Length());
// We have to destroy the hidden widget before the event loop stops // running.
nsCOMPtr<nsIObserverService> obsServ =
mozilla::services::GetObserverService();
obsServ->AddObserver(this, "quit-application", false);
// our hidden source widget // Using an offscreen window works around bug 983843.
mHiddenWidget = gtk_offscreen_window_new(); // make sure that the widget is realized so that // we can use it as a drag source.
gtk_widget_realize(mHiddenWidget); // hook up our internal signals so that we can get some feedback // from our drag source
g_signal_connect(mHiddenWidget, "drag_begin",
G_CALLBACK(invisibleSourceDragBegin), this);
g_signal_connect(mHiddenWidget, "drag_data_get",
G_CALLBACK(invisibleSourceDragDataGet), this);
g_signal_connect(mHiddenWidget, "drag_end",
G_CALLBACK(invisibleSourceDragEnd), this);
g_signal_connect(mHiddenWidget, "drag-failed",
G_CALLBACK(invisibleSourceDragFailed), this);
// set up our logging module
mTempFileTimerID = 0;
// http://www.whatwg.org/specs/web-apps/current-work/multipage/dnd.html#drag-and-drop-processing-model // and the Xdnd protocol both recommend that drag events are sent periodically, // but GTK does not normally provide this. // // Here GTK is periodically stimulated by copies of the most recent mouse // motion events so as to send drag position messages to the destination when // appropriate (after it has received a status event from the previous // message). // // (If events were sent only on the destination side then the destination // would have no message to which it could reply with a drag status. Without // sending a drag status to the source, the destination would not be able to // change its feedback re whether it could accept the drop, and so the // source's behavior on drop will not be consistent.)
static gboolean DispatchMotionEventCopy(gpointer aData) { // Clear the timer id before OnSourceGrabEventAfter is called during event // dispatch.
sMotionEventTimerID = 0;
GUniquePtr<GdkEvent> event = TakeMotionEvent(); // If there is no longer a grab on the widget, then the drag is over and // there is no need to continue drag motion. if (gtk_widget_has_grab(sGrabWidget)) {
gtk_propagate_event(sGrabWidget, event.get());
}
// Cancel this timer; // We've already started another if the motion event was dispatched. returnFALSE;
}
staticvoid OnSourceGrabEventAfter(GtkWidget* widget, GdkEvent* event,
gpointer user_data) { // If there is no longer a grab on the widget, then the drag motion is // over (though the data may not be fetched yet). if (!gtk_widget_has_grab(sGrabWidget)) return;
if (event->type == GDK_MOTION_NOTIFY) {
SetMotionEvent(GUniquePtr<GdkEvent>(gdk_event_copy(event)));
// Update the cursor position. The last of these recorded gets used for // the eDragEnd event.
nsDragService* dragService = static_cast<nsDragService*>(user_data);
nsCOMPtr<nsIDragSession> session;
dragService->GetCurrentSession(nullptr, getter_AddRefs(session)); if (session) {
gint scale = mozilla::widget::ScreenHelperGTK::GetGTKMonitorScaleFactor(); auto p = LayoutDeviceIntPoint::Round(event->motion.x_root * scale,
event->motion.y_root * scale);
session->SetDragEndPoint(p.x, p.y);
}
} elseif (sMotionEvent &&
(event->type == GDK_KEY_PRESS || event->type == GDK_KEY_RELEASE)) { // Update modifier state from key events.
sMotionEvent->motion.state = event->key.state;
} else { return;
}
if (sMotionEventTimerID) {
g_source_remove(sMotionEventTimerID);
}
// G_PRIORITY_DEFAULT_IDLE is lower priority than GDK's redraw idle source // and lower than GTK's idle source that sends drag position messages after // motion-notify signals. // // http://www.whatwg.org/specs/web-apps/current-work/multipage/dnd.html#drag-and-drop-processing-model // recommends an interval of 350ms +/- 200ms.
sMotionEventTimerID = g_timeout_add_full(
G_PRIORITY_DEFAULT_IDLE, 350, DispatchMotionEventCopy, nullptr, nullptr);
}
static GtkWindow* GetGtkWindow(dom::Document* aDocument) { if (!aDocument) return nullptr;
PresShell* presShell = aDocument->GetPresShell(); if (!presShell) { return nullptr;
}
RefPtr<nsViewManager> vm = presShell->GetViewManager(); if (!vm) return nullptr;
nsCOMPtr<nsIWidget> widget = vm->GetRootWidget(); if (!widget) return nullptr;
GtkWidget* gtkWidget = static_cast<nsWindow*>(widget.get())->GetGtkWidget(); if (!gtkWidget) return nullptr;
// If the previous source drag has not yet completed, signal handlers need // to be removed from sGrabWidget and dragend needs to be dispatched to // the source node, but we can't call EndDragSession yet because we don't // know whether or not the drag succeeded. if (mSourceNode) return NS_ERROR_NOT_AVAILABLE;
// nsBaseDragSession
nsresult nsDragSession::InvokeDragSessionImpl(
nsIWidget* aWidget, nsIArray* aArrayTransferables, const Maybe<CSSIntRegion>& aRegion, uint32_t aActionType) { // make sure that we have an array of transferables to use if (!aArrayTransferables) return NS_ERROR_INVALID_ARG; // set our reference to the transferables. this will also addref // the transferables since we're going to hang onto this beyond the // length of this call
mSourceDataItems = aArrayTransferables;
GdkDevice* device = widget::GdkGetPointer();
GdkWindow* originGdkWindow = nullptr; if (widget::GdkIsWaylandDisplay() || widget::IsXWaylandProtocol()) {
originGdkWindow =
gdk_device_get_window_at_position(device, nullptr, nullptr); // Workaround for https://gitlab.gnome.org/GNOME/gtk/-/issues/6385 // Check we have GdkWindow drag source. if (!originGdkWindow) {
NS_WARNING( "nsDragSession::InvokeDragSessionImpl(): Missing origin GdkWindow!"); return NS_ERROR_FAILURE;
}
}
// get the list of items we offer for drags
GtkTargetList* sourceList = GetSourceList();
if (!sourceList) return NS_OK;
// save our action type
GdkDragAction action = GDK_ACTION_DEFAULT;
if (aActionType & nsIDragService::DRAGDROP_ACTION_COPY)
action = (GdkDragAction)(action | GDK_ACTION_COPY); if (aActionType & nsIDragService::DRAGDROP_ACTION_MOVE)
action = (GdkDragAction)(action | GDK_ACTION_MOVE); if (aActionType & nsIDragService::DRAGDROP_ACTION_LINK)
action = (GdkDragAction)(action | GDK_ACTION_LINK);
GdkEvent* existingEvent = widget::GetLastMousePressEvent();
GdkEvent fakeEvent; if (!existingEvent) { // Create a fake event for the drag so we can pass the time (so to speak). // If we don't do this, then, when the timestamp for the pending button // release event is used for the ungrab, the ungrab can fail due to the // timestamp being _earlier_ than CurrentTime.
memset(&fakeEvent, 0, sizeof(GdkEvent));
fakeEvent.type = GDK_BUTTON_PRESS;
fakeEvent.button.window = gtk_widget_get_window(mHiddenWidget);
fakeEvent.button.time = nsWindow::GetLastUserInputTime();
fakeEvent.button.device = device;
}
// Put the drag widget in the window group of the source node so that the // gtk_grab_add during gtk_drag_begin is effective. // gtk_window_get_group(nullptr) returns the default window group.
GtkWindowGroup* window_group =
gtk_window_get_group(GetGtkWindow(mSourceDocument));
gtk_window_group_add_window(window_group, GTK_WINDOW(mHiddenWidget));
nsresult rv; if (context) { // GTK uses another hidden window for receiving mouse events.
sGrabWidget = gtk_window_group_get_current_grab(window_group); if (sGrabWidget) {
g_object_ref(sGrabWidget); // Only motion and key events are required but connect to // "event-after" as this is never blocked by other handlers.
g_signal_connect(sGrabWidget, "event-after",
G_CALLBACK(OnSourceGrabEventAfter), this);
} // We don't have a drag end point yet.
mEndDragPoint = LayoutDeviceIntPoint(-1, -1);
rv = NS_OK;
} else {
rv = NS_ERROR_FAILURE;
}
// Transparent drag icons need, like a lot of transparency-related things, // a compositing X window manager if (!gdk_screen_is_composited(screen)) { returnfalse;
}
#ifdef cairo_image_surface_create # error "Looks like we're including Mozilla's cairo instead of system cairo" #endif
// TODO: grab X11 pixmap or image data instead of expensive readback.
cairo_surface_t* surf = cairo_image_surface_create(
CAIRO_FORMAT_ARGB32, dragRect.width, dragRect.height); if (!surf) returnfalse;
// Ensure that the surface is drawn at the correct scale on HiDPI displays. staticauto sCairoSurfaceSetDeviceScalePtr =
(void (*)(cairo_surface_t*, double, double))dlsym(
RTLD_DEFAULT, "cairo_surface_set_device_scale"); if (sCairoSurfaceSetDeviceScalePtr) {
gint scale = mozilla::widget::ScreenHelperGTK::GetGTKMonitorScaleFactor();
sCairoSurfaceSetDeviceScalePtr(surf, scale, scale);
}
// We can not delete the temporary files immediately after the // drag has finished, because the target application might have not // copied the temporary file yet. The Qt toolkit does not provide a // way to mark a drop as finished in an asynchronous way, so most // Qt based applications do send the dnd_finished signal before they // have actually accessed the data from the temporary file. // (https://bugreports.qt.io/browse/QTBUG-5441) // // To work also with these applications we collect all temporary // files in mTemporaryFiles array and remove them here in the timer event. auto files = std::move(mTemporaryFiles); for (nsIFile* file : files) { #ifdef MOZ_LOGGING if (MOZ_LOG_TEST(gWidgetDragLog, LogLevel::Debug)) {
nsAutoCString path; if (NS_SUCCEEDED(file->GetNativePath(path))) {
LOGDRAGSERVICE(" removing %s", path.get());
}
} #endif
file->Remove(/* recursive = */ true);
}
MOZ_ASSERT(mTemporaryFiles.IsEmpty());
mTempFileTimerID = 0; // Return false to remove the timer added by g_timeout_add_full(). returnfalse;
}
/* static */
gboolean nsDragSession::TaskRemoveTempFiles(gpointer data) { // data is a manually addrefed drag session from EndDragSession. // We manually deref it here.
RefPtr<nsDragSession> session = static_cast<nsDragSession*>(data);
session.get()->Release(); return session->RemoveTempFiles();
}
// start timer to remove temporary files if (mTemporaryFiles.Count() > 0 && !mTempFileTimerID) {
LOGDRAGSERVICE(" queue removing of temporary files"); // |this| won't be used after nsDragSession delete because the timer is // removed in the nsDragSession destructor. // Manually addref this and pass to TaskRemoveTempFiles where it is // derefed.
AddRef();
mTempFileTimerID =
g_timeout_add(NS_DND_TMP_CLEANUP_TIMEOUT, TaskRemoveTempFiles, this);
mTempFileUrls.Clear();
}
// We're done with the drag context. if (mSourceWindow) {
mSourceWindow->SetDragSource(nullptr);
mSourceWindow = nullptr;
}
mTargetDragContextForRemote = nullptr;
mTargetWindow = nullptr;
mPendingWindow = nullptr;
mCachedDragContext = 0;
// Spins event loop, called from JS. // Can lead to another round of drag_motion events.
NS_IMETHODIMP
nsDragSession::GetNumDropItems(uint32_t* aNumItems) {
LOGDRAGSERVICE("nsDragSession::GetNumDropItems");
if (!mTargetWidget) {
LOGDRAGSERVICE( "*** warning: GetNumDropItems \
called without a valid target widget!\n");
*aNumItems = 0; return NS_OK;
}
// Put text/uri-list first, text/x-moz-url tends to be poorly supported // by third party apps, we got only one file instead of file list // for instance (Bug 1908196). // // We're getting the data to only get number of items here, // actual data will be received at nsDragSession::GetData(). const GdkAtom fileListFlavors[] = {sTextUriListTypeAtom, // text/uri-list
sPortalFileAtom, sPortalFileTransferAtom,
sURLMimeAtom}; // text/x-moz-url
for (auto fileFlavour : fileListFlavors) {
RefPtr<DragData> data = GetDragData(fileFlavour); if (data) {
*aNumItems = data->GetURIsNum();
LOGDRAGSERVICE("GetNumDropItems(): Found MIME %s items %d",
GUniquePtr<gchar>(gdk_atom_name(fileFlavour)).get(),
*aNumItems); return NS_OK;
}
}
// We're missing any file list MIME, return only one item.
*aNumItems = 1;
LOGDRAGSERVICE("GetNumDropItems(): no list available"); return NS_OK;
}
// Spins event loop, called from JS. // Can lead to another round of drag_motion events.
NS_IMETHODIMP
nsDragSession::GetData(nsITransferable* aTransferable, uint32_t aItemIndex) {
LOGDRAGSERVICE("nsDragSession::GetData(), index %d", aItemIndex);
// make sure that we have a transferable if (!aTransferable) { return NS_ERROR_INVALID_ARG;
}
if (!mTargetWidget) {
LOGDRAGSERVICE( "*** failed: GetData called without a valid target widget!\n"); return NS_ERROR_FAILURE;
}
// get flavor list that includes all acceptable flavors (including // ones obtained through conversion).
nsTArray<nsCString> flavors;
nsresult rv = aTransferable->FlavorsTransferableCanImport(flavors); if (NS_FAILED(rv)) {
LOGDRAGSERVICE(" failed to get flavors, quit."); return rv;
}
// check to see if this is an internal list if (IsTargetContextList()) {
LOGDRAGSERVICE(" Process as a list..."); // find a matching flavor for (uint32_t i = 0; i < flavors.Length(); ++i) {
nsCString& flavorStr = flavors[i];
LOGDRAGSERVICE(" [%d] flavor is %s\n", i, flavorStr.get()); // get the item with the right index
nsCOMPtr<nsITransferable> item =
do_QueryElementAt(mSourceDataItems, aItemIndex); if (!item) continue;
nsCOMPtr<nsISupports> data;
LOGDRAGSERVICE(" trying to get transfer data for %s\n", flavorStr.get());
rv = item->GetTransferData(flavorStr.get(), getter_AddRefs(data)); if (NS_FAILED(rv)) {
LOGDRAGSERVICE(" failed.\n"); continue;
}
rv = aTransferable->SetTransferData(flavorStr.get(), data); if (NS_FAILED(rv)) {
LOGDRAGSERVICE(" fail to set transfer data into transferable!\n"); continue;
}
LOGDRAGSERVICE(" succeeded\n"); // ok, we got the data return NS_OK;
} // if we got this far, we failed
LOGDRAGSERVICE(" failed to match flavors\n"); return NS_ERROR_FAILURE;
}
// Now walk down the list of flavors. When we find one that is // actually present, copy out the data into the transferable in that // format. SetTransferData() implicitly handles conversions. for (uint32_t i = 0; i < flavors.Length(); ++i) {
nsCString& flavorStr = flavors[i];
GdkAtom requestedFlavor = gdk_atom_intern(flavorStr.get(), FALSE); if (!requestedFlavor) { continue;
}
LOGDRAGSERVICE(" we're getting data %s\n", flavorStr.get());
RefPtr<DragData> dragData;
// Let's do conversions first. We may be asked for some kind of MIME // type data but we rather try to get something different and // convert to desider MIME type.
// We're asked to get text data. Try to get UTF-8 variant first. if (requestedFlavor == sTextMimeAtom) {
dragData = GetDragData(sTextPlainUTF8TypeAtom);
}
// We are looking for text/x-moz-url. That format may be poorly supported, // try first with text/uri-list, and then _NETSCAPE_URL if (requestedFlavor == sURLMimeAtom) {
LOGDRAGSERVICE(" conversion %s => %s", gTextUriListType, kURLMime);
dragData = GetDragData(sTextUriListTypeAtom); if (dragData) {
dragData = dragData->ConvertToMozURL();
mCachedDragData.InsertOrUpdate(dragData->GetFlavor(), dragData);
} if (!dragData) {
LOGDRAGSERVICE(" conversion %s => %s", gMozUrlType, kURLMime);
dragData = GetDragData(sMozUrlTypeAtom); if (dragData) {
dragData = dragData->ConvertToMozURL(); if (dragData) {
mCachedDragData.InsertOrUpdate(dragData->GetFlavor(), dragData);
}
}
}
}
// Try to get requested MIME directly if (!dragData) {
dragData = GetDragData(requestedFlavor);
}
// We're asked to get file mime type but we failed. // Try portal variants and text/uri-list conversion. if (!dragData && requestedFlavor == sFileMimeAtom) { // application/vnd.portal.files
dragData = GetDragData(sPortalFileAtom);
// application/vnd.portal.filetransfer if (!dragData) {
dragData = GetDragData(sPortalFileTransferAtom);
}
if (!dragData) {
LOGDRAGSERVICE( " file not found, proceed with conversion %s => %s flavor\n",
gTextUriListType, kFileMime); // Conversion text/uri-list => application/x-moz-file
dragData = GetDragData(sTextUriListTypeAtom); if (dragData) {
dragData = dragData->ConvertToFile(); if (dragData) {
mCachedDragData.InsertOrUpdate(dragData->GetFlavor(), dragData);
}
}
}
}
if (dragData && dragData->Export(aTransferable, aItemIndex)) { // We usually want to also get URL for images so continue if (dragData->IsImageFlavor()) { continue;
} return NS_OK;
}
}
// check to make sure that we have a drag object set, here if (!mTargetWidget) {
LOGDRAGSERVICE( "*** warning: IsDataFlavorSupported called without a valid target " "widget!\n"); return NS_OK;
}
// check to see if the target context is a list. // if it is, just look in the internal data since we are the source // for it. if (IsTargetContextList()) {
LOGDRAGSERVICE(" It's a list");
uint32_t numDragItems = 0; // if we don't have mDataItems we didn't start this drag so it's // an external client trying to fool us. if (!mSourceDataItems) {
LOGDRAGSERVICE(" quit"); return NS_OK;
}
mSourceDataItems->GetLength(&numDragItems);
LOGDRAGSERVICE(" drag items %d", numDragItems); for (uint32_t itemIndex = 0; itemIndex < numDragItems; ++itemIndex) {
nsCOMPtr<nsITransferable> currItem =
do_QueryElementAt(mSourceDataItems, itemIndex); if (currItem) {
nsTArray<nsCString> flavors;
currItem->FlavorsTransferableCanExport(flavors); for (uint32_t i = 0; i < flavors.Length(); ++i) {
LOGDRAGSERVICE(" checking %s against %s\n", flavors[i].get(),
aDataFlavor); if (flavors[i].Equals(aDataFlavor)) {
LOGDRAGSERVICE(" found.\n");
*_retval = true;
}
}
}
} return NS_OK;
}
GdkAtom requestedFlavor = gdk_atom_intern(aDataFlavor, FALSE); if (IsDragFlavorAvailable(requestedFlavor)) {
LOGDRAGSERVICE(" %s is supported", aDataFlavor);
*_retval = true; return NS_OK;
}
// Check for file/url conversion from uri list if ((requestedFlavor == sURLMimeAtom || requestedFlavor == sFileMimeAtom) &&
IsDragFlavorAvailable(sTextUriListTypeAtom)) {
LOGDRAGSERVICE(" %s supported with conversion from %s", aDataFlavor,
gTextUriListType);
*_retval = true; return NS_OK;
}
// check for automatic _NETSCAPE_URL -> text/x-moz-url mapping if (requestedFlavor == sURLMimeAtom &&
IsDragFlavorAvailable(sMozUrlTypeAtom)) {
LOGDRAGSERVICE(" %s supported with conversion from %s", aDataFlavor,
gMozUrlType);
*_retval = true; return NS_OK;
}
// If we're asked for kURLMime/kFileMime we can get it from PortalFile // or PortalFileTransfer flavors. if ((requestedFlavor == sURLMimeAtom || requestedFlavor == sFileMimeAtom) &&
(IsDragFlavorAvailable(sPortalFileAtom) ||
IsDragFlavorAvailable(sPortalFileTransferAtom))) {
LOGDRAGSERVICE(" %s supported with conversion from %s/%s", aDataFlavor,
gPortalFile, gPortalFileTransfer);
*_retval = true; return NS_OK;
}
LOGDRAGSERVICE(" %s is not supported", aDataFlavor); return NS_OK;
}
void nsDragSession::ReplyToDragMotion(GdkDragContext* aDragContext,
guint aTime) {
LOGDRAGSERVICE("nsDragSession::ReplyToDragMotion(%p) can drop %d",
aDragContext, mCanDrop);
GdkDragAction action = (GdkDragAction)0; if (mCanDrop) { // notify the dragger if we can drop switch (mDragAction) { case nsIDragService::DRAGDROP_ACTION_COPY:
LOGDRAGSERVICE(" set explicit action copy");
action = GDK_ACTION_COPY; break; case nsIDragService::DRAGDROP_ACTION_LINK:
LOGDRAGSERVICE(" set explicit action link");
action = GDK_ACTION_LINK; break; case nsIDragService::DRAGDROP_ACTION_NONE:
LOGDRAGSERVICE(" set explicit action none");
action = (GdkDragAction)0; break; default:
LOGDRAGSERVICE(" set explicit action move");
action = GDK_ACTION_MOVE; break;
}
} else {
LOGDRAGSERVICE(" mCanDrop is false, disable drop");
}
// gdk_drag_status() is a kind of red herring here. // It does not control final D&D operation type (copy/move) but controls // drop/no-drop D&D state and default cursor type (copy/move).
// Actual D&D operation is determined by mDragAction which is set by // SetDragAction() from UpdateDragAction() or gecko/layout.
// State passed to gdk_drag_status() sets default D&D cursor type // which can be switched by key control (CTRL/SHIFT). // If user changes D&D cursor (and D&D operation) we're notified by // gdk_drag_context_get_selected_action() and update mDragAction.
// But if we pass mDragAction back to gdk_drag_status() the D&D operation // becames locked and won't be returned when D&D modifiers (CTRL/SHIFT) // are released.
// This gdk_drag_context_get_selected_action() -> gdk_drag_status() -> // gdk_drag_context_get_selected_action() cycle happens on Wayland. if (widget::GdkIsWaylandDisplay() && action == GDK_ACTION_COPY) {
LOGDRAGSERVICE(" Wayland: switch copy to move");
action = GDK_ACTION_MOVE;
}
void nsDragSession::SetCachedDragContext(GdkDragContext* aDragContext) {
LOGDRAGSERVICE("nsDragSession::SetCachedDragContext(): [drag %p / cached %p]",
aDragContext, (void*)mCachedDragContext); // Clear cache data if we're going to D&D with different drag context.
uintptr_t recentDragContext = reinterpret_cast<uintptr_t>(aDragContext); if (recentDragContext && recentDragContext != mCachedDragContext) {
LOGDRAGSERVICE(" cache clear, new context %p", (void*)recentDragContext);
mCachedDragContext = recentDragContext;
mCachedDragData.Clear();
mCachedDragFlavors.Clear();
}
}
bool nsDragSession::IsTargetContextList(void) { // gMimeListType drags only work for drags within a single process. The // gtk_drag_get_source_widget() function will return nullptr if the source // of the drag is another app, so we use it to check if a gMimeListType // drop will work or not. if (mTargetDragContext &&
gtk_drag_get_source_widget(mTargetDragContext) == nullptr) { returnfalse;
}
// Spins event loop, called from eDragTaskMotion handler by // DispatchMotionEvents(). // Can lead to another round of drag_motion events.
RefPtr<DragData> nsDragSession::GetDragData(GdkAtom aRequestedFlavor) {
LOGDRAGSERVICE("nsDragSession::GetDragData(%p) requested '%s'\n",
mTargetDragContext.get(),
GUniquePtr<gchar>(gdk_atom_name(aRequestedFlavor)).get());
// Return early when requested MIME is not offered by D&D. if (!IsDragFlavorAvailable(aRequestedFlavor)) {
LOGDRAGSERVICE(" %s is missing",
GUniquePtr<gchar>(gdk_atom_name(aRequestedFlavor)).get()); return nullptr;
}
if (!mTargetDragContext) {
LOGDRAGSERVICE(" failed, missing mTargetDragContext"); return nullptr;
}
{ auto data = mCachedDragData.MaybeGet(GDK_ATOM_TO_POINTER(aRequestedFlavor)); if (data) {
LOGDRAGSERVICE(" MIME %s found in cache, %s",
GUniquePtr<gchar>(gdk_atom_name(aRequestedFlavor)).get(),
*data ? "got correctly" : "failed to get"); return *data;
}
}
mWaitingForDragDataRequests++;
// We'll get the data by nsDragSession::TargetDataReceived()
gtk_drag_get_data(mTargetWidget, mTargetDragContext, aRequestedFlavor,
mTargetTime);
LOGDRAGSERVICE( " about to start inner iteration, mWaitingForDragDataRequests %d",
mWaitingForDragDataRequests);
gtk_main_iteration();
PRTime entryTime = PR_Now(); while (mWaitingForDragDataRequests && mDoingDrag) { // check the number of iterations
LOGDRAGSERVICE(" doing iteration, mWaitingForDragDataRequests %d ...",
mWaitingForDragDataRequests);
PR_Sleep(PR_MillisecondsToInterval(10)); /* sleep for 10 ms/iteration */ if (PR_Now() - entryTime > NS_DND_TIMEOUT) {
LOGDRAGSERVICE(" failed to get D&D data in time!\n"); break;
}
gtk_main_iteration();
}
// We failed to get all data in time if (mWaitingForDragDataRequests) {
LOGDRAGSERVICE(" failed to get all data, mWaitingForDragDataRequests %d",
mWaitingForDragDataRequests);
}
RefPtr<DragData> data =
mCachedDragData.Get(GDK_ATOM_TO_POINTER(aRequestedFlavor)); if (data) {
LOGDRAGSERVICE(" %s received",
GUniquePtr<gchar>(gdk_atom_name(aRequestedFlavor)).get()); return data;
}
LOGDRAGSERVICE(" %s failed to get from system",
GUniquePtr<gchar>(gdk_atom_name(aRequestedFlavor)).get()); return nullptr;
}
auto saveData = MakeScopeExit([&] { if (dragData && !dragData->IsDataValid()) {
dragData = nullptr;
}
if (!dragData) {
LOGDRAGSERVICE(" failed to get data, MIME %s",
GUniquePtr<gchar>(gdk_atom_name(target)).get());
}
// We set cache even for empty received data. // It saves time if we're asked for the same data type // again.
mCachedDragData.InsertOrUpdate(target, dragData);
});
if (target == sPortalFileAtom || target == sPortalFileTransferAtom) { const guchar* data = gtk_selection_data_get_data(aSelectionData); if (!data || data[0] == '\0') {
LOGDRAGSERVICE( "nsDragSession::TargetDataReceived() failed to get file portal data " "(%s)",
GUniquePtr<gchar>(gdk_atom_name(target)).get()); return;
}
// A workaround for https://gitlab.gnome.org/GNOME/gtk/-/issues/6563 // // For the vnd.portal.filetransfer and vnd.portal.files we receive numeric // id when it's a local file. The numeric id is then used by // gtk_selection_data_get_uris implementation to get the actual file // available in the flatpak environment. // // However due to GTK implementation also for example the uris like https // are also provided by the vnd.portal.filetransfer target. In this case // the call gtk_selection_data_get_uris fails. This is a bug in the gtk. // To workaround it we try to create the valid uri and only if we fail // we try to use the gtk_selection_data_get_uris. We ignore the valid uris // for the vnd.portal.file* targets.
nsCOMPtr<nsIURI> sourceURI;
nsresult rv =
NS_NewURI(getter_AddRefs(sourceURI), (const gchar*)data, nullptr); if (NS_SUCCEEDED(rv)) {
LOGDRAGSERVICE( " TargetDataReceived(): got valid uri for MIME %s - this is bug " "in GTK - expected numeric value for portal, got %s\n",
GUniquePtr<gchar>(gdk_atom_name(target)).get(), data); return;
}
dragData = new DragData(target, gtk_selection_data_get_uris(aSelectionData));
LOGDRAGSERVICE(" TargetDataReceived(): FILE PORTAL data, MIME %s",
GUniquePtr<gchar>(gdk_atom_name(target)).get());
} elseif (target == sTextUriListTypeAtom) {
dragData = new DragData(target, gtk_selection_data_get_uris(aSelectionData));
LOGDRAGSERVICE(" TargetDataReceived(): URI data, MIME %s",
GUniquePtr<gchar>(gdk_atom_name(target)).get());
} else { const guchar* data = gtk_selection_data_get_data(aSelectionData);
gint len = gtk_selection_data_get_length(aSelectionData); if (len < 0 && !data) {
LOGDRAGSERVICE(" TargetDataReceived() failed"); return;
}
dragData = new DragData(target, data, len);
LOGDRAGSERVICE(" TargetDataReceived(): plain data, MIME %s len = %d",
GUniquePtr<gchar>(gdk_atom_name(target)).get(), len);
}
#if MOZ_LOGGING if (dragData) {
dragData->Print();
} #endif
}
// Check to see if we're dragging > 1 item. if (numDragItems > 1) { // as the Xdnd protocol only supports a single item (or is it just // gtk's implementation?), we don't advertise all flavours listed // in the nsITransferable.
// the application/x-moz-internal-item-list format, which preserves // all information for drags within the same mozilla instance.
TargetArrayAddTarget(targetArray, gMimeListType);
// check what flavours are supported so we can decide what other // targets to advertise.
nsCOMPtr<nsITransferable> currItem = do_QueryElementAt(mSourceDataItems, 0);
if (currItem) {
nsTArray<nsCString> flavors;
currItem->FlavorsTransferableCanExport(flavors); for (uint32_t i = 0; i < flavors.Length(); ++i) { // check if text/x-moz-url is supported. // If so, advertise // text/uri-list. if (flavors[i].EqualsLiteral(kURLMime)) {
TargetArrayAddTarget(targetArray, gTextUriListType); break;
}
}
} // if item is a transferable
} elseif (numDragItems == 1) {
nsCOMPtr<nsITransferable> currItem = do_QueryElementAt(mSourceDataItems, 0); if (currItem) {
nsTArray<nsCString> flavors;
currItem->FlavorsTransferableCanExport(flavors); for (uint32_t i = 0; i < flavors.Length(); ++i) {
nsCString& flavorStr = flavors[i];
GdkAtom requestedFlavor = gdk_atom_intern(flavorStr.get(), FALSE); if (!requestedFlavor) { continue;
}
// If there is a file, add the text/uri-list type. if (requestedFlavor == sFileMimeAtom) {
TargetArrayAddTarget(targetArray, gTextUriListType);
} // Check to see if this is text/plain. elseif (requestedFlavor == sTextMimeAtom) {
TargetArrayAddTarget(targetArray, gTextPlainUTF8Type);
} // Check to see if this is the x-moz-url type. // If it is, add _NETSCAPE_URL // this is a type used by everybody. elseif (requestedFlavor == sURLMimeAtom) {
nsCOMPtr<nsISupports> data; if (NS_SUCCEEDED(currItem->GetTransferData(flavorStr.get(),
getter_AddRefs(data)))) { void* tmpData = nullptr;
uint32_t tmpDataLen = 0;
nsPrimitiveHelpers::CreateDataFromPrimitive(
nsDependentCString(flavorStr.get()), data, &tmpData,
&tmpDataLen); if (tmpData) { if (CanExportAsURLTarget(reinterpret_cast<char16_t*>(tmpData),
tmpDataLen / 2)) {
TargetArrayAddTarget(targetArray, gMozUrlType);
}
free(tmpData);
}
}
} // check if application/x-moz-file-promise url is supported. // If so, advertise text/uri-list. elseif (requestedFlavor == sFilePromiseURLMimeAtom) {
TargetArrayAddTarget(targetArray, gTextUriListType);
} // XdndDirectSave, use on X.org only. elseif (widget::GdkIsX11Display() && !widget::IsXWaylandProtocol() &&
requestedFlavor == sFilePromiseMimeAtom) {
TargetArrayAddTarget(targetArray, gXdndDirectSaveType);
} // kNativeImageMime elseif (requestedFlavor == sNativeImageMimeAtom) {
TargetArrayAddTarget(targetArray, kPNGImageMime);
TargetArrayAddTarget(targetArray, kJPEGImageMime);
TargetArrayAddTarget(targetArray, kJPGImageMime);
TargetArrayAddTarget(targetArray, kGIFImageMime);
}
}
}
}
// get all the elements that we created.
targetCount = targetArray.Length(); if (targetCount) { // allocate space to create the list of valid targets
targets = (GtkTargetEntry*)g_malloc(sizeof(GtkTargetEntry) * targetCount);
uint32_t targetIndex; for (targetIndex = 0; targetIndex < targetCount; ++targetIndex) {
GtkTargetEntry* disEntry = targetArray.ElementAt(targetIndex); // this is a string reference but it will be freed later.
targets[targetIndex].target = disEntry->target;
targets[targetIndex].flags = disEntry->flags;
targets[targetIndex].info = 0;
}
targetList = gtk_target_list_new(targets, targetCount); // clean up the target list for (uint32_t cleanIndex = 0; cleanIndex < targetCount; ++cleanIndex) {
GtkTargetEntry* thisTarget = targetArray.ElementAt(cleanIndex);
g_free(thisTarget->target);
g_free(thisTarget);
}
g_free(targets);
} else { // We need to create a dummy target list to be able initialize dnd.
targetList = gtk_target_list_new(nullptr, 0);
} return targetList;
}
// this just releases the list of data items that we provide
mSourceDataItems = nullptr;
// Remove this property, if it exists, to satisfy the Direct Save Protocol.
GdkAtom property = sXdndDirectSaveTypeAtom;
gdk_property_delete(gdk_drag_context_get_source_window(aContext), property);
if (!mDoingDrag || mScheduledTask == eDragTaskSourceEnd) // EndDragSession() was already called on drop // or SourceEndDragSession on drag-failed return;
if (mEndDragPoint.x < 0) { // We don't have a drag end point, so guess
gint x, y;
GdkDisplay* display = gdk_display_get_default();
GdkScreen* screen = gdk_display_get_default_screen(display);
GtkWindow* window = GetGtkWindow(mSourceDocument);
GdkWindow* gdkWindow = window ? gtk_widget_get_window(GTK_WIDGET(window))
: gdk_screen_get_root_window(screen); if (!gdkWindow) { return;
}
gdk_window_get_device_position(
gdkWindow, gdk_drag_context_get_device(aContext), &x, &y, nullptr);
gint scale = gdk_window_get_scale_factor(gdkWindow);
SetDragEndPoint(x * scale, y * scale);
LOGDRAGSERVICE(" guess drag end point %d %d\n", x * scale, y * scale);
}
// Either the drag was aborted or the drop occurred outside the app. // The dropEffect of mDataTransfer is not updated for motion outside the // app, but is needed for the dragend event, so set it now.
uint32_t dropEffect;
if (aResult == GTK_DRAG_RESULT_SUCCESS) {
LOGDRAGSERVICE(" drop is accepted"); // With GTK+ versions 2.10.x and prior the drag may have been // cancelled (but no drag-failed signal would have been sent). // aContext->dest_window will be non-nullptr only if the drop was // sent.
GdkDragAction action = gdk_drag_context_get_dest_window(aContext)
? gdk_drag_context_get_actions(aContext)
: (GdkDragAction)0;
// Only one bit of action should be set, but, just in case someone // does something funny, erring away from MOVE, and not recording // unusual action combinations as NONE. if (!action) {
LOGDRAGSERVICE(" drop action is none");
dropEffect = nsIDragService::DRAGDROP_ACTION_NONE;
} elseif (action & GDK_ACTION_COPY) {
LOGDRAGSERVICE(" drop action is copy");
dropEffect = nsIDragService::DRAGDROP_ACTION_COPY;
} elseif (action & GDK_ACTION_LINK) {
LOGDRAGSERVICE(" drop action is link");
dropEffect = nsIDragService::DRAGDROP_ACTION_LINK;
} elseif (action & GDK_ACTION_MOVE) {
LOGDRAGSERVICE(" drop action is move");
dropEffect = nsIDragService::DRAGDROP_ACTION_MOVE;
} else {
LOGDRAGSERVICE(" drop action is copy");
dropEffect = nsIDragService::DRAGDROP_ACTION_COPY;
}
} else {
--> --------------------
--> maximum size reached
--> --------------------
¤ Dauer der Verarbeitung: 0.42 Sekunden
(vorverarbeitet)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.