OLD | NEW |
1 // Copyright (c) 2013 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "ui/base/x/selection_requestor.h" | 5 #include "ui/base/x/selection_requestor.h" |
6 | 6 |
| 7 #include <algorithm> |
7 #include <X11/Xlib.h> | 8 #include <X11/Xlib.h> |
8 | 9 |
9 #include "base/run_loop.h" | 10 #include "base/run_loop.h" |
10 #include "ui/base/x/selection_utils.h" | 11 #include "ui/base/x/selection_utils.h" |
11 #include "ui/base/x/x11_util.h" | 12 #include "ui/base/x/x11_util.h" |
12 #include "ui/events/platform/platform_event_dispatcher.h" | 13 #include "ui/events/platform/platform_event_dispatcher.h" |
13 #include "ui/events/platform/platform_event_source.h" | 14 #include "ui/events/platform/platform_event_source.h" |
14 #include "ui/gfx/x/x11_types.h" | 15 #include "ui/gfx/x/x11_types.h" |
15 | 16 |
16 namespace ui { | 17 namespace ui { |
17 | 18 |
18 namespace { | 19 namespace { |
19 | 20 |
20 const char kChromeSelection[] = "CHROME_SELECTION"; | 21 const char kChromeSelection[] = "CHROME_SELECTION"; |
21 | 22 |
22 const char* kAtomsToCache[] = { | 23 const char* kAtomsToCache[] = { |
23 kChromeSelection, | 24 kChromeSelection, |
24 NULL | 25 NULL |
25 }; | 26 }; |
26 | 27 |
| 28 // The amount of time to wait for a request to complete. |
| 29 const int kRequestTimeoutMs = 300; |
| 30 |
27 } // namespace | 31 } // namespace |
28 | 32 |
29 SelectionRequestor::SelectionRequestor(XDisplay* x_display, | 33 SelectionRequestor::SelectionRequestor(XDisplay* x_display, |
30 XID x_window, | 34 XID x_window, |
31 XAtom selection_name, | |
32 PlatformEventDispatcher* dispatcher) | 35 PlatformEventDispatcher* dispatcher) |
33 : x_display_(x_display), | 36 : x_display_(x_display), |
34 x_window_(x_window), | 37 x_window_(x_window), |
35 selection_name_(selection_name), | 38 x_property_(None), |
36 dispatcher_(dispatcher), | 39 dispatcher_(dispatcher), |
| 40 current_request_index_(0u), |
37 atom_cache_(x_display_, kAtomsToCache) { | 41 atom_cache_(x_display_, kAtomsToCache) { |
| 42 x_property_ = atom_cache_.GetAtom(kChromeSelection); |
38 } | 43 } |
39 | 44 |
40 SelectionRequestor::~SelectionRequestor() {} | 45 SelectionRequestor::~SelectionRequestor() {} |
41 | 46 |
42 bool SelectionRequestor::PerformBlockingConvertSelection( | 47 bool SelectionRequestor::PerformBlockingConvertSelection( |
| 48 XAtom selection, |
43 XAtom target, | 49 XAtom target, |
44 scoped_refptr<base::RefCountedMemory>* out_data, | 50 scoped_refptr<base::RefCountedMemory>* out_data, |
45 size_t* out_data_items, | 51 size_t* out_data_items, |
46 XAtom* out_type) { | 52 XAtom* out_type) { |
47 // The name of the property that we are either: | 53 base::TimeTicks timeout = |
48 // - Passing as a parameter with the XConvertSelection() request. | 54 base::TimeTicks::Now() + |
49 // OR | 55 base::TimeDelta::FromMilliseconds(kRequestTimeoutMs); |
50 // - Asking the selection owner to set on |x_window_|. | 56 Request request(selection, target, timeout); |
51 XAtom property = atom_cache_.GetAtom(kChromeSelection); | 57 requests_.push_back(&request); |
| 58 if (requests_.size() == 1u) |
| 59 ConvertSelectionForCurrentRequest(); |
| 60 BlockTillSelectionNotifyForRequest(&request); |
52 | 61 |
53 XConvertSelection(x_display_, | 62 std::vector<Request*>::iterator request_it = std::find( |
54 selection_name_, | 63 requests_.begin(), requests_.end(), &request); |
55 target, | 64 CHECK(request_it != requests_.end()); |
56 property, | 65 if (static_cast<int>(current_request_index_) > |
57 x_window_, | 66 request_it - requests_.begin()) { |
58 CurrentTime); | 67 --current_request_index_; |
| 68 } |
| 69 requests_.erase(request_it); |
59 | 70 |
60 // Now that we've thrown our message off to the X11 server, we block waiting | 71 if (requests_.empty()) |
61 // for a response. | 72 abort_timer_.Stop(); |
62 PendingRequest pending_request(target); | |
63 BlockTillSelectionNotifyForRequest(&pending_request); | |
64 | 73 |
65 bool success = false; | 74 if (request.success) { |
66 if (pending_request.returned_property == property) { | 75 if (out_data) |
67 success = ui::GetRawBytesOfProperty(x_window_, | 76 *out_data = request.out_data; |
68 pending_request.returned_property, | 77 if (out_data_items) |
69 out_data, out_data_items, out_type); | 78 *out_data_items = request.out_data_items; |
| 79 if (out_type) |
| 80 *out_type = request.out_type; |
70 } | 81 } |
71 if (pending_request.returned_property != None) | 82 return request.success; |
72 XDeleteProperty(x_display_, x_window_, pending_request.returned_property); | |
73 return success; | |
74 } | 83 } |
75 | 84 |
76 void SelectionRequestor::PerformBlockingConvertSelectionWithParameter( | 85 void SelectionRequestor::PerformBlockingConvertSelectionWithParameter( |
| 86 XAtom selection, |
77 XAtom target, | 87 XAtom target, |
78 const std::vector<XAtom>& parameter) { | 88 const std::vector<XAtom>& parameter) { |
79 SetAtomArrayProperty(x_window_, kChromeSelection, "ATOM", parameter); | 89 SetAtomArrayProperty(x_window_, kChromeSelection, "ATOM", parameter); |
80 PerformBlockingConvertSelection(target, NULL, NULL, NULL); | 90 PerformBlockingConvertSelection(selection, target, NULL, NULL, NULL); |
81 } | 91 } |
82 | 92 |
83 SelectionData SelectionRequestor::RequestAndWaitForTypes( | 93 SelectionData SelectionRequestor::RequestAndWaitForTypes( |
| 94 XAtom selection, |
84 const std::vector<XAtom>& types) { | 95 const std::vector<XAtom>& types) { |
85 for (std::vector<XAtom>::const_iterator it = types.begin(); | 96 for (std::vector<XAtom>::const_iterator it = types.begin(); |
86 it != types.end(); ++it) { | 97 it != types.end(); ++it) { |
87 scoped_refptr<base::RefCountedMemory> data; | 98 scoped_refptr<base::RefCountedMemory> data; |
88 XAtom type = None; | 99 XAtom type = None; |
89 if (PerformBlockingConvertSelection(*it, | 100 if (PerformBlockingConvertSelection(selection, |
| 101 *it, |
90 &data, | 102 &data, |
91 NULL, | 103 NULL, |
92 &type) && | 104 &type) && |
93 type == *it) { | 105 type == *it) { |
94 return SelectionData(type, data); | 106 return SelectionData(type, data); |
95 } | 107 } |
96 } | 108 } |
97 | 109 |
98 return SelectionData(); | 110 return SelectionData(); |
99 } | 111 } |
100 | 112 |
101 void SelectionRequestor::OnSelectionNotify(const XEvent& event) { | 113 void SelectionRequestor::OnSelectionNotify(const XEvent& event) { |
102 // Find the PendingRequest for the corresponding XConvertSelection call. If | 114 Request* request = GetCurrentRequest(); |
103 // there are multiple pending requests on the same target, satisfy them in | 115 XAtom event_property = event.xselection.property; |
104 // FIFO order. | 116 if (!request || |
105 PendingRequest* request_notified = NULL; | 117 request->completed || |
106 if (selection_name_ == event.xselection.selection) { | 118 request->selection != event.xselection.selection || |
107 for (std::list<PendingRequest*>::iterator iter = pending_requests_.begin(); | 119 request->target != event.xselection.target) { |
108 iter != pending_requests_.end(); ++iter) { | 120 // ICCCM requires us to delete the property passed into SelectionNotify. |
109 PendingRequest* request = *iter; | 121 if (event_property != None) |
110 if (request->returned) | 122 XDeleteProperty(x_display_, x_window_, event_property); |
111 continue; | |
112 if (request->target != event.xselection.target) | |
113 continue; | |
114 request_notified = request; | |
115 break; | |
116 } | |
117 } | |
118 | |
119 // This event doesn't correspond to any XConvertSelection calls that we | |
120 // issued in PerformBlockingConvertSelection. This shouldn't happen, but any | |
121 // client can send any message, so it can happen. | |
122 XAtom returned_property = event.xselection.property; | |
123 if (!request_notified) { | |
124 // ICCCM requires us to delete the property passed into SelectionNotify. If | |
125 // |request_notified| is true, the property will be deleted when the run | |
126 // loop has quit. | |
127 if (returned_property != None) | |
128 XDeleteProperty(x_display_, x_window_, returned_property); | |
129 return; | 123 return; |
130 } | 124 } |
131 | 125 |
132 request_notified->returned_property = returned_property; | 126 request->success = false; |
133 request_notified->returned = true; | 127 if (event_property == x_property_) { |
| 128 request->success = ui::GetRawBytesOfProperty(x_window_, |
| 129 x_property_, |
| 130 &request->out_data, |
| 131 &request->out_data_items, |
| 132 &request->out_type); |
| 133 } |
| 134 if (event_property != None) |
| 135 XDeleteProperty(x_display_, x_window_, event_property); |
134 | 136 |
135 if (!request_notified->quit_closure.is_null()) | 137 CompleteRequest(current_request_index_); |
136 request_notified->quit_closure.Run(); | |
137 } | 138 } |
138 | 139 |
139 void SelectionRequestor::BlockTillSelectionNotifyForRequest( | 140 void SelectionRequestor::AbortStaleRequests() { |
140 PendingRequest* request) { | 141 base::TimeTicks now = base::TimeTicks::Now(); |
141 pending_requests_.push_back(request); | 142 for (size_t i = current_request_index_; |
| 143 i < requests_.size() && requests_[i]->timeout <= now; |
| 144 ++i) { |
| 145 CompleteRequest(i); |
| 146 } |
| 147 } |
142 | 148 |
143 const int kMaxWaitTimeForClipboardResponse = 300; | 149 void SelectionRequestor::CompleteRequest(size_t index) { |
| 150 if (index >= requests_.size()) |
| 151 return; |
| 152 |
| 153 Request* request = requests_[index]; |
| 154 if (request->completed) |
| 155 return; |
| 156 request->completed = true; |
| 157 |
| 158 if (index == current_request_index_) { |
| 159 ++current_request_index_; |
| 160 ConvertSelectionForCurrentRequest(); |
| 161 } |
| 162 |
| 163 if (!request->quit_closure.is_null()) |
| 164 request->quit_closure.Run(); |
| 165 } |
| 166 |
| 167 void SelectionRequestor::ConvertSelectionForCurrentRequest() { |
| 168 Request* request = GetCurrentRequest(); |
| 169 if (request) { |
| 170 XConvertSelection(x_display_, |
| 171 request->selection, |
| 172 request->target, |
| 173 x_property_, |
| 174 x_window_, |
| 175 CurrentTime); |
| 176 } |
| 177 } |
| 178 |
| 179 void SelectionRequestor::BlockTillSelectionNotifyForRequest(Request* request) { |
144 if (PlatformEventSource::GetInstance()) { | 180 if (PlatformEventSource::GetInstance()) { |
145 base::MessageLoopForUI* loop = base::MessageLoopForUI::current(); | 181 if (!abort_timer_.IsRunning()) { |
146 base::MessageLoop::ScopedNestableTaskAllower allow_nested(loop); | 182 abort_timer_.Start(FROM_HERE, |
| 183 base::TimeDelta::FromMilliseconds(kRequestTimeoutMs), |
| 184 this, |
| 185 &SelectionRequestor::AbortStaleRequests); |
| 186 } |
| 187 |
| 188 base::MessageLoop::ScopedNestableTaskAllower allow_nested( |
| 189 base::MessageLoopForUI::current()); |
147 base::RunLoop run_loop; | 190 base::RunLoop run_loop; |
| 191 request->quit_closure = run_loop.QuitClosure(); |
| 192 run_loop.Run(); |
148 | 193 |
149 request->quit_closure = run_loop.QuitClosure(); | 194 // We cannot put logic to process the next request here because the RunLoop |
150 loop->PostDelayedTask( | 195 // might be nested. For instance, request 'B' may start a RunLoop while the |
151 FROM_HERE, | 196 // RunLoop for request 'A' is running. It is not possible to end the RunLoop |
152 request->quit_closure, | 197 // for request 'A' without first ending the RunLoop for request 'B'. |
153 base::TimeDelta::FromMilliseconds(kMaxWaitTimeForClipboardResponse)); | |
154 | |
155 run_loop.Run(); | |
156 } else { | 198 } else { |
157 // This occurs if PerformBlockingConvertSelection() is called during | 199 // This occurs if PerformBlockingConvertSelection() is called during |
158 // shutdown and the PlatformEventSource has already been destroyed. | 200 // shutdown and the PlatformEventSource has already been destroyed. |
159 base::TimeTicks start = base::TimeTicks::Now(); | 201 while (!request->completed && |
160 while (!request->returned) { | 202 request->timeout > base::TimeTicks::Now()) { |
161 if (XPending(x_display_)) { | 203 if (XPending(x_display_)) { |
162 XEvent event; | 204 XEvent event; |
163 XNextEvent(x_display_, &event); | 205 XNextEvent(x_display_, &event); |
164 dispatcher_->DispatchEvent(&event); | 206 dispatcher_->DispatchEvent(&event); |
165 } | 207 } |
166 base::TimeDelta wait_time = base::TimeTicks::Now() - start; | |
167 if (wait_time.InMilliseconds() > kMaxWaitTimeForClipboardResponse) | |
168 break; | |
169 } | 208 } |
170 } | 209 } |
171 | |
172 DCHECK(!pending_requests_.empty()); | |
173 DCHECK_EQ(request, pending_requests_.back()); | |
174 pending_requests_.pop_back(); | |
175 } | 210 } |
176 | 211 |
177 SelectionRequestor::PendingRequest::PendingRequest(XAtom target) | 212 SelectionRequestor::Request* SelectionRequestor::GetCurrentRequest() { |
178 : target(target), | 213 return current_request_index_ == requests_.size() ? |
179 returned_property(None), | 214 NULL : requests_[current_request_index_]; |
180 returned(false) { | |
181 } | 215 } |
182 | 216 |
183 SelectionRequestor::PendingRequest::~PendingRequest() { | 217 SelectionRequestor::Request::Request(XAtom selection, |
| 218 XAtom target, |
| 219 base::TimeTicks timeout) |
| 220 : selection(selection), |
| 221 target(target), |
| 222 out_data_items(0u), |
| 223 out_type(None), |
| 224 success(false), |
| 225 timeout(timeout), |
| 226 completed(false) { |
| 227 } |
| 228 |
| 229 SelectionRequestor::Request::~Request() { |
184 } | 230 } |
185 | 231 |
186 } // namespace ui | 232 } // namespace ui |
OLD | NEW |