text:
// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <stddef.h>
#include <memory>
#include <string>

#include "base/bind.h"
#include "base/callback.h"
#include "base/feature_list.h"
#include "base/path_service.h"
#include "base/run_loop.h"
#include "base/task/sequenced_task_runner.h"
#include "base/test/bind.h"
#include "base/test/scoped_feature_list.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "build/chromeos_buildflags.h"
#include "chrome/browser/browsing_data/browsing_data_remover_browsertest_base.h"
#include "chrome/browser/browsing_data/chrome_browsing_data_remover_constants.h"
#include "chrome/browser/browsing_data/counters/cache_counter.h"
#include "chrome/browser/browsing_data/counters/site_data_counting_helper.h"
#include "chrome/browser/browsing_data/local_data_container.h"
#include "chrome/browser/content_settings/host_content_settings_map_factory.h"
#include "chrome/browser/external_protocol/external_protocol_handler.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/signin/account_reconcilor_factory.h"
#include "chrome/browser/signin/identity_manager_factory.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/common/pref_names.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/base/ui_test_utils.h"
#include "components/metrics/content/subprocess_metrics_provider.h"
#include "components/password_manager/core/browser/password_manager_features_util.h"
#include "components/password_manager/core/common/password_manager_features.h"
#include "components/prefs/pref_service.h"
#include "components/signin/core/browser/account_reconcilor.h"
#include "components/signin/public/identity_manager/identity_manager.h"
#include "components/signin/public/identity_manager/identity_test_utils.h"
#include "components/sync/test/test_sync_service.h"
#include "content/public/browser/browsing_data_filter_builder.h"
#include "content/public/browser/clear_site_data_utils.h"
#include "content/public/browser/storage_partition.h"
#include "content/public/browser/storage_usage_info.h"
#include "content/public/common/content_paths.h"
#include "content/public/common/content_switches.h"
#include "content/public/common/network_service_util.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browsing_data_remover_test_util.h"
#include "google_apis/gaia/google_service_auth_error.h"
#include "media/base/media_switches.h"
#include "media/mojo/mojom/media_types.mojom.h"
#include "media/mojo/services/video_decode_perf_history.h"
#include "media/mojo/services/webrtc_video_perf_history.h"
#include "net/cookies/cookie_partition_key.h"
#include "net/dns/mock_host_resolver.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "net/traffic_annotation/network_traffic_annotation_test_helper.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/gurl.h"

#if BUILDFLAG(ENABLE_LIBRARY_CDMS)
#if BUILDFLAG(IS_MAC)
#include "base/threading/platform_thread.h"
#endif
#include "base/memory/scoped_refptr.h"
#include "chrome/browser/media/library_cdm_test_helper.h"
#endif  // BUILDFLAG(ENABLE_LIBRARY_CDMS)

#if BUILDFLAG(IS_CHROMEOS_ASH)
#include "chrome/browser/ash/net/system_proxy_manager.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/browser_process_platform_part.h"
#include "chromeos/ash/components/dbus/system_proxy/system_proxy_client.h"
#endif  // BUILDFLAG(IS_CHROMEOS_ASH)

#if BUILDFLAG(IS_CHROMEOS_LACROS)
#include "chromeos/startup/browser_init_params.h"
#include "components/account_manager_core/account.h"
#include "components/account_manager_core/account_manager_util.h"
#endif

using content::BrowserThread;
using content::BrowsingDataFilterBuilder;

namespace {
static const char* kExampleHost = "example.com";
static const char* kLocalHost = "localhost";
static const base::Time kStartTime = base::Time::Now();
static const base::Time kLastHourTime = kStartTime - base::Hours(1);

// This enum is used in the place of base::Time because using base::Time
// as a test param causes problems on Fuchsia. See https://crbug.com/1308948 for
// details.
enum TimeEnum {
  kDefault,
  kStart,
  kLastHour,
  kMax,
};

base::Time TimeEnumToTime(TimeEnum time) {
  switch (time) {
    case TimeEnum::kStart:
      return kStartTime;
    case TimeEnum::kLastHour:
      return kLastHourTime;
    case TimeEnum::kMax:
      return base::Time::Max();
    default:
      return base::Time();
  }
}
}  // namespace

class BrowsingDataRemoverBrowserTest
    : public BrowsingDataRemoverBrowserTestBase {
 public:
  BrowsingDataRemoverBrowserTest() {
    std::vector<base::test::FeatureRef> enabled_features = {};
#if BUILDFLAG(ENABLE_LIBRARY_CDMS)
    enabled_features.push_back(media::kExternalClearKeyForTesting);
#endif
    InitFeatureList(std::move(enabled_features));
  }

#if BUILDFLAG(IS_CHROMEOS_LACROS)
  void CreatedBrowserMainParts(
      content::BrowserMainParts* browser_main_parts) override {
    crosapi::mojom::BrowserInitParamsPtr init_params =
        crosapi::mojom::BrowserInitParams::New();
    std::string device_account_email = "primaryaccount@gmail.com";
    account_manager::AccountKey key(
        signin::GetTestGaiaIdForEmail(device_account_email),
        ::account_manager::AccountType::kGaia);
    init_params->device_account =
        account_manager::ToMojoAccount({key, device_account_email});
    chromeos::BrowserInitParams::SetInitParamsForTests(std::move(init_params));
    InProcessBrowserTest::CreatedBrowserMainParts(browser_main_parts);
  }
#endif

  void SetUpOnMainThread() override {
    BrowsingDataRemoverBrowserTestBase::SetUpOnMainThread();
    host_resolver()->AddRule(kExampleHost, "127.0.0.1");
  }
  void RemoveAndWait(uint64_t remove_mask) {
    RemoveAndWait(remove_mask, TimeEnum::kDefault, TimeEnum::kMax);
  }

  void RemoveAndWait(uint64_t remove_mask, TimeEnum delete_begin) {
    RemoveAndWait(remove_mask, delete_begin, TimeEnum::kMax);
  }

  void RemoveAndWait(uint64_t remove_mask,
                     TimeEnum delete_begin,
                     TimeEnum delete_end) {
    content::BrowsingDataRemover* remover =
        GetBrowser()->profile()->GetBrowsingDataRemover();
    content::BrowsingDataRemoverCompletionObserver completion_observer(remover);
    remover->RemoveAndReply(
        TimeEnumToTime(delete_begin), TimeEnumToTime(delete_end), remove_mask,
        content::BrowsingDataRemover::ORIGIN_TYPE_UNPROTECTED_WEB,
        &completion_observer);
    completion_observer.BlockUntilCompletion();
  }

  void RemoveWithFilterAndWait(
      uint64_t remove_mask,
      std::unique_ptr<BrowsingDataFilterBuilder> filter_builder) {
    content::BrowsingDataRemover* remover =
        GetBrowser()->profile()->GetBrowsingDataRemover();
    content::BrowsingDataRemoverCompletionObserver completion_observer(remover);
    remover->RemoveWithFilterAndReply(
        base::Time(), base::Time::Max(), remove_mask,
        content::BrowsingDataRemover::ORIGIN_TYPE_UNPROTECTED_WEB,
        std::move(filter_builder), &completion_observer);
    completion_observer.BlockUntilCompletion();
  }

  // Test a data type by creating a value and checking it is counted by the
  // cookie counter. Then it deletes the value and checks that it has been
  // deleted and the cookie counter is back to zero.
  void TestSiteData(const std::string& type, TimeEnum delete_begin) {
    EXPECT_EQ(0, GetSiteDataCount());
    GURL url = embedded_test_server()->GetURL("/browsing_data/site_data.html");
    ASSERT_TRUE(ui_test_utils::NavigateToURL(GetBrowser(), url));

    EXPECT_EQ(0, GetSiteDataCount());
    ExpectCookieTreeModelCount(0);
    EXPECT_FALSE(HasDataForType(type));

    SetDataForType(type);
    EXPECT_EQ(1, GetSiteDataCount());
    ExpectCookieTreeModelCount(1);
    EXPECT_TRUE(HasDataForType(type));

    RemoveAndWait(chrome_browsing_data_remover::DATA_TYPE_SITE_DATA,
                  delete_begin);
    EXPECT_EQ(0, GetSiteDataCount());
    ExpectCookieTreeModelCount(0);
    EXPECT_FALSE(HasDataForType(type));
  }

  // Test that storage systems like filesystem and websql, where just an access
  // creates an empty store, are counted and deleted correctly.
  void TestEmptySiteData(const std::string& type, TimeEnum delete_begin) {
    EXPECT_EQ(0, GetSiteDataCount());
    ExpectCookieTreeModelCount(0);
    GURL url = embedded_test_server()->GetURL("/browsing_data/site_data.html");
    ASSERT_TRUE(ui_test_utils::NavigateToURL(GetBrowser(), url));
    EXPECT_EQ(0, GetSiteDataCount());
    ExpectCookieTreeModelCount(0);
    // Opening a store of this type creates a site data entry.
    EXPECT_FALSE(HasDataForType(type));
    EXPECT_EQ(1, GetSiteDataCount());
    ExpectCookieTreeModelCount(1);
    RemoveAndWait(chrome_browsing_data_remover::DATA_TYPE_SITE_DATA,
                  delete_begin);

    EXPECT_EQ(0, GetSiteDataCount());
    ExpectCookieTreeModelCount(0);
  }

#if BUILDFLAG(ENABLE_LIBRARY_CDMS)
  // TODO(crbug.com/1307796): Include quota nodes in CookieTreeModelCount to
  // allow testing media licenses with TestSiteData().
  int GetMediaLicenseCount() { return 0; }
#endif

  inline void ExpectCookieTreeModelCount(int expected) {
    std::unique_ptr<CookiesTreeModel> model = GetCookiesTreeModel(GetProfile());
    EXPECT_EQ(expected, GetCookiesTreeModelCount(model->GetRoot()))
        << GetCookiesTreeModelInfo(model->GetRoot());
  }

  void OnVideoDecodePerfInfo(base::RunLoop* run_loop,
                             bool* out_is_smooth,
                             bool* out_is_power_efficient,
                             bool is_smooth,
                             bool is_power_efficient) {
    *out_is_smooth = is_smooth;
    *out_is_power_efficient = is_power_efficient;
    run_loop->QuitWhenIdle();
  }

  network::mojom::NetworkContext* network_context() const {
    return GetBrowser()
        ->profile()
        ->GetDefaultStoragePartition()
        ->GetNetworkContext();
  }

 private:
  void OnCacheSizeResult(
      base::RunLoop* run_loop,
      browsing_data::BrowsingDataCounter::ResultInt* out_size,
      std::unique_ptr<browsing_data::BrowsingDataCounter::Result> result) {
    if (!result->Finished())
      return;

    *out_size =
        static_cast<browsing_data::BrowsingDataCounter::FinishedResult*>(
            result.get())
            ->Value();
    run_loop->Quit();
  }

  void SetUpCommandLine(base::CommandLine* command_line) override {
#if BUILDFLAG(ENABLE_LIBRARY_CDMS)
    // Testing MediaLicenses requires additional command line parameters as
    // it uses the External Clear Key CDM.
    RegisterClearKeyCdm(command_line);
#endif
    command_line->AppendSwitchASCII(switches::kEnableBlinkFeatures,
                                    "StorageFoundationAPI");
  }
};

#if BUILDFLAG(ENABLE_DICE_SUPPORT)
// Same as BrowsingDataRemoverBrowserTest, but forces Dice to be enabled.
class DiceBrowsingDataRemoverBrowserTest
    : public BrowsingDataRemoverBrowserTest {
 public:
  AccountInfo AddAccountToProfile(const std::string& account_id,
                                  Profile* profile,
                                  bool is_primary) {
    auto* identity_manager = IdentityManagerFactory::GetForProfile(profile);
    if (is_primary) {
      DCHECK(!identity_manager->HasPrimaryAccount(signin::ConsentLevel::kSync));
      return signin::MakePrimaryAccountAvailable(identity_manager,
                                                 account_id + "@gmail.com",
                                                 signin::ConsentLevel::kSync);
    }
    auto account_info =
        signin::MakeAccountAvailable(identity_manager, account_id);
    DCHECK(
        identity_manager->HasAccountWithRefreshToken(account_info.account_id));
    return account_info;
  }
};
#endif

// Test BrowsingDataRemover for downloads.
IN_PROC_BROWSER_TEST_F(BrowsingDataRemoverBrowserTest, Download) {
  DownloadAnItem();
  RemoveAndWait(content::BrowsingDataRemover::DATA_TYPE_DOWNLOADS);
  VerifyDownloadCount(0u);
}

// Test that the salt for media device IDs is reset when cookies are cleared.
IN_PROC_BROWSER_TEST_F(BrowsingDataRemoverBrowserTest, MediaDeviceIdSalt) {
  std::string original_salt = GetBrowser()->profile()->GetMediaDeviceIDSalt();
  RemoveAndWait(content::BrowsingDataRemover::DATA_TYPE_COOKIES);
  std::string new_salt = GetBrowser()->profile()->GetMediaDeviceIDSalt();
  EXPECT_NE(original_salt, new_salt);
}

