普通文本  |  570行  |  21.36 KB

// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "base/command_line.h"
#include "base/logging.h"
#include "base/path_service.h"
#include "base/prefs/pref_service.h"
#include "base/strings/stringprintf.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/download/download_service.h"
#include "chrome/browser/download/download_service_factory.h"
#include "chrome/browser/download/download_test_file_activity_observer.h"
#include "chrome/browser/net/url_request_mock_util.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_iterator.h"
#include "chrome/browser/ui/browser_tabstrip.h"
#include "chrome/browser/ui/browser_window.h"
#include "chrome/browser/ui/host_desktop.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/common/pref_names.h"
#include "chrome/common/url_constants.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/base/ui_test_utils.h"
#include "content/public/browser/download_item.h"
#include "content/public/common/page_transition_types.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/download_test_observer.h"
#include "content/test/net/url_request_slow_download_job.h"

using content::BrowserContext;
using content::BrowserThread;
using content::DownloadItem;
using content::DownloadManager;
using content::URLRequestSlowDownloadJob;

class BrowserCloseTest : public InProcessBrowserTest {
 public:
  // Structure defining test cases for DownloadsCloseCheck.
  struct DownloadsCloseCheckCase {
    std::string DebugString() const;

    // Input
    struct {
      struct {
        int windows;
        int downloads;
      } regular;
      struct {
        int windows;
        int downloads;
      } incognito;
    } profile_a;

    struct {
      struct {
        int windows;
        int downloads;
      } regular;
      struct {
        int windows;
        int downloads;
      } incognito;
    } profile_b;

    // We always probe a window in profile A.
    enum { REGULAR = 0, INCOGNITO = 1 } window_to_probe;

    // Output
    Browser::DownloadClosePreventionType type;

    // Unchecked if type == DOWNLOAD_CLOSE_OK.
    int num_blocking;
  };

 protected:
  virtual void SetUpOnMainThread() OVERRIDE {
    BrowserThread::PostTask(
        BrowserThread::IO, FROM_HERE,
        base::Bind(&chrome_browser_net::SetUrlRequestMocksEnabled, true));
  }

  // Create a second profile to work within multi-profile.
  Profile* CreateSecondProfile() {
    base::FilePath user_data_dir;
    PathService::Get(chrome::DIR_USER_DATA, &user_data_dir);

    if (!second_profile_data_dir_.CreateUniqueTempDirUnderPath(user_data_dir))
      return NULL;

    Profile* second_profile =
        g_browser_process->profile_manager()->GetProfile(
            second_profile_data_dir_.path());
    if (!second_profile)
      return NULL;

    bool result = second_profile_downloads_dir_.CreateUniqueTempDir();
    if (!result)
      return NULL;
    second_profile->GetPrefs()->SetFilePath(
        prefs::kDownloadDefaultDirectory,
        second_profile_downloads_dir_.path());

    return second_profile;
  }

  // Create |num_downloads| number of downloads that are stalled
  // (will quickly get to a place where the server won't
  // provide any more data) so that we can test closing the
  // browser with active downloads.
  void CreateStalledDownloads(Browser* browser, int num_downloads) {
    GURL url(URLRequestSlowDownloadJob::kKnownSizeUrl);

    if (num_downloads == 0)
      return;

    // Setup an observer waiting for the given number of downloads
    // to get to IN_PROGRESS.
    DownloadManager* download_manager =
        BrowserContext::GetDownloadManager(browser->profile());
    scoped_ptr<content::DownloadTestObserver> observer(
        new content::DownloadTestObserverInProgress(download_manager,
                                                    num_downloads));

    // Set of that number of downloads.
    size_t count_downloads = num_downloads;
    while (num_downloads--)
      ui_test_utils::NavigateToURLWithDisposition(
          browser, url, NEW_BACKGROUND_TAB,
          ui_test_utils::BROWSER_TEST_NONE);

    // Wait for them.
    observer->WaitForFinished();
    EXPECT_EQ(count_downloads,
              observer->NumDownloadsSeenInState(DownloadItem::IN_PROGRESS));
  }

