Add ICU message format support
[chromium-blink-merge.git] / ui / base / x / selection_requestor.cc
blob977de29f4b8298703b9a7409f75f27a0712e34d6
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.
5 #include "ui/base/x/selection_requestor.h"
7 #include <algorithm>
8 #include <X11/Xlib.h>
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"
17 namespace ui {
19 namespace {
21 const char kChromeSelection[] = "CHROME_SELECTION";
22 const char kIncr[] = "INCR";
24 const char* kAtomsToCache[] = {
25 kChromeSelection,
26 kIncr,
27 NULL
30 // The period of |abort_timer_|. Arbitrary but must be <= than
31 // kRequestTimeoutMs.
32 const int kTimerPeriodMs = 100;
34 // The amount of time to wait for a request to complete before aborting it.
35 const int kRequestTimeoutMs = 10000;
37 static_assert(kTimerPeriodMs <= kRequestTimeoutMs,
38 "timer period must be <= request timeout");
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];
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);
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());
57 return scoped_refptr<base::RefCountedMemory>(
58 base::RefCountedBytes::TakeVector(&combined_data));
61 } // namespace
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);
75 SelectionRequestor::~SelectionRequestor() {}
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);
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_;
99 requests_.erase(request_it);
101 if (requests_.empty())
102 abort_timer_.Stop();
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;
112 return request.success;
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);
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);
140 return SelectionData();
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;
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);
169 if (event_property != None)
170 XDeleteProperty(x_display_, x_window_, event_property);
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);
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;
190 void SelectionRequestor::OnPropertyEvent(const XEvent& event) {
191 Request* request = GetCurrentRequest();
192 if (!request || !request->data_sent_incrementally)
193 return;
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;
208 if (request->out_type != None && request->out_type != out_type) {
209 CompleteRequest(current_request_index_, false);
210 return;
213 request->out_data.push_back(out_data);
214 request->out_data_items += out_data_items;
215 request->out_type = out_type;
217 // Delete the property to tell the selection owner to send the next chunk.
218 XDeleteProperty(x_display_, x_window_, x_property_);
220 request->timeout = base::TimeTicks::Now() +
221 base::TimeDelta::FromMilliseconds(kRequestTimeoutMs);
223 if (out_data->size() == 0u)
224 CompleteRequest(current_request_index_, true);
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);
235 void SelectionRequestor::CompleteRequest(size_t index, bool success) {
236 if (index >= requests_.size())
237 return;
239 Request* request = requests_[index];
240 if (request->completed)
241 return;
242 request->success = success;
243 request->completed = true;
245 if (index == current_request_index_) {
246 while (GetCurrentRequest() && GetCurrentRequest()->completed)
247 ++current_request_index_;
248 ConvertSelectionForCurrentRequest();
251 if (!request->quit_closure.is_null())
252 request->quit_closure.Run();
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);
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);
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();
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);
300 SelectionRequestor::Request* SelectionRequestor::GetCurrentRequest() {
301 return current_request_index_ == requests_.size() ?
302 NULL : requests_[current_request_index_];
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) {
318 SelectionRequestor::Request::~Request() {
321 } // namespace ui