#if BUILDFLAG(ENABLE_DICE_SUPPORT)
// Test that Sync is paused when cookies are cleared.
IN_PROC_BROWSER_TEST_F(DiceBrowsingDataRemoverBrowserTest, SyncToken) {
  Profile* profile = browser()->profile();
  // Set a Gaia cookie.
  ASSERT_TRUE(SetGaiaCookieForProfile(profile));
  // Set a Sync account and a secondary account.
  const char kPrimaryAccountId[] = "primary_account_id";
  AccountInfo primary_account =
      AddAccountToProfile(kPrimaryAccountId, profile, /*is_primary=*/true);
  const char kSecondaryAccountId[] = "secondary_account_id";
  AccountInfo secondary_account =
      AddAccountToProfile(kSecondaryAccountId, profile, /*is_primary=*/false);
  // Clear cookies.
  RemoveAndWait(content::BrowsingDataRemover::DATA_TYPE_COOKIES);
  // Check that the Sync account was not removed and Sync was paused.
  signin::IdentityManager* identity_manager =
      IdentityManagerFactory::GetForProfile(profile);
  EXPECT_TRUE(
      identity_manager->HasAccountWithRefreshToken(primary_account.account_id));
  EXPECT_EQ(
      GoogleServiceAuthError::InvalidGaiaCredentialsReason::
          CREDENTIALS_REJECTED_BY_CLIENT,
      identity_manager
          ->GetErrorStateOfRefreshTokenForAccount(primary_account.account_id)
          .GetInvalidGaiaCredentialsReason());
  // Check that the secondary token was revoked.
  EXPECT_FALSE(identity_manager->HasAccountWithRefreshToken(
      secondary_account.account_id));
}

// Test that Sync is not paused when cookies are cleared, if synced data is
// being deleted.
IN_PROC_BROWSER_TEST_F(DiceBrowsingDataRemoverBrowserTest,
                       SyncTokenScopedDeletion) {
  Profile* profile = browser()->profile();
  // Set a Gaia cookie.
  ASSERT_TRUE(SetGaiaCookieForProfile(profile));
  // Set a Sync account and a secondary account.
  const char kPrimaryAccountId[] = "primary_account_id";
  AccountInfo primary_account =
      AddAccountToProfile(kPrimaryAccountId, profile, /*is_primary=*/true);
  const char kSecondaryAccountId[] = "secondary_account_id";
  AccountInfo secondary_account =
      AddAccountToProfile(kSecondaryAccountId, profile, /*is_primary=*/false);
  // Sync data is being deleted.
  std::unique_ptr<AccountReconcilor::ScopedSyncedDataDeletion> deletion =
      AccountReconcilorFactory::GetForProfile(profile)
          ->GetScopedSyncDataDeletion();
  // Clear cookies.
  RemoveAndWait(content::BrowsingDataRemover::DATA_TYPE_COOKIES);
  // Check that the Sync token was not revoked.
  auto* identity_manager = IdentityManagerFactory::GetForProfile(profile);
  EXPECT_TRUE(
      identity_manager->HasAccountWithRefreshToken(primary_account.account_id));
  EXPECT_FALSE(
      identity_manager->HasAccountWithRefreshTokenInPersistentErrorState(
          primary_account.account_id));
  // Check that the secondary token was revoked.
  EXPECT_FALSE(identity_manager->HasAccountWithRefreshToken(
      secondary_account.account_id));
}

// Test that Sync is paused when cookies are cleared if Sync was in error, even
// if synced data is being deleted.
IN_PROC_BROWSER_TEST_F(DiceBrowsingDataRemoverBrowserTest, SyncTokenError) {
  Profile* profile = browser()->profile();
  // Set a Gaia cookie.
  ASSERT_TRUE(SetGaiaCookieForProfile(profile));
  // Set a Sync account with authentication error.
  const char kAccountId[] = "account_id";
  AccountInfo primary_account =
      AddAccountToProfile(kAccountId, profile, /*is_primary=*/true);
  auto* identity_manager = IdentityManagerFactory::GetForProfile(profile);
  signin::UpdatePersistentErrorOfRefreshTokenForAccount(
      identity_manager, primary_account.account_id,
      GoogleServiceAuthError::FromInvalidGaiaCredentialsReason(
          GoogleServiceAuthError::InvalidGaiaCredentialsReason::
              CREDENTIALS_REJECTED_BY_SERVER));

  // Sync data is being deleted.
  std::unique_ptr<AccountReconcilor::ScopedSyncedDataDeletion> deletion =
      AccountReconcilorFactory::GetForProfile(profile)
          ->GetScopedSyncDataDeletion();
  // Clear cookies.
  RemoveAndWait(content::BrowsingDataRemover::DATA_TYPE_COOKIES);
  // Check that the account was not removed and Sync was paused.
  EXPECT_TRUE(
      identity_manager->HasAccountWithRefreshToken(primary_account.account_id));
  EXPECT_EQ(
      GoogleServiceAuthError::InvalidGaiaCredentialsReason::
          CREDENTIALS_REJECTED_BY_CLIENT,
      identity_manager
          ->GetErrorStateOfRefreshTokenForAccount(primary_account.account_id)
          .GetInvalidGaiaCredentialsReason());
}

// Test that the tokens are revoked when cookies are cleared when there is no
// primary account.
IN_PROC_BROWSER_TEST_F(DiceBrowsingDataRemoverBrowserTest, NoSync) {
  Profile* profile = browser()->profile();
  // Set a Gaia cookie.
  ASSERT_TRUE(SetGaiaCookieForProfile(profile));
  // Set a non-Sync account.
  const char kAccountId[] = "account_id";
  AccountInfo secondary_account =
      AddAccountToProfile(kAccountId, profile, /*is_primary=*/false);
  // Clear cookies.
  RemoveAndWait(content::BrowsingDataRemover::DATA_TYPE_COOKIES);
  // Check that the account was removed.
  auto* identity_manager = IdentityManagerFactory::GetForProfile(profile);
  EXPECT_FALSE(identity_manager->HasAccountWithRefreshToken(
      secondary_account.account_id));
}
#endif

// The call to Remove() should crash in debug (DCHECK), but the browser-test
// process model prevents using a death test.
#if defined(NDEBUG) && !defined(DCHECK_ALWAYS_ON)
// Test BrowsingDataRemover for prohibited downloads. Note that this only
// really exercises the code in a Release build.
IN_PROC_BROWSER_TEST_F(BrowsingDataRemoverBrowserTest, DownloadProhibited) {
  PrefService* prefs = GetBrowser()->profile()->GetPrefs();
  prefs->SetBoolean(prefs::kAllowDeletingBrowserHistory, false);

  DownloadAnItem();
  RemoveAndWait(content::BrowsingDataRemover::DATA_TYPE_DOWNLOADS);
  VerifyDownloadCount(1u);
}
#endif

// Verify VideoDecodePerfHistory is cleared when deleting all history from
// beginning of time.
IN_PROC_BROWSER_TEST_F(BrowsingDataRemoverBrowserTest, VideoDecodePerfHistory) {
  media::VideoDecodePerfHistory* video_decode_perf_history =
      GetBrowser()->profile()->GetVideoDecodePerfHistory();

  // Save a video decode record. Note: we avoid using a web page to generate the
  // stats as this takes at least 5 seconds and even then is not a guarantee
  // depending on scheduler. Manual injection is quick and non-flaky.
  const media::VideoCodecProfile kProfile = media::VP9PROFILE_PROFILE0;
  const gfx::Size kSize(100, 200);
  const int kFrameRate = 30;
  const int kFramesDecoded = 1000;
  const int kFramesDropped = .9 * kFramesDecoded;
  const int kFramesPowerEfficient = 0;
  const url::Origin kOrigin = url::Origin::Create(GURL("http://example.com"));
  const bool kIsTopFrame = true;
  const uint64_t kPlayerId = 1234u;

  media::mojom::PredictionFeatures prediction_features;
  prediction_features.profile = kProfile;
  prediction_features.video_size = kSize;
  prediction_features.frames_per_sec = kFrameRate;

  media::mojom::PredictionTargets prediction_targets;
  prediction_targets.frames_decoded = kFramesDecoded;
  prediction_targets.frames_dropped = kFramesDropped;
  prediction_targets.frames_power_efficient = kFramesPowerEfficient;

  {
    base::RunLoop run_loop;
    video_decode_perf_history->GetSaveCallback().Run(
        ukm::kInvalidSourceId, media::learning::FeatureValue(0), kIsTopFrame,
        prediction_features, prediction_targets, kPlayerId,
        run_loop.QuitWhenIdleClosure());
    run_loop.Run();
  }

  // Verify history exists.
  // Expect |is_smooth| = false and |is_power_efficient| = false given that 90%
  // of recorded frames were dropped and 0 were power efficient.
  bool is_smooth = true;
  bool is_power_efficient = true;
  {
    base::RunLoop run_loop;
    video_decode_perf_history->GetPerfInfo(
        media::mojom::PredictionFeatures::New(prediction_features),
        base::BindOnce(&BrowsingDataRemoverBrowserTest::OnVideoDecodePerfInfo,
                       base::Unretained(this), &run_loop, &is_smooth,
                       &is_power_efficient));
    run_loop.Run();
  }
  EXPECT_FALSE(is_smooth);
  EXPECT_FALSE(is_power_efficient);

  // Clear history.
  RemoveAndWait(chrome_browsing_data_remover::DATA_TYPE_HISTORY);

  // Verify history no longer exists. Both |is_smooth| and |is_power_efficient|
  // should now report true because the VideoDecodePerfHistory optimistically
  // returns true when it has no data.
  {
    base::RunLoop run_loop;
    video_decode_perf_history->GetPerfInfo(
        media::mojom::PredictionFeatures::New(prediction_features),
        base::BindOnce(&BrowsingDataRemoverBrowserTest::OnVideoDecodePerfInfo,
                       base::Unretained(this), &run_loop, &is_smooth,
                       &is_power_efficient));
    run_loop.Run();
  }
  EXPECT_TRUE(is_smooth);
  EXPECT_TRUE(is_power_efficient);
}

// Verify WebrtcVideoPerfHistory is cleared when deleting all history from
// beginning of time.
IN_PROC_BROWSER_TEST_F(BrowsingDataRemoverBrowserTest, WebrtcVideoPerfHistory) {
  media::WebrtcVideoPerfHistory* webrtc_video_perf_history =
      GetBrowser()->profile()->GetWebrtcVideoPerfHistory();

  // Save a video decode record. Note: we avoid using a web page to generate the
  // stats as this takes at least 5 seconds and even then is not a guarantee
  // depending on scheduler. Manual injection is quick and non-flaky.
  const media::VideoCodecProfile kProfile = media::VP9PROFILE_PROFILE0;
  const int kVideoPixels = 1920 * 1080;
  const int kFrameRate = 30;

  const int kFramesProcessed = 1000;
  const int kKeyFramesProcessed = 11;
  const float kP99ProcessingTimeMs = 100.0;

  media::mojom::WebrtcPredictionFeatures features;
  features.is_decode_stats = true;
  features.profile = kProfile;
  features.video_pixels = kVideoPixels;
  features.hardware_accelerated = false;

  media::mojom::WebrtcVideoStats video_stats;
  video_stats.frames_processed = kFramesProcessed;
  video_stats.key_frames_processed = kKeyFramesProcessed;
  video_stats.p99_processing_time_ms = kP99ProcessingTimeMs;

  {
    base::RunLoop run_loop;
    webrtc_video_perf_history->GetSaveCallback().Run(features, video_stats,
                                                     run_loop.QuitClosure());
    run_loop.Run();
  }

  // Verify history exists.
  // Expect |is_smooth| = false given that the 99th percentile processing time
  // is 100 ms.
  {
    base::RunLoop run_loop;
    webrtc_video_perf_history->GetPerfInfo(
        media::mojom::WebrtcPredictionFeatures::New(features), kFrameRate,
        base::BindLambdaForTesting([&](bool smooth) {
          EXPECT_FALSE(smooth);
          run_loop.Quit();
        }));
    run_loop.Run();
  }

  // Clear history.
  RemoveAndWait(chrome_browsing_data_remover::DATA_TYPE_HISTORY);

  // Verify history no longer exists. |is_smooth| should now report true because
  // the WebrtcVideoPerfHistory optimistically returns true when it has no data.
  {
    base::RunLoop run_loop;
    webrtc_video_perf_history->GetPerfInfo(
        media::mojom::WebrtcPredictionFeatures::New(features), kFrameRate,
        base::BindLambdaForTesting([&](bool smooth) {
          EXPECT_TRUE(smooth);
          run_loop.Quit();
        }));
    run_loop.Run();
  }
}

// TODO(crbug.com/1317431): WebSQL does not work on Fuchsia.
#if BUILDFLAG(IS_FUCHSIA)
#define MAYBE_Database DISABLED_Database
#else
#define MAYBE_Database Database
#endif
// Verify can modify database after deleting it.
IN_PROC_BROWSER_TEST_F(BrowsingDataRemoverBrowserTest, MAYBE_Database) {
  GURL url = embedded_test_server()->GetURL("/simple_database.html");
  ASSERT_TRUE(ui_test_utils::NavigateToURL(GetBrowser(), url));

  RunScriptAndCheckResult("createTable()", "done");
  RunScriptAndCheckResult("insertRecord('text')", "done");
  RunScriptAndCheckResult("getRecords()", "text");

  RemoveAndWait(chrome_browsing_data_remover::DATA_TYPE_SITE_DATA);

  ASSERT_TRUE(ui_test_utils::NavigateToURL(GetBrowser(), url));
  RunScriptAndCheckResult("createTable()", "done");
  RunScriptAndCheckResult("insertRecord('text2')", "done");
  RunScriptAndCheckResult("getRecords()", "text2");
}

