Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(7)

Side by Side Diff: chrome/browser/ui/website_settings/permission_bubble_manager.cc

Issue 1610753002: Fixes Permission Bubbles never responding to duplicate requests (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Address nits & rebase onto https://codereview.chromium.org/1637913002 Created 4 years, 11 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « chrome/browser/ui/website_settings/permission_bubble_manager.h ('k') | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 // Copyright 2014 The Chromium Authors. All rights reserved. 1 // Copyright 2014 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 "chrome/browser/ui/website_settings/permission_bubble_manager.h" 5 #include "chrome/browser/ui/website_settings/permission_bubble_manager.h"
6 6
7 #include "base/command_line.h" 7 #include "base/command_line.h"
8 #include "base/metrics/user_metrics_action.h" 8 #include "base/metrics/user_metrics_action.h"
9 #include "build/build_config.h" 9 #include "build/build_config.h"
10 #include "chrome/browser/ui/website_settings/permission_bubble_request.h" 10 #include "chrome/browser/ui/website_settings/permission_bubble_request.h"
(...skipping 35 matching lines...) Expand 10 before | Expand all | Expand 10 after
46 void RequestFinished() override { delete this; } 46 void RequestFinished() override { delete this; }
47 47
48 private: 48 private:
49 int icon_; 49 int icon_;
50 base::string16 message_text_; 50 base::string16 message_text_;
51 base::string16 message_fragment_; 51 base::string16 message_fragment_;
52 bool user_gesture_; 52 bool user_gesture_;
53 GURL origin_; 53 GURL origin_;
54 }; 54 };
55 55
56 bool IsMessageTextEqual(PermissionBubbleRequest* a,
57 PermissionBubbleRequest* b) {
58 if (a == b)
59 return true;
60 if (a->GetMessageTextFragment() == b->GetMessageTextFragment() &&
61 a->GetOrigin() == b->GetOrigin()) {
62 return true;
63 }
64 return false;
65 }
66
56 } // namespace 67 } // namespace
57 68
58 // PermissionBubbleManager::Observer ------------------------------------------- 69 // PermissionBubbleManager::Observer -------------------------------------------
59 70
60 PermissionBubbleManager::Observer::~Observer() { 71 PermissionBubbleManager::Observer::~Observer() {
61 } 72 }
62 73
63 void PermissionBubbleManager::Observer::OnBubbleAdded() { 74 void PermissionBubbleManager::Observer::OnBubbleAdded() {
64 } 75 }
65 76
(...skipping 10 matching lines...) Expand all
76 view_(nullptr), 87 view_(nullptr),
77 main_frame_has_fully_loaded_(false), 88 main_frame_has_fully_loaded_(false),
78 auto_response_for_test_(NONE), 89 auto_response_for_test_(NONE),
79 weak_factory_(this) { 90 weak_factory_(this) {
80 } 91 }
81 92
82 PermissionBubbleManager::~PermissionBubbleManager() { 93 PermissionBubbleManager::~PermissionBubbleManager() {
83 if (view_ != NULL) 94 if (view_ != NULL)
84 view_->SetDelegate(NULL); 95 view_->SetDelegate(NULL);
85 96
86 std::vector<PermissionBubbleRequest*>::iterator requests_iter; 97 for (PermissionBubbleRequest* request : requests_)
87 for (requests_iter = requests_.begin(); 98 request->RequestFinished();
88 requests_iter != requests_.end(); 99 for (PermissionBubbleRequest* request : queued_requests_)
89 requests_iter++) { 100 request->RequestFinished();
90 (*requests_iter)->RequestFinished(); 101 for (PermissionBubbleRequest* request : queued_frame_requests_)
91 } 102 request->RequestFinished();
92 for (requests_iter = queued_requests_.begin(); 103 for (const auto& entry : duplicate_requests_)
93 requests_iter != queued_requests_.end(); 104 entry.second->RequestFinished();
94 requests_iter++) {
95 (*requests_iter)->RequestFinished();
96 }
97 } 105 }
98 106
99 void PermissionBubbleManager::AddRequest(PermissionBubbleRequest* request) { 107 void PermissionBubbleManager::AddRequest(PermissionBubbleRequest* request) {
100 content::RecordAction(base::UserMetricsAction("PermissionBubbleRequest")); 108 content::RecordAction(base::UserMetricsAction("PermissionBubbleRequest"));
101 // TODO(gbillock): is there a race between an early request on a 109 // TODO(gbillock): is there a race between an early request on a
102 // newly-navigated page and the to-be-cleaned-up requests on the previous 110 // newly-navigated page and the to-be-cleaned-up requests on the previous
103 // page? We should maybe listen to DidStartNavigationToPendingEntry (and 111 // page? We should maybe listen to DidStartNavigationToPendingEntry (and
104 // any other renderer-side nav initiations?). Double-check this for 112 // any other renderer-side nav initiations?). Double-check this for
105 // correct behavior on interstitials -- we probably want to basically queue 113 // correct behavior on interstitials -- we probably want to basically queue
106 // any request for which GetVisibleURL != GetLastCommittedURL. 114 // any request for which GetVisibleURL != GetLastCommittedURL.
107 request_url_ = web_contents()->GetLastCommittedURL(); 115 request_url_ = web_contents()->GetLastCommittedURL();
108 bool is_main_frame = url::Origin(request_url_) 116 bool is_main_frame = url::Origin(request_url_)
109 .IsSameOriginWith(url::Origin(request->GetOrigin())); 117 .IsSameOriginWith(url::Origin(request->GetOrigin()));
110 118
111 // Don't re-add an existing request or one with a duplicate text request. 119 // Don't re-add an existing request or one with a duplicate text request.
112 // TODO(johnme): Instead of dropping duplicate requests, we should queue them 120 PermissionBubbleRequest* existing_request = GetExistingRequest(request);
113 // and eventually run their PermissionGranted/PermissionDenied/Cancelled 121 if (existing_request) {
114 // callback (crbug.com/577313). 122 // |request| is a duplicate. Add it to |duplicate_requests_| unless it's the
115 bool same_object = false; 123 // same object as |existing_request| or an existing duplicate.
116 if (ExistingRequest(request, requests_, &same_object) || 124 if (request == existing_request)
117 ExistingRequest(request, queued_requests_, &same_object) || 125 return;
118 ExistingRequest(request, queued_frame_requests_, &same_object)) { 126 auto range = duplicate_requests_.equal_range(existing_request);
119 if (!same_object) 127 for (auto it = range.first; it != range.second; ++it) {
120 request->RequestFinished(); 128 if (request == it->second)
129 return;
130 }
131 duplicate_requests_.insert(std::make_pair(existing_request, request));
121 return; 132 return;
122 } 133 }
123 134
124 if (IsBubbleVisible()) { 135 if (IsBubbleVisible()) {
125 if (is_main_frame) { 136 if (is_main_frame) {
126 content::RecordAction( 137 content::RecordAction(
127 base::UserMetricsAction("PermissionBubbleRequestQueued")); 138 base::UserMetricsAction("PermissionBubbleRequestQueued"));
128 queued_requests_.push_back(request); 139 queued_requests_.push_back(request);
129 } else { 140 } else {
130 content::RecordAction( 141 content::RecordAction(
(...skipping 10 matching lines...) Expand all
141 } else { 152 } else {
142 content::RecordAction( 153 content::RecordAction(
143 base::UserMetricsAction("PermissionBubbleIFrameRequestQueued")); 154 base::UserMetricsAction("PermissionBubbleIFrameRequestQueued"));
144 queued_frame_requests_.push_back(request); 155 queued_frame_requests_.push_back(request);
145 } 156 }
146 157
147 ScheduleShowBubble(); 158 ScheduleShowBubble();
148 } 159 }
149 160
150 void PermissionBubbleManager::CancelRequest(PermissionBubbleRequest* request) { 161 void PermissionBubbleManager::CancelRequest(PermissionBubbleRequest* request) {
151 // First look in the queued requests, where we can simply delete the request 162 // First look in the queued requests, where we can simply finish the request
152 // and go on. 163 // and go on.
153 std::vector<PermissionBubbleRequest*>::iterator requests_iter; 164 std::vector<PermissionBubbleRequest*>::iterator requests_iter;
154 for (requests_iter = queued_requests_.begin(); 165 for (requests_iter = queued_requests_.begin();
155 requests_iter != queued_requests_.end(); 166 requests_iter != queued_requests_.end();
156 requests_iter++) { 167 requests_iter++) {
157 if (*requests_iter == request) { 168 if (*requests_iter == request) {
158 (*requests_iter)->RequestFinished(); 169 RequestFinishedIncludingDuplicates(*requests_iter);
159 queued_requests_.erase(requests_iter); 170 queued_requests_.erase(requests_iter);
160 return; 171 return;
161 } 172 }
162 } 173 }
174 for (requests_iter = queued_frame_requests_.begin();
175 requests_iter != queued_frame_requests_.end(); requests_iter++) {
176 if (*requests_iter == request) {
177 RequestFinishedIncludingDuplicates(*requests_iter);
178 queued_frame_requests_.erase(requests_iter);
179 return;
180 }
181 }
163 182
164 std::vector<bool>::iterator accepts_iter = accept_states_.begin(); 183 std::vector<bool>::iterator accepts_iter = accept_states_.begin();
165 for (requests_iter = requests_.begin(), accepts_iter = accept_states_.begin(); 184 for (requests_iter = requests_.begin(), accepts_iter = accept_states_.begin();
166 requests_iter != requests_.end(); 185 requests_iter != requests_.end();
167 requests_iter++, accepts_iter++) { 186 requests_iter++, accepts_iter++) {
168 if (*requests_iter != request) 187 if (*requests_iter != request)
169 continue; 188 continue;
170 189
171 // We can simply erase the current entry in the request table if we aren't 190 // We can simply erase the current entry in the request table if we aren't
172 // showing the dialog, or if we are showing it and it can accept the update. 191 // showing the dialog, or if we are showing it and it can accept the update.
173 bool can_erase = !IsBubbleVisible() || view_->CanAcceptRequestUpdate(); 192 bool can_erase = !IsBubbleVisible() || view_->CanAcceptRequestUpdate();
174 if (can_erase) { 193 if (can_erase) {
175 (*requests_iter)->RequestFinished(); 194 RequestFinishedIncludingDuplicates(*requests_iter);
176 requests_.erase(requests_iter); 195 requests_.erase(requests_iter);
177 accept_states_.erase(accepts_iter); 196 accept_states_.erase(accepts_iter);
178 197
179 if (IsBubbleVisible()) { 198 if (IsBubbleVisible()) {
180 view_->Hide(); 199 view_->Hide();
181 // Will redraw the bubble if it is being shown. 200 // Will redraw the bubble if it is being shown.
182 TriggerShowBubble(); 201 TriggerShowBubble();
183 } 202 }
184 return; 203 return;
185 } 204 }
186 205
187 // Cancel the existing request and replace it with a dummy. 206 // Cancel the existing request and replace it with a dummy.
188 PermissionBubbleRequest* cancelled_request = 207 PermissionBubbleRequest* cancelled_request =
189 new CancelledRequest(*requests_iter); 208 new CancelledRequest(*requests_iter);
190 (*requests_iter)->RequestFinished(); 209 RequestFinishedIncludingDuplicates(*requests_iter);
191 *requests_iter = cancelled_request; 210 *requests_iter = cancelled_request;
192 return; 211 return;
193 } 212 }
194 213
214 // Since |request| wasn't found in queued_requests_, queued_frame_requests_ or
215 // requests_ it must have been marked as a duplicate. We can't search
216 // duplicate_requests_ by value, so instead use GetExistingRequest to find the
217 // key (request it was duped against), and iterate through duplicates of that.
218 PermissionBubbleRequest* existing_request = GetExistingRequest(request);
219 auto range = duplicate_requests_.equal_range(existing_request);
220 for (auto it = range.first; it != range.second; ++it) {
221 if (request == it->second) {
222 it->second->RequestFinished();
223 duplicate_requests_.erase(it);
224 return;
225 }
226 }
227
195 NOTREACHED(); // Callers should not cancel requests that are not pending. 228 NOTREACHED(); // Callers should not cancel requests that are not pending.
196 } 229 }
197 230
198 void PermissionBubbleManager::HideBubble() { 231 void PermissionBubbleManager::HideBubble() {
199 // Disengage from the existing view if there is one. 232 // Disengage from the existing view if there is one.
200 if (!view_) 233 if (!view_)
201 return; 234 return;
202 235
203 view_->SetDelegate(nullptr); 236 view_->SetDelegate(nullptr);
204 view_->Hide(); 237 view_->Hide();
(...skipping 73 matching lines...) Expand 10 before | Expand all | Expand 10 after
278 DCHECK(request_index < static_cast<int>(accept_states_.size())); 311 DCHECK(request_index < static_cast<int>(accept_states_.size()));
279 accept_states_[request_index] = new_value; 312 accept_states_[request_index] = new_value;
280 } 313 }
281 314
282 void PermissionBubbleManager::Accept() { 315 void PermissionBubbleManager::Accept() {
283 std::vector<PermissionBubbleRequest*>::iterator requests_iter; 316 std::vector<PermissionBubbleRequest*>::iterator requests_iter;
284 std::vector<bool>::iterator accepts_iter = accept_states_.begin(); 317 std::vector<bool>::iterator accepts_iter = accept_states_.begin();
285 for (requests_iter = requests_.begin(), accepts_iter = accept_states_.begin(); 318 for (requests_iter = requests_.begin(), accepts_iter = accept_states_.begin();
286 requests_iter != requests_.end(); 319 requests_iter != requests_.end();
287 requests_iter++, accepts_iter++) { 320 requests_iter++, accepts_iter++) {
288 if (*accepts_iter) 321 if (*accepts_iter) {
289 (*requests_iter)->PermissionGranted(); 322 PermissionGrantedIncludingDuplicates(*requests_iter);
290 else 323 } else {
291 (*requests_iter)->PermissionDenied(); 324 PermissionDeniedIncludingDuplicates(*requests_iter);
325 }
292 } 326 }
293 FinalizeBubble(); 327 FinalizeBubble();
294 } 328 }
295 329
296 void PermissionBubbleManager::Deny() { 330 void PermissionBubbleManager::Deny() {
297 std::vector<PermissionBubbleRequest*>::iterator requests_iter; 331 std::vector<PermissionBubbleRequest*>::iterator requests_iter;
298 for (requests_iter = requests_.begin(); 332 for (requests_iter = requests_.begin();
299 requests_iter != requests_.end(); 333 requests_iter != requests_.end();
300 requests_iter++) { 334 requests_iter++) {
301 (*requests_iter)->PermissionDenied(); 335 PermissionDeniedIncludingDuplicates(*requests_iter);
302 } 336 }
303 FinalizeBubble(); 337 FinalizeBubble();
304 } 338 }
305 339
306 void PermissionBubbleManager::Closing() { 340 void PermissionBubbleManager::Closing() {
307 std::vector<PermissionBubbleRequest*>::iterator requests_iter; 341 std::vector<PermissionBubbleRequest*>::iterator requests_iter;
308 for (requests_iter = requests_.begin(); 342 for (requests_iter = requests_.begin();
309 requests_iter != requests_.end(); 343 requests_iter != requests_.end();
310 requests_iter++) { 344 requests_iter++) {
311 (*requests_iter)->Cancelled(); 345 CancelledIncludingDuplicates(*requests_iter);
312 } 346 }
313 FinalizeBubble(); 347 FinalizeBubble();
314 } 348 }
315 349
316 void PermissionBubbleManager::ScheduleShowBubble() { 350 void PermissionBubbleManager::ScheduleShowBubble() {
317 // ::ScheduleShowBubble() will be called again when the main frame will be 351 // ::ScheduleShowBubble() will be called again when the main frame will be
318 // loaded. 352 // loaded.
319 if (!main_frame_has_fully_loaded_) 353 if (!main_frame_has_fully_loaded_)
320 return; 354 return;
321 355
(...skipping 43 matching lines...) Expand 10 before | Expand all | Expand 10 after
365 } 399 }
366 400
367 void PermissionBubbleManager::FinalizeBubble() { 401 void PermissionBubbleManager::FinalizeBubble() {
368 if (view_) 402 if (view_)
369 view_->Hide(); 403 view_->Hide();
370 404
371 std::vector<PermissionBubbleRequest*>::iterator requests_iter; 405 std::vector<PermissionBubbleRequest*>::iterator requests_iter;
372 for (requests_iter = requests_.begin(); 406 for (requests_iter = requests_.begin();
373 requests_iter != requests_.end(); 407 requests_iter != requests_.end();
374 requests_iter++) { 408 requests_iter++) {
375 (*requests_iter)->RequestFinished(); 409 RequestFinishedIncludingDuplicates(*requests_iter);
376 } 410 }
377 requests_.clear(); 411 requests_.clear();
378 accept_states_.clear(); 412 accept_states_.clear();
379 if (queued_requests_.size() || queued_frame_requests_.size()) 413 if (queued_requests_.size() || queued_frame_requests_.size())
380 TriggerShowBubble(); 414 TriggerShowBubble();
381 else 415 else
382 request_url_ = GURL(); 416 request_url_ = GURL();
383 } 417 }
384 418
385 void PermissionBubbleManager::CancelPendingQueues() { 419 void PermissionBubbleManager::CancelPendingQueues() {
386 std::vector<PermissionBubbleRequest*>::iterator requests_iter; 420 std::vector<PermissionBubbleRequest*>::iterator requests_iter;
387 for (requests_iter = queued_requests_.begin(); 421 for (requests_iter = queued_requests_.begin();
388 requests_iter != queued_requests_.end(); 422 requests_iter != queued_requests_.end();
389 requests_iter++) { 423 requests_iter++) {
390 (*requests_iter)->RequestFinished(); 424 RequestFinishedIncludingDuplicates(*requests_iter);
391 } 425 }
392 for (requests_iter = queued_frame_requests_.begin(); 426 for (requests_iter = queued_frame_requests_.begin();
393 requests_iter != queued_frame_requests_.end(); 427 requests_iter != queued_frame_requests_.end();
394 requests_iter++) { 428 requests_iter++) {
395 (*requests_iter)->RequestFinished(); 429 RequestFinishedIncludingDuplicates(*requests_iter);
396 } 430 }
397 queued_requests_.clear(); 431 queued_requests_.clear();
398 queued_frame_requests_.clear(); 432 queued_frame_requests_.clear();
399 } 433 }
400 434
401 bool PermissionBubbleManager::ExistingRequest( 435 PermissionBubbleRequest* PermissionBubbleManager::GetExistingRequest(
402 PermissionBubbleRequest* request, 436 PermissionBubbleRequest* request) {
403 const std::vector<PermissionBubbleRequest*>& queue, 437 for (PermissionBubbleRequest* existing_request : requests_)
404 bool* same_object) { 438 if (IsMessageTextEqual(existing_request, request))
405 CHECK(same_object); 439 return existing_request;
406 *same_object = false; 440 for (PermissionBubbleRequest* existing_request : queued_requests_)
407 std::vector<PermissionBubbleRequest*>::const_iterator iter; 441 if (IsMessageTextEqual(existing_request, request))
408 for (iter = queue.begin(); iter != queue.end(); iter++) { 442 return existing_request;
409 if (*iter == request) { 443 for (PermissionBubbleRequest* existing_request : queued_frame_requests_)
410 *same_object = true; 444 if (IsMessageTextEqual(existing_request, request))
411 return true; 445 return existing_request;
412 } 446 return nullptr;
413 if ((*iter)->GetMessageTextFragment() ==
414 request->GetMessageTextFragment() &&
415 (*iter)->GetOrigin() == request->GetOrigin()) {
416 return true;
417 }
418 }
419 return false;
420 } 447 }
421 448
422 bool PermissionBubbleManager::HasUserGestureRequest( 449 bool PermissionBubbleManager::HasUserGestureRequest(
423 const std::vector<PermissionBubbleRequest*>& queue) { 450 const std::vector<PermissionBubbleRequest*>& queue) {
424 std::vector<PermissionBubbleRequest*>::const_iterator iter; 451 std::vector<PermissionBubbleRequest*>::const_iterator iter;
425 for (iter = queue.begin(); iter != queue.end(); iter++) { 452 for (iter = queue.begin(); iter != queue.end(); iter++) {
426 if ((*iter)->HasUserGesture()) 453 if ((*iter)->HasUserGesture())
427 return true; 454 return true;
428 } 455 }
429 return false; 456 return false;
430 } 457 }
431 458
459 void PermissionBubbleManager::PermissionGrantedIncludingDuplicates(
460 PermissionBubbleRequest* request) {
461 DCHECK_EQ(request, GetExistingRequest(request))
felt 2016/01/28 06:18:14 nit: wondering why this isn't DCHECKing that GetEx
johnme 2016/01/28 15:40:11 I've changed the DCHECK comment to "Only requests
462 << "Request must not be a duplicate";
463 request->PermissionGranted();
464 auto range = duplicate_requests_.equal_range(request);
465 for (auto it = range.first; it != range.second; ++it)
466 it->second->PermissionGranted();
467 }
468 void PermissionBubbleManager::PermissionDeniedIncludingDuplicates(
469 PermissionBubbleRequest* request) {
470 DCHECK_EQ(request, GetExistingRequest(request))
471 << "Request must not be a duplicate";
472 request->PermissionDenied();
473 auto range = duplicate_requests_.equal_range(request);
474 for (auto it = range.first; it != range.second; ++it)
475 it->second->PermissionDenied();
476 }
477 void PermissionBubbleManager::CancelledIncludingDuplicates(
478 PermissionBubbleRequest* request) {
479 DCHECK_EQ(request, GetExistingRequest(request))
480 << "Request must not be a duplicate";
481 request->Cancelled();
482 auto range = duplicate_requests_.equal_range(request);
483 for (auto it = range.first; it != range.second; ++it)
484 it->second->Cancelled();
485 }
486 void PermissionBubbleManager::RequestFinishedIncludingDuplicates(
487 PermissionBubbleRequest* request) {
488 DCHECK_EQ(request, GetExistingRequest(request))
489 << "Request must not be a duplicate";
490 request->RequestFinished();
491 auto range = duplicate_requests_.equal_range(request);
492 for (auto it = range.first; it != range.second; ++it)
493 it->second->RequestFinished();
494 // Additionally, we can now remove the duplicates.
495 duplicate_requests_.erase(request);
496 }
497
432 void PermissionBubbleManager::AddObserver(Observer* observer) { 498 void PermissionBubbleManager::AddObserver(Observer* observer) {
433 observer_list_.AddObserver(observer); 499 observer_list_.AddObserver(observer);
434 } 500 }
435 501
436 void PermissionBubbleManager::RemoveObserver(Observer* observer) { 502 void PermissionBubbleManager::RemoveObserver(Observer* observer) {
437 observer_list_.RemoveObserver(observer); 503 observer_list_.RemoveObserver(observer);
438 } 504 }
439 505
440 void PermissionBubbleManager::NotifyBubbleAdded() { 506 void PermissionBubbleManager::NotifyBubbleAdded() {
441 FOR_EACH_OBSERVER(Observer, observer_list_, OnBubbleAdded()); 507 FOR_EACH_OBSERVER(Observer, observer_list_, OnBubbleAdded());
442 } 508 }
443 509
444 void PermissionBubbleManager::DoAutoResponseForTesting() { 510 void PermissionBubbleManager::DoAutoResponseForTesting() {
445 switch (auto_response_for_test_) { 511 switch (auto_response_for_test_) {
446 case ACCEPT_ALL: 512 case ACCEPT_ALL:
447 Accept(); 513 Accept();
448 break; 514 break;
449 case DENY_ALL: 515 case DENY_ALL:
450 Deny(); 516 Deny();
451 break; 517 break;
452 case DISMISS: 518 case DISMISS:
453 Closing(); 519 Closing();
454 break; 520 break;
455 case NONE: 521 case NONE:
456 NOTREACHED(); 522 NOTREACHED();
457 } 523 }
458 } 524 }
OLDNEW
« no previous file with comments | « chrome/browser/ui/website_settings/permission_bubble_manager.h ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698