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