shaka-player 3.2.2 → 3.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (156) hide show
  1. package/.github/ISSUE_TEMPLATE/bug_report.md +1 -1
  2. package/.github/ISSUE_TEMPLATE/feature_request.md +1 -1
  3. package/.github/ISSUE_TEMPLATE/question.md +1 -1
  4. package/AUTHORS +6 -5
  5. package/CHANGELOG.md +247 -0
  6. package/CONTRIBUTORS +4 -2
  7. package/build/types/core +3 -0
  8. package/build/types/offline +1 -0
  9. package/build/types/polyfill +1 -0
  10. package/build/types/ui +2 -0
  11. package/demo/common/assets.js +18 -0
  12. package/demo/common/message_ids.js +13 -1
  13. package/demo/config.js +31 -1
  14. package/demo/index.html +1 -1
  15. package/demo/locales/en.json +12 -0
  16. package/demo/locales/source.json +48 -0
  17. package/dist/controls.css +26 -2
  18. package/dist/controls.css.map +1 -1
  19. package/dist/demo.compiled.debug.js +19 -16
  20. package/dist/demo.compiled.debug.map +3 -3
  21. package/dist/demo.compiled.js +19 -16
  22. package/dist/demo.compiled.map +3 -3
  23. package/dist/deps.js +18 -11
  24. package/dist/locales.js +28 -26
  25. package/dist/receiver.compiled.debug.js +11 -9
  26. package/dist/receiver.compiled.debug.map +3 -3
  27. package/dist/receiver.compiled.js +11 -9
  28. package/dist/receiver.compiled.map +3 -3
  29. package/dist/shaka-player.compiled.d.ts +205 -15
  30. package/dist/shaka-player.compiled.debug.d.ts +205 -15
  31. package/dist/shaka-player.compiled.debug.externs.js +256 -6
  32. package/dist/shaka-player.compiled.debug.js +406 -354
  33. package/dist/shaka-player.compiled.debug.map +4 -4
  34. package/dist/shaka-player.compiled.externs.js +256 -6
  35. package/dist/shaka-player.compiled.js +826 -800
  36. package/dist/shaka-player.compiled.map +4 -4
  37. package/dist/shaka-player.ui.d.ts +221 -16
  38. package/dist/shaka-player.ui.debug.d.ts +221 -16
  39. package/dist/shaka-player.ui.debug.externs.js +283 -6
  40. package/dist/shaka-player.ui.debug.js +547 -476
  41. package/dist/shaka-player.ui.debug.map +4 -4
  42. package/dist/shaka-player.ui.externs.js +283 -6
  43. package/dist/shaka-player.ui.js +1019 -980
  44. package/dist/shaka-player.ui.map +4 -4
  45. package/docs/design/bg-fetch-after.gv +29 -0
  46. package/docs/design/bg-fetch-after.gv.png +0 -0
  47. package/docs/design/bg-fetch-before.gv +18 -0
  48. package/docs/design/bg-fetch-before.gv.png +0 -0
  49. package/docs/design/bg-fetch.md +134 -0
  50. package/docs/tutorials/blob-url.md +15 -0
  51. package/docs/tutorials/index.json +1 -0
  52. package/docs/tutorials/ui-customization.md +66 -0
  53. package/docs/tutorials/ui.md +5 -5
  54. package/externs/ima.js +3 -0
  55. package/externs/shaka/ads.js +5 -0
  56. package/externs/shaka/manifest.js +12 -1
  57. package/externs/shaka/manifest_parser.js +10 -1
  58. package/externs/shaka/net.js +20 -4
  59. package/externs/shaka/offline.js +31 -2
  60. package/externs/shaka/player.js +130 -5
  61. package/externs/shaka/text.js +14 -0
  62. package/externs/webcrypto.js +18 -0
  63. package/lib/abr/ewma.js +14 -0
  64. package/lib/abr/ewma_bandwidth_estimator.js +16 -2
  65. package/lib/abr/simple_abr_manager.js +6 -0
  66. package/lib/ads/client_side_ad.js +8 -0
  67. package/lib/ads/client_side_ad_manager.js +8 -4
  68. package/lib/ads/server_side_ad.js +7 -0
  69. package/lib/cast/cast_utils.js +1 -0
  70. package/lib/dash/dash_parser.js +48 -14
  71. package/lib/dash/segment_base.js +25 -1
  72. package/lib/dash/segment_list.js +18 -9
  73. package/lib/dash/segment_template.js +23 -10
  74. package/lib/hls/hls_parser.js +86 -23
  75. package/lib/media/quality_observer.js +291 -0
  76. package/lib/media/segment_index.js +6 -0
  77. package/lib/media/segment_reference.js +18 -3
  78. package/lib/media/streaming_engine.js +42 -4
  79. package/lib/net/http_fetch_plugin.js +31 -11
  80. package/lib/net/http_plugin_utils.js +1 -0
  81. package/lib/net/http_xhr_plugin.js +40 -14
  82. package/lib/net/networking_engine.js +81 -18
  83. package/lib/offline/download_info.js +68 -0
  84. package/lib/offline/download_manager.js +13 -5
  85. package/lib/offline/indexeddb/base_storage_cell.js +35 -0
  86. package/lib/offline/indexeddb/storage_mechanism.js +1 -1
  87. package/lib/offline/indexeddb/v5_storage_cell.js +5 -0
  88. package/lib/offline/storage.js +393 -284
  89. package/lib/offline/stored_content_utils.js +2 -0
  90. package/lib/player.js +206 -7
  91. package/lib/polyfill/random_uuid.js +52 -0
  92. package/lib/text/cue.js +12 -0
  93. package/lib/text/ttml_text_parser.js +25 -2
  94. package/lib/text/ui_text_displayer.js +2 -2
  95. package/lib/util/cmcd_manager.js +826 -0
  96. package/lib/util/error.js +6 -0
  97. package/lib/util/language_utils.js +12 -0
  98. package/lib/util/mutex.js +52 -0
  99. package/lib/util/periods.js +98 -64
  100. package/lib/util/player_configuration.js +18 -0
  101. package/package.json +1 -1
  102. package/shaka-player.uncompiled.js +3 -0
  103. package/test/dash/dash_parser_content_protection_unit.js +2 -0
  104. package/test/dash/dash_parser_live_unit.js +101 -1
  105. package/test/dash/dash_parser_manifest_unit.js +35 -1
  106. package/test/dash/dash_parser_segment_base_unit.js +2 -0
  107. package/test/dash/dash_parser_segment_list_unit.js +2 -1
  108. package/test/dash/dash_parser_segment_template_unit.js +2 -1
  109. package/test/hls/hls_live_unit.js +2 -0
  110. package/test/hls/hls_parser_unit.js +2 -0
  111. package/test/media/quality_observer_unit.js +139 -0
  112. package/test/media/segment_reference_unit.js +16 -1
  113. package/test/media/streaming_engine_integration.js +2 -1
  114. package/test/media/streaming_engine_unit.js +2 -0
  115. package/test/net/http_plugin_unit.js +20 -15
  116. package/test/offline/storage_integration.js +108 -4
  117. package/test/player_integration.js +99 -0
  118. package/test/test/util/dash_parser_util.js +4 -0
  119. package/test/test/util/fake_ad.js +7 -0
  120. package/test/test/util/fake_drm_engine.js +9 -2
  121. package/test/test/util/manifest_generator.js +3 -2
  122. package/test/test/util/test_scheme.js +14 -1
  123. package/test/text/ttml_text_parser_unit.js +47 -0
  124. package/test/ui/ui_unit.js +250 -0
  125. package/test/util/cmcd_manager_unit.js +277 -0
  126. package/ui/airplay_button.js +1 -0
  127. package/ui/audio_language_selection.js +7 -0
  128. package/ui/cast_button.js +1 -0
  129. package/ui/context_menu.js +109 -0
  130. package/ui/controls.js +41 -7
  131. package/ui/controls.less +1 -0
  132. package/ui/enums.js +4 -0
  133. package/ui/externs/ui.js +28 -1
  134. package/ui/fast_forward_button.js +14 -5
  135. package/ui/fullscreen_button.js +1 -0
  136. package/ui/less/buttons.less +0 -4
  137. package/ui/less/containers.less +74 -0
  138. package/ui/less/overflow_menu.less +0 -10
  139. package/ui/less/tooltip.less +97 -0
  140. package/ui/locales/en.json +1 -0
  141. package/ui/locales/source.json +4 -0
  142. package/ui/loop_button.js +5 -0
  143. package/ui/mute_button.js +2 -1
  144. package/ui/overflow_menu.js +5 -25
  145. package/ui/pip_button.js +5 -0
  146. package/ui/play_button.js +2 -2
  147. package/ui/playback_rate_selection.js +8 -9
  148. package/ui/resolution_selection.js +7 -0
  149. package/ui/rewind_button.js +15 -6
  150. package/ui/seek_bar.js +4 -2
  151. package/ui/settings_menu.js +36 -4
  152. package/ui/small_play_button.js +1 -0
  153. package/ui/statistics_button.js +253 -0
  154. package/ui/text_selection.js +11 -4
  155. package/ui/ui.js +30 -0
  156. package/ui/volume_bar.js +1 -1
