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