  // All all downloads created in CreateStalledDownloads() to
  // complete, and block in this routine until they do complete.
  void CompleteAllDownloads(Browser* browser) {
    GURL finish_url(URLRequestSlowDownloadJob::kFinishDownloadUrl);
    ui_test_utils::NavigateToURL(browser, finish_url);

    // Go through and, for every single profile, wait until there are
    // no active downloads on that download manager.
    std::vector<Profile*> profiles(
        g_browser_process->profile_manager()->GetLoadedProfiles());
    for (std::vector<Profile*>::const_iterator pit = profiles.begin();
         pit != profiles.end(); ++pit) {
      DownloadService* download_service =
          DownloadServiceFactory::GetForBrowserContext(*pit);
      if (download_service->HasCreatedDownloadManager()) {
        DownloadManager *mgr = BrowserContext::GetDownloadManager(*pit);
        scoped_refptr<content::DownloadTestFlushObserver> observer(
            new content::DownloadTestFlushObserver(mgr));
        observer->WaitForFlush();
      }
      if ((*pit)->HasOffTheRecordProfile()) {
        DownloadService* incognito_download_service =
          DownloadServiceFactory::GetForBrowserContext(
              (*pit)->GetOffTheRecordProfile());
        if (incognito_download_service->HasCreatedDownloadManager()) {
          DownloadManager *mgr = BrowserContext::GetDownloadManager(
              (*pit)->GetOffTheRecordProfile());
          scoped_refptr<content::DownloadTestFlushObserver> observer(
              new content::DownloadTestFlushObserver(mgr));
          observer->WaitForFlush();
        }
      }
    }
  }

  // Create a Browser (with associated window) on the specified profile.
  Browser* CreateBrowserOnProfile(Profile* profile,
                                  chrome::HostDesktopType host_desktop_type) {
    Browser* new_browser =
        new Browser(Browser::CreateParams(profile, host_desktop_type));
    chrome::AddSelectedTabWithURL(new_browser, GURL(content::kAboutBlankURL),
                                  content::PAGE_TRANSITION_AUTO_TOPLEVEL);
    content::WaitForLoadStop(
        new_browser->tab_strip_model()->GetActiveWebContents());
    new_browser->window()->Show();
    return new_browser;
  }

  // Adjust the number of browsers and associated windows up or down
  // to |num_windows|.  This routine assumes that there is only a single
  // browser associated with the profile on entry.  |*base_browser| contains
  // this browser, and the profile is derived from that browser.  On output,
  // if |*base_browser| was destroyed (because |num_windows == 0|), NULL
  // is assigned to that memory location.
  bool AdjustBrowsersOnProfile(Browser** base_browser, int num_windows) {
    int num_downloads_blocking;
    if (num_windows == 0) {
      if (Browser::DOWNLOAD_CLOSE_OK !=
          (*base_browser)->OkToCloseWithInProgressDownloads(
              &num_downloads_blocking))
        return false;
      (*base_browser)->window()->Close();
      *base_browser = 0;
      return true;
    }

    // num_windows > 0
    Profile* profile((*base_browser)->profile());
    chrome::HostDesktopType host_desktop_type =
        (*base_browser)->host_desktop_type();
    for (int w = 1; w < num_windows; ++w) {
      CreateBrowserOnProfile(profile, host_desktop_type);
    }
    return true;
  }

  int TotalUnclosedBrowsers() {
    int count = 0;
    for (chrome::BrowserIterator it; !it.done(); it.Next()) {
      if (!it->IsAttemptingToCloseBrowser())
        count++;
    }
    return count;
  }

  // Note that this is invalid to call if TotalUnclosedBrowsers() == 0.
  Browser* FirstUnclosedBrowser() {
    for (chrome::BrowserIterator it; !it.done(); it.Next()) {
      if (!it->IsAttemptingToCloseBrowser())
        return *it;
    }
    return NULL;
  }

