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

Side by Side Diff: cc/trees/layer_tree_impl.cc

Issue 1973083002: Use element id's for animations (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: address reviewer feedback 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 // Copyright 2011 The Chromium Authors. All rights reserved. 1 // Copyright 2011 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 "cc/trees/layer_tree_impl.h" 5 #include "cc/trees/layer_tree_impl.h"
6 6
7 #include <stddef.h> 7 #include <stddef.h>
8 #include <stdint.h> 8 #include <stdint.h>
9 9
10 #include <algorithm> 10 #include <algorithm>
(...skipping 442 matching lines...) Expand 10 before | Expand all | Expand 10 after
453 } 453 }
454 454
455 LayerListReverseIterator<LayerImpl> LayerTreeImpl::rbegin() { 455 LayerListReverseIterator<LayerImpl> LayerTreeImpl::rbegin() {
456 return LayerListReverseIterator<LayerImpl>(root_layer_); 456 return LayerListReverseIterator<LayerImpl>(root_layer_);
457 } 457 }
458 458
459 LayerListReverseIterator<LayerImpl> LayerTreeImpl::rend() { 459 LayerListReverseIterator<LayerImpl> LayerTreeImpl::rend() {
460 return LayerListReverseIterator<LayerImpl>(nullptr); 460 return LayerListReverseIterator<LayerImpl>(nullptr);
461 } 461 }
462 462
463 LayerImpl* LayerTreeImpl::LayerByElementId(ElementId element_id) const {
464 auto iter = element_layers_map_.find(element_id);
465 if (iter == element_layers_map_.end())
466 return nullptr;
467
468 return iter->second;
469 }
470
463 void LayerTreeImpl::AddToElementMap(LayerImpl* layer) { 471 void LayerTreeImpl::AddToElementMap(LayerImpl* layer) {
464 if (!layer->element_id() || !layer->mutable_properties()) 472 if (!layer->element_id())
465 return; 473 return;
466 474
467 TRACE_EVENT2(TRACE_DISABLED_BY_DEFAULT("compositor-worker"), 475 TRACE_EVENT2(TRACE_DISABLED_BY_DEFAULT("compositor-worker"),
468 "LayerTreeImpl::AddToElementMap", "element_id", 476 "LayerTreeImpl::AddToElementMap", "element",
469 layer->element_id(), "layer_id", layer->id()); 477 layer->element_id().AsValue().release(), "layer_id",
478 layer->id());
470 479
471 ElementLayers& layers = element_layers_map_[layer->element_id()]; 480 element_layers_map_[layer->element_id()] = layer;
472 if ((!layers.main || layer->IsActive()) && !layer->scrollable()) { 481
473 layers.main = layer; 482 layer_tree_host_impl_->animation_host()->RegisterElement(
474 } else if ((!layers.scroll || layer->IsActive()) && layer->scrollable()) { 483 layer->element_id(),
475 TRACE_EVENT2("compositor-worker", "LayerTreeImpl::AddToElementMap scroll", 484 IsActiveTree() ? ElementListType::ACTIVE : ElementListType::PENDING);
476 "element_id", layer->element_id(), "layer_id", layer->id());
477 layers.scroll = layer;
478 }
479 } 485 }
480 486
481 void LayerTreeImpl::RemoveFromElementMap(LayerImpl* layer) { 487 void LayerTreeImpl::RemoveFromElementMap(LayerImpl* layer) {
482 if (!layer->element_id()) 488 if (!layer->element_id())
483 return; 489 return;
484 490
485 TRACE_EVENT2(TRACE_DISABLED_BY_DEFAULT("compositor-worker"), 491 TRACE_EVENT2(TRACE_DISABLED_BY_DEFAULT("compositor-worker"),
486 "LayerTreeImpl::RemoveFromElementMap", "element_id", 492 "LayerTreeImpl::RemoveFromElementMap", "element",
487 layer->element_id(), "layer_id", layer->id()); 493 layer->element_id().AsValue().release(), "layer_id",
494 layer->id());
488 495
489 ElementLayers& layers = element_layers_map_[layer->element_id()]; 496 layer_tree_host_impl_->animation_host()->UnregisterElement(
490 if (!layer->scrollable()) 497 layer->element_id(),
491 layers.main = nullptr; 498 IsActiveTree() ? ElementListType::ACTIVE : ElementListType::PENDING);
492 if (layer->scrollable())
493 layers.scroll = nullptr;
494 499
495 if (!layers.main && !layers.scroll) 500 element_layers_map_.erase(layer->element_id());
496 element_layers_map_.erase(layer->element_id());
497 } 501 }
498 502
499 void LayerTreeImpl::AddToOpacityAnimationsMap(int id, float opacity) { 503 void LayerTreeImpl::AddToOpacityAnimationsMap(int id, float opacity) {
500 opacity_animations_map_[id] = opacity; 504 opacity_animations_map_[id] = opacity;
501 } 505 }
502 506
503 void LayerTreeImpl::AddToTransformAnimationsMap(int id, 507 void LayerTreeImpl::AddToTransformAnimationsMap(int id,
504 gfx::Transform transform) { 508 gfx::Transform transform) {
505 transform_animations_map_[id] = transform; 509 transform_animations_map_[id] = transform;
506 } 510 }
507 511
508 LayerTreeImpl::ElementLayers LayerTreeImpl::GetMutableLayers(
509 uint64_t element_id) {
510 auto iter = element_layers_map_.find(element_id);
511 if (iter == element_layers_map_.end())
512 return ElementLayers();
513
514 return iter->second;
515 }
516
517 LayerImpl* LayerTreeImpl::InnerViewportContainerLayer() const { 512 LayerImpl* LayerTreeImpl::InnerViewportContainerLayer() const {
518 return InnerViewportScrollLayer() 513 return InnerViewportScrollLayer()
519 ? InnerViewportScrollLayer()->scroll_clip_layer() 514 ? InnerViewportScrollLayer()->scroll_clip_layer()
520 : NULL; 515 : NULL;
521 } 516 }
522 517
523 LayerImpl* LayerTreeImpl::OuterViewportContainerLayer() const { 518 LayerImpl* LayerTreeImpl::OuterViewportContainerLayer() const {
524 return OuterViewportScrollLayer() 519 return OuterViewportScrollLayer()
525 ? OuterViewportScrollLayer()->scroll_clip_layer() 520 ? OuterViewportScrollLayer()->scroll_clip_layer()
526 : NULL; 521 : NULL;
(...skipping 278 matching lines...) Expand 10 before | Expand all | Expand 10 after
805 outer_viewport_scroll_layer_id_ = outer_viewport_scroll_layer_id; 800 outer_viewport_scroll_layer_id_ = outer_viewport_scroll_layer_id;
806 } 801 }
807 802
808 void LayerTreeImpl::ClearViewportLayers() { 803 void LayerTreeImpl::ClearViewportLayers() {
809 overscroll_elasticity_layer_id_ = Layer::INVALID_ID; 804 overscroll_elasticity_layer_id_ = Layer::INVALID_ID;
810 page_scale_layer_id_ = Layer::INVALID_ID; 805 page_scale_layer_id_ = Layer::INVALID_ID;
811 inner_viewport_scroll_layer_id_ = Layer::INVALID_ID; 806 inner_viewport_scroll_layer_id_ = Layer::INVALID_ID;
812 outer_viewport_scroll_layer_id_ = Layer::INVALID_ID; 807 outer_viewport_scroll_layer_id_ = Layer::INVALID_ID;
813 } 808 }
814 809
810 // For unit tests, we use the layer's id as its element id.
811 static void SetElementIdForTesting(LayerImpl* layer) {
812 layer->SetElementId(LayerIdToElementIdForTesting(layer->id()));
813 }
814
815 void LayerTreeImpl::SetElementIdsForTesting() {
816 LayerTreeHostCommon::CallFunctionForEveryLayer(this, SetElementIdForTesting);
817 }
818
815 bool LayerTreeImpl::UpdateDrawProperties(bool update_lcd_text) { 819 bool LayerTreeImpl::UpdateDrawProperties(bool update_lcd_text) {
816 if (!needs_update_draw_properties_) 820 if (!needs_update_draw_properties_)
817 return true; 821 return true;
818 822
819 // Calling UpdateDrawProperties must clear this flag, so there can be no 823 // Calling UpdateDrawProperties must clear this flag, so there can be no
820 // early outs before this. 824 // early outs before this.
821 needs_update_draw_properties_ = false; 825 needs_update_draw_properties_ = false;
822 826
823 // For max_texture_size. When the renderer is re-created in 827 // For max_texture_size. When the renderer is re-created in
824 // CreateAndSetRenderer, the needs update draw properties flag is set 828 // CreateAndSetRenderer, the needs update draw properties flag is set
(...skipping 211 matching lines...) Expand 10 before | Expand all | Expand 10 after
1036 } 1040 }
1037 1041
1038 bool LayerTreeImpl::LayerNeedsPushPropertiesForTesting(LayerImpl* layer) { 1042 bool LayerTreeImpl::LayerNeedsPushPropertiesForTesting(LayerImpl* layer) {
1039 return layers_that_should_push_properties_.find(layer) != 1043 return layers_that_should_push_properties_.find(layer) !=
1040 layers_that_should_push_properties_.end(); 1044 layers_that_should_push_properties_.end();
1041 } 1045 }
1042 1046
1043 void LayerTreeImpl::RegisterLayer(LayerImpl* layer) { 1047 void LayerTreeImpl::RegisterLayer(LayerImpl* layer) {
1044 DCHECK(!LayerById(layer->id())); 1048 DCHECK(!LayerById(layer->id()));
1045 layer_id_map_[layer->id()] = layer; 1049 layer_id_map_[layer->id()] = layer;
1046 layer_tree_host_impl_->animation_host()->RegisterElement(
1047 layer->id(),
1048 IsActiveTree() ? ElementListType::ACTIVE : ElementListType::PENDING);
1049 } 1050 }
1050 1051
1051 void LayerTreeImpl::UnregisterLayer(LayerImpl* layer) { 1052 void LayerTreeImpl::UnregisterLayer(LayerImpl* layer) {
1052 DCHECK(LayerById(layer->id())); 1053 DCHECK(LayerById(layer->id()));
1053 layer_tree_host_impl_->animation_host()->UnregisterElement(
1054 layer->id(),
1055 IsActiveTree() ? ElementListType::ACTIVE : ElementListType::PENDING);
1056 layer_id_map_.erase(layer->id()); 1054 layer_id_map_.erase(layer->id());
1057 DCHECK_NE(root_layer_, layer); 1055 DCHECK_NE(root_layer_, layer);
1058 } 1056 }
1059 1057
1060 // These manage ownership of the LayerImpl. 1058 // These manage ownership of the LayerImpl.
1061 void LayerTreeImpl::AddLayer(std::unique_ptr<LayerImpl> layer) { 1059 void LayerTreeImpl::AddLayer(std::unique_ptr<LayerImpl> layer) {
1062 DCHECK(std::find(layers_->begin(), layers_->end(), layer) == layers_->end()); 1060 DCHECK(std::find(layers_->begin(), layers_->end(), layer) == layers_->end());
1063 layers_->push_back(std::move(layer)); 1061 layers_->push_back(std::move(layer));
1064 set_needs_update_draw_properties(); 1062 set_needs_update_draw_properties();
1065 } 1063 }
(...skipping 869 matching lines...) Expand 10 before | Expand all | Expand 10 after
1935 1933
1936 std::unique_ptr<PendingPageScaleAnimation> 1934 std::unique_ptr<PendingPageScaleAnimation>
1937 LayerTreeImpl::TakePendingPageScaleAnimation() { 1935 LayerTreeImpl::TakePendingPageScaleAnimation() {
1938 return std::move(pending_page_scale_animation_); 1936 return std::move(pending_page_scale_animation_);
1939 } 1937 }
1940 1938
1941 bool LayerTreeImpl::IsAnimatingFilterProperty(const LayerImpl* layer) const { 1939 bool LayerTreeImpl::IsAnimatingFilterProperty(const LayerImpl* layer) const {
1942 ElementListType list_type = 1940 ElementListType list_type =
1943 IsActiveTree() ? ElementListType::ACTIVE : ElementListType::PENDING; 1941 IsActiveTree() ? ElementListType::ACTIVE : ElementListType::PENDING;
1944 return layer_tree_host_impl_->animation_host()->IsAnimatingFilterProperty( 1942 return layer_tree_host_impl_->animation_host()->IsAnimatingFilterProperty(
1945 layer->id(), list_type); 1943 layer->element_id(), list_type);
1946 } 1944 }
1947 1945
1948 bool LayerTreeImpl::IsAnimatingOpacityProperty(const LayerImpl* layer) const { 1946 bool LayerTreeImpl::IsAnimatingOpacityProperty(const LayerImpl* layer) const {
1949 ElementListType list_type = 1947 ElementListType list_type =
1950 IsActiveTree() ? ElementListType::ACTIVE : ElementListType::PENDING; 1948 IsActiveTree() ? ElementListType::ACTIVE : ElementListType::PENDING;
1951 return layer_tree_host_impl_->animation_host()->IsAnimatingOpacityProperty( 1949 return layer_tree_host_impl_->animation_host()->IsAnimatingOpacityProperty(
1952 layer->id(), list_type); 1950 layer->element_id(), list_type);
1953 } 1951 }
1954 1952
1955 bool LayerTreeImpl::IsAnimatingTransformProperty(const LayerImpl* layer) const { 1953 bool LayerTreeImpl::IsAnimatingTransformProperty(const LayerImpl* layer) const {
1956 ElementListType list_type = 1954 ElementListType list_type =
1957 IsActiveTree() ? ElementListType::ACTIVE : ElementListType::PENDING; 1955 IsActiveTree() ? ElementListType::ACTIVE : ElementListType::PENDING;
1958 return layer_tree_host_impl_->animation_host()->IsAnimatingTransformProperty( 1956 return layer_tree_host_impl_->animation_host()->IsAnimatingTransformProperty(
1959 layer->id(), list_type); 1957 layer->element_id(), list_type);
1960 } 1958 }
1961 1959
1962 bool LayerTreeImpl::HasPotentiallyRunningFilterAnimation( 1960 bool LayerTreeImpl::HasPotentiallyRunningFilterAnimation(
1963 const LayerImpl* layer) const { 1961 const LayerImpl* layer) const {
1964 ElementListType list_type = 1962 ElementListType list_type =
1965 IsActiveTree() ? ElementListType::ACTIVE : ElementListType::PENDING; 1963 IsActiveTree() ? ElementListType::ACTIVE : ElementListType::PENDING;
1966 return layer_tree_host_impl_->animation_host() 1964 return layer_tree_host_impl_->animation_host()
1967 ->HasPotentiallyRunningFilterAnimation(layer->id(), list_type); 1965 ->HasPotentiallyRunningFilterAnimation(layer->element_id(), list_type);
1968 } 1966 }
1969 1967
1970 bool LayerTreeImpl::HasPotentiallyRunningOpacityAnimation( 1968 bool LayerTreeImpl::HasPotentiallyRunningOpacityAnimation(
1971 const LayerImpl* layer) const { 1969 const LayerImpl* layer) const {
1972 ElementListType list_type = 1970 ElementListType list_type =
1973 IsActiveTree() ? ElementListType::ACTIVE : ElementListType::PENDING; 1971 IsActiveTree() ? ElementListType::ACTIVE : ElementListType::PENDING;
1974 return layer_tree_host_impl_->animation_host() 1972 return layer_tree_host_impl_->animation_host()
1975 ->HasPotentiallyRunningOpacityAnimation(layer->id(), list_type); 1973 ->HasPotentiallyRunningOpacityAnimation(layer->element_id(), list_type);
1976 } 1974 }
1977 1975
1978 bool LayerTreeImpl::HasPotentiallyRunningTransformAnimation( 1976 bool LayerTreeImpl::HasPotentiallyRunningTransformAnimation(
1979 const LayerImpl* layer) const { 1977 const LayerImpl* layer) const {
1980 ElementListType list_type = 1978 ElementListType list_type =
1981 IsActiveTree() ? ElementListType::ACTIVE : ElementListType::PENDING; 1979 IsActiveTree() ? ElementListType::ACTIVE : ElementListType::PENDING;
1982 return layer_tree_host_impl_->animation_host() 1980 return layer_tree_host_impl_->animation_host()
1983 ->HasPotentiallyRunningTransformAnimation(layer->id(), list_type); 1981 ->HasPotentiallyRunningTransformAnimation(layer->element_id(), list_type);
1984 } 1982 }
1985 1983
1986 bool LayerTreeImpl::HasAnyAnimationTargetingProperty( 1984 bool LayerTreeImpl::HasAnyAnimationTargetingProperty(
1987 const LayerImpl* layer, 1985 const LayerImpl* layer,
1988 TargetProperty::Type property) const { 1986 TargetProperty::Type property) const {
1989 return layer_tree_host_impl_->animation_host() 1987 return layer_tree_host_impl_->animation_host()
1990 ->HasAnyAnimationTargetingProperty(layer->id(), property); 1988 ->HasAnyAnimationTargetingProperty(layer->element_id(), property);
1991 } 1989 }
1992 1990
1993 bool LayerTreeImpl::AnimationsPreserveAxisAlignment( 1991 bool LayerTreeImpl::AnimationsPreserveAxisAlignment(
1994 const LayerImpl* layer) const { 1992 const LayerImpl* layer) const {
1995 return layer_tree_host_impl_->animation_host() 1993 return layer_tree_host_impl_->animation_host()
1996 ->AnimationsPreserveAxisAlignment(layer->id()); 1994 ->AnimationsPreserveAxisAlignment(layer->element_id());
1997 } 1995 }
1998 1996
1999 bool LayerTreeImpl::HasOnlyTranslationTransforms(const LayerImpl* layer) const { 1997 bool LayerTreeImpl::HasOnlyTranslationTransforms(const LayerImpl* layer) const {
2000 ElementListType list_type = 1998 ElementListType list_type =
2001 IsActiveTree() ? ElementListType::ACTIVE : ElementListType::PENDING; 1999 IsActiveTree() ? ElementListType::ACTIVE : ElementListType::PENDING;
2002 return layer_tree_host_impl_->animation_host()->HasOnlyTranslationTransforms( 2000 return layer_tree_host_impl_->animation_host()->HasOnlyTranslationTransforms(
2003 layer->id(), list_type); 2001 layer->element_id(), list_type);
2004 } 2002 }
2005 2003
2006 bool LayerTreeImpl::MaximumTargetScale(const LayerImpl* layer, 2004 bool LayerTreeImpl::MaximumTargetScale(const LayerImpl* layer,
2007 float* max_scale) const { 2005 float* max_scale) const {
2008 *max_scale = 0.f; 2006 *max_scale = 0.f;
2009 ElementListType list_type = 2007 ElementListType list_type =
2010 IsActiveTree() ? ElementListType::ACTIVE : ElementListType::PENDING; 2008 IsActiveTree() ? ElementListType::ACTIVE : ElementListType::PENDING;
2011 return layer_tree_host_impl_->animation_host()->MaximumTargetScale( 2009 return layer_tree_host_impl_->animation_host()->MaximumTargetScale(
2012 layer->id(), list_type, max_scale); 2010 layer->element_id(), list_type, max_scale);
2013 } 2011 }
2014 2012
2015 bool LayerTreeImpl::AnimationStartScale(const LayerImpl* layer, 2013 bool LayerTreeImpl::AnimationStartScale(const LayerImpl* layer,
2016 float* start_scale) const { 2014 float* start_scale) const {
2017 *start_scale = 0.f; 2015 *start_scale = 0.f;
2018 ElementListType list_type = 2016 ElementListType list_type =
2019 IsActiveTree() ? ElementListType::ACTIVE : ElementListType::PENDING; 2017 IsActiveTree() ? ElementListType::ACTIVE : ElementListType::PENDING;
2020 return layer_tree_host_impl_->animation_host()->AnimationStartScale( 2018 return layer_tree_host_impl_->animation_host()->AnimationStartScale(
2021 layer->id(), list_type, start_scale); 2019 layer->element_id(), list_type, start_scale);
2022 } 2020 }
2023 2021
2024 bool LayerTreeImpl::HasFilterAnimationThatInflatesBounds( 2022 bool LayerTreeImpl::HasFilterAnimationThatInflatesBounds(
2025 const LayerImpl* layer) const { 2023 const LayerImpl* layer) const {
2026 return layer_tree_host_impl_->animation_host() 2024 return layer_tree_host_impl_->animation_host()
2027 ->HasFilterAnimationThatInflatesBounds(layer->id()); 2025 ->HasFilterAnimationThatInflatesBounds(layer->element_id());
2028 } 2026 }
2029 2027
2030 bool LayerTreeImpl::HasTransformAnimationThatInflatesBounds( 2028 bool LayerTreeImpl::HasTransformAnimationThatInflatesBounds(
2031 const LayerImpl* layer) const { 2029 const LayerImpl* layer) const {
2032 return layer_tree_host_impl_->animation_host() 2030 return layer_tree_host_impl_->animation_host()
2033 ->HasTransformAnimationThatInflatesBounds(layer->id()); 2031 ->HasTransformAnimationThatInflatesBounds(layer->element_id());
2034 } 2032 }
2035 2033
2036 bool LayerTreeImpl::HasAnimationThatInflatesBounds( 2034 bool LayerTreeImpl::HasAnimationThatInflatesBounds(
2037 const LayerImpl* layer) const { 2035 const LayerImpl* layer) const {
2038 return layer_tree_host_impl_->animation_host() 2036 return layer_tree_host_impl_->animation_host()
2039 ->HasAnimationThatInflatesBounds(layer->id()); 2037 ->HasAnimationThatInflatesBounds(layer->element_id());
2040 } 2038 }
2041 2039
2042 bool LayerTreeImpl::FilterAnimationBoundsForBox(const LayerImpl* layer, 2040 bool LayerTreeImpl::FilterAnimationBoundsForBox(const LayerImpl* layer,
2043 const gfx::BoxF& box, 2041 const gfx::BoxF& box,
2044 gfx::BoxF* bounds) const { 2042 gfx::BoxF* bounds) const {
2045 return layer_tree_host_impl_->animation_host()->FilterAnimationBoundsForBox( 2043 return layer_tree_host_impl_->animation_host()->FilterAnimationBoundsForBox(
2046 layer->id(), box, bounds); 2044 layer->element_id(), box, bounds);
2047 } 2045 }
2048 2046
2049 bool LayerTreeImpl::TransformAnimationBoundsForBox(const LayerImpl* layer, 2047 bool LayerTreeImpl::TransformAnimationBoundsForBox(const LayerImpl* layer,
2050 const gfx::BoxF& box, 2048 const gfx::BoxF& box,
2051 gfx::BoxF* bounds) const { 2049 gfx::BoxF* bounds) const {
2052 *bounds = gfx::BoxF(); 2050 *bounds = gfx::BoxF();
2053 return layer_tree_host_impl_->animation_host() 2051 return layer_tree_host_impl_->animation_host()
2054 ->TransformAnimationBoundsForBox(layer->id(), box, bounds); 2052 ->TransformAnimationBoundsForBox(layer->element_id(), box, bounds);
2055 } 2053 }
2056 2054
2057 void LayerTreeImpl::ScrollAnimationAbort(bool needs_completion) { 2055 void LayerTreeImpl::ScrollAnimationAbort(bool needs_completion) {
2058 layer_tree_host_impl_->animation_host()->ScrollAnimationAbort( 2056 layer_tree_host_impl_->animation_host()->ScrollAnimationAbort(
2059 needs_completion); 2057 needs_completion);
2060 } 2058 }
2061 2059
2062 void LayerTreeImpl::ResetAllChangeTracking() { 2060 void LayerTreeImpl::ResetAllChangeTracking() {
2063 layers_that_should_push_properties_.clear(); 2061 layers_that_should_push_properties_.clear();
2064 for (auto* layer : *this) 2062 for (auto* layer : *this)
2065 layer->ResetChangeTracking(); 2063 layer->ResetChangeTracking();
2066 property_trees_.ResetAllChangeTracking(); 2064 property_trees_.ResetAllChangeTracking();
2067 } 2065 }
2068 2066
2069 } // namespace cc 2067 } // namespace cc
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698