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

#include "base/bind.h"
#include "base/command_line.h"
#include "base/logging.h"
#include "base/memory/scoped_ptr.h"
#include "base/message_loop/message_loop.h"
#include "content/public/common/content_switches.h"
#include "content/public/renderer/render_frame.h"
#include "content/renderer/pepper/plugin_instance_throttler_impl.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/WebKit/public/web/WebInputEvent.h"
#include "third_party/WebKit/public/web/WebPluginParams.h"
#include "ui/gfx/canvas.h"
#include "url/gurl.h"
#include "url/origin.h"

using testing::_;
using testing::Return;

namespace content {

class PluginInstanceThrottlerImplTest
    : public testing::Test,
      public PluginInstanceThrottler::Observer {
 protected:
  const int kMaximumFramesToExamine =
      PluginInstanceThrottlerImpl::kMaximumFramesToExamine;

  PluginInstanceThrottlerImplTest() : change_callback_calls_(0) {}
  ~PluginInstanceThrottlerImplTest() override {
    throttler_->RemoveObserver(this);
  }

  void SetUp() override {
    throttler_.reset(new PluginInstanceThrottlerImpl);
    throttler_->Initialize(nullptr, url::Origin(GURL("http://example.com")),
                           "Shockwave Flash", gfx::Size(100, 100));
    throttler_->AddObserver(this);
  }

  PluginInstanceThrottlerImpl* throttler() {
    DCHECK(throttler_.get());
    return throttler_.get();
  }

  void DisablePowerSaverByRetroactiveWhitelist() {
    throttler()->MarkPluginEssential(
        PluginInstanceThrottlerImpl::UNTHROTTLE_METHOD_BY_WHITELIST);
  }

  int change_callback_calls() { return change_callback_calls_; }

  void EngageThrottle() { throttler_->EngageThrottle(); }

  void SendEventAndTest(blink::WebInputEvent::Type event_type,
                        bool expect_consumed,
                        bool expect_throttled,
                        int expect_change_callback_count) {
    blink::WebMouseEvent event;
    event.type = event_type;
    event.modifiers = blink::WebInputEvent::Modifiers::LeftButtonDown;
    EXPECT_EQ(expect_consumed, throttler()->ConsumeInputEvent(event));
    EXPECT_EQ(expect_throttled, throttler()->IsThrottled());
    EXPECT_EQ(expect_change_callback_count, change_callback_calls());
  }

 private:
  // PluginInstanceThrottlerImpl::Observer
  void OnThrottleStateChange() override { ++change_callback_calls_; }

  scoped_ptr<PluginInstanceThrottlerImpl> throttler_;

  int change_callback_calls_;

  base::MessageLoop loop_;
};

TEST_F(PluginInstanceThrottlerImplTest, ThrottleAndUnthrottleByClick) {
  EXPECT_FALSE(throttler()->IsThrottled());
  EXPECT_EQ(0, change_callback_calls());

  EngageThrottle();
  EXPECT_TRUE(throttler()->IsThrottled());
  EXPECT_EQ(1, change_callback_calls());

  // MouseUp while throttled should be consumed and disengage throttling.
  SendEventAndTest(blink::WebInputEvent::Type::MouseUp, true, false, 2);
}

TEST_F(PluginInstanceThrottlerImplTest, ThrottleByKeyframe) {
  EXPECT_FALSE(throttler()->IsThrottled());
  EXPECT_EQ(0, change_callback_calls());

  SkBitmap boring_bitmap;
  gfx::Canvas canvas(gfx::Size(20, 10), 1.0f, true);
  canvas.FillRect(gfx::Rect(20, 10), SK_ColorBLACK);
  canvas.FillRect(gfx::Rect(10, 10), SK_ColorWHITE);
  SkBitmap interesting_bitmap = skia::ReadPixels(canvas.sk_canvas());

  // Don't throttle for a boring frame.
  throttler()->OnImageFlush(&boring_bitmap);
  EXPECT_FALSE(throttler()->IsThrottled());
  EXPECT_EQ(0, change_callback_calls());

  // Throttle after an interesting frame.
  throttler()->OnImageFlush(&interesting_bitmap);
  EXPECT_TRUE(throttler()->IsThrottled());
  EXPECT_EQ(1, change_callback_calls());
}

TEST_F(PluginInstanceThrottlerImplTest, MaximumKeyframesAnalyzed) {
  EXPECT_FALSE(throttler()->IsThrottled());
  EXPECT_EQ(0, change_callback_calls());

  SkBitmap boring_bitmap;

  // Throttle after tons of boring bitmaps.
  for (int i = 0; i < kMaximumFramesToExamine; ++i) {
    throttler()->OnImageFlush(&boring_bitmap);
  }
  EXPECT_TRUE(throttler()->IsThrottled());
  EXPECT_EQ(1, change_callback_calls());
}
TEST_F(PluginInstanceThrottlerImplTest, IgnoreThrottlingAfterMouseUp) {
  EXPECT_FALSE(throttler()->IsThrottled());
  EXPECT_EQ(0, change_callback_calls());

  // MouseUp before throttling engaged should not be consumed, but should
  // prevent subsequent throttling from engaging.
  SendEventAndTest(blink::WebInputEvent::Type::MouseUp, false, false, 0);

  EngageThrottle();
  EXPECT_FALSE(throttler()->IsThrottled());
  EXPECT_EQ(0, change_callback_calls());
}

TEST_F(PluginInstanceThrottlerImplTest, FastWhitelisting) {
  EXPECT_FALSE(throttler()->IsThrottled());
  EXPECT_EQ(0, change_callback_calls());

  DisablePowerSaverByRetroactiveWhitelist();

  EngageThrottle();
  EXPECT_FALSE(throttler()->IsThrottled());
  EXPECT_EQ(0, change_callback_calls());
}

TEST_F(PluginInstanceThrottlerImplTest, SlowWhitelisting) {
  EXPECT_FALSE(throttler()->IsThrottled());
  EXPECT_EQ(0, change_callback_calls());

  EngageThrottle();
  EXPECT_TRUE(throttler()->IsThrottled());
  EXPECT_EQ(1, change_callback_calls());

  DisablePowerSaverByRetroactiveWhitelist();
  EXPECT_FALSE(throttler()->IsThrottled());
  EXPECT_EQ(2, change_callback_calls());
}

TEST_F(PluginInstanceThrottlerImplTest, EventConsumption) {
  EXPECT_FALSE(throttler()->IsThrottled());
  EXPECT_EQ(0, change_callback_calls());

  EngageThrottle();
  EXPECT_TRUE(throttler()->IsThrottled());
  EXPECT_EQ(1, change_callback_calls());

  // Consume but don't unthrottle on a variety of other events.
  SendEventAndTest(blink::WebInputEvent::Type::MouseDown, true, true, 1);
  SendEventAndTest(blink::WebInputEvent::Type::MouseWheel, true, true, 1);
  SendEventAndTest(blink::WebInputEvent::Type::MouseMove, true, true, 1);
  SendEventAndTest(blink::WebInputEvent::Type::KeyDown, true, true, 1);
  SendEventAndTest(blink::WebInputEvent::Type::KeyUp, true, true, 1);

  // Consume and unthrottle on MouseUp
  SendEventAndTest(blink::WebInputEvent::Type::MouseUp, true, false, 2);

  // Don't consume events after unthrottle.
  SendEventAndTest(blink::WebInputEvent::Type::MouseDown, false, false, 2);
  SendEventAndTest(blink::WebInputEvent::Type::MouseWheel, false, false, 2);
  SendEventAndTest(blink::WebInputEvent::Type::MouseMove, false, false, 2);
  SendEventAndTest(blink::WebInputEvent::Type::KeyDown, false, false, 2);
  SendEventAndTest(blink::WebInputEvent::Type::KeyUp, false, false, 2);

  // Subsequent MouseUps should also not be consumed.
  SendEventAndTest(blink::WebInputEvent::Type::MouseUp, false, false, 2);
}

TEST_F(PluginInstanceThrottlerImplTest, ThrottleOnLeftClickOnly) {
  EXPECT_FALSE(throttler()->IsThrottled());
  EXPECT_EQ(0, change_callback_calls());

  EngageThrottle();
  EXPECT_TRUE(throttler()->IsThrottled());
  EXPECT_EQ(1, change_callback_calls());

  blink::WebMouseEvent event;
  event.type = blink::WebInputEvent::Type::MouseUp;

  event.modifiers = blink::WebInputEvent::Modifiers::RightButtonDown;
  EXPECT_FALSE(throttler()->ConsumeInputEvent(event));
  EXPECT_TRUE(throttler()->IsThrottled());

  event.modifiers = blink::WebInputEvent::Modifiers::MiddleButtonDown;
  EXPECT_TRUE(throttler()->ConsumeInputEvent(event));
  EXPECT_TRUE(throttler()->IsThrottled());

  event.modifiers = blink::WebInputEvent::Modifiers::LeftButtonDown;
  EXPECT_TRUE(throttler()->ConsumeInputEvent(event));
  EXPECT_FALSE(throttler()->IsThrottled());
}

}  // namespace content
