Files
cromite/build/patches/Add-an-always-incognito-mode.patch
csagan5 986d04a2c9 Updated timezone customization patch
Added patch for DIAL repeating discovery
2020-09-04 01:06:12 +02:00

422 lines
23 KiB
Diff

From: Ryan Archer <ryan.bradley.archer@gmail.com>
Date: Wed, 2 Aug 2017 01:41:28 -0400
Subject: Add an always-incognito mode
More specifically, add a preference that causes all new tabs and all
clicked links to launch as incognito.
Make sure initial incognito status is correctly recognized.
Enable incognito custom tabs and fix crashes for incognito/custom tab intents (credits to @uazo)
---
chrome/android/chrome_java_sources.gni | 1 +
.../java/res/xml/privacy_preferences.xml | 5 ++
.../AlwaysIncognitoLinkInterceptor.java | 74 +++++++++++++++++++
.../chrome/browser/ChromeActivity.java | 4 +
.../chrome/browser/ChromeTabbedActivity.java | 6 +-
.../AppMenuPropertiesDelegateImpl.java | 6 ++
.../ChromeContextMenuPopulator.java | 8 +-
.../CustomTabIntentDataProvider.java | 6 ++
.../browser/init/StartupTabPreloader.java | 11 ++-
.../browser/tabmodel/ChromeTabCreator.java | 16 +++-
.../tabmodel/TabModelSelectorBase.java | 8 ++
.../browser/tabmodel/TabPersistentStore.java | 9 +++
.../flags/android/chrome_feature_list.cc | 2 +-
.../strings/android_chrome_strings.grd | 7 ++
14 files changed, 155 insertions(+), 8 deletions(-)
create mode 100644 chrome/android/java/src/org/chromium/chrome/browser/AlwaysIncognitoLinkInterceptor.java
diff --git a/chrome/android/chrome_java_sources.gni b/chrome/android/chrome_java_sources.gni
--- a/chrome/android/chrome_java_sources.gni
+++ b/chrome/android/chrome_java_sources.gni
@@ -3,6 +3,7 @@
# found in the LICENSE file.
chrome_java_sources = [
+ "java/src/org/chromium/chrome/browser/AlwaysIncognitoLinkInterceptor.java",
"java/src/com/google/android/apps/chrome/appwidget/bookmarks/BookmarkThumbnailWidgetProvider.java",
"java/src/org/chromium/chrome/browser/ActivityTabProvider.java",
"java/src/org/chromium/chrome/browser/AfterStartupTaskUtils.java",
diff --git a/chrome/android/java/res/xml/privacy_preferences.xml b/chrome/android/java/res/xml/privacy_preferences.xml
--- a/chrome/android/java/res/xml/privacy_preferences.xml
+++ b/chrome/android/java/res/xml/privacy_preferences.xml
@@ -20,6 +20,11 @@
android:title="@string/preload_pages_title"
android:summary="@string/preload_pages_summary"
android:persistent="false" />
+ <org.chromium.components.browser_ui.settings.ChromeBaseCheckBoxPreference
+ android:key="always_incognito"
+ android:title="@string/always_incognito_title"
+ android:summary="@string/always_incognito_summary"
+ android:defaultValue="false" />
<Preference
android:fragment="org.chromium.chrome.browser.privacy.settings.DoNotTrackSettings"
android:key="do_not_track"
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/AlwaysIncognitoLinkInterceptor.java b/chrome/android/java/src/org/chromium/chrome/browser/AlwaysIncognitoLinkInterceptor.java
new file mode 100644
--- /dev/null
+++ b/chrome/android/java/src/org/chromium/chrome/browser/AlwaysIncognitoLinkInterceptor.java
@@ -0,0 +1,74 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package org.chromium.chrome.browser;
+
+import android.content.SharedPreferences;
+
+import org.chromium.chrome.browser.tab.EmptyTabObserver;
+import org.chromium.chrome.browser.tab.Tab;
+import org.chromium.chrome.browser.tab.TabImpl;
+import org.chromium.chrome.browser.tabmodel.TabCreatorManager;
+import org.chromium.chrome.browser.tab.TabLaunchType;
+import org.chromium.chrome.browser.tabmodel.TabModel;
+import org.chromium.content_public.browser.LoadUrlParams;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+/**
+ * A {@link TabObserver} that implements the always-incognito preference behavior for links. When the preference is set
+ * to true, it intercepts links opened within observed {@link Tab}s and opens them in new incognito <code>Tab</code>s instead.
+ */
+public class AlwaysIncognitoLinkInterceptor extends EmptyTabObserver {
+
+ public static final String PREF_ALWAYS_INCOGNITO = "always_incognito";
+
+ private final SharedPreferences alwaysIncognitoContainer;
+ private final Map<Tab, String> lastUrls;
+ private final Set<Tab> revertingTabs;
+
+ public AlwaysIncognitoLinkInterceptor(final SharedPreferences alwaysIncognitoContainer) {
+
+ assert alwaysIncognitoContainer != null;
+
+ this.alwaysIncognitoContainer = alwaysIncognitoContainer;
+ lastUrls = new HashMap<Tab, String>();
+ revertingTabs = new HashSet<Tab>();
+ }
+
+ @Override
+ public void onDestroyed(final Tab tab)
+ {
+ lastUrls.remove(tab);
+ revertingTabs.remove(tab);
+ }
+
+ @Override
+ public void onUpdateUrl(final Tab tab, final String url) {
+
+ if (tab == null) return;
+ if (url == null) return;
+ if (tab.isIncognito()) return;
+ if (alwaysIncognitoContainer == null) return;
+
+ final String lastUrl = lastUrls.put(tab, url);
+
+ if (!alwaysIncognitoContainer.getBoolean(PREF_ALWAYS_INCOGNITO, false)) return;
+ if (revertingTabs.contains(tab)) {
+ revertingTabs.remove(tab);
+ return;
+ }
+
+ ((ChromeTabbedActivity)tab.getWindowAndroid().getActivity().get()).getTabCreator(true).createNewTab(new LoadUrlParams(url), TabLaunchType.FROM_LINK, tab);
+
+ if ((url.equals(lastUrl)) || (!tab.canGoBack())) {
+ // this call was triggered by a reload
+ } else {
+ revertingTabs.add(tab);
+ tab.goBack();
+ }
+ }
+}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ChromeActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/ChromeActivity.java
--- a/chrome/android/java/src/org/chromium/chrome/browser/ChromeActivity.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ChromeActivity.java
@@ -51,6 +51,7 @@ import org.chromium.base.metrics.RecordUserAction;
import org.chromium.base.supplier.ObservableSupplier;
import org.chromium.base.supplier.ObservableSupplierImpl;
import org.chromium.chrome.R;
+import org.chromium.chrome.browser.AlwaysIncognitoLinkInterceptor;
import org.chromium.chrome.browser.IntentHandler.IntentHandlerDelegate;
import org.chromium.chrome.browser.IntentHandler.TabOpenType;
import org.chromium.chrome.browser.app.appmenu.AppMenuPropertiesDelegateImpl;
@@ -1509,6 +1510,9 @@ public abstract class ChromeActivity<C extends ChromeActivityComponent>
throw new IllegalStateException(
"Attempting to access TabCreator before initialization");
}
+ if (ContextUtils.getAppSharedPreferences().getBoolean(AlwaysIncognitoLinkInterceptor.PREF_ALWAYS_INCOGNITO, false)) {
+ return mIncognitoTabCreator;
+ }
return incognito ? mIncognitoTabCreator : mRegularTabCreator;
}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java
--- a/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java
@@ -51,6 +51,7 @@ import org.chromium.base.supplier.OneShotCallback;
import org.chromium.base.supplier.Supplier;
import org.chromium.base.task.PostTask;
import org.chromium.chrome.R;
+import org.chromium.chrome.browser.AlwaysIncognitoLinkInterceptor;
import org.chromium.chrome.browser.IntentHandler.IntentHandlerDelegate;
import org.chromium.chrome.browser.IntentHandler.TabOpenType;
import org.chromium.chrome.browser.accessibility_tab_switcher.OverviewListLayout;
@@ -1664,8 +1665,9 @@ public class ChromeTabbedActivity extends ChromeActivity<ChromeActivityComponent
Bundle savedInstanceState = getSavedInstanceState();
// We determine the model as soon as possible so every systems get initialized coherently.
- boolean startIncognito = savedInstanceState != null
- && savedInstanceState.getBoolean("is_incognito_selected", false);
+ boolean startIncognito = ContextUtils.getAppSharedPreferences().getBoolean(AlwaysIncognitoLinkInterceptor.PREF_ALWAYS_INCOGNITO, false)
+ || (savedInstanceState != null
+ && savedInstanceState.getBoolean("is_incognito_selected", false));
int index = savedInstanceState != null ? savedInstanceState.getInt(WINDOW_INDEX, 0) : 0;
NextTabPolicySupplier nextTabPolicySupplier =
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/app/appmenu/AppMenuPropertiesDelegateImpl.java b/chrome/android/java/src/org/chromium/chrome/browser/app/appmenu/AppMenuPropertiesDelegateImpl.java
--- a/chrome/android/java/src/org/chromium/chrome/browser/app/appmenu/AppMenuPropertiesDelegateImpl.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/app/appmenu/AppMenuPropertiesDelegateImpl.java
@@ -296,6 +296,12 @@ public class AppMenuPropertiesDelegateImpl implements AppMenuPropertiesDelegate
}
private void prepareCommonMenuItems(Menu menu, @MenuGroup int menuGroup, boolean isIncognito) {
+ if (ContextUtils.getAppSharedPreferences().getBoolean("always_incognito", false)) {
+ final MenuItem newTabOption = menu.findItem(R.id.new_tab_menu_id);
+ if (newTabOption != null)
+ newTabOption.setVisible(false);
+ }
+
// We have to iterate all menu items since same menu item ID may be associated with more
// than one menu items.
boolean isMenuGroupTabsVisible = TabUiFeatureUtilities.isTabGroupsAndroidEnabled()
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/contextmenu/ChromeContextMenuPopulator.java b/chrome/android/java/src/org/chromium/chrome/browser/contextmenu/ChromeContextMenuPopulator.java
--- a/chrome/android/java/src/org/chromium/chrome/browser/contextmenu/ChromeContextMenuPopulator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/contextmenu/ChromeContextMenuPopulator.java
@@ -27,7 +27,9 @@ import org.chromium.base.annotations.CalledByNative;
import org.chromium.base.annotations.NativeMethods;
import org.chromium.base.metrics.RecordHistogram;
import org.chromium.base.supplier.Supplier;
+import org.chromium.base.ContextUtils;
import org.chromium.chrome.R;
+import org.chromium.chrome.browser.AlwaysIncognitoLinkInterceptor;
import org.chromium.chrome.browser.compositor.bottombar.ephemeraltab.EphemeralTabCoordinator;
import org.chromium.chrome.browser.contextmenu.ChromeContextMenuItem.Item;
import org.chromium.chrome.browser.externalauth.ExternalAuthUtils;
@@ -399,7 +401,11 @@ public class ChromeContextMenuPopulator implements ContextMenuPopulator {
if (FirstRunStatus.getFirstRunFlowComplete() && !isEmptyUrl(params.getUrl())
&& UrlUtilities.isAcceptedScheme(params.getUrl())) {
if (mMode == ContextMenuMode.NORMAL) {
- linkTab.add(new ChromeContextMenuItem(Item.OPEN_IN_NEW_TAB));
+ if (ContextUtils.getAppSharedPreferences().getBoolean(AlwaysIncognitoLinkInterceptor.PREF_ALWAYS_INCOGNITO, false)
+ && !mDelegate.isIncognito()) {
+ // disallow open in new tab
+ } else
+ linkTab.add(new ChromeContextMenuItem(Item.OPEN_IN_NEW_TAB));
if (!mDelegate.isIncognito() && mDelegate.isIncognitoSupported()) {
linkTab.add(new ChromeContextMenuItem(Item.OPEN_IN_INCOGNITO_TAB));
}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabIntentDataProvider.java b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabIntentDataProvider.java
--- a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabIntentDataProvider.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabIntentDataProvider.java
@@ -50,6 +50,9 @@ import org.chromium.components.browser_ui.widget.TintedDrawable;
import org.chromium.components.embedder_support.util.UrlConstants;
import org.chromium.ui.util.ColorUtils;
+import org.chromium.base.ContextUtils;
+import org.chromium.chrome.browser.AlwaysIncognitoLinkInterceptor;
+
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
@@ -409,6 +412,9 @@ public class CustomTabIntentDataProvider extends BrowserServicesIntentDataProvid
// TODO(https://crbug.com/1023759): Remove this function and enable
// incognito CCT request for all apps.
private boolean isValidIncognitoIntent(Intent intent) {
+ if (ContextUtils.getAppSharedPreferences().getBoolean(AlwaysIncognitoLinkInterceptor.PREF_ALWAYS_INCOGNITO, false)) {
+ return true;
+ }
if (!isIncognitoRequested(intent)) return false;
// Incognito requests for payments flow are supported without
// INCOGNITO_CCT flag as an exceptional case that can use Chrome
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/init/StartupTabPreloader.java b/chrome/android/java/src/org/chromium/chrome/browser/init/StartupTabPreloader.java
--- a/chrome/android/java/src/org/chromium/chrome/browser/init/StartupTabPreloader.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/init/StartupTabPreloader.java
@@ -36,6 +36,9 @@ import org.chromium.ui.base.PageTransition;
import org.chromium.ui.base.WindowAndroid;
import org.chromium.url.GURL;
+import org.chromium.base.ContextUtils;
+import org.chromium.chrome.browser.AlwaysIncognitoLinkInterceptor;
+
/**
* This class attempts to preload the tab if the url is known from the intent when the profile
* is created. This is done to improve startup latency.
@@ -187,9 +190,11 @@ public class StartupTabPreloader implements ProfileManager.Observer, Destroyable
Intent intent = mIntentSupplier.get();
GURL url = UrlFormatter.fixupUrl(getUrlFromIntent(intent));
+ boolean isIncognito = ContextUtils.getAppSharedPreferences().getBoolean(AlwaysIncognitoLinkInterceptor.PREF_ALWAYS_INCOGNITO, false);
+
ChromeTabCreator chromeTabCreator =
- (ChromeTabCreator) mTabCreatorManager.getTabCreator(false);
- WebContents webContents = WebContentsFactory.createWebContents(false, false);
+ (ChromeTabCreator) mTabCreatorManager.getTabCreator(isIncognito);
+ WebContents webContents = WebContentsFactory.createWebContents(isIncognito, false);
mLoadUrlParams = new LoadUrlParams(url.getValidSpecOrEmpty());
String referrer = IntentHandler.getReferrerUrlIncludingExtraHeaders(intent);
@@ -203,7 +208,7 @@ public class StartupTabPreloader implements ProfileManager.Observer, Destroyable
// Create a detached tab, but don't add it to the tab model yet. We'll do that
// later if the loadUrlParams etc... match.
mTab = TabBuilder.createLiveTab(false)
- .setIncognito(false)
+ .setIncognito(isIncognito)
.setLaunchType(TabLaunchType.FROM_EXTERNAL_APP)
.setWindow(mWindowAndroid)
.setWebContents(webContents)
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/ChromeTabCreator.java b/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/ChromeTabCreator.java
--- a/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/ChromeTabCreator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/ChromeTabCreator.java
@@ -39,6 +39,10 @@ import org.chromium.ui.base.PageTransition;
import org.chromium.ui.base.WindowAndroid;
import org.chromium.url.GURL;
+import org.chromium.base.ContextUtils;
+import org.chromium.chrome.browser.AlwaysIncognitoLinkInterceptor;
+import org.chromium.chrome.browser.tab.TabObserver;
+
/**
* This class creates various kinds of new tabs and adds them to the right {@link TabModel}.
*/
@@ -59,6 +63,7 @@ public class ChromeTabCreator extends TabCreatorManager.TabCreator {
private final ChromeActivity mActivity;
private final StartupTabPreloader mStartupTabPreloader;
private final boolean mIncognito;
+ private final TabObserver mExtraLogic;
private WindowAndroid mNativeWindow;
private TabModel mTabModel;
@@ -76,6 +81,10 @@ public class ChromeTabCreator extends TabCreatorManager.TabCreator {
mNativeWindow = nativeWindow;
mTabDelegateFactorySupplier = tabDelegateFactory;
mIncognito = incognito;
+ if (!mIncognito)
+ mExtraLogic = new AlwaysIncognitoLinkInterceptor(ContextUtils.getAppSharedPreferences());
+ else
+ mExtraLogic = null;
mOverviewNTPCreator = overviewNTPCreator;
}
@@ -228,6 +237,8 @@ public class ChromeTabCreator extends TabCreatorManager.TabCreator {
if (creationState == TabCreationState.LIVE_IN_FOREGROUND && !openInForeground) {
creationState = TabCreationState.LIVE_IN_BACKGROUND;
}
+ if (mExtraLogic != null)
+ tab.addObserver(mExtraLogic);
mTabModel.addTab(tab, position, type, creationState);
return tab;
} finally {
@@ -262,6 +273,8 @@ public class ChromeTabCreator extends TabCreatorManager.TabCreator {
@TabCreationState
int creationState = openInForeground ? TabCreationState.LIVE_IN_FOREGROUND
: TabCreationState.LIVE_IN_BACKGROUND;
+ if (mExtraLogic != null)
+ tab.addObserver(mExtraLogic);
mTabModel.addTab(tab, position, type, creationState);
return true;
}
@@ -305,7 +318,6 @@ public class ChromeTabCreator extends TabCreatorManager.TabCreator {
// TODO(crbug.com/1081924): Clean up the launches from SearchActivity/Chrome.
public Tab launchUrlFromExternalApp(String url, String referer, String headers, String appId,
boolean forceNewTab, Intent intent, long intentTimestamp) {
- assert !mIncognito;
boolean isLaunchedFromChrome = TextUtils.equals(appId, mActivity.getPackageName());
if (forceNewTab && !isLaunchedFromChrome) {
@@ -412,6 +424,8 @@ public class ChromeTabCreator extends TabCreatorManager.TabCreator {
.setTabState(state)
.build();
}
+ if (mExtraLogic != null)
+ tab.addObserver(mExtraLogic);
if (state.isIncognito() != mIncognito) {
throw new IllegalStateException("Incognito state mismatch. TabState: "
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/TabModelSelectorBase.java b/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/TabModelSelectorBase.java
--- a/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/TabModelSelectorBase.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/TabModelSelectorBase.java
@@ -247,7 +247,15 @@ public abstract class TabModelSelectorBase implements TabModelSelector {
public void markTabStateInitialized() {
if (mTabStateInitialized) return;
mTabStateInitialized = true;
+
for (TabModelSelectorObserver listener : mObservers) listener.onTabStateInitialized();
+
+ if (mStartIncognito) {
+ // profile is not set in always-incognito mode in TabModelSelectorProfileSupplier
+ // so force it
+ selectModel(false); // restore model so next call always set incognito mode
+ selectModel(true);
+ }
}
@Override
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/TabPersistentStore.java b/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/TabPersistentStore.java
--- a/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/TabPersistentStore.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/TabPersistentStore.java
@@ -47,6 +47,8 @@ import org.chromium.components.embedder_support.util.UrlConstants;
import org.chromium.content_public.browser.LoadUrlParams;
import org.chromium.content_public.browser.UiThreadTaskTraits;
+import org.chromium.chrome.browser.AlwaysIncognitoLinkInterceptor;
+
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
@@ -599,6 +601,13 @@ public class TabPersistentStore extends TabPersister {
}
}
+ if (ContextUtils.getAppSharedPreferences().getBoolean(AlwaysIncognitoLinkInterceptor.PREF_ALWAYS_INCOGNITO, false)) {
+ if (!isIncognito) {
+ Log.w(TAG, "Failed to restore tab: not in incognito mode.");
+ return;
+ }
+ }
+
TabModel model = mTabModelSelector.getModel(isIncognito);
if (model.isIncognito() != isIncognito) {
diff --git a/chrome/browser/flags/android/chrome_feature_list.cc b/chrome/browser/flags/android/chrome_feature_list.cc
--- a/chrome/browser/flags/android/chrome_feature_list.cc
+++ b/chrome/browser/flags/android/chrome_feature_list.cc
@@ -333,7 +333,7 @@ const base::Feature kCCTExternalLinkHandling{"CCTExternalLinkHandling",
base::FEATURE_ENABLED_BY_DEFAULT};
const base::Feature kCCTIncognito{"CCTIncognito",
- base::FEATURE_DISABLED_BY_DEFAULT};
+ base::FEATURE_ENABLED_BY_DEFAULT};
const base::Feature kCCTPostMessageAPI{"CCTPostMessageAPI",
base::FEATURE_ENABLED_BY_DEFAULT};
diff --git a/chrome/browser/ui/android/strings/android_chrome_strings.grd b/chrome/browser/ui/android/strings/android_chrome_strings.grd
--- a/chrome/browser/ui/android/strings/android_chrome_strings.grd
+++ b/chrome/browser/ui/android/strings/android_chrome_strings.grd
@@ -773,6 +773,13 @@ Your Google account may have other forms of browsing history like searches and a
<message name="IDS_CLEAR_BROWSING_HISTORY_SUMMARY" desc="A text for the basic tab explaining browsing history.">
Clears history and autocompletions in the address bar.
</message>
+ <!-- always incognito -->
+ <message name="IDS_ALWAYS_INCOGNITO_TITLE" desc="Title for always incognito mode">
+ Open links in incognito tabs always
+ </message>
+ <message name="IDS_ALWAYS_INCOGNITO_SUMMARY" desc="Summary for always incognito mode">
+ Opens links in incognito tabs when you click on new tab or on a link
+ </message>
<message name="IDS_CLEAR_BROWSING_HISTORY_SUMMARY_SIGNED_IN" desc="A text explaining other forms of activity for signed in users.">
Clears history and autocompletions in the address bar. Your Google Account may have other forms of browsing history at <ph name="BEGIN_LINK">&lt;link&gt;</ph>myactivity.google.com<ph name="END_LINK">&lt;/link&gt;</ph>.
</message>
--
2.17.1