Home | History | Annotate | Download | only in x
      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