// Verifies that cache deletion finishes successfully. Completes deletion of
// cache should leave it empty, and partial deletion should leave nonzero
// amount of data. Note that this tests the integration of BrowsingDataRemover
// with ConditionalCacheDeletionHelper. Whether ConditionalCacheDeletionHelper
// actually deletes the correct entries is tested
// in ConditionalCacheDeletionHelperBrowsertest.
// TODO(crbug.com/817417): check the cache size instead of stopping the server
// and loading the request again.
IN_PROC_BROWSER_TEST_F(BrowsingDataRemoverBrowserTest, Cache) {
  // Load several resources.
  GURL url1 = embedded_test_server()->GetURL("/cachetime");
  GURL url2 = embedded_test_server()->GetURL(kExampleHost, "/cachetime");
  ASSERT_FALSE(url::IsSameOriginWith(url1, url2));

  EXPECT_EQ(net::OK, content::LoadBasicRequest(network_context(), url1));
  EXPECT_EQ(net::OK, content::LoadBasicRequest(network_context(), url2));

  // Check that the cache has been populated by revisiting these pages with the
  // server stopped.
  ASSERT_TRUE(embedded_test_server()->ShutdownAndWaitUntilComplete());
  EXPECT_EQ(net::OK, content::LoadBasicRequest(network_context(), url1));
  EXPECT_EQ(net::OK, content::LoadBasicRequest(network_context(), url2));

  // Partially delete cache data. Delete data for localhost, which is the origin
  // of |url1|, but not for |kExampleHost|, which is the origin of |url2|.
  std::unique_ptr<BrowsingDataFilterBuilder> filter_builder =
      BrowsingDataFilterBuilder::Create(
          BrowsingDataFilterBuilder::Mode::kDelete);
  filter_builder->AddOrigin(url::Origin::Create(url1));
  RemoveWithFilterAndWait(content::BrowsingDataRemover::DATA_TYPE_CACHE,
                          std::move(filter_builder));

  // After the partial deletion, the cache should be smaller but still nonempty.
  EXPECT_NE(net::OK, content::LoadBasicRequest(network_context(), url1));
  EXPECT_EQ(net::OK, content::LoadBasicRequest(network_context(), url2));

  // Another partial deletion with the same filter should have no effect.
  filter_builder = BrowsingDataFilterBuilder::Create(
      BrowsingDataFilterBuilder::Mode::kDelete);
  filter_builder->AddOrigin(url::Origin::Create(url1));
  RemoveWithFilterAndWait(content::BrowsingDataRemover::DATA_TYPE_CACHE,
                          std::move(filter_builder));
  EXPECT_NE(net::OK, content::LoadBasicRequest(network_context(), url1));
  EXPECT_EQ(net::OK, content::LoadBasicRequest(network_context(), url2));

  // Delete the remaining data.
  RemoveAndWait(content::BrowsingDataRemover::DATA_TYPE_CACHE);

  // The cache should be empty.
  EXPECT_NE(net::OK, content::LoadBasicRequest(network_context(), url1));
  EXPECT_NE(net::OK, content::LoadBasicRequest(network_context(), url2));
}

// Crashes the network service while clearing the HTTP cache to make sure the
// clear operation does complete.
// Note that there is a race between crashing the network service and clearing
// the cache, so the test might flakily fail if the tested behavior does not
// work.
// TODO(crbug.com/813882): test retry behavior by validating the cache is empty
// after the crash.
IN_PROC_BROWSER_TEST_F(BrowsingDataRemoverBrowserTest,
                       ClearCacheAndNetworkServiceCrashes) {
  if (!content::IsOutOfProcessNetworkService())
    return;

  // Clear the cached data with a task posted to crash the network service.
  // The task should be run while waiting for the cache clearing operation to
  // complete, hopefully it happens before the cache has been cleared.
  base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
      FROM_HERE,
      base::BindOnce(&content::BrowserTestBase::SimulateNetworkServiceCrash,
                     base::Unretained(this)));

  RemoveAndWait(content::BrowsingDataRemover::DATA_TYPE_CACHE);
}

// Verifies that the network quality prefs are cleared.
IN_PROC_BROWSER_TEST_F(BrowsingDataRemoverBrowserTest, VerifyNQECacheCleared) {
  base::HistogramTester histogram_tester;
  RemoveAndWait(content::BrowsingDataRemover::DATA_TYPE_CACHE);

  // Wait until there is at least one sample in NQE.PrefsSizeOnClearing.
  bool histogram_populated = false;
  for (size_t attempt = 0; attempt < 3; ++attempt) {
    const std::vector<base::Bucket> buckets =
        histogram_tester.GetAllSamples("NQE.PrefsSizeOnClearing");
    for (const auto& bucket : buckets) {
      if (bucket.count > 0) {
        histogram_populated = true;
        break;
      }
    }
    if (histogram_populated)
      break;

    // Retry fetching the histogram since it's not populated yet.
    content::FetchHistogramsFromChildProcesses();
    metrics::SubprocessMetricsProvider::MergeHistogramDeltasForTesting();
    base::RunLoop().RunUntilIdle();
  }

  histogram_tester.ExpectTotalCount("NQE.PrefsSizeOnClearing", 1);
}

IN_PROC_BROWSER_TEST_F(BrowsingDataRemoverBrowserTest,
                       ExternalProtocolHandlerPerOriginPrefs) {
  Profile* profile = GetBrowser()->profile();
  url::Origin test_origin = url::Origin::Create(GURL("https://example.test/"));
  const std::string serialized_test_origin = test_origin.Serialize();
  base::Value origin_pref(base::Value::Type::DICTIONARY);
  origin_pref.SetKey(serialized_test_origin,
                     base::Value(base::Value::Type::DICTIONARY));
  base::Value* allowed_protocols_for_origin =
      origin_pref.FindDictKey(serialized_test_origin);
  allowed_protocols_for_origin->SetBoolKey("tel", true);
  profile->GetPrefs()->Set(prefs::kProtocolHandlerPerOriginAllowedProtocols,
                           origin_pref);
  ExternalProtocolHandler::BlockState block_state =
      ExternalProtocolHandler::GetBlockState("tel", &test_origin, profile);
  ASSERT_EQ(ExternalProtocolHandler::DONT_BLOCK, block_state);
  RemoveAndWait(chrome_browsing_data_remover::DATA_TYPE_SITE_DATA);
  block_state =
      ExternalProtocolHandler::GetBlockState("tel", &test_origin, profile);
  ASSERT_EQ(ExternalProtocolHandler::UNKNOWN, block_state);
}

IN_PROC_BROWSER_TEST_F(BrowsingDataRemoverBrowserTest, HistoryDeletion) {
  const std::string kType = "History";
  GURL url = embedded_test_server()->GetURL("/browsing_data/site_data.html");
  // Create a new tab to avoid confusion from having a NTP navigation entry.
  ui_test_utils::NavigateToURLWithDisposition(
      GetBrowser(), url, WindowOpenDisposition::NEW_FOREGROUND_TAB,
      ui_test_utils::BROWSER_TEST_WAIT_FOR_LOAD_STOP);
  EXPECT_FALSE(HasDataForType(kType));
  SetDataForType(kType);
  EXPECT_TRUE(HasDataForType(kType));
  // Remove history from navigation to site_data.html.
  RemoveAndWait(chrome_browsing_data_remover::DATA_TYPE_HISTORY);
  EXPECT_FALSE(HasDataForType(kType));
  SetDataForType(kType);
  EXPECT_TRUE(HasDataForType(kType));
  // Remove history from previous pushState() call in setHistory().
  RemoveAndWait(chrome_browsing_data_remover::DATA_TYPE_HISTORY);
  EXPECT_FALSE(HasDataForType(kType));
}

class BrowsingDataRemoverWithPasswordsAccountStorageBrowserTest
    : public BrowsingDataRemoverBrowserTest {
 public:
  BrowsingDataRemoverWithPasswordsAccountStorageBrowserTest() {
    features_.InitAndEnableFeature(
        password_manager::features::kEnablePasswordsAccountStorage);
  }

  void ClearSiteDataAndWait(
      const url::Origin& origin,
      const absl::optional<net::CookiePartitionKey>& cookie_partition_key,
      const absl::optional<blink::StorageKey>& storage_key) {
    base::RunLoop loop;
    content::ClearSiteData(/*browser_context_getter=*/base::BindRepeating(
                               [](content::BrowserContext* browser_context) {
                                 return browser_context;
                               },
                               base::Unretained(GetBrowser()->profile())),
                           /*origin=*/origin,
                           /*clear_cookies=*/true, /*clear_storage=*/true,
                           /*clear_cache=*/true,
                           /*avoid_closing_connections=*/true,
                           /*cookie_partition_key=*/cookie_partition_key,
                           /*storage_key=*/storage_key,
                           /*callback=*/loop.QuitClosure());
    loop.Run();
  }

 private:
  base::test::ScopedFeatureList features_;
};

IN_PROC_BROWSER_TEST_F(
    BrowsingDataRemoverWithPasswordsAccountStorageBrowserTest,
    ClearingCookiesAlsoClearsPasswordAccountStorageOptIn) {
  PrefService* prefs = GetBrowser()->profile()->GetPrefs();

  CoreAccountInfo account;
  account.email = "name@account.com";
  account.gaia = "name";
  account.account_id = CoreAccountId::FromGaiaId(account.gaia);

  syncer::TestSyncService sync_service;
  sync_service.SetHasSyncConsent(false);
  sync_service.SetAccountInfo(account);
  ASSERT_EQ(sync_service.GetTransportState(),
            syncer::SyncService::TransportState::ACTIVE);
  password_manager::features_util::OptInToAccountStorage(prefs, &sync_service);
  ASSERT_TRUE(password_manager::features_util::IsOptedInForAccountStorage(
      prefs, &sync_service));

  RemoveAndWait(chrome_browsing_data_remover::DATA_TYPE_SITE_DATA);

  EXPECT_FALSE(password_manager::features_util::IsOptedInForAccountStorage(
      prefs, &sync_service));
}

IN_PROC_BROWSER_TEST_F(
    BrowsingDataRemoverWithPasswordsAccountStorageBrowserTest,
    ClearingCookiesWithFilterAlsoClearsPasswordAccountStorageOptIn) {
  PrefService* prefs = GetBrowser()->profile()->GetPrefs();

  CoreAccountInfo account;
  account.email = "name@account.com";
  account.gaia = "name";
  account.account_id = CoreAccountId::FromGaiaId(account.gaia);

  syncer::TestSyncService sync_service;
  sync_service.SetHasSyncConsent(false);
  sync_service.SetAccountInfo(account);
  ASSERT_EQ(sync_service.GetTransportState(),
            syncer::SyncService::TransportState::ACTIVE);
  password_manager::features_util::OptInToAccountStorage(prefs, &sync_service);
  ASSERT_TRUE(password_manager::features_util::IsOptedInForAccountStorage(
      prefs, &sync_service));

  // Clearing cookies for some random domain should have no effect on the
  // opt-in.
  {
    std::unique_ptr<BrowsingDataFilterBuilder> filter_builder =
        BrowsingDataFilterBuilder::Create(
            BrowsingDataFilterBuilder::Mode::kDelete);
    filter_builder->AddRegisterableDomain("example.com");
    RemoveWithFilterAndWait(chrome_browsing_data_remover::DATA_TYPE_SITE_DATA,
                            std::move(filter_builder));
  }
  EXPECT_TRUE(password_manager::features_util::IsOptedInForAccountStorage(
      prefs, &sync_service));

  // Clearing cookies for google.com should clear the opt-in.
  {
    std::unique_ptr<BrowsingDataFilterBuilder> filter_builder =
        BrowsingDataFilterBuilder::Create(
            BrowsingDataFilterBuilder::Mode::kDelete);
    filter_builder->AddRegisterableDomain("google.com");
    RemoveWithFilterAndWait(chrome_browsing_data_remover::DATA_TYPE_SITE_DATA,
                            std::move(filter_builder));
  }
  EXPECT_FALSE(password_manager::features_util::IsOptedInForAccountStorage(
      prefs, &sync_service));
}

