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

Side by Side Diff: chrome/browser/ui/browser_close_browsertest.cc

Issue 7466033: Fix warning prompting on closing a window that will cancel downloads. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Make sure to use temporary download directory Created 9 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 | Annotate | Revision Log
« no previous file with comments | « chrome/browser/ui/browser.cc ('k') | chrome/browser/ui/browser_list.cc » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
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
3 // found in the LICENSE file.
4
5 #include "base/command_line.h"
6 #include "base/logging.h"
7 #include "base/path_service.h"
8 #include "base/stringprintf.h"
9 #include "chrome/browser/browser_process.h"
10 #include "chrome/browser/download/download_service.h"
11 #include "chrome/browser/download/download_service_factory.h"
12 #include "chrome/browser/download/download_test_observer.h"
13 #include "chrome/browser/net/url_request_mock_util.h"
14 #include "chrome/browser/prefs/pref_service.h"
15 #include "chrome/browser/profiles/profile.h"
16 #include "chrome/browser/profiles/profile_manager.h"
17 #include "chrome/browser/ui/browser.h"
18 #include "chrome/browser/ui/browser_window.h"
19 #include "chrome/browser/ui/webui/active_downloads_ui.h"
20 #include "chrome/common/chrome_paths.h"
21 #include "chrome/common/pref_names.h"
22 #include "chrome/common/url_constants.h"
23 #include "chrome/test/base/in_process_browser_test.h"
24 #include "chrome/test/base/ui_test_utils.h"
25 #include "content/browser/download/download_item.h"
26 #include "content/browser/net/url_request_slow_download_job.h"
27 #include "content/browser/tab_contents/tab_contents.h"
28 #include "content/public/common/page_transition_types.h"
29
30 class BrowserCloseTest : public InProcessBrowserTest {
31 public:
32 // Structure defining test cases for DownloadsCloseCheck.
33 struct DownloadsCloseCheckCase {
34 std::string DebugString() const;
35
36 // Input
37 struct {
38 struct {
39 int windows;
40 int downloads;
41 } regular;
42 struct {
43 int windows;
44 int downloads;
45 } incognito;
46 } profile_a;
47
48 struct {
49 struct {
50 int windows;
51 int downloads;
52 } regular;
53 struct {
54 int windows;
55 int downloads;
56 } incognito;
57 } profile_b;
58
59 // We always probe a window in profile A.
60 enum { REGULAR = 0, INCOGNITO = 1 } window_to_probe;
61
62 // Output
63 Browser::DownloadClosePreventionType type;
64
65 // Unchecked if type == DOWNLOAD_CLOSE_OK.
66 int num_blocking;
67 };
68
69 protected:
70 virtual void SetUpOnMainThread() OVERRIDE {
71 BrowserThread::PostTask(
72 BrowserThread::IO, FROM_HERE,
73 base::Bind(&chrome_browser_net::SetUrlRequestMocksEnabled, true));
74 }
75
76 // Create a second profile to work within multi-profile.
77 Profile* CreateSecondProfile() {
78 FilePath user_data_dir;
79 PathService::Get(chrome::DIR_USER_DATA, &user_data_dir);
80
81 if (!second_profile_data_dir_.CreateUniqueTempDirUnderPath(user_data_dir))
82 return NULL;
83
84 Profile* second_profile =
85 g_browser_process->profile_manager()->GetProfile(
86 second_profile_data_dir_.path());
87 if (!second_profile)
88 return NULL;
89
90 bool result = second_profile_downloads_dir_.CreateUniqueTempDir();
91 if (!result)
92 return NULL;
93 second_profile->GetPrefs()->SetFilePath(
94 prefs::kDownloadDefaultDirectory,
95 second_profile_downloads_dir_.path());
96
97 return second_profile;
98 }
99
100 // Create |num_downloads| number of downloads that are stalled
101 // (will quickly get to a place where the server won't
102 // provide any more data) so that we can test closing the
103 // browser with active downloads.
104 void CreateStalledDownloads(Browser* browser, int num_downloads) {
105 GURL url(URLRequestSlowDownloadJob::kKnownSizeUrl);
106
107 if (num_downloads == 0)
108 return;
109
110 // Setup an observer waiting for the given number of downloads
111 // to get to IN_PROGRESS.
112 DownloadManager* download_manager =
113 browser->profile()->GetDownloadManager();
114 scoped_ptr<DownloadTestObserver> observer(
115 new DownloadTestObserver(
116 download_manager, num_downloads,
117 DownloadItem::IN_PROGRESS,
118 true, // Bail on select file.
119 DownloadTestObserver::ON_DANGEROUS_DOWNLOAD_FAIL));
120
121 // Set of that number of downloads.
122 while (num_downloads--)
123 ui_test_utils::NavigateToURLWithDisposition(
124 browser, url, NEW_BACKGROUND_TAB,
125 ui_test_utils::BROWSER_TEST_NONE);
126
127 // Wait for them.
128 observer->WaitForFinished();
129 }
130
131 // All all downloads created in CreateStalledDownloads() to
132 // complete, and block in this routine until they do complete.
133 void CompleteAllDownloads(Browser* browser) {
134 GURL finish_url(URLRequestSlowDownloadJob::kFinishDownloadUrl);
135 ui_test_utils::NavigateToURL(browser, finish_url);
136
137 // Go through and, for every single profile, wait until there are
138 // no active downloads on that download manager.
139 std::vector<Profile*> profiles(
140 g_browser_process->profile_manager()->GetLoadedProfiles());
141 for (std::vector<Profile*>::const_iterator pit = profiles.begin();
142 pit != profiles.end(); ++pit) {
143 DownloadService* download_service =
144 DownloadServiceFactory::GetForProfile(*pit);
145 if (download_service->HasCreatedDownloadManager()) {
146 DownloadManager *mgr = download_service->GetDownloadManager();
147 scoped_refptr<DownloadTestFlushObserver> observer(
148 new DownloadTestFlushObserver(mgr));
149 observer->WaitForFlush();
150 }
151 if ((*pit)->HasOffTheRecordProfile()) {
152 DownloadService* incognito_download_service =
153 DownloadServiceFactory::GetForProfile(
154 (*pit)->GetOffTheRecordProfile());
155 if (incognito_download_service->HasCreatedDownloadManager()) {
156 DownloadManager *mgr =
157 incognito_download_service->GetDownloadManager();
158 scoped_refptr<DownloadTestFlushObserver> observer(
159 new DownloadTestFlushObserver(mgr));
160 observer->WaitForFlush();
161 }
162 }
163 }
164 }
165
166 // Create a Browser (with associated window) on the specified profile.
167 Browser* CreateBrowserOnProfile(Profile* profile) {
168 Browser* new_browser = Browser::Create(profile);
169 new_browser->AddSelectedTabWithURL(GURL(chrome::kAboutBlankURL),
170 content::PAGE_TRANSITION_START_PAGE);
171 ui_test_utils::WaitForLoadStop(
172 new_browser->GetSelectedTabContents());
173 new_browser->window()->Show();
174 return new_browser;
175 }
176
177 // Adjust the number of browsers and associated windows up or down
178 // to |num_windows|. This routine assumes that there is only a single
179 // browser associated with the profile on entry. |*base_browser| contains
180 // this browser, and the profile is derived from that browser. On output,
181 // if |*base_browser| was destroyed (because |num_windows == 0|), NULL
182 // is assigned to that memory location.
183 bool AdjustBrowsersOnProfile(Browser** base_browser, int num_windows) {
184 int num_downloads_blocking;
185 if (num_windows == 0) {
186 if (Browser::DOWNLOAD_CLOSE_OK !=
187 (*base_browser)->OkToCloseWithInProgressDownloads(
188 &num_downloads_blocking))
189 return false;
190 (*base_browser)->window()->Close();
191 *base_browser = 0;
192 return true;
193 }
194
195 // num_windows > 0
196 Profile* profile((*base_browser)->profile());
197 for (int w = 1; w < num_windows; ++w) {
198 CreateBrowserOnProfile(profile);
199 }
200 return true;
201 }
202
203 int TotalUnclosedBrowsers() {
204 int count = 0;
205 for (BrowserList::const_iterator iter = BrowserList::begin();
206 iter != BrowserList::end(); ++iter)
207 if (!(*iter)->IsAttemptingToCloseBrowser()) {
208 count++;
209 }
210 return count;
211 }
212
213 // Note that this is invalid to call if TotalUnclosedBrowsers() == 0.
214 Browser* FirstUnclosedBrowser() {
215 for (BrowserList::const_iterator iter = BrowserList::begin();
216 iter != BrowserList::end(); ++iter)
217 if (!(*iter)->IsAttemptingToCloseBrowser())
218 return (*iter);
219 return NULL;
220 }
221
222 bool SetupForDownloadCloseCheck() {
223 first_profile_ = browser()->profile();
224
225 bool result = first_profile_downloads_dir_.CreateUniqueTempDir();
226 EXPECT_TRUE(result);
227 if (!result) return false;
228 first_profile_->GetPrefs()->SetFilePath(
229 prefs::kDownloadDefaultDirectory,
230 first_profile_downloads_dir_.path());
231
232 second_profile_ = CreateSecondProfile();
233 EXPECT_TRUE(second_profile_);
234 if (!second_profile_) return false;
235
236 return true;
237 }
238
239 // Test a specific DownloadsCloseCheckCase. Returns false if
240 // an assertion has failed and the test should be aborted.
241 bool ExecuteDownloadCloseCheckCase(size_t i) {
242 const DownloadsCloseCheckCase& check_case(download_close_check_cases[i]);
243
244 // Test invariant: so that we don't actually try and close the browser,
245 // we always enter the function with a single browser window open on the
246 // main profile. That means we need to exit the function the same way.
247 // So we setup everything except for the |first_profile_| regular, and then
248 // flip the bit on the main window.
249 // Note that this means that browser() is unreliable in the context
250 // of this function or its callers; we'll be killing that main window
251 // and recreating it fairly frequently.
252 int unclosed_browsers = TotalUnclosedBrowsers();
253 EXPECT_EQ(1, unclosed_browsers);
254 if (1 != unclosed_browsers)
255 return false;
256
257 Browser* entry_browser = FirstUnclosedBrowser();
258 EXPECT_EQ(first_profile_, entry_browser->profile())
259 << "Case" << i
260 << ": " << check_case.DebugString();
261 if (first_profile_ != entry_browser->profile())
262 return false;
263 int total_download_count = DownloadService::DownloadCountAllProfiles();
264 EXPECT_EQ(0, total_download_count)
265 << "Case " << i
266 << ": " << check_case.DebugString();
267 if (0 != total_download_count)
268 return false;
269
270 Profile* first_profile_incognito = first_profile_->GetOffTheRecordProfile();
271 Profile* second_profile_incognito =
272 second_profile_->GetOffTheRecordProfile();
273
274 // For simplicty of coding, we create a window on each profile so that
275 // we can easily create downloads, then we destroy or create windows
276 // as necessary.
277 Browser* browser_a_regular(CreateBrowserOnProfile(first_profile_));
278 Browser* browser_a_incognito(
279 CreateBrowserOnProfile(first_profile_incognito));
280 Browser* browser_b_regular(CreateBrowserOnProfile(second_profile_));
281 Browser* browser_b_incognito(
282 CreateBrowserOnProfile(second_profile_incognito));
283
284 // Kill our entry browser.
285 entry_browser->window()->Close();
286 entry_browser = NULL;
287
288 // Create all downloads needed.
289 CreateStalledDownloads(
290 browser_a_regular, check_case.profile_a.regular.downloads);
291 CreateStalledDownloads(
292 browser_a_incognito, check_case.profile_a.incognito.downloads);
293 CreateStalledDownloads(
294 browser_b_regular, check_case.profile_b.regular.downloads);
295 CreateStalledDownloads(
296 browser_b_incognito, check_case.profile_b.incognito.downloads);
297
298 // Adjust the windows
299 Browser** browsers[] = {
300 &browser_a_regular, &browser_a_incognito,
301 &browser_b_regular, &browser_b_incognito
302 };
303 int window_counts[] = {
304 check_case.profile_a.regular.windows,
305 check_case.profile_a.incognito.windows,
306 check_case.profile_b.regular.windows,
307 check_case.profile_b.incognito.windows,
308 };
309 for (size_t j = 0; j < arraysize(browsers); ++j) {
310 bool result = AdjustBrowsersOnProfile(browsers[j], window_counts[j]);
311 EXPECT_TRUE(result);
312 if (!result)
313 return false;
314 }
315 ui_test_utils::RunAllPendingInMessageLoop();
316
317 #if defined(OS_CHROMEOS)
318 // Get rid of downloads panel on ChromeOS
319 Browser* panel = NULL;
320 #if defined(TOUCH_UI)
321 ActiveDownloadsUI::GetPopup(&panel);
322 #else
323 panel = ActiveDownloadsUI::GetPopup();
324 #endif
325 if (panel)
326 panel->CloseWindow();
327 ui_test_utils::RunAllPendingInMessageLoop();
328 #endif
329
330 // All that work, for this one little test.
331 EXPECT_TRUE((check_case.window_to_probe ==
332 DownloadsCloseCheckCase::REGULAR) ||
333 (check_case.window_to_probe ==
334 DownloadsCloseCheckCase::INCOGNITO));
335 if (!((check_case.window_to_probe ==
336 DownloadsCloseCheckCase::REGULAR) ||
337 (check_case.window_to_probe ==
338 DownloadsCloseCheckCase::INCOGNITO)))
339 return false;
340
341 int num_downloads_blocking;
342 Browser* browser_to_probe =
343 (check_case.window_to_probe == DownloadsCloseCheckCase::REGULAR ?
344 browser_a_regular :
345 browser_a_incognito);
346 Browser::DownloadClosePreventionType type =
347 browser_to_probe->OkToCloseWithInProgressDownloads(
348 &num_downloads_blocking);
349 EXPECT_EQ(check_case.type, type) << "Case " << i
350 << ": " << check_case.DebugString();
351 if (type != Browser::DOWNLOAD_CLOSE_OK)
352 EXPECT_EQ(check_case.num_blocking, num_downloads_blocking)
353 << "Case " << i
354 << ": " << check_case.DebugString();
355
356 // Release all the downloads.
357 CompleteAllDownloads(browser_to_probe);
358
359 // Create a new main window and kill everything else.
360 entry_browser = CreateBrowserOnProfile(first_profile_);
361 for (BrowserList::const_iterator bit = BrowserList::begin();
362 bit != BrowserList::end(); ++bit) {
363 if ((*bit) != entry_browser) {
364 EXPECT_TRUE((*bit)->window());
365 if (!(*bit)->window())
366 return false;
367 (*bit)->window()->Close();
368 }
369 }
370 ui_test_utils::RunAllPendingInMessageLoop();
371
372 return true;
373 }
374
375 static const DownloadsCloseCheckCase download_close_check_cases[];
376
377 // DownloadCloseCheck variables.
378 Profile* first_profile_;
379 Profile* second_profile_;
380
381 ScopedTempDir first_profile_downloads_dir_;
382 ScopedTempDir second_profile_data_dir_;
383 ScopedTempDir second_profile_downloads_dir_;
384 };
385
386 const BrowserCloseTest::DownloadsCloseCheckCase
387 BrowserCloseTest::download_close_check_cases[] = {
388 // Top level nesting is {profile_a, profile_b}
389 // Second level nesting is {regular, incognito
390 // Third level (inner) nesting is {windows, downloads}
391
392 // Last window (incognito) triggers browser close warning.
393 {{{0, 0}, {1, 1}}, {{0, 0}, {0, 0}},
394 BrowserCloseTest::DownloadsCloseCheckCase::INCOGNITO,
395 Browser::DOWNLOAD_CLOSE_BROWSER_SHUTDOWN, 1},
396
397 // Last incognito window triggers incognito close warning.
398 {{{1, 0}, {1, 1}}, {{0, 0}, {0, 0}},
399 BrowserCloseTest::DownloadsCloseCheckCase::INCOGNITO,
400 Browser::DOWNLOAD_CLOSE_LAST_WINDOW_IN_INCOGNITO_PROFILE, 1},
401
402 // Last incognito window with no downloads triggers no warning.
403 {{{0, 0}, {1, 0}}, {{0, 0}, {0, 0}},
404 BrowserCloseTest::DownloadsCloseCheckCase::INCOGNITO,
405 Browser::DOWNLOAD_CLOSE_OK},
406
407 // Last incognito window with window+download on another incognito profile
408 // triggers no warning.
409 {{{0, 0}, {1, 0}}, {{0, 0}, {1, 1}},
410 BrowserCloseTest::DownloadsCloseCheckCase::INCOGNITO,
411 Browser::DOWNLOAD_CLOSE_OK},
412
413 // Non-last incognito window triggers no warning.
414 {{{0, 0}, {2, 1}}, {{0, 0}, {0, 0}},
415 BrowserCloseTest::DownloadsCloseCheckCase::INCOGNITO,
416 Browser::DOWNLOAD_CLOSE_OK},
417
418 // Non-last regular window triggers no warning.
419 {{{2, 1}, {0, 0}}, {{0, 0}, {0, 0}},
420 BrowserCloseTest::DownloadsCloseCheckCase::REGULAR,
421 Browser::DOWNLOAD_CLOSE_OK},
422
423 // Last regular window triggers browser close.
424 {{{1, 1}, {0, 0}}, {{0, 0}, {0, 0}},
425 BrowserCloseTest::DownloadsCloseCheckCase::REGULAR,
426 Browser::DOWNLOAD_CLOSE_BROWSER_SHUTDOWN, 1},
427
428 // Last regular window triggers browser close for download on different
429 // profile.
430 {{{1, 0}, {0, 0}}, {{0, 1}, {0, 0}},
431 BrowserCloseTest::DownloadsCloseCheckCase::REGULAR,
432 Browser::DOWNLOAD_CLOSE_BROWSER_SHUTDOWN, 1},
433
434 // Last regular window triggers no warning if incognito
435 // active (http://crbug.com/61257).
436 {{{1, 0}, {1, 1}}, {{0, 0}, {0, 0}},
437 BrowserCloseTest::DownloadsCloseCheckCase::REGULAR,
438 Browser::DOWNLOAD_CLOSE_OK},
439
440 // Last regular window triggers no warning if other profile window active.
441 {{{1, 1}, {0, 0}}, {{1, 0}, {0, 0}},
442 BrowserCloseTest::DownloadsCloseCheckCase::REGULAR,
443 Browser::DOWNLOAD_CLOSE_OK},
444
445 // Last regular window triggers no warning if other incognito window
446 // active.
447 {{{1, 0}, {0, 0}}, {{0, 0}, {1, 1}},
448 BrowserCloseTest::DownloadsCloseCheckCase::REGULAR,
449 Browser::DOWNLOAD_CLOSE_OK},
450
451 // Last regular window triggers no warning if incognito active.
452 {{{1, 1}, {1, 0}}, {{0, 0}, {0, 0}},
453 BrowserCloseTest::DownloadsCloseCheckCase::REGULAR,
454 Browser::DOWNLOAD_CLOSE_OK},
455
456 // Test plural for regular.
457 {{{1, 2}, {0, 0}}, {{0, 0}, {0, 0}},
458 BrowserCloseTest::DownloadsCloseCheckCase::REGULAR,
459 Browser::DOWNLOAD_CLOSE_BROWSER_SHUTDOWN, 2},
460
461 // Test plural for incognito.
462 {{{1, 0}, {1, 2}}, {{0, 0}, {0, 0}},
463 BrowserCloseTest::DownloadsCloseCheckCase::INCOGNITO,
464 Browser::DOWNLOAD_CLOSE_LAST_WINDOW_IN_INCOGNITO_PROFILE, 2},
465 };
466
467 std::string BrowserCloseTest::DownloadsCloseCheckCase::DebugString() const {
468 std::string result;
469 result += "{";
470 if (profile_a.regular.windows || profile_a.regular.downloads)
471 result += base::StringPrintf("Regular profile A: (%d w, %d d), ",
472 profile_a.regular.windows,
473 profile_a.regular.downloads);
474 if (profile_a.incognito.windows || profile_a.incognito.downloads)
475 result += base::StringPrintf("Incognito profile A: (%d w, %d d), ",
476 profile_a.incognito.windows,
477 profile_a.incognito.downloads);
478 if (profile_b.regular.windows || profile_b.regular.downloads)
479 result += base::StringPrintf("Regular profile B: (%d w, %d d), ",
480 profile_b.regular.windows,
481 profile_b.regular.downloads);
482 if (profile_b.incognito.windows || profile_b.incognito.downloads)
483 result += base::StringPrintf("Incognito profile B: (%d w, %d d), ",
484 profile_b.incognito.windows,
485 profile_b.incognito.downloads);
486 result += (window_to_probe == REGULAR ? "Probe regular" :
487 window_to_probe == INCOGNITO ? "Probe incognito" :
488 "Probe unknown");
489 result += "} -> ";
490 if (type == Browser::DOWNLOAD_CLOSE_OK) {
491 result += "No warning";
492 } else {
493 result += base::StringPrintf(
494 "%s (%d downloads) warning",
495 (type == Browser::DOWNLOAD_CLOSE_BROWSER_SHUTDOWN ? "Browser shutdown" :
496 type == Browser::DOWNLOAD_CLOSE_LAST_WINDOW_IN_INCOGNITO_PROFILE ?
497 "Incognito close" : "Unknown"),
498 num_blocking);
499 }
500 return result;
501 }
502
503 // The following test is split into three chunks to reduce the chance
504 // of hitting the 25s timeout.
505
506 IN_PROC_BROWSER_TEST_F(BrowserCloseTest, DownloadsCloseCheck_0) {
507 ASSERT_TRUE(SetupForDownloadCloseCheck());
508 for (size_t i = 0; i < arraysize(download_close_check_cases) / 6; ++i) {
509 ExecuteDownloadCloseCheckCase(i);
510 }
511 }
512
513 IN_PROC_BROWSER_TEST_F(BrowserCloseTest, DownloadsCloseCheck_1) {
514 ASSERT_TRUE(SetupForDownloadCloseCheck());
515 for (size_t i = arraysize(download_close_check_cases) / 6;
516 i < 2 * arraysize(download_close_check_cases) / 6; ++i) {
517 ExecuteDownloadCloseCheckCase(i);
518 }
519 }
520
521 IN_PROC_BROWSER_TEST_F(BrowserCloseTest, DownloadsCloseCheck_2) {
522 ASSERT_TRUE(SetupForDownloadCloseCheck());
523 for (size_t i = 2 * arraysize(download_close_check_cases) / 6;
524 i < 3 * arraysize(download_close_check_cases) / 6; ++i) {
525 ExecuteDownloadCloseCheckCase(i);
526 }
527 }
528
529 IN_PROC_BROWSER_TEST_F(BrowserCloseTest, DownloadsCloseCheck_3) {
530 ASSERT_TRUE(SetupForDownloadCloseCheck());
531 for (size_t i = 3 * arraysize(download_close_check_cases) / 6;
532 i < 4 * arraysize(download_close_check_cases) / 6; ++i) {
533 ExecuteDownloadCloseCheckCase(i);
534 }
535 }
536
537 IN_PROC_BROWSER_TEST_F(BrowserCloseTest, DownloadsCloseCheck_4) {
538 ASSERT_TRUE(SetupForDownloadCloseCheck());
539 for (size_t i = 4 * arraysize(download_close_check_cases) / 6;
540 i < 5 * arraysize(download_close_check_cases) / 6; ++i) {
541 ExecuteDownloadCloseCheckCase(i);
542 }
543 }
544
545 IN_PROC_BROWSER_TEST_F(BrowserCloseTest, DownloadsCloseCheck_5) {
546 ASSERT_TRUE(SetupForDownloadCloseCheck());
547 for (size_t i = 5 * arraysize(download_close_check_cases) / 6;
548 i < 6 * arraysize(download_close_check_cases) / 6; ++i) {
549 ExecuteDownloadCloseCheckCase(i);
550 }
551 }
OLDNEW
« no previous file with comments | « chrome/browser/ui/browser.cc ('k') | chrome/browser/ui/browser_list.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698