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

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: move into extensions namespace 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/extension_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 168 matching lines...) Expand 10 before | Expand all | Expand 10 after
372 for (size_t i = 0; i < urls->size();) { 373 for (size_t i = 0; i < urls->size();) {
373 if (chrome::IsURLAllowedInIncognito((*urls)[i], profile())) { 374 if (chrome::IsURLAllowedInIncognito((*urls)[i], profile())) {
374 i++; 375 i++;
375 } else { 376 } else {
376 if (first_url_erased.empty()) 377 if (first_url_erased.empty())
377 first_url_erased = (*urls)[i].spec(); 378 first_url_erased = (*urls)[i].spec();
378 urls->erase(urls->begin() + i); 379 urls->erase(urls->begin() + i);
379 } 380 }
380 } 381 }
381 if (urls->empty() && !first_url_erased.empty()) { 382 if (urls->empty() && !first_url_erased.empty()) {
382 error_ = ExtensionErrorUtils::FormatErrorMessage( 383 error_ = ErrorUtils::FormatErrorMessage(
383 keys::kURLsNotAllowedInIncognitoError, first_url_erased); 384 keys::kURLsNotAllowedInIncognitoError, first_url_erased);
384 *is_error = true; 385 *is_error = true;
385 return false; 386 return false;
386 } 387 }
387 } 388 }
388 return incognito; 389 return incognito;
389 } 390 }
390 391
391 bool CreateWindowFunction::RunImpl() { 392 bool CreateWindowFunction::RunImpl() {
392 DictionaryValue* args = NULL; 393 DictionaryValue* args = NULL;
(...skipping 23 matching lines...) Expand all
416 url_strings.push_back(url_string); 417 url_strings.push_back(url_string);
417 } 418 }
418 } 419 }
419 420
420 // Second, resolve, validate and convert them to GURLs. 421 // Second, resolve, validate and convert them to GURLs.
421 for (std::vector<std::string>::iterator i = url_strings.begin(); 422 for (std::vector<std::string>::iterator i = url_strings.begin();
422 i != url_strings.end(); ++i) { 423 i != url_strings.end(); ++i) {
423 GURL url = ExtensionTabUtil::ResolvePossiblyRelativeURL( 424 GURL url = ExtensionTabUtil::ResolvePossiblyRelativeURL(
424 *i, GetExtension()); 425 *i, GetExtension());
425 if (!url.is_valid()) { 426 if (!url.is_valid()) {
426 error_ = ExtensionErrorUtils::FormatErrorMessage( 427 error_ = ErrorUtils::FormatErrorMessage(
427 keys::kInvalidUrlError, *i); 428 keys::kInvalidUrlError, *i);
428 return false; 429 return false;
429 } 430 }
430 // Don't let the extension crash the browser or renderers. 431 // Don't let the extension crash the browser or renderers.
431 if (ExtensionTabUtil::IsCrashURL(url)) { 432 if (ExtensionTabUtil::IsCrashURL(url)) {
432 error_ = keys::kNoCrashBrowserError; 433 error_ = keys::kNoCrashBrowserError;
433 return false; 434 return false;
434 } 435 }
435 urls.push_back(url); 436 urls.push_back(url);
436 } 437 }
437 } 438 }
438 } 439 }
439 440
440 // Look for optional tab id. 441 // Look for optional tab id.
441 if (args) { 442 if (args) {
442 int tab_id = -1; 443 int tab_id = -1;
443 if (args->HasKey(keys::kTabIdKey)) { 444 if (args->HasKey(keys::kTabIdKey)) {
444 EXTENSION_FUNCTION_VALIDATE(args->GetInteger(keys::kTabIdKey, &tab_id)); 445 EXTENSION_FUNCTION_VALIDATE(args->GetInteger(keys::kTabIdKey, &tab_id));
445 446
446 // Find the tab and detach it from the original window. 447 // Find the tab and detach it from the original window.
447 TabStripModel* source_tab_strip = NULL; 448 TabStripModel* source_tab_strip = NULL;
448 int tab_index = -1; 449 int tab_index = -1;
449 if (!GetTabById(tab_id, profile(), include_incognito(), 450 if (!GetTabById(tab_id, profile(), include_incognito(),
450 NULL, &source_tab_strip, 451 NULL, &source_tab_strip,
451 NULL, &tab_index, &error_)) 452 NULL, &tab_index, &error_))
452 return false; 453 return false;
453 contents = source_tab_strip->DetachTabContentsAt(tab_index); 454 contents = source_tab_strip->DetachTabContentsAt(tab_index);
454 if (!contents) { 455 if (!contents) {
455 error_ = ExtensionErrorUtils::FormatErrorMessage( 456 error_ = ErrorUtils::FormatErrorMessage(
456 keys::kTabNotFoundError, base::IntToString(tab_id)); 457 keys::kTabNotFoundError, base::IntToString(tab_id));
457 return false; 458 return false;
458 } 459 }
459 } 460 }
460 } 461 }
461 462
462 Profile* window_profile = profile(); 463 Profile* window_profile = profile();
463 Browser::Type window_type = Browser::TYPE_TABBED; 464 Browser::Type window_type = Browser::TYPE_TABBED;
464 465
465 // panel_create_mode only applies if window is TYPE_PANEL. 466 // panel_create_mode only applies if window is TYPE_PANEL.
(...skipping 553 matching lines...) Expand 10 before | Expand all | Expand 10 after
1019 // -favIconUrl 1020 // -favIconUrl
1020 1021
1021 std::string url_string; 1022 std::string url_string;
1022 GURL url; 1023 GURL url;
1023 if (args->HasKey(keys::kUrlKey)) { 1024 if (args->HasKey(keys::kUrlKey)) {
1024 EXTENSION_FUNCTION_VALIDATE(args->GetString(keys::kUrlKey, 1025 EXTENSION_FUNCTION_VALIDATE(args->GetString(keys::kUrlKey,
1025 &url_string)); 1026 &url_string));
1026 url = ExtensionTabUtil::ResolvePossiblyRelativeURL(url_string, 1027 url = ExtensionTabUtil::ResolvePossiblyRelativeURL(url_string,
1027 GetExtension()); 1028 GetExtension());
1028 if (!url.is_valid()) { 1029 if (!url.is_valid()) {
1029 error_ = ExtensionErrorUtils::FormatErrorMessage(keys::kInvalidUrlError, 1030 error_ = ErrorUtils::FormatErrorMessage(keys::kInvalidUrlError,
1030 url_string); 1031 url_string);
1031 return false; 1032 return false;
1032 } 1033 }
1033 } 1034 }
1034 1035
1035 // Don't let extensions crash the browser or renderers. 1036 // Don't let extensions crash the browser or renderers.
1036 if (ExtensionTabUtil::IsCrashURL(url)) { 1037 if (ExtensionTabUtil::IsCrashURL(url)) {
1037 error_ = keys::kNoCrashBrowserError; 1038 error_ = keys::kNoCrashBrowserError;
1038 return false; 1039 return false;
1039 } 1040 }
(...skipping 147 matching lines...) Expand 10 before | Expand all | Expand 10 after
1187 std::vector<int> tab_indices; 1188 std::vector<int> tab_indices;
1188 EXTENSION_FUNCTION_VALIDATE(extensions::ReadOneOrMoreIntegers( 1189 EXTENSION_FUNCTION_VALIDATE(extensions::ReadOneOrMoreIntegers(
1189 tab_value, &tab_indices)); 1190 tab_value, &tab_indices));
1190 1191
1191 // Create a new selection model as we read the list of tab indices. 1192 // Create a new selection model as we read the list of tab indices.
1192 for (size_t i = 0; i < tab_indices.size(); ++i) { 1193 for (size_t i = 0; i < tab_indices.size(); ++i) {
1193 int index = tab_indices[i]; 1194 int index = tab_indices[i];
1194 1195
1195 // Make sure the index is in range. 1196 // Make sure the index is in range.
1196 if (!tabstrip->ContainsIndex(index)) { 1197 if (!tabstrip->ContainsIndex(index)) {
1197 error_ = ExtensionErrorUtils::FormatErrorMessage( 1198 error_ = ErrorUtils::FormatErrorMessage(
1198 keys::kTabIndexNotFoundError, base::IntToString(index)); 1199 keys::kTabIndexNotFoundError, base::IntToString(index));
1199 return false; 1200 return false;
1200 } 1201 }
1201 1202
1202 // By default, we make the first tab in the list active. 1203 // By default, we make the first tab in the list active.
1203 if (active_index == -1) 1204 if (active_index == -1)
1204 active_index = index; 1205 active_index = index;
1205 1206
1206 selection.AddIndexToSelection(index); 1207 selection.AddIndexToSelection(index);
1207 } 1208 }
(...skipping 125 matching lines...) Expand 10 before | Expand all | Expand 10 after
1333 if (!update_props->HasKey(keys::kUrlKey)) 1334 if (!update_props->HasKey(keys::kUrlKey))
1334 return true; 1335 return true;
1335 1336
1336 std::string url_string; 1337 std::string url_string;
1337 EXTENSION_FUNCTION_VALIDATE(update_props->GetString( 1338 EXTENSION_FUNCTION_VALIDATE(update_props->GetString(
1338 keys::kUrlKey, &url_string)); 1339 keys::kUrlKey, &url_string));
1339 GURL url = ExtensionTabUtil::ResolvePossiblyRelativeURL( 1340 GURL url = ExtensionTabUtil::ResolvePossiblyRelativeURL(
1340 url_string, GetExtension()); 1341 url_string, GetExtension());
1341 1342
1342 if (!url.is_valid()) { 1343 if (!url.is_valid()) {
1343 error_ = ExtensionErrorUtils::FormatErrorMessage( 1344 error_ = ErrorUtils::FormatErrorMessage(
1344 keys::kInvalidUrlError, url_string); 1345 keys::kInvalidUrlError, url_string);
1345 return false; 1346 return false;
1346 } 1347 }
1347 1348
1348 // Don't let the extension crash the browser or renderers. 1349 // Don't let the extension crash the browser or renderers.
1349 if (ExtensionTabUtil::IsCrashURL(url)) { 1350 if (ExtensionTabUtil::IsCrashURL(url)) {
1350 error_ = keys::kNoCrashBrowserError; 1351 error_ = keys::kNoCrashBrowserError;
1351 return false; 1352 return false;
1352 } 1353 }
1353 1354
(...skipping 110 matching lines...) Expand 10 before | Expand all | Expand 10 after
1464 return false; 1465 return false;
1465 } 1466 }
1466 1467
1467 // If windowId is different from the current window, move between windows. 1468 // If windowId is different from the current window, move between windows.
1468 if (ExtensionTabUtil::GetWindowId(target_browser) != 1469 if (ExtensionTabUtil::GetWindowId(target_browser) !=
1469 ExtensionTabUtil::GetWindowId(source_browser)) { 1470 ExtensionTabUtil::GetWindowId(source_browser)) {
1470 TabStripModel* target_tab_strip = target_browser->tab_strip_model(); 1471 TabStripModel* target_tab_strip = target_browser->tab_strip_model();
1471 TabContents* tab_contents = 1472 TabContents* tab_contents =
1472 source_tab_strip->DetachTabContentsAt(tab_index); 1473 source_tab_strip->DetachTabContentsAt(tab_index);
1473 if (!tab_contents) { 1474 if (!tab_contents) {
1474 error_ = ExtensionErrorUtils::FormatErrorMessage( 1475 error_ = ErrorUtils::FormatErrorMessage(
1475 keys::kTabNotFoundError, base::IntToString(tab_ids[i])); 1476 keys::kTabNotFoundError, base::IntToString(tab_ids[i]));
1476 return false; 1477 return false;
1477 } 1478 }
1478 1479
1479 // Clamp move location to the last position. 1480 // Clamp move location to the last position.
1480 // This is ">" because it can append to a new index position. 1481 // This is ">" because it can append to a new index position.
1481 // -1 means set the move location to the last position. 1482 // -1 means set the move location to the last position.
1482 if (new_index > target_tab_strip->count() || new_index < 0) 1483 if (new_index > target_tab_strip->count() || new_index < 0)
1483 new_index = target_tab_strip->count(); 1484 new_index = target_tab_strip->count();
1484 1485
(...skipping 382 matching lines...) Expand 10 before | Expand all | Expand 10 after
1867 // called for every API call the extension made. 1868 // called for every API call the extension made.
1868 GotLanguage(language); 1869 GotLanguage(language);
1869 } 1870 }
1870 1871
1871 void DetectTabLanguageFunction::GotLanguage(const std::string& language) { 1872 void DetectTabLanguageFunction::GotLanguage(const std::string& language) {
1872 SetResult(Value::CreateStringValue(language.c_str())); 1873 SetResult(Value::CreateStringValue(language.c_str()));
1873 SendResponse(true); 1874 SendResponse(true);
1874 1875
1875 Release(); // Balanced in Run() 1876 Release(); // Balanced in Run()
1876 } 1877 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698