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 322