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 (out_data) |
66 if (pending_request.returned_property == property) { | 75 *out_data = request.out_data; |
67 success = ui::GetRawBytesOfProperty(x_window_, | 76 if (out_data_items) |
68 pending_request.returned_property, | 77 *out_data_items = request.out_data_items; |
69 out_data, out_data_items, out_type); | 78 if (out_type) |
70 } | 79 *out_type = request.out_type; |
71 if (pending_request.returned_property != None) | 80 return request.success; |
72 XDeleteProperty(x_display_, x_window_, pending_request.returned_property); | |
73 return success; | |
74 } | 81 } |
75 | 82 |
76 void SelectionRequestor::PerformBlockingConvertSelectionWithParameter( | 83 void SelectionRequestor::PerformBlockingConvertSelectionWithParameter( |
| 84 XAtom selection, |
77 XAtom target, | 85 XAtom target, |
78 const std::vector<XAtom>& parameter) { | 86 const std::vector<XAtom>& parameter) { |
79 SetAtomArrayProperty(x_window_, kChromeSelection, "ATOM", parameter); | 87 SetAtomArrayProperty(x_window_, kChromeSelection, "ATOM", parameter); |
80 PerformBlockingConvertSelection(target, NULL, NULL, NULL); | 88 PerformBlockingConvertSelection(selection, target, NULL, NULL, NULL); |
81 } | 89 } |
82 | 90 |
83 SelectionData SelectionRequestor::RequestAndWaitForTypes( | 91 SelectionData SelectionRequestor::RequestAndWaitForTypes( |
| 92 XAtom selection, |
84 const std::vector<XAtom>& types) { | 93 const std::vector<XAtom>& types) { |
85 for (std::vector<XAtom>::const_iterator it = types.begin(); | 94 for (std::vector<XAtom>::const_iterator it = types.begin(); |
86 it != types.end(); ++it) { | 95 it != types.end(); ++it) { |
87 scoped_refptr<base::RefCountedMemory> data; | 96 scoped_refptr<base::RefCountedMemory> data; |
88 XAtom type = None; | 97 XAtom type = None; |
89 if (PerformBlockingConvertSelection(*it, | 98 if (PerformBlockingConvertSelection(selection, |
| 99 *it, |
90 &data, | 100 &data, |
91 NULL, | 101 NULL, |
92 &type) && | 102 &type) && |
93 type == *it) { | 103 type == *it) { |
94 return SelectionData(type, data); | 104 return SelectionData(type, data); |
95 } | 105 } |
96 } | 106 } |
97 | 107 |
98 return SelectionData(); | 108 return SelectionData(); |
99 } | 109 } |
100 | 110 |
101 void SelectionRequestor::OnSelectionNotify(const XEvent& event) { | 111 void SelectionRequestor::OnSelectionNotify(const XEvent& event) { |
102 // Find the PendingRequest for the corresponding XConvertSelection call. If | 112 Request* request = GetCurrentRequest(); |
103 // there are multiple pending requests on the same target, satisfy them in | 113 XAtom event_property = event.xselection.property; |
104 // FIFO order. | 114 if (!request || |
105 PendingRequest* request_notified = NULL; | 115 request->completed || |
106 if (selection_name_ == event.xselection.selection) { | 116 request->selection != event.xselection.selection || |
107 for (std::list<PendingRequest*>::iterator iter = pending_requests_.begin(); | 117 request->target != event.xselection.target) { |
108 iter != pending_requests_.end(); ++iter) { | 118 // ICCCM requires us to delete the property passed into SelectionNotify. |
109 PendingRequest* request = *iter; | 119 if (event_property != None) |
110 if (request->returned) | 120 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; | 121 return; |
130 } | 122 } |
131 | 123 |
132 request_notified->returned_property = returned_property; | 124 request->success = false; |
133 request_notified->returned = true; | 125 if (event_property == x_property_) { |
| 126 request->success = ui::GetRawBytesOfProperty(x_window_, |
| 127 x_property_, |
| 128 &request->out_data, |
| 129 &request->out_data_items, |
| 130 &request->out_type); |
| 131 } |
| 132 if (event_property != None) |
| 133 XDeleteProperty(x_display_, x_window_, event_property); |
134 | 134 |
135 if (!request_notified->quit_closure.is_null()) | 135 CompleteRequest(current_request_index_); |
136 request_notified->quit_closure.Run(); | |
137 } | 136 } |
138 | 137 |
139 void SelectionRequestor::BlockTillSelectionNotifyForRequest( | 138 void SelectionRequestor::AbortStaleRequests() { |
140 PendingRequest* request) { | 139 base::TimeTicks now = base::TimeTicks::Now(); |
141 pending_requests_.push_back(request); | 140 for (size_t i = 0; i < requests_.size(); ++i) { |
| 141 if (requests_[i]->timeout <= now) |
| 142 CompleteRequest(i); |
| 143 } |
| 144 } |
142 | 145 |
143 const int kMaxWaitTimeForClipboardResponse = 300; | 146 void SelectionRequestor::CompleteRequest(size_t index) { |
| 147 if (index >= requests_.size()) |
| 148 return; |
| 149 |
| 150 Request* request = requests_[index]; |
| 151 if (request->completed) |
| 152 return; |
| 153 request->completed = true; |
| 154 |
| 155 if (index == current_request_index_) { |
| 156 ++current_request_index_; |
| 157 ConvertSelectionForCurrentRequest(); |
| 158 } |
| 159 |
| 160 if (!request->quit_closure.is_null()) |
| 161 request->quit_closure.Run(); |
| 162 } |
| 163 |
| 164 void SelectionRequestor::ConvertSelectionForCurrentRequest() { |
| 165 Request* request = GetCurrentRequest(); |
| 166 if (request) { |
| 167 XConvertSelection(x_display_, |
| 168 request->selection, |
| 169 request->target, |
| 170 x_property_, |
| 171 x_window_, |
| 172 CurrentTime); |
| 173 } |
| 174 } |
| 175 |
| 176 void SelectionRequestor::BlockTillSelectionNotifyForRequest(Request* request) { |
144 if (PlatformEventSource::GetInstance()) { | 177 if (PlatformEventSource::GetInstance()) { |
145 base::MessageLoopForUI* loop = base::MessageLoopForUI::current(); | 178 if (!abort_timer_.IsRunning()) { |
146 base::MessageLoop::ScopedNestableTaskAllower allow_nested(loop); | 179 abort_timer_.Start(FROM_HERE, |
| 180 base::TimeDelta::FromMilliseconds(kRequestTimeoutMs), |
| 181 this, |
| 182 &SelectionRequestor::AbortStaleRequests); |
| 183 } |
| 184 |
| 185 base::MessageLoop::ScopedNestableTaskAllower allow_nested( |
| 186 base::MessageLoopForUI::current()); |
147 base::RunLoop run_loop; | 187 base::RunLoop run_loop; |
| 188 request->quit_closure = run_loop.QuitClosure(); |
| 189 run_loop.Run(); |
148 | 190 |
149 request->quit_closure = run_loop.QuitClosure(); | 191 // We cannot put logic to process the next request here because the RunLoop |
150 loop->PostDelayedTask( | 192 // might be nested. For instance, request 'B' may start a RunLoop while the |
151 FROM_HERE, | 193 // RunLoop for request 'A' is running. It is not possible to end the RunLoop |
152 request->quit_closure, | 194 // for request 'A' without first ending the RunLoop for request 'B'. |
153 base::TimeDelta::FromMilliseconds(kMaxWaitTimeForClipboardResponse)); | |
154 | |
155 run_loop.Run(); | |
156 } else { | 195 } else { |
157 // This occurs if PerformBlockingConvertSelection() is called during | 196 // This occurs if PerformBlockingConvertSelection() is called during |
158 // shutdown and the PlatformEventSource has already been destroyed. | 197 // shutdown and the PlatformEventSource has already been destroyed. |
159 base::TimeTicks start = base::TimeTicks::Now(); | 198 while (!request->completed && |
160 while (!request->returned) { | 199 request->timeout > base::TimeTicks::Now()) { |
161 if (XPending(x_display_)) { | 200 if (XPending(x_display_)) { |
162 XEvent event; | 201 XEvent event; |
163 XNextEvent(x_display_, &event); | 202 XNextEvent(x_display_, &event); |
164 dispatcher_->DispatchEvent(&event); | 203 dispatcher_->DispatchEvent(&event); |
165 } | 204 } |
166 base::TimeDelta wait_time = base::TimeTicks::Now() - start; | |
167 if (wait_time.InMilliseconds() > kMaxWaitTimeForClipboardResponse) | |
168 break; | |
169 } | 205 } |
170 } | 206 } |
171 | |
172 DCHECK(!pending_requests_.empty()); | |
173 DCHECK_EQ(request, pending_requests_.back()); | |
174 pending_requests_.pop_back(); | |
175 } | 207 } |
176 | 208 |
177 SelectionRequestor::PendingRequest::PendingRequest(XAtom target) | 209 SelectionRequestor::Request* SelectionRequestor::GetCurrentRequest() { |
178 : target(target), | 210 return current_request_index_ == requests_.size() ? |
179 returned_property(None), | 211 NULL : requests_[current_request_index_]; |
180 returned(false) { | |
181 } | 212 } |
182 | 213 |
183 SelectionRequestor::PendingRequest::~PendingRequest() { | 214 SelectionRequestor::Request::Request(XAtom selection, |
| 215 XAtom target, |
| 216 base::TimeTicks timeout) |
| 217 : selection(selection), |
| 218 target(target), |
| 219 out_data_items(0u), |
| 220 out_type(None), |
| 221 success(false), |
| 222 timeout(timeout), |
| 223 completed(false) { |
| 224 } |
| 225 |
| 226 SelectionRequestor::Request::~Request() { |
184 } | 227 } |
185 | 228 |
186 } // namespace ui | 229 } // namespace ui |
OLD | NEW |