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

Side by Side Diff: chrome/renderer/extensions/dispatcher.cc

Issue 15855010: Make ExtensionMsg_MessageInvoke run a module system function rather than a (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: go Created 7 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 | Annotate | Revision Log
OLDNEW
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 1 // Copyright (c) 2012 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 "chrome/renderer/extensions/dispatcher.h" 5 #include "chrome/renderer/extensions/dispatcher.h"
6 6
7 #include "base/callback.h" 7 #include "base/callback.h"
8 #include "base/command_line.h" 8 #include "base/command_line.h"
9 #include "base/debug/alias.h" 9 #include "base/debug/alias.h"
10 #include "base/json/json_reader.h" 10 #include "base/json/json_reader.h"
(...skipping 81 matching lines...) Expand 10 before | Expand all | Expand 10 after
92 using WebKit::WebView; 92 using WebKit::WebView;
93 using content::RenderThread; 93 using content::RenderThread;
94 using content::RenderView; 94 using content::RenderView;
95 95
96 namespace extensions { 96 namespace extensions {
97 97
98 namespace { 98 namespace {
99 99
100 static const int64 kInitialExtensionIdleHandlerDelayMs = 5*1000; 100 static const int64 kInitialExtensionIdleHandlerDelayMs = 5*1000;
101 static const int64 kMaxExtensionIdleHandlerDelayMs = 5*60*1000; 101 static const int64 kMaxExtensionIdleHandlerDelayMs = 5*60*1000;
102 static const char kEventDispatchFunction[] = "Event.dispatchEvent"; 102 static const char kEventModule[] = "event_bindings";
103 static const char kEventDispatchFunction[] = "dispatchEvent";
103 static const char kOnSuspendEvent[] = "runtime.onSuspend"; 104 static const char kOnSuspendEvent[] = "runtime.onSuspend";
104 static const char kOnSuspendCanceledEvent[] = "runtime.onSuspendCanceled"; 105 static const char kOnSuspendCanceledEvent[] = "runtime.onSuspendCanceled";
105 106
106 static v8::Handle<v8::Object> GetOrCreateChrome( 107 static v8::Handle<v8::Object> GetOrCreateChrome(
107 v8::Handle<v8::Context> context) { 108 v8::Handle<v8::Context> context) {
108 v8::Handle<v8::String> chrome_string(v8::String::New("chrome")); 109 v8::Handle<v8::String> chrome_string(v8::String::New("chrome"));
109 v8::Handle<v8::Object> global(context->Global()); 110 v8::Handle<v8::Object> global(context->Global());
110 v8::Handle<v8::Value> chrome(global->Get(chrome_string)); 111 v8::Handle<v8::Value> chrome(global->Get(chrome_string));
111 if (chrome.IsEmpty() || chrome->IsUndefined()) { 112 if (chrome.IsEmpty() || chrome->IsUndefined()) {
112 v8::Handle<v8::Object> chrome_object(v8::Object::New()); 113 v8::Handle<v8::Object> chrome_object(v8::Object::New());
(...skipping 251 matching lines...) Expand 10 before | Expand all | Expand 10 after
364 }; 365 };
365 366
366 class LoggingNativeHandler : public ObjectBackedNativeHandler { 367 class LoggingNativeHandler : public ObjectBackedNativeHandler {
367 public: 368 public:
368 explicit LoggingNativeHandler(v8::Handle<v8::Context> context) 369 explicit LoggingNativeHandler(v8::Handle<v8::Context> context)
369 : ObjectBackedNativeHandler(context) { 370 : ObjectBackedNativeHandler(context) {
370 RouteFunction("DCHECK", 371 RouteFunction("DCHECK",
371 base::Bind(&LoggingNativeHandler::Dcheck, base::Unretained(this))); 372 base::Bind(&LoggingNativeHandler::Dcheck, base::Unretained(this)));
372 RouteFunction("CHECK", 373 RouteFunction("CHECK",
373 base::Bind(&LoggingNativeHandler::Check, base::Unretained(this))); 374 base::Bind(&LoggingNativeHandler::Check, base::Unretained(this)));
375 RouteFunction("DCHECK_IS_ON",
376 base::Bind(&LoggingNativeHandler::DcheckIsOn, base::Unretained(this)));
374 } 377 }
375 378
376 v8::Handle<v8::Value> Check(const v8::Arguments& args) { 379 v8::Handle<v8::Value> Check(const v8::Arguments& args) {
377 bool check_value; 380 bool check_value;
378 std::string error_message; 381 std::string error_message;
379 ParseArgs(args, &check_value, &error_message); 382 ParseArgs(args, &check_value, &error_message);
380 CHECK(check_value) << error_message; 383 CHECK(check_value) << error_message;
381 return v8::Undefined(); 384 return v8::Undefined();
382 } 385 }
383 386
384 v8::Handle<v8::Value> Dcheck(const v8::Arguments& args) { 387 v8::Handle<v8::Value> Dcheck(const v8::Arguments& args) {
385 bool check_value; 388 bool check_value;
386 std::string error_message; 389 std::string error_message;
387 ParseArgs(args, &check_value, &error_message); 390 ParseArgs(args, &check_value, &error_message);
388 DCHECK(check_value) << error_message; 391 DCHECK(check_value) << error_message;
389 return v8::Undefined(); 392 return v8::Undefined();
390 } 393 }
391 394
395 v8::Handle<v8::Value> DcheckIsOn(const v8::Arguments& args) {
396 return v8::Boolean::New(DCHECK_IS_ON());
397 }
398
392 private: 399 private:
393 void ParseArgs(const v8::Arguments& args, 400 void ParseArgs(const v8::Arguments& args,
394 bool* check_value, 401 bool* check_value,
395 std::string* error_message) { 402 std::string* error_message) {
396 CHECK_LE(args.Length(), 2); 403 CHECK_LE(args.Length(), 2);
397 *check_value = args[0]->BooleanValue(); 404 *check_value = args[0]->BooleanValue();
398 if (args.Length() == 2) 405 if (args.Length() == 2)
399 *error_message = "Error: " + std::string( 406 *error_message = "Error: " + std::string(
400 *v8::String::AsciiValue(args[1])); 407 *v8::String::AsciiValue(args[1]));
401 408
(...skipping 20 matching lines...) Expand all
422 return dflt; 429 return dflt;
423 std::string ascii_value = *v8::String::AsciiValue(v8_string); 430 std::string ascii_value = *v8::String::AsciiValue(v8_string);
424 return ascii_value.empty() ? dflt : ascii_value; 431 return ascii_value.empty() ? dflt : ascii_value;
425 } 432 }
426 }; 433 };
427 434
428 void InstallAppBindings(ModuleSystem* module_system, 435 void InstallAppBindings(ModuleSystem* module_system,
429 v8::Handle<v8::Object> chrome, 436 v8::Handle<v8::Object> chrome,
430 v8::Handle<v8::Object> chrome_hidden) { 437 v8::Handle<v8::Object> chrome_hidden) {
431 module_system->SetLazyField(chrome, "app", "app", "chromeApp"); 438 module_system->SetLazyField(chrome, "app", "app", "chromeApp");
432 module_system->SetLazyField(chrome_hidden, "app", "app",
433 "chromeHiddenApp");
434 } 439 }
435 440
436 void InstallWebstoreBindings(ModuleSystem* module_system, 441 void InstallWebstoreBindings(ModuleSystem* module_system,
437 v8::Handle<v8::Object> chrome, 442 v8::Handle<v8::Object> chrome,
438 v8::Handle<v8::Object> chrome_hidden) { 443 v8::Handle<v8::Object> chrome_hidden) {
439 module_system->SetLazyField(chrome, "webstore", "webstore", "chromeWebstore"); 444 module_system->SetLazyField(chrome, "webstore", "webstore", "chromeWebstore");
440 module_system->SetLazyField(chrome_hidden, "webstore", "webstore", 445 module_system->SetLazyField(chrome_hidden, "webstore", "webstore",
441 "chromeHiddenWebstore"); 446 "chromeHiddenWebstore");
442 } 447 }
443 448
(...skipping 99 matching lines...) Expand 10 before | Expand all | Expand 10 after
543 for (size_t i = 0; i < names.size(); ++i) 548 for (size_t i = 0; i < names.size(); ++i)
544 function_names_.insert(names[i]); 549 function_names_.insert(names[i]);
545 } 550 }
546 551
547 void Dispatcher::OnSetChannel(int channel) { 552 void Dispatcher::OnSetChannel(int channel) {
548 Feature::SetCurrentChannel( 553 Feature::SetCurrentChannel(
549 static_cast<chrome::VersionInfo::Channel>(channel)); 554 static_cast<chrome::VersionInfo::Channel>(channel));
550 } 555 }
551 556
552 void Dispatcher::OnMessageInvoke(const std::string& extension_id, 557 void Dispatcher::OnMessageInvoke(const std::string& extension_id,
558 const std::string& module_name,
553 const std::string& function_name, 559 const std::string& function_name,
554 const base::ListValue& args, 560 const base::ListValue& args,
555 bool user_gesture) { 561 bool user_gesture) {
556 scoped_ptr<WebScopedUserGesture> web_user_gesture; 562 scoped_ptr<WebScopedUserGesture> web_user_gesture;
557 if (user_gesture) { 563 if (user_gesture) {
558 web_user_gesture.reset(new WebScopedUserGesture); 564 web_user_gesture.reset(new WebScopedUserGesture);
559 } 565 }
560 566
561 v8_context_set_.DispatchChromeHiddenMethod( 567 v8_context_set_.ForEach(
562 extension_id, function_name, args, NULL); 568 extension_id,
569 NULL, // all render views
570 base::Bind(&CallModuleMethod, module_name, function_name, &args));
563 571
564 // Reset the idle handler each time there's any activity like event or message 572 // Reset the idle handler each time there's any activity like event or message
565 // dispatch, for which Invoke is the chokepoint. 573 // dispatch, for which Invoke is the chokepoint.
566 if (is_extension_process_) { 574 if (is_extension_process_) {
567 RenderThread::Get()->ScheduleIdleHandler( 575 RenderThread::Get()->ScheduleIdleHandler(
568 kInitialExtensionIdleHandlerDelayMs); 576 kInitialExtensionIdleHandlerDelayMs);
569 } 577 }
570 578
571 // Tell the browser process when an event has been dispatched with a lazy 579 // Tell the browser process when an event has been dispatched with a lazy
572 // background page active. 580 // background page active.
573 const Extension* extension = extensions_.GetByID(extension_id); 581 const Extension* extension = extensions_.GetByID(extension_id);
574 if (extension && BackgroundInfo::HasLazyBackgroundPage(extension) && 582 if (extension && BackgroundInfo::HasLazyBackgroundPage(extension) &&
583 module_name == kEventModule &&
575 function_name == kEventDispatchFunction) { 584 function_name == kEventDispatchFunction) {
576 RenderView* background_view = 585 RenderView* background_view =
577 ExtensionHelper::GetBackgroundPage(extension_id); 586 ExtensionHelper::GetBackgroundPage(extension_id);
578 if (background_view) { 587 if (background_view) {
579 background_view->Send(new ExtensionHostMsg_EventAck( 588 background_view->Send(new ExtensionHostMsg_EventAck(
580 background_view->GetRoutingID())); 589 background_view->GetRoutingID()));
581 } 590 }
582 } 591 }
583 } 592 }
584 593
(...skipping 285 matching lines...) Expand 10 before | Expand all | Expand 10 after
870 source_map_.RegisterSource("json_schema", IDR_JSON_SCHEMA_JS); 879 source_map_.RegisterSource("json_schema", IDR_JSON_SCHEMA_JS);
871 source_map_.RegisterSource("test", IDR_TEST_CUSTOM_BINDINGS_JS); 880 source_map_.RegisterSource("test", IDR_TEST_CUSTOM_BINDINGS_JS);
872 881
873 // Libraries. 882 // Libraries.
874 source_map_.RegisterSource("contentWatcher", IDR_CONTENT_WATCHER_JS); 883 source_map_.RegisterSource("contentWatcher", IDR_CONTENT_WATCHER_JS);
875 source_map_.RegisterSource("imageUtil", IDR_IMAGE_UTIL_JS); 884 source_map_.RegisterSource("imageUtil", IDR_IMAGE_UTIL_JS);
876 source_map_.RegisterSource("lastError", IDR_LAST_ERROR_JS); 885 source_map_.RegisterSource("lastError", IDR_LAST_ERROR_JS);
877 source_map_.RegisterSource("schemaUtils", IDR_SCHEMA_UTILS_JS); 886 source_map_.RegisterSource("schemaUtils", IDR_SCHEMA_UTILS_JS);
878 source_map_.RegisterSource("sendRequest", IDR_SEND_REQUEST_JS); 887 source_map_.RegisterSource("sendRequest", IDR_SEND_REQUEST_JS);
879 source_map_.RegisterSource("setIcon", IDR_SET_ICON_JS); 888 source_map_.RegisterSource("setIcon", IDR_SET_ICON_JS);
889 source_map_.RegisterSource("unload_event", IDR_UNLOAD_EVENT_JS);
880 source_map_.RegisterSource("utils", IDR_UTILS_JS); 890 source_map_.RegisterSource("utils", IDR_UTILS_JS);
881 source_map_.RegisterSource("entryIdManager", IDR_ENTRY_ID_MANAGER); 891 source_map_.RegisterSource("entryIdManager", IDR_ENTRY_ID_MANAGER);
882 892
883 // Custom bindings. 893 // Custom bindings.
884 source_map_.RegisterSource("app", IDR_APP_CUSTOM_BINDINGS_JS); 894 source_map_.RegisterSource("app", IDR_APP_CUSTOM_BINDINGS_JS);
885 source_map_.RegisterSource("app.runtime", IDR_APP_RUNTIME_CUSTOM_BINDINGS_JS); 895 source_map_.RegisterSource("app.runtime", IDR_APP_RUNTIME_CUSTOM_BINDINGS_JS);
886 source_map_.RegisterSource("app.window", IDR_APP_WINDOW_CUSTOM_BINDINGS_JS); 896 source_map_.RegisterSource("app.window", IDR_APP_WINDOW_CUSTOM_BINDINGS_JS);
887 source_map_.RegisterSource("bluetooth", IDR_BLUETOOTH_CUSTOM_BINDINGS_JS); 897 source_map_.RegisterSource("bluetooth", IDR_BLUETOOTH_CUSTOM_BINDINGS_JS);
888 source_map_.RegisterSource("browserAction", 898 source_map_.RegisterSource("browserAction",
889 IDR_BROWSER_ACTION_CUSTOM_BINDINGS_JS); 899 IDR_BROWSER_ACTION_CUSTOM_BINDINGS_JS);
(...skipping 121 matching lines...) Expand 10 before | Expand all | Expand 10 after
1011 ExtensionURLInfo url_info(frame->document().securityOrigin(), 1021 ExtensionURLInfo url_info(frame->document().securityOrigin(),
1012 UserScriptSlave::GetDataSourceURLForFrame(frame)); 1022 UserScriptSlave::GetDataSourceURLForFrame(frame));
1013 1023
1014 Feature::Context context_type = 1024 Feature::Context context_type =
1015 ClassifyJavaScriptContext(extension_id, extension_group, url_info); 1025 ClassifyJavaScriptContext(extension_id, extension_group, url_info);
1016 1026
1017 ChromeV8Context* context = 1027 ChromeV8Context* context =
1018 new ChromeV8Context(v8_context, frame, extension, context_type); 1028 new ChromeV8Context(v8_context, frame, extension, context_type);
1019 v8_context_set_.Add(context); 1029 v8_context_set_.Add(context);
1020 1030
1021 scoped_ptr<ModuleSystem> module_system(new ModuleSystem(v8_context, 1031 scoped_ptr<ModuleSystem> module_system(new ModuleSystem(context,
1022 &source_map_)); 1032 &source_map_));
1023 // Enable natives in startup. 1033 // Enable natives in startup.
1024 ModuleSystem::NativesEnabledScope natives_enabled_scope(module_system.get()); 1034 ModuleSystem::NativesEnabledScope natives_enabled_scope(module_system.get());
1025 1035
1026 RegisterNativeHandlers(module_system.get(), context); 1036 RegisterNativeHandlers(module_system.get(), context);
1027 1037
1028 module_system->RegisterNativeHandler("chrome", 1038 module_system->RegisterNativeHandler("chrome",
1029 scoped_ptr<NativeHandler>(new ChromeNativeHandler(v8_context))); 1039 scoped_ptr<NativeHandler>(new ChromeNativeHandler(v8_context)));
1030 module_system->RegisterNativeHandler("chrome_hidden", 1040 module_system->RegisterNativeHandler("chrome_hidden",
1031 scoped_ptr<NativeHandler>(new ChromeHiddenNativeHandler(v8_context))); 1041 scoped_ptr<NativeHandler>(new ChromeHiddenNativeHandler(v8_context)));
(...skipping 30 matching lines...) Expand all
1062 switch (context_type) { 1072 switch (context_type) {
1063 case Feature::UNSPECIFIED_CONTEXT: 1073 case Feature::UNSPECIFIED_CONTEXT:
1064 case Feature::WEB_PAGE_CONTEXT: 1074 case Feature::WEB_PAGE_CONTEXT:
1065 // TODO(kalman): see comment below about ExtensionAPI. 1075 // TODO(kalman): see comment below about ExtensionAPI.
1066 InstallBindings(module_system.get(), v8_context, "app"); 1076 InstallBindings(module_system.get(), v8_context, "app");
1067 InstallBindings(module_system.get(), v8_context, "webstore"); 1077 InstallBindings(module_system.get(), v8_context, "webstore");
1068 break; 1078 break;
1069 case Feature::BLESSED_EXTENSION_CONTEXT: 1079 case Feature::BLESSED_EXTENSION_CONTEXT:
1070 case Feature::UNBLESSED_EXTENSION_CONTEXT: 1080 case Feature::UNBLESSED_EXTENSION_CONTEXT:
1071 case Feature::CONTENT_SCRIPT_CONTEXT: 1081 case Feature::CONTENT_SCRIPT_CONTEXT:
1072 if (extension && !extension->is_platform_app())
1073 module_system->Require("miscellaneous_bindings");
1074 module_system->Require("json"); // see paranoid comment in json.js 1082 module_system->Require("json"); // see paranoid comment in json.js
1075 1083
1076 // TODO(kalman): move this code back out of the switch and execute it 1084 // TODO(kalman): move this code back out of the switch and execute it
1077 // regardless of |context_type|. ExtensionAPI knows how to return the 1085 // regardless of |context_type|. ExtensionAPI knows how to return the
1078 // correct APIs, however, until it doesn't have a 2MB overhead we can't 1086 // correct APIs, however, until it doesn't have a 2MB overhead we can't
1079 // load it in every process. 1087 // load it in every process.
1080 RegisterSchemaGeneratedBindings(module_system.get(), context); 1088 RegisterSchemaGeneratedBindings(module_system.get(), context);
1081 break; 1089 break;
1082 } 1090 }
1083 1091
(...skipping 263 matching lines...) Expand 10 before | Expand all | Expand 10 after
1347 RenderThread::Get()->Send( 1355 RenderThread::Get()->Send(
1348 new ExtensionHostMsg_ShouldSuspendAck(extension_id, sequence_id)); 1356 new ExtensionHostMsg_ShouldSuspendAck(extension_id, sequence_id));
1349 } 1357 }
1350 1358
1351 void Dispatcher::OnSuspend(const std::string& extension_id) { 1359 void Dispatcher::OnSuspend(const std::string& extension_id) {
1352 // Dispatch the suspend event. This doesn't go through the standard event 1360 // Dispatch the suspend event. This doesn't go through the standard event
1353 // dispatch machinery because it requires special handling. We need to let 1361 // dispatch machinery because it requires special handling. We need to let
1354 // the browser know when we are starting and stopping the event dispatch, so 1362 // the browser know when we are starting and stopping the event dispatch, so
1355 // that it still considers the extension idle despite any activity the suspend 1363 // that it still considers the extension idle despite any activity the suspend
1356 // event creates. 1364 // event creates.
1357 base::ListValue args; 1365 DispatchEvent(extension_id, kOnSuspendEvent);
1358 args.Set(0, new base::StringValue(kOnSuspendEvent));
1359 args.Set(1, new base::ListValue());
1360 v8_context_set_.DispatchChromeHiddenMethod(
1361 extension_id, kEventDispatchFunction, args, NULL);
1362
1363 RenderThread::Get()->Send(new ExtensionHostMsg_SuspendAck(extension_id)); 1366 RenderThread::Get()->Send(new ExtensionHostMsg_SuspendAck(extension_id));
1364 } 1367 }
1365 1368
1366 void Dispatcher::OnCancelSuspend(const std::string& extension_id) { 1369 void Dispatcher::OnCancelSuspend(const std::string& extension_id) {
1367 base::ListValue args; 1370 DispatchEvent(extension_id, kOnSuspendCanceledEvent);
1368 args.Set(0, new base::StringValue(kOnSuspendCanceledEvent));
1369 args.Set(1, new base::ListValue());
1370 v8_context_set_.DispatchChromeHiddenMethod(
1371 extension_id, kEventDispatchFunction, args, NULL);
1372 } 1371 }
1373 1372
1374 Feature::Context Dispatcher::ClassifyJavaScriptContext( 1373 Feature::Context Dispatcher::ClassifyJavaScriptContext(
1375 const std::string& extension_id, 1374 const std::string& extension_id,
1376 int extension_group, 1375 int extension_group,
1377 const ExtensionURLInfo& url_info) { 1376 const ExtensionURLInfo& url_info) {
1378 if (extension_group == EXTENSION_GROUP_CONTENT_SCRIPTS) { 1377 if (extension_group == EXTENSION_GROUP_CONTENT_SCRIPTS) {
1379 return extensions_.Contains(extension_id) ? 1378 return extensions_.Contains(extension_id) ?
1380 Feature::CONTENT_SCRIPT_CONTEXT : Feature::UNSPECIFIED_CONTEXT; 1379 Feature::CONTENT_SCRIPT_CONTEXT : Feature::UNSPECIFIED_CONTEXT;
1381 } 1380 }
(...skipping 75 matching lines...) Expand 10 before | Expand all | Expand 10 after
1457 "%s cannot be used within a sandboxed frame."; 1456 "%s cannot be used within a sandboxed frame.";
1458 std::string error_msg = base::StringPrintf(kMessage, function_name.c_str()); 1457 std::string error_msg = base::StringPrintf(kMessage, function_name.c_str());
1459 v8::ThrowException( 1458 v8::ThrowException(
1460 v8::Exception::Error(v8::String::New(error_msg.c_str()))); 1459 v8::Exception::Error(v8::String::New(error_msg.c_str())));
1461 return false; 1460 return false;
1462 } 1461 }
1463 1462
1464 return true; 1463 return true;
1465 } 1464 }
1466 1465
1466 void Dispatcher::DispatchEvent(const std::string& extension_id,
1467 const std::string& event_name) const {
1468 base::ListValue args;
1469 args.Set(0, new base::StringValue(event_name));
1470 args.Set(1, new base::ListValue());
1471 v8_context_set_.ForEach(
1472 extension_id,
1473 NULL, // all render views
1474 base::Bind(&CallModuleMethod,
1475 kEventModule,
1476 kEventDispatchFunction,
1477 &args));
1478 }
1479
1480 // static
1481 void Dispatcher::CallModuleMethod(const std::string& module_name,
1482 const std::string& method_name,
1483 const base::ListValue* args,
1484 ChromeV8Context* context) {
1485 v8::HandleScope handle_scope;
1486 v8::Context::Scope context_scope(context->v8_context());
1487
1488 scoped_ptr<content::V8ValueConverter> converter(
1489 content::V8ValueConverter::create());
1490
1491 std::vector<v8::Handle<v8::Value> > arguments;
1492 for (base::ListValue::const_iterator it = args->begin(); it != args->end();
1493 ++it) {
1494 arguments.push_back(converter->ToV8Value(*it, context->v8_context()));
1495 }
1496
1497 context->module_system()->CallModuleMethod(
1498 module_name, method_name, &arguments);
1499 }
1500
1467 } // namespace extensions 1501 } // namespace extensions
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698