IN_PROC_BROWSER_TEST_F(
    BrowsingDataRemoverWithPasswordsAccountStorageBrowserTest,
    ClearSiteData) {
  PrefService* prefs = GetBrowser()->profile()->GetPrefs();

  CoreAccountInfo account;
  account.email = "name@account.com";
  account.gaia = "name";
  account.account_id = CoreAccountId::FromGaiaId(account.gaia);

  syncer::TestSyncService sync_service;
  sync_service.SetHasSyncConsent(false);
  sync_service.SetAccountInfo(account);
  ASSERT_EQ(sync_service.GetTransportState(),
            syncer::SyncService::TransportState::ACTIVE);

  const GURL kFirstPartyURL("https://google.com");
  const GURL kCrossSiteURL("https://example.com");

  const struct {
    const url::Origin origin;
    const absl::optional<net::CookiePartitionKey> cookie_partition_key;
    const absl::optional<blink::StorageKey> storage_key;
    bool expects_opted_in;
  } test_cases[] = {
      {
          url::Origin::Create(kFirstPartyURL),
          absl::nullopt,
          absl::nullopt,
          false,
      },
      {
          url::Origin::Create(kCrossSiteURL),
          absl::nullopt,
          absl::nullopt,
          true,
      },
      {
          url::Origin::Create(kFirstPartyURL),
          net::CookiePartitionKey::FromURLForTesting(kFirstPartyURL),
          absl::nullopt,
          false,
      },
      {
          url::Origin::Create(kFirstPartyURL),
          net::CookiePartitionKey::FromURLForTesting(kFirstPartyURL),
          blink::StorageKey(url::Origin::Create(kFirstPartyURL)),
          false,
      },
      {
          url::Origin::Create(kFirstPartyURL),
          net::CookiePartitionKey::FromURLForTesting(kCrossSiteURL),
          absl::nullopt,
          true,
      },
      {
          url::Origin::Create(kFirstPartyURL),
          net::CookiePartitionKey::FromURLForTesting(kCrossSiteURL),
          blink::StorageKey::CreateWithOptionalNonce(
              url::Origin::Create(kCrossSiteURL),
              net::SchemefulSite(url::Origin::Create(kFirstPartyURL)), nullptr,
              blink::mojom::AncestorChainBit::kCrossSite),
          true,
      },
  };
  for (const auto& test_case : test_cases) {
    password_manager::features_util::OptInToAccountStorage(prefs,
                                                           &sync_service);

    ClearSiteDataAndWait(test_case.origin, test_case.cookie_partition_key,
                         test_case.storage_key);

    ASSERT_EQ(password_manager::features_util::IsOptedInForAccountStorage(
                  prefs, &sync_service),
              test_case.expects_opted_in);
  }
}

// Parameterized to run tests for different deletion time ranges.
class BrowsingDataRemoverBrowserTestP
    : public BrowsingDataRemoverBrowserTest,
      public testing::WithParamInterface<TimeEnum> {};

IN_PROC_BROWSER_TEST_P(BrowsingDataRemoverBrowserTestP, CookieDeletion) {
  TestSiteData("Cookie", GetParam());
}

IN_PROC_BROWSER_TEST_P(BrowsingDataRemoverBrowserTestP,
                       CookieIncognitoDeletion) {
  UseIncognitoBrowser();
  TestSiteData("Cookie", GetParam());
}

// Regression test for https://crbug.com/1216406.
IN_PROC_BROWSER_TEST_P(BrowsingDataRemoverBrowserTestP,
                       BrowserContextDestructionVsCookieRemoval) {
  // Open an incognito browser.
  UseIncognitoBrowser();

  // Set a cookie.
  const char kDataType[] = "Cookie";
  GURL url = embedded_test_server()->GetURL("/browsing_data/site_data.html");
  ASSERT_TRUE(ui_test_utils::NavigateToURL(GetBrowser(), url));
  SetDataForType(kDataType);
  EXPECT_EQ(1, GetSiteDataCount());
  ExpectCookieTreeModelCount(1);
  EXPECT_TRUE(HasDataForType(kDataType));

  // Start data removal.  This will CreateTaskCompletionClosureForMojo and
  // register it as a completion callback for mojo calls to NetworkContext
  // and other StorageParition-owned mojo::Remote(s).
  //
  // kRemoveMask contains:
  // - DATA_TYPE_SITE_DATA - cargo-culted default from other tests
  // - DEFERRED_COOKIE_DELETION_DATA_TYPES - to get non-empty result from
  //   ChromeBrowsingDataRemoverDelegate::GetDomainsForDeferredCookieDeletion
  //   (which is needed to touch StoragePartition in
  //   BrowsingDataRemoverImpl::OnTaskComplete when it is called later,
  //   after starting destruction of the BrowserContext - see the description
  //   of the next test step below).
  constexpr uint64_t kRemoveMask =
      chrome_browsing_data_remover::DATA_TYPE_SITE_DATA |
      chrome_browsing_data_remover::DEFERRED_COOKIE_DELETION_DATA_TYPES;
  content::BrowserContext* browser_context = GetBrowser()->profile();
  content::BrowsingDataRemover* remover =
      browser_context->GetBrowsingDataRemover();
  content::BrowsingDataRemoverCompletionObserver completion_observer(remover);
  remover->RemoveAndReply(
      base::Time(),       // delete_begin
      base::Time::Max(),  // delete_end
      kRemoveMask, content::BrowsingDataRemover::ORIGIN_TYPE_UNPROTECTED_WEB,
      &completion_observer);

  // Close the incognito browser.  This will tear down its
  // Profile/BrowserContext, which will tear down the StoragePartition, which
  // will tear down some mojo::Remote(s), which will end up running the closures
  // returned from CreateTaskCompletionClosureForMojo (see the previous test
  // step), which will run BrowsingDataRemoverImpl::OnTaskComplete.  In
  // https://crbug.com/1216406 OnTaskComplete would attempt to use its
  // `browser_context_` (half-way destructed at this point) to get a
  // StoragePartition and this would lead to DumpWithoutCrashing initially (and
  // potentially crashes down the line).
  CloseBrowserSynchronously(GetBrowser());

  // Verify that the completion observer will get notified, even if there might
  // have been a failure with the removal.
  completion_observer.BlockUntilCompletion();

  // Expect that removing the cookies failed, because the StoragePartition has
  // been already gone by the time BrowsingDataRemoverImpl::OnTaskComplete run.
  EXPECT_TRUE(content::StoragePartition::REMOVE_DATA_MASK_COOKIES &
              completion_observer.failed_data_types());
}

IN_PROC_BROWSER_TEST_P(BrowsingDataRemoverBrowserTestP, SessionCookieDeletion) {
  TestSiteData("SessionCookie", GetParam());
}

IN_PROC_BROWSER_TEST_P(BrowsingDataRemoverBrowserTestP, LocalStorageDeletion) {
  TestSiteData("LocalStorage", GetParam());
}

IN_PROC_BROWSER_TEST_P(BrowsingDataRemoverBrowserTestP,
                       LocalStorageIncognitoDeletion) {
  UseIncognitoBrowser();
  TestSiteData("LocalStorage", GetParam());
}

// TODO(crbug.com/772337): DISABLED until session storage is working correctly.
// Add Incognito variant when this is re-enabled.
IN_PROC_BROWSER_TEST_P(BrowsingDataRemoverBrowserTestP,
                       DISABLED_SessionStorageDeletion) {
  TestSiteData("SessionStorage", GetParam());
}

// SessionStorage is not supported by site data counting and the cookie tree
// model but we can test the web visible behavior.
IN_PROC_BROWSER_TEST_P(BrowsingDataRemoverBrowserTestP,
                       SessionStorageDeletionWebOnly) {
  GURL url = embedded_test_server()->GetURL("/browsing_data/site_data.html");
  ASSERT_TRUE(ui_test_utils::NavigateToURL(GetBrowser(), url));
  const std::string type = "SessionStorage";
  EXPECT_FALSE(HasDataForType(type));
  SetDataForType(type);
  EXPECT_TRUE(HasDataForType(type));
  RemoveAndWait(chrome_browsing_data_remover::DATA_TYPE_SITE_DATA, GetParam());
  EXPECT_FALSE(HasDataForType(type));
}

// Test that session storage is not counted until crbug.com/772337 is fixed.
IN_PROC_BROWSER_TEST_F(BrowsingDataRemoverBrowserTest, SessionStorageCounting) {
  EXPECT_EQ(0, GetSiteDataCount());
  ExpectCookieTreeModelCount(0);
  GURL url = embedded_test_server()->GetURL("/browsing_data/site_data.html");
  ASSERT_TRUE(ui_test_utils::NavigateToURL(GetBrowser(), url));
  EXPECT_EQ(0, GetSiteDataCount());
  ExpectCookieTreeModelCount(0);
  SetDataForType("SessionStorage");
  EXPECT_EQ(0, GetSiteDataCount());
  ExpectCookieTreeModelCount(0);
  EXPECT_TRUE(HasDataForType("SessionStorage"));
}

IN_PROC_BROWSER_TEST_P(BrowsingDataRemoverBrowserTestP, ServiceWorkerDeletion) {
  TestSiteData("ServiceWorker", GetParam());
}

IN_PROC_BROWSER_TEST_P(BrowsingDataRemoverBrowserTestP,
                       ServiceWorkerIncognitoDeletion) {
  UseIncognitoBrowser();
  TestSiteData("ServiceWorker", GetParam());
}

IN_PROC_BROWSER_TEST_P(BrowsingDataRemoverBrowserTestP, CacheStorageDeletion) {
  TestSiteData("CacheStorage", GetParam());
}

IN_PROC_BROWSER_TEST_P(BrowsingDataRemoverBrowserTestP,
                       CacheStorageIncognitoDeletion) {
  UseIncognitoBrowser();
  TestSiteData("CacheStorage", GetParam());
}

IN_PROC_BROWSER_TEST_P(BrowsingDataRemoverBrowserTestP, FileSystemDeletion) {
  TestSiteData("FileSystem", GetParam());
}

IN_PROC_BROWSER_TEST_P(BrowsingDataRemoverBrowserTestP,
                       FileSystemIncognitoDeletion) {
  UseIncognitoBrowser();
  TestSiteData("FileSystem", GetParam());
}

// Test that empty filesystems are deleted correctly.
IN_PROC_BROWSER_TEST_P(BrowsingDataRemoverBrowserTestP,
                       EmptyFileSystemDeletion) {
  TestEmptySiteData("FileSystem", GetParam());
}

// Test that empty filesystems are deleted correctly in incognito mode.
IN_PROC_BROWSER_TEST_P(BrowsingDataRemoverBrowserTestP,
                       EmptyFileSystemIncognitoDeletion) {
  UseIncognitoBrowser();
  TestEmptySiteData("FileSystem", GetParam());
}

IN_PROC_BROWSER_TEST_P(BrowsingDataRemoverBrowserTestP, NativeIODeletion) {
  TestSiteData("StorageFoundation", GetParam());
}

// TODO(crbug.com/1317431): WebSQL does not work on Fuchsia.
#if BUILDFLAG(IS_FUCHSIA)
#define MAYBE_WebSqlDeletion DISABLED_WebSqlDeletion
#else
#define MAYBE_WebSqlDeletion WebSqlDeletion
#endif
IN_PROC_BROWSER_TEST_P(BrowsingDataRemoverBrowserTestP, MAYBE_WebSqlDeletion) {
  TestSiteData("WebSql", GetParam());
}

// TODO(crbug.com/1317431): WebSQL does not work on Fuchsia.
#if BUILDFLAG(IS_FUCHSIA)
#define MAYBE_WebSqlIncognitoDeletion DISABLED_WebSqlIncognitoDeletion
#else
#define MAYBE_WebSqlIncognitoDeletion WebSqlIncognitoDeletion
#endif
IN_PROC_BROWSER_TEST_P(BrowsingDataRemoverBrowserTestP,
                       MAYBE_WebSqlIncognitoDeletion) {
  UseIncognitoBrowser();
  TestSiteData("WebSql", GetParam());
}

// TODO(crbug.com/1317431): WebSQL does not work on Fuchsia.
#if BUILDFLAG(IS_FUCHSIA)
#define MAYBE_EmptyWebSqlDeletion DISABLED_EmptyWebSqlDeletion
#else
#define MAYBE_EmptyWebSqlDeletion EmptyWebSqlDeletion
#endif
IN_PROC_BROWSER_TEST_P(BrowsingDataRemoverBrowserTestP,
                       MAYBE_EmptyWebSqlDeletion) {
  TestEmptySiteData("WebSql", GetParam());
}

IN_PROC_BROWSER_TEST_P(BrowsingDataRemoverBrowserTestP, IndexedDbDeletion) {
  TestSiteData("IndexedDb", GetParam());
}

IN_PROC_BROWSER_TEST_P(BrowsingDataRemoverBrowserTestP,
                       IndexedDbIncognitoDeletion) {
  UseIncognitoBrowser();
  TestSiteData("IndexedDb", GetParam());
}

// Test that empty indexed dbs are deleted correctly.
IN_PROC_BROWSER_TEST_P(BrowsingDataRemoverBrowserTestP, EmptyIndexedDb) {
  TestEmptySiteData("IndexedDb", GetParam());
}

#if BUILDFLAG(ENABLE_LIBRARY_CDMS)
// Test Media Licenses by creating one and checking it is counted by the
// cookie counter. Then delete it and check that the cookie counter is back
// to zero.
IN_PROC_BROWSER_TEST_P(BrowsingDataRemoverBrowserTestP, MediaLicenseDeletion) {
  const std::string kMediaLicenseType = "MediaLicense";
  const TimeEnum delete_begin = GetParam();

  EXPECT_EQ(0, GetSiteDataCount());
  EXPECT_EQ(0, GetMediaLicenseCount());
  GURL url =
      embedded_test_server()->GetURL("/browsing_data/media_license.html");
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));

  EXPECT_EQ(0, GetSiteDataCount());
  EXPECT_EQ(0, GetMediaLicenseCount());
  ExpectCookieTreeModelCount(0);
  EXPECT_FALSE(HasDataForType(kMediaLicenseType));

  // TODO(crbug.com/1307796): Fix GetCookiesTreeModelCount() to include quota
  // nodes. `count` should be 1 here.
  int count = 0;
  SetDataForType(kMediaLicenseType);
  EXPECT_EQ(1, GetSiteDataCount());
  EXPECT_EQ(count, GetMediaLicenseCount());
  ExpectCookieTreeModelCount(count);
  EXPECT_TRUE(HasDataForType(kMediaLicenseType));

  // Try to remove the Media Licenses using a time frame up until an hour ago,
  // which should not remove the recently created Media License.
  RemoveAndWait(chrome_browsing_data_remover::DATA_TYPE_SITE_DATA, delete_begin,
                TimeEnum::kLastHour);
  EXPECT_EQ(1, GetSiteDataCount());
  EXPECT_EQ(count, GetMediaLicenseCount());
  ExpectCookieTreeModelCount(count);
  EXPECT_TRUE(HasDataForType(kMediaLicenseType));

  // Now try with a time range that includes the current time, which should
  // clear the Media License created for this test.
  RemoveAndWait(chrome_browsing_data_remover::DATA_TYPE_SITE_DATA, delete_begin,
                TimeEnum::kMax);
  EXPECT_EQ(0, GetSiteDataCount());
  EXPECT_EQ(0, GetMediaLicenseCount());
  ExpectCookieTreeModelCount(0);
  EXPECT_FALSE(HasDataForType(kMediaLicenseType));
}

// Create and save a media license (which will be deleted in the following
// test).
IN_PROC_BROWSER_TEST_F(BrowsingDataRemoverBrowserTest,
                       PRE_MediaLicenseTimedDeletion) {
  const std::string kMediaLicenseType = "MediaLicense";

  EXPECT_EQ(0, GetSiteDataCount());
  EXPECT_EQ(0, GetMediaLicenseCount());

  GURL url =
      embedded_test_server()->GetURL("/browsing_data/media_license.html");
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));

  EXPECT_EQ(0, GetSiteDataCount());
  EXPECT_EQ(0, GetMediaLicenseCount());
  ExpectCookieTreeModelCount(0);
  EXPECT_FALSE(HasDataForType(kMediaLicenseType));

  // TODO(crbug.com/1307796): Fix GetCookiesTreeModelCount() to include quota
  // nodes. `count` should be 1 here.
  int count = 0;
  SetDataForType(kMediaLicenseType);
  EXPECT_EQ(1, GetSiteDataCount());
  EXPECT_EQ(count, GetMediaLicenseCount());
  ExpectCookieTreeModelCount(count);
  EXPECT_TRUE(HasDataForType(kMediaLicenseType));
}

// Create and save a second media license, and then verify that timed deletion
// selects the correct license to delete.
IN_PROC_BROWSER_TEST_F(BrowsingDataRemoverBrowserTest,
                       MediaLicenseTimedDeletion) {
  const std::string kMediaLicenseType = "MediaLicense";

  // TODO(crbug.com/1307796): Fix GetCookiesTreeModelCount() to include quota
  // nodes. `count` should be 1 here.
  int count = 0;

  // As the PRE_ test should run first, there should be one media license
  // still stored. The time of it's creation should be sometime before
  // this test starts. We can't see the license, since it's stored for a
  // different origin (but we can delete it).
  LOG(INFO) << "MediaLicenseTimedDeletion starting @ " << kStartTime;
  EXPECT_EQ(count, GetMediaLicenseCount());

  GURL url =
      embedded_test_server()->GetURL("/browsing_data/media_license.html");
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));

#if BUILDFLAG(IS_MAC)
  // On some Macs the file system uses second granularity. So before
  // creating the second license, delay for 1 second so that the new
  // license's time is not the same second as |kStartTime|.
  base::PlatformThread::Sleep(base::Seconds(1));
#endif

  // This test should use a different domain than the PRE_ test, so there
  // should be no existing media license for it.
  // Note that checking HasDataForType() may result in an empty file being
  // created. Deleting licenses checks for any file within the time range
  // specified in order to delete all the files for the domain, so this may
  // cause problems (especially with Macs that use second granularity).
  // http://crbug.com/909829.
  EXPECT_FALSE(HasDataForType(kMediaLicenseType));

  // TODO(crbug.com/1307796): Fix GetCookiesTreeModelCount() to include quota
  // nodes. `count` should be 2 here.
  count = 0;
  // Create a media license for this domain.
  SetDataForType(kMediaLicenseType);
  EXPECT_EQ(count, GetMediaLicenseCount());
  EXPECT_TRUE(HasDataForType(kMediaLicenseType));

  // As Clear Browsing Data typically deletes recent data (e.g. last hour,
  // last day, etc.), try to remove the Media Licenses created since the
  // the start of this test, which should only delete the just created
  // media license, and leave the one created by the PRE_ test.
  RemoveAndWait(chrome_browsing_data_remover::DATA_TYPE_SITE_DATA,
                TimeEnum::kStart);
  // TODO(crbug.com/1307796): Fix GetCookiesTreeModelCount() to include quota
  // nodes. `count` should be 1 here.
  count = 0;
  EXPECT_EQ(1, GetSiteDataCount());
  EXPECT_EQ(count, GetMediaLicenseCount());
  EXPECT_FALSE(HasDataForType(kMediaLicenseType));

  // Now try with a time range that includes all time, which should
  // clear the media license created by the PRE_ test.
  RemoveAndWait(chrome_browsing_data_remover::DATA_TYPE_SITE_DATA);
  EXPECT_EQ(0, GetSiteDataCount());
  EXPECT_EQ(0, GetMediaLicenseCount());
  ExpectCookieTreeModelCount(0);
}

IN_PROC_BROWSER_TEST_F(BrowsingDataRemoverBrowserTest,
                       MediaLicenseDeletionWithFilter) {
  const std::string kMediaLicenseType = "MediaLicense";

  GURL url =
      embedded_test_server()->GetURL("/browsing_data/media_license.html");
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));

  EXPECT_EQ(0, GetMediaLicenseCount());
  EXPECT_FALSE(HasDataForType(kMediaLicenseType));

  // TODO(crbug.com/1307796): Fix GetCookiesTreeModelCount() to include quota
  // nodes. `count` should be 1 here.
  int count = 0;
  SetDataForType(kMediaLicenseType);
  EXPECT_EQ(count, GetMediaLicenseCount());
  EXPECT_TRUE(HasDataForType(kMediaLicenseType));

  // Try to remove the Media Licenses using a deletelist that doesn't include
  // the current URL. Media License should not be deleted.
  std::unique_ptr<BrowsingDataFilterBuilder> filter_builder =
      BrowsingDataFilterBuilder::Create(
          BrowsingDataFilterBuilder::Mode::kDelete);
  filter_builder->AddOrigin(
      url::Origin::CreateFromNormalizedTuple("https", "test-origin", 443));
  RemoveWithFilterAndWait(
      content::BrowsingDataRemover::DATA_TYPE_MEDIA_LICENSES,
      std::move(filter_builder));
  EXPECT_EQ(count, GetMediaLicenseCount());

  // Now try with a preservelist that includes the current URL. Media License
  // should not be deleted.
  filter_builder = BrowsingDataFilterBuilder::Create(
      BrowsingDataFilterBuilder::Mode::kPreserve);
  filter_builder->AddOrigin(url::Origin::Create(url));
  RemoveWithFilterAndWait(
      content::BrowsingDataRemover::DATA_TYPE_MEDIA_LICENSES,
      std::move(filter_builder));
  EXPECT_EQ(count, GetMediaLicenseCount());

  // Now try with a deletelist that includes the current URL. Media License
  // should be deleted this time.
  filter_builder = BrowsingDataFilterBuilder::Create(
      BrowsingDataFilterBuilder::Mode::kDelete);
  filter_builder->AddOrigin(url::Origin::Create(url));
  RemoveWithFilterAndWait(
      content::BrowsingDataRemover::DATA_TYPE_MEDIA_LICENSES,
      std::move(filter_builder));
  EXPECT_EQ(0, GetMediaLicenseCount());
}
#endif  // BUILDFLAG(ENABLE_LIBRARY_CDMS)

const std::vector<std::string> kStorageTypes{
    "Cookie",         "LocalStorage", "FileSystem",
    "SessionStorage", "IndexedDb",    "WebSql",
    "ServiceWorker",  "CacheStorage", "StorageFoundation",
    "MediaLicense"};

// Test that storage doesn't leave any traces on disk.
IN_PROC_BROWSER_TEST_F(BrowsingDataRemoverBrowserTest,
                       PRE_PRE_StorageRemovedFromDisk) {
  // Checking leveldb content fails in most cases. See
  // https://crbug.com/1238325.
  ASSERT_EQ(0, CheckUserDirectoryForString(kLocalHost, {},
                                           /*check_leveldb_content=*/false));
  ASSERT_EQ(0, GetSiteDataCount());
  ExpectCookieTreeModelCount(0);

  // To use secure-only features on a host name, we need an https server.
  net::EmbeddedTestServer https_server(net::EmbeddedTestServer::TYPE_HTTPS);
  https_server.SetSSLConfig(
      net::EmbeddedTestServer::CERT_COMMON_NAME_IS_DOMAIN);
  base::FilePath path;
  base::PathService::Get(content::DIR_TEST_DATA, &path);
  https_server.ServeFilesFromDirectory(path);
  ASSERT_TRUE(https_server.Start());

  GURL url = https_server.GetURL(kLocalHost, "/browsing_data/site_data.html");
  ASSERT_TRUE(ui_test_utils::NavigateToURL(GetBrowser(), url));

  for (const std::string& type : kStorageTypes) {
    SetDataForType(type);
    EXPECT_TRUE(HasDataForType(type));
  }
  // TODO(crbug.com/846297): Add more datatypes for testing. E.g. notifications,
  // payment handler, content settings, autofill, ...?
}

// Restart after creating the data to ensure that everything was written to
// disk.
IN_PROC_BROWSER_TEST_F(BrowsingDataRemoverBrowserTest,
                       PRE_StorageRemovedFromDisk) {
  EXPECT_EQ(1, GetSiteDataCount());
  // Expect all datatypes from above except SessionStorage, NativeIO, and
  // possibly MediaLicense. SessionStorage is not supported by the
  // CookieTreeModel yet. NativeIO is shown as FileSystem in the CookieTree
  // model. MediaLicense is integrated into the quota node, which is not yet
  // fully hooked into CookieTreeModel (see crbug.com/1307796).
  ExpectCookieTreeModelCount(kStorageTypes.size() - 3);
  RemoveAndWait(chrome_browsing_data_remover::DATA_TYPE_SITE_DATA |
                content::BrowsingDataRemover::DATA_TYPE_CACHE |
                chrome_browsing_data_remover::DATA_TYPE_HISTORY |
                chrome_browsing_data_remover::DATA_TYPE_CONTENT_SETTINGS);
  EXPECT_EQ(0, GetSiteDataCount());
  ExpectCookieTreeModelCount(0);
}

// TODO(crbug.com/1317431): WebSQL does not work on Fuchsia.
#if BUILDFLAG(IS_FUCHSIA)
#define MAYBE_StorageRemovedFromDisk DISABLED_StorageRemovedFromDisk
#else
#define MAYBE_StorageRemovedFromDisk StorageRemovedFromDisk
#endif
// Check if any data remains after a deletion and a Chrome restart to force
// all writes to be finished.
IN_PROC_BROWSER_TEST_F(BrowsingDataRemoverBrowserTest,
                       MAYBE_StorageRemovedFromDisk) {
  // Deletions should remove all traces of browsing data from disk
  // but there are a few bugs that need to be fixed.
  // Any addition to this list must have an associated TODO().
  static const std::vector<std::string> ignore_file_patterns = {
#if BUILDFLAG(IS_CHROMEOS_ASH)
    // TODO(crbug.com/846297): Many leveldb files remain on ChromeOS. I couldn't
    // reproduce this in manual testing, so it might be a timing issue when
    // Chrome is closed after the second test?
    "[0-9]{6}",
#endif
  };
  int found = CheckUserDirectoryForString(kLocalHost, ignore_file_patterns,
                                          /*check_leveldb_content=*/false);
  EXPECT_EQ(0, found) << "A non-ignored file contains the hostname.";
}

const std::vector<std::string> kSessionOnlyStorageTestTypes{
    "Cookie",    "LocalStorage", "FileSystem",    "SessionStorage",
    "IndexedDb", "WebSql",       "ServiceWorker", "CacheStorage",
};

// Test that storage gets deleted if marked as SessionOnly.
IN_PROC_BROWSER_TEST_F(BrowsingDataRemoverBrowserTest,
                       PRE_SessionOnlyStorageRemoved) {
  ExpectCookieTreeModelCount(0);
  GURL url = embedded_test_server()->GetURL("/browsing_data/site_data.html");
  ASSERT_TRUE(ui_test_utils::NavigateToURL(GetBrowser(), url));

  for (const std::string& type : kSessionOnlyStorageTestTypes) {
    SetDataForType(type);
    EXPECT_TRUE(HasDataForType(type));
  }
  // Expect the datatypes from above except SessionStorage. SessionStorage is
  // not supported by the CookieTreeModel yet.
  ExpectCookieTreeModelCount(kSessionOnlyStorageTestTypes.size() - 1);
  HostContentSettingsMapFactory::GetForProfile(GetBrowser()->profile())
      ->SetDefaultContentSetting(ContentSettingsType::COOKIES,
                                 CONTENT_SETTING_SESSION_ONLY);
}

