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)
+ }
+ }
+}