Chromium Code Reviews| Index: ui/base/x/selection_owner.cc |
| diff --git a/ui/base/x/selection_owner.cc b/ui/base/x/selection_owner.cc |
| index 1664e5912e334d90304b56499f305c3ff97aa592..ac0b594a0f8abff1d5da03f45369bd43e94cb95d 100644 |
| --- a/ui/base/x/selection_owner.cc |
| +++ b/ui/base/x/selection_owner.cc |
| @@ -4,11 +4,13 @@ |
| #include "ui/base/x/selection_owner.h" |
| +#include <algorithm> |
| #include <X11/Xlib.h> |
| #include <X11/Xatom.h> |
| #include "base/logging.h" |
| #include "ui/base/x/selection_utils.h" |
| +#include "ui/base/x/x11_foreign_window_manager.h" |
| #include "ui/base/x/x11_util.h" |
| namespace ui { |
| @@ -16,18 +18,37 @@ namespace ui { |
| namespace { |
| const char kAtomPair[] = "ATOM_PAIR"; |
| +const char kIncr[] = "INCR"; |
| const char kMultiple[] = "MULTIPLE"; |
| const char kSaveTargets[] = "SAVE_TARGETS"; |
| const char kTargets[] = "TARGETS"; |
| const char* kAtomsToCache[] = { |
| kAtomPair, |
| + kIncr, |
| kMultiple, |
| kSaveTargets, |
| kTargets, |
| NULL |
| }; |
| +// The period of |incr_timer_|. Arbitrary but must be <= than |
| +// kIncrTransferTimeoutMs. |
| +const int kIncrTimerPeriodMs = 1000; |
|
Daniel Erat
2014/07/25 19:38:44
s/Incr/Incremental/ here and in other constants
|
| + |
| +// The amount of time to wait for the selection requestor to process the data |
| +// sent by the selection owner before aborting an incremental data transfer. |
| +const int kIncrTransferTimeoutMs = 10000; |
| + |
| +// Returns a conservative max size of the data we can pass into |
| +// XChangeProperty(). Copied from GTK. |
| +size_t GetMaxRequestSize(XDisplay* display) { |
| + long extended_max_size = XExtendedMaxRequestSize(display); |
| + return std::min( |
| + static_cast<long>(0x40000), |
| + (extended_max_size ? extended_max_size : XMaxRequestSize(display)) - 100); |
|
Daniel Erat
2014/07/25 19:38:43
nit: check for negative wraparound
pkotwicz
2014/07/26 20:09:53
Done.
|
| +} |
| + |
| // Gets the value of an atom pair array property. On success, true is returned |
| // and the value is stored in |value|. |
| bool GetAtomPairArrayProperty(XID window, |
| @@ -78,6 +99,7 @@ SelectionOwner::SelectionOwner(XDisplay* x_display, |
| : x_display_(x_display), |
| x_window_(x_window), |
| selection_name_(selection_name), |
| + max_request_size_(GetMaxRequestSize(x_display)), |
| atom_cache_(x_display_, kAtomsToCache) { |
| } |
| @@ -173,6 +195,21 @@ void SelectionOwner::OnSelectionClear(const XEvent& event) { |
| // we need to delay clearing. |
| } |
| +bool SelectionOwner::CanDispatchPropertyEvent(const XEvent& event) { |
| + return event.xproperty.state == PropertyDelete && |
| + FindIncrTransferForEvent(event) != incrs_.end(); |
| +} |
| + |
| +void SelectionOwner::OnPropertyEvent(const XEvent& event) { |
| + std::vector<IncrTransfer>::iterator it = FindIncrTransferForEvent(event); |
| + if (it == incrs_.end()) |
| + return; |
| + |
| + ProcessIncr(&(*it)); |
| + if (!it->data.get()) |
| + CompleteIncrTransfer(it); |
| +} |
| + |
| bool SelectionOwner::ProcessTarget(XAtom target, |
| XID requestor, |
| XAtom property) { |
| @@ -201,12 +238,58 @@ bool SelectionOwner::ProcessTarget(XAtom target, |
| // Try to find the data type in map. |
| SelectionFormatMap::const_iterator it = format_map_.find(target); |
| if (it != format_map_.end()) { |
| - XChangeProperty(x_display_, requestor, property, target, 8, |
| - PropModeReplace, |
| - const_cast<unsigned char*>( |
| - reinterpret_cast<const unsigned char*>( |
| - it->second->front())), |
| - it->second->size()); |
| + if (it->second->size() > max_request_size_) { |
| + // We must send the data back in several chunks due to a limitation in |
| + // the size of X requests. Notify the selection requestor that the data |
| + // will be sent incrementally by returning data of type "INCR". |
| + int length = it->second->size(); |
| + XChangeProperty(x_display_, |
| + requestor, |
| + property, |
| + atom_cache_.GetAtom(kIncr), |
| + 32, |
| + PropModeReplace, |
| + reinterpret_cast<unsigned char*>(&length), |
| + 1); |
| + |
| + // Wait for the selection requestor to indicate that it has processed |
| + // the selection result before sending the first chunk of data. The |
| + // selection requestor indicates this by deleting |property|. |
| + base::TimeTicks timeout = |
| + base::TimeTicks::Now() + |
| + base::TimeDelta::FromMilliseconds(kIncrTransferTimeoutMs); |
| + int foreign_window_manager_id = |
| + ui::XForeignWindowManager::GetInstance()->RequestEvents( |
| + requestor, PropertyChangeMask); |
| + incrs_.push_back(IncrTransfer(requestor, |
| + target, |
| + property, |
| + it->second, |
| + 0, |
| + timeout, |
| + foreign_window_manager_id)); |
| + |
| + // Start a timer to abort the data transfer in case that the selection |
| + // requestor does not support the INCR property or gets destroyed during |
| + // the data transfer. |
| + if (!incr_timer_.IsRunning()) { |
| + incr_timer_.Start( |
| + FROM_HERE, |
| + base::TimeDelta::FromMilliseconds(kIncrTimerPeriodMs), |
| + this, |
| + &SelectionOwner::AbortStaleIncrTransfers); |
| + } |
| + } else { |
| + XChangeProperty( |
| + x_display_, |
| + requestor, |
| + property, |
| + target, |
| + 8, |
| + PropModeReplace, |
| + const_cast<unsigned char*>(it->second->front()), |
| + it->second->size()); |
| + } |
| return true; |
| } |
| // I would put error logging here, but GTK ignores TARGETS and spams us |
| @@ -215,4 +298,78 @@ bool SelectionOwner::ProcessTarget(XAtom target, |
| return false; |
| } |
| +void SelectionOwner::ProcessIncr(IncrTransfer* transfer) { |
| + size_t remaining = transfer->data->size() - transfer->offset; |
| + size_t chunk_length = std::min(remaining, max_request_size_); |
| + XChangeProperty( |
| + x_display_, |
| + transfer->window, |
| + transfer->property, |
| + transfer->target, |
| + 8, |
| + PropModeReplace, |
| + const_cast<unsigned char*>(transfer->data->front() + transfer->offset), |
| + chunk_length); |
| + transfer->offset += chunk_length; |
| + transfer->timeout = base::TimeTicks::Now() + |
| + base::TimeDelta::FromMilliseconds(kIncrTransferTimeoutMs); |
| + |
| + // When offset == data->size(), we still need to transfer a zero-sized chunk |
| + // to notify the selection requestor that the transfer is complete. Clear |
| + // transfer->data once the zero-sized chunk is sent to indicate that state |
| + // related to this data transfer can be cleared. |
| + if (chunk_length == 0) |
| + transfer->data = NULL; |
| +} |
| + |
| +void SelectionOwner::AbortStaleIncrTransfers() { |
| + base::TimeTicks now = base::TimeTicks::Now(); |
| + for (int i = static_cast<int>(incrs_.size()) - 1; i >= 0; --i) { |
| + if (incrs_[i].timeout < now) |
| + CompleteIncrTransfer(incrs_.begin() + i); |
| + } |
| +} |
| + |
| +void SelectionOwner::CompleteIncrTransfer( |
| + std::vector<IncrTransfer>::iterator it) { |
| + ui::XForeignWindowManager::GetInstance()->CancelRequest( |
| + it->foreign_window_manager_id); |
| + incrs_.erase(it); |
| + |
| + if (incrs_.empty()) |
| + incr_timer_.Stop(); |
| +} |
| + |
| +std::vector<SelectionOwner::IncrTransfer>::iterator |
| + SelectionOwner::FindIncrTransferForEvent(const XEvent& event) { |
| + for (std::vector<IncrTransfer>::iterator it = incrs_.begin(); |
| + it != incrs_.end(); ++it) { |
| + if (it->window == event.xproperty.window && |
| + it->property == event.xproperty.atom) { |
| + return it; |
| + } |
| + } |
| + return incrs_.end(); |
| +} |
| + |
| +SelectionOwner::IncrTransfer::IncrTransfer( |
| + XID window, |
| + XAtom target, |
| + XAtom property, |
| + const scoped_refptr<base::RefCountedMemory>& data, |
| + int offset, |
| + base::TimeTicks timeout, |
| + int foreign_window_manager_id) |
| + : window(window), |
| + target(target), |
| + property(property), |
| + data(data), |
| + offset(offset), |
| + timeout(timeout), |
| + foreign_window_manager_id(foreign_window_manager_id) { |
| +} |
| + |
| +SelectionOwner::IncrTransfer::~IncrTransfer() { |
| +} |
| + |
| } // namespace ui |