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

Side by Side Diff: third_party/WebKit/Source/platform/graphics/Canvas2DLayerBridgeTest.cpp

Issue 1307143005: Make 2D canvas smarter about chosing whether or not to use GPU acceleration (Closed) Base URL: svn://svn.chromium.org/blink/trunk
Patch Set: migrated to chromium repo Created 5 years, 2 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
OLDNEW
1 /* 1 /*
2 * Copyright (C) 2011 Google Inc. All rights reserved. 2 * Copyright (C) 2011 Google 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 15 matching lines...) Expand all
26 #include "platform/graphics/Canvas2DLayerBridge.h" 26 #include "platform/graphics/Canvas2DLayerBridge.h"
27 27
28 #include "SkSurface.h" 28 #include "SkSurface.h"
29 #include "platform/graphics/ImageBuffer.h" 29 #include "platform/graphics/ImageBuffer.h"
30 #include "platform/graphics/test/MockWebGraphicsContext3D.h" 30 #include "platform/graphics/test/MockWebGraphicsContext3D.h"
31 #include "public/platform/Platform.h" 31 #include "public/platform/Platform.h"
32 #include "public/platform/WebExternalBitmap.h" 32 #include "public/platform/WebExternalBitmap.h"
33 #include "public/platform/WebGraphicsContext3DProvider.h" 33 #include "public/platform/WebGraphicsContext3DProvider.h"
34 #include "public/platform/WebThread.h" 34 #include "public/platform/WebThread.h"
35 #include "third_party/skia/include/core/SkDevice.h" 35 #include "third_party/skia/include/core/SkDevice.h"
36 #include "third_party/skia/include/gpu/GrContext.h"
37 #include "third_party/skia/include/gpu/gl/SkNullGLContext.h"
36 #include "wtf/RefPtr.h" 38 #include "wtf/RefPtr.h"
37 #include <gmock/gmock.h> 39 #include <gmock/gmock.h>
38 #include <gtest/gtest.h> 40 #include <gtest/gtest.h>
39 41
40 using testing::InSequence; 42 using testing::InSequence;
41 using testing::Return; 43 using testing::Return;
42 using testing::Test; 44 using testing::Test;
43 45
44 namespace blink { 46 namespace blink {
45 47
46 namespace { 48 namespace {
47 49
48 class MockCanvasContext : public MockWebGraphicsContext3D { 50 class MockCanvasContext : public MockWebGraphicsContext3D {
49 public: 51 public:
50 MOCK_METHOD0(flush, void(void)); 52 MOCK_METHOD0(flush, void(void));
51 MOCK_METHOD0(createTexture, unsigned(void)); 53 MOCK_METHOD0(createTexture, unsigned(void));
52 MOCK_METHOD1(deleteTexture, void(unsigned)); 54 MOCK_METHOD1(deleteTexture, void(unsigned));
53 }; 55 };
54 56
55 class MockWebGraphicsContext3DProvider : public WebGraphicsContext3DProvider { 57 class MockWebGraphicsContext3DProvider : public WebGraphicsContext3DProvider {
56 public: 58 public:
57 MockWebGraphicsContext3DProvider(WebGraphicsContext3D* context3d) 59 MockWebGraphicsContext3DProvider(WebGraphicsContext3D* context3d)
58 : m_context3d(context3d) { } 60 : m_context3d(context3d)
61 {
62 RefPtr<SkGLContext> glContext = adoptRef(SkNullGLContext::Create(kNone_G rGLStandard));
63 glContext->makeCurrent();
64 m_grContext = adoptRef(GrContext::Create(kOpenGL_GrBackend, reinterpret_ cast<GrBackendContext>(glContext->gl())));
65 }
59 66
60 WebGraphicsContext3D* context3d() override 67 WebGraphicsContext3D* context3d() override
61 { 68 {
62 return m_context3d; 69 return m_context3d;
63 } 70 }
64 71
65 GrContext* grContext() override 72 GrContext* grContext() override
66 { 73 {
67 return 0; 74 return m_grContext.get();
68 } 75 }
69 76
70 private: 77 private:
71 WebGraphicsContext3D* m_context3d; 78 WebGraphicsContext3D* m_context3d;
79 RefPtr<GrContext> m_grContext;
72 }; 80 };
73 81
74 class Canvas2DLayerBridgePtr { 82 class Canvas2DLayerBridgePtr {
75 public: 83 public:
76 Canvas2DLayerBridgePtr(PassRefPtr<Canvas2DLayerBridge> layerBridge) 84 Canvas2DLayerBridgePtr(PassRefPtr<Canvas2DLayerBridge> layerBridge)
77 : m_layerBridge(layerBridge) { } 85 : m_layerBridge(layerBridge) { }
78 86
79 ~Canvas2DLayerBridgePtr() 87 ~Canvas2DLayerBridgePtr()
80 { 88 {
81 m_layerBridge->beginDestruction(); 89 m_layerBridge->beginDestruction();
(...skipping 24 matching lines...) Expand all
106 }; 114 };
107 115
108 } // anonymous namespace 116 } // anonymous namespace
109 117
110 class Canvas2DLayerBridgeTest : public Test { 118 class Canvas2DLayerBridgeTest : public Test {
111 protected: 119 protected:
112 void fullLifecycleTest() 120 void fullLifecycleTest()
113 { 121 {
114 MockCanvasContext mainMock; 122 MockCanvasContext mainMock;
115 OwnPtr<MockWebGraphicsContext3DProvider> mainMockProvider = adoptPtr(new MockWebGraphicsContext3DProvider(&mainMock)); 123 OwnPtr<MockWebGraphicsContext3DProvider> mainMockProvider = adoptPtr(new MockWebGraphicsContext3DProvider(&mainMock));
116 RefPtr<SkSurface> surface = adoptRef(SkSurface::NewRasterN32Premul(300, 150));
117 124
118 ::testing::Mock::VerifyAndClearExpectations(&mainMock); 125 ::testing::Mock::VerifyAndClearExpectations(&mainMock);
119 126
120 { 127 {
121 Canvas2DLayerBridgePtr bridge(adoptRef(new Canvas2DLayerBridge(mainM ockProvider.release(), surface, 0, NonOpaque))); 128 Canvas2DLayerBridgePtr bridge(adoptRef(new Canvas2DLayerBridge(mainM ockProvider.release(), IntSize(300, 150), 0, NonOpaque, Canvas2DLayerBridge::Dis ableAcceleration)));
122 129
123 ::testing::Mock::VerifyAndClearExpectations(&mainMock); 130 ::testing::Mock::VerifyAndClearExpectations(&mainMock);
124 131
125 unsigned textureId = bridge->newImageSnapshot()->getTextureHandle(tr ue); 132 unsigned textureId = bridge->newImageSnapshot(PreferAcceleration)->g etTextureHandle(true);
126 EXPECT_EQ(textureId, 0u); 133 EXPECT_EQ(textureId, 0u);
127 134
128 ::testing::Mock::VerifyAndClearExpectations(&mainMock); 135 ::testing::Mock::VerifyAndClearExpectations(&mainMock);
129 } // bridge goes out of scope here 136 } // bridge goes out of scope here
130 137
131 ::testing::Mock::VerifyAndClearExpectations(&mainMock); 138 ::testing::Mock::VerifyAndClearExpectations(&mainMock);
132 } 139 }
133 140
141 void fallbackToSoftwareIfContextLost()
142 {
143 MockCanvasContext mainMock;
144 OwnPtr<MockWebGraphicsContext3DProvider> mainMockProvider = adoptPtr(new MockWebGraphicsContext3DProvider(&mainMock));
145
146 ::testing::Mock::VerifyAndClearExpectations(&mainMock);
147
148 {
149 mainMock.fakeContextLost();
150 Canvas2DLayerBridgePtr bridge(adoptRef(new Canvas2DLayerBridge(mainM ockProvider.release(), IntSize(300, 150), 0, NonOpaque, Canvas2DLayerBridge::Ena bleAcceleration)));
151 ::testing::Mock::VerifyAndClearExpectations(&mainMock);
152 EXPECT_TRUE(bridge->checkSurfaceValid());
153 EXPECT_FALSE(bridge->isAccelerated());
154 ::testing::Mock::VerifyAndClearExpectations(&mainMock);
155 }
156
157 ::testing::Mock::VerifyAndClearExpectations(&mainMock);
158 }
159
160 void fallbackToSoftwareOnFailedTextureAlloc()
161 {
162 MockCanvasContext mainMock;
163
164 ::testing::Mock::VerifyAndClearExpectations(&mainMock);
165
166 {
167 // No fallback case
168 OwnPtr<MockWebGraphicsContext3DProvider> mainMockProvider = adoptPtr (new MockWebGraphicsContext3DProvider(&mainMock));
169 Canvas2DLayerBridgePtr bridge(adoptRef(new Canvas2DLayerBridge(mainM ockProvider.release(), IntSize(300, 150), 0, NonOpaque, Canvas2DLayerBridge::Ena bleAcceleration)));
170 ::testing::Mock::VerifyAndClearExpectations(&mainMock);
171 EXPECT_TRUE(bridge->checkSurfaceValid());
172 EXPECT_TRUE(bridge->isAccelerated());
173 ::testing::Mock::VerifyAndClearExpectations(&mainMock);
174 RefPtr<SkImage> snapshot = bridge->newImageSnapshot(PreferAccelerati on);
175 EXPECT_TRUE(bridge->isAccelerated());
176 EXPECT_TRUE(snapshot->isTextureBacked());
177 }
178
179 {
180 // Fallback case
181 OwnPtr<MockWebGraphicsContext3DProvider> mainMockProvider = adoptPtr (new MockWebGraphicsContext3DProvider(&mainMock));
182 GrContext* gr = mainMockProvider->grContext();
183 Canvas2DLayerBridgePtr bridge(adoptRef(new Canvas2DLayerBridge(mainM ockProvider.release(), IntSize(300, 150), 0, NonOpaque, Canvas2DLayerBridge::Ena bleAcceleration)));
184 ::testing::Mock::VerifyAndClearExpectations(&mainMock);
185 EXPECT_TRUE(bridge->checkSurfaceValid());
186 EXPECT_TRUE(bridge->isAccelerated()); // We don't yet know that allo cation will fail
187 ::testing::Mock::VerifyAndClearExpectations(&mainMock);
188 gr->abandonContext(); // This will cause SkSurface_Gpu creation to f ail without Canvas2DLayerBridge otherwise detecting that anything was disabled.
189 RefPtr<SkImage> snapshot = bridge->newImageSnapshot(PreferAccelerati on);
190 EXPECT_FALSE(bridge->isAccelerated());
191 EXPECT_FALSE(snapshot->isTextureBacked());
192 }
193
194 ::testing::Mock::VerifyAndClearExpectations(&mainMock);
195 }
196
134 void noDrawOnContextLostTest() 197 void noDrawOnContextLostTest()
135 { 198 {
136 MockCanvasContext mainMock; 199 MockCanvasContext mainMock;
137 OwnPtr<MockWebGraphicsContext3DProvider> mainMockProvider = adoptPtr(new MockWebGraphicsContext3DProvider(&mainMock)); 200 OwnPtr<MockWebGraphicsContext3DProvider> mainMockProvider = adoptPtr(new MockWebGraphicsContext3DProvider(&mainMock));
138 RefPtr<SkSurface> surface = adoptRef(SkSurface::NewRasterN32Premul(300, 150));
139 201
140 ::testing::Mock::VerifyAndClearExpectations(&mainMock); 202 ::testing::Mock::VerifyAndClearExpectations(&mainMock);
141 203
142 { 204 {
143 Canvas2DLayerBridgePtr bridge(adoptRef(new Canvas2DLayerBridge(mainM ockProvider.release(), surface, 0, NonOpaque))); 205 Canvas2DLayerBridgePtr bridge(adoptRef(new Canvas2DLayerBridge(mainM ockProvider.release(), IntSize(300, 150), 0, NonOpaque, Canvas2DLayerBridge::For ceAccelerationForTesting)));
144 ::testing::Mock::VerifyAndClearExpectations(&mainMock); 206 ::testing::Mock::VerifyAndClearExpectations(&mainMock);
145 EXPECT_TRUE(bridge->checkSurfaceValid()); 207 EXPECT_TRUE(bridge->checkSurfaceValid());
146 SkPaint paint; 208 SkPaint paint;
147 uint32_t genID = surface->generationID(); 209 uint32_t genID = bridge->getOrCreateSurface()->generationID();
148 bridge->canvas()->drawRect(SkRect::MakeXYWH(0, 0, 1, 1), paint); 210 bridge->canvas()->drawRect(SkRect::MakeXYWH(0, 0, 1, 1), paint);
149 EXPECT_EQ(genID, surface->generationID()); 211 EXPECT_EQ(genID, bridge->getOrCreateSurface()->generationID());
150 mainMock.fakeContextLost(); 212 mainMock.fakeContextLost();
151 EXPECT_EQ(genID, surface->generationID()); 213 EXPECT_EQ(genID, bridge->getOrCreateSurface()->generationID());
152 bridge->canvas()->drawRect(SkRect::MakeXYWH(0, 0, 1, 1), paint); 214 bridge->canvas()->drawRect(SkRect::MakeXYWH(0, 0, 1, 1), paint);
153 EXPECT_EQ(genID, surface->generationID()); 215 EXPECT_EQ(genID, bridge->getOrCreateSurface()->generationID());
154 EXPECT_FALSE(bridge->checkSurfaceValid()); 216 EXPECT_FALSE(bridge->checkSurfaceValid()); // This results in the in ternal surface being torn down in response to the context loss
155 EXPECT_EQ(genID, surface->generationID()); 217 EXPECT_EQ(nullptr, bridge->getOrCreateSurface());
218 // The following passes by not crashing
156 bridge->canvas()->drawRect(SkRect::MakeXYWH(0, 0, 1, 1), paint); 219 bridge->canvas()->drawRect(SkRect::MakeXYWH(0, 0, 1, 1), paint);
157 EXPECT_EQ(genID, surface->generationID());
158 bridge->flush(); 220 bridge->flush();
159 EXPECT_EQ(genID, surface->generationID());
160 ::testing::Mock::VerifyAndClearExpectations(&mainMock); 221 ::testing::Mock::VerifyAndClearExpectations(&mainMock);
161 } 222 }
162 223
163 ::testing::Mock::VerifyAndClearExpectations(&mainMock); 224 ::testing::Mock::VerifyAndClearExpectations(&mainMock);
164 } 225 }
165 226
166 void prepareMailboxWithBitmapTest() 227 void prepareMailboxWithBitmapTest()
167 { 228 {
168 MockCanvasContext mainMock; 229 MockCanvasContext mainMock;
169 RefPtr<SkSurface> surface = adoptRef(SkSurface::NewRasterN32Premul(300, 150));
170 OwnPtr<MockWebGraphicsContext3DProvider> mainMockProvider = adoptPtr(new MockWebGraphicsContext3DProvider(&mainMock)); 230 OwnPtr<MockWebGraphicsContext3DProvider> mainMockProvider = adoptPtr(new MockWebGraphicsContext3DProvider(&mainMock));
171 Canvas2DLayerBridgePtr bridge(adoptRef(new Canvas2DLayerBridge(mainMockP rovider.release(), surface, 0, NonOpaque))); 231 Canvas2DLayerBridgePtr bridge(adoptRef(new Canvas2DLayerBridge(mainMockP rovider.release(), IntSize(300, 150), 0, NonOpaque, Canvas2DLayerBridge::ForceAc celerationForTesting)));
172 bridge->m_lastImageId = 1; 232 bridge->m_lastImageId = 1;
173 233
174 NullWebExternalBitmap bitmap; 234 NullWebExternalBitmap bitmap;
175 bridge->prepareMailbox(0, &bitmap); 235 bridge->prepareMailbox(0, &bitmap);
176 EXPECT_EQ(0u, bridge->m_lastImageId); 236 EXPECT_EQ(0u, bridge->m_lastImageId);
177 } 237 }
178 238
179 void prepareMailboxAndLoseResourceTest() 239 void prepareMailboxAndLoseResourceTest()
180 { 240 {
181 MockCanvasContext mainMock; 241 MockCanvasContext mainMock;
182 RefPtr<SkSurface> surface = adoptRef(SkSurface::NewRasterN32Premul(300, 150));
183 bool lostResource = true; 242 bool lostResource = true;
184 243
185 // Prepare a mailbox, then report the resource as lost. 244 // Prepare a mailbox, then report the resource as lost.
186 // This test passes by not crashing and not triggering assertions. 245 // This test passes by not crashing and not triggering assertions.
187 { 246 {
188 WebExternalTextureMailbox mailbox; 247 WebExternalTextureMailbox mailbox;
189 OwnPtr<MockWebGraphicsContext3DProvider> mainMockProvider = adoptPtr (new MockWebGraphicsContext3DProvider(&mainMock)); 248 OwnPtr<MockWebGraphicsContext3DProvider> mainMockProvider = adoptPtr (new MockWebGraphicsContext3DProvider(&mainMock));
190 Canvas2DLayerBridgePtr bridge(adoptRef(new Canvas2DLayerBridge(mainM ockProvider.release(), surface, 0, NonOpaque))); 249 Canvas2DLayerBridgePtr bridge(adoptRef(new Canvas2DLayerBridge(mainM ockProvider.release(), IntSize(300, 150), 0, NonOpaque, Canvas2DLayerBridge::For ceAccelerationForTesting)));
191 bridge->prepareMailbox(&mailbox, 0); 250 bridge->prepareMailbox(&mailbox, 0);
192 bridge->mailboxReleased(mailbox, lostResource); 251 bridge->mailboxReleased(mailbox, lostResource);
193 } 252 }
194 253
195 // Retry with mailbox released while bridge destruction is in progress 254 // Retry with mailbox released while bridge destruction is in progress
196 { 255 {
197 OwnPtr<MockWebGraphicsContext3DProvider> mainMockProvider = adoptPtr (new MockWebGraphicsContext3DProvider(&mainMock)); 256 OwnPtr<MockWebGraphicsContext3DProvider> mainMockProvider = adoptPtr (new MockWebGraphicsContext3DProvider(&mainMock));
198 WebExternalTextureMailbox mailbox; 257 WebExternalTextureMailbox mailbox;
199 Canvas2DLayerBridge* rawBridge; 258 Canvas2DLayerBridge* rawBridge;
200 { 259 {
201 Canvas2DLayerBridgePtr bridge(adoptRef(new Canvas2DLayerBridge(m ainMockProvider.release(), surface, 0, NonOpaque))); 260 Canvas2DLayerBridgePtr bridge(adoptRef(new Canvas2DLayerBridge(m ainMockProvider.release(), IntSize(300, 150), 0, NonOpaque, Canvas2DLayerBridge: :ForceAccelerationForTesting)));
202 bridge->prepareMailbox(&mailbox, 0); 261 bridge->prepareMailbox(&mailbox, 0);
203 rawBridge = bridge.get(); 262 rawBridge = bridge.get();
204 } // bridge goes out of scope, but object is kept alive by self refe rences 263 } // bridge goes out of scope, but object is kept alive by self refe rences
205 // before fixing crbug.com/411864, the following line you cause a me mory use after free 264 // before fixing crbug.com/411864, the following line you cause a me mory use after free
206 // that sometimes causes a crash in normal builds and crashes consis tently with ASAN. 265 // that sometimes causes a crash in normal builds and crashes consis tently with ASAN.
207 rawBridge->mailboxReleased(mailbox, lostResource); // This should se lf-destruct the bridge. 266 rawBridge->mailboxReleased(mailbox, lostResource); // This should se lf-destruct the bridge.
208 } 267 }
209 } 268 }
269
270 void accelerationHintTest()
271 {
272 MockCanvasContext mainMock;
273 {
274
275 OwnPtr<MockWebGraphicsContext3DProvider> mainMockProvider = adoptPtr (new MockWebGraphicsContext3DProvider(&mainMock));
276 ::testing::Mock::VerifyAndClearExpectations(&mainMock);
277 Canvas2DLayerBridgePtr bridge(adoptRef(new Canvas2DLayerBridge(mainM ockProvider.release(), IntSize(300, 300), 0, NonOpaque, Canvas2DLayerBridge::Ena bleAcceleration)));
278 ::testing::Mock::VerifyAndClearExpectations(&mainMock);
279 SkPaint paint;
280 bridge->canvas()->drawRect(SkRect::MakeXYWH(0, 0, 1, 1), paint);
281 RefPtr<SkImage> image = bridge->newImageSnapshot(PreferAcceleration) ;
282 ::testing::Mock::VerifyAndClearExpectations(&mainMock);
283 EXPECT_TRUE(bridge->checkSurfaceValid());
284 EXPECT_TRUE(bridge->isAccelerated());
285 }
286 ::testing::Mock::VerifyAndClearExpectations(&mainMock);
287
288 {
289 OwnPtr<MockWebGraphicsContext3DProvider> mainMockProvider = adoptPtr (new MockWebGraphicsContext3DProvider(&mainMock));
290 ::testing::Mock::VerifyAndClearExpectations(&mainMock);
291 Canvas2DLayerBridgePtr bridge(adoptRef(new Canvas2DLayerBridge(mainM ockProvider.release(), IntSize(300, 300), 0, NonOpaque, Canvas2DLayerBridge::Ena bleAcceleration)));
292 ::testing::Mock::VerifyAndClearExpectations(&mainMock);
293 SkPaint paint;
294 bridge->canvas()->drawRect(SkRect::MakeXYWH(0, 0, 1, 1), paint);
295 RefPtr<SkImage> image = bridge->newImageSnapshot(PreferNoAcceleratio n);
296 ::testing::Mock::VerifyAndClearExpectations(&mainMock);
297 EXPECT_TRUE(bridge->checkSurfaceValid());
298 EXPECT_FALSE(bridge->isAccelerated());
299 }
300 ::testing::Mock::VerifyAndClearExpectations(&mainMock);
301 }
210 }; 302 };
211 303
212 TEST_F(Canvas2DLayerBridgeTest, testFullLifecycleSingleThreaded) 304 TEST_F(Canvas2DLayerBridgeTest, FullLifecycleSingleThreaded)
213 { 305 {
214 fullLifecycleTest(); 306 fullLifecycleTest();
215 } 307 }
216 308
217 TEST_F(Canvas2DLayerBridgeTest, testNoDrawOnContextLost) 309 TEST_F(Canvas2DLayerBridgeTest, NoDrawOnContextLost)
218 { 310 {
219 noDrawOnContextLostTest(); 311 noDrawOnContextLostTest();
220 } 312 }
221 313
222 TEST_F(Canvas2DLayerBridgeTest, testPrepareMailboxWithBitmap) 314 TEST_F(Canvas2DLayerBridgeTest, PrepareMailboxWithBitmap)
223 { 315 {
224 prepareMailboxWithBitmapTest(); 316 prepareMailboxWithBitmapTest();
225 } 317 }
226 318
227 TEST_F(Canvas2DLayerBridgeTest, testPrepareMailboxAndLoseResource) 319 TEST_F(Canvas2DLayerBridgeTest, PrepareMailboxAndLoseResource)
228 { 320 {
229 prepareMailboxAndLoseResourceTest(); 321 prepareMailboxAndLoseResourceTest();
230 } 322 }
231 323
324 TEST_F(Canvas2DLayerBridgeTest, AccelerationHint)
325 {
326 accelerationHintTest();
327 }
328
329 TEST_F(Canvas2DLayerBridgeTest, FallbackToSoftwareIfContextLost)
330 {
331 fallbackToSoftwareIfContextLost();
332 }
333
334 TEST_F(Canvas2DLayerBridgeTest, FallbackToSoftwareOnFailedTextureAlloc)
335 {
336 fallbackToSoftwareOnFailedTextureAlloc();
337 }
338
232 } // namespace blink 339 } // namespace blink
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698