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