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

#include "chrome/browser/chromeos/power/extension_event_observer.h"

#include <stdint.h>

#include <memory>
#include <string>

#include "base/macros.h"
#include "base/memory/ptr_util.h"
#include "base/message_loop/message_loop.h"
#include "base/run_loop.h"
#include "chrome/browser/chromeos/login/users/fake_chrome_user_manager.h"
#include "chrome/browser/chromeos/login/users/scoped_user_manager_enabler.h"
#include "chrome/browser/chromeos/settings/cros_settings.h"
#include "chrome/browser/chromeos/settings/device_settings_service.h"
#include "chrome/common/extensions/api/gcm.h"
#include "chrome/test/base/testing_browser_process.h"
#include "chrome/test/base/testing_profile.h"
#include "chrome/test/base/testing_profile_manager.h"
#include "chromeos/dbus/dbus_thread_manager.h"
#include "chromeos/dbus/fake_power_manager_client.h"
#include "content/public/test/test_browser_thread_bundle.h"
#include "content/public/test/test_renderer_host.h"
#include "extensions/browser/extension_host.h"
#include "extensions/browser/extension_host_observer.h"
#include "extensions/browser/process_manager.h"
#include "extensions/common/extension.h"
#include "extensions/common/extension_builder.h"
#include "extensions/common/manifest_handlers/background_info.h"
#include "extensions/common/value_builder.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/aura/test/test_screen.h"
#include "ui/display/screen.h"

namespace chromeos {

class ExtensionEventObserverTest : public ::testing::Test {
 public:
  ExtensionEventObserverTest()
      : power_manager_client_(new FakePowerManagerClient()),
        test_screen_(aura::TestScreen::Create(gfx::Size())),
        fake_user_manager_(new FakeChromeUserManager()),
        scoped_user_manager_enabler_(fake_user_manager_) {
    DBusThreadManager::GetSetterForTesting()->SetPowerManagerClient(
        base::WrapUnique(power_manager_client_));

    profile_manager_.reset(
        new TestingProfileManager(TestingBrowserProcess::GetGlobal()));

    extension_event_observer_.reset(new ExtensionEventObserver());
    test_api_ = extension_event_observer_->CreateTestApi();
  }

  ~ExtensionEventObserverTest() override {
    extension_event_observer_.reset();
    profile_manager_.reset();
    DBusThreadManager::Shutdown();
  }

  // ::testing::Test overrides. 
  void SetUp() override {
    ::testing::Test::SetUp();

    display::Screen::SetScreenInstance(test_screen_.get());

    // Must be called from ::testing::Test::SetUp. 
    ASSERT_TRUE(profile_manager_->SetUp());

    const char kUserProfile[] = "profile1@example.com";
    const AccountId account_id(AccountId::FromUserEmail(kUserProfile));
    fake_user_manager_->AddUser(account_id);
    fake_user_manager_->LoginUser(account_id);
    profile_ =
        profile_manager_->CreateTestingProfile(account_id. GetUserEmail());

    profile_manager_->SetLoggedIn(true);
  }
  void TearDown() override {
    profile_ = NULL;
    profile_manager_->DeleteAllTestingProfiles();
    display::Screen::SetScreenInstance(nullptr);
    ::testing::Test::TearDown();
  }

 protected:
  scoped_refptr<extensions::Extension> CreateApp(const std::string& name,
                                                 bool uses_gcm) {
    scoped_refptr<extensions::Extension> app =
        extensions::ExtensionBuilder()
            .SetManifest(
                extensions::DictionaryBuilder()
                    .Set("name", name)
                    .Set("version", "1.0.0")
                    .Set("manifest_version", 2)
                    .Set("app", extensions::DictionaryBuilder()
                                    .Set("background",
                                         extensions::DictionaryBuilder()
                                             .Set("scripts",
                                                  extensions::ListBuilder()
                                                      .Append("background.js")
                                                      .Build())
                                             .Build())
                                    .Build())
                    .Set("permissions", extensions::ListBuilder()
                                            .Append(uses_gcm ? " gcm" : "")
                                            .Build())
                    .Build())
            .Build();

    created_apps_.push_back(app);

    return app;
  }

