diff options
author | Tim Volodine <timvolodine@google.com> | 2019-02-15 12:42:13 -0800 |
---|---|---|
committer | android-build-merger <android-build-merger@google.com> | 2019-02-15 12:42:13 -0800 |
commit | 22b6f0db9912ab9fbb0168e1526f690411c71059 (patch) | |
tree | c7c1a85cd733316721c157b18e7cc854bc22e022 | |
parent | f80442a6a3358918f0ce0d9d619f707ebdc0d84b (diff) | |
parent | 1bcf52c56633007056862292df247f9a8b20dcd5 (diff) | |
download | Browser2-22b6f0db9912ab9fbb0168e1526f690411c71059.tar.gz |
[Browser2] Sync from chromium code, including increase target_sdk version to 28 (P) and enable cleartext traffic am: aa6c899803
am: 1bcf52c566
Change-Id: I4804ff2996c55d470a3355ecf51e8d8697ee748f
-rw-r--r-- | AndroidManifest.xml | 61 | ||||
-rw-r--r-- | res/drawable-mdpi/breadcrumb_arrow_black.png | bin | 0 -> 295 bytes | |||
-rw-r--r-- | res/drawable-mdpi/ic_launcher.png | bin | 0 -> 9397 bytes | |||
-rw-r--r-- | res/drawable-mdpi/item_more_black.png | bin | 0 -> 325 bytes | |||
-rw-r--r-- | res/layout/activity_empty.xml | 12 | ||||
-rw-r--r-- | res/layout/activity_webview_animation_test.xml | 65 | ||||
-rw-r--r-- | res/layout/activity_webview_browser.xml | 14 | ||||
-rw-r--r-- | res/menu/main_menu.xml | 9 | ||||
-rw-r--r-- | res/values/strings.xml | 15 | ||||
-rw-r--r-- | res/xml/network_security_config.xml | 17 | ||||
-rw-r--r-- | src/org/chromium/webview_shell/JankActivity.java | 1 | ||||
-rw-r--r-- | src/org/chromium/webview_shell/TelemetryActivity.java | 51 | ||||
-rw-r--r-- | src/org/chromium/webview_shell/TelemetryMemoryPressureActivity.java | 9 | ||||
-rw-r--r-- | src/org/chromium/webview_shell/WebViewAnimationTestActivity.java | 157 | ||||
-rw-r--r-- | src/org/chromium/webview_shell/WebViewBrowserActivity.java | 295 |
15 files changed, 639 insertions, 67 deletions
diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 95b33ed..4376adc 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -10,7 +10,7 @@ android:versionCode="1" android:versionName="1.0" > - <uses-sdk android:minSdkVersion="19" android:targetSdkVersion="24" /> + <uses-sdk android:minSdkVersion="19" android:targetSdkVersion="28" /> <!-- "Normal" permissions which do not require user prompt --> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> @@ -31,35 +31,47 @@ <application android:icon="@drawable/ic_launcher" android:label="@string/app_name" - android:theme="@android:style/Theme.Light" > + android:theme="@android:style/Theme.Light" + android:networkSecurityConfig="@xml/network_security_config" + android:debuggable="true" > + <meta-data android:name="android.webkit.WebView.EnableSafeBrowsing" + android:value="true" /> <activity - android:name=".TelemetryActivity" + android:name="org.chromium.webview_shell.TelemetryActivity" + android:launchMode="singleTask" android:label="@string/title_activity_telemetry" android:exported="true"> </activity> <activity - android:name=".TelemetryMemoryPressureActivity" + android:name="org.chromium.webview_shell.TelemetryMemoryPressureActivity" android:launchMode="singleTask" android:label="@string/title_activity_telemetry" android:exported="true"> </activity> <activity - android:name=".JankActivity" + android:name="org.chromium.webview_shell.JankActivity" android:label="@string/title_activity_jank" android:noHistory="true" android:exported="true"> </activity> <activity - android:name=".StartupTimeActivity" + android:name="org.chromium.webview_shell.StartupTimeActivity" android:label="@string/title_activity_startup_time" android:noHistory="true" android:exported="true"> </activity> <activity - android:name=".WebViewBrowserActivity" + android:name="org.chromium.webview_shell.WebViewCreateDestroyActivity" + android:launchMode="singleTask" + android:label="@string/title_activity_create_destroy" + android:exported="true"> + </activity> + <activity + android:name="org.chromium.webview_shell.WebViewBrowserActivity" android:label="@string/title_activity_browser" android:exported="true" - android:windowSoftInputMode="adjustResize"> + android:windowSoftInputMode="adjustResize" + android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|density"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> @@ -85,5 +97,38 @@ <data android:mimeType="application/vnd.wap.xhtml+xml"/> <!-- XHTML MP --> </intent-filter> </activity> + <activity + android:name="org.chromium.webview_shell.WebViewLayoutTestActivity" + android:label="@string/title_activity_layout_test" + android:exported="true"> + </activity> + <activity + android:name="org.chromium.webview_shell.WebViewThreadTestActivity" + android:label="@string/title_activity_thread_test" + android:exported="true"> + </activity> + <activity android:name="org.chromium.test.broker.OnDeviceInstrumentationBroker" + android:exported="true"/> + + <activity + android:name="org.chromium.webview_shell.PageCyclerTestActivity" + android:label="@string/title_activity_page_cycler" + android:exported="true"> + </activity> + + <activity + android:name="org.chromium.webview_shell.WebViewTracingActivity" + android:label="@string/title_activity_telemetry" + android:noHistory="true" + android:exported="true"> + </activity> + + <activity + android:name="org.chromium.webview_shell.WebViewAnimationTestActivity" + android:noHistory="true" + android:exported="true"> + </activity> + + <uses-library android:name="android.test.runner" /> </application> </manifest> diff --git a/res/drawable-mdpi/breadcrumb_arrow_black.png b/res/drawable-mdpi/breadcrumb_arrow_black.png Binary files differnew file mode 100644 index 0000000..7b9ff79 --- /dev/null +++ b/res/drawable-mdpi/breadcrumb_arrow_black.png diff --git a/res/drawable-mdpi/ic_launcher.png b/res/drawable-mdpi/ic_launcher.png Binary files differnew file mode 100644 index 0000000..96a442e --- /dev/null +++ b/res/drawable-mdpi/ic_launcher.png diff --git a/res/drawable-mdpi/item_more_black.png b/res/drawable-mdpi/item_more_black.png Binary files differnew file mode 100644 index 0000000..b984062 --- /dev/null +++ b/res/drawable-mdpi/item_more_black.png diff --git a/res/layout/activity_empty.xml b/res/layout/activity_empty.xml new file mode 100644 index 0000000..868453d --- /dev/null +++ b/res/layout/activity_empty.xml @@ -0,0 +1,12 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright 2016 The Chromium Authors. All rights reserved. + Use of this source code is governed by a BSD-style license that can be + found in the LICENSE file. +--> + +<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:id="@+id/emptyview"> +</RelativeLayout>
\ No newline at end of file diff --git a/res/layout/activity_webview_animation_test.xml b/res/layout/activity_webview_animation_test.xml new file mode 100644 index 0000000..44cf7c0 --- /dev/null +++ b/res/layout/activity_webview_animation_test.xml @@ -0,0 +1,65 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright 2019 The Chromium Authors. All rights reserved. + Use of this source code is governed by a BSD-style license that can be + found in the LICENSE file. +--> + +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical"> + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content"> + <Button + android:id="@+id/translate" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/translate_button" /> + <Button + android:id="@+id/scale" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/scale_button" /> + <Button + android:id="@+id/rotate" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/rotate_button" /> + <CheckBox + android:id="@+id/use_layer" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:checked="false" + android:text="@string/layer_button" /> + </LinearLayout> + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content"> + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/alpha_button" + android:layout_gravity="center_vertical" /> + <SeekBar + android:id="@+id/view_alpha" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_weight="1" + android:max="100" + android:progress="100" + android:layout_marginStart="4dp" + android:layout_marginEnd="8dp" /> + </LinearLayout> + <FrameLayout + android:layout_width="match_parent" + android:layout_height="0dip" + android:layout_weight="1"> + <WebView + android:id="@+id/webview" + android:layout_width="300dp" + android:layout_height="300dp" + android:layout_gravity="center" /> + </FrameLayout> +</LinearLayout> diff --git a/res/layout/activity_webview_browser.xml b/res/layout/activity_webview_browser.xml index 07483c5..80464d7 100644 --- a/res/layout/activity_webview_browser.xml +++ b/res/layout/activity_webview_browser.xml @@ -13,7 +13,7 @@ android:gravity="center"> <LinearLayout android:orientation="horizontal" - android:layout_width="fill_parent" + android:layout_width="match_parent" android:layout_height="wrap_content"> <EditText android:id="@+id/url_field" @@ -22,16 +22,18 @@ android:layout_weight="1.0" android:singleLine="true" android:inputType="textUri" - android:imeOptions="actionGo" /> + android:selectAllOnFocus="true" + android:imeOptions="actionGo" + android:importantForAutofill="no" /> <ImageButton - android:layout_width="wrap_content" - android:layout_height="match_parent" + android:layout_width="wrap_content" + android:layout_height="match_parent" android:src="@drawable/breadcrumb_arrow_black" android:contentDescription="@string/load_url" android:onClick="loadUrlFromUrlBar" /> <ImageButton - android:layout_width="wrap_content" - android:layout_height="match_parent" + android:layout_width="wrap_content" + android:layout_height="match_parent" android:src="@drawable/item_more_black" android:contentDescription="@string/menu_about" android:onClick="showPopup" /> diff --git a/res/menu/main_menu.xml b/res/menu/main_menu.xml index a22610d..14c90c2 100644 --- a/res/menu/main_menu.xml +++ b/res/menu/main_menu.xml @@ -6,6 +6,15 @@ <menu xmlns:android="http://schemas.android.com/apk/res/android"> <item android:id="@+id/menu_reset_webview" android:title="@string/menu_reset_webview"/> + <item android:id="@+id/menu_clear_cache" + android:title="@string/menu_clear_cache"/> + <item android:id="@+id/menu_enable_tracing" + android:checkable="true" + android:title="@string/menu_enable_tracing"/> + <item android:id="@+id/menu_print" + android:title="@string/menu_print"/> + <item android:id="@+id/start_animation_activity" + android:title="@string/menu_start_animation_activity"/> <item android:id="@+id/menu_about" android:title="@string/menu_about"/> </menu> diff --git a/res/values/strings.xml b/res/values/strings.xml index 1108c76..c39328a 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -10,7 +10,22 @@ <string name="title_activity_jank">WebView Jank Tester</string> <string name="title_activity_startup_time">WebView Startup Time Tester</string> <string name="title_activity_browser">WebView Browser Tester</string> + <string name="title_activity_layout_test">WebView Layout Test</string> + <string name="title_activity_thread_test">WebView Thread Test</string> + <string name="title_activity_page_cycler">WebView Page Cycler Test</string> + <string name="title_activity_create_destroy">WebView Create Destroy</string> <string name="menu_reset_webview">Destroy and create new WebView</string> + <string name="menu_clear_cache">Clear cache</string> + <string name="menu_enable_tracing">Enable tracing</string> + <string name="menu_start_animation_activity">Animation test</string> + <string name="menu_print">Print</string> <string name="menu_about">About WebView</string> <string name="load_url">Load URL</string> + + <!-- activity_webview_animation_test strings --> + <string name="alpha_button">View Alpha</string> + <string name="layer_button">Layer</string> + <string name="rotate_button">Rotate</string> + <string name="scale_button">Scale</string> + <string name="translate_button">Translate</string> </resources> diff --git a/res/xml/network_security_config.xml b/res/xml/network_security_config.xml new file mode 100644 index 0000000..4669e3b --- /dev/null +++ b/res/xml/network_security_config.xml @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright 2018 The Chromium Authors. All rights reserved. + Use of this source code is governed by a BSD-style license that can be + found in the LICENSE file. --> + +<network-security-config> + <!-- Starting with Android P (API level 28), the default value of + isCleartextTrafficPermitted() is false. For the SystemWebViewShell + test browser we explicitly set cleartextTrafficPermitted here to + preserve functionality. (crbug.com/898190) --> + <base-config cleartextTrafficPermitted="true"> + <trust-anchors> + <certificates src="user"/> + <certificates src="system"/> + </trust-anchors> + </base-config> +</network-security-config>
\ No newline at end of file diff --git a/src/org/chromium/webview_shell/JankActivity.java b/src/org/chromium/webview_shell/JankActivity.java index 13390b7..a508e44 100644 --- a/src/org/chromium/webview_shell/JankActivity.java +++ b/src/org/chromium/webview_shell/JankActivity.java @@ -28,6 +28,7 @@ public class JankActivity extends Activity { CookieManager.setAcceptFileSchemeCookies(true); webView.setWebViewClient(new WebViewClient() { + @SuppressWarnings("deprecation") // because we support api level 19 and up. @Override public boolean shouldOverrideUrlLoading(WebView webView, String url) { return false; diff --git a/src/org/chromium/webview_shell/TelemetryActivity.java b/src/org/chromium/webview_shell/TelemetryActivity.java index 11fa10b..a29e525 100644 --- a/src/org/chromium/webview_shell/TelemetryActivity.java +++ b/src/org/chromium/webview_shell/TelemetryActivity.java @@ -1,12 +1,14 @@ // Copyright 2015 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. - package org.chromium.webview_shell; import android.app.Activity; +import android.content.Intent; import android.os.Bundle; +import android.os.Trace; import android.webkit.CookieManager; +import android.webkit.WebSettings; import android.webkit.WebView; import android.webkit.WebViewClient; @@ -14,25 +16,58 @@ import android.webkit.WebViewClient; * This activity is designed for Telemetry testing of WebView. */ public class TelemetryActivity extends Activity { + static final String DEFAULT_START_UP_TRACE_TAG = "WebViewStartupInterval"; + static final String DEFAULT_LOAD_URL_TRACE_TAG = "WebViewBlankUrlLoadInterval"; + static final String DEFAULT_START_UP_AND_LOAD_URL_TRACE_TAG = + "WebViewStartupAndLoadBlankUrlInterval"; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); getWindow().setTitle( getResources().getString(R.string.title_activity_telemetry)); - setContentView(R.layout.activity_webview); - WebView webView = (WebView) findViewById(R.id.webview); + Intent intent = getIntent(); + final String startUpTraceTag = intent.getStringExtra("WebViewStartUpTraceTag"); + final String loadUrlTraceTag = intent.getStringExtra("WebViewLoadUrlTraceTag"); + final String startUpAndLoadUrlTraceTag = + intent.getStringExtra("WebViewStartUpAndLoadUrlTraceTag"); + + Trace.beginSection(startUpTraceTag == null ? DEFAULT_START_UP_AND_LOAD_URL_TRACE_TAG + : startUpAndLoadUrlTraceTag); + Trace.beginSection(startUpTraceTag == null ? DEFAULT_START_UP_TRACE_TAG : startUpTraceTag); + WebView webView = new WebView(this); + setContentView(webView); + Trace.endSection(); + CookieManager.setAcceptFileSchemeCookies(true); - webView.getSettings().setJavaScriptEnabled(true); + WebSettings settings = webView.getSettings(); + settings.setJavaScriptEnabled(true); + settings.setUseWideViewPort(true); + settings.setLoadWithOverviewMode(true); + settings.setDomStorageEnabled(true); + settings.setMediaPlaybackRequiresUserGesture(false); + String userAgentString = intent.getStringExtra("userAgent"); + if (userAgentString != null) { + settings.setUserAgentString(userAgentString); + } webView.setWebViewClient(new WebViewClient() { - @Override - public boolean shouldOverrideUrlLoading(WebView webView, String url) { - return false; - } + @SuppressWarnings("deprecation") // because we support api level 19 and up. + @Override + public boolean shouldOverrideUrlLoading(WebView view, String url) { + return false; + } + + @Override + public void onPageFinished(WebView view, String url) { + super.onPageFinished(view, url); + Trace.endSection(); + Trace.endSection(); + } }); + Trace.beginSection(loadUrlTraceTag == null ? DEFAULT_LOAD_URL_TRACE_TAG : loadUrlTraceTag); webView.loadUrl("about:blank"); } } diff --git a/src/org/chromium/webview_shell/TelemetryMemoryPressureActivity.java b/src/org/chromium/webview_shell/TelemetryMemoryPressureActivity.java index d9e6dfc..a6a9de8 100644 --- a/src/org/chromium/webview_shell/TelemetryMemoryPressureActivity.java +++ b/src/org/chromium/webview_shell/TelemetryMemoryPressureActivity.java @@ -30,10 +30,11 @@ public class TelemetryMemoryPressureActivity extends Activity { webview.getSettings().setJavaScriptEnabled(true); webview.setWebViewClient(new WebViewClient() { - @Override - public boolean shouldOverrideUrlLoading(WebView webView, String url) { - return false; - } + @SuppressWarnings("deprecation") // because we support api level 19 and up. + @Override + public boolean shouldOverrideUrlLoading(WebView webView, String url) { + return false; + } }); webview.loadUrl("about:blank"); diff --git a/src/org/chromium/webview_shell/WebViewAnimationTestActivity.java b/src/org/chromium/webview_shell/WebViewAnimationTestActivity.java new file mode 100644 index 0000000..e67b6e5 --- /dev/null +++ b/src/org/chromium/webview_shell/WebViewAnimationTestActivity.java @@ -0,0 +1,157 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.webview_shell; + +import android.app.Activity; +import android.os.Bundle; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.WindowManager; +import android.webkit.WebView; +import android.widget.CheckBox; +import android.widget.CompoundButton; +import android.widget.SeekBar; +import android.widget.SeekBar.OnSeekBarChangeListener; + +/** + * Activity to exercise transform animations on WebView. + */ +public class WebViewAnimationTestActivity extends Activity { + private static final String HTML = "<html>" + + " <head>" + + " <style type =\"text/css\">" + + " .container {" + + " display: grid;" + + " grid-template-columns: 100px 100px 100px 100px 100px;" + + " grid-template-rows: 100px 100px 100px 100px 100px;" + + " }" + + " .alt1 {" + + " background-color: #aaffaa;" + + " }" + + " .alt2 {" + + " background-color: #ff4545;" + + " }" + + " </style>" + + " </head>" + + " <body>" + + " <div class=\"container\">" + + " <div class=\"alt1\">00</div>" + + " <div class=\"alt2\">01</div>" + + " <div class=\"alt1\">02</div>" + + " <div class=\"alt2\">03</div>" + + " <div class=\"alt1\">04</div>" + + " <div class=\"alt2\">05</div>" + + " <div class=\"alt1\">06</div>" + + " <div class=\"alt2\">07</div>" + + " <div class=\"alt1\">08</div>" + + " <div class=\"alt2\">09</div>" + + " <div class=\"alt1\">10</div>" + + " <div class=\"alt2\">11</div>" + + " <div class=\"alt1\">12</div>" + + " <div class=\"alt2\">13</div>" + + " <div class=\"alt1\">14</div>" + + " <div class=\"alt2\">15</div>" + + " <div class=\"alt1\">16</div>" + + " <div class=\"alt2\">17</div>" + + " <div class=\"alt1\">18</div>" + + " <div class=\"alt2\">19</div>" + + " <div class=\"alt1\">20</div>" + + " <div class=\"alt2\">21</div>" + + " <div class=\"alt1\">22</div>" + + " <div class=\"alt2\">23</div>" + + " <div class=\"alt1\">24</div>" + + " </div>" + + " </body>" + + "</html>"; + + private WebView mWebView; + private boolean mIsWindowHardwareAccelerated; + + /** Called when the activity is first created. */ + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_webview_animation_test); + mWebView = (WebView) findViewById(R.id.webview); + + mIsWindowHardwareAccelerated = + (getWindow().getAttributes().flags + | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED) + != 0; + mWebView.setBackgroundColor(0); + mWebView.loadDataWithBaseURL("http://foo.bar", HTML, "text/html", null, "http://foo.bar"); + OnClickListener onClickListner = (View v) -> { + switch (v.getId()) { + case R.id.translate: + runTranslate(); + break; + case R.id.scale: + runScale(); + break; + case R.id.rotate: + runRotate(); + break; + } + }; + findViewById(R.id.scale).setOnClickListener(onClickListner); + findViewById(R.id.translate).setOnClickListener(onClickListner); + findViewById(R.id.rotate).setOnClickListener(onClickListner); + ((SeekBar) findViewById(R.id.view_alpha)) + .setOnSeekBarChangeListener(new OnSeekBarChangeListener() { + @Override + public void onProgressChanged(SeekBar view, int progress, boolean fromUser) { + switch (view.getId()) { + case R.id.view_alpha: + mWebView.setAlpha(progress / 100f); + break; + } + } + + @Override + public void onStartTrackingTouch(SeekBar seekBar) {} + + @Override + public void onStopTrackingTouch(SeekBar seekBar) {} + }); + CheckBox checkBox = ((CheckBox) findViewById(R.id.use_layer)); + checkBox.setOnCheckedChangeListener( + (CompoundButton arg0, boolean checked) -> { setWebViewLayer(checked); }); + setWebViewLayer(checkBox.isChecked()); + } + + private void runTranslate() { + if (mWebView.getTranslationX() == 0f) { + mWebView.animate().translationX(100f).translationY(100f); + } else { + mWebView.animate().translationX(0f).translationY(0f); + } + } + + private void runScale() { + if (mWebView.getScaleX() == 1f) { + mWebView.animate().scaleX(.5f).scaleY(.5f); + } else { + mWebView.animate().scaleX(1f).scaleY(1f); + } + } + + private void runRotate() { + if (mWebView.getRotationX() == 0f) { + mWebView.animate().rotationX(45f).rotationY(45f).rotation(90f); + } else { + mWebView.animate().rotationX(0f).rotationY(0f).rotation(0f); + } + } + + private void setWebViewLayer(boolean isOnLayer) { + if (isOnLayer) { + mWebView.setLayerType(mIsWindowHardwareAccelerated ? View.LAYER_TYPE_HARDWARE + : View.LAYER_TYPE_SOFTWARE, + null); + } else { + mWebView.setLayerType(View.LAYER_TYPE_NONE, null); + } + } +} diff --git a/src/org/chromium/webview_shell/WebViewBrowserActivity.java b/src/org/chromium/webview_shell/WebViewBrowserActivity.java index 1da563e..0a7b637 100644 --- a/src/org/chromium/webview_shell/WebViewBrowserActivity.java +++ b/src/org/chromium/webview_shell/WebViewBrowserActivity.java @@ -5,6 +5,8 @@ package org.chromium.webview_shell; import android.Manifest; +import android.annotation.SuppressLint; +import android.annotation.TargetApi; import android.app.Activity; import android.app.AlertDialog; import android.content.ActivityNotFoundException; @@ -18,39 +20,47 @@ import android.graphics.Color; import android.net.Uri; import android.os.Build; import android.os.Bundle; +import android.os.StrictMode; +import android.print.PrintAttributes; +import android.print.PrintDocumentAdapter; +import android.print.PrintManager; import android.provider.Browser; import android.util.Log; import android.util.SparseArray; - +import android.view.Gravity; import android.view.KeyEvent; import android.view.MenuItem; import android.view.View; import android.view.View.OnKeyListener; import android.view.ViewGroup; import android.view.ViewGroup.LayoutParams; +import android.view.WindowManager; import android.view.inputmethod.InputMethodManager; - import android.webkit.GeolocationPermissions; import android.webkit.PermissionRequest; +import android.webkit.TracingConfig; +import android.webkit.TracingController; import android.webkit.WebChromeClient; -import android.webkit.WebResourceRequest; import android.webkit.WebSettings; import android.webkit.WebView; import android.webkit.WebViewClient; - import android.widget.EditText; +import android.widget.FrameLayout; import android.widget.PopupMenu; import android.widget.TextView; +import android.widget.Toast; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; - -import java.net.URI; -import java.net.URISyntaxException; - import java.util.ArrayList; import java.util.HashMap; import java.util.List; +import java.util.Locale; +import java.util.concurrent.Executors; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -69,6 +79,12 @@ public class WebViewBrowserActivity extends Activity implements PopupMenu.OnMenu // WebKit permissions with no corresponding Android permission can always be granted private static final String NO_ANDROID_PERMISSION = "NO_ANDROID_PERMISSION"; + // TODO(timav): Remove these variables after http://crbug.com/626202 is fixed. + // The Bundle key for WebView serialized state + private static final String SAVE_RESTORE_STATE_KEY = "WEBVIEW_CHROMIUM_STATE"; + // Maximal size of this state. + private static final int MAX_STATE_LENGTH = 300 * 1024; + // Map from WebKit permissions to Android permissions private static final HashMap<String, String> sPermissions; static { @@ -88,15 +104,18 @@ public class WebViewBrowserActivity extends Activity implements PopupMenu.OnMenu private EditText mUrlBar; private WebView mWebView; + private View mFullscreenView; private String mWebViewVersion; + private boolean mEnableTracing; // Each time we make a request, store it here with an int key. onRequestPermissionsResult will // look up the request in order to grant the approprate permissions. private SparseArray<PermissionRequest> mPendingRequests = new SparseArray<PermissionRequest>(); - private int mNextRequestKey = 0; + private int mNextRequestKey; // Work around our wonky API by wrapping a geo permission prompt inside a regular // PermissionRequest. + @SuppressLint("NewApi") // GeoPermissionRequest class requires API level 21. private static class GeoPermissionRequest extends PermissionRequest { private String mOrigin; private GeolocationPermissions.Callback mCallback; @@ -106,20 +125,24 @@ public class WebViewBrowserActivity extends Activity implements PopupMenu.OnMenu mCallback = callback; } + @Override public Uri getOrigin() { return Uri.parse(mOrigin); } + @Override public String[] getResources() { return new String[] { WebViewBrowserActivity.RESOURCE_GEO }; } + @Override public void grant(String[] resources) { assert resources.length == 1; assert WebViewBrowserActivity.RESOURCE_GEO.equals(resources[0]); mCallback.invoke(mOrigin, true, false); } + @Override public void deny() { mCallback.invoke(mOrigin, false, false); } @@ -127,6 +150,7 @@ public class WebViewBrowserActivity extends Activity implements PopupMenu.OnMenu // For simplicity, also treat the read access needed for file:// URLs as a regular // PermissionRequest. + @SuppressLint("NewApi") // FilePermissionRequest class requires API level 21. private class FilePermissionRequest extends PermissionRequest { private String mOrigin; @@ -134,14 +158,17 @@ public class WebViewBrowserActivity extends Activity implements PopupMenu.OnMenu mOrigin = origin; } + @Override public Uri getOrigin() { return Uri.parse(mOrigin); } + @Override public String[] getResources() { return new String[] { WebViewBrowserActivity.RESOURCE_FILE_URL }; } + @Override public void grant(String[] resources) { assert resources.length == 1; assert WebViewBrowserActivity.RESOURCE_FILE_URL.equals(resources[0]); @@ -149,20 +176,62 @@ public class WebViewBrowserActivity extends Activity implements PopupMenu.OnMenu WebViewBrowserActivity.this.mWebView.loadUrl(mOrigin); } + @Override public void deny() { // womp womp } } + private static class TracingLogger extends FileOutputStream { + private long mByteCount; + private long mChunkCount; + private final Activity mActivity; + + public TracingLogger(String fileName, Activity activity) throws FileNotFoundException { + super(fileName); + mActivity = activity; + } + + @Override + public void write(byte[] chunk) throws IOException { + mByteCount += chunk.length; + mChunkCount++; + super.write(chunk); + } + + @Override + public void close() throws IOException { + super.close(); + showDialog(mByteCount); + } + + private void showDialog(long nbBytes) { + StringBuilder info = new StringBuilder(); + info.append("Tracing data written to file\n"); + info.append("number of bytes: " + nbBytes); + + mActivity.runOnUiThread(new Runnable() { + @Override + public void run() { + AlertDialog dialog = new AlertDialog.Builder(mActivity) + .setTitle("Tracing API") + .setMessage(info) + .setNeutralButton(" OK ", null) + .create(); + dialog.show(); + } + }); + } + } + @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { - WebView.setWebContentsDebuggingEnabled(true); - } + WebView.setWebContentsDebuggingEnabled(true); setContentView(R.layout.activity_webview_browser); mUrlBar = (EditText) findViewById(R.id.url_field); mUrlBar.setOnKeyListener(new OnKeyListener() { + @Override public boolean onKey(View view, int keyCode, KeyEvent event) { if (keyCode == KeyEvent.KEYCODE_ENTER && event.getAction() == KeyEvent.ACTION_UP) { loadUrlFromUrlBar(view); @@ -172,13 +241,75 @@ public class WebViewBrowserActivity extends Activity implements PopupMenu.OnMenu } }); + StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder() + .detectAll() + .penaltyLog() + .penaltyDeath() + .build()); + // Conspicuously omitted: detectCleartextNetwork() and detectFileUriExposure() to permit + // http:// and file:// origins. + StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder() + .detectActivityLeaks() + .detectLeakedClosableObjects() + .detectLeakedRegistrationObjects() + .detectLeakedSqlLiteObjects() + .penaltyLog() + .penaltyDeath() + .build()); + createAndInitializeWebView(); String url = getUrlFromIntent(getIntent()); - if (url != null) { - setUrlBarText(url); - setUrlFail(false); - loadUrlFromUrlBar(mUrlBar); + if (url == null) { + mWebView.restoreState(savedInstanceState); + url = mWebView.getUrl(); + if (url != null) { + // If we have restored state, and that state includes + // a loaded URL, we reload. This allows us to keep the + // scroll offset, and also doesn't add an additional + // navigation history entry. + setUrlBarText(url); + // The immediately previous loadUrlFromurlbar must + // have got as far as calling loadUrl, so there is no + // URI parsing error at this point. + setUrlFail(false); + hideKeyboard(mUrlBar); + mWebView.reload(); + mWebView.requestFocus(); + return; + } + // Make sure to load a blank page to make it immediately inspectable with + // chrome://inspect. + url = "about:blank"; + } + setUrlBarText(url); + setUrlFail(false); + loadUrlFromUrlBar(mUrlBar); + } + + @Override + public void onSaveInstanceState(Bundle savedInstanceState) { + // Deliberately don't catch TransactionTooLargeException here. + mWebView.saveState(savedInstanceState); + + // TODO(timav): Remove this hack after http://crbug.com/626202 is fixed. + // Drop the saved state of it is too long since Android N and above + // can't handle large states without a crash. + byte[] webViewState = savedInstanceState.getByteArray(SAVE_RESTORE_STATE_KEY); + if (webViewState != null && webViewState.length > MAX_STATE_LENGTH) { + savedInstanceState.remove(SAVE_RESTORE_STATE_KEY); + String message = String.format( + Locale.US, "Can't save state: %dkb is too long", webViewState.length / 1024); + Toast.makeText(this, message, Toast.LENGTH_SHORT).show(); + } + } + + @Override + public void onBackPressed() { + if (mWebView.canGoBack()) { + mWebView.goBack(); + } else { + super.onBackPressed(); } } @@ -210,18 +341,18 @@ public class WebViewBrowserActivity extends Activity implements PopupMenu.OnMenu setUrlBarText(url); } + @SuppressWarnings("deprecation") // because we support api level 19 and up. @Override - public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) { - String url = request.getUrl().toString(); + public boolean shouldOverrideUrlLoading(WebView webView, String url) { // "about:" and "chrome:" schemes are internal to Chromium; // don't want these to be dispatched to other apps. if (url.startsWith("about:") || url.startsWith("chrome:")) { return false; } - boolean allowLaunchingApps = request.hasGesture() || request.isRedirect(); - return startBrowsingIntent(WebViewBrowserActivity.this, url, allowLaunchingApps); + return startBrowsingIntent(WebViewBrowserActivity.this, url); } + @SuppressWarnings("deprecation") // because we support api level 19 and up. @Override public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) { @@ -239,6 +370,13 @@ public class WebViewBrowserActivity extends Activity implements PopupMenu.OnMenu @Override public void onGeolocationPermissionsShowPrompt(String origin, GeolocationPermissions.Callback callback) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { + // Pre Lollipop versions (< api level 21) do not have PermissionRequest, + // hence grant here immediately. + callback.invoke(origin, true, false); + return; + } + onPermissionRequest(new GeoPermissionRequest(origin, callback)); } @@ -246,6 +384,28 @@ public class WebViewBrowserActivity extends Activity implements PopupMenu.OnMenu public void onPermissionRequest(PermissionRequest request) { WebViewBrowserActivity.this.requestPermissionsForPage(request); } + + @Override + public void onShowCustomView(View view, WebChromeClient.CustomViewCallback callback) { + if (mFullscreenView != null) { + ((ViewGroup) mFullscreenView.getParent()).removeView(mFullscreenView); + } + mFullscreenView = view; + getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); + getWindow().addContentView(mFullscreenView, + new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT, Gravity.CENTER)); + } + + @Override + public void onHideCustomView() { + if (mFullscreenView == null) { + return; + } + getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); + ((ViewGroup) mFullscreenView.getParent()).removeView(mFullscreenView); + mFullscreenView = null; + } }); mWebView = webview; @@ -256,14 +416,16 @@ public class WebViewBrowserActivity extends Activity implements PopupMenu.OnMenu // WebKit permissions which can be granted because either they have no associated Android // permission or the associated Android permission has been granted + @TargetApi(Build.VERSION_CODES.M) private boolean canGrant(String webkitPermission) { String androidPermission = sPermissions.get(webkitPermission); - if (androidPermission == NO_ANDROID_PERMISSION) { + if (androidPermission.equals(NO_ANDROID_PERMISSION)) { return true; } return PackageManager.PERMISSION_GRANTED == checkSelfPermission(androidPermission); } + @SuppressLint("NewApi") // PermissionRequest#deny requires API level 21. private void requestPermissionsForPage(PermissionRequest request) { // Deny any unrecognized permissions. for (String webkitPermission : request.getResources()) { @@ -309,12 +471,14 @@ public class WebViewBrowserActivity extends Activity implements PopupMenu.OnMenu } @Override + @SuppressLint("NewApi") // PermissionRequest#deny requires API level 21. public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) { // Verify that we can now grant all the requested permissions. Note that although grant() // takes a list of permissions, grant() is actually all-or-nothing. If there are any // requested permissions not included in the granted permissions, all will be denied. PermissionRequest request = mPendingRequests.get(requestCode); + mPendingRequests.delete(requestCode); for (String webkitPermission : request.getResources()) { if (!canGrant(webkitPermission)) { request.deny(); @@ -322,21 +486,14 @@ public class WebViewBrowserActivity extends Activity implements PopupMenu.OnMenu } } request.grant(request.getResources()); - mPendingRequests.delete(requestCode); } public void loadUrlFromUrlBar(View view) { String url = mUrlBar.getText().toString(); - try { - URI uri = new URI(url); - url = (uri.getScheme() == null) ? "http://" + uri.toString() : uri.toString(); - } catch (URISyntaxException e) { - String message = "<html><body>URISyntaxException: " + e.getMessage() + "</body></html>"; - mWebView.loadData(message, "text/html", "UTF-8"); - setUrlFail(true); - return; - } - + // Parse with android.net.Uri instead of java.net.URI because Uri does no validation. Rather + // than failing in the browser, let WebView handle weird URLs. WebView will escape illegal + // characters and display error pages for bad URLs like "blah://example.com". + if (Uri.parse(url).getScheme() == null) url = "http://" + url; setUrlBarText(url); setUrlFail(false); loadUrl(url); @@ -347,10 +504,12 @@ public class WebViewBrowserActivity extends Activity implements PopupMenu.OnMenu PopupMenu popup = new PopupMenu(this, v); popup.setOnMenuItemClickListener(this); popup.inflate(R.menu.main_menu); + popup.getMenu().findItem(R.id.menu_enable_tracing).setChecked(mEnableTracing); popup.show(); } @Override + @SuppressLint("NewApi") // TracingController related methods require API level 28. public boolean onMenuItemClick(MenuItem item) { switch(item.getItemId()) { case R.id.menu_reset_webview: @@ -362,6 +521,42 @@ public class WebViewBrowserActivity extends Activity implements PopupMenu.OnMenu } createAndInitializeWebView(); return true; + case R.id.menu_clear_cache: + if (mWebView != null) { + mWebView.clearCache(true); + } + return true; + case R.id.menu_enable_tracing: + mEnableTracing = !mEnableTracing; + item.setChecked(mEnableTracing); + TracingController tracingController = TracingController.getInstance(); + if (mEnableTracing) { + tracingController.start( + new TracingConfig.Builder() + .addCategories(TracingConfig.CATEGORIES_WEB_DEVELOPER) + .setTracingMode(TracingConfig.RECORD_CONTINUOUSLY) + .build()); + } else { + StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskWrites(); + String outFileName = getFilesDir() + "/webview_tracing.json"; + try { + tracingController.stop(new TracingLogger(outFileName, this), + Executors.newSingleThreadExecutor()); + } catch (FileNotFoundException e) { + throw new RuntimeException(e); + } + StrictMode.setThreadPolicy(oldPolicy); + } + return true; + case R.id.start_animation_activity: + startActivity(new Intent(this, WebViewAnimationTestActivity.class)); + return true; + case R.id.menu_print: + PrintManager printManager = (PrintManager) getSystemService(Context.PRINT_SERVICE); + String jobName = "WebViewShell document"; + PrintDocumentAdapter printAdapter = mWebView.createPrintDocumentAdapter(jobName); + printManager.print(jobName, printAdapter, new PrintAttributes.Builder().build()); + return true; case R.id.menu_about: about(); hideKeyboard(mUrlBar); @@ -371,18 +566,33 @@ public class WebViewBrowserActivity extends Activity implements PopupMenu.OnMenu } } + // setGeolocationDatabasePath deprecated in api level 24, + // but we still use it because we support api level 19 and up. + @SuppressWarnings("deprecation") private void initializeSettings(WebSettings settings) { + File appcache = null; + File geolocation = null; + + StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskWrites(); + appcache = getDir("appcache", 0); + geolocation = getDir("geolocation", 0); + StrictMode.setThreadPolicy(oldPolicy); + settings.setJavaScriptEnabled(true); // configure local storage apis and their database paths. - settings.setAppCachePath(getDir("appcache", 0).getPath()); - settings.setGeolocationDatabasePath(getDir("geolocation", 0).getPath()); - settings.setDatabasePath(getDir("databases", 0).getPath()); + settings.setAppCachePath(appcache.getPath()); + settings.setGeolocationDatabasePath(geolocation.getPath()); settings.setAppCacheEnabled(true); settings.setGeolocationEnabled(true); settings.setDatabaseEnabled(true); settings.setDomStorageEnabled(true); + + // Default layout behavior for chrome on android. + settings.setUseWideViewPort(true); + settings.setLoadWithOverviewMode(true); + settings.setLayoutAlgorithm(WebSettings.LayoutAlgorithm.TEXT_AUTOSIZING); } private void about() { @@ -404,7 +614,7 @@ public class WebViewBrowserActivity extends Activity implements PopupMenu.OnMenu .setPositiveButton("OK", null) .create(); dialog.show(); - dialog.getWindow().setLayout(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT); + dialog.getWindow().setLayout(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); } // Returns true is a method has no arguments and returns either a boolean or a String. @@ -461,8 +671,7 @@ public class WebViewBrowserActivity extends Activity implements PopupMenu.OnMenu + ")" + "(.*)"); - private static boolean startBrowsingIntent(Context context, String url, - boolean allowLaunchingApps) { + private static boolean startBrowsingIntent(Context context, String url) { Intent intent; // Perform generic parsing of the URI to turn it into an Intent. try { @@ -492,12 +701,16 @@ public class WebViewBrowserActivity extends Activity implements PopupMenu.OnMenu // same application can be opened in the same tab. intent.putExtra(Browser.EXTRA_APPLICATION_ID, context.getPackageName()); try { - if (allowLaunchingApps) { - context.startActivity(intent); - } + context.startActivity(intent); return true; } catch (ActivityNotFoundException ex) { Log.w(TAG, "No application can handle " + url); + } catch (SecurityException ex) { + // This can happen if the Activity is exported="true", guarded by a permission, and sets + // up an intent filter matching this intent. This is a valid configuration for an + // Activity, so instead of crashing, we catch the exception and do nothing. See + // https://crbug.com/808494 and https://crbug.com/889300. + Log.w(TAG, "SecurityException when starting intent for " + url); } return false; } |