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

Side by Side Diff: content/browser/loader/resource_dispatcher_host_browsertest.cc

Issue 2405483002: Make the request initiator Optional (Closed)
Patch Set: Introduce NullableOrigin to use for request initiator Created 4 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
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 "content/public/browser/resource_dispatcher_host.h" 5 #include "content/public/browser/resource_dispatcher_host.h"
6 6
7 #include <utility> 7 #include <utility>
8 8
9 #include "base/bind.h" 9 #include "base/bind.h"
10 #include "base/bind_helpers.h" 10 #include "base/bind_helpers.h"
(...skipping 737 matching lines...) Expand 10 before | Expand all | Expand 10 after
748 shell()->web_contents()->GetController().ReloadDisableLoFi(true); 748 shell()->web_contents()->GetController().ReloadDisableLoFi(true);
749 tab_observer.Wait(); 749 tab_observer.Wait();
750 CheckResourcesRequested(false); 750 CheckResourcesRequested(false);
751 } 751 }
752 752
753 namespace { 753 namespace {
754 754
755 struct RequestDataForDelegate { 755 struct RequestDataForDelegate {
756 const GURL url; 756 const GURL url;
757 const GURL first_party; 757 const GURL first_party;
758 const url::Origin initiator; 758 scoped_refptr<url::NullableOrigin> initiator;
759 759
760 RequestDataForDelegate(const GURL& url, 760 RequestDataForDelegate(const GURL& url,
761 const GURL& first_party, 761 const GURL& first_party,
762 const url::Origin initiator) 762 scoped_refptr<url::NullableOrigin> initiator)
763 : url(url), first_party(first_party), initiator(initiator) {} 763 : url(url), first_party(first_party), initiator(initiator) {}
764 }; 764 };
765 765
766 // Captures calls to 'RequestBeginning' and records the URL, first-party for 766 // Captures calls to 'RequestBeginning' and records the URL, first-party for
767 // cookies, and initiator. 767 // cookies, and initiator.
768 class RequestDataResourceDispatcherHostDelegate 768 class RequestDataResourceDispatcherHostDelegate
769 : public ResourceDispatcherHostDelegate { 769 : public ResourceDispatcherHostDelegate {
770 public: 770 public:
771 RequestDataResourceDispatcherHostDelegate() {} 771 RequestDataResourceDispatcherHostDelegate() {}
772 772
(...skipping 51 matching lines...) Expand 10 before | Expand all | Expand 10 after
824 NavigateToURLBlockUntilNavigationsComplete(shell(), top_url, 1); 824 NavigateToURLBlockUntilNavigationsComplete(shell(), top_url, 1);
825 825
826 EXPECT_EQ(8u, delegate_->data().size()); 826 EXPECT_EQ(8u, delegate_->data().size());
827 827
828 // All resources loaded directly by the top-level document (including the 828 // All resources loaded directly by the top-level document (including the
829 // top-level document itself) should have a |first_party| and |initiator| 829 // top-level document itself) should have a |first_party| and |initiator|
830 // that match the URL of the top-level document. 830 // that match the URL of the top-level document.
831 for (auto* request : delegate_->data()) { 831 for (auto* request : delegate_->data()) {
832 SCOPED_TRACE(request->url); 832 SCOPED_TRACE(request->url);
833 EXPECT_EQ(top_url, request->first_party); 833 EXPECT_EQ(top_url, request->first_party);
834 EXPECT_EQ(top_origin, request->initiator); 834 ASSERT_TRUE(request->initiator);
835 EXPECT_EQ(top_origin, *request->initiator);
835 } 836 }
836 } 837 }
837 838
838 IN_PROC_BROWSER_TEST_F(RequestDataResourceDispatcherHostBrowserTest, 839 IN_PROC_BROWSER_TEST_F(RequestDataResourceDispatcherHostBrowserTest,
839 BasicCrossSite) { 840 BasicCrossSite) {
840 host_resolver()->AddRule("*", "127.0.0.1"); 841 host_resolver()->AddRule("*", "127.0.0.1");
841 GURL top_url(embedded_test_server()->GetURL( 842 GURL top_url(embedded_test_server()->GetURL(
842 "a.com", "/nested_page_with_subresources.html")); 843 "a.com", "/nested_page_with_subresources.html"));
843 GURL nested_url(embedded_test_server()->GetURL( 844 GURL nested_url(embedded_test_server()->GetURL(
844 "not-a.com", "/page_with_subresources.html")); 845 "not-a.com", "/page_with_subresources.html"));
845 url::Origin top_origin(top_url); 846 url::Origin top_origin(top_url);
846 url::Origin nested_origin(nested_url); 847 url::Origin nested_origin(nested_url);
847 848
848 NavigateToURLBlockUntilNavigationsComplete(shell(), top_url, 1); 849 NavigateToURLBlockUntilNavigationsComplete(shell(), top_url, 1);
849 850
850 EXPECT_EQ(9u, delegate_->data().size()); 851 EXPECT_EQ(9u, delegate_->data().size());
851 852
852 // The first items loaded are the top-level and nested documents. These should 853 // The first items loaded are the top-level and nested documents. These should
853 // both have a |first_party| and |initiator| that match the URL of the 854 // both have a |first_party| and |initiator| that match the URL of the
854 // top-level document: 855 // top-level document:
855 EXPECT_EQ(top_url, delegate_->data()[0]->url); 856 EXPECT_EQ(top_url, delegate_->data()[0]->url);
856 EXPECT_EQ(top_url, delegate_->data()[0]->first_party); 857 EXPECT_EQ(top_url, delegate_->data()[0]->first_party);
857 EXPECT_EQ(top_origin, delegate_->data()[0]->initiator); 858 ASSERT_TRUE(delegate_->data()[0]->initiator);
859 EXPECT_EQ(top_origin, *delegate_->data()[0]->initiator);
858 860
859 EXPECT_EQ(nested_url, delegate_->data()[1]->url); 861 EXPECT_EQ(nested_url, delegate_->data()[1]->url);
860 EXPECT_EQ(top_url, delegate_->data()[1]->first_party); 862 EXPECT_EQ(top_url, delegate_->data()[1]->first_party);
861 EXPECT_EQ(top_origin, delegate_->data()[1]->initiator); 863 ASSERT_TRUE(delegate_->data()[1]->initiator);
864 EXPECT_EQ(top_origin, *delegate_->data()[1]->initiator);
862 865
863 // The remaining items are loaded as subresources in the nested document, and 866 // The remaining items are loaded as subresources in the nested document, and
864 // should have a unique first-party, and an initiator that matches the 867 // should have a unique first-party, and an initiator that matches the
865 // document in which they're embedded. 868 // document in which they're embedded.
866 for (size_t i = 2; i < delegate_->data().size(); i++) { 869 for (size_t i = 2; i < delegate_->data().size(); i++) {
867 SCOPED_TRACE(delegate_->data()[i]->url); 870 SCOPED_TRACE(delegate_->data()[i]->url);
868 EXPECT_EQ(kURLWithUniqueOrigin, delegate_->data()[i]->first_party); 871 EXPECT_EQ(kURLWithUniqueOrigin, delegate_->data()[i]->first_party);
869 EXPECT_EQ(nested_origin, delegate_->data()[i]->initiator); 872 ASSERT_TRUE(delegate_->data()[i]->initiator);
873 EXPECT_EQ(nested_origin, *delegate_->data()[i]->initiator);
870 } 874 }
871 } 875 }
872 876
873 IN_PROC_BROWSER_TEST_F(RequestDataResourceDispatcherHostBrowserTest, 877 IN_PROC_BROWSER_TEST_F(RequestDataResourceDispatcherHostBrowserTest,
874 SameOriginNested) { 878 SameOriginNested) {
875 GURL top_url(embedded_test_server()->GetURL("/page_with_iframe.html")); 879 GURL top_url(embedded_test_server()->GetURL("/page_with_iframe.html"));
876 GURL image_url(embedded_test_server()->GetURL("/image.jpg")); 880 GURL image_url(embedded_test_server()->GetURL("/image.jpg"));
877 GURL nested_url(embedded_test_server()->GetURL("/title1.html")); 881 GURL nested_url(embedded_test_server()->GetURL("/title1.html"));
878 url::Origin top_origin(top_url); 882 url::Origin top_origin(top_url);
879 883
880 NavigateToURLBlockUntilNavigationsComplete(shell(), top_url, 1); 884 NavigateToURLBlockUntilNavigationsComplete(shell(), top_url, 1);
881 885
882 EXPECT_EQ(3u, delegate_->data().size()); 886 EXPECT_EQ(3u, delegate_->data().size());
883 887
884 // User-initiated top-level navigations have a first-party and initiator that 888 // User-initiated top-level navigations have a first-party and initiator that
885 // matches the URL to which they navigate. 889 // matches the URL to which they navigate.
886 EXPECT_EQ(top_url, delegate_->data()[0]->url); 890 EXPECT_EQ(top_url, delegate_->data()[0]->url);
887 EXPECT_EQ(top_url, delegate_->data()[0]->first_party); 891 EXPECT_EQ(top_url, delegate_->data()[0]->first_party);
888 EXPECT_EQ(top_origin, delegate_->data()[0]->initiator); 892 ASSERT_TRUE(delegate_->data()[0]->initiator);
893 EXPECT_EQ(top_origin, *delegate_->data()[0]->initiator);
889 894
890 // Subresource requests have a first-party and initiator that matches the 895 // Subresource requests have a first-party and initiator that matches the
891 // document in which they're embedded. 896 // document in which they're embedded.
892 EXPECT_EQ(image_url, delegate_->data()[1]->url); 897 EXPECT_EQ(image_url, delegate_->data()[1]->url);
893 EXPECT_EQ(top_url, delegate_->data()[1]->first_party); 898 EXPECT_EQ(top_url, delegate_->data()[1]->first_party);
894 EXPECT_EQ(top_origin, delegate_->data()[1]->initiator); 899 ASSERT_TRUE(delegate_->data()[1]->initiator);
900 EXPECT_EQ(top_origin, *delegate_->data()[1]->initiator);
895 901
896 // Same-origin nested frames have a first-party and initiator that matches 902 // Same-origin nested frames have a first-party and initiator that matches
897 // the document in which they're embedded. 903 // the document in which they're embedded.
898 EXPECT_EQ(nested_url, delegate_->data()[2]->url); 904 EXPECT_EQ(nested_url, delegate_->data()[2]->url);
899 EXPECT_EQ(top_url, delegate_->data()[2]->first_party); 905 EXPECT_EQ(top_url, delegate_->data()[2]->first_party);
900 EXPECT_EQ(top_origin, delegate_->data()[2]->initiator); 906 ASSERT_TRUE(delegate_->data()[2]->initiator);
907 EXPECT_EQ(top_origin, *delegate_->data()[2]->initiator);
901 } 908 }
902 909
903 IN_PROC_BROWSER_TEST_F(RequestDataResourceDispatcherHostBrowserTest, 910 IN_PROC_BROWSER_TEST_F(RequestDataResourceDispatcherHostBrowserTest,
904 SameOriginAuxiliary) { 911 SameOriginAuxiliary) {
905 GURL top_url(embedded_test_server()->GetURL("/simple_links.html")); 912 GURL top_url(embedded_test_server()->GetURL("/simple_links.html"));
906 GURL auxiliary_url(embedded_test_server()->GetURL("/title2.html")); 913 GURL auxiliary_url(embedded_test_server()->GetURL("/title2.html"));
907 url::Origin top_origin(top_url); 914 url::Origin top_origin(top_url);
908 915
909 NavigateToURLBlockUntilNavigationsComplete(shell(), top_url, 1); 916 NavigateToURLBlockUntilNavigationsComplete(shell(), top_url, 1);
910 917
911 ShellAddedObserver new_shell_observer; 918 ShellAddedObserver new_shell_observer;
912 bool success = false; 919 bool success = false;
913 EXPECT_TRUE(ExecuteScriptAndExtractBool( 920 EXPECT_TRUE(ExecuteScriptAndExtractBool(
914 shell(), 921 shell(),
915 "window.domAutomationController.send(clickSameSiteNewWindowLink());", 922 "window.domAutomationController.send(clickSameSiteNewWindowLink());",
916 &success)); 923 &success));
917 EXPECT_TRUE(success); 924 EXPECT_TRUE(success);
918 Shell* new_shell = new_shell_observer.GetShell(); 925 Shell* new_shell = new_shell_observer.GetShell();
919 WaitForLoadStop(new_shell->web_contents()); 926 WaitForLoadStop(new_shell->web_contents());
920 927
921 EXPECT_EQ(2u, delegate_->data().size()); 928 EXPECT_EQ(2u, delegate_->data().size());
922 929
923 // User-initiated top-level navigations have a first-party and initiator that 930 // User-initiated top-level navigations have a first-party and initiator that
924 // matches the URL to which they navigate, even if they fail to load. 931 // matches the URL to which they navigate, even if they fail to load.
925 EXPECT_EQ(top_url, delegate_->data()[0]->url); 932 EXPECT_EQ(top_url, delegate_->data()[0]->url);
926 EXPECT_EQ(top_url, delegate_->data()[0]->first_party); 933 EXPECT_EQ(top_url, delegate_->data()[0]->first_party);
927 EXPECT_EQ(top_origin, delegate_->data()[0]->initiator); 934 ASSERT_TRUE(delegate_->data()[0]->initiator);
935 EXPECT_EQ(top_origin, *delegate_->data()[0]->initiator);
928 936
929 // Auxiliary navigations have a first-party that matches the URL to which they 937 // Auxiliary navigations have a first-party that matches the URL to which they
930 // navigate, and an initiator that matches the document that triggered them. 938 // navigate, and an initiator that matches the document that triggered them.
931 EXPECT_EQ(auxiliary_url, delegate_->data()[1]->url); 939 EXPECT_EQ(auxiliary_url, delegate_->data()[1]->url);
932 EXPECT_EQ(auxiliary_url, delegate_->data()[1]->first_party); 940 EXPECT_EQ(auxiliary_url, delegate_->data()[1]->first_party);
933 EXPECT_EQ(top_origin, delegate_->data()[1]->initiator); 941 ASSERT_TRUE(delegate_->data()[1]->initiator);
942 EXPECT_EQ(top_origin, *delegate_->data()[1]->initiator);
934 } 943 }
935 944
936 IN_PROC_BROWSER_TEST_F(RequestDataResourceDispatcherHostBrowserTest, 945 IN_PROC_BROWSER_TEST_F(RequestDataResourceDispatcherHostBrowserTest,
937 CrossOriginAuxiliary) { 946 CrossOriginAuxiliary) {
938 GURL top_url(embedded_test_server()->GetURL("/simple_links.html")); 947 GURL top_url(embedded_test_server()->GetURL("/simple_links.html"));
939 GURL auxiliary_url(embedded_test_server()->GetURL("foo.com", "/title2.html")); 948 GURL auxiliary_url(embedded_test_server()->GetURL("foo.com", "/title2.html"));
940 url::Origin top_origin(top_url); 949 url::Origin top_origin(top_url);
941 950
942 NavigateToURLBlockUntilNavigationsComplete(shell(), top_url, 1); 951 NavigateToURLBlockUntilNavigationsComplete(shell(), top_url, 1);
943 952
(...skipping 14 matching lines...) Expand all
958 EXPECT_TRUE(success); 967 EXPECT_TRUE(success);
959 Shell* new_shell = new_shell_observer.GetShell(); 968 Shell* new_shell = new_shell_observer.GetShell();
960 WaitForLoadStop(new_shell->web_contents()); 969 WaitForLoadStop(new_shell->web_contents());
961 970
962 EXPECT_EQ(2u, delegate_->data().size()); 971 EXPECT_EQ(2u, delegate_->data().size());
963 972
964 // User-initiated top-level navigations have a first-party and initiator that 973 // User-initiated top-level navigations have a first-party and initiator that
965 // matches the URL to which they navigate, even if they fail to load. 974 // matches the URL to which they navigate, even if they fail to load.
966 EXPECT_EQ(top_url, delegate_->data()[0]->url); 975 EXPECT_EQ(top_url, delegate_->data()[0]->url);
967 EXPECT_EQ(top_url, delegate_->data()[0]->first_party); 976 EXPECT_EQ(top_url, delegate_->data()[0]->first_party);
968 EXPECT_EQ(top_origin, delegate_->data()[0]->initiator); 977 ASSERT_TRUE(delegate_->data()[0]->initiator);
978 EXPECT_EQ(top_origin, *delegate_->data()[0]->initiator);
969 979
970 // Auxiliary navigations have a first-party that matches the URL to which they 980 // Auxiliary navigations have a first-party that matches the URL to which they
971 // navigate, and an initiator that matches the document that triggered them. 981 // navigate, and an initiator that matches the document that triggered them.
972 EXPECT_EQ(auxiliary_url, delegate_->data()[1]->url); 982 EXPECT_EQ(auxiliary_url, delegate_->data()[1]->url);
973 EXPECT_EQ(auxiliary_url, delegate_->data()[1]->first_party); 983 EXPECT_EQ(auxiliary_url, delegate_->data()[1]->first_party);
974 EXPECT_EQ(top_origin, delegate_->data()[1]->initiator); 984 ASSERT_TRUE(delegate_->data()[1]->initiator);
985 EXPECT_EQ(top_origin, *delegate_->data()[1]->initiator);
975 } 986 }
976 987
977 IN_PROC_BROWSER_TEST_F(RequestDataResourceDispatcherHostBrowserTest, 988 IN_PROC_BROWSER_TEST_F(RequestDataResourceDispatcherHostBrowserTest,
978 FailedNavigation) { 989 FailedNavigation) {
979 // Navigating to this URL will fail, as we haven't taught the host resolver 990 // Navigating to this URL will fail, as we haven't taught the host resolver
980 // about 'a.com'. 991 // about 'a.com'.
981 GURL top_url(embedded_test_server()->GetURL("a.com", "/simple_page.html")); 992 GURL top_url(embedded_test_server()->GetURL("a.com", "/simple_page.html"));
982 url::Origin top_origin(top_url); 993 url::Origin top_origin(top_url);
983 994
984 NavigateToURLBlockUntilNavigationsComplete(shell(), top_url, 1); 995 NavigateToURLBlockUntilNavigationsComplete(shell(), top_url, 1);
985 996
986 EXPECT_EQ(1u, delegate_->data().size()); 997 EXPECT_EQ(1u, delegate_->data().size());
987 998
988 // User-initiated top-level navigations have a first-party and initiator that 999 // User-initiated top-level navigations have a first-party and initiator that
989 // matches the URL to which they navigate, even if they fail to load. 1000 // matches the URL to which they navigate, even if they fail to load.
990 EXPECT_EQ(top_url, delegate_->data()[0]->url); 1001 EXPECT_EQ(top_url, delegate_->data()[0]->url);
991 EXPECT_EQ(top_url, delegate_->data()[0]->first_party); 1002 EXPECT_EQ(top_url, delegate_->data()[0]->first_party);
992 EXPECT_EQ(top_origin, delegate_->data()[0]->initiator); 1003 ASSERT_TRUE(delegate_->data()[0]->initiator);
1004 EXPECT_EQ(top_origin, *delegate_->data()[0]->initiator);
993 } 1005 }
994 1006
995 IN_PROC_BROWSER_TEST_F(RequestDataResourceDispatcherHostBrowserTest, 1007 IN_PROC_BROWSER_TEST_F(RequestDataResourceDispatcherHostBrowserTest,
996 CrossOriginNested) { 1008 CrossOriginNested) {
997 host_resolver()->AddRule("*", "127.0.0.1"); 1009 host_resolver()->AddRule("*", "127.0.0.1");
998 GURL top_url(embedded_test_server()->GetURL( 1010 GURL top_url(embedded_test_server()->GetURL(
999 "a.com", "/cross_site_iframe_factory.html?a(b)")); 1011 "a.com", "/cross_site_iframe_factory.html?a(b)"));
1000 GURL top_js_url( 1012 GURL top_js_url(
1001 embedded_test_server()->GetURL("a.com", "/tree_parser_util.js")); 1013 embedded_test_server()->GetURL("a.com", "/tree_parser_util.js"));
1002 GURL nested_url(embedded_test_server()->GetURL( 1014 GURL nested_url(embedded_test_server()->GetURL(
1003 "b.com", "/cross_site_iframe_factory.html?b()")); 1015 "b.com", "/cross_site_iframe_factory.html?b()"));
1004 GURL nested_js_url( 1016 GURL nested_js_url(
1005 embedded_test_server()->GetURL("b.com", "/tree_parser_util.js")); 1017 embedded_test_server()->GetURL("b.com", "/tree_parser_util.js"));
1006 url::Origin top_origin(top_url); 1018 url::Origin top_origin(top_url);
1007 url::Origin nested_origin(nested_url); 1019 url::Origin nested_origin(nested_url);
1008 1020
1009 NavigateToURLBlockUntilNavigationsComplete(shell(), top_url, 1); 1021 NavigateToURLBlockUntilNavigationsComplete(shell(), top_url, 1);
1010 1022
1011 EXPECT_EQ(4u, delegate_->data().size()); 1023 EXPECT_EQ(4u, delegate_->data().size());
1012 1024
1013 // User-initiated top-level navigations have a first-party and initiator that 1025 // User-initiated top-level navigations have a first-party and initiator that
1014 // matches the URL to which they navigate. 1026 // matches the URL to which they navigate.
1015 EXPECT_EQ(top_url, delegate_->data()[0]->url); 1027 EXPECT_EQ(top_url, delegate_->data()[0]->url);
1016 EXPECT_EQ(top_url, delegate_->data()[0]->first_party); 1028 EXPECT_EQ(top_url, delegate_->data()[0]->first_party);
1017 EXPECT_EQ(top_origin, delegate_->data()[0]->initiator); 1029 ASSERT_TRUE(delegate_->data()[0]->initiator);
1030 EXPECT_EQ(top_origin, *delegate_->data()[0]->initiator);
1018 1031
1019 EXPECT_EQ(top_js_url, delegate_->data()[1]->url); 1032 EXPECT_EQ(top_js_url, delegate_->data()[1]->url);
1020 EXPECT_EQ(top_url, delegate_->data()[1]->first_party); 1033 EXPECT_EQ(top_url, delegate_->data()[1]->first_party);
1021 EXPECT_EQ(top_origin, delegate_->data()[1]->initiator); 1034 ASSERT_TRUE(delegate_->data()[1]->initiator);
1035 EXPECT_EQ(top_origin, *delegate_->data()[1]->initiator);
1022 1036
1023 // Cross-origin frames have a first-party and initiator that matches the URL 1037 // Cross-origin frames have a first-party and initiator that matches the URL
1024 // in which they're embedded. 1038 // in which they're embedded.
1025 EXPECT_EQ(nested_url, delegate_->data()[2]->url); 1039 EXPECT_EQ(nested_url, delegate_->data()[2]->url);
1026 EXPECT_EQ(top_url, delegate_->data()[2]->first_party); 1040 EXPECT_EQ(top_url, delegate_->data()[2]->first_party);
1027 EXPECT_EQ(top_origin, delegate_->data()[2]->initiator); 1041 ASSERT_TRUE(delegate_->data()[2]->initiator);
1042 EXPECT_EQ(top_origin, *delegate_->data()[2]->initiator);
1028 1043
1029 // Cross-origin subresource requests have a unique first-party, and an 1044 // Cross-origin subresource requests have a unique first-party, and an
1030 // initiator that matches the document in which they're embedded. 1045 // initiator that matches the document in which they're embedded.
1031 EXPECT_EQ(nested_js_url, delegate_->data()[3]->url); 1046 EXPECT_EQ(nested_js_url, delegate_->data()[3]->url);
1032 EXPECT_EQ(kURLWithUniqueOrigin, delegate_->data()[3]->first_party); 1047 EXPECT_EQ(kURLWithUniqueOrigin, delegate_->data()[3]->first_party);
1033 EXPECT_EQ(nested_origin, delegate_->data()[3]->initiator); 1048 ASSERT_TRUE(delegate_->data()[3]->initiator);
1049 EXPECT_EQ(nested_origin, *delegate_->data()[3]->initiator);
1034 } 1050 }
1035 1051
1036 } // namespace content 1052 } // namespace content
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698