Commit 0e6b28f3 authored by sauski's avatar sauski Committed by Commit Bot

Settings Virtual Prefs: Connect Password Leak Detection preference

Connects the previously introduced Password Leak Detection generated
preference to the toggle control present in the security page. This
also deprecates the passwords_leak_detection_toggle element and the
associated test.

Bug: 1063265
Change-Id: I0ddacd706696db6f60fdc2035537c60da594a685
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2330152
Commit-Queue: Theodore Olsauskas-Warren <sauski@google.com>
Reviewed-by: default avatardpapad <dpapad@chromium.org>
Cr-Commit-Position: refs/heads/master@{#807873}
parent f62d0074
......@@ -427,8 +427,8 @@
<message name="IDS_SETTINGS_PASSWORDS_LEAK_DETECTION_LABEL" desc="Label for a checkbox that allows users to choose whether chrome should check that credentials have been part of a leak.">
Warn you if passwords are exposed in a data breach
</message>
<message name="IDS_SETTINGS_PASSWORDS_LEAK_DETECTION_SIGNED_OUT_ENABLED_DESC" desc="Text that describes the 'Password Leak Detection' functionality to signed-out users who have not disabled the feature.">
When you sign in to your Google Account, this feature is turned on
<message name="IDS_SETTINGS_PASSWORDS_LEAK_DETECTION_SIGNED_OUT_ENABLED_DESC" desc="Text that describes the 'Password Leak Detection' functionality to signed-out users who have not disabled the feature. This text always follows a sentence describing the feature.">
When you sign in to your Google Account, this feature is turned on.
</message>
<message name="IDS_SETTINGS_PASSWORDS_SAVED_HEADING" desc="The title for a list of username/site/password items. These items are already saved by the browser and can be deleted/edited.">
Saved Passwords
......
624b93b715dba37f77a0d950c97d6cec4051a66e
\ No newline at end of file
3bf2c7d38228234d32a2a6cb5cdff1555c4c593d
\ No newline at end of file
......@@ -45,7 +45,6 @@ js_type_check("closure_compile_module") {
":cookies_page",
":disable_safebrowsing_dialog",
":do_not_track_toggle",
":passwords_leak_detection_toggle",
":personalization_options.m",
":privacy_page",
":privacy_page_browser_proxy.m",
......@@ -120,17 +119,6 @@ js_library("do_not_track_toggle") {
]
}
js_library("passwords_leak_detection_toggle") {
deps = [
"..:metrics_browser_proxy",
"../people_page:sync_browser_proxy.m",
"../prefs:prefs_behavior.m",
"//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
"//ui/webui/resources/js:i18n_behavior.m",
"//ui/webui/resources/js:web_ui_listener_behavior.m",
]
}
js_library("personalization_options.m") {
sources = [ "$root_gen_dir/chrome/browser/resources/settings/privacy_page/personalization_options.m.js" ]
deps = [
......@@ -267,7 +255,6 @@ html_to_js("v3_ready_elements") {
"cookies_page.js",
"disable_safebrowsing_dialog.js",
"do_not_track_toggle.js",
"passwords_leak_detection_toggle.js",
"privacy_page.js",
"secure_dns.js",
"secure_dns_input.js",
......
<style include="settings-shared">
:host(.collapsable) settings-toggle-button {
border-top: none;
padding-bottom: 12px;
padding-inline-end: 0;
padding-inline-start: 0;
}
</style>
<settings-toggle-button id="passwordsLeakDetectionCheckbox" class="hr"
pref="[[passwordsLeakDetectionPref_]]" no-set-pref
label="$i18n{passwordsLeakDetectionLabel}"
sub-label="[[getPasswordsLeakDetectionSubLabel_(
userSignedIn_, passwordsLeakDetectionAvailable_)]]"
disabled="[[getDisabledLeakDetection_(
userSignedIn_, prefs.safebrowsing.enabled.value,
prefs.safebrowsing.enhanced.value)]]"
on-settings-boolean-control-change="onPasswordsLeakDetectionChange_">
</settings-toggle-button>
// 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.
import 'chrome://resources/cr_elements/shared_style_css.m.js';
import '../controls/settings_toggle_button.m.js';
import '../prefs/prefs.m.js';
import '../settings_shared_css.m.js';
import {I18nBehavior} from 'chrome://resources/js/i18n_behavior.m.js';
import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js';
import {WebUIListenerBehavior} from 'chrome://resources/js/web_ui_listener_behavior.m.js';
import {html, Polymer} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
import {MetricsBrowserProxyImpl, PrivacyElementInteractions} from '../metrics_browser_proxy.js';
import {StoredAccount, SyncBrowserProxyImpl, SyncStatus} from '../people_page/sync_browser_proxy.m.js';
import {PrefsBehavior} from '../prefs/prefs_behavior.m.js';
Polymer({
is: 'settings-passwords-leak-detection-toggle',
_template: html`{__html_template__}`,
behaviors: [
I18nBehavior,
PrefsBehavior,
WebUIListenerBehavior,
],
properties: {
/** @type {SyncStatus} */
syncStatus: Object,
// <if expr="not chromeos">
/** @private {Array<!StoredAccount>} */
storedAccounts_: Object,
// </if>
/** @private */
userSignedIn_: {
type: Boolean,
computed: 'computeUserSignedIn_(syncStatus, storedAccounts_)',
},
/** @private */
passwordsLeakDetectionAvailable_: {
type: Boolean,
computed: 'computePasswordsLeakDetectionAvailable_(prefs.*)',
},
// A "virtual" preference that is use to control the associated toggle
// independently of the preference it represents. This is updated with
// the registered setPasswordsLeakDetectionPref_ observer. Changes to
// the real preference are handled in onPasswordsLeakDetectionChange_
/** @private {chrome.settingsPrivate.PrefObject} */
passwordsLeakDetectionPref_: {
type: Object,
value() {
return /** @type {chrome.settingsPrivate.PrefObject} */ ({});
},
},
},
observers: [
'setPasswordsLeakDetectionPref_(userSignedIn_,' +
'passwordsLeakDetectionAvailable_)',
],
/** @override */
ready() {
// <if expr="not chromeos">
const storedAccountsChanged = storedAccounts => this.storedAccounts_ =
storedAccounts;
const syncBrowserProxy = SyncBrowserProxyImpl.getInstance();
syncBrowserProxy.getStoredAccounts().then(storedAccountsChanged);
this.addWebUIListener('stored-accounts-updated', storedAccountsChanged);
// </if>
this.metricsBrowserProxy_ = MetricsBrowserProxyImpl.getInstance();
},
/**
* @return {boolean}
* @private
*/
computeUserSignedIn_() {
return (!!this.syncStatus && !!this.syncStatus.signedIn) ?
!this.syncStatus.hasError :
(!!this.storedAccounts_ && this.storedAccounts_.length > 0);
},
/**
* @return {boolean}
* @private
*/
computePasswordsLeakDetectionAvailable_() {
if (this.prefs === undefined) {
return false;
}
return !!this.getPref('profile.password_manager_leak_detection').value;
},
/**
* @return {string}
* @private
*/
getPasswordsLeakDetectionSubLabel_() {
let subLabel = this.i18n('passwordsLeakDetectionGeneralDescription');
if (!this.userSignedIn_ && this.passwordsLeakDetectionAvailable_) {
subLabel +=
' ' + // Whitespace is a valid sentence separator w.r.t. i18n.
this.i18n('passwordsLeakDetectionSignedOutEnabledDescription') +
// The string appended on the previous line was added as a standalone
// sentence that did not end with a period. Since here we're appending
// it to a two-sentence string, with both of those sentences ending
// with periods, we must add a period at the end.
// TODO(crbug.com/1032584): After the privacy settings redesign, this
// string will never appear standalone. Include the period in it.
this.i18n('sentenceEnd');
}
return subLabel;
},
/**
* @return {boolean}
* @private
*/
getDisabledLeakDetection_() {
if (this.prefs === undefined) {
return false;
}
return !this.userSignedIn_ || !this.getPref('safebrowsing.enabled').value ||
!!this.getPref('safebrowsing.enhanced').value;
},
/** @private */
onPasswordsLeakDetectionChange_() {
this.metricsBrowserProxy_.recordSettingsPageHistogram(
PrivacyElementInteractions.PASSWORD_CHECK);
this.setPrefValue(
'profile.password_manager_leak_detection',
this.$.passwordsLeakDetectionCheckbox.checked);
},
/** @private */
setPasswordsLeakDetectionPref_() {
if (this.prefs === undefined) {
return;
}
const passwordManagerLeakDetectionPref =
this.getPref('profile.password_manager_leak_detection');
const prefValue =
this.userSignedIn_ && this.passwordsLeakDetectionAvailable_;
this.passwordsLeakDetectionPref_ = {
key: '',
type: chrome.settingsPrivate.PrefType.BOOLEAN,
value: prefValue,
enforcement: passwordManagerLeakDetectionPref.enforcement,
controlledBy: passwordManagerLeakDetectionPref.controlledBy,
};
},
});
......@@ -38,12 +38,7 @@
padding-bottom: 12px;
}
settings-passwords-leak-detection-toggle,
settings-toggle-button:not([disabled]) {
pointer-events: all;
}
#safeBrowsingReportingToggle {
settings-toggle-button {
padding-inline-end: 0;
padding-inline-start: 0;
}
......@@ -137,11 +132,13 @@
disabled="[[getDisabledExtendedSafeBrowsing_(
prefs.generated.safe_browsing.*)]]">
</settings-toggle-button>
<settings-passwords-leak-detection-toggle id="passwordsLeakToggle"
class="collapsable"
prefs="{{prefs}}"
sync-status="[[syncStatus]]">
</settings-passwords-leak-detection-toggle>
<settings-toggle-button id="passwordsLeakToggle"
label="$i18n{passwordsLeakDetectionLabel}"
pref="{{prefs.generated.password_leak_detection}}"
sub-label="[[getPasswordsLeakToggleSubLabel_(
prefs.profile.password_manager_leak_detection.*,
syncStatus.*)]]">
</settings-toggle-button>
</div>
</settings-collapse-radio-button>
<settings-collapse-radio-button id="safeBrowsingDisabled" no-collapse
......
......@@ -8,7 +8,6 @@ import 'chrome://resources/cr_elements/cr_link_row/cr_link_row.m.js';
import 'chrome://resources/cr_elements/shared_style_css.m.js';
import './collapse_radio_button.js';
import './disable_safebrowsing_dialog.js';
import './passwords_leak_detection_toggle.js';
import './secure_dns.js';
import '../controls/settings_toggle_button.m.js';
import '../icons.m.js';
......@@ -17,6 +16,7 @@ import '../settings_shared_css.m.js';
import {assert} from 'chrome://resources/js/assert.m.js';
import {focusWithoutInk} from 'chrome://resources/js/cr/ui/focus_without_ink.m.js';
import {I18nBehavior} from 'chrome://resources/js/i18n_behavior.m.js';
import {html, Polymer} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
import {loadTimeData} from '../i18n_setup.js';
......@@ -46,6 +46,7 @@ Polymer({
_template: html`{__html_template__}`,
behaviors: [
I18nBehavior,
PrefsBehavior,
RouteObserverBehavior,
],
......@@ -206,6 +207,22 @@ Polymer({
SafeBrowsingSetting.STANDARD;
},
/**
* @return {string}
* @private
*/
getPasswordsLeakToggleSubLabel_() {
let subLabel = this.i18n('passwordsLeakDetectionGeneralDescription');
if (this.getPref('profile.password_manager_leak_detection').value &&
(!this.syncStatus.signedIn ||
!!this.syncStatus.signedIn && !!this.syncStatus.hasError)) {
subLabel +=
' ' + // Whitespace is a valid sentence separator w.r.t. i18n.
this.i18n('passwordsLeakDetectionSignedOutEnabledDescription');
}
return subLabel;
},
/** @private */
onManageCertificatesClick_() {
// <if expr="use_nss_certs">
......
......@@ -505,11 +505,6 @@
use_base_dir="false"
preprocess="true"
compress="false" type="BINDATA" />
<include name="IDR_SETTINGS_PRIVACY_PAGE_PASSWORDS_LEAK_DETECTION_TOGGLE_JS"
file="${root_gen_dir}/chrome/browser/resources/settings/privacy_page/passwords_leak_detection_toggle.js"
use_base_dir="false"
preprocess="true"
compress="false" type="BINDATA" />
<include name="IDR_SETTINGS_PRIVACY_PAGE_PERSONALIZATION_OPTIONS_M_JS"
file="${root_gen_dir}/chrome/browser/resources/settings/privacy_page/personalization_options.m.js"
use_base_dir="false"
......
......@@ -115,7 +115,6 @@ js_type_check("closure_compile") {
":passwords_and_autofill_fake_data",
#":passwords_export_test",
#":passwords_leak_detection_toggle_test",
#":passwords_section_test",
#":passwords_section_test_cros",
#":payments_section_test",
......
......@@ -4,7 +4,6 @@ per-file collapse_radio_button_tests.js=sauski@google.com
per-file cookies_page_test.js=sauski@google.com
per-file do_not_track_toggle_test.js=sauski@google.com
per-file metrics_reporting_tests.js=sauski@google.com
per-file passwords_leak_detection_toggle_test.js=sauski@google.com
per-file personalization_options_test.js=sauski@google.com
per-file privacy_page_test.js=sauski@google.com
per-file recent_site_permissions_test.js=sauski@google.com
......@@ -22,7 +21,6 @@ per-file site_list_entry_tests.js=sauski@google.com
per-file site_list_tests.js=sauski@google.com
per-file site_settings_page_test.js=sauski@google.com
per-file test_privacy_page_browser_proxy.js=sauski@google.com
per-file test_safe_browsing_browser_proxy.js=sauski@google.com
per-file test_site_settings_prefs_browser_proxy.js=sauski@google.com
# COMPONENT: UI>Settings
......@@ -510,7 +510,6 @@ TEST_F('CrSettingsAdvancedPageV3Test', 'MAYBE_Load', function() {
['Languages', 'languages_tests.js'],
['Menu', 'settings_menu_test.js'],
['OnStartupPage', 'on_startup_page_tests.js'],
['PasswordsLeakDetectionToggle', 'passwords_leak_detection_toggle_test.js'],
['PaymentsSection', 'payments_section_test.js'],
['PeoplePage', 'people_page_test.js'],
['PeoplePageSyncControls', 'people_page_sync_controls_test.js'],
......
// 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.
// clang-format off
import 'chrome://settings/lazy_load.js';
import {isChromeOS} from 'chrome://resources/js/cr.m.js';
import {flush} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
import {MetricsBrowserProxyImpl, PrivacyElementInteractions} from 'chrome://settings/settings.js';
import {PrivacyPageBrowserProxyImpl, StatusAction, SyncBrowserProxyImpl} from 'chrome://settings/settings.js';
import {simulateStoredAccounts} from 'chrome://test/settings/sync_test_util.m.js';
import {TestMetricsBrowserProxy} from 'chrome://test/settings/test_metrics_browser_proxy.js';
import {TestPrivacyPageBrowserProxy} from 'chrome://test/settings/test_privacy_page_browser_proxy.js';
import {TestSyncBrowserProxy} from 'chrome://test/settings/test_sync_browser_proxy.m.js';
// clang-format on
suite('CrSettingsPasswordsLeakDetectionToggleTest', function() {
/** @type {settings.TestMetricsBrowserProxy} */
let testMetricsBrowserProxy;
/** @type {settings.TestPrivacyPageBrowserProxy} */
let privacyPageBrowserProxy;
/** @type {SyncBrowserProxy} */
let syncBrowserProxy;
/** @type {SettingsPersonalizationOptionsElement} */
let testElement;
/** @type {String} */
let signedInSubLabel;
/** @type {String} */
let signedOutSubLabel;
suiteSetup(function() {
signedInSubLabel =
loadTimeData.getString('passwordsLeakDetectionGeneralDescription');
signedOutSubLabel =
loadTimeData.getString('passwordsLeakDetectionGeneralDescription') +
' ' +
loadTimeData.getString(
'passwordsLeakDetectionSignedOutEnabledDescription') +
loadTimeData.getString('sentenceEnd');
});
setup(function() {
privacyPageBrowserProxy = new TestPrivacyPageBrowserProxy();
PrivacyPageBrowserProxyImpl.instance_ = privacyPageBrowserProxy;
syncBrowserProxy = new TestSyncBrowserProxy();
SyncBrowserProxyImpl.instance_ = syncBrowserProxy;
testMetricsBrowserProxy = new TestMetricsBrowserProxy();
MetricsBrowserProxyImpl.instance_ = testMetricsBrowserProxy;
PolymerTest.clearBody();
testElement =
document.createElement('settings-passwords-leak-detection-toggle');
testElement.prefs = {
profile: {password_manager_leak_detection: {value: true}},
safebrowsing: {
enabled: {value: true},
scout_reporting_enabled: {value: true},
enhanced: {value: false}
},
};
document.body.appendChild(testElement);
flush();
});
teardown(function() {
testElement.remove();
});
test('logPasswordLeakDetectionClick', function() {
testElement.set(
'prefs.profile.password_manager_leak_detection.value', true);
testElement.syncStatus = {signedIn: true};
flush();
console.log(testElement.$$('#passwordsLeakDetectionCheckbox').disabled);
testElement.$$('#passwordsLeakDetectionCheckbox').click();
return testMetricsBrowserProxy.whenCalled('recordSettingsPageHistogram')
.then(result => {
assertEquals(PrivacyElementInteractions.PASSWORD_CHECK, result);
});
});
test('leakDetectionToggleSignedOutWithFalsePref', function() {
testElement.set(
'prefs.profile.password_manager_leak_detection.value', false);
testElement.syncStatus = {signedIn: false};
flush();
assertTrue(testElement.$.passwordsLeakDetectionCheckbox.disabled);
assertFalse(testElement.$.passwordsLeakDetectionCheckbox.checked);
assertEquals(
signedInSubLabel,
testElement.$.passwordsLeakDetectionCheckbox.subLabel);
});
test('leakDetectionToggleSignedOutWithTruePref', function() {
testElement.syncStatus = {signedIn: false};
flush();
assertTrue(testElement.$.passwordsLeakDetectionCheckbox.disabled);
assertFalse(testElement.$.passwordsLeakDetectionCheckbox.checked);
assertEquals(
signedOutSubLabel,
testElement.$.passwordsLeakDetectionCheckbox.subLabel);
});
if (!isChromeOS) {
test('leakDetectionToggleSignedInNotSyncingWithFalsePref', function() {
testElement.set(
'prefs.profile.password_manager_leak_detection.value', false);
testElement.syncStatus = {signedIn: false};
simulateStoredAccounts([
{
fullName: 'testName',
givenName: 'test',
email: 'test@test.com',
},
]);
flush();
assertFalse(testElement.$.passwordsLeakDetectionCheckbox.disabled);
assertFalse(testElement.$.passwordsLeakDetectionCheckbox.checked);
assertEquals(
signedInSubLabel,
testElement.$.passwordsLeakDetectionCheckbox.subLabel);
});
test('leakDetectionToggleSignedInNotSyncingWithTruePref', function() {
testElement.syncStatus = {signedIn: false};
simulateStoredAccounts([
{
fullName: 'testName',
givenName: 'test',
email: 'test@test.com',
},
]);
flush();
assertFalse(testElement.$.passwordsLeakDetectionCheckbox.disabled);
assertTrue(testElement.$.passwordsLeakDetectionCheckbox.checked);
assertEquals(
signedInSubLabel,
testElement.$.passwordsLeakDetectionCheckbox.subLabel);
});
}
test('leakDetectionToggleSignedInAndSyncingWithFalsePref', function() {
testElement.set(
'prefs.profile.password_manager_leak_detection.value', false);
testElement.syncStatus = {signedIn: true};
flush();
assertFalse(testElement.$.passwordsLeakDetectionCheckbox.disabled);
assertFalse(testElement.$.passwordsLeakDetectionCheckbox.checked);
assertEquals(
signedInSubLabel,
testElement.$.passwordsLeakDetectionCheckbox.subLabel);
});
test('leakDetectionToggleSignedInAndSyncingWithTruePref', function() {
testElement.syncStatus = {signedIn: true};
flush();
assertFalse(testElement.$.passwordsLeakDetectionCheckbox.disabled);
assertTrue(testElement.$.passwordsLeakDetectionCheckbox.checked);
assertEquals(
signedInSubLabel,
testElement.$.passwordsLeakDetectionCheckbox.subLabel);
});
});
......@@ -229,14 +229,11 @@ suite('HappinessTrackingSurveys', function() {
cookie_session_only: {value: false},
cookie_primary_setting:
{type: chrome.settingsPrivate.PrefType.NUMBER, value: 0},
password_manager_leak_detection: {value: false},
},
profile: {password_manager_leak_detection: {value: false}},
dns_over_https:
{mode: {value: SecureDnsMode.AUTOMATIC}, templates: {value: ''}},
signin: {
allowed_on_next_startup:
{type: chrome.settingsPrivate.PrefType.BOOLEAN, value: true}
},
};
document.body.appendChild(page);
return flushTasks();
......
......@@ -49,24 +49,20 @@ suite('CrSettingsSecurityPageTest', function() {
document.createElement('settings-security-page'));
page.prefs = {
profile: {password_manager_leak_detection: {value: false}},
signin: {
allowed_on_next_startup:
{type: chrome.settingsPrivate.PrefType.BOOLEAN, value: true}
},
safebrowsing: {
enabled: {value: true},
scout_reporting_enabled: {value: true},
enhanced: {value: false}
},
generated: {
safe_browsing: {
type: chrome.settingsPrivate.PrefType.NUMBER,
value: SafeBrowsingSetting.STANDARD,
},
password_leak_detection: {value: true, userControlDisabled: false},
},
dns_over_https:
{mode: {value: SecureDnsMode.AUTOMATIC}, templates: {value: ''}},
};
page.set('syncStatus', {signedIn: false, hasError: false});
document.body.appendChild(page);
page.$$('#safeBrowsingEnhanced').updateCollapsed();
page.$$('#safeBrowsingStandard').updateCollapsed();
......@@ -102,6 +98,30 @@ suite('CrSettingsSecurityPageTest', function() {
assertTrue(isChildVisible(page, '#security-keys-subpage-trigger'));
});
test('PasswordsLeakDetectionSubLabel', function() {
const toggle = page.$$('#passwordsLeakToggle');
const defaultSubLabel =
loadTimeData.getString('passwordsLeakDetectionGeneralDescription');
const activeWhenSignedInSubLabel =
loadTimeData.getString('passwordsLeakDetectionGeneralDescription') +
' ' +
loadTimeData.getString(
'passwordsLeakDetectionSignedOutEnabledDescription');
assertEquals(defaultSubLabel, toggle.subLabel);
page.set('prefs.profile.password_manager_leak_detection.value', true);
flush();
assertEquals(activeWhenSignedInSubLabel, toggle.subLabel);
page.set('syncStatus', {signedIn: true});
flush();
assertEquals(defaultSubLabel, toggle.subLabel);
page.set('syncStatus', {signedIn: true, hasError: true});
flush();
assertEquals(activeWhenSignedInSubLabel, toggle.subLabel);
});
test('LogSafeBrowsingExtendedToggle', async function() {
page.$$('#safeBrowsingStandard').click();
flush();
......@@ -504,24 +524,20 @@ suite('CrSettingsSecurityPageTest_FlagsDisabled', function() {
document.createElement('settings-security-page'));
page.prefs = {
profile: {password_manager_leak_detection: {value: true}},
signin: {
allowed_on_next_startup:
{type: chrome.settingsPrivate.PrefType.BOOLEAN, value: true}
},
safebrowsing: {
enabled: {value: true},
scout_reporting_enabled: {value: true},
enhanced: {value: false}
},
generated: {
safe_browsing: {
type: chrome.settingsPrivate.PrefType.NUMBER,
value: SafeBrowsingSetting.STANDARD,
},
password_leak_detection: {value: true, userControlDisabled: false},
},
dns_over_https:
{mode: {value: SecureDnsMode.AUTOMATIC}, templates: {value: ''}},
};
page.set('syncStatus', {signedIn: true, hasError: false});
document.body.appendChild(page);
flush();
});
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment