Upstream version 9.38.198.0
[platform/framework/web/crosswalk.git] / src / ui / base / x / selection_owner.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_owner.h"
6
7 #include <algorithm>
8 #include <X11/Xlib.h>
9 #include <X11/Xatom.h>
10
11 #include "base/logging.h"
12 #include "ui/base/x/selection_utils.h"
13 #include "ui/base/x/x11_foreign_window_manager.h"
14 #include "ui/base/x/x11_util.h"
15
16 namespace ui {
17
18 namespace {
19
20 const char kAtomPair[] = "ATOM_PAIR";
21 const char kIncr[] = "INCR";
22 const char kMultiple[] = "MULTIPLE";
23 const char kSaveTargets[] = "SAVE_TARGETS";
24 const char kTargets[] = "TARGETS";
25
26 const char* kAtomsToCache[] = {
27   kAtomPair,
28   kIncr,
29   kMultiple,
30   kSaveTargets,
31   kTargets,
32   NULL
33 };
34
35 // The period of |incremental_transfer_abort_timer_|. Arbitrary but must be <=
36 // than kIncrementalTransferTimeoutMs.
37 const int kTimerPeriodMs = 1000;
38
39 // The amount of time to wait for the selection requestor to process the data
40 // sent by the selection owner before aborting an incremental data transfer.
41 const int kIncrementalTransferTimeoutMs = 10000;
42
43 COMPILE_ASSERT(kTimerPeriodMs <= kIncrementalTransferTimeoutMs,
44                timer_period_must_be_less_or_equal_to_transfer_timeout);
45
46 // Returns a conservative max size of the data we can pass into
47 // XChangeProperty(). Copied from GTK.
48 size_t GetMaxRequestSize(XDisplay* display) {
49   long extended_max_size = XExtendedMaxRequestSize(display);
50   long max_size =
51       (extended_max_size ? extended_max_size : XMaxRequestSize(display)) - 100;
52   return std::min(static_cast<long>(0x40000),
53                   std::max(static_cast<long>(0), max_size));
54 }
55
56 // Gets the value of an atom pair array property. On success, true is returned
57 // and the value is stored in |value|.
58 bool GetAtomPairArrayProperty(XID window,
59                               XAtom property,
60                               std::vector<std::pair<XAtom,XAtom> >* value) {
61   XAtom type = None;
62   int format = 0;  // size in bits of each item in 'property'
63   unsigned long num_items = 0;
64   unsigned char* properties = NULL;
65   unsigned long remaining_bytes = 0;
66
67   int result = XGetWindowProperty(gfx::GetXDisplay(),
68                                   window,
69                                   property,
70                                   0,          // offset into property data to
71                                               // read
72                                   (~0L),      // entire array
73                                   False,      // deleted
74                                   AnyPropertyType,
75                                   &type,
76                                   &format,
77                                   &num_items,
78                                   &remaining_bytes,
79                                   &properties);
80
81   if (result != Success)
82     return false;
83
84   // GTK does not require |type| to be kAtomPair.
85   if (format != 32 || num_items % 2 != 0) {
86     XFree(properties);
87     return false;
88   }
89
90   XAtom* atom_properties = reinterpret_cast<XAtom*>(properties);
91   value->clear();
92   for (size_t i = 0; i < num_items; i+=2)
93     value->push_back(std::make_pair(atom_properties[i], atom_properties[i+1]));
94   XFree(properties);
95   return true;
96 }
97
98 }  // namespace
99
100 SelectionOwner::SelectionOwner(XDisplay* x_display,
101                                XID x_window,
102                                XAtom selection_name)
103     : x_display_(x_display),
104       x_window_(x_window),
105       selection_name_(selection_name),
106       max_request_size_(GetMaxRequestSize(x_display)),
107       atom_cache_(x_display_, kAtomsToCache) {
108 }
109
110 SelectionOwner::~SelectionOwner() {
111   // If we are the selection owner, we need to release the selection so we
112   // don't receive further events. However, we don't call ClearSelectionOwner()
113   // because we don't want to do this indiscriminately.
114   if (XGetSelectionOwner(x_display_, selection_name_) == x_window_)
115     XSetSelectionOwner(x_display_, selection_name_, None, CurrentTime);
116 }
117
118 void SelectionOwner::RetrieveTargets(std::vector<XAtom>* targets) {
119   for (SelectionFormatMap::const_iterator it = format_map_.begin();
120        it != format_map_.end(); ++it) {
121     targets->push_back(it->first);
122   }
123 }
124
125 void SelectionOwner::TakeOwnershipOfSelection(
126     const SelectionFormatMap& data) {
127   XSetSelectionOwner(x_display_, selection_name_, x_window_, CurrentTime);
128
129   if (XGetSelectionOwner(x_display_, selection_name_) == x_window_) {
130     // The X server agrees that we are the selection owner. Commit our data.
131     format_map_ = data;
132   }
133 }
134
135 void SelectionOwner::ClearSelectionOwner() {
136   XSetSelectionOwner(x_display_, selection_name_, None, CurrentTime);
137   format_map_ = SelectionFormatMap();
138 }
139
140 void SelectionOwner::OnSelectionRequest(const XEvent& event) {
141   XID requestor = event.xselectionrequest.requestor;
142   XAtom requested_target = event.xselectionrequest.target;
143   XAtom requested_property = event.xselectionrequest.property;
144
145   // Incrementally build our selection. By default this is a refusal, and we'll
146   // override the parts indicating success in the different cases.
147   XEvent reply;
148   reply.xselection.type = SelectionNotify;
149   reply.xselection.requestor = requestor;
150   reply.xselection.selection = event.xselectionrequest.selection;
151   reply.xselection.target = requested_target;
152   reply.xselection.property = None;  // Indicates failure
153   reply.xselection.time = event.xselectionrequest.time;
154
155   if (requested_target == atom_cache_.GetAtom(kMultiple)) {
156     // The contents of |requested_property| should be a list of
157     // <target,property> pairs.
158     std::vector<std::pair<XAtom,XAtom> > conversions;
159     if (GetAtomPairArrayProperty(requestor,
160                                  requested_property,
161                                  &conversions)) {
162       std::vector<XAtom> conversion_results;
163       for (size_t i = 0; i < conversions.size(); ++i) {
164         bool conversion_successful = ProcessTarget(conversions[i].first,
165                                                    requestor,
166                                                    conversions[i].second);
167         conversion_results.push_back(conversions[i].first);
168         conversion_results.push_back(
169             conversion_successful ? conversions[i].second : None);
170       }
171
172       // Set the property to indicate which conversions succeeded. This matches
173       // what GTK does.
174       XChangeProperty(
175           x_display_,
176           requestor,
177           requested_property,
178           atom_cache_.GetAtom(kAtomPair),
179           32,
180           PropModeReplace,
181           reinterpret_cast<const unsigned char*>(&conversion_results.front()),
182           conversion_results.size());
183
184       reply.xselection.property = requested_property;
185     }
186   } else {
187     if (ProcessTarget(requested_target, requestor, requested_property))
188       reply.xselection.property = requested_property;
189   }
190
191   // Send off the reply.
192   XSendEvent(x_display_, requestor, False, 0, &reply);
193 }
194
195 void SelectionOwner::OnSelectionClear(const XEvent& event) {
196   DLOG(ERROR) << "SelectionClear";
197
198   // TODO(erg): If we receive a SelectionClear event while we're handling data,
199   // we need to delay clearing.
200 }
201
202 bool SelectionOwner::CanDispatchPropertyEvent(const XEvent& event) {
203   return event.xproperty.state == PropertyDelete &&
204          FindIncrementalTransferForEvent(event) != incremental_transfers_.end();
205 }
206
207 void SelectionOwner::OnPropertyEvent(const XEvent& event) {
208   std::vector<IncrementalTransfer>::iterator it =
209       FindIncrementalTransferForEvent(event);
210   if (it == incremental_transfers_.end())
211     return;
212
213   ProcessIncrementalTransfer(&(*it));
214   if (!it->data.get())
215     CompleteIncrementalTransfer(it);
216 }
217
218 bool SelectionOwner::ProcessTarget(XAtom target,
219                                    XID requestor,
220                                    XAtom property) {
221   XAtom multiple_atom = atom_cache_.GetAtom(kMultiple);
222   XAtom save_targets_atom = atom_cache_.GetAtom(kSaveTargets);
223   XAtom targets_atom = atom_cache_.GetAtom(kTargets);
224
225   if (target == multiple_atom || target == save_targets_atom)
226     return false;
227
228   if (target == targets_atom) {
229     // We have been asked for TARGETS. Send an atom array back with the data
230     // types we support.
231     std::vector<XAtom> targets;
232     targets.push_back(targets_atom);
233     targets.push_back(save_targets_atom);
234     targets.push_back(multiple_atom);
235     RetrieveTargets(&targets);
236
237     XChangeProperty(x_display_, requestor, property, XA_ATOM, 32,
238                     PropModeReplace,
239                     reinterpret_cast<unsigned char*>(&targets.front()),
240                     targets.size());
241     return true;
242   } else {
243     // Try to find the data type in map.
244     SelectionFormatMap::const_iterator it = format_map_.find(target);
245     if (it != format_map_.end()) {
246       if (it->second->size() > max_request_size_) {
247         // We must send the data back in several chunks due to a limitation in
248         // the size of X requests. Notify the selection requestor that the data
249         // will be sent incrementally by returning data of type "INCR".
250         int length = it->second->size();
251         XChangeProperty(x_display_,
252                         requestor,
253                         property,
254                         atom_cache_.GetAtom(kIncr),
255                         32,
256                         PropModeReplace,
257                         reinterpret_cast<unsigned char*>(&length),
258                         1);
259
260         // Wait for the selection requestor to indicate that it has processed
261         // the selection result before sending the first chunk of data. The
262         // selection requestor indicates this by deleting |property|.
263         base::TimeTicks timeout =
264             base::TimeTicks::Now() +
265             base::TimeDelta::FromMilliseconds(kIncrementalTransferTimeoutMs);
266         int foreign_window_manager_id =
267             ui::XForeignWindowManager::GetInstance()->RequestEvents(
268                 requestor, PropertyChangeMask);
269         incremental_transfers_.push_back(
270             IncrementalTransfer(requestor,
271                                 target,
272                                 property,
273                                 it->second,
274                                 0,
275                                 timeout,
276                                 foreign_window_manager_id));
277
278         // Start a timer to abort the data transfer in case that the selection
279         // requestor does not support the INCR property or gets destroyed during
280         // the data transfer.
281         if (!incremental_transfer_abort_timer_.IsRunning()) {
282           incremental_transfer_abort_timer_.Start(
283               FROM_HERE,
284               base::TimeDelta::FromMilliseconds(kTimerPeriodMs),
285               this,
286               &SelectionOwner::AbortStaleIncrementalTransfers);
287         }
288       } else {
289         XChangeProperty(
290             x_display_,
291             requestor,
292             property,
293             target,
294             8,
295             PropModeReplace,
296             const_cast<unsigned char*>(it->second->front()),
297             it->second->size());
298       }
299       return true;
300     }
301     // I would put error logging here, but GTK ignores TARGETS and spams us
302     // looking for its own internal types.
303   }
304   return false;
305 }
306
307 void SelectionOwner::ProcessIncrementalTransfer(IncrementalTransfer* transfer) {
308   size_t remaining = transfer->data->size() - transfer->offset;
309   size_t chunk_length = std::min(remaining, max_request_size_);
310   XChangeProperty(
311       x_display_,
312       transfer->window,
313       transfer->property,
314       transfer->target,
315       8,
316       PropModeReplace,
317       const_cast<unsigned char*>(transfer->data->front() + transfer->offset),
318       chunk_length);
319   transfer->offset += chunk_length;
320   transfer->timeout = base::TimeTicks::Now() +
321       base::TimeDelta::FromMilliseconds(kIncrementalTransferTimeoutMs);
322
323   // When offset == data->size(), we still need to transfer a zero-sized chunk
324   // to notify the selection requestor that the transfer is complete. Clear
325   // transfer->data once the zero-sized chunk is sent to indicate that state
326   // related to this data transfer can be cleared.
327   if (chunk_length == 0)
328     transfer->data = NULL;
329 }
330
331 void SelectionOwner::AbortStaleIncrementalTransfers() {
332   base::TimeTicks now = base::TimeTicks::Now();
333   for (int i = static_cast<int>(incremental_transfers_.size()) - 1;
334        i >= 0; --i) {
335     if (incremental_transfers_[i].timeout <= now)
336       CompleteIncrementalTransfer(incremental_transfers_.begin() + i);
337   }
338 }
339
340 void SelectionOwner::CompleteIncrementalTransfer(
341     std::vector<IncrementalTransfer>::iterator it) {
342   ui::XForeignWindowManager::GetInstance()->CancelRequest(
343       it->foreign_window_manager_id);
344   incremental_transfers_.erase(it);
345
346   if (incremental_transfers_.empty())
347     incremental_transfer_abort_timer_.Stop();
348 }
349
350 std::vector<SelectionOwner::IncrementalTransfer>::iterator
351     SelectionOwner::FindIncrementalTransferForEvent(const XEvent& event) {
352   for (std::vector<IncrementalTransfer>::iterator it =
353            incremental_transfers_.begin();
354        it != incremental_transfers_.end();
355        ++it) {
356     if (it->window == event.xproperty.window &&
357         it->property == event.xproperty.atom) {
358       return it;
359     }
360   }
361   return incremental_transfers_.end();
362 }
363
364 SelectionOwner::IncrementalTransfer::IncrementalTransfer(
365     XID window,
366     XAtom target,
367     XAtom property,
368     const scoped_refptr<base::RefCountedMemory>& data,
369     int offset,
370     base::TimeTicks timeout,
371     int foreign_window_manager_id)
372     : window(window),
373       target(target),
374       property(property),
375       data(data),
376       offset(offset),
377       timeout(timeout),
378       foreign_window_manager_id(foreign_window_manager_id) {
379 }
380
381 SelectionOwner::IncrementalTransfer::~IncrementalTransfer() {
382 }
383
384 }  // namespace ui