Skip to content

Resolve macros in VariableService.#22379

Closed
calebcordry wants to merge 17 commits intoampproject:masterfrom
calebcordry:var-encoding-bug
Closed

Resolve macros in VariableService.#22379
calebcordry wants to merge 17 commits intoampproject:masterfrom
calebcordry:var-encoding-bug

Conversation

@calebcordry
Copy link
Copy Markdown
Member

Closes #21751

Changes VariableService.expandTemplate to resolve macros through the UrlReplacementService as it encounters them. This avoids the problem of the variableService encoding certain macros before they are resolved. $, ( and , would be encoded in nested macros etc.

One note: I had to pass the element along everywhere this is called to check if amp-analytics is sandboxed. I am open to other suggestions on how this could be accomplished.

@calebcordry calebcordry changed the title R Resolve macros in VariableService. May 19, 2019
@calebcordry calebcordry requested review from lannka and zhouyx May 21, 2019 18:29
@calebcordry calebcordry marked this pull request as ready for review May 21, 2019 18:30
@lannka
Copy link
Copy Markdown
Contributor

lannka commented Jun 13, 2019

Is this PR blocked by anything? Or is it ready for review?

@calebcordry
Copy link
Copy Markdown
Member Author

@lannka I keep forgetting about this PR, but it is ready for review.

Copy link
Copy Markdown
Contributor

@zhouyx zhouyx left a comment

Choose a reason for hiding this comment

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

@calebcordry Can we sync on the fix offline sometime? I want to understand what approaches has been discussed before.

From what I found, you're trying to expand the macros in variables before replacing them. Which avoids the encoding issue. However I have two concerns. 1. expandTemplate and expandUrlAsync used to be two different expansion. With this change we start to mix them together. 2. Instead of trying to resolve macro at event trigger time, we may resolve macros too early. TIMESTAMP for example.

this.linkerReader_ = linkerReaderServiceFor(this.ampdoc_.win);

/** @const @private {!../../../src/service/url-replacements-impl.UrlReplacements} */
this.urlReplacementService_ = Services.urlReplacementsForDoc(
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

We can't do this. Instead we'll have to call Services.urlReplacementsForDoc(element) for every element we want to expand. Because FIE doesn't have its own ampdoc, but it has its own urlReplacementService.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Moved to get urlReplacementService from the element inside the expandTemplate call.

@calebcordry
Copy link
Copy Markdown
Member Author

Sounds good, let's plan on syncing sometime Thursday.

@zhouyx
Copy link
Copy Markdown
Contributor

zhouyx commented Jun 18, 2019

sgtm

@calebcordry
Copy link
Copy Markdown
Member Author

@zhouyx @lannka I think this is now in a good updated state for review.

* @return {!Promise<string>}
*/
function serializeResourceTiming(ampdoc, resourceTimingSpec) {
function serializeResourceTiming(ampdoc, element, resourceTimingSpec) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

nit: can we get rid of ampdoc here?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I think we need it to get the win? Let me know if I am missing something.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Still not sure about this one.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

getAmpDoc() {

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Oh I see, thanks, done.

'p:nth-child(2)',
].map(selectorExpansionTest);

it('does not expands selector with platform variable', () => {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Any way to preserve the existing behavior?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Is there a reason we don't want to use the platform macros here? We could add something like a noPlatform flag for certain callers if you like. This would come with the cost that these callers could not use nested.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

This behavior has changed, let me know if you think we need to support not expanding macros in certain cases.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I guess it's fine. Please add a note to the code. Thanks

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Added a note to expandTemplate

@mrjoro
Copy link
Copy Markdown
Member

mrjoro commented Aug 8, 2019

Is this PR still active?

@calebcordry
Copy link
Copy Markdown
Member Author

Yep working on it now.

@calebcordry
Copy link
Copy Markdown
Member Author

@zhouyx I think this is finally ready for another round of review

@amp-bundle-size amp-bundle-size bot requested a review from dreamofabear August 8, 2019 21:26
* @return {!Promise<string>}
*/
function serializeResourceTiming(ampdoc, resourceTimingSpec) {
function serializeResourceTiming(ampdoc, element, resourceTimingSpec) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

getAmpDoc() {

});
})
.then(() =>
// TODO: fix this, should be 'AAA(BBB(1,2))'
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

nit: remove since we are not going to fix it.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Done.

// valid macros when they are seen in `Variables#expandTemplate`.
check('${foo}', 'AAA(BBB(1%2C2))', {
'foo': 'AAA(BBB(1,2))',
})
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

result

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Done.

})
)
.then(() =>
// TODO: fix this, should be 'AAA(1,2)%26BBB(3,4)%26CCC(5,6)%26DDD(7,8)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

nit: remove TODO

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Done.

expandTemplateSync(template, options) {
return template.replace(/\${([^}]*)}/g, (match, key) => {
expandTemplate(template, options, element) {
return this.asyncStringReplace_(template, /\${([^}]*)}/g, (match, key) => {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

You may have explained this before. I'm not an expert on regex.
${abc${def}} would break right? If so can we add a test to cover that. Thanks

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

That is correct. Added test.

* @param {Function} replacer
* @return {!Promise<string>}
*/
asyncStringReplace_(str, regex, replacer) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

If I understand the code correctly. What we want here to an array of promise that waits for each variable to resolve. Personally I think this helper method makes the code more difficult to understand.
Can we inline the code as previous and do
const promises = []
str.replace(regex, () => {
// some operation
promises.push(promise)
})
return Promise.all(promises)


So that we can get rid of the stringBuilder here. Thanks

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Talked offline. Moved asyncStringReplace to a separate function in string.js

'p:nth-child(2)',
].map(selectorExpansionTest);

it('does not expands selector with platform variable', () => {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I guess it's fine. Please add a note to the code. Thanks


it('expands nested vars (encode once)', () => {
check('${a}', 'https%3A%2F%2Fwww.google.com%2Fa%3Fb%3D1%26c%3D2', vars);
return check(
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Are there test coverage for expanding the nested macro? Please help link to the test. Thanks.
Also given there are a bunch of special cases, it would be great to add a manual test file which helps demonstrate all the expected behaviors. Thanks

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Added a manual file, let me know if there is anything else that you can think of. There is a test here

.then(() =>
check('${nested}', 'default', {
'nested': '${deeper}',
'deeper': '$IF(true, QUERY_PARAM(foo, default), never)',
})
));

that shows the new behavior, let me know if have idea for more.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LG

@calebcordry
Copy link
Copy Markdown
Member Author

@zhouyx this should be ready for another round of review whenever you have time. Thanks!

expandTemplateWithUrlParams_(spec, expansionOptions) {
return this.variableService_
.expandTemplate(spec, expansionOptions)
.expandTemplate(spec, expansionOptions, this.element)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Since we pass the binding info to expandUrlAsync, do we want to pass the same to expandTemplate?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I think this only matters for RESOURCE_TIMING as the way expandTemplate is now written it will try to get the bindings from the element if not passed in. If you prefer to explicitly pass it the bindings everywhere, I am happy to do so.

const bindings = this.variableService_.getMacros(this.element_);
return this.variableService_
.expandTemplate(template, expansionOptions)
.expandTemplate(template, expansionOptions, this.element_)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

ditto

this.requestOriginPromise_ = this.variableService_
// expand variables in request origin
.expandTemplate(this.requestOrigin_, requestOriginExpansionOpt)
.expandTemplate(
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

ditto

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

This one should definitely have the bindings, as it will include the RESOURCE_TIMING macro. Done.


const basePromise = variableService
.expandTemplate(msg, expansionOption)
.expandTemplate(msg, expansionOption, element)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

ditto


it('expands nested vars (encode once)', () => {
check('${a}', 'https%3A%2F%2Fwww.google.com%2Fa%3Fb%3D1%26c%3D2', vars);
return check(
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LG

* @param {Function} replacer
* @return {!Promise<string>}
*/
export function asyncStringReplace(str, regex, replacer) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Please add unit test for this function

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Done.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Actually hit a bug here with variable number of capture groups. Didn't think of this when we moved to make it generic. Working on fix.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

OK fixed :) should work with any number of capture groups.

You can use the ${} format in conjuction with nested macros, as long as there are no nested ${}.
See combo below.
-->
<amp-analytics>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks for the manual test file

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

It was a good idea 🙂. You are welcome.

@dreamofabear dreamofabear removed their request for review August 19, 2019 19:44
@calebcordry
Copy link
Copy Markdown
Member Author

@zhouyx I think this is ready for another round whenever you have time. Thanks!

@lannka
Copy link
Copy Markdown
Contributor

lannka commented Jan 27, 2020

is this still needed? or did @micajuine-ho take over?

@lannka
Copy link
Copy Markdown
Contributor

lannka commented Jan 27, 2020

saw #26271

@lannka lannka closed this Jan 27, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

getNameArgs in variables.js doesn't understand nested macros

5 participants