// TODO(crbug.com/1317431): WebSQL does not work on Fuchsia.
#if BUILDFLAG(IS_FUCHSIA)
#define MAYBE_SessionOnlyStorageRemoved DISABLED_SessionOnlyStorageRemoved
#else
#define MAYBE_SessionOnlyStorageRemoved SessionOnlyStorageRemoved
#endif
// Restart to delete session only storage.
IN_PROC_BROWSER_TEST_F(BrowsingDataRemoverBrowserTest,
                       MAYBE_SessionOnlyStorageRemoved) {
  // All cookies should have been deleted.
  ExpectCookieTreeModelCount(0);
  GURL url = embedded_test_server()->GetURL("/browsing_data/site_data.html");
  ASSERT_TRUE(ui_test_utils::NavigateToURL(GetBrowser(), url));
  for (const std::string& type : kSessionOnlyStorageTestTypes) {
    EXPECT_FALSE(HasDataForType(type));
  }
}

#if BUILDFLAG(IS_CHROMEOS_ASH)
// Test that removing passwords, when System-proxy is enabled on Chrome OS,
// sends a request to System-proxy to clear the cached user credentials.
IN_PROC_BROWSER_TEST_F(BrowsingDataRemoverBrowserTest,
                       SystemProxyClearsUserCredentials) {
  ash::SystemProxyManager::Get()->SetSystemProxyEnabledForTest(true);
  EXPECT_EQ(0, ash::SystemProxyClient::Get()
                   ->GetTestInterface()
                   ->GetClearUserCredentialsCount());
  RemoveAndWait(chrome_browsing_data_remover::DATA_TYPE_PASSWORDS);

  EXPECT_EQ(1, ash::SystemProxyClient::Get()
                   ->GetTestInterface()
                   ->GetClearUserCredentialsCount());
}
#endif  // BUILDFLAG(IS_CHROMEOS_ASH)

// Some storage backend use a different code path for full deletions and
// partial deletions, so we need to test both.
INSTANTIATE_TEST_SUITE_P(All,
                         BrowsingDataRemoverBrowserTestP,
                         ::testing::Values(TimeEnum::kDefault,
                                           TimeEnum::kLastHour));


text:
layout: default
---

        {{ content }}

    {% assign a = page.collection %}
    {% assign x = site.[a] | group_by: 'tag' | sort: 'level', 'last' %}

      {% for a_grouped in x %}

      {% capture name %}{{ a_grouped.name }}{% endcapture %}

      {% for p in a_grouped.items limit: 1 %}
      {% if p.image_path != null %}

      {% endif %}
      {% endfor %}
      {% endfor %}

    {% if site.data.materials[a] %}

      {% for data in site.data.materials[a] %}

        {{ data.title | upcase }}

      {% for q in data.docs %}

        {% if q.image_path %}

        {% else %}
        {{ page.icon }}
        {% endif %}
        {{ q.title }}

{{ q.description }}

      {% endfor %}

      {% endfor %}

    {% endif %}

redpajama_set_name: RedPajamaGithub


text:
---
layout:     post
title:      "(转)ReactNativeAndroid源码分析-Js如何调用Native的代码"
subtitle:   ""
date:       2016-01-02 12:00:00
author:     "hi大头鬼hi"
catalog:    true
tags:
    - react-native
---

> 著作权归作者所有。
>
> 商业转载请联系作者获得授权,非商业转载请注明出处。
>
> 作者:hi大头鬼hi
>
> 链接:http://zhuanlan.zhihu.com/program-life/20464825
>
> 来源:知乎

先来看一个简单的demo,如何在RN中调用Android原生的的Toast模块。

```js
var React = require('react-native');
var {
  ToastAndroid,
} = React;
...
ToastAndroid.show('This is a toast with short duration', ToastAndroid.SHORT)
```

