| OLD | NEW |
| 1 // Copyright (c) 2013 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2013 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "ui/base/x/selection_requestor.h" | 5 #include "ui/base/x/selection_requestor.h" |
| 6 | 6 |
| 7 #include <algorithm> |
| 7 #include <X11/Xlib.h> | 8 #include <X11/Xlib.h> |
| 8 | 9 |
| 9 #include "base/run_loop.h" | 10 #include "base/run_loop.h" |
| 10 #include "ui/base/x/selection_utils.h" | 11 #include "ui/base/x/selection_utils.h" |
| 11 #include "ui/base/x/x11_util.h" | 12 #include "ui/base/x/x11_util.h" |
| 12 #include "ui/events/platform/platform_event_dispatcher.h" | 13 #include "ui/events/platform/platform_event_dispatcher.h" |
| 13 #include "ui/events/platform/platform_event_source.h" | 14 #include "ui/events/platform/platform_event_source.h" |
| 14 #include "ui/gfx/x/x11_types.h" | 15 #include "ui/gfx/x/x11_types.h" |
| 15 | 16 |
| 16 namespace ui { | 17 namespace ui { |
| 17 | 18 |
| 18 namespace { | 19 namespace { |
| 19 | 20 |
| 20 const char kChromeSelection[] = "CHROME_SELECTION"; | 21 const char kChromeSelection[] = "CHROME_SELECTION"; |
| 21 | 22 |
| 22 const char* kAtomsToCache[] = { | 23 const char* kAtomsToCache[] = { |
| 23 kChromeSelection, | 24 kChromeSelection, |
| 24 NULL | 25 NULL |
| 25 }; | 26 }; |
| 26 | 27 |
| 28 // The amount of time to wait for a request to complete. |
| 29 const int kRequestTimeoutMs = 300; |
| 30 |
| 27 } // namespace | 31 } // namespace |
| 28 | 32 |
| 29 SelectionRequestor::SelectionRequestor(XDisplay* x_display, | 33 SelectionRequestor::SelectionRequestor(XDisplay* x_display, |
| 30 XID x_window, | 34 XID x_window, |
| 31 XAtom selection_name, | |
| 32 PlatformEventDispatcher* dispatcher) | 35 PlatformEventDispatcher* dispatcher) |
| 33 : x_display_(x_display), | 36 : x_display_(x_display), |
| 34 x_window_(x_window), | 37 x_window_(x_window), |
| 35 selection_name_(selection_name), | 38 x_property_(None), |
| 36 dispatcher_(dispatcher), | 39 dispatcher_(dispatcher), |
| 40 current_request_index_(0u), |
| 37 atom_cache_(x_display_, kAtomsToCache) { | 41 atom_cache_(x_display_, kAtomsToCache) { |
| 42 x_property_ = atom_cache_.GetAtom(kChromeSelection); |
| 38 } | 43 } |
| 39 | 44 |
| 40 SelectionRequestor::~SelectionRequestor() {} | 45 SelectionRequestor::~SelectionRequestor() {} |
| 41 | 46 |
| 42 bool SelectionRequestor::PerformBlockingConvertSelection( | 47 bool SelectionRequestor::PerformBlockingConvertSelection( |
| 48 XAtom selection, |
| 43 XAtom target, | 49 XAtom target, |
| 44 scoped_refptr<base::RefCountedMemory>* out_data, | 50 scoped_refptr<base::RefCountedMemory>* out_data, |
| 45 size_t* out_data_items, | 51 size_t* out_data_items, |
| 46 XAtom* out_type) { | 52 XAtom* out_type) { |
| 47 // The name of the property that we are either: | 53 base::TimeTicks timeout = |
| 48 // - Passing as a parameter with the XConvertSelection() request. | 54 base::TimeTicks::Now() + |
| 49 // OR | 55 base::TimeDelta::FromMilliseconds(kRequestTimeoutMs); |
| 50 // - Asking the selection owner to set on |x_window_|. | 56 Request request(selection, target, timeout); |
| 51 XAtom property = atom_cache_.GetAtom(kChromeSelection); | 57 requests_.push_back(&request); |
| 58 if (requests_.size() == 1u) |
| 59 ConvertSelectionForCurrentRequest(); |
| 60 BlockTillSelectionNotifyForRequest(&request); |
| 52 | 61 |
| 53 XConvertSelection(x_display_, | 62 std::vector<Request*>::iterator request_it = std::find( |
| 54 selection_name_, | 63 requests_.begin(), requests_.end(), &request); |
| 55 target, | 64 CHECK(request_it != requests_.end()); |
| 56 property, | 65 if (static_cast<int>(current_request_index_) > |
| 57 x_window_, | 66 request_it - requests_.begin()) { |
| 58 CurrentTime); | 67 --current_request_index_; |
| 68 } |
| 69 requests_.erase(request_it); |
| 59 | 70 |
| 60 // Now that we've thrown our message off to the X11 server, we block waiting | 71 if (requests_.empty()) |
| 61 // for a response. | 72 abort_timer_.Stop(); |
| 62 PendingRequest pending_request(target); | |
| 63 BlockTillSelectionNotifyForRequest(&pending_request); | |
| 64 | 73 |
| 65 bool success = false; | 74 if (request.success) { |
| 66 if (pending_request.returned_property == property) { | 75 if (out_data) |
| 67 success = ui::GetRawBytesOfProperty(x_window_, | 76 *out_data = request.out_data; |
| 68 pending_request.returned_property, | 77 if (out_data_items) |
| 69 out_data, out_data_items, out_type); | 78 *out_data_items = request.out_data_items; |
| 79 if (out_type) |
| 80 *out_type = request.out_type; |
| 70 } | 81 } |
| 71 if (pending_request.returned_property != None) | 82 return request.success; |
| 72 XDeleteProperty(x_display_, x_window_, pending_request.returned_property); | |
| 73 return success; | |
| 74 } | 83 } |
| 75 | 84 |
| 76 void SelectionRequestor::PerformBlockingConvertSelectionWithParameter( | 85 void SelectionRequestor::PerformBlockingConvertSelectionWithParameter( |
| 86 XAtom selection, |
| 77 XAtom target, | 87 XAtom target, |
| 78 const std::vector<XAtom>& parameter) { | 88 const std::vector<XAtom>& parameter) { |
| 79 SetAtomArrayProperty(x_window_, kChromeSelection, "ATOM", parameter); | 89 SetAtomArrayProperty(x_window_, kChromeSelection, "ATOM", parameter); |
| 80 PerformBlockingConvertSelection(target, NULL, NULL, NULL); | 90 PerformBlockingConvertSelection(selection, target, NULL, NULL, NULL); |
| 81 } | 91 } |
| 82 | 92 |
| 83 SelectionData SelectionRequestor::RequestAndWaitForTypes( | 93 SelectionData SelectionRequestor::RequestAndWaitForTypes( |
| 94 XAtom selection, |
| 84 const std::vector<XAtom>& types) { | 95 const std::vector<XAtom>& types) { |
| 85 for (std::vector<XAtom>::const_iterator it = types.begin(); | 96 for (std::vector<XAtom>::const_iterator it = types.begin(); |
| 86 it != types.end(); ++it) { | 97 it != types.end(); ++it) { |
| 87 scoped_refptr<base::RefCountedMemory> data; | 98 scoped_refptr<base::RefCountedMemory> data; |
| 88 XAtom type = None; | 99 XAtom type = None; |
| 89 if (PerformBlockingConvertSelection(*it, | 100 if (PerformBlockingConvertSelection(selection, |
| 101 *it, |
| 90 &data, | 102 &data, |
| 91 NULL, | 103 NULL, |
| 92 &type) && | 104 &type) && |
| 93 type == *it) { | 105 type == *it) { |
| 94 return SelectionData(type, data); | 106 return SelectionData(type, data); |
| 95 } | 107 } |
| 96 } | 108 } |
| 97 | 109 |
| 98 return SelectionData(); | 110 return SelectionData(); |
| 99 } | 111 } |
| 100 | 112 |
| 101 void SelectionRequestor::OnSelectionNotify(const XEvent& event) { | 113 void SelectionRequestor::OnSelectionNotify(const XEvent& event) { |
| 102 // Find the PendingRequest for the corresponding XConvertSelection call. If | 114 Request* request = GetCurrentRequest(); |
| 103 // there are multiple pending requests on the same target, satisfy them in | 115 XAtom event_property = event.xselection.property; |
| 104 // FIFO order. | 116 if (!request || |
| 105 PendingRequest* request_notified = NULL; | 117 request->completed || |
| 106 if (selection_name_ == event.xselection.selection) { | 118 request->selection != event.xselection.selection || |
| 107 for (std::list<PendingRequest*>::iterator iter = pending_requests_.begin(); | 119 request->target != event.xselection.target) { |
| 108 iter != pending_requests_.end(); ++iter) { | 120 // ICCCM requires us to delete the property passed into SelectionNotify. |
| 109 PendingRequest* request = *iter; | 121 if (event_property != None) |
| 110 if (request->returned) | 122 XDeleteProperty(x_display_, x_window_, event_property); |
| 111 continue; | |
| 112 if (request->target != event.xselection.target) | |
| 113 continue; | |
| 114 request_notified = request; | |
| 115 break; | |
| 116 } | |
| 117 } | |
| 118 | |
| 119 // This event doesn't correspond to any XConvertSelection calls that we | |
| 120 // issued in PerformBlockingConvertSelection. This shouldn't happen, but any | |
| 121 // client can send any message, so it can happen. | |
| 122 XAtom returned_property = event.xselection.property; | |
| 123 if (!request_notified) { | |
| 124 // ICCCM requires us to delete the property passed into SelectionNotify. If | |
| 125 // |request_notified| is true, the property will be deleted when the run | |
| 126 // loop has quit. | |
| 127 if (returned_property != None) | |
| 128 XDeleteProperty(x_display_, x_window_, returned_property); | |
| 129 return; | 123 return; |
| 130 } | 124 } |
| 131 | 125 |
| 132 request_notified->returned_property = returned_property; | 126 request->success = false; |
| 133 request_notified->returned = true; | 127 if (event_property == x_property_) { |
| 128 request->success = ui::GetRawBytesOfProperty(x_window_, |
| 129 x_property_, |
| 130 &request->out_data, |
| 131 &request->out_data_items, |
| 132 &request->out_type); |
| 133 } |
| 134 if (event_property != None) |
| 135 XDeleteProperty(x_display_, x_window_, event_property); |
| 134 | 136 |
| 135 if (!request_notified->quit_closure.is_null()) | 137 CompleteRequest(current_request_index_); |
| 136 request_notified->quit_closure.Run(); | |
| 137 } | 138 } |
| 138 | 139 |
| 139 void SelectionRequestor::BlockTillSelectionNotifyForRequest( | 140 void SelectionRequestor::AbortStaleRequests() { |
| 140 PendingRequest* request) { | 141 base::TimeTicks now = base::TimeTicks::Now(); |
| 141 pending_requests_.push_back(request); | 142 for (size_t i = current_request_index_; |
| 143 i < requests_.size() && requests_[i]->timeout <= now; |
| 144 ++i) { |
| 145 CompleteRequest(i); |
| 146 } |
| 147 } |
| 142 | 148 |
| 143 const int kMaxWaitTimeForClipboardResponse = 300; | 149 void SelectionRequestor::CompleteRequest(size_t index) { |
| 150 if (index >= requests_.size()) |
| 151 return; |
| 152 |
| 153 Request* request = requests_[index]; |
| 154 if (request->completed) |
| 155 return; |
| 156 request->completed = true; |
| 157 |
| 158 if (index == current_request_index_) { |
| 159 ++current_request_index_; |
| 160 ConvertSelectionForCurrentRequest(); |
| 161 } |
| 162 |
| 163 if (!request->quit_closure.is_null()) |
| 164 request->quit_closure.Run(); |
| 165 } |
| 166 |
| 167 void SelectionRequestor::ConvertSelectionForCurrentRequest() { |
| 168 Request* request = GetCurrentRequest(); |
| 169 if (request) { |
| 170 XConvertSelection(x_display_, |
| 171 request->selection, |
| 172 request->target, |
| 173 x_property_, |
| 174 x_window_, |
| 175 CurrentTime); |
| 176 } |
| 177 } |
| 178 |
| 179 void SelectionRequestor::BlockTillSelectionNotifyForRequest(Request* request) { |
| 144 if (PlatformEventSource::GetInstance()) { | 180 if (PlatformEventSource::GetInstance()) { |
| 145 base::MessageLoopForUI* loop = base::MessageLoopForUI::current(); | 181 if (!abort_timer_.IsRunning()) { |
| 146 base::MessageLoop::ScopedNestableTaskAllower allow_nested(loop); | 182 abort_timer_.Start(FROM_HERE, |
| 183 base::TimeDelta::FromMilliseconds(kRequestTimeoutMs), |
| 184 this, |
| 185 &SelectionRequestor::AbortStaleRequests); |
| 186 } |
| 187 |
| 188 base::MessageLoop::ScopedNestableTaskAllower allow_nested( |
| 189 base::MessageLoopForUI::current()); |
| 147 base::RunLoop run_loop; | 190 base::RunLoop run_loop; |
| 191 request->quit_closure = run_loop.QuitClosure(); |
| 192 run_loop.Run(); |
| 148 | 193 |
| 149 request->quit_closure = run_loop.QuitClosure(); | 194 // We cannot put logic to process the next request here because the RunLoop |
| 150 loop->PostDelayedTask( | 195 // might be nested. For instance, request 'B' may start a RunLoop while the |
| 151 FROM_HERE, | 196 // RunLoop for request 'A' is running. It is not possible to end the RunLoop |
| 152 request->quit_closure, | 197 // for request 'A' without first ending the RunLoop for request 'B'. |
| 153 base::TimeDelta::FromMilliseconds(kMaxWaitTimeForClipboardResponse)); | |
| 154 | |
| 155 run_loop.Run(); | |
| 156 } else { | 198 } else { |
| 157 // This occurs if PerformBlockingConvertSelection() is called during | 199 // This occurs if PerformBlockingConvertSelection() is called during |
| 158 // shutdown and the PlatformEventSource has already been destroyed. | 200 // shutdown and the PlatformEventSource has already been destroyed. |
| 159 base::TimeTicks start = base::TimeTicks::Now(); | 201 while (!request->completed && |
| 160 while (!request->returned) { | 202 request->timeout > base::TimeTicks::Now()) { |
| 161 if (XPending(x_display_)) { | 203 if (XPending(x_display_)) { |
| 162 XEvent event; | 204 XEvent event; |
| 163 XNextEvent(x_display_, &event); | 205 XNextEvent(x_display_, &event); |
| 164 dispatcher_->DispatchEvent(&event); | 206 dispatcher_->DispatchEvent(&event); |
| 165 } | 207 } |
| 166 base::TimeDelta wait_time = base::TimeTicks::Now() - start; | |
| 167 if (wait_time.InMilliseconds() > kMaxWaitTimeForClipboardResponse) | |
| 168 break; | |
| 169 } | 208 } |
| 170 } | 209 } |
| 171 | |
| 172 DCHECK(!pending_requests_.empty()); | |
| 173 DCHECK_EQ(request, pending_requests_.back()); | |
| 174 pending_requests_.pop_back(); | |
| 175 } | 210 } |
| 176 | 211 |
| 177 SelectionRequestor::PendingRequest::PendingRequest(XAtom target) | 212 SelectionRequestor::Request* SelectionRequestor::GetCurrentRequest() { |
| 178 : target(target), | 213 return current_request_index_ == requests_.size() ? |
| 179 returned_property(None), | 214 NULL : requests_[current_request_index_]; |
| 180 returned(false) { | |
| 181 } | 215 } |
| 182 | 216 |
| 183 SelectionRequestor::PendingRequest::~PendingRequest() { | 217 SelectionRequestor::Request::Request(XAtom selection, |
| 218 XAtom target, |
| 219 base::TimeTicks timeout) |
| 220 : selection(selection), |
| 221 target(target), |
| 222 out_data_items(0u), |
| 223 out_type(None), |
| 224 success(false), |
| 225 timeout(timeout), |
| 226 completed(false) { |
| 227 } |
| 228 |
| 229 SelectionRequestor::Request::~Request() { |
| 184 } | 230 } |
| 185 | 231 |
| 186 } // namespace ui | 232 } // namespace ui |
| OLD | NEW |