1 // Copyright 2013 The Chromium Authors. All rights reserved.
2 // Copyright (c) 2013-2014 Intel Corporation. All rights reserved.
3 // Use of this source code is governed by a BSD-style license that can be
4 // found in the LICENSE file.
6 package org.xwalk.core;
8 import android.app.Activity;
9 import android.content.Context;
10 import android.content.Intent;
11 import android.content.SharedPreferences;
12 import android.graphics.Rect;
13 import android.os.Bundle;
14 import android.text.TextUtils;
15 import android.util.AttributeSet;
16 import android.view.ViewGroup;
17 import android.webkit.ValueCallback;
18 import android.webkit.WebResourceResponse;
19 import android.widget.FrameLayout;
21 import org.chromium.base.CalledByNative;
22 import org.chromium.base.JNINamespace;
23 import org.chromium.base.ThreadUtils;
24 import org.chromium.components.navigation_interception.InterceptNavigationDelegate;
25 import org.chromium.content.browser.ContentVideoView;
26 import org.chromium.content.browser.ContentView;
27 import org.chromium.content.browser.ContentViewCore;
28 import org.chromium.content.browser.ContentViewRenderView;
29 import org.chromium.content.browser.ContentViewStatics;
30 import org.chromium.content.browser.LoadUrlParams;
31 import org.chromium.content.browser.NavigationHistory;
32 import org.chromium.media.MediaPlayerBridge;
33 import org.chromium.ui.base.ActivityWindowAndroid;
35 @JNINamespace("xwalk")
37 * This class is the implementation class for XWalkView by calling internal
40 public class XWalkContent extends FrameLayout implements XWalkPreferences.KeyValueChangeListener {
41 private ContentViewCore mContentViewCore;
42 private ContentView mContentView;
43 private ContentViewRenderView mContentViewRenderView;
44 private ActivityWindowAndroid mWindow;
45 private XWalkDevToolsServer mDevToolsServer;
46 private XWalkView mXWalkView;
47 private XWalkContentsClientBridge mContentsClientBridge;
48 private XWalkContentsIoThreadClient mIoThreadClient;
49 private XWalkWebContentsDelegateAdapter mXWalkContentsDelegateAdapter;
50 private XWalkSettings mSettings;
51 private XWalkGeolocationPermissions mGeolocationPermissions;
52 private XWalkLaunchScreenManager mLaunchScreenManager;
56 boolean mReadyToLoad = false;
57 String mPendingUrl = null;
59 public XWalkContent(Context context, AttributeSet attrs, XWalkView xwView) {
60 super(context, attrs);
62 // Initialize the WebContensDelegate.
64 mContentsClientBridge = new XWalkContentsClientBridge(mXWalkView);
65 mXWalkContentsDelegateAdapter = new XWalkWebContentsDelegateAdapter(
66 mContentsClientBridge);
67 mIoThreadClient = new XWalkIoThreadClientImpl();
69 // Initialize mWindow which is needed by content
70 mWindow = new ActivityWindowAndroid(xwView.getActivity());
72 // Initialize ContentViewRenderView
73 mContentViewRenderView = new ContentViewRenderView(context, mWindow) {
74 protected void onReadyToRender() {
75 if (mPendingUrl != null) {
76 doLoadUrl(mPendingUrl);
83 mLaunchScreenManager = new XWalkLaunchScreenManager(context, mXWalkView);
84 mContentViewRenderView.registerFirstRenderedFrameListener(mLaunchScreenManager);
85 addView(mContentViewRenderView,
86 new FrameLayout.LayoutParams(
87 FrameLayout.LayoutParams.MATCH_PARENT,
88 FrameLayout.LayoutParams.MATCH_PARENT));
90 mXWalkContent = nativeInit(mXWalkContentsDelegateAdapter, mContentsClientBridge);
91 mWebContents = nativeGetWebContents(mXWalkContent, mIoThreadClient,
92 mContentsClientBridge.getInterceptNavigationDelegate());
94 // Initialize ContentView.
95 mContentView = ContentView.newInstance(getContext(), mWebContents, mWindow);
97 new FrameLayout.LayoutParams(
98 FrameLayout.LayoutParams.MATCH_PARENT,
99 FrameLayout.LayoutParams.MATCH_PARENT));
100 mContentView.setContentViewClient(mContentsClientBridge);
102 mContentViewRenderView.setCurrentContentView(mContentView);
104 // For addJavascriptInterface
105 mContentViewCore = mContentView.getContentViewCore();
106 mContentsClientBridge.installWebContentsObserver(mContentViewCore);
108 mContentView.getContentViewCore().setDownloadDelegate(mContentsClientBridge);
110 // Set the third argument isAccessFromFileURLsGrantedByDefault to false, so that
111 // the members mAllowUniversalAccessFromFileURLs and mAllowFileAccessFromFileURLs
112 // won't be changed from false to true at the same time in the constructor of
113 // XWalkSettings class.
114 mSettings = new XWalkSettings(getContext(), mWebContents, false);
115 // Enable AllowFileAccessFromFileURLs, so that files under file:// path could be
116 // loaded by XMLHttpRequest.
117 mSettings.setAllowFileAccessFromFileURLs(true);
119 SharedPreferences sharedPreferences = new InMemorySharedPreferences();
120 mGeolocationPermissions = new XWalkGeolocationPermissions(sharedPreferences);
122 MediaPlayerBridge.setResourceLoadingFilter(
123 new XWalkMediaPlayerResourceLoadingFilter());
125 XWalkPreferences.load(this);
128 void doLoadUrl(String url) {
129 //TODO(Xingnan): Configure appropriate parameters here.
130 // Handle the same url loading by parameters.
131 if (TextUtils.equals(url, mContentView.getUrl())) {
132 mContentView.getContentViewCore().reload(true);
134 LoadUrlParams params = new LoadUrlParams(url);
135 params.setOverrideUserAgent(LoadUrlParams.UA_OVERRIDE_TRUE);
136 mContentView.loadUrl(params);
139 mContentView.requestFocus();
142 public void loadUrl(String url) {
152 public void reload() {
154 mContentView.getContentViewCore().reload(true);
158 public String getUrl() {
159 String url = mContentView.getUrl();
160 if (url == null || url.trim().isEmpty()) return null;
164 public String getTitle() {
165 String title = mContentView.getTitle().trim();
166 if (title == null) title = "";
170 public void addJavascriptInterface(Object object, String name) {
171 mContentViewCore.addJavascriptInterface(object, name);
174 public void evaluateJavascript(String script, ValueCallback<String> callback) {
175 final ValueCallback<String> fCallback = callback;
176 ContentViewCore.JavaScriptCallback coreCallback = new ContentViewCore.JavaScriptCallback() {
178 public void handleJavaScriptResult(String jsonResult) {
179 fCallback.onReceiveValue(jsonResult);
182 mContentViewCore.evaluateJavaScript(script, coreCallback);
185 public void setUIClient(XWalkUIClient client) {
186 mContentsClientBridge.setUIClient(client);
189 public void setResourceClient(XWalkResourceClient client) {
190 mContentsClientBridge.setResourceClient(client);
193 public void setXWalkWebChromeClient(XWalkWebChromeClient client) {
194 mContentsClientBridge.setXWalkWebChromeClient(client);
197 public XWalkWebChromeClient getXWalkWebChromeClient() {
198 return mContentsClientBridge.getXWalkWebChromeClient();
201 public void setXWalkClient(XWalkClient client) {
202 mContentsClientBridge.setXWalkClient(client);
205 public void setDownloadListener(DownloadListener listener) {
206 mContentsClientBridge.setDownloadListener(listener);
209 public void setNavigationHandler(XWalkNavigationHandler handler) {
210 mContentsClientBridge.setNavigationHandler(handler);
213 public void setNotificationService(XWalkNotificationService service) {
214 mContentsClientBridge.setNotificationService(service);
217 public void onPause() {
218 mContentView.onHide();
221 public void onResume() {
222 mContentView.onShow();
225 public void onActivityResult(int requestCode, int resultCode, Intent data) {
226 mWindow.onActivityResult(requestCode, resultCode, data);
229 public boolean onNewIntent(Intent intent) {
230 return mContentsClientBridge.onNewIntent(intent);
233 public void clearCache(boolean includeDiskFiles) {
234 if (mXWalkContent == 0) return;
235 nativeClearCache(mXWalkContent, includeDiskFiles);
238 public void clearHistory() {
239 mContentView.getContentViewCore().clearHistory();
242 public boolean canGoBack() {
243 return mContentView.canGoBack();
246 public void goBack() {
247 mContentView.goBack();
250 public boolean canGoForward() {
251 return mContentView.canGoForward();
254 public void goForward() {
255 mContentView.goForward();
258 void navigateTo(int offset) {
259 mContentView.getContentViewCore().goToOffset(offset);
262 public void stopLoading() {
263 mContentView.getContentViewCore().stopLoading();
266 // TODO(Guangzhen): ContentViewStatics will be removed in upstream,
267 // details in content_view_statics.cc.
268 // We need follow up after upstream updates that.
269 public void pauseTimers() {
270 ContentViewStatics.setWebKitSharedTimersSuspended(true);
273 public void resumeTimers() {
274 ContentViewStatics.setWebKitSharedTimersSuspended(false);
277 public String getOriginalUrl() {
278 NavigationHistory history = mContentViewCore.getNavigationHistory();
279 int currentIndex = history.getCurrentEntryIndex();
280 if (currentIndex >= 0 && currentIndex < history.getEntryCount()) {
281 return history.getEntryAtIndex(currentIndex).getOriginalUrl();
286 public String getXWalkVersion() {
287 if (mXWalkContent == 0) return "";
288 return nativeGetVersion(mXWalkContent);
291 public void setNetworkAvailable(boolean networkUp) {
292 if (mXWalkContent == 0) return;
293 nativeSetJsOnlineProperty(mXWalkContent, networkUp);
296 // For instrumentation test.
297 public ContentViewCore getContentViewCoreForTest() {
298 return mContentViewCore;
301 // For instrumentation test.
302 public void installWebContentsObserverForTest(XWalkContentsClient contentClient) {
303 contentClient.installWebContentsObserver(mContentViewCore);
306 public String devToolsAgentId() {
307 if (mXWalkContent == 0) return "";
308 return nativeDevToolsAgentId(mXWalkContent);
311 public XWalkSettings getSettings() {
315 public void loadAppFromManifest(String path, String manifest) {
316 if (path == null || manifest == null || mXWalkContent == 0) {
320 if (!nativeSetManifest(mXWalkContent, path, manifest)) {
321 throw new RuntimeException("Failed to parse the manifest file.");
325 public XWalkNavigationHistory getNavigationHistory() {
326 return new XWalkNavigationHistory(mXWalkView, mContentViewCore.getNavigationHistory());
329 public static final String SAVE_RESTORE_STATE_KEY = "XWALKVIEW_STATE";
331 public XWalkNavigationHistory saveState(Bundle outState) {
332 if (outState == null) return null;
334 byte[] state = nativeGetState(mXWalkContent);
335 if (state == null) return null;
337 outState.putByteArray(SAVE_RESTORE_STATE_KEY, state);
338 return getNavigationHistory();
341 public XWalkNavigationHistory restoreState(Bundle inState) {
342 if (inState == null) return null;
344 byte[] state = inState.getByteArray(SAVE_RESTORE_STATE_KEY);
345 if (state == null) return null;
347 boolean result = nativeSetState(mXWalkContent, state);
349 // The onUpdateTitle callback normally happens when a page is loaded,
350 // but is optimized out in the restoreState case because the title is
351 // already restored. See WebContentsImpl::UpdateTitleForEntry. So we
352 // call the callback explicitly here.
354 mContentsClientBridge.onUpdateTitle(mContentViewCore.getTitle());
357 return result ? getNavigationHistory() : null;
360 boolean hasEnteredFullscreen() {
361 return mContentsClientBridge.hasEnteredFullscreen();
364 void exitFullscreen() {
365 if (hasEnteredFullscreen()) {
366 mContentsClientBridge.exitFullscreen(mWebContents);
371 public void onGetUrlFromManifest(String url) {
372 if (url != null && !url.isEmpty()) {
378 public void onGetUrlAndLaunchScreenFromManifest(String url, String readyWhen) {
379 if (url == null || url.isEmpty()) return;
380 mLaunchScreenManager.displayLaunchScreen(readyWhen);
381 mContentsClientBridge.registerPageLoadListener(mLaunchScreenManager);
386 public void onGetFullscreenFlagFromManifest(boolean enterFullscreen) {
387 if (enterFullscreen) mContentsClientBridge.onToggleFullscreen(true);
390 public void destroy() {
391 if (mXWalkContent == 0) return;
393 // Reset existing notification service in order to destruct it.
394 setNotificationService(null);
395 // Remove its children used for page rendering from view hierarchy.
396 removeView(mContentView);
397 removeView(mContentViewRenderView);
398 mContentViewRenderView.setCurrentContentView(null);
400 // Destroy the native resources.
401 mContentViewRenderView.destroy();
402 mContentView.destroy();
404 nativeDestroy(mXWalkContent);
408 public int getRoutingID() {
409 return nativeGetRoutingID(mXWalkContent);
412 //--------------------------------------------------------------------------------------------
413 private class XWalkIoThreadClientImpl implements XWalkContentsIoThreadClient {
414 // All methods are called on the IO thread.
417 public int getCacheMode() {
418 return mSettings.getCacheMode();
422 public InterceptedRequestData shouldInterceptRequest(final String url,
423 boolean isMainFrame) {
425 // Notify a resource load is started. This is not the best place to start the callback
426 // but it's a workable way.
427 mContentsClientBridge.onResourceLoadStarted(url);
429 WebResourceResponse webResourceResponse = mContentsClientBridge.shouldInterceptRequest(url);
430 InterceptedRequestData interceptedRequestData = null;
432 if (webResourceResponse == null) {
433 mContentsClientBridge.getCallbackHelper().postOnLoadResource(url);
435 if (isMainFrame && webResourceResponse.getData() == null) {
436 mContentsClientBridge.getCallbackHelper().postOnReceivedError(-1, null, url);
438 interceptedRequestData = new InterceptedRequestData(webResourceResponse.getMimeType(),
439 webResourceResponse.getEncoding(),
440 webResourceResponse.getData());
442 return interceptedRequestData;
446 public boolean shouldBlockContentUrls() {
447 return !mSettings.getAllowContentAccess();
451 public boolean shouldBlockFileUrls() {
452 return !mSettings.getAllowFileAccess();
456 public boolean shouldBlockNetworkLoads() {
457 return mSettings.getBlockNetworkLoads();
461 public void onDownloadStart(String url,
463 String contentDisposition,
465 long contentLength) {
466 mContentsClientBridge.getCallbackHelper().postOnDownloadStart(url, userAgent,
467 contentDisposition, mimeType, contentLength);
471 public void newLoginRequest(String realm, String account, String args) {
472 mContentsClientBridge.getCallbackHelper().postOnReceivedLoginRequest(realm, account, args);
476 private class XWalkGeolocationCallback implements XWalkGeolocationPermissions.Callback {
478 public void invoke(final String origin, final boolean allow, final boolean retain) {
479 ThreadUtils.runOnUiThread(new Runnable() {
484 mGeolocationPermissions.allow(origin);
486 mGeolocationPermissions.deny(origin);
489 nativeInvokeGeolocationCallback(mXWalkContent, allow, origin);
496 private void onGeolocationPermissionsShowPrompt(String origin) {
497 // Reject if geolocation is disabled, or the origin has a retained deny.
498 if (!mSettings.getGeolocationEnabled()) {
499 nativeInvokeGeolocationCallback(mXWalkContent, false, origin);
502 // Allow if the origin has a retained allow.
503 if (mGeolocationPermissions.hasOrigin(origin)) {
504 nativeInvokeGeolocationCallback(mXWalkContent,
505 mGeolocationPermissions.isOriginAllowed(origin),
509 mContentsClientBridge.onGeolocationPermissionsShowPrompt(
510 origin, new XWalkGeolocationCallback());
514 public void onGeolocationPermissionsHidePrompt() {
515 mContentsClientBridge.onGeolocationPermissionsHidePrompt();
518 public String enableRemoteDebugging(int allowedUid) {
519 // Chrome looks for "devtools_remote" pattern in the name of a unix domain socket
520 // to identify a debugging page
521 final String socketName = getContext().getApplicationContext().getPackageName() + "_devtools_remote";
522 if (mDevToolsServer == null) {
523 mDevToolsServer = new XWalkDevToolsServer(socketName);
524 mDevToolsServer.allowConnectionFromUid(allowedUid);
525 mDevToolsServer.setRemoteDebuggingEnabled(true);
527 // devtools/page is hardcoded in devtools_http_handler_impl.cc (kPageUrlPrefix)
528 return "ws://" + socketName + "/devtools/page/" + devToolsAgentId();
531 // Enables remote debugging and returns the URL at which the dev tools server is listening
532 // for commands. Only the current process is allowed to connect to the server.
533 String enableRemoteDebugging() {
534 return enableRemoteDebugging(getContext().getApplicationInfo().uid);
537 void disableRemoteDebugging() {
538 if (mDevToolsServer == null) return;
540 if (mDevToolsServer.isRemoteDebuggingEnabled()) {
541 mDevToolsServer.setRemoteDebuggingEnabled(false);
543 mDevToolsServer.destroy();
544 mDevToolsServer = null;
548 public void onKeyValueChanged(String key, boolean value) {
549 if (key == XWalkPreferences.REMOTE_DEBUGGING) {
550 if (value) enableRemoteDebugging();
551 else disableRemoteDebugging();
555 private native int nativeInit(XWalkWebContentsDelegate webViewContentsDelegate,
556 XWalkContentsClientBridge bridge);
557 private static native void nativeDestroy(int nativeXWalkContent);
558 private native int nativeGetWebContents(int nativeXWalkContent,
559 XWalkContentsIoThreadClient ioThreadClient,
560 InterceptNavigationDelegate delegate);
561 private native void nativeClearCache(int nativeXWalkContent, boolean includeDiskFiles);
562 private native String nativeDevToolsAgentId(int nativeXWalkContent);
563 private native String nativeGetVersion(int nativeXWalkContent);
564 private native void nativeSetJsOnlineProperty(int nativeXWalkContent, boolean networkUp);
565 private native boolean nativeSetManifest(int nativeXWalkContent, String path, String manifest);
566 private native int nativeGetRoutingID(int nativeXWalkContent);
567 private native void nativeInvokeGeolocationCallback(
568 int nativeXWalkContent, boolean value, String requestingFrame);
569 private native byte[] nativeGetState(int nativeXWalkContent);
570 private native boolean nativeSetState(int nativeXWalkContent, byte[] state);