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

Side by Side Diff: third_party/WebKit/Source/core/layout/compositing/CompositedLayerMapping.cpp

Issue 2019583002: Don't include scroll offset in offsetFromLayoutObject for scrolling contents layers. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Created 4 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
OLDNEW
1 /* 1 /*
2 * Copyright (C) 2009, 2010, 2011 Apple Inc. All rights reserved. 2 * Copyright (C) 2009, 2010, 2011 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 987 matching lines...) Expand 10 before | Expand all | Expand 10 after
998 m_childClippingMaskLayer->setOffsetFromLayoutObject(toIntSize(overflowCl ipRect.location())); 998 m_childClippingMaskLayer->setOffsetFromLayoutObject(toIntSize(overflowCl ipRect.location()));
999 } 999 }
1000 1000
1001 bool overflowClipRectOffsetChanged = oldScrollingLayerOffset != m_scrollingL ayer->offsetFromLayoutObject(); 1001 bool overflowClipRectOffsetChanged = oldScrollingLayerOffset != m_scrollingL ayer->offsetFromLayoutObject();
1002 1002
1003 IntSize scrollSize(layoutBox->scrollWidth(), layoutBox->scrollHeight()); 1003 IntSize scrollSize(layoutBox->scrollWidth(), layoutBox->scrollHeight());
1004 if (scrollSize != m_scrollingContentsLayer->size() || overflowClipRectOffset Changed) 1004 if (scrollSize != m_scrollingContentsLayer->size() || overflowClipRectOffset Changed)
1005 m_scrollingContentsLayer->setNeedsDisplay(); 1005 m_scrollingContentsLayer->setNeedsDisplay();
1006 1006
1007 DoubleSize scrollingContentsOffset(overflowClipRect.location().x() - adjuste dScrollOffset.width(), overflowClipRect.location().y() - adjustedScrollOffset.he ight()); 1007 DoubleSize scrollingContentsOffset(overflowClipRect.location().x() - adjuste dScrollOffset.width(), overflowClipRect.location().y() - adjustedScrollOffset.he ight());
1008 // The scroll offset change is compared using floating point so that fractio nal scroll offset 1008 // // The scroll offset change is compared using floating point so that frac tional scroll offset
1009 // change can be propagated to compositor. 1009 // // change can be propagated to compositor.
Xianzhu 2016/05/31 16:46:27 Nit: //
chrishtr 2016/05/31 16:58:40 Done.
1010 if (scrollingContentsOffset != m_scrollingContentsLayer->offsetDoubleFromLay outObject() || scrollSize != m_scrollingContentsLayer->size()) { 1010 if (scrollingContentsOffset != m_scrollingContentsOffset || scrollSize != m_ scrollingContentsLayer->size()) {
1011 bool coordinatorHandlesOffset = compositor()->scrollingLayerDidChange(&m _owningLayer); 1011 bool coordinatorHandlesOffset = compositor()->scrollingLayerDidChange(&m _owningLayer);
1012 m_scrollingContentsLayer->setPosition(coordinatorHandlesOffset ? FloatPo int() : FloatPoint(-toFloatSize(adjustedScrollOffset))); 1012 m_scrollingContentsLayer->setPosition(coordinatorHandlesOffset ? FloatPo int() : FloatPoint(-toFloatSize(adjustedScrollOffset)));
1013 } 1013 }
1014 m_scrollingContentsOffset = scrollingContentsOffset;
1014 1015
1015 m_scrollingContentsLayer->setSize(FloatSize(scrollSize)); 1016 m_scrollingContentsLayer->setSize(FloatSize(scrollSize));
1016 // FIXME: The paint offset and the scroll offset should really be separate c oncepts. 1017 m_scrollingContentsLayer->setOffsetDoubleFromLayoutObject(toIntSize(overflow ClipRect.location()), GraphicsLayer::DontSetNeedsDisplay);
1017 m_scrollingContentsLayer->setOffsetDoubleFromLayoutObject(scrollingContentsO ffset, GraphicsLayer::DontSetNeedsDisplay);
1018 1018
1019 if (m_foregroundLayer) { 1019 if (m_foregroundLayer) {
1020 if (m_foregroundLayer->size() != m_scrollingContentsLayer->size()) 1020 if (m_foregroundLayer->size() != m_scrollingContentsLayer->size())
1021 m_foregroundLayer->setSize(m_scrollingContentsLayer->size()); 1021 m_foregroundLayer->setSize(m_scrollingContentsLayer->size());
1022 m_foregroundLayer->setNeedsDisplay(); 1022 m_foregroundLayer->setNeedsDisplay();
1023 m_foregroundLayer->setOffsetFromLayoutObject(m_scrollingContentsLayer->o ffsetFromLayoutObject()); 1023 m_foregroundLayer->setOffsetFromLayoutObject(m_scrollingContentsLayer->o ffsetFromLayoutObject());
1024 } 1024 }
1025 } 1025 }
1026 1026
1027 void CompositedLayerMapping::updateChildClippingMaskLayerGeometry() 1027 void CompositedLayerMapping::updateChildClippingMaskLayerGeometry()
(...skipping 381 matching lines...) Expand 10 before | Expand all | Expand 10 after
1409 } 1409 }
1410 1410
1411 enum ApplyToGraphicsLayersModeFlags { 1411 enum ApplyToGraphicsLayersModeFlags {
1412 ApplyToLayersAffectedByPreserve3D = (1 << 0), 1412 ApplyToLayersAffectedByPreserve3D = (1 << 0),
1413 ApplyToSquashingLayer = (1 << 1), 1413 ApplyToSquashingLayer = (1 << 1),
1414 ApplyToScrollbarLayers = (1 << 2), 1414 ApplyToScrollbarLayers = (1 << 2),
1415 ApplyToBackgroundLayer = (1 << 3), 1415 ApplyToBackgroundLayer = (1 << 3),
1416 ApplyToMaskLayers = (1 << 4), 1416 ApplyToMaskLayers = (1 << 4),
1417 ApplyToContentLayers = (1 << 5), 1417 ApplyToContentLayers = (1 << 5),
1418 ApplyToChildContainingLayers = (1 << 6), // layers between m_graphicsLayer a nd children 1418 ApplyToChildContainingLayers = (1 << 6), // layers between m_graphicsLayer a nd children
1419 ApplyToAllGraphicsLayers = (ApplyToSquashingLayer | ApplyToScrollbarLayers | ApplyToBackgroundLayer | ApplyToMaskLayers | ApplyToLayersAffectedByPreserve3D | ApplyToContentLayers) 1419 ApplyToScrollingContentLayers = (1 << 7),
1420 ApplyToAllGraphicsLayers = (ApplyToSquashingLayer | ApplyToScrollbarLayers | ApplyToBackgroundLayer | ApplyToMaskLayers | ApplyToLayersAffectedByPreserve3D | ApplyToContentLayers | ApplyToScrollingContentLayers)
1420 }; 1421 };
1421 typedef unsigned ApplyToGraphicsLayersMode; 1422 typedef unsigned ApplyToGraphicsLayersMode;
1422 1423
1423 template <typename Func> 1424 template <typename Func>
1424 static void ApplyToGraphicsLayers(const CompositedLayerMapping* mapping, const F unc& f, ApplyToGraphicsLayersMode mode) 1425 static void ApplyToGraphicsLayers(const CompositedLayerMapping* mapping, const F unc& f, ApplyToGraphicsLayersMode mode)
1425 { 1426 {
1426 ASSERT(mode); 1427 ASSERT(mode);
1427 1428
1428 if ((mode & ApplyToLayersAffectedByPreserve3D) && mapping->childTransformLay er()) 1429 if ((mode & ApplyToLayersAffectedByPreserve3D) && mapping->childTransformLay er())
1429 f(mapping->childTransformLayer()); 1430 f(mapping->childTransformLayer());
1430 if (((mode & ApplyToLayersAffectedByPreserve3D) || (mode & ApplyToContentLay ers)) && mapping->mainGraphicsLayer()) 1431 if (((mode & ApplyToLayersAffectedByPreserve3D) || (mode & ApplyToContentLay ers)) && mapping->mainGraphicsLayer())
1431 f(mapping->mainGraphicsLayer()); 1432 f(mapping->mainGraphicsLayer());
1432 if (((mode & ApplyToLayersAffectedByPreserve3D) || (mode & ApplyToChildConta iningLayers)) && mapping->clippingLayer()) 1433 if (((mode & ApplyToLayersAffectedByPreserve3D) || (mode & ApplyToChildConta iningLayers)) && mapping->clippingLayer())
1433 f(mapping->clippingLayer()); 1434 f(mapping->clippingLayer());
1434 if (((mode & ApplyToLayersAffectedByPreserve3D) || (mode & ApplyToChildConta iningLayers)) && mapping->scrollingLayer()) 1435 if (((mode & ApplyToLayersAffectedByPreserve3D) || (mode & ApplyToChildConta iningLayers)) && mapping->scrollingLayer())
1435 f(mapping->scrollingLayer()); 1436 f(mapping->scrollingLayer());
1436 if (((mode & ApplyToLayersAffectedByPreserve3D) || (mode & ApplyToContentLay ers) || (mode & ApplyToChildContainingLayers)) && mapping->scrollingContentsLaye r()) 1437 if (((mode & ApplyToLayersAffectedByPreserve3D) || (mode & ApplyToContentLay ers) || (mode & ApplyToChildContainingLayers) || (mode & ApplyToScrollingContent Layers)) && mapping->scrollingContentsLayer())
1437 f(mapping->scrollingContentsLayer()); 1438 f(mapping->scrollingContentsLayer());
1438 if (((mode & ApplyToLayersAffectedByPreserve3D) || (mode & ApplyToContentLay ers)) && mapping->foregroundLayer()) 1439 if (((mode & ApplyToLayersAffectedByPreserve3D) || (mode & ApplyToContentLay ers) || (mode & ApplyToScrollingContentLayers)) && mapping->foregroundLayer())
1439 f(mapping->foregroundLayer()); 1440 f(mapping->foregroundLayer());
1440 1441
1441 if ((mode & ApplyToChildContainingLayers) && mapping->childTransformLayer()) 1442 if ((mode & ApplyToChildContainingLayers) && mapping->childTransformLayer())
1442 f(mapping->childTransformLayer()); 1443 f(mapping->childTransformLayer());
1443 1444
1444 if ((mode & ApplyToSquashingLayer) && mapping->squashingLayer()) 1445 if ((mode & ApplyToSquashingLayer) && mapping->squashingLayer())
1445 f(mapping->squashingLayer()); 1446 f(mapping->squashingLayer());
1446 1447
1447 if (((mode & ApplyToMaskLayers) || (mode & ApplyToContentLayers)) && mapping ->maskLayer()) 1448 if (((mode & ApplyToMaskLayers) || (mode & ApplyToContentLayers)) && mapping ->maskLayer())
1448 f(mapping->maskLayer()); 1449 f(mapping->maskLayer());
(...skipping 653 matching lines...) Expand 10 before | Expand all | Expand 10 after
2102 // ASSERT(!RuntimeEnabledFeatures::slimmingPaintV2Enabled()); 2103 // ASSERT(!RuntimeEnabledFeatures::slimmingPaintV2Enabled());
2103 2104
2104 SetContentsNeedsDisplayInRectFunctor functor = { 2105 SetContentsNeedsDisplayInRectFunctor functor = {
2105 enclosingIntRect(LayoutRect(r.location() + m_owningLayer.subpixelAccumul ation(), r.size())), 2106 enclosingIntRect(LayoutRect(r.location() + m_owningLayer.subpixelAccumul ation(), r.size())),
2106 invalidationReason, 2107 invalidationReason,
2107 client 2108 client
2108 }; 2109 };
2109 ApplyToGraphicsLayers(this, functor, ApplyToContentLayers); 2110 ApplyToGraphicsLayers(this, functor, ApplyToContentLayers);
2110 } 2111 }
2111 2112
2113 void CompositedLayerMapping::setScrollingContentsNeedDisplayInRect(const LayoutR ect& r, PaintInvalidationReason invalidationReason, const DisplayItemClient& cli ent)
2114 {
2115 // TODO(wangxianzhu): Enable the following assert after paint invalidation f or spv2 is ready.
2116 // ASSERT(!RuntimeEnabledFeatures::slimmingPaintV2Enabled());
2117
2118 SetContentsNeedsDisplayInRectFunctor functor = {
2119 enclosingIntRect(LayoutRect(r.location() + m_owningLayer.subpixelAccumul ation(), r.size())),
2120 invalidationReason,
2121 client
2122 };
2123 ApplyToGraphicsLayers(this, functor, ApplyToScrollingContentLayers);
2124 }
2125
2112 void CompositedLayerMapping::displayItemClientWasInvalidated(const DisplayItemCl ient& displayItemClient, PaintInvalidationReason paintInvalidationReason) 2126 void CompositedLayerMapping::displayItemClientWasInvalidated(const DisplayItemCl ient& displayItemClient, PaintInvalidationReason paintInvalidationReason)
2113 { 2127 {
2114 ApplyToGraphicsLayers(this, [&displayItemClient, paintInvalidationReason](Gr aphicsLayer* layer) { 2128 ApplyToGraphicsLayers(this, [&displayItemClient, paintInvalidationReason](Gr aphicsLayer* layer) {
2115 layer->displayItemClientWasInvalidated(displayItemClient, paintInvalidat ionReason); 2129 layer->displayItemClientWasInvalidated(displayItemClient, paintInvalidat ionReason);
2116 }, ApplyToContentLayers); 2130 }, ApplyToContentLayers);
2117 } 2131 }
2118 2132
2119 const GraphicsLayerPaintInfo* CompositedLayerMapping::containingSquashedLayer(co nst LayoutObject* layoutObject, const Vector<GraphicsLayerPaintInfo>& layers, un signed maxSquashedLayerIndex) 2133 const GraphicsLayerPaintInfo* CompositedLayerMapping::containingSquashedLayer(co nst LayoutObject* layoutObject, const Vector<GraphicsLayerPaintInfo>& layers, un signed maxSquashedLayerIndex)
2120 { 2134 {
2121 for (size_t i = 0; i < layers.size() && i < maxSquashedLayerIndex; ++i) { 2135 for (size_t i = 0; i < layers.size() && i < maxSquashedLayerIndex; ++i) {
(...skipping 111 matching lines...) Expand 10 before | Expand all | Expand 10 after
2233 return IntRect(); 2247 return IntRect();
2234 // All squashed layers have the same clip and transform space, so we can use the first squashed layer's 2248 // All squashed layers have the same clip and transform space, so we can use the first squashed layer's
2235 // layoutObject to map the squashing layer's bounds into viewport space, with offsetFromAnchorLayoutObject 2249 // layoutObject to map the squashing layer's bounds into viewport space, with offsetFromAnchorLayoutObject
2236 // to translate squashing layer's bounds into the first squashed layer's space. 2250 // to translate squashing layer's bounds into the first squashed layer's space.
2237 anchorLayoutObject = m_squashedLayers[0].paintLayer->layoutObject(); 2251 anchorLayoutObject = m_squashedLayers[0].paintLayer->layoutObject();
2238 offsetFromAnchorLayoutObject = m_squashedLayers[0].offsetFromLayoutObjec t; 2252 offsetFromAnchorLayoutObject = m_squashedLayers[0].offsetFromLayoutObjec t;
2239 } else { 2253 } else {
2240 ASSERT(graphicsLayer == m_graphicsLayer || graphicsLayer == m_scrollingC ontentsLayer); 2254 ASSERT(graphicsLayer == m_graphicsLayer || graphicsLayer == m_scrollingC ontentsLayer);
2241 anchorLayoutObject = m_owningLayer.layoutObject(); 2255 anchorLayoutObject = m_owningLayer.layoutObject();
2242 offsetFromAnchorLayoutObject = graphicsLayer->offsetFromLayoutObject(); 2256 offsetFromAnchorLayoutObject = graphicsLayer->offsetFromLayoutObject();
2257 adjustForCompositedScrolling(graphicsLayer, offsetFromAnchorLayoutObject );
2243 } 2258 }
2244 2259
2245 // Start with the bounds of the graphics layer in the space of the anchor La youtObject. 2260 // Start with the bounds of the graphics layer in the space of the anchor La youtObject.
2246 FloatRect graphicsLayerBoundsInObjectSpace(graphicsLayerBounds); 2261 FloatRect graphicsLayerBoundsInObjectSpace(graphicsLayerBounds);
2247 graphicsLayerBoundsInObjectSpace.move(offsetFromAnchorLayoutObject); 2262 graphicsLayerBoundsInObjectSpace.move(offsetFromAnchorLayoutObject);
2248 // The object space means including writing mode flip. 2263 // The object space means including writing mode flip.
2249 if (anchorLayoutObject->isBox()) 2264 if (anchorLayoutObject->isBox())
2250 toLayoutBox(anchorLayoutObject)->flipForWritingMode(graphicsLayerBoundsI nObjectSpace); 2265 toLayoutBox(anchorLayoutObject)->flipForWritingMode(graphicsLayerBoundsI nObjectSpace);
2251 2266
2252 // Now map the bounds to its visible content rect in screen space, including applying clips along the way. 2267 // Now map the bounds to its visible content rect in screen space, including applying clips along the way.
(...skipping 80 matching lines...) Expand 10 before | Expand all | Expand 10 after
2333 LayoutSize CompositedLayerMapping::subpixelAccumulation() const 2348 LayoutSize CompositedLayerMapping::subpixelAccumulation() const
2334 { 2349 {
2335 return m_owningLayer.subpixelAccumulation(); 2350 return m_owningLayer.subpixelAccumulation();
2336 } 2351 }
2337 2352
2338 bool CompositedLayerMapping::needsRepaint(const GraphicsLayer& graphicsLayer) co nst 2353 bool CompositedLayerMapping::needsRepaint(const GraphicsLayer& graphicsLayer) co nst
2339 { 2354 {
2340 return isScrollableAreaLayer(&graphicsLayer) ? true : m_owningLayer.needsRep aint(); 2355 return isScrollableAreaLayer(&graphicsLayer) ? true : m_owningLayer.needsRep aint();
2341 } 2356 }
2342 2357
2358 void CompositedLayerMapping::adjustForCompositedScrolling(const GraphicsLayer* g raphicsLayer, IntSize& offset) const
2359 {
2360 if (graphicsLayer == m_scrollingContentsLayer.get() || graphicsLayer == m_fo regroundLayer.get()) {
2361 DoubleSize adjustedScrollOffset = m_owningLayer.getScrollableArea()->adj ustedScrollOffset();
2362 offset.expand(-adjustedScrollOffset.width(), -adjustedScrollOffset.heigh t());
2363 }
2364 }
2365
2343 void CompositedLayerMapping::paintContents(const GraphicsLayer* graphicsLayer, G raphicsContext& context, 2366 void CompositedLayerMapping::paintContents(const GraphicsLayer* graphicsLayer, G raphicsContext& context,
2344 GraphicsLayerPaintingPhase graphicsLayerPaintingPhase, const IntRect& intere stRect) const 2367 GraphicsLayerPaintingPhase graphicsLayerPaintingPhase, const IntRect& intere stRect) const
2345 { 2368 {
2346 // https://code.google.com/p/chromium/issues/detail?id=343772 2369 // https://code.google.com/p/chromium/issues/detail?id=343772
2347 DisableCompositingQueryAsserts disabler; 2370 DisableCompositingQueryAsserts disabler;
2348 // Allow throttling to make sure no painting paths (e.g., 2371 // Allow throttling to make sure no painting paths (e.g.,
2349 // ContentLayerDelegate::paintContents) try to paint throttled content. 2372 // ContentLayerDelegate::paintContents) try to paint throttled content.
2350 DocumentLifecycle::AllowThrottlingScope allowThrottling(m_owningLayer.layout Object()->document().lifecycle()); 2373 DocumentLifecycle::AllowThrottlingScope allowThrottling(m_owningLayer.layout Object()->document().lifecycle());
2351 #if ENABLE(ASSERT) 2374 #if ENABLE(ASSERT)
2352 // FIXME: once the state machine is ready, this can be removed and we can re fer to that instead. 2375 // FIXME: once the state machine is ready, this can be removed and we can re fer to that instead.
(...skipping 26 matching lines...) Expand all
2379 || graphicsLayer == m_foregroundLayer.get() 2402 || graphicsLayer == m_foregroundLayer.get()
2380 || graphicsLayer == m_backgroundLayer.get() 2403 || graphicsLayer == m_backgroundLayer.get()
2381 || graphicsLayer == m_maskLayer.get() 2404 || graphicsLayer == m_maskLayer.get()
2382 || graphicsLayer == m_childClippingMaskLayer.get() 2405 || graphicsLayer == m_childClippingMaskLayer.get()
2383 || graphicsLayer == m_scrollingContentsLayer.get()) { 2406 || graphicsLayer == m_scrollingContentsLayer.get()) {
2384 2407
2385 GraphicsLayerPaintInfo paintInfo; 2408 GraphicsLayerPaintInfo paintInfo;
2386 paintInfo.paintLayer = &m_owningLayer; 2409 paintInfo.paintLayer = &m_owningLayer;
2387 paintInfo.compositedBounds = compositedBounds(); 2410 paintInfo.compositedBounds = compositedBounds();
2388 paintInfo.offsetFromLayoutObject = graphicsLayer->offsetFromLayoutObject (); 2411 paintInfo.offsetFromLayoutObject = graphicsLayer->offsetFromLayoutObject ();
2412 adjustForCompositedScrolling(graphicsLayer, paintInfo.offsetFromLayoutOb ject);
2389 2413
2390 // We have to use the same root as for hit testing, because both methods can compute and cache clipRects. 2414 // We have to use the same root as for hit testing, because both methods can compute and cache clipRects.
2391 doPaintTask(paintInfo, *graphicsLayer, paintLayerFlags, context, interes tRect); 2415 doPaintTask(paintInfo, *graphicsLayer, paintLayerFlags, context, interes tRect);
2392 } else if (graphicsLayer == m_squashingLayer.get()) { 2416 } else if (graphicsLayer == m_squashingLayer.get()) {
2393 for (size_t i = 0; i < m_squashedLayers.size(); ++i) 2417 for (size_t i = 0; i < m_squashedLayers.size(); ++i)
2394 doPaintTask(m_squashedLayers[i], *graphicsLayer, paintLayerFlags, co ntext, interestRect); 2418 doPaintTask(m_squashedLayers[i], *graphicsLayer, paintLayerFlags, co ntext, interestRect);
2395 } else if (isScrollableAreaLayer(graphicsLayer)) { 2419 } else if (isScrollableAreaLayer(graphicsLayer)) {
2396 paintScrollableArea(graphicsLayer, context, interestRect); 2420 paintScrollableArea(graphicsLayer, context, interestRect);
2397 } 2421 }
2398 InspectorInstrumentation::didPaint(m_owningLayer.layoutObject()->frame(), gr aphicsLayer, context, LayoutRect(interestRect)); 2422 InspectorInstrumentation::didPaint(m_owningLayer.layoutObject()->frame(), gr aphicsLayer, context, LayoutRect(interestRect));
(...skipping 206 matching lines...) Expand 10 before | Expand all | Expand 10 after
2605 } else if (graphicsLayer == m_scrollingContentsLayer.get()) { 2629 } else if (graphicsLayer == m_scrollingContentsLayer.get()) {
2606 name = "Scrolling Contents Layer"; 2630 name = "Scrolling Contents Layer";
2607 } else { 2631 } else {
2608 ASSERT_NOT_REACHED(); 2632 ASSERT_NOT_REACHED();
2609 } 2633 }
2610 2634
2611 return name; 2635 return name;
2612 } 2636 }
2613 2637
2614 } // namespace blink 2638 } // namespace blink
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698