Commit 5fe94897 authored by Tibor Goldschwendt's avatar Tibor Goldschwendt Committed by Chromium LUCI CQ

[ntp] Add timeout which aborts module load

All modules wait for each other before showing up. A timeout protects
against long loading modules. It is not clear yet what the best timeout
is. Thus, the timeout duration is Finch-configurable.

Fixed: 1154008
Change-Id: I1dcd29a168c4f01408979c439edb47903837371f
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2623718
Commit-Queue: Tibor Goldschwendt <tiborg@chromium.org>
Reviewed-by: default avatarMoe Ahmadi <mahmadi@chromium.org>
Cr-Commit-Position: refs/heads/master@{#843371}
parent c2c619e2
...@@ -536,7 +536,8 @@ class AppElement extends PolymerElement { ...@@ -536,7 +536,8 @@ class AppElement extends PolymerElement {
return; return;
} }
this.moduleDescriptors_ = this.moduleDescriptors_ =
await ModuleRegistry.getInstance().initializeModules(); await ModuleRegistry.getInstance().initializeModules(
loadTimeData.getInteger('modulesLoadTimeout'));
} }
/** @private */ /** @private */
......
...@@ -47,9 +47,22 @@ export class ModuleDescriptor { ...@@ -47,9 +47,22 @@ export class ModuleDescriptor {
return this.element_; return this.element_;
} }
async initialize() { /**
* Initializes the module. On success, |this.element| will be populated after
* the returned promise has resolved.
* @param {number} timeout Timeout in milliseconds after which initialization
* aborts.
* @return {!Promise}
*/
async initialize(timeout) {
const loadStartTime = BrowserProxy.getInstance().now(); const loadStartTime = BrowserProxy.getInstance().now();
this.element_ = await this.initializeCallback_(); this.element_ = await Promise.race([
this.initializeCallback_(), new Promise(resolve => {
BrowserProxy.getInstance().setTimeout(() => {
resolve(null);
}, timeout);
})
]);
if (!this.element_) { if (!this.element_) {
return; return;
} }
......
...@@ -29,10 +29,12 @@ export class ModuleRegistry { ...@@ -29,10 +29,12 @@ export class ModuleRegistry {
/** /**
* Initializes the modules previously set via |registerModules| and returns * Initializes the modules previously set via |registerModules| and returns
* the initialized descriptors. * the initialized descriptors.
* @param {number} timeout Timeout in milliseconds after which initialization
* of a particular module aborts.
* @return {!Promise<!Array<!ModuleDescriptor>>} * @return {!Promise<!Array<!ModuleDescriptor>>}
*/ */
async initializeModules() { async initializeModules(timeout) {
await Promise.all(this.descriptors_.map(d => d.initialize())); await Promise.all(this.descriptors_.map(d => d.initialize(timeout)));
return this.descriptors_.filter(descriptor => !!descriptor.element); return this.descriptors_.filter(descriptor => !!descriptor.element);
} }
} }
......
...@@ -78,4 +78,22 @@ TEST(NTPFeaturesTest, LocalHistoryRepeatableQueriesFrequencyExponent) { ...@@ -78,4 +78,22 @@ TEST(NTPFeaturesTest, LocalHistoryRepeatableQueriesFrequencyExponent) {
EXPECT_EQ(2, frequency_exponent); EXPECT_EQ(2, frequency_exponent);
} }
TEST(NTPFeaturesTest, ModulesLoadTimeout) {
base::test::ScopedFeatureList scoped_feature_list_;
// The default value can be overridden.
scoped_feature_list_.InitWithFeaturesAndParameters(
{{kModules, {{kNtpModulesLoadTimeoutMillisecondsParam, "123"}}}}, {});
base::TimeDelta timeout = GetModulesLoadTimeout();
EXPECT_EQ(123, timeout.InMilliseconds());
// If the timeout is not parsable to an unsigned integer, the default value is
// used.
scoped_feature_list_.Reset();
scoped_feature_list_.InitWithFeaturesAndParameters(
{{kModules, {{kNtpModulesLoadTimeoutMillisecondsParam, "j"}}}}, {});
timeout = GetModulesLoadTimeout();
EXPECT_EQ(3, timeout.InSeconds());
}
} // namespace ntp_features } // namespace ntp_features
...@@ -100,6 +100,8 @@ content::WebUIDataSource* CreateNewTabPageUiHtmlSource(Profile* profile) { ...@@ -100,6 +100,8 @@ content::WebUIDataSource* CreateNewTabPageUiHtmlSource(Profile* profile) {
base::FeatureList::IsEnabled(ntp_features::kWebUIThemeModeDoodles)); base::FeatureList::IsEnabled(ntp_features::kWebUIThemeModeDoodles));
source->AddBoolean("modulesEnabled", source->AddBoolean("modulesEnabled",
base::FeatureList::IsEnabled(ntp_features::kModules)); base::FeatureList::IsEnabled(ntp_features::kModules));
source->AddInteger("modulesLoadTimeout",
ntp_features::GetModulesLoadTimeout().InMilliseconds());
static constexpr webui::LocalizedString kStrings[] = { static constexpr webui::LocalizedString kStrings[] = {
{"doneButton", IDS_DONE}, {"doneButton", IDS_DONE},
......
...@@ -31,6 +31,7 @@ suite('NewTabPageAppTest', () => { ...@@ -31,6 +31,7 @@ suite('NewTabPageAppTest', () => {
suiteSetup(() => { suiteSetup(() => {
loadTimeData.overrideValues({ loadTimeData.overrideValues({
modulesLoadTimeout: 0,
realboxEnabled: false, realboxEnabled: false,
}); });
}); });
......
...@@ -53,4 +53,20 @@ suite('NewTabPageModulesModuleDescriptorTest', () => { ...@@ -53,4 +53,20 @@ suite('NewTabPageModulesModuleDescriptorTest', () => {
assertEquals(null, moduleDescriptor.element); assertEquals(null, moduleDescriptor.element);
assertEquals(0, testProxy.handler.getCallCount('onModuleLoaded')); assertEquals(0, testProxy.handler.getCallCount('onModuleLoaded'));
}); });
test('module load times out', async () => {
// Arrange.
const moduleDescriptor = new ModuleDescriptor(
'foo', 100, () => new Promise(() => {}) /* Never resolves. */);
// Act.
const initializePromise = moduleDescriptor.initialize(123);
const [callback, timeout] = await testProxy.whenCalled('setTimeout');
callback();
await initializePromise;
// Assert.
assertEquals(null, moduleDescriptor.element);
assertEquals(123, timeout);
});
}); });
...@@ -18,7 +18,7 @@ suite('NewTabPageModulesModuleRegistryTest', () => { ...@@ -18,7 +18,7 @@ suite('NewTabPageModulesModuleRegistryTest', () => {
]); ]);
// Act. // Act.
const modulesPromise = ModuleRegistry.getInstance().initializeModules(); const modulesPromise = ModuleRegistry.getInstance().initializeModules(0);
// Delayed promise resolution to test async module instantiation. // Delayed promise resolution to test async module instantiation.
bazModuleResolver.resolve(bazModule); bazModuleResolver.resolve(bazModule);
const modules = await modulesPromise; const modules = await modulesPromise;
......
...@@ -99,6 +99,8 @@ const char kNtpRepeatableQueriesFrequencyExponentParam[] = ...@@ -99,6 +99,8 @@ const char kNtpRepeatableQueriesFrequencyExponentParam[] =
const char kNtpRepeatableQueriesInsertPositionParam[] = const char kNtpRepeatableQueriesInsertPositionParam[] =
"NtpRepeatableQueriesInsertPosition"; "NtpRepeatableQueriesInsertPosition";
const char kNtpModulesLoadTimeoutMillisecondsParam[] =
"NtpModulesLoadTimeoutMillisecondsParam";
const char kNtpStatefulTasksModuleDataParam[] = const char kNtpStatefulTasksModuleDataParam[] =
"NtpStatefulTasksModuleDataParam"; "NtpStatefulTasksModuleDataParam";
...@@ -156,4 +158,16 @@ RepeatableQueriesInsertPosition GetRepeatableQueriesInsertPosition() { ...@@ -156,4 +158,16 @@ RepeatableQueriesInsertPosition GetRepeatableQueriesInsertPosition() {
: RepeatableQueriesInsertPosition::kStart; : RepeatableQueriesInsertPosition::kStart;
} }
base::TimeDelta GetModulesLoadTimeout() {
std::string param_value = base::GetFieldTrialParamValueByFeature(
kModules, kNtpModulesLoadTimeoutMillisecondsParam);
// If the field trial param is not found or cannot be parsed to an unsigned
// integer, return the default value.
unsigned int param_value_as_int = 0;
if (!base::StringToUint(param_value, &param_value_as_int)) {
return base::TimeDelta::FromSeconds(3);
}
return base::TimeDelta::FromMilliseconds(param_value_as_int);
}
} // namespace ntp_features } // namespace ntp_features
...@@ -57,6 +57,8 @@ enum class RepeatableQueriesInsertPosition { ...@@ -57,6 +57,8 @@ enum class RepeatableQueriesInsertPosition {
kEnd, // At the end of MV tiles. kEnd, // At the end of MV tiles.
}; };
// Parameter determining the module load timeout.
extern const char kNtpModulesLoadTimeoutMillisecondsParam[];
// Parameter determining the type of stateful data to request. // Parameter determining the type of stateful data to request.
extern const char kNtpStatefulTasksModuleDataParam[]; extern const char kNtpStatefulTasksModuleDataParam[];
...@@ -72,6 +74,8 @@ double GetLocalHistoryRepeatableQueriesFrequencyExponent(); ...@@ -72,6 +74,8 @@ double GetLocalHistoryRepeatableQueriesFrequencyExponent();
// queries should be inserted. // queries should be inserted.
RepeatableQueriesInsertPosition GetRepeatableQueriesInsertPosition(); RepeatableQueriesInsertPosition GetRepeatableQueriesInsertPosition();
// Returns the timeout after which the load of a module should be aborted.
base::TimeDelta GetModulesLoadTimeout();
} // namespace ntp_features } // namespace ntp_features
#endif // COMPONENTS_SEARCH_NTP_FEATURES_H_ #endif // COMPONENTS_SEARCH_NTP_FEATURES_H_
...@@ -2349,6 +2349,9 @@ ...@@ -2349,6 +2349,9 @@
"experiments": [ "experiments": [
{ {
"name": "Enabled", "name": "Enabled",
"params": {
"NtpModulesLoadTimeoutMillisecondsParam": "3000"
},
"enable_features": [ "enable_features": [
"NtpModules", "NtpModules",
"NtpShoppingTasksModule" "NtpShoppingTasksModule"
......
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