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

Side by Side Diff: chrome/browser/extensions/api/tabs/tabs.cc

Issue 11312228: Move extension_error_utils.* and url_pattern_set.* into (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: hate Created 8 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) 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/browser/extensions/api/tabs/tabs.h" 5 #include "chrome/browser/extensions/api/tabs/tabs.h"
6 6
7 #include <algorithm> 7 #include <algorithm>
8 #include <limits> 8 #include <limits>
9 #include <vector> 9 #include <vector>
10 10
(...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after
45 #include "chrome/browser/ui/panels/panel_manager.h" 45 #include "chrome/browser/ui/panels/panel_manager.h"
46 #include "chrome/browser/ui/snapshot_tab_helper.h" 46 #include "chrome/browser/ui/snapshot_tab_helper.h"
47 #include "chrome/browser/ui/tab_contents/tab_contents.h" 47 #include "chrome/browser/ui/tab_contents/tab_contents.h"
48 #include "chrome/browser/ui/tabs/tab_strip_model.h" 48 #include "chrome/browser/ui/tabs/tab_strip_model.h"
49 #include "chrome/browser/ui/window_sizer/window_sizer.h" 49 #include "chrome/browser/ui/window_sizer/window_sizer.h"
50 #include "chrome/browser/web_applications/web_app.h" 50 #include "chrome/browser/web_applications/web_app.h"
51 #include "chrome/common/chrome_notification_types.h" 51 #include "chrome/common/chrome_notification_types.h"
52 #include "chrome/common/chrome_switches.h" 52 #include "chrome/common/chrome_switches.h"
53 #include "chrome/common/extensions/api/windows.h" 53 #include "chrome/common/extensions/api/windows.h"
54 #include "chrome/common/extensions/extension.h" 54 #include "chrome/common/extensions/extension.h"
55 #include "chrome/common/extensions/extension_error_utils.h"
56 #include "chrome/common/extensions/extension_manifest_constants.h" 55 #include "chrome/common/extensions/extension_manifest_constants.h"
57 #include "chrome/common/extensions/extension_messages.h" 56 #include "chrome/common/extensions/extension_messages.h"
58 #include "chrome/common/extensions/user_script.h" 57 #include "chrome/common/extensions/user_script.h"
59 #include "chrome/common/pref_names.h" 58 #include "chrome/common/pref_names.h"
60 #include "chrome/common/url_constants.h" 59 #include "chrome/common/url_constants.h"
61 #include "content/public/browser/navigation_controller.h" 60 #include "content/public/browser/navigation_controller.h"
62 #include "content/public/browser/navigation_entry.h" 61 #include "content/public/browser/navigation_entry.h"
63 #include "content/public/browser/notification_details.h" 62 #include "content/public/browser/notification_details.h"
64 #include "content/public/browser/notification_source.h" 63 #include "content/public/browser/notification_source.h"
65 #include "content/public/browser/render_view_host.h" 64 #include "content/public/browser/render_view_host.h"
66 #include "content/public/browser/render_widget_host_view.h" 65 #include "content/public/browser/render_widget_host_view.h"
67 #include "content/public/browser/web_contents.h" 66 #include "content/public/browser/web_contents.h"
68 #include "content/public/browser/web_contents_view.h" 67 #include "content/public/browser/web_contents_view.h"
69 #include "content/public/common/url_constants.h" 68 #include "content/public/common/url_constants.h"
70 #include "extensions/common/constants.h" 69 #include "extensions/common/constants.h"
70 #include "extensions/common/error_utils.h"
71 #include "skia/ext/image_operations.h" 71 #include "skia/ext/image_operations.h"
72 #include "skia/ext/platform_canvas.h" 72 #include "skia/ext/platform_canvas.h"
73 #include "third_party/skia/include/core/SkBitmap.h" 73 #include "third_party/skia/include/core/SkBitmap.h"
74 #include "ui/base/ui_base_types.h" 74 #include "ui/base/ui_base_types.h"
75 #include "ui/gfx/codec/jpeg_codec.h" 75 #include "ui/gfx/codec/jpeg_codec.h"
76 #include "ui/gfx/codec/png_codec.h" 76 #include "ui/gfx/codec/png_codec.h"
77 77
78 #if defined(OS_WIN) 78 #if defined(OS_WIN)
79 #include "base/win/metro.h" 79 #include "base/win/metro.h"
80 #endif // OS_WIN 80 #endif // OS_WIN
81 81
82 namespace Get = extensions::api::windows::Get; 82 namespace Get = extensions::api::windows::Get;
83 namespace GetAll = extensions::api::windows::GetAll; 83 namespace GetAll = extensions::api::windows::GetAll;
84 namespace GetCurrent = extensions::api::windows::GetCurrent; 84 namespace GetCurrent = extensions::api::windows::GetCurrent;
85 namespace GetLastFocused = extensions::api::windows::GetLastFocused; 85 namespace GetLastFocused = extensions::api::windows::GetLastFocused;
86 namespace errors = extension_manifest_errors; 86 namespace errors = extension_manifest_errors;
87 namespace keys = extensions::tabs_constants; 87 namespace keys = extensions::tabs_constants;
88 88
89 using content::NavigationController; 89 using content::NavigationController;
90 using content::NavigationEntry; 90 using content::NavigationEntry;
91 using content::OpenURLParams; 91 using content::OpenURLParams;
92 using content::Referrer; 92 using content::Referrer;
93 using content::RenderViewHost; 93 using content::RenderViewHost;
94 using content::WebContents; 94 using content::WebContents;
95 using extensions::ErrorUtils;
95 using extensions::ScriptExecutor; 96 using extensions::ScriptExecutor;
96 using extensions::WindowController; 97 using extensions::WindowController;
97 using extensions::WindowControllerList; 98 using extensions::WindowControllerList;
98 99
99 const int CaptureVisibleTabFunction::kDefaultQuality = 90; 100 const int CaptureVisibleTabFunction::kDefaultQuality = 90;
100 101
101 namespace { 102 namespace {
102 103
103 // |error_message| can optionally be passed in a will be set with an appropriate 104 // |error_message| can optionally be passed in a will be set with an appropriate
104 // message if the window cannot be found by id. 105 // message if the window cannot be found by id.
105 Browser* GetBrowserInProfileWithId(Profile* profile, 106 Browser* GetBrowserInProfileWithId(Profile* profile,
106 const int window_id, 107 const int window_id,
107 bool include_incognito, 108 bool include_incognito,
108 std::string* error_message) { 109 std::string* error_message) {
109 Profile* incognito_profile = 110 Profile* incognito_profile =
110 include_incognito && profile->HasOffTheRecordProfile() ? 111 include_incognito && profile->HasOffTheRecordProfile() ?
111 profile->GetOffTheRecordProfile() : NULL; 112 profile->GetOffTheRecordProfile() : NULL;
112 for (BrowserList::const_iterator browser = BrowserList::begin(); 113 for (BrowserList::const_iterator browser = BrowserList::begin();
113 browser != BrowserList::end(); ++browser) { 114 browser != BrowserList::end(); ++browser) {
114 if (((*browser)->profile() == profile || 115 if (((*browser)->profile() == profile ||
115 (*browser)->profile() == incognito_profile) && 116 (*browser)->profile() == incognito_profile) &&
116 ExtensionTabUtil::GetWindowId(*browser) == window_id && 117 ExtensionTabUtil::GetWindowId(*browser) == window_id &&
117 ((*browser)->window())) 118 ((*browser)->window()))
118 return *browser; 119 return *browser;
119 } 120 }
120 121
121 if (error_message) 122 if (error_message)
122 *error_message = ExtensionErrorUtils::FormatErrorMessage( 123 *error_message = ErrorUtils::FormatErrorMessage(
123 keys::kWindowNotFoundError, base::IntToString(window_id)); 124 keys::kWindowNotFoundError, base::IntToString(window_id));
124 125
125 return NULL; 126 return NULL;
126 } 127 }
127 128
128 bool GetBrowserFromWindowID( 129 bool GetBrowserFromWindowID(
129 UIThreadExtensionFunction* function, int window_id, Browser** browser) { 130 UIThreadExtensionFunction* function, int window_id, Browser** browser) {
130 if (window_id == extension_misc::kCurrentWindowId) { 131 if (window_id == extension_misc::kCurrentWindowId) {
131 *browser = function->GetCurrentBrowser(); 132 *browser = function->GetCurrentBrowser();
132 if (!(*browser) || !(*browser)->window()) { 133 if (!(*browser) || !(*browser)->window()) {
(...skipping 27 matching lines...) Expand all
160 CurrentWindowForFunction(function); 161 CurrentWindowForFunction(function);
161 } 162 }
162 if (!(*controller)) { 163 if (!(*controller)) {
163 function->SetError(keys::kNoCurrentWindowError); 164 function->SetError(keys::kNoCurrentWindowError);
164 return false; 165 return false;
165 } 166 }
166 } else { 167 } else {
167 *controller = WindowControllerList::GetInstance()-> 168 *controller = WindowControllerList::GetInstance()->
168 FindWindowForFunctionById(function, window_id); 169 FindWindowForFunctionById(function, window_id);
169 if (!(*controller)) { 170 if (!(*controller)) {
170 function->SetError(ExtensionErrorUtils::FormatErrorMessage( 171 function->SetError(ErrorUtils::FormatErrorMessage(
171 keys::kWindowNotFoundError, base::IntToString(window_id))); 172 keys::kWindowNotFoundError, base::IntToString(window_id)));
172 return false; 173 return false;
173 } 174 }
174 } 175 }
175 return true; 176 return true;
176 } 177 }
177 178
178 // |error_message| can optionally be passed in and will be set with an 179 // |error_message| can optionally be passed in and will be set with an
179 // appropriate message if the tab cannot be found by id. 180 // appropriate message if the tab cannot be found by id.
180 bool GetTabById(int tab_id, 181 bool GetTabById(int tab_id,
181 Profile* profile, 182 Profile* profile,
182 bool include_incognito, 183 bool include_incognito,
183 Browser** browser, 184 Browser** browser,
184 TabStripModel** tab_strip, 185 TabStripModel** tab_strip,
185 content::WebContents** contents, 186 content::WebContents** contents,
186 int* tab_index, 187 int* tab_index,
187 std::string* error_message) { 188 std::string* error_message) {
188 if (ExtensionTabUtil::GetTabById(tab_id, profile, include_incognito, 189 if (ExtensionTabUtil::GetTabById(tab_id, profile, include_incognito,
189 browser, tab_strip, contents, tab_index)) 190 browser, tab_strip, contents, tab_index))
190 return true; 191 return true;
191 192
192 if (error_message) 193 if (error_message)
193 *error_message = ExtensionErrorUtils::FormatErrorMessage( 194 *error_message = ErrorUtils::FormatErrorMessage(
194 keys::kTabNotFoundError, base::IntToString(tab_id)); 195 keys::kTabNotFoundError, base::IntToString(tab_id));
195 196
196 return false; 197 return false;
197 } 198 }
198 199
199 // A three state enum to distinguish between when a boolean query argument is 200 // A three state enum to distinguish between when a boolean query argument is
200 // set or not. 201 // set or not.
201 enum QueryArg { 202 enum QueryArg {
202 NOT_SET = -1, 203 NOT_SET = -1,
203 MATCH_FALSE, 204 MATCH_FALSE,
(...skipping 169 matching lines...) Expand 10 before | Expand all | Expand 10 after
373 for (size_t i = 0; i < urls->size();) { 374 for (size_t i = 0; i < urls->size();) {
374 if (chrome::IsURLAllowedInIncognito((*urls)[i], profile())) { 375 if (chrome::IsURLAllowedInIncognito((*urls)[i], profile())) {
375 i++; 376 i++;
376 } else { 377 } else {
377 if (first_url_erased.empty()) 378 if (first_url_erased.empty())
378 first_url_erased = (*urls)[i].spec(); 379 first_url_erased = (*urls)[i].spec();
379 urls->erase(urls->begin() + i); 380 urls->erase(urls->begin() + i);
380 } 381 }
381 } 382 }
382 if (urls->empty() && !first_url_erased.empty()) { 383 if (urls->empty() && !first_url_erased.empty()) {
383 error_ = ExtensionErrorUtils::FormatErrorMessage( 384 error_ = ErrorUtils::FormatErrorMessage(
384 keys::kURLsNotAllowedInIncognitoError, first_url_erased); 385 keys::kURLsNotAllowedInIncognitoError, first_url_erased);
385 *is_error = true; 386 *is_error = true;
386 return false; 387 return false;
387 } 388 }
388 } 389 }
389 return incognito; 390 return incognito;
390 } 391 }
391 392
392 bool CreateWindowFunction::RunImpl() { 393 bool CreateWindowFunction::RunImpl() {
393 DictionaryValue* args = NULL; 394 DictionaryValue* args = NULL;
(...skipping 23 matching lines...) Expand all
417 url_strings.push_back(url_string); 418 url_strings.push_back(url_string);
418 } 419 }
419 } 420 }
420 421
421 // Second, resolve, validate and convert them to GURLs. 422 // Second, resolve, validate and convert them to GURLs.
422 for (std::vector<std::string>::iterator i = url_strings.begin(); 423 for (std::vector<std::string>::iterator i = url_strings.begin();
423 i != url_strings.end(); ++i) { 424 i != url_strings.end(); ++i) {
424 GURL url = ExtensionTabUtil::ResolvePossiblyRelativeURL( 425 GURL url = ExtensionTabUtil::ResolvePossiblyRelativeURL(
425 *i, GetExtension()); 426 *i, GetExtension());
426 if (!url.is_valid()) { 427 if (!url.is_valid()) {
427 error_ = ExtensionErrorUtils::FormatErrorMessage( 428 error_ = ErrorUtils::FormatErrorMessage(
428 keys::kInvalidUrlError, *i); 429 keys::kInvalidUrlError, *i);
429 return false; 430 return false;
430 } 431 }
431 // Don't let the extension crash the browser or renderers. 432 // Don't let the extension crash the browser or renderers.
432 if (ExtensionTabUtil::IsCrashURL(url)) { 433 if (ExtensionTabUtil::IsCrashURL(url)) {
433 error_ = keys::kNoCrashBrowserError; 434 error_ = keys::kNoCrashBrowserError;
434 return false; 435 return false;
435 } 436 }
436 urls.push_back(url); 437 urls.push_back(url);
437 } 438 }
438 } 439 }
439 } 440 }
440 441
441 // Look for optional tab id. 442 // Look for optional tab id.
442 if (args) { 443 if (args) {
443 int tab_id = -1; 444 int tab_id = -1;
444 if (args->HasKey(keys::kTabIdKey)) { 445 if (args->HasKey(keys::kTabIdKey)) {
445 EXTENSION_FUNCTION_VALIDATE(args->GetInteger(keys::kTabIdKey, &tab_id)); 446 EXTENSION_FUNCTION_VALIDATE(args->GetInteger(keys::kTabIdKey, &tab_id));
446 447
447 // Find the tab and detach it from the original window. 448 // Find the tab and detach it from the original window.
448 TabStripModel* source_tab_strip = NULL; 449 TabStripModel* source_tab_strip = NULL;
449 int tab_index = -1; 450 int tab_index = -1;
450 if (!GetTabById(tab_id, profile(), include_incognito(), 451 if (!GetTabById(tab_id, profile(), include_incognito(),
451 NULL, &source_tab_strip, 452 NULL, &source_tab_strip,
452 NULL, &tab_index, &error_)) 453 NULL, &tab_index, &error_))
453 return false; 454 return false;
454 contents = source_tab_strip->DetachTabContentsAt(tab_index); 455 contents = source_tab_strip->DetachTabContentsAt(tab_index);
455 if (!contents) { 456 if (!contents) {
456 error_ = ExtensionErrorUtils::FormatErrorMessage( 457 error_ = ErrorUtils::FormatErrorMessage(
457 keys::kTabNotFoundError, base::IntToString(tab_id)); 458 keys::kTabNotFoundError, base::IntToString(tab_id));
458 return false; 459 return false;
459 } 460 }
460 } 461 }
461 } 462 }
462 463
463 Profile* window_profile = profile(); 464 Profile* window_profile = profile();
464 Browser::Type window_type = Browser::TYPE_TABBED; 465 Browser::Type window_type = Browser::TYPE_TABBED;
465 466
466 // panel_create_mode only applies if window is TYPE_PANEL. 467 // panel_create_mode only applies if window is TYPE_PANEL.
(...skipping 553 matching lines...) Expand 10 before | Expand all | Expand 10 after
1020 // -favIconUrl 1021 // -favIconUrl
1021 1022
1022 std::string url_string; 1023 std::string url_string;
1023 GURL url; 1024 GURL url;
1024 if (args->HasKey(keys::kUrlKey)) { 1025 if (args->HasKey(keys::kUrlKey)) {
1025 EXTENSION_FUNCTION_VALIDATE(args->GetString(keys::kUrlKey, 1026 EXTENSION_FUNCTION_VALIDATE(args->GetString(keys::kUrlKey,
1026 &url_string)); 1027 &url_string));
1027 url = ExtensionTabUtil::ResolvePossiblyRelativeURL(url_string, 1028 url = ExtensionTabUtil::ResolvePossiblyRelativeURL(url_string,
1028 GetExtension()); 1029 GetExtension());
1029 if (!url.is_valid()) { 1030 if (!url.is_valid()) {
1030 error_ = ExtensionErrorUtils::FormatErrorMessage(keys::kInvalidUrlError, 1031 error_ = ErrorUtils::FormatErrorMessage(keys::kInvalidUrlError,
1031 url_string); 1032 url_string);
1032 return false; 1033 return false;
1033 } 1034 }
1034 } 1035 }
1035 1036
1036 // Don't let extensions crash the browser or renderers. 1037 // Don't let extensions crash the browser or renderers.
1037 if (ExtensionTabUtil::IsCrashURL(url)) { 1038 if (ExtensionTabUtil::IsCrashURL(url)) {
1038 error_ = keys::kNoCrashBrowserError; 1039 error_ = keys::kNoCrashBrowserError;
1039 return false; 1040 return false;
1040 } 1041 }
(...skipping 147 matching lines...) Expand 10 before | Expand all | Expand 10 after
1188 std::vector<int> tab_indices; 1189 std::vector<int> tab_indices;
1189 EXTENSION_FUNCTION_VALIDATE(extensions::ReadOneOrMoreIntegers( 1190 EXTENSION_FUNCTION_VALIDATE(extensions::ReadOneOrMoreIntegers(
1190 tab_value, &tab_indices)); 1191 tab_value, &tab_indices));
1191 1192
1192 // Create a new selection model as we read the list of tab indices. 1193 // Create a new selection model as we read the list of tab indices.
1193 for (size_t i = 0; i < tab_indices.size(); ++i) { 1194 for (size_t i = 0; i < tab_indices.size(); ++i) {
1194 int index = tab_indices[i]; 1195 int index = tab_indices[i];
1195 1196
1196 // Make sure the index is in range. 1197 // Make sure the index is in range.
1197 if (!tabstrip->ContainsIndex(index)) { 1198 if (!tabstrip->ContainsIndex(index)) {
1198 error_ = ExtensionErrorUtils::FormatErrorMessage( 1199 error_ = ErrorUtils::FormatErrorMessage(
1199 keys::kTabIndexNotFoundError, base::IntToString(index)); 1200 keys::kTabIndexNotFoundError, base::IntToString(index));
1200 return false; 1201 return false;
1201 } 1202 }
1202 1203
1203 // By default, we make the first tab in the list active. 1204 // By default, we make the first tab in the list active.
1204 if (active_index == -1) 1205 if (active_index == -1)
1205 active_index = index; 1206 active_index = index;
1206 1207
1207 selection.AddIndexToSelection(index); 1208 selection.AddIndexToSelection(index);
1208 } 1209 }
(...skipping 125 matching lines...) Expand 10 before | Expand all | Expand 10 after
1334 if (!update_props->HasKey(keys::kUrlKey)) 1335 if (!update_props->HasKey(keys::kUrlKey))
1335 return true; 1336 return true;
1336 1337
1337 std::string url_string; 1338 std::string url_string;
1338 EXTENSION_FUNCTION_VALIDATE(update_props->GetString( 1339 EXTENSION_FUNCTION_VALIDATE(update_props->GetString(
1339 keys::kUrlKey, &url_string)); 1340 keys::kUrlKey, &url_string));
1340 GURL url = ExtensionTabUtil::ResolvePossiblyRelativeURL( 1341 GURL url = ExtensionTabUtil::ResolvePossiblyRelativeURL(
1341 url_string, GetExtension()); 1342 url_string, GetExtension());
1342 1343
1343 if (!url.is_valid()) { 1344 if (!url.is_valid()) {
1344 error_ = ExtensionErrorUtils::FormatErrorMessage( 1345 error_ = ErrorUtils::FormatErrorMessage(
1345 keys::kInvalidUrlError, url_string); 1346 keys::kInvalidUrlError, url_string);
1346 return false; 1347 return false;
1347 } 1348 }
1348 1349
1349 // Don't let the extension crash the browser or renderers. 1350 // Don't let the extension crash the browser or renderers.
1350 if (ExtensionTabUtil::IsCrashURL(url)) { 1351 if (ExtensionTabUtil::IsCrashURL(url)) {
1351 error_ = keys::kNoCrashBrowserError; 1352 error_ = keys::kNoCrashBrowserError;
1352 return false; 1353 return false;
1353 } 1354 }
1354 1355
(...skipping 110 matching lines...) Expand 10 before | Expand all | Expand 10 after
1465 return false; 1466 return false;
1466 } 1467 }
1467 1468
1468 // If windowId is different from the current window, move between windows. 1469 // If windowId is different from the current window, move between windows.
1469 if (ExtensionTabUtil::GetWindowId(target_browser) != 1470 if (ExtensionTabUtil::GetWindowId(target_browser) !=
1470 ExtensionTabUtil::GetWindowId(source_browser)) { 1471 ExtensionTabUtil::GetWindowId(source_browser)) {
1471 TabStripModel* target_tab_strip = target_browser->tab_strip_model(); 1472 TabStripModel* target_tab_strip = target_browser->tab_strip_model();
1472 TabContents* tab_contents = 1473 TabContents* tab_contents =
1473 source_tab_strip->DetachTabContentsAt(tab_index); 1474 source_tab_strip->DetachTabContentsAt(tab_index);
1474 if (!tab_contents) { 1475 if (!tab_contents) {
1475 error_ = ExtensionErrorUtils::FormatErrorMessage( 1476 error_ = ErrorUtils::FormatErrorMessage(
1476 keys::kTabNotFoundError, base::IntToString(tab_ids[i])); 1477 keys::kTabNotFoundError, base::IntToString(tab_ids[i]));
1477 return false; 1478 return false;
1478 } 1479 }
1479 1480
1480 // Clamp move location to the last position. 1481 // Clamp move location to the last position.
1481 // This is ">" because it can append to a new index position. 1482 // This is ">" because it can append to a new index position.
1482 // -1 means set the move location to the last position. 1483 // -1 means set the move location to the last position.
1483 if (new_index > target_tab_strip->count() || new_index < 0) 1484 if (new_index > target_tab_strip->count() || new_index < 0)
1484 new_index = target_tab_strip->count(); 1485 new_index = target_tab_strip->count();
1485 1486
(...skipping 382 matching lines...) Expand 10 before | Expand all | Expand 10 after
1868 // called for every API call the extension made. 1869 // called for every API call the extension made.
1869 GotLanguage(language); 1870 GotLanguage(language);
1870 } 1871 }
1871 1872
1872 void DetectTabLanguageFunction::GotLanguage(const std::string& language) { 1873 void DetectTabLanguageFunction::GotLanguage(const std::string& language) {
1873 SetResult(Value::CreateStringValue(language.c_str())); 1874 SetResult(Value::CreateStringValue(language.c_str()));
1874 SendResponse(true); 1875 SendResponse(true);
1875 1876
1876 Release(); // Balanced in Run() 1877 Release(); // Balanced in Run()
1877 } 1878 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698