diff --git a/build-system/global-configs/canary-config.json b/build-system/global-configs/canary-config.json
index a2322ad9f52e5..da20a87a74144 100644
--- a/build-system/global-configs/canary-config.json
+++ b/build-system/global-configs/canary-config.json
@@ -15,6 +15,7 @@
"amp-auto-ads-adsense-holdout": 0.1,
"amp-auto-ads-no-op-experiment": 0.05,
"amp-consent-restrict-fullscreen": 1,
+ "amp-list-init-from-state": 1,
"amp-mega-menu": 1,
"amp-nested-menu": 1,
"amp-playbuzz": 1,
diff --git a/build-system/global-configs/prod-config.json b/build-system/global-configs/prod-config.json
index 9c09aa65d6bf1..00c9f64479b04 100644
--- a/build-system/global-configs/prod-config.json
+++ b/build-system/global-configs/prod-config.json
@@ -15,6 +15,7 @@
"amp-auto-ads-adsense-holdout": 0.1,
"amp-auto-ads-no-op-experiment": 0.05,
"amp-consent-restrict-fullscreen": 1,
+ "amp-list-init-from-state": 1,
"amp-mega-menu": 1,
"amp-nested-menu": 1,
"amp-playbuzz": 1,
diff --git a/examples/amp-list.state.html b/examples/amp-list.state.html
index 26f83d259fc7a..8ba27452e8f47 100644
--- a/examples/amp-list.state.html
+++ b/examples/amp-list.state.html
@@ -20,7 +20,6 @@
-
Refresh All Lists
diff --git a/extensions/amp-list/0.1/amp-list.js b/extensions/amp-list/0.1/amp-list.js
index c3c603ae59064..364b724f1d8bd 100644
--- a/extensions/amp-list/0.1/amp-list.js
+++ b/extensions/amp-list/0.1/amp-list.js
@@ -60,6 +60,7 @@ import {
setupJsonFetchInit,
} from '../../../src/utils/xhr-utils';
import {isArray, toArray} from '../../../src/types';
+import {isExperimentOn} from '../../../src/experiments';
import {px, setStyles, toggle} from '../../../src/style';
import {setDOM} from '../../../third_party/set-dom/set-dom';
import {startsWith} from '../../../src/string';
@@ -351,7 +352,10 @@ export class AmpList extends AMP.BaseElement {
* @private
*/
isAmpStateSrc_(src) {
- return startsWith(src, AMP_STATE_URI_SCHEME);
+ return (
+ isExperimentOn(this.win, 'amp-list-init-from-state') &&
+ startsWith(src, AMP_STATE_URI_SCHEME)
+ );
}
/**
diff --git a/extensions/amp-list/0.1/test/test-amp-list.js b/extensions/amp-list/0.1/test/test-amp-list.js
index a6edc1ed1f043..7d35dc5f81e3d 100644
--- a/extensions/amp-list/0.1/test/test-amp-list.js
+++ b/extensions/amp-list/0.1/test/test-amp-list.js
@@ -20,6 +20,10 @@ import {AmpEvents} from '../../../../src/amp-events';
import {AmpList} from '../amp-list';
import {Deferred} from '../../../../src/utils/promise';
import {Services} from '../../../../src/services';
+import {
+ resetExperimentTogglesForTesting,
+ toggleExperiment,
+} from '../../../../src/experiments';
describes.repeated(
'amp-list',
@@ -806,6 +810,8 @@ describes.repeated(
});
it('"amp-state:" uri should skip rendering and emit an error', () => {
+ toggleExperiment(win, 'amp-list-init-from-state', true);
+
const ampStateEl = doc.createElement('amp-state');
ampStateEl.setAttribute('id', 'okapis');
const ampStateJson = doc.createElement('script');
@@ -1224,14 +1230,24 @@ describes.repeated(
});
describe('Using amp-state: protocol', () => {
+ const experimentName = 'amp-list-init-from-state';
+
beforeEach(() => {
+ resetExperimentTogglesForTesting(win);
element = createAmpListElement();
element.setAttribute('src', 'amp-state:okapis');
element.toggleLoading = () => {};
list = createAmpList(element);
});
+ it('should throw an error if used without the experiment enabled', async () => {
+ const errorMsg = /Invalid value: amp-state:okapis/;
+ expectAsyncConsoleError(errorMsg);
+ expect(list.layoutCallback()).to.eventually.throw(errorMsg);
+ });
+
it('should throw error if there is no associated amp-state el', async () => {
+ toggleExperiment(win, experimentName, true);
bind.getStateAsync = () => Promise.reject();
const errorMsg = /element with id 'okapis' was not found/;
@@ -1240,6 +1256,7 @@ describes.repeated(
});
it('should log an error if amp-bind was not included', async () => {
+ toggleExperiment(win, experimentName, true);
Services.bindForDocOrNull.returns(Promise.resolve(null));
const ampStateEl = doc.createElement('amp-state');
@@ -1255,6 +1272,7 @@ describes.repeated(
});
it('should render a list using local data', async () => {
+ toggleExperiment(win, experimentName, true);
bind.getStateAsync = () => Promise.resolve({items: [1, 2, 3]});
const ampStateEl = doc.createElement('amp-state');
@@ -1274,6 +1292,7 @@ describes.repeated(
});
it('should render a list using async data', async () => {
+ toggleExperiment(win, experimentName, true);
const {resolve, promise} = new Deferred();
bind.getStateAsync = () => promise;
diff --git a/extensions/amp-list/amp-list.md b/extensions/amp-list/amp-list.md
index b6d3369b19a24..d05f7e6398b5e 100644
--- a/extensions/amp-list/amp-list.md
+++ b/extensions/amp-list/amp-list.md
@@ -222,19 +222,32 @@ In several cases, we may need the `` to resize on user interaction. Fo
### Initialization from amp-state
-In some cases, it may be desirable to have your `` component initialize off of `` rather than from a json endpoint.
-You may do that by utilizing the `amp-state:` protocol in the `src` attribute. The data in state must follow the same rules as the json that
-would have been retrieved from an endpoint. For example,
+For cases where you can server-side-render the JSON for an ``, it may be
+desirable to have your `` component initialize off of `` rather
+than from a JSON endpoint. This allows the component to render faster by skipping the fetch,
+but also means the data may be stale if served from the AMP Cache.
+
+You may enable this by following two steps:
+
+1. Add the [amp-bind](https://amp.dev/documentation/components/amp-bind/) script to the `` of your document.
+2. Utilize an `amp-state:` protocol in the `src` attribute. The data in state
+ must follow the same rules as the JSON that would have been retrieved from an endpoint.
+
+For example,
```html
-
+
-...
+
+
+ {{id}}
+
+
```
## Attributes