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.os.Handler;
19 import android.provider.MediaStore;
20 import android.util.Log;
21 import android.view.KeyEvent;
22 import android.view.View;
23 import android.webkit.ConsoleMessage;
24 import android.webkit.ValueCallback;
25 import android.webkit.WebResourceResponse;
27 import org.chromium.base.CalledByNative;
28 import org.chromium.base.JNINamespace;
29 import org.chromium.base.ThreadUtils;
30 import org.chromium.components.navigation_interception.InterceptNavigationDelegate;
31 import org.chromium.components.navigation_interception.NavigationParams;
32 import org.chromium.content.browser.ContentVideoViewClient;
33 import org.chromium.content.browser.ContentViewDownloadDelegate;
34 import org.chromium.content.browser.DownloadInfo;
35 import org.xwalk.core.internal.XWalkUIClientInternal.LoadStatusInternal;
37 // Help bridge callback in XWalkContentsClient to XWalkViewClient and
38 // XWalkWebChromeClient; Also handle the JNI conmmunication logic.
39 @JNINamespace("xwalk")
40 class XWalkContentsClientBridge extends XWalkContentsClient
41 implements ContentViewDownloadDelegate {
42 private static final String TAG = XWalkContentsClientBridge.class.getName();
43 private static final int NEW_XWALKVIEW_CREATED = 100;
45 private XWalkViewInternal mXWalkView;
46 private XWalkUIClientInternal mXWalkUIClient;
47 private XWalkResourceClientInternal mXWalkResourceClient;
48 private XWalkClient mXWalkClient;
49 private XWalkWebChromeClient mXWalkWebChromeClient;
50 private Bitmap mFavicon;
51 private DownloadListener mDownloadListener;
52 private InterceptNavigationDelegate mInterceptNavigationDelegate;
53 private PageLoadListener mPageLoadListener;
54 private XWalkNavigationHandler mNavigationHandler;
55 private XWalkNotificationService mNotificationService;
56 private Handler mUiThreadHandler;
58 /** State recording variables */
59 // For fullscreen state.
60 private boolean mIsFullscreen = false;
62 private LoadStatusInternal mLoadStatus = LoadStatusInternal.FINISHED;
63 private String mLoadingUrl = null;
65 // The native peer of the object
66 private long mNativeContentsClientBridge;
68 private float mPageScaleFactor;
70 private class InterceptNavigationDelegateImpl implements InterceptNavigationDelegate {
71 private XWalkContentsClient mContentsClient;
73 public InterceptNavigationDelegateImpl(XWalkContentsClient client) {
74 mContentsClient = client;
77 public boolean shouldIgnoreNavigation(NavigationParams navigationParams) {
78 final String url = navigationParams.url;
79 boolean ignoreNavigation = shouldOverrideUrlLoading(url) ||
80 (mNavigationHandler != null &&
81 mNavigationHandler.handleNavigation(navigationParams));
83 if (!ignoreNavigation) {
84 // Post a message to UI thread to notify the page is starting to load.
85 mContentsClient.getCallbackHelper().postOnPageStarted(url);
88 return ignoreNavigation;
92 public XWalkContentsClientBridge(XWalkViewInternal xwView) {
95 mInterceptNavigationDelegate = new InterceptNavigationDelegateImpl(this);
97 mUiThreadHandler = new Handler() {
99 public void handleMessage(Message msg) {
101 case NEW_XWALKVIEW_CREATED:
102 XWalkViewInternal newXWalkView = (XWalkViewInternal) msg.obj;
103 if (newXWalkView == mXWalkView) {
104 throw new IllegalArgumentException("Parent XWalkView cannot host it's own popup window");
107 if (newXWalkView != null && newXWalkView.getNavigationHistory().size() != 0) {
108 throw new IllegalArgumentException("New WebView for popup window must not have been previously navigated.");
111 mXWalkView.completeWindowCreation(newXWalkView);
114 throw new IllegalStateException();
121 public void setUIClient(XWalkUIClientInternal client) {
122 // If it's null, use Crosswalk implementation.
123 if (client != null) {
124 mXWalkUIClient = client;
127 mXWalkUIClient = new XWalkUIClientInternal(mXWalkView);
130 public void setResourceClient(XWalkResourceClientInternal client) {
131 // If it's null, use Crosswalk implementation.
132 if (client != null) {
133 mXWalkResourceClient = client;
136 mXWalkResourceClient = new XWalkResourceClientInternal(mXWalkView);
140 public void setXWalkWebChromeClient(XWalkWebChromeClient client) {
141 // If it's null, use Crosswalk implementation.
142 if (client == null) return;
143 client.setContentsClient(this);
144 mXWalkWebChromeClient = client;
147 public XWalkWebChromeClient getXWalkWebChromeClient() {
148 return mXWalkWebChromeClient;
151 public void setXWalkClient(XWalkClient client) {
152 mXWalkClient = client;
155 public void setNavigationHandler(XWalkNavigationHandler handler) {
156 mNavigationHandler = handler;
159 void registerPageLoadListener(PageLoadListener listener) {
160 mPageLoadListener = listener;
163 public void setNotificationService(XWalkNotificationService service) {
164 if (mNotificationService != null) mNotificationService.shutdown();
165 mNotificationService = service;
166 if (mNotificationService != null) mNotificationService.setBridge(this);
169 public boolean onNewIntent(Intent intent) {
170 return mNotificationService.maybeHandleIntent(intent);
173 public InterceptNavigationDelegate getInterceptNavigationDelegate() {
174 return mInterceptNavigationDelegate;
177 private boolean isOwnerActivityRunning() {
178 if (mXWalkView != null && mXWalkView.isOwnerActivityRunning()) {
184 // TODO(Xingnan): All the empty functions need to be implemented.
186 public boolean shouldOverrideUrlLoading(String url) {
187 if (mXWalkResourceClient != null && mXWalkView != null) {
188 return mXWalkResourceClient.shouldOverrideUrlLoading(mXWalkView, url);
194 public boolean shouldOverrideKeyEvent(KeyEvent event) {
195 boolean overridden = false;
196 if (mXWalkUIClient != null && mXWalkView != null) {
197 overridden = mXWalkUIClient.shouldOverrideKeyEvent(mXWalkView, event);
200 return super.shouldOverrideKeyEvent(event);
206 public void onUnhandledKeyEvent(KeyEvent event) {
207 if (mXWalkUIClient != null && mXWalkView != null) {
208 mXWalkUIClient.onUnhandledKeyEvent(mXWalkView, event);
213 public void getVisitedHistory(ValueCallback<String[]> callback) {
217 public void doUpdateVisitedHistory(String url, boolean isReload) {
221 public void onProgressChanged(int progress) {
222 if (isOwnerActivityRunning()) {
223 mXWalkResourceClient.onProgressChanged(mXWalkView, progress);
228 public WebResourceResponse shouldInterceptRequest(String url) {
229 if (isOwnerActivityRunning()) {
230 return mXWalkResourceClient.shouldInterceptLoadRequest(mXWalkView, url);
236 public void onResourceLoadStarted(String url) {
237 if (isOwnerActivityRunning()) {
238 mXWalkResourceClient.onLoadStarted(mXWalkView, url);
243 public void onResourceLoadFinished(String url) {
244 if (isOwnerActivityRunning()) {
245 mXWalkResourceClient.onLoadFinished(mXWalkView, url);
250 public void onLoadResource(String url) {
251 if (mXWalkClient != null && isOwnerActivityRunning()) {
252 mXWalkClient.onLoadResource(mXWalkView, url);
257 public boolean onConsoleMessage(ConsoleMessage consoleMessage) {
262 public void onReceivedHttpAuthRequest(XWalkHttpAuthHandler handler, String host, String realm) {
263 if (mXWalkClient != null && isOwnerActivityRunning()) {
264 mXWalkClient.onReceivedHttpAuthRequest(mXWalkView, handler, host, realm);
269 public void onReceivedSslError(ValueCallback<Boolean> callback, SslError error) {
270 if (mXWalkClient != null && isOwnerActivityRunning()) {
271 mXWalkClient.onReceivedSslError(mXWalkView, callback, error);
276 public void onReceivedLoginRequest(String realm, String account, String args) {
280 public void onGeolocationPermissionsShowPrompt(String origin,
281 XWalkGeolocationPermissions.Callback callback) {
282 if (mXWalkWebChromeClient != null && isOwnerActivityRunning()) {
283 mXWalkWebChromeClient.onGeolocationPermissionsShowPrompt(origin, callback);
288 public void onGeolocationPermissionsHidePrompt() {
289 if (mXWalkWebChromeClient != null && isOwnerActivityRunning()) {
290 mXWalkWebChromeClient.onGeolocationPermissionsHidePrompt();
295 public void onFindResultReceived(int activeMatchOrdinal, int numberOfMatches,
296 boolean isDoneCounting) {
300 public void onNewPicture(Picture picture) {
304 public void onPageStarted(String url) {
305 if (mXWalkUIClient != null && isOwnerActivityRunning()) {
307 mLoadStatus = LoadStatusInternal.FINISHED;
308 mXWalkUIClient.onPageLoadStarted(mXWalkView, url);
313 public void onPageFinished(String url) {
314 if (!isOwnerActivityRunning()) return;
315 if (mPageLoadListener != null) mPageLoadListener.onPageFinished(url);
316 if (mXWalkUIClient != null) {
317 if (mLoadStatus == LoadStatusInternal.CANCELLED && mLoadingUrl != null) {
318 mXWalkUIClient.onPageLoadStopped(mXWalkView, mLoadingUrl, mLoadStatus);
320 mXWalkUIClient.onPageLoadStopped(mXWalkView, url, mLoadStatus);
325 // This isn't the accurate point to notify a resource loading is finished,
326 // but it's a workable way. We could enhance this by extending Content
327 // API in future if we have the demand.
328 onResourceLoadFinished(url);
332 protected void onStopLoading() {
333 mLoadStatus = LoadStatusInternal.CANCELLED;
337 public void onReceivedError(int errorCode, String description, String failingUrl) {
338 if (isOwnerActivityRunning()) {
339 if (mLoadingUrl != null && mLoadingUrl.equals(failingUrl)) {
340 mLoadStatus = LoadStatusInternal.FAILED;
342 mXWalkResourceClient.onReceivedLoadError(mXWalkView, errorCode, description, failingUrl);
347 public void onRendererUnresponsive() {
348 if (mXWalkClient != null && isOwnerActivityRunning()) {
349 mXWalkClient.onRendererUnresponsive(mXWalkView);
354 public void onRendererResponsive() {
355 if (mXWalkClient != null && isOwnerActivityRunning()) {
356 mXWalkClient.onRendererResponsive(mXWalkView);
361 public void onFormResubmission(Message dontResend, Message resend) {
362 dontResend.sendToTarget();
366 public void onDownloadStart(String url,
368 String contentDisposition,
370 long contentLength) {
374 public boolean onCreateWindow(boolean isDialog, boolean isUserGesture) {
375 if (isDialog) return false;
377 XWalkUIClientInternal.InitiateByInternal initiator =
378 XWalkUIClientInternal.InitiateByInternal.BY_JAVASCRIPT;
380 initiator = XWalkUIClientInternal.InitiateByInternal.BY_USER_GESTURE;
383 ValueCallback<XWalkViewInternal> callback = new ValueCallback<XWalkViewInternal>() {
385 public void onReceiveValue(XWalkViewInternal newXWalkView) {
386 Message m = mUiThreadHandler.obtainMessage(NEW_XWALKVIEW_CREATED, newXWalkView);
391 return mXWalkUIClient.onCreateWindowRequested(mXWalkView, initiator, callback);
395 public void onRequestFocus() {
396 if (isOwnerActivityRunning()) {
397 mXWalkUIClient.onRequestFocus(mXWalkView);
402 public void onCloseWindow() {
403 if (isOwnerActivityRunning()) {
404 mXWalkUIClient.onJavascriptCloseWindow(mXWalkView);
409 public void onReceivedIcon(Bitmap bitmap) {
410 if (mXWalkWebChromeClient != null && mXWalkView != null) {
411 mXWalkWebChromeClient.onReceivedIcon(mXWalkView, bitmap);
417 public void onShowCustomView(View view, XWalkWebChromeClient.CustomViewCallback callback) {
418 if (mXWalkWebChromeClient != null && isOwnerActivityRunning()) {
419 mXWalkWebChromeClient.onShowCustomView(view, callback);
424 public void onHideCustomView() {
425 if (mXWalkWebChromeClient != null && isOwnerActivityRunning()) {
426 mXWalkWebChromeClient.onHideCustomView();
431 public void onScaleChangedScaled(float oldScale, float newScale) {
432 if (isOwnerActivityRunning()) {
433 mXWalkUIClient.onScaleChanged(mXWalkView, oldScale, newScale);
438 public void didFinishLoad(String url) {
442 public void onTitleChanged(String title) {
443 if (mXWalkUIClient != null && isOwnerActivityRunning()) {
444 mXWalkUIClient.onReceivedTitle(mXWalkView, title);
449 public void onToggleFullscreen(boolean enterFullscreen) {
450 if (isOwnerActivityRunning()) {
451 mIsFullscreen = enterFullscreen;
452 mXWalkUIClient.onFullscreenToggled(mXWalkView, enterFullscreen);
457 public boolean hasEnteredFullscreen() {
458 return mIsFullscreen;
462 public boolean shouldOpenWithDefaultBrowser(String contentUrl) {
463 Intent intent = new Intent();
464 intent.setAction("android.intent.action.VIEW");
465 Uri url = Uri.parse(contentUrl);
468 mXWalkView.getActivity().startActivity(intent);
469 } catch (ActivityNotFoundException exception) {
470 Log.w(TAG, "Activity not found for Intent:");
478 public boolean shouldOverrideRunFileChooser(
479 final int processId, final int renderId, final int modeFlags,
480 String acceptTypes, boolean capture) {
481 if (!isOwnerActivityRunning()) return false;
482 abstract class UriCallback implements ValueCallback<Uri> {
483 boolean syncNullReceived = false;
484 boolean syncCallFinished = false;
485 protected String resolveFileName(Uri uri, ContentResolver contentResolver) {
486 if (contentResolver == null || uri == null) return "";
487 Cursor cursor = null;
489 cursor = contentResolver.query(uri, null, null, null, null);
491 if (cursor != null && cursor.getCount() >= 1) {
492 cursor.moveToFirst();
493 int index = cursor.getColumnIndex(MediaStore.MediaColumns.DISPLAY_NAME);
494 if (index > -1) return cursor.getString(index);
496 } catch (NullPointerException e) {
497 // Some android models don't handle the provider call correctly.
498 // see crbug.com/345393
501 if (cursor != null) cursor.close();
506 UriCallback uploadFile = new UriCallback() {
507 boolean completed = false;
509 public void onReceiveValue(Uri value) {
511 throw new IllegalStateException("Duplicate openFileChooser result");
514 if (value == null && !syncCallFinished) {
515 syncNullReceived = true;
519 nativeOnFilesNotSelected(mNativeContentsClientBridge,
520 processId, renderId, modeFlags);
523 String displayName = null;
524 if (ContentResolver.SCHEME_FILE.equals(value.getScheme())) {
525 result = value.getSchemeSpecificPart();
526 displayName = value.getLastPathSegment();
527 } else if (ContentResolver.SCHEME_CONTENT.equals(value.getScheme())) {
528 result = value.toString();
529 displayName = resolveFileName(
530 value, mXWalkView.getActivity().getContentResolver());
532 result = value.getPath();
533 displayName = value.getLastPathSegment();
535 if (displayName == null || displayName.isEmpty()) displayName = result;
536 nativeOnFilesSelected(mNativeContentsClientBridge,
537 processId, renderId, modeFlags, result, displayName);
541 mXWalkUIClient.openFileChooser(
542 mXWalkView, uploadFile, acceptTypes, Boolean.toString(capture));
543 uploadFile.syncCallFinished = true;
544 // File chooser requires user interaction, valid derives should handle it in async process.
545 // If the ValueCallback receive a sync result with null value, it is considered the
546 // file chooser is not overridden.
547 return !uploadFile.syncNullReceived;
551 public ContentVideoViewClient getContentVideoViewClient() {
552 return new XWalkContentVideoViewClient(this, mXWalkView.getActivity(), mXWalkView);
555 // Used by the native peer to set/reset a weak ref to the native peer.
557 private void setNativeContentsClientBridge(long nativeContentsClientBridge) {
558 mNativeContentsClientBridge = nativeContentsClientBridge;
561 // If returns false, the request is immediately canceled, and any call to proceedSslError
562 // has no effect. If returns true, the request should be canceled or proceeded using
563 // proceedSslError().
564 // Unlike the webview classic, we do not keep keep a database of certificates that
565 // are allowed by the user, because this functionality is already handled via
566 // ssl_policy in native layers.
568 private boolean allowCertificateError(int certError, byte[] derBytes, final String url,
570 final SslCertificate cert = SslUtil.getCertificateFromDerBytes(derBytes);
572 // if the certificate or the client is null, cancel the request
575 final SslError sslError = SslUtil.sslErrorFromNetErrorCode(certError, cert, url);
576 ValueCallback<Boolean> callback = new ValueCallback<Boolean>() {
578 public void onReceiveValue(Boolean value) {
579 proceedSslError(value.booleanValue(), id);
582 onReceivedSslError(callback, sslError);
586 private void proceedSslError(boolean proceed, int id) {
587 if (mNativeContentsClientBridge == 0) return;
588 nativeProceedSslError(mNativeContentsClientBridge, proceed, id);
592 private void handleJsAlert(String url, String message, int id) {
593 if (isOwnerActivityRunning()) {
594 XWalkJavascriptResultHandlerInternal result =
595 new XWalkJavascriptResultHandlerInternal(this, id);
596 mXWalkUIClient.onJavascriptModalDialog(mXWalkView,
597 XWalkUIClientInternal.JavascriptMessageTypeInternal.JAVASCRIPT_ALERT,
598 url, message, "", result);
603 private void handleJsConfirm(String url, String message, int id) {
604 if (isOwnerActivityRunning()) {
605 XWalkJavascriptResultHandlerInternal result =
606 new XWalkJavascriptResultHandlerInternal(this, id);
607 mXWalkUIClient.onJavascriptModalDialog(mXWalkView,
608 XWalkUIClientInternal.JavascriptMessageTypeInternal.JAVASCRIPT_CONFIRM,
609 url, message, "", result);
614 private void handleJsPrompt(String url, String message, String defaultValue, int id) {
615 if (isOwnerActivityRunning()) {
616 XWalkJavascriptResultHandlerInternal result =
617 new XWalkJavascriptResultHandlerInternal(this, id);
618 mXWalkUIClient.onJavascriptModalDialog(mXWalkView,
619 XWalkUIClientInternal.JavascriptMessageTypeInternal.JAVASCRIPT_PROMPT,
620 url, message, defaultValue, result);
625 private void handleJsBeforeUnload(String url, String message, int id) {
626 if (isOwnerActivityRunning()) {
627 XWalkJavascriptResultHandlerInternal result =
628 new XWalkJavascriptResultHandlerInternal(this, id);
629 mXWalkUIClient.onJavascriptModalDialog(mXWalkView,
630 XWalkUIClientInternal.JavascriptMessageTypeInternal.JAVASCRIPT_BEFOREUNLOAD,
631 url, message, "", result);
636 private void updateNotificationIcon(int notificationId, Bitmap icon) {
637 mNotificationService.updateNotificationIcon(notificationId, icon);
641 private void showNotification(String title, String message, String replaceId,
642 int notificationId) {
643 mNotificationService.showNotification(
644 title, message, replaceId, notificationId);
648 private void cancelNotification(int notificationId) {
649 mNotificationService.cancelNotification(notificationId);
652 void confirmJsResult(int id, String prompt) {
653 if (mNativeContentsClientBridge == 0) return;
654 nativeConfirmJsResult(mNativeContentsClientBridge, id, prompt);
657 void cancelJsResult(int id) {
658 if (mNativeContentsClientBridge == 0) return;
659 nativeCancelJsResult(mNativeContentsClientBridge, id);
662 void exitFullscreen(long nativeWebContents) {
663 if (mNativeContentsClientBridge == 0) return;
664 nativeExitFullscreen(mNativeContentsClientBridge, nativeWebContents);
667 public void notificationDisplayed(int id) {
668 if (mNativeContentsClientBridge == 0) return;
669 nativeNotificationDisplayed(mNativeContentsClientBridge, id);
672 public void notificationError(int id) {
673 if (mNativeContentsClientBridge == 0) return;
674 nativeNotificationError(mNativeContentsClientBridge, id);
677 public void notificationClicked(int id) {
678 if (mNativeContentsClientBridge == 0) return;
679 nativeNotificationClicked(mNativeContentsClientBridge, id);
682 public void notificationClosed(int id, boolean byUser) {
683 if (mNativeContentsClientBridge == 0) return;
684 nativeNotificationClosed(mNativeContentsClientBridge, id, byUser);
687 void setDownloadListener(DownloadListener listener) {
688 mDownloadListener = listener;
691 // Implement ContentViewDownloadDelegate methods.
692 public void requestHttpGetDownload(DownloadInfo downloadInfo) {
693 if (mDownloadListener != null) {
694 mDownloadListener.onDownloadStart(downloadInfo.getUrl(), downloadInfo.getUserAgent(),
695 downloadInfo.getContentDisposition(), downloadInfo.getMimeType(), downloadInfo.getContentLength());
699 public void onDownloadStarted(String filename, String mimeType) {
702 public void onDangerousDownload(String filename, int downloadId) {
706 public void onWebLayoutPageScaleFactorChanged(float pageScaleFactor) {
707 if (mPageScaleFactor == pageScaleFactor) return;
709 float oldPageScaleFactor = mPageScaleFactor;
710 mPageScaleFactor = pageScaleFactor;
711 onScaleChanged(oldPageScaleFactor, mPageScaleFactor);
714 //--------------------------------------------------------------------------------------------
716 //--------------------------------------------------------------------------------------------
717 private native void nativeProceedSslError(long nativeXWalkContentsClientBridge,
718 boolean proceed, int id);
720 private native void nativeConfirmJsResult(long nativeXWalkContentsClientBridge, int id,
722 private native void nativeCancelJsResult(long nativeXWalkContentsClientBridge, int id);
723 private native void nativeExitFullscreen(long nativeXWalkContentsClientBridge, long nativeWebContents);
724 private native void nativeNotificationDisplayed(long nativeXWalkContentsClientBridge, int id);
725 private native void nativeNotificationError(long nativeXWalkContentsClientBridge, int id);
726 private native void nativeNotificationClicked(long nativeXWalkContentsClientBridge, int id);
727 private native void nativeNotificationClosed(long nativeXWalkContentsClientBridge, int id, boolean byUser);
728 private native void nativeOnFilesSelected(long nativeXWalkContentsClientBridge,
729 int processId, int renderId, int mode_flags, String filepath, String displayName);
730 private native void nativeOnFilesNotSelected(long nativeXWalkContentsClientBridge,
731 int processId, int renderId, int mode_flags);