blob: 48a408022e2c54aabb900257f9bf45d30c823a5a [file] [log] [blame]
// 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 "base/run_loop.h"
#include "ui/base/x/selection_owner.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.h"
#include "ui/gfx/x/x11_atom_cache.h"
#include "ui/gfx/x/x11_types.h"
namespace ui {
namespace {
const char kChromeSelection[] = "CHROME_SELECTION";
// The period of |abort_timer_|. Arbitrary but must be <= than
// kRequestTimeoutMs.
const int KSelectionRequestorTimerPeriodMs = 100;
// The amount of time to wait for a request to complete before aborting it.
const int kRequestTimeoutMs = 10000;
static_assert(KSelectionRequestorTimerPeriodMs <= 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 combined_length = 0;
for (const auto& datum : data)
combined_length += datum->size();
std::vector<unsigned char> combined_data;
combined_data.reserve(combined_length);
for (const auto& datum : data) {
combined_data.insert(combined_data.end(), datum->front(),
datum->front() + datum->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_(x11::None),
dispatcher_(dispatcher),
current_request_index_(0u) {
x_property_ = gfx::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);
auto 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, nullptr, nullptr, nullptr);
}
SelectionData SelectionRequestor::RequestAndWaitForTypes(
XAtom selection,
const std::vector<XAtom>& types) {
for (const XAtom& item : types) {
scoped_refptr<base::RefCountedMemory> data;
XAtom type = x11::None;
if (PerformBlockingConvertSelection(selection, item, &data, nullptr,
&type) && type == item) {
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 != x11::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 != x11::None)
XDeleteProperty(x_display_, x_window_, event_property);
if (request->out_type == gfx::GetAtom(kIncr)) {
request->data_sent_incrementally = true;
request->out_data.clear();
request->out_data_items = 0u;
request->out_type = x11::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 = x11::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 != x11::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_, x11::CurrentTime);
}
}
void SelectionRequestor::BlockTillSelectionNotifyForRequest(Request* request) {
if (PlatformEventSource::GetInstance()) {
if (!abort_timer_.IsRunning()) {
abort_timer_.Start(
FROM_HERE,
base::TimeDelta::FromMilliseconds(KSelectionRequestorTimerPeriodMs),
this, &SelectionRequestor::AbortStaleRequests);
}
base::RunLoop run_loop(base::RunLoop::Type::kNestableTasksAllowed);
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()
? nullptr
: 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(x11::None),
success(false),
timeout(timeout),
completed(false) {}
SelectionRequestor::Request::~Request() {
}
} // namespace ui