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.internal;
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.Build.VERSION;
14 import android.os.Build.VERSION_CODES;
15 import android.os.Bundle;
16 import android.view.View;
17 import android.view.WindowManager;
18 import android.text.TextUtils;
19 import android.util.AttributeSet;
20 import android.util.Log;
21 import android.view.ViewGroup;
22 import android.webkit.ValueCallback;
23 import android.webkit.WebResourceResponse;
24 import android.widget.FrameLayout;
26 import java.io.IOException;
27 import java.io.InputStream;
28 import java.lang.annotation.Annotation;
30 import org.chromium.base.CalledByNative;
31 import org.chromium.base.JNINamespace;
32 import org.chromium.base.ThreadUtils;
33 import org.chromium.components.navigation_interception.InterceptNavigationDelegate;
34 import org.chromium.content.browser.ContentView;
35 import org.chromium.content.browser.ContentViewCore;
36 import org.chromium.content.browser.ContentViewRenderView;
37 import org.chromium.content.browser.ContentViewRenderView.CompositingSurfaceType;
38 import org.chromium.content.browser.ContentViewStatics;
39 import org.chromium.content.browser.LoadUrlParams;
40 import org.chromium.content.browser.NavigationHistory;
41 import org.chromium.content.common.CleanupReference;
42 import org.chromium.content_public.browser.JavaScriptCallback;
43 import org.chromium.media.MediaPlayerBridge;
44 import org.chromium.ui.base.ActivityWindowAndroid;
45 import org.chromium.ui.gfx.DeviceDisplayInfo;
47 @JNINamespace("xwalk")
49 * This class is the implementation class for XWalkViewInternal by calling internal
52 class XWalkContent extends FrameLayout implements XWalkPreferencesInternal.KeyValueChangeListener {
53 private static String TAG = "XWalkContent";
54 private static Class<? extends Annotation> javascriptInterfaceClass = null;
56 private ContentViewCore mContentViewCore;
57 private ContentView mContentView;
58 private ContentViewRenderView mContentViewRenderView;
59 private ActivityWindowAndroid mWindow;
60 private XWalkDevToolsServer mDevToolsServer;
61 private XWalkViewInternal mXWalkView;
62 private XWalkContentsClientBridge mContentsClientBridge;
63 private XWalkContentsIoThreadClient mIoThreadClient;
64 private XWalkWebContentsDelegateAdapter mXWalkContentsDelegateAdapter;
65 private XWalkSettings mSettings;
66 private XWalkGeolocationPermissions mGeolocationPermissions;
67 private XWalkLaunchScreenManager mLaunchScreenManager;
72 static void setJavascriptInterfaceClass(Class<? extends Annotation> clazz) {
73 assert(javascriptInterfaceClass == null);
74 javascriptInterfaceClass = clazz;
77 private static final class DestroyRunnable implements Runnable {
78 private final long mXWalkContent;
79 private DestroyRunnable(long nativeXWalkContent) {
80 mXWalkContent = nativeXWalkContent;
85 nativeDestroy(mXWalkContent);
89 // Reference to the active mXWalkContent pointer while it is active use
90 // (ie before it is destroyed).
91 private CleanupReference mCleanupReference;
93 public XWalkContent(Context context, AttributeSet attrs, XWalkViewInternal xwView) {
94 super(context, attrs);
96 // Initialize the WebContensDelegate.
98 mContentsClientBridge = new XWalkContentsClientBridge(mXWalkView);
99 mXWalkContentsDelegateAdapter = new XWalkWebContentsDelegateAdapter(
100 mContentsClientBridge);
101 mIoThreadClient = new XWalkIoThreadClientImpl();
103 // Initialize mWindow which is needed by content
104 mWindow = new ActivityWindowAndroid(xwView.getActivity());
106 // Initialize ContentViewRenderView
107 boolean animated = XWalkPreferencesInternal.getValue(XWalkPreferencesInternal.ANIMATABLE_XWALK_VIEW);
108 CompositingSurfaceType surfaceType =
109 animated ? CompositingSurfaceType.TEXTURE_VIEW : CompositingSurfaceType.SURFACE_VIEW;
110 mContentViewRenderView = new ContentViewRenderView(context, surfaceType) {
111 protected void onReadyToRender() {
112 // Anything depending on the underlying Surface readiness should
116 mContentViewRenderView.onNativeLibraryLoaded(mWindow);
117 mLaunchScreenManager = new XWalkLaunchScreenManager(context, mXWalkView);
118 mContentViewRenderView.registerFirstRenderedFrameListener(mLaunchScreenManager);
119 addView(mContentViewRenderView,
120 new FrameLayout.LayoutParams(
121 FrameLayout.LayoutParams.MATCH_PARENT,
122 FrameLayout.LayoutParams.MATCH_PARENT));
124 mXWalkContent = nativeInit(mXWalkContentsDelegateAdapter, mContentsClientBridge);
126 // The native side object has been bound to this java instance, so now is the time to
127 // bind all the native->java relationships.
128 mCleanupReference = new CleanupReference(this, new DestroyRunnable(mXWalkContent));
130 mWebContents = nativeGetWebContents(mXWalkContent, mIoThreadClient,
131 mContentsClientBridge.getInterceptNavigationDelegate());
133 // Initialize ContentView.
134 mContentViewCore = new ContentViewCore(getContext());
135 mContentView = ContentView.newInstance(getContext(), mContentViewCore);
136 mContentViewCore.initialize(mContentView, mContentView, mWebContents, mWindow);
137 addView(mContentView,
138 new FrameLayout.LayoutParams(
139 FrameLayout.LayoutParams.MATCH_PARENT,
140 FrameLayout.LayoutParams.MATCH_PARENT));
141 mContentViewCore.setContentViewClient(mContentsClientBridge);
142 mContentViewRenderView.setCurrentContentViewCore(mContentViewCore);
143 // For addJavascriptInterface
144 mContentsClientBridge.installWebContentsObserver(mContentViewCore.getWebContents());
147 mContentsClientBridge.setDIPScale(DeviceDisplayInfo.create(context).getDIPScale());
149 mContentViewCore.setDownloadDelegate(mContentsClientBridge);
151 // Set the third argument isAccessFromFileURLsGrantedByDefault to false, so that
152 // the members mAllowUniversalAccessFromFileURLs and mAllowFileAccessFromFileURLs
153 // won't be changed from false to true at the same time in the constructor of
154 // XWalkSettings class.
155 mSettings = new XWalkSettings(getContext(), mWebContents, false);
156 // Enable AllowFileAccessFromFileURLs, so that files under file:// path could be
157 // loaded by XMLHttpRequest.
158 mSettings.setAllowFileAccessFromFileURLs(true);
160 SharedPreferences sharedPreferences = new InMemorySharedPreferences();
161 mGeolocationPermissions = new XWalkGeolocationPermissions(sharedPreferences);
163 MediaPlayerBridge.setResourceLoadingFilter(
164 new XWalkMediaPlayerResourceLoadingFilter());
166 XWalkPreferencesInternal.load(this);
169 void doLoadUrl(String url, String content) {
170 // Handle the same url loading by parameters.
171 if (url != null && !url.isEmpty() &&
172 TextUtils.equals(url, mContentViewCore.getUrl())) {
173 mContentViewCore.reload(true);
175 LoadUrlParams params = null;
176 if (content == null || content.isEmpty()) {
177 params = new LoadUrlParams(url);
179 params = LoadUrlParams.createLoadDataParamsWithBaseUrl(
180 content, "text/html", false, url, null);
182 params.setOverrideUserAgent(LoadUrlParams.UA_OVERRIDE_TRUE);
183 mContentViewCore.loadUrl(params);
186 mContentView.requestFocus();
189 public void loadUrl(String url, String data) {
190 if ((url == null || url.isEmpty()) &&
191 (data == null || data.isEmpty())) {
195 doLoadUrl(url, data);
198 public void reload(int mode) {
200 case XWalkViewInternal.RELOAD_IGNORE_CACHE:
201 mContentViewCore.reloadIgnoringCache(true);
203 case XWalkViewInternal.RELOAD_NORMAL:
205 mContentViewCore.reload(true);
209 public String getUrl() {
210 String url = mContentViewCore.getUrl();
211 if (url == null || url.trim().isEmpty()) return null;
215 public String getTitle() {
216 String title = mContentViewCore.getTitle().trim();
217 if (title == null) title = "";
221 public void addJavascriptInterface(Object object, String name) {
222 mContentViewCore.addPossiblyUnsafeJavascriptInterface(object, name,
223 javascriptInterfaceClass);
226 public void evaluateJavascript(String script, ValueCallback<String> callback) {
227 final ValueCallback<String> fCallback = callback;
228 JavaScriptCallback coreCallback = null;
229 if (fCallback != null) {
230 coreCallback = new JavaScriptCallback() {
232 public void handleJavaScriptResult(String jsonResult) {
233 fCallback.onReceiveValue(jsonResult);
237 mContentViewCore.evaluateJavaScript(script, coreCallback);
240 public void setUIClient(XWalkUIClientInternal client) {
241 mContentsClientBridge.setUIClient(client);
244 public void setResourceClient(XWalkResourceClientInternal client) {
245 mContentsClientBridge.setResourceClient(client);
248 public void setXWalkWebChromeClient(XWalkWebChromeClient client) {
249 mContentsClientBridge.setXWalkWebChromeClient(client);
252 public XWalkWebChromeClient getXWalkWebChromeClient() {
253 return mContentsClientBridge.getXWalkWebChromeClient();
256 public void setXWalkClient(XWalkClient client) {
257 mContentsClientBridge.setXWalkClient(client);
260 public void setDownloadListener(DownloadListener listener) {
261 mContentsClientBridge.setDownloadListener(listener);
264 public void setNavigationHandler(XWalkNavigationHandler handler) {
265 mContentsClientBridge.setNavigationHandler(handler);
268 public void setNotificationService(XWalkNotificationService service) {
269 mContentsClientBridge.setNotificationService(service);
272 public void onPause() {
273 mContentViewCore.onHide();
276 public void onResume() {
277 mContentViewCore.onShow();
280 public void onActivityResult(int requestCode, int resultCode, Intent data) {
281 mWindow.onActivityResult(requestCode, resultCode, data);
284 public boolean onNewIntent(Intent intent) {
285 return mContentsClientBridge.onNewIntent(intent);
288 public void clearCache(boolean includeDiskFiles) {
289 if (mXWalkContent == 0) return;
290 nativeClearCache(mXWalkContent, includeDiskFiles);
293 public void clearHistory() {
294 mContentViewCore.clearHistory();
297 public boolean canGoBack() {
298 return mContentViewCore.canGoBack();
301 public void goBack() {
302 mContentViewCore.goBack();
305 public boolean canGoForward() {
306 return mContentViewCore.canGoForward();
309 public void goForward() {
310 mContentViewCore.goForward();
313 void navigateTo(int offset) {
314 mContentViewCore.goToOffset(offset);
317 public void stopLoading() {
318 mContentViewCore.stopLoading();
319 mContentsClientBridge.onStopLoading();
322 // Currently, timer pause/resume is actually
323 // a global setting. And multiple pause will fail the
324 // DCHECK in content (content_view_statics.cc:57).
325 // Here uses a static boolean to avoid this issue.
326 private static boolean timerPaused = false;
328 // TODO(Guangzhen): ContentViewStatics will be removed in upstream,
329 // details in content_view_statics.cc.
330 // We need follow up after upstream updates that.
331 public void pauseTimers() {
332 if (timerPaused) return;
333 ContentViewStatics.setWebKitSharedTimersSuspended(true);
337 public void resumeTimers() {
338 if (!timerPaused) return;
339 ContentViewStatics.setWebKitSharedTimersSuspended(false);
343 public String getOriginalUrl() {
344 NavigationHistory history = mContentViewCore.getNavigationHistory();
345 int currentIndex = history.getCurrentEntryIndex();
346 if (currentIndex >= 0 && currentIndex < history.getEntryCount()) {
347 return history.getEntryAtIndex(currentIndex).getOriginalUrl();
352 public String getXWalkVersion() {
353 if (mXWalkContent == 0) return "";
354 return nativeGetVersion(mXWalkContent);
357 public void setNetworkAvailable(boolean networkUp) {
358 if (mXWalkContent == 0) return;
359 nativeSetJsOnlineProperty(mXWalkContent, networkUp);
362 // For instrumentation test.
363 public ContentViewCore getContentViewCoreForTest() {
364 return mContentViewCore;
367 // For instrumentation test.
368 public void installWebContentsObserverForTest(XWalkContentsClient contentClient) {
369 contentClient.installWebContentsObserver(mContentViewCore.getWebContents());
372 public String devToolsAgentId() {
373 if (mXWalkContent == 0) return "";
374 return nativeDevToolsAgentId(mXWalkContent);
377 public XWalkSettings getSettings() {
381 public void loadAppFromManifest(String url, String data) {
382 if (mXWalkContent == 0 ||
383 ((url == null || url.isEmpty()) &&
384 (data == null || data.isEmpty()))) {
388 String content = data;
389 // If the data of manifest.json is not set, try to load it.
390 if (data == null || data.isEmpty()) {
392 content = AndroidProtocolHandler.getUrlContent(mXWalkView.getActivity(), url);
393 } catch (IOException e) {
394 throw new RuntimeException("Failed to read the manifest: " + url);
398 // Calculate the base url of manifestUrl. Used by native side.
399 // TODO(yongsheng): It's from runtime side. Need to find a better way
401 String baseUrl = url;
402 int position = url.lastIndexOf("/");
403 if (position != -1) {
404 baseUrl = url.substring(0, position + 1);
406 Log.w(TAG, "The url of manifest.json is probably not set correctly.");
409 if (!nativeSetManifest(mXWalkContent, baseUrl, content)) {
410 throw new RuntimeException("Failed to parse the manifest file: " + url);
414 public XWalkNavigationHistoryInternal getNavigationHistory() {
415 return new XWalkNavigationHistoryInternal(mXWalkView, mContentViewCore.getNavigationHistory());
418 public static final String SAVE_RESTORE_STATE_KEY = "XWALKVIEW_STATE";
420 public XWalkNavigationHistoryInternal saveState(Bundle outState) {
421 if (outState == null) return null;
423 byte[] state = nativeGetState(mXWalkContent);
424 if (state == null) return null;
426 outState.putByteArray(SAVE_RESTORE_STATE_KEY, state);
427 return getNavigationHistory();
430 public XWalkNavigationHistoryInternal restoreState(Bundle inState) {
431 if (inState == null) return null;
433 byte[] state = inState.getByteArray(SAVE_RESTORE_STATE_KEY);
434 if (state == null) return null;
436 boolean result = nativeSetState(mXWalkContent, state);
438 // The onUpdateTitle callback normally happens when a page is loaded,
439 // but is optimized out in the restoreState case because the title is
440 // already restored. See WebContentsImpl::UpdateTitleForEntry. So we
441 // call the callback explicitly here.
443 mContentsClientBridge.onUpdateTitle(mContentViewCore.getTitle());
446 return result ? getNavigationHistory() : null;
449 boolean hasEnteredFullscreen() {
450 return mContentsClientBridge.hasEnteredFullscreen();
453 void exitFullscreen() {
454 if (hasEnteredFullscreen()) {
455 mContentsClientBridge.exitFullscreen(mWebContents);
460 public void onGetUrlFromManifest(String url) {
461 if (url != null && !url.isEmpty()) {
467 public void onGetUrlAndLaunchScreenFromManifest(String url, String readyWhen, String imageBorder) {
468 if (url == null || url.isEmpty()) return;
469 mLaunchScreenManager.displayLaunchScreen(readyWhen, imageBorder);
470 mContentsClientBridge.registerPageLoadListener(mLaunchScreenManager);
475 public void onGetFullscreenFlagFromManifest(boolean enterFullscreen) {
476 if (enterFullscreen) {
477 if (VERSION.SDK_INT >= VERSION_CODES.KITKAT) {
478 View decorView = mXWalkView.getActivity().getWindow().getDecorView();
479 decorView.setSystemUiVisibility(
480 View.SYSTEM_UI_FLAG_LAYOUT_STABLE |
481 View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION |
482 View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN |
483 View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
484 View.SYSTEM_UI_FLAG_FULLSCREEN |
485 View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
487 mXWalkView.getActivity().getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
492 public void destroy() {
493 if (mXWalkContent == 0) return;
495 XWalkPreferencesInternal.unload(this);
496 // Reset existing notification service in order to destruct it.
497 setNotificationService(null);
498 // Remove its children used for page rendering from view hierarchy.
499 removeView(mContentView);
500 removeView(mContentViewRenderView);
501 mContentViewRenderView.setCurrentContentViewCore(null);
503 // Destroy the native resources.
504 mContentViewRenderView.destroy();
505 mContentViewCore.destroy();
507 mCleanupReference.cleanupNow();
508 mCleanupReference = null;
512 public int getRoutingID() {
513 return nativeGetRoutingID(mXWalkContent);
516 //--------------------------------------------------------------------------------------------
517 private class XWalkIoThreadClientImpl implements XWalkContentsIoThreadClient {
518 // All methods are called on the IO thread.
521 public int getCacheMode() {
522 return mSettings.getCacheMode();
526 public InterceptedRequestData shouldInterceptRequest(final String url,
527 boolean isMainFrame) {
529 // Notify a resource load is started. This is not the best place to start the callback
530 // but it's a workable way.
531 mContentsClientBridge.getCallbackHelper().postOnResourceLoadStarted(url);
533 WebResourceResponse webResourceResponse = mContentsClientBridge.shouldInterceptRequest(url);
534 InterceptedRequestData interceptedRequestData = null;
536 if (webResourceResponse == null) {
537 mContentsClientBridge.getCallbackHelper().postOnLoadResource(url);
539 if (isMainFrame && webResourceResponse.getData() == null) {
540 mContentsClientBridge.getCallbackHelper().postOnReceivedError(
541 XWalkResourceClientInternal.ERROR_UNKNOWN, null, url);
543 interceptedRequestData = new InterceptedRequestData(webResourceResponse.getMimeType(),
544 webResourceResponse.getEncoding(),
545 webResourceResponse.getData());
547 return interceptedRequestData;
551 public boolean shouldBlockContentUrls() {
552 return !mSettings.getAllowContentAccess();
556 public boolean shouldBlockFileUrls() {
557 return !mSettings.getAllowFileAccess();
561 public boolean shouldBlockNetworkLoads() {
562 return mSettings.getBlockNetworkLoads();
566 public void onDownloadStart(String url,
568 String contentDisposition,
570 long contentLength) {
571 mContentsClientBridge.getCallbackHelper().postOnDownloadStart(url, userAgent,
572 contentDisposition, mimeType, contentLength);
576 public void newLoginRequest(String realm, String account, String args) {
577 mContentsClientBridge.getCallbackHelper().postOnReceivedLoginRequest(realm, account, args);
581 private class XWalkGeolocationCallback implements XWalkGeolocationPermissions.Callback {
583 public void invoke(final String origin, final boolean allow, final boolean retain) {
584 ThreadUtils.runOnUiThread(new Runnable() {
589 mGeolocationPermissions.allow(origin);
591 mGeolocationPermissions.deny(origin);
594 nativeInvokeGeolocationCallback(mXWalkContent, allow, origin);
601 private void onGeolocationPermissionsShowPrompt(String origin) {
602 // Reject if geolocation is disabled, or the origin has a retained deny.
603 if (!mSettings.getGeolocationEnabled()) {
604 nativeInvokeGeolocationCallback(mXWalkContent, false, origin);
607 // Allow if the origin has a retained allow.
608 if (mGeolocationPermissions.hasOrigin(origin)) {
609 nativeInvokeGeolocationCallback(mXWalkContent,
610 mGeolocationPermissions.isOriginAllowed(origin),
614 mContentsClientBridge.onGeolocationPermissionsShowPrompt(
615 origin, new XWalkGeolocationCallback());
619 public void onGeolocationPermissionsHidePrompt() {
620 mContentsClientBridge.onGeolocationPermissionsHidePrompt();
623 public String enableRemoteDebugging(int allowedUid) {
624 // Chrome looks for "devtools_remote" pattern in the name of a unix domain socket
625 // to identify a debugging page
626 final String socketName = getContext().getApplicationContext().getPackageName() + "_devtools_remote";
627 if (mDevToolsServer == null) {
628 mDevToolsServer = new XWalkDevToolsServer(socketName);
629 mDevToolsServer.allowConnectionFromUid(allowedUid);
630 mDevToolsServer.setRemoteDebuggingEnabled(true);
632 // devtools/page is hardcoded in devtools_http_handler_impl.cc (kPageUrlPrefix)
633 return "ws://" + socketName + "/devtools/page/" + devToolsAgentId();
636 // Enables remote debugging and returns the URL at which the dev tools server is listening
637 // for commands. Only the current process is allowed to connect to the server.
638 String enableRemoteDebugging() {
639 return enableRemoteDebugging(getContext().getApplicationInfo().uid);
642 void disableRemoteDebugging() {
643 if (mDevToolsServer == null) return;
645 if (mDevToolsServer.isRemoteDebuggingEnabled()) {
646 mDevToolsServer.setRemoteDebuggingEnabled(false);
648 mDevToolsServer.destroy();
649 mDevToolsServer = null;
653 public void onKeyValueChanged(String key, XWalkPreferencesInternal.PreferenceValue value) {
654 if (key == null) return;
655 if (key.equals(XWalkPreferencesInternal.REMOTE_DEBUGGING)) {
656 if (value.getBooleanValue()) enableRemoteDebugging();
657 else disableRemoteDebugging();
658 } else if (key.equals(XWalkPreferencesInternal.ENABLE_JAVASCRIPT)) {
659 if (mSettings != null) {
660 mSettings.setJavaScriptEnabled(value.getBooleanValue());
662 } else if (key.equals(XWalkPreferencesInternal.JAVASCRIPT_CAN_OPEN_WINDOW)) {
663 if (mSettings != null) {
664 mSettings.setJavaScriptCanOpenWindowsAutomatically(value.getBooleanValue());
666 } else if (key.equals(XWalkPreferencesInternal.ALLOW_UNIVERSAL_ACCESS_FROM_FILE)) {
667 if (mSettings != null) {
668 mSettings.setAllowUniversalAccessFromFileURLs(value.getBooleanValue());
670 } else if (key.equals(XWalkPreferencesInternal.SUPPORT_MULTIPLE_WINDOWS)) {
671 if (mSettings != null) {
672 mSettings.setSupportMultipleWindows(value.getBooleanValue());
677 public void setOverlayVideoMode(boolean enabled) {
678 if (mContentViewRenderView != null) {
679 mContentViewRenderView.setOverlayVideoMode(enabled);
683 private native long nativeInit(XWalkWebContentsDelegate webViewContentsDelegate,
684 XWalkContentsClientBridge bridge);
685 private static native void nativeDestroy(long nativeXWalkContent);
686 private native long nativeGetWebContents(long nativeXWalkContent,
687 XWalkContentsIoThreadClient ioThreadClient,
688 InterceptNavigationDelegate delegate);
689 private native void nativeClearCache(long nativeXWalkContent, boolean includeDiskFiles);
690 private native String nativeDevToolsAgentId(long nativeXWalkContent);
691 private native String nativeGetVersion(long nativeXWalkContent);
692 private native void nativeSetJsOnlineProperty(long nativeXWalkContent, boolean networkUp);
693 private native boolean nativeSetManifest(long nativeXWalkContent, String path, String manifest);
694 private native int nativeGetRoutingID(long nativeXWalkContent);
695 private native void nativeInvokeGeolocationCallback(
696 long nativeXWalkContent, boolean value, String requestingFrame);
697 private native byte[] nativeGetState(long nativeXWalkContent);
698 private native boolean nativeSetState(long nativeXWalkContent, byte[] state);