1 // Copyright (c) 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.content.ActivityNotFoundException;
9 import android.content.ContentResolver;
10 import android.content.Intent;
11 import android.database.Cursor;
12 import android.graphics.Bitmap;
13 import android.graphics.Picture;
14 import android.net.Uri;
15 import android.net.http.SslCertificate;
16 import android.net.http.SslError;
17 import android.os.Message;
18 import android.provider.MediaStore;
19 import android.util.Log;
20 import android.view.KeyEvent;
21 import android.view.View;
22 import android.webkit.ConsoleMessage;
23 import android.webkit.ValueCallback;
24 import android.webkit.WebResourceResponse;
26 import org.chromium.base.CalledByNative;
27 import org.chromium.base.JNINamespace;
28 import org.chromium.base.ThreadUtils;
29 import org.chromium.components.navigation_interception.InterceptNavigationDelegate;
30 import org.chromium.components.navigation_interception.NavigationParams;
31 import org.chromium.content.browser.ContentVideoViewClient;
32 import org.chromium.content.browser.ContentViewDownloadDelegate;
33 import org.chromium.content.browser.DownloadInfo;
34 import org.xwalk.core.internal.XWalkUIClientInternal.LoadStatusInternal;
36 // Help bridge callback in XWalkContentsClient to XWalkViewClient and
37 // XWalkWebChromeClient; Also handle the JNI conmmunication logic.
38 @JNINamespace("xwalk")
39 class XWalkContentsClientBridge extends XWalkContentsClient
40 implements ContentViewDownloadDelegate {
41 private static final String TAG = XWalkContentsClientBridge.class.getName();
43 private XWalkViewInternal mXWalkView;
44 private XWalkUIClientInternal mXWalkUIClient;
45 private XWalkResourceClientInternal mXWalkResourceClient;
46 private XWalkClient mXWalkClient;
47 private XWalkWebChromeClient mXWalkWebChromeClient;
48 private Bitmap mFavicon;
49 private DownloadListener mDownloadListener;
50 private InterceptNavigationDelegate mInterceptNavigationDelegate;
51 private PageLoadListener mPageLoadListener;
52 private XWalkNavigationHandler mNavigationHandler;
53 private XWalkNotificationService mNotificationService;
55 /** State recording variables */
56 // For fullscreen state.
57 private boolean mIsFullscreen = false;
59 private LoadStatusInternal mLoadStatus = LoadStatusInternal.FINISHED;
60 private String mLoadingUrl = null;
62 // The native peer of the object
63 private long mNativeContentsClientBridge;
65 private float mPageScaleFactor;
67 private class InterceptNavigationDelegateImpl implements InterceptNavigationDelegate {
68 private XWalkContentsClient mContentsClient;
70 public InterceptNavigationDelegateImpl(XWalkContentsClient client) {
71 mContentsClient = client;
74 public boolean shouldIgnoreNavigation(NavigationParams navigationParams) {
75 final String url = navigationParams.url;
76 boolean ignoreNavigation = shouldOverrideUrlLoading(url) ||
77 (mNavigationHandler != null &&
78 mNavigationHandler.handleNavigation(navigationParams));
80 if (!ignoreNavigation) {
81 // Post a message to UI thread to notify the page is starting to load.
82 mContentsClient.getCallbackHelper().postOnPageStarted(url);
85 return ignoreNavigation;
89 public XWalkContentsClientBridge(XWalkViewInternal xwView) {
92 mInterceptNavigationDelegate = new InterceptNavigationDelegateImpl(this);
95 public void setUIClient(XWalkUIClientInternal client) {
96 // If it's null, use Crosswalk implementation.
98 mXWalkUIClient = client;
101 mXWalkUIClient = new XWalkUIClientInternal(mXWalkView);
104 public void setResourceClient(XWalkResourceClientInternal client) {
105 // If it's null, use Crosswalk implementation.
106 if (client != null) {
107 mXWalkResourceClient = client;
110 mXWalkResourceClient = new XWalkResourceClientInternal(mXWalkView);
114 public void setXWalkWebChromeClient(XWalkWebChromeClient client) {
115 // If it's null, use Crosswalk implementation.
116 if (client == null) return;
117 client.setContentsClient(this);
118 mXWalkWebChromeClient = client;
121 public XWalkWebChromeClient getXWalkWebChromeClient() {
122 return mXWalkWebChromeClient;
125 public void setXWalkClient(XWalkClient client) {
126 mXWalkClient = client;
129 public void setNavigationHandler(XWalkNavigationHandler handler) {
130 mNavigationHandler = handler;
133 void registerPageLoadListener(PageLoadListener listener) {
134 mPageLoadListener = listener;
137 public void setNotificationService(XWalkNotificationService service) {
138 if (mNotificationService != null) mNotificationService.shutdown();
139 mNotificationService = service;
140 if (mNotificationService != null) mNotificationService.setBridge(this);
143 public boolean onNewIntent(Intent intent) {
144 return mNotificationService.maybeHandleIntent(intent);
147 public InterceptNavigationDelegate getInterceptNavigationDelegate() {
148 return mInterceptNavigationDelegate;
151 private boolean isOwnerActivityRunning() {
152 if (mXWalkView != null && mXWalkView.isOwnerActivityRunning()) {
158 // TODO(Xingnan): All the empty functions need to be implemented.
160 public boolean shouldOverrideUrlLoading(String url) {
161 if (mXWalkResourceClient != null && mXWalkView != null) {
162 return mXWalkResourceClient.shouldOverrideUrlLoading(mXWalkView, url);
168 public boolean shouldOverrideKeyEvent(KeyEvent event) {
169 boolean overridden = false;
170 if (mXWalkUIClient != null && mXWalkView != null) {
171 overridden = mXWalkUIClient.shouldOverrideKeyEvent(mXWalkView, event);
174 return super.shouldOverrideKeyEvent(event);
180 public void onUnhandledKeyEvent(KeyEvent event) {
181 if (mXWalkUIClient != null && mXWalkView != null) {
182 mXWalkUIClient.onUnhandledKeyEvent(mXWalkView, event);
187 public void getVisitedHistory(ValueCallback<String[]> callback) {
191 public void doUpdateVisitedHistory(String url, boolean isReload) {
195 public void onProgressChanged(int progress) {
196 if (isOwnerActivityRunning()) {
197 mXWalkResourceClient.onProgressChanged(mXWalkView, progress);
202 public WebResourceResponse shouldInterceptRequest(String url) {
203 if (isOwnerActivityRunning()) {
204 return mXWalkResourceClient.shouldInterceptLoadRequest(mXWalkView, url);
210 public void onResourceLoadStarted(String url) {
211 if (isOwnerActivityRunning()) {
212 mXWalkResourceClient.onLoadStarted(mXWalkView, url);
217 public void onResourceLoadFinished(String url) {
218 if (isOwnerActivityRunning()) {
219 mXWalkResourceClient.onLoadFinished(mXWalkView, url);
224 public void onLoadResource(String url) {
225 if (mXWalkClient != null && isOwnerActivityRunning()) {
226 mXWalkClient.onLoadResource(mXWalkView, url);
231 public boolean onConsoleMessage(ConsoleMessage consoleMessage) {
236 public void onReceivedHttpAuthRequest(XWalkHttpAuthHandler handler, String host, String realm) {
237 if (mXWalkClient != null && isOwnerActivityRunning()) {
238 mXWalkClient.onReceivedHttpAuthRequest(mXWalkView, handler, host, realm);
243 public void onReceivedSslError(ValueCallback<Boolean> callback, SslError error) {
244 if (mXWalkClient != null && isOwnerActivityRunning()) {
245 mXWalkClient.onReceivedSslError(mXWalkView, callback, error);
250 public void onReceivedLoginRequest(String realm, String account, String args) {
254 public void onGeolocationPermissionsShowPrompt(String origin,
255 XWalkGeolocationPermissions.Callback callback) {
256 if (mXWalkWebChromeClient != null && isOwnerActivityRunning()) {
257 mXWalkWebChromeClient.onGeolocationPermissionsShowPrompt(origin, callback);
262 public void onGeolocationPermissionsHidePrompt() {
263 if (mXWalkWebChromeClient != null && isOwnerActivityRunning()) {
264 mXWalkWebChromeClient.onGeolocationPermissionsHidePrompt();
269 public void onFindResultReceived(int activeMatchOrdinal, int numberOfMatches,
270 boolean isDoneCounting) {
274 public void onNewPicture(Picture picture) {
278 public void onPageStarted(String url) {
279 if (mXWalkUIClient != null && isOwnerActivityRunning()) {
281 mLoadStatus = LoadStatusInternal.FINISHED;
282 mXWalkUIClient.onPageLoadStarted(mXWalkView, url);
287 public void onPageFinished(String url) {
288 if (!isOwnerActivityRunning()) return;
289 if (mPageLoadListener != null) mPageLoadListener.onPageFinished(url);
290 if (mXWalkUIClient != null) {
291 if (mLoadStatus == LoadStatusInternal.CANCELLED && mLoadingUrl != null) {
292 mXWalkUIClient.onPageLoadStopped(mXWalkView, mLoadingUrl, mLoadStatus);
294 mXWalkUIClient.onPageLoadStopped(mXWalkView, url, mLoadStatus);
299 // This isn't the accurate point to notify a resource loading is finished,
300 // but it's a workable way. We could enhance this by extending Content
301 // API in future if we have the demand.
302 onResourceLoadFinished(url);
306 protected void onStopLoading() {
307 mLoadStatus = LoadStatusInternal.CANCELLED;
311 public void onReceivedError(int errorCode, String description, String failingUrl) {
312 if (isOwnerActivityRunning()) {
313 if (mLoadingUrl != null && mLoadingUrl.equals(failingUrl)) {
314 mLoadStatus = LoadStatusInternal.FAILED;
316 mXWalkResourceClient.onReceivedLoadError(mXWalkView, errorCode, description, failingUrl);
321 public void onRendererUnresponsive() {
322 if (mXWalkClient != null && isOwnerActivityRunning()) {
323 mXWalkClient.onRendererUnresponsive(mXWalkView);
328 public void onRendererResponsive() {
329 if (mXWalkClient != null && isOwnerActivityRunning()) {
330 mXWalkClient.onRendererResponsive(mXWalkView);
335 public void onFormResubmission(Message dontResend, Message resend) {
336 dontResend.sendToTarget();
340 public void onDownloadStart(String url,
342 String contentDisposition,
344 long contentLength) {
348 public boolean onCreateWindow(boolean isDialog, boolean isUserGesture) {
353 public void onRequestFocus() {
354 if (isOwnerActivityRunning()) {
355 mXWalkUIClient.onRequestFocus(mXWalkView);
360 public void onCloseWindow() {
361 if (isOwnerActivityRunning()) {
362 mXWalkUIClient.onJavascriptCloseWindow(mXWalkView);
367 public void onReceivedIcon(Bitmap bitmap) {
368 if (mXWalkWebChromeClient != null && mXWalkView != null) {
369 mXWalkWebChromeClient.onReceivedIcon(mXWalkView, bitmap);
375 public void onShowCustomView(View view, XWalkWebChromeClient.CustomViewCallback callback) {
376 if (mXWalkWebChromeClient != null && isOwnerActivityRunning()) {
377 mXWalkWebChromeClient.onShowCustomView(view, callback);
382 public void onHideCustomView() {
383 if (mXWalkWebChromeClient != null && isOwnerActivityRunning()) {
384 mXWalkWebChromeClient.onHideCustomView();
389 public void onScaleChangedScaled(float oldScale, float newScale) {
390 if (isOwnerActivityRunning()) {
391 mXWalkUIClient.onScaleChanged(mXWalkView, oldScale, newScale);
396 public void didFinishLoad(String url) {
400 public void onTitleChanged(String title) {
401 if (mXWalkUIClient != null && isOwnerActivityRunning()) {
402 mXWalkUIClient.onReceivedTitle(mXWalkView, title);
407 public void onToggleFullscreen(boolean enterFullscreen) {
408 if (isOwnerActivityRunning()) {
409 mIsFullscreen = enterFullscreen;
410 mXWalkUIClient.onFullscreenToggled(mXWalkView, enterFullscreen);
415 public boolean hasEnteredFullscreen() {
416 return mIsFullscreen;
420 public boolean shouldOpenWithDefaultBrowser(String contentUrl) {
421 Intent intent = new Intent();
422 intent.setAction("android.intent.action.VIEW");
423 Uri url = Uri.parse(contentUrl);
426 mXWalkView.getActivity().startActivity(intent);
427 } catch (ActivityNotFoundException exception) {
428 Log.w(TAG, "Activity not found for Intent:");
436 public boolean shouldOverrideRunFileChooser(
437 final int processId, final int renderId, final int modeFlags,
438 String acceptTypes, boolean capture) {
439 if (!isOwnerActivityRunning()) return false;
440 abstract class UriCallback implements ValueCallback<Uri> {
441 boolean syncNullReceived = false;
442 boolean syncCallFinished = false;
443 protected String resolveFileName(Uri uri, ContentResolver contentResolver) {
444 if (contentResolver == null || uri == null) return "";
445 Cursor cursor = null;
447 cursor = contentResolver.query(uri, null, null, null, null);
449 if (cursor != null && cursor.getCount() >= 1) {
450 cursor.moveToFirst();
451 int index = cursor.getColumnIndex(MediaStore.MediaColumns.DISPLAY_NAME);
452 if (index > -1) return cursor.getString(index);
454 } catch (NullPointerException e) {
455 // Some android models don't handle the provider call correctly.
456 // see crbug.com/345393
459 if (cursor != null) cursor.close();
464 UriCallback uploadFile = new UriCallback() {
465 boolean completed = false;
467 public void onReceiveValue(Uri value) {
469 throw new IllegalStateException("Duplicate openFileChooser result");
472 if (value == null && !syncCallFinished) {
473 syncNullReceived = true;
477 nativeOnFilesNotSelected(mNativeContentsClientBridge,
478 processId, renderId, modeFlags);
481 String displayName = null;
482 if (ContentResolver.SCHEME_FILE.equals(value.getScheme())) {
483 result = value.getSchemeSpecificPart();
484 displayName = value.getLastPathSegment();
485 } else if (ContentResolver.SCHEME_CONTENT.equals(value.getScheme())) {
486 result = value.toString();
487 displayName = resolveFileName(
488 value, mXWalkView.getActivity().getContentResolver());
490 result = value.getPath();
491 displayName = value.getLastPathSegment();
493 if (displayName == null || displayName.isEmpty()) displayName = result;
494 nativeOnFilesSelected(mNativeContentsClientBridge,
495 processId, renderId, modeFlags, result, displayName);
499 mXWalkUIClient.openFileChooser(
500 mXWalkView, uploadFile, acceptTypes, Boolean.toString(capture));
501 uploadFile.syncCallFinished = true;
502 // File chooser requires user interaction, valid derives should handle it in async process.
503 // If the ValueCallback receive a sync result with null value, it is considered the
504 // file chooser is not overridden.
505 return !uploadFile.syncNullReceived;
509 public ContentVideoViewClient getContentVideoViewClient() {
510 return new XWalkContentVideoViewClient(this, mXWalkView.getActivity(), mXWalkView);
513 // Used by the native peer to set/reset a weak ref to the native peer.
515 private void setNativeContentsClientBridge(long nativeContentsClientBridge) {
516 mNativeContentsClientBridge = nativeContentsClientBridge;
519 // If returns false, the request is immediately canceled, and any call to proceedSslError
520 // has no effect. If returns true, the request should be canceled or proceeded using
521 // proceedSslError().
522 // Unlike the webview classic, we do not keep keep a database of certificates that
523 // are allowed by the user, because this functionality is already handled via
524 // ssl_policy in native layers.
526 private boolean allowCertificateError(int certError, byte[] derBytes, final String url,
528 final SslCertificate cert = SslUtil.getCertificateFromDerBytes(derBytes);
530 // if the certificate or the client is null, cancel the request
533 final SslError sslError = SslUtil.sslErrorFromNetErrorCode(certError, cert, url);
534 ValueCallback<Boolean> callback = new ValueCallback<Boolean>() {
536 public void onReceiveValue(Boolean value) {
537 proceedSslError(value.booleanValue(), id);
540 onReceivedSslError(callback, sslError);
544 private void proceedSslError(boolean proceed, int id) {
545 if (mNativeContentsClientBridge == 0) return;
546 nativeProceedSslError(mNativeContentsClientBridge, proceed, id);
550 private void handleJsAlert(String url, String message, int id) {
551 if (isOwnerActivityRunning()) {
552 XWalkJavascriptResultHandlerInternal result =
553 new XWalkJavascriptResultHandlerInternal(this, id);
554 mXWalkUIClient.onJavascriptModalDialog(mXWalkView,
555 XWalkUIClientInternal.JavascriptMessageTypeInternal.JAVASCRIPT_ALERT,
556 url, message, "", result);
561 private void handleJsConfirm(String url, String message, int id) {
562 if (isOwnerActivityRunning()) {
563 XWalkJavascriptResultHandlerInternal result =
564 new XWalkJavascriptResultHandlerInternal(this, id);
565 mXWalkUIClient.onJavascriptModalDialog(mXWalkView,
566 XWalkUIClientInternal.JavascriptMessageTypeInternal.JAVASCRIPT_CONFIRM,
567 url, message, "", result);
572 private void handleJsPrompt(String url, String message, String defaultValue, int id) {
573 if (isOwnerActivityRunning()) {
574 XWalkJavascriptResultHandlerInternal result =
575 new XWalkJavascriptResultHandlerInternal(this, id);
576 mXWalkUIClient.onJavascriptModalDialog(mXWalkView,
577 XWalkUIClientInternal.JavascriptMessageTypeInternal.JAVASCRIPT_PROMPT,
578 url, message, defaultValue, result);
583 private void handleJsBeforeUnload(String url, String message, int id) {
584 if (isOwnerActivityRunning()) {
585 XWalkJavascriptResultHandlerInternal result =
586 new XWalkJavascriptResultHandlerInternal(this, id);
587 mXWalkUIClient.onJavascriptModalDialog(mXWalkView,
588 XWalkUIClientInternal.JavascriptMessageTypeInternal.JAVASCRIPT_BEFOREUNLOAD,
589 url, message, "", result);
594 private void updateNotificationIcon(int notificationId, Bitmap icon) {
595 mNotificationService.updateNotificationIcon(notificationId, icon);
599 private void showNotification(String title, String message, String replaceId,
600 int notificationId, long delegate) {
601 // FIXME(wang16): use replaceId to replace exist notification. It happens when
602 // a notification with same name and tag fires.
603 mNotificationService.showNotification(
604 title, message, notificationId, delegate);
608 private void cancelNotification(int notificationId, long delegate) {
609 mNotificationService.cancelNotification(notificationId, delegate);
612 void confirmJsResult(int id, String prompt) {
613 if (mNativeContentsClientBridge == 0) return;
614 nativeConfirmJsResult(mNativeContentsClientBridge, id, prompt);
617 void cancelJsResult(int id) {
618 if (mNativeContentsClientBridge == 0) return;
619 nativeCancelJsResult(mNativeContentsClientBridge, id);
622 void exitFullscreen(long nativeWebContents) {
623 if (mNativeContentsClientBridge == 0) return;
624 nativeExitFullscreen(mNativeContentsClientBridge, nativeWebContents);
627 public void notificationDisplayed(long delegate) {
628 if (mNativeContentsClientBridge == 0) return;
629 nativeNotificationDisplayed(mNativeContentsClientBridge, delegate);
632 public void notificationError(long delegate) {
633 if (mNativeContentsClientBridge == 0) return;
634 nativeNotificationError(mNativeContentsClientBridge, delegate);
637 public void notificationClicked(int id, long delegate) {
638 if (mNativeContentsClientBridge == 0) return;
639 nativeNotificationClicked(mNativeContentsClientBridge, id, delegate);
642 public void notificationClosed(int id, boolean byUser, long delegate) {
643 if (mNativeContentsClientBridge == 0) return;
644 nativeNotificationClosed(mNativeContentsClientBridge, id, byUser, delegate);
647 void setDownloadListener(DownloadListener listener) {
648 mDownloadListener = listener;
651 // Implement ContentViewDownloadDelegate methods.
652 public void requestHttpGetDownload(DownloadInfo downloadInfo) {
653 if (mDownloadListener != null) {
654 mDownloadListener.onDownloadStart(downloadInfo.getUrl(), downloadInfo.getUserAgent(),
655 downloadInfo.getContentDisposition(), downloadInfo.getMimeType(), downloadInfo.getContentLength());
659 public void onDownloadStarted(String filename, String mimeType) {
662 public void onDangerousDownload(String filename, int downloadId) {
666 public void onWebLayoutPageScaleFactorChanged(float pageScaleFactor) {
667 if (mPageScaleFactor == pageScaleFactor) return;
669 float oldPageScaleFactor = mPageScaleFactor;
670 double dipScale = getDIPScale();
671 mPageScaleFactor = pageScaleFactor;
672 onScaleChanged((float)(oldPageScaleFactor * dipScale),
673 (float)(mPageScaleFactor * dipScale));
676 //--------------------------------------------------------------------------------------------
678 //--------------------------------------------------------------------------------------------
679 private native void nativeProceedSslError(long nativeXWalkContentsClientBridge,
680 boolean proceed, int id);
682 private native void nativeConfirmJsResult(long nativeXWalkContentsClientBridge, int id,
684 private native void nativeCancelJsResult(long nativeXWalkContentsClientBridge, int id);
685 private native void nativeExitFullscreen(long nativeXWalkContentsClientBridge, long nativeWebContents);
686 private native void nativeNotificationDisplayed(long nativeXWalkContentsClientBridge, long delegate);
687 private native void nativeNotificationError(long nativeXWalkContentsClientBridge, long delegate);
688 private native void nativeNotificationClicked(long nativeXWalkContentsClientBridge, int id, long delegate);
689 private native void nativeNotificationClosed(long nativeXWalkContentsClientBridge, int id, boolean byUser, long delegate);
690 private native void nativeOnFilesSelected(long nativeXWalkContentsClientBridge,
691 int processId, int renderId, int mode_flags, String filepath, String displayName);
692 private native void nativeOnFilesNotSelected(long nativeXWalkContentsClientBridge,
693 int processId, int renderId, int mode_flags);