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

Whitelist actions on the special AMP target #13192

Merged
merged 14 commits into from
Feb 23, 2018

Conversation

hamousavi
Copy link
Contributor

@hamousavi hamousavi commented Jan 31, 2018

If a whitelist of actions on AMP target is specified via a meta tag for example as follows
<meta name="amp-action-whitelist" content="setState,pushState">
then the runtime should respect it. In our example the runtime must reject the print, gotBack and navigateTo actions.

In order to do this, we parse the meta tag in StandardActions's constructor and cache the result. In handleAmpTarget we ensure that the given action is whitelisted before allowing it to trigger.

@hamousavi
Copy link
Contributor Author

hamousavi commented Jan 31, 2018

Also cc'ing @raywainman @zhangsu


// Cache the whitelist of allowed AMP actions (if provided).
if (meta) {
/** @const @private {!Array<string>} */
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, given the current code this field should really be type-annotated as !Array<string>|undefined given that it's conditionally initialized. It might be better to extract this logic to a helper method, and use null as the default value.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

// in its content attribute, a whitelist of actions on the special AMP target.
if (this.ampdoc.getRootNode() && this.ampdoc.getRootNode().head) {
const meta =
this.ampdoc.getRootNode().head

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hoist duplicate calls to a local var.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

@@ -68,6 +68,21 @@ export class StandardActions {
/** @const @private {!./viewport/viewport-impl.Viewport} */
this.viewport_ = Services.viewportForDoc(ampdoc);

// A meta[name="amp-action-whitelist"] tag, if present, contains,
// in its content attribute, a whitelist of actions on the special AMP target.
if (this.ampdoc.getRootNode() && this.ampdoc.getRootNode().head) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's lazily create this whitelist instead.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

*/
handleAmpTarget(invocation, opt_actionIndex, opt_actionInfos) {
const method = invocation.method;
if (this.ampActionWhitelist_ &&
!this.ampActionWhitelist_.includes(method)) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's include the target as well to disambiguate.

<meta name="amp-action-whitelist" content="AMP.setState, AMP.pushState">

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

Copy link

@dreamofabear dreamofabear left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this feature meant to be valid AMP? If so, we need a validator change too since <meta> tags whose name starts with "amp" are whitelisted.

createElementWithAttributes(window.document, 'meta', {
name: 'amp-action-whitelist',
content: 'pushState,setState',
}));

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will mutate the test window which is shared by other tests. Instead, use stubs or describes.realWin to test in an iframe that will be teared down after the test.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done.

return standardActions.handleAmpTarget(pushState, 0, []).then(result => {
expect(result).to.equal('push-state-complete');
expect(pushStateWithExpression).to.be.calledOnce;
expect(pushStateWithExpression).to.be.calledWith('{foo: 123}');

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Simply stubbing/spying handleAmpBindAction_ should suffice. Let's not dupe test logic from other functions unless it pertains to the unit under test.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done.

*/
handleAmpTarget(invocation, opt_actionIndex, opt_actionInfos) {
const method = invocation.method;
if (this.ampActionWhitelist_ &&
!this.ampActionWhitelist_.includes(method)) {
throw user().createError('AMP action', method, 'is not whitelisted');
Copy link

@dreamofabear dreamofabear Feb 6, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"AMP.print" is not whitelisted in <meta name="amp-action-whitelist" content="AMP.setState">. See (link) for more details."

(link) should point to a new section in amp-actions-and-events.md.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding this meta tag makes the amp document invalid (as the insertion of meta tag is post validation). With this in mind, could you please clarify what should go in the documentation?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No documentation update then. 😄 Just want to make sure the error message is developer friendly.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@lannka This is the conversation I told you about

@dreamofabear
Copy link

Sorry for the late review, this slipped under my radar.

Copy link
Contributor Author

@hamousavi hamousavi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks a lot Will. Regarding your previous comment, this feature is not meant to be valid AMP. This embedding of meta tag containing the whitelist will happen post validation.

return standardActions.handleAmpTarget(pushState, 0, []).then(result => {
expect(result).to.equal('push-state-complete');
expect(pushStateWithExpression).to.be.calledOnce;
expect(pushStateWithExpression).to.be.calledWith('{foo: 123}');
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done.

*/
createWhitelist_() {
const head = this.ampdoc.getRootNode().head;
if (head) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider exiting early here with the negative condition to make the precondition clear and the code below more width-efficient.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

const meta =
head.querySelector('meta[name="amp-action-whitelist"]');

if (meta) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

this.installActions_(this.actions_);
}

/**
* @return {?Array<string>} the whitelist of allowed AMP actions
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

... on the special AMP target.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

const method = invocation.method;
if (this.ampActionWhitelist_ &&
!this.ampActionWhitelist_.includes('AMP.' + method)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

`AMP.${method}`

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

const method = invocation.method;
if (this.ampActionWhitelist_ &&
!this.ampActionWhitelist_.includes('AMP.' + method)) {
throw user().createError('AMP.${method}$ is not whitelisted.');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't you need a template string (escaped with the backtick) for string interpolation?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

getAttribute: () => 'AMP.pushState,AMP.setState',
};
sandbox.stub(window.document.head,
'querySelector').callsFake(() => fakeMeta);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I'd much prefer using describes.realWin to create a real <meta> tag and use real querySelector. The test would be much useful that way, since it actually tests the real thing that will be running under the code in prod.

Copy link
Member

@zhangsu zhangsu left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, just one last set of suggestions...

@@ -354,6 +354,57 @@ describes.sandboxed('StandardActions', {}, () => {
});
});

describes.realWin('#whitelist', {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think #foo is usually for describing a method in the class under test. In this case, whitelist is not really a method, so we probably want to describe the behavior in English (i.e., drop the #).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

* special AMP target.
* @private
*/
createWhitelist_() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about calling this method getAmpActionWhitelist, and return this.ampActionWhitelist_ if it is already cached (and update the JSDoc accordingly). That way, callers don't need to worry about this.ampActionWhitelist_ and can just call this.getAmpActionWhitelist_() everywhere.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

* the special AMP target, e.g.,
* <meta name="amp-action-whitelist" content="AMP.setState,AMP.pushState">
* @return {?Array<string>} the whitelist of actions on the
* special AMP target.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't the text here fit on the previous line? If not, this line should be indented 4 space, starting from the column where the @ on the previous line is.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

const method = invocation.method;
if (this.ampActionWhitelist_ &&
!this.ampActionWhitelist_.includes(`AMP.${method}`)) {
throw user().createError(`AMP.${method}$ is not whitelisted.`);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the second $ doing?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

@hamousavi
Copy link
Contributor Author

Thanks a lot Su.

@hamousavi
Copy link
Contributor Author

@choumx appreciate if you could take a look

@dreamofabear
Copy link

Try merging master to restart the presubmit. Also need to fix a minor import sorting issue.

@hamousavi
Copy link
Contributor Author

Thanks @choumx. Fixed the lint error.

@dreamofabear dreamofabear merged commit 19e6e78 into ampproject:master Feb 23, 2018
RanAbram pushed a commit to RanAbram/amphtml that referenced this pull request Mar 12, 2018
* respect the whitelist of actions specified in meta tags

* fixed the failing tests

* lint fix

* Fixed some of the concerns in the review

* Faking the window

* ran lint --fix

* simplifying the functional tests

* more code review fixes

* using real window in test

* a few suggestions by Su

* fixing import orders

* fixed lint errors

* removing const identifier
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants