text: // Copyright (c) 2013 The Chromium Authors. All rights reserved.
  // Use of this source code is governed by a BSD-style license that can be
  // found in the LICENSE file.
  
  #include "ui/base/x/selection_requestor.h"
  
  #include <algorithm>
  #include <X11/Xlib.h>
  
  #include "base/run_loop.h"
  #include "ui/base/x/selection_utils.h"
  #include "ui/base/x/x11_util.h"
  #include "ui/events/platform/platform_event_dispatcher.h"
  #include "ui/events/platform/platform_event_source.h"
  #include "ui/gfx/x/x11_types.h"
  
  namespace ui {
  
  namespace {
  
  const char kChromeSelection[] = "CHROME_SELECTION";
  const char kIncr[] = "INCR";
  
  const char* kAtomsToCache[] = {
    kChromeSelection,
    kIncr,
    NULL
  };
  
  // The period of |abort_timer_|. Arbitrary but must be <= than
  // kRequestTimeoutMs.
  const int kTimerPeriodMs = 100;
  
  // The amount of time to wait for a request to complete before aborting it.
  const int kRequestTimeoutMs = 10000;
  
  static_assert(kTimerPeriodMs <= kRequestTimeoutMs,
                "timer period must be <= request timeout");
  
  // Combines |data| into a single RefCountedMemory object.
  scoped_refptr<base::RefCountedMemory> CombineRefCountedMemory(
      const std::vector<scoped_refptr<base::RefCountedMemory> >& data) {
    if (data.size() == 1u)
      return data[0];
  
    size_t length = 0;
    for (size_t i = 0; i < data.size(); ++i)
      length += data[i]->size();
    std::vector<unsigned char> combined_data;
    combined_data.reserve(length);
  
    for (size_t i = 0; i < data.size(); ++i) {
      combined_data.insert(combined_data.end(),
                           data[i]->front(),
                           data[i]->front() + data[i]->size());
    }
    return base::RefCountedBytes::TakeVector(&combined_data);
  }
  
  }  // namespace
  
  SelectionRequestor::SelectionRequestor(XDisplay* x_display,
                                         XID x_window,
                                         PlatformEventDispatcher* dispatcher)
      : x_display_(x_display),
        x_window_(x_window),
        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) {
    base::TimeTicks timeout =
        base::TimeTicks::Now() +
        base::TimeDelta::FromMilliseconds(kRequestTimeoutMs);
    Request request(selection, target, timeout);
    requests_.push_back(&request);
    if (current_request_index_ == (requests_.size() - 1))
      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 = CombineRefCountedMemory(request.out_data);
      if (out_data_items)
        *out_data_items = request.out_data_items;
      if (out_type)
        *out_type = request.out_type;
    }
    return request.success;
  }
  
  void SelectionRequestor::PerformBlockingConvertSelectionWithParameter(
      XAtom selection,
      XAtom target,
      const std::vector<XAtom>& parameter) {
    SetAtomArrayProperty(x_window_, kChromeSelection, "ATOM", parameter);
    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(selection,
                                          *it,
                                          &data,
                                          NULL,
                                          &type) &&
          type == *it) {
        return SelectionData(type, data);
      }
    }
  
    return SelectionData();
  }
  
  void SelectionRequestor::OnSelectionNotify(const XEvent& event) {
    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;
    }
  
    bool success = false;
    if (event_property == x_property_) {
      scoped_refptr<base::RefCountedMemory> out_data;
      success = ui::GetRawBytesOfProperty(x_window_,
                                          x_property_,
                                          &out_data,
                                          &request->out_data_items,
                                          &request->out_type);
      if (success) {
        request->out_data.clear();
        request->out_data.push_back(out_data);
      }
    }
    if (event_property != None)
      XDeleteProperty(x_display_, x_window_, event_property);
  
    if (request->out_type == atom_cache_.GetAtom(kIncr)) {
      request->data_sent_incrementally = true;
      request->out_data.clear();
      request->out_data_items = 0u;
      request->out_type = None;
      request->timeout = base::TimeTicks::Now() +
          base::TimeDelta::FromMilliseconds(kRequestTimeoutMs);
    } else {
      CompleteRequest(current_request_index_, success);
    }
  }
  
  bool SelectionRequestor::CanDispatchPropertyEvent(const XEvent& event) {
    return event.xproperty.window == x_window_ &&
        event.xproperty.atom == x_property_ &&
        event.xproperty.state == PropertyNewValue;
  }
  
  void SelectionRequestor::OnPropertyEvent(const XEvent& event) {
    Request* request = GetCurrentRequest();
    if (!request || !request->data_sent_incrementally)
      return;
  
    scoped_refptr<base::RefCountedMemory> out_data;
    size_t out_data_items = 0u;
    Atom out_type = None;
    bool success = ui::GetRawBytesOfProperty(x_window_,
                                             x_property_,
                                             &out_data,
                                             &out_data_items,
                                             &out_type);
    if (!success) {
      CompleteRequest(current_request_index_, false);
      return;
    }
  
    if (request->out_type != None && request->out_type != out_type) {
      CompleteRequest(current_request_index_, false);
      return;
    }
  
    request->out_data.push_back(out_data);
    request->out_data_items += out_data_items;
    request->out_type = out_type;
  
    // Delete the property to tell the selection owner to send the next chunk.
    XDeleteProperty(x_display_, x_window_, x_property_);
  
    request->timeout = base::TimeTicks::Now() +
        base::TimeDelta::FromMilliseconds(kRequestTimeoutMs);
  
    if (out_data->size() == 0u)
      CompleteRequest(current_request_index_, true);
  }
  
  void SelectionRequestor::AbortStaleRequests() {
    base::TimeTicks now = base::TimeTicks::Now();
    for (size_t i = current_request_index_; i < requests_.size(); ++i) {
      if (requests_[i]->timeout <= now)
        CompleteRequest(i, false);
    }
  }
  
  void SelectionRequestor::CompleteRequest(size_t index, bool success) {
     if (index >= requests_.size())
       return;
  
    Request* request = requests_[index];
    if (request->completed)
      return;
    request->success = success;
    request->completed = true;
  
    if (index == current_request_index_) {
      while (GetCurrentRequest() && GetCurrentRequest()->completed)
        ++current_request_index_;
      ConvertSelectionForCurrentRequest();
    }
  
    if (!request->quit_closure.is_null())
      request->quit_closure.Run();
  }
  
  void SelectionRequestor::ConvertSelectionForCurrentRequest() {
    Request* request = GetCurrentRequest();
    if (request) {
      XConvertSelection(x_display_,
                        request->selection,
                        request->target,
                        x_property_,
                        x_window_,
                        CurrentTime);
    }
  }
  
  void SelectionRequestor::BlockTillSelectionNotifyForRequest(Request* request) {
    if (PlatformEventSource::GetInstance()) {
      if (!abort_timer_.IsRunning()) {
        abort_timer_.Start(FROM_HERE,
                           base::TimeDelta::FromMilliseconds(kTimerPeriodMs),
                           this,
                           &SelectionRequestor::AbortStaleRequests);
      }
  
      base::MessageLoop::ScopedNestableTaskAllower allow_nested(
          base::MessageLoopForUI::current());
      base::RunLoop run_loop;
      request->quit_closure = run_loop.QuitClosure();
      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.
      while (!request->completed &&
             request->timeout > base::TimeTicks::Now()) {
        if (XPending(x_display_)) {
          XEvent event;
          XNextEvent(x_display_, &event);
          dispatcher_->DispatchEvent(&event);
        }
      }
    }
  }
  
  SelectionRequestor::Request* SelectionRequestor::GetCurrentRequest() {
    return current_request_index_ == requests_.size() ?
        NULL : requests_[current_request_index_];
  }
  
  SelectionRequestor::Request::Request(XAtom selection,
                                       XAtom target,
                                       base::TimeTicks timeout)
      : selection(selection),
        target(target),
        data_sent_incrementally(false),
        out_data_items(0u),
        out_type(None),
        success(false),
        timeout(timeout),
        completed(false) {
  }
  
  SelectionRequestor::Request::~Request() {
  }
  
  }  // namespace ui
  