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

#include "content/browser/android/web_contents_observer_proxy.h"

#include <string>

#include "base/android/jni_android.h"
#include "base/android/jni_string.h"
#include "base/android/scoped_java_ref.h"
#include "base/feature_list.h"
#include "base/metrics/histogram_functions.h"
#include "base/strings/utf_string_conversions.h"
#include "base/time/time.h"
#include "base/trace_event/named_trigger.h"
#include "base/trace_event/trace_event.h"
#include "content/browser/android/navigation_handle_proxy.h"
#include "content/browser/media/session/media_session_android.h"
#include "content/browser/media/session/media_session_impl.h"
#include "content/browser/renderer_host/navigation_request.h"
#include "content/browser/renderer_host/render_widget_host_impl.h"
#include "content/browser/web_contents/web_contents_impl.h"
#include "content/public/browser/media_player_id.h"
#include "content/public/browser/navigation_details.h"
#include "content/public/browser/navigation_entry.h"
#include "content/public/browser/navigation_handle.h"
#include "url/android/gurl_android.h"

// Must come after all headers that specialize FromJniType() / ToJniType().
#include "content/public/android/content_jni_headers/LoadCommittedDetails_jni.h"
#include "content/public/android/content_jni_headers/WebContentsObserverProxy_jni.h"

using base::android::AttachCurrentThread;
using base::android::ConvertUTF16ToJavaString;
using base::android::ConvertUTF8ToJavaString;
using base::android::JavaRef;
using base::android::ScopedJavaLocalRef;