index.android.js是ReactNative的入口文件,后缀Android表示是在Android平台使用的代码。ReactNative内置了babel,所以可以使用最新的JavaScript语法来开发(ECMAScript6简称es6),不熟悉es6的同学可以看看阮一峰写的这本[e6入门教程](http://es6.ruanyifeng.com/)。这里我简单介绍一下require,Android程序员可以把require对应到Java的import,使用来导入一个JavaScript模块的。`var {ToastAndroid} = React`这种写法叫结构赋值,就是从React这个对象中,提取出ToastAndroid这个属性所对应的值,并赋值给ToastAndroid这个变量。可以看出toast模块就是从react-native这个模块中的ToastAndroid属性,js中的Toast模块API和Android中的JavaAPI基本是保持一致的。我们来看看`react-native.js`中,是如何定义ToastAndroid这个属性的

```js
var ReactNative = Object.assign(Object.create(require('React')), {
  ...
  ToastAndroid: require('ToastAndroid'),
});
module.exports = ReactNative;
```

react-native.js其实就是声明了ReactNative提供的可以在js中使用的各种模块。这里我们拿ToastAndroid这个模块来分析RN中js和native的通讯。ToastAndroid模块的源代码在ToastAndroid.android.js中。

```js
var RCTToastAndroid = require('NativeModules').ToastAndroid;

var ToastAndroid = {

  SHORT: RCTToastAndroid.SHORT,
  LONG: RCTToastAndroid.LONG,

  show: function (
    message: string,
    duration: number
  ): void {
    RCTToastAndroid.show(message, duration);
  },

};

module.exports = ToastAndroid;
```

可以看到show方法其实就是调用了RCTToastAndroid.show(message, duration),而RCTToastAndroid又是定义在NativeModules中的。

> NativeModules.js

```js
var NativeModules = require('BatchedBridge').RemoteModules;

var nativeModulePrefixNormalizer = require('nativeModulePrefixNormalizer');

nativeModulePrefixNormalizer(NativeModules);

module.exports = NativeModules;
```

最终,所有的模块都是来自BatchedBridge

> BatchedBridge.js

```js
let MessageQueue = require('MessageQueue');

let BatchedBridge = new MessageQueue(
  __fbBatchedBridgeConfig.remoteModuleConfig,
  __fbBatchedBridgeConfig.localModulesConfig,
);

module.exports = BatchedBridge;
```

BatchedBridge.js中做的事情就是构造一个MessageQueue对象。
*注意: 我们最开始的ToastAndroid.show方法其实最终调用的是MessageQueue.ToastAndroid.show方法。*

检查MessageQueue源码,其实你是找不到ToastAndroid属性的声明的,因为这个属性是动态生成的,下面我们就看看这个方法是怎么生成,又是怎么调用的吧。在分析MessageQueue源码之前,先大概说一下构造函数中传入的两个参数

```js
__fbBatchedBridgeConfig.remoteModuleConfig,
__fbBatchedBridgeConfig.localModulesConfig,
```

__fbBatchedBridgeConfig是一个全局js变量,它是在CatalystInstance.java中声明赋值的,通过调用ReactBridge.setGlobalVariable方法。setGlobalVariable是在Jni中声明的方法,最终会调用JavaScriptCore,把Java中定义的JSON字符串,赋值给js的全局对象__fbBatchedBridgeConfig,这个对象会有两个属性remoteModuleConfig和localModulesConfig。

```java
private void initializeBridge(
    JavaScriptExecutor jsExecutor,
    NativeModuleRegistry registry,
    JavaScriptModulesConfig jsModulesConfig,
    JSBundleLoader jsBundleLoader) {
  mCatalystQueueConfiguration.getJSQueueThread().assertIsOnThread();
  Assertions.assertCondition(mBridge == null, "initializeBridge should be called once");
  mBridge = new ReactBridge(
      jsExecutor,
      new NativeModulesReactCallback(),
      mCatalystQueueConfiguration.getNativeModulesQueueThread());
  mBridge.setGlobalVariable(
      "__fbBatchedBridgeConfig",
      buildModulesConfigJSONProperty(registry, jsModulesConfig));
  jsBundleLoader.loadScript(mBridge);
}
```

__fbBatchedBridgeConfig.remoteModuleConfig代表的是Java中定义的一些模块,这些模块可以在js中被调用。
__fbBatchedBridgeConfig.remoteModuleConfig的格式大概如下:

```json
{
  "remoteModuleConfig": {
    "Logger": {
      "constants": { /* If we had exported constants... */ },
      "moduleID": 1,
      "methods": {
        "requestPermissions": {
          "type": "remote",
          "methodID": 1
        }
      }
    }
  }
}
{
  'ToastAndroid': {
    moduleId: 0,
    methods: {
      'show': {
        methodID: 0
      }
    },
    constants: {
      'SHORT': '0',
      'LONG': '1'
    }
  },
  'moduleB': {
    moduleId: 0,
    methods: {
      'method1': {
        methodID: 0
      }
    },
    'key1': 'value1',
    'key2': 'value2'
  }
}
```

至于这里的数据是如何生成的,我后面会再写一篇文章分析,这里我们先知道它的格式,以方便我们梳理js代码。
好了,我们回过头来继续分析MessageQueue。MessageQueue构造函数中首先定义了一些实例变量,下面的代码中,我加入了一些注释,来解释这些变量的作用,注释里面的js module指的是只在js中定义的模块,native module指的是在native(这里就是Java)层定义的模块,这些模块都可以在js中使用。

```js
this.RemoteModules = {};//存储最终生成的各个模块信息,包含模块名,模块中的方法,常量等信息
this._require = customRequire || require;//用于加载模块的函数
this._queue = [[],[],[]];//队列,用于存放调用的模块,方法和参数信息,分别存储在第一二三个数组中
this._moduleTable = {};//moduleId查找moduleName的map,用于js module
this._methodTable = {};//methodId查找methodName的map,用于js module
this._callbacks = [];//回调函数数组,和queue一一对应,每个queue中调用的方法,如果有回调函数,那么就在这个数组的对应坐标上
this._callbackID = 0;//回调函数的id,自增

this._genModules(remoteModules);
localModules && this._genLookupTables(
  localModules, this._moduleTable, this._methodTable);

this._debugInfo = {};//放置一些debug相关的信息,主要是调用模块,函数,参数的信息
this._remoteModuleTable = {};//moduleId查找moduleName的map,用于native module
this._remoteMethodTable = {};//methodId查找methodName的map,用于native module

this._genLookupTables(
  remoteModules, this._remoteModuleTable, this._remoteMethodTable);
```

我们来看一下_genModules方法

```js
_genModules(remoteModules) {
  let moduleNames = Object.keys(remoteModules);
  for (var i = 0, l = moduleNames.length; i < l; i++) {
    let moduleName = moduleNames[i];
    let moduleConfig = remoteModules[moduleName];
    this.RemoteModules[moduleName] = this._genModule({}, moduleConfig);
  }
}
```

遍历传过来的remoteModules所有的key,得到moduleName,然后针对每个module调用_genModule方法

```js
_genModule(module, moduleConfig) {
  let methodNames = Object.keys(moduleConfig.methods);
  for (var i = 0, l = methodNames.length; i < l; i++) {
    let methodName = methodNames[i];
    let methodConfig = moduleConfig.methods[methodName];
    module[methodName] = this._genMethod(
      moduleConfig.moduleID, methodConfig.methodID, methodConfig.type);
  }
  Object.assign(module, moduleConfig.constants);
  return module;
}
```

_genModule方法和_genModules方法类似,遍历module下面的所有的方法,对每个方法,调用_genMethod方法。

```js
_genMethod(module, method, type) {
  ...
  fn = function(...args) {
    let lastArg = args.length > 0 ? args[args.length - 1] : null;
    let secondLastArg = args.length > 1 ? args[args.length - 2] : null;
    let hasSuccCB = typeof lastArg === 'function';
    let hasErrorCB = typeof secondLastArg === 'function';
    hasErrorCB && invariant(
      hasSuccCB,
      'Cannot have a non-function arg after a function arg.'
    );
    let numCBs = hasSuccCB + hasErrorCB;
    let onSucc = hasSuccCB ? lastArg : null;
    let onFail = hasErrorCB ? secondLastArg : null;
    args = args.slice(0, args.length - numCBs);
    return self.__nativeCall(module, method, args, onFail, onSucc);
  };
}
```

_genModule分两种情况,根据type来,如果是remoteAsync类型,其实就是异步方法,就用promise来包装一下,我们先不看这种情况,来看看普通的同步方法是如何处理的。
处理部分也比较简单,就是处理一下参数,把onFail和onSucc回调函数从参数中取出来,再调用__nativeCall方法。

```js
__nativeCall(module, method, params, onFail, onSucc) {
  if (onFail || onSucc) {
    // eventually delete old debug info
    (this._callbackID > (1 << 5)) &&
      (this._debugInfo[this._callbackID >> 5] = null);

    this._debugInfo[this._callbackID >> 1] = [module, method];
    onFail && params.push(this._callbackID);
    this._callbacks[this._callbackID++] = onFail;
    onSucc && params.push(this._callbackID);
    this._callbacks[this._callbackID++] = onSucc;
  }
  this._queue[MODULE_IDS].push(module);
  this._queue[METHOD_IDS].push(method);
  this._queue[PARAMS].push(params);

  var now = new Date().getTime();
  if (global.nativeFlushQueueImmediate &&
      now - this._lastFlush >= MIN_TIME_BETWEEN_FLUSHES_MS) {
    global.nativeFlushQueueImmediate(this._queue);
    this._queue = [[],[],[]];
    this._lastFlush = now;
  }

  if (__DEV__ && SPY_MODE && isFinite(module)) {
    console.log('JS->N : ' + this._remoteModuleTable[module] + '.' +
      this._remoteMethodTable[module][method] + '(' + JSON.stringify(params) + ')');
  }
}
```

__nativeCall方法首先检查是否有onFail和onSucc,如果有的话就压入_callbacks栈中,同时把_callbackID存入参数中接着可以看到,_queue其实被当做了三个栈来使用,分别压入模块名,方法名和参数信息。到这里MessageQueue的构造函数就分析的差不多了,那么我们最开始的ToastAndroid.show(message, duration);方法调用,其实就是往_queue栈中压入了一些信息而已,那么最终是怎么在Java层调用到Android原生的Toast模块的呢?这里的关键就是__nativeCall中调用的nativeFlushQueueImmediate方法,这个方法其实C++代码中注入到Js的一个全局变量,具体怎么注入的,就是在`JSCExecutor.cpp`中调用installGlobalFunction,installGlobalFunction的是通过JavaScriptCore的API来实现让Js可以调用C++代码的。

```js
installGlobalFunction(m_context, "nativeFlushQueueImmediate", nativeFlushQueueImmediate);
installGlobalFunction(m_context, "nativeLoggingHook", nativeLoggingHook);
installGlobalFunction(m_context, "nativePerformanceNow", nativePerformanceNow);
```

nativeFlushQueueImmediate其实又调用了`JSCExecutor.cpp`中的flushQueueImmediate方法

> JSCExecutor.cpp

```c++
void JSCExecutor::flushQueueImmediate(std::string queueJSON) {
  m_flushImmediateCallback(queueJSON);
}
```

其中,m_flushImmediateCallback是在JSCExecutor的构造函数中初始化

```c++
JSCExecutor::JSCExecutor(FlushImmediateCallback cb) :
    m_flushImmediateCallback(cb) {
```

那么JSCExecutor对象又是在哪了被创建出来的呢?RN中是通过JSCExecutorFactory这个工厂的createJSExecutor方法来创建JSCExecutor对象的,而这个方法的实现刚好就在`JSCExecutor.cpp`中

```c++
std::unique_ptr<JSExecutor> JSCExecutorFactory::createJSExecutor(FlushImmediateCallback cb) {
  return std::unique_ptr<JSExecutor>(new JSCExecutor(cb));
}
```

接下来问题又来了,又是谁调用了JSCExecutorFactory.createJSExecutor呢?答案就是`Bridge.cpp`,`Bridge.cpp`是RN的Jni层的入口,Java层大部分调用的Jni函数都是在这个文件中定义的。

> Bridge.cpp

```c++
Bridge::Bridge(const RefPtr<JSExecutorFactory>& jsExecutorFactory, Callback callback) :
  m_threadState.reset(new JSThreadState(jsExecutorFactory, std::move(proxyCallback)));

JSThreadState(const RefPtr<JSExecutorFactory>& jsExecutorFactory, Bridge::Callback&& callback) :
    m_callback(callback)
  {
    m_jsExecutor = jsExecutorFactory->createJSExecutor([this, callback] (std::string queueJSON) {
      m_callback(parseMethodCalls(queueJSON), false /* = isEndOfBatch */);
    });
  }
```

`ReactBridge.java`回去调用jni中注册的initialize方法。RN所有jni中注册的方法,都在`OnLoad.cpp`

```c++
registerNatives("com/facebook/react/bridge/ReactBridge", {
    makeNativeMethod("initialize", "(Lcom/facebook/react/bridge/JavaScriptExecutor;Lcom/facebook/react/bridge/ReactCallback;Lcom/facebook/react/bridge/queue/MessageQueueThread;)V", bridge::create),
    makeNativeMethod(
      "loadScriptFromAssets", "(Landroid/content/res/AssetManager;Ljava/lang/String;)V",
      bridge::loadScriptFromAssets),
    makeNativeMethod("loadScriptFromFile", bridge::loadScriptFromFile),
    makeNativeMethod("callFunction", bridge::callFunction),
    makeNativeMethod("invokeCallback", bridge::invokeCallback),
    makeNativeMethod("setGlobalVariable", bridge::setGlobalVariable),
    makeNativeMethod("supportsProfiling", bridge::supportsProfiling),
    makeNativeMethod("startProfiler", bridge::startProfiler),
    makeNativeMethod("stopProfiler", bridge::stopProfiler),
    makeNativeMethod("handleMemoryPressureModerate", bridge::handleMemoryPressureModerate),
    makeNativeMethod("handleMemoryPressureCritical", bridge::handleMemoryPressureCritical),

});
```

可以看到initialize方法其实就是OnLoad.cpp中的bridge这个namespace下得create方法

```c++
static void create(JNIEnv* env, jobject obj, jobject executor, jobject callback,
                   jobject callbackQueueThread) {
  auto weakCallback = createNew<WeakReference>(callback);
  auto weakCallbackQueueThread = createNew<WeakReference>(callbackQueueThread);
  auto bridgeCallback = [weakCallback, weakCallbackQueueThread] (std::vector<MethodCall> calls, bool isEndOfBatch) {
    dispatchCallbacksToJava(weakCallback, weakCallbackQueueThread, std::move(calls), isEndOfBatch);
  };
  auto nativeExecutorFactory = extractRefPtr<JSExecutorFactory>(env, executor);
  auto bridge = createNew<Bridge>(nativeExecutorFactory, bridgeCallback);
  setCountableForJava(env, obj, std::move(bridge));
}
```

这里调用了Bridge类的构造函数,而Bridge的构造函数中,回去构造一个JSThreadState对象,Bridge所有的API调用都会委托给这个创建的JSThreadState对象。

```c++
Bridge::Bridge(const RefPtr<JSExecutorFactory>& jsExecutorFactory, Callback callback) :
  m_callback(callback),
  m_destroyed(std::shared_ptr<bool>(new bool(false)))
{
  auto destroyed = m_destroyed;
  auto proxyCallback = [this, destroyed] (std::vector<MethodCall> calls, bool isEndOfBatch) {
    if (*destroyed) {
      return;
    }
    m_callback(std::move(calls), isEndOfBatch);
  };
  m_threadState.reset(new JSThreadState(jsExecutorFactory, std::move(proxyCallback)));
}
```

JSThreadState的构造函数中,会去创建一个JSCExecutor类的对象

```c++
JSThreadState(const RefPtr<JSExecutorFactory>& jsExecutorFactory, Bridge::Callback&& callback) :
    m_callback(callback)
{
  m_jsExecutor = jsExecutorFactory->createJSExecutor([this, callback] (std::string queueJSON) {
    m_callback(parseMethodCalls(queueJSON), false /* = isEndOfBatch */);
  });
}
```

这里createJSExecutor方法中,传递的是一个C++的函数(猜测是CPP新规范中的lambda的写法,我不熟悉,欢迎指导),这个函数会被赋值给m_flushImmediateCallback成员变量。

找到了创建JSCExecutor对象的地方了,回到刚才,我们说,所有的Js调用Native Module的API的时候,都会调用flushQueueImmediate方法,而flushQueueImmediate方法中会去调用m_flushImmediateCallback函数。

```c++
JSCExecutor::JSCExecutor(FlushImmediateCallback cb) :
    m_flushImmediateCallback(cb) {

std::unique_ptr<JSExecutor> JSCExecutorFactory::createJSExecutor(FlushImmediateCallback cb) {
  return std::unique_ptr<JSExecutor>(new JSCExecutor(cb));
}
```

也就是说,Js层所有的API调用,都会走到这个m_flushImmediateCallback函数的调用中,这个函数是实现Js和Native通讯的核心。而这里的m_flushImmediateCallback在CPP层,最终是在`OnLoad.cpp`的bridge::create方法中传入的,这个create方法又是由Java层来调用的,`Bridge.java`的构造函数中会调用这个create方法,所以说,Js层调用Native API的时候,最终就是调用了`Bridge.java`中传递过来的ReactCallback对象

```java
public ReactBridge(
      JavaScriptExecutor jsExecutor,
      ReactCallback callback,
      MessageQueueThread nativeModulesQueueThread) {
        .....
      }
```

ReactBridge对象的创建,是在`CatalystInstanceImpl.java`中

```java
private ReactBridge initializeBridge
  bridge = new ReactBridge(
            jsExecutor,
            new NativeModulesReactCallback(),
            mCatalystQueueConfiguration.getNativeModulesQueueThread());

  private class NativeModulesReactCallback implements ReactCallback
      public void call(int moduleId, int methodId, ReadableNativeArray parameters) {
        mJavaRegistry.call(CatalystInstanceImpl.this, moduleId, methodId, parameters);
    }
```

这里传递的是ReactCallback类的子类`NativeModulesReactCallback`,最终调用的是call方法,而call方法又调用了mJavaRegistry.call方法。看到mJavaRegistry,开发过RN应用的同学应该感到很熟悉了,对的,这里就是对应我们在应用启动的时候注册的NativeModule对象。

> NativeModuleRegistry.Java

```java
/* package */ void call(
    CatalystInstance catalystInstance,
    int moduleId,
    int methodId,
    ReadableNativeArray parameters) {
  ModuleDefinition definition = mModuleTable.get(moduleId);
  if (definition == null) {
    throw new RuntimeException("Call to unknown module: " + moduleId);
  }
  definition.call(catalystInstance, methodId, parameters);
}
```

我们在RN中注册NativeModule是通过add一个ReactPackage对象来实现,家下来我们看一下,RN是如何把我们注册的各个module添加到NativeModuleRegistry中的。

```java
mReactInstanceManager = ReactInstanceManager.builder()
                .addPackage(new MainReactPackage())
                .build();
```

ReactInstanceManager.Builder的build方法,会new一个ReactInstanceManagerImpl对象,把我们的ReactPackage对象传递过去。ReactInstanceManagerImpl类的核心就是createReactContext方法,createReactContext会首先遍历所有注册的ReactPackage,对所有的NativeModule,构造一个ModuleDefinition对象,保存到nativeModuleRegistry对象的mModuleTable中。最后,createReactContext会通过CatalystInstanceImpl.Builder构造一个CatalystInstance对象,并把包含各个NativeModule信息的nativeModuleRegistry对象传递过去。具体的模块的注册过程,我后面再写一篇单独的博客介绍,此处就不啰嗦了。

```java
private ReactApplicationContext createReactContext(JavaScriptExecutor jsExecutor, JSBundleLoader jsBundleLoader) {
    ...
    for (ReactPackage reactPackage : mPackages) {
      processPackage(reactPackage, reactContext, nativeRegistryBuilder, jsModulesBuilder);
    }
    ...
    CatalystInstanceImpl.Builder catalystInstanceBuilder = new CatalystInstanceImpl.Builder()
      .setCatalystQueueConfigurationSpec(CatalystQueueConfigurationSpec.createDefault())
      .setJSExecutor(jsExecutor)
      .setRegistry(nativeModuleRegistry)
      .setJSModulesConfig(javaScriptModulesConfig)
      .setJSBundleLoader(jsBundleLoader)
      .setNativeModuleCallExceptionHandler(exceptionHandler);
}
```

回到我们最开始的例子中,我们要在Js中使用Toast,那么我们的MainReactPackage中,就需要构造一个ToastModule,来注册给RN,这样才可以给Js调用。

```java
public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
    return Arrays.<NativeModule>asList(
      new ToastModule(reactContext));
  }
```

需要注意的是,所有要给Js中使用的模块,都需要在Native这边注册一个对应的Module,具体的规范,请参考RN的官方文档,我就不啰嗦了。

最终我们在Js中调用ToastAndroid这个模块的流程就是,Js调用会调用到C++中m_flushImmediateCallback函数,参数就是Js函数的参数加上调用的模块名构成的一个JSON字符串。C++中,有一个parseMethodCalls方法,会从Js传递的JSON中,解析出moduleName,function name,参数等一些列信息,然后C++层会调用Java层的ReactCallback类,Java代码中,会根据传递来的ModuleName functionName找到对应的模块中的方法,然后通过反射执行这些方法,并把参数传递过去。这样就完成了Js对Native代码的调用。


text:
package net.ripe.db.whois.common.dao;

import net.ripe.db.whois.common.domain.CIString;
import net.ripe.db.whois.common.rpsl.AttributeType;
import net.ripe.db.whois.common.rpsl.ObjectType;
import net.ripe.db.whois.common.rpsl.RpslAttribute;
import net.ripe.db.whois.common.rpsl.RpslObject;

import javax.annotation.CheckForNull;
import java.util.Map;
import java.util.Set;

public interface RpslObjectUpdateDao {

    RpslObjectUpdateInfo lookupObject(ObjectType type, String pkey);

    RpslObjectUpdateInfo deleteObject(int objectId, String pkey);

    RpslObjectUpdateInfo undeleteObject(int objectId);

    RpslObjectUpdateInfo updateObject(int objectId, RpslObject object);

    RpslObjectUpdateInfo createObject(RpslObject object);

    boolean isReferenced(RpslObject object);

    Set<RpslObjectInfo> getReferences(RpslObject object);

    Map<RpslAttribute, Set<CIString>> getInvalidReferences(RpslObject object);

    @CheckForNull
    RpslObjectInfo getAttributeReference(AttributeType attributeType, CIString keyValue);
}


text:
#ifndef PatchVerificationDialogHandler_H
#define PatchVerificationDialogHandler_H

#include "PatchVerificationDialog.h"

class PatchVerificationDialogHandlerParent : public QObject
{
Q_OBJECT

signals:
  void signal_RunDialog(Node* selectedNode);

public slots:
  /** This function is implemented in DialogHandler. */
  virtual void slot_RunDialog(Node* selectedNode) = 0;

public:
  PatchVerificationDialogHandlerParent()
  {
    /** When this class' signal is emitted, it should call its own slot. */
    connect(this, SIGNAL( signal_RunDialog(Node*) ), this, SLOT( slot_RunDialog(Node*) ),
            Qt::BlockingQueuedConnection);
  }

  /** This function should be called from a different thread.
    * It emits a signal that calls its own slot.
    */
  void EmitSignal(Node* selectedNode)
  {
    emit signal_RunDialog(selectedNode);
  }

};

template <typename TImage>
class PatchVerificationDialogHandler : public PatchVerificationDialogHandlerParent
{
  const TImage* Image;
  const Mask* MaskImage;
  unsigned int PatchHalfWidth;
  Node SourceNode;
  Node QueryNode;

  PatchVerificationDialog<TImage>* TopPatchVerifier = nullptr;

public:
  PatchVerificationDialogHandler(const TImage* image, const Mask* mask, const unsigned int patchHalfWidth,
                QWidget* parent = nullptr) :
    PatchVerificationDialogHandlerParent(),
    Image(image), MaskImage(mask), PatchHalfWidth(patchHalfWidth)
  {
    this->TopPatchVerifier =
        new PatchVerificationDialog<TImage>(this->Image, this->MaskImage, this->PatchHalfWidth, parent);
  }

  PatchVerificationDialog<TImage>* GetTopPatchVerifier()
  {
    return this->TopPatchVerifier;
  }

  ~PatchVerificationDialogHandler()
  {
    delete this->TopPatchVerifier;
  }

  /** Give the handler the query node that it will pass to the GUI object. */
  void SetQueryNode(Node queryNode)
  {
    this->QueryNode = queryNode;
  }

  /** Give the handler the source nodes that it will pass to the GUI object. */
  void SetSourceNode(Node& sourceNode)
  {
    this->SourceNode = sourceNode;
  }

  /** This function should only be called from a QueuedConnection. As long as DialogHandler
   * is in the GUI thread and it is called in this way, Qt is happy. */
  void slot_RunDialog(Node* selectedNode) override
  {
    this->TopPatchVerifier->SetQueryNode(this->QueryNode);
    this->TopPatchVerifier->SetSourceNode(this->SourceNode);
    this->TopPatchVerifier->PositionNextToParent();
    this->TopPatchVerifier->exec();

    *selectedNode = this->TopPatchVerifier->GetSelectedNode();
  }
};

#endif // PatchVerificationDialogHandler_H


text:
'''
   Copyright 2016 BroEZ

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
'''
import sys
import macholibre

import os
from tempfile import mkstemp

def META_MACHO(s, buff):
    tmpfd, tmpfile = mkstemp()
    tmpf = os.fdopen(tmpfd, 'wb')

    try:
        #Writing the buffer to a temp file
        tmpf.write(buff)
        tmpf.close()
        dictionary = macholibre.parse(tmpfile)
    finally:
        #Remove it to save space
        os.remove(tmpfile)

    if dictionary.has_key('name'):
        #The name doesn't make sense with the temp file
        dictionary.pop('name')

    # META_BASIC_INFO already hs this informaton
    if dictionary.has_key('hashes'):
        dictionary.pop('hashes')
    if dictionary.has_key('size'):
        dictionary.pop('size')
    dictionary['architecutures'] = []

    #Macholibre either has macho or universal
    if dictionary.has_key('macho'):
        popMachoKeys(dictionary['macho'])
        dictionary['Universal'] = False
        macho = dictionary.pop('macho')
        #I need it twice so there's no point in searching

        # Makes the key Macho + the cputype from the macho dictionary, if it has that key. Also replaces spaces with '_'
        machoKey = "macho_" + macho['cputype'].replace(' ', '_') if macho.has_key('cputype') else 'macho'
        dictionary['machos'] = [{machoKey: macho}]
        dictionary['architecutures'].append(macho['subtype'] if macho.has_key('subtype') else '')

        del macho, machoKey

    elif dictionary.has_key('universal'):
        #Universal has embedded machos
        if dictionary['universal'].has_key('machos'):
            dictionary['Universal'] = True
            dictionary['machos'] = []

            for index, macho in enumerate(dictionary['universal']['machos']):
                popMachoKeys(macho)
                hasCPU = macho.has_key('cputype')
                # Does the same thing but make sure not to overwrite the indexes if neither has 'cputype' as a key
                machoKey = "macho_" + macho['cputype'].replace(' ', '_') if hasCPU else 'macho_' + str(index)
                if macho.has_key('subtype'):
                    dictionary['architecutures'].append(macho['subtype'])
                dictionary['machos'].append({machoKey: macho})
            dictionary.pop('universal')

    return dictionary

def popMachoKeys(macho):
    #Keys to keep to prevent too much printout (These can be added and removed by just adding them to the list)
    keepKeys = ['filetype', 'signature', 'flags', 'offset', 'cputype', 'minos', 'dylibs', 'subtype']
    for key in macho.keys():
        if (key not in keepKeys):
            macho.pop(key)

if __name__ == '__main__':
    print(META_MACHO(None, sys.stdin.read()))


text:
define('helper/UrlHelper', function() {

  var isDev =
    document.URL.indexOf('localhost') !== -1 ||
    document.URL.indexOf('debug') !== -1;

  return {
    isDev: function() {
      return isDev;
    }
  };

});


text:
package com.izmailoff.mm.service

import akka.actor.{ActorRef, ActorSystem, Props}

trait TestMqttMongoServiceImpl
  extends MqttMongoService
  with TestMqttIntermediary
  with TestMongoDbProviderImpl {}

trait TestMqttIntermediary {
  def system: ActorSystem

  def startMqttIntermediary(): ActorRef =
    system.actorOf(Props[TestMqttIntermediaryActor])
}


text:
require File.dirname(__FILE__) + '/../test_helper'

class TestRemotes < Test::Unit::TestCase
  def setup
    set_file_paths
  end

  def test_remote_fun
    in_temp_dir do |path|
      loc = Git.clone(@wbare, 'local')
      rem = Git.clone(@wbare, 'remote')

      r = loc.add_remote('testrem', rem)

      Dir.chdir('remote') do
        new_file('test-file1', 'blahblahblah1')
        rem.add
        rem.commit('master commit')

        rem.branch('testbranch').in_branch('tb commit') do
          new_file('test-file3', 'blahblahblah3')
          rem.add
          true
        end
      end
      assert(!loc.status['test-file1'])
      assert(!loc.status['test-file3'])

      r.fetch
      r.merge
      assert(loc.status['test-file1'])

      loc.merge(loc.remote('testrem').branch('testbranch'))
      assert(loc.status['test-file3'])

      #puts loc.remotes.map { |r| r.to_s }.inspect

      #r.remove
      #puts loc.remotes.inspect
    end
  end

  def test_push
    in_temp_dir do |path|
      loc = Git.clone(@wbare, 'local')
      rem = Git.clone(@wbare, 'remote')
      rem.config('receive.denyCurrentBranch', 'ignore')

      r = loc.add_remote('testrem', rem)

      loc.chdir do
        new_file('test-file1', 'blahblahblah1')
        loc.add
        loc.commit('master commit')
        loc.add_tag('test-tag')

        loc.branch('testbranch').in_branch('tb commit') do
          new_file('test-file3', 'blahblahblah3')
          loc.add
          true
        end
      end
      assert(!rem.status['test-file1'])
      assert(!rem.status['test-file3'])

      loc.push('testrem')

      assert(rem.status['test-file1'])
      assert(!rem.status['test-file3'])
      assert_raise Git::GitTagNameDoesNotExist do
        rem.tag('test-tag')
      end

      loc.push('testrem', 'testbranch', true)

      rem.checkout('testbranch')
      assert(rem.status['test-file1'])
      assert(rem.status['test-file3'])
      assert(rem.tag('test-tag'))
    end
  end

end


text:
package com.amazonaws.services.codedeploy.model;

import java.io.Serializable;
import com.amazonaws.AmazonWebServiceRequest;

/**
 *

 * Represents the input of a batch get deployments operation.
 *

 */
public class BatchGetDeploymentsRequest extends AmazonWebServiceRequest
        implements Serializable, Cloneable {

    /**
     *

     * A list of deployment IDs, with multiple deployment IDs separated by
     * spaces.
     *

     */
    private com.amazonaws.internal.SdkInternalList<String> deploymentIds;

    /**
     *

     * A list of deployment IDs, with multiple deployment IDs separated by
     * spaces.
     *

     *
     * @return A list of deployment IDs, with multiple deployment IDs separated
     *         by spaces.
     */
    public java.util.List<String> getDeploymentIds() {
        if (deploymentIds == null) {
            deploymentIds = new com.amazonaws.internal.SdkInternalList<String>();
        }
        return deploymentIds;
    }

    /**
     *

     * A list of deployment IDs, with multiple deployment IDs separated by
     * spaces.
     *

     *
     * @param deploymentIds
     *        A list of deployment IDs, with multiple deployment IDs separated
     *        by spaces.
     */
    public void setDeploymentIds(java.util.Collection<String> deploymentIds) {
        if (deploymentIds == null) {
            this.deploymentIds = null;
            return;
        }

        this.deploymentIds = new com.amazonaws.internal.SdkInternalList<String>(
                deploymentIds);
    }

    /**
     *

     * A list of deployment IDs, with multiple deployment IDs separated by
     * spaces.
     *

     *

     * NOTE: This method appends the values to the existing list (if
     * any). Use {@link #setDeploymentIds(java.util.Collection)} or
     * {@link #withDeploymentIds(java.util.Collection)} if you want to override
     * the existing values.
     *

     *
     * @param deploymentIds
     *        A list of deployment IDs, with multiple deployment IDs separated
     *        by spaces.
     * @return Returns a reference to this object so that method calls can be
     *         chained together.
     */
    public BatchGetDeploymentsRequest withDeploymentIds(String... deploymentIds) {
        if (this.deploymentIds == null) {
            setDeploymentIds(new com.amazonaws.internal.SdkInternalList<String>(
                    deploymentIds.length));
        }
        for (String ele : deploymentIds) {
            this.deploymentIds.add(ele);
        }
        return this;
    }

    /**
     *

     * A list of deployment IDs, with multiple deployment IDs separated by
     * spaces.
     *

     *
     * @param deploymentIds
     *        A list of deployment IDs, with multiple deployment IDs separated
     *        by spaces.
     * @return Returns a reference to this object so that method calls can be
     *         chained together.
     */
    public BatchGetDeploymentsRequest withDeploymentIds(
            java.util.Collection<String> deploymentIds) {
        setDeploymentIds(deploymentIds);
        return this;
    }

    /**
     * Returns a string representation of this object; useful for testing and
     * debugging.
     *
     * @return A string representation of this object.
     *
     * @see java.lang.Object#toString()
     */
    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append("{");
        if (getDeploymentIds() != null)
            sb.append("DeploymentIds: " + getDeploymentIds());
        sb.append("}");
        return sb.toString();
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;

        if (obj instanceof BatchGetDeploymentsRequest == false)
            return false;
        BatchGetDeploymentsRequest other = (BatchGetDeploymentsRequest) obj;
        if (other.getDeploymentIds() == null ^ this.getDeploymentIds() == null)
            return false;
        if (other.getDeploymentIds() != null
                && other.getDeploymentIds().equals(this.getDeploymentIds()) == false)
            return false;
        return true;
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int hashCode = 1;

        hashCode = prime
                * hashCode
                + ((getDeploymentIds() == null) ? 0 : getDeploymentIds()
                        .hashCode());
        return hashCode;
    }

    @Override
    public BatchGetDeploymentsRequest clone() {
        return (BatchGetDeploymentsRequest) super.clone();
    }
}


