// Copyright (c) 2012 The Chromium Embedded Framework Authors.
// Portions copyright (c) 2011 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 "libcef/renderer/browser_impl.h"

#include <string>
#include <vector>

#include "libcef/common/app_manager.h"
#include "libcef/common/cef_messages.h"
#include "libcef/renderer/alloy/alloy_content_renderer_client.h"
#include "libcef/renderer/blink_glue.h"
#include "libcef/renderer/render_frame_util.h"
#include "libcef/renderer/thread_util.h"

#include "base/strings/string16.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "content/public/renderer/document_state.h"
#include "content/public/renderer/render_frame.h"
#include "content/public/renderer/render_view.h"
#include "content/renderer/navigation_state.h"
#include "third_party/blink/public/platform/web_string.h"
#include "third_party/blink/public/platform/web_url_error.h"
#include "third_party/blink/public/platform/web_url_response.h"
#include "third_party/blink/public/web/web_document.h"
#include "third_party/blink/public/web/web_frame.h"
#include "third_party/blink/public/web/web_frame_content_dumper.h"
#include "third_party/blink/public/web/web_local_frame.h"
#include "third_party/blink/public/web/web_security_policy.h"
#include "third_party/blink/public/web/web_view.h"

// CefBrowserImpl static methods.
// -----------------------------------------------------------------------------

// static
CefRefPtr<CefBrowserImpl> CefBrowserImpl::GetBrowserForView(
    content::RenderView* view) {
  return AlloyContentRendererClient::Get()->GetBrowserForView(view);
}

// static
CefRefPtr<CefBrowserImpl> CefBrowserImpl::GetBrowserForMainFrame(
    blink::WebFrame* frame) {
  return AlloyContentRendererClient::Get()->GetBrowserForMainFrame(frame);
}

// CefBrowser methods.
// -----------------------------------------------------------------------------

CefRefPtr<CefBrowserHost> CefBrowserImpl::GetHost() {
  NOTREACHED() << "GetHost cannot be called from the render process";
  return nullptr;
}

bool CefBrowserImpl::CanGoBack() {
  CEF_REQUIRE_RT_RETURN(false);

  return blink_glue::CanGoBack(render_view()->GetWebView());
}

void CefBrowserImpl::GoBack() {
  CEF_REQUIRE_RT_RETURN_VOID();

  blink_glue::GoBack(render_view()->GetWebView());
}

bool CefBrowserImpl::CanGoForward() {
  CEF_REQUIRE_RT_RETURN(false);

  return blink_glue::CanGoForward(render_view()->GetWebView());
}

void CefBrowserImpl::GoForward() {
  CEF_REQUIRE_RT_RETURN_VOID();

  blink_glue::GoForward(render_view()->GetWebView());
}

bool CefBrowserImpl::IsLoading() {
  CEF_REQUIRE_RT_RETURN(false);

  if (render_view()->GetWebView()) {
    blink::WebFrame* main_frame = render_view()->GetWebView()->MainFrame();
    if (main_frame)
      return main_frame->ToWebLocalFrame()->IsLoading();
  }
  return false;
}

void CefBrowserImpl::Reload() {
  CEF_REQUIRE_RT_RETURN_VOID();

  if (render_view()->GetWebView()) {
    blink::WebFrame* main_frame = render_view()->GetWebView()->MainFrame();
    if (main_frame && main_frame->IsWebLocalFrame()) {
      main_frame->ToWebLocalFrame()->StartReload(
          blink::WebFrameLoadType::kReload);
    }
  }
}

void CefBrowserImpl::ReloadIgnoreCache() {
  CEF_REQUIRE_RT_RETURN_VOID();

  if (render_view()->GetWebView()) {
    blink::WebFrame* main_frame = render_view()->GetWebView()->MainFrame();
    if (main_frame && main_frame->IsWebLocalFrame()) {
      main_frame->ToWebLocalFrame()->StartReload(
          blink::WebFrameLoadType::kReloadBypassingCache);
    }
  }
}

void CefBrowserImpl::StopLoad() {
  CEF_REQUIRE_RT_RETURN_VOID();

  if (render_view()->GetWebView()) {
    blink::WebFrame* main_frame = render_view()->GetWebView()->MainFrame();
    if (main_frame && main_frame->IsWebLocalFrame()) {
      main_frame->ToWebLocalFrame()->StopLoading();
    }
  }
}

int CefBrowserImpl::GetIdentifier() {
  CEF_REQUIRE_RT_RETURN(0);

  return browser_id();
}

bool CefBrowserImpl::IsSame(CefRefPtr<CefBrowser> that) {
  CEF_REQUIRE_RT_RETURN(false);

  CefBrowserImpl* impl = static_cast<CefBrowserImpl*>(that.get());
  return (impl == this);
}

