Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Simplify amp-state/bindReady race fix #21052

Merged
merged 4 commits into from Feb 26, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
27 changes: 9 additions & 18 deletions extensions/amp-bind/0.1/bind-impl.js
Expand Up @@ -186,15 +186,11 @@ export class Bind {
return this.initialize_(root);
});

/** @private {?Node} */
this.rootNode_ = null;

/** @private {Promise} */
this.setStatePromise_ = null;

/** @private @const {!../../../src/utils/signals.Signals} */
this.signals_ = new Signals();
this.signals_.whenSignal('READY').then(() => this.onReady_());

// Install debug tools.
const g = self.AMP;
Expand Down Expand Up @@ -234,7 +230,6 @@ export class Bind {
dev().info(TAG, 'state:', this.state_);

if (opt_skipEval) {
this.checkReadiness_();
return Promise.resolve();
}

Expand Down Expand Up @@ -454,8 +449,6 @@ export class Bind {
* @private
*/
initialize_(root) {
this.rootNode_ = root;

// Disallow URL property bindings in AMP4EMAIL.
const allowUrlProperties = !this.isAmp4Email_();
this.validator_ = new BindValidator(allowUrlProperties);
Expand All @@ -475,25 +468,23 @@ export class Bind {
if (getMode().development) {
return this.evaluate_().then(results => this.verify_(results));
}
}).then(() => this.checkReadiness_());
}).then(() => this.checkReadiness_(root));
}

/**
* Bind is "ready" when its initialization completes _and_ all <amp-state>
* elements' local data is parsed and processed (not remote data).
* @param {!Node} root
* @private
*/
checkReadiness_() {
if (!this.rootNode_) {
return;
}
const unbuilt =
this.rootNode_.querySelectorAll('AMP-STATE.i-amphtml-notbuilt');
if (unbuilt.length > 0) {
return;
checkReadiness_(root) {
const ampStates = root.querySelectorAll('AMP-STATE');
if (ampStates.length > 0) {
const whenBuilt = toArray(ampStates).map(el => el.whenBuilt());
Promise.all(whenBuilt).then(() => this.onReady_());
} else {
this.onReady_();
}
// Use a signal to ensure that onReady_() is only invoked once.
this.initializePromise_.then(() => this.signals_.signal('READY'));
}

/**
Expand Down
31 changes: 10 additions & 21 deletions extensions/amp-bind/0.1/test/integration/test-bind-impl.js
Expand Up @@ -293,39 +293,26 @@ describe.configure().ifChrome().run('Bind', function() {
});

it('should send "bindReady" to viewer on init', () => {
const signals = bind.signals();
sandbox.spy(signals, 'signal');

expect(signals.signal).to.not.be.called;
expect(viewer.sendMessage).to.not.be.called;

return onBindReady(env, bind).then(() => {
expect(signals.signal).to.be.calledWith('READY');
return signals.whenSignal('READY');
}).then(() => {
expect(viewer.sendMessage).to.be.calledOnce;
expect(viewer.sendMessage).to.be.calledWith('bindReady');
});
});

it('should not send "bindReady" until all <amp-state> are built', () => {
const element = createElement(env, container, '', 'amp-state', true);
element.classList.add('i-amphtml-notbuilt');

const signals = bind.signals();
sandbox.spy(signals, 'signal');

expect(signals.signal).to.not.be.called;
expect(viewer.sendMessage).to.not.be.called;
let buildAmpState;
const builtPromise = new Promise(resolve => {
buildAmpState = resolve;
});
element.whenBuilt = () => builtPromise;

return onBindReady(env, bind).then(() => {
expect(signals.signal).to.not.be.called;

// Simulate <amp-state> buildCallback().
element.classList.remove('i-amphtml-notbuilt');
bind.setState({}, /* opt_skipEval */ true);

return signals.whenSignal('READY');
expect(viewer.sendMessage).to.not.be.called;
buildAmpState();
return element.whenBuilt();
}).then(() => {
expect(viewer.sendMessage).to.be.calledOnce;
expect(viewer.sendMessage).to.be.calledWith('bindReady');
Expand Down Expand Up @@ -778,7 +765,9 @@ describe.configure().ifChrome().run('Bind', function() {

it('should ignore <amp-state> updates if specified in setState()', () => {
const element = createElement(env, container, '[src]="foo"', 'amp-state');
element.whenBuilt = () => Promise.resolve();
expect(element.getAttribute('src')).to.be.null;

const promise = onBindReadyAndSetState(env, bind,
{foo: '/foo'}, /* opt_isAmpStateMutation */ true);
return promise.then(() => {
Expand Down