text:
<?php

namespace ApiGen\ElementReflection\Exception;

class NonExistingElementException extends RuntimeException
{

}


text:
import { join, normalize, strings } from '@angular-devkit/core';
import { Rule } from '@angular-devkit/schematics';
import { getWorkspace } from '../../utility/workspace';
import { ProjectType } from '../../utility/workspace-models';

export default function (): Rule {
  return async (host, context) => {
    const workspace = await getWorkspace(host);
    const logger = context.logger;

    for (const [projectName, project] of workspace.projects) {
      if (project.extensions.projectType !== ProjectType.Application) {
        // Only interested in application projects
        continue;
      }

      const appDir = join(normalize(project.sourceRoot || ''), 'app');
      const { subdirs, subfiles } = host.getDir(appDir);
      if (!subdirs.length && !subfiles.length) {
        logger.error(`Application directory '${appDir}' for project '${projectName}' doesn't exist.`);
        continue;
      }

      const pkgJson = join(appDir, 'package.json');
      if (!host.exists(pkgJson)) {
        const pkgJsonContent = {
          name: strings.dasherize(projectName),
          private: true,
          description: `This is a special package.json file that is not used by package managers. It is however used to tell the tools and bundlers whether the code under this directory is free of code with non-local side-effect. Any code that does have non-local side-effects can't be well optimized (tree-shaken) and will result in unnecessary increased payload size. It should be safe to set this option to 'false' for new applications, but existing code bases could be broken when built with the production config if the application code does contain non-local side-effects that the application depends on.`,
          sideEffects: true,
        };

        host.create(pkgJson, JSON.stringify(pkgJsonContent, undefined, 2));
      }
    }
  };
}


text: [the Maven Project](%%GITHUB-REPO%%)