Upstream version 9.38.198.0
[platform/framework/web/crosswalk.git] / src / ui / base / x / selection_requestor.cc
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