Skip to content

Commit

Permalink
Runtime: Viewer must be trusted for ssr templates to be supported
Browse files Browse the repository at this point in the history
  • Loading branch information
samouri committed Nov 13, 2019
1 parent eb05855 commit d8cdcf7
Show file tree
Hide file tree
Showing 5 changed files with 177 additions and 148 deletions.
13 changes: 7 additions & 6 deletions build-system/tasks/presubmit-checks.js
Expand Up @@ -427,15 +427,16 @@ const forbiddenTerms = {
message: requiresReviewPrivacy,
whitelist: [
'extensions/amp-bind/0.1/bind-impl.js',
'extensions/amp-viewer-assistance/0.1/amp-viewer-assistance.js',
'src/error.js',
'src/utils/xhr-utils.js',
'src/service/viewer-impl.js',
'src/service/viewer-interface.js',
'src/service/viewer-cid-api.js',
'src/impression.js',
'src/inabox/inabox-viewer.js',
'src/service/cid-impl.js',
'src/impression.js',
'extensions/amp-viewer-assistance/0.1/amp-viewer-assistance.js',
'src/service/viewer-cid-api.js',
'src/service/viewer-impl.js',
'src/service/viewer-interface.js',
'src/ssr-template-helper.js',
'src/utils/xhr-utils.js',
],
},
'prerenderSafe': {
Expand Down
93 changes: 50 additions & 43 deletions extensions/amp-form/0.1/amp-form.js
Expand Up @@ -385,32 +385,36 @@ export class AmpForm {
);

// Form verification is not supported when SSRing templates is enabled.
if (!this.ssrTemplateHelper_.isSupported()) {
this.form_.addEventListener('change', e => {
this.verifier_.onCommit().then(({updatedElements, errors}) => {
updatedElements.forEach(checkUserValidityAfterInteraction_);
// Tell the validation to reveal any input.validationMessage added
// by the form verifier.
this.validator_.onBlur(e);

// Only make the verify XHR if the user hasn't pressed submit.
if (this.state_ === FormState.VERIFYING) {
if (errors.length) {
this.setState_(FormState.VERIFY_ERROR);
this.renderTemplate_(dict({'verifyErrors': errors})).then(() => {
this.triggerAction_(FormEvents.VERIFY_ERROR, errors);
});
} else {
this.setState_(FormState.INITIAL);
this.ssrTemplateHelper_.isSupported().then(isSSRSupported => {
if (!isSSRSupported) {
this.form_.addEventListener('change', e => {
this.verifier_.onCommit().then(({updatedElements, errors}) => {
updatedElements.forEach(checkUserValidityAfterInteraction_);
// Tell the validation to reveal any input.validationMessage added
// by the form verifier.
this.validator_.onBlur(e);

// Only make the verify XHR if the user hasn't pressed submit.
if (this.state_ === FormState.VERIFYING) {
if (errors.length) {
this.setState_(FormState.VERIFY_ERROR);
this.renderTemplate_(dict({'verifyErrors': errors})).then(
() => {
this.triggerAction_(FormEvents.VERIFY_ERROR, errors);
}
);
} else {
this.setState_(FormState.INITIAL);
}
}
}
});
});
});
}
}

this.form_.addEventListener('input', e => {
checkUserValidityAfterInteraction_(dev().assertElement(e.target));
this.validator_.onInput(e);
this.form_.addEventListener('input', e => {
checkUserValidityAfterInteraction_(dev().assertElement(e.target));
this.validator_.onInput(e);
});
});
}

Expand Down Expand Up @@ -674,20 +678,22 @@ export class AmpForm {
* @return {!Promise}
*/
handleXhrSubmit_(trust) {
let p;
if (this.ssrTemplateHelper_.isSupported()) {
p = this.handleSsrTemplate_(trust);
} else {
this.submittingWithTrust_(trust);
p = this.doActionXhr_().then(
response => this.handleXhrSubmitSuccess_(response),
error => this.handleXhrSubmitFailure_(error)
);
}
if (getMode().test) {
this.xhrSubmitPromise_ = p;
}
return p;
return this.ssrTemplateHelper_.isSupported().then(isSSRSupported => {
let p;
if (isSSRSupported) {
p = this.handleSsrTemplate_(trust);
} else {
this.submittingWithTrust_(trust);
p = this.doActionXhr_().then(
response => this.handleXhrSubmitSuccess_(response),
error => this.handleXhrSubmitFailure_(error)
);
}
if (getMode().test) {
this.xhrSubmitPromise_ = p;
}
return p;
});
}

/**
Expand Down Expand Up @@ -1007,12 +1013,13 @@ export class AmpForm {
* @private
*/
assertSsrTemplate_(value, msg) {
const supported = this.ssrTemplateHelper_.isSupported();
userAssert(
supported === value,
'[amp-form]: viewerRenderTemplate | %s',
msg
);
this.ssrTemplateHelper_.isSupported().then(supported => {
userAssert(
supported === value,
'[amp-form]: viewerRenderTemplate | %s',
msg
);
});
}

/**
Expand Down
72 changes: 35 additions & 37 deletions extensions/amp-list/0.1/amp-list.js
Expand Up @@ -555,26 +555,26 @@ export class AmpList extends AMP.BaseElement {
return Promise.resolve();
}
let fetch;
if (this.ssrTemplateHelper_.isSupported()) {
fetch = this.ssrTemplate_(opt_refresh);
} else {
fetch = this.prepareAndSendFetch_(opt_refresh).then(data => {
const items = this.computeListItems_(data);
if (this.loadMoreEnabled_) {
this.updateLoadMoreSrc_(/** @type {!JsonObject} */ (data));
}
return this.scheduleRender_(
items,
/*opt_append*/ false,
data
).then(() => this.maybeSetLoadMore_());
});
}
return this.ssrTemplateHelper_.isSupported().then(isSSRSupported => {
if (isSSRSupported) {
fetch = this.ssrTemplate_(opt_refresh);
} else {
fetch = this.prepareAndSendFetch_(opt_refresh).then(data => {
const items = this.computeListItems_(data);
if (this.loadMoreEnabled_) {
this.updateLoadMoreSrc_(/** @type {!JsonObject} */ (data));
}
return this.scheduleRender_(items, /*opt_append*/ false, data).then(
() => this.maybeSetLoadMore_()
);
});
}

return fetch.catch(error => {
this.triggerFetchErrorEvent_(error);
this.showFallback_();
throw error;
return fetch.catch(error => {
this.triggerFetchErrorEvent_(error);
this.showFallback_();
throw error;
});
});
}

Expand Down Expand Up @@ -622,10 +622,10 @@ export class AmpList extends AMP.BaseElement {

/**
* Proxies the template rendering to the viewer.
* @param {boolean} refresh
* @param {boolean=} refresh
* @return {!Promise}
*/
ssrTemplate_(refresh) {
ssrTemplate_(refresh = false) {
let request;
// Construct the fetch init data that would be called by the viewer
// passed in as the 'originalRequest'.
Expand Down Expand Up @@ -746,18 +746,19 @@ export class AmpList extends AMP.BaseElement {
scheduleNextPass();
current.rejecter();
};
const isSSR = this.ssrTemplateHelper_.isSupported();
let renderPromise = this.ssrTemplateHelper_
.applySsrOrCsrTemplate(this.element, current.data)
.then(result => this.updateBindings_(result, current.append))
.then(elements => this.render_(elements, current.append));
if (!isSSR) {
const payload = /** @type {!JsonObject} */ (current.payload);
renderPromise = renderPromise.then(() =>
this.maybeRenderLoadMoreTemplates_(payload)
);
}
renderPromise.then(onFulfilledCallback, onRejectedCallback);
this.ssrTemplateHelper_.isSupported().then(isSSR => {
let renderPromise = this.ssrTemplateHelper_
.applySsrOrCsrTemplate(this.element, current.data)
.then(result => this.updateBindings_(result, current.append))
.then(elements => this.render_(elements, current.append));
if (!isSSR) {
const payload = /** @type {!JsonObject} */ (current.payload);
renderPromise = renderPromise.then(() =>
this.maybeRenderLoadMoreTemplates_(payload)
);
}
renderPromise.then(onFulfilledCallback, onRejectedCallback);
});
}

/**
Expand Down Expand Up @@ -845,10 +846,7 @@ export class AmpList extends AMP.BaseElement {
// Forward elements to chained promise on success or failure.
return bind
.rescan(elements, removedElements, {'fast': true, 'update': true})
.then(
() => elements,
() => elements
);
.then(() => elements, () => elements);
};

// binding=refresh: Only do render-blocking update after initial render.
Expand Down
64 changes: 36 additions & 28 deletions src/ssr-template-helper.js
Expand Up @@ -51,17 +51,23 @@ export class SsrTemplateHelper {
* Whether the viewer can render templates. A doc-level opt in as
* trusted viewers must set this capability explicitly, as a security
* measure for potential abuse of feature.
* @return {boolean}
* @return {Promise<boolean>}
*/
isSupported() {
const ampdoc = this.viewer_.getAmpDoc();
if (ampdoc.isSingleDoc()) {
const htmlElement = ampdoc.getRootNode().documentElement;
if (htmlElement.hasAttribute('allow-viewer-render-template')) {
return this.viewer_.hasCapability('viewerRenderTemplate');
}
if (!ampdoc.isSingleDoc()) {
return Promise.resolve(false);
}
return false;
return this.viewer_.isTrustedViewer().then(isTrusted => {
if (!isTrusted) {
return false;
}
const htmlElement = ampdoc.getRootNode().documentElement;
return (
htmlElement.hasAttribute('allow-viewer-render-template') &&
this.viewer_.hasCapability('viewerRenderTemplate')
);
});
}

/**
Expand Down Expand Up @@ -100,28 +106,30 @@ export class SsrTemplateHelper {
*/
applySsrOrCsrTemplate(element, data) {
let renderTemplatePromise;
if (this.isSupported()) {
userAssert(
typeof data['html'] === 'string',
'Server side html response must be defined'
);
renderTemplatePromise = this.templates_.findAndSetHtmlForTemplate(
element,
/** @type {string} */ (data['html'])
);
} else if (isArray(data)) {
renderTemplatePromise = this.templates_.findAndRenderTemplateArray(
element,
/** @type {!Array} */ (data)
);
} else {
renderTemplatePromise = this.templates_.findAndRenderTemplate(
element,
/** @type {!JsonObject} */ (data)
);
}
return this.isSupported().then(isSupported => {
if (isSupported) {
userAssert(
typeof data['html'] === 'string',
'Server side html response must be defined'
);
renderTemplatePromise = this.templates_.findAndSetHtmlForTemplate(
element,
/** @type {string} */ (data['html'])
);
} else if (isArray(data)) {
renderTemplatePromise = this.templates_.findAndRenderTemplateArray(
element,
/** @type {!Array} */ (data)
);
} else {
renderTemplatePromise = this.templates_.findAndRenderTemplate(
element,
/** @type {!JsonObject} */ (data)
);
}

return renderTemplatePromise;
return renderTemplatePromise;
});
}

/**
Expand Down

0 comments on commit d8cdcf7

Please sign in to comment.