// 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 "chrome/browser/guest_view/guest_view_base.h"

#include "base/command_line.h"
#include "base/lazy_instance.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/browser/guest_view/app_view/app_view_guest.h"
#include "chrome/browser/guest_view/guest_view_constants.h"
#include "chrome/browser/guest_view/guest_view_manager.h"
#include "chrome/browser/guest_view/web_view/web_view_guest.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/content_settings.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/render_view_host.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/url_constants.h"
#include "extensions/browser/event_router.h"
#include "third_party/WebKit/public/web/WebInputEvent.h"

using content::WebContents;

namespace {

typedef std::map<WebContents*, GuestViewBase*> WebContentsGuestViewMap;
static base::LazyInstance<WebContentsGuestViewMap> webcontents_guestview_map =
    LAZY_INSTANCE_INITIALIZER;

}  // namespace

GuestViewBase::Event::Event(const std::string& name,
                            scoped_ptr<base::DictionaryValue> args)
    : name_(name), args_(args.Pass()) {
}

GuestViewBase::Event::~Event() {
}

scoped_ptr<base::DictionaryValue> GuestViewBase::Event::GetArguments() {
  return args_.Pass();
}

// This observer ensures that the GuestViewBase destroys itself when its
// embedder goes away.
class GuestViewBase::EmbedderWebContentsObserver : public WebContentsObserver {
 public:
  explicit EmbedderWebContentsObserver(GuestViewBase* guest)
      : WebContentsObserver(guest->embedder_web_contents()),
        guest_(guest) {
  }

  virtual ~EmbedderWebContentsObserver() {
  }

  // WebContentsObserver implementation.
  virtual void WebContentsDestroyed() OVERRIDE {
    guest_->embedder_web_contents_ = NULL;
    guest_->EmbedderDestroyed();
    guest_->Destroy();
  }

 private:
  GuestViewBase* guest_;

  DISALLOW_COPY_AND_ASSIGN(EmbedderWebContentsObserver);
};

GuestViewBase::GuestViewBase(content::BrowserContext* browser_context,
                             int guest_instance_id)
    : embedder_web_contents_(NULL),
      embedder_render_process_id_(0),
      browser_context_(browser_context),
      guest_instance_id_(guest_instance_id),
      view_instance_id_(guestview::kInstanceIDNone),
      initialized_(false),
      weak_ptr_factory_(this) {
}

void GuestViewBase::Init(
    const std::string& embedder_extension_id,
    int embedder_render_process_id,
    const base::DictionaryValue& create_params,
    const WebContentsCreatedCallback& callback) {
  if (initialized_)
    return;
  initialized_ = true;

  CreateWebContents(embedder_extension_id,
                    embedder_render_process_id,
                    create_params,
                    base::Bind(&GuestViewBase::CompleteCreateWebContents,
                               AsWeakPtr(),
                               embedder_extension_id,
                               embedder_render_process_id,
                               callback));
}

void GuestViewBase::InitWithWebContents(
    const std::string& embedder_extension_id,
    int embedder_render_process_id,
    content::WebContents* guest_web_contents) {
  DCHECK(guest_web_contents);
  content::RenderProcessHost* embedder_render_process_host =
      content::RenderProcessHost::FromID(embedder_render_process_id);

  embedder_extension_id_ = embedder_extension_id;
  embedder_render_process_id_ = embedder_render_process_host->GetID();
  embedder_render_process_host->AddObserver(this);

  WebContentsObserver::Observe(guest_web_contents);
  guest_web_contents->SetDelegate(this);
  webcontents_guestview_map.Get().insert(
      std::make_pair(guest_web_contents, this));
  GuestViewManager::FromBrowserContext(browser_context_)->
      AddGuest(guest_instance_id_, guest_web_contents);

  // Give the derived class an opportunity to perform additional initialization.
  DidInitialize();
}

