diff --git a/build/patches/Disable-floc.patch b/build/patches/Disable-floc.patch new file mode 100644 index 00000000..4d02dc1e --- /dev/null +++ b/build/patches/Disable-floc.patch @@ -0,0 +1,479 @@ +From: uazo +Date: Sat, 13 Nov 2021 18:02:15 +0000 +Subject: Disable Floc + +Permanently removes floc support, disabling the download of lsh clusters, +the marking the history navigation and the javascript api and permission policies. +Also added the disabling of blink features through the DisabledForBromite tag +--- + .../browser/chrome_content_browser_client.cc | 4 ++ + .../floc_component_installer.cc | 10 +--- + .../floc_eligibility_observer.cc | 58 +------------------ + .../floc_id_provider_impl.cc | 4 +- + .../privacy_sandbox_settings.cc | 14 +++-- + .../federated_learning/features/features.cc | 12 ++-- + components/federated_learning/floc_id.cc | 19 +++--- + .../history/core/browser/history_backend.cc | 13 +---- + .../history/core/browser/history_service.cc | 9 +-- + components/history/core/browser/url_row.h | 1 - + third_party/blink/common/features.cc | 4 +- + .../permissions_policy_feature.mojom | 5 -- + .../blink/renderer/core/dom/document.cc | 3 +- + .../blink/renderer/core/dom/document.idl | 1 - + .../renderer/core/dom/interest_cohort.idl | 4 -- + .../permissions_policy_features.json5 | 6 -- + .../platform/runtime_enabled_features.json5 | 12 +++- + 17 files changed, 45 insertions(+), 134 deletions(-) + +diff --git a/chrome/browser/chrome_content_browser_client.cc b/chrome/browser/chrome_content_browser_client.cc +--- a/chrome/browser/chrome_content_browser_client.cc ++++ b/chrome/browser/chrome_content_browser_client.cc +@@ -2674,6 +2674,10 @@ bool ChromeContentBrowserClient::IsConversionMeasurementOperationAllowed( + const url::Origin* impression_origin, + const url::Origin* conversion_origin, + const url::Origin* reporting_origin) { ++ // redundant code, privacy sandbox is already disabled in the prefs ++ // but I leave it anyway for now ++ if ((true)) ++ return false; + Profile* profile = Profile::FromBrowserContext(browser_context); + + PrivacySandboxSettings* privacy_sandbox_settings = +diff --git a/chrome/browser/component_updater/floc_component_installer.cc b/chrome/browser/component_updater/floc_component_installer.cc +--- a/chrome/browser/component_updater/floc_component_installer.cc ++++ b/chrome/browser/component_updater/floc_component_installer.cc +@@ -18,7 +18,7 @@ namespace component_updater { + // The extension id is: cmahhnpholdijhjokonmfdjbfmklppij + constexpr uint8_t kFlocComponentPublicKeySHA256[32] = { + 0x2c, 0x07, 0x7d, 0xf7, 0xeb, 0x38, 0x97, 0x9e, 0xae, 0xdc, 0x53, +- 0x91, 0x5c, 0xab, 0xff, 0x89, 0xbc, 0xf0, 0xd9, 0x30, 0xd2, 0x2e, ++ 0x01, 0x02, 0x03, 0x04, 0x89, 0xbc, 0xf0, 0xd9, 0x30, 0xd2, 0x2e, + 0x8f, 0x68, 0x3a, 0xf9, 0x21, 0x91, 0x9f, 0xc1, 0x84, 0xa1}; + + constexpr char kFlocComponentFetcherManifestName[] = +@@ -55,10 +55,6 @@ void FlocComponentInstallerPolicy::ComponentReady( + const base::FilePath& install_dir, + std::unique_ptr manifest) { + DCHECK(!install_dir.empty()); +- +- floc_sorting_lsh_clusters_service_->OnSortingLshClustersFileReady( +- install_dir.Append(federated_learning::kSortingLshClustersFileName), +- version); + } + + // Called during startup and installation before ComponentReady(). +@@ -101,10 +97,6 @@ void RegisterFlocComponent( + ComponentUpdateService* cus, + federated_learning::FlocSortingLshClustersService* + floc_sorting_lsh_clusters_service) { +- auto installer = base::MakeRefCounted( +- std::make_unique( +- floc_sorting_lsh_clusters_service)); +- installer->Register(cus, base::OnceClosure()); + } + + } // namespace component_updater +diff --git a/chrome/browser/federated_learning/floc_eligibility_observer.cc b/chrome/browser/federated_learning/floc_eligibility_observer.cc +--- a/chrome/browser/federated_learning/floc_eligibility_observer.cc ++++ b/chrome/browser/federated_learning/floc_eligibility_observer.cc +@@ -16,55 +16,14 @@ + + namespace federated_learning { + +-namespace { +- +-history::HistoryService* GetHistoryService(content::WebContents* web_contents) { +- DCHECK(web_contents); +- +- Profile* profile = +- Profile::FromBrowserContext(web_contents->GetBrowserContext()); +- if (profile->IsOffTheRecord()) +- return nullptr; +- +- return HistoryServiceFactory::GetForProfile( +- profile, ServiceAccessType::IMPLICIT_ACCESS); +-} +- +-} // namespace +- + FlocEligibilityObserver::~FlocEligibilityObserver() = default; + + page_load_metrics::PageLoadMetricsObserver::ObservePolicy + FlocEligibilityObserver::OnCommit( + content::NavigationHandle* navigation_handle) { +- // At this point the add-page-to-history decision should have been made, +- // because history is added in HistoryTabHelper::DidFinishNavigation, and this +- // OnEligibleCommit method is invoked in the same broadcasting family through +- // MetricsWebContentsObserver::DidFinishNavigation. +- +- // TODO(yaoxia): Perhaps we want an explicit signal for "the page was added +- // to history or was ineligible". This way we don't need to count on the above +- // relation, and can also stop observing if the history was not added. +- +- // If the IP was not publicly routable, the navigation history is not eligible +- // for floc. We can stop observing now. +- if (!navigation_handle->GetSocketAddress().address().IsPubliclyRoutable() && +- !base::FeatureList::IsEnabled(kFlocBypassIPIsPubliclyRoutableCheck)) { +- return ObservePolicy::STOP_OBSERVING; +- } +- +- // If the interest-cohort permissions policy in the main document disallows +- // the floc inclusion, the navigation history is not eligible for floc. We can +- // stop observing now. +- if (!navigation_handle->GetRenderFrameHost()->IsFeatureEnabled( +- blink::mojom::PermissionsPolicyFeature::kInterestCohort)) { +- return ObservePolicy::STOP_OBSERVING; +- } +- +- DCHECK(!eligible_commit_); +- eligible_commit_ = true; +- +- return ObservePolicy::CONTINUE_OBSERVING; ++ // do not mark navigation as interesting for floc ++ // see https://github.com/WICG/floc#sites-which-interest-cohorts-will-be-calculated-on ++ return ObservePolicy::STOP_OBSERVING; + } + + void FlocEligibilityObserver::OnAdResource() { +@@ -84,17 +43,6 @@ FlocEligibilityObserver::FlocEligibilityObserver(content::RenderFrameHost* rfh) + : web_contents_(content::WebContents::FromRenderFrameHost(rfh)) {} + + void FlocEligibilityObserver::OnOptInSignalObserved() { +- if (!eligible_commit_ || observed_opt_in_signal_) +- return; +- +- if (history::HistoryService* hs = GetHistoryService(web_contents_)) { +- hs->SetFlocAllowed( +- history::ContextIDForWebContents(web_contents_), +- web_contents_->GetController().GetLastCommittedEntry()->GetUniqueID(), +- web_contents_->GetLastCommittedURL()); +- } +- +- observed_opt_in_signal_ = true; + } + + RENDER_DOCUMENT_HOST_USER_DATA_KEY_IMPL(FlocEligibilityObserver) +diff --git a/chrome/browser/federated_learning/floc_id_provider_impl.cc b/chrome/browser/federated_learning/floc_id_provider_impl.cc +--- a/chrome/browser/federated_learning/floc_id_provider_impl.cc ++++ b/chrome/browser/federated_learning/floc_id_provider_impl.cc +@@ -396,10 +396,8 @@ void FlocIdProviderImpl::OnGetRecentlyVisitedURLsCompleted( + base::Time history_end_time = base::Time::Min(); + + for (const history::URLResult& url_result : results) { +- if (!(url_result.content_annotations().annotation_flags & +- history::VisitContentAnnotationFlag::kFlocEligibleRelaxed)) { ++ if ((true)) + continue; +- } + + if (url_result.visit_time() < history_begin_time) + history_begin_time = url_result.visit_time(); +diff --git a/chrome/browser/privacy_sandbox/privacy_sandbox_settings.cc b/chrome/browser/privacy_sandbox/privacy_sandbox_settings.cc +--- a/chrome/browser/privacy_sandbox/privacy_sandbox_settings.cc ++++ b/chrome/browser/privacy_sandbox/privacy_sandbox_settings.cc +@@ -182,6 +182,9 @@ PrivacySandboxSettings::PrivacySandboxSettings( + + // as default, privacy sandbox is disabled + pref_service_->SetBoolean(prefs::kPrivacySandboxApisEnabled, false); ++ // as default, floc is not enabled ++ pref_service_->SetBoolean(prefs::kPrivacySandboxFlocEnabled, false); ++ + // On first entering the privacy sandbox experiment, users may have the + // privacy sandbox disabled (or "reconciled") based on their current cookie + // settings (e.g. blocking 3P cookies). Depending on the state of the sync +@@ -301,7 +304,8 @@ bool PrivacySandboxSettings::IsFlocPrefEnabled() const { + } + + void PrivacySandboxSettings::SetFlocPrefEnabled(bool enabled) const { +- pref_service_->SetBoolean(prefs::kPrivacySandboxFlocEnabled, enabled); ++ // never enable floc ++ pref_service_->SetBoolean(prefs::kPrivacySandboxFlocEnabled, false); + base::RecordAction(base::UserMetricsAction( + enabled ? "Settings.PrivacySandbox.FlocEnabled" + : "Settings.PrivacySandbox.FlocDisabled")); +@@ -454,6 +458,7 @@ void PrivacySandboxSettings::MaybeReconcilePrivacySandboxPref() { + // this code could be eliminated in the future, as initially + // the feauture was tied to the cookies flag + pref_service_->SetBoolean(prefs::kPrivacySandboxApisEnabled, false); ++ pref_service_->SetBoolean(prefs::kPrivacySandboxFlocEnabled, false); + if((true)) + return; + +@@ -552,11 +557,8 @@ void PrivacySandboxSettings::ReconcilePrivacySandboxPref() { + + void PrivacySandboxSettings::SetFlocDataAccessibleFromNow( + bool reset_calculate_timer) const { +- pref_service_->SetTime(prefs::kPrivacySandboxFlocDataAccessibleSince, +- base::Time::Now()); +- +- for (auto& observer : observers_) +- observer.OnFlocDataAccessibleSinceUpdated(reset_calculate_timer); ++ // don't tell anyone, thanks. ++ pref_service_->ClearPref(prefs::kPrivacySandboxFlocDataAccessibleSince); + } + + void PrivacySandboxSettings::StopObserving() { +diff --git a/components/federated_learning/features/features.cc b/components/federated_learning/features/features.cc +--- a/components/federated_learning/features/features.cc ++++ b/components/federated_learning/features/features.cc +@@ -12,13 +12,13 @@ namespace federated_learning { + // bypassed when determining the eligibility for a page to be included in floc + // computation. This is useful for developers to test FLoC in local environment. + const base::Feature kFlocBypassIPIsPubliclyRoutableCheck{ +- "FlocBypassIPIsPubliclyRoutableCheck", base::FEATURE_DISABLED_BY_DEFAULT}; ++ "FlocBypassIPIsPubliclyRoutableCheck", base::FEATURE_DISABLED_BY_DEFAULT}; // guard this + + // Enables or disables the FlocIdComputed event logging, which happens when a + // floc id is first computed for a browsing session or is refreshed due to a + // long period of time has passed since the last computation. + const base::Feature kFlocIdComputedEventLogging{ +- "FlocIdComputedEventLogging", base::FEATURE_ENABLED_BY_DEFAULT}; ++ "FlocIdComputedEventLogging", base::FEATURE_DISABLED_BY_DEFAULT}; + + // If enabled, pages that had ad resources will be included in floc computation; + // otherwise, only pages that used the document.interestCohort API will be +@@ -27,19 +27,19 @@ const base::Feature kFlocIdComputedEventLogging{ + // criteria. + const base::Feature kFlocPagesWithAdResourcesDefaultIncludedInFlocComputation{ + "FlocPagesWithAdResourcesDefaultIncludedInFlocComputation", +- base::FEATURE_DISABLED_BY_DEFAULT}; ++ base::FEATURE_DISABLED_BY_DEFAULT}; // guard this + + // The main floc feature for all the subsidiary control and setting params. It's + // controlling the floc update rate, and the minimum history domain size + // required. + // TODO(yaoxia): merge other floc features into this one. + const base::Feature kFederatedLearningOfCohorts{ +- "FederatedLearningOfCohorts", base::FEATURE_DISABLED_BY_DEFAULT}; ++ "FederatedLearningOfCohorts", base::FEATURE_DISABLED_BY_DEFAULT}; // guard this + constexpr base::FeatureParam kFlocIdScheduledUpdateInterval{ + &kFederatedLearningOfCohorts, "update_interval", +- base::TimeDelta::FromDays(7)}; ++ base::TimeDelta::FromDays(1)}; + constexpr base::FeatureParam kFlocIdMinimumHistoryDomainSizeRequired{ +- &kFederatedLearningOfCohorts, "minimum_history_domain_size_required", 3}; ++ &kFederatedLearningOfCohorts, "minimum_history_domain_size_required", 99999}; + constexpr base::FeatureParam kFlocIdFinchConfigVersion{ + &kFederatedLearningOfCohorts, "finch_config_version", 1}; + +diff --git a/components/federated_learning/floc_id.cc b/components/federated_learning/floc_id.cc +--- a/components/federated_learning/floc_id.cc ++++ b/components/federated_learning/floc_id.cc +@@ -38,9 +38,8 @@ FlocId FlocId::CreateValid(uint64_t id, + base::Time history_begin_time, + base::Time history_end_time, + uint32_t sorting_lsh_version) { +- return FlocId(id, Status::kValid, history_begin_time, history_end_time, +- kFlocIdFinchConfigVersion.Get(), sorting_lsh_version, +- /*compute_time=*/base::Time::Now()); ++ // always return an invalid floc id ++ return CreateInvalid(Status::kInvalidBlocked); + } + + FlocId::FlocId(const FlocId& id) = default; +@@ -100,15 +99,7 @@ void FlocId::RegisterPrefs(PrefRegistrySimple* registry) { + } + + void FlocId::SaveToPrefs(PrefService* prefs) { +- DCHECK_NE(status_, Status::kInvalidNoStatusPrefs); +- +- prefs->SetUint64(kFlocIdValuePrefKey, id_); +- prefs->SetInteger(kFlocIdStatusPrefKey, static_cast(status_)); +- prefs->SetTime(kFlocIdHistoryBeginTimePrefKey, history_begin_time_); +- prefs->SetTime(kFlocIdHistoryEndTimePrefKey, history_end_time_); +- prefs->SetUint64(kFlocIdFinchConfigVersionPrefKey, finch_config_version_); +- prefs->SetUint64(kFlocIdSortingLshVersionPrefKey, sorting_lsh_version_); +- prefs->SetTime(kFlocIdComputeTimePrefKey, compute_time_); ++ // don't save anything + } + + void FlocId::UpdateStatusAndSaveToPrefs(PrefService* prefs, Status status) { +@@ -126,6 +117,10 @@ void FlocId::ResetComputeTimeAndSaveToPrefs(base::Time compute_time, + + // static + FlocId FlocId::ReadFromPrefs(PrefService* prefs) { ++ // don't load anything ++ if ((true)) ++ return CreateInvalid(FlocId::Status::kInvalidBlocked); ++ + Status status = Status::kInvalidNoStatusPrefs; + + // We rely on the time to tell whether it's a fresh profile. +diff --git a/components/history/core/browser/history_backend.cc b/components/history/core/browser/history_backend.cc +--- a/components/history/core/browser/history_backend.cc ++++ b/components/history/core/browser/history_backend.cc +@@ -463,18 +463,7 @@ void HistoryBackend::SetFlocAllowed(ContextID context_id, + if (!visit_id) + return; + +- // Only add to the annotations table if the visit_id exists in the visits +- // table. +- VisitContentAnnotations annotations; +- if (db_->GetContentAnnotationsForVisit(visit_id, &annotations)) { +- annotations.annotation_flags |= +- VisitContentAnnotationFlag::kFlocEligibleRelaxed; +- db_->UpdateContentAnnotationsForVisit(visit_id, annotations); +- } else { +- annotations.annotation_flags |= +- VisitContentAnnotationFlag::kFlocEligibleRelaxed; +- db_->AddContentAnnotationsForVisit(visit_id, annotations); +- } ++ // we don't want anyone to mark history as floc enabled + ScheduleCommit(); + } + +diff --git a/components/history/core/browser/history_service.cc b/components/history/core/browser/history_service.cc +--- a/components/history/core/browser/history_service.cc ++++ b/components/history/core/browser/history_service.cc +@@ -383,7 +383,7 @@ void HistoryService::AddPage(const GURL& url, + AddPage(HistoryAddPageArgs( + url, time, context_id, nav_entry_id, referrer, redirects, transition, + !ui::PageTransitionIsMainFrame(transition), visit_source, +- did_replace_entry, /*consider_for_ntp_most_visited=*/true, floc_allowed)); ++ did_replace_entry, /*consider_for_ntp_most_visited=*/true, /*floc_allowed*/false)); + } + + void HistoryService::AddPage(const GURL& url, +@@ -463,12 +463,7 @@ void HistoryService::UpdateWithPageEndTime(ContextID context_id, + void HistoryService::SetFlocAllowed(ContextID context_id, + int nav_entry_id, + const GURL& url) { +- TRACE_EVENT0("browser", "HistoryService::SetFlocAllowed"); +- DCHECK(backend_task_runner_) << "History service being called after cleanup"; +- DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); +- ScheduleTask(PRIORITY_NORMAL, +- base::BindOnce(&HistoryBackend::SetFlocAllowed, history_backend_, +- context_id, nav_entry_id, url)); ++ // we don't want anyone to mark history as floc enabled + } + + void HistoryService::AddContentModelAnnotationsForVisit( +diff --git a/components/history/core/browser/url_row.h b/components/history/core/browser/url_row.h +--- a/components/history/core/browser/url_row.h ++++ b/components/history/core/browser/url_row.h +@@ -166,7 +166,6 @@ enum VisitContentAnnotationFlag : uint64_t { + // 3. Page opted in / Either one of the following holds: + // - document.interestCohort API is used in the page + // - the page has heuristically detected ad resources +- kFlocEligibleRelaxed = 1 << 0, + }; + + using VisitContentAnnotationFlags = uint64_t; +diff --git a/third_party/blink/common/features.cc b/third_party/blink/common/features.cc +--- a/third_party/blink/common/features.cc ++++ b/third_party/blink/common/features.cc +@@ -883,11 +883,11 @@ const base::Feature kDeclarativeShadowDOM{"DeclarativeShadowDOM", + // API exposure will be disabled regardless of the OT config. + // (See https://github.com/WICG/floc.) + const base::Feature kInterestCohortAPIOriginTrial{ +- "InterestCohortAPIOriginTrial", base::FEATURE_DISABLED_BY_DEFAULT}; ++ "InterestCohortAPIOriginTrial", base::FEATURE_DISABLED_BY_DEFAULT}; // guard this + + // Enable the availability of the "interest-cohort" permissions policy. + const base::Feature kInterestCohortFeaturePolicy{ +- "InterestCohortFeaturePolicy", base::FEATURE_DISABLED_BY_DEFAULT}; ++ "InterestCohortFeaturePolicy", base::FEATURE_DISABLED_BY_DEFAULT}; // guard this + + // Changes the default background color of the Text Fragment from + // bright yellow rgb(255, 255, 0) to light purple rgb(233, 210, 253) +diff --git a/third_party/blink/public/mojom/permissions_policy/permissions_policy_feature.mojom b/third_party/blink/public/mojom/permissions_policy/permissions_policy_feature.mojom +--- a/third_party/blink/public/mojom/permissions_policy/permissions_policy_feature.mojom ++++ b/third_party/blink/public/mojom/permissions_policy/permissions_policy_feature.mojom +@@ -126,11 +126,6 @@ enum PermissionsPolicyFeature { + // Controls access to screen capture via getDisplayMedia(). + kDisplayCapture = 81, + +- // Controls the access to the interest cohort as well as the eligibility for +- // the navigation history entry to be included in the interest cohort +- // computation. (https://github.com/WICG/floc) +- kInterestCohort = 82, +- + // Controls whether Autofill may fill fields in that frame when triggered on + // a field with the main frame's origin. + kSharedAutofill = 83, +diff --git a/third_party/blink/renderer/core/dom/document.cc b/third_party/blink/renderer/core/dom/document.cc +--- a/third_party/blink/renderer/core/dom/document.cc ++++ b/third_party/blink/renderer/core/dom/document.cc +@@ -6009,8 +6009,7 @@ ScriptPromise Document::interestCohort(ScriptState* script_state, + return ScriptPromise(); + } + +- if (!GetExecutionContext()->IsFeatureEnabled( +- mojom::blink::PermissionsPolicyFeature::kInterestCohort)) { ++ if ((true)) { + exception_state.ThrowDOMException( + DOMExceptionCode::kInvalidAccessError, + "The \"interest-cohort\" Permissions Policy denied the use of " +diff --git a/third_party/blink/renderer/core/dom/document.idl b/third_party/blink/renderer/core/dom/document.idl +--- a/third_party/blink/renderer/core/dom/document.idl ++++ b/third_party/blink/renderer/core/dom/document.idl +@@ -190,7 +190,6 @@ typedef (HTMLScriptElement or SVGScriptElement) HTMLOrSVGScriptElement; + + // Interest Cohort API + // TODO(yaoxia): Add web tests. http://crbug/1130074. +- [CallWith=ScriptState, RaisesException, NewObject, SecureContext, RuntimeEnabled=InterestCohortAPI, MeasureAs=InterestCohortAPI_interestCohort_Method] Promise interestCohort(); + + // Text fragment directive API + // https://wicg.github.io/scroll-to-text-fragment/#feature-detectability +diff --git a/third_party/blink/renderer/core/dom/interest_cohort.idl b/third_party/blink/renderer/core/dom/interest_cohort.idl +--- a/third_party/blink/renderer/core/dom/interest_cohort.idl ++++ b/third_party/blink/renderer/core/dom/interest_cohort.idl +@@ -4,7 +4,3 @@ + + // https://wicg.github.io/floc/#dictdef-interestcohort + +-dictionary InterestCohort { +- DOMString id; +- DOMString version; +-}; +diff --git a/third_party/blink/renderer/core/permissions_policy/permissions_policy_features.json5 b/third_party/blink/renderer/core/permissions_policy/permissions_policy_features.json5 +--- a/third_party/blink/renderer/core/permissions_policy/permissions_policy_features.json5 ++++ b/third_party/blink/renderer/core/permissions_policy/permissions_policy_features.json5 +@@ -256,12 +256,6 @@ + permissions_policy_name: "idle-detection", + depends_on: ["IdleDetection"], + }, +- { +- name: "InterestCohort", +- permissions_policy_name: "interest-cohort", +- feature_default: "EnableForAll", +- depends_on: ["InterestCohortFeaturePolicy"], +- }, + { + name: "Magnetometer", + permissions_policy_name: "magnetometer", +diff --git a/third_party/blink/renderer/platform/runtime_enabled_features.json5 b/third_party/blink/renderer/platform/runtime_enabled_features.json5 +--- a/third_party/blink/renderer/platform/runtime_enabled_features.json5 ++++ b/third_party/blink/renderer/platform/runtime_enabled_features.json5 +@@ -106,6 +106,13 @@ + }, + + data: [ ++ { ++ // disable by default features by marking as ++ // depends_on: ["DisabledForBromite"] ++ // to work is needed to remove "origin_trial_feature_name" ++ // and "origin_trial_allows_third_party" ++ name: "DisabledForBromite", ++ }, + { + name: "Accelerated2dCanvas", + settable_from_internals: true, +@@ -1211,12 +1218,11 @@ + }, + { + name: "InterestCohortAPI", +- origin_trial_feature_name: "InterestCohortAPI", +- origin_trial_allows_third_party: true, ++ depends_on: ["DisabledForBromite"], + }, + { + name: "InterestCohortFeaturePolicy", +- status: "experimental", ++ depends_on: ["DisabledForBromite"], + }, + { + name: "IntersectionObserverDocumentScrollingElementRoot", +-- +2.20.1 +