Index: ui/base/x/selection_requestor.cc |
diff --git a/ui/base/x/selection_requestor.cc b/ui/base/x/selection_requestor.cc |
index 9268a7f3538bdb42d1a4b53d1dcc26272077ec25..444401ae42fa3f84bed4157defef92b13e4f9a45 100644 |
--- a/ui/base/x/selection_requestor.cc |
+++ b/ui/base/x/selection_requestor.cc |
@@ -4,6 +4,7 @@ |
#include "ui/base/x/selection_requestor.h" |
+#include <algorithm> |
#include <X11/Xlib.h> |
#include "base/run_loop.h" |
@@ -24,69 +25,80 @@ const char* kAtomsToCache[] = { |
NULL |
}; |
+// The amount of time to wait for a request to complete. |
+const int kRequestTimeoutMs = 300; |
+ |
} // namespace |
SelectionRequestor::SelectionRequestor(XDisplay* x_display, |
XID x_window, |
- XAtom selection_name, |
PlatformEventDispatcher* dispatcher) |
: x_display_(x_display), |
x_window_(x_window), |
- selection_name_(selection_name), |
+ x_property_(None), |
dispatcher_(dispatcher), |
+ current_request_index_(0u), |
atom_cache_(x_display_, kAtomsToCache) { |
+ x_property_ = atom_cache_.GetAtom(kChromeSelection); |
} |
SelectionRequestor::~SelectionRequestor() {} |
bool SelectionRequestor::PerformBlockingConvertSelection( |
+ XAtom selection, |
XAtom target, |
scoped_refptr<base::RefCountedMemory>* out_data, |
size_t* out_data_items, |
XAtom* out_type) { |
- // The name of the property that we are either: |
- // - Passing as a parameter with the XConvertSelection() request. |
- // OR |
- // - Asking the selection owner to set on |x_window_|. |
- XAtom property = atom_cache_.GetAtom(kChromeSelection); |
- |
- XConvertSelection(x_display_, |
- selection_name_, |
- target, |
- property, |
- x_window_, |
- CurrentTime); |
- |
- // Now that we've thrown our message off to the X11 server, we block waiting |
- // for a response. |
- PendingRequest pending_request(target); |
- BlockTillSelectionNotifyForRequest(&pending_request); |
- |
- bool success = false; |
- if (pending_request.returned_property == property) { |
- success = ui::GetRawBytesOfProperty(x_window_, |
- pending_request.returned_property, |
- out_data, out_data_items, out_type); |
+ base::TimeTicks timeout = |
+ base::TimeTicks::Now() + |
+ base::TimeDelta::FromMilliseconds(kRequestTimeoutMs); |
+ Request request(selection, target, timeout); |
+ requests_.push_back(&request); |
+ if (requests_.size() == 1u) |
+ ConvertSelectionForCurrentRequest(); |
+ BlockTillSelectionNotifyForRequest(&request); |
+ |
+ std::vector<Request*>::iterator request_it = std::find( |
+ requests_.begin(), requests_.end(), &request); |
+ CHECK(request_it != requests_.end()); |
+ if (static_cast<int>(current_request_index_) > |
+ request_it - requests_.begin()) { |
+ --current_request_index_; |
+ } |
+ requests_.erase(request_it); |
+ |
+ if (requests_.empty()) |
+ abort_timer_.Stop(); |
+ |
+ if (request.success) { |
+ if (out_data) |
+ *out_data = request.out_data; |
+ if (out_data_items) |
+ *out_data_items = request.out_data_items; |
+ if (out_type) |
+ *out_type = request.out_type; |
} |
- if (pending_request.returned_property != None) |
- XDeleteProperty(x_display_, x_window_, pending_request.returned_property); |
- return success; |
+ return request.success; |
} |
void SelectionRequestor::PerformBlockingConvertSelectionWithParameter( |
+ XAtom selection, |
XAtom target, |
const std::vector<XAtom>& parameter) { |
SetAtomArrayProperty(x_window_, kChromeSelection, "ATOM", parameter); |
- PerformBlockingConvertSelection(target, NULL, NULL, NULL); |
+ PerformBlockingConvertSelection(selection, target, NULL, NULL, NULL); |
} |
SelectionData SelectionRequestor::RequestAndWaitForTypes( |
+ XAtom selection, |
const std::vector<XAtom>& types) { |
for (std::vector<XAtom>::const_iterator it = types.begin(); |
it != types.end(); ++it) { |
scoped_refptr<base::RefCountedMemory> data; |
XAtom type = None; |
- if (PerformBlockingConvertSelection(*it, |
+ if (PerformBlockingConvertSelection(selection, |
+ *it, |
&data, |
NULL, |
&type) && |
@@ -99,88 +111,122 @@ SelectionData SelectionRequestor::RequestAndWaitForTypes( |
} |
void SelectionRequestor::OnSelectionNotify(const XEvent& event) { |
- // Find the PendingRequest for the corresponding XConvertSelection call. If |
- // there are multiple pending requests on the same target, satisfy them in |
- // FIFO order. |
- PendingRequest* request_notified = NULL; |
- if (selection_name_ == event.xselection.selection) { |
- for (std::list<PendingRequest*>::iterator iter = pending_requests_.begin(); |
- iter != pending_requests_.end(); ++iter) { |
- PendingRequest* request = *iter; |
- if (request->returned) |
- continue; |
- if (request->target != event.xselection.target) |
- continue; |
- request_notified = request; |
- break; |
- } |
+ Request* request = GetCurrentRequest(); |
+ XAtom event_property = event.xselection.property; |
+ if (!request || |
+ request->completed || |
+ request->selection != event.xselection.selection || |
+ request->target != event.xselection.target) { |
+ // ICCCM requires us to delete the property passed into SelectionNotify. |
+ if (event_property != None) |
+ XDeleteProperty(x_display_, x_window_, event_property); |
+ return; |
} |
- // This event doesn't correspond to any XConvertSelection calls that we |
- // issued in PerformBlockingConvertSelection. This shouldn't happen, but any |
- // client can send any message, so it can happen. |
- XAtom returned_property = event.xselection.property; |
- if (!request_notified) { |
- // ICCCM requires us to delete the property passed into SelectionNotify. If |
- // |request_notified| is true, the property will be deleted when the run |
- // loop has quit. |
- if (returned_property != None) |
- XDeleteProperty(x_display_, x_window_, returned_property); |
- return; |
+ request->success = false; |
+ if (event_property == x_property_) { |
+ request->success = ui::GetRawBytesOfProperty(x_window_, |
+ x_property_, |
+ &request->out_data, |
+ &request->out_data_items, |
+ &request->out_type); |
} |
+ if (event_property != None) |
+ XDeleteProperty(x_display_, x_window_, event_property); |
+ |
+ CompleteRequest(current_request_index_); |
+} |
+ |
+void SelectionRequestor::AbortStaleRequests() { |
+ base::TimeTicks now = base::TimeTicks::Now(); |
+ for (size_t i = current_request_index_; |
+ i < requests_.size() && requests_[i]->timeout <= now; |
+ ++i) { |
+ CompleteRequest(i); |
+ } |
+} |
+ |
+void SelectionRequestor::CompleteRequest(size_t index) { |
+ if (index >= requests_.size()) |
+ return; |
- request_notified->returned_property = returned_property; |
- request_notified->returned = true; |
+ Request* request = requests_[index]; |
+ if (request->completed) |
+ return; |
+ request->completed = true; |
+ |
+ if (index == current_request_index_) { |
+ ++current_request_index_; |
+ ConvertSelectionForCurrentRequest(); |
+ } |
- if (!request_notified->quit_closure.is_null()) |
- request_notified->quit_closure.Run(); |
+ if (!request->quit_closure.is_null()) |
+ request->quit_closure.Run(); |
} |
-void SelectionRequestor::BlockTillSelectionNotifyForRequest( |
- PendingRequest* request) { |
- pending_requests_.push_back(request); |
+void SelectionRequestor::ConvertSelectionForCurrentRequest() { |
+ Request* request = GetCurrentRequest(); |
+ if (request) { |
+ XConvertSelection(x_display_, |
+ request->selection, |
+ request->target, |
+ x_property_, |
+ x_window_, |
+ CurrentTime); |
+ } |
+} |
- const int kMaxWaitTimeForClipboardResponse = 300; |
+void SelectionRequestor::BlockTillSelectionNotifyForRequest(Request* request) { |
if (PlatformEventSource::GetInstance()) { |
- base::MessageLoopForUI* loop = base::MessageLoopForUI::current(); |
- base::MessageLoop::ScopedNestableTaskAllower allow_nested(loop); |
- base::RunLoop run_loop; |
+ if (!abort_timer_.IsRunning()) { |
+ abort_timer_.Start(FROM_HERE, |
+ base::TimeDelta::FromMilliseconds(kRequestTimeoutMs), |
+ this, |
+ &SelectionRequestor::AbortStaleRequests); |
+ } |
+ base::MessageLoop::ScopedNestableTaskAllower allow_nested( |
+ base::MessageLoopForUI::current()); |
+ base::RunLoop run_loop; |
request->quit_closure = run_loop.QuitClosure(); |
- loop->PostDelayedTask( |
- FROM_HERE, |
- request->quit_closure, |
- base::TimeDelta::FromMilliseconds(kMaxWaitTimeForClipboardResponse)); |
- |
run_loop.Run(); |
+ |
+ // We cannot put logic to process the next request here because the RunLoop |
+ // might be nested. For instance, request 'B' may start a RunLoop while the |
+ // RunLoop for request 'A' is running. It is not possible to end the RunLoop |
+ // for request 'A' without first ending the RunLoop for request 'B'. |
} else { |
// This occurs if PerformBlockingConvertSelection() is called during |
// shutdown and the PlatformEventSource has already been destroyed. |
- base::TimeTicks start = base::TimeTicks::Now(); |
- while (!request->returned) { |
+ while (!request->completed && |
+ request->timeout > base::TimeTicks::Now()) { |
if (XPending(x_display_)) { |
XEvent event; |
XNextEvent(x_display_, &event); |
dispatcher_->DispatchEvent(&event); |
} |
- base::TimeDelta wait_time = base::TimeTicks::Now() - start; |
- if (wait_time.InMilliseconds() > kMaxWaitTimeForClipboardResponse) |
- break; |
} |
} |
+} |
- DCHECK(!pending_requests_.empty()); |
- DCHECK_EQ(request, pending_requests_.back()); |
- pending_requests_.pop_back(); |
+SelectionRequestor::Request* SelectionRequestor::GetCurrentRequest() { |
+ return current_request_index_ == requests_.size() ? |
+ NULL : requests_[current_request_index_]; |
} |
-SelectionRequestor::PendingRequest::PendingRequest(XAtom target) |
- : target(target), |
- returned_property(None), |
- returned(false) { |
+SelectionRequestor::Request::Request(XAtom selection, |
+ XAtom target, |
+ base::TimeTicks timeout) |
+ : selection(selection), |
+ target(target), |
+ out_data_items(0u), |
+ out_type(None), |
+ success(false), |
+ timeout(timeout), |
+ completed(false) { |
} |
-SelectionRequestor::PendingRequest::~PendingRequest() { |
+SelectionRequestor::Request::~Request() { |
} |
} // namespace ui |