blob: 33296c100ab1fde8f60151aee7c38d474b1afd0a [file] [log] [blame]
// Copyright 2013 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 "content/browser/web_contents/web_contents_android.h"
#include "base/android/jni_android.h"
#include "base/android/jni_array.h"
#include "base/android/jni_string.h"
#include "base/command_line.h"
#include "base/containers/hash_tables.h"
#include "base/json/json_writer.h"
#include "base/lazy_instance.h"
#include "base/logging.h"
#include "content/browser/accessibility/browser_accessibility_android.h"
#include "content/browser/accessibility/browser_accessibility_manager_android.h"
#include "content/browser/android/content_view_core_impl.h"
#include "content/browser/android/interstitial_page_delegate_android.h"
#include "content/browser/frame_host/interstitial_page_impl.h"
#include "content/browser/media/android/browser_media_player_manager.h"
#include "content/browser/media/media_web_contents_observer.h"
#include "content/browser/renderer_host/render_view_host_impl.h"
#include "content/browser/web_contents/web_contents_impl.h"
#include "content/common/devtools_messages.h"
#include "content/common/frame_messages.h"
#include "content/common/input_messages.h"
#include "content/common/view_messages.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/content_switches.h"
#include "jni/WebContentsImpl_jni.h"
#include "net/android/network_library.h"
#include "ui/accessibility/ax_node_data.h"
#include "ui/gfx/android/device_display_info.h"
using base::android::AttachCurrentThread;
using base::android::ConvertJavaStringToUTF8;
using base::android::ConvertJavaStringToUTF16;
using base::android::ConvertUTF8ToJavaString;
using base::android::ConvertUTF16ToJavaString;
using base::android::ScopedJavaGlobalRef;
using base::android::ToJavaIntArray;
namespace content {
namespace {
// Track all WebContentsAndroid objects here so that we don't deserialize a
// destroyed WebContents object.
base::LazyInstance<base::hash_set<WebContentsAndroid*> >::Leaky
g_allocated_web_contents_androids = LAZY_INSTANCE_INITIALIZER;
void JavaScriptResultCallback(const ScopedJavaGlobalRef<jobject>& callback,
const base::Value* result) {
JNIEnv* env = base::android::AttachCurrentThread();
std::string json;
base::JSONWriter::Write(*result, &json);
ScopedJavaLocalRef<jstring> j_json = ConvertUTF8ToJavaString(env, json);
Java_WebContentsImpl_onEvaluateJavaScriptResult(
env, j_json.obj(), callback.obj());
}
ScopedJavaLocalRef<jobject> WalkAXTreeDepthFirst(JNIEnv* env,
BrowserAccessibilityAndroid* node, float scale_factor,
float y_offset, float x_scroll) {
ScopedJavaLocalRef<jstring> j_text =
ConvertUTF16ToJavaString(env, node->GetText());
ScopedJavaLocalRef<jstring> j_class =
ConvertUTF8ToJavaString(env, node->GetClassName());
const gfx::Rect& location = node->GetLocalBoundsRect();
// The style attributes exists and valid if size attribute exists. Otherwise,
// they are not. Use a negative size information to indicate the existence
// of style information.
float size = -1.0;
int color = 0;
int bgcolor = 0;
int text_style = 0;
if (node->HasFloatAttribute(ui::AX_ATTR_FONT_SIZE)) {
color = node->GetIntAttribute(ui::AX_ATTR_COLOR);
bgcolor = node->GetIntAttribute(ui::AX_ATTR_BACKGROUND_COLOR);
size = node->GetFloatAttribute(ui::AX_ATTR_FONT_SIZE);
text_style = node->GetIntAttribute(ui::AX_ATTR_TEXT_STYLE);
}
ScopedJavaLocalRef<jobject> j_node =
Java_WebContentsImpl_createAccessibilitySnapshotNode(env,
scale_factor * location.x() - x_scroll,
scale_factor * location.y() + y_offset,
scale_factor * node->GetScrollX(), scale_factor * node->GetScrollY(),
scale_factor * location.width(), scale_factor * location.height(),
j_text.obj(), color, bgcolor, scale_factor * size, text_style,
j_class.obj());
for(uint32 i = 0; i < node->PlatformChildCount(); i++) {
BrowserAccessibilityAndroid* child =
static_cast<BrowserAccessibilityAndroid*>(
node->PlatformGetChild(i));
Java_WebContentsImpl_addAccessibilityNodeAsChild(env,
j_node.obj(), WalkAXTreeDepthFirst(env, child, scale_factor, y_offset,
x_scroll).obj());
}
return j_node;
}
// Walks over the AXTreeUpdate and creates a light weight snapshot.
void AXTreeSnapshotCallback(const ScopedJavaGlobalRef<jobject>& callback,
float scale_factor,
float y_offset,
float x_scroll,
const ui::AXTreeUpdate& result) {
JNIEnv* env = base::android::AttachCurrentThread();
if (result.nodes.empty()) {
Java_WebContentsImpl_onAccessibilitySnapshot(env, nullptr, callback.obj());
return;
}
scoped_ptr<BrowserAccessibilityManagerAndroid> manager(
static_cast<BrowserAccessibilityManagerAndroid*>(
BrowserAccessibilityManager::Create(result, nullptr)));
manager->set_prune_tree_for_screen_reader(false);
BrowserAccessibilityAndroid* root =
static_cast<BrowserAccessibilityAndroid*>(manager->GetRoot());
ScopedJavaLocalRef<jobject> j_root =
WalkAXTreeDepthFirst(env, root, scale_factor, y_offset, x_scroll);
Java_WebContentsImpl_onAccessibilitySnapshot(
env, j_root.obj(), callback.obj());
}
void ReleaseAllMediaPlayers(WebContents* web_contents,
RenderFrameHost* render_frame_host) {
BrowserMediaPlayerManager* manager =
static_cast<WebContentsImpl*>(web_contents)->
media_web_contents_observer()->GetMediaPlayerManager(
render_frame_host);
if (manager)
manager->ReleaseAllMediaPlayers();
}
} // namespace
// static
WebContents* WebContents::FromJavaWebContents(
jobject jweb_contents_android) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
if (!jweb_contents_android)
return NULL;
WebContentsAndroid* web_contents_android =
reinterpret_cast<WebContentsAndroid*>(
Java_WebContentsImpl_getNativePointer(AttachCurrentThread(),
jweb_contents_android));
if (!web_contents_android)
return NULL;
return web_contents_android->web_contents();
}
// static
static void DestroyWebContents(JNIEnv* env,
jclass clazz,
jlong jweb_contents_android_ptr) {
WebContentsAndroid* web_contents_android =
reinterpret_cast<WebContentsAndroid*>(jweb_contents_android_ptr);
if (!web_contents_android)
return;
WebContents* web_contents = web_contents_android->web_contents();
if (!web_contents)
return;
delete web_contents;
}
// static
jobject FromNativePtr(JNIEnv* env,
jclass clazz,
jlong web_contents_ptr) {
WebContentsAndroid* web_contents_android =
reinterpret_cast<WebContentsAndroid*>(web_contents_ptr);
if (!web_contents_android)
return 0;
// Check to make sure this object hasn't been destroyed.
if (g_allocated_web_contents_androids.Get().find(web_contents_android) ==
g_allocated_web_contents_androids.Get().end()) {
return 0;
}
return web_contents_android->GetJavaObject().Release();
}
// static
bool WebContentsAndroid::Register(JNIEnv* env) {
return RegisterNativesImpl(env);
}
WebContentsAndroid::WebContentsAndroid(WebContents* web_contents)
: web_contents_(web_contents),
navigation_controller_(&(web_contents->GetController())),
weak_factory_(this) {
g_allocated_web_contents_androids.Get().insert(this);
JNIEnv* env = AttachCurrentThread();
obj_.Reset(env,
Java_WebContentsImpl_create(
env,
reinterpret_cast<intptr_t>(this),
navigation_controller_.GetJavaObject().obj()).obj());
RendererPreferences* prefs = web_contents_->GetMutableRendererPrefs();
base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
prefs->network_contry_iso =
command_line->HasSwitch(switches::kNetworkCountryIso) ?
command_line->GetSwitchValueASCII(switches::kNetworkCountryIso)
: net::android::GetTelephonyNetworkCountryIso();
}
WebContentsAndroid::~WebContentsAndroid() {
DCHECK(g_allocated_web_contents_androids.Get().find(this) !=
g_allocated_web_contents_androids.Get().end());
g_allocated_web_contents_androids.Get().erase(this);
Java_WebContentsImpl_clearNativePtr(AttachCurrentThread(), obj_.obj());
}
base::android::ScopedJavaLocalRef<jobject>
WebContentsAndroid::GetJavaObject() {
return base::android::ScopedJavaLocalRef<jobject>(obj_);
}
ScopedJavaLocalRef<jstring> WebContentsAndroid::GetTitle(
JNIEnv* env, jobject obj) const {
return base::android::ConvertUTF16ToJavaString(env,
web_contents_->GetTitle());
}
ScopedJavaLocalRef<jstring> WebContentsAndroid::GetVisibleURL(
JNIEnv* env, jobject obj) const {
return base::android::ConvertUTF8ToJavaString(
env, web_contents_->GetVisibleURL().spec());
}
bool WebContentsAndroid::IsLoading(JNIEnv* env, jobject obj) const {
return web_contents_->IsLoading();
}
bool WebContentsAndroid::IsLoadingToDifferentDocument(JNIEnv* env,
jobject obj) const {
return web_contents_->IsLoadingToDifferentDocument();
}
void WebContentsAndroid::Stop(JNIEnv* env, jobject obj) {
web_contents_->Stop();
}
void WebContentsAndroid::Cut(JNIEnv* env, jobject obj) {
web_contents_->Cut();
}
void WebContentsAndroid::Copy(JNIEnv* env, jobject obj) {
web_contents_->Copy();
}
void WebContentsAndroid::Paste(JNIEnv* env, jobject obj) {
web_contents_->Paste();
}
void WebContentsAndroid::SelectAll(JNIEnv* env, jobject obj) {
web_contents_->SelectAll();
}
void WebContentsAndroid::Unselect(JNIEnv* env, jobject obj) {
web_contents_->Unselect();
}
void WebContentsAndroid::InsertCSS(
JNIEnv* env, jobject jobj, jstring jcss) {
web_contents_->InsertCSS(base::android::ConvertJavaStringToUTF8(env, jcss));
}
RenderWidgetHostViewAndroid*
WebContentsAndroid::GetRenderWidgetHostViewAndroid() {
RenderWidgetHostView* rwhv = NULL;
rwhv = web_contents_->GetRenderWidgetHostView();
if (web_contents_->ShowingInterstitialPage()) {
rwhv = web_contents_->GetInterstitialPage()
->GetMainFrame()
->GetRenderViewHost()
->GetView();
}
return static_cast<RenderWidgetHostViewAndroid*>(rwhv);
}
jint WebContentsAndroid::GetBackgroundColor(JNIEnv* env, jobject obj) {
RenderWidgetHostViewAndroid* rwhva = GetRenderWidgetHostViewAndroid();
if (!rwhva)
return SK_ColorWHITE;
return rwhva->GetCachedBackgroundColor();
}
ScopedJavaLocalRef<jstring> WebContentsAndroid::GetURL(JNIEnv* env,
jobject obj) const {
return ConvertUTF8ToJavaString(env, web_contents_->GetURL().spec());
}
ScopedJavaLocalRef<jstring> WebContentsAndroid::GetLastCommittedURL(
JNIEnv* env,
jobject) const {
return ConvertUTF8ToJavaString(env,
web_contents_->GetLastCommittedURL().spec());
}
jboolean WebContentsAndroid::IsIncognito(JNIEnv* env, jobject obj) {
return web_contents_->GetBrowserContext()->IsOffTheRecord();
}
void WebContentsAndroid::ResumeLoadingCreatedWebContents(JNIEnv* env,
jobject obj) {
web_contents_->ResumeLoadingCreatedWebContents();
}
void WebContentsAndroid::OnHide(JNIEnv* env, jobject obj) {
web_contents_->WasHidden();
}
void WebContentsAndroid::OnShow(JNIEnv* env, jobject obj) {
web_contents_->WasShown();
}
void WebContentsAndroid::ReleaseMediaPlayers(JNIEnv* env, jobject jobj) {
#if defined(ENABLE_BROWSER_CDMS)
web_contents_->ForEachFrame(
base::Bind(&ReleaseAllMediaPlayers, base::Unretained(web_contents_)));
#endif // defined(ENABLE_BROWSER_CDMS)
}
void WebContentsAndroid::ShowInterstitialPage(
JNIEnv* env,
jobject obj,
jstring jurl,
jlong delegate_ptr) {
GURL url(base::android::ConvertJavaStringToUTF8(env, jurl));
InterstitialPageDelegateAndroid* delegate =
reinterpret_cast<InterstitialPageDelegateAndroid*>(delegate_ptr);
InterstitialPage* interstitial = InterstitialPage::Create(
web_contents_, false, url, delegate);
delegate->set_interstitial_page(interstitial);
interstitial->Show();
}
jboolean WebContentsAndroid::IsShowingInterstitialPage(JNIEnv* env,
jobject obj) {
return web_contents_->ShowingInterstitialPage();
}
jboolean WebContentsAndroid::IsRenderWidgetHostViewReady(
JNIEnv* env,
jobject obj) {
RenderWidgetHostViewAndroid* view = GetRenderWidgetHostViewAndroid();
return view && view->HasValidFrame();
}
void WebContentsAndroid::ExitFullscreen(JNIEnv* env, jobject obj) {
web_contents_->ExitFullscreen();
}
void WebContentsAndroid::UpdateTopControlsState(
JNIEnv* env,
jobject obj,
bool enable_hiding,
bool enable_showing,
bool animate) {
RenderViewHost* host = web_contents_->GetRenderViewHost();
if (!host)
return;
host->Send(new ViewMsg_UpdateTopControlsState(host->GetRoutingID(),
enable_hiding,
enable_showing,
animate));
}
void WebContentsAndroid::ShowImeIfNeeded(JNIEnv* env, jobject obj) {
RenderViewHost* host = web_contents_->GetRenderViewHost();
if (!host)
return;
host->Send(new ViewMsg_ShowImeIfNeeded(host->GetRoutingID()));
}
void WebContentsAndroid::ScrollFocusedEditableNodeIntoView(
JNIEnv* env,
jobject obj) {
RenderViewHost* host = web_contents_->GetRenderViewHost();
if (!host)
return;
host->Send(new InputMsg_ScrollFocusedEditableNodeIntoRect(
host->GetRoutingID(), gfx::Rect()));
}
void WebContentsAndroid::SelectWordAroundCaret(JNIEnv* env, jobject obj) {
RenderViewHost* host = web_contents_->GetRenderViewHost();
if (!host)
return;
host->SelectWordAroundCaret();
}
void WebContentsAndroid::AdjustSelectionByCharacterOffset(JNIEnv* env,
jobject obj,
jint start_adjust,
jint end_adjust) {
web_contents_->AdjustSelectionByCharacterOffset(start_adjust, end_adjust);
}
void WebContentsAndroid::EvaluateJavaScript(JNIEnv* env,
jobject obj,
jstring script,
jobject callback) {
RenderViewHost* rvh = web_contents_->GetRenderViewHost();
DCHECK(rvh);
if (!rvh->IsRenderViewLive()) {
if (!static_cast<WebContentsImpl*>(web_contents_)->
CreateRenderViewForInitialEmptyDocument()) {
LOG(ERROR) << "Failed to create RenderView in EvaluateJavaScript";
return;
}
}
if (!callback) {
// No callback requested.
web_contents_->GetMainFrame()->ExecuteJavaScript(
ConvertJavaStringToUTF16(env, script));
return;
}
// Secure the Java callback in a scoped object and give ownership of it to the
// base::Callback.
ScopedJavaGlobalRef<jobject> j_callback;
j_callback.Reset(env, callback);
RenderFrameHost::JavaScriptResultCallback js_callback =
base::Bind(&JavaScriptResultCallback, j_callback);
web_contents_->GetMainFrame()->ExecuteJavaScript(
ConvertJavaStringToUTF16(env, script), js_callback);
}
void WebContentsAndroid::AddMessageToDevToolsConsole(JNIEnv* env,
jobject jobj,
jint level,
jstring message) {
DCHECK_GE(level, 0);
DCHECK_LE(level, CONSOLE_MESSAGE_LEVEL_LAST);
web_contents_->GetMainFrame()->AddMessageToConsole(
static_cast<ConsoleMessageLevel>(level),
ConvertJavaStringToUTF8(env, message));
}
jboolean WebContentsAndroid::HasAccessedInitialDocument(
JNIEnv* env,
jobject jobj) {
return static_cast<WebContentsImpl*>(web_contents_)->
HasAccessedInitialDocument();
}
jint WebContentsAndroid::GetThemeColor(JNIEnv* env, jobject obj) {
return web_contents_->GetThemeColor();
}
void WebContentsAndroid::RequestAccessibilitySnapshot(JNIEnv* env,
jobject obj,
jobject callback,
jfloat y_offset,
jfloat x_scroll) {
// Secure the Java callback in a scoped object and give ownership of it to the
// base::Callback.
ScopedJavaGlobalRef<jobject> j_callback;
j_callback.Reset(env, callback);
gfx::DeviceDisplayInfo device_info;
ContentViewCoreImpl* contentViewCore =
ContentViewCoreImpl::FromWebContents(web_contents_);
WebContentsImpl::AXTreeSnapshotCallback snapshot_callback =
base::Bind(&AXTreeSnapshotCallback, j_callback,
contentViewCore->GetScaleFactor(), y_offset, x_scroll);
static_cast<WebContentsImpl*>(web_contents_)->RequestAXTreeSnapshot(
snapshot_callback);
}
void WebContentsAndroid::ResumeMediaSession(JNIEnv* env, jobject obj) {
web_contents_->ResumeMediaSession();
}
void WebContentsAndroid::SuspendMediaSession(JNIEnv* env, jobject obj) {
web_contents_->SuspendMediaSession();
}
} // namespace content