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