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

Side by Side Diff: content/browser/service_worker/embedded_worker_instance.cc

Issue 2787883003: [ServiceWorker] Add EmbeddedWorkerInstanceHost Interface. (Closed)
Patch Set: Refine code comments Created 3 years, 8 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 2013 The Chromium Authors. All rights reserved. 1 // Copyright 2013 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/browser/service_worker/embedded_worker_instance.h" 5 #include "content/browser/service_worker/embedded_worker_instance.h"
6 6
7 #include <utility> 7 #include <utility>
8 8
9 #include "base/bind_helpers.h" 9 #include "base/bind_helpers.h"
10 #include "base/macros.h" 10 #include "base/macros.h"
11 #include "base/memory/ptr_util.h" 11 #include "base/memory/ptr_util.h"
12 #include "base/metrics/histogram_macros.h" 12 #include "base/metrics/histogram_macros.h"
13 #include "base/threading/non_thread_safe.h" 13 #include "base/threading/non_thread_safe.h"
14 #include "base/trace_event/trace_event.h" 14 #include "base/trace_event/trace_event.h"
15 #include "content/browser/bad_message.h"
15 #include "content/browser/devtools/service_worker_devtools_manager.h" 16 #include "content/browser/devtools/service_worker_devtools_manager.h"
16 #include "content/browser/service_worker/embedded_worker_registry.h" 17 #include "content/browser/service_worker/embedded_worker_registry.h"
17 #include "content/browser/service_worker/embedded_worker_status.h" 18 #include "content/browser/service_worker/embedded_worker_status.h"
18 #include "content/browser/service_worker/service_worker_context_core.h" 19 #include "content/browser/service_worker/service_worker_context_core.h"
19 #include "content/common/content_switches_internal.h" 20 #include "content/common/content_switches_internal.h"
20 #include "content/common/service_worker/embedded_worker_messages.h" 21 #include "content/common/service_worker/embedded_worker_messages.h"
21 #include "content/common/service_worker/embedded_worker_settings.h" 22 #include "content/common/service_worker/embedded_worker_settings.h"
22 #include "content/common/service_worker/embedded_worker_start_params.h" 23 #include "content/common/service_worker/embedded_worker_start_params.h"
23 #include "content/common/service_worker/service_worker_types.h" 24 #include "content/common/service_worker/service_worker_types.h"
24 #include "content/common/service_worker/service_worker_utils.h" 25 #include "content/common/service_worker/service_worker_utils.h"
(...skipping 445 matching lines...) Expand 10 before | Expand all | Expand 10 after
470 } 471 }
471 472
472 bool EmbeddedWorkerInstance::Stop() { 473 bool EmbeddedWorkerInstance::Stop() {
473 DCHECK(status_ == EmbeddedWorkerStatus::STARTING || 474 DCHECK(status_ == EmbeddedWorkerStatus::STARTING ||
474 status_ == EmbeddedWorkerStatus::RUNNING) 475 status_ == EmbeddedWorkerStatus::RUNNING)
475 << static_cast<int>(status_); 476 << static_cast<int>(status_);
476 477
477 // Abort an inflight start task. 478 // Abort an inflight start task.
478 inflight_start_task_.reset(); 479 inflight_start_task_.reset();
479 480
480 if (ServiceWorkerUtils::IsMojoForServiceWorkerEnabled()) {
481 if (status_ == EmbeddedWorkerStatus::STARTING && 481 if (status_ == EmbeddedWorkerStatus::STARTING &&
482 !HasSentStartWorker(starting_phase())) { 482 !HasSentStartWorker(starting_phase())) {
483 // Don't send the StopWorker message when the StartWorker message hasn't 483 // Don't send the StopWorker message when the StartWorker message hasn't
484 // been sent. 484 // been sent.
485 // TODO(shimazu): Invoke OnStopping/OnStopped after the legacy IPC path is 485 // TODO(shimazu): Invoke OnStopping/OnStopped after the legacy IPC path is
486 // removed. 486 // removed.
487 OnDetached(); 487 OnDetached();
488 return false; 488 return false;
489 } 489 }
490 client_->StopWorker(base::Bind(&EmbeddedWorkerRegistry::OnWorkerStopped, 490 client_->StopWorker();
491 base::Unretained(registry_.get()),
492 process_id(), embedded_worker_id()));
493 } else {
494 ServiceWorkerStatusCode status =
495 registry_->StopWorker(process_id(), embedded_worker_id_);
496 UMA_HISTOGRAM_ENUMERATION("ServiceWorker.SendStopWorker.Status", status,
497 SERVICE_WORKER_ERROR_MAX_VALUE);
498 // StopWorker could fail if we were starting up and don't have a process
499 // yet, or we can no longer communicate with the process. So just detach.
500 if (status != SERVICE_WORKER_OK) {
501 OnDetached();
502 return false;
503 }
504 }
505 491
506 status_ = EmbeddedWorkerStatus::STOPPING; 492 status_ = EmbeddedWorkerStatus::STOPPING;
507 for (auto& observer : listener_list_) 493 for (auto& observer : listener_list_)
508 observer.OnStopping(); 494 observer.OnStopping();
509 return true; 495 return true;
510 } 496 }
511 497
512 void EmbeddedWorkerInstance::StopIfIdle() { 498 void EmbeddedWorkerInstance::StopIfIdle() {
513 if (devtools_attached_) { 499 if (devtools_attached_) {
514 if (devtools_proxy_) { 500 if (devtools_proxy_) {
515 // Check ShouldNotifyWorkerStopIgnored not to show the same message 501 // Check ShouldNotifyWorkerStopIgnored not to show the same message
516 // multiple times in DevTools. 502 // multiple times in DevTools.
517 if (devtools_proxy_->ShouldNotifyWorkerStopIgnored()) { 503 if (devtools_proxy_->ShouldNotifyWorkerStopIgnored()) {
518 AddMessageToConsole(blink::WebConsoleMessage::kLevelVerbose, 504 AddMessageToConsole(blink::WebConsoleMessage::kLevelVerbose,
519 kServiceWorkerTerminationCanceledMesage); 505 kServiceWorkerTerminationCanceledMesage);
(...skipping 29 matching lines...) Expand all
549 EmbeddedWorkerInstance::EmbeddedWorkerInstance( 535 EmbeddedWorkerInstance::EmbeddedWorkerInstance(
550 base::WeakPtr<ServiceWorkerContextCore> context, 536 base::WeakPtr<ServiceWorkerContextCore> context,
551 int embedded_worker_id) 537 int embedded_worker_id)
552 : context_(context), 538 : context_(context),
553 registry_(context->embedded_worker_registry()), 539 registry_(context->embedded_worker_registry()),
554 embedded_worker_id_(embedded_worker_id), 540 embedded_worker_id_(embedded_worker_id),
555 status_(EmbeddedWorkerStatus::STOPPED), 541 status_(EmbeddedWorkerStatus::STOPPED),
556 starting_phase_(NOT_STARTING), 542 starting_phase_(NOT_STARTING),
557 restart_count_(0), 543 restart_count_(0),
558 thread_id_(kInvalidEmbeddedWorkerThreadId), 544 thread_id_(kInvalidEmbeddedWorkerThreadId),
545 instance_host_binding_(this),
559 devtools_attached_(false), 546 devtools_attached_(false),
560 network_accessed_for_script_(false), 547 network_accessed_for_script_(false),
561 weak_factory_(this) {} 548 weak_factory_(this) {}
562 549
563 void EmbeddedWorkerInstance::OnProcessAllocated( 550 void EmbeddedWorkerInstance::OnProcessAllocated(
564 std::unique_ptr<WorkerProcessHandle> handle, 551 std::unique_ptr<WorkerProcessHandle> handle,
565 ServiceWorkerMetrics::StartSituation start_situation) { 552 ServiceWorkerMetrics::StartSituation start_situation) {
566 DCHECK_EQ(EmbeddedWorkerStatus::STARTING, status_); 553 DCHECK_EQ(EmbeddedWorkerStatus::STARTING, status_);
567 DCHECK(!process_handle_); 554 DCHECK(!process_handle_);
568 555
(...skipping 19 matching lines...) Expand all
588 step_time_ = base::TimeTicks(); 575 step_time_ = base::TimeTicks();
589 } 576 }
590 for (auto& observer : listener_list_) 577 for (auto& observer : listener_list_)
591 observer.OnRegisteredToDevToolsManager(); 578 observer.OnRegisteredToDevToolsManager();
592 } 579 }
593 580
594 ServiceWorkerStatusCode EmbeddedWorkerInstance::SendStartWorker( 581 ServiceWorkerStatusCode EmbeddedWorkerInstance::SendStartWorker(
595 std::unique_ptr<EmbeddedWorkerStartParams> params) { 582 std::unique_ptr<EmbeddedWorkerStartParams> params) {
596 if (!context_) 583 if (!context_)
597 return SERVICE_WORKER_ERROR_ABORT; 584 return SERVICE_WORKER_ERROR_ABORT;
585
586 DCHECK(!instance_host_binding_.is_bound());
587 mojom::EmbeddedWorkerInstanceHostAssociatedPtrInfo host_ptr_info;
588 instance_host_binding_.Bind(&host_ptr_info);
589
598 DCHECK(pending_dispatcher_request_.is_pending()); 590 DCHECK(pending_dispatcher_request_.is_pending());
599 client_->StartWorker(*params, std::move(pending_dispatcher_request_)); 591 client_->StartWorker(*params, std::move(pending_dispatcher_request_),
592 std::move(host_ptr_info));
600 registry_->BindWorkerToProcess(process_id(), embedded_worker_id()); 593 registry_->BindWorkerToProcess(process_id(), embedded_worker_id());
601 TRACE_EVENT_ASYNC_STEP_PAST0("ServiceWorker", "EmbeddedWorkerInstance::Start", 594 TRACE_EVENT_ASYNC_STEP_PAST0("ServiceWorker", "EmbeddedWorkerInstance::Start",
602 this, "SendStartWorker"); 595 this, "SendStartWorker");
603 OnStartWorkerMessageSent(); 596 OnStartWorkerMessageSent();
604 return SERVICE_WORKER_OK; 597 return SERVICE_WORKER_OK;
605 } 598 }
606 599
607 void EmbeddedWorkerInstance::OnStartWorkerMessageSent() { 600 void EmbeddedWorkerInstance::OnStartWorkerMessageSent() {
608 if (!step_time_.is_null()) { 601 if (!step_time_.is_null()) {
609 base::TimeDelta duration = UpdateStepTime(); 602 base::TimeDelta duration = UpdateStepTime();
610 if (inflight_start_task_->is_installed()) { 603 if (inflight_start_task_->is_installed()) {
611 ServiceWorkerMetrics::RecordTimeToSendStartWorker(duration, 604 ServiceWorkerMetrics::RecordTimeToSendStartWorker(duration,
612 start_situation_); 605 start_situation_);
613 } 606 }
614 } 607 }
615 608
616 starting_phase_ = SENT_START_WORKER; 609 starting_phase_ = SENT_START_WORKER;
617 for (auto& observer : listener_list_) 610 for (auto& observer : listener_list_)
618 observer.OnStartWorkerMessageSent(); 611 observer.OnStartWorkerMessageSent();
619 } 612 }
620 613
621 void EmbeddedWorkerInstance::OnReadyForInspection() { 614 void EmbeddedWorkerInstance::OnReadyForInspection() {
615 TRACE_EVENT0("ServiceWorker", "EmbeddedWorkerInstance::OnReadyForInspection");
622 if (devtools_proxy_) 616 if (devtools_proxy_)
623 devtools_proxy_->NotifyWorkerReadyForInspection(); 617 devtools_proxy_->NotifyWorkerReadyForInspection();
624 } 618 }
625 619
626 void EmbeddedWorkerInstance::OnScriptReadStarted() { 620 void EmbeddedWorkerInstance::OnScriptReadStarted() {
627 starting_phase_ = SCRIPT_READ_STARTED; 621 starting_phase_ = SCRIPT_READ_STARTED;
628 } 622 }
629 623
630 void EmbeddedWorkerInstance::OnScriptReadFinished() { 624 void EmbeddedWorkerInstance::OnScriptReadFinished() {
631 starting_phase_ = SCRIPT_READ_FINISHED; 625 starting_phase_ = SCRIPT_READ_FINISHED;
632 } 626 }
633 627
634 void EmbeddedWorkerInstance::OnScriptLoaded() { 628 void EmbeddedWorkerInstance::OnScriptLoaded() {
635 using LoadSource = ServiceWorkerMetrics::LoadSource; 629 using LoadSource = ServiceWorkerMetrics::LoadSource;
636 630
631 TRACE_EVENT0("ServiceWorker", "EmbeddedWorkerInstance::OnScriptLoaded");
632
637 if (!inflight_start_task_) 633 if (!inflight_start_task_)
638 return; 634 return;
639 LoadSource source; 635 LoadSource source;
640 if (network_accessed_for_script_) { 636 if (network_accessed_for_script_) {
641 DCHECK(!inflight_start_task_->is_installed()); 637 DCHECK(!inflight_start_task_->is_installed());
642 source = LoadSource::NETWORK; 638 source = LoadSource::NETWORK;
643 } else if (inflight_start_task_->is_installed()) { 639 } else if (inflight_start_task_->is_installed()) {
644 source = LoadSource::SERVICE_WORKER_STORAGE; 640 source = LoadSource::SERVICE_WORKER_STORAGE;
645 } else { 641 } else {
646 source = LoadSource::HTTP_CACHE; 642 source = LoadSource::HTTP_CACHE;
(...skipping 29 matching lines...) Expand all
676 void EmbeddedWorkerInstance::OnWorkerVersionInstalled() { 672 void EmbeddedWorkerInstance::OnWorkerVersionInstalled() {
677 if (devtools_proxy_) 673 if (devtools_proxy_)
678 devtools_proxy_->NotifyWorkerVersionInstalled(); 674 devtools_proxy_->NotifyWorkerVersionInstalled();
679 } 675 }
680 676
681 void EmbeddedWorkerInstance::OnWorkerVersionDoomed() { 677 void EmbeddedWorkerInstance::OnWorkerVersionDoomed() {
682 if (devtools_proxy_) 678 if (devtools_proxy_)
683 devtools_proxy_->NotifyWorkerVersionDoomed(); 679 devtools_proxy_->NotifyWorkerVersionDoomed();
684 } 680 }
685 681
686 void EmbeddedWorkerInstance::OnThreadStarted(int thread_id) { 682 void EmbeddedWorkerInstance::OnThreadStarted(int thread_id, int provider_id) {
683 TRACE_EVENT0("ServiceWorker", "EmbeddedWorkerInstance::OnThreadStarted");
684 if (!context_)
685 return;
686
687 ServiceWorkerProviderHost* provider_host =
688 context_->GetProviderHost(process_id(), provider_id);
689 if (!provider_host) {
690 bad_message::ReceivedBadMessage(
691 process_id(), bad_message::SWDH_WORKER_SCRIPT_LOAD_NO_HOST);
692 return;
693 }
694
695 provider_host->SetReadyToSendMessagesToWorker(thread_id);
696
687 if (!inflight_start_task_) 697 if (!inflight_start_task_)
688 return; 698 return;
689 TRACE_EVENT_ASYNC_STEP_PAST0("ServiceWorker", "EmbeddedWorkerInstance::Start", 699 TRACE_EVENT_ASYNC_STEP_PAST0("ServiceWorker", "EmbeddedWorkerInstance::Start",
690 inflight_start_task_.get(), "OnThreadStarted"); 700 inflight_start_task_.get(), "OnThreadStarted");
691 701
692 starting_phase_ = THREAD_STARTED; 702 starting_phase_ = THREAD_STARTED;
693 if (!step_time_.is_null()) { 703 if (!step_time_.is_null()) {
694 base::TimeDelta duration = UpdateStepTime(); 704 base::TimeDelta duration = UpdateStepTime();
695 if (inflight_start_task_->is_installed()) 705 if (inflight_start_task_->is_installed())
696 ServiceWorkerMetrics::RecordTimeToStartThread(duration, start_situation_); 706 ServiceWorkerMetrics::RecordTimeToStartThread(duration, start_situation_);
697 } 707 }
698 708
699 thread_id_ = thread_id; 709 thread_id_ = thread_id;
700 for (auto& observer : listener_list_) 710 for (auto& observer : listener_list_)
701 observer.OnThreadStarted(); 711 observer.OnThreadStarted();
702 } 712 }
703 713
704 void EmbeddedWorkerInstance::OnScriptLoadFailed() { 714 void EmbeddedWorkerInstance::OnScriptLoadFailed() {
715 TRACE_EVENT0("ServiceWorker", "EmbeddedWorkerInstance::OnScriptLoadFailed");
705 if (!inflight_start_task_) 716 if (!inflight_start_task_)
706 return; 717 return;
707 TRACE_EVENT_ASYNC_STEP_PAST0("ServiceWorker", "EmbeddedWorkerInstance::Start", 718 TRACE_EVENT_ASYNC_STEP_PAST0("ServiceWorker", "EmbeddedWorkerInstance::Start",
708 inflight_start_task_.get(), 719 inflight_start_task_.get(),
709 "OnScriptLoadFailed"); 720 "OnScriptLoadFailed");
710 for (auto& observer : listener_list_) 721 for (auto& observer : listener_list_)
711 observer.OnScriptLoadFailed(); 722 observer.OnScriptLoadFailed();
712 } 723 }
713 724
714 void EmbeddedWorkerInstance::OnScriptEvaluated(bool success) { 725 void EmbeddedWorkerInstance::OnScriptEvaluated(bool success) {
726 TRACE_EVENT0("ServiceWorker", "EmbeddedWorkerInstance::OnScriptEvaluated");
715 if (!inflight_start_task_) 727 if (!inflight_start_task_)
716 return; 728 return;
717 DCHECK_EQ(EmbeddedWorkerStatus::STARTING, status_); 729 DCHECK_EQ(EmbeddedWorkerStatus::STARTING, status_);
718 730
719 TRACE_EVENT_ASYNC_STEP_PAST1("ServiceWorker", "EmbeddedWorkerInstance::Start", 731 TRACE_EVENT_ASYNC_STEP_PAST1("ServiceWorker", "EmbeddedWorkerInstance::Start",
720 inflight_start_task_.get(), "OnScriptEvaluated", 732 inflight_start_task_.get(), "OnScriptEvaluated",
721 "Success", success); 733 "Success", success);
722 starting_phase_ = SCRIPT_EVALUATED; 734 starting_phase_ = SCRIPT_EVALUATED;
723 if (!step_time_.is_null()) { 735 if (!step_time_.is_null()) {
724 base::TimeDelta duration = UpdateStepTime(); 736 base::TimeDelta duration = UpdateStepTime();
725 if (success && inflight_start_task_->is_installed()) 737 if (success && inflight_start_task_->is_installed())
726 ServiceWorkerMetrics::RecordTimeToEvaluateScript(duration, 738 ServiceWorkerMetrics::RecordTimeToEvaluateScript(duration,
727 start_situation_); 739 start_situation_);
728 } 740 }
729 741
730 base::WeakPtr<EmbeddedWorkerInstance> weak_this = weak_factory_.GetWeakPtr(); 742 base::WeakPtr<EmbeddedWorkerInstance> weak_this = weak_factory_.GetWeakPtr();
731 StartTask::RunStartCallback( 743 StartTask::RunStartCallback(
732 inflight_start_task_.get(), 744 inflight_start_task_.get(),
733 success ? SERVICE_WORKER_OK 745 success ? SERVICE_WORKER_OK
734 : SERVICE_WORKER_ERROR_SCRIPT_EVALUATE_FAILED); 746 : SERVICE_WORKER_ERROR_SCRIPT_EVALUATE_FAILED);
735 // |this| may be destroyed by the callback. 747 // |this| may be destroyed by the callback.
736 } 748 }
737 749
738 void EmbeddedWorkerInstance::OnStarted() { 750 void EmbeddedWorkerInstance::OnStarted() {
751 TRACE_EVENT0("ServiceWorker", "EmbeddedWorkerInstance::OnStarted");
752 if (!registry_->OnWorkerStarted(process_id(), embedded_worker_id_))
753 return;
739 // Stop is requested before OnStarted is sent back from the worker. 754 // Stop is requested before OnStarted is sent back from the worker.
740 if (status_ == EmbeddedWorkerStatus::STOPPING) 755 if (status_ == EmbeddedWorkerStatus::STOPPING)
741 return; 756 return;
742 DCHECK(status_ == EmbeddedWorkerStatus::STARTING); 757 DCHECK(status_ == EmbeddedWorkerStatus::STARTING);
743 status_ = EmbeddedWorkerStatus::RUNNING; 758 status_ = EmbeddedWorkerStatus::RUNNING;
744 inflight_start_task_.reset(); 759 inflight_start_task_.reset();
745 for (auto& observer : listener_list_) 760 for (auto& observer : listener_list_)
746 observer.OnStarted(); 761 observer.OnStarted();
747 } 762 }
748 763
749 void EmbeddedWorkerInstance::OnStopped() { 764 void EmbeddedWorkerInstance::OnStopped() {
765 registry_->OnWorkerStopped(process_id(), embedded_worker_id_);
766
750 EmbeddedWorkerStatus old_status = status_; 767 EmbeddedWorkerStatus old_status = status_;
751 ReleaseProcess(); 768 ReleaseProcess();
752 for (auto& observer : listener_list_) 769 for (auto& observer : listener_list_)
753 observer.OnStopped(old_status); 770 observer.OnStopped(old_status);
754 } 771 }
755 772
756 void EmbeddedWorkerInstance::OnDetached() { 773 void EmbeddedWorkerInstance::OnDetached() {
757 EmbeddedWorkerStatus old_status = status_; 774 EmbeddedWorkerStatus old_status = status_;
758 ReleaseProcess(); 775 ReleaseProcess();
759 for (auto& observer : listener_list_) 776 for (auto& observer : listener_list_)
(...skipping 74 matching lines...) Expand 10 before | Expand all | Expand 10 after
834 void EmbeddedWorkerInstance::OnNetworkAccessedForScriptLoad() { 851 void EmbeddedWorkerInstance::OnNetworkAccessedForScriptLoad() {
835 starting_phase_ = SCRIPT_DOWNLOADING; 852 starting_phase_ = SCRIPT_DOWNLOADING;
836 network_accessed_for_script_ = true; 853 network_accessed_for_script_ = true;
837 } 854 }
838 855
839 void EmbeddedWorkerInstance::ReleaseProcess() { 856 void EmbeddedWorkerInstance::ReleaseProcess() {
840 // Abort an inflight start task. 857 // Abort an inflight start task.
841 inflight_start_task_.reset(); 858 inflight_start_task_.reset();
842 859
843 client_.reset(); 860 client_.reset();
861 instance_host_binding_.Close();
844 devtools_proxy_.reset(); 862 devtools_proxy_.reset();
845 process_handle_.reset(); 863 process_handle_.reset();
846 status_ = EmbeddedWorkerStatus::STOPPED; 864 status_ = EmbeddedWorkerStatus::STOPPED;
847 starting_phase_ = NOT_STARTING; 865 starting_phase_ = NOT_STARTING;
848 thread_id_ = kInvalidEmbeddedWorkerThreadId; 866 thread_id_ = kInvalidEmbeddedWorkerThreadId;
849 } 867 }
850 868
851 void EmbeddedWorkerInstance::OnStartFailed(const StatusCallback& callback, 869 void EmbeddedWorkerInstance::OnStartFailed(const StatusCallback& callback,
852 ServiceWorkerStatusCode status) { 870 ServiceWorkerStatusCode status) {
853 EmbeddedWorkerStatus old_status = status_; 871 EmbeddedWorkerStatus old_status = status_;
(...skipping 65 matching lines...) Expand 10 before | Expand all | Expand 10 after
919 case SCRIPT_READ_FINISHED: 937 case SCRIPT_READ_FINISHED:
920 return "Script read finished"; 938 return "Script read finished";
921 case STARTING_PHASE_MAX_VALUE: 939 case STARTING_PHASE_MAX_VALUE:
922 NOTREACHED(); 940 NOTREACHED();
923 } 941 }
924 NOTREACHED() << phase; 942 NOTREACHED() << phase;
925 return std::string(); 943 return std::string();
926 } 944 }
927 945
928 } // namespace content 946 } // namespace content
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698