Commit a2a6febd authored by Scott Little's avatar Scott Little Committed by Commit Bot

LazyLoad: Hook up support for using loading=lazy on iframes.

Previously, support for the "loading" attribute to control lazy iframe
loading was only used in conjunction with automatic lazy loading. This
CL separates those two concepts, allowing for "loading" attribute
support to be enabled for iframes without automatically lazily loading
iframes that have no "loading" attribute set.

Bug: 937980
Change-Id: I0bf8f0e4d073fee3a3530b654a7834d1eb70cee1
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1633790Reviewed-by: default avatarrajendrant <rajendrant@chromium.org>
Reviewed-by: default avatarDaniel Cheng <dcheng@chromium.org>
Reviewed-by: default avatarKentaro Hara <haraken@chromium.org>
Commit-Queue: Scott Little <sclittle@chromium.org>
Cr-Commit-Position: refs/heads/master@{#664608}
parent 3a394526
......@@ -389,7 +389,11 @@ void SetIndividualRuntimeFeatures(
if (base::FeatureList::IsEnabled(features::kLazyImageVisibleLoadTimeMetrics))
WebRuntimeFeatures::EnableLazyImageVisibleLoadTimeMetrics(true);
WebRuntimeFeatures::EnableRestrictLazyFrameLoadingToDataSaver(
WebRuntimeFeatures::EnableAutomaticLazyFrameLoading(
base::GetFieldTrialParamByFeatureAsBool(
features::kLazyFrameLoading, "automatic-lazy-load-frames-enabled",
false));
WebRuntimeFeatures::EnableRestrictAutomaticLazyFrameLoadingToDataSaver(
base::GetFieldTrialParamByFeatureAsBool(
features::kLazyFrameLoading,
"restrict-lazy-load-frames-to-data-saver-only", false));
......
......@@ -81,6 +81,7 @@ class WebRuntimeFeatures {
BLINK_PLATFORM_EXPORT static void EnableAdTagging(bool);
BLINK_PLATFORM_EXPORT static void EnableAllowActivationDelegationAttr(bool);
BLINK_PLATFORM_EXPORT static void EnableAudioOutputDevices(bool);
BLINK_PLATFORM_EXPORT static void EnableAutomaticLazyFrameLoading(bool);
BLINK_PLATFORM_EXPORT static void EnableAutomaticLazyImageLoading(bool);
BLINK_PLATFORM_EXPORT static void EnableBackgroundFetch(bool);
BLINK_PLATFORM_EXPORT static void EnableBlinkHeapIncrementalMarking(bool);
......@@ -166,8 +167,8 @@ class WebRuntimeFeatures {
BLINK_PLATFORM_EXPORT static void EnableRemotePlaybackAPI(bool);
BLINK_PLATFORM_EXPORT static void EnableRenderingPipelineThrottling(bool);
BLINK_PLATFORM_EXPORT static void EnableResourceLoadScheduler(bool);
BLINK_PLATFORM_EXPORT static void EnableRestrictLazyFrameLoadingToDataSaver(
bool);
BLINK_PLATFORM_EXPORT static void
EnableRestrictAutomaticLazyFrameLoadingToDataSaver(bool);
BLINK_PLATFORM_EXPORT static void
EnableRestrictAutomaticLazyImageLoadingToDataSaver(bool);
BLINK_PLATFORM_EXPORT static void EnableScriptedSpeechRecognition(bool);
......
......@@ -77,9 +77,9 @@ bool DoesParentAllowLazyLoadingChildren(Document& document) {
return containing_frame_owner->ShouldLazyLoadChildren();
}
bool IsFrameLazyLoadable(Document& document,
bool IsFrameLazyLoadable(const Document& document,
const KURL& url,
bool is_load_attr_lazy,
bool is_loading_attr_lazy,
bool should_lazy_load_children) {
if (!RuntimeEnabledFeatures::LazyFrameLoadingEnabled() &&
!RuntimeEnabledFeatures::LazyFrameVisibleLoadTimeMetricsEnabled()) {
......@@ -92,7 +92,7 @@ bool IsFrameLazyLoadable(Document& document,
if (!url.ProtocolIsInHTTPFamily())
return false;
if (is_load_attr_lazy)
if (is_loading_attr_lazy)
return true;
if (!should_lazy_load_children ||
......@@ -105,12 +105,28 @@ bool IsFrameLazyLoadable(Document& document,
return false;
}
return true;
}
bool ShouldLazilyLoadFrame(const Document& document,
bool is_loading_attr_lazy) {
DCHECK(document.GetSettings());
if (!RuntimeEnabledFeatures::LazyFrameLoadingEnabled() ||
!document.GetSettings()->GetLazyLoadEnabled()) {
return false;
}
if (is_loading_attr_lazy)
return true;
if (!RuntimeEnabledFeatures::AutomaticLazyFrameLoadingEnabled())
return false;
// If lazy loading is restricted to only Data Saver users, then avoid
// lazy loading unless Data Saver is enabled, taking the Data Saver
// holdback into consideration.
if (RuntimeEnabledFeatures::RestrictLazyFrameLoadingToDataSaverEnabled() &&
((document.GetSettings() &&
document.GetSettings()->GetDataSaverHoldbackWebApi()) ||
if (RuntimeEnabledFeatures::
RestrictAutomaticLazyFrameLoadingToDataSaverEnabled() &&
(document.GetSettings()->GetDataSaverHoldbackWebApi() ||
!GetNetworkStateNotifier().SaveDataEnabled())) {
return false;
}
......@@ -452,10 +468,11 @@ bool HTMLFrameOwnerElement::LoadOrRedirectSubframe(
bool loading_lazy_set = EqualIgnoringASCIICase(loading_attr, "lazy") ||
(IsLoadingFrameDefaultEagerEnforced() &&
!EqualIgnoringASCIICase(loading_attr, "eager"));
if (!lazy_load_frame_observer_ &&
IsFrameLazyLoadable(GetDocument(), url, loading_lazy_set,
should_lazy_load_children_)) {
// By default, avoid deferring subresources inside a lazily loaded frame.
// Avoid automatically deferring subresources inside a lazily loaded frame.
// This will make it possible for subresources in hidden frames to load that
// will never be visible, as well as make it so that deferred frames that
// have multiple layers of iframes inside them can load faster once they're
......@@ -468,9 +485,7 @@ bool HTMLFrameOwnerElement::LoadOrRedirectSubframe(
if (RuntimeEnabledFeatures::LazyFrameVisibleLoadTimeMetricsEnabled())
lazy_load_frame_observer_->StartTrackingVisibilityMetrics();
if (RuntimeEnabledFeatures::LazyFrameLoadingEnabled() &&
GetDocument().GetSettings() &&
GetDocument().GetSettings()->GetLazyLoadEnabled()) {
if (ShouldLazilyLoadFrame(GetDocument(), loading_lazy_set)) {
lazy_load_frame_observer_->DeferLoadUntilNearViewport(request,
child_load_type);
return true;
......
......@@ -92,10 +92,14 @@ class LazyLoadFramesParamsTest
: scoped_lazy_frame_loading_for_test_(
std::get<LazyFrameLoadingFeatureStatus>(GetParam()) ==
LazyFrameLoadingFeatureStatus::kEnabled),
scoped_automatic_lazy_frame_loading_for_test_(
std::get<LazyFrameLoadingFeatureStatus>(GetParam()) ==
LazyFrameLoadingFeatureStatus::kEnabled),
scoped_restrict_automatic_lazy_frame_loading_to_data_saver_for_test_(
false),
scoped_lazy_frame_visible_load_time_metrics_for_test_(
std::get<LazyFrameVisibleLoadTimeFeatureStatus>(GetParam()) ==
LazyFrameVisibleLoadTimeFeatureStatus::kEnabled),
scoped_restrict_lazy_frame_loading_to_data_saver_for_test_(false) {}
LazyFrameVisibleLoadTimeFeatureStatus::kEnabled) {}
void SetUp() override {
WebFrameClient().SetEffectiveConnectionTypeForTesting(
......@@ -247,10 +251,12 @@ class LazyLoadFramesParamsTest
private:
ScopedLazyFrameLoadingForTest scoped_lazy_frame_loading_for_test_;
ScopedAutomaticLazyFrameLoadingForTest
scoped_automatic_lazy_frame_loading_for_test_;
ScopedRestrictAutomaticLazyFrameLoadingToDataSaverForTest
scoped_restrict_automatic_lazy_frame_loading_to_data_saver_for_test_;
ScopedLazyFrameVisibleLoadTimeMetricsForTest
scoped_lazy_frame_visible_load_time_metrics_for_test_;
ScopedRestrictLazyFrameLoadingToDataSaverForTest
scoped_restrict_lazy_frame_loading_to_data_saver_for_test_;
HistogramTester histogram_tester_;
};
......@@ -738,7 +744,7 @@ TEST_P(LazyLoadFramesParamsTest, JavascriptStringFrameUrl) {
}
TEST_P(LazyLoadFramesParamsTest,
CrossOriginFrameFarFromViewportWithLazyLoadAttrOff) {
CrossOriginFrameFarFromViewportWithLoadingAttributeEager) {
SimRequest main_resource("https://example.com/", "text/html");
SimRequest child_frame_resource("https://crossorigin.com/subframe.html",
"text/html");
......@@ -779,7 +785,7 @@ TEST_P(LazyLoadFramesParamsTest,
}
TEST_P(LazyLoadFramesParamsTest,
LoadSameOriginFrameFarFromViewportWithLazyLoadAttributeOn) {
LoadSameOriginFrameFarFromViewportWithLoadingAttributeLazy) {
SimRequest main_resource("https://example.com/", "text/html");
base::Optional<SimRequest> child_frame_resource;
......@@ -876,7 +882,7 @@ TEST_P(LazyLoadFramesParamsTest,
}
TEST_P(LazyLoadFramesParamsTest,
LoadCrossOriginFrameFarFromViewportThenSetLazyLoadAttributeOff) {
LoadCrossOriginFrameFarFromViewportThenSetLoadingAttributeEager) {
SimRequest main_resource("https://example.com/", "text/html");
base::Optional<SimRequest> child_frame_resource;
......@@ -945,7 +951,7 @@ TEST_P(LazyLoadFramesParamsTest,
}
TEST_P(LazyLoadFramesParamsTest,
NestedFrameWithLazyLoadAttributeOnInFrameWithNoLazyLoadAttribute) {
NestedFrameWithLazyLoadAttributeOnInFrameWithNoLoadingAttribute) {
std::unique_ptr<SimRequest> child_frame_resource =
LoadPageWithCrossOriginFrameFarFromViewport();
......@@ -989,7 +995,7 @@ TEST_P(LazyLoadFramesParamsTest,
}
TEST_P(LazyLoadFramesParamsTest,
NestedFrameWithLazyLoadAttributeOnInFrameWithLazyLoadAttributeOff) {
NestedFrameWithLazyLoadAttributeOnInFrameWithLoadingAttributeEager) {
SimRequest main_resource("https://example.com/", "text/html");
SimRequest child_frame_resource("https://crossorigin.com/subframe.html",
"text/html");
......@@ -1040,7 +1046,7 @@ TEST_P(LazyLoadFramesParamsTest,
}
TEST_P(LazyLoadFramesParamsTest,
NestedFrameWithLazyLoadAttributeOffInFrameWithLazyLoadAttributeOff) {
NestedFrameWithLazyLoadAttributeOffInFrameWithLoadingAttributeEager) {
SimRequest main_resource("https://example.com/", "text/html");
SimRequest child_frame_resource("https://crossorigin.com/subframe.html",
"text/html");
......@@ -1108,6 +1114,7 @@ class LazyLoadFramesTest : public SimTest {
static constexpr int kLoadingDistanceThresholdPx = 1000;
void SetUp() override {
GetNetworkStateNotifier().SetSaveDataEnabled(false);
WebFrameClient().SetEffectiveConnectionTypeForTesting(
WebEffectiveConnectionType::kTypeUnknown);
......@@ -1119,6 +1126,7 @@ class LazyLoadFramesTest : public SimTest {
settings.SetLazyFrameLoadingDistanceThresholdPxUnknown(
kLoadingDistanceThresholdPx);
settings.SetLazyLoadEnabled(true);
settings.SetDataSaverHoldbackWebApi(false);
}
void TearDown() override {
......@@ -1198,67 +1206,143 @@ class LazyLoadFramesTest : public SimTest {
}
};
TEST_F(LazyLoadFramesTest, LazyLoadWhenNotRestricted) {
TEST_F(LazyLoadFramesTest, LazyLoadWhenDisabledAndAttrLazy) {
ScopedLazyFrameLoadingForTest scoped_lazy_frame_loading_for_test(false);
ScopedAutomaticLazyFrameLoadingForTest
scoped_automatic_lazy_frame_loading_for_test(false);
ScopedRestrictAutomaticLazyFrameLoadingToDataSaverForTest
scoped_restrict_automatic_lazy_frame_loading_to_data_saver_for_test_(
false);
TestCrossOriginFrameIsImmediatelyLoaded("loading='lazy'");
}
TEST_F(LazyLoadFramesTest, LazyLoadWhenAttrLazy) {
ScopedLazyFrameLoadingForTest scoped_lazy_frame_loading_for_test(true);
ScopedRestrictLazyFrameLoadingToDataSaverForTest
scoped_restrict_lazy_frame_loading_to_data_saver_for_test_(false);
ScopedAutomaticLazyFrameLoadingForTest
scoped_automatic_lazy_frame_loading_for_test(false);
ScopedRestrictAutomaticLazyFrameLoadingToDataSaverForTest
scoped_restrict_automatic_lazy_frame_loading_to_data_saver_for_test_(
false);
TestCrossOriginFrameIsLazilyLoaded("");
TestCrossOriginFrameIsLazilyLoaded("loading='lazy'");
}
TEST_F(LazyLoadFramesTest,
LazyLoadWhenDataSaverDisabledAndNotRestrictedAttrOff) {
TEST_F(LazyLoadFramesTest, LazyLoadWhenAttrEager) {
ScopedLazyFrameLoadingForTest scoped_lazy_frame_loading_for_test(true);
ScopedRestrictLazyFrameLoadingToDataSaverForTest
scoped_restrict_lazy_frame_loading_to_data_saver_for_test_(false);
ScopedAutomaticLazyFrameLoadingForTest
scoped_automatic_lazy_frame_loading_for_test(false);
ScopedRestrictAutomaticLazyFrameLoadingToDataSaverForTest
scoped_restrict_automatic_lazy_frame_loading_to_data_saver_for_test_(
false);
TestCrossOriginFrameIsImmediatelyLoaded("loading='eager'");
}
TEST_F(LazyLoadFramesTest, LazyLoadWhenDataSaverDisabledAndRestricted) {
TEST_F(LazyLoadFramesTest, LazyLoadWhenAutomaticAndAttrLazy) {
ScopedLazyFrameLoadingForTest scoped_lazy_frame_loading_for_test(true);
ScopedRestrictLazyFrameLoadingToDataSaverForTest
scoped_restrict_lazy_frame_loading_to_data_saver_for_test_(true);
ScopedAutomaticLazyFrameLoadingForTest
scoped_automatic_lazy_frame_loading_for_test(true);
ScopedRestrictAutomaticLazyFrameLoadingToDataSaverForTest
scoped_restrict_automatic_lazy_frame_loading_to_data_saver_for_test_(
false);
GetNetworkStateNotifier().SetSaveDataEnabled(false);
WebView().GetPage()->GetSettings().SetDataSaverHoldbackWebApi(false);
TestCrossOriginFrameIsImmediatelyLoaded("");
TestCrossOriginFrameIsLazilyLoaded("loading='lazy'");
}
TEST_F(LazyLoadFramesTest, LazyLoadWhenDataSaverEnabledHoldbackAndRestricted) {
TEST_F(LazyLoadFramesTest, LazyLoadWhenAutomaticAndAttrEager) {
ScopedLazyFrameLoadingForTest scoped_lazy_frame_loading_for_test(true);
ScopedRestrictLazyFrameLoadingToDataSaverForTest
scoped_restrict_lazy_frame_loading_to_data_saver_for_test_(true);
ScopedAutomaticLazyFrameLoadingForTest
scoped_automatic_lazy_frame_loading_for_test(true);
ScopedRestrictAutomaticLazyFrameLoadingToDataSaverForTest
scoped_restrict_automatic_lazy_frame_loading_to_data_saver_for_test_(
false);
TestCrossOriginFrameIsImmediatelyLoaded("loading='eager'");
}
TEST_F(LazyLoadFramesTest, LazyLoadWhenAutomaticRestrictedAndAttrLazy) {
ScopedLazyFrameLoadingForTest scoped_lazy_frame_loading_for_test(true);
ScopedAutomaticLazyFrameLoadingForTest
scoped_automatic_lazy_frame_loading_for_test(true);
ScopedRestrictAutomaticLazyFrameLoadingToDataSaverForTest
scoped_restrict_automatic_lazy_frame_loading_to_data_saver_for_test_(
true);
GetNetworkStateNotifier().SetSaveDataEnabled(true);
WebView().GetPage()->GetSettings().SetDataSaverHoldbackWebApi(true);
TestCrossOriginFrameIsLazilyLoaded("loading='lazy'");
TestCrossOriginFrameIsImmediatelyLoaded("");
}
TEST_F(LazyLoadFramesTest, LazyLoadWhenDataSaverEnabledAndRestricted) {
TEST_F(LazyLoadFramesTest, LazyLoadWhenAutomaticRestrictedAndAttrEager) {
ScopedLazyFrameLoadingForTest scoped_lazy_frame_loading_for_test(true);
ScopedRestrictLazyFrameLoadingToDataSaverForTest
scoped_restrict_lazy_frame_loading_to_data_saver_for_test_(true);
ScopedAutomaticLazyFrameLoadingForTest
scoped_automatic_lazy_frame_loading_for_test(true);
ScopedRestrictAutomaticLazyFrameLoadingToDataSaverForTest
scoped_restrict_automatic_lazy_frame_loading_to_data_saver_for_test_(
true);
GetNetworkStateNotifier().SetSaveDataEnabled(true);
WebView().GetPage()->GetSettings().SetDataSaverHoldbackWebApi(false);
TestCrossOriginFrameIsImmediatelyLoaded("loading='eager'");
}
TEST_F(LazyLoadFramesTest, LazyLoadWhenAutomaticDisabled) {
ScopedLazyFrameLoadingForTest scoped_lazy_frame_loading_for_test(true);
ScopedAutomaticLazyFrameLoadingForTest
scoped_automatic_lazy_frame_loading_for_test(false);
ScopedRestrictAutomaticLazyFrameLoadingToDataSaverForTest
scoped_restrict_automatic_lazy_frame_loading_to_data_saver_for_test_(
false);
TestCrossOriginFrameIsImmediatelyLoaded("");
}
TEST_F(LazyLoadFramesTest, LazyLoadWhenAutomaticEnabled) {
ScopedLazyFrameLoadingForTest scoped_lazy_frame_loading_for_test(true);
ScopedAutomaticLazyFrameLoadingForTest
scoped_automatic_lazy_frame_loading_for_test(true);
ScopedRestrictAutomaticLazyFrameLoadingToDataSaverForTest
scoped_restrict_automatic_lazy_frame_loading_to_data_saver_for_test_(
false);
TestCrossOriginFrameIsLazilyLoaded("");
}
TEST_F(LazyLoadFramesTest, LazyLoadWhenDataSaverDisabledAndRestrictedAttrOn) {
TEST_F(LazyLoadFramesTest, LazyLoadWhenDataSaverDisabledAndRestricted) {
ScopedLazyFrameLoadingForTest scoped_lazy_frame_loading_for_test(true);
ScopedRestrictLazyFrameLoadingToDataSaverForTest
scoped_restrict_lazy_frame_loading_to_data_saver_for_test_(true);
ScopedAutomaticLazyFrameLoadingForTest
scoped_automatic_lazy_frame_loading_for_test(true);
ScopedRestrictAutomaticLazyFrameLoadingToDataSaverForTest
scoped_restrict_automatic_lazy_frame_loading_to_data_saver_for_test_(
true);
GetNetworkStateNotifier().SetSaveDataEnabled(false);
WebView().GetPage()->GetSettings().SetDataSaverHoldbackWebApi(false);
TestCrossOriginFrameIsImmediatelyLoaded("");
}
// Even when restricted to data saver, the attribute should be respected.
TestCrossOriginFrameIsLazilyLoaded("loading='lazy'");
TEST_F(LazyLoadFramesTest, LazyLoadWhenDataSaverEnabledHoldbackAndRestricted) {
ScopedLazyFrameLoadingForTest scoped_lazy_frame_loading_for_test(true);
ScopedAutomaticLazyFrameLoadingForTest
scoped_automatic_lazy_frame_loading_for_test(true);
ScopedRestrictAutomaticLazyFrameLoadingToDataSaverForTest
scoped_restrict_automatic_lazy_frame_loading_to_data_saver_for_test_(
true);
GetNetworkStateNotifier().SetSaveDataEnabled(true);
WebView().GetPage()->GetSettings().SetDataSaverHoldbackWebApi(true);
TestCrossOriginFrameIsImmediatelyLoaded("");
}
TEST_F(LazyLoadFramesTest, LazyLoadWhenDataSaverEnabledAndRestricted) {
ScopedLazyFrameLoadingForTest scoped_lazy_frame_loading_for_test(true);
ScopedAutomaticLazyFrameLoadingForTest
scoped_automatic_lazy_frame_loading_for_test(true);
ScopedRestrictAutomaticLazyFrameLoadingToDataSaverForTest
scoped_restrict_automatic_lazy_frame_loading_to_data_saver_for_test_(
true);
GetNetworkStateNotifier().SetSaveDataEnabled(true);
TestCrossOriginFrameIsLazilyLoaded("");
}
} // namespace
} // namespace blink
......@@ -116,6 +116,10 @@ void WebRuntimeFeatures::EnableAudioOutputDevices(bool enable) {
RuntimeEnabledFeatures::SetAudioOutputDevicesEnabled(enable);
}
void WebRuntimeFeatures::EnableAutomaticLazyFrameLoading(bool enable) {
RuntimeEnabledFeatures::SetAutomaticLazyFrameLoadingEnabled(enable);
}
void WebRuntimeFeatures::EnableAutomaticLazyImageLoading(bool enable) {
RuntimeEnabledFeatures::SetAutomaticLazyImageLoadingEnabled(enable);
}
......@@ -516,9 +520,10 @@ void WebRuntimeFeatures::EnableResourceLoadScheduler(bool enable) {
RuntimeEnabledFeatures::SetResourceLoadSchedulerEnabled(enable);
}
void WebRuntimeFeatures::EnableRestrictLazyFrameLoadingToDataSaver(
void WebRuntimeFeatures::EnableRestrictAutomaticLazyFrameLoadingToDataSaver(
bool enable) {
RuntimeEnabledFeatures::SetRestrictLazyFrameLoadingToDataSaverEnabled(enable);
RuntimeEnabledFeatures::
SetRestrictAutomaticLazyFrameLoadingToDataSaverEnabled(enable);
}
void WebRuntimeFeatures::EnableRestrictAutomaticLazyImageLoadingToDataSaver(
......
......@@ -132,6 +132,10 @@
name: "AudioWorkletRealtimeThread",
status: "experimental",
},
{
name: "AutomaticLazyFrameLoading",
depends_on: ["LazyFrameLoading"],
},
{
name: "AutomaticLazyImageLoading",
depends_on: ["LazyImageLoading"],
......@@ -1241,11 +1245,12 @@
status: "stable",
},
{
name: "RestrictAutomaticLazyImageLoadingToDataSaver",
depends_on: ["AutomaticLazyImageLoading"],
name: "RestrictAutomaticLazyFrameLoadingToDataSaver",
depends_on: ["AutomaticLazyFrameLoading"],
},
{
name: "RestrictLazyFrameLoadingToDataSaver",
name: "RestrictAutomaticLazyImageLoadingToDataSaver",
depends_on: ["AutomaticLazyImageLoading"],
},
{
name: "RtcAudioJitterBufferMaxPackets",
......
......@@ -657,8 +657,8 @@
{
"prefix" : "lazyload-policy",
"base": "external/wpt/feature-policy/experimental-features/lazyload",
"args": ["--enable-blink-features=LazyFrameLoading,LazyImageLoading,AutomaticLazyImageLoading",
"--disable-blink-features=RestrictAutomaticLazyImageLoadingToDataSaver"]
"args": ["--enable-blink-features=LazyFrameLoading,AutomaticLazyFrameLoading,LazyImageLoading,AutomaticLazyImageLoading",
"--disable-blink-features=RestrictAutomaticLazyFrameLoadingToDataSaver,RestrictAutomaticLazyImageLoadingToDataSaver"]
},
{
"prefix": "display-lock",
......
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