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.util.Log;
17 import android.view.ViewGroup;
18 import android.webkit.ValueCallback;
19 import android.webkit.WebResourceResponse;
20 import android.widget.FrameLayout;
22 import java.io.IOException;
23 import java.io.InputStream;
25 import org.chromium.base.CalledByNative;
26 import org.chromium.base.JNINamespace;
27 import org.chromium.base.ThreadUtils;
28 import org.chromium.components.navigation_interception.InterceptNavigationDelegate;
29 import org.chromium.content.browser.ContentView;
30 import org.chromium.content.browser.ContentViewCore;
31 import org.chromium.content.browser.ContentViewRenderView;
32 import org.chromium.content.browser.ContentViewRenderView.CompositingSurfaceType;
33 import org.chromium.content.browser.ContentViewStatics;
34 import org.chromium.content.browser.LoadUrlParams;
35 import org.chromium.content.browser.NavigationHistory;
36 import org.chromium.media.MediaPlayerBridge;
37 import org.chromium.ui.base.ActivityWindowAndroid;
39 @JNINamespace("xwalk")
41 * This class is the implementation class for XWalkView by calling internal
44 class XWalkContent extends FrameLayout implements XWalkPreferences.KeyValueChangeListener {
45 private static String TAG = "XWalkContent";
46 private ContentViewCore mContentViewCore;
47 private ContentView mContentView;
48 private ContentViewRenderView mContentViewRenderView;
49 private ActivityWindowAndroid mWindow;
50 private XWalkDevToolsServer mDevToolsServer;
51 private XWalkView mXWalkView;
52 private XWalkContentsClientBridge mContentsClientBridge;
53 private XWalkContentsIoThreadClient mIoThreadClient;
54 private XWalkWebContentsDelegateAdapter mXWalkContentsDelegateAdapter;
55 private XWalkSettings mSettings;
56 private XWalkGeolocationPermissions mGeolocationPermissions;
57 private XWalkLaunchScreenManager mLaunchScreenManager;
61 boolean mReadyToLoad = false;
62 String mPendingUrl = null;
63 String mPendingData = null;
65 public XWalkContent(Context context, AttributeSet attrs, XWalkView xwView) {
66 super(context, attrs);
68 // Initialize the WebContensDelegate.
70 mContentsClientBridge = new XWalkContentsClientBridge(mXWalkView);
71 mXWalkContentsDelegateAdapter = new XWalkWebContentsDelegateAdapter(
72 mContentsClientBridge);
73 mIoThreadClient = new XWalkIoThreadClientImpl();
75 // Initialize mWindow which is needed by content
76 mWindow = new ActivityWindowAndroid(xwView.getActivity());
78 // Initialize ContentViewRenderView
79 boolean animated = XWalkPreferences.getValue(XWalkPreferences.ANIMATABLE_XWALK_VIEW);
80 CompositingSurfaceType surfaceType =
81 animated ? CompositingSurfaceType.TEXTURE_VIEW : CompositingSurfaceType.SURFACE_VIEW;
82 mContentViewRenderView = new ContentViewRenderView(context, mWindow, surfaceType) {
83 protected void onReadyToRender() {
84 if (mPendingUrl != null) {
85 doLoadUrl(mPendingUrl, mPendingData);
93 mLaunchScreenManager = new XWalkLaunchScreenManager(context, mXWalkView);
94 mContentViewRenderView.registerFirstRenderedFrameListener(mLaunchScreenManager);
95 addView(mContentViewRenderView,
96 new FrameLayout.LayoutParams(
97 FrameLayout.LayoutParams.MATCH_PARENT,
98 FrameLayout.LayoutParams.MATCH_PARENT));
100 mXWalkContent = nativeInit(mXWalkContentsDelegateAdapter, mContentsClientBridge);
101 mWebContents = nativeGetWebContents(mXWalkContent, mIoThreadClient,
102 mContentsClientBridge.getInterceptNavigationDelegate());
104 // Initialize ContentView.
105 mContentView = ContentView.newInstance(getContext(), mWebContents, mWindow);
106 addView(mContentView,
107 new FrameLayout.LayoutParams(
108 FrameLayout.LayoutParams.MATCH_PARENT,
109 FrameLayout.LayoutParams.MATCH_PARENT));
110 mContentView.getContentViewCore().setContentViewClient(mContentsClientBridge);
112 mContentViewCore = mContentView.getContentViewCore();
113 mContentViewRenderView.setCurrentContentViewCore(mContentViewCore);
114 // For addJavascriptInterface
115 mContentsClientBridge.installWebContentsObserver(mContentViewCore);
117 mContentViewCore.setDownloadDelegate(mContentsClientBridge);
119 // Set the third argument isAccessFromFileURLsGrantedByDefault to false, so that
120 // the members mAllowUniversalAccessFromFileURLs and mAllowFileAccessFromFileURLs
121 // won't be changed from false to true at the same time in the constructor of
122 // XWalkSettings class.
123 mSettings = new XWalkSettings(getContext(), mWebContents, false);
124 // Enable AllowFileAccessFromFileURLs, so that files under file:// path could be
125 // loaded by XMLHttpRequest.
126 mSettings.setAllowFileAccessFromFileURLs(true);
128 SharedPreferences sharedPreferences = new InMemorySharedPreferences();
129 mGeolocationPermissions = new XWalkGeolocationPermissions(sharedPreferences);
131 MediaPlayerBridge.setResourceLoadingFilter(
132 new XWalkMediaPlayerResourceLoadingFilter());
134 XWalkPreferences.load(this);
137 void doLoadUrl(String url, String content) {
138 // Handle the same url loading by parameters.
139 if (url != null && !url.isEmpty() &&
140 TextUtils.equals(url, mContentViewCore.getUrl())) {
141 mContentViewCore.reload(true);
143 LoadUrlParams params = null;
144 if (content == null || content.isEmpty()) {
145 params = new LoadUrlParams(url);
147 params = LoadUrlParams.createLoadDataParamsWithBaseUrl(
148 content, "text/html", false, url, null);
150 params.setOverrideUserAgent(LoadUrlParams.UA_OVERRIDE_TRUE);
151 mContentViewCore.loadUrl(params);
154 mContentView.requestFocus();
157 public void loadUrl(String url, String data) {
158 if ((url == null || url.isEmpty()) &&
159 (data == null || data.isEmpty())) {
164 doLoadUrl(url, data);
171 public void reload(int mode) {
174 case XWalkView.RELOAD_IGNORE_CACHE:
175 mContentViewCore.reloadIgnoringCache(true);
177 case XWalkView.RELOAD_NORMAL:
179 mContentViewCore.reload(true);
185 public String getUrl() {
186 String url = mContentViewCore.getUrl();
187 if (url == null || url.trim().isEmpty()) return null;
191 public String getTitle() {
192 String title = mContentViewCore.getTitle().trim();
193 if (title == null) title = "";
197 public void addJavascriptInterface(Object object, String name) {
198 mContentViewCore.addPossiblyUnsafeJavascriptInterface(object, name,
199 JavascriptInterface.class);
202 public void evaluateJavascript(String script, ValueCallback<String> callback) {
203 final ValueCallback<String> fCallback = callback;
204 ContentViewCore.JavaScriptCallback coreCallback = null;
205 if (fCallback != null) {
206 coreCallback = new ContentViewCore.JavaScriptCallback() {
208 public void handleJavaScriptResult(String jsonResult) {
209 fCallback.onReceiveValue(jsonResult);
213 mContentViewCore.evaluateJavaScript(script, coreCallback);
216 public void setUIClient(XWalkUIClient client) {
217 mContentsClientBridge.setUIClient(client);
220 public void setResourceClient(XWalkResourceClient client) {
221 mContentsClientBridge.setResourceClient(client);
224 public void setXWalkWebChromeClient(XWalkWebChromeClient client) {
225 mContentsClientBridge.setXWalkWebChromeClient(client);
228 public XWalkWebChromeClient getXWalkWebChromeClient() {
229 return mContentsClientBridge.getXWalkWebChromeClient();
232 public void setXWalkClient(XWalkClient client) {
233 mContentsClientBridge.setXWalkClient(client);
236 public void setDownloadListener(DownloadListener listener) {
237 mContentsClientBridge.setDownloadListener(listener);
240 public void setNavigationHandler(XWalkNavigationHandler handler) {
241 mContentsClientBridge.setNavigationHandler(handler);
244 public void setNotificationService(XWalkNotificationService service) {
245 mContentsClientBridge.setNotificationService(service);
248 public void onPause() {
249 mContentViewCore.onHide();
252 public void onResume() {
253 mContentViewCore.onShow();
256 public void onActivityResult(int requestCode, int resultCode, Intent data) {
257 mWindow.onActivityResult(requestCode, resultCode, data);
260 public boolean onNewIntent(Intent intent) {
261 return mContentsClientBridge.onNewIntent(intent);
264 public void clearCache(boolean includeDiskFiles) {
265 if (mXWalkContent == 0) return;
266 nativeClearCache(mXWalkContent, includeDiskFiles);
269 public void clearHistory() {
270 mContentViewCore.clearHistory();
273 public boolean canGoBack() {
274 return mContentViewCore.canGoBack();
277 public void goBack() {
278 mContentViewCore.goBack();
281 public boolean canGoForward() {
282 return mContentViewCore.canGoForward();
285 public void goForward() {
286 mContentViewCore.goForward();
289 void navigateTo(int offset) {
290 mContentViewCore.goToOffset(offset);
293 public void stopLoading() {
294 mContentViewCore.stopLoading();
297 // TODO(Guangzhen): ContentViewStatics will be removed in upstream,
298 // details in content_view_statics.cc.
299 // We need follow up after upstream updates that.
300 public void pauseTimers() {
301 ContentViewStatics.setWebKitSharedTimersSuspended(true);
304 public void resumeTimers() {
305 ContentViewStatics.setWebKitSharedTimersSuspended(false);
308 public String getOriginalUrl() {
309 NavigationHistory history = mContentViewCore.getNavigationHistory();
310 int currentIndex = history.getCurrentEntryIndex();
311 if (currentIndex >= 0 && currentIndex < history.getEntryCount()) {
312 return history.getEntryAtIndex(currentIndex).getOriginalUrl();
317 public String getXWalkVersion() {
318 if (mXWalkContent == 0) return "";
319 return nativeGetVersion(mXWalkContent);
322 public void setNetworkAvailable(boolean networkUp) {
323 if (mXWalkContent == 0) return;
324 nativeSetJsOnlineProperty(mXWalkContent, networkUp);
327 // For instrumentation test.
328 public ContentViewCore getContentViewCoreForTest() {
329 return mContentViewCore;
332 // For instrumentation test.
333 public void installWebContentsObserverForTest(XWalkContentsClient contentClient) {
334 contentClient.installWebContentsObserver(mContentViewCore);
337 public String devToolsAgentId() {
338 if (mXWalkContent == 0) return "";
339 return nativeDevToolsAgentId(mXWalkContent);
342 public XWalkSettings getSettings() {
346 public void loadAppFromManifest(String url, String data) {
347 if (mXWalkContent == 0 ||
348 ((url == null || url.isEmpty()) &&
349 (data == null || data.isEmpty()))) {
353 String content = data;
354 // If the data of manifest.json is not set, try to load it.
355 if (data == null || data.isEmpty()) {
357 content = AndroidProtocolHandler.getUrlContent(mXWalkView.getActivity(), url);
358 } catch (IOException e) {
359 throw new RuntimeException("Failed to read the manifest: " + url);
363 // Calculate the base url of manifestUrl. Used by native side.
364 // TODO(yongsheng): It's from runtime side. Need to find a better way
366 String baseUrl = url;
367 int position = url.lastIndexOf("/");
368 if (position != -1) {
369 baseUrl = url.substring(0, position + 1);
371 Log.w(TAG, "The url of manifest.json is probably not set correctly.");
374 if (!nativeSetManifest(mXWalkContent, baseUrl, content)) {
375 throw new RuntimeException("Failed to parse the manifest file: " + url);
379 public XWalkNavigationHistory getNavigationHistory() {
380 return new XWalkNavigationHistory(mXWalkView, mContentViewCore.getNavigationHistory());
383 public static final String SAVE_RESTORE_STATE_KEY = "XWALKVIEW_STATE";
385 public XWalkNavigationHistory saveState(Bundle outState) {
386 if (outState == null) return null;
388 byte[] state = nativeGetState(mXWalkContent);
389 if (state == null) return null;
391 outState.putByteArray(SAVE_RESTORE_STATE_KEY, state);
392 return getNavigationHistory();
395 public XWalkNavigationHistory restoreState(Bundle inState) {
396 if (inState == null) return null;
398 byte[] state = inState.getByteArray(SAVE_RESTORE_STATE_KEY);
399 if (state == null) return null;
401 boolean result = nativeSetState(mXWalkContent, state);
403 // The onUpdateTitle callback normally happens when a page is loaded,
404 // but is optimized out in the restoreState case because the title is
405 // already restored. See WebContentsImpl::UpdateTitleForEntry. So we
406 // call the callback explicitly here.
408 mContentsClientBridge.onUpdateTitle(mContentViewCore.getTitle());
411 return result ? getNavigationHistory() : null;
414 boolean hasEnteredFullscreen() {
415 return mContentsClientBridge.hasEnteredFullscreen();
418 void exitFullscreen() {
419 if (hasEnteredFullscreen()) {
420 mContentsClientBridge.exitFullscreen(mWebContents);
425 public void onGetUrlFromManifest(String url) {
426 if (url != null && !url.isEmpty()) {
432 public void onGetUrlAndLaunchScreenFromManifest(String url, String readyWhen, String imageBorder) {
433 if (url == null || url.isEmpty()) return;
434 mLaunchScreenManager.displayLaunchScreen(readyWhen, imageBorder);
435 mContentsClientBridge.registerPageLoadListener(mLaunchScreenManager);
440 public void onGetFullscreenFlagFromManifest(boolean enterFullscreen) {
441 if (enterFullscreen) mContentsClientBridge.onToggleFullscreen(true);
444 public void destroy() {
445 if (mXWalkContent == 0) return;
447 XWalkPreferences.unload(this);
448 // Reset existing notification service in order to destruct it.
449 setNotificationService(null);
450 // Remove its children used for page rendering from view hierarchy.
451 removeView(mContentView);
452 removeView(mContentViewRenderView);
453 mContentViewRenderView.setCurrentContentViewCore(null);
455 // Destroy the native resources.
456 mContentViewRenderView.destroy();
457 mContentViewCore.destroy();
459 nativeDestroy(mXWalkContent);
463 public int getRoutingID() {
464 return nativeGetRoutingID(mXWalkContent);
467 //--------------------------------------------------------------------------------------------
468 private class XWalkIoThreadClientImpl implements XWalkContentsIoThreadClient {
469 // All methods are called on the IO thread.
472 public int getCacheMode() {
473 return mSettings.getCacheMode();
477 public InterceptedRequestData shouldInterceptRequest(final String url,
478 boolean isMainFrame) {
480 // Notify a resource load is started. This is not the best place to start the callback
481 // but it's a workable way.
482 mContentsClientBridge.getCallbackHelper().postOnResourceLoadStarted(url);
484 WebResourceResponse webResourceResponse = mContentsClientBridge.shouldInterceptRequest(url);
485 InterceptedRequestData interceptedRequestData = null;
487 if (webResourceResponse == null) {
488 mContentsClientBridge.getCallbackHelper().postOnLoadResource(url);
490 if (isMainFrame && webResourceResponse.getData() == null) {
491 mContentsClientBridge.getCallbackHelper().postOnReceivedError(
492 XWalkResourceClient.ERROR_UNKNOWN, null, url);
494 interceptedRequestData = new InterceptedRequestData(webResourceResponse.getMimeType(),
495 webResourceResponse.getEncoding(),
496 webResourceResponse.getData());
498 return interceptedRequestData;
502 public boolean shouldBlockContentUrls() {
503 return !mSettings.getAllowContentAccess();
507 public boolean shouldBlockFileUrls() {
508 return !mSettings.getAllowFileAccess();
512 public boolean shouldBlockNetworkLoads() {
513 return mSettings.getBlockNetworkLoads();
517 public void onDownloadStart(String url,
519 String contentDisposition,
521 long contentLength) {
522 mContentsClientBridge.getCallbackHelper().postOnDownloadStart(url, userAgent,
523 contentDisposition, mimeType, contentLength);
527 public void newLoginRequest(String realm, String account, String args) {
528 mContentsClientBridge.getCallbackHelper().postOnReceivedLoginRequest(realm, account, args);
532 private class XWalkGeolocationCallback implements XWalkGeolocationPermissions.Callback {
534 public void invoke(final String origin, final boolean allow, final boolean retain) {
535 ThreadUtils.runOnUiThread(new Runnable() {
540 mGeolocationPermissions.allow(origin);
542 mGeolocationPermissions.deny(origin);
545 nativeInvokeGeolocationCallback(mXWalkContent, allow, origin);
552 private void onGeolocationPermissionsShowPrompt(String origin) {
553 // Reject if geolocation is disabled, or the origin has a retained deny.
554 if (!mSettings.getGeolocationEnabled()) {
555 nativeInvokeGeolocationCallback(mXWalkContent, false, origin);
558 // Allow if the origin has a retained allow.
559 if (mGeolocationPermissions.hasOrigin(origin)) {
560 nativeInvokeGeolocationCallback(mXWalkContent,
561 mGeolocationPermissions.isOriginAllowed(origin),
565 mContentsClientBridge.onGeolocationPermissionsShowPrompt(
566 origin, new XWalkGeolocationCallback());
570 public void onGeolocationPermissionsHidePrompt() {
571 mContentsClientBridge.onGeolocationPermissionsHidePrompt();
574 public String enableRemoteDebugging(int allowedUid) {
575 // Chrome looks for "devtools_remote" pattern in the name of a unix domain socket
576 // to identify a debugging page
577 final String socketName = getContext().getApplicationContext().getPackageName() + "_devtools_remote";
578 if (mDevToolsServer == null) {
579 mDevToolsServer = new XWalkDevToolsServer(socketName);
580 mDevToolsServer.allowConnectionFromUid(allowedUid);
581 mDevToolsServer.setRemoteDebuggingEnabled(true);
583 // devtools/page is hardcoded in devtools_http_handler_impl.cc (kPageUrlPrefix)
584 return "ws://" + socketName + "/devtools/page/" + devToolsAgentId();
587 // Enables remote debugging and returns the URL at which the dev tools server is listening
588 // for commands. Only the current process is allowed to connect to the server.
589 String enableRemoteDebugging() {
590 return enableRemoteDebugging(getContext().getApplicationInfo().uid);
593 void disableRemoteDebugging() {
594 if (mDevToolsServer == null) return;
596 if (mDevToolsServer.isRemoteDebuggingEnabled()) {
597 mDevToolsServer.setRemoteDebuggingEnabled(false);
599 mDevToolsServer.destroy();
600 mDevToolsServer = null;
604 public void onKeyValueChanged(String key, boolean value) {
605 if (key == XWalkPreferences.REMOTE_DEBUGGING) {
606 if (value) enableRemoteDebugging();
607 else disableRemoteDebugging();
611 public void setOverlayVideoMode(boolean enabled) {
612 if (mContentViewRenderView != null) {
613 mContentViewRenderView.setOverlayVideoMode(enabled);
617 private native long nativeInit(XWalkWebContentsDelegate webViewContentsDelegate,
618 XWalkContentsClientBridge bridge);
619 private static native void nativeDestroy(long nativeXWalkContent);
620 private native long nativeGetWebContents(long nativeXWalkContent,
621 XWalkContentsIoThreadClient ioThreadClient,
622 InterceptNavigationDelegate delegate);
623 private native void nativeClearCache(long nativeXWalkContent, boolean includeDiskFiles);
624 private native String nativeDevToolsAgentId(long nativeXWalkContent);
625 private native String nativeGetVersion(long nativeXWalkContent);
626 private native void nativeSetJsOnlineProperty(long nativeXWalkContent, boolean networkUp);
627 private native boolean nativeSetManifest(long nativeXWalkContent, String path, String manifest);
628 private native int nativeGetRoutingID(long nativeXWalkContent);
629 private native void nativeInvokeGeolocationCallback(
630 long nativeXWalkContent, boolean value, String requestingFrame);
631 private native byte[] nativeGetState(long nativeXWalkContent);
632 private native boolean nativeSetState(long nativeXWalkContent, byte[] state);