  bool SetupForDownloadCloseCheck() {
    first_profile_ = browser()->profile();

    bool result = first_profile_downloads_dir_.CreateUniqueTempDir();
    EXPECT_TRUE(result);
    if (!result) return false;
    first_profile_->GetPrefs()->SetFilePath(
        prefs::kDownloadDefaultDirectory,
        first_profile_downloads_dir_.path());

    second_profile_ = CreateSecondProfile();
    EXPECT_TRUE(second_profile_);
    if (!second_profile_) return false;

    DownloadTestFileActivityObserver(first_profile_) .EnableFileChooser(false);
    DownloadTestFileActivityObserver(second_profile_).EnableFileChooser(false);
    return true;
  }

  // Test a specific DownloadsCloseCheckCase.  Returns false if
  // an assertion has failed and the test should be aborted.
  bool ExecuteDownloadCloseCheckCase(size_t i) {
    const DownloadsCloseCheckCase& check_case(download_close_check_cases[i]);
    SCOPED_TRACE(testing::Message() << "Case" << i
                                    << ": " << check_case.DebugString());

    // Test invariant: so that we don't actually try and close the browser,
    // we always enter the function with a single browser window open on the
    // main profile.  That means we need to exit the function the same way.
    // So we setup everything except for the |first_profile_| regular, and then
    // flip the bit on the main window.
    // Note that this means that browser() is unreliable in the context
    // of this function or its callers; we'll be killing that main window
    // and recreating it fairly frequently.
    int unclosed_browsers = TotalUnclosedBrowsers();
    EXPECT_EQ(1, unclosed_browsers);
    if (1 != unclosed_browsers)
      return false;

    Browser* entry_browser = FirstUnclosedBrowser();
    EXPECT_EQ(first_profile_, entry_browser->profile());
    if (first_profile_ != entry_browser->profile())
      return false;
    int total_download_count =
        DownloadService::NonMaliciousDownloadCountAllProfiles();
    EXPECT_EQ(0, total_download_count);
    if (0 != total_download_count)
      return false;
    Profile* first_profile_incognito = first_profile_->GetOffTheRecordProfile();
    Profile* second_profile_incognito =
        second_profile_->GetOffTheRecordProfile();
    DownloadTestFileActivityObserver(first_profile_incognito)
        .EnableFileChooser(false);
    DownloadTestFileActivityObserver(second_profile_incognito)
        .EnableFileChooser(false);

    // For simplicty of coding, we create a window on each profile so that
    // we can easily create downloads, then we destroy or create windows
    // as necessary.
    chrome::HostDesktopType host_desktop_type =
        entry_browser->host_desktop_type();
    Browser* browser_a_regular(CreateBrowserOnProfile(first_profile_,
                                                      host_desktop_type));
    Browser* browser_a_incognito(
        CreateBrowserOnProfile(first_profile_incognito, host_desktop_type));
    Browser* browser_b_regular(CreateBrowserOnProfile(second_profile_,
                                                      host_desktop_type));
    Browser* browser_b_incognito(
        CreateBrowserOnProfile(second_profile_incognito, host_desktop_type));

    // Kill our entry browser.
    entry_browser->window()->Close();
    entry_browser = NULL;

    // Create all downloads needed.
    CreateStalledDownloads(
        browser_a_regular, check_case.profile_a.regular.downloads);
    CreateStalledDownloads(
        browser_a_incognito, check_case.profile_a.incognito.downloads);
    CreateStalledDownloads(
        browser_b_regular, check_case.profile_b.regular.downloads);
    CreateStalledDownloads(
        browser_b_incognito, check_case.profile_b.incognito.downloads);

    // Adjust the windows
    Browser** browsers[] = {
      &browser_a_regular, &browser_a_incognito,
      &browser_b_regular, &browser_b_incognito
    };
    int window_counts[] = {
      check_case.profile_a.regular.windows,
      check_case.profile_a.incognito.windows,
      check_case.profile_b.regular.windows,
      check_case.profile_b.incognito.windows,
    };
    for (size_t j = 0; j < arraysize(browsers); ++j) {
      bool result = AdjustBrowsersOnProfile(browsers[j], window_counts[j]);
      EXPECT_TRUE(result);
      if (!result)
        return false;
    }
    content::RunAllPendingInMessageLoop();

    // All that work, for this one little test.
    EXPECT_TRUE((check_case.window_to_probe ==
                 DownloadsCloseCheckCase::REGULAR) ||
                (check_case.window_to_probe ==
                 DownloadsCloseCheckCase::INCOGNITO));
    if (!((check_case.window_to_probe ==
           DownloadsCloseCheckCase::REGULAR) ||
          (check_case.window_to_probe ==
           DownloadsCloseCheckCase::INCOGNITO)))
      return false;

    int num_downloads_blocking;
    Browser* browser_to_probe =
        (check_case.window_to_probe == DownloadsCloseCheckCase::REGULAR ?
         browser_a_regular :
         browser_a_incognito);
    Browser::DownloadClosePreventionType type =
        browser_to_probe->OkToCloseWithInProgressDownloads(
            &num_downloads_blocking);
    EXPECT_EQ(check_case.type, type);
    if (type != Browser::DOWNLOAD_CLOSE_OK)
      EXPECT_EQ(check_case.num_blocking, num_downloads_blocking);

    // Release all the downloads.
    CompleteAllDownloads(browser_to_probe);

    // Create a new main window and kill everything else.
    entry_browser = CreateBrowserOnProfile(first_profile_, host_desktop_type);
    for (chrome::BrowserIterator it; !it.done(); it.Next()) {
      if ((*it) != entry_browser) {
        if (!it->window()) {
          ADD_FAILURE();
          return false;
        }
        it->window()->Close();
      }
    }
    content::RunAllPendingInMessageLoop();

    return true;
  }

