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

Side by Side Diff: third_party/WebKit/Source/bindings/core/v8/V8SnapshotUtil.cpp

Issue 2841443005: [Bindings] Create and use V8 context snapshots (Closed)
Patch Set: Work for yuki's comments Created 3 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
(Empty)
1 // Copyright 2017 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "bindings/core/v8/V8SnapshotUtil.h"
6
7 #include <cstring>
8
9 #include "bindings/core/v8/GeneratedCodeHelper.h"
10 #include "bindings/core/v8/V8Document.h"
11 #include "bindings/core/v8/V8EventTarget.h"
12 #include "bindings/core/v8/V8HTMLDocument.h"
13 #include "bindings/core/v8/V8Initializer.h"
14 #include "bindings/core/v8/V8Node.h"
15 #include "bindings/core/v8/V8Window.h"
16 #include "platform/bindings/DOMWrapperWorld.h"
17 #include "platform/bindings/V8ObjectConstructor.h"
18 #include "platform/bindings/V8PerIsolateData.h"
19 #include "platform/bindings/V8PrivateProperty.h"
20 #include "v8/include/v8.h"
21
22 namespace blink {
23
24 namespace {
25
26 intptr_t* g_snapshot_reference_table = nullptr;
27
28 // TODO(peria): This method is almost a copy of
29 // V8PerContext::ConstructorForTypeSlowCase(), so merge with it.
30 v8::Local<v8::Function> ConstructPlainType(v8::Isolate* isolate,
31 const DOMWrapperWorld& world,
32 v8::Local<v8::Context> context,
33 const WrapperTypeInfo* type) {
34 v8::Context::Scope scope(context);
35 // We shouldn't reach this point for the types that are implemented in v8 such
36 // as typed arrays and hence don't have domTemplateFunction.
37 DCHECK(type->dom_template_function);
38 v8::Local<v8::FunctionTemplate> interface_template =
39 type->domTemplate(isolate, world);
40 // Getting the function might fail if we're running out of stack or memory.
41 v8::Local<v8::Function> interface_object;
42 if (!interface_template->GetFunction(context).ToLocal(&interface_object))
Yuki 2017/06/23 15:20:45 s/ToLocal/ToLocalChecked/
peria 2017/06/27 09:52:37 Done.
43 return v8::Local<v8::Function>();
44
45 if (type->parent_class) {
46 v8::Local<v8::Object> prototype_template =
47 ConstructPlainType(isolate, world, context, type->parent_class);
48 CHECK(interface_object->SetPrototype(context, prototype_template)
49 .ToChecked());
50 }
51
52 v8::Local<v8::Value> prototype_value;
53 CHECK(interface_object->Get(context, V8AtomicString(isolate, "prototype"))
54 .ToLocal(&prototype_value));
Yuki 2017/06/23 15:20:44 s/CHECK(...ToLocal(...))/ToLocalChecked()/
peria 2017/06/27 09:52:37 Done.
55 CHECK(prototype_value->IsObject());
56 v8::Local<v8::Object> prototype_object = prototype_value.As<v8::Object>();
57 if (prototype_object->InternalFieldCount() ==
58 kV8PrototypeInternalFieldcount &&
59 type->wrapper_type_prototype ==
60 WrapperTypeInfo::kWrapperTypeObjectPrototype) {
61 prototype_object->SetAlignedPointerInInternalField(
62 kV8PrototypeTypeIndex, const_cast<WrapperTypeInfo*>(type));
63 }
64 type->PreparePrototypeAndInterfaceObject(
65 context, world, prototype_object, interface_object, interface_template);
66
67 return interface_object;
68 }
69
70 // TODO(peria): This method is almost a copy of
71 // V8PerContext::CreateWrapperFromCacheSlowCase(), so merge with it.
72 v8::Local<v8::Object> CreatePlainWrapper(v8::Isolate* isolate,
73 const DOMWrapperWorld& world,
74 v8::Local<v8::Context> context,
75 const WrapperTypeInfo* type) {
76 CHECK(V8HTMLDocument::wrapperTypeInfo.Equals(type));
77
78 v8::Context::Scope scope(context);
79 v8::Local<v8::Function> interface_object =
80 ConstructPlainType(isolate, world, context, type);
81 CHECK(!interface_object.IsEmpty());
82 v8::Local<v8::Object> instance_template;
83 CHECK(V8ObjectConstructor::NewInstance(isolate, interface_object)
84 .ToLocal(&instance_template));
Yuki 2017/06/23 15:20:44 s/CHECK(...ToLocal(...))/ToLocalChecked()/
peria 2017/06/27 09:52:37 Done.
85 v8::Local<v8::Object> wrapper = instance_template->Clone();
86 wrapper->SetAlignedPointerInInternalField(kV8DOMWrapperTypeIndex,
87 const_cast<WrapperTypeInfo*>(type));
88 return wrapper;
89 }
90
91 constexpr int kWorldIdForNonMainWorld =
92 DOMWrapperWorld::WorldId::kIsolatedWorldIdLimit - 1;
93
94 int GetSnapshotIndexForWorld(const DOMWrapperWorld& world) {
95 return world.IsMainWorld() ? 0 : 1;
96 }
97
98 constexpr const WrapperTypeInfo* kSnapshotWrapperTypes[] = {
99 &V8Window::wrapperTypeInfo, &V8HTMLDocument::wrapperTypeInfo,
100 &V8EventTarget::wrapperTypeInfo, &V8Node::wrapperTypeInfo,
101 &V8Document::wrapperTypeInfo,
102 };
103
104 enum class InternalFieldType : uint8_t {
105 kNone,
106 kNodeType,
107 kDocumentType,
108 kHTMLDocumentType,
109 kHTMLDocumentObject,
110 };
111
112 const WrapperTypeInfo* FieldTypeToWrapperTypeInfo(InternalFieldType type) {
113 switch (type) {
114 case InternalFieldType::kNone:
115 NOTREACHED();
116 break;
117 case InternalFieldType::kNodeType:
118 return &V8Node::wrapperTypeInfo;
119 case InternalFieldType::kDocumentType:
120 return &V8Document::wrapperTypeInfo;
121 case InternalFieldType::kHTMLDocumentType:
122 return &V8HTMLDocument::wrapperTypeInfo;
123 case InternalFieldType::kHTMLDocumentObject:
124 return &V8HTMLDocument::wrapperTypeInfo;
125 }
126 NOTREACHED();
127 return nullptr;
128 }
129
130 struct DataForDeserializer {
131 STACK_ALLOCATED();
132 Member<Document> document;
133 };
134
135 int CountExternalReferenceEntries() {
136 if (!g_snapshot_reference_table)
137 return 0;
138
139 int count = 0;
140 for (intptr_t* p = g_snapshot_reference_table; *p; ++p)
141 ++count;
142 return count;
143 }
144
145 } // namespace
146
147 v8::StartupData V8SnapshotUtil::TakeSnapshot() {
148 DCHECK_EQ(V8PerIsolateData::From(V8PerIsolateData::MainThreadIsolate())
149 ->GetV8ContextMode(),
150 V8PerIsolateData::V8ContextMode::kTakeSnapshot);
151
152 v8::SnapshotCreator* creator =
153 V8PerIsolateData::From(V8PerIsolateData::MainThreadIsolate())
154 ->GetSnapshotCreator();
155 v8::Isolate* isolate = creator->GetIsolate();
156 CHECK_EQ(isolate, v8::Isolate::GetCurrent());
157
158 VLOG(1) << "External reference table has " << CountExternalReferenceEntries()
159 << " entries.";
160
161 // Disable all runtime enabled featuers
162 RuntimeEnabledFeatures::SetStableFeaturesEnabled(false);
163 RuntimeEnabledFeatures::SetExperimentalFeaturesEnabled(false);
164 RuntimeEnabledFeatures::SetTestFeaturesEnabled(false);
165
166 {
167 v8::HandleScope handleScope(isolate);
168 creator->SetDefaultContext(v8::Context::New(isolate));
169
170 TakeSnapshotForWorld(creator, DOMWrapperWorld::MainWorld());
171 // For non main worlds, we can use any type to create a context.
172 TakeSnapshotForWorld(creator, *DOMWrapperWorld::EnsureIsolatedWorld(
173 isolate, kWorldIdForNonMainWorld));
174 }
175
176 // Snapshot is taken on the main thread, but it can be used on other threads.
177 // So we remove a message handler for the main thread.
178 isolate->RemoveMessageListeners(V8Initializer::MessageHandlerInMainThread);
179
180 return creator->CreateBlob(v8::SnapshotCreator::FunctionCodeHandling::kClear);
181 }
182
183 void V8SnapshotUtil::TakeSnapshotForWorld(v8::SnapshotCreator* creator,
Yuki 2017/06/23 15:20:45 Be consistent with the declaration order in the he
peria 2017/06/27 09:52:37 Done.
184 const DOMWrapperWorld& world) {
185 v8::Isolate* isolate = creator->GetIsolate();
186 CHECK_EQ(isolate, v8::Isolate::GetCurrent());
187
188 // Function templates
189 v8::HandleScope handleScope(isolate);
190 Vector<v8::Local<v8::FunctionTemplate>> interface_templates;
Yuki 2017/06/23 15:20:45 It's not allowed to put v8::Local on heap. Vector
peria 2017/06/27 09:52:37 Done.
191 for (const WrapperTypeInfo* wrapper_type_info : kSnapshotWrapperTypes) {
192 v8::Local<v8::FunctionTemplate> interface_template =
193 wrapper_type_info->domTemplate(isolate, world);
194 CHECK(!interface_template.IsEmpty());
195 interface_templates.push_back(interface_template);
196 }
197
198 // TODO(peria): Confirm interface_tempaltes[0] is a template of V8Window.
199 v8::Local<v8::ObjectTemplate> window_template =
200 interface_templates[0]->InstanceTemplate();
201 CHECK(!window_template.IsEmpty());
202
203 v8::Local<v8::Context> context;
204 {
205 V8PerIsolateData::UseCounterDisabledScope use_counter_disabled(
206 V8PerIsolateData::From(isolate));
207 context = v8::Context::New(isolate, nullptr, window_template);
208 }
209 CHECK(!context.IsEmpty());
210
211 if (world.IsMainWorld()) {
212 v8::Context::Scope scope(context);
213 v8::Local<v8::Object> document_wrapper = CreatePlainWrapper(
214 isolate, world, context, &V8HTMLDocument::wrapperTypeInfo);
215 int indices[] = {kV8DOMWrapperObjectIndex, kV8DOMWrapperTypeIndex};
216 void* values[] = {nullptr, const_cast<WrapperTypeInfo*>(
217 &V8HTMLDocument::wrapperTypeInfo)};
218 document_wrapper->SetAlignedPointerInInternalFields(
219 WTF_ARRAY_LENGTH(indices), indices, values);
220
221 // Update the cached accessor for window.document.
222 CHECK(V8PrivateProperty::GetWindowDocumentCachedAccessor(isolate).Set(
Yuki 2017/06/23 15:20:44 Probably, this should be gone. You're now setting
peria 2017/06/27 09:52:37 Hmm. This code is required here to keep the HTMLDo
223 context->Global(), document_wrapper));
224 }
225
226 for (auto& interface_template : interface_templates) {
227 creator->AddTemplate(interface_template);
228 }
229 creator->AddContext(context, SerializeInternalField);
230
231 V8PerIsolateData::From(isolate)->ClearPersistentsForV8Snapshot();
232 }
233
234 void V8SnapshotUtil::EnsureInterfaceTemplates(v8::Isolate* isolate) {
235 if (V8PerIsolateData::From(isolate)->GetV8ContextMode() !=
236 V8PerIsolateData::V8ContextMode::kUseSnapshot) {
237 return;
238 }
239
240 EnsureInterfaceTemplatesForWorld(isolate, DOMWrapperWorld::MainWorld());
241 EnsureInterfaceTemplatesForWorld(
242 isolate,
243 *DOMWrapperWorld::EnsureIsolatedWorld(isolate, kWorldIdForNonMainWorld));
244 }
245
246 void V8SnapshotUtil::EnsureInterfaceTemplatesForWorld(
247 v8::Isolate* isolate,
248 const DOMWrapperWorld& world) {
249 V8PerIsolateData* data = V8PerIsolateData::From(isolate);
250 for (const WrapperTypeInfo* wrapper_type_info : kSnapshotWrapperTypes) {
251 v8::Local<v8::FunctionTemplate> interface =
252 InterfaceTemplateFromSnapshot(isolate, world, wrapper_type_info);
253 CHECK(!interface.IsEmpty());
254 data->SetInterfaceTemplate(world, wrapper_type_info, interface);
255 }
256 }
257
258 v8::Local<v8::FunctionTemplate> V8SnapshotUtil::InterfaceTemplateFromSnapshot(
Yuki 2017/06/23 15:20:44 InterfaceTemplateFromSnapshot is only used in Ensu
peria 2017/06/27 09:52:37 Done.
259 v8::Isolate* isolate,
260 const DOMWrapperWorld& world,
261 const WrapperTypeInfo* wrapper_type_info) {
262 const int index_offset =
263 world.IsMainWorld() ? 0 : WTF_ARRAY_LENGTH(kSnapshotWrapperTypes);
264
265 if (V8Window::wrapperTypeInfo.Equals(wrapper_type_info)) {
266 v8::Local<v8::FunctionTemplate> interface_template =
267 v8::FunctionTemplate::FromSnapshot(isolate, index_offset + 0)
268 .ToLocalChecked();
269 V8Window::installV8WindowRuntimeEnabledOnTemplateFunction(
Yuki 2017/06/23 15:20:44 s/installV8Window.../InstallV8Window.../ Does thi
peria 2017/06/27 09:52:37 For V8Window, we call it via static function point
270 isolate, world, interface_template);
271 return interface_template;
272 }
273 if (V8HTMLDocument::wrapperTypeInfo.Equals(wrapper_type_info)) {
274 v8::Local<v8::FunctionTemplate> interface_template =
275 v8::FunctionTemplate::FromSnapshot(isolate, index_offset + 1)
276 .ToLocalChecked();
277 V8HTMLDocument::InstallRuntimeEnabledFeaturesOnTemplate(isolate, world,
278 interface_template);
279 return interface_template;
280 }
281 if (V8EventTarget::wrapperTypeInfo.Equals(wrapper_type_info)) {
282 v8::Local<v8::FunctionTemplate> interface_template =
283 v8::FunctionTemplate::FromSnapshot(isolate, index_offset + 2)
284 .ToLocalChecked();
285 V8EventTarget::InstallRuntimeEnabledFeaturesOnTemplate(isolate, world,
286 interface_template);
287 return interface_template;
288 }
289 if (V8Node::wrapperTypeInfo.Equals(wrapper_type_info)) {
290 v8::Local<v8::FunctionTemplate> interface_template =
291 v8::FunctionTemplate::FromSnapshot(isolate, index_offset + 3)
292 .ToLocalChecked();
293 V8Node::InstallRuntimeEnabledFeaturesOnTemplate(isolate, world,
294 interface_template);
295 return interface_template;
296 }
297 if (V8Document::wrapperTypeInfo.Equals(wrapper_type_info)) {
298 v8::Local<v8::FunctionTemplate> interface_template =
299 v8::FunctionTemplate::FromSnapshot(isolate, index_offset + 4)
300 .ToLocalChecked();
301 V8Document::InstallRuntimeEnabledFeaturesOnTemplate(isolate, world,
302 interface_template);
303 return interface_template;
304 }
305
306 NOTREACHED();
307 return v8::Local<v8::FunctionTemplate>();
308 }
309
310 v8::StartupData V8SnapshotUtil::SerializeInternalField(
311 v8::Local<v8::Object> holder,
312 int index,
313 void*) {
314 InternalFieldType field_type = InternalFieldType::kNone;
315 const WrapperTypeInfo* wrapper_type = ToWrapperTypeInfo(holder);
316 if (kV8DOMWrapperObjectIndex == index) {
317 if (blink::V8HTMLDocument::wrapperTypeInfo.Equals(wrapper_type)) {
318 field_type = InternalFieldType::kHTMLDocumentObject;
319 }
320 } else if (kV8DOMWrapperTypeIndex == index) {
321 if (blink::V8HTMLDocument::wrapperTypeInfo.Equals(wrapper_type)) {
322 field_type = InternalFieldType::kHTMLDocumentType;
323 } else if (blink::V8Document::wrapperTypeInfo.Equals(wrapper_type)) {
324 field_type = InternalFieldType::kDocumentType;
325 } else if (blink::V8Node::wrapperTypeInfo.Equals(wrapper_type)) {
326 field_type = InternalFieldType::kNodeType;
327 }
328 }
329 CHECK_NE(field_type, InternalFieldType::kNone);
330
331 int size = sizeof(InternalFieldType);
332 char* data = new char[size];
333 std::memcpy(data, &field_type, size);
334
335 return {data, size};
336 }
337
338 void V8SnapshotUtil::DeserializeInternalField(v8::Local<v8::Object> holder,
Yuki 2017/06/23 15:20:44 s/holder/object/ This is not relevant to a holder
peria 2017/06/27 09:52:37 Done.
339 int index,
340 v8::StartupData payload,
341 void* ptr) {
342 // DeserializeInternalField() expects to be called in the main world
343 // with |document| being HTMLDocument.
344 CHECK_EQ(payload.raw_size, static_cast<int>(sizeof(InternalFieldType)));
345 InternalFieldType type =
346 *reinterpret_cast<const InternalFieldType*>(payload.data);
347
348 const WrapperTypeInfo* wrapper_type_info = FieldTypeToWrapperTypeInfo(type);
349 switch (type) {
350 case InternalFieldType::kNodeType:
351 case InternalFieldType::kDocumentType:
352 case InternalFieldType::kHTMLDocumentType: {
353 CHECK_EQ(index, kV8DOMWrapperTypeIndex);
354 holder->SetAlignedPointerInInternalField(
355 index, const_cast<WrapperTypeInfo*>(wrapper_type_info));
356 return;
357 }
358 case InternalFieldType::kHTMLDocumentObject: {
359 CHECK_EQ(index, kV8DOMWrapperObjectIndex);
360 v8::Isolate* isolate = v8::Isolate::GetCurrent();
361 DataForDeserializer* data = static_cast<DataForDeserializer*>(ptr);
362 ScriptWrappable* document = data->document;
363 DCHECK(document);
364
365 // Make reference from wrapper to document
366 holder->SetAlignedPointerInInternalField(index, document);
367 // Make reference from document to wrapper
368 CHECK(document->SetWrapper(isolate, wrapper_type_info, holder));
369 WrapperTypeInfo::WrapperCreated();
370 return;
371 }
372 case InternalFieldType::kNone:
373 NOTREACHED();
374 return;
375 }
376
377 NOTREACHED();
378 }
379
380 v8::Local<v8::Context> V8SnapshotUtil::CreateContext(
381 v8::Isolate* isolate,
382 const DOMWrapperWorld& world,
383 v8::ExtensionConfiguration* extension_configuration,
384 v8::Local<v8::Object> global_proxy,
385 Document* document) {
386 if (V8PerIsolateData::From(isolate)->GetV8ContextMode() !=
387 V8PerIsolateData::V8ContextMode::kUseSnapshot) {
388 return v8::Local<v8::Context>();
389 }
390 // For main world frames, we don't support the case that |document| is not
391 // an instance of HTMLDocument, e.g. XMLDocument.
392 if (world.IsMainWorld() && !(document && document->IsHTMLDocument())) {
393 return v8::Local<v8::Context>();
394 }
395 const int index = GetSnapshotIndexForWorld(world);
396 DataForDeserializer data{document};
397 v8::DeserializeInternalFieldsCallback callback =
398 v8::DeserializeInternalFieldsCallback(&DeserializeInternalField, &data);
399 v8::Local<v8::Context> context =
400 v8::Context::FromSnapshot(isolate, index, callback,
401 extension_configuration, global_proxy)
402 .ToLocalChecked();
403 CHECK(!context.IsEmpty());
404 DVLOG(1) << "A context is created from snapshot";
405
406 return context;
407 }
408
409 void V8SnapshotUtil::SetupContext(v8::Local<v8::Context> context,
410 Document* document) {
411 ScriptState* script_state = ScriptState::From(context);
412 v8::Isolate* isolate = script_state->GetIsolate();
413 if (V8PerIsolateData::From(isolate)->GetV8ContextMode() !=
414 V8PerIsolateData::V8ContextMode::kUseSnapshot) {
415 return;
416 }
417
418 v8::Local<v8::String> prototype_str = V8AtomicString(isolate, "prototype");
419 DOMWrapperWorld& world = script_state->World();
420 V8PerContextData* data = script_state->PerContextData();
421
422 v8::Local<v8::Object> global_proxy = context->Global();
423 v8::Local<v8::Object> window_wrapper =
424 global_proxy->GetPrototype().As<v8::Object>();
425 {
426 const WrapperTypeInfo* type = &V8EventTarget::wrapperTypeInfo;
427 v8::Local<v8::Function> constructor = data->ConstructorForType(type);
Yuki 2017/06/23 15:20:44 nit: s/constructor/interface/ We're not intereste
peria 2017/06/27 09:52:37 Done.
428 v8::Local<v8::Object> prototype = constructor->Get(context, prototype_str)
429 .ToLocalChecked()
430 .As<v8::Object>();
431 V8EventTarget::InstallRuntimeEnabledFeatures(isolate, world, window_wrapper,
432 prototype, constructor);
433 }
434 {
435 const WrapperTypeInfo* type = &V8Window::wrapperTypeInfo;
436 v8::Local<v8::Function> constructor = data->ConstructorForType(type);
437 v8::Local<v8::Object> prototype = constructor->Get(context, prototype_str)
438 .ToLocalChecked()
439 .As<v8::Object>();
440 V8Window::installV8WindowRuntimeEnabledFunction(
441 isolate, world, window_wrapper, prototype, constructor);
442 }
443
444 // In case we create a context for the main world from snapshot, we also have
445 // to work for HTMLDocument wrapper and its prototype chain.
446 if (world.IsMainWorld() && document && document->IsHTMLDocument()) {
Yuki 2017/06/23 15:20:44 This condition must appear at the very beginning o
peria 2017/06/27 09:52:37 Done.
447 CHECK(document->ContainsWrapper());
448 v8::Local<v8::Object> document_wrapper =
449 document->MainWorldWrapper(isolate);
450
451 {
452 // Prototype object and interface object of EventTarget were set up with
453 // Window wrapper.
454 V8EventTarget::InstallRuntimeEnabledFeatures(
455 isolate, world, document_wrapper, v8::Local<v8::Object>(),
456 v8::Local<v8::Function>());
457 }
458 {
459 const WrapperTypeInfo* type = &V8Node::wrapperTypeInfo;
460 v8::Local<v8::Function> constructor = data->ConstructorForType(type);
461 v8::Local<v8::Object> prototype = constructor->Get(context, prototype_str)
462 .ToLocalChecked()
463 .As<v8::Object>();
464 V8Node::InstallRuntimeEnabledFeatures(isolate, world, document_wrapper,
465 prototype, constructor);
466 }
467 {
468 const WrapperTypeInfo* type = &V8Document::wrapperTypeInfo;
469 v8::Local<v8::Function> constructor = data->ConstructorForType(type);
470 v8::Local<v8::Object> prototype = constructor->Get(context, prototype_str)
471 .ToLocalChecked()
472 .As<v8::Object>();
473 V8Document::InstallRuntimeEnabledFeatures(
474 isolate, world, document_wrapper, prototype, constructor);
475 }
476 {
477 const WrapperTypeInfo* type = &V8HTMLDocument::wrapperTypeInfo;
478 v8::Local<v8::Function> constructor = data->ConstructorForType(type);
479 v8::Local<v8::Object> prototype = constructor->Get(context, prototype_str)
480 .ToLocalChecked()
481 .As<v8::Object>();
482 V8HTMLDocument::InstallRuntimeEnabledFeatures(
483 isolate, world, document_wrapper, prototype, constructor);
484 }
485 }
486 }
487
488 void V8SnapshotUtil::SetReferenceTable(intptr_t* table) {
489 DCHECK(!g_snapshot_reference_table);
490 g_snapshot_reference_table = table;
491 }
492
493 intptr_t* V8SnapshotUtil::GetReferenceTable() {
494 DCHECK(g_snapshot_reference_table);
495 return g_snapshot_reference_table;
496 }
497
498 } // namespace blink
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698