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

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

Issue 7016039: PrintPreview: Added new histograms for print preview. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Updated the CL and fixed a style. Created 9 years, 7 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
« no previous file with comments | « chrome/browser/ui/webui/print_preview_handler.h ('k') | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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 <string> 7 #include <string>
8 8
9 #include "base/i18n/file_util_icu.h" 9 #include "base/i18n/file_util_icu.h"
10 #include "base/json/json_reader.h" 10 #include "base/json/json_reader.h"
11 #include "base/metrics/histogram.h"
11 #include "base/path_service.h" 12 #include "base/path_service.h"
12 #include "base/threading/thread.h" 13 #include "base/threading/thread.h"
13 #include "base/threading/thread_restrictions.h" 14 #include "base/threading/thread_restrictions.h"
14 #include "base/utf_string_conversions.h" 15 #include "base/utf_string_conversions.h"
15 #include "base/values.h" 16 #include "base/values.h"
16 #include "chrome/browser/browser_process.h" 17 #include "chrome/browser/browser_process.h"
17 #include "chrome/browser/platform_util.h" 18 #include "chrome/browser/platform_util.h"
18 #include "chrome/browser/printing/background_printing_manager.h" 19 #include "chrome/browser/printing/background_printing_manager.h"
19 #include "chrome/browser/printing/printer_manager_dialog.h" 20 #include "chrome/browser/printing/printer_manager_dialog.h"
20 #include "chrome/browser/printing/print_preview_tab_controller.h" 21 #include "chrome/browser/printing/print_preview_tab_controller.h"
(...skipping 25 matching lines...) Expand all
46 47
47 const char kDisableColorOption[] = "disableColorOption"; 48 const char kDisableColorOption[] = "disableColorOption";
48 const char kSetColorAsDefault[] = "setColorAsDefault"; 49 const char kSetColorAsDefault[] = "setColorAsDefault";
49 50
50 #if defined(USE_CUPS) 51 #if defined(USE_CUPS)
51 const char kColorDevice[] = "ColorDevice"; 52 const char kColorDevice[] = "ColorDevice";
52 #elif defined(OS_WIN) 53 #elif defined(OS_WIN)
53 const char kPskColor[] = "psk:Color"; 54 const char kPskColor[] = "psk:Color";
54 #endif 55 #endif
55 56
57 // Histogram buckets
58 enum UserActionBuckets {
59 PRINT_TO_PRINTER,
60 PRINT_TO_PDF,
61 CANCEL,
62 FALLBACK_TO_ADVANCED_SETTINGS_DIALOG,
63 USERACTION_BUCKET_BOUNDARY
64 };
65
66
67 // Print preview user action histogram names.
68 const char kUserAction[] = "PrintPreview.UserAction";
69 const char kManagePrinters[] = "PrintPreview.ManagePrinters";
70 const char kNumberOfPrinters[] = "PrintPreview.NumberOfPrinters";
71 const char kPreviewFailedInitiatorTabDoesNotExist[] =
72 "PrintPreview.Failed.InitiatorTabDoesNotExist";
73
74 const char kRegeneratePreviewRequestsRcvdBeforeCancel[] =
75 "PrintPreview.RegeneratePreviewRequest.BeforeCancel";
76
77 const char kRegeneratePreviewRequestsRcvdBeforePrint[] =
78 "PrintPreview.RegeneratePreviewRequest.BeforePrint";
79
56 // Get the print job settings dictionary from |args|. The caller takes 80 // Get the print job settings dictionary from |args|. The caller takes
57 // ownership of the returned DictionaryValue. Returns NULL on failure. 81 // ownership of the returned DictionaryValue. Returns NULL on failure.
58 DictionaryValue* GetSettingsDictionary(const ListValue* args) { 82 DictionaryValue* GetSettingsDictionary(const ListValue* args) {
59 std::string json_str; 83 std::string json_str;
60 if (!args->GetString(0, &json_str)) { 84 if (!args->GetString(0, &json_str)) {
61 NOTREACHED() << "Could not read JSON argument"; 85 NOTREACHED() << "Could not read JSON argument";
62 return NULL; 86 return NULL;
63 } 87 }
64 if (json_str.empty()) { 88 if (json_str.empty()) {
65 NOTREACHED() << "Empty print job settings"; 89 NOTREACHED() << "Empty print job settings";
(...skipping 14 matching lines...) Expand all
80 return settings.release(); 104 return settings.release();
81 } 105 }
82 106
83 } // namespace 107 } // namespace
84 108
85 class PrintSystemTaskProxy 109 class PrintSystemTaskProxy
86 : public base::RefCountedThreadSafe<PrintSystemTaskProxy, 110 : public base::RefCountedThreadSafe<PrintSystemTaskProxy,
87 BrowserThread::DeleteOnUIThread> { 111 BrowserThread::DeleteOnUIThread> {
88 public: 112 public:
89 PrintSystemTaskProxy(const base::WeakPtr<PrintPreviewHandler>& handler, 113 PrintSystemTaskProxy(const base::WeakPtr<PrintPreviewHandler>& handler,
90 printing::PrintBackend* print_backend) 114 printing::PrintBackend* print_backend,
115 bool has_logged_printers_count)
91 : handler_(handler), 116 : handler_(handler),
92 print_backend_(print_backend) { 117 print_backend_(print_backend),
118 has_logged_printers_count_(has_logged_printers_count) {
93 } 119 }
94 120
95 void EnumeratePrinters() { 121 void EnumeratePrinters() {
96 ListValue* printers = new ListValue; 122 ListValue* printers = new ListValue;
97 int default_printer_index = -1; 123 int default_printer_index = -1;
98 124
99 printing::PrinterList printer_list; 125 printing::PrinterList printer_list;
100 print_backend_->EnumeratePrinters(&printer_list); 126 print_backend_->EnumeratePrinters(&printer_list);
127
128 if (!has_logged_printers_count_) {
129 // Record the total number of printers.
130 UMA_HISTOGRAM_COUNTS(kNumberOfPrinters, printer_list.size());
131 }
132
101 int i = 0; 133 int i = 0;
102 for (printing::PrinterList::iterator index = printer_list.begin(); 134 for (printing::PrinterList::iterator index = printer_list.begin();
103 index != printer_list.end(); ++index, ++i) { 135 index != printer_list.end(); ++index, ++i) {
104 DictionaryValue* printer_info = new DictionaryValue; 136 DictionaryValue* printer_info = new DictionaryValue;
105 std::string printerName; 137 std::string printerName;
106 #if defined(OS_MACOSX) 138 #if defined(OS_MACOSX)
107 // On Mac, |index->printer_description| specifies the printer name and 139 // On Mac, |index->printer_description| specifies the printer name and
108 // |index->printer_name| specifies the device name / printer queue name. 140 // |index->printer_name| specifies the device name / printer queue name.
109 printerName = index->printer_description; 141 printerName = index->printer_description;
110 #else 142 #else
(...skipping 83 matching lines...) Expand 10 before | Expand all | Expand 10 after
194 private: 226 private:
195 friend struct BrowserThread::DeleteOnThread<BrowserThread::UI>; 227 friend struct BrowserThread::DeleteOnThread<BrowserThread::UI>;
196 friend class DeleteTask<PrintSystemTaskProxy>; 228 friend class DeleteTask<PrintSystemTaskProxy>;
197 229
198 ~PrintSystemTaskProxy() {} 230 ~PrintSystemTaskProxy() {}
199 231
200 base::WeakPtr<PrintPreviewHandler> handler_; 232 base::WeakPtr<PrintPreviewHandler> handler_;
201 233
202 scoped_refptr<printing::PrintBackend> print_backend_; 234 scoped_refptr<printing::PrintBackend> print_backend_;
203 235
236 bool has_logged_printers_count_;
237
204 DISALLOW_COPY_AND_ASSIGN(PrintSystemTaskProxy); 238 DISALLOW_COPY_AND_ASSIGN(PrintSystemTaskProxy);
205 }; 239 };
206 240
207 // A Task implementation that stores a PDF file on disk. 241 // A Task implementation that stores a PDF file on disk.
208 class PrintToPdfTask : public Task { 242 class PrintToPdfTask : public Task {
209 public: 243 public:
210 // Takes ownership of |metafile|. 244 // Takes ownership of |metafile|.
211 PrintToPdfTask(printing::Metafile* metafile, const FilePath& path) 245 PrintToPdfTask(printing::Metafile* metafile, const FilePath& path)
212 : metafile_(metafile), path_(path) { 246 : metafile_(metafile), path_(path) {
213 DCHECK(metafile); 247 DCHECK(metafile);
(...skipping 15 matching lines...) Expand all
229 scoped_ptr<printing::Metafile> metafile_; 263 scoped_ptr<printing::Metafile> metafile_;
230 264
231 // The absolute path where the file will be saved. 265 // The absolute path where the file will be saved.
232 FilePath path_; 266 FilePath path_;
233 }; 267 };
234 268
235 // static 269 // static
236 FilePath* PrintPreviewHandler::last_saved_path_ = NULL; 270 FilePath* PrintPreviewHandler::last_saved_path_ = NULL;
237 271
238 PrintPreviewHandler::PrintPreviewHandler() 272 PrintPreviewHandler::PrintPreviewHandler()
239 : print_backend_(printing::PrintBackend::CreateInstance(NULL)) { 273 : print_backend_(printing::PrintBackend::CreateInstance(NULL)),
274 regenerate_preview_request_count_(0),
275 manage_printers_dialog_request_count_(0),
276 print_preview_failed_count_(0),
277 has_logged_printers_count_(false) {
240 } 278 }
241 279
242 PrintPreviewHandler::~PrintPreviewHandler() { 280 PrintPreviewHandler::~PrintPreviewHandler() {
243 if (select_file_dialog_.get()) 281 if (select_file_dialog_.get())
244 select_file_dialog_->ListenerDestroyed(); 282 select_file_dialog_->ListenerDestroyed();
245 } 283 }
246 284
247 void PrintPreviewHandler::RegisterMessages() { 285 void PrintPreviewHandler::RegisterMessages() {
248 web_ui_->RegisterMessageCallback("getPrinters", 286 web_ui_->RegisterMessageCallback("getPrinters",
249 NewCallback(this, &PrintPreviewHandler::HandleGetPrinters)); 287 NewCallback(this, &PrintPreviewHandler::HandleGetPrinters));
(...skipping 10 matching lines...) Expand all
260 web_ui_->RegisterMessageCallback("closePrintPreviewTab", 298 web_ui_->RegisterMessageCallback("closePrintPreviewTab",
261 NewCallback(this, &PrintPreviewHandler::HandleClosePreviewTab)); 299 NewCallback(this, &PrintPreviewHandler::HandleClosePreviewTab));
262 } 300 }
263 301
264 TabContents* PrintPreviewHandler::preview_tab() { 302 TabContents* PrintPreviewHandler::preview_tab() {
265 return web_ui_->tab_contents(); 303 return web_ui_->tab_contents();
266 } 304 }
267 305
268 void PrintPreviewHandler::HandleGetPrinters(const ListValue*) { 306 void PrintPreviewHandler::HandleGetPrinters(const ListValue*) {
269 scoped_refptr<PrintSystemTaskProxy> task = 307 scoped_refptr<PrintSystemTaskProxy> task =
270 new PrintSystemTaskProxy(AsWeakPtr(), print_backend_.get()); 308 new PrintSystemTaskProxy(AsWeakPtr(),
309 print_backend_.get(),
310 has_logged_printers_count_);
311 has_logged_printers_count_ = true;
312
271 BrowserThread::PostTask( 313 BrowserThread::PostTask(
272 BrowserThread::FILE, FROM_HERE, 314 BrowserThread::FILE, FROM_HERE,
273 NewRunnableMethod(task.get(), 315 NewRunnableMethod(task.get(),
274 &PrintSystemTaskProxy::EnumeratePrinters)); 316 &PrintSystemTaskProxy::EnumeratePrinters));
275 } 317 }
276 318
277 void PrintPreviewHandler::HandleGetPreview(const ListValue* args) { 319 void PrintPreviewHandler::HandleGetPreview(const ListValue* args) {
320 // Increment request count.
321 ++regenerate_preview_request_count_;
322
278 TabContents* initiator_tab = GetInitiatorTab(); 323 TabContents* initiator_tab = GetInitiatorTab();
279 if (!initiator_tab) { 324 if (!initiator_tab) {
325 ++print_preview_failed_count_;
280 web_ui_->CallJavascriptFunction("printPreviewFailed"); 326 web_ui_->CallJavascriptFunction("printPreviewFailed");
281 return; 327 return;
282 } 328 }
283 scoped_ptr<DictionaryValue> settings(GetSettingsDictionary(args)); 329 scoped_ptr<DictionaryValue> settings(GetSettingsDictionary(args));
284 if (!settings.get()) 330 if (!settings.get())
285 return; 331 return;
286 332
287 RenderViewHost* rvh = initiator_tab->render_view_host(); 333 RenderViewHost* rvh = initiator_tab->render_view_host();
288 rvh->Send(new PrintMsg_PrintPreview(rvh->routing_id(), *settings)); 334 rvh->Send(new PrintMsg_PrintPreview(rvh->routing_id(), *settings));
289 } 335 }
290 336
291 void PrintPreviewHandler::HandlePrint(const ListValue* args) { 337 void PrintPreviewHandler::HandlePrint(const ListValue* args) {
338 ReportStats();
339
340 // Record the number of times the user requests to regenerate preview data
341 // before printing.
342 UMA_HISTOGRAM_COUNTS(kRegeneratePreviewRequestsRcvdBeforePrint,
343 regenerate_preview_request_count_);
344
292 TabContents* initiator_tab = GetInitiatorTab(); 345 TabContents* initiator_tab = GetInitiatorTab();
293 if (initiator_tab) { 346 if (initiator_tab) {
294 RenderViewHost* rvh = initiator_tab->render_view_host(); 347 RenderViewHost* rvh = initiator_tab->render_view_host();
295 rvh->Send(new PrintMsg_ResetScriptedPrintCount(rvh->routing_id())); 348 rvh->Send(new PrintMsg_ResetScriptedPrintCount(rvh->routing_id()));
296 } 349 }
297 350
298 scoped_ptr<DictionaryValue> settings(GetSettingsDictionary(args)); 351 scoped_ptr<DictionaryValue> settings(GetSettingsDictionary(args));
299 if (!settings.get()) 352 if (!settings.get())
300 return; 353 return;
301 354
302 bool print_to_pdf = false; 355 bool print_to_pdf = false;
303 settings->GetBoolean(printing::kSettingPrintToPDF, &print_to_pdf); 356 settings->GetBoolean(printing::kSettingPrintToPDF, &print_to_pdf);
304 357
305 TabContentsWrapper* preview_tab_wrapper = 358 TabContentsWrapper* preview_tab_wrapper =
306 TabContentsWrapper::GetCurrentWrapperForContents(preview_tab()); 359 TabContentsWrapper::GetCurrentWrapperForContents(preview_tab());
307 360
308 if (print_to_pdf) { 361 if (print_to_pdf) {
362 UMA_HISTOGRAM_ENUMERATION(kUserAction,
363 PRINT_TO_PDF,
364 USERACTION_BUCKET_BOUNDARY);
365
309 // Pre-populating select file dialog with print job title. 366 // Pre-populating select file dialog with print job title.
310 string16 print_job_title_utf16 = 367 string16 print_job_title_utf16 =
311 preview_tab_wrapper->print_view_manager()->RenderSourceName(); 368 preview_tab_wrapper->print_view_manager()->RenderSourceName();
312 369
313 #if defined(OS_WIN) 370 #if defined(OS_WIN)
314 FilePath::StringType print_job_title(print_job_title_utf16); 371 FilePath::StringType print_job_title(print_job_title_utf16);
315 #elif defined(OS_POSIX) 372 #elif defined(OS_POSIX)
316 FilePath::StringType print_job_title = UTF16ToUTF8(print_job_title_utf16); 373 FilePath::StringType print_job_title = UTF16ToUTF8(print_job_title_utf16);
317 #endif 374 #endif
318 375
319 file_util::ReplaceIllegalCharactersInPath(&print_job_title, '_'); 376 file_util::ReplaceIllegalCharactersInPath(&print_job_title, '_');
320 FilePath default_filename(print_job_title); 377 FilePath default_filename(print_job_title);
321 default_filename = 378 default_filename =
322 default_filename.ReplaceExtension(FILE_PATH_LITERAL("pdf")); 379 default_filename.ReplaceExtension(FILE_PATH_LITERAL("pdf"));
323 380
324 SelectFile(default_filename); 381 SelectFile(default_filename);
325 } else { 382 } else {
383 UMA_HISTOGRAM_ENUMERATION(kUserAction,
384 PRINT_TO_PRINTER,
385 USERACTION_BUCKET_BOUNDARY);
326 g_browser_process->background_printing_manager()->OwnTabContents( 386 g_browser_process->background_printing_manager()->OwnTabContents(
327 preview_tab_wrapper); 387 preview_tab_wrapper);
328 388
329 // The PDF being printed contains only the pages that the user selected, 389 // The PDF being printed contains only the pages that the user selected,
330 // so ignore the page range and print all pages. 390 // so ignore the page range and print all pages.
331 settings->Remove(printing::kSettingPageRange, NULL); 391 settings->Remove(printing::kSettingPageRange, NULL);
332 RenderViewHost* rvh = web_ui_->GetRenderViewHost(); 392 RenderViewHost* rvh = web_ui_->GetRenderViewHost();
333 rvh->Send(new PrintMsg_PrintForPrintPreview(rvh->routing_id(), *settings)); 393 rvh->Send(new PrintMsg_PrintForPrintPreview(rvh->routing_id(), *settings));
334 } 394 }
335 } 395 }
336 396
337 void PrintPreviewHandler::HandleGetPrinterCapabilities( 397 void PrintPreviewHandler::HandleGetPrinterCapabilities(
338 const ListValue* args) { 398 const ListValue* args) {
339 std::string printer_name; 399 std::string printer_name;
340 bool ret = args->GetString(0, &printer_name); 400 bool ret = args->GetString(0, &printer_name);
341 if (!ret || printer_name.empty()) 401 if (!ret || printer_name.empty())
342 return; 402 return;
343 403
344 scoped_refptr<PrintSystemTaskProxy> task = 404 scoped_refptr<PrintSystemTaskProxy> task =
345 new PrintSystemTaskProxy(AsWeakPtr(), print_backend_.get()); 405 new PrintSystemTaskProxy(AsWeakPtr(),
406 print_backend_.get(),
407 has_logged_printers_count_);
346 408
347 BrowserThread::PostTask( 409 BrowserThread::PostTask(
348 BrowserThread::FILE, FROM_HERE, 410 BrowserThread::FILE, FROM_HERE,
349 NewRunnableMethod(task.get(), 411 NewRunnableMethod(task.get(),
350 &PrintSystemTaskProxy::GetPrinterCapabilities, 412 &PrintSystemTaskProxy::GetPrinterCapabilities,
351 printer_name)); 413 printer_name));
352 } 414 }
353 415
354 void PrintPreviewHandler::HandleShowSystemDialog(const ListValue* args) { 416 void PrintPreviewHandler::HandleShowSystemDialog(const ListValue* args) {
417 ReportStats();
418 UMA_HISTOGRAM_ENUMERATION(kUserAction,
419 FALLBACK_TO_ADVANCED_SETTINGS_DIALOG,
420 USERACTION_BUCKET_BOUNDARY);
421
355 TabContents* initiator_tab = GetInitiatorTab(); 422 TabContents* initiator_tab = GetInitiatorTab();
356 if (!initiator_tab) 423 if (!initiator_tab)
357 return; 424 return;
358 initiator_tab->Activate(); 425 initiator_tab->Activate();
359 426
360 TabContentsWrapper* wrapper = 427 TabContentsWrapper* wrapper =
361 TabContentsWrapper::GetCurrentWrapperForContents(initiator_tab); 428 TabContentsWrapper::GetCurrentWrapperForContents(initiator_tab);
362 wrapper->print_view_manager()->PrintNow(); 429 wrapper->print_view_manager()->PrintNow();
363 430
364 ClosePrintPreviewTab(); 431 ClosePrintPreviewTab();
365 } 432 }
366 433
367 void PrintPreviewHandler::HandleManagePrinters(const ListValue* args) { 434 void PrintPreviewHandler::HandleManagePrinters(const ListValue* args) {
435 ++manage_printers_dialog_request_count_;
368 printing::PrinterManagerDialog::ShowPrinterManagerDialog(); 436 printing::PrinterManagerDialog::ShowPrinterManagerDialog();
369 } 437 }
370 438
371 void PrintPreviewHandler::HandleClosePreviewTab(const ListValue* args) { 439 void PrintPreviewHandler::HandleClosePreviewTab(const ListValue* args) {
440 ReportStats();
441 UMA_HISTOGRAM_ENUMERATION(kUserAction,
442 CANCEL,
443 USERACTION_BUCKET_BOUNDARY);
444
445 // Record the number of times the user requests to regenerate preview data
446 // before cancelling.
447 UMA_HISTOGRAM_COUNTS(kRegeneratePreviewRequestsRcvdBeforeCancel,
448 regenerate_preview_request_count_);
449
372 ActivateInitiatorTabAndClosePreviewTab(); 450 ActivateInitiatorTabAndClosePreviewTab();
373 } 451 }
374 452
453 void PrintPreviewHandler::ReportStats() {
454 if (print_preview_failed_count_ > 0) {
455 UMA_HISTOGRAM_COUNTS(kPreviewFailedInitiatorTabDoesNotExist,
456 print_preview_failed_count_);
457 }
458
459 UMA_HISTOGRAM_COUNTS(kManagePrinters,
460 manage_printers_dialog_request_count_);
461 }
462
375 void PrintPreviewHandler::ActivateInitiatorTabAndClosePreviewTab() { 463 void PrintPreviewHandler::ActivateInitiatorTabAndClosePreviewTab() {
376 TabContents* initiator_tab = GetInitiatorTab(); 464 TabContents* initiator_tab = GetInitiatorTab();
377 if (initiator_tab) 465 if (initiator_tab)
378 initiator_tab->Activate(); 466 initiator_tab->Activate();
379 ClosePrintPreviewTab(); 467 ClosePrintPreviewTab();
380 } 468 }
381 469
382 void PrintPreviewHandler::SendPrinterCapabilities( 470 void PrintPreviewHandler::SendPrinterCapabilities(
383 const DictionaryValue& settings_info) { 471 const DictionaryValue& settings_info) {
384 web_ui_->CallJavascriptFunction("updateWithPrinterCapabilities", 472 web_ui_->CallJavascriptFunction("updateWithPrinterCapabilities",
(...skipping 72 matching lines...) Expand 10 before | Expand all | Expand 10 after
457 metafile->InitFromData(data.first->memory(), data.second); 545 metafile->InitFromData(data.first->memory(), data.second);
458 546
459 // Updating last_saved_path_ to the newly selected folder. 547 // Updating last_saved_path_ to the newly selected folder.
460 *last_saved_path_ = path.DirName(); 548 *last_saved_path_ = path.DirName();
461 549
462 PrintToPdfTask* task = new PrintToPdfTask(metafile, path); 550 PrintToPdfTask* task = new PrintToPdfTask(metafile, path);
463 BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE, task); 551 BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE, task);
464 552
465 ActivateInitiatorTabAndClosePreviewTab(); 553 ActivateInitiatorTabAndClosePreviewTab();
466 } 554 }
OLDNEW
« no previous file with comments | « chrome/browser/ui/webui/print_preview_handler.h ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698