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"
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"
21 const char kChromeSelection
[] = "CHROME_SELECTION";
22 const char kIncr
[] = "INCR";
24 const char* kAtomsToCache
[] = {
30 // The period of |abort_timer_|. Arbitrary but must be <= than
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)
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(),
55 data
[i
]->front() + data
[i
]->size());
57 return scoped_refptr
<base::RefCountedMemory
>(
58 base::RefCountedBytes::TakeVector(&combined_data
));
63 SelectionRequestor::SelectionRequestor(XDisplay
* x_display
,
65 PlatformEventDispatcher
* dispatcher
)
66 : x_display_(x_display
),
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(
80 scoped_refptr
<base::RefCountedMemory
>* out_data
,
81 size_t* out_data_items
,
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())
104 if (request
.success
) {
106 *out_data
= CombineRefCountedMemory(request
.out_data
);
108 *out_data_items
= request
.out_data_items
;
110 *out_type
= request
.out_type
;
112 return request
.success
;
115 void SelectionRequestor::PerformBlockingConvertSelectionWithParameter(
118 const std::vector
<XAtom
>& parameter
) {
119 SetAtomArrayProperty(x_window_
, kChromeSelection
, "ATOM", parameter
);
120 PerformBlockingConvertSelection(selection
, target
, NULL
, NULL
, NULL
);
123 SelectionData
SelectionRequestor::RequestAndWaitForTypes(
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
;
130 if (PerformBlockingConvertSelection(selection
,
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
;
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
);
156 bool success
= false;
157 if (event_property
== x_property_
) {
158 scoped_refptr
<base::RefCountedMemory
> out_data
;
159 success
= ui::GetRawBytesOfProperty(x_window_
,
162 &request
->out_data_items
,
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
);
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
)
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_
,
204 CompleteRequest(current_request_index_
, false);
208 if (request
->out_type
!= None
&& request
->out_type
!= out_type
) {
209 CompleteRequest(current_request_index_
, false);
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())
239 Request
* request
= requests_
[index
];
240 if (request
->completed
)
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();
258 XConvertSelection(x_display_
,
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
),
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();
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'.
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_
)) {
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
,
307 base::TimeTicks timeout
)
308 : selection(selection
),
310 data_sent_incrementally(false),
318 SelectionRequestor::Request::~Request() {