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

Side by Side Diff: content/renderer/gpu/queue_message_swap_promise_unittest.cc

Issue 1126963006: Move VISUAL_STATE promise to activation (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Current state (presubmit warnings, cc_unittests tests failing) Created 5 years, 7 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 2014 The Chromium Authors. All rights reserved. 1 // Copyright 2014 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 "content/renderer/gpu/queue_message_swap_promise.h" 5 #include "content/renderer/gpu/queue_message_swap_promise.h"
6 6
7 #include <vector> 7 #include <vector>
8 8
9 #include "base/memory/scoped_vector.h" 9 #include "base/memory/scoped_vector.h"
10 #include "cc/output/swap_promise.h" 10 #include "cc/output/swap_promise.h"
11 #include "content/renderer/gpu/frame_swap_message_queue.h" 11 #include "content/renderer/gpu/frame_update_message_queue.h"
12 #include "content/renderer/gpu/render_widget_compositor.h" 12 #include "content/renderer/gpu/render_widget_compositor.h"
13 #include "content/renderer/render_widget.h" 13 #include "content/renderer/render_widget.h"
14 #include "content/test/mock_render_process.h" 14 #include "content/test/mock_render_process.h"
15 #include "ipc/ipc_message.h" 15 #include "ipc/ipc_message.h"
16 #include "ipc/ipc_sync_message_filter.h" 16 #include "ipc/ipc_sync_message_filter.h"
17 #include "ipc/ipc_test_sink.h" 17 #include "ipc/ipc_test_sink.h"
18 #include "testing/gtest/include/gtest/gtest.h" 18 #include "testing/gtest/include/gtest/gtest.h"
19 19
20 namespace content { 20 namespace content {
21 21
(...skipping 27 matching lines...) Expand all
49 }; 49 };
50 50
51 struct QueueMessageData { 51 struct QueueMessageData {
52 MessageDeliveryPolicy policy; 52 MessageDeliveryPolicy policy;
53 int source_frame_number; 53 int source_frame_number;
54 }; 54 };
55 55
56 class QueueMessageSwapPromiseTest : public testing::Test { 56 class QueueMessageSwapPromiseTest : public testing::Test {
57 public: 57 public:
58 QueueMessageSwapPromiseTest() 58 QueueMessageSwapPromiseTest()
59 : frame_swap_message_queue_(new FrameSwapMessageQueue()), 59 : frame_update_message_queue_(new FrameUpdateMessageQueue()),
60 sync_message_filter_(new TestSyncMessageFilter()) {} 60 sync_message_filter_(new TestSyncMessageFilter()) {}
61 61
62 ~QueueMessageSwapPromiseTest() override {} 62 ~QueueMessageSwapPromiseTest() override {}
63 63
64 scoped_ptr<cc::SwapPromise> QueueMessageImpl(IPC::Message* msg, 64 scoped_ptr<cc::SwapPromise> QueueMessageImpl(IPC::Message* msg,
65 MessageDeliveryPolicy policy, 65 MessageDeliveryPolicy policy,
66 int source_frame_number) { 66 int source_frame_number) {
67 return TestRenderWidget::QueueMessageImpl(msg, 67 return TestRenderWidget::QueueMessageImpl(msg,
68 policy, 68 policy,
69 frame_swap_message_queue_.get(), 69 frame_update_message_queue_.get(),
70 sync_message_filter_, 70 sync_message_filter_,
71 source_frame_number).Pass(); 71 source_frame_number).Pass();
72 } 72 }
73 73
74 ScopedVector<IPC::Message>& DirectSendMessages() { 74 ScopedVector<IPC::Message>& DirectSendMessages() {
75 return sync_message_filter_->messages(); 75 return sync_message_filter_->messages();
76 } 76 }
77 77
78 ScopedVector<IPC::Message>& NextSwapMessages() { 78 ScopedVector<IPC::Message>& NextSwapMessages() {
79 next_swap_messages_.clear(); 79 next_swap_messages_.clear();
80 scoped_ptr<FrameSwapMessageQueue::SendMessageScope> send_message_scope = 80 scoped_ptr<FrameUpdateMessageQueue::SendMessageScope> send_message_scope =
81 frame_swap_message_queue_->AcquireSendMessageScope(); 81 frame_update_message_queue_->AcquireSendMessageScope();
82 frame_swap_message_queue_->DrainMessages(&next_swap_messages_); 82 frame_update_message_queue_->DrainMessagesForSwap(&next_swap_messages_);
83 return next_swap_messages_; 83 return next_swap_messages_;
84 } 84 }
85 85
86 bool ContainsMessage(const ScopedVector<IPC::Message>& messages, 86 bool ContainsMessage(const ScopedVector<IPC::Message>& messages,
87 const IPC::Message& message) { 87 const IPC::Message& message) {
88 if (messages.empty()) 88 if (messages.empty())
89 return false; 89 return false;
90 for (ScopedVector<IPC::Message>::const_iterator i = messages.begin(); 90 for (ScopedVector<IPC::Message>::const_iterator i = messages.begin();
91 i != messages.end(); 91 i != messages.end();
92 ++i) { 92 ++i) {
(...skipping 24 matching lines...) Expand all
117 ++i) { 117 ++i) {
118 if (*i) 118 if (*i)
119 (*i)->DidSwap(NULL); 119 (*i)->DidSwap(NULL);
120 } 120 }
121 } 121 }
122 122
123 protected: 123 protected:
124 void VisualStateSwapPromiseDidNotSwap( 124 void VisualStateSwapPromiseDidNotSwap(
125 cc::SwapPromise::DidNotSwapReason reason); 125 cc::SwapPromise::DidNotSwapReason reason);
126 126
127 scoped_refptr<FrameSwapMessageQueue> frame_swap_message_queue_; 127 scoped_refptr<FrameUpdateMessageQueue> frame_update_message_queue_;
128 scoped_refptr<TestSyncMessageFilter> sync_message_filter_; 128 scoped_refptr<TestSyncMessageFilter> sync_message_filter_;
129 std::vector<IPC::Message> messages_; 129 std::vector<IPC::Message> messages_;
130 ScopedVector<cc::SwapPromise> promises_; 130 ScopedVector<cc::SwapPromise> promises_;
131 131
132 private: 132 private:
133 ScopedVector<IPC::Message> next_swap_messages_; 133 ScopedVector<IPC::Message> next_swap_messages_;
134 134
135 DISALLOW_COPY_AND_ASSIGN(QueueMessageSwapPromiseTest); 135 DISALLOW_COPY_AND_ASSIGN(QueueMessageSwapPromiseTest);
136 }; 136 };
137 137
138 TEST_F(QueueMessageSwapPromiseTest, NextSwapPolicySchedulesMessageForNextSwap) { 138 TEST_F(QueueMessageSwapPromiseTest, NextSwapPolicySchedulesMessageForNextSwap) {
139 QueueMessageData data[] = { 139 QueueMessageData data[] = {
140 /* { policy, source_frame_number } */ 140 /* { policy, source_frame_number } */
141 {MESSAGE_DELIVERY_POLICY_WITH_NEXT_SWAP, 1}, 141 {MESSAGE_DELIVERY_POLICY_WITH_NEXT_SWAP, 1},
142 }; 142 };
143 QueueMessages(data, arraysize(data)); 143 QueueMessages(data, arraysize(data));
144 144
145 ASSERT_TRUE(promises_[0]); 145 ASSERT_TRUE(promises_[0]);
146 EXPECT_TRUE(DirectSendMessages().empty()); 146 EXPECT_TRUE(DirectSendMessages().empty());
147 EXPECT_FALSE(frame_swap_message_queue_->Empty()); 147 EXPECT_FALSE(frame_update_message_queue_->Empty());
148 EXPECT_TRUE(NextSwapHasMessage(messages_[0])); 148 EXPECT_TRUE(NextSwapHasMessage(messages_[0]));
149 149
150 CleanupPromises(); 150 CleanupPromises();
151 } 151 }
152 152
153 TEST_F(QueueMessageSwapPromiseTest, NextSwapPolicyNeedsAtMostOnePromise) { 153 TEST_F(QueueMessageSwapPromiseTest, NextSwapPolicyNeedsAtMostOnePromise) {
154 QueueMessageData data[] = { 154 QueueMessageData data[] = {
155 /* { policy, source_frame_number } */ 155 /* { policy, source_frame_number } */
156 {MESSAGE_DELIVERY_POLICY_WITH_NEXT_SWAP, 1}, 156 {MESSAGE_DELIVERY_POLICY_WITH_NEXT_SWAP, 1},
157 {MESSAGE_DELIVERY_POLICY_WITH_NEXT_SWAP, 1}, 157 {MESSAGE_DELIVERY_POLICY_WITH_NEXT_SWAP, 1},
158 }; 158 };
159 QueueMessages(data, arraysize(data)); 159 QueueMessages(data, arraysize(data));
160 160
161 ASSERT_TRUE(promises_[0]); 161 ASSERT_TRUE(promises_[0]);
162 ASSERT_FALSE(promises_[1]); 162 ASSERT_FALSE(promises_[1]);
163 163
164 CleanupPromises(); 164 CleanupPromises();
165 } 165 }
166 166
167 TEST_F(QueueMessageSwapPromiseTest, NextSwapPolicySendsMessageOnNoUpdate) { 167 TEST_F(QueueMessageSwapPromiseTest, NextSwapPolicySendsMessageOnNoUpdate) {
168 QueueMessageData data[] = { 168 QueueMessageData data[] = {
169 /* { policy, source_frame_number } */ 169 /* { policy, source_frame_number } */
170 {MESSAGE_DELIVERY_POLICY_WITH_NEXT_SWAP, 1}, 170 {MESSAGE_DELIVERY_POLICY_WITH_NEXT_SWAP, 1},
171 }; 171 };
172 QueueMessages(data, arraysize(data)); 172 QueueMessages(data, arraysize(data));
173 173
174 promises_[0]->DidNotSwap(cc::SwapPromise::COMMIT_NO_UPDATE); 174 promises_[0]->DidNotSwap(cc::SwapPromise::COMMIT_NO_UPDATE);
175 EXPECT_TRUE(ContainsMessage(DirectSendMessages(), messages_[0])); 175 EXPECT_TRUE(ContainsMessage(DirectSendMessages(), messages_[0]));
176 EXPECT_TRUE(NextSwapMessages().empty()); 176 EXPECT_TRUE(NextSwapMessages().empty());
177 EXPECT_TRUE(frame_swap_message_queue_->Empty()); 177 EXPECT_TRUE(frame_update_message_queue_->Empty());
178 } 178 }
179 179
180 TEST_F(QueueMessageSwapPromiseTest, NextSwapPolicySendsMessageOnSwapFails) { 180 TEST_F(QueueMessageSwapPromiseTest, NextSwapPolicySendsMessageOnSwapFails) {
181 QueueMessageData data[] = { 181 QueueMessageData data[] = {
182 /* { policy, source_frame_number } */ 182 /* { policy, source_frame_number } */
183 {MESSAGE_DELIVERY_POLICY_WITH_NEXT_SWAP, 1}, 183 {MESSAGE_DELIVERY_POLICY_WITH_NEXT_SWAP, 1},
184 }; 184 };
185 QueueMessages(data, arraysize(data)); 185 QueueMessages(data, arraysize(data));
186 186
187 promises_[0]->DidNotSwap(cc::SwapPromise::SWAP_FAILS); 187 promises_[0]->DidNotSwap(cc::SwapPromise::SWAP_FAILS);
188 EXPECT_TRUE(ContainsMessage(DirectSendMessages(), messages_[0])); 188 EXPECT_TRUE(ContainsMessage(DirectSendMessages(), messages_[0]));
189 EXPECT_TRUE(NextSwapMessages().empty()); 189 EXPECT_TRUE(NextSwapMessages().empty());
190 EXPECT_TRUE(frame_swap_message_queue_->Empty()); 190 EXPECT_TRUE(frame_update_message_queue_->Empty());
191 } 191 }
192 192
193 TEST_F(QueueMessageSwapPromiseTest, NextSwapPolicyRetainsMessageOnCommitFails) { 193 TEST_F(QueueMessageSwapPromiseTest, NextSwapPolicyRetainsMessageOnCommitFails) {
194 QueueMessageData data[] = { 194 QueueMessageData data[] = {
195 /* { policy, source_frame_number } */ 195 /* { policy, source_frame_number } */
196 {MESSAGE_DELIVERY_POLICY_WITH_NEXT_SWAP, 1}, 196 {MESSAGE_DELIVERY_POLICY_WITH_NEXT_SWAP, 1},
197 }; 197 };
198 QueueMessages(data, arraysize(data)); 198 QueueMessages(data, arraysize(data));
199 199
200 promises_[0]->DidNotSwap(cc::SwapPromise::COMMIT_FAILS); 200 promises_[0]->DidNotSwap(cc::SwapPromise::COMMIT_FAILS);
201 EXPECT_TRUE(DirectSendMessages().empty()); 201 EXPECT_TRUE(DirectSendMessages().empty());
202 EXPECT_FALSE(frame_swap_message_queue_->Empty()); 202 EXPECT_FALSE(frame_update_message_queue_->Empty());
203 EXPECT_TRUE(NextSwapHasMessage(messages_[0])); 203 EXPECT_TRUE(NextSwapHasMessage(messages_[0]));
204 } 204 }
205 205
206 TEST_F(QueueMessageSwapPromiseTest, 206 TEST_F(QueueMessageSwapPromiseTest,
207 VisualStateQueuesMessageWhenCommitRequested) { 207 VisualStateQueuesMessageWhenCommitRequested) {
208 QueueMessageData data[] = { 208 QueueMessageData data[] = {
209 /* { policy, source_frame_number } */ 209 /* { policy, source_frame_number } */
210 {MESSAGE_DELIVERY_POLICY_WITH_VISUAL_STATE, 1}, 210 {MESSAGE_DELIVERY_POLICY_WITH_VISUAL_STATE, 1},
211 }; 211 };
212 QueueMessages(data, arraysize(data)); 212 QueueMessages(data, arraysize(data));
213 213
214 ASSERT_TRUE(promises_[0]); 214 ASSERT_TRUE(promises_[0]);
215 EXPECT_TRUE(DirectSendMessages().empty()); 215 EXPECT_TRUE(DirectSendMessages().empty());
216 EXPECT_FALSE(frame_swap_message_queue_->Empty()); 216 EXPECT_FALSE(frame_update_message_queue_->Empty());
217 EXPECT_TRUE(NextSwapMessages().empty()); 217 EXPECT_TRUE(NextSwapMessages().empty());
218 218
219 CleanupPromises(); 219 CleanupPromises();
220 } 220 }
221 221
222 TEST_F(QueueMessageSwapPromiseTest, 222 TEST_F(QueueMessageSwapPromiseTest,
223 VisualStateQueuesMessageWhenOtherMessageAlreadyQueued) { 223 VisualStateQueuesMessageWhenOtherMessageAlreadyQueued) {
224 QueueMessageData data[] = { 224 QueueMessageData data[] = {
225 /* { policy, source_frame_number } */ 225 /* { policy, source_frame_number } */
226 {MESSAGE_DELIVERY_POLICY_WITH_VISUAL_STATE, 1}, 226 {MESSAGE_DELIVERY_POLICY_WITH_VISUAL_STATE, 1},
227 {MESSAGE_DELIVERY_POLICY_WITH_VISUAL_STATE, 1}, 227 {MESSAGE_DELIVERY_POLICY_WITH_VISUAL_STATE, 1},
228 }; 228 };
229 QueueMessages(data, arraysize(data)); 229 QueueMessages(data, arraysize(data));
230 230
231 EXPECT_TRUE(DirectSendMessages().empty()); 231 EXPECT_TRUE(DirectSendMessages().empty());
232 EXPECT_FALSE(frame_swap_message_queue_->Empty()); 232 EXPECT_FALSE(frame_update_message_queue_->Empty());
233 EXPECT_FALSE(NextSwapHasMessage(messages_[1])); 233 EXPECT_FALSE(NextSwapHasMessage(messages_[1]));
234 234
235 CleanupPromises(); 235 CleanupPromises();
236 } 236 }
237 237
238 TEST_F(QueueMessageSwapPromiseTest, VisualStateSwapPromiseDidSwap) { 238 TEST_F(QueueMessageSwapPromiseTest, VisualStateSwapPromiseDidSwap) {
239 QueueMessageData data[] = { 239 QueueMessageData data[] = {
240 /* { policy, source_frame_number } */ 240 /* { policy, source_frame_number } */
241 {MESSAGE_DELIVERY_POLICY_WITH_VISUAL_STATE, 1}, 241 {MESSAGE_DELIVERY_POLICY_WITH_VISUAL_STATE, 1},
242 {MESSAGE_DELIVERY_POLICY_WITH_VISUAL_STATE, 1}, 242 {MESSAGE_DELIVERY_POLICY_WITH_VISUAL_STATE, 1},
(...skipping 10 matching lines...) Expand all
253 EXPECT_TRUE(ContainsMessage(messages, messages_[1])); 253 EXPECT_TRUE(ContainsMessage(messages, messages_[1]));
254 EXPECT_FALSE(ContainsMessage(messages, messages_[2])); 254 EXPECT_FALSE(ContainsMessage(messages, messages_[2]));
255 255
256 promises_[2]->DidSwap(NULL); 256 promises_[2]->DidSwap(NULL);
257 messages.swap(NextSwapMessages()); 257 messages.swap(NextSwapMessages());
258 EXPECT_EQ(1u, messages.size()); 258 EXPECT_EQ(1u, messages.size());
259 EXPECT_TRUE(ContainsMessage(messages, messages_[2])); 259 EXPECT_TRUE(ContainsMessage(messages, messages_[2]));
260 260
261 EXPECT_TRUE(DirectSendMessages().empty()); 261 EXPECT_TRUE(DirectSendMessages().empty());
262 EXPECT_TRUE(NextSwapMessages().empty()); 262 EXPECT_TRUE(NextSwapMessages().empty());
263 EXPECT_TRUE(frame_swap_message_queue_->Empty()); 263 EXPECT_TRUE(frame_update_message_queue_->Empty());
264 } 264 }
265 265
266 void QueueMessageSwapPromiseTest::VisualStateSwapPromiseDidNotSwap( 266 void QueueMessageSwapPromiseTest::VisualStateSwapPromiseDidNotSwap(
267 cc::SwapPromise::DidNotSwapReason reason) { 267 cc::SwapPromise::DidNotSwapReason reason) {
268 QueueMessageData data[] = { 268 QueueMessageData data[] = {
269 /* { policy, source_frame_number } */ 269 /* { policy, source_frame_number } */
270 {MESSAGE_DELIVERY_POLICY_WITH_VISUAL_STATE, 1}, 270 {MESSAGE_DELIVERY_POLICY_WITH_VISUAL_STATE, 1},
271 {MESSAGE_DELIVERY_POLICY_WITH_VISUAL_STATE, 1}, 271 {MESSAGE_DELIVERY_POLICY_WITH_VISUAL_STATE, 1},
272 {MESSAGE_DELIVERY_POLICY_WITH_VISUAL_STATE, 2}, 272 {MESSAGE_DELIVERY_POLICY_WITH_VISUAL_STATE, 2},
273 }; 273 };
274 QueueMessages(data, arraysize(data)); 274 QueueMessages(data, arraysize(data));
275 275
276 promises_[0]->DidNotSwap(reason); 276 promises_[0]->DidNotSwap(reason);
277 ASSERT_FALSE(promises_[1]); 277 ASSERT_FALSE(promises_[1]);
278 EXPECT_TRUE(NextSwapMessages().empty()); 278 EXPECT_TRUE(NextSwapMessages().empty());
279 EXPECT_EQ(2u, DirectSendMessages().size()); 279 EXPECT_EQ(2u, DirectSendMessages().size());
280 EXPECT_TRUE(ContainsMessage(DirectSendMessages(), messages_[0])); 280 EXPECT_TRUE(ContainsMessage(DirectSendMessages(), messages_[0]));
281 EXPECT_TRUE(ContainsMessage(DirectSendMessages(), messages_[1])); 281 EXPECT_TRUE(ContainsMessage(DirectSendMessages(), messages_[1]));
282 EXPECT_FALSE(ContainsMessage(DirectSendMessages(), messages_[2])); 282 EXPECT_FALSE(ContainsMessage(DirectSendMessages(), messages_[2]));
283 283
284 promises_[2]->DidNotSwap(reason); 284 promises_[2]->DidNotSwap(reason);
285 EXPECT_TRUE(NextSwapMessages().empty()); 285 EXPECT_TRUE(NextSwapMessages().empty());
286 EXPECT_TRUE(ContainsMessage(DirectSendMessages(), messages_[2])); 286 EXPECT_TRUE(ContainsMessage(DirectSendMessages(), messages_[2]));
287 287
288 EXPECT_TRUE(NextSwapMessages().empty()); 288 EXPECT_TRUE(NextSwapMessages().empty());
289 EXPECT_TRUE(frame_swap_message_queue_->Empty()); 289 EXPECT_TRUE(frame_update_message_queue_->Empty());
290 } 290 }
291 291
292 TEST_F(QueueMessageSwapPromiseTest, VisalStateSwapPromiseDidNotSwapNoUpdate) { 292 TEST_F(QueueMessageSwapPromiseTest, VisalStateSwapPromiseDidNotSwapNoUpdate) {
293 VisualStateSwapPromiseDidNotSwap(cc::SwapPromise::COMMIT_NO_UPDATE); 293 VisualStateSwapPromiseDidNotSwap(cc::SwapPromise::COMMIT_NO_UPDATE);
294 } 294 }
295 295
296 TEST_F(QueueMessageSwapPromiseTest, 296 TEST_F(QueueMessageSwapPromiseTest,
297 VisualStateSwapPromiseDidNotSwapCommitFails) { 297 VisualStateSwapPromiseDidNotSwapCommitFails) {
298 // COMMIT_FAILS is treated differently: 298 // COMMIT_FAILS is treated differently:
299 // If we fail to swap with COMMIT_FAILS, then the renderer is 299 // If we fail to swap with COMMIT_FAILS, then the renderer is
(...skipping 14 matching lines...) Expand all
314 EXPECT_EQ(0u, DirectSendMessages().size()); 314 EXPECT_EQ(0u, DirectSendMessages().size());
315 EXPECT_FALSE(ContainsMessage(DirectSendMessages(), messages_[0])); 315 EXPECT_FALSE(ContainsMessage(DirectSendMessages(), messages_[0]));
316 EXPECT_FALSE(ContainsMessage(DirectSendMessages(), messages_[1])); 316 EXPECT_FALSE(ContainsMessage(DirectSendMessages(), messages_[1]));
317 EXPECT_FALSE(ContainsMessage(DirectSendMessages(), messages_[2])); 317 EXPECT_FALSE(ContainsMessage(DirectSendMessages(), messages_[2]));
318 318
319 promises_[2]->DidNotSwap(cc::SwapPromise::COMMIT_FAILS); 319 promises_[2]->DidNotSwap(cc::SwapPromise::COMMIT_FAILS);
320 EXPECT_TRUE(NextSwapMessages().empty()); 320 EXPECT_TRUE(NextSwapMessages().empty());
321 EXPECT_FALSE(ContainsMessage(DirectSendMessages(), messages_[2])); 321 EXPECT_FALSE(ContainsMessage(DirectSendMessages(), messages_[2]));
322 322
323 EXPECT_TRUE(NextSwapMessages().empty()); 323 EXPECT_TRUE(NextSwapMessages().empty());
324 EXPECT_FALSE(frame_swap_message_queue_->Empty()); 324 EXPECT_FALSE(frame_update_message_queue_->Empty());
325 } 325 }
326 326
327 TEST_F(QueueMessageSwapPromiseTest, VisalStateSwapPromiseDidNotSwapSwapFails) { 327 TEST_F(QueueMessageSwapPromiseTest, VisalStateSwapPromiseDidNotSwapSwapFails) {
328 VisualStateSwapPromiseDidNotSwap(cc::SwapPromise::SWAP_FAILS); 328 VisualStateSwapPromiseDidNotSwap(cc::SwapPromise::SWAP_FAILS);
329 } 329 }
330 330
331 } // namespace content 331 } // namespace content
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698