namespace content {

// TODO(dcheng): File a bug. This class incorrectly passes just a frame ID,
// which is not sufficient to identify a frame (since frame IDs are scoped per
// render process, and so may collide).
WebContentsObserverProxy::WebContentsObserverProxy(WebContents* web_contents)
    : WebContentsObserver(web_contents) {}

WebContentsObserverProxy::~WebContentsObserverProxy() {}

static int64_t JNI_WebContentsObserverProxy_Init(JNIEnv* env,
                                                 WebContents* web_contents) {
  CHECK(web_contents);

  WebContentsObserverProxy* native_observer =
      new WebContentsObserverProxy(web_contents);
  return reinterpret_cast<intptr_t>(native_observer);
}

void WebContentsObserverProxy::Destroy(JNIEnv* env) {
  delete this;
}

void WebContentsObserverProxy::WebContentsDestroyed() {
  JNIEnv* env = AttachCurrentThread();
  // The java side will destroy |this|
  Java_WebContentsObserverProxy_webContentsDestroyed(env,
                                                     GetJavaObjectChecked(env));
}

void WebContentsObserverProxy::RenderFrameCreated(
    RenderFrameHost* render_frame_host) {
  JNIEnv* env = AttachCurrentThread();
  Java_WebContentsObserverProxy_renderFrameCreated(
      env, GetJavaObjectChecked(env),
      render_frame_host->GetProcess()->GetDeprecatedID(),
      render_frame_host->GetRoutingID());
}

void WebContentsObserverProxy::RenderFrameDeleted(
    RenderFrameHost* render_frame_host) {
  JNIEnv* env = AttachCurrentThread();
  Java_WebContentsObserverProxy_renderFrameDeleted(
      env, GetJavaObjectChecked(env),
      render_frame_host->GetProcess()->GetDeprecatedID(),
      render_frame_host->GetRoutingID());
}

void WebContentsObserverProxy::PrimaryPageChanged(content::Page& page) {
  JNIEnv* env = AttachCurrentThread();
  Java_WebContentsObserverProxy_primaryPageChanged(
      env, GetJavaObjectChecked(env), page.GetJavaPage());
}

void WebContentsObserverProxy::PrimaryMainFrameRenderProcessGone(
    base::TerminationStatus termination_status) {
  JNIEnv* env = AttachCurrentThread();
  Java_WebContentsObserverProxy_primaryMainFrameRenderProcessGone(
      env, GetJavaObjectChecked(env), termination_status);
}

void WebContentsObserverProxy::DidStartLoading() {
  TRACE_EVENT("browser", "WebContentsObserverProxy::DidStartLoading");
  JNIEnv* env = AttachCurrentThread();
  Java_WebContentsObserverProxy_didStartLoading(
      env, GetJavaObjectChecked(env),
      url::GURLAndroid::FromNativeGURL(env, web_contents()->GetVisibleURL()));
}

void WebContentsObserverProxy::DidStopLoading() {
  JNIEnv* env = AttachCurrentThread();
  GURL url = web_contents()->GetLastCommittedURL();
  bool assume_valid = SetToBaseURLForDataURLIfNeeded(&url);
  Java_WebContentsObserverProxy_didStopLoading(
      env, GetJavaObjectChecked(env),
      url::GURLAndroid::FromNativeGURL(env, url), assume_valid);
}

void WebContentsObserverProxy::LoadProgressChanged(double progress) {
  JNIEnv* env = AttachCurrentThread();
  Java_WebContentsObserverProxy_loadProgressChanged(
      env, GetJavaObjectChecked(env), static_cast<float>(progress));
}

void WebContentsObserverProxy::DidFailLoad(RenderFrameHost* render_frame_host,
                                           const GURL& validated_url,
                                           int error_code) {
  JNIEnv* env = AttachCurrentThread();
  Java_WebContentsObserverProxy_didFailLoad(
      env, GetJavaObjectChecked(env), render_frame_host->IsInPrimaryMainFrame(),
      error_code, url::GURLAndroid::FromNativeGURL(env, validated_url),
      static_cast<int32_t>(render_frame_host->GetLifecycleState()));
}

void WebContentsObserverProxy::DidChangeVisibleSecurityState() {
  JNIEnv* env = AttachCurrentThread();
  Java_WebContentsObserverProxy_didChangeVisibleSecurityState(
      env, GetJavaObjectChecked(env));
}

void WebContentsObserverProxy::PrimaryMainDocumentElementAvailable() {
  JNIEnv* env = AttachCurrentThread();
  Java_WebContentsObserverProxy_primaryMainDocumentElementAvailable(
      env, GetJavaObjectChecked(env));
}

void WebContentsObserverProxy::DidStartNavigation(
    NavigationHandle* navigation_handle) {
  TRACE_EVENT("browser", "WebContentsObserverProxy::DidStartNavigation");
  if (navigation_handle->IsInPrimaryMainFrame()) {
    JNIEnv* env = AttachCurrentThread();
    Java_WebContentsObserverProxy_didStartNavigationInPrimaryMainFrame(
        env, GetJavaObjectChecked(env),
        navigation_handle->GetJavaNavigationHandle());
  }
}

void WebContentsObserverProxy::DidRedirectNavigation(
    NavigationHandle* navigation_handle) {
  JNIEnv* env = AttachCurrentThread();
  Java_WebContentsObserverProxy_didRedirectNavigation(
      env, GetJavaObjectChecked(env),
      navigation_handle->GetJavaNavigationHandle());
}

void WebContentsObserverProxy::DidFinishNavigation(
    NavigationHandle* navigation_handle) {
  // Remove after fixing https://crbug/905461.
  TRACE_EVENT0("browser", "Java_WebContentsObserverProxy_didFinishNavigation");

  if (navigation_handle->IsInPrimaryMainFrame()) {
    base::trace_event::EmitNamedTrigger("did-finish-navigation-in-pmf");
    JNIEnv* env = AttachCurrentThread();
    base::TimeTicks start_time = base::TimeTicks::Now();
    Java_WebContentsObserverProxy_didFinishNavigationInPrimaryMainFrame(
        env, GetJavaObjectChecked(env),
        navigation_handle->GetJavaNavigationHandle());
    base::UmaHistogramTimes(
        "Android.Navigation.JavaObserverDuration.DidFinishNavigation",
        base::TimeTicks::Now() - start_time);
  }
}

void WebContentsObserverProxy::DidFinishLoad(RenderFrameHost* render_frame_host,
                                             const GURL& validated_url) {
  JNIEnv* env = AttachCurrentThread();

  GURL url = validated_url;
  bool assume_valid = SetToBaseURLForDataURLIfNeeded(&url);

  if (render_frame_host->IsInPrimaryMainFrame()) {
    Java_WebContentsObserverProxy_didFinishLoadInPrimaryMainFrame(
        env, GetJavaObjectChecked(env),
        render_frame_host->GetPage().GetJavaPage(),
        render_frame_host->GetProcess()->GetDeprecatedID(),
        render_frame_host->GetRoutingID(),
        url::GURLAndroid::FromNativeGURL(env, url), assume_valid,
        static_cast<int32_t>(render_frame_host->GetLifecycleState()));
  }
}

void WebContentsObserverProxy::DOMContentLoaded(
    RenderFrameHost* render_frame_host) {
  if (render_frame_host->IsInPrimaryMainFrame()) {
    JNIEnv* env = AttachCurrentThread();
    Java_WebContentsObserverProxy_documentLoadedInPrimaryMainFrame(
        env, GetJavaObjectChecked(env),
        render_frame_host->GetPage().GetJavaPage(),
        render_frame_host->GetProcess()->GetDeprecatedID(),
        render_frame_host->GetRoutingID(),
        static_cast<int32_t>(render_frame_host->GetLifecycleState()));
  }
}

void WebContentsObserverProxy::NavigationEntryCommitted(
    const LoadCommittedDetails& load_details) {
  JNIEnv* env = AttachCurrentThread();
  Java_WebContentsObserverProxy_navigationEntryCommitted(
      env, GetJavaObjectChecked(env),
      Java_LoadCommittedDetails_Constructor(
          env, load_details.previous_entry_index,
          url::GURLAndroid::FromNativeGURL(
              env, load_details.previous_main_frame_url),
          load_details.did_replace_entry, load_details.is_same_document,
          load_details.is_main_frame, load_details.http_status_code));
}

void WebContentsObserverProxy::NavigationEntriesDeleted() {
  JNIEnv* env = AttachCurrentThread();
  Java_WebContentsObserverProxy_navigationEntriesDeleted(
      env, GetJavaObjectChecked(env));
}

void WebContentsObserverProxy::NavigationEntryChanged(
    const EntryChangedDetails& change_details) {
  JNIEnv* env = AttachCurrentThread();
  // TODO(jinsukkim): Convert |change_details| to Java object when needed.
  Java_WebContentsObserverProxy_navigationEntriesChanged(
      env, GetJavaObjectChecked(env));
}

void WebContentsObserverProxy::FrameReceivedUserActivation(RenderFrameHost*) {
  JNIEnv* env = AttachCurrentThread();
  Java_WebContentsObserverProxy_frameReceivedUserActivation(
      env, GetJavaObjectChecked(env));
}

void WebContentsObserverProxy::DidChangeThemeColor() {
  JNIEnv* env = AttachCurrentThread();
  Java_WebContentsObserverProxy_didChangeThemeColor(env,
                                                    GetJavaObjectChecked(env));
}

void WebContentsObserverProxy::OnBackgroundColorChanged() {
  JNIEnv* env = AttachCurrentThread();
  Java_WebContentsObserverProxy_onBackgroundColorChanged(
      env, GetJavaObjectChecked(env));
}

void WebContentsObserverProxy::MediaStartedPlaying(
    const MediaPlayerInfo& video_type,
    const MediaPlayerId& id) {
  JNIEnv* env = AttachCurrentThread();
  Java_WebContentsObserverProxy_mediaStartedPlaying(
      env, GetJavaObjectChecked(env), id.player_id, video_type.has_audio,
      video_type.has_video);
}

void WebContentsObserverProxy::MediaStoppedPlaying(
    const MediaPlayerInfo& video_type,
    const MediaPlayerId& id,
    WebContentsObserver::MediaStoppedReason reason) {
  JNIEnv* env = AttachCurrentThread();
  Java_WebContentsObserverProxy_mediaStoppedPlaying(
      env, GetJavaObjectChecked(env), id.player_id);
}

void WebContentsObserverProxy::MediaEffectivelyFullscreenChanged(
    bool is_fullscreen) {
  JNIEnv* env = AttachCurrentThread();
  Java_WebContentsObserverProxy_hasEffectivelyFullscreenVideoChange(
      env, GetJavaObjectChecked(env), is_fullscreen);
}

void WebContentsObserverProxy::DidToggleFullscreenModeForTab(
    bool entered_fullscreen,
    bool will_cause_resize) {
  JNIEnv* env = AttachCurrentThread();
  Java_WebContentsObserverProxy_didToggleFullscreenModeForTab(
      env, GetJavaObjectChecked(env), entered_fullscreen, will_cause_resize);
}

void WebContentsObserverProxy::DidFirstVisuallyNonEmptyPaint() {
  JNIEnv* env = AttachCurrentThread();
  Java_WebContentsObserverProxy_didFirstVisuallyNonEmptyPaint(
      env, GetJavaObjectChecked(env));
}

void WebContentsObserverProxy::OnVisibilityChanged(
    content::Visibility visibility) {
  JNIEnv* env = AttachCurrentThread();
  Java_WebContentsObserverProxy_onVisibilityChanged(
      env, GetJavaObjectChecked(env), static_cast<int32_t>(visibility));
}

void WebContentsObserverProxy::TitleWasSet(NavigationEntry* entry) {
  JNIEnv* env = AttachCurrentThread();
  ScopedJavaLocalRef<jstring> jstring_title = ConvertUTF8ToJavaString(
      env, base::UTF16ToUTF8(web_contents()->GetTitle()));
  Java_WebContentsObserverProxy_titleWasSet(env, GetJavaObjectChecked(env),
                                            jstring_title);
}

bool WebContentsObserverProxy::SetToBaseURLForDataURLIfNeeded(GURL* url) {
  NavigationEntry* entry =
      web_contents()->GetController().GetLastCommittedEntry();
  // Note that GetBaseURLForDataURL is only used by the Android WebView.
  // FIXME: Should we only return valid specs and "about:blank" for invalid
  // ones? This may break apps.
  if (entry && !entry->GetBaseURLForDataURL().is_empty()) {
    *url = entry->GetBaseURLForDataURL();
    return false;
  }
  return true;
}

void WebContentsObserverProxy::ViewportFitChanged(
    blink::mojom::ViewportFit value) {
  JNIEnv* env = AttachCurrentThread();
  Java_WebContentsObserverProxy_viewportFitChanged(
      env, GetJavaObjectChecked(env), static_cast<int32_t>(value));
}

void WebContentsObserverProxy::SafeAreaConstraintChanged(bool has_constraint) {
  JNIEnv* env = AttachCurrentThread();
  Java_WebContentsObserverProxy_safeAreaConstraintChanged(
      env, GetJavaObjectChecked(env), has_constraint);
}

void WebContentsObserverProxy::VirtualKeyboardModeChanged(
    ui::mojom::VirtualKeyboardMode mode) {
  JNIEnv* env = AttachCurrentThread();
  Java_WebContentsObserverProxy_virtualKeyboardModeChanged(
      env, GetJavaObjectChecked(env), static_cast<int32_t>(mode));
}

void WebContentsObserverProxy::OnWebContentsFocused(RenderWidgetHost*) {
  JNIEnv* env = AttachCurrentThread();
  Java_WebContentsObserverProxy_onWebContentsFocused(env,
                                                     GetJavaObjectChecked(env));
}

void WebContentsObserverProxy::OnWebContentsLostFocus(RenderWidgetHost*) {
  JNIEnv* env = AttachCurrentThread();
  Java_WebContentsObserverProxy_onWebContentsLostFocus(
      env, GetJavaObjectChecked(env));
}

void WebContentsObserverProxy::MediaSessionCreated(MediaSession* session) {
  JNIEnv* env = AttachCurrentThread();
  Java_WebContentsObserverProxy_mediaSessionCreated(
      env, GetJavaObjectChecked(env),
      static_cast<MediaSessionImpl*>(session)
          ->GetMediaSessionAndroid()
          ->GetJavaObject());
}

void WebContentsObserverProxy::WasDiscarded() {
  JNIEnv* env = AttachCurrentThread();
  Java_WebContentsObserverProxy_wasDiscarded(env, GetJavaObjectChecked(env));
}

ScopedJavaLocalRef<jobject> WebContentsObserverProxy::GetJavaObjectChecked(
    JNIEnv* env) const {
  CHECK(web_contents());
  auto obj =
      Java_WebContentsObserverProxy_getFromWebContents(env, web_contents());
  DCHECK(!obj.is_null());
  return obj;
}

}  // namespace content

DEFINE_JNI(LoadCommittedDetails)
DEFINE_JNI(WebContentsObserverProxy)
