View Javadoc
1   /*
2    * Copyright (c) 2002-2025 Gargoyle Software Inc.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    * https://www.apache.org/licenses/LICENSE-2.0
8    *
9    * Unless required by applicable law or agreed to in writing, software
10   * distributed under the License is distributed on an "AS IS" BASIS,
11   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12   * See the License for the specific language governing permissions and
13   * limitations under the License.
14   */
15  package org.htmlunit;
16  
17  import java.io.File;
18  import java.io.Serializable;
19  import java.lang.reflect.Field;
20  import java.util.EnumSet;
21  import java.util.HashMap;
22  import java.util.Locale;
23  import java.util.Map;
24  import java.util.Objects;
25  import java.util.Set;
26  import java.util.TimeZone;
27  
28  import org.apache.commons.io.FilenameUtils;
29  import org.htmlunit.css.CssPixelValueConverter;
30  import org.htmlunit.javascript.configuration.BrowserFeature;
31  import org.htmlunit.javascript.configuration.SupportedBrowser;
32  import org.htmlunit.util.MimeType;
33  
34  /**
35   * Objects of this class represent one specific version of a given browser. Predefined
36   * constants are provided for common browser versions.
37   *
38   * <p>You can create a different browser setup by using the BrowserVersionFactory.
39   * <pre id='htmlUnitCode'>
40   *         final String applicationName = "APPNAME";
41   *         final String applicationVersion = "APPVERSION";
42   *         final String userAgent = "USERAGENT";
43   * </pre>
44   * <pre>
45   *         final BrowserVersion browser =
46   *                 new BrowserVersion.BrowserVersionBuilder(BrowserVersion.FIREFOX)
47   *                     .setApplicationName(applicationName)
48   *                     .setApplicationVersion(applicationVersion)
49   *                     .setUserAgent(userAgent)
50   *                     .build();
51   * </pre>
52   * <p>But keep in mind this new one still behaves like an FF, only the stuff reported to the
53   * outside is changed. This is more or less the same you can do with real browsers installing
54   * plugins like UserAgentSwitcher.
55   *
56   * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a>
57   * @author Daniel Gredler
58   * @author Marc Guillemot
59   * @author Chris Erskine
60   * @author Ahmed Ashour
61   * @author Frank Danek
62   * @author Ronald Brill
63   */
64  @SuppressWarnings({"PMD.AvoidDuplicateLiterals", "PMD.TooManyFields"})
65  public final class BrowserVersion implements Serializable {
66  
67      /** Latest Firefox. */
68      public static final BrowserVersion FIREFOX = new BrowserVersion(138, "FF");
69  
70      private static final int FIREFOX_ESR_NUMERIC = 128;
71  
72      /** Firefox ESR. */
73      public static final BrowserVersion FIREFOX_ESR = new BrowserVersion(FIREFOX_ESR_NUMERIC, "FF-ESR");
74  
75      /** Latest Edge. */
76      public static final BrowserVersion EDGE = new BrowserVersion(136, "Edge");
77  
78      /** Latest Chrome. */
79      public static final BrowserVersion CHROME = new BrowserVersion(136, "Chrome");
80  
81      /**
82       * Array with all supported browsers.
83       */
84      public static final BrowserVersion[] ALL_SUPPORTED_BROWSERS = {CHROME, EDGE, FIREFOX, FIREFOX_ESR};
85  
86      /**
87       * The best supported browser version at the moment.
88       */
89      public static final BrowserVersion BEST_SUPPORTED = CHROME;
90  
91      /** The default browser version. */
92      private static BrowserVersion DefaultBrowserVersion_ = BEST_SUPPORTED;
93  
94      static {
95          FIREFOX_ESR.applicationVersion_ = "5.0 (Windows)";
96          FIREFOX_ESR.userAgent_ = "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:"
97                                      + FIREFOX_ESR.getBrowserVersionNumeric() + ".0) Gecko/20100101 Firefox/"
98                                      + FIREFOX_ESR.getBrowserVersionNumeric() + ".0";
99          FIREFOX_ESR.buildId_ = "20181001000000";
100         FIREFOX_ESR.vendor_ = "";
101         FIREFOX_ESR.productSub_ = "20100101";
102         FIREFOX_ESR.headerNamesOrdered_ = new String[] {
103             HttpHeader.HOST,
104             HttpHeader.USER_AGENT,
105             HttpHeader.ACCEPT,
106             HttpHeader.ACCEPT_LANGUAGE,
107             HttpHeader.ACCEPT_ENCODING,
108             HttpHeader.CONNECTION,
109             HttpHeader.REFERER,
110             HttpHeader.COOKIE,
111             HttpHeader.UPGRADE_INSECURE_REQUESTS,
112             HttpHeader.SEC_FETCH_DEST,
113             HttpHeader.SEC_FETCH_MODE,
114             HttpHeader.SEC_FETCH_SITE,
115             HttpHeader.SEC_FETCH_USER,
116             HttpHeader.PRIORITY};
117         FIREFOX_ESR.htmlAcceptHeader_ = "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8";
118         FIREFOX_ESR.acceptLanguageHeader_ = "en-US,en;q=0.5";
119         FIREFOX_ESR.xmlHttpRequestAcceptHeader_ = "*/*";
120         FIREFOX_ESR.imgAcceptHeader_ = "image/avif,image/webp,image/png,image/svg+xml,image/*;q=0.8,*/*;q=0.5";
121         FIREFOX_ESR.cssAcceptHeader_ = "text/css,*/*;q=0.1";
122         FIREFOX_ESR.fontHeights_ = new int[] {
123             0, 2, 3, 5, 6, 6, 7, 9, 10, 11, 12, 13, 15, 16, 16, 17, 18, 20, 21, 22, 23, 25, 26, 26, 28, 29,
124             31, 32, 33, 34, 35, 37, 38, 38, 39, 41, 42, 43, 44, 45, 47, 48, 48, 49, 51, 52, 53, 54, 56, 58, 59, 59,
125             60, 61, 63, 64, 65, 66, 68, 69, 69, 70, 71, 73, 74, 75, 76, 77, 79, 79,
126             80, 82, 84, 85, 86, 87, 88, 90, 91, 91, 92, 94, 95, 96, 97, 98,
127             100, 101, 101, 102, 103, 105, 106, 107, 108, 111, 112, 112, 113, 114, 116, 117, 118, 119,
128             120, 122, 122, 123, 124, 126, 127, 128, 129, 130, 132, 132, 133, 134, 137, 138, 139,
129             140, 141, 143, 143, 144, 145, 146, 148};
130 
131         FIREFOX.applicationVersion_ = "5.0 (Windows)";
132         FIREFOX.userAgent_ = "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:"
133                                             + FIREFOX.getBrowserVersionNumeric() + ".0) Gecko/20100101 Firefox/"
134                                             + FIREFOX.getBrowserVersionNumeric() + ".0";
135         FIREFOX.buildId_ = "20181001000000";
136         FIREFOX.vendor_ = "";
137         FIREFOX.productSub_ = "20100101";
138         FIREFOX.headerNamesOrdered_ = new String[] {
139             HttpHeader.HOST,
140             HttpHeader.USER_AGENT,
141             HttpHeader.ACCEPT,
142             HttpHeader.ACCEPT_LANGUAGE,
143             HttpHeader.ACCEPT_ENCODING,
144             HttpHeader.CONNECTION,
145             HttpHeader.REFERER,
146             HttpHeader.COOKIE,
147             HttpHeader.UPGRADE_INSECURE_REQUESTS,
148             HttpHeader.SEC_FETCH_DEST,
149             HttpHeader.SEC_FETCH_MODE,
150             HttpHeader.SEC_FETCH_SITE,
151             HttpHeader.SEC_FETCH_USER,
152             HttpHeader.PRIORITY};
153         FIREFOX.htmlAcceptHeader_ = "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8";
154         FIREFOX.acceptLanguageHeader_ = "en-US,en;q=0.5";
155         FIREFOX.xmlHttpRequestAcceptHeader_ = "*/*";
156         FIREFOX.imgAcceptHeader_ = "image/avif,image/webp,image/png,image/svg+xml,image/*;q=0.8,*/*;q=0.5";
157         FIREFOX.cssAcceptHeader_ = "text/css,*/*;q=0.1";
158         FIREFOX.fontHeights_ = new int[] {
159             0, 2, 3, 5, 6, 6, 7, 9, 10, 11, 12, 13, 15, 16, 16, 17, 18, 20, 21, 22, 23, 25, 26, 26, 28, 29,
160             31, 32, 33, 34, 35, 37, 38, 38, 39, 41, 42, 43, 44, 45, 47, 48, 48, 49, 51, 52, 53, 54, 56, 58, 59, 59,
161             60, 61, 63, 64, 65, 66, 68, 69, 69, 70, 71, 73, 74, 75, 76, 77, 79, 79,
162             80, 82, 84, 85, 86, 87, 88, 90, 91, 91, 92, 94, 95, 96, 97, 98,
163             100, 101, 101, 102, 103, 105, 106, 107, 108, 111, 112, 112, 113, 114, 116, 117, 118, 119,
164             120, 122, 122, 123, 124, 126, 127, 128, 129, 130, 132, 132, 133, 134, 137, 138, 139,
165             140, 141, 143, 143, 144, 145, 146, 148};
166 
167         // CHROME (Win10 64bit)
168         CHROME.applicationVersion_ = "5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/"
169                                         + CHROME.getBrowserVersionNumeric() + ".0.0.0 Safari/537.36";
170         CHROME.userAgent_ = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/"
171                                         + CHROME.getBrowserVersionNumeric() + ".0.0.0 Safari/537.36";
172 
173         CHROME.vendor_ = "Google Inc.";
174         CHROME.productSub_ = "20030107";
175         CHROME.headerNamesOrdered_ = new String[] {
176             HttpHeader.HOST,
177             HttpHeader.CONNECTION,
178             HttpHeader.SEC_CH_UA,
179             HttpHeader.SEC_CH_UA_MOBILE,
180             HttpHeader.SEC_CH_UA_PLATFORM,
181             "Upgrade-Insecure-Requests",
182             HttpHeader.USER_AGENT,
183             HttpHeader.ACCEPT,
184             HttpHeader.SEC_FETCH_SITE,
185             HttpHeader.SEC_FETCH_MODE,
186             HttpHeader.SEC_FETCH_USER,
187             HttpHeader.SEC_FETCH_DEST,
188             HttpHeader.REFERER,
189             HttpHeader.ACCEPT_ENCODING,
190             HttpHeader.ACCEPT_LANGUAGE,
191             HttpHeader.COOKIE};
192         CHROME.acceptLanguageHeader_ = "en-US,en;q=0.9";
193         CHROME.htmlAcceptHeader_ = "text/html,application/xhtml+xml,application/xml;"
194                                             + "q=0.9,image/avif,image/webp,image/apng,*/*;"
195                                             + "q=0.8,application/signed-exchange;v=b3;q=0.7";
196         CHROME.imgAcceptHeader_ = "image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8";
197         CHROME.cssAcceptHeader_ = "text/css,*/*;q=0.1";
198         CHROME.scriptAcceptHeader_ = "*/*";
199 
200         /*
201         CHROME.secClientHintUserAgentHeader_ = "\"Not(A:Brand\";v=\"99\", \"Google Chrome\";v=\""
202                 + CHROME.getBrowserVersionNumeric() + "\", \"Chromium\";v=\""
203                 + CHROME.getBrowserVersionNumeric() + "\"";
204         CHROME.secClientHintUserAgentHeader_ = "\"Chromium\";v=\""
205                 + CHROME.getBrowserVersionNumeric() + "\", \"Not:A-Brand\";v=\"24\", \"Google Chrome\";v=\""
206                 + CHROME.getBrowserVersionNumeric() + "\"";
207         */
208         // 135
209         /*
210         CHROME.secClientHintUserAgentHeader_ = "\"Google Chrome\";v=\""
211                 + CHROME.getBrowserVersionNumeric() + "\", \"Not-A.Brand\";v=\"8\", \"Chromium\";v=\""
212                 + CHROME.getBrowserVersionNumeric() + "\"";
213         */
214         // 136
215         CHROME.secClientHintUserAgentHeader_ = "\"Chromium\";v=\""
216                 + CHROME.getBrowserVersionNumeric() + "\", \"Google Chrome\";v=\""
217                 + CHROME.getBrowserVersionNumeric() + "\", \"Not.A/Brand\";v=\"99\"";
218 
219         CHROME.fontHeights_ = new int[] {
220             0, 1, 2, 4, 5, 5, 6, 8, 9, 10, 11, 12, 15, 16, 16, 17, 18, 20, 21, 22, 23, 25, 26, 26,
221             27, 28, 30, 31, 32, 33, 34, 36, 37, 37, 38, 40, 42, 43, 44, 45, 47, 48, 48, 49, 51, 52, 53, 54, 55, 57,
222             58, 58, 59, 60, 62, 63, 64, 65, 67, 69, 69, 70, 71, 73, 74, 75, 76, 77, 79, 79, 80, 81, 83, 84, 85, 86,
223             87, 89, 90, 90, 91, 93, 94, 96, 97, 98, 100, 101, 101, 102, 103, 105, 106, 107, 108, 110, 111, 111, 112,
224             113, 115, 116, 117, 118, 119, 121, 122, 123, 124, 126, 127, 128, 129, 130, 132, 132, 133, 134, 136, 137,
225             138, 139, 140, 142, 142, 143, 144, 145, 147};
226 
227         // EDGE (Win10 64bit)
228         EDGE.applicationVersion_ = "5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/"
229                                         + EDGE.getBrowserVersionNumeric() + ".0.0.0 Safari/537.36 Edg/"
230                                         + EDGE.getBrowserVersionNumeric() + ".0.0.0";
231         EDGE.userAgent_ = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/"
232                                         + EDGE.getBrowserVersionNumeric() + ".0.0.0 Safari/537.36 Edg/"
233                                         + EDGE.getBrowserVersionNumeric() + ".0.0.0";
234 
235         EDGE.vendor_ = "Google Inc.";
236         EDGE.productSub_ = "20030107";
237         EDGE.headerNamesOrdered_ = new String[] {
238             HttpHeader.HOST,
239             HttpHeader.CONNECTION,
240             HttpHeader.SEC_CH_UA,
241             HttpHeader.SEC_CH_UA_MOBILE,
242             HttpHeader.SEC_CH_UA_PLATFORM,
243             "Upgrade-Insecure-Requests",
244             HttpHeader.USER_AGENT,
245             HttpHeader.ACCEPT,
246             HttpHeader.SEC_FETCH_SITE,
247             HttpHeader.SEC_FETCH_MODE,
248             HttpHeader.SEC_FETCH_USER,
249             HttpHeader.SEC_FETCH_DEST,
250             HttpHeader.REFERER,
251             HttpHeader.ACCEPT_ENCODING,
252             HttpHeader.ACCEPT_LANGUAGE,
253             HttpHeader.COOKIE};
254         EDGE.acceptLanguageHeader_ = "en-US,en;q=0.9";
255         EDGE.htmlAcceptHeader_ = "text/html,application/xhtml+xml,application/xml;"
256                                             + "q=0.9,image/avif,image/webp,image/apng,*/*;"
257                                             + "q=0.8,application/signed-exchange;v=b3;q=0.7";
258         EDGE.imgAcceptHeader_ = "image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8";
259         EDGE.cssAcceptHeader_ = "text/css,*/*;q=0.1";
260         EDGE.scriptAcceptHeader_ = "*/*";
261 
262         /*
263         EDGE.secClientHintUserAgentHeader_ = "\"Chromium\";v=\""
264                 + EDGE.getBrowserVersionNumeric() + "\", \"Not:A-Brand\";v=\"24\", \"Microsoft Edge\";v=\""
265                 + EDGE.getBrowserVersionNumeric() + "\"";
266         EDGE.secClientHintUserAgentHeader_ = "\"Not(A:Brand\";v=\"99\", \"Microsoft Edge\";v=\""
267                 + EDGE.getBrowserVersionNumeric() + "\", \"Chromium\";v=\""
268                 + EDGE.getBrowserVersionNumeric() + "\"";
269         */
270         // 135
271         /*
272         EDGE.secClientHintUserAgentHeader_ = "\"Microsoft Edge\";v=\""
273                 + EDGE.getBrowserVersionNumeric() + "\", \"Not-A.Brand\";v=\"8\", \"Chromium\";v=\""
274                 + EDGE.getBrowserVersionNumeric() + "\"";
275         */
276         // 136
277         EDGE.secClientHintUserAgentHeader_ = "\"Chromium\";v=\""
278                 + EDGE.getBrowserVersionNumeric() + "\", \"Microsoft Edge\";v=\""
279                 + EDGE.getBrowserVersionNumeric() + "\", \"Not.A/Brand\";v=\"99\"";
280 
281         EDGE.fontHeights_ = new int[] {
282             0, 1, 2, 4, 5, 5, 6, 8, 9, 10, 11, 12, 15, 16, 16, 17, 18, 20, 21, 22, 23, 25, 26, 26,
283             27, 28, 30, 31, 32, 33, 34, 36, 37, 37, 38, 40, 42, 43, 44, 45, 47, 48, 48, 49, 51, 52, 53, 54, 55, 57,
284             58, 58, 59, 60, 62, 63, 64, 65, 67, 69, 69, 70, 71, 73, 74, 75, 76, 77, 79, 79, 80, 81, 83, 84, 85, 86,
285             87, 89, 90, 90, 91, 93, 94, 96, 97, 98, 100, 101, 101, 102, 103, 105, 106, 107, 108, 110, 111, 111, 112,
286             113, 115, 116, 117, 118, 119, 121, 122, 123, 124, 126, 127, 128, 129, 130, 132, 132, 133, 134, 136, 137,
287             138, 139, 140, 142, 142, 143, 144, 145, 147};
288 
289         // default file upload mime types
290         CHROME.registerUploadMimeType("html", MimeType.TEXT_HTML);
291         CHROME.registerUploadMimeType("htm", MimeType.TEXT_HTML);
292         CHROME.registerUploadMimeType("css", MimeType.TEXT_CSS);
293         CHROME.registerUploadMimeType("xml", MimeType.TEXT_XML);
294         CHROME.registerUploadMimeType("gif", MimeType.IMAGE_GIF);
295         CHROME.registerUploadMimeType("jpeg", MimeType.IMAGE_JPEG);
296         CHROME.registerUploadMimeType("jpg", MimeType.IMAGE_JPEG);
297         CHROME.registerUploadMimeType("png", MimeType.IMAGE_PNG);
298         CHROME.registerUploadMimeType("pdf", "application/pdf");
299         CHROME.registerUploadMimeType("webp", "image/webp");
300         CHROME.registerUploadMimeType("mp4", "video/mp4");
301         CHROME.registerUploadMimeType("m4v", "video/mp4");
302         CHROME.registerUploadMimeType("m4a", "audio/x-m4a");
303         CHROME.registerUploadMimeType("mp3", "audio/mpeg");
304         CHROME.registerUploadMimeType("ogv", "video/ogg");
305         CHROME.registerUploadMimeType("ogm", "video/ogg");
306         CHROME.registerUploadMimeType("ogg", "audio/ogg");
307         CHROME.registerUploadMimeType("oga", "audio/ogg");
308         CHROME.registerUploadMimeType("opus", "audio/ogg");
309         CHROME.registerUploadMimeType("webm", "video/webm");
310         CHROME.registerUploadMimeType("wav", "audio/wav");
311         CHROME.registerUploadMimeType("flac", "audio/flac");
312         CHROME.registerUploadMimeType("xhtml", "application/xhtml+xml");
313         CHROME.registerUploadMimeType("xht", "application/xhtml+xml");
314         CHROME.registerUploadMimeType("xhtm", "application/xhtml+xml");
315         CHROME.registerUploadMimeType("txt", MimeType.TEXT_PLAIN);
316         CHROME.registerUploadMimeType("text", MimeType.TEXT_PLAIN);
317 
318         EDGE.registerUploadMimeType("html", MimeType.TEXT_HTML);
319         EDGE.registerUploadMimeType("htm", MimeType.TEXT_HTML);
320         EDGE.registerUploadMimeType("css", MimeType.TEXT_CSS);
321         EDGE.registerUploadMimeType("xml", MimeType.TEXT_XML);
322         EDGE.registerUploadMimeType("gif", MimeType.IMAGE_GIF);
323         EDGE.registerUploadMimeType("jpeg", MimeType.IMAGE_JPEG);
324         EDGE.registerUploadMimeType("jpg", MimeType.IMAGE_JPEG);
325         EDGE.registerUploadMimeType("png", MimeType.IMAGE_PNG);
326         EDGE.registerUploadMimeType("pdf", "application/pdf");
327         EDGE.registerUploadMimeType("webp", "image/webp");
328         EDGE.registerUploadMimeType("mp4", "video/mp4");
329         EDGE.registerUploadMimeType("m4v", "video/mp4");
330         EDGE.registerUploadMimeType("m4a", "audio/x-m4a");
331         EDGE.registerUploadMimeType("mp3", "audio/mpeg");
332         EDGE.registerUploadMimeType("ogv", "video/ogg");
333         EDGE.registerUploadMimeType("ogm", "video/ogg");
334         EDGE.registerUploadMimeType("ogg", "audio/ogg");
335         EDGE.registerUploadMimeType("oga", "audio/ogg");
336         EDGE.registerUploadMimeType("opus", "audio/ogg");
337         EDGE.registerUploadMimeType("webm", "video/webm");
338         EDGE.registerUploadMimeType("wav", "audio/wav");
339         EDGE.registerUploadMimeType("flac", "audio/flac");
340         EDGE.registerUploadMimeType("xhtml", "application/xhtml+xml");
341         EDGE.registerUploadMimeType("xht", "application/xhtml+xml");
342         EDGE.registerUploadMimeType("xhtm", "application/xhtml+xml");
343         EDGE.registerUploadMimeType("txt", MimeType.TEXT_PLAIN);
344         EDGE.registerUploadMimeType("text", MimeType.TEXT_PLAIN);
345 
346         FIREFOX_ESR.registerUploadMimeType("html", MimeType.TEXT_HTML);
347         FIREFOX_ESR.registerUploadMimeType("htm", MimeType.TEXT_HTML);
348         FIREFOX_ESR.registerUploadMimeType("css", MimeType.TEXT_CSS);
349         FIREFOX_ESR.registerUploadMimeType("xml", MimeType.TEXT_XML);
350         FIREFOX_ESR.registerUploadMimeType("gif", MimeType.IMAGE_GIF);
351         FIREFOX_ESR.registerUploadMimeType("jpeg", MimeType.IMAGE_JPEG);
352         FIREFOX_ESR.registerUploadMimeType("jpg", MimeType.IMAGE_JPEG);
353         FIREFOX_ESR.registerUploadMimeType("pdf", "application/pdf");
354         FIREFOX_ESR.registerUploadMimeType("mp4", "video/mp4");
355         FIREFOX_ESR.registerUploadMimeType("m4v", "video/mp4");
356         FIREFOX_ESR.registerUploadMimeType("m4a", "audio/mp4");
357         FIREFOX_ESR.registerUploadMimeType("png", MimeType.IMAGE_PNG);
358         FIREFOX_ESR.registerUploadMimeType("mp3", "audio/mpeg");
359         FIREFOX_ESR.registerUploadMimeType("ogv", "video/ogg");
360         FIREFOX_ESR.registerUploadMimeType("ogm", "video/ogg");
361         FIREFOX_ESR.registerUploadMimeType("ogg", "video/ogg");
362         FIREFOX_ESR.registerUploadMimeType("oga", "audio/ogg");
363         FIREFOX_ESR.registerUploadMimeType("opus", "audio/ogg");
364         FIREFOX_ESR.registerUploadMimeType("webm", "video/webm");
365         FIREFOX_ESR.registerUploadMimeType("webp", "image/webp");
366         FIREFOX_ESR.registerUploadMimeType("wav", "audio/wav");
367         FIREFOX_ESR.registerUploadMimeType("flac", "audio/x-flac");
368         FIREFOX_ESR.registerUploadMimeType("xhtml", "application/xhtml+xml");
369         FIREFOX_ESR.registerUploadMimeType("xht", "application/xhtml+xml");
370         FIREFOX_ESR.registerUploadMimeType("txt", MimeType.TEXT_PLAIN);
371         FIREFOX_ESR.registerUploadMimeType("text", MimeType.TEXT_PLAIN);
372 
373         FIREFOX.registerUploadMimeType("html", MimeType.TEXT_HTML);
374         FIREFOX.registerUploadMimeType("htm", MimeType.TEXT_HTML);
375         FIREFOX.registerUploadMimeType("css", MimeType.TEXT_CSS);
376         FIREFOX.registerUploadMimeType("xml", MimeType.TEXT_XML);
377         FIREFOX.registerUploadMimeType("gif", MimeType.IMAGE_GIF);
378         FIREFOX.registerUploadMimeType("jpeg", MimeType.IMAGE_JPEG);
379         FIREFOX.registerUploadMimeType("jpg", MimeType.IMAGE_JPEG);
380         FIREFOX.registerUploadMimeType("pdf", "application/pdf");
381         FIREFOX.registerUploadMimeType("mp4", "video/mp4");
382         FIREFOX.registerUploadMimeType("m4v", "video/mp4");
383         FIREFOX.registerUploadMimeType("m4a", "audio/mp4");
384         FIREFOX.registerUploadMimeType("png", MimeType.IMAGE_PNG);
385         FIREFOX.registerUploadMimeType("mp3", "audio/mpeg");
386         FIREFOX.registerUploadMimeType("ogv", "video/ogg");
387         FIREFOX.registerUploadMimeType("ogm", "video/ogg");
388         FIREFOX.registerUploadMimeType("ogg", "application/ogg");
389         FIREFOX.registerUploadMimeType("oga", "audio/ogg");
390         FIREFOX.registerUploadMimeType("opus", "audio/ogg");
391         FIREFOX.registerUploadMimeType("webm", "video/webm");
392         FIREFOX.registerUploadMimeType("webp", "image/webp");
393         FIREFOX.registerUploadMimeType("wav", "audio/wav");
394         FIREFOX.registerUploadMimeType("flac", "audio/x-flac");
395         FIREFOX.registerUploadMimeType("xhtml", "application/xhtml+xml");
396         FIREFOX.registerUploadMimeType("xht", "application/xhtml+xml");
397         FIREFOX.registerUploadMimeType("txt", MimeType.TEXT_PLAIN);
398         FIREFOX.registerUploadMimeType("text", MimeType.TEXT_PLAIN);
399     }
400 
401     private final int browserVersionNumeric_;
402     private final String nickname_;
403 
404     private String applicationCodeName_;
405     private String applicationMinorVersion_;
406     private String applicationName_;
407     private String applicationVersion_;
408     private String buildId_;
409     private String productSub_;
410     private String vendor_;
411     private Locale browserLocale_;
412     private boolean onLine_;
413     private String platform_;
414     private TimeZone systemTimezone_;
415     private String userAgent_;
416     private final Set<BrowserVersionFeatures> features_;
417     private String acceptEncodingHeader_;
418     private String acceptLanguageHeader_;
419     private String htmlAcceptHeader_;
420     private String imgAcceptHeader_;
421     private String cssAcceptHeader_;
422     private String scriptAcceptHeader_;
423     private String secClientHintUserAgentHeader_;
424     private String secClientHintUserAgentPlatformHeader_;
425     private String xmlHttpRequestAcceptHeader_;
426     private String[] headerNamesOrdered_;
427     private int[] fontHeights_;
428     private final Map<String, String> uploadMimeTypes_;
429 
430     /**
431      * Creates a new browser version instance.
432      *
433      * @param browserVersionNumeric the floating number version of the browser
434      * @param nickname the short name of the browser (like "FF", "CHROME", ...) - has to be unique
435      */
436     BrowserVersion(final int browserVersionNumeric, final String nickname) {
437         browserVersionNumeric_ = browserVersionNumeric;
438         nickname_ = nickname;
439 
440         applicationCodeName_ = "Mozilla";
441         applicationMinorVersion_ = "0";
442         applicationName_ = "Netscape";
443         onLine_ = true;
444         platform_ = "Win32";
445 
446         browserLocale_ = Locale.forLanguageTag("en-US");
447         systemTimezone_ = TimeZone.getTimeZone("America/New_York");
448 
449         acceptEncodingHeader_ = "gzip, deflate, br";
450         htmlAcceptHeader_ = "*/*";
451         imgAcceptHeader_ = "*/*";
452         cssAcceptHeader_ = "*/*";
453         scriptAcceptHeader_ = "*/*";
454         xmlHttpRequestAcceptHeader_ = "*/*";
455         secClientHintUserAgentHeader_ = "";
456         secClientHintUserAgentPlatformHeader_ = "\"Windows\"";
457 
458         features_ = EnumSet.noneOf(BrowserVersionFeatures.class);
459         uploadMimeTypes_ = new HashMap<>();
460 
461         initFeatures();
462     }
463 
464     /**
465      * @param other the {@link BrowserVersion} to compare with
466      * @return true if the nickname and the numeric version are the same
467      */
468     public boolean isSameBrowser(final BrowserVersion other) {
469         return Objects.equals(nickname_, other.nickname_) && browserVersionNumeric_ == other.browserVersionNumeric_;
470     }
471 
472     private void initFeatures() {
473         final SupportedBrowser expectedBrowser;
474         if (isEdge()) {
475             expectedBrowser = SupportedBrowser.EDGE;
476         }
477         else if (isFirefoxESR()) {
478             expectedBrowser = SupportedBrowser.FF_ESR;
479         }
480         else if (isFirefox()) {
481             expectedBrowser = SupportedBrowser.FF;
482         }
483         else {
484             expectedBrowser = SupportedBrowser.CHROME;
485         }
486 
487         for (final BrowserVersionFeatures features : BrowserVersionFeatures.values()) {
488             try {
489                 final Field field = BrowserVersionFeatures.class.getField(features.name());
490                 final BrowserFeature browserFeature = field.getAnnotation(BrowserFeature.class);
491                 if (browserFeature != null) {
492                     for (final SupportedBrowser browser : browserFeature.value()) {
493                         if (expectedBrowser == browser) {
494                             features_.add(features);
495                         }
496                     }
497                 }
498             }
499             catch (final NoSuchFieldException e) {
500                 // should never happen
501                 throw new IllegalStateException(e);
502             }
503         }
504     }
505 
506     /**
507      * Returns the default browser version that is used whenever a specific version isn't specified.
508      * Defaults to {@link #BEST_SUPPORTED}.
509      * @return the default browser version
510      */
511     public static BrowserVersion getDefault() {
512         return DefaultBrowserVersion_;
513     }
514 
515     /**
516      * Sets the default browser version that is used whenever a specific version isn't specified.
517      * @param newBrowserVersion the new default browser version
518      */
519     public static void setDefault(final BrowserVersion newBrowserVersion) {
520         WebAssert.notNull("newBrowserVersion", newBrowserVersion);
521         DefaultBrowserVersion_ = newBrowserVersion;
522     }
523 
524     /**
525      * Returns {@code true} if this <code>BrowserVersion</code> instance represents some
526      * version of Google Chrome. Note that Google Chrome does not return 'Chrome'
527      * in the application name, we have to look in the nickname.
528      * @return whether this version is a version of a Chrome browser
529      */
530     public boolean isChrome() {
531         return getNickname().startsWith("Chrome");
532     }
533 
534     /**
535      * Returns {@code true} if this <code>BrowserVersion</code> instance represents some
536      * version of Microsoft Edge.
537      * @return whether this version is a version of a Chrome browser
538      */
539     public boolean isEdge() {
540         return getNickname().startsWith("Edge");
541     }
542 
543     /**
544      * Returns {@code true} if this <code>BrowserVersion</code> instance represents some
545      * version of Firefox.
546      * @return whether this version is a version of a Firefox browser
547      */
548     public boolean isFirefox() {
549         return getNickname().startsWith("FF");
550     }
551 
552     /**
553      * <span style="color:red">INTERNAL API - SUBJECT TO CHANGE AT ANY TIME - USE AT YOUR OWN RISK.</span><br>
554      * @return whether this is version 78  of a Firefox browser
555      */
556     public boolean isFirefoxESR() {
557         return isFirefox() && getBrowserVersionNumeric() == FIREFOX_ESR_NUMERIC;
558     }
559 
560     /**
561      * Returns the short name of the browser like {@code FF}, {@code CHROME}, etc.
562      *
563      * @return the short name (if any)
564      */
565     public String getNickname() {
566         return nickname_;
567     }
568 
569     /**
570      * @return the browserVersionNumeric
571      */
572     public int getBrowserVersionNumeric() {
573         return browserVersionNumeric_;
574     }
575 
576     /**
577      * Returns the application code name, for example "Mozilla".
578      * Default value is "Mozilla" if not explicitly configured.
579      * @return the application code name
580      * @see <a href="http://msdn.microsoft.com/en-us/library/ms533077.aspx">MSDN documentation</a>
581      */
582     public String getApplicationCodeName() {
583         return applicationCodeName_;
584     }
585 
586     /**
587      * Returns the application minor version, for example "0".
588      * Default value is "0" if not explicitly configured.
589      * @return the application minor version
590      * @see <a href="http://msdn.microsoft.com/en-us/library/ms533078.aspx">MSDN documentation</a>
591      */
592     public String getApplicationMinorVersion() {
593         return applicationMinorVersion_;
594     }
595 
596     /**
597      * Returns the application name, for example "Netscape".
598      * @return the application name
599      * @see <a href="http://msdn.microsoft.com/en-us/library/ms533079.aspx">MSDN documentation</a>
600      */
601     public String getApplicationName() {
602         return applicationName_;
603     }
604 
605     /**
606      * Returns the application version, for example "4.0 (compatible; MSIE 6.0b; Windows 98)".
607      * @return the application version
608      * @see <a href="http://msdn.microsoft.com/en-us/library/ms533080.aspx">MSDN documentation</a>
609      */
610     public String getApplicationVersion() {
611         return applicationVersion_;
612     }
613 
614     /**
615      * @return the vendor
616      */
617     public String getVendor() {
618         return vendor_;
619     }
620 
621     /**
622      * Returns the browser locale.
623      * Default value is ENGLISH_US if not explicitly configured.
624      * @return the system locale
625      */
626     public Locale getBrowserLocale() {
627         return browserLocale_;
628     }
629 
630     /**
631      * Returns the browser application language, for example "en-us".
632      * Default value is ENGLISH_US if not explicitly configured.
633      * @return the browser application language
634      * @see <a href="http://msdn.microsoft.com/en-us/library/ms533542.aspx">MSDN documentation</a>
635      */
636     public String getBrowserLanguage() {
637         return browserLocale_.toLanguageTag();
638     }
639 
640     /**
641      * Returns {@code true} if the browser is currently online.
642      * Default value is {@code true} if not explicitly configured.
643      * @return {@code true} if the browser is currently online
644      * @see <a href="http://msdn.microsoft.com/en-us/library/ms534307.aspx">MSDN documentation</a>
645      */
646     public boolean isOnLine() {
647         return onLine_;
648     }
649 
650     /**
651      * Returns the platform on which the application is running, for example "Win32".
652      * Default value is 'Win32' if not explicitly configured.
653      * @return the platform on which the application is running
654      * @see <a href="http://msdn.microsoft.com/en-us/library/ms534340.aspx">MSDN documentation</a>
655      */
656     public String getPlatform() {
657         return platform_;
658     }
659 
660     /**
661      * Returns the system {@link TimeZone}.
662      * Default value is {@code America/New_York} if not explicitly configured.
663      * @return the system {@link TimeZone}
664      */
665     public TimeZone getSystemTimezone() {
666         return systemTimezone_;
667     }
668 
669     /**
670      * Returns the user agent string, for example "Mozilla/4.0 (compatible; MSIE 6.0b; Windows 98)".
671      * @return the user agent string
672      */
673     public String getUserAgent() {
674         return userAgent_;
675     }
676 
677     /**
678      * Returns the value used by the browser for the {@code Accept_Encoding} header.
679      * @return the accept encoding header string
680      */
681     public String getAcceptEncodingHeader() {
682         return acceptEncodingHeader_;
683     }
684 
685     /**
686      * Returns the value used by the browser for the {@code Accept_Language} header.
687      * @return the accept language header string
688      */
689     public String getAcceptLanguageHeader() {
690         return acceptLanguageHeader_;
691     }
692 
693     /**
694      * Returns the value used by the browser for the {@code Accept} header if requesting a page.
695      * @return the accept header string
696      */
697     public String getHtmlAcceptHeader() {
698         return htmlAcceptHeader_;
699     }
700 
701     /**
702      * Returns the value used by the browser for the {@code Accept} header
703      * if requesting a script.
704      * @return the accept header string
705      */
706     public String getScriptAcceptHeader() {
707         return scriptAcceptHeader_;
708     }
709 
710     /**
711      * Returns the value used by the browser for the {@code Accept} header
712      * if performing an XMLHttpRequest.
713      * @return the accept header string
714      */
715     public String getXmlHttpRequestAcceptHeader() {
716         return xmlHttpRequestAcceptHeader_;
717     }
718 
719     /**
720      * Returns the value used by the browser for the {@code Accept} header
721      * if requesting an image.
722      * @return the accept header string
723      */
724     public String getImgAcceptHeader() {
725         return imgAcceptHeader_;
726     }
727 
728     /**
729      * Returns the value used by the browser for the {@code Accept} header
730      * if requesting a CSS declaration.
731      * @return the accept header string
732      */
733     public String getCssAcceptHeader() {
734         return cssAcceptHeader_;
735     }
736 
737     /**
738      * Returns the value used by the browser for the {@code sec-ch-ua} header.
739      * @return the sec-ch-ua header string
740      */
741     public String getSecClientHintUserAgentHeader() {
742         return secClientHintUserAgentHeader_;
743     }
744 
745     /**
746      * Returns the value used by the browser for the {@code sec-ch-ua-platform} header.
747      * @return the sec-ch-ua-platform header string
748      */
749     public String getSecClientHintUserAgentPlatformHeader() {
750         return secClientHintUserAgentPlatformHeader_;
751     }
752 
753     /**
754      * Indicates if this instance has the given feature. Used for HtmlUnit internal processing.
755      * @param property the property name
756      * @return {@code false} if this browser doesn't have this feature
757      */
758     public boolean hasFeature(final BrowserVersionFeatures property) {
759         return features_.contains(property);
760     }
761 
762     /**
763      * Returns the buildId.
764      * @return the buildId
765      */
766     public String getBuildId() {
767         return buildId_;
768     }
769 
770     /**
771      * Returns the productSub.
772      * @return the buildId
773      */
774     public String getProductSub() {
775         return productSub_;
776     }
777 
778     /**
779      * Gets the headers names, so they are sent in the given order (if included in the request).
780      * @return headerNames the header names in ordered manner
781      */
782     public String[] getHeaderNamesOrdered() {
783         return headerNamesOrdered_;
784     }
785 
786     /**
787      * Registers a new mime type for the provided file extension.
788      * @param fileExtension the file extension used to determine the mime type
789      * @param mimeType the mime type to be used when uploading files with this extension
790      */
791     public void registerUploadMimeType(final String fileExtension, final String mimeType) {
792         if (fileExtension != null) {
793             uploadMimeTypes_.put(fileExtension.toLowerCase(Locale.ROOT), mimeType);
794         }
795     }
796 
797     /**
798      * Determines the content type for the given file.
799      * @param file the file
800      * @return a content type or an empty string if unknown
801      */
802     public String getUploadMimeType(final File file) {
803         if (file == null) {
804             return "";
805         }
806 
807         final String fileExtension = FilenameUtils.getExtension(file.getName());
808 
809         final String mimeType = uploadMimeTypes_.get(fileExtension.toLowerCase(Locale.ROOT));
810         if (mimeType != null) {
811             return mimeType;
812         }
813         return "";
814     }
815 
816     /**
817      * Returns the corresponding height of the specified {@code fontSize}.
818      * @param fontSize the font size
819      * @return the corresponding height
820      */
821     public int getFontHeight(final String fontSize) {
822         if (fontHeights_ == null || fontSize.isEmpty()) {
823             return 18;
824         }
825 
826         if ("xx-small".equalsIgnoreCase(fontSize)) {
827             return fontHeights_[9];
828         }
829         if ("x-small".equalsIgnoreCase(fontSize)) {
830             return fontHeights_[10];
831         }
832         if ("small".equalsIgnoreCase(fontSize)) {
833             return fontHeights_[13];
834         }
835         if ("medium".equalsIgnoreCase(fontSize)) {
836             return fontHeights_[16];
837         }
838         if ("large".equalsIgnoreCase(fontSize)) {
839             return fontHeights_[18];
840         }
841         if ("x-large".equalsIgnoreCase(fontSize)) {
842             return fontHeights_[24];
843         }
844         if ("xx-large".equalsIgnoreCase(fontSize)) {
845             return fontHeights_[32];
846         }
847         if ("xxx-large".equalsIgnoreCase(fontSize)) {
848             return fontHeights_[48];
849         }
850 
851         if ("smaller".equalsIgnoreCase(fontSize)) {
852             return fontHeights_[13];
853         }
854         if ("larger".equalsIgnoreCase(fontSize)) {
855             return fontHeights_[19];
856         }
857 
858         final int fontSizeInt = CssPixelValueConverter.pixelValue(fontSize);
859         if (fontSizeInt < fontHeights_.length) {
860             return fontHeights_[fontSizeInt];
861         }
862 
863         return (int) (fontSizeInt * 1.2);
864     }
865 
866     /**
867      * @return the pixesPerChar based on the specified {@code fontSize}
868      */
869     public int getPixesPerChar() {
870         return 10;
871     }
872 
873     @Override
874     public String toString() {
875         return nickname_;
876     }
877 
878     /**
879      * Because BrowserVersion is immutable we need a builder
880      * for this complex object setup.
881      */
882     @SuppressWarnings("PMD.LinguisticNaming")
883     public static class BrowserVersionBuilder {
884         private final BrowserVersion workPiece_;
885 
886         /**
887          * Creates a new BrowserVersionBuilder using the given browser version
888          * as template for the browser to be constructed.
889          * @param version the blueprint
890          */
891         public BrowserVersionBuilder(final BrowserVersion version) {
892             workPiece_ = new BrowserVersion(version.getBrowserVersionNumeric(), version.getNickname());
893 
894             setApplicationVersion(version.getApplicationVersion())
895                 .setUserAgent(version.getUserAgent())
896                 .setApplicationName(version.getApplicationName())
897                 .setApplicationCodeName(version.getApplicationCodeName())
898                 .setApplicationMinorVersion(version.getApplicationMinorVersion())
899                 .setVendor(version.getVendor())
900                 .setBrowserLanguage(version.getBrowserLanguage())
901                 .setOnLine(version.isOnLine())
902                 .setPlatform(version.getPlatform())
903                 .setSystemTimezone(version.getSystemTimezone())
904                 .setBuildId(version.getBuildId())
905                 .setProductSub(version.getProductSub())
906                 .setAcceptEncodingHeader(version.getAcceptEncodingHeader())
907                 .setAcceptLanguageHeader(version.getAcceptLanguageHeader())
908                 .setHtmlAcceptHeader(version.getHtmlAcceptHeader())
909                 .setImgAcceptHeader(version.getImgAcceptHeader())
910                 .setCssAcceptHeader(version.getCssAcceptHeader())
911                 .setScriptAcceptHeader(version.getScriptAcceptHeader())
912                 .setXmlHttpRequestAcceptHeader(version.getXmlHttpRequestAcceptHeader())
913                 .setSecClientHintUserAgentHeader(version.getSecClientHintUserAgentHeader())
914                 .setSecClientHintUserAgentPlatformHeader(version.getSecClientHintUserAgentPlatformHeader())
915                 .setHeaderNamesOrdered(version.getHeaderNamesOrdered())
916                 .setFontHeights(version.fontHeights_);
917 
918             workPiece_.features_.addAll(version.features_);
919             workPiece_.uploadMimeTypes_.putAll(version.uploadMimeTypes_);
920         }
921 
922         /**
923          * @return the new immutable browser version
924          */
925         public BrowserVersion build() {
926             return workPiece_;
927         }
928 
929         /**
930          * @param applicationMinorVersion the applicationMinorVersion to set
931          * @return this for fluent use
932          */
933         public BrowserVersionBuilder setApplicationMinorVersion(final String applicationMinorVersion) {
934             workPiece_.applicationMinorVersion_ = applicationMinorVersion;
935             return this;
936         }
937 
938         /**
939          * @param applicationName the applicationName to set
940          * @return this for fluent use
941          */
942         public BrowserVersionBuilder setApplicationName(final String applicationName) {
943             workPiece_.applicationName_ = applicationName;
944             return this;
945         }
946 
947         /**
948          * @param applicationVersion the applicationVersion to set
949          * @return this for fluent use
950          */
951         public BrowserVersionBuilder setApplicationVersion(final String applicationVersion) {
952             workPiece_.applicationVersion_ = applicationVersion;
953             return this;
954         }
955 
956         /**
957          * @param vendor the vendor to set
958          * @return this for fluent use
959          */
960         public BrowserVersionBuilder setVendor(final String vendor) {
961             workPiece_.vendor_ = vendor;
962             return this;
963         }
964 
965         /**
966          * @param applicationCodeName the applicationCodeName to set
967          * @return this for fluent use
968          */
969         public BrowserVersionBuilder setApplicationCodeName(final String applicationCodeName) {
970             workPiece_.applicationCodeName_ = applicationCodeName;
971             return this;
972         }
973 
974         /**
975          * Changes the browser language property. This is used for various output
976          * formating. If you like change the language the browser requests from the server
977          * you have to adjust the {@link #setAcceptLanguageHeader(String)}.
978          *
979          * @param browserLanguage the browserLanguage to set
980          * @return this for fluent use
981          */
982         public BrowserVersionBuilder setBrowserLanguage(final String browserLanguage) {
983             workPiece_.browserLocale_ = Locale.forLanguageTag(browserLanguage);
984             return this;
985         }
986 
987         /**
988          * @param onLine the onLine to set
989          * @return this for fluent use
990          */
991         public BrowserVersionBuilder setOnLine(final boolean onLine) {
992             workPiece_.onLine_ = onLine;
993             return this;
994         }
995 
996         /**
997          * @param platform the platform to set
998          * @return this for fluent use
999          */
1000         public BrowserVersionBuilder setPlatform(final String platform) {
1001             workPiece_.platform_ = platform;
1002             return this;
1003         }
1004 
1005         /**
1006          * @param systemTimezone the systemTimezone to set
1007          * @return this for fluent use
1008          */
1009         public BrowserVersionBuilder setSystemTimezone(final TimeZone systemTimezone) {
1010             workPiece_.systemTimezone_ = systemTimezone;
1011             return this;
1012         }
1013 
1014         /**
1015          * @param userAgent the userAgent to set
1016          * @return this for fluent use
1017          */
1018         public BrowserVersionBuilder setUserAgent(final String userAgent) {
1019             workPiece_.userAgent_ = userAgent;
1020             return this;
1021         }
1022 
1023         /**
1024          * @param acceptEncodingHeader the {@code Accept-Encoding} header
1025          * @return this for fluent use
1026          */
1027         public BrowserVersionBuilder setAcceptEncodingHeader(final String acceptEncodingHeader) {
1028             workPiece_.acceptEncodingHeader_ = acceptEncodingHeader;
1029             return this;
1030         }
1031 
1032         /**
1033          * @param acceptLanguageHeader the {@code Accept-Language} header
1034          * @return this for fluent use
1035          */
1036         public BrowserVersionBuilder setAcceptLanguageHeader(final String acceptLanguageHeader) {
1037             workPiece_.acceptLanguageHeader_ = acceptLanguageHeader;
1038             return this;
1039         }
1040 
1041         /**
1042          * @param htmlAcceptHeader the {@code Accept} header to be used when retrieving pages
1043          * @return this for fluent use
1044          */
1045         public BrowserVersionBuilder setHtmlAcceptHeader(final String htmlAcceptHeader) {
1046             workPiece_.htmlAcceptHeader_ = htmlAcceptHeader;
1047             return this;
1048         }
1049 
1050         /**
1051          * @param imgAcceptHeader the {@code Accept} header to be used when retrieving images
1052          * @return this for fluent use
1053          */
1054         public BrowserVersionBuilder setImgAcceptHeader(final String imgAcceptHeader) {
1055             workPiece_.imgAcceptHeader_ = imgAcceptHeader;
1056             return this;
1057         }
1058 
1059         /**
1060          * @param cssAcceptHeader the {@code Accept} header to be used when retrieving pages
1061          * @return this for fluent use
1062          */
1063         public BrowserVersionBuilder setCssAcceptHeader(final String cssAcceptHeader) {
1064             workPiece_.cssAcceptHeader_ = cssAcceptHeader;
1065             return this;
1066         }
1067 
1068         /**
1069          * @param scriptAcceptHeader the {@code Accept} header to be used when retrieving scripts
1070          * @return this for fluent use
1071          */
1072         public BrowserVersionBuilder setScriptAcceptHeader(final String scriptAcceptHeader) {
1073             workPiece_.scriptAcceptHeader_ = scriptAcceptHeader;
1074             return this;
1075         }
1076 
1077         /**
1078          * @param xmlHttpRequestAcceptHeader the {@code Accept} header to be used when
1079          *        performing XMLHttpRequests
1080          * @return this for fluent use
1081          */
1082         public BrowserVersionBuilder setXmlHttpRequestAcceptHeader(final String xmlHttpRequestAcceptHeader) {
1083             workPiece_.xmlHttpRequestAcceptHeader_ = xmlHttpRequestAcceptHeader;
1084             return this;
1085         }
1086 
1087         /**
1088          * @param secClientHintUserAgentHeader the {@code sec-ch-ua} header value
1089          * @return this for fluent use
1090          */
1091         public BrowserVersionBuilder setSecClientHintUserAgentHeader(final String secClientHintUserAgentHeader) {
1092             workPiece_.secClientHintUserAgentHeader_ = secClientHintUserAgentHeader;
1093             return this;
1094         }
1095 
1096         /**
1097          * @param secClientHintUserAgentPlatformHeader the {@code sec-ch-ua-platform} header value
1098          * @return this for fluent use
1099          */
1100         public BrowserVersionBuilder setSecClientHintUserAgentPlatformHeader(final String secClientHintUserAgentPlatformHeader) {
1101             workPiece_.secClientHintUserAgentPlatformHeader_ = secClientHintUserAgentPlatformHeader;
1102             return this;
1103         }
1104 
1105         /**
1106          * @param productSub the productSub
1107          * @return this for fluent use
1108          */
1109         BrowserVersionBuilder setProductSub(final String productSub) {
1110             workPiece_.productSub_ = productSub;
1111             return this;
1112         }
1113 
1114         /**
1115          * @param headerNamesOrdered the headerNamesOrdered
1116          * @return this for fluent use
1117          */
1118         BrowserVersionBuilder setHeaderNamesOrdered(final String[] headerNamesOrdered) {
1119             workPiece_.headerNamesOrdered_ = headerNamesOrdered;
1120             return this;
1121         }
1122 
1123         /**
1124          * @param fontHeights the fontHeights
1125          * @return this for fluent use
1126          */
1127         BrowserVersionBuilder setFontHeights(final int[] fontHeights) {
1128             workPiece_.fontHeights_ = fontHeights;
1129             return this;
1130         }
1131 
1132         /**
1133          * @param buildId the buildId
1134          * @return this for fluent use
1135          */
1136         BrowserVersionBuilder setBuildId(final String buildId) {
1137             workPiece_.buildId_ = buildId;
1138             return this;
1139         }
1140     }
1141 }