bool CefBrowserImpl::IsPopup() {
  CEF_REQUIRE_RT_RETURN(false);

  return is_popup();
}

bool CefBrowserImpl::HasDocument() {
  CEF_REQUIRE_RT_RETURN(false);

  if (render_view()->GetWebView()) {
    blink::WebFrame* main_frame = render_view()->GetWebView()->MainFrame();
    if (main_frame && main_frame->IsWebLocalFrame()) {
      return !main_frame->ToWebLocalFrame()->GetDocument().IsNull();
    }
  }
  return false;
}

CefRefPtr<CefFrame> CefBrowserImpl::GetMainFrame() {
  CEF_REQUIRE_RT_RETURN(nullptr);

  if (render_view()->GetWebView()) {
    blink::WebFrame* main_frame = render_view()->GetWebView()->MainFrame();
    if (main_frame && main_frame->IsWebLocalFrame()) {
      return GetWebFrameImpl(main_frame->ToWebLocalFrame()).get();
    }
  }
  return nullptr;
}

CefRefPtr<CefFrame> CefBrowserImpl::GetFocusedFrame() {
  CEF_REQUIRE_RT_RETURN(nullptr);

  if (render_view()->GetWebView() &&
      render_view()->GetWebView()->FocusedFrame()) {
    return GetWebFrameImpl(render_view()->GetWebView()->FocusedFrame()).get();
  }
  return nullptr;
}

CefRefPtr<CefFrame> CefBrowserImpl::GetFrame(int64 identifier) {
  CEF_REQUIRE_RT_RETURN(nullptr);

  return GetWebFrameImpl(identifier).get();
}

CefRefPtr<CefFrame> CefBrowserImpl::GetFrame(const CefString& name) {
  CEF_REQUIRE_RT_RETURN(nullptr);

  blink::WebView* web_view = render_view()->GetWebView();
  if (web_view) {
    const blink::WebString& frame_name =
        blink::WebString::FromUTF16(name.ToString16());
    // Search by assigned frame name (Frame::name).
    blink::WebFrame* frame = web_view->MainFrame();
    if (frame && frame->IsWebLocalFrame())
      frame = frame->ToWebLocalFrame()->FindFrameByName(frame_name);
    if (!frame) {
      // Search by unique frame name (Frame::uniqueName).
      const std::string& searchname = name;
      for (blink::WebFrame* cur_frame = web_view->MainFrame(); cur_frame;
           cur_frame = cur_frame->TraverseNext()) {
        if (cur_frame->IsWebLocalFrame() &&
            render_frame_util::GetName(cur_frame->ToWebLocalFrame()) ==
                searchname) {
          frame = cur_frame;
          break;
        }
      }
    }
    if (frame && frame->IsWebLocalFrame())
      return GetWebFrameImpl(frame->ToWebLocalFrame()).get();
  }

  return nullptr;
}

size_t CefBrowserImpl::GetFrameCount() {
  CEF_REQUIRE_RT_RETURN(0);

  int count = 0;

  if (render_view()->GetWebView()) {
    for (blink::WebFrame* frame = render_view()->GetWebView()->MainFrame();
         frame; frame = frame->TraverseNext()) {
      count++;
    }
  }

  return count;
}

void CefBrowserImpl::GetFrameIdentifiers(std::vector<int64>& identifiers) {
  CEF_REQUIRE_RT_RETURN_VOID();

  if (identifiers.size() > 0)
    identifiers.clear();

  if (render_view()->GetWebView()) {
    for (blink::WebFrame* frame = render_view()->GetWebView()->MainFrame();
         frame; frame = frame->TraverseNext()) {
      if (frame->IsWebLocalFrame())
        identifiers.push_back(
            render_frame_util::GetIdentifier(frame->ToWebLocalFrame()));
    }
  }
}

void CefBrowserImpl::GetFrameNames(std::vector<CefString>& names) {
  CEF_REQUIRE_RT_RETURN_VOID();

  if (names.size() > 0)
    names.clear();

  if (render_view()->GetWebView()) {
    for (blink::WebFrame* frame = render_view()->GetWebView()->MainFrame();
         frame; frame = frame->TraverseNext()) {
      if (frame->IsWebLocalFrame())
        names.push_back(render_frame_util::GetName(frame->ToWebLocalFrame()));
    }
  }
}

// CefBrowserImpl public methods.
// -----------------------------------------------------------------------------

CefBrowserImpl::CefBrowserImpl(content::RenderView* render_view,
                               int browser_id,
                               bool is_popup,
                               bool is_windowless)
    : content::RenderViewObserver(render_view),
      browser_id_(browser_id),
      is_popup_(is_popup),
      is_windowless_(is_windowless) {}

