View Javadoc
1   /*
2    * Copyright (c) 2002-2017 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    * http://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 com.gargoylesoftware.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.HashSet;
23  import java.util.Locale;
24  import java.util.Map;
25  import java.util.Set;
26  import java.util.TimeZone;
27  
28  import org.apache.commons.io.FilenameUtils;
29  
30  import com.gargoylesoftware.htmlunit.javascript.configuration.AbstractJavaScriptConfiguration;
31  import com.gargoylesoftware.htmlunit.javascript.configuration.BrowserFeature;
32  import com.gargoylesoftware.htmlunit.javascript.configuration.SupportedBrowser;
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   *      String applicationName = "APPNAME";
41   *      String applicationVersion = "APPVERSION";
42   *      String userAgent = "USERAGENT";
43   *      int browserVersionNumeric = NUMERIC;
44   *
45   *      BrowserVersion browser = new BrowserVersion.BrowserVersionFactory(FF52)
46   *          .setApplicationName(applicationName)
47   *          .setApplicationVersion(applicationVersion)
48   *          .setUserAgent(userAgent)
49   *          .build();
50   * </pre>
51   * <p>But keep in mind this now one still behaves like a FF52, only the stuff reported to the
52   * outside is changed. This is more or less the same you can do with real browsers installing
53   * plugins like UserAgentSwitcher.
54   *
55   * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a>
56   * @author Daniel Gredler
57   * @author Marc Guillemot
58   * @author Chris Erskine
59   * @author Ahmed Ashour
60   * @author Frank Danek
61   * @author Ronald Brill
62   */
63  public final class BrowserVersion implements Serializable {
64  
65      /**
66       * Application name the Netscape navigator series of browsers.
67       */
68      private static final String NETSCAPE = "Netscape";
69  
70      /**
71       * United States English language identifier.
72       */
73      private static final String LANGUAGE_ENGLISH_US = "en-US";
74  
75      /**
76       * United States.
77       */
78      private static final String TIMEZONE_NEW_YORK = "America/New_York";
79  
80      /**
81       * The X86 CPU class.
82       */
83      private static final String CPU_CLASS_X86 = "x86";
84  
85      /**
86       * The WIN32 platform.
87       */
88      private static final String PLATFORM_WIN32 = "Win32";
89  
90      /**
91       * The WIN64 platform.
92       */
93      private static final String PLATFORM_WIN64 = "Win64";
94  
95      /**
96       * Firefox 45 ESR.
97       * @since 2.21
98       */
99      public static final BrowserVersion FIREFOX_45 = new BrowserVersion(45, "FF45");
100 
101     /**
102      * Firefox 52 ESR.
103      * @since 2.26
104      */
105     public static final BrowserVersion FIREFOX_52 = new BrowserVersion(52, "FF52");
106 
107     /** Internet Explorer 11. */
108     public static final BrowserVersion INTERNET_EXPLORER = new BrowserVersion(11, "IE");
109 
110     /** Latest Chrome. */
111     public static final BrowserVersion CHROME = new BrowserVersion(60, "Chrome");
112 
113     /** Microsoft Edge. Work In Progress!!! */
114     public static final BrowserVersion EDGE = new BrowserVersion(14, "Edge");
115 
116     /**
117      * The best supported browser version at the moment.
118      */
119     public static final BrowserVersion BEST_SUPPORTED = CHROME;
120 
121     /** The default browser version. */
122     private static BrowserVersion DefaultBrowserVersion_ = BEST_SUPPORTED;
123 
124     /** Register plugins for the browser versions. */
125     static {
126         // FF45
127         FIREFOX_45.applicationVersion_ = "5.0 (Windows)";
128         FIREFOX_45.userAgent_ = "Mozilla/5.0 (Windows NT 6.1; rv:45.0) Gecko/20100101 Firefox/45.0";
129         FIREFOX_45.platform_ = PLATFORM_WIN32;
130         FIREFOX_45.buildId_ = "20170411115307";
131         FIREFOX_45.productSub_ = "20100101";
132         FIREFOX_45.headerNamesOrdered_ = new String[] {
133             HttpHeader.HOST,
134             HttpHeader.USER_AGENT,
135             HttpHeader.ACCEPT,
136             HttpHeader.ACCEPT_LANGUAGE,
137             HttpHeader.ACCEPT_ENCODING,
138             HttpHeader.REFERER,
139             HttpHeader.COOKIE,
140             HttpHeader.CONNECTION};
141         FIREFOX_45.htmlAcceptHeader_ = "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8";
142         FIREFOX_45.xmlHttpRequestAcceptHeader_ = "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8";
143         FIREFOX_45.imgAcceptHeader_ = "image/png,image/*;q=0.8,*/*;q=0.5";
144         FIREFOX_45.cssAcceptHeader_ = "text/css,*/*;q=0.1";
145         FIREFOX_45.fontHeights_ = new int[] {
146             0, 3, 4, 5, 6, 8, 9, 10, 11, 12, 13, 14, 15, 16, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27,
147             30, 31, 32, 33, 34, 35, 36, 37, 38, 40, 41, 42, 43, 44, 45, 46, 47, 48, 50, 51, 52, 53, 53, 55, 57, 58,
148             59, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 72, 73, 74, 75, 76, 77, 78, 79, 80, 82, 84, 85, 86, 87, 88,
149             89, 90, 91, 93, 94, 95, 96, 96, 98, 99, 100, 101, 103, 104, 105, 106, 106, 108, 109, 111, 112, 113, 115,
150             116, 117, 118, 119, 120, 121, 122, 123, 125, 126, 127, 128, 129, 130, 131, 132, 133, 135, 136, 138, 139,
151             139, 141, 142, 143, 144, 146, 147, 148, 149};
152 
153         // FF52
154         FIREFOX_52.applicationVersion_ = "5.0 (Windows)";
155         FIREFOX_52.userAgent_ = "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:52.0) Gecko/20100101 Firefox/52.0";
156         FIREFOX_52.buildId_ = "20171206101620";
157         FIREFOX_52.productSub_ = "20100101";
158         FIREFOX_52.headerNamesOrdered_ = new String[] {
159             HttpHeader.HOST,
160             HttpHeader.USER_AGENT,
161             HttpHeader.ACCEPT,
162             HttpHeader.ACCEPT_LANGUAGE,
163             HttpHeader.ACCEPT_ENCODING,
164             HttpHeader.REFERER,
165             HttpHeader.COOKIE,
166             HttpHeader.CONNECTION,
167             "Upgrade-Insecure-Requests"};
168         FIREFOX_52.htmlAcceptHeader_ = "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8";
169         FIREFOX_52.cssAcceptHeader_ = "text/css,*/*;q=0.1";
170         FIREFOX_52.fontHeights_ = new int[] {
171             0, 3, 4, 5, 6, 8, 9, 10, 11, 12, 13, 14, 15, 16, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27,
172             30, 31, 32, 33, 34, 35, 36, 37, 38, 40, 41, 42, 43, 44, 45, 46, 47, 48, 50, 51, 52, 53, 53, 55, 57, 58,
173             59, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 72, 73, 74, 75, 76, 77, 78, 79, 80, 82, 84, 85, 86, 87, 88,
174             89, 90, 91, 93, 94, 95, 96, 96, 98, 99, 100, 101, 103, 104, 105, 106, 106, 108, 109, 111, 112, 113, 115,
175             116, 117, 118, 119, 120, 121, 122, 123, 125, 126, 127, 128, 129, 130, 131, 132, 133, 135, 136, 138, 139,
176             139, 141, 142, 143, 144, 146, 147, 148, 149};
177 
178         // IE
179         INTERNET_EXPLORER.applicationVersion_ = "5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko";
180         INTERNET_EXPLORER.userAgent_ = "Mozilla/5.0 (Windows NT 6.1; Trident/7.0; rv:11.0) like Gecko";
181         INTERNET_EXPLORER.platform_ = PLATFORM_WIN32;
182         INTERNET_EXPLORER.headerNamesOrdered_ = new String[] {
183             HttpHeader.ACCEPT,
184             HttpHeader.REFERER,
185             HttpHeader.ACCEPT_LANGUAGE,
186             HttpHeader.USER_AGENT,
187             HttpHeader.ACCEPT_ENCODING,
188             HttpHeader.HOST,
189             HttpHeader.DNT,
190             HttpHeader.CONNECTION,
191             HttpHeader.COOKIE};
192         INTERNET_EXPLORER.htmlAcceptHeader_ = "text/html, application/xhtml+xml, */*";
193         INTERNET_EXPLORER.imgAcceptHeader_ = "image/png, image/svg+xml, image/*;q=0.8, */*;q=0.5";
194         INTERNET_EXPLORER.cssAcceptHeader_ = "text/css, */*";
195         INTERNET_EXPLORER.scriptAcceptHeader_ = "application/javascript, */*;q=0.8";
196         INTERNET_EXPLORER.fontHeights_ = new int[] {
197             0, 1, 2, 3, 5, 6, 7, 8, 9, 10, 12, 13, 14, 15, 16, 17, 18, 20, 21, 22, 23, 24, 25, 26, 28,
198             29, 30, 31, 32, 33, 35, 36, 37, 38, 39, 40, 41, 43, 44, 45, 46, 47, 48, 49, 51, 52, 53, 54, 55, 56, 58,
199             59, 60, 61, 62, 63, 64, 66, 67, 68, 69, 70, 71, 72, 74, 75, 76, 77, 78, 79, 80, 82, 83, 84, 85, 86, 87,
200             89, 90, 91, 92, 93, 94, 95, 97, 98, 99, 100, 101, 102, 103, 105, 106, 107, 108, 109, 110, 112, 113, 114,
201             115, 116, 117, 118, 120, 121, 122, 123, 124, 125, 126, 128, 129, 130, 131, 132, 133, 135, 136, 137, 138,
202             139, 140, 141, 143, 144, 145, 146, 147};
203 
204         // EDGE
205         EDGE.applicationVersion_ = "5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.79 Safari/537.36 Edge/14.14393";
206         EDGE.userAgent_ = "Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.79 Safari/537.36 Edge/14.14393";
207 
208         // CHROME
209         CHROME.applicationVersion_ = "5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.108 Safari/537.36";
210         CHROME.userAgent_ = "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.108 Safari/537.36";
211 
212         CHROME.applicationCodeName_ = "Mozilla";
213         CHROME.vendor_ = "Google Inc.";
214         CHROME.platform_ = PLATFORM_WIN32;
215         CHROME.cpuClass_ = null;
216         CHROME.productSub_ = "20030107";
217         CHROME.headerNamesOrdered_ = new String[] {
218             HttpHeader.HOST,
219             HttpHeader.CONNECTION,
220             "Upgrade-Insecure-Requests",
221             HttpHeader.USER_AGENT,
222             HttpHeader.ACCEPT,
223             HttpHeader.REFERER,
224             HttpHeader.ACCEPT_ENCODING,
225             HttpHeader.ACCEPT_LANGUAGE,
226             HttpHeader.COOKIE};
227         CHROME.htmlAcceptHeader_ = "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8";
228         CHROME.imgAcceptHeader_ = "image/webp,image/apng,image/*,*/*;q=0.8";
229         CHROME.cssAcceptHeader_ = "text/css,*/*;q=0.1";
230         CHROME.scriptAcceptHeader_ = "*/*";
231         // there are other issues with Chrome; a different productSub, etc.
232         CHROME.fontHeights_ = new int[] {
233             0, 1, 2, 4, 5, 5, 6, 8, 9, 10, 11, 12, 15, 16, 16, 17, 18, 20, 21, 22, 23, 25, 26, 26,
234             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,
235             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,
236             87, 89, 90, 90, 91, 93, 94, 96, 97, 98, 100, 101, 101, 102, 103, 105, 106, 107, 108, 110, 111, 111, 112,
237             113, 115, 116, 117, 118, 119, 121, 122, 123, 124, 126, 127, 128, 129, 130, 132, 132, 133, 134, 136, 137,
238             138, 139, 140, 142, 142, 143, 144, 145, 147};
239 
240         // default file upload mime types
241         CHROME.registerUploadMimeType("html", "text/html");
242         CHROME.registerUploadMimeType("htm", "text/html");
243         CHROME.registerUploadMimeType("css", "text/css");
244         CHROME.registerUploadMimeType("xml", "text/xml");
245         CHROME.registerUploadMimeType("gif", "image/gif");
246         CHROME.registerUploadMimeType("jpeg", "image/jpeg");
247         CHROME.registerUploadMimeType("jpg", "image/jpeg");
248         CHROME.registerUploadMimeType("webp", "image/webp");
249         CHROME.registerUploadMimeType("mp4", "video/mp4");
250         CHROME.registerUploadMimeType("m4v", "video/mp4");
251         CHROME.registerUploadMimeType("m4a", "audio/x-m4a");
252         CHROME.registerUploadMimeType("mp3", "audio/mp3");
253         CHROME.registerUploadMimeType("ogv", "video/ogg");
254         CHROME.registerUploadMimeType("ogm", "video/ogg");
255         CHROME.registerUploadMimeType("ogg", "audio/ogg");
256         CHROME.registerUploadMimeType("oga", "audio/ogg");
257         CHROME.registerUploadMimeType("opus", "audio/ogg");
258         CHROME.registerUploadMimeType("webm", "video/webm");
259         CHROME.registerUploadMimeType("wav", "audio/wav");
260         CHROME.registerUploadMimeType("flac", "audio/flac");
261         CHROME.registerUploadMimeType("xhtml", "application/xhtml+xml");
262         CHROME.registerUploadMimeType("xht", "application/xhtml+xml");
263         CHROME.registerUploadMimeType("xhtm", "application/xhtml+xml");
264         CHROME.registerUploadMimeType("txt", "text/plain");
265         CHROME.registerUploadMimeType("text", "text/plain");
266 
267         FIREFOX_45.registerUploadMimeType("html", "text/html");
268         FIREFOX_45.registerUploadMimeType("htm", "text/html");
269         FIREFOX_45.registerUploadMimeType("css", "text/css");
270         FIREFOX_45.registerUploadMimeType("xml", "text/xml");
271         FIREFOX_45.registerUploadMimeType("gif", "image/gif");
272         FIREFOX_45.registerUploadMimeType("jpeg", "image/jpeg");
273         FIREFOX_45.registerUploadMimeType("jpg", "image/jpeg");
274         FIREFOX_45.registerUploadMimeType("mp4", "video/mp4");
275         FIREFOX_45.registerUploadMimeType("m4v", "video/mp4");
276         FIREFOX_45.registerUploadMimeType("m4a", "audio/mp4");
277         FIREFOX_45.registerUploadMimeType("mp3", "audio/mpeg");
278         FIREFOX_45.registerUploadMimeType("ogv", "video/ogg");
279         FIREFOX_45.registerUploadMimeType("ogm", "video/x-ogm");
280         FIREFOX_45.registerUploadMimeType("ogg", "video/ogg");
281         FIREFOX_45.registerUploadMimeType("oga", "audio/ogg");
282         FIREFOX_45.registerUploadMimeType("opus", "audio/ogg");
283         FIREFOX_45.registerUploadMimeType("webm", "video/webm");
284         FIREFOX_45.registerUploadMimeType("wav", "audio/wav");
285         FIREFOX_45.registerUploadMimeType("flac", "audio/x-flac");
286         FIREFOX_45.registerUploadMimeType("xhtml", "application/xhtml+xml");
287         FIREFOX_45.registerUploadMimeType("xht", "application/xhtml+xml");
288         FIREFOX_45.registerUploadMimeType("txt", "text/plain");
289         FIREFOX_45.registerUploadMimeType("text", "text/plain");
290 
291         FIREFOX_52.registerUploadMimeType("html", "text/html");
292         FIREFOX_52.registerUploadMimeType("htm", "text/html");
293         FIREFOX_52.registerUploadMimeType("css", "text/css");
294         FIREFOX_52.registerUploadMimeType("xml", "text/xml");
295         FIREFOX_52.registerUploadMimeType("gif", "image/gif");
296         FIREFOX_52.registerUploadMimeType("jpeg", "image/jpeg");
297         FIREFOX_52.registerUploadMimeType("jpg", "image/jpeg");
298         FIREFOX_52.registerUploadMimeType("mp4", "video/mp4");
299         FIREFOX_52.registerUploadMimeType("m4v", "video/mp4");
300         FIREFOX_52.registerUploadMimeType("m4a", "audio/mp4");
301         FIREFOX_52.registerUploadMimeType("mp3", "audio/mpeg");
302         FIREFOX_52.registerUploadMimeType("ogv", "video/ogg");
303         FIREFOX_52.registerUploadMimeType("ogm", "video/x-ogm");
304         FIREFOX_52.registerUploadMimeType("ogg", "video/ogg");
305         FIREFOX_52.registerUploadMimeType("oga", "audio/ogg");
306         FIREFOX_52.registerUploadMimeType("opus", "audio/ogg");
307         FIREFOX_52.registerUploadMimeType("webm", "video/webm");
308         FIREFOX_52.registerUploadMimeType("wav", "audio/wav");
309         FIREFOX_52.registerUploadMimeType("xhtml", "application/xhtml+xml");
310         FIREFOX_52.registerUploadMimeType("xht", "application/xhtml+xml");
311         FIREFOX_52.registerUploadMimeType("txt", "text/plain");
312         FIREFOX_52.registerUploadMimeType("text", "text/plain");
313 
314         INTERNET_EXPLORER.registerUploadMimeType("html", "text/html");
315         INTERNET_EXPLORER.registerUploadMimeType("htm", "text/html");
316         INTERNET_EXPLORER.registerUploadMimeType("css", "text/css");
317         INTERNET_EXPLORER.registerUploadMimeType("xml", "text/xml");
318         INTERNET_EXPLORER.registerUploadMimeType("gif", "image/gif");
319         INTERNET_EXPLORER.registerUploadMimeType("jpeg", "image/jpeg");
320         INTERNET_EXPLORER.registerUploadMimeType("jpg", "image/jpeg");
321         INTERNET_EXPLORER.registerUploadMimeType("mp4", "video/mp4");
322         INTERNET_EXPLORER.registerUploadMimeType("m4v", "video/mp4");
323         INTERNET_EXPLORER.registerUploadMimeType("m4a", "audio/mp4");
324         INTERNET_EXPLORER.registerUploadMimeType("mp3", "audio/mpeg");
325         INTERNET_EXPLORER.registerUploadMimeType("ogm", "video/x-ogm");
326         INTERNET_EXPLORER.registerUploadMimeType("ogg", "application/ogg");
327         INTERNET_EXPLORER.registerUploadMimeType("wav", "audio/wav");
328         INTERNET_EXPLORER.registerUploadMimeType("xhtml", "application/xhtml+xml");
329         INTERNET_EXPLORER.registerUploadMimeType("xht", "application/xhtml+xml");
330         INTERNET_EXPLORER.registerUploadMimeType("txt", "text/plain");
331 
332         EDGE.registerUploadMimeType("html", "text/html");
333         EDGE.registerUploadMimeType("htm", "text/html");
334         EDGE.registerUploadMimeType("css", "text/css");
335         EDGE.registerUploadMimeType("xml", "text/xml");
336         EDGE.registerUploadMimeType("gif", "image/gif");
337         EDGE.registerUploadMimeType("jpeg", "image/jpeg");
338         EDGE.registerUploadMimeType("jpg", "image/jpeg");
339         EDGE.registerUploadMimeType("mp4", "video/mp4");
340         EDGE.registerUploadMimeType("m4v", "video/mp4");
341         EDGE.registerUploadMimeType("m4a", "audio/mp4");
342         EDGE.registerUploadMimeType("mp3", "audio/mpeg");
343         EDGE.registerUploadMimeType("ogm", "video/x-ogm");
344         EDGE.registerUploadMimeType("ogg", "application/ogg");
345         EDGE.registerUploadMimeType("wav", "audio/wav");
346         EDGE.registerUploadMimeType("xhtml", "application/xhtml+xml");
347         EDGE.registerUploadMimeType("xht", "application/xhtml+xml");
348         EDGE.registerUploadMimeType("txt", "text/plain");
349 
350         // flush plugin (windows version)
351         PluginConfiguration flash = new PluginConfiguration("Shockwave Flash",
352                 "Shockwave Flash 28.0 r0", "28.0.0.126", "NPSWF32_28_0_0_126.dll");
353         flash.getMimeTypes().add(new PluginConfiguration.MimeType("application/x-shockwave-flash",
354                 "Shockwave Flash", "swf"));
355         FIREFOX_45.plugins_.add(flash);
356 
357         flash = new PluginConfiguration("Shockwave Flash",
358                 "Shockwave Flash 28.0 r0", "28.0.0.126", "NPSWF64_28_0_0_126.dll");
359         flash.getMimeTypes().add(new PluginConfiguration.MimeType("application/x-shockwave-flash",
360                 "Shockwave Flash", "swf"));
361         FIREFOX_52.plugins_.add(flash);
362 
363         flash = new PluginConfiguration("Shockwave Flash",
364                 "Shockwave Flash 28.0 r0", "28.0.0.126", "Flash32_28_0_0_126.ocx");
365         flash.getMimeTypes().add(new PluginConfiguration.MimeType("application/x-shockwave-flash",
366                 "Shockwave Flash", "swf"));
367         INTERNET_EXPLORER.plugins_.add(flash);
368 
369         flash = new PluginConfiguration("Shockwave Flash",
370                 "Shockwave Flash 18.0 r0", "18.0.0.232", "Flash.ocx");
371         flash.getMimeTypes().add(new PluginConfiguration.MimeType("application/x-shockwave-flash",
372                 "Shockwave Flash", "swf"));
373         EDGE.plugins_.add(flash);
374     }
375 
376     private final int browserVersionNumeric_;
377     private final String nickname_;
378 
379     private String applicationCodeName_ = "Mozilla";
380     private String applicationMinorVersion_ = "0";
381     private String applicationName_;
382     private String applicationVersion_;
383     private String buildId_;
384     private String productSub_;
385     private String vendor_ = "";
386     private String browserLanguage_ = LANGUAGE_ENGLISH_US;
387     private String cpuClass_ = CPU_CLASS_X86;
388     private boolean onLine_ = true;
389     private String platform_ = PLATFORM_WIN64;
390     private String systemLanguage_ = LANGUAGE_ENGLISH_US;
391     private TimeZone systemTimezone_ = TimeZone.getTimeZone(TIMEZONE_NEW_YORK);
392     private String userAgent_;
393     private String userLanguage_ = LANGUAGE_ENGLISH_US;
394     private final Set<PluginConfiguration> plugins_;
395     private final Set<BrowserVersionFeatures> features_;
396     private String htmlAcceptHeader_;
397     private String imgAcceptHeader_;
398     private String cssAcceptHeader_;
399     private String scriptAcceptHeader_;
400     private String xmlHttpRequestAcceptHeader_;
401     private String[] headerNamesOrdered_;
402     private int[] fontHeights_;
403     private final Map<String, String> uploadMimeTypes_;
404 
405     /**
406      * Creates a new browser version instance.
407      *
408      * @param browserVersionNumeric the floating number version of the browser
409      * @param nickname the short name of the browser (like "FF52", "IE", ...) - has to be unique
410      */
411     private BrowserVersion(final int browserVersionNumeric, final String nickname) {
412         browserVersionNumeric_ = browserVersionNumeric;
413         nickname_ = nickname;
414 
415         applicationName_ = NETSCAPE;
416         htmlAcceptHeader_ = "*/*";
417         imgAcceptHeader_ = "*/*";
418         cssAcceptHeader_ = "*/*";
419         scriptAcceptHeader_ = "*/*";
420         xmlHttpRequestAcceptHeader_ = "*/*";
421 
422         plugins_ = new HashSet<>();
423         features_ = EnumSet.noneOf(BrowserVersionFeatures.class);
424         uploadMimeTypes_ = new HashMap<>();
425 
426         initFeatures();
427     }
428 
429     private void initFeatures() {
430         final SupportedBrowser expectedBrowser;
431         if (isChrome()) {
432             expectedBrowser = SupportedBrowser.CHROME;
433         }
434         else if (isFirefox52()) {
435             expectedBrowser = SupportedBrowser.FF52;
436         }
437         else if (isFirefox()) {
438             expectedBrowser = SupportedBrowser.FF45;
439         }
440         else if (isIE()) {
441             expectedBrowser = SupportedBrowser.IE;
442         }
443         else {
444             expectedBrowser = SupportedBrowser.EDGE;
445         }
446 
447         for (final BrowserVersionFeatures features : BrowserVersionFeatures.values()) {
448             try {
449                 final Field field = BrowserVersionFeatures.class.getField(features.name());
450                 final BrowserFeature browserFeature = field.getAnnotation(BrowserFeature.class);
451                 if (browserFeature != null) {
452                     for (final SupportedBrowser browser : browserFeature.value()) {
453                         if (AbstractJavaScriptConfiguration.isCompatible(expectedBrowser, browser)) {
454                             features_.add(features);
455                         }
456                     }
457                 }
458             }
459             catch (final NoSuchFieldException e) {
460                 // should never happen
461                 throw new IllegalStateException(e);
462             }
463         }
464     }
465 
466     /**
467      * Returns the default browser version that is used whenever a specific version isn't specified.
468      * Defaults to {@link #BEST_SUPPORTED}.
469      * @return the default browser version
470      */
471     public static BrowserVersion getDefault() {
472         return DefaultBrowserVersion_;
473     }
474 
475     /**
476      * Sets the default browser version that is used whenever a specific version isn't specified.
477      * @param newBrowserVersion the new default browser version
478      */
479     public static void setDefault(final BrowserVersion newBrowserVersion) {
480         WebAssert.notNull("newBrowserVersion", newBrowserVersion);
481         DefaultBrowserVersion_ = newBrowserVersion;
482     }
483 
484     /**
485      * Returns {@code true} if this <tt>BrowserVersion</tt> instance represents some
486      * version of Internet Explorer.
487      * @return whether or not this version is a version of IE
488      */
489     public boolean isIE() {
490         return getNickname().startsWith("IE");
491     }
492 
493     /**
494      * Returns {@code true} if this <tt>BrowserVersion</tt> instance represents some
495      * version of Google Chrome. Note that Google Chrome does not return 'Chrome'
496      * in the application name, we have to look in the nickname.
497      * @return whether or not this version is a version of a Chrome browser
498      */
499     public boolean isChrome() {
500         return getNickname().startsWith("Chrome");
501     }
502 
503     /**
504      * Returns {@code true} if this <tt>BrowserVersion</tt> instance represents some
505      * version of Microsoft Edge.
506      * @return whether or not this version is a version of an Edge browser
507      */
508     public boolean isEdge() {
509         return getNickname().startsWith("Edge");
510     }
511 
512     /**
513      * Returns {@code true} if this <tt>BrowserVersion</tt> instance represents some
514      * version of Firefox.
515      * @return whether or not this version is a version of a Firefox browser
516      */
517     public boolean isFirefox() {
518         return getNickname().startsWith("FF");
519     }
520 
521     /**
522      * <span style="color:red">INTERNAL API - SUBJECT TO CHANGE AT ANY TIME - USE AT YOUR OWN RISK.</span><br>
523      * @return whether or not this version version 52 of a Firefox browser
524      */
525     public boolean isFirefox52() {
526         return isFirefox() && getBrowserVersionNumeric() == 52;
527     }
528 
529     /**
530      * Returns the short name of the browser like {@code FF3}, {@code IE}, etc.
531      *
532      * @return the short name (if any)
533      */
534     public String getNickname() {
535         return nickname_;
536     }
537 
538     /**
539      * @return the browserVersionNumeric
540      */
541     public int getBrowserVersionNumeric() {
542         return browserVersionNumeric_;
543     }
544 
545     /**
546      * Returns the application code name, for example "Mozilla".
547      * Default value is "Mozilla" if not explicitly configured.
548      * @return the application code name
549      * @see <a href="http://msdn.microsoft.com/en-us/library/ms533077.aspx">MSDN documentation</a>
550      */
551     public String getApplicationCodeName() {
552         return applicationCodeName_;
553     }
554 
555     /**
556      * Returns the application minor version, for example "0".
557      * Default value is "0" if not explicitly configured.
558      * @return the application minor version
559      * @see <a href="http://msdn.microsoft.com/en-us/library/ms533078.aspx">MSDN documentation</a>
560      */
561     public String getApplicationMinorVersion() {
562         return applicationMinorVersion_;
563     }
564 
565     /**
566      * Returns the application name, for example "Microsoft Internet Explorer".
567      * @return the application name
568      * @see <a href="http://msdn.microsoft.com/en-us/library/ms533079.aspx">MSDN documentation</a>
569      */
570     public String getApplicationName() {
571         return applicationName_;
572     }
573 
574     /**
575      * Returns the application version, for example "4.0 (compatible; MSIE 6.0b; Windows 98)".
576      * @return the application version
577      * @see <a href="http://msdn.microsoft.com/en-us/library/ms533080.aspx">MSDN documentation</a>
578      */
579     public String getApplicationVersion() {
580         return applicationVersion_;
581     }
582 
583     /**
584      * @return the vendor
585      */
586     public String getVendor() {
587         return vendor_;
588     }
589 
590     /**
591      * Returns the browser application language, for example "en-us".
592      * Default value is {@link #LANGUAGE_ENGLISH_US} if not explicitly configured.
593      * @return the browser application language
594      * @see <a href="http://msdn.microsoft.com/en-us/library/ms533542.aspx">MSDN documentation</a>
595      */
596     public String getBrowserLanguage() {
597         return browserLanguage_;
598     }
599 
600     /**
601      * Returns the type of CPU in the machine, for example "x86".
602      * Default value is {@link #CPU_CLASS_X86} if not explicitly configured.
603      * @return the type of CPU in the machine
604      * @see <a href="http://msdn.microsoft.com/en-us/library/ms533697.aspx">MSDN documentation</a>
605      */
606     public String getCpuClass() {
607         return cpuClass_;
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 {@link #PLATFORM_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 language, for example "en-us".
632      * Default value is {@link #LANGUAGE_ENGLISH_US} if not explicitly configured.
633      * @return the system language
634      * @see <a href="http://msdn.microsoft.com/en-us/library/ms534653.aspx">MSDN documentation</a>
635      */
636     public String getSystemLanguage() {
637         return systemLanguage_;
638     }
639 
640     /**
641      * Returns the system {@link TimeZone}.
642      * Default value is {@code America/New_York} if not explicitly configured.
643      * @return the system {@link TimeZone}
644      */
645     public TimeZone getSystemTimezone() {
646         return systemTimezone_;
647     }
648 
649     /**
650      * Returns the user agent string, for example "Mozilla/4.0 (compatible; MSIE 6.0b; Windows 98)".
651      * @return the user agent string
652      */
653     public String getUserAgent() {
654         return userAgent_;
655     }
656 
657     /**
658      * Returns the user language, for example "en-us".
659      * Default value is {@link #LANGUAGE_ENGLISH_US} if not explicitly configured.
660      * @return the user language
661      * @see <a href="http://msdn.microsoft.com/en-us/library/ms534713.aspx">MSDN documentation</a>
662      */
663     public String getUserLanguage() {
664         return userLanguage_;
665     }
666 
667     /**
668      * Returns the value used by the browser for the {@code Accept} header if requesting a page.
669      * @return the accept header string
670      */
671     public String getHtmlAcceptHeader() {
672         return htmlAcceptHeader_;
673     }
674 
675     /**
676      * Returns the value used by the browser for the {@code Accept} header
677      * if requesting a script.
678      * @return the accept header string
679      */
680     public String getScriptAcceptHeader() {
681         return scriptAcceptHeader_;
682     }
683 
684     /**
685      * Returns the value used by the browser for the {@code Accept} header
686      * if performing an XMLHttpRequest.
687      * @return the accept header string
688      */
689     public String getXmlHttpRequestAcceptHeader() {
690         return xmlHttpRequestAcceptHeader_;
691     }
692 
693     /**
694      * Returns the value used by the browser for the {@code Accept} header
695      * if requesting an image.
696      * @return the accept header string
697      */
698     public String getImgAcceptHeader() {
699         return imgAcceptHeader_;
700     }
701 
702     /**
703      * Returns the value used by the browser for the {@code Accept} header
704      * if requesting a CSS declaration.
705      * @return the accept header string
706      */
707     public String getCssAcceptHeader() {
708         return cssAcceptHeader_;
709     }
710 
711     /**
712      * Returns the available plugins. This makes only sense for Firefox as only this
713      * browser makes this kind of information available via JavaScript.
714      * @return the available plugins
715      */
716     public Set<PluginConfiguration> getPlugins() {
717         return plugins_;
718     }
719 
720     /**
721      * Indicates if this instance has the given feature. Used for HtmlUnit internal processing.
722      * @param property the property name
723      * @return {@code false} if this browser doesn't have this feature
724      */
725     public boolean hasFeature(final BrowserVersionFeatures property) {
726         return features_.contains(property);
727     }
728 
729     /**
730      * Returns the buildId.
731      * @return the buildId
732      */
733     public String getBuildId() {
734         return buildId_;
735     }
736 
737     /**
738      * Returns the productSub.
739      * @return the buildId
740      */
741     public String getProductSub() {
742         return productSub_;
743     }
744 
745     /**
746      * Gets the headers names, so they are sent in the given order (if included in the request).
747      * @return headerNames the header names in ordered manner
748      */
749     public String[] getHeaderNamesOrdered() {
750         return headerNamesOrdered_;
751     }
752 
753     /**
754      * Registers a new mime type for the provided file extension.
755      * @param fileExtension the file extension used to determine the mime type
756      * @param mimeType the mime type to be used when uploading files with this extension
757      */
758     public void registerUploadMimeType(final String fileExtension, final String mimeType) {
759         if (fileExtension != null) {
760             uploadMimeTypes_.put(fileExtension.toLowerCase(Locale.ROOT), mimeType);
761         }
762     }
763 
764     /**
765      * Determines the content type for the given file.
766      * @param file the file
767      * @return a content type or an empty string if unknown
768      */
769     public String getUploadMimeType(final File file) {
770         if (file == null) {
771             return "";
772         }
773 
774         final String fileExtension = FilenameUtils.getExtension(file.getName());
775 
776         final String mimeType = uploadMimeTypes_.get(fileExtension.toLowerCase(Locale.ROOT));
777         if (mimeType != null) {
778             return mimeType;
779         }
780         return "";
781     }
782 
783     /**
784      * Returns the corresponding height of the specified {@code fontSize}
785      * @param fontSize the font size
786      * @return the corresponding height
787      */
788     public int getFontHeight(final String fontSize) {
789         if (fontHeights_ == null) {
790             return 18;
791         }
792         final int fontSizeInt = Integer.parseInt(fontSize.substring(0, fontSize.length() - 2));
793         if (fontSizeInt < fontHeights_.length) {
794             return fontHeights_[fontSizeInt];
795         }
796         return (int) (fontSizeInt * 1.2);
797     }
798 
799     /**
800      * @return the pixesPerChar based on the specified {@code fontSize}
801      */
802     public int getPixesPerChar() {
803         return 10;
804     }
805 
806     @Override
807     public String toString() {
808         return nickname_;
809     }
810 
811     /**
812      * Because BrowserVersion is immutable we need a builder
813      * for this complex object setup.
814      */
815     public static class BrowserVersionBuilder {
816         private final BrowserVersion workPiece_;
817 
818         /**
819          * Creates a new BrowserVersionBuilder using the given browser version
820          * as template for the browser to be constructed.
821          * @param version the blueprint
822          */
823         public BrowserVersionBuilder(final BrowserVersion version) {
824             workPiece_ = new BrowserVersion(version.getBrowserVersionNumeric(), version.getNickname());
825 
826             setApplicationVersion(version.getApplicationVersion())
827                 .setUserAgent(version.getUserAgent())
828                 .setApplicationName(version.getApplicationName())
829                 .setApplicationCodeName(version.getApplicationCodeName())
830                 .setApplicationMinorVersion(version.getApplicationMinorVersion())
831                 .setVendor(version.getVendor())
832                 .setBrowserLanguage(version.getBrowserLanguage())
833                 .setCpuClass(version.getCpuClass())
834                 .setOnLine(version.isOnLine())
835                 .setPlatform(version.getPlatform())
836                 .setSystemLanguage(version.getSystemLanguage())
837                 .setSystemTimezone(version.getSystemTimezone())
838                 .setUserLanguage(version.getUserLanguage());
839 
840             workPiece_.buildId_ = version.getBuildId();
841             workPiece_.productSub_ = version.getProductSub();
842             workPiece_.htmlAcceptHeader_ = version.getHtmlAcceptHeader();
843             workPiece_.imgAcceptHeader_ = version.getImgAcceptHeader();
844             workPiece_.cssAcceptHeader_ = version.getCssAcceptHeader();
845             workPiece_.scriptAcceptHeader_ = version.getScriptAcceptHeader();
846             workPiece_.xmlHttpRequestAcceptHeader_ = version.getXmlHttpRequestAcceptHeader();
847             workPiece_.headerNamesOrdered_ = version.getHeaderNamesOrdered();
848             workPiece_.fontHeights_ = version.fontHeights_;
849 
850             for (final PluginConfiguration pluginConf : version.getPlugins()) {
851                 workPiece_.plugins_.add(pluginConf.clone());
852             }
853 
854             workPiece_.features_.addAll(version.features_);
855             workPiece_.uploadMimeTypes_.putAll(version.uploadMimeTypes_);
856         }
857 
858         /**
859          * @return the new immutable browser version
860          */
861         public BrowserVersion build() {
862             return workPiece_;
863         }
864 
865         /**
866          * @param applicationMinorVersion the applicationMinorVersion to set
867          * @return this for fluent use
868          */
869         public BrowserVersionBuilder setApplicationMinorVersion(final String applicationMinorVersion) {
870             workPiece_.applicationMinorVersion_ = applicationMinorVersion;
871             return this;
872         }
873 
874         /**
875          * @param applicationName the applicationName to set
876          * @return this for fluent use
877          */
878         public BrowserVersionBuilder setApplicationName(final String applicationName) {
879             workPiece_.applicationName_ = applicationName;
880             return this;
881         }
882 
883         /**
884          * @param applicationVersion the applicationVersion to set
885          * @return this for fluent use
886          */
887         public BrowserVersionBuilder setApplicationVersion(final String applicationVersion) {
888             workPiece_.applicationVersion_ = applicationVersion;
889             return this;
890         }
891 
892         /**
893          * @param vendor the vendor to set
894          * @return this for fluent use
895          */
896         public BrowserVersionBuilder setVendor(final String vendor) {
897             workPiece_.vendor_ = vendor;
898             return this;
899         }
900 
901         /**
902          * @param applicationCodeName the applicationCodeName to set
903          * @return this for fluent use
904          */
905         public BrowserVersionBuilder setApplicationCodeName(final String applicationCodeName) {
906             workPiece_.applicationCodeName_ = applicationCodeName;
907             return this;
908         }
909 
910         /**
911          * @param browserLanguage the browserLanguage to set
912          * @return this for fluent use
913          */
914         public BrowserVersionBuilder setBrowserLanguage(final String browserLanguage) {
915             workPiece_.browserLanguage_ = browserLanguage;
916             return this;
917         }
918 
919         /**
920          * @param cpuClass the cpuClass to set
921          * @return this for fluent use
922          */
923         public BrowserVersionBuilder setCpuClass(final String cpuClass) {
924             workPiece_.cpuClass_ = cpuClass;
925             return this;
926         }
927 
928         /**
929          * @param onLine the onLine to set
930          * @return this for fluent use
931          */
932         public BrowserVersionBuilder setOnLine(final boolean onLine) {
933             workPiece_.onLine_ = onLine;
934             return this;
935         }
936 
937         /**
938          * @param platform the platform to set
939          * @return this for fluent use
940          */
941         public BrowserVersionBuilder setPlatform(final String platform) {
942             workPiece_.platform_ = platform;
943             return this;
944         }
945 
946         /**
947          * @param systemLanguage the systemLanguage to set
948          * @return this for fluent use
949          */
950         public BrowserVersionBuilder setSystemLanguage(final String systemLanguage) {
951             workPiece_.systemLanguage_ = systemLanguage;
952             return this;
953         }
954 
955         /**
956          * @param systemTimezone the systemTimezone to set
957          * @return this for fluent use
958          */
959         public BrowserVersionBuilder setSystemTimezone(final TimeZone systemTimezone) {
960             workPiece_.systemTimezone_ = systemTimezone;
961             return this;
962         }
963 
964         /**
965          * @param userAgent the userAgent to set
966          * @return this for fluent use
967          */
968         public BrowserVersionBuilder setUserAgent(final String userAgent) {
969             workPiece_.userAgent_ = userAgent;
970             return this;
971         }
972 
973         /**
974          * @param userLanguage the userLanguage to set
975          * @return this for fluent use
976          */
977         public BrowserVersionBuilder setUserLanguage(final String userLanguage) {
978             workPiece_.userLanguage_ = userLanguage;
979             return this;
980         }
981 
982         /**
983          * @param htmlAcceptHeader the {@code Accept} header to be used when retrieving pages
984          * @return this for fluent use
985          */
986         public BrowserVersionBuilder setHtmlAcceptHeader(final String htmlAcceptHeader) {
987             workPiece_.htmlAcceptHeader_ = htmlAcceptHeader;
988             return this;
989         }
990 
991         /**
992          * @param imgAcceptHeader the {@code Accept} header to be used when retrieving images
993          * @return this for fluent use
994          */
995         public BrowserVersionBuilder setImgAcceptHeader(final String imgAcceptHeader) {
996             workPiece_.imgAcceptHeader_ = imgAcceptHeader;
997             return this;
998         }
999 
1000         /**
1001          * @param cssAcceptHeader the {@code Accept} header to be used when retrieving pages
1002          * @return this for fluent use
1003          */
1004         public BrowserVersionBuilder setCssAcceptHeader(final String cssAcceptHeader) {
1005             workPiece_.cssAcceptHeader_ = cssAcceptHeader;
1006             return this;
1007         }
1008 
1009         /**
1010          * @param scriptAcceptHeader the {@code Accept} header to be used when retrieving scripts
1011          * @return this for fluent use
1012          */
1013         public BrowserVersionBuilder setScriptAcceptHeader(final String scriptAcceptHeader) {
1014             workPiece_.scriptAcceptHeader_ = scriptAcceptHeader;
1015             return this;
1016         }
1017 
1018         /**
1019          * @param xmlHttpRequestAcceptHeader the {@code Accept} header to be used when
1020          * performing XMLHttpRequests
1021          * @return this for fluent use
1022          */
1023         public BrowserVersionBuilder setXmlHttpRequestAcceptHeader(final String xmlHttpRequestAcceptHeader) {
1024             workPiece_.xmlHttpRequestAcceptHeader_ = xmlHttpRequestAcceptHeader;
1025             return this;
1026         }
1027     }
1028 }