  extensions::ExtensionHost* CreateHostForApp(Profile* profile,
                                              extensions::Extension* app) {
    extensions::ProcessManager::Get(profile)->CreateBackgroundHost(
        app, extensions::BackgroundInfo::GetBackgroundURL(app));
    base::RunLoop().RunUntilIdle();

    return extensions::ProcessManager::Get(profile)
        ->GetBackgroundHostForExtension(app->id());
  }

  // Owned by DBusThreadManager. 
  FakePowerManagerClient* power_manager_client_;

  std::unique_ptr<ExtensionEventObserver> extension_event_observer_;
  std::unique_ptr<ExtensionEventObserver::TestApi> test_api_;

  // Owned by |profile_manager_|. 
  TestingProfile* profile_;
  std::unique_ptr<TestingProfileManager> profile_manager_;

 private:
  std::unique_ptr<aura::TestScreen> test_screen_;
  content::TestBrowserThreadBundle browser_thread_bundle_;

  // Needed to ensure we don't end up creating actual RenderViewHosts
  // and RenderProcessHosts. 
  content::RenderViewHostTestEnabler render_view_host_test_enabler_;

  // Chrome OS needs extra services to run in the following order. 
  ScopedTestDeviceSettingsService test_device_settings_service_;
  ScopedTestCrosSettings test_cros_settings_;

  // Owned by |scoped_user_manager_enabler_|. 
  FakeChromeUserManager* fake_user_manager_;
  ScopedUserManagerEnabler scoped_user_manager_enabler_;

  std::vector<scoped_refptr<extensions::Extension>> created_apps_;

