| OLD | NEW |
| (Empty) |
| 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 | |
| 3 // found in the LICENSE file. | |
| 4 | |
| 5 #include "ui/base/x/selection_requestor.h" | |
| 6 | |
| 7 #include <algorithm> | |
| 8 #include <X11/Xlib.h> | |
| 9 | |
| 10 #include "base/run_loop.h" | |
| 11 #include "ui/base/x/selection_utils.h" | |
| 12 #include "ui/base/x/x11_util.h" | |
| 13 #include "ui/events/platform/platform_event_dispatcher.h" | |
| 14 #include "ui/events/platform/platform_event_source.h" | |
| 15 #include "ui/gfx/x/x11_types.h" | |
| 16 | |
| 17 namespace ui { | |
| 18 | |
| 19 namespace { | |
| 20 | |
| 21 const char kChromeSelection[] = "CHROME_SELECTION"; | |
| 22 const char kIncr[] = "INCR"; | |
| 23 | |
| 24 const char* kAtomsToCache[] = { | |
| 25 kChromeSelection, | |
| 26 kIncr, | |
| 27 NULL | |
| 28 }; | |
| 29 | |
| 30 // The period of |abort_timer_|. Arbitrary but must be <= than | |
| 31 // kRequestTimeoutMs. | |
| 32 const int kTimerPeriodMs = 100; | |
| 33 | |
| 34 // The amount of time to wait for a request to complete before aborting it. | |
| 35 const int kRequestTimeoutMs = 10000; | |
| 36 | |
| 37 COMPILE_ASSERT(kTimerPeriodMs <= kRequestTimeoutMs, | |
| 38 timer_period_must_be_less_or_equal_to_request_timeout); | |
| 39 | |
| 40 // Combines |data| into a single RefCountedMemory object. | |
| 41 scoped_refptr<base::RefCountedMemory> CombineRefCountedMemory( | |
| 42 const std::vector<scoped_refptr<base::RefCountedMemory> >& data) { | |
| 43 if (data.size() == 1u) | |
| 44 return data[0]; | |
| 45 | |
| 46 size_t length = 0; | |
| 47 for (size_t i = 0; i < data.size(); ++i) | |
| 48 length += data[i]->size(); | |
| 49 std::vector<unsigned char> combined_data; | |
| 50 combined_data.reserve(length); | |
| 51 | |
| 52 for (size_t i = 0; i < data.size(); ++i) { | |
| 53 combined_data.insert(combined_data.end(), | |
| 54 data[i]->front(), | |
| 55 data[i]->front() + data[i]->size()); | |
| 56 } | |
| 57 return scoped_refptr<base::RefCountedMemory>( | |
| 58 base::RefCountedBytes::TakeVector(&combined_data)); | |
| 59 } | |
| 60 | |
| 61 } // namespace | |
| 62 | |
| 63 SelectionRequestor::SelectionRequestor(XDisplay* x_display, | |
| 64 XID x_window, | |
| 65 PlatformEventDispatcher* dispatcher) | |
| 66 : x_display_(x_display), | |
| 67 x_window_(x_window), | |
| 68 x_property_(None), | |
| 69 dispatcher_(dispatcher), | |
| 70 current_request_index_(0u), | |
| 71 atom_cache_(x_display_, kAtomsToCache) { | |
| 72 x_property_ = atom_cache_.GetAtom(kChromeSelection); | |
| 73 } | |
| 74 | |
| 75 SelectionRequestor::~SelectionRequestor() {} | |
| 76 | |
| 77 bool SelectionRequestor::PerformBlockingConvertSelection( | |
| 78 XAtom selection, | |
| 79 XAtom target, | |
| 80 scoped_refptr<base::RefCountedMemory>* out_data, | |
| 81 size_t* out_data_items, | |
| 82 XAtom* out_type) { | |
| 83 base::TimeTicks timeout = | |
| 84 base::TimeTicks::Now() + | |
| 85 base::TimeDelta::FromMilliseconds(kRequestTimeoutMs); | |
| 86 Request request(selection, target, timeout); | |
| 87 requests_.push_back(&request); | |
| 88 if (current_request_index_ == (requests_.size() - 1)) | |
| 89 ConvertSelectionForCurrentRequest(); | |
| 90 BlockTillSelectionNotifyForRequest(&request); | |
| 91 | |
| 92 std::vector<Request*>::iterator request_it = std::find( | |
| 93 requests_.begin(), requests_.end(), &request); | |
| 94 CHECK(request_it != requests_.end()); | |
| 95 if (static_cast<int>(current_request_index_) > | |
| 96 request_it - requests_.begin()) { | |
| 97 --current_request_index_; | |
| 98 } | |
| 99 requests_.erase(request_it); | |
| 100 | |
| 101 if (requests_.empty()) | |
| 102 abort_timer_.Stop(); | |
| 103 | |
| 104 if (request.success) { | |
| 105 if (out_data) | |
| 106 *out_data = CombineRefCountedMemory(request.out_data); | |
| 107 if (out_data_items) | |
| 108 *out_data_items = request.out_data_items; | |
| 109 if (out_type) | |
| 110 *out_type = request.out_type; | |
| 111 } | |
| 112 return request.success; | |
| 113 } | |
| 114 | |
| 115 void SelectionRequestor::PerformBlockingConvertSelectionWithParameter( | |
| 116 XAtom selection, | |
| 117 XAtom target, | |
| 118 const std::vector<XAtom>& parameter) { | |
| 119 SetAtomArrayProperty(x_window_, kChromeSelection, "ATOM", parameter); | |
| 120 PerformBlockingConvertSelection(selection, target, NULL, NULL, NULL); | |
| 121 } | |
| 122 | |
| 123 SelectionData SelectionRequestor::RequestAndWaitForTypes( | |
| 124 XAtom selection, | |
| 125 const std::vector<XAtom>& types) { | |
| 126 for (std::vector<XAtom>::const_iterator it = types.begin(); | |
| 127 it != types.end(); ++it) { | |
| 128 scoped_refptr<base::RefCountedMemory> data; | |
| 129 XAtom type = None; | |
| 130 if (PerformBlockingConvertSelection(selection, | |
| 131 *it, | |
| 132 &data, | |
| 133 NULL, | |
| 134 &type) && | |
| 135 type == *it) { | |
| 136 return SelectionData(type, data); | |
| 137 } | |
| 138 } | |
| 139 | |
| 140 return SelectionData(); | |
| 141 } | |
| 142 | |
| 143 void SelectionRequestor::OnSelectionNotify(const XEvent& event) { | |
| 144 Request* request = GetCurrentRequest(); | |
| 145 XAtom event_property = event.xselection.property; | |
| 146 if (!request || | |
| 147 request->completed || | |
| 148 request->selection != event.xselection.selection || | |
| 149 request->target != event.xselection.target) { | |
| 150 // ICCCM requires us to delete the property passed into SelectionNotify. | |
| 151 if (event_property != None) | |
| 152 XDeleteProperty(x_display_, x_window_, event_property); | |
| 153 return; | |
| 154 } | |
| 155 | |
| 156 bool success = false; | |
| 157 if (event_property == x_property_) { | |
| 158 scoped_refptr<base::RefCountedMemory> out_data; | |
| 159 success = ui::GetRawBytesOfProperty(x_window_, | |
| 160 x_property_, | |
| 161 &out_data, | |
| 162 &request->out_data_items, | |
| 163 &request->out_type); | |
| 164 if (success) { | |
| 165 request->out_data.clear(); | |
| 166 request->out_data.push_back(out_data); | |
| 167 } | |
| 168 } | |
| 169 if (event_property != None) | |
| 170 XDeleteProperty(x_display_, x_window_, event_property); | |
| 171 | |
| 172 if (request->out_type == atom_cache_.GetAtom(kIncr)) { | |
| 173 request->data_sent_incrementally = true; | |
| 174 request->out_data.clear(); | |
| 175 request->out_data_items = 0u; | |
| 176 request->out_type = None; | |
| 177 request->timeout = base::TimeTicks::Now() + | |
| 178 base::TimeDelta::FromMilliseconds(kRequestTimeoutMs); | |
| 179 } else { | |
| 180 CompleteRequest(current_request_index_, success); | |
| 181 } | |
| 182 } | |
| 183 | |
| 184 bool SelectionRequestor::CanDispatchPropertyEvent(const XEvent& event) { | |
| 185 return event.xproperty.window == x_window_ && | |
| 186 event.xproperty.atom == x_property_ && | |
| 187 event.xproperty.state == PropertyNewValue; | |
| 188 } | |
| 189 | |
| 190 void SelectionRequestor::OnPropertyEvent(const XEvent& event) { | |
| 191 Request* request = GetCurrentRequest(); | |
| 192 if (!request || !request->data_sent_incrementally) | |
| 193 return; | |
| 194 | |
| 195 scoped_refptr<base::RefCountedMemory> out_data; | |
| 196 size_t out_data_items = 0u; | |
| 197 Atom out_type = None; | |
| 198 bool success = ui::GetRawBytesOfProperty(x_window_, | |
| 199 x_property_, | |
| 200 &out_data, | |
| 201 &out_data_items, | |
| 202 &out_type); | |
| 203 if (!success) { | |
| 204 CompleteRequest(current_request_index_, false); | |
| 205 return; | |
| 206 } | |
| 207 | |
| 208 if (request->out_type != None && request->out_type != out_type) { | |
| 209 CompleteRequest(current_request_index_, false); | |
| 210 return; | |
| 211 } | |
| 212 | |
| 213 request->out_data.push_back(out_data); | |
| 214 request->out_data_items += out_data_items; | |
| 215 request->out_type = out_type; | |
| 216 | |
| 217 // Delete the property to tell the selection owner to send the next chunk. | |
| 218 XDeleteProperty(x_display_, x_window_, x_property_); | |
| 219 | |
| 220 request->timeout = base::TimeTicks::Now() + | |
| 221 base::TimeDelta::FromMilliseconds(kRequestTimeoutMs); | |
| 222 | |
| 223 if (out_data->size() == 0u) | |
| 224 CompleteRequest(current_request_index_, true); | |
| 225 } | |
| 226 | |
| 227 void SelectionRequestor::AbortStaleRequests() { | |
| 228 base::TimeTicks now = base::TimeTicks::Now(); | |
| 229 for (size_t i = current_request_index_; i < requests_.size(); ++i) { | |
| 230 if (requests_[i]->timeout <= now) | |
| 231 CompleteRequest(i, false); | |
| 232 } | |
| 233 } | |
| 234 | |
| 235 void SelectionRequestor::CompleteRequest(size_t index, bool success) { | |
| 236 if (index >= requests_.size()) | |
| 237 return; | |
| 238 | |
| 239 Request* request = requests_[index]; | |
| 240 if (request->completed) | |
| 241 return; | |
| 242 request->success = success; | |
| 243 request->completed = true; | |
| 244 | |
| 245 if (index == current_request_index_) { | |
| 246 while (GetCurrentRequest() && GetCurrentRequest()->completed) | |
| 247 ++current_request_index_; | |
| 248 ConvertSelectionForCurrentRequest(); | |
| 249 } | |
| 250 | |
| 251 if (!request->quit_closure.is_null()) | |
| 252 request->quit_closure.Run(); | |
| 253 } | |
| 254 | |
| 255 void SelectionRequestor::ConvertSelectionForCurrentRequest() { | |
| 256 Request* request = GetCurrentRequest(); | |
| 257 if (request) { | |
| 258 XConvertSelection(x_display_, | |
| 259 request->selection, | |
| 260 request->target, | |
| 261 x_property_, | |
| 262 x_window_, | |
| 263 CurrentTime); | |
| 264 } | |
| 265 } | |
| 266 | |
| 267 void SelectionRequestor::BlockTillSelectionNotifyForRequest(Request* request) { | |
| 268 if (PlatformEventSource::GetInstance()) { | |
| 269 if (!abort_timer_.IsRunning()) { | |
| 270 abort_timer_.Start(FROM_HERE, | |
| 271 base::TimeDelta::FromMilliseconds(kTimerPeriodMs), | |
| 272 this, | |
| 273 &SelectionRequestor::AbortStaleRequests); | |
| 274 } | |
| 275 | |
| 276 base::MessageLoop::ScopedNestableTaskAllower allow_nested( | |
| 277 base::MessageLoopForUI::current()); | |
| 278 base::RunLoop run_loop; | |
| 279 request->quit_closure = run_loop.QuitClosure(); | |
| 280 run_loop.Run(); | |
| 281 | |
| 282 // We cannot put logic to process the next request here because the RunLoop | |
| 283 // might be nested. For instance, request 'B' may start a RunLoop while the | |
| 284 // RunLoop for request 'A' is running. It is not possible to end the RunLoop | |
| 285 // for request 'A' without first ending the RunLoop for request 'B'. | |
| 286 } else { | |
| 287 // This occurs if PerformBlockingConvertSelection() is called during | |
| 288 // shutdown and the PlatformEventSource has already been destroyed. | |
| 289 while (!request->completed && | |
| 290 request->timeout > base::TimeTicks::Now()) { | |
| 291 if (XPending(x_display_)) { | |
| 292 XEvent event; | |
| 293 XNextEvent(x_display_, &event); | |
| 294 dispatcher_->DispatchEvent(&event); | |
| 295 } | |
| 296 } | |
| 297 } | |
| 298 } | |
| 299 | |
| 300 SelectionRequestor::Request* SelectionRequestor::GetCurrentRequest() { | |
| 301 return current_request_index_ == requests_.size() ? | |
| 302 NULL : requests_[current_request_index_]; | |
| 303 } | |
| 304 | |
| 305 SelectionRequestor::Request::Request(XAtom selection, | |
| 306 XAtom target, | |
| 307 base::TimeTicks timeout) | |
| 308 : selection(selection), | |
| 309 target(target), | |
| 310 data_sent_incrementally(false), | |
| 311 out_data_items(0u), | |
| 312 out_type(None), | |
| 313 success(false), | |
| 314 timeout(timeout), | |
| 315 completed(false) { | |
| 316 } | |
| 317 | |
| 318 SelectionRequestor::Request::~Request() { | |
| 319 } | |
| 320 | |
| 321 } // namespace ui | |
| OLD | NEW |