  static const DownloadsCloseCheckCase download_close_check_cases[];

  // DownloadCloseCheck variables.
  Profile* first_profile_;
  Profile* second_profile_;

  base::ScopedTempDir first_profile_downloads_dir_;
  base::ScopedTempDir second_profile_data_dir_;
  base::ScopedTempDir second_profile_downloads_dir_;
};

const BrowserCloseTest::DownloadsCloseCheckCase
BrowserCloseTest::download_close_check_cases[] = {
  // Top level nesting is {profile_a, profile_b}
  // Second level nesting is {regular, incognito
  // Third level (inner) nesting is {windows, downloads}

  // Last window (incognito) triggers browser close warning.
  {{{0, 0}, {1, 1}}, {{0, 0}, {0, 0}},
   BrowserCloseTest::DownloadsCloseCheckCase::INCOGNITO,
   Browser::DOWNLOAD_CLOSE_BROWSER_SHUTDOWN, 1},

  // Last incognito window triggers incognito close warning.
  {{{1, 0}, {1, 1}}, {{0, 0}, {0, 0}},
   BrowserCloseTest::DownloadsCloseCheckCase::INCOGNITO,
   Browser::DOWNLOAD_CLOSE_LAST_WINDOW_IN_INCOGNITO_PROFILE, 1},

  // Last incognito window with no downloads triggers no warning.
  {{{0, 0}, {1, 0}}, {{0, 0}, {0, 0}},
   BrowserCloseTest::DownloadsCloseCheckCase::INCOGNITO,
   Browser::DOWNLOAD_CLOSE_OK},

  // Last incognito window with window+download on another incognito profile
  // triggers no warning.
  {{{0, 0}, {1, 0}}, {{0, 0}, {1, 1}},
   BrowserCloseTest::DownloadsCloseCheckCase::INCOGNITO,
   Browser::DOWNLOAD_CLOSE_OK},

  // Non-last incognito window triggers no warning.
  {{{0, 0}, {2, 1}}, {{0, 0}, {0, 0}},
   BrowserCloseTest::DownloadsCloseCheckCase::INCOGNITO,
   Browser::DOWNLOAD_CLOSE_OK},

  // Non-last regular window triggers no warning.
  {{{2, 1}, {0, 0}}, {{0, 0}, {0, 0}},
   BrowserCloseTest::DownloadsCloseCheckCase::REGULAR,
   Browser::DOWNLOAD_CLOSE_OK},

  // Last regular window triggers browser close.
  {{{1, 1}, {0, 0}}, {{0, 0}, {0, 0}},
   BrowserCloseTest::DownloadsCloseCheckCase::REGULAR,
   Browser::DOWNLOAD_CLOSE_BROWSER_SHUTDOWN, 1},

  // Last regular window triggers browser close for download on different
  // profile.
  {{{1, 0}, {0, 0}}, {{0, 1}, {0, 0}},
   BrowserCloseTest::DownloadsCloseCheckCase::REGULAR,
   Browser::DOWNLOAD_CLOSE_BROWSER_SHUTDOWN, 1},

  // Last regular window triggers no warning if incognito
  // active (http://crbug.com/61257).
  {{{1, 0}, {1, 1}}, {{0, 0}, {0, 0}},
   BrowserCloseTest::DownloadsCloseCheckCase::REGULAR,
   Browser::DOWNLOAD_CLOSE_OK},

  // Last regular window triggers no warning if other profile window active.
  {{{1, 1}, {0, 0}}, {{1, 0}, {0, 0}},
   BrowserCloseTest::DownloadsCloseCheckCase::REGULAR,
   Browser::DOWNLOAD_CLOSE_OK},

  // Last regular window triggers no warning if other incognito window
  // active.
  {{{1, 0}, {0, 0}}, {{0, 0}, {1, 1}},
   BrowserCloseTest::DownloadsCloseCheckCase::REGULAR,
   Browser::DOWNLOAD_CLOSE_OK},

  // Last regular window triggers no warning if incognito active.
  {{{1, 1}, {1, 0}}, {{0, 0}, {0, 0}},
   BrowserCloseTest::DownloadsCloseCheckCase::REGULAR,
   Browser::DOWNLOAD_CLOSE_OK},

  // Test plural for regular.
  {{{1, 2}, {0, 0}}, {{0, 0}, {0, 0}},
   BrowserCloseTest::DownloadsCloseCheckCase::REGULAR,
   Browser::DOWNLOAD_CLOSE_BROWSER_SHUTDOWN, 2},

  // Test plural for incognito.
  {{{1, 0}, {1, 2}}, {{0, 0}, {0, 0}},
   BrowserCloseTest::DownloadsCloseCheckCase::INCOGNITO,
   Browser::DOWNLOAD_CLOSE_LAST_WINDOW_IN_INCOGNITO_PROFILE, 2},
};