CefBrowserImpl::~CefBrowserImpl() {}

CefRefPtr<CefFrameImpl> CefBrowserImpl::GetWebFrameImpl(
    blink::WebLocalFrame* frame) {
  DCHECK(frame);
  int64_t frame_id = render_frame_util::GetIdentifier(frame);

  // Frames are re-used between page loads. Only add the frame to the map once.
  FrameMap::const_iterator it = frames_.find(frame_id);
  if (it != frames_.end())
    return it->second;

  CefRefPtr<CefFrameImpl> framePtr(new CefFrameImpl(this, frame, frame_id));
  frames_.insert(std::make_pair(frame_id, framePtr));

  return framePtr;
}

CefRefPtr<CefFrameImpl> CefBrowserImpl::GetWebFrameImpl(int64_t frame_id) {
  if (frame_id == blink_glue::kInvalidFrameId) {
    if (render_view()->GetWebView()) {
      blink::WebFrame* main_frame = render_view()->GetWebView()->MainFrame();
      if (main_frame && main_frame->IsWebLocalFrame()) {
        return GetWebFrameImpl(main_frame->ToWebLocalFrame());
      }
    }
    return nullptr;
  }

  // Check if we already know about the frame.
  FrameMap::const_iterator it = frames_.find(frame_id);
  if (it != frames_.end())
    return it->second;

  if (render_view()->GetWebView()) {
    // Check if the frame exists but we don't know about it yet.
    for (blink::WebFrame* frame = render_view()->GetWebView()->MainFrame();
         frame; frame = frame->TraverseNext()) {
      if (frame->IsWebLocalFrame() &&
          render_frame_util::GetIdentifier(frame->ToWebLocalFrame()) ==
              frame_id) {
        return GetWebFrameImpl(frame->ToWebLocalFrame());
      }
    }
  }

  return nullptr;
}

void CefBrowserImpl::AddFrameObject(int64_t frame_id,
                                    CefTrackNode* tracked_object) {
  CefRefPtr<CefTrackManager> manager;

  if (!frame_objects_.empty()) {
    FrameObjectMap::const_iterator it = frame_objects_.find(frame_id);
    if (it != frame_objects_.end())
      manager = it->second;
  }

  if (!manager.get()) {
    manager = new CefTrackManager();
    frame_objects_.insert(std::make_pair(frame_id, manager));
  }

  manager->Add(tracked_object);
}

// RenderViewObserver methods.
// -----------------------------------------------------------------------------

void CefBrowserImpl::OnDestruct() {
  // Notify that the browser window has been destroyed.
  CefRefPtr<CefApp> app = CefAppManager::Get()->GetApplication();
  if (app.get()) {
    CefRefPtr<CefRenderProcessHandler> handler = app->GetRenderProcessHandler();
    if (handler.get())
      handler->OnBrowserDestroyed(this);
  }

  AlloyContentRendererClient::Get()->OnBrowserDestroyed(this);
}

void CefBrowserImpl::FrameDetached(int64_t frame_id) {
  if (!frames_.empty()) {
    // Remove the frame from the map.
    FrameMap::iterator it = frames_.find(frame_id);
    if (it != frames_.end()) {
      frames_.erase(it);
    }
  }

  if (!frame_objects_.empty()) {
    // Remove any tracked objects associated with the frame.
    FrameObjectMap::iterator it = frame_objects_.find(frame_id);
    if (it != frame_objects_.end())
      frame_objects_.erase(it);
  }
}

void CefBrowserImpl::OnLoadingStateChange(bool isLoading) {
  CefRefPtr<CefApp> app = CefAppManager::Get()->GetApplication();
  if (app.get()) {
    CefRefPtr<CefRenderProcessHandler> handler = app->GetRenderProcessHandler();
    if (handler.get()) {
      CefRefPtr<CefLoadHandler> load_handler = handler->GetLoadHandler();
      if (load_handler.get()) {
        blink::WebView* web_view = render_view()->GetWebView();
        const bool canGoBack = blink_glue::CanGoBack(web_view);
        const bool canGoForward = blink_glue::CanGoForward(web_view);

        // Don't call OnLoadingStateChange multiple times with the same status.
        // This can occur in cases where there are multiple highest-level
        // LocalFrames in-process for the same browser.
        if (last_loading_state_ &&
            last_loading_state_->IsMatch(isLoading, canGoBack, canGoForward)) {
          return;
        }

        load_handler->OnLoadingStateChange(this, isLoading, canGoBack,
                                           canGoForward);
        last_loading_state_.reset(
            new LoadingState(isLoading, canGoBack, canGoForward));
      }
    }
  }
}