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.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.content.common.CleanupReference;
37 import org.chromium.media.MediaPlayerBridge;
38 import org.chromium.ui.base.ActivityWindowAndroid;
40 import org.xwalk.core.JavascriptInterface;
42 @JNINamespace("xwalk")
44 * This class is the implementation class for XWalkViewInternal by calling internal
47 class XWalkContent extends FrameLayout implements XWalkPreferencesInternal.KeyValueChangeListener {
48 private static String TAG = "XWalkContent";
49 private ContentViewCore mContentViewCore;
50 private ContentView mContentView;
51 private ContentViewRenderView mContentViewRenderView;
52 private ActivityWindowAndroid mWindow;
53 private XWalkDevToolsServer mDevToolsServer;
54 private XWalkViewInternal mXWalkView;
55 private XWalkContentsClientBridge mContentsClientBridge;
56 private XWalkContentsIoThreadClient mIoThreadClient;
57 private XWalkWebContentsDelegateAdapter mXWalkContentsDelegateAdapter;
58 private XWalkSettings mSettings;
59 private XWalkGeolocationPermissions mGeolocationPermissions;
60 private XWalkLaunchScreenManager mLaunchScreenManager;
65 private static final class DestroyRunnable implements Runnable {
66 private final long mXWalkContent;
67 private DestroyRunnable(long nativeXWalkContent) {
68 mXWalkContent = nativeXWalkContent;
73 nativeDestroy(mXWalkContent);
77 // Reference to the active mXWalkContent pointer while it is active use
78 // (ie before it is destroyed).
79 private CleanupReference mCleanupReference;
81 public XWalkContent(Context context, AttributeSet attrs, XWalkViewInternal xwView) {
82 super(context, attrs);
84 // Initialize the WebContensDelegate.
86 mContentsClientBridge = new XWalkContentsClientBridge(mXWalkView);
87 mXWalkContentsDelegateAdapter = new XWalkWebContentsDelegateAdapter(
88 mContentsClientBridge);
89 mIoThreadClient = new XWalkIoThreadClientImpl();
91 // Initialize mWindow which is needed by content
92 mWindow = new ActivityWindowAndroid(xwView.getActivity());
94 // Initialize ContentViewRenderView
95 boolean animated = XWalkPreferencesInternal.getValue(XWalkPreferencesInternal.ANIMATABLE_XWALK_VIEW);
96 CompositingSurfaceType surfaceType =
97 animated ? CompositingSurfaceType.TEXTURE_VIEW : CompositingSurfaceType.SURFACE_VIEW;
98 mContentViewRenderView = new ContentViewRenderView(context, mWindow, surfaceType) {
99 protected void onReadyToRender() {
100 // Anything depending on the underlying Surface readiness should
104 mLaunchScreenManager = new XWalkLaunchScreenManager(context, mXWalkView);
105 mContentViewRenderView.registerFirstRenderedFrameListener(mLaunchScreenManager);
106 addView(mContentViewRenderView,
107 new FrameLayout.LayoutParams(
108 FrameLayout.LayoutParams.MATCH_PARENT,
109 FrameLayout.LayoutParams.MATCH_PARENT));
111 mXWalkContent = nativeInit(mXWalkContentsDelegateAdapter, mContentsClientBridge);
113 // The native side object has been bound to this java instance, so now is the time to
114 // bind all the native->java relationships.
115 mCleanupReference = new CleanupReference(this, new DestroyRunnable(mXWalkContent));
117 mWebContents = nativeGetWebContents(mXWalkContent, mIoThreadClient,
118 mContentsClientBridge.getInterceptNavigationDelegate());
120 // Initialize ContentView.
121 mContentViewCore = new ContentViewCore(getContext());
122 mContentView = ContentView.newInstance(getContext(), mContentViewCore);
123 mContentViewCore.initialize(mContentView, mContentView, mWebContents, mWindow);
124 addView(mContentView,
125 new FrameLayout.LayoutParams(
126 FrameLayout.LayoutParams.MATCH_PARENT,
127 FrameLayout.LayoutParams.MATCH_PARENT));
128 mContentViewCore.setContentViewClient(mContentsClientBridge);
129 mContentViewRenderView.setCurrentContentViewCore(mContentViewCore);
130 // For addJavascriptInterface
131 mContentsClientBridge.installWebContentsObserver(mContentViewCore);
133 mContentViewCore.setDownloadDelegate(mContentsClientBridge);
135 // Set the third argument isAccessFromFileURLsGrantedByDefault to false, so that
136 // the members mAllowUniversalAccessFromFileURLs and mAllowFileAccessFromFileURLs
137 // won't be changed from false to true at the same time in the constructor of
138 // XWalkSettings class.
139 mSettings = new XWalkSettings(getContext(), mWebContents, false);
140 // Enable AllowFileAccessFromFileURLs, so that files under file:// path could be
141 // loaded by XMLHttpRequest.
142 mSettings.setAllowFileAccessFromFileURLs(true);
144 SharedPreferences sharedPreferences = new InMemorySharedPreferences();
145 mGeolocationPermissions = new XWalkGeolocationPermissions(sharedPreferences);
147 MediaPlayerBridge.setResourceLoadingFilter(
148 new XWalkMediaPlayerResourceLoadingFilter());
150 XWalkPreferencesInternal.load(this);
153 void doLoadUrl(String url, String content) {
154 // Handle the same url loading by parameters.
155 if (url != null && !url.isEmpty() &&
156 TextUtils.equals(url, mContentViewCore.getUrl())) {
157 mContentViewCore.reload(true);
159 LoadUrlParams params = null;
160 if (content == null || content.isEmpty()) {
161 params = new LoadUrlParams(url);
163 params = LoadUrlParams.createLoadDataParamsWithBaseUrl(
164 content, "text/html", false, url, null);
166 params.setOverrideUserAgent(LoadUrlParams.UA_OVERRIDE_TRUE);
167 mContentViewCore.loadUrl(params);
170 mContentView.requestFocus();
173 public void loadUrl(String url, String data) {
174 if ((url == null || url.isEmpty()) &&
175 (data == null || data.isEmpty())) {
179 doLoadUrl(url, data);
182 public void reload(int mode) {
184 case XWalkViewInternal.RELOAD_IGNORE_CACHE:
185 mContentViewCore.reloadIgnoringCache(true);
187 case XWalkViewInternal.RELOAD_NORMAL:
189 mContentViewCore.reload(true);
193 public String getUrl() {
194 String url = mContentViewCore.getUrl();
195 if (url == null || url.trim().isEmpty()) return null;
199 public String getTitle() {
200 String title = mContentViewCore.getTitle().trim();
201 if (title == null) title = "";
205 public void addJavascriptInterface(Object object, String name) {
206 mContentViewCore.addPossiblyUnsafeJavascriptInterface(object, name,
207 JavascriptInterface.class);
210 public void evaluateJavascript(String script, ValueCallback<String> callback) {
211 final ValueCallback<String> fCallback = callback;
212 ContentViewCore.JavaScriptCallback coreCallback = null;
213 if (fCallback != null) {
214 coreCallback = new ContentViewCore.JavaScriptCallback() {
216 public void handleJavaScriptResult(String jsonResult) {
217 fCallback.onReceiveValue(jsonResult);
221 mContentViewCore.evaluateJavaScript(script, coreCallback);
224 public void setUIClient(XWalkUIClientInternal client) {
225 mContentsClientBridge.setUIClient(client);
228 public void setResourceClient(XWalkResourceClientInternal client) {
229 mContentsClientBridge.setResourceClient(client);
232 public void setXWalkWebChromeClient(XWalkWebChromeClient client) {
233 mContentsClientBridge.setXWalkWebChromeClient(client);
236 public XWalkWebChromeClient getXWalkWebChromeClient() {
237 return mContentsClientBridge.getXWalkWebChromeClient();
240 public void setXWalkClient(XWalkClient client) {
241 mContentsClientBridge.setXWalkClient(client);
244 public void setDownloadListener(DownloadListener listener) {
245 mContentsClientBridge.setDownloadListener(listener);
248 public void setNavigationHandler(XWalkNavigationHandler handler) {
249 mContentsClientBridge.setNavigationHandler(handler);
252 public void setNotificationService(XWalkNotificationService service) {
253 mContentsClientBridge.setNotificationService(service);
256 public void onPause() {
257 mContentViewCore.onHide();
260 public void onResume() {
261 mContentViewCore.onShow();
264 public void onActivityResult(int requestCode, int resultCode, Intent data) {
265 mWindow.onActivityResult(requestCode, resultCode, data);
268 public boolean onNewIntent(Intent intent) {
269 return mContentsClientBridge.onNewIntent(intent);
272 public void clearCache(boolean includeDiskFiles) {
273 if (mXWalkContent == 0) return;
274 nativeClearCache(mXWalkContent, includeDiskFiles);
277 public void clearHistory() {
278 mContentViewCore.clearHistory();
281 public boolean canGoBack() {
282 return mContentViewCore.canGoBack();
285 public void goBack() {
286 mContentViewCore.goBack();
289 public boolean canGoForward() {
290 return mContentViewCore.canGoForward();
293 public void goForward() {
294 mContentViewCore.goForward();
297 void navigateTo(int offset) {
298 mContentViewCore.goToOffset(offset);
301 public void stopLoading() {
302 mContentViewCore.stopLoading();
303 mContentsClientBridge.onStopLoading();
306 // Currently, timer pause/resume is actually
307 // a global setting. And multiple pause will fail the
308 // DCHECK in content (content_view_statics.cc:57).
309 // Here uses a static boolean to avoid this issue.
310 private static boolean timerPaused = false;
312 // TODO(Guangzhen): ContentViewStatics will be removed in upstream,
313 // details in content_view_statics.cc.
314 // We need follow up after upstream updates that.
315 public void pauseTimers() {
316 if (timerPaused) return;
317 ContentViewStatics.setWebKitSharedTimersSuspended(true);
321 public void resumeTimers() {
322 if (!timerPaused) return;
323 ContentViewStatics.setWebKitSharedTimersSuspended(false);
327 public String getOriginalUrl() {
328 NavigationHistory history = mContentViewCore.getNavigationHistory();
329 int currentIndex = history.getCurrentEntryIndex();
330 if (currentIndex >= 0 && currentIndex < history.getEntryCount()) {
331 return history.getEntryAtIndex(currentIndex).getOriginalUrl();
336 public String getXWalkVersion() {
337 if (mXWalkContent == 0) return "";
338 return nativeGetVersion(mXWalkContent);
341 public void setNetworkAvailable(boolean networkUp) {
342 if (mXWalkContent == 0) return;
343 nativeSetJsOnlineProperty(mXWalkContent, networkUp);
346 // For instrumentation test.
347 public ContentViewCore getContentViewCoreForTest() {
348 return mContentViewCore;
351 // For instrumentation test.
352 public void installWebContentsObserverForTest(XWalkContentsClient contentClient) {
353 contentClient.installWebContentsObserver(mContentViewCore);
356 public String devToolsAgentId() {
357 if (mXWalkContent == 0) return "";
358 return nativeDevToolsAgentId(mXWalkContent);
361 public XWalkSettings getSettings() {
365 public void loadAppFromManifest(String url, String data) {
366 if (mXWalkContent == 0 ||
367 ((url == null || url.isEmpty()) &&
368 (data == null || data.isEmpty()))) {
372 String content = data;
373 // If the data of manifest.json is not set, try to load it.
374 if (data == null || data.isEmpty()) {
376 content = AndroidProtocolHandler.getUrlContent(mXWalkView.getActivity(), url);
377 } catch (IOException e) {
378 throw new RuntimeException("Failed to read the manifest: " + url);
382 // Calculate the base url of manifestUrl. Used by native side.
383 // TODO(yongsheng): It's from runtime side. Need to find a better way
385 String baseUrl = url;
386 int position = url.lastIndexOf("/");
387 if (position != -1) {
388 baseUrl = url.substring(0, position + 1);
390 Log.w(TAG, "The url of manifest.json is probably not set correctly.");
393 if (!nativeSetManifest(mXWalkContent, baseUrl, content)) {
394 throw new RuntimeException("Failed to parse the manifest file: " + url);
398 public XWalkNavigationHistoryInternal getNavigationHistory() {
399 return new XWalkNavigationHistoryInternal(mXWalkView, mContentViewCore.getNavigationHistory());
402 public static final String SAVE_RESTORE_STATE_KEY = "XWALKVIEW_STATE";
404 public XWalkNavigationHistoryInternal saveState(Bundle outState) {
405 if (outState == null) return null;
407 byte[] state = nativeGetState(mXWalkContent);
408 if (state == null) return null;
410 outState.putByteArray(SAVE_RESTORE_STATE_KEY, state);
411 return getNavigationHistory();
414 public XWalkNavigationHistoryInternal restoreState(Bundle inState) {
415 if (inState == null) return null;
417 byte[] state = inState.getByteArray(SAVE_RESTORE_STATE_KEY);
418 if (state == null) return null;
420 boolean result = nativeSetState(mXWalkContent, state);
422 // The onUpdateTitle callback normally happens when a page is loaded,
423 // but is optimized out in the restoreState case because the title is
424 // already restored. See WebContentsImpl::UpdateTitleForEntry. So we
425 // call the callback explicitly here.
427 mContentsClientBridge.onUpdateTitle(mContentViewCore.getTitle());
430 return result ? getNavigationHistory() : null;
433 boolean hasEnteredFullscreen() {
434 return mContentsClientBridge.hasEnteredFullscreen();
437 void exitFullscreen() {
438 if (hasEnteredFullscreen()) {
439 mContentsClientBridge.exitFullscreen(mWebContents);
444 public void onGetUrlFromManifest(String url) {
445 if (url != null && !url.isEmpty()) {
451 public void onGetUrlAndLaunchScreenFromManifest(String url, String readyWhen, String imageBorder) {
452 if (url == null || url.isEmpty()) return;
453 mLaunchScreenManager.displayLaunchScreen(readyWhen, imageBorder);
454 mContentsClientBridge.registerPageLoadListener(mLaunchScreenManager);
459 public void onGetFullscreenFlagFromManifest(boolean enterFullscreen) {
460 if (enterFullscreen) mContentsClientBridge.onToggleFullscreen(true);
463 public void destroy() {
464 if (mXWalkContent == 0) return;
466 XWalkPreferencesInternal.unload(this);
467 // Reset existing notification service in order to destruct it.
468 setNotificationService(null);
469 // Remove its children used for page rendering from view hierarchy.
470 removeView(mContentView);
471 removeView(mContentViewRenderView);
472 mContentViewRenderView.setCurrentContentViewCore(null);
474 // Destroy the native resources.
475 mContentViewRenderView.destroy();
476 mContentViewCore.destroy();
478 mCleanupReference.cleanupNow();
479 mCleanupReference = null;
483 public int getRoutingID() {
484 return nativeGetRoutingID(mXWalkContent);
487 //--------------------------------------------------------------------------------------------
488 private class XWalkIoThreadClientImpl implements XWalkContentsIoThreadClient {
489 // All methods are called on the IO thread.
492 public int getCacheMode() {
493 return mSettings.getCacheMode();
497 public InterceptedRequestData shouldInterceptRequest(final String url,
498 boolean isMainFrame) {
500 // Notify a resource load is started. This is not the best place to start the callback
501 // but it's a workable way.
502 mContentsClientBridge.getCallbackHelper().postOnResourceLoadStarted(url);
504 WebResourceResponse webResourceResponse = mContentsClientBridge.shouldInterceptRequest(url);
505 InterceptedRequestData interceptedRequestData = null;
507 if (webResourceResponse == null) {
508 mContentsClientBridge.getCallbackHelper().postOnLoadResource(url);
510 if (isMainFrame && webResourceResponse.getData() == null) {
511 mContentsClientBridge.getCallbackHelper().postOnReceivedError(
512 XWalkResourceClientInternal.ERROR_UNKNOWN, null, url);
514 interceptedRequestData = new InterceptedRequestData(webResourceResponse.getMimeType(),
515 webResourceResponse.getEncoding(),
516 webResourceResponse.getData());
518 return interceptedRequestData;
522 public boolean shouldBlockContentUrls() {
523 return !mSettings.getAllowContentAccess();
527 public boolean shouldBlockFileUrls() {
528 return !mSettings.getAllowFileAccess();
532 public boolean shouldBlockNetworkLoads() {
533 return mSettings.getBlockNetworkLoads();
537 public void onDownloadStart(String url,
539 String contentDisposition,
541 long contentLength) {
542 mContentsClientBridge.getCallbackHelper().postOnDownloadStart(url, userAgent,
543 contentDisposition, mimeType, contentLength);
547 public void newLoginRequest(String realm, String account, String args) {
548 mContentsClientBridge.getCallbackHelper().postOnReceivedLoginRequest(realm, account, args);
552 private class XWalkGeolocationCallback implements XWalkGeolocationPermissions.Callback {
554 public void invoke(final String origin, final boolean allow, final boolean retain) {
555 ThreadUtils.runOnUiThread(new Runnable() {
560 mGeolocationPermissions.allow(origin);
562 mGeolocationPermissions.deny(origin);
565 nativeInvokeGeolocationCallback(mXWalkContent, allow, origin);
572 private void onGeolocationPermissionsShowPrompt(String origin) {
573 // Reject if geolocation is disabled, or the origin has a retained deny.
574 if (!mSettings.getGeolocationEnabled()) {
575 nativeInvokeGeolocationCallback(mXWalkContent, false, origin);
578 // Allow if the origin has a retained allow.
579 if (mGeolocationPermissions.hasOrigin(origin)) {
580 nativeInvokeGeolocationCallback(mXWalkContent,
581 mGeolocationPermissions.isOriginAllowed(origin),
585 mContentsClientBridge.onGeolocationPermissionsShowPrompt(
586 origin, new XWalkGeolocationCallback());
590 public void onGeolocationPermissionsHidePrompt() {
591 mContentsClientBridge.onGeolocationPermissionsHidePrompt();
594 public String enableRemoteDebugging(int allowedUid) {
595 // Chrome looks for "devtools_remote" pattern in the name of a unix domain socket
596 // to identify a debugging page
597 final String socketName = getContext().getApplicationContext().getPackageName() + "_devtools_remote";
598 if (mDevToolsServer == null) {
599 mDevToolsServer = new XWalkDevToolsServer(socketName);
600 mDevToolsServer.allowConnectionFromUid(allowedUid);
601 mDevToolsServer.setRemoteDebuggingEnabled(true);
603 // devtools/page is hardcoded in devtools_http_handler_impl.cc (kPageUrlPrefix)
604 return "ws://" + socketName + "/devtools/page/" + devToolsAgentId();
607 // Enables remote debugging and returns the URL at which the dev tools server is listening
608 // for commands. Only the current process is allowed to connect to the server.
609 String enableRemoteDebugging() {
610 return enableRemoteDebugging(getContext().getApplicationInfo().uid);
613 void disableRemoteDebugging() {
614 if (mDevToolsServer == null) return;
616 if (mDevToolsServer.isRemoteDebuggingEnabled()) {
617 mDevToolsServer.setRemoteDebuggingEnabled(false);
619 mDevToolsServer.destroy();
620 mDevToolsServer = null;
624 public void onKeyValueChanged(String key, boolean value) {
625 if (key == null) return;
626 if (key.equals(XWalkPreferencesInternal.REMOTE_DEBUGGING)) {
627 if (value) enableRemoteDebugging();
628 else disableRemoteDebugging();
629 } else if (key.equals(XWalkPreferencesInternal.ENABLE_JAVASCRIPT)) {
630 if (mSettings != null) mSettings.setJavaScriptEnabled(value);
631 } else if (key.equals(XWalkPreferencesInternal.JAVASCRIPT_CAN_OPEN_WINDOW)) {
632 if (mSettings != null) mSettings.setJavaScriptCanOpenWindowsAutomatically(value);
633 } else if (key.equals(XWalkPreferencesInternal.ALLOW_UNIVERSAL_ACCESS_FROM_FILE)) {
634 if (mSettings != null) mSettings.setAllowUniversalAccessFromFileURLs(value);
635 } else if (key.equals(XWalkPreferencesInternal.SUPPORT_MULTIPLE_WINDOWS)) {
636 if (mSettings != null) mSettings.setSupportMultipleWindows(value);
640 public void setOverlayVideoMode(boolean enabled) {
641 if (mContentViewRenderView != null) {
642 mContentViewRenderView.setOverlayVideoMode(enabled);
646 private native long nativeInit(XWalkWebContentsDelegate webViewContentsDelegate,
647 XWalkContentsClientBridge bridge);
648 private static native void nativeDestroy(long nativeXWalkContent);
649 private native long nativeGetWebContents(long nativeXWalkContent,
650 XWalkContentsIoThreadClient ioThreadClient,
651 InterceptNavigationDelegate delegate);
652 private native void nativeClearCache(long nativeXWalkContent, boolean includeDiskFiles);
653 private native String nativeDevToolsAgentId(long nativeXWalkContent);
654 private native String nativeGetVersion(long nativeXWalkContent);
655 private native void nativeSetJsOnlineProperty(long nativeXWalkContent, boolean networkUp);
656 private native boolean nativeSetManifest(long nativeXWalkContent, String path, String manifest);
657 private native int nativeGetRoutingID(long nativeXWalkContent);
658 private native void nativeInvokeGeolocationCallback(
659 long nativeXWalkContent, boolean value, String requestingFrame);
660 private native byte[] nativeGetState(long nativeXWalkContent);
661 private native boolean nativeSetState(long nativeXWalkContent, byte[] state);