std::string BrowserCloseTest::DownloadsCloseCheckCase::DebugString() const {
  std::string result;
  result += "{";
  if (profile_a.regular.windows || profile_a.regular.downloads)
    result += base::StringPrintf("Regular profile A: (%d w, %d d), ",
                                 profile_a.regular.windows,
                                 profile_a.regular.downloads);
  if (profile_a.incognito.windows || profile_a.incognito.downloads)
    result += base::StringPrintf("Incognito profile A: (%d w, %d d), ",
                                 profile_a.incognito.windows,
                                 profile_a.incognito.downloads);
  if (profile_b.regular.windows || profile_b.regular.downloads)
    result += base::StringPrintf("Regular profile B: (%d w, %d d), ",
                                 profile_b.regular.windows,
                                 profile_b.regular.downloads);
  if (profile_b.incognito.windows || profile_b.incognito.downloads)
    result += base::StringPrintf("Incognito profile B: (%d w, %d d), ",
                                 profile_b.incognito.windows,
                                 profile_b.incognito.downloads);
  result += (window_to_probe == REGULAR ? "Probe regular" :
             window_to_probe == INCOGNITO ? "Probe incognito" :
             "Probe unknown");
  result += "} -> ";
  if (type == Browser::DOWNLOAD_CLOSE_OK) {
    result += "No warning";
  } else {
    result += base::StringPrintf(
        "%s (%d downloads) warning",
        (type == Browser::DOWNLOAD_CLOSE_BROWSER_SHUTDOWN ? "Browser shutdown" :
         type == Browser::DOWNLOAD_CLOSE_LAST_WINDOW_IN_INCOGNITO_PROFILE ?
         "Incognito close" : "Unknown"),
        num_blocking);
  }
  return result;
}

