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

Side by Side Diff: Source/platform/scroll/ScrollView.cpp

Issue 314583008: Refactor ScrollView::updateScrollbars() (Closed) Base URL: svn://svn.chromium.org/blink/trunk
Patch Set: revised Created 6 years, 6 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 | Annotate | Revision Log
« no previous file with comments | « Source/platform/scroll/ScrollView.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 /* 1 /*
2 * Copyright (C) 2006, 2007, 2008 Apple Inc. All rights reserved. 2 * Copyright (C) 2006, 2007, 2008 Apple Inc. All rights reserved.
3 * 3 *
4 * Redistribution and use in source and binary forms, with or without 4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions 5 * modification, are permitted provided that the following conditions
6 * are met: 6 * are met:
7 * 1. Redistributions of source code must retain the above copyright 7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer. 8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright 9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the 10 * notice, this list of conditions and the following disclaimer in the
(...skipping 13 matching lines...) Expand all
24 */ 24 */
25 25
26 #include "config.h" 26 #include "config.h"
27 #include "platform/scroll/ScrollView.h" 27 #include "platform/scroll/ScrollView.h"
28 28
29 #include "platform/graphics/GraphicsContextStateSaver.h" 29 #include "platform/graphics/GraphicsContextStateSaver.h"
30 #include "platform/graphics/GraphicsLayer.h" 30 #include "platform/graphics/GraphicsLayer.h"
31 #include "platform/HostWindow.h" 31 #include "platform/HostWindow.h"
32 #include "platform/scroll/ScrollbarTheme.h" 32 #include "platform/scroll/ScrollbarTheme.h"
33 #include "wtf/StdLibExtras.h" 33 #include "wtf/StdLibExtras.h"
34 #include "wtf/TemporaryChange.h"
34 35
35 using namespace std; 36 using namespace std;
36 37
37 namespace WebCore { 38 namespace WebCore {
38 39
39 ScrollView::ScrollView() 40 ScrollView::ScrollView()
40 : m_horizontalScrollbarMode(ScrollbarAuto) 41 : m_horizontalScrollbarMode(ScrollbarAuto)
41 , m_verticalScrollbarMode(ScrollbarAuto) 42 , m_verticalScrollbarMode(ScrollbarAuto)
42 , m_horizontalScrollbarLock(false) 43 , m_horizontalScrollbarLock(false)
43 , m_verticalScrollbarLock(false) 44 , m_verticalScrollbarLock(false)
44 , m_scrollbarsAvoidingResizer(0) 45 , m_scrollbarsAvoidingResizer(0)
45 , m_scrollbarsSuppressed(false) 46 , m_scrollbarsSuppressed(false)
46 , m_inUpdateScrollbars(false) 47 , m_inUpdateScrollbars(false)
47 , m_updateScrollbarsPass(0)
48 , m_drawPanScrollIcon(false) 48 , m_drawPanScrollIcon(false)
49 , m_paintsEntireContents(false) 49 , m_paintsEntireContents(false)
50 , m_clipsRepaints(true) 50 , m_clipsRepaints(true)
51 { 51 {
52 } 52 }
53 53
54 ScrollView::~ScrollView() 54 ScrollView::~ScrollView()
55 { 55 {
56 } 56 }
57 57
(...skipping 259 matching lines...) Expand 10 before | Expand all | Expand 10 after
317 stretch.setHeight(currentScrollPosition.y() - maxScrollPosition.y()); 317 stretch.setHeight(currentScrollPosition.y() - maxScrollPosition.y());
318 318
319 return stretch; 319 return stretch;
320 } 320 }
321 321
322 void ScrollView::windowResizerRectChanged() 322 void ScrollView::windowResizerRectChanged()
323 { 323 {
324 updateScrollbars(scrollOffset()); 324 updateScrollbars(scrollOffset());
325 } 325 }
326 326
327 static const unsigned cMaxUpdateScrollbarsPass = 2; 327 static bool useOverlayScrollbars()
328 {
329 // FIXME: Need to detect the presence of CSS custom scrollbars, which are no n-overlay regardless the ScrollbarTheme.
330 return ScrollbarTheme::theme()->usesOverlayScrollbars();
331 }
328 332
329 void ScrollView::updateScrollbars(const IntSize& desiredOffset) 333 void ScrollView::computeScrollbarExistence(bool& newHasHorizontalScrollbar, bool & newHasVerticalScrollbar, ComputeScrollbarExistenceOption option) const
330 { 334 {
331 if (m_inUpdateScrollbars)
332 return;
333
334 // If we came in here with the view already needing a layout, then go ahead and do that
335 // first. (This will be the common case, e.g., when the page changes due to window resizing for example).
336 // This layout will not re-enter updateScrollbars and does not count towards our max layout pass total.
337 if (!m_scrollbarsSuppressed) {
338 m_inUpdateScrollbars = true;
339 scrollbarExistenceDidChange();
340 m_inUpdateScrollbars = false;
341 }
342
343 IntRect oldScrollCornerRect = scrollCornerRect();
344
345 bool hasHorizontalScrollbar = m_horizontalScrollbar; 335 bool hasHorizontalScrollbar = m_horizontalScrollbar;
346 bool hasVerticalScrollbar = m_verticalScrollbar; 336 bool hasVerticalScrollbar = m_verticalScrollbar;
347 337
348 bool newHasHorizontalScrollbar = hasHorizontalScrollbar; 338 newHasHorizontalScrollbar = hasHorizontalScrollbar;
349 bool newHasVerticalScrollbar = hasVerticalScrollbar; 339 newHasVerticalScrollbar = hasVerticalScrollbar;
350 340
351 ScrollbarMode hScroll = m_horizontalScrollbarMode; 341 ScrollbarMode hScroll = m_horizontalScrollbarMode;
352 ScrollbarMode vScroll = m_verticalScrollbarMode; 342 ScrollbarMode vScroll = m_verticalScrollbarMode;
353 343
354 if (hScroll != ScrollbarAuto) 344 if (hScroll != ScrollbarAuto)
355 newHasHorizontalScrollbar = (hScroll == ScrollbarAlwaysOn); 345 newHasHorizontalScrollbar = (hScroll == ScrollbarAlwaysOn);
356 if (vScroll != ScrollbarAuto) 346 if (vScroll != ScrollbarAuto)
357 newHasVerticalScrollbar = (vScroll == ScrollbarAlwaysOn); 347 newHasVerticalScrollbar = (vScroll == ScrollbarAlwaysOn);
358 348
359 if (m_scrollbarsSuppressed || (hScroll != ScrollbarAuto && vScroll != Scroll barAuto)) { 349 if (m_scrollbarsSuppressed || (hScroll != ScrollbarAuto && vScroll != Scroll barAuto))
360 if (hasHorizontalScrollbar != newHasHorizontalScrollbar) 350 return;
361 setHasHorizontalScrollbar(newHasHorizontalScrollbar);
362 if (hasVerticalScrollbar != newHasVerticalScrollbar)
363 setHasVerticalScrollbar(newHasVerticalScrollbar);
364 } else {
365 bool scrollbarExistenceChanged = false;
366 351
367 IntSize docSize = contentsSize(); 352 IntSize docSize = contentsSize();
368 IntSize fullVisibleSize = visibleContentRect(IncludeScrollbars).size();
369 353
370 bool scrollbarsAreOverlay = ScrollbarTheme::theme()->usesOverlayScrollba rs(); 354 if (hScroll == ScrollbarAuto)
355 newHasHorizontalScrollbar = docSize.width() > visibleWidth();
356 if (vScroll == ScrollbarAuto)
357 newHasVerticalScrollbar = docSize.height() > visibleHeight();
371 358
372 if (hScroll == ScrollbarAuto) { 359 if (useOverlayScrollbars())
373 newHasHorizontalScrollbar = docSize.width() > visibleWidth(); 360 return;
374 if (!scrollbarsAreOverlay && newHasHorizontalScrollbar && !m_updateS crollbarsPass && docSize.width() <= fullVisibleSize.width() && docSize.height() <= fullVisibleSize.height())
375 newHasHorizontalScrollbar = false;
376 }
377 if (vScroll == ScrollbarAuto) {
378 newHasVerticalScrollbar = docSize.height() > visibleHeight();
379 if (!scrollbarsAreOverlay && newHasVerticalScrollbar && !m_updateScr ollbarsPass && docSize.width() <= fullVisibleSize.width() && docSize.height() <= fullVisibleSize.height())
380 newHasVerticalScrollbar = false;
381 }
382 361
383 if (!scrollbarsAreOverlay) { 362 IntSize fullVisibleSize = visibleContentRect(IncludeScrollbars).size();
384 // If we ever turn one scrollbar off, always turn the other one off too. Never ever
385 // try to both gain/lose a scrollbar in the same pass.
386 if (!newHasHorizontalScrollbar && hasHorizontalScrollbar && vScroll != ScrollbarAlwaysOn)
387 newHasVerticalScrollbar = false;
388 if (!newHasVerticalScrollbar && hasVerticalScrollbar && hScroll != S crollbarAlwaysOn)
389 newHasHorizontalScrollbar = false;
390 }
391 363
392 if (hasHorizontalScrollbar != newHasHorizontalScrollbar) { 364 bool attemptToRemoveScrollbars = (option == FirstPass
393 scrollbarExistenceChanged = true; 365 && docSize.width() <= fullVisibleSize.width() && docSize.height() <= ful lVisibleSize.height());
394 if (scrollOrigin().y() && !newHasHorizontalScrollbar && !scrollbarsA reOverlay) 366 if (attemptToRemoveScrollbars) {
395 ScrollableArea::setScrollOrigin(IntPoint(scrollOrigin().x(), scr ollOrigin().y() - m_horizontalScrollbar->height())); 367 if (hScroll == ScrollbarAuto)
396 if (hasHorizontalScrollbar) 368 newHasHorizontalScrollbar = false;
397 m_horizontalScrollbar->invalidate(); 369 if (vScroll == ScrollbarAuto)
398 setHasHorizontalScrollbar(newHasHorizontalScrollbar); 370 newHasVerticalScrollbar = false;
399 }
400
401 if (hasVerticalScrollbar != newHasVerticalScrollbar) {
402 scrollbarExistenceChanged = true;
403 if (scrollOrigin().x() && !newHasVerticalScrollbar && !scrollbarsAre Overlay)
404 ScrollableArea::setScrollOrigin(IntPoint(scrollOrigin().x() - m_ verticalScrollbar->width(), scrollOrigin().y()));
405 if (hasVerticalScrollbar)
406 m_verticalScrollbar->invalidate();
407 setHasVerticalScrollbar(newHasVerticalScrollbar);
408 }
409
410 if (scrollbarExistenceChanged) {
411 if (scrollbarsAreOverlay) {
412 // Synchronize status of scrollbar layers if necessary.
413 m_inUpdateScrollbars = true;
414 scrollbarExistenceDidChange();
415 m_inUpdateScrollbars = false;
416 } else if (m_updateScrollbarsPass < cMaxUpdateScrollbarsPass) {
417 m_updateScrollbarsPass++;
418 contentsResized();
419 scrollbarExistenceDidChange();
420 IntSize newDocSize = contentsSize();
421 if (newDocSize == docSize) {
422 // The layout with the new scroll state had no impact on
423 // the document's overall size, so updateScrollbars didn't g et called.
424 // Recur manually.
425 updateScrollbars(desiredOffset);
426 }
427 m_updateScrollbarsPass--;
428 }
429 }
430 } 371 }
431 372
432 // Set up the range, but only do this if we're not in a nested call (to avoi d 373 // If we ever turn one scrollbar off, always turn the other one off too.
433 // doing it multiple times). 374 // Never ever try to both gain/lose a scrollbar in the same pass.
434 if (m_updateScrollbarsPass) 375 if (!newHasHorizontalScrollbar && hasHorizontalScrollbar && vScroll != Scrol lbarAlwaysOn)
435 return; 376 newHasVerticalScrollbar = false;
377 if (!newHasVerticalScrollbar && hasVerticalScrollbar && hScroll != Scrollbar AlwaysOn)
378 newHasHorizontalScrollbar = false;
379 }
436 380
437 m_inUpdateScrollbars = true; 381 void ScrollView::updateScrollbarGeometry()
438 382 {
439 if (m_horizontalScrollbar) { 383 if (m_horizontalScrollbar) {
440 int clientWidth = visibleWidth(); 384 int clientWidth = visibleWidth();
441 IntRect oldRect(m_horizontalScrollbar->frameRect()); 385 IntRect oldRect(m_horizontalScrollbar->frameRect());
442 IntRect hBarRect((shouldPlaceVerticalScrollbarOnLeft() && m_verticalScro llbar) ? m_verticalScrollbar->width() : 0, 386 IntRect hBarRect((shouldPlaceVerticalScrollbarOnLeft() && m_verticalScro llbar) ? m_verticalScrollbar->width() : 0,
443 height() - m_horizontalScrollbar->height(), 387 height() - m_horizontalScrollbar->height(),
444 width() - (m_verticalScrollbar ? m_verticalScrollbar->wi dth() : 0), 388 width() - (m_verticalScrollbar ? m_verticalScrollbar->wi dth() : 0),
445 m_horizontalScrollbar->height()); 389 m_horizontalScrollbar->height());
446 m_horizontalScrollbar->setFrameRect(hBarRect); 390 m_horizontalScrollbar->setFrameRect(hBarRect);
447 if (!m_scrollbarsSuppressed && oldRect != m_horizontalScrollbar->frameRe ct()) 391 if (!m_scrollbarsSuppressed && oldRect != m_horizontalScrollbar->frameRe ct())
448 m_horizontalScrollbar->invalidate(); 392 m_horizontalScrollbar->invalidate();
449 393
450 if (m_scrollbarsSuppressed) 394 if (m_scrollbarsSuppressed)
451 m_horizontalScrollbar->setSuppressInvalidation(true); 395 m_horizontalScrollbar->setSuppressInvalidation(true);
452 m_horizontalScrollbar->setEnabled(contentsWidth() > clientWidth); 396 m_horizontalScrollbar->setEnabled(contentsWidth() > clientWidth);
453 m_horizontalScrollbar->setProportion(clientWidth, contentsWidth()); 397 m_horizontalScrollbar->setProportion(clientWidth, contentsWidth());
398 m_horizontalScrollbar->offsetDidChange();
454 if (m_scrollbarsSuppressed) 399 if (m_scrollbarsSuppressed)
455 m_horizontalScrollbar->setSuppressInvalidation(false); 400 m_horizontalScrollbar->setSuppressInvalidation(false);
456 } 401 }
457 402
458 if (m_verticalScrollbar) { 403 if (m_verticalScrollbar) {
459 int clientHeight = visibleHeight(); 404 int clientHeight = visibleHeight();
460 IntRect oldRect(m_verticalScrollbar->frameRect()); 405 IntRect oldRect(m_verticalScrollbar->frameRect());
461 IntRect vBarRect(shouldPlaceVerticalScrollbarOnLeft() ? 0 : (width() - m _verticalScrollbar->width()), 406 IntRect vBarRect(shouldPlaceVerticalScrollbarOnLeft() ? 0 : (width() - m _verticalScrollbar->width()),
462 0, 407 0,
463 m_verticalScrollbar->width(), 408 m_verticalScrollbar->width(),
464 height() - (m_horizontalScrollbar ? m_horizontalScrollb ar->height() : 0)); 409 height() - (m_horizontalScrollbar ? m_horizontalScrollb ar->height() : 0));
465 m_verticalScrollbar->setFrameRect(vBarRect); 410 m_verticalScrollbar->setFrameRect(vBarRect);
466 if (!m_scrollbarsSuppressed && oldRect != m_verticalScrollbar->frameRect ()) 411 if (!m_scrollbarsSuppressed && oldRect != m_verticalScrollbar->frameRect ())
467 m_verticalScrollbar->invalidate(); 412 m_verticalScrollbar->invalidate();
468 413
469 if (m_scrollbarsSuppressed) 414 if (m_scrollbarsSuppressed)
470 m_verticalScrollbar->setSuppressInvalidation(true); 415 m_verticalScrollbar->setSuppressInvalidation(true);
471 m_verticalScrollbar->setEnabled(contentsHeight() > clientHeight); 416 m_verticalScrollbar->setEnabled(contentsHeight() > clientHeight);
472 m_verticalScrollbar->setProportion(clientHeight, contentsHeight()); 417 m_verticalScrollbar->setProportion(clientHeight, contentsHeight());
418 m_verticalScrollbar->offsetDidChange();
473 if (m_scrollbarsSuppressed) 419 if (m_scrollbarsSuppressed)
474 m_verticalScrollbar->setSuppressInvalidation(false); 420 m_verticalScrollbar->setSuppressInvalidation(false);
475 } 421 }
422 }
476 423
477 if (hasHorizontalScrollbar != newHasHorizontalScrollbar || hasVerticalScroll bar != newHasVerticalScrollbar) { 424 bool ScrollView::adjustScrollbarExistence(ComputeScrollbarExistenceOption option )
425 {
426 ASSERT(m_inUpdateScrollbars);
427
428 // If we came in here with the view already needing a layout, then go ahead and do that
429 // first. (This will be the common case, e.g., when the page changes due to window resizing for example).
430 // This layout will not re-enter updateScrollbars and does not count towards our max layout pass total.
431 if (!m_scrollbarsSuppressed)
432 scrollbarExistenceDidChange();
433
434 bool hasHorizontalScrollbar = m_horizontalScrollbar;
435 bool hasVerticalScrollbar = m_verticalScrollbar;
436
437 bool newHasHorizontalScrollbar = false;
438 bool newHasVerticalScrollbar = false;
439 computeScrollbarExistence(newHasHorizontalScrollbar, newHasVerticalScrollbar , option);
440
441 bool scrollbarExistenceChanged = hasHorizontalScrollbar != newHasHorizontalS crollbar || hasVerticalScrollbar != newHasVerticalScrollbar;
442 if (!scrollbarExistenceChanged)
443 return false;
444
445 setHasHorizontalScrollbar(newHasHorizontalScrollbar);
446 setHasVerticalScrollbar(newHasVerticalScrollbar);
447
448 if (m_scrollbarsSuppressed)
449 return true;
450
451 if (!useOverlayScrollbars())
452 contentsResized();
453 scrollbarExistenceDidChange();
454 return true;
455 }
456
457 void ScrollView::updateScrollbars(const IntSize& desiredOffset)
458 {
459 if (m_inUpdateScrollbars)
460 return;
461 TemporaryChange<bool> inUpdateScrollbarsChange(m_inUpdateScrollbars, true);
462
463 IntSize oldVisibleSize = visibleSize();
464
465 bool scrollbarExistenceChanged = false;
466 int maxUpdateScrollbarsPass = useOverlayScrollbars() || m_scrollbarsSuppress ed ? 1 : 3;
467 for (int updateScrollbarsPass = 0; updateScrollbarsPass < maxUpdateScrollbar sPass; updateScrollbarsPass++) {
468 if (!adjustScrollbarExistence(updateScrollbarsPass ? Incremental : First Pass))
469 break;
470 scrollbarExistenceChanged = true;
471 }
472
473 updateScrollbarGeometry();
474
475 if (scrollbarExistenceChanged) {
478 // FIXME: Is frameRectsChanged really necessary here? Have any frame rec ts changed? 476 // FIXME: Is frameRectsChanged really necessary here? Have any frame rec ts changed?
479 frameRectsChanged(); 477 frameRectsChanged();
480 positionScrollbarLayers(); 478 positionScrollbarLayers();
481 updateScrollCorner(); 479 updateScrollCorner();
482 if (!m_horizontalScrollbar && !m_verticalScrollbar)
483 invalidateScrollCornerRect(oldScrollCornerRect);
484 } 480 }
485 481
482 // FIXME: We don't need to do this if we are composited.
483 IntSize newVisibleSize = visibleSize();
484 if (newVisibleSize.width() > oldVisibleSize.width()) {
485 if (shouldPlaceVerticalScrollbarOnLeft())
486 invalidateRect(IntRect(0, 0, newVisibleSize.width() - oldVisibleSize .width(), newVisibleSize.height()));
487 else
488 invalidateRect(IntRect(oldVisibleSize.width(), 0, newVisibleSize.wid th() - oldVisibleSize.width(), newVisibleSize.height()));
489 }
490 if (newVisibleSize.height() > oldVisibleSize.height())
491 invalidateRect(IntRect(0, oldVisibleSize.height(), newVisibleSize.width( ), newVisibleSize.height() - oldVisibleSize.height()));
492
486 IntPoint adjustedScrollPosition = IntPoint(desiredOffset); 493 IntPoint adjustedScrollPosition = IntPoint(desiredOffset);
487 if (!isRubberBandInProgress()) 494 if (!isRubberBandInProgress())
488 adjustedScrollPosition = adjustScrollPositionWithinRange(adjustedScrollP osition); 495 adjustedScrollPosition = adjustScrollPositionWithinRange(adjustedScrollP osition);
489
490 if (adjustedScrollPosition != scrollPosition() || scrollOriginChanged()) { 496 if (adjustedScrollPosition != scrollPosition() || scrollOriginChanged()) {
491 ScrollableArea::scrollToOffsetWithoutAnimation(adjustedScrollPosition); 497 ScrollableArea::scrollToOffsetWithoutAnimation(adjustedScrollPosition);
492 resetScrollOriginChanged(); 498 resetScrollOriginChanged();
493 } 499 }
494
495 // Make sure the scrollbar offsets are up to date.
496 if (m_horizontalScrollbar)
497 m_horizontalScrollbar->offsetDidChange();
498 if (m_verticalScrollbar)
499 m_verticalScrollbar->offsetDidChange();
500
501 m_inUpdateScrollbars = false;
502 } 500 }
503 501
504 const int panIconSizeLength = 16; 502 const int panIconSizeLength = 16;
505 503
506 IntRect ScrollView::rectToCopyOnScroll() const 504 IntRect ScrollView::rectToCopyOnScroll() const
507 { 505 {
508 IntRect scrollViewRect = convertToRootView(IntRect((shouldPlaceVerticalScrol lbarOnLeft() && verticalScrollbar()) ? verticalScrollbar()->width() : 0, 0, visi bleWidth(), visibleHeight())); 506 IntRect scrollViewRect = convertToRootView(IntRect((shouldPlaceVerticalScrol lbarOnLeft() && verticalScrollbar()) ? verticalScrollbar()->width() : 0, 0, visi bleWidth(), visibleHeight()));
509 if (hasOverlayScrollbars()) { 507 if (hasOverlayScrollbars()) {
510 int verticalScrollbarWidth = (verticalScrollbar() && !hasLayerForVertica lScrollbar()) ? verticalScrollbar()->width() : 0; 508 int verticalScrollbarWidth = (verticalScrollbar() && !hasLayerForVertica lScrollbar()) ? verticalScrollbar()->width() : 0;
511 int horizontalScrollbarHeight = (horizontalScrollbar() && !hasLayerForHo rizontalScrollbar()) ? horizontalScrollbar()->height() : 0; 509 int horizontalScrollbarHeight = (horizontalScrollbar() && !hasLayerForHo rizontalScrollbar()) ? horizontalScrollbar()->height() : 0;
(...skipping 602 matching lines...) Expand 10 before | Expand all | Expand 10 after
1114 return; 1112 return;
1115 1113
1116 ScrollableArea::setScrollOrigin(origin); 1114 ScrollableArea::setScrollOrigin(origin);
1117 1115
1118 // Update if the scroll origin changes, since our position will be different if the content size did not change. 1116 // Update if the scroll origin changes, since our position will be different if the content size did not change.
1119 if (updatePositionAtAll && updatePositionSynchronously) 1117 if (updatePositionAtAll && updatePositionSynchronously)
1120 updateScrollbars(scrollOffset()); 1118 updateScrollbars(scrollOffset());
1121 } 1119 }
1122 1120
1123 } // namespace WebCore 1121 } // namespace WebCore
OLDNEW
« no previous file with comments | « Source/platform/scroll/ScrollView.h ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698