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.media.MediaPlayerBridge;
43 import org.chromium.ui.base.ActivityWindowAndroid;
44 import org.chromium.ui.gfx.DeviceDisplayInfo;
46 @JNINamespace("xwalk")
48 * This class is the implementation class for XWalkViewInternal by calling internal
51 class XWalkContent extends FrameLayout implements XWalkPreferencesInternal.KeyValueChangeListener {
52 private static String TAG = "XWalkContent";
53 private static Class<? extends Annotation> javascriptInterfaceClass = null;
55 private ContentViewCore mContentViewCore;
56 private ContentView mContentView;
57 private ContentViewRenderView mContentViewRenderView;
58 private ActivityWindowAndroid mWindow;
59 private XWalkDevToolsServer mDevToolsServer;
60 private XWalkViewInternal mXWalkView;
61 private XWalkContentsClientBridge mContentsClientBridge;
62 private XWalkContentsIoThreadClient mIoThreadClient;
63 private XWalkWebContentsDelegateAdapter mXWalkContentsDelegateAdapter;
64 private XWalkSettings mSettings;
65 private XWalkGeolocationPermissions mGeolocationPermissions;
66 private XWalkLaunchScreenManager mLaunchScreenManager;
71 static void setJavascriptInterfaceClass(Class<? extends Annotation> clazz) {
72 assert(javascriptInterfaceClass == null);
73 javascriptInterfaceClass = clazz;
76 private static final class DestroyRunnable implements Runnable {
77 private final long mXWalkContent;
78 private DestroyRunnable(long nativeXWalkContent) {
79 mXWalkContent = nativeXWalkContent;
84 nativeDestroy(mXWalkContent);
88 // Reference to the active mXWalkContent pointer while it is active use
89 // (ie before it is destroyed).
90 private CleanupReference mCleanupReference;
92 public XWalkContent(Context context, AttributeSet attrs, XWalkViewInternal xwView) {
93 super(context, attrs);
95 // Initialize the WebContensDelegate.
97 mContentsClientBridge = new XWalkContentsClientBridge(mXWalkView);
98 mXWalkContentsDelegateAdapter = new XWalkWebContentsDelegateAdapter(
99 mContentsClientBridge);
100 mIoThreadClient = new XWalkIoThreadClientImpl();
102 // Initialize mWindow which is needed by content
103 mWindow = new ActivityWindowAndroid(xwView.getActivity());
105 // Initialize ContentViewRenderView
106 boolean animated = XWalkPreferencesInternal.getValue(XWalkPreferencesInternal.ANIMATABLE_XWALK_VIEW);
107 CompositingSurfaceType surfaceType =
108 animated ? CompositingSurfaceType.TEXTURE_VIEW : CompositingSurfaceType.SURFACE_VIEW;
109 mContentViewRenderView = new ContentViewRenderView(context, surfaceType) {
110 protected void onReadyToRender() {
111 // Anything depending on the underlying Surface readiness should
115 mContentViewRenderView.onNativeLibraryLoaded(mWindow);
116 mLaunchScreenManager = new XWalkLaunchScreenManager(context, mXWalkView);
117 mContentViewRenderView.registerFirstRenderedFrameListener(mLaunchScreenManager);
118 addView(mContentViewRenderView,
119 new FrameLayout.LayoutParams(
120 FrameLayout.LayoutParams.MATCH_PARENT,
121 FrameLayout.LayoutParams.MATCH_PARENT));
123 mXWalkContent = nativeInit(mXWalkContentsDelegateAdapter, mContentsClientBridge);
125 // The native side object has been bound to this java instance, so now is the time to
126 // bind all the native->java relationships.
127 mCleanupReference = new CleanupReference(this, new DestroyRunnable(mXWalkContent));
129 mWebContents = nativeGetWebContents(mXWalkContent, mIoThreadClient,
130 mContentsClientBridge.getInterceptNavigationDelegate());
132 // Initialize ContentView.
133 mContentViewCore = new ContentViewCore(getContext());
134 mContentView = ContentView.newInstance(getContext(), mContentViewCore);
135 mContentViewCore.initialize(mContentView, mContentView, mWebContents, mWindow);
136 addView(mContentView,
137 new FrameLayout.LayoutParams(
138 FrameLayout.LayoutParams.MATCH_PARENT,
139 FrameLayout.LayoutParams.MATCH_PARENT));
140 mContentViewCore.setContentViewClient(mContentsClientBridge);
141 mContentViewRenderView.setCurrentContentViewCore(mContentViewCore);
142 // For addJavascriptInterface
143 mContentsClientBridge.installWebContentsObserver(mContentViewCore);
146 mContentsClientBridge.setDIPScale(DeviceDisplayInfo.create(context).getDIPScale());
148 mContentViewCore.setDownloadDelegate(mContentsClientBridge);
150 // Set the third argument isAccessFromFileURLsGrantedByDefault to false, so that
151 // the members mAllowUniversalAccessFromFileURLs and mAllowFileAccessFromFileURLs
152 // won't be changed from false to true at the same time in the constructor of
153 // XWalkSettings class.
154 mSettings = new XWalkSettings(getContext(), mWebContents, false);
155 // Enable AllowFileAccessFromFileURLs, so that files under file:// path could be
156 // loaded by XMLHttpRequest.
157 mSettings.setAllowFileAccessFromFileURLs(true);
159 SharedPreferences sharedPreferences = new InMemorySharedPreferences();
160 mGeolocationPermissions = new XWalkGeolocationPermissions(sharedPreferences);
162 MediaPlayerBridge.setResourceLoadingFilter(
163 new XWalkMediaPlayerResourceLoadingFilter());
165 XWalkPreferencesInternal.load(this);
168 void doLoadUrl(String url, String content) {
169 // Handle the same url loading by parameters.
170 if (url != null && !url.isEmpty() &&
171 TextUtils.equals(url, mContentViewCore.getUrl())) {
172 mContentViewCore.reload(true);
174 LoadUrlParams params = null;
175 if (content == null || content.isEmpty()) {
176 params = new LoadUrlParams(url);
178 params = LoadUrlParams.createLoadDataParamsWithBaseUrl(
179 content, "text/html", false, url, null);
181 params.setOverrideUserAgent(LoadUrlParams.UA_OVERRIDE_TRUE);
182 mContentViewCore.loadUrl(params);
185 mContentView.requestFocus();
188 public void loadUrl(String url, String data) {
189 if ((url == null || url.isEmpty()) &&
190 (data == null || data.isEmpty())) {
194 doLoadUrl(url, data);
197 public void reload(int mode) {
199 case XWalkViewInternal.RELOAD_IGNORE_CACHE:
200 mContentViewCore.reloadIgnoringCache(true);
202 case XWalkViewInternal.RELOAD_NORMAL:
204 mContentViewCore.reload(true);
208 public String getUrl() {
209 String url = mContentViewCore.getUrl();
210 if (url == null || url.trim().isEmpty()) return null;
214 public String getTitle() {
215 String title = mContentViewCore.getTitle().trim();
216 if (title == null) title = "";
220 public void addJavascriptInterface(Object object, String name) {
221 mContentViewCore.addPossiblyUnsafeJavascriptInterface(object, name,
222 javascriptInterfaceClass);
225 public void evaluateJavascript(String script, ValueCallback<String> callback) {
226 final ValueCallback<String> fCallback = callback;
227 ContentViewCore.JavaScriptCallback coreCallback = null;
228 if (fCallback != null) {
229 coreCallback = new ContentViewCore.JavaScriptCallback() {
231 public void handleJavaScriptResult(String jsonResult) {
232 fCallback.onReceiveValue(jsonResult);
236 mContentViewCore.evaluateJavaScript(script, coreCallback);
239 public void setUIClient(XWalkUIClientInternal client) {
240 mContentsClientBridge.setUIClient(client);
243 public void setResourceClient(XWalkResourceClientInternal client) {
244 mContentsClientBridge.setResourceClient(client);
247 public void setXWalkWebChromeClient(XWalkWebChromeClient client) {
248 mContentsClientBridge.setXWalkWebChromeClient(client);
251 public XWalkWebChromeClient getXWalkWebChromeClient() {
252 return mContentsClientBridge.getXWalkWebChromeClient();
255 public void setXWalkClient(XWalkClient client) {
256 mContentsClientBridge.setXWalkClient(client);
259 public void setDownloadListener(DownloadListener listener) {
260 mContentsClientBridge.setDownloadListener(listener);
263 public void setNavigationHandler(XWalkNavigationHandler handler) {
264 mContentsClientBridge.setNavigationHandler(handler);
267 public void setNotificationService(XWalkNotificationService service) {
268 mContentsClientBridge.setNotificationService(service);
271 public void onPause() {
272 mContentViewCore.onHide();
275 public void onResume() {
276 mContentViewCore.onShow();
279 public void onActivityResult(int requestCode, int resultCode, Intent data) {
280 mWindow.onActivityResult(requestCode, resultCode, data);
283 public boolean onNewIntent(Intent intent) {
284 return mContentsClientBridge.onNewIntent(intent);
287 public void clearCache(boolean includeDiskFiles) {
288 if (mXWalkContent == 0) return;
289 nativeClearCache(mXWalkContent, includeDiskFiles);
292 public void clearHistory() {
293 mContentViewCore.clearHistory();
296 public boolean canGoBack() {
297 return mContentViewCore.canGoBack();
300 public void goBack() {
301 mContentViewCore.goBack();
304 public boolean canGoForward() {
305 return mContentViewCore.canGoForward();
308 public void goForward() {
309 mContentViewCore.goForward();
312 void navigateTo(int offset) {
313 mContentViewCore.goToOffset(offset);
316 public void stopLoading() {
317 mContentViewCore.stopLoading();
318 mContentsClientBridge.onStopLoading();
321 // Currently, timer pause/resume is actually
322 // a global setting. And multiple pause will fail the
323 // DCHECK in content (content_view_statics.cc:57).
324 // Here uses a static boolean to avoid this issue.
325 private static boolean timerPaused = false;
327 // TODO(Guangzhen): ContentViewStatics will be removed in upstream,
328 // details in content_view_statics.cc.
329 // We need follow up after upstream updates that.
330 public void pauseTimers() {
331 if (timerPaused) return;
332 ContentViewStatics.setWebKitSharedTimersSuspended(true);
336 public void resumeTimers() {
337 if (!timerPaused) return;
338 ContentViewStatics.setWebKitSharedTimersSuspended(false);
342 public String getOriginalUrl() {
343 NavigationHistory history = mContentViewCore.getNavigationHistory();
344 int currentIndex = history.getCurrentEntryIndex();
345 if (currentIndex >= 0 && currentIndex < history.getEntryCount()) {
346 return history.getEntryAtIndex(currentIndex).getOriginalUrl();
351 public String getXWalkVersion() {
352 if (mXWalkContent == 0) return "";
353 return nativeGetVersion(mXWalkContent);
356 public void setNetworkAvailable(boolean networkUp) {
357 if (mXWalkContent == 0) return;
358 nativeSetJsOnlineProperty(mXWalkContent, networkUp);
361 // For instrumentation test.
362 public ContentViewCore getContentViewCoreForTest() {
363 return mContentViewCore;
366 // For instrumentation test.
367 public void installWebContentsObserverForTest(XWalkContentsClient contentClient) {
368 contentClient.installWebContentsObserver(mContentViewCore);
371 public String devToolsAgentId() {
372 if (mXWalkContent == 0) return "";
373 return nativeDevToolsAgentId(mXWalkContent);
376 public XWalkSettings getSettings() {
380 public void loadAppFromManifest(String url, String data) {
381 if (mXWalkContent == 0 ||
382 ((url == null || url.isEmpty()) &&
383 (data == null || data.isEmpty()))) {
387 String content = data;
388 // If the data of manifest.json is not set, try to load it.
389 if (data == null || data.isEmpty()) {
391 content = AndroidProtocolHandler.getUrlContent(mXWalkView.getActivity(), url);
392 } catch (IOException e) {
393 throw new RuntimeException("Failed to read the manifest: " + url);
397 // Calculate the base url of manifestUrl. Used by native side.
398 // TODO(yongsheng): It's from runtime side. Need to find a better way
400 String baseUrl = url;
401 int position = url.lastIndexOf("/");
402 if (position != -1) {
403 baseUrl = url.substring(0, position + 1);
405 Log.w(TAG, "The url of manifest.json is probably not set correctly.");
408 if (!nativeSetManifest(mXWalkContent, baseUrl, content)) {
409 throw new RuntimeException("Failed to parse the manifest file: " + url);
413 public XWalkNavigationHistoryInternal getNavigationHistory() {
414 return new XWalkNavigationHistoryInternal(mXWalkView, mContentViewCore.getNavigationHistory());
417 public static final String SAVE_RESTORE_STATE_KEY = "XWALKVIEW_STATE";
419 public XWalkNavigationHistoryInternal saveState(Bundle outState) {
420 if (outState == null) return null;
422 byte[] state = nativeGetState(mXWalkContent);
423 if (state == null) return null;
425 outState.putByteArray(SAVE_RESTORE_STATE_KEY, state);
426 return getNavigationHistory();
429 public XWalkNavigationHistoryInternal restoreState(Bundle inState) {
430 if (inState == null) return null;
432 byte[] state = inState.getByteArray(SAVE_RESTORE_STATE_KEY);
433 if (state == null) return null;
435 boolean result = nativeSetState(mXWalkContent, state);
437 // The onUpdateTitle callback normally happens when a page is loaded,
438 // but is optimized out in the restoreState case because the title is
439 // already restored. See WebContentsImpl::UpdateTitleForEntry. So we
440 // call the callback explicitly here.
442 mContentsClientBridge.onUpdateTitle(mContentViewCore.getTitle());
445 return result ? getNavigationHistory() : null;
448 boolean hasEnteredFullscreen() {
449 return mContentsClientBridge.hasEnteredFullscreen();
452 void exitFullscreen() {
453 if (hasEnteredFullscreen()) {
454 mContentsClientBridge.exitFullscreen(mWebContents);
459 public void onGetUrlFromManifest(String url) {
460 if (url != null && !url.isEmpty()) {
466 public void onGetUrlAndLaunchScreenFromManifest(String url, String readyWhen, String imageBorder) {
467 if (url == null || url.isEmpty()) return;
468 mLaunchScreenManager.displayLaunchScreen(readyWhen, imageBorder);
469 mContentsClientBridge.registerPageLoadListener(mLaunchScreenManager);
474 public void onGetFullscreenFlagFromManifest(boolean enterFullscreen) {
475 if (enterFullscreen) {
476 if (VERSION.SDK_INT >= VERSION_CODES.KITKAT) {
477 View decorView = mXWalkView.getActivity().getWindow().getDecorView();
478 decorView.setSystemUiVisibility(
479 View.SYSTEM_UI_FLAG_LAYOUT_STABLE |
480 View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION |
481 View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN |
482 View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
483 View.SYSTEM_UI_FLAG_FULLSCREEN |
484 View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
486 mXWalkView.getActivity().getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
491 public void destroy() {
492 if (mXWalkContent == 0) return;
494 XWalkPreferencesInternal.unload(this);
495 // Reset existing notification service in order to destruct it.
496 setNotificationService(null);
497 // Remove its children used for page rendering from view hierarchy.
498 removeView(mContentView);
499 removeView(mContentViewRenderView);
500 mContentViewRenderView.setCurrentContentViewCore(null);
502 // Destroy the native resources.
503 mContentViewRenderView.destroy();
504 mContentViewCore.destroy();
506 mCleanupReference.cleanupNow();
507 mCleanupReference = null;
511 public int getRoutingID() {
512 return nativeGetRoutingID(mXWalkContent);
515 //--------------------------------------------------------------------------------------------
516 private class XWalkIoThreadClientImpl implements XWalkContentsIoThreadClient {
517 // All methods are called on the IO thread.
520 public int getCacheMode() {
521 return mSettings.getCacheMode();
525 public InterceptedRequestData shouldInterceptRequest(final String url,
526 boolean isMainFrame) {
528 // Notify a resource load is started. This is not the best place to start the callback
529 // but it's a workable way.
530 mContentsClientBridge.getCallbackHelper().postOnResourceLoadStarted(url);
532 WebResourceResponse webResourceResponse = mContentsClientBridge.shouldInterceptRequest(url);
533 InterceptedRequestData interceptedRequestData = null;
535 if (webResourceResponse == null) {
536 mContentsClientBridge.getCallbackHelper().postOnLoadResource(url);
538 if (isMainFrame && webResourceResponse.getData() == null) {
539 mContentsClientBridge.getCallbackHelper().postOnReceivedError(
540 XWalkResourceClientInternal.ERROR_UNKNOWN, null, url);
542 interceptedRequestData = new InterceptedRequestData(webResourceResponse.getMimeType(),
543 webResourceResponse.getEncoding(),
544 webResourceResponse.getData());
546 return interceptedRequestData;
550 public boolean shouldBlockContentUrls() {
551 return !mSettings.getAllowContentAccess();
555 public boolean shouldBlockFileUrls() {
556 return !mSettings.getAllowFileAccess();
560 public boolean shouldBlockNetworkLoads() {
561 return mSettings.getBlockNetworkLoads();
565 public void onDownloadStart(String url,
567 String contentDisposition,
569 long contentLength) {
570 mContentsClientBridge.getCallbackHelper().postOnDownloadStart(url, userAgent,
571 contentDisposition, mimeType, contentLength);
575 public void newLoginRequest(String realm, String account, String args) {
576 mContentsClientBridge.getCallbackHelper().postOnReceivedLoginRequest(realm, account, args);
580 private class XWalkGeolocationCallback implements XWalkGeolocationPermissions.Callback {
582 public void invoke(final String origin, final boolean allow, final boolean retain) {
583 ThreadUtils.runOnUiThread(new Runnable() {
588 mGeolocationPermissions.allow(origin);
590 mGeolocationPermissions.deny(origin);
593 nativeInvokeGeolocationCallback(mXWalkContent, allow, origin);
600 private void onGeolocationPermissionsShowPrompt(String origin) {
601 // Reject if geolocation is disabled, or the origin has a retained deny.
602 if (!mSettings.getGeolocationEnabled()) {
603 nativeInvokeGeolocationCallback(mXWalkContent, false, origin);
606 // Allow if the origin has a retained allow.
607 if (mGeolocationPermissions.hasOrigin(origin)) {
608 nativeInvokeGeolocationCallback(mXWalkContent,
609 mGeolocationPermissions.isOriginAllowed(origin),
613 mContentsClientBridge.onGeolocationPermissionsShowPrompt(
614 origin, new XWalkGeolocationCallback());
618 public void onGeolocationPermissionsHidePrompt() {
619 mContentsClientBridge.onGeolocationPermissionsHidePrompt();
622 public String enableRemoteDebugging(int allowedUid) {
623 // Chrome looks for "devtools_remote" pattern in the name of a unix domain socket
624 // to identify a debugging page
625 final String socketName = getContext().getApplicationContext().getPackageName() + "_devtools_remote";
626 if (mDevToolsServer == null) {
627 mDevToolsServer = new XWalkDevToolsServer(socketName);
628 mDevToolsServer.allowConnectionFromUid(allowedUid);
629 mDevToolsServer.setRemoteDebuggingEnabled(true);
631 // devtools/page is hardcoded in devtools_http_handler_impl.cc (kPageUrlPrefix)
632 return "ws://" + socketName + "/devtools/page/" + devToolsAgentId();
635 // Enables remote debugging and returns the URL at which the dev tools server is listening
636 // for commands. Only the current process is allowed to connect to the server.
637 String enableRemoteDebugging() {
638 return enableRemoteDebugging(getContext().getApplicationInfo().uid);
641 void disableRemoteDebugging() {
642 if (mDevToolsServer == null) return;
644 if (mDevToolsServer.isRemoteDebuggingEnabled()) {
645 mDevToolsServer.setRemoteDebuggingEnabled(false);
647 mDevToolsServer.destroy();
648 mDevToolsServer = null;
652 public void onKeyValueChanged(String key, boolean value) {
653 if (key == null) return;
654 if (key.equals(XWalkPreferencesInternal.REMOTE_DEBUGGING)) {
655 if (value) enableRemoteDebugging();
656 else disableRemoteDebugging();
657 } else if (key.equals(XWalkPreferencesInternal.ENABLE_JAVASCRIPT)) {
658 if (mSettings != null) mSettings.setJavaScriptEnabled(value);
659 } else if (key.equals(XWalkPreferencesInternal.JAVASCRIPT_CAN_OPEN_WINDOW)) {
660 if (mSettings != null) mSettings.setJavaScriptCanOpenWindowsAutomatically(value);
661 } else if (key.equals(XWalkPreferencesInternal.ALLOW_UNIVERSAL_ACCESS_FROM_FILE)) {
662 if (mSettings != null) mSettings.setAllowUniversalAccessFromFileURLs(value);
663 } else if (key.equals(XWalkPreferencesInternal.SUPPORT_MULTIPLE_WINDOWS)) {
664 if (mSettings != null) mSettings.setSupportMultipleWindows(value);
668 public void setOverlayVideoMode(boolean enabled) {
669 if (mContentViewRenderView != null) {
670 mContentViewRenderView.setOverlayVideoMode(enabled);
674 private native long nativeInit(XWalkWebContentsDelegate webViewContentsDelegate,
675 XWalkContentsClientBridge bridge);
676 private static native void nativeDestroy(long nativeXWalkContent);
677 private native long nativeGetWebContents(long nativeXWalkContent,
678 XWalkContentsIoThreadClient ioThreadClient,
679 InterceptNavigationDelegate delegate);
680 private native void nativeClearCache(long nativeXWalkContent, boolean includeDiskFiles);
681 private native String nativeDevToolsAgentId(long nativeXWalkContent);
682 private native String nativeGetVersion(long nativeXWalkContent);
683 private native void nativeSetJsOnlineProperty(long nativeXWalkContent, boolean networkUp);
684 private native boolean nativeSetManifest(long nativeXWalkContent, String path, String manifest);
685 private native int nativeGetRoutingID(long nativeXWalkContent);
686 private native void nativeInvokeGeolocationCallback(
687 long nativeXWalkContent, boolean value, String requestingFrame);
688 private native byte[] nativeGetState(long nativeXWalkContent);
689 private native boolean nativeSetState(long nativeXWalkContent, byte[] state);