// static
GuestViewBase* GuestViewBase::Create(
    content::BrowserContext* browser_context,
    int guest_instance_id,
    const std::string& view_type) {
  if (view_type == WebViewGuest::Type) {
    return new WebViewGuest(browser_context, guest_instance_id);
  } else if (view_type == AppViewGuest::Type) {
    if (!base::CommandLine::ForCurrentProcess()->HasSwitch(
        switches::kEnableAppView)) {
      return NULL;
    }
    return new AppViewGuest(browser_context, guest_instance_id);
  }
  NOTREACHED();
  return NULL;
}

// static
GuestViewBase* GuestViewBase::FromWebContents(WebContents* web_contents) {
  WebContentsGuestViewMap* guest_map = webcontents_guestview_map.Pointer();
  WebContentsGuestViewMap::iterator it = guest_map->find(web_contents);
  return it == guest_map->end() ? NULL : it->second;
}

// static
GuestViewBase* GuestViewBase::From(int embedder_process_id,
                                   int guest_instance_id) {
  content::RenderProcessHost* host =
      content::RenderProcessHost::FromID(embedder_process_id);
  if (!host)
    return NULL;

  content::WebContents* guest_web_contents =
      GuestViewManager::FromBrowserContext(host->GetBrowserContext())->
          GetGuestByInstanceIDSafely(guest_instance_id, embedder_process_id);
  if (!guest_web_contents)
    return NULL;

  return GuestViewBase::FromWebContents(guest_web_contents);
}

// static
bool GuestViewBase::IsGuest(WebContents* web_contents) {
  return !!GuestViewBase::FromWebContents(web_contents);
}

// static
void GuestViewBase::GetDefaultContentSettingRules(
    RendererContentSettingRules* rules,
    bool incognito) {
  rules->image_rules.push_back(
      ContentSettingPatternSource(ContentSettingsPattern::Wildcard(),
                                  ContentSettingsPattern::Wildcard(),
                                  CONTENT_SETTING_ALLOW,
                                  std::string(),
                                  incognito));

  rules->script_rules.push_back(
      ContentSettingPatternSource(ContentSettingsPattern::Wildcard(),
                                  ContentSettingsPattern::Wildcard(),
                                  CONTENT_SETTING_ALLOW,
                                  std::string(),
                                  incognito));
}

base::WeakPtr<GuestViewBase> GuestViewBase::AsWeakPtr() {
  return weak_ptr_factory_.GetWeakPtr();
}

bool GuestViewBase::IsDragAndDropEnabled() const {
  return false;
}

void GuestViewBase::RenderProcessExited(content::RenderProcessHost* host,
                                        base::ProcessHandle handle,
                                        base::TerminationStatus status,
                                        int exit_code) {
  // GuestViewBase tracks the lifetime of its embedder render process until it
  // is attached to a particular embedder WebContents. At that point, its
  // lifetime is restricted in scope to the lifetime of its embedder
  // WebContents.
  CHECK(!attached());
  CHECK_EQ(host->GetID(), embedder_render_process_id());

  // This code path may be reached if the embedder WebContents is killed for
  // whatever reason immediately after a called to GuestViewInternal.createGuest
  // and before attaching the new guest to a frame.
  Destroy();
}

void GuestViewBase::Destroy() {
  content::RenderProcessHost* host =
      content::RenderProcessHost::FromID(embedder_render_process_id());
  if (host)
    host->RemoveObserver(this);
  WillDestroy();
  if (!destruction_callback_.is_null())
    destruction_callback_.Run();
  delete guest_web_contents();
}

void GuestViewBase::DidAttach() {
  // Give the derived class an opportunity to perform some actions.
  DidAttachToEmbedder();

  SendQueuedEvents();
}

int GuestViewBase::GetGuestInstanceID() const {
  return guest_instance_id_;
}

void GuestViewBase::SetOpener(GuestViewBase* guest) {
  if (guest && guest->IsViewType(GetViewType())) {
    opener_ = guest->AsWeakPtr();
    return;
  }
  opener_ = base::WeakPtr<GuestViewBase>();
}