  DISALLOW_COPY_AND_ASSIGN(ExtensionEventObserverTest);
};

// Tests that the ExtensionEventObserver reports readiness for suspend when
// there is nothing interesting going on. 
TEST_F(ExtensionEventObserverTest, BasicSuspendAndDarkSuspend) {
  power_manager_client_->SendSuspendImminent();
  EXPECT_EQ(1, power_manager_client_->GetNumPendingSuspendReadinessCallbacks());

  EXPECT_TRUE(test_api_->MaybeRunSuspendReadinessCallback());
  EXPECT_EQ(0, power_manager_client_->GetNumPendingSuspendReadinessCallbacks());

  power_manager_client_->SendDarkSuspendImminent();
  EXPECT_EQ(1, power_manager_client_->GetNumPendingSuspendReadinessCallbacks());

  EXPECT_TRUE(test_api_->MaybeRunSuspendReadinessCallback());
  EXPECT_EQ(0, power_manager_client_->GetNumPendingSuspendReadinessCallbacks());
}

// Tests that the ExtensionEventObserver properly handles a canceled suspend
// attempt. 
TEST_F(ExtensionEventObserverTest, CanceledSuspend) {
  power_manager_client_->SendSuspendImminent();
  EXPECT_EQ(1, power_manager_client_->GetNumPendingSuspendReadinessCallbacks());

  power_manager_client_->SendSuspendDone();
  EXPECT_FALSE(test_api_->MaybeRunSuspendReadinessCallback());
}

// Tests that the ExtensionEventObserver delays suspends and dark suspends while
// there is a push message pending for an app that uses GCM. 
TEST_F(ExtensionEventObserverTest, PushMessagesDelaySuspend) {
  scoped_refptr<extensions::Extension> gcm_app =
      CreateApp("DelaysSuspendForPushMessages", true /* uses_gcm */);
  extensions::ExtensionHost* host = CreateHostForApp(profile_, gcm_app.get());
  ASSERT_TRUE(host);
  EXPECT_TRUE(test_api_->WillDelaySuspendForExtensionHost(host));

  // Test that a push message received before a suspend attempt delays the
  // attempt. 
  const int kSuspendPushId = 23874;
  extension_event_observer_->OnBackgroundEventDispatched(
      host, extensions::api::gcm::OnMessage::kEventName, kSuspendPushId);
  power_manager_client_->SendSuspendImminent();

  EXPECT_TRUE(test_api_->MaybeRunSuspendReadinessCallback());
  EXPECT_EQ(1, power_manager_client_->GetNumPendingSuspendReadinessCallbacks());

  extension_event_observer_->OnBackgroundEventAcked(host, kSuspendPushId);
  EXPECT_EQ(0, power_manager_client_->GetNumPendingSuspendReadinessCallbacks());

  // Now test receiving the suspend attempt before the push message. 
  const int kDarkSuspendPushId = 56674;
  power_manager_client_->SendDarkSuspendImminent();
  extension_event_observer_->OnBackgroundEventDispatched(
      host, extensions::api::gcm::OnMessage::kEventName, kDarkSuspendPushId);

  EXPECT_TRUE(test_api_->MaybeRunSuspendReadinessCallback());
  EXPECT_EQ(1, power_manager_client_->GetNumPendingSuspendReadinessCallbacks());

  extension_event_observer_->OnBackgroundEventAcked(host, kDarkSuspendPushId);
  EXPECT_EQ(0, power_manager_client_->GetNumPendingSuspendReadinessCallbacks());

  // Test that non-push messages do not delay the suspend. 
  const int kNonPushId = 5687;
  power_manager_client_->SendDarkSuspendImminent();
  extension_event_observer_->OnBackgroundEventDispatched(host, "FakeMessage",
                                                         kNonPushId);

  EXPECT_TRUE(test_api_->MaybeRunSuspendReadinessCallback());
  EXPECT_EQ(0, power_manager_client_->GetNumPendingSuspendReadinessCallbacks());
}

// Tests that messages sent for apps that don't use GCM are ignored. 
TEST_F(ExtensionEventObserverTest, IgnoresNonGCMApps) {
  scoped_refptr<extensions::Extension> app = CreateApp("Non-GCM", false);
  extensions::ExtensionHost* host = CreateHostForApp(profile_, app.get());
  ASSERT_TRUE(host);

  EXPECT_FALSE(test_api_->WillDelaySuspendForExtensionHost(host));

  power_manager_client_->SendSuspendImminent();
  EXPECT_TRUE(test_api_->MaybeRunSuspendReadinessCallback());
  EXPECT_EQ(0, power_manager_client_->GetNumPendingSuspendReadinessCallbacks());
}

// Tests that network requests started by an app while it is processing a push
// message delay any suspend attempt. 
TEST_F(ExtensionEventObserverTest, NetworkRequestsMayDelaySuspend) {
  scoped_refptr<extensions::Extension> app = CreateApp("NetworkRequests", true);
  extensions::ExtensionHost* host = CreateHostForApp(profile_, app.get());
  ASSERT_TRUE(host);
  EXPECT_TRUE(test_api_->WillDelaySuspendForExtensionHost(host));

  // Test that network requests started while there is no pending push message
  // are ignored. 
  const uint64_t kNonPushRequestId = 5170725;
  extension_event_observer_->OnNetworkRequestStarted(host, kNonPushRequestId);
  power_manager_client_->SendSuspendImminent();

  EXPECT_TRUE(test_api_->MaybeRunSuspendReadinessCallback());
  EXPECT_EQ(0, power_manager_client_->GetNumPendingSuspendReadinessCallbacks());

  // Test that network requests started while a push message is pending delay
  // the suspend even after the push message has been acked. 
  const int kPushMessageId = 178674;
  const uint64_t kNetworkRequestId = 78917089;
  power_manager_client_->SendDarkSuspendImminent();
  extension_event_observer_->OnBackgroundEventDispatched(
      host, extensions::api::gcm::OnMessage::kEventName, kPushMessageId);

  EXPECT_TRUE(test_api_->MaybeRunSuspendReadinessCallback());
  EXPECT_EQ(1, power_manager_client_->GetNumPendingSuspendReadinessCallbacks());

  extension_event_observer_->OnNetworkRequestStarted(host, kNetworkRequestId);
  extension_event_observer_->OnBackgroundEventAcked(host, kPushMessageId);
  EXPECT_EQ(1, power_manager_client_->GetNumPendingSuspendReadinessCallbacks());

  extension_event_observer_->OnNetworkRequestDone(host, kNetworkRequestId);
  EXPECT_EQ(0, power_manager_client_->GetNumPendingSuspendReadinessCallbacks());
}

// Tests that any outstanding push messages or network requests for an
// ExtensionHost that is destroyed do not end up blocking system suspend. 
TEST_F(ExtensionEventObserverTest, DeletedExtensionHostDoesNotBlockSuspend) {
  scoped_refptr<extensions::Extension> app =
      CreateApp("DeletedExtensionHost", true);

  // The easiest way to delete an extension host is to delete the Profile it is
  // associated with so we create a new Profile here. 
  const char kProfileName[] = "DeletedExtensionHostProfile";
  Profile* new_profile = profile_manager_->CreateTestingProfile(kProfileName);

  extensions::ExtensionHost* host = CreateHostForApp(new_profile, app.get());
  ASSERT_TRUE(host);
  EXPECT_TRUE(test_api_->WillDelaySuspendForExtensionHost(host));

  const int kPushId = 156178;
  const uint64_t kNetworkId = 791605;
  extension_event_observer_->OnBackgroundEventDispatched(
      host, extensions::api::gcm::OnMessage::kEventName, kPushId);
  extension_event_observer_->OnNetworkRequestStarted(host, kNetworkId);

  // Now delete the Profile.   This has the side-effect of also deleting all the
  // ExtensionHosts. 
  profile_manager_->DeleteTestingProfile(kProfileName);

  power_manager_client_->SendSuspendImminent();
  EXPECT_TRUE(test_api_->MaybeRunSuspendReadinessCallback());
  EXPECT_EQ(0, power_manager_client_->GetNumPendingSuspendReadinessCallbacks());
}

// Tests that the ExtensionEventObserver does not delay suspend attempts when it
// is disabled. 
TEST_F(ExtensionEventObserverTest, DoesNotDelaySuspendWhenDisabled) {
  scoped_refptr<extensions::Extension> app =
      CreateApp("NoDelayWhenDisabled", true);
  extensions::ExtensionHost* host = CreateHostForApp(profile_, app.get());
  ASSERT_TRUE(host);
  EXPECT_TRUE(test_api_->WillDelaySuspendForExtensionHost(host));

  // Test that disabling the suspend delay while a suspend is pending will cause
  // the ExtensionEventObserver to immediately report readiness. 
  const int kPushId = 416753;
  extension_event_observer_->OnBackgroundEventDispatched(
      host, extensions::api::gcm::OnMessage::kEventName, kPushId);
  power_manager_client_->SendSuspendImminent();
  EXPECT_EQ(1, power_manager_client_->GetNumPendingSuspendReadinessCallbacks());

  extension_event_observer_->SetShouldDelaySuspend(false);
  EXPECT_FALSE(test_api_->MaybeRunSuspendReadinessCallback());
  EXPECT_EQ(0, power_manager_client_->GetNumPendingSuspendReadinessCallbacks());

  // Test that the ExtensionEventObserver does not delay suspend attempts when
  // it is disabled. 
  power_manager_client_->SendDarkSuspendImminent();
  EXPECT_EQ(0, power_manager_client_->GetNumPendingSuspendReadinessCallbacks());
}

}  // namespace chromeos
