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

Side by Side Diff: chrome/browser/ui/webui/print_preview_handler.cc

Issue 8564040: Revert 110035 - Print Preview: Making margin selection sticky (part 2/2) (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src/
Patch Set: Created 9 years, 1 month 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) 2011 The Chromium Authors. All rights reserved. 1 // Copyright (c) 2011 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/browser/ui/webui/print_preview_handler.h" 5 #include "chrome/browser/ui/webui/print_preview_handler.h"
6 6
7 #include <ctype.h> 7 #include <ctype.h>
8 8
9 #include <string> 9 #include <string>
10 #include <vector> 10 #include <vector>
(...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after
43 #include "chrome/common/chrome_paths.h" 43 #include "chrome/common/chrome_paths.h"
44 #include "chrome/common/pref_names.h" 44 #include "chrome/common/pref_names.h"
45 #include "chrome/common/print_messages.h" 45 #include "chrome/common/print_messages.h"
46 #include "content/browser/renderer_host/render_view_host.h" 46 #include "content/browser/renderer_host/render_view_host.h"
47 #include "content/browser/tab_contents/tab_contents.h" 47 #include "content/browser/tab_contents/tab_contents.h"
48 #include "content/public/browser/browser_thread.h" 48 #include "content/public/browser/browser_thread.h"
49 #include "printing/backend/print_backend.h" 49 #include "printing/backend/print_backend.h"
50 #include "printing/metafile.h" 50 #include "printing/metafile.h"
51 #include "printing/metafile_impl.h" 51 #include "printing/metafile_impl.h"
52 #include "printing/page_range.h" 52 #include "printing/page_range.h"
53 #include "printing/page_size_margins.h"
54 #include "printing/print_settings.h" 53 #include "printing/print_settings.h"
55 #include "unicode/ulocdata.h" 54 #include "unicode/ulocdata.h"
56 55
57 #if !defined(OS_CHROMEOS) 56 #if !defined(OS_CHROMEOS)
58 #include "base/command_line.h" 57 #include "base/command_line.h"
59 #include "chrome/common/chrome_switches.h" 58 #include "chrome/common/chrome_switches.h"
60 #endif 59 #endif
61 60
62 using content::BrowserThread; 61 using content::BrowserThread;
63 62
(...skipping 26 matching lines...) Expand all
90 void ReportUserActionHistogram(enum UserActionBuckets event) { 89 void ReportUserActionHistogram(enum UserActionBuckets event) {
91 UMA_HISTOGRAM_ENUMERATION("PrintPreview.UserAction", event, 90 UMA_HISTOGRAM_ENUMERATION("PrintPreview.UserAction", event,
92 USERACTION_BUCKET_BOUNDARY); 91 USERACTION_BUCKET_BOUNDARY);
93 } 92 }
94 93
95 void ReportPrintSettingHistogram(enum PrintSettingsBuckets setting) { 94 void ReportPrintSettingHistogram(enum PrintSettingsBuckets setting) {
96 UMA_HISTOGRAM_ENUMERATION("PrintPreview.PrintSettings", setting, 95 UMA_HISTOGRAM_ENUMERATION("PrintPreview.PrintSettings", setting,
97 PRINT_SETTINGS_BUCKET_BOUNDARY); 96 PRINT_SETTINGS_BUCKET_BOUNDARY);
98 } 97 }
99 98
100 // Name of a dictionary fielad holdong cloud print related data;
101 const char kCloudPrintData[] = "cloudPrintData";
102 // Name of a dictionary field holding the initiator tab title.
103 const char kInitiatorTabTitle[] = "initiatorTabTitle";
104 // Name of a dictionary field holding the measurement system according to the
105 // locale.
106 const char kMeasurementSystem[] = "measurementSystem";
107 // Name of a dictionary field holding the number format according to the locale.
108 const char kNumberFormat[] = "numberFormat";
109
110
111 // Get the print job settings dictionary from |args|. The caller takes 99 // Get the print job settings dictionary from |args|. The caller takes
112 // ownership of the returned DictionaryValue. Returns NULL on failure. 100 // ownership of the returned DictionaryValue. Returns NULL on failure.
113 DictionaryValue* GetSettingsDictionary(const ListValue* args) { 101 DictionaryValue* GetSettingsDictionary(const ListValue* args) {
114 std::string json_str; 102 std::string json_str;
115 if (!args->GetString(0, &json_str)) { 103 if (!args->GetString(0, &json_str)) {
116 NOTREACHED() << "Could not read JSON argument"; 104 NOTREACHED() << "Could not read JSON argument";
117 return NULL; 105 return NULL;
118 } 106 }
119 if (json_str.empty()) { 107 if (json_str.empty()) {
120 NOTREACHED() << "Empty print job settings"; 108 NOTREACHED() << "Empty print job settings";
(...skipping 90 matching lines...) Expand 10 before | Expand all | Expand 10 after
211 }; 199 };
212 200
213 // static 201 // static
214 FilePath* PrintPreviewHandler::last_saved_path_ = NULL; 202 FilePath* PrintPreviewHandler::last_saved_path_ = NULL;
215 std::string* PrintPreviewHandler::last_used_printer_cloud_print_data_ = NULL; 203 std::string* PrintPreviewHandler::last_used_printer_cloud_print_data_ = NULL;
216 std::string* PrintPreviewHandler::last_used_printer_name_ = NULL; 204 std::string* PrintPreviewHandler::last_used_printer_name_ = NULL;
217 printing::ColorModels PrintPreviewHandler::last_used_color_model_ = 205 printing::ColorModels PrintPreviewHandler::last_used_color_model_ =
218 printing::UNKNOWN_COLOR_MODEL; 206 printing::UNKNOWN_COLOR_MODEL;
219 printing::MarginType PrintPreviewHandler::last_used_margins_type_ = 207 printing::MarginType PrintPreviewHandler::last_used_margins_type_ =
220 printing::DEFAULT_MARGINS; 208 printing::DEFAULT_MARGINS;
221 printing::PageSizeMargins*
222 PrintPreviewHandler::last_used_page_size_margins_ = NULL;
223 209
224 PrintPreviewHandler::PrintPreviewHandler() 210 PrintPreviewHandler::PrintPreviewHandler()
225 : print_backend_(printing::PrintBackend::CreateInstance(NULL)), 211 : print_backend_(printing::PrintBackend::CreateInstance(NULL)),
226 regenerate_preview_request_count_(0), 212 regenerate_preview_request_count_(0),
227 manage_printers_dialog_request_count_(0), 213 manage_printers_dialog_request_count_(0),
228 reported_failed_preview_(false), 214 reported_failed_preview_(false),
229 has_logged_printers_count_(false) { 215 has_logged_printers_count_(false) {
230 ReportUserActionHistogram(PREVIEW_STARTED); 216 ReportUserActionHistogram(PREVIEW_STARTED);
231 } 217 }
232 218
233 PrintPreviewHandler::~PrintPreviewHandler() { 219 PrintPreviewHandler::~PrintPreviewHandler() {
234 if (select_file_dialog_.get()) 220 if (select_file_dialog_.get())
235 select_file_dialog_->ListenerDestroyed(); 221 select_file_dialog_->ListenerDestroyed();
236 } 222 }
237 223
238 void PrintPreviewHandler::RegisterMessages() { 224 void PrintPreviewHandler::RegisterMessages() {
225 web_ui_->RegisterMessageCallback("getDefaultPrinter",
226 base::Bind(&PrintPreviewHandler::HandleGetDefaultPrinter,
227 base::Unretained(this)));
239 web_ui_->RegisterMessageCallback("getPrinters", 228 web_ui_->RegisterMessageCallback("getPrinters",
240 base::Bind(&PrintPreviewHandler::HandleGetPrinters, 229 base::Bind(&PrintPreviewHandler::HandleGetPrinters,
241 base::Unretained(this))); 230 base::Unretained(this)));
242 web_ui_->RegisterMessageCallback("getPreview", 231 web_ui_->RegisterMessageCallback("getPreview",
243 base::Bind(&PrintPreviewHandler::HandleGetPreview, 232 base::Bind(&PrintPreviewHandler::HandleGetPreview,
244 base::Unretained(this))); 233 base::Unretained(this)));
245 web_ui_->RegisterMessageCallback("print", 234 web_ui_->RegisterMessageCallback("print",
246 base::Bind(&PrintPreviewHandler::HandlePrint, 235 base::Bind(&PrintPreviewHandler::HandlePrint,
247 base::Unretained(this))); 236 base::Unretained(this)));
248 web_ui_->RegisterMessageCallback("getPrinterCapabilities", 237 web_ui_->RegisterMessageCallback("getPrinterCapabilities",
(...skipping 19 matching lines...) Expand all
268 base::Unretained(this))); 257 base::Unretained(this)));
269 web_ui_->RegisterMessageCallback("hidePreview", 258 web_ui_->RegisterMessageCallback("hidePreview",
270 base::Bind(&PrintPreviewHandler::HandleHidePreview, 259 base::Bind(&PrintPreviewHandler::HandleHidePreview,
271 base::Unretained(this))); 260 base::Unretained(this)));
272 web_ui_->RegisterMessageCallback("cancelPendingPrintRequest", 261 web_ui_->RegisterMessageCallback("cancelPendingPrintRequest",
273 base::Bind(&PrintPreviewHandler::HandleCancelPendingPrintRequest, 262 base::Bind(&PrintPreviewHandler::HandleCancelPendingPrintRequest,
274 base::Unretained(this))); 263 base::Unretained(this)));
275 web_ui_->RegisterMessageCallback("saveLastPrinter", 264 web_ui_->RegisterMessageCallback("saveLastPrinter",
276 base::Bind(&PrintPreviewHandler::HandleSaveLastPrinter, 265 base::Bind(&PrintPreviewHandler::HandleSaveLastPrinter,
277 base::Unretained(this))); 266 base::Unretained(this)));
278 web_ui_->RegisterMessageCallback("getInitialSettings", 267 web_ui_->RegisterMessageCallback("getInitiatorTabTitle",
279 base::Bind(&PrintPreviewHandler::HandleGetInitialSettings, 268 base::Bind(&PrintPreviewHandler::HandleGetInitiatorTabTitle,
280 base::Unretained(this))); 269 base::Unretained(this)));
270 web_ui_->RegisterMessageCallback("getNumberFormatAndMeasurementSystem",
271 base::Bind(
272 &PrintPreviewHandler::HandleGetNumberFormatAndMeasurementSystem,
273 base::Unretained(this)));
281 } 274 }
282 275
283 TabContentsWrapper* PrintPreviewHandler::preview_tab_wrapper() const { 276 TabContentsWrapper* PrintPreviewHandler::preview_tab_wrapper() const {
284 return TabContentsWrapper::GetCurrentWrapperForContents(preview_tab()); 277 return TabContentsWrapper::GetCurrentWrapperForContents(preview_tab());
285 } 278 }
286 TabContents* PrintPreviewHandler::preview_tab() const { 279 TabContents* PrintPreviewHandler::preview_tab() const {
287 return web_ui_->tab_contents(); 280 return web_ui_->tab_contents();
288 } 281 }
289 282
283 void PrintPreviewHandler::HandleGetDefaultPrinter(const ListValue* /*args*/) {
284 scoped_refptr<PrintSystemTaskProxy> task =
285 new PrintSystemTaskProxy(AsWeakPtr(),
286 print_backend_.get(),
287 has_logged_printers_count_);
288 BrowserThread::PostTask(
289 BrowserThread::FILE, FROM_HERE,
290 base::Bind(&PrintSystemTaskProxy::GetDefaultPrinter, task.get()));
291 }
292
290 void PrintPreviewHandler::HandleGetPrinters(const ListValue* /*args*/) { 293 void PrintPreviewHandler::HandleGetPrinters(const ListValue* /*args*/) {
291 scoped_refptr<PrintSystemTaskProxy> task = 294 scoped_refptr<PrintSystemTaskProxy> task =
292 new PrintSystemTaskProxy(AsWeakPtr(), 295 new PrintSystemTaskProxy(AsWeakPtr(),
293 print_backend_.get(), 296 print_backend_.get(),
294 has_logged_printers_count_); 297 has_logged_printers_count_);
295 has_logged_printers_count_ = true; 298 has_logged_printers_count_ = true;
296 299
297 BrowserThread::PostTask( 300 BrowserThread::PostTask(
298 BrowserThread::FILE, FROM_HERE, 301 BrowserThread::FILE, FROM_HERE,
299 base::Bind(&PrintSystemTaskProxy::EnumeratePrinters, task.get())); 302 base::Bind(&PrintSystemTaskProxy::EnumeratePrinters, task.get()));
(...skipping 87 matching lines...) Expand 10 before | Expand all | Expand 10 after
387 if (!settings.get()) 390 if (!settings.get())
388 return; 391 return;
389 392
390 // Storing last used color model. 393 // Storing last used color model.
391 int color_model; 394 int color_model;
392 if (!settings->GetInteger(printing::kSettingColor, &color_model)) 395 if (!settings->GetInteger(printing::kSettingColor, &color_model))
393 color_model = printing::GRAY; 396 color_model = printing::GRAY;
394 last_used_color_model_ = static_cast<printing::ColorModels>(color_model); 397 last_used_color_model_ = static_cast<printing::ColorModels>(color_model);
395 398
396 // Storing last used margin settings. 399 // Storing last used margin settings.
397 bool is_modifiable; 400 int margin_type;
398 settings->GetBoolean(printing::kSettingPreviewModifiable, &is_modifiable); 401 if (!settings->GetInteger(printing::kSettingMarginsType, &margin_type))
399 if (is_modifiable) { 402 margin_type = printing::DEFAULT_MARGINS;
400 int margin_type; 403 last_used_margins_type_ = static_cast<printing::MarginType>(margin_type);
401 if (!settings->GetInteger(printing::kSettingMarginsType, &margin_type))
402 margin_type = printing::DEFAULT_MARGINS;
403 last_used_margins_type_ = static_cast<printing::MarginType>(margin_type);
404 if (last_used_margins_type_ == printing::CUSTOM_MARGINS) {
405 if (!last_used_page_size_margins_)
406 last_used_page_size_margins_ = new printing::PageSizeMargins();
407 getCustomMarginsFromJobSettings(*settings, last_used_page_size_margins_);
408 }
409 }
410 404
411 bool print_to_pdf = false; 405 bool print_to_pdf = false;
412 settings->GetBoolean(printing::kSettingPrintToPDF, &print_to_pdf); 406 settings->GetBoolean(printing::kSettingPrintToPDF, &print_to_pdf);
413 407
414 settings->SetBoolean(printing::kSettingHeaderFooterEnabled, false); 408 settings->SetBoolean(printing::kSettingHeaderFooterEnabled, false);
415 409
416 bool is_cloud_printer = settings->HasKey(printing::kSettingCloudPrintId); 410 bool is_cloud_printer = settings->HasKey(printing::kSettingCloudPrintId);
417 bool is_cloud_dialog = false; 411 bool is_cloud_dialog = false;
418 settings->GetBoolean(printing::kSettingCloudPrintDialog, &is_cloud_dialog); 412 settings->GetBoolean(printing::kSettingCloudPrintDialog, &is_cloud_dialog);
419 if (is_cloud_printer) { 413 if (is_cloud_printer) {
(...skipping 192 matching lines...) Expand 10 before | Expand all | Expand 10 after
612 regenerate_preview_request_count_); 606 regenerate_preview_request_count_);
613 607
614 ActivateInitiatorTabAndClosePreviewTab(); 608 ActivateInitiatorTabAndClosePreviewTab();
615 } 609 }
616 610
617 void PrintPreviewHandler::ReportStats() { 611 void PrintPreviewHandler::ReportStats() {
618 UMA_HISTOGRAM_COUNTS("PrintPreview.ManagePrinters", 612 UMA_HISTOGRAM_COUNTS("PrintPreview.ManagePrinters",
619 manage_printers_dialog_request_count_); 613 manage_printers_dialog_request_count_);
620 } 614 }
621 615
622 void PrintPreviewHandler::GetNumberFormatAndMeasurementSystem( 616 void PrintPreviewHandler::HandleGetInitiatorTabTitle(
623 base::DictionaryValue* settings) { 617 const ListValue* /*args*/) {
618 PrintPreviewUI* print_preview_ui = static_cast<PrintPreviewUI*>(web_ui_);
619 base::StringValue tab_title(print_preview_ui->initiator_tab_title());
620 web_ui_->CallJavascriptFunction("setInitiatorTabTitle", tab_title);
621 }
622
623 void PrintPreviewHandler::HandleGetNumberFormatAndMeasurementSystem(
624 const ListValue* /*args*/) {
624 625
625 // Getting the measurement system based on the locale. 626 // Getting the measurement system based on the locale.
626 UErrorCode errorCode = U_ZERO_ERROR; 627 UErrorCode errorCode = U_ZERO_ERROR;
627 const char* locale = g_browser_process->GetApplicationLocale().c_str(); 628 const char* locale = g_browser_process->GetApplicationLocale().c_str();
628 UMeasurementSystem system = ulocdata_getMeasurementSystem(locale, &errorCode); 629 UMeasurementSystem measurement_system =
629 if (errorCode > U_ZERO_ERROR || system == UMS_LIMIT) 630 ulocdata_getMeasurementSystem(locale, &errorCode);
630 system = UMS_SI; 631 if (errorCode > U_ZERO_ERROR || measurement_system == UMS_LIMIT)
632 measurement_system = UMS_SI;
631 633
632 // Getting the number formatting based on the locale and writing to 634 // Getting the number formatting based on the locale.
633 // dictionary. 635 StringValue number_format(base::FormatDouble(123456.78, 2));
634 settings->SetString(kNumberFormat, base::FormatDouble(123456.78, 2)); 636 base::FundamentalValue system(measurement_system);
635 settings->SetInteger(kMeasurementSystem, system);
636 }
637 637
638 void PrintPreviewHandler::GetLastUsedMarginSettings( 638 web_ui_->CallJavascriptFunction(
639 base::DictionaryValue* custom_margins) { 639 "print_preview.setNumberFormatAndMeasurementSystem",
640 custom_margins->SetInteger(printing::kSettingMarginsType, 640 number_format,
641 PrintPreviewHandler::last_used_margins_type_); 641 system);
642 if (last_used_page_size_margins_) {
643 custom_margins->SetDouble(printing::kSettingMarginTop,
644 last_used_page_size_margins_->margin_top);
645 custom_margins->SetDouble(printing::kSettingMarginBottom,
646 last_used_page_size_margins_->margin_bottom);
647 custom_margins->SetDouble(printing::kSettingMarginLeft,
648 last_used_page_size_margins_->margin_left);
649 custom_margins->SetDouble(printing::kSettingMarginRight,
650 last_used_page_size_margins_->margin_right);
651 }
652 }
653
654 void PrintPreviewHandler::HandleGetInitialSettings(const ListValue* /*args*/) {
655 scoped_refptr<PrintSystemTaskProxy> task =
656 new PrintSystemTaskProxy(AsWeakPtr(),
657 print_backend_.get(),
658 has_logged_printers_count_);
659 BrowserThread::PostTask(
660 BrowserThread::FILE, FROM_HERE,
661 base::Bind(&PrintSystemTaskProxy::GetDefaultPrinter, task.get()));
662 }
663
664 void PrintPreviewHandler::SendInitialSettings(
665 const std::string& default_printer,
666 const std::string& cloud_print_data) {
667 PrintPreviewUI* print_preview_ui = static_cast<PrintPreviewUI*>(web_ui_);
668
669 base::DictionaryValue initial_settings;
670 initial_settings.SetString(kInitiatorTabTitle,
671 print_preview_ui->initiator_tab_title());
672 initial_settings.SetBoolean(printing::kSettingPreviewModifiable,
673 print_preview_ui->source_is_modifiable());
674 initial_settings.SetString(printing::kSettingPrinterName,
675 default_printer);
676 initial_settings.SetString(kCloudPrintData, cloud_print_data);
677
678 if (print_preview_ui->source_is_modifiable()) {
679 GetLastUsedMarginSettings(&initial_settings);
680 GetNumberFormatAndMeasurementSystem(&initial_settings);
681 }
682 web_ui_->CallJavascriptFunction("setInitialSettings", initial_settings);
683 } 642 }
684 643
685 void PrintPreviewHandler::ActivateInitiatorTabAndClosePreviewTab() { 644 void PrintPreviewHandler::ActivateInitiatorTabAndClosePreviewTab() {
686 TabContentsWrapper* initiator_tab = GetInitiatorTab(); 645 TabContentsWrapper* initiator_tab = GetInitiatorTab();
687 if (initiator_tab) { 646 if (initiator_tab) {
688 static_cast<RenderViewHostDelegate*>( 647 static_cast<RenderViewHostDelegate*>(
689 initiator_tab->tab_contents())->Activate(); 648 initiator_tab->tab_contents())->Activate();
690 } 649 }
691 ClosePrintPreviewTab(); 650 ClosePrintPreviewTab();
692 } 651 }
693 652
694 void PrintPreviewHandler::SendPrinterCapabilities( 653 void PrintPreviewHandler::SendPrinterCapabilities(
695 const DictionaryValue& settings_info) { 654 const DictionaryValue& settings_info) {
696 VLOG(1) << "Get printer capabilities finished"; 655 VLOG(1) << "Get printer capabilities finished";
697 web_ui_->CallJavascriptFunction("updateWithPrinterCapabilities", 656 web_ui_->CallJavascriptFunction("updateWithPrinterCapabilities",
698 settings_info); 657 settings_info);
699 } 658 }
700 659
660 void PrintPreviewHandler::SendDefaultPrinter(
661 const StringValue& default_printer,
662 const StringValue& cloud_print_data) {
663 base::FundamentalValue margins_type(
664 PrintPreviewHandler::last_used_margins_type_);
665 web_ui_->CallJavascriptFunction("setDefaultPrinter",
666 default_printer,
667 cloud_print_data,
668 margins_type);
669 }
670
701 void PrintPreviewHandler::SetupPrinterList(const ListValue& printers) { 671 void PrintPreviewHandler::SetupPrinterList(const ListValue& printers) {
702 SendCloudPrintEnabled(); 672 SendCloudPrintEnabled();
703 web_ui_->CallJavascriptFunction("setPrinters", printers); 673 web_ui_->CallJavascriptFunction("setPrinters", printers);
704 } 674 }
705 675
706 void PrintPreviewHandler::SendCloudPrintEnabled() { 676 void PrintPreviewHandler::SendCloudPrintEnabled() {
707 Profile* profile = BrowserList::GetLastActive()->profile(); 677 Profile* profile = BrowserList::GetLastActive()->profile();
708 PrefService* prefs = profile->GetPrefs(); 678 PrefService* prefs = profile->GetPrefs();
709 if (prefs->GetBoolean(prefs::kCloudPrintSubmitEnabled)) { 679 if (prefs->GetBoolean(prefs::kCloudPrintSubmitEnabled)) {
710 GURL gcp_url(CloudPrintURL(profile).GetCloudPrintServiceURL()); 680 GURL gcp_url(CloudPrintURL(profile).GetCloudPrintServiceURL());
(...skipping 187 matching lines...) Expand 10 before | Expand all | Expand 10 after
898 return; 868 return;
899 869
900 // We no longer require the initiator tab details. Remove those details 870 // We no longer require the initiator tab details. Remove those details
901 // associated with the preview tab to allow the initiator tab to create 871 // associated with the preview tab to allow the initiator tab to create
902 // another preview tab. 872 // another preview tab.
903 printing::PrintPreviewTabController* tab_controller = 873 printing::PrintPreviewTabController* tab_controller =
904 printing::PrintPreviewTabController::GetInstance(); 874 printing::PrintPreviewTabController::GetInstance();
905 if (tab_controller) 875 if (tab_controller)
906 tab_controller->EraseInitiatorTabInfo(preview_tab_wrapper()); 876 tab_controller->EraseInitiatorTabInfo(preview_tab_wrapper());
907 } 877 }
OLDNEW
« no previous file with comments | « chrome/browser/ui/webui/print_preview_handler.h ('k') | chrome/browser/ui/webui/print_preview_handler_unittest.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698