@@ -1714,10 +1714,10 @@ __wbg_set_wasm(wasm);"
1714
1714
if !self . should_write_global ( "text_encoder" ) {
1715
1715
return Ok ( ( ) ) ;
1716
1716
}
1717
- self . expose_text_processor ( "TextEncoder" , "encode" , "('utf-8')" , None )
1717
+ self . expose_text_processor ( "const" , " TextEncoder", "encode" , "('utf-8')" , None )
1718
1718
}
1719
1719
1720
- fn expose_text_decoder ( & mut self ) -> Result < ( ) , Error > {
1720
+ fn expose_text_decoder ( & mut self , mem : & MemView , memory : MemoryId ) -> Result < ( ) , Error > {
1721
1721
if !self . should_write_global ( "text_decoder" ) {
1722
1722
return Ok ( ( ) ) ;
1723
1723
}
@@ -1729,32 +1729,82 @@ __wbg_set_wasm(wasm);"
1729
1729
// `ignoreBOM` is needed so that the BOM will be preserved when sending a string from Rust to JS
1730
1730
// `fatal` is needed to catch any weird encoding bugs when sending a string from Rust to JS
1731
1731
self . expose_text_processor (
1732
+ "let" ,
1732
1733
"TextDecoder" ,
1733
1734
"decode" ,
1734
1735
"('utf-8', { ignoreBOM: true, fatal: true })" ,
1735
1736
init,
1736
1737
) ?;
1737
1738
1739
+ let text_decoder_decode = self . generate_text_decoder_decode ( mem, memory) ?;
1740
+ match & self . config . mode {
1741
+ OutputMode :: Bundler { .. } | OutputMode :: Web => {
1742
+ // For targets that can run in a browser, we need a workaround for the fact that
1743
+ // (at least) Safari 16 to 18 has a TextDecoder that can't decode anymore after
1744
+ // processing 2GiB of data. The workaround is that we keep track of how much the
1745
+ // decoder has decoded and just create a new decoder when we're getting close to
1746
+ // the limit.
1747
+ // See MAX_SAFARI_DECODE_BYTES below for link to bug report.
1748
+
1749
+ let cached_text_processor = self . generate_cached_text_processor_init (
1750
+ "TextDecoder" ,
1751
+ "decode" ,
1752
+ "('utf-8', { ignoreBOM: true, fatal: true })" ,
1753
+ ) ?;
1754
+
1755
+ // Maximum number of bytes Safari can handle for one TextDecoder is 2GiB (2147483648)
1756
+ // but empirically it seems to crash a bit before the end, so we remove 1MiB of margin.
1757
+ // Workaround for a bug in Safari.
1758
+ // See https://github.com/rustwasm/wasm-bindgen/issues/4471
1759
+ const MAX_SAFARI_DECODE_BYTES : u32 = 2147483648 - 1048576 ;
1760
+ self . global ( & format ! (
1761
+ "
1762
+ const MAX_SAFARI_DECODE_BYTES = {0};
1763
+ let numBytesDecoded = 0;
1764
+ function decodeText(ptr, len) {{
1765
+ numBytesDecoded += len;
1766
+ if (numBytesDecoded >= MAX_SAFARI_DECODE_BYTES) {{
1767
+ {1}
1768
+ cachedTextDecoder.decode();
1769
+ numBytesDecoded = len;
1770
+ }}
1771
+ return {2};
1772
+ }}
1773
+ " ,
1774
+ MAX_SAFARI_DECODE_BYTES , cached_text_processor, text_decoder_decode,
1775
+ ) ) ;
1776
+ }
1777
+ _ => {
1778
+ // For any non-browser target, we can just use the TextDecoder without any workarounds.
1779
+ // For browser-targets, see the workaround for Safari above.
1780
+ self . global ( & format ! (
1781
+ "
1782
+ function decodeText(ptr, len) {{
1783
+ return {};
1784
+ }}
1785
+ " ,
1786
+ text_decoder_decode,
1787
+ ) ) ;
1788
+ }
1789
+ }
1790
+
1738
1791
Ok ( ( ) )
1739
1792
}
1740
1793
1741
1794
fn expose_text_processor (
1742
1795
& mut self ,
1796
+ decl_kind : & str ,
1743
1797
s : & str ,
1744
1798
op : & str ,
1745
1799
args : & str ,
1746
1800
init : Option < & str > ,
1747
1801
) -> Result < ( ) , Error > {
1802
+ let cached_text_processor_init = self . generate_cached_text_processor_init ( s, op, args) ?;
1748
1803
match & self . config . mode {
1749
1804
OutputMode :: Node { .. } => {
1750
- let name = self . import_name ( & JsImport {
1751
- name : JsImportName :: Module {
1752
- module : "util" . to_string ( ) ,
1753
- name : s. to_string ( ) ,
1754
- } ,
1755
- fields : Vec :: new ( ) ,
1756
- } ) ?;
1757
- self . global ( & format ! ( "let cached{} = new {}{};" , s, name, args) ) ;
1805
+ // decl_kind is the kind of the kind of the declaration: let or const
1806
+ // cached_text_processor_init is the rest of the statement for initializing a cached text processor
1807
+ self . global ( & format ! ( "{} {}" , decl_kind, cached_text_processor_init) ) ;
1758
1808
}
1759
1809
OutputMode :: Bundler {
1760
1810
browser_only : false ,
@@ -1766,13 +1816,15 @@ __wbg_set_wasm(wasm);"
1766
1816
",
1767
1817
s
1768
1818
) ) ;
1769
- self . global ( & format ! ( "let cached{0} = new l{0}{1}; " , s , args ) ) ;
1819
+ self . global ( & format ! ( "{} {} " , decl_kind , cached_text_processor_init ) ) ;
1770
1820
}
1771
1821
OutputMode :: Deno
1772
1822
| OutputMode :: Web
1773
1823
| OutputMode :: NoModules { .. }
1774
1824
| OutputMode :: Bundler { browser_only : true } => {
1775
- self . global ( & format ! ( "const cached{0} = (typeof {0} !== 'undefined' ? new {0}{1} : {{ {2}: () => {{ throw Error('{0} not available') }} }} );" , s, args, op) )
1825
+ // decl_kind is the kind of the kind of the declaration: let or const
1826
+ // cached_text_processor_init is the rest of the statement for initializing a cached text processor
1827
+ self . global ( & format ! ( "{} {}" , decl_kind, cached_text_processor_init) )
1776
1828
}
1777
1829
} ;
1778
1830
@@ -1795,9 +1847,43 @@ __wbg_set_wasm(wasm);"
1795
1847
Ok ( ( ) )
1796
1848
}
1797
1849
1850
+ /// Generates a partial text processor statement, everything except the declaration kind,
1851
+ /// i.e. everything except for `const` or `let` which the caller needs to handle itself.
1852
+ fn generate_cached_text_processor_init (
1853
+ & mut self ,
1854
+ s : & str ,
1855
+ op : & str ,
1856
+ args : & str ,
1857
+ ) -> Result < String , Error > {
1858
+ let new_cached_text_procesor = match & self . config . mode {
1859
+ OutputMode :: Node { .. } => {
1860
+ let name = self . import_name ( & JsImport {
1861
+ name : JsImportName :: Module {
1862
+ module : "util" . to_string ( ) ,
1863
+ name : s. to_string ( ) ,
1864
+ } ,
1865
+ fields : Vec :: new ( ) ,
1866
+ } ) ?;
1867
+ format ! ( "cached{} = new {}{};" , s, name, args)
1868
+ }
1869
+ OutputMode :: Bundler {
1870
+ browser_only : false ,
1871
+ } => {
1872
+ format ! ( "cached{0} = new l{0}{1};" , s, args)
1873
+ }
1874
+ OutputMode :: Deno
1875
+ | OutputMode :: Web
1876
+ | OutputMode :: NoModules { .. }
1877
+ | OutputMode :: Bundler { browser_only : true } => {
1878
+ format ! ( "cached{0} = (typeof {0} !== 'undefined' ? new {0}{1} : {{ {2}: () => {{ throw Error('{0} not available') }} }} );" , s, args, op)
1879
+ }
1880
+ } ;
1881
+ Ok ( new_cached_text_procesor)
1882
+ }
1883
+
1798
1884
fn expose_get_string_from_wasm ( & mut self , memory : MemoryId ) -> Result < MemView , Error > {
1799
- self . expose_text_decoder ( ) ?;
1800
1885
let mem = self . expose_uint8_memory ( memory) ;
1886
+ self . expose_text_decoder ( & mem, memory) ?;
1801
1887
let ret = MemView {
1802
1888
name : "getStringFromWasm" . into ( ) ,
1803
1889
num : mem. num ,
@@ -1807,6 +1893,23 @@ __wbg_set_wasm(wasm);"
1807
1893
return Ok ( ret) ;
1808
1894
}
1809
1895
1896
+ self . global ( & format ! (
1897
+ "
1898
+ function {}(ptr, len) {{
1899
+ ptr = ptr >>> 0;
1900
+ return decodeText(ptr, len);
1901
+ }}
1902
+ " ,
1903
+ ret,
1904
+ ) ) ;
1905
+ Ok ( ret)
1906
+ }
1907
+
1908
+ fn generate_text_decoder_decode (
1909
+ & self ,
1910
+ mem : & MemView ,
1911
+ memory : MemoryId ,
1912
+ ) -> Result < String , Error > {
1810
1913
// Typically we try to give a raw view of memory out to `TextDecoder` to
1811
1914
// avoid copying too much data. If, however, a `SharedArrayBuffer` is
1812
1915
// being used it looks like that is rejected by `TextDecoder` or
@@ -1818,16 +1921,10 @@ __wbg_set_wasm(wasm);"
1818
1921
let is_shared = self . module . memories . get ( memory) . shared ;
1819
1922
let method = if is_shared { "slice" } else { "subarray" } ;
1820
1923
1821
- self . global ( & format ! (
1822
- "
1823
- function {}(ptr, len) {{
1824
- ptr = ptr >>> 0;
1825
- return cachedTextDecoder.decode({}().{}(ptr, ptr + len));
1826
- }}
1827
- " ,
1828
- ret, mem, method
1829
- ) ) ;
1830
- Ok ( ret)
1924
+ Ok ( format ! (
1925
+ "cachedTextDecoder.decode({}().{}(ptr, ptr + len))" ,
1926
+ mem, method
1927
+ ) )
1831
1928
}
1832
1929
1833
1930
fn expose_get_cached_string_from_wasm (
0 commit comments