diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 000000000..b74de28e0 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1 @@ +See https://github.com/ampproject/meta/blob/master/CODE_OF_CONDUCT.md diff --git a/transformer/transformer.go b/transformer/transformer.go index 258f45912..a07be5b12 100644 --- a/transformer/transformer.go +++ b/transformer/transformer.go @@ -30,8 +30,8 @@ import ( rpb "github.com/ampproject/amppackager/transformer/request" "github.com/ampproject/amppackager/transformer/transformers" "github.com/pkg/errors" - "golang.org/x/net/html/atom" "golang.org/x/net/html" + "golang.org/x/net/html/atom" ) // Transformer functions must be added here in order to be passed in from @@ -44,6 +44,7 @@ var transformerFunctionMap = map[string]func(*transformers.Context) error{ "absoluteurl": transformers.AbsoluteURL, "ampboilerplate": transformers.AMPBoilerplate, "ampruntimecss": transformers.AMPRuntimeCSS, + "ampruntimejs": transformers.AMPRuntimeJS, "linktag": transformers.LinkTag, "nodecleanup": transformers.NodeCleanup, "preloadimage": transformers.PreloadImage, @@ -72,6 +73,7 @@ var configMap = map[rpb.Request_TransformersConfig][]func(*transformers.Context) transformers.AMPRuntimeCSS, transformers.TransformedIdentifier, transformers.URLRewrite, + transformers.AMPRuntimeJS, transformers.PreloadImage, // ReorderHead should run after all transformers that modify the // , as they may do so without preserving the proper order. @@ -90,7 +92,6 @@ var configMap = map[rpb.Request_TransformersConfig][]func(*transformers.Context) // from an unnecessary number of fetches. const maxPreloads = 20 - // Override for tests. var runTransformers = func(c *transformers.Context, fns []func(*transformers.Context) error) error { // Invoke the configured transformers @@ -224,11 +225,11 @@ func extractPreloads(dom *amphtml.DOM) []*rpb.Metadata_Preload { // amp-script without an explicit max-age. This is 1 day, to parallel the // security precautions put in place around service workers: // https://dev.chromium.org/Home/chromium-security/security-faq/service-worker-security-faq#TOC-Do-Service-Workers-live-forever- -const defaultMaxAgeSeconds int32 = 86400 // number of seconds in a day +const defaultMaxAgeSeconds int32 = 86400 // number of seconds in a day // maxMaxAgeSeconds is the max duration of an SXG, per // https://wicg.github.io/webpackage/draft-yasskin-http-origin-signed-responses.html#signature-validity. -const maxMaxAgeSeconds int32 = 7*86400 +const maxMaxAgeSeconds int32 = 7 * 86400 // computeMaxAgeSeconds returns the suggested max-age based on the presence of // any inline tags on the page; callers should min() the return @@ -317,7 +318,7 @@ func Process(r *rpb.Request) (string, *rpb.Metadata, error) { return "", nil, err } metadata := rpb.Metadata{ - Preloads: extractPreloads(context.DOM), + Preloads: extractPreloads(context.DOM), MaxAgeSecs: computeMaxAgeSeconds(context.DOM), } return o.String(), &metadata, nil diff --git a/transformer/transformer_test.go b/transformer/transformer_test.go index 32ff55527..0af4046fa 100644 --- a/transformer/transformer_test.go +++ b/transformer/transformer_test.go @@ -27,7 +27,7 @@ func TestProcess(t *testing.T) { config rpb.Request_TransformersConfig expectedLen int }{ - {rpb.Request_DEFAULT, 13}, + {rpb.Request_DEFAULT, 14}, {rpb.Request_NONE, 0}, {rpb.Request_VALIDATION, 1}, {rpb.Request_CUSTOM, 0}, diff --git a/transformer/transformers/ampruntimejs.go b/transformer/transformers/ampruntimejs.go new file mode 100644 index 000000000..4046c244e --- /dev/null +++ b/transformer/transformers/ampruntimejs.go @@ -0,0 +1,38 @@ +package transformers + +import ( + "net/url" + "strings" + + "github.com/ampproject/amppackager/transformer/internal/amphtml" + "github.com/ampproject/amppackager/transformer/internal/htmlnode" + "golang.org/x/net/html" + "golang.org/x/net/html/atom" +) + +// AMPRuntimeJS rewrites the value of src in script nodes, where applicable. +// If the value is of the form "*.js", replace it with "*.js?f=sxg". +func AMPRuntimeJS(e *Context) error { + for n := e.DOM.HeadNode.FirstChild; n != nil; n = n.NextSibling { + if n.Type == html.ElementNode && n.DataAtom == atom.Script { + src, ok := htmlnode.FindAttribute(n, "", "src") + if ok && strings.HasPrefix(src.Val, amphtml.AMPCacheRootURL) { + u, uerr := url.Parse(src.Val) + if uerr != nil { + continue + } + query, queryerr := url.ParseQuery(u.RawQuery) + if queryerr != nil { + continue + } + path := u.Path + if strings.HasSuffix(path, ".js") { + query.Set("f", "sxg") + u.RawQuery = query.Encode() + src.Val = u.String() + } + } + } + } + return nil +} diff --git a/transformer/transformers/ampruntimejs_test.go b/transformer/transformers/ampruntimejs_test.go new file mode 100644 index 000000000..88bee8bec --- /dev/null +++ b/transformer/transformers/ampruntimejs_test.go @@ -0,0 +1,100 @@ +package transformers_test + +import ( + "strings" + "testing" + + "github.com/ampproject/amppackager/transformer/internal/amphtml" + tt "github.com/ampproject/amppackager/transformer/internal/testing" + "github.com/ampproject/amppackager/transformer/transformers" + "golang.org/x/net/html" +) + +func TestAmpRuntimeJS(t *testing.T) { + tcs := []tt.TestCase{ + { + Desc: "no script node", + Input: "", + Expected: "", + }, + { + Desc: "no prefix", + Input: "`, + Expected: ``, + }, + { + Desc: "transform on two scripts", + Input: ``, + Expected: ``, + }, + { + Desc: "skip one, transform one", + Input: ``, + Expected: ``, + }, + { + Desc: "additional params exist", + Input: ``, + Expected: ``, + }, + { + Desc: "existing f param", + Input: ``, + Expected: ``, + }, + { + Desc: "url escape parsing bug in query param", + Input: ``, + Expected: ``, + }, + { + Desc: "url escape parsing bug", + Input: ``, + Expected: ``, + }, + } + + for _, tc := range tcs { + inputDoc, err := html.Parse(strings.NewReader(tc.Input)) + if err != nil { + t.Errorf("%s: html.Parse failed %q", tc.Input, err) + continue + } + inputDOM, err := amphtml.NewDOM(inputDoc) + if err != nil { + t.Errorf("%s\namphtml.NewDOM for %s failed %q", tc.Desc, tc.Input, err) + continue + } + transformers.AMPRuntimeJS(&transformers.Context{DOM: inputDOM}) + var input strings.Builder + if err := html.Render(&input, inputDoc); err != nil { + t.Errorf("%s: html.Render failed %q", tc.Input, err) + continue + } + + expectedDoc, err := html.Parse(strings.NewReader(tc.Expected)) + if err != nil { + t.Errorf("%s: html.Parse failed %q", tc.Expected, err) + continue + } + var expected strings.Builder + err = html.Render(&expected, expectedDoc) + if err != nil { + t.Errorf("%s: html.Render failed %q", tc.Expected, err) + continue + } + if input.String() != expected.String() { + t.Errorf("%s: Transform=\n%q\nwant=\n%q", tc.Desc, &input, &expected) + } + } +}