// The following test is split into six chunks to reduce the chance
// of hitting the 25s timeout.

// This test is timing out very often under AddressSanitizer.
// http://crbug.com/111914 and http://crbug.com/103371.
// Crashing on Linux. http://crbug.com/100566
// Timing out on XP debug. http://crbug.com/111914
// Timing out, http://crbug.com/159449 .

#define MAYBE_DownloadsCloseCheck_0 DISABLED_DownloadsCloseCheck_0
#define MAYBE_DownloadsCloseCheck_1 DISABLED_DownloadsCloseCheck_1
#define MAYBE_DownloadsCloseCheck_2 DISABLED_DownloadsCloseCheck_2
#define MAYBE_DownloadsCloseCheck_3 DISABLED_DownloadsCloseCheck_3
#define MAYBE_DownloadsCloseCheck_4 DISABLED_DownloadsCloseCheck_4
#define MAYBE_DownloadsCloseCheck_5 DISABLED_DownloadsCloseCheck_5

IN_PROC_BROWSER_TEST_F(BrowserCloseTest, MAYBE_DownloadsCloseCheck_0) {
  ASSERT_TRUE(SetupForDownloadCloseCheck());
  for (size_t i = 0; i < arraysize(download_close_check_cases) / 6; ++i) {
    ExecuteDownloadCloseCheckCase(i);
  }
}

IN_PROC_BROWSER_TEST_F(BrowserCloseTest, MAYBE_DownloadsCloseCheck_1) {
  ASSERT_TRUE(SetupForDownloadCloseCheck());
  for (size_t i = arraysize(download_close_check_cases) / 6;
       i < 2 * arraysize(download_close_check_cases) / 6; ++i) {
    ExecuteDownloadCloseCheckCase(i);
  }
}

IN_PROC_BROWSER_TEST_F(BrowserCloseTest, MAYBE_DownloadsCloseCheck_2) {
  ASSERT_TRUE(SetupForDownloadCloseCheck());
  for (size_t i = 2 * arraysize(download_close_check_cases) / 6;
       i < 3 * arraysize(download_close_check_cases) / 6; ++i) {
    ExecuteDownloadCloseCheckCase(i);
  }
}

IN_PROC_BROWSER_TEST_F(BrowserCloseTest, MAYBE_DownloadsCloseCheck_3) {
  ASSERT_TRUE(SetupForDownloadCloseCheck());
  for (size_t i = 3 * arraysize(download_close_check_cases) / 6;
       i < 4 * arraysize(download_close_check_cases) / 6; ++i) {
    ExecuteDownloadCloseCheckCase(i);
  }
}

IN_PROC_BROWSER_TEST_F(BrowserCloseTest, MAYBE_DownloadsCloseCheck_4) {
  ASSERT_TRUE(SetupForDownloadCloseCheck());
  for (size_t i = 4 * arraysize(download_close_check_cases) / 6;
       i < 5 * arraysize(download_close_check_cases) / 6; ++i) {
    ExecuteDownloadCloseCheckCase(i);
  }
}

IN_PROC_BROWSER_TEST_F(BrowserCloseTest, MAYBE_DownloadsCloseCheck_5) {
  ASSERT_TRUE(SetupForDownloadCloseCheck());
  for (size_t i = 5 * arraysize(download_close_check_cases) / 6;
       i < 6 * arraysize(download_close_check_cases) / 6; ++i) {
    ExecuteDownloadCloseCheckCase(i);
  }
}