@@ -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,22 +1729,77 @@ __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
1805
let name = self . import_name ( & JsImport {
@@ -1754,7 +1809,9 @@ __wbg_set_wasm(wasm);"
1754
1809
} ,
1755
1810
fields : Vec :: new ( ) ,
1756
1811
} ) ?;
1757
- self . global ( & format ! ( "let cached{} = new {}{};" , s, name, args) ) ;
1812
+ // decl_kind is the kind of the kind of the declaration: let or const
1813
+ // cached_text_processor_init is the rest of the statement for initializing a cached text processor
1814
+ self . global ( & format ! ( "{} {}" , decl_kind, cached_text_processor_init) ) ;
1758
1815
}
1759
1816
OutputMode :: Bundler {
1760
1817
browser_only : false ,
@@ -1766,13 +1823,15 @@ __wbg_set_wasm(wasm);"
1766
1823
",
1767
1824
s
1768
1825
) ) ;
1769
- self . global ( & format ! ( "let cached{0} = new l{0}{1}; " , s , args ) ) ;
1826
+ self . global ( & format ! ( "{} {} " , decl_kind , cached_text_processor_init ) ) ;
1770
1827
}
1771
1828
OutputMode :: Deno
1772
1829
| OutputMode :: Web
1773
1830
| OutputMode :: NoModules { .. }
1774
1831
| 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) )
1832
+ // decl_kind is the kind of the kind of the declaration: let or const
1833
+ // cached_text_processor_init is the rest of the statement for initializing a cached text processor
1834
+ self . global ( & format ! ( "{} {}" , decl_kind, cached_text_processor_init) )
1776
1835
}
1777
1836
} ;
1778
1837
@@ -1795,9 +1854,43 @@ __wbg_set_wasm(wasm);"
1795
1854
Ok ( ( ) )
1796
1855
}
1797
1856
1857
+ /// Generates a partial text processor statement, everything except the declaration kind,
1858
+ /// i.e. everything except for `const` or `let` which the caller needs to handle itself.
1859
+ fn generate_cached_text_processor_init (
1860
+ & mut self ,
1861
+ s : & str ,
1862
+ op : & str ,
1863
+ args : & str ,
1864
+ ) -> Result < String , Error > {
1865
+ let new_cached_text_procesor = match & self . config . mode {
1866
+ OutputMode :: Node { .. } => {
1867
+ let name = self . import_name ( & JsImport {
1868
+ name : JsImportName :: Module {
1869
+ module : "util" . to_string ( ) ,
1870
+ name : s. to_string ( ) ,
1871
+ } ,
1872
+ fields : Vec :: new ( ) ,
1873
+ } ) ?;
1874
+ format ! ( "cached{} = new {}{};" , s, name, args)
1875
+ }
1876
+ OutputMode :: Bundler {
1877
+ browser_only : false ,
1878
+ } => {
1879
+ format ! ( "cached{0} = new l{0}{1};" , s, args)
1880
+ }
1881
+ OutputMode :: Deno
1882
+ | OutputMode :: Web
1883
+ | OutputMode :: NoModules { .. }
1884
+ | OutputMode :: Bundler { browser_only : true } => {
1885
+ format ! ( "cached{0} = (typeof {0} !== 'undefined' ? new {0}{1} : {{ {2}: () => {{ throw Error('{0} not available') }} }} );" , s, args, op)
1886
+ }
1887
+ } ;
1888
+ Ok ( new_cached_text_procesor)
1889
+ }
1890
+
1798
1891
fn expose_get_string_from_wasm ( & mut self , memory : MemoryId ) -> Result < MemView , Error > {
1799
- self . expose_text_decoder ( ) ?;
1800
1892
let mem = self . expose_uint8_memory ( memory) ;
1893
+ self . expose_text_decoder ( & mem, memory) ?;
1801
1894
let ret = MemView {
1802
1895
name : "getStringFromWasm" . into ( ) ,
1803
1896
num : mem. num ,
@@ -1807,6 +1900,23 @@ __wbg_set_wasm(wasm);"
1807
1900
return Ok ( ret) ;
1808
1901
}
1809
1902
1903
+ self . global ( & format ! (
1904
+ "
1905
+ function {}(ptr, len) {{
1906
+ ptr = ptr >>> 0;
1907
+ return decodeText(ptr, len);
1908
+ }}
1909
+ " ,
1910
+ ret,
1911
+ ) ) ;
1912
+ Ok ( ret)
1913
+ }
1914
+
1915
+ fn generate_text_decoder_decode (
1916
+ & self ,
1917
+ mem : & MemView ,
1918
+ memory : MemoryId ,
1919
+ ) -> Result < String , Error > {
1810
1920
// Typically we try to give a raw view of memory out to `TextDecoder` to
1811
1921
// avoid copying too much data. If, however, a `SharedArrayBuffer` is
1812
1922
// being used it looks like that is rejected by `TextDecoder` or
@@ -1818,16 +1928,10 @@ __wbg_set_wasm(wasm);"
1818
1928
let is_shared = self . module . memories . get ( memory) . shared ;
1819
1929
let method = if is_shared { "slice" } else { "subarray" } ;
1820
1930
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)
1931
+ Ok ( format ! (
1932
+ "cachedTextDecoder.decode({}().{}(ptr, ptr + len))" ,
1933
+ mem, method
1934
+ ) )
1831
1935
}
1832
1936
1833
1937
fn expose_get_cached_string_from_wasm (
0 commit comments