@@ -33,6 +33,8 @@ shaka.offline = {};
33
33
  shaka.text = {};
34
34
  /** @const */
35
35
  shaka.util = {};
36
+ /** @const */
37
+ shaka.util.CmcdManager = {};
36
38
 
37
39
  /**
38
40
  * @implements {shaka.extern.Cue}
@@ -124,6 +126,14 @@ shaka.text.Cue.prototype.backgroundImage;
124
126
  * @override
125
127
  */
126
128
  shaka.text.Cue.prototype.border;
129
+ /**
130
+ * @override
131
+ */
132
+ shaka.text.Cue.prototype.textStrokeColor;
133
+ /**
134
+ * @override
135
+ */
136
+ shaka.text.Cue.prototype.textStrokeWidth;
127
137
  /**
128
138
  * @override
129
139
  */
@@ -631,6 +641,7 @@ shaka.util.Error.Code = {
631
641
  'MISSING_STORAGE_CELL': 9013,
632
642
  'STORAGE_LIMIT_REACHED': 9014,
633
643
  'DOWNLOAD_SIZE_CALLBACK_ERROR': 9015,
644
+ 'MODIFY_OPERATION_NOT_SUPPORTED': 9016,
634
645
  'CS_IMA_SDK_MISSING': 10000,
635
646
  'CS_AD_MANAGER_NOT_INITIALIZED': 10001,
636
647
  'SS_IMA_SDK_MISSING': 10002,
@@ -764,6 +775,149 @@ shaka.text.TextEngine = class {
764
775
  */
765
776
  destroy() {}
766
777
  };
778
+ /**
779
+ * @summary A set of language utility functions.
780
+ * @final
781
+ */
782
+ shaka.util.LanguageUtils = class {
783
+ /**
784
+ * Check if |locale1| and |locale2| are locale-compatible.
785
+ * Locale-compatible is defined as all components in each locale match. Since
786
+ * we only respect the language and region components, we only check that
787
+ * the language and region components match.
788
+ * Examples:
789
+ * Locale A | Locale B | Locale Compatible
790
+ * ---------------------------------------
791
+ * en-US | en-US | true
792
+ * en | en-US | false
793
+ * en-US | en-CA | false
794
+ * @param {string} locale1
795
+ * @param {string} locale2
796
+ * @return {boolean}
797
+ */
798
+ static areLocaleCompatible(locale1, locale2) {}
799
+ /**
800
+ * Check if |locale1| and |locale2| are language-compatible.
801
+ * Language compatible is when the language component of each locale matches.
802
+ * This means that no matter what region they have (or don't have) as long as
803
+ * the language components match, they are language-compatible.
804
+ * Examples:
805
+ * Locale A | Locale B | Language-Compatible
806
+ * -----------------------------------------
807
+ * en-US | en-US | true
808
+ * en-US | en | true
809
+ * en-US | en-CA | true
810
+ * en-CA | fr-CA | false
811
+ * @param {string} locale1
812
+ * @param {string} locale2
813
+ * @return {boolean}
814
+ */
815
+ static areLanguageCompatible(locale1, locale2) {}
816
+ /**
817
+ * Check if |possibleParent| is the parent locale of |possibleChild|. Because
818
+ * we do not support dialects, the parent-child relationship is a lot simpler.
819
+ * In a parent child relationship:
820
+ * - The parent and child have the same language-component
821
+ * - The parent has no region-component
822
+ * - The child has a region-component
823
+ * Example:
824
+ * Locale A | Locale B | Is A The parent of B?
825
+ * --------------------------------------------
826
+ * en-US | en-US | no
827
+ * en-US | en | no
828
+ * en | en-US | yes
829
+ * en | en | no
830
+ * en | fr | no
831
+ * @param {string} possibleParent
832
+ * @param {string} possibleChild
833
+ * @return {boolean}
834
+ */
835
+ static isParentOf(possibleParent, possibleChild) {}
836
+ /**
837
+ * Check if |localeA| shares the same parent with |localeB|. Since we don't
838
+ * support dialect, we will only look at language and region. For two locales
839
+ * to be siblings:
840
+ * - Both must have language-components
841
+ * - Both must have region-components
842
+ * - Both must have the same language-component
843
+ * Example:
844
+ * Locale A | Locale B | Siblings?
845
+ * --------------------------------------------
846
+ * en-US | en-US | yes
847
+ * en-US | en-CA | yes
848
+ * en-US | en | no
849
+ * en | en-US | no
850
+ * en | en | no
851
+ * en | fr | no
852
+ * @param {string} localeA
853
+ * @param {string} localeB
854
+ * @return {boolean}
855
+ */
856
+ static isSiblingOf(localeA, localeB) {}
857
+ /**
858
+ * Normalize a locale. This will take a locale and canonicalize it to a state
859
+ * that we are prepared to work with.
860
+ * We only support with:
861
+ * - language
862
+ * - language-REGION
863
+ * If given a dialect, we will discard it. We will convert any 3-character
864
+ * codes to 2-character codes. We will force language codes to lowercase and
865
+ * region codes to uppercase.
866
+ * @param {string} locale
867
+ * @return {string}
868
+ */
869
+ static normalize(locale) {}
870
+ /**
871
+ * Check if two language codes are siblings. Language codes are siblings if
872
+ * they share the same base language while neither one is the base language.
873
+ * For example, "en-US" and "en-CA" are siblings but "en-US" and "en" are not
874
+ * siblings.
875
+ * @param {string} a
876
+ * @param {string} b
877
+ * @return {boolean}
878
+ */
879
+ static areSiblings(a, b) {}
880
+ /**
881
+ * Compute a numerical relatedness for language codes. Language codes with a
882
+ * higher relatedness are a better match. Unrelated language codes have a
883
+ * relatedness score of 0.
884
+ * @param {string} target
885
+ * @param {string} candidate
886
+ * @return {number}
887
+ */
888
+ static relatedness(target, candidate) {}
889
+ /**
890
+ * Get the normalized base language for a language code.
891
+ * @param {string} lang
892
+ * @return {string}
893
+ */
894
+ static getBase(lang) {}
895
+ /**
896
+ * Get the normalized language of the given text stream. Will return 'und' if
897
+ * a language is not found on the text stream.
898
+ * This should always be used to get the language from a text stream.
899
+ * @param {shaka.extern.Stream} stream
900
+ * @return {string}
901
+ */
902
+ static getLocaleForText(stream) {}
903
+ /**
904
+ * Get the normalized locale for the given variant. This will look through
905
+ * the variant to find the locale that represents the content in the variant.
906
+ * This will return 'und' if no language can be found.
907
+ * This should always be used to get the locale from a variant.
908
+ * @param {shaka.extern.Variant} variant
909
+ * @return {string}
910
+ */
911
+ static getLocaleForVariant(variant) {}
912
+ /**
913
+ * Find the locale in |searchSpace| that comes closest to |target|. If no
914
+ * locale is found to be close to |target|, then |null| will be returned.
915
+ * @param {string} target
916
+ * @param {!Iterable.<string>} searchSpace
917
+ * @return {?string}
918
+ */
919
+ static findClosestLocale(target, searchSpace) {}
920
+ };
767
921
  /**
768
922
  * A timer allows a single function to be executed at a later time or at
769
923
  * regular intervals.
@@ -1021,8 +1175,12 @@ shaka.net.NetworkingEngine = class extends shaka.util.FakeEventTarget {
1021
1175
  * @param {function(number, number)=} onProgressUpdated Called when a progress
1022
1176
  * event is triggered. Passed the duration, in milliseconds, that the
1023
1177
  * request took, and the number of bytes transferred.
1178
+ * @param {shaka.net.NetworkingEngine.OnHeadersReceived=} onHeadersReceived
1179
+ * Called when the headers are received for a download.
1180
+ * @param {shaka.net.NetworkingEngine.OnDownloadFailed=} onDownloadFailed
1181
+ * Called when a download fails, for any reason.
1024
1182
  */
1025
- constructor(onProgressUpdated) {}
1183
+ constructor(onProgressUpdated, onHeadersReceived, onDownloadFailed) {}
1026
1184
  /**
1027
1185
  * @param {boolean} forceHTTPS
1028
1186
  */
@@ -1161,6 +1319,27 @@ shaka.net.NetworkingEngine.PluginPriority = {
1161
1319
  'PREFERRED': 2,
1162
1320
  'APPLICATION': 3
1163
1321
  };
1322
+ /**
1323
+ * @typedef {function(
1324
+ * !Object.<string, string>,
1325
+ * !shaka.extern.Request,
1326
+ * !shaka.net.NetworkingEngine.RequestType)}
1327
+ * @description
1328
+ * A callback function that passes the shaka.extern.HeadersReceived along to
1329
+ * the player, plus some extra data.
1330
+ */
1331
+ shaka.net.NetworkingEngine.OnHeadersReceived;
1332
+ /**
1333
+ * @typedef {function(
1334
+ * !shaka.extern.Request,
1335
+ * ?shaka.util.Error,
1336
+ * number,
1337
+ * boolean)}
1338
+ * @description
1339
+ * A callback function that notifies the player when a download fails, for any
1340
+ * reason (e.g. even if the download was aborted).
1341
+ */
1342
+ shaka.net.NetworkingEngine.OnDownloadFailed;
1164
1343
  /**
1165
1344
  * An interface to standardize how objects release internal references
1166
1345
  * synchronously. If an object needs to asynchronously release references, then
@@ -1459,11 +1638,13 @@ shaka.media.InitSegmentReference = class {
1459
1638
  * of the resource containing the segment.
1460
1639
  * @param {number} startByte The offset from the start of the resource to the
1461
1640
  * start of the segment.
1462
- * @param {?number} endByte The offset from the start of the resource to the
1463
- * end of the segment, inclusive. A value of null indicates that the
1641
+ * @param {?number} endByte The offset from the start of the resource
1642
+ * to the end of the segment, inclusive. A value of null indicates that the
1464
1643
  * segment extends to the end of the resource.
1644
+ * @param {null|shaka.extern.MediaQualityInfo=} mediaQuality Information about
1645
+ * the quality of the media associated with this init segment.
1465
1646
  */
1466
- constructor(uris, startByte, endByte) {}
1647
+ constructor(uris, startByte, endByte, mediaQuality) {}
1467
1648
  /**
1468
1649
  * Returns the offset from the start of the resource to the
1469
1650
  * start of the segment.
@@ -1994,6 +2175,46 @@ shaka.text.UITextDisplayer = class {
1994
2175
  */
1995
2176
  shaka.text.WebVttGenerator = class {
1996
2177
  };
2178
+ /**
2179
+ * @typedef {{
2180
+ * type: string,
2181
+ * init: boolean,
2182
+ * duration: number,
2183
+ * mimeType: string,
2184
+ * codecs: string,
2185
+ * bandwidth: (number|undefined)
2186
+ * }}
2187
+ * @property {string} type
2188
+ * The media type
2189
+ * @property {boolean} init
2190
+ * Flag indicating whether the segment is an init segment
2191
+ * @property {number} duration
2192
+ * The duration of the segment in seconds
2193
+ * @property {string} mimeType
2194
+ * The segment's mime type
2195
+ * @property {string} codecs
2196
+ * The segment's codecs
2197
+ * @property {(number|undefined)} bandwidth
2198
+ * The segment's variation bandwidth
2199
+ */
2200
+ shaka.util.CmcdManager.SegmentInfo;
2201
+ /**
2202
+ * @typedef {{
2203
+ * format: shaka.util.CmcdManager.StreamingFormat
2204
+ * }}
2205
+ * @property {shaka.util.CmcdManager.StreamingFormat} format
2206
+ * The manifest's stream format
2207
+ */
2208
+ shaka.util.CmcdManager.ManifestInfo;
2209
+ /**
2210
+ * @enum {string}
2211
+ */
2212
+ shaka.util.CmcdManager.StreamingFormat = {
2213
+ DASH: 'd',
2214
+ HLS: 'h',
2215
+ SMOOTH: 's',
2216
+ OTHER: 'o'
2217
+ };
1997
2218
  /** */
1998
2219
  shaka.util.ConfigUtils = class {
1999
2220
  /**
@@ -2133,6 +2354,14 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
2133
2354
  * @return {!Promise}
2134
2355
  */
2135
2356
  unload(initializeMediaSource) {}
2357
+ /**
2358
+ * Provides a way to update the stream start position during the media loading
2359
+ * process. Can for example be called from the <code>manifestparsed</code>
2360
+ * event handler to update the start position based on information in the
2361
+ * manifest.
2362
+ * @param {number} startTime
2363
+ */
2364
+ updateStartTime(startTime) {}
2136
2365
  /**
2137
2366
  * Tell the player to load the content at <code>assetUri</code> and start
2138
2367
  * playback at <code>startTime</code>. Before calling <code>load</code>,
@@ -2642,6 +2871,10 @@ shaka.ads.ClientSideAd = class {
2642
2871
  * @override
2643
2872
  */
2644
2873
  isMuted() {}
2874
+ /**
2875
+ * @override
2876
+ */
2877
+ isLinear() {}
2645
2878
  /**
2646
2879
  * @override
2647
2880
  */
@@ -2724,6 +2957,10 @@ shaka.ads.ServerSideAd = class {
2724
2957
  * @override
2725
2958
  */
2726
2959
  isMuted() {}
2960
+ /**
2961
+ * @override
2962
+ */
2963
+ isLinear() {}
2727
2964
  /**
2728
2965
  * @override
2729
2966
  */
@@ -3194,9 +3431,11 @@ shaka.net.HttpFetchPlugin = class {
3194
3431
  * @param {shaka.net.NetworkingEngine.RequestType} requestType
3195
3432
  * @param {shaka.extern.ProgressUpdated} progressUpdated Called when a
3196
3433
  * progress event happened.
3434
+ * @param {shaka.extern.HeadersReceived} headersReceived Called when the
3435
+ * headers for the download are received, but before the body is.
3197
3436
  * @return {!shaka.extern.IAbortableOperation.<shaka.extern.Response>}
3198
3437
  */
3199
- static parse(uri, request, requestType, progressUpdated) {}
3438
+ static parse(uri, request, requestType, progressUpdated, headersReceived) {}
3200
3439
  /**
3201
3440
  * Determine if the Fetch API is supported in the browser. Note: this is
3202
3441
  * deliberately exposed as a method to allow the client app to use the same
@@ -3215,9 +3454,11 @@ shaka.net.HttpXHRPlugin = class {
3215
3454
  * @param {shaka.net.NetworkingEngine.RequestType} requestType
3216
3455
  * @param {shaka.extern.ProgressUpdated} progressUpdated Called when a
3217
3456
  * progress event happened.
3457
+ * @param {shaka.extern.HeadersReceived} headersReceived Called when the
3458
+ * headers for the download are received, but before the body is.
3218
3459
  * @return {!shaka.extern.IAbortableOperation.<shaka.extern.Response>}
3219
3460
  */
3220
- static parse(uri, request, requestType, progressUpdated) {}
3461
+ static parse(uri, request, requestType, progressUpdated, headersReceived) {}
3221
3462
  };
3222
3463
  /**
3223
3464
  * StorageMuxer is responsible for managing StorageMechanisms and addressing
@@ -3544,6 +3785,15 @@ shaka.polyfill.PiPWebkit = class {
3544
3785
  */
3545
3786
  static install() {}
3546
3787
  };
3788
+ /**
3789
+ * @summary A polyfill to provide window.crypto.randomUUID in all browsers.
3790
+ */
3791
+ shaka.polyfill.RandomUUID = class {
3792
+ /**
3793
+ * Install the polyfill if needed.
3794
+ */
3795
+ static install() {}
3796
+ };
3547
3797
  /**
3548
3798
  * @summary A polyfill to provide navigator.storage.estimate in old
3549
3799
  * webkit browsers.