void GuestViewBase::RegisterDestructionCallback(
    const DestructionCallback& callback) {
  destruction_callback_ = callback;
}

void GuestViewBase::WillAttach(content::WebContents* embedder_web_contents,
                               const base::DictionaryValue& extra_params) {
  // After attachment, this GuestViewBase's lifetime is restricted to the
  // lifetime of its embedder WebContents. Observing the RenderProcessHost
  // of the embedder is no longer necessary.
  embedder_web_contents->GetRenderProcessHost()->RemoveObserver(this);
  embedder_web_contents_ = embedder_web_contents;
  embedder_web_contents_observer_.reset(
      new EmbedderWebContentsObserver(this));
  extra_params.GetInteger(guestview::kParameterInstanceId, &view_instance_id_);
  extra_params_.reset(extra_params.DeepCopy());

  WillAttachToEmbedder();
}

void GuestViewBase::DidStopLoading(content::RenderViewHost* render_view_host) {
  if (!IsDragAndDropEnabled()) {
    const char script[] = "window.addEventListener('dragstart', function() { "
                          "  window.event.preventDefault(); "
                          "});";
    render_view_host->GetMainFrame()->ExecuteJavaScript(
        base::ASCIIToUTF16(script));
  }
  DidStopLoading();
}

void GuestViewBase::WebContentsDestroyed() {
  GuestDestroyed();
  delete this;
}

bool GuestViewBase::ShouldFocusPageAfterCrash() {
  // Focus is managed elsewhere.
  return false;
}

bool GuestViewBase::PreHandleGestureEvent(content::WebContents* source,
                                         const blink::WebGestureEvent& event) {
  return event.type == blink::WebGestureEvent::GesturePinchBegin ||
      event.type == blink::WebGestureEvent::GesturePinchUpdate ||
      event.type == blink::WebGestureEvent::GesturePinchEnd;
}

GuestViewBase::~GuestViewBase() {
  std::pair<int, int> key(embedder_render_process_id_, guest_instance_id_);

  webcontents_guestview_map.Get().erase(guest_web_contents());

  GuestViewManager::FromBrowserContext(browser_context_)->
      RemoveGuest(guest_instance_id_);

  pending_events_.clear();
}

void GuestViewBase::DispatchEventToEmbedder(Event* event) {
  scoped_ptr<Event> event_ptr(event);
  if (!in_extension()) {
    NOTREACHED();
    return;
  }

  if (!attached()) {
    pending_events_.push_back(linked_ptr<Event>(event_ptr.release()));
    return;
  }

  Profile* profile = Profile::FromBrowserContext(browser_context_);

  extensions::EventFilteringInfo info;
  info.SetInstanceID(view_instance_id_);
  scoped_ptr<base::ListValue> args(new base::ListValue());
  args->Append(event->GetArguments().release());

  extensions::EventRouter::DispatchEvent(
      embedder_web_contents_,
      profile,
      embedder_extension_id_,
      event->name(),
      args.Pass(),
      extensions::EventRouter::USER_GESTURE_UNKNOWN,
      info);
}

void GuestViewBase::SendQueuedEvents() {
  if (!attached())
    return;
  while (!pending_events_.empty()) {
    linked_ptr<Event> event_ptr = pending_events_.front();
    pending_events_.pop_front();
    DispatchEventToEmbedder(event_ptr.release());
  }
}

void GuestViewBase::CompleteCreateWebContents(
    const std::string& embedder_extension_id,
    int embedder_render_process_id,
    const WebContentsCreatedCallback& callback,
    content::WebContents* guest_web_contents) {
  if (!guest_web_contents) {
    callback.Run(NULL);
    return;
  }
  InitWithWebContents(embedder_extension_id,
                      embedder_render_process_id,
                      guest_web_contents);
  callback.Run(guest_web_contents);
}