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

Side by Side Diff: third_party/WebKit/Source/core/layout/LayoutObject.h

Issue 1813383002: Move all fast-path paint invalidation mapping into PaintInvalidationState (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Disable fast-path/slow-path comparison because of saturated operations of LayoutUnit Created 4 years, 9 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) 2000 Lars Knoll (knoll@kde.org) 2 * Copyright (C) 2000 Lars Knoll (knoll@kde.org)
3 * (C) 2000 Antti Koivisto (koivisto@kde.org) 3 * (C) 2000 Antti Koivisto (koivisto@kde.org)
4 * (C) 2000 Dirk Mueller (mueller@kde.org) 4 * (C) 2000 Dirk Mueller (mueller@kde.org)
5 * (C) 2004 Allan Sandfeld Jensen (kde@carewolf.com) 5 * (C) 2004 Allan Sandfeld Jensen (kde@carewolf.com)
6 * Copyright (C) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2012 Apple Inc. All r ights reserved. 6 * Copyright (C) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2012 Apple Inc. All r ights reserved.
7 * Copyright (C) 2009 Google Inc. All rights reserved. 7 * Copyright (C) 2009 Google Inc. All rights reserved.
8 * 8 *
9 * This library is free software; you can redistribute it and/or 9 * This library is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU Library General Public 10 * modify it under the terms of the GNU Library General Public
(...skipping 77 matching lines...) Expand 10 before | Expand all | Expand 10 after
88 enum MarkingBehavior { 88 enum MarkingBehavior {
89 MarkOnlyThis, 89 MarkOnlyThis,
90 MarkContainerChain, 90 MarkContainerChain,
91 }; 91 };
92 92
93 enum MapCoordinatesMode { 93 enum MapCoordinatesMode {
94 IsFixed = 1 << 0, 94 IsFixed = 1 << 0,
95 UseTransforms = 1 << 1, 95 UseTransforms = 1 << 1,
96 ApplyContainerFlip = 1 << 2, 96 ApplyContainerFlip = 1 << 2,
97 TraverseDocumentBoundaries = 1 << 3, 97 TraverseDocumentBoundaries = 1 << 3,
98
99 // Applies to LayoutView::mapLocalToAncestor() and LayoutView::mapToVisibleR ectInAncestorSpace()
100 // only, to indicate the input point or rect is in frame coordinates instead of frame contents
101 // coordinates. This disables view clipping and scroll offset adjustment.
102 // TODO(wangxianzhu): Remove this when root-layer-scrolls launches.
103 InputIsInFrameCoordinates = 1 << 4,
98 }; 104 };
99 typedef unsigned MapCoordinatesFlags; 105 typedef unsigned MapCoordinatesFlags;
100 106
101 enum ScheduleRelayoutBehavior { 107 enum ScheduleRelayoutBehavior {
102 ScheduleRelayout, 108 ScheduleRelayout,
103 DontScheduleRelayout 109 DontScheduleRelayout
104 }; 110 };
105 111
106 const LayoutUnit& caretWidth(); 112 const LayoutUnit& caretWidth();
107 113
(...skipping 208 matching lines...) Expand 10 before | Expand all | Expand 10 after
316 #endif 322 #endif
317 ASSERT_WITH_SECURITY_IMPLICATION(!needsLayout()); 323 ASSERT_WITH_SECURITY_IMPLICATION(!needsLayout());
318 } 324 }
319 325
320 void assertSubtreeIsLaidOut() const 326 void assertSubtreeIsLaidOut() const
321 { 327 {
322 for (const LayoutObject* layoutObject = this; layoutObject; layoutObject = layoutObject->nextInPreOrder()) 328 for (const LayoutObject* layoutObject = this; layoutObject; layoutObject = layoutObject->nextInPreOrder())
323 layoutObject->assertLaidOut(); 329 layoutObject->assertLaidOut();
324 } 330 }
325 331
326 void assertClearedPaintInvalidationState() const 332 void assertClearedPaintInvalidationFlags() const
327 { 333 {
328 #ifndef NDEBUG 334 #ifndef NDEBUG
329 if (paintInvalidationStateIsDirty()) { 335 if (paintInvalidationStateIsDirty()) {
330 showLayoutTreeForThis(); 336 showLayoutTreeForThis();
331 ASSERT_NOT_REACHED(); 337 ASSERT_NOT_REACHED();
332 } 338 }
333 #endif 339 #endif
334 } 340 }
335 341
336 void assertSubtreeClearedPaintInvalidationState() const 342 void assertSubtreeClearedPaintInvalidationFlags() const
337 { 343 {
338 for (const LayoutObject* layoutObject = this; layoutObject; layoutObject = layoutObject->nextInPreOrder()) 344 for (const LayoutObject* layoutObject = this; layoutObject; layoutObject = layoutObject->nextInPreOrder())
339 layoutObject->assertClearedPaintInvalidationState(); 345 layoutObject->assertClearedPaintInvalidationFlags();
340 } 346 }
341 347
342 #endif 348 #endif
343 349
344 // Correct version of !layoutObjectHasNoBoxEffectObsolete(). 350 // Correct version of !layoutObjectHasNoBoxEffectObsolete().
345 bool hasBoxEffect() const 351 bool hasBoxEffect() const
346 { 352 {
347 return hasBoxDecorationBackground() || style()->hasVisualOverflowingEffe ct(); 353 return hasBoxDecorationBackground() || style()->hasVisualOverflowingEffe ct();
348 } 354 }
349 355
(...skipping 634 matching lines...) Expand 10 before | Expand all | Expand 10 after
984 { 990 {
985 return ancestorToLocalQuad(nullptr, quad, mode); 991 return ancestorToLocalQuad(nullptr, quad, mode);
986 } 992 }
987 993
988 // Convert a local quad into the coordinate system of container, taking tran sforms into account. 994 // Convert a local quad into the coordinate system of container, taking tran sforms into account.
989 // If the LayoutBoxModelObject ancestor is non-null, the result will be in t he space of the ancestor. 995 // If the LayoutBoxModelObject ancestor is non-null, the result will be in t he space of the ancestor.
990 // Otherwise: 996 // Otherwise:
991 // If TraverseDocumentBoundaries is specified, the result will be in the s pace of the local root frame. 997 // If TraverseDocumentBoundaries is specified, the result will be in the s pace of the local root frame.
992 // Otherwise, the result will be in the space of the containing frame. 998 // Otherwise, the result will be in the space of the containing frame.
993 FloatQuad localToAncestorQuad(const FloatQuad&, const LayoutBoxModelObject* ancestor, MapCoordinatesFlags = 0, bool* wasFixed = nullptr) const; 999 FloatQuad localToAncestorQuad(const FloatQuad&, const LayoutBoxModelObject* ancestor, MapCoordinatesFlags = 0, bool* wasFixed = nullptr) const;
994 FloatPoint localToAncestorPoint(const FloatPoint&, const LayoutBoxModelObjec t* ancestor, MapCoordinatesFlags = 0, bool* wasFixed = nullptr, const PaintInval idationState* = nullptr) const; 1000 FloatPoint localToAncestorPoint(const FloatPoint&, const LayoutBoxModelObjec t* ancestor, MapCoordinatesFlags = 0, bool* wasFixed = nullptr) const;
995 void localToAncestorRects(Vector<LayoutRect>&, const LayoutBoxModelObject* a ncestor, const LayoutPoint& preOffset, const LayoutPoint& postOffset) const; 1001 void localToAncestorRects(Vector<LayoutRect>&, const LayoutBoxModelObject* a ncestor, const LayoutPoint& preOffset, const LayoutPoint& postOffset) const;
996 1002
997 // Return the transformation matrix to map points from local to the coordina te system of a container, taking transforms into account. 1003 // Return the transformation matrix to map points from local to the coordina te system of a container, taking transforms into account.
998 // Passing null for |ancestor| behaves the same as localToAncestorQuad. 1004 // Passing null for |ancestor| behaves the same as localToAncestorQuad.
999 TransformationMatrix localToAncestorTransform(const LayoutBoxModelObject* an cestor, MapCoordinatesFlags = 0, bool* wasFixed = nullptr) const; 1005 TransformationMatrix localToAncestorTransform(const LayoutBoxModelObject* an cestor, MapCoordinatesFlags = 0, bool* wasFixed = nullptr) const;
1000 TransformationMatrix localToAbsoluteTransform(MapCoordinatesFlags mode = 0, bool* wasFixed = nullptr) const 1006 TransformationMatrix localToAbsoluteTransform(MapCoordinatesFlags mode = 0, bool* wasFixed = nullptr) const
1001 { 1007 {
1002 return localToAncestorTransform(nullptr, mode, wasFixed); 1008 return localToAncestorTransform(nullptr, mode, wasFixed);
1003 } 1009 }
1004 1010
(...skipping 91 matching lines...) Expand 10 before | Expand all | Expand 10 after
1096 // is responsible for painting this object. The function crosses 1102 // is responsible for painting this object. The function crosses
1097 // frames boundaries so the returned value can be in a 1103 // frames boundaries so the returned value can be in a
1098 // different document. 1104 // different document.
1099 // 1105 //
1100 // This is the container that should be passed to 1106 // This is the container that should be passed to
1101 // the '*forPaintInvalidation' methods. 1107 // the '*forPaintInvalidation' methods.
1102 const LayoutBoxModelObject& containerForPaintInvalidation() const; 1108 const LayoutBoxModelObject& containerForPaintInvalidation() const;
1103 1109
1104 bool isPaintInvalidationContainer() const; 1110 bool isPaintInvalidationContainer() const;
1105 1111
1106 // Returns the paint invalidation rect for this LayoutObject in the coordina te space of the paint backing (typically a GraphicsLayer) for |paintInvalidation Container|.
1107 LayoutRect computePaintInvalidationRect(const LayoutBoxModelObject& paintInv alidationContainer, const PaintInvalidationState* = nullptr) const;
1108
1109 // Returns the rect bounds needed to invalidate the paint of this object, in the coordinate space of the layoutObject backing of |paintInvalidationContainer |
1110 LayoutRect boundsRectForPaintInvalidation(const LayoutBoxModelObject& paintI nvalidationContainer, const PaintInvalidationState* = nullptr) const;
1111
1112 // Actually do the paint invalidate of rect r for this object which has been computed in the coordinate space 1112 // Actually do the paint invalidate of rect r for this object which has been computed in the coordinate space
1113 // of the GraphicsLayer backing of |paintInvalidationContainer|. Note that t his coordinaten space is not the same 1113 // of the GraphicsLayer backing of |paintInvalidationContainer|. Note that t his coordinaten space is not the same
1114 // as the local coordinate space of |paintInvalidationContainer| in the pres ence of layer squashing. 1114 // as the local coordinate space of |paintInvalidationContainer| in the pres ence of layer squashing.
1115 void invalidatePaintUsingContainer(const LayoutBoxModelObject& paintInvalida tionContainer, const LayoutRect&, PaintInvalidationReason) const; 1115 void invalidatePaintUsingContainer(const LayoutBoxModelObject& paintInvalida tionContainer, const LayoutRect&, PaintInvalidationReason) const;
1116 1116
1117 // Invalidate the paint of a specific subrectangle within a given object. Th e rect is in the object's coordinate space. 1117 // Invalidate the paint of a specific subrectangle within a given object. Th e rect is in the object's coordinate space.
1118 void invalidatePaintRectangle(const LayoutRect&) const; 1118 void invalidatePaintRectangle(const LayoutRect&) const;
1119 void invalidatePaintRectangleNotInvalidatingDisplayItemClients(const LayoutR ect&) const; 1119 void invalidatePaintRectangleNotInvalidatingDisplayItemClients(const LayoutR ect&) const;
1120 1120
1121 // Walk the tree after layout issuing paint invalidations for layoutObjects that have changed or moved, updating bounds that have changed, and clearing pain t invalidation state. 1121 // Walk the tree after layout issuing paint invalidations for layoutObjects that have changed or moved, updating bounds that have changed, and clearing pain t invalidation state.
1122 virtual void invalidateTreeIfNeeded(const PaintInvalidationState&); 1122 virtual void invalidateTreeIfNeeded(const PaintInvalidationState&);
1123 1123
1124 // This function only invalidates the visual overflow. 1124 // This function only invalidates the visual overflow.
1125 // 1125 //
1126 // Note that overflow is a box concept but this function 1126 // Note that overflow is a box concept but this function
1127 // is only supported for block-flow. 1127 // is only supported for block-flow.
1128 virtual void invalidatePaintForOverflow(); 1128 virtual void invalidatePaintForOverflow();
1129 void invalidatePaintForOverflowIfNeeded(); 1129 void invalidatePaintForOverflowIfNeeded();
1130 1130
1131 void invalidatePaintIncludingNonCompositingDescendants(); 1131 void invalidatePaintIncludingNonCompositingDescendants();
1132 void invalidatePaintIncludingNonSelfPaintingLayerDescendants(const LayoutBox ModelObject& paintInvalidationContainer); 1132 void invalidatePaintIncludingNonSelfPaintingLayerDescendants(const LayoutBox ModelObject& paintInvalidationContainer);
1133 void setShouldDoFullPaintInvalidationIncludingNonCompositingDescendants(); 1133 void setShouldDoFullPaintInvalidationIncludingNonCompositingDescendants();
1134 1134
1135 // Returns the rect that should have paint invalidated whenever this object changes. The rect is in the view's 1135 // Returns the rect that should have paint invalidated whenever this object changes. The rect is in the view's
1136 // coordinate space. This method deals with outlines and overflow. 1136 // coordinate space. This method deals with outlines and overflow.
1137 virtual LayoutRect absoluteClippedOverflowRect() const; 1137 virtual LayoutRect absoluteClippedOverflowRect() const;
1138 virtual LayoutRect clippedOverflowRectForPaintInvalidation(const LayoutBoxMo delObject* paintInvalidationContainer, const PaintInvalidationState* = nullptr) const;
1139 1138
1140 // Returns the rect that should have paint invalidated whenever this object changes. The rect is in the object's 1139 // Returns the rect that should have paint invalidated whenever this object changes. The rect is in the object's
1141 // local coordinate space. 1140 // local coordinate space. This is for non-SVG objects and LayoutSVGRoot onl y. SVG objects (except LayoutSVGRoot)
1141 // should use paintInvalidationRectInLocalSVGCoordinates() and map with SVG transforms instead.
1142 virtual LayoutRect localOverflowRectForPaintInvalidation() const; 1142 virtual LayoutRect localOverflowRectForPaintInvalidation() const;
1143 1143
1144 // Given a rect in the object's coordinate space, compute a rect in the coor dinate space of |ancestor|. If 1144 // Given a rect in the object's coordinate space, compute a rect in the coor dinate space of |ancestor|. If
1145 // intermediate containers have clipping or scrolling of any kind, it is app lied; but overflow clipping is 1145 // intermediate containers have clipping or scrolling of any kind, it is app lied; but overflow clipping is
1146 // *not* applied for |ancestor| itself. The output rect is suitable for purp oses such as paint invalidation. 1146 // *not* applied for |ancestor| itself. The output rect is suitable for purp oses such as paint invalidation.
1147 // 1147 //
1148 // If visibleRectFlags has the EdgeInclusive bit set, clipping operations wi ll use 1148 // If visibleRectFlags has the EdgeInclusive bit set, clipping operations wi ll use
1149 // LayoutRect::inclusiveIntersect, and the return value of inclusiveIntersec t will be propagated 1149 // LayoutRect::inclusiveIntersect, and the return value of inclusiveIntersec t will be propagated
1150 // to the return value of this method. Otherwise, clipping operations will use LayoutRect::intersect, 1150 // to the return value of this method. Otherwise, clipping operations will use LayoutRect::intersect,
1151 // and the return value will be true only if the clipped rect has non-zero a rea. 1151 // and the return value will be true only if the clipped rect has non-zero a rea.
1152 // See the documentation for LayoutRect::inclusiveIntersect for more informa tion. 1152 // See the documentation for LayoutRect::inclusiveIntersect for more informa tion.
1153 virtual bool mapToVisibleRectInAncestorSpace(const LayoutBoxModelObject* anc estor, LayoutRect&, const PaintInvalidationState*, VisibleRectFlags = DefaultVis ibleRectFlags) const; 1153 virtual bool mapToVisibleRectInAncestorSpace(const LayoutBoxModelObject* anc estor, LayoutRect&, VisibleRectFlags = DefaultVisibleRectFlags) const;
1154 1154
1155 // Return the offset to the column in which the specified point (in flow-thr ead coordinates) 1155 // Return the offset to the column in which the specified point (in flow-thr ead coordinates)
1156 // lives. This is used to convert a flow-thread point to a visual point. 1156 // lives. This is used to convert a flow-thread point to a visual point.
1157 virtual LayoutSize columnOffset(const LayoutPoint&) const { return LayoutSiz e(); } 1157 virtual LayoutSize columnOffset(const LayoutPoint&) const { return LayoutSiz e(); }
1158 1158
1159 virtual unsigned length() const { return 1; } 1159 virtual unsigned length() const { return 1; }
1160 1160
1161 bool isFloatingOrOutOfFlowPositioned() const { return (isFloating() || isOut OfFlowPositioned()); } 1161 bool isFloatingOrOutOfFlowPositioned() const { return (isFloating() || isOut OfFlowPositioned()); }
1162 1162
1163 bool isTransparent() const { return style()->hasOpacity(); } 1163 bool isTransparent() const { return style()->hasOpacity(); }
(...skipping 77 matching lines...) Expand 10 before | Expand all | Expand 10 after
1241 if (parent()) 1241 if (parent())
1242 parent()->removeChild(this); 1242 parent()->removeChild(this);
1243 } 1243 }
1244 1244
1245 bool visibleToHitTestRequest(const HitTestRequest& request) const { return s tyle()->visibility() == VISIBLE && (request.ignorePointerEventsNone() || style() ->pointerEvents() != PE_NONE) && !isInert(); } 1245 bool visibleToHitTestRequest(const HitTestRequest& request) const { return s tyle()->visibility() == VISIBLE && (request.ignorePointerEventsNone() || style() ->pointerEvents() != PE_NONE) && !isInert(); }
1246 1246
1247 bool visibleToHitTesting() const { return style()->visibility() == VISIBLE & & style()->pointerEvents() != PE_NONE && !isInert(); } 1247 bool visibleToHitTesting() const { return style()->visibility() == VISIBLE & & style()->pointerEvents() != PE_NONE && !isInert(); }
1248 1248
1249 // Map points and quads through elements, potentially via 3d transforms. You should never need to call these directly; use 1249 // Map points and quads through elements, potentially via 3d transforms. You should never need to call these directly; use
1250 // localToAbsolute/absoluteToLocal methods instead. 1250 // localToAbsolute/absoluteToLocal methods instead.
1251 virtual void mapLocalToAncestor(const LayoutBoxModelObject* ancestor, Transf ormState&, MapCoordinatesFlags = ApplyContainerFlip, bool* wasFixed = nullptr, c onst PaintInvalidationState* = nullptr) const; 1251 virtual void mapLocalToAncestor(const LayoutBoxModelObject* ancestor, Transf ormState&, MapCoordinatesFlags = ApplyContainerFlip, bool* wasFixed = nullptr) c onst;
1252 // If the LayoutBoxModelObject ancestor is non-null, the input quad is in th e space of the ancestor. 1252 // If the LayoutBoxModelObject ancestor is non-null, the input quad is in th e space of the ancestor.
1253 // Otherwise: 1253 // Otherwise:
1254 // If TraverseDocumentBoundaries is specified, the input quad is in the sp ace of the local root frame. 1254 // If TraverseDocumentBoundaries is specified, the input quad is in the sp ace of the local root frame.
1255 // Otherwise, the input quad is in the space of the containing frame. 1255 // Otherwise, the input quad is in the space of the containing frame.
1256 virtual void mapAncestorToLocal(const LayoutBoxModelObject*, TransformState& , MapCoordinatesFlags = ApplyContainerFlip) const; 1256 virtual void mapAncestorToLocal(const LayoutBoxModelObject*, TransformState& , MapCoordinatesFlags = ApplyContainerFlip) const;
1257 void mapAbsoluteToLocalPoint(MapCoordinatesFlags flags, TransformState& tran sformState) const 1257 void mapAbsoluteToLocalPoint(MapCoordinatesFlags flags, TransformState& tran sformState) const
1258 { 1258 {
1259 return mapAncestorToLocal(nullptr, transformState, flags); 1259 return mapAncestorToLocal(nullptr, transformState, flags);
1260 } 1260 }
1261 1261
(...skipping 96 matching lines...) Expand 10 before | Expand all | Expand 10 after
1358 return paintInvalidationState.forcedSubtreeInvalidationWithinContainer() 1358 return paintInvalidationState.forcedSubtreeInvalidationWithinContainer()
1359 || paintInvalidationState.forcedSubtreeInvalidationRectUpdateWithinC ontainer() 1359 || paintInvalidationState.forcedSubtreeInvalidationRectUpdateWithinC ontainer()
1360 || shouldCheckForPaintInvalidationRegardlessOfPaintInvalidationState (); 1360 || shouldCheckForPaintInvalidationRegardlessOfPaintInvalidationState ();
1361 } 1361 }
1362 1362
1363 bool shouldCheckForPaintInvalidationRegardlessOfPaintInvalidationState() con st 1363 bool shouldCheckForPaintInvalidationRegardlessOfPaintInvalidationState() con st
1364 { 1364 {
1365 return mayNeedPaintInvalidation() || shouldDoFullPaintInvalidation() || shouldInvalidateSelection() || m_bitfields.childShouldCheckForPaintInvalidation( ); 1365 return mayNeedPaintInvalidation() || shouldDoFullPaintInvalidation() || shouldInvalidateSelection() || m_bitfields.childShouldCheckForPaintInvalidation( );
1366 } 1366 }
1367 1367
1368 virtual bool supportsPaintInvalidationStateCachedOffsets() const { return !h asTransformRelatedProperty() && !hasReflection() && !style()->isFlippedBlocksWri tingMode(); }
1369
1370 virtual LayoutRect viewRect() const; 1368 virtual LayoutRect viewRect() const;
1371 1369
1372 void invalidateDisplayItemClient(const DisplayItemClient&) const; 1370 void invalidateDisplayItemClient(const DisplayItemClient&) const;
1373 void invalidateDisplayItemClientsIncludingNonCompositingDescendants(const La youtBoxModelObject* paintInvalidationContainer, PaintInvalidationReason) const; 1371 void invalidateDisplayItemClientsIncludingNonCompositingDescendants(const La youtBoxModelObject* paintInvalidationContainer, PaintInvalidationReason) const;
1374 1372
1375 // Called before anonymousChild.setStyle(). Override to set custom styles fo r the child. 1373 // Called before anonymousChild.setStyle(). Override to set custom styles fo r the child.
1376 virtual void updateAnonymousChildStyle(const LayoutObject& anonymousChild, C omputedStyle& style) const { } 1374 virtual void updateAnonymousChildStyle(const LayoutObject& anonymousChild, C omputedStyle& style) const { }
1377 1375
1378 // Painters can use const methods only, except for these explicitly declared methods. 1376 // Painters can use const methods only, except for these explicitly declared methods.
1379 class MutableForPainting { 1377 class MutableForPainting {
(...skipping 788 matching lines...) Expand 10 before | Expand all | Expand 10 after
2168 void showTree(const blink::LayoutObject*); 2166 void showTree(const blink::LayoutObject*);
2169 void showLineTree(const blink::LayoutObject*); 2167 void showLineTree(const blink::LayoutObject*);
2170 void showLayoutTree(const blink::LayoutObject* object1); 2168 void showLayoutTree(const blink::LayoutObject* object1);
2171 // We don't make object2 an optional parameter so that showLayoutTree 2169 // We don't make object2 an optional parameter so that showLayoutTree
2172 // can be called from gdb easily. 2170 // can be called from gdb easily.
2173 void showLayoutTree(const blink::LayoutObject* object1, const blink::LayoutObjec t* object2); 2171 void showLayoutTree(const blink::LayoutObject* object1, const blink::LayoutObjec t* object2);
2174 2172
2175 #endif 2173 #endif
2176 2174
2177 #endif // LayoutObject_h 2175 #endif // LayoutObject_h
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698