Skip to content

Commit 3894247

Browse files
amaltasalin04
authored andcommitted
Populates preload image data in the Context.
PiperOrigin-RevId: 247660168
1 parent 517face commit 3894247

File tree

3 files changed

+279
-63
lines changed

3 files changed

+279
-63
lines changed

transformer/transformers/context.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,9 @@ import (
2424
// PreloadData stores the links of type script, image and style that are
2525
// added as Link http headers in SXG package.
2626
type PreloadData struct {
27-
url *url.URL
28-
as string
29-
media string
27+
URL *url.URL
28+
As string
29+
Media string
3030
}
3131

3232
// Context stores the root DOM Node and contextual data used for the

transformer/transformers/preloadimage.go

Lines changed: 72 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,10 @@
1515
package transformers
1616

1717
import (
18+
"fmt"
1819
"math/big"
20+
"net/url"
21+
"sort"
1922
"strconv"
2023
"strings"
2124

@@ -34,34 +37,26 @@ const maxAspectRatioSize int = 16
3437

3538
// PreloadImage adds link rel="prefetch" head element to preload the most revalent image in the AMP document.
3639
func PreloadImage(e *Context) error {
37-
importantImageUrls := imagesToPreload(e)
38-
if len(importantImageUrls) > 0 {
39-
prefetchLink := htmlnode.Element("link", html.Attribute{Key: "rel", Val: "prefetch"}, html.Attribute{Key: "href", Val: importantImageUrls[0]})
40-
e.DOM.HeadNode.AppendChild(prefetchLink)
41-
}
42-
return nil
43-
}
44-
45-
// Returns list of images on the document that qualifies for preloading.
46-
func imagesToPreload(e *Context) []string {
47-
candidateImages := []string{}
48-
4940
for n := e.DOM.BodyNode; n != nil; n = htmlnode.Next(n) {
5041
if isNodeHiddenInLayout(n) {
5142
continue
5243
}
5344

5445
if n.Data == "amp-img" {
55-
if imgsrc, ok := candidateImageForPreloading(n); ok {
56-
candidateImages = append(candidateImages, imgsrc)
46+
if imgsrcset, ok := candidateImageForPreloading(n); ok {
47+
srcsetToPreloadData(imgsrcset, e)
5748
}
5849
} else if n.Data == "amp-video" || n.Data == "amp-video-iframe" {
59-
if imgsrc, ok := candidateVideoPosterImage(n); ok {
60-
candidateImages = append(candidateImages, imgsrc)
50+
if poster, ok := candidateVideoPosterImage(n); ok {
51+
posterURL, err := url.Parse(poster)
52+
if err != nil {
53+
continue
54+
}
55+
e.Preloads = append(e.Preloads, PreloadData{URL: posterURL, As: "image"})
6156
}
6257
}
6358
}
64-
return candidateImages
59+
return nil
6560
}
6661

6762
func candidateVideoPosterImage(i *html.Node) (string, bool) {
@@ -82,22 +77,75 @@ func isNodeHiddenInLayout(n *html.Node) bool {
8277
return layout.ParseAMPLayout(n) == amppb.AmpLayout_NODISPLAY
8378
}
8479

80+
// Converts the raw srcset attribute value and populates Context.Preloads field.
81+
func srcsetToPreloadData(srcset string, e *Context) {
82+
type imageWithTargetSize struct {
83+
imgURL *url.URL
84+
size int
85+
}
86+
87+
srcSets := strings.FieldsFunc(strings.TrimSpace(srcset), func(c rune) bool { return c == ',' })
88+
srcSetsSize := len(srcSets)
89+
imgSet := []imageWithTargetSize{}
90+
91+
for _, src := range srcSets {
92+
imgComponents := strings.Fields(src)
93+
if len(imgComponents) != 2 {
94+
e.Preloads = nil
95+
return
96+
}
97+
imgTargetSize, err := strconv.Atoi(strings.TrimSuffix(imgComponents[1], "w"))
98+
99+
if err != nil {
100+
e.Preloads = nil
101+
return
102+
}
103+
104+
urlObj, err := url.Parse(imgComponents[0])
105+
106+
if err != nil {
107+
e.Preloads = nil
108+
return
109+
}
110+
111+
imgSet = append(imgSet, imageWithTargetSize{urlObj, imgTargetSize})
112+
}
113+
114+
// Sort the images based on their target sizes in asc order.
115+
sort.Slice(imgSet, func(i, j int) bool { return imgSet[i].size < imgSet[j].size })
116+
117+
for i, ci := range imgSet {
118+
var mediaQuery string
119+
// srcset images should be sorted by width.
120+
if i == 0 {
121+
mediaQuery = fmt.Sprintf("(max-width: %d)", ci.size)
122+
// Largest image has only min width limit of second largest image.
123+
} else if i == srcSetsSize-1 {
124+
mediaQuery = fmt.Sprintf("(min-width: %d)", imgSet[i-1].size+1)
125+
} else {
126+
mediaQuery = fmt.Sprintf("(min-width: %d) and (max-width: %d)", imgSet[i-1].size+1, ci.size)
127+
}
128+
129+
e.Preloads = append(e.Preloads, PreloadData{URL: ci.imgURL, As: "image", Media: mediaQuery})
130+
}
131+
}
132+
85133
// Decides if the given image node qualifies for preloading and returns tuple of
86134
// (imagesrc, true) if the node qualifies for preloading, otherwise returns
87135
// empty string and false.
88136
func candidateImageForPreloading(n *html.Node) (string, bool) {
89137
// amp-image under following containers do not qualify for preloading.
90-
imgsrc, hasSrc := htmlnode.GetAttributeVal(n, "", "src")
138+
imgsrcset, hasSrcset := htmlnode.GetAttributeVal(n, "", "srcset")
91139

92140
// Ignores images with no src attribute.
93141
// These can be css images inside class definition.
94-
if !hasSrc || len(imgsrc) == 0 {
142+
if !hasSrcset || len(imgsrcset) == 0 {
95143
return "", false
96144
}
97145

98146
// Ignores if image src is not a https url.
99147
// URL rewrite transformer guarantees img srcs are https protocol.
100-
if !strings.HasPrefix(imgsrc, "https://") {
148+
if !strings.HasPrefix(imgsrcset, "https://") {
101149
return "", false
102150
}
103151

@@ -110,7 +158,7 @@ func candidateImageForPreloading(n *html.Node) (string, bool) {
110158
// Small images of icon types inside input type container types
111159
// are ignored.
112160
if widthInt > 0 && widthInt <= maxAspectRatioSize && heightInt > 0 && heightInt <= maxAspectRatioSize && isAspectRatioDimensions(n, widthInt, heightInt) && !containerTypeInput(n) {
113-
return imgsrc, true
161+
return imgsrcset, true
114162
}
115163
return "", false
116164
}
@@ -124,7 +172,7 @@ func candidateImageForPreloading(n *html.Node) (string, bool) {
124172
if isTinyNode(parentWidthInt, parentHeightInt) {
125173
return "", false
126174
}
127-
return imgsrc, true
175+
return imgsrcset, true
128176
}
129177
return "", false
130178
}
@@ -136,7 +184,7 @@ func candidateImageForPreloading(n *html.Node) (string, bool) {
136184
if isTinyNode(parentWidthInt, parentHeightInt) {
137185
return "", false
138186
}
139-
return imgsrc, true
187+
return imgsrcset, true
140188
}
141189

142190
// Actual image dimension check is performed later.
@@ -153,7 +201,7 @@ func candidateImageForPreloading(n *html.Node) (string, bool) {
153201
// Ignores the width size if it is not specified. In most layouts it
154202
// defaults to auto or 100% size of container.
155203
if (widthInt >= minImageSize || widthInt == 0) && heightInt >= minImageSize {
156-
return imgsrc, true
204+
return imgsrcset, true
157205
}
158206

159207
return "", false

0 commit comments

Comments
 (0)