15
15
package transformers
16
16
17
17
import (
18
+ "fmt"
18
19
"math/big"
20
+ "net/url"
21
+ "sort"
19
22
"strconv"
20
23
"strings"
21
24
@@ -34,34 +37,26 @@ const maxAspectRatioSize int = 16
34
37
35
38
// PreloadImage adds link rel="prefetch" head element to preload the most revalent image in the AMP document.
36
39
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
-
49
40
for n := e .DOM .BodyNode ; n != nil ; n = htmlnode .Next (n ) {
50
41
if isNodeHiddenInLayout (n ) {
51
42
continue
52
43
}
53
44
54
45
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 )
57
48
}
58
49
} 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" })
61
56
}
62
57
}
63
58
}
64
- return candidateImages
59
+ return nil
65
60
}
66
61
67
62
func candidateVideoPosterImage (i * html.Node ) (string , bool ) {
@@ -82,22 +77,75 @@ func isNodeHiddenInLayout(n *html.Node) bool {
82
77
return layout .ParseAMPLayout (n ) == amppb .AmpLayout_NODISPLAY
83
78
}
84
79
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
+
85
133
// Decides if the given image node qualifies for preloading and returns tuple of
86
134
// (imagesrc, true) if the node qualifies for preloading, otherwise returns
87
135
// empty string and false.
88
136
func candidateImageForPreloading (n * html.Node ) (string , bool ) {
89
137
// amp-image under following containers do not qualify for preloading.
90
- imgsrc , hasSrc := htmlnode .GetAttributeVal (n , "" , "src " )
138
+ imgsrcset , hasSrcset := htmlnode .GetAttributeVal (n , "" , "srcset " )
91
139
92
140
// Ignores images with no src attribute.
93
141
// These can be css images inside class definition.
94
- if ! hasSrc || len (imgsrc ) == 0 {
142
+ if ! hasSrcset || len (imgsrcset ) == 0 {
95
143
return "" , false
96
144
}
97
145
98
146
// Ignores if image src is not a https url.
99
147
// URL rewrite transformer guarantees img srcs are https protocol.
100
- if ! strings .HasPrefix (imgsrc , "https://" ) {
148
+ if ! strings .HasPrefix (imgsrcset , "https://" ) {
101
149
return "" , false
102
150
}
103
151
@@ -110,7 +158,7 @@ func candidateImageForPreloading(n *html.Node) (string, bool) {
110
158
// Small images of icon types inside input type container types
111
159
// are ignored.
112
160
if widthInt > 0 && widthInt <= maxAspectRatioSize && heightInt > 0 && heightInt <= maxAspectRatioSize && isAspectRatioDimensions (n , widthInt , heightInt ) && ! containerTypeInput (n ) {
113
- return imgsrc , true
161
+ return imgsrcset , true
114
162
}
115
163
return "" , false
116
164
}
@@ -124,7 +172,7 @@ func candidateImageForPreloading(n *html.Node) (string, bool) {
124
172
if isTinyNode (parentWidthInt , parentHeightInt ) {
125
173
return "" , false
126
174
}
127
- return imgsrc , true
175
+ return imgsrcset , true
128
176
}
129
177
return "" , false
130
178
}
@@ -136,7 +184,7 @@ func candidateImageForPreloading(n *html.Node) (string, bool) {
136
184
if isTinyNode (parentWidthInt , parentHeightInt ) {
137
185
return "" , false
138
186
}
139
- return imgsrc , true
187
+ return imgsrcset , true
140
188
}
141
189
142
190
// Actual image dimension check is performed later.
@@ -153,7 +201,7 @@ func candidateImageForPreloading(n *html.Node) (string, bool) {
153
201
// Ignores the width size if it is not specified. In most layouts it
154
202
// defaults to auto or 100% size of container.
155
203
if (widthInt >= minImageSize || widthInt == 0 ) && heightInt >= minImageSize {
156
- return imgsrc , true
204
+ return imgsrcset , true
157
205
}
158
206
159
207
return "" , false
0 commit comments