expo 55.0.28 → 55.0.29
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.
- package/android/build.gradle +2 -2
- package/android/src/main/java/expo/modules/fetch/NativeResponse.kt +20 -7
- package/build/winter/TextDecoder.d.ts +2 -8
- package/build/winter/TextDecoder.d.ts.map +1 -1
- package/bundledNativeModules.json +66 -66
- package/package.json +8 -8
- package/src/winter/TextDecoder.ts +360 -362
- package/src/winter/__tests__/TextDecoder.test.native.ts +142 -0
- package/template.tgz +0 -0
package/android/build.gradle
CHANGED
|
@@ -10,7 +10,7 @@ buildscript {
|
|
|
10
10
|
}
|
|
11
11
|
|
|
12
12
|
group = 'host.exp.exponent'
|
|
13
|
-
version = '55.0.
|
|
13
|
+
version = '55.0.29'
|
|
14
14
|
|
|
15
15
|
expoModule {
|
|
16
16
|
// We can't prebuild the module because it depends on the generated files.
|
|
@@ -21,7 +21,7 @@ android {
|
|
|
21
21
|
namespace "expo.core"
|
|
22
22
|
defaultConfig {
|
|
23
23
|
versionCode 1
|
|
24
|
-
versionName "55.0.
|
|
24
|
+
versionName "55.0.29"
|
|
25
25
|
consumerProguardFiles("proguard-rules.pro")
|
|
26
26
|
}
|
|
27
27
|
testOptions {
|
|
@@ -8,7 +8,9 @@ import expo.modules.kotlin.AppContext
|
|
|
8
8
|
import expo.modules.kotlin.sharedobjects.SharedObject
|
|
9
9
|
import kotlinx.coroutines.CoroutineScope
|
|
10
10
|
import kotlinx.coroutines.Dispatchers
|
|
11
|
+
import kotlinx.coroutines.NonCancellable
|
|
11
12
|
import kotlinx.coroutines.launch
|
|
13
|
+
import kotlinx.coroutines.withContext
|
|
12
14
|
import okhttp3.Call
|
|
13
15
|
import okhttp3.Callback
|
|
14
16
|
import okhttp3.Response
|
|
@@ -138,10 +140,17 @@ internal class NativeResponse(appContext: AppContext, private val coroutineScope
|
|
|
138
140
|
responseInit = createResponseInit(response)
|
|
139
141
|
state = ResponseState.RESPONSE_RECEIVED
|
|
140
142
|
|
|
141
|
-
coroutineScope.launch
|
|
143
|
+
coroutineScope.launch {
|
|
142
144
|
val stream = response.body?.source() ?: return@launch
|
|
143
|
-
|
|
144
|
-
|
|
145
|
+
try {
|
|
146
|
+
pumpResponseBodyStream(stream)
|
|
147
|
+
} finally {
|
|
148
|
+
// Close off the modules queue so the blocking call doesn't stall it.
|
|
149
|
+
// NonCancellable guarantees the response is released even if the scope is canceled mid-stream.
|
|
150
|
+
withContext(NonCancellable + Dispatchers.IO) {
|
|
151
|
+
response.close()
|
|
152
|
+
}
|
|
153
|
+
}
|
|
145
154
|
|
|
146
155
|
if (this@NativeResponse.state == ResponseState.BODY_STREAMING_STARTED) {
|
|
147
156
|
emit("didComplete")
|
|
@@ -182,9 +191,13 @@ internal class NativeResponse(appContext: AppContext, private val coroutineScope
|
|
|
182
191
|
)
|
|
183
192
|
}
|
|
184
193
|
|
|
185
|
-
private fun pumpResponseBodyStream(stream: BufferedSource) {
|
|
194
|
+
private suspend fun pumpResponseBodyStream(stream: BufferedSource) {
|
|
186
195
|
try {
|
|
187
|
-
while (
|
|
196
|
+
while (true) {
|
|
197
|
+
val data = withContext(Dispatchers.IO) {
|
|
198
|
+
if (stream.exhausted()) null else stream.buffer.readByteArray()
|
|
199
|
+
} ?: break
|
|
200
|
+
|
|
188
201
|
if (isInvalidState(
|
|
189
202
|
ResponseState.RESPONSE_RECEIVED,
|
|
190
203
|
ResponseState.BODY_STREAMING_STARTED,
|
|
@@ -194,9 +207,9 @@ internal class NativeResponse(appContext: AppContext, private val coroutineScope
|
|
|
194
207
|
break
|
|
195
208
|
}
|
|
196
209
|
if (state == ResponseState.RESPONSE_RECEIVED) {
|
|
197
|
-
sink.appendBufferBody(
|
|
210
|
+
sink.appendBufferBody(data)
|
|
198
211
|
} else if (state == ResponseState.BODY_STREAMING_STARTED) {
|
|
199
|
-
emit("didReceiveResponseData",
|
|
212
|
+
emit("didReceiveResponseData", data)
|
|
200
213
|
} else {
|
|
201
214
|
break
|
|
202
215
|
}
|
|
@@ -1,10 +1,5 @@
|
|
|
1
1
|
export declare class TextDecoder {
|
|
2
|
-
private
|
|
3
|
-
private _ignoreBOM;
|
|
4
|
-
private _errorMode;
|
|
5
|
-
private _BOMseen;
|
|
6
|
-
private _doNotFlush;
|
|
7
|
-
private _decoder;
|
|
2
|
+
private readonly _state;
|
|
8
3
|
constructor(label?: string, options?: {
|
|
9
4
|
fatal?: boolean;
|
|
10
5
|
ignoreBOM?: boolean;
|
|
@@ -12,9 +7,8 @@ export declare class TextDecoder {
|
|
|
12
7
|
get encoding(): string;
|
|
13
8
|
get fatal(): boolean;
|
|
14
9
|
get ignoreBOM(): boolean;
|
|
15
|
-
decode(input?: ArrayBuffer |
|
|
10
|
+
decode(input?: ArrayBuffer | ArrayBufferView, options?: {
|
|
16
11
|
stream?: boolean;
|
|
17
12
|
}): string;
|
|
18
|
-
private serializeStream;
|
|
19
13
|
}
|
|
20
14
|
//# sourceMappingURL=TextDecoder.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TextDecoder.d.ts","sourceRoot":"","sources":["../../src/winter/TextDecoder.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"TextDecoder.d.ts","sourceRoot":"","sources":["../../src/winter/TextDecoder.ts"],"names":[],"mappings":"AAqXA,qBAAa,WAAW;IACtB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAmB;gBAE9B,KAAK,GAAE,MAAgB,EAAE,OAAO,GAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAC;QAAC,SAAS,CAAC,EAAE,OAAO,CAAA;KAAO;IAgB3F,IAAI,QAAQ,IAAI,MAAM,CAErB;IAED,IAAI,KAAK,IAAI,OAAO,CAEnB;IAED,IAAI,SAAS,IAAI,OAAO,CAEvB;IAED,MAAM,CAAC,KAAK,CAAC,EAAE,WAAW,GAAG,eAAe,EAAE,OAAO,GAAE;QAAE,MAAM,CAAC,EAAE,OAAO,CAAA;KAAO,GAAG,MAAM;CAsB1F"}
|
|
@@ -16,84 +16,84 @@
|
|
|
16
16
|
"expo-analytics-amplitude": "~11.3.0",
|
|
17
17
|
"expo-app-auth": "~11.1.0",
|
|
18
18
|
"expo-app-loader-provider": "~8.0.0",
|
|
19
|
-
"expo-app-metrics": "~0.2.
|
|
20
|
-
"expo-apple-authentication": "~55.0.
|
|
21
|
-
"expo-application": "~55.0.
|
|
22
|
-
"expo-asset": "~55.0.
|
|
23
|
-
"expo-audio": "~55.0.
|
|
24
|
-
"expo-auth-session": "~55.0.
|
|
25
|
-
"expo-background-fetch": "~55.0.
|
|
26
|
-
"expo-background-task": "~55.0.
|
|
27
|
-
"expo-battery": "~55.0.
|
|
28
|
-
"expo-blur": "~55.0.
|
|
29
|
-
"expo-brightness": "~55.0.
|
|
30
|
-
"expo-brownfield": "~55.0.
|
|
31
|
-
"expo-build-properties": "~55.0.
|
|
32
|
-
"expo-calendar": "~55.0.
|
|
33
|
-
"expo-camera": "~55.0.
|
|
34
|
-
"expo-cellular": "~55.0.
|
|
19
|
+
"expo-app-metrics": "~0.2.5",
|
|
20
|
+
"expo-apple-authentication": "~55.0.16",
|
|
21
|
+
"expo-application": "~55.0.18",
|
|
22
|
+
"expo-asset": "~55.0.19",
|
|
23
|
+
"expo-audio": "~55.0.17",
|
|
24
|
+
"expo-auth-session": "~55.0.18",
|
|
25
|
+
"expo-background-fetch": "~55.0.19",
|
|
26
|
+
"expo-background-task": "~55.0.21",
|
|
27
|
+
"expo-battery": "~55.0.16",
|
|
28
|
+
"expo-blur": "~55.0.17",
|
|
29
|
+
"expo-brightness": "~55.0.16",
|
|
30
|
+
"expo-brownfield": "~55.0.27",
|
|
31
|
+
"expo-build-properties": "~55.0.17",
|
|
32
|
+
"expo-calendar": "~55.0.18",
|
|
33
|
+
"expo-camera": "~55.0.22",
|
|
34
|
+
"expo-cellular": "~55.0.16",
|
|
35
35
|
"expo-checkbox": "~55.0.4",
|
|
36
|
-
"expo-clipboard": "~55.0.
|
|
36
|
+
"expo-clipboard": "~55.0.16",
|
|
37
37
|
"expo-constants": "~55.0.17",
|
|
38
|
-
"expo-contacts": "~55.0.
|
|
39
|
-
"expo-crypto": "~55.0.
|
|
40
|
-
"expo-dev-client": "~55.0.
|
|
41
|
-
"expo-device": "~55.0.
|
|
42
|
-
"expo-document-picker": "~55.0.
|
|
43
|
-
"expo-file-system": "~55.0.
|
|
38
|
+
"expo-contacts": "~55.0.17",
|
|
39
|
+
"expo-crypto": "~55.0.18",
|
|
40
|
+
"expo-dev-client": "~55.0.38",
|
|
41
|
+
"expo-device": "~55.0.20",
|
|
42
|
+
"expo-document-picker": "~55.0.16",
|
|
43
|
+
"expo-file-system": "~55.0.25",
|
|
44
44
|
"expo-font": "~55.0.8",
|
|
45
|
-
"expo-gl": "~55.0.
|
|
45
|
+
"expo-gl": "~55.0.17",
|
|
46
46
|
"expo-glass-effect": "~55.0.11",
|
|
47
47
|
"expo-google-app-auth": "~8.3.0",
|
|
48
|
-
"expo-haptics": "~55.0.
|
|
48
|
+
"expo-haptics": "~55.0.17",
|
|
49
49
|
"expo-image": "~55.0.11",
|
|
50
50
|
"expo-image-loader": "~55.0.1",
|
|
51
|
-
"expo-image-manipulator": "~55.0.
|
|
52
|
-
"expo-image-picker": "~55.0.
|
|
53
|
-
"expo-intent-launcher": "~55.0.
|
|
54
|
-
"expo-insights": "~55.0.
|
|
51
|
+
"expo-image-manipulator": "~55.0.20",
|
|
52
|
+
"expo-image-picker": "~55.0.23",
|
|
53
|
+
"expo-intent-launcher": "~55.0.15",
|
|
54
|
+
"expo-insights": "~55.0.20",
|
|
55
55
|
"expo-keep-awake": "~55.0.8",
|
|
56
|
-
"expo-linear-gradient": "~55.0.
|
|
57
|
-
"expo-linking": "~55.0.
|
|
58
|
-
"expo-local-authentication": "~55.0.
|
|
59
|
-
"expo-localization": "~55.0.
|
|
60
|
-
"expo-location": "~55.1.
|
|
61
|
-
"expo-mail-composer": "~55.0.
|
|
62
|
-
"expo-manifests": "~55.0.
|
|
63
|
-
"expo-maps": "~55.0.
|
|
56
|
+
"expo-linear-gradient": "~55.0.17",
|
|
57
|
+
"expo-linking": "~55.0.17",
|
|
58
|
+
"expo-local-authentication": "~55.0.17",
|
|
59
|
+
"expo-localization": "~55.0.18",
|
|
60
|
+
"expo-location": "~55.1.13",
|
|
61
|
+
"expo-mail-composer": "~55.0.17",
|
|
62
|
+
"expo-manifests": "~55.0.20",
|
|
63
|
+
"expo-maps": "~55.0.22",
|
|
64
64
|
"expo-mcp": "~0.2.1",
|
|
65
|
-
"expo-media-library": "~55.0.
|
|
66
|
-
"expo-mesh-gradient": "~55.0.
|
|
67
|
-
"expo-module-template": "~55.0.
|
|
65
|
+
"expo-media-library": "~55.0.20",
|
|
66
|
+
"expo-mesh-gradient": "~55.0.17",
|
|
67
|
+
"expo-module-template": "~55.0.24",
|
|
68
68
|
"expo-modules-core": "~55.0.25",
|
|
69
|
-
"expo-navigation-bar": "~55.0.
|
|
70
|
-
"expo-network": "~55.0.
|
|
71
|
-
"expo-notifications": "~55.0.
|
|
72
|
-
"expo-observe": "~0.2.
|
|
73
|
-
"expo-print": "~55.0.
|
|
74
|
-
"expo-live-photo": "~55.0.
|
|
75
|
-
"expo-router": "~55.0.
|
|
76
|
-
"expo-screen-capture": "~55.0.
|
|
77
|
-
"expo-screen-orientation": "~55.0.
|
|
78
|
-
"expo-secure-store": "~55.0.
|
|
79
|
-
"expo-sensors": "~55.0.
|
|
80
|
-
"expo-server": "~55.0.
|
|
81
|
-
"expo-sharing": "~55.0.
|
|
82
|
-
"expo-sms": "~55.0.
|
|
83
|
-
"expo-speech": "~55.0.
|
|
84
|
-
"expo-splash-screen": "~55.0.
|
|
85
|
-
"expo-sqlite": "~55.0.
|
|
69
|
+
"expo-navigation-bar": "~55.0.16",
|
|
70
|
+
"expo-network": "~55.0.17",
|
|
71
|
+
"expo-notifications": "~55.0.26",
|
|
72
|
+
"expo-observe": "~0.2.5",
|
|
73
|
+
"expo-print": "~55.0.18",
|
|
74
|
+
"expo-live-photo": "~55.0.18",
|
|
75
|
+
"expo-router": "~55.0.18",
|
|
76
|
+
"expo-screen-capture": "~55.0.17",
|
|
77
|
+
"expo-screen-orientation": "~55.0.19",
|
|
78
|
+
"expo-secure-store": "~55.0.17",
|
|
79
|
+
"expo-sensors": "~55.0.18",
|
|
80
|
+
"expo-server": "~55.0.12",
|
|
81
|
+
"expo-sharing": "~55.0.23",
|
|
82
|
+
"expo-sms": "~55.0.17",
|
|
83
|
+
"expo-speech": "~55.0.17",
|
|
84
|
+
"expo-splash-screen": "~55.0.24",
|
|
85
|
+
"expo-sqlite": "~55.0.19",
|
|
86
86
|
"expo-status-bar": "~55.0.6",
|
|
87
|
-
"expo-store-review": "~55.0.
|
|
87
|
+
"expo-store-review": "~55.0.17",
|
|
88
88
|
"expo-symbols": "~55.0.9",
|
|
89
|
-
"expo-system-ui": "~55.0.
|
|
90
|
-
"expo-task-manager": "~55.0.
|
|
91
|
-
"expo-tracking-transparency": "~55.0.
|
|
92
|
-
"expo-updates": "~55.0.
|
|
93
|
-
"expo-video-thumbnails": "~55.0.
|
|
94
|
-
"expo-video": "~55.0.
|
|
95
|
-
"expo-web-browser": "~55.0.
|
|
96
|
-
"jest-expo": "~55.0.
|
|
89
|
+
"expo-system-ui": "~55.0.21",
|
|
90
|
+
"expo-task-manager": "~55.0.19",
|
|
91
|
+
"expo-tracking-transparency": "~55.0.17",
|
|
92
|
+
"expo-updates": "~55.0.27",
|
|
93
|
+
"expo-video-thumbnails": "~55.0.18",
|
|
94
|
+
"expo-video": "~55.0.20",
|
|
95
|
+
"expo-web-browser": "~55.0.19",
|
|
96
|
+
"jest-expo": "~55.0.21",
|
|
97
97
|
"lottie-react-native": "~7.3.4",
|
|
98
98
|
"react": "19.2.0",
|
|
99
99
|
"react-dom": "19.2.0",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "expo",
|
|
3
|
-
"version": "55.0.
|
|
3
|
+
"version": "55.0.29",
|
|
4
4
|
"description": "The Expo SDK",
|
|
5
5
|
"main": "src/Expo.ts",
|
|
6
6
|
"module": "src/Expo.ts",
|
|
@@ -77,24 +77,24 @@
|
|
|
77
77
|
"homepage": "https://github.com/expo/expo/tree/main/packages/expo",
|
|
78
78
|
"dependencies": {
|
|
79
79
|
"@babel/runtime": "^7.20.0",
|
|
80
|
-
"@expo/cli": "55.0.
|
|
81
|
-
"@expo/config": "~55.0.
|
|
80
|
+
"@expo/cli": "55.0.35",
|
|
81
|
+
"@expo/config": "~55.0.20",
|
|
82
82
|
"@expo/config-plugins": "~55.0.11",
|
|
83
83
|
"@expo/devtools": "55.0.3",
|
|
84
84
|
"@expo/fingerprint": "0.16.8",
|
|
85
85
|
"@expo/local-build-cache-provider": "55.0.15",
|
|
86
86
|
"@expo/log-box": "55.0.13",
|
|
87
87
|
"@expo/metro": "~55.1.1",
|
|
88
|
-
"@expo/metro-config": "55.0.
|
|
88
|
+
"@expo/metro-config": "55.0.26",
|
|
89
89
|
"@expo/vector-icons": "^15.0.2",
|
|
90
90
|
"@ungap/structured-clone": "^1.3.0",
|
|
91
91
|
"babel-preset-expo": "~55.0.24",
|
|
92
|
-
"expo-asset": "~55.0.
|
|
92
|
+
"expo-asset": "~55.0.19",
|
|
93
93
|
"expo-constants": "~55.0.17",
|
|
94
|
-
"expo-file-system": "~55.0.
|
|
94
|
+
"expo-file-system": "~55.0.25",
|
|
95
95
|
"expo-font": "~55.0.8",
|
|
96
96
|
"expo-keep-awake": "~55.0.8",
|
|
97
|
-
"expo-modules-autolinking": "55.0.
|
|
97
|
+
"expo-modules-autolinking": "55.0.26",
|
|
98
98
|
"expo-modules-core": "55.0.25",
|
|
99
99
|
"pretty-format": "^29.7.0",
|
|
100
100
|
"react-refresh": "^0.14.2",
|
|
@@ -130,5 +130,5 @@
|
|
|
130
130
|
"optional": true
|
|
131
131
|
}
|
|
132
132
|
},
|
|
133
|
-
"gitHead": "
|
|
133
|
+
"gitHead": "764641f87074c1e49d004790c5a5983f4b5bfc6b"
|
|
134
134
|
}
|
|
@@ -1,429 +1,427 @@
|
|
|
1
|
-
//
|
|
2
|
-
// `TextEncoder` is in Hermes and we only need utf-8 decoder for React Server Components.
|
|
3
|
-
//
|
|
4
|
-
// https://github.com/inexorabletash/text-encoding/blob/3f330964c0e97e1ed344c2a3e963f4598610a7ad/lib/encoding.js#L1
|
|
1
|
+
// UTF-8-only TextDecoder fallback for runtimes that do not provide one.
|
|
5
2
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
return min <= a && a <= max;
|
|
3
|
+
const EMPTY_BYTES = new Uint8Array(0);
|
|
4
|
+
|
|
5
|
+
interface TextDecoderState {
|
|
6
|
+
ignoreBOM: boolean;
|
|
7
|
+
fatal: boolean;
|
|
8
|
+
bomSeen: boolean;
|
|
9
|
+
pending: Uint8Array;
|
|
10
|
+
streaming: boolean;
|
|
15
11
|
}
|
|
16
12
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
cp -= 0x10000;
|
|
30
|
-
s += String.fromCharCode((cp >> 10) + 0xd800, (cp & 0x3ff) + 0xdc00);
|
|
31
|
-
}
|
|
13
|
+
function assertValidUTF8Label(label: unknown): void {
|
|
14
|
+
const normalizedLabel = String(label).trim().toLowerCase();
|
|
15
|
+
switch (normalizedLabel) {
|
|
16
|
+
case 'unicode-1-1-utf-8':
|
|
17
|
+
case 'unicode11utf8':
|
|
18
|
+
case 'unicode20utf8':
|
|
19
|
+
case 'utf-8':
|
|
20
|
+
case 'utf8':
|
|
21
|
+
case 'x-unicode20utf8':
|
|
22
|
+
return;
|
|
23
|
+
default:
|
|
24
|
+
throw new RangeError(`Unknown encoding: ${label} (normalized: ${normalizedLabel})`);
|
|
32
25
|
}
|
|
33
|
-
return s;
|
|
34
26
|
}
|
|
35
27
|
|
|
36
|
-
function normalizeBytes(input
|
|
37
|
-
if (
|
|
38
|
-
return
|
|
28
|
+
function normalizeBytes(input: ArrayBuffer | ArrayBufferView | undefined): Uint8Array {
|
|
29
|
+
if (input === undefined) {
|
|
30
|
+
return EMPTY_BYTES;
|
|
31
|
+
} else if (input instanceof Uint8Array) {
|
|
32
|
+
return input;
|
|
33
|
+
} else if (ArrayBuffer.isView(input)) {
|
|
34
|
+
return new Uint8Array(input.buffer, input.byteOffset, input.byteLength);
|
|
39
35
|
} else if (
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
input.buffer instanceof ArrayBuffer
|
|
36
|
+
(input[Symbol.toStringTag] as string) === 'ArrayBuffer' ||
|
|
37
|
+
(input[Symbol.toStringTag] as string) === 'SharedArrayBuffer'
|
|
43
38
|
) {
|
|
44
|
-
return new Uint8Array(input
|
|
39
|
+
return new Uint8Array(input as ArrayBuffer);
|
|
40
|
+
} else {
|
|
41
|
+
throw new TypeError('The input must be an ArrayBuffer or ArrayBufferView');
|
|
45
42
|
}
|
|
46
|
-
return new Uint8Array(0);
|
|
47
43
|
}
|
|
48
44
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
*/
|
|
53
|
-
const END_OF_STREAM = -1;
|
|
54
|
-
|
|
55
|
-
const FINISHED = -1;
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* A stream represents an ordered sequence of tokens.
|
|
59
|
-
*
|
|
60
|
-
* @constructor
|
|
61
|
-
* @param {!(number[]|Uint8Array)} tokens Array of tokens that provide the stream.
|
|
62
|
-
*/
|
|
63
|
-
class Stream {
|
|
64
|
-
private tokens: number[];
|
|
65
|
-
|
|
66
|
-
constructor(tokens: number[] | Uint8Array) {
|
|
67
|
-
this.tokens = Array.prototype.slice.call(tokens);
|
|
68
|
-
// Reversed as push/pop is more efficient than shift/unshift.
|
|
69
|
-
this.tokens.reverse();
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
* @return {boolean} True if end-of-stream has been hit.
|
|
74
|
-
*/
|
|
75
|
-
endOfStream(): boolean {
|
|
76
|
-
return !this.tokens.length;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
* When a token is read from a stream, the first token in the
|
|
81
|
-
* stream must be returned and subsequently removed, and
|
|
82
|
-
* end-of-stream must be returned otherwise.
|
|
83
|
-
*
|
|
84
|
-
* @return {number} Get the next token from the stream, or
|
|
85
|
-
* end_of_stream.
|
|
86
|
-
*/
|
|
87
|
-
read(): number {
|
|
88
|
-
if (!this.tokens.length) return END_OF_STREAM;
|
|
89
|
-
return this.tokens.pop()!;
|
|
45
|
+
function decoderError(state: TextDecoderState): string {
|
|
46
|
+
if (state.fatal) {
|
|
47
|
+
throw new TypeError('Decoder error');
|
|
90
48
|
}
|
|
49
|
+
return '\ufffd';
|
|
50
|
+
}
|
|
91
51
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
/**
|
|
108
|
-
* When one or more tokens are pushed to a stream, those tokens
|
|
109
|
-
* must be inserted, in given order, after the last token in the
|
|
110
|
-
* stream.
|
|
111
|
-
*
|
|
112
|
-
* @param token The tokens(s) to push to the stream.
|
|
113
|
-
*/
|
|
114
|
-
push(token: number | number[]): void {
|
|
115
|
-
if (Array.isArray(token)) {
|
|
116
|
-
while (token.length) this.tokens.unshift(token.shift()!);
|
|
117
|
-
} else {
|
|
118
|
-
this.tokens.unshift(token);
|
|
119
|
-
}
|
|
52
|
+
function appendASCII(output: string, bytes: Uint8Array, start: number, end: number): string {
|
|
53
|
+
// NOTE(@kitten): For longer strings, direct `apply` + `subarray` conversion w/o byte-for-byte copy or spreads is
|
|
54
|
+
// the most efficient by far (3x), leaning on native conversion below the variadic limit
|
|
55
|
+
const HERMES_VARIADIC_ARGUMENT_LIMIT = 4096;
|
|
56
|
+
while (start < end) {
|
|
57
|
+
output += String.fromCharCode.apply(
|
|
58
|
+
null,
|
|
59
|
+
bytes.subarray(
|
|
60
|
+
start,
|
|
61
|
+
Math.min(start + HERMES_VARIADIC_ARGUMENT_LIMIT, end)
|
|
62
|
+
) as unknown as number[]
|
|
63
|
+
);
|
|
64
|
+
start += HERMES_VARIADIC_ARGUMENT_LIMIT;
|
|
120
65
|
}
|
|
66
|
+
return output;
|
|
121
67
|
}
|
|
122
68
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
69
|
+
interface DecodeFallback {
|
|
70
|
+
output: string;
|
|
71
|
+
index: number;
|
|
126
72
|
}
|
|
127
73
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
labels: string[];
|
|
74
|
+
function fallback(output: string, index: number): DecodeFallback {
|
|
75
|
+
return { output, index };
|
|
131
76
|
}
|
|
132
77
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
78
|
+
function decodeUTF8Fast(bytes: Uint8Array, start: number): string | DecodeFallback {
|
|
79
|
+
let output = '';
|
|
80
|
+
let i = start;
|
|
81
|
+
const length = bytes.length;
|
|
82
|
+
while (i < length) {
|
|
83
|
+
const b0 = bytes[i]!;
|
|
84
|
+
if (b0 < 0x80) {
|
|
85
|
+
// NOTE(@kitten): For ASCII text, it's fastest to process the first 32 chars directly
|
|
86
|
+
// After, `appendASCII`'s variadic apply wins out
|
|
87
|
+
const directEnd = Math.min(i + 32, length);
|
|
88
|
+
do {
|
|
89
|
+
output += String.fromCharCode(bytes[i++]!);
|
|
90
|
+
} while (i < directEnd && bytes[i]! < 0x80);
|
|
91
|
+
if (i === directEnd && i < length && bytes[i]! < 0x80) {
|
|
92
|
+
const asciiStart = i;
|
|
93
|
+
do {
|
|
94
|
+
i++;
|
|
95
|
+
} while (i < length && bytes[i]! < 0x80);
|
|
96
|
+
output = appendASCII(output, bytes, asciiStart, i);
|
|
97
|
+
}
|
|
98
|
+
} else if (b0 >= 0xc2 && b0 <= 0xdf) {
|
|
99
|
+
if (i + 1 >= length) return fallback(output, i);
|
|
100
|
+
const b1 = bytes[i + 1]!;
|
|
101
|
+
if ((b1 & 0xc0) !== 0x80) return fallback(output, i);
|
|
102
|
+
output += String.fromCharCode(((b0 & 0x1f) << 6) | (b1 & 0x3f));
|
|
103
|
+
i += 2;
|
|
104
|
+
} else if (b0 >= 0xe0 && b0 <= 0xef) {
|
|
105
|
+
if (i + 2 >= length) return fallback(output, i);
|
|
106
|
+
const b1 = bytes[i + 1]!;
|
|
107
|
+
const b2 = bytes[i + 2]!;
|
|
108
|
+
if (
|
|
109
|
+
b1 < (b0 === 0xe0 ? 0xa0 : 0x80) ||
|
|
110
|
+
b1 > (b0 === 0xed ? 0x9f : 0xbf) ||
|
|
111
|
+
(b2 & 0xc0) !== 0x80
|
|
112
|
+
) {
|
|
113
|
+
return fallback(output, i);
|
|
114
|
+
}
|
|
115
|
+
output += String.fromCharCode(((b0 & 0x0f) << 12) | ((b1 & 0x3f) << 6) | (b2 & 0x3f));
|
|
116
|
+
i += 3;
|
|
117
|
+
} else if (b0 >= 0xf0 && b0 <= 0xf4) {
|
|
118
|
+
if (i + 3 >= length) return fallback(output, i);
|
|
119
|
+
const b1 = bytes[i + 1]!;
|
|
120
|
+
const b2 = bytes[i + 2]!;
|
|
121
|
+
const b3 = bytes[i + 3]!;
|
|
122
|
+
if (
|
|
123
|
+
b1 < (b0 === 0xf0 ? 0x90 : 0x80) ||
|
|
124
|
+
b1 > (b0 === 0xf4 ? 0x8f : 0xbf) ||
|
|
125
|
+
(b2 & 0xc0) !== 0x80 ||
|
|
126
|
+
(b3 & 0xc0) !== 0x80
|
|
127
|
+
) {
|
|
128
|
+
return fallback(output, i);
|
|
129
|
+
}
|
|
130
|
+
const codePoint =
|
|
131
|
+
(((b0 & 0x07) << 18) | ((b1 & 0x3f) << 12) | ((b2 & 0x3f) << 6) | (b3 & 0x3f)) - 0x10000;
|
|
132
|
+
output += String.fromCharCode((codePoint >> 10) + 0xd800, (codePoint & 0x3ff) + 0xdc00);
|
|
133
|
+
i += 4;
|
|
134
|
+
} else {
|
|
135
|
+
return fallback(output, i);
|
|
136
|
+
}
|
|
139
137
|
}
|
|
140
|
-
return null;
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
/** [Encodings table](https://encoding.spec.whatwg.org/encodings.json) (Incomplete as we only need TextDecoder utf8 in Expo RSC. A more complete implementation should be added to Hermes as native code.) */
|
|
144
|
-
const ENCODING_MAP: { heading: string; encodings: Encoding[] }[] = [
|
|
145
|
-
{
|
|
146
|
-
encodings: [
|
|
147
|
-
{
|
|
148
|
-
labels: [
|
|
149
|
-
'unicode-1-1-utf-8',
|
|
150
|
-
'unicode11utf8',
|
|
151
|
-
'unicode20utf8',
|
|
152
|
-
'utf-8',
|
|
153
|
-
'utf8',
|
|
154
|
-
'x-unicode20utf8',
|
|
155
|
-
],
|
|
156
|
-
name: 'UTF-8',
|
|
157
|
-
},
|
|
158
|
-
],
|
|
159
|
-
heading: 'The Encoding',
|
|
160
|
-
},
|
|
161
|
-
];
|
|
162
138
|
|
|
163
|
-
|
|
164
|
-
category.encodings.forEach((encoding) => {
|
|
165
|
-
encoding.labels.forEach((label) => {
|
|
166
|
-
LABEL_ENCODING_MAP[label] = encoding;
|
|
167
|
-
});
|
|
168
|
-
});
|
|
169
|
-
});
|
|
170
|
-
|
|
171
|
-
// Registry of of encoder/decoder factories, by encoding name.
|
|
172
|
-
const DECODERS: { [key: string]: (options: { fatal: boolean }) => UTF8Decoder } = {
|
|
173
|
-
'UTF-8': (options) => new UTF8Decoder(options),
|
|
174
|
-
};
|
|
175
|
-
|
|
176
|
-
// 9.1.1 utf-8 decoder
|
|
177
|
-
|
|
178
|
-
interface Decoder {
|
|
179
|
-
handler: (stream: Stream, bite: number) => number | number[] | null | -1;
|
|
139
|
+
return output;
|
|
180
140
|
}
|
|
181
141
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
if (bite === END_OF_STREAM && this.utf8BytesNeeded !== 0) {
|
|
204
|
-
this.utf8BytesNeeded = 0;
|
|
205
|
-
return decoderError(this.options.fatal);
|
|
142
|
+
function decodeUTF8General(
|
|
143
|
+
bytes: Uint8Array,
|
|
144
|
+
state: TextDecoderState,
|
|
145
|
+
stream: boolean,
|
|
146
|
+
output = '',
|
|
147
|
+
start = 0
|
|
148
|
+
): string {
|
|
149
|
+
const { ignoreBOM } = state;
|
|
150
|
+
let { pending, bomSeen } = state;
|
|
151
|
+
let i = start;
|
|
152
|
+
const length = bytes.length;
|
|
153
|
+
|
|
154
|
+
if (pending.length > 0) {
|
|
155
|
+
const b0 = pending[0]!;
|
|
156
|
+
const bytesNeeded = b0 <= 0xdf ? 1 : b0 <= 0xef ? 2 : 3;
|
|
157
|
+
let codePoint = b0 & (bytesNeeded === 1 ? 0x1f : bytesNeeded === 2 ? 0x0f : 0x07);
|
|
158
|
+
let bytesSeen = 0;
|
|
159
|
+
|
|
160
|
+
for (let p = 1; p < pending.length; p++) {
|
|
161
|
+
codePoint = (codePoint << 6) | (pending[p]! & 0x3f);
|
|
162
|
+
bytesSeen++;
|
|
206
163
|
}
|
|
207
164
|
|
|
208
|
-
|
|
209
|
-
|
|
165
|
+
while (bytesSeen < bytesNeeded && i < length) {
|
|
166
|
+
const byte = bytes[i]!;
|
|
167
|
+
if (
|
|
168
|
+
(byte & 0xc0) !== 0x80 ||
|
|
169
|
+
(bytesSeen === 0 &&
|
|
170
|
+
((b0 === 0xe0 && byte < 0xa0) ||
|
|
171
|
+
(b0 === 0xed && byte > 0x9f) ||
|
|
172
|
+
(b0 === 0xf0 && byte < 0x90) ||
|
|
173
|
+
(b0 === 0xf4 && byte > 0x8f)))
|
|
174
|
+
) {
|
|
175
|
+
pending = EMPTY_BYTES;
|
|
176
|
+
output += decoderError(state);
|
|
177
|
+
bomSeen = true;
|
|
178
|
+
break;
|
|
179
|
+
}
|
|
180
|
+
codePoint = (codePoint << 6) | (byte & 0x3f);
|
|
181
|
+
bytesSeen++;
|
|
182
|
+
i++;
|
|
183
|
+
}
|
|
210
184
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
185
|
+
if (bytesSeen === bytesNeeded) {
|
|
186
|
+
pending = EMPTY_BYTES;
|
|
187
|
+
if (bomSeen || ignoreBOM || codePoint !== 0xfeff) {
|
|
188
|
+
if (codePoint <= 0xffff) {
|
|
189
|
+
output += String.fromCharCode(codePoint);
|
|
190
|
+
} else {
|
|
191
|
+
codePoint -= 0x10000;
|
|
192
|
+
output += String.fromCharCode((codePoint >> 10) + 0xd800, (codePoint & 0x3ff) + 0xdc00);
|
|
193
|
+
}
|
|
217
194
|
}
|
|
195
|
+
bomSeen = true;
|
|
196
|
+
} else if (i === length) {
|
|
197
|
+
if (stream && i > 0) {
|
|
198
|
+
const nextPending = new Uint8Array(pending.length + i);
|
|
199
|
+
nextPending.set(pending);
|
|
200
|
+
nextPending.set(bytes.subarray(0, i), pending.length);
|
|
201
|
+
pending = nextPending;
|
|
202
|
+
} else if (!stream) {
|
|
203
|
+
pending = EMPTY_BYTES;
|
|
204
|
+
output += decoderError(state);
|
|
205
|
+
bomSeen = true;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
218
208
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
209
|
+
if (pending.length > 0) {
|
|
210
|
+
state.pending = pending;
|
|
211
|
+
state.bomSeen = bomSeen;
|
|
212
|
+
return output;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
223
215
|
|
|
224
|
-
|
|
225
|
-
|
|
216
|
+
while (i < length) {
|
|
217
|
+
const b0 = bytes[i]!;
|
|
218
|
+
if (b0 < 0x80) {
|
|
219
|
+
bomSeen = true;
|
|
220
|
+
const directEnd = Math.min(i + 32, length);
|
|
221
|
+
do {
|
|
222
|
+
output += String.fromCharCode(bytes[i++]!);
|
|
223
|
+
} while (i < directEnd && bytes[i]! < 0x80);
|
|
224
|
+
if (i === directEnd && i < length && bytes[i]! < 0x80) {
|
|
225
|
+
const asciiStart = i;
|
|
226
|
+
do {
|
|
227
|
+
i++;
|
|
228
|
+
} while (i < length && bytes[i]! < 0x80);
|
|
229
|
+
output = appendASCII(output, bytes, asciiStart, i);
|
|
226
230
|
}
|
|
231
|
+
continue;
|
|
232
|
+
}
|
|
227
233
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
234
|
+
let codePoint: number;
|
|
235
|
+
const remaining = length - i;
|
|
236
|
+
if (b0 >= 0xc2 && b0 <= 0xdf) {
|
|
237
|
+
if (remaining < 2) {
|
|
238
|
+
if (stream) {
|
|
239
|
+
pending = bytes.slice(i);
|
|
240
|
+
} else {
|
|
241
|
+
output += decoderError(state);
|
|
242
|
+
}
|
|
243
|
+
break;
|
|
238
244
|
}
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
if (bite === 0xf4) this.utf8UpperBoundary = 0x8f;
|
|
246
|
-
// 3. Set utf-8 bytes needed to 3.
|
|
247
|
-
this.utf8BytesNeeded = 3;
|
|
248
|
-
// 4. Set UTF-8 code point to byte & 0x7.
|
|
249
|
-
this.utf8CodePoint = bite & 0x7;
|
|
245
|
+
const b1 = bytes[i + 1]!;
|
|
246
|
+
if ((b1 & 0xc0) !== 0x80) {
|
|
247
|
+
output += decoderError(state);
|
|
248
|
+
bomSeen = true;
|
|
249
|
+
i++;
|
|
250
|
+
continue;
|
|
250
251
|
}
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
252
|
+
codePoint = ((b0 & 0x1f) << 6) | (b1 & 0x3f);
|
|
253
|
+
i += 2;
|
|
254
|
+
} else if (b0 >= 0xe0 && b0 <= 0xef) {
|
|
255
|
+
if (remaining < 2) {
|
|
256
|
+
if (stream) {
|
|
257
|
+
pending = bytes.slice(i);
|
|
258
|
+
} else {
|
|
259
|
+
output += decoderError(state);
|
|
260
|
+
}
|
|
261
|
+
break;
|
|
256
262
|
}
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
263
|
+
const b1 = bytes[i + 1]!;
|
|
264
|
+
if ((b1 & 0xc0) !== 0x80 || (b0 === 0xe0 && b1 < 0xa0) || (b0 === 0xed && b1 > 0x9f)) {
|
|
265
|
+
output += decoderError(state);
|
|
266
|
+
bomSeen = true;
|
|
267
|
+
i++;
|
|
268
|
+
continue;
|
|
269
|
+
} else if (remaining < 3) {
|
|
270
|
+
if (stream) {
|
|
271
|
+
pending = bytes.slice(i);
|
|
272
|
+
} else {
|
|
273
|
+
output += decoderError(state);
|
|
274
|
+
}
|
|
275
|
+
break;
|
|
276
|
+
}
|
|
277
|
+
const b2 = bytes[i + 2]!;
|
|
278
|
+
if ((b2 & 0xc0) !== 0x80) {
|
|
279
|
+
output += decoderError(state);
|
|
280
|
+
bomSeen = true;
|
|
281
|
+
i += 2;
|
|
282
|
+
continue;
|
|
283
|
+
}
|
|
284
|
+
codePoint = ((b0 & 0x0f) << 12) | ((b1 & 0x3f) << 6) | (b2 & 0x3f);
|
|
285
|
+
i += 3;
|
|
286
|
+
} else if (b0 >= 0xf0 && b0 <= 0xf4) {
|
|
287
|
+
if (remaining < 2) {
|
|
288
|
+
if (stream) {
|
|
289
|
+
pending = bytes.slice(i);
|
|
290
|
+
} else {
|
|
291
|
+
output += decoderError(state);
|
|
292
|
+
}
|
|
293
|
+
break;
|
|
294
|
+
}
|
|
295
|
+
const b1 = bytes[i + 1]!;
|
|
296
|
+
if ((b1 & 0xc0) !== 0x80 || (b0 === 0xf0 && b1 < 0x90) || (b0 === 0xf4 && b1 > 0x8f)) {
|
|
297
|
+
output += decoderError(state);
|
|
298
|
+
bomSeen = true;
|
|
299
|
+
i++;
|
|
300
|
+
continue;
|
|
301
|
+
} else if (remaining < 3) {
|
|
302
|
+
if (stream) {
|
|
303
|
+
pending = bytes.slice(i);
|
|
304
|
+
} else {
|
|
305
|
+
output += decoderError(state);
|
|
306
|
+
}
|
|
307
|
+
break;
|
|
308
|
+
}
|
|
309
|
+
const b2 = bytes[i + 2]!;
|
|
310
|
+
if ((b2 & 0xc0) !== 0x80) {
|
|
311
|
+
output += decoderError(state);
|
|
312
|
+
bomSeen = true;
|
|
313
|
+
i += 2;
|
|
314
|
+
continue;
|
|
315
|
+
} else if (remaining < 4) {
|
|
316
|
+
if (stream) {
|
|
317
|
+
pending = bytes.slice(i);
|
|
318
|
+
} else {
|
|
319
|
+
output += decoderError(state);
|
|
320
|
+
}
|
|
321
|
+
break;
|
|
322
|
+
}
|
|
323
|
+
const b3 = bytes[i + 3]!;
|
|
324
|
+
if ((b3 & 0xc0) !== 0x80) {
|
|
325
|
+
output += decoderError(state);
|
|
326
|
+
bomSeen = true;
|
|
327
|
+
i += 3;
|
|
328
|
+
continue;
|
|
329
|
+
}
|
|
330
|
+
codePoint = ((b0 & 0x07) << 18) | ((b1 & 0x3f) << 12) | ((b2 & 0x3f) << 6) | (b3 & 0x3f);
|
|
331
|
+
i += 4;
|
|
332
|
+
} else {
|
|
333
|
+
output += decoderError(state);
|
|
334
|
+
bomSeen = true;
|
|
335
|
+
i++;
|
|
336
|
+
continue;
|
|
260
337
|
}
|
|
261
338
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
this.utf8BytesNeeded = 0;
|
|
270
|
-
this.utf8BytesSeen = 0;
|
|
271
|
-
this.utf8LowerBoundary = 0x80;
|
|
272
|
-
this.utf8UpperBoundary = 0xbf;
|
|
273
|
-
|
|
274
|
-
// 2. Prepend byte to stream.
|
|
275
|
-
stream.prepend(bite);
|
|
276
|
-
|
|
277
|
-
// 3. Return error.
|
|
278
|
-
return decoderError(this.options.fatal);
|
|
339
|
+
if (bomSeen || ignoreBOM || codePoint !== 0xfeff) {
|
|
340
|
+
if (codePoint <= 0xffff) {
|
|
341
|
+
output += String.fromCharCode(codePoint);
|
|
342
|
+
} else {
|
|
343
|
+
codePoint -= 0x10000;
|
|
344
|
+
output += String.fromCharCode((codePoint >> 10) + 0xd800, (codePoint & 0x3ff) + 0xdc00);
|
|
345
|
+
}
|
|
279
346
|
}
|
|
347
|
+
bomSeen = true;
|
|
348
|
+
}
|
|
280
349
|
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
// 6. Set UTF-8 code point to (UTF-8 code point << 6) | (byte &
|
|
287
|
-
// 0x3F)
|
|
288
|
-
this.utf8CodePoint = (this.utf8CodePoint << 6) | (bite & 0x3f);
|
|
289
|
-
|
|
290
|
-
// 7. Increase utf-8 bytes seen by one.
|
|
291
|
-
this.utf8BytesSeen += 1;
|
|
292
|
-
|
|
293
|
-
// 8. If utf-8 bytes seen is not equal to utf-8 bytes needed,
|
|
294
|
-
// continue.
|
|
295
|
-
if (this.utf8BytesSeen !== this.utf8BytesNeeded) return null;
|
|
296
|
-
|
|
297
|
-
// 9. Let code point be utf-8 code point.
|
|
298
|
-
const code_point = this.utf8CodePoint;
|
|
299
|
-
|
|
300
|
-
// 10. Set utf-8 code point, utf-8 bytes needed, and utf-8 bytes
|
|
301
|
-
// seen to 0.
|
|
302
|
-
this.utf8CodePoint = 0;
|
|
303
|
-
this.utf8BytesNeeded = 0;
|
|
304
|
-
this.utf8BytesSeen = 0;
|
|
350
|
+
state.pending = pending;
|
|
351
|
+
state.bomSeen = bomSeen;
|
|
352
|
+
return output;
|
|
353
|
+
}
|
|
305
354
|
|
|
306
|
-
|
|
307
|
-
|
|
355
|
+
function decodeUTF8(bytes: Uint8Array, state: TextDecoderState, stream: boolean): string {
|
|
356
|
+
if (state.pending.length === 0) {
|
|
357
|
+
if (!stream && !state.bomSeen) {
|
|
358
|
+
const skipBOM =
|
|
359
|
+
!state.ignoreBOM && bytes[0] === 0xef && bytes[1] === 0xbb && bytes[2] === 0xbf ? 3 : 0;
|
|
360
|
+
const result = decodeUTF8Fast(bytes, skipBOM);
|
|
361
|
+
if (typeof result === 'string') {
|
|
362
|
+
state.bomSeen = bytes.length > 0;
|
|
363
|
+
return result;
|
|
364
|
+
}
|
|
365
|
+
state.bomSeen = skipBOM > 0 || result.index > skipBOM;
|
|
366
|
+
return decodeUTF8General(bytes, state, false, result.output, result.index);
|
|
367
|
+
}
|
|
308
368
|
}
|
|
369
|
+
|
|
370
|
+
return decodeUTF8General(bytes, state, stream);
|
|
309
371
|
}
|
|
310
372
|
|
|
311
|
-
// 8.1 Interface TextDecoder
|
|
312
373
|
// @docsMissing
|
|
313
374
|
export class TextDecoder {
|
|
314
|
-
private
|
|
315
|
-
private _ignoreBOM: boolean;
|
|
316
|
-
private _errorMode: string;
|
|
317
|
-
private _BOMseen: boolean = false;
|
|
318
|
-
private _doNotFlush: boolean = false;
|
|
319
|
-
private _decoder: UTF8Decoder | null = null;
|
|
375
|
+
private readonly _state: TextDecoderState;
|
|
320
376
|
|
|
321
|
-
constructor(
|
|
322
|
-
|
|
323
|
-
options: {
|
|
324
|
-
fatal?: boolean;
|
|
325
|
-
ignoreBOM?: boolean;
|
|
326
|
-
} = {}
|
|
327
|
-
) {
|
|
328
|
-
if (options != null && typeof options !== 'object') {
|
|
377
|
+
constructor(label: string = 'utf-8', options: { fatal?: boolean; ignoreBOM?: boolean } = {}) {
|
|
378
|
+
if (options == null || (typeof options !== 'object' && typeof options !== 'function')) {
|
|
329
379
|
throw new TypeError(
|
|
330
380
|
'Second argument of TextDecoder must be undefined or an object, e.g. { fatal: true }'
|
|
331
381
|
);
|
|
332
382
|
}
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
throw new Error(`Decoder not present: ${encoding.name}`);
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
this._encoding = encoding;
|
|
345
|
-
this._ignoreBOM = !!options.ignoreBOM;
|
|
346
|
-
this._errorMode = options.fatal ? 'fatal' : 'replacement';
|
|
383
|
+
assertValidUTF8Label(label);
|
|
384
|
+
this._state = {
|
|
385
|
+
fatal: Boolean(options.fatal),
|
|
386
|
+
ignoreBOM: Boolean(options.ignoreBOM),
|
|
387
|
+
bomSeen: false,
|
|
388
|
+
pending: EMPTY_BYTES,
|
|
389
|
+
streaming: false,
|
|
390
|
+
};
|
|
347
391
|
}
|
|
348
392
|
|
|
349
|
-
// Getter methods for encoding, fatal, and ignoreBOM
|
|
350
393
|
get encoding(): string {
|
|
351
|
-
return
|
|
394
|
+
return 'utf-8';
|
|
352
395
|
}
|
|
353
396
|
|
|
354
397
|
get fatal(): boolean {
|
|
355
|
-
return this.
|
|
398
|
+
return this._state.fatal;
|
|
356
399
|
}
|
|
357
400
|
|
|
358
401
|
get ignoreBOM(): boolean {
|
|
359
|
-
return this.
|
|
402
|
+
return this._state.ignoreBOM;
|
|
360
403
|
}
|
|
361
404
|
|
|
362
|
-
decode(input?: ArrayBuffer |
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
// 1. If the do not flush flag is unset, set decoder to a new
|
|
366
|
-
// encoding's decoder, set stream to a new stream, and unset the
|
|
367
|
-
// BOM seen flag.
|
|
368
|
-
if (!this._doNotFlush) {
|
|
369
|
-
this._decoder = DECODERS[this._encoding!.name]({
|
|
370
|
-
fatal: this.fatal,
|
|
371
|
-
});
|
|
372
|
-
this._BOMseen = false;
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
// 2. If options's stream is true, set the do not flush flag, and
|
|
376
|
-
// unset the do not flush flag otherwise.
|
|
377
|
-
this._doNotFlush = Boolean(options['stream']);
|
|
378
|
-
|
|
379
|
-
// 3. If input is given, push a copy of input to stream.
|
|
380
|
-
// TODO: Align with spec algorithm - maintain stream on instance.
|
|
381
|
-
const input_stream = new Stream(bytes);
|
|
382
|
-
|
|
383
|
-
// 4. Let output be a new stream.
|
|
384
|
-
const output: number[] = [];
|
|
385
|
-
|
|
386
|
-
while (true) {
|
|
387
|
-
const token = input_stream.read();
|
|
388
|
-
|
|
389
|
-
if (token === END_OF_STREAM) break;
|
|
390
|
-
|
|
391
|
-
const result = this._decoder!.handler(input_stream, token);
|
|
392
|
-
|
|
393
|
-
if (result === FINISHED) break;
|
|
394
|
-
|
|
395
|
-
if (result !== null) {
|
|
396
|
-
output.push(result);
|
|
397
|
-
}
|
|
405
|
+
decode(input?: ArrayBuffer | ArrayBufferView, options: { stream?: boolean } = {}): string {
|
|
406
|
+
if (options == null || (typeof options !== 'object' && typeof options !== 'function')) {
|
|
407
|
+
throw new TypeError('The options argument must be undefined or an object');
|
|
398
408
|
}
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
if (Array.isArray(result)) output.push(...result);
|
|
406
|
-
else output.push(result);
|
|
407
|
-
} while (!input_stream.endOfStream());
|
|
408
|
-
this._decoder = null;
|
|
409
|
+
const bytes = normalizeBytes(input);
|
|
410
|
+
const stream = Boolean(options.stream);
|
|
411
|
+
const state = this._state;
|
|
412
|
+
if (!state.streaming) {
|
|
413
|
+
state.bomSeen = false;
|
|
414
|
+
state.pending = EMPTY_BYTES;
|
|
409
415
|
}
|
|
410
416
|
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
this._BOMseen = true;
|
|
420
|
-
stream.shift(); // Remove the BOM
|
|
421
|
-
} else if (stream.length > 0) {
|
|
422
|
-
this._BOMseen = true;
|
|
423
|
-
}
|
|
417
|
+
try {
|
|
418
|
+
const output = decodeUTF8(bytes, state, stream);
|
|
419
|
+
state.streaming = stream;
|
|
420
|
+
return output;
|
|
421
|
+
} catch (error) {
|
|
422
|
+
state.pending = EMPTY_BYTES;
|
|
423
|
+
state.streaming = false;
|
|
424
|
+
throw error;
|
|
424
425
|
}
|
|
425
|
-
|
|
426
|
-
// Convert the stream of code points to a string
|
|
427
|
-
return codePointsToString(stream);
|
|
428
426
|
}
|
|
429
427
|
}
|
|
@@ -249,6 +249,8 @@ describe('TextDecoder', () => {
|
|
|
249
249
|
chars.substring(TypeConstructor.BYTES_PER_ELEMENT, TypeConstructor.BYTES_PER_ELEMENT + 8)
|
|
250
250
|
);
|
|
251
251
|
});
|
|
252
|
+
|
|
253
|
+
expect(decoder.decode(new DataView(buffer, 2, 8))).toBe(chars.substring(2, 10));
|
|
252
254
|
});
|
|
253
255
|
});
|
|
254
256
|
|
|
@@ -274,6 +276,15 @@ describe('TextDecoder', () => {
|
|
|
274
276
|
});
|
|
275
277
|
});
|
|
276
278
|
|
|
279
|
+
it('accepts callable options dictionaries', () => {
|
|
280
|
+
const options = () => {};
|
|
281
|
+
|
|
282
|
+
// @ts-expect-error Web IDL dictionaries accept callable objects
|
|
283
|
+
expect(new TextDecoder('utf-8', options).fatal).toBe(false);
|
|
284
|
+
// @ts-expect-error Web IDL dictionaries accept callable objects
|
|
285
|
+
expect(new TextDecoder().decode(new Uint8Array([0x61]), options)).toBe('a');
|
|
286
|
+
});
|
|
287
|
+
|
|
277
288
|
// https://github.com/inexorabletash/text-encoding/blob/3f330964c0e97e1ed344c2a3e963f4598610a7ad/test/test-misc.js#L383
|
|
278
289
|
it('encode() called with falsy arguments (polyfill bindings)', () => {
|
|
279
290
|
const encoder = new TextEncoder();
|
|
@@ -283,6 +294,137 @@ describe('TextDecoder', () => {
|
|
|
283
294
|
expect([...encoder.encode(0)]).toEqual([48]);
|
|
284
295
|
});
|
|
285
296
|
|
|
297
|
+
describe('indexed UTF-8 decoder', () => {
|
|
298
|
+
it('decodes valid sequences at each UTF-8 boundary', () => {
|
|
299
|
+
const bytes = new Uint8Array([
|
|
300
|
+
0x00, 0x7f, 0xc2, 0x80, 0xdf, 0xbf, 0xe0, 0xa0, 0x80, 0xed, 0x9f, 0xbf, 0xee, 0x80, 0x80,
|
|
301
|
+
0xf0, 0x90, 0x80, 0x80, 0xf4, 0x8f, 0xbf, 0xbf,
|
|
302
|
+
]);
|
|
303
|
+
|
|
304
|
+
expect(new TextDecoder().decode(bytes)).toBe(
|
|
305
|
+
'\u0000\u007f\u0080\u07ff\u0800\ud7ff\ue000\u{10000}\u{10ffff}'
|
|
306
|
+
);
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
it('reads the stream option once for malformed input', () => {
|
|
310
|
+
let reads = 0;
|
|
311
|
+
const options = {
|
|
312
|
+
get stream() {
|
|
313
|
+
reads++;
|
|
314
|
+
return false;
|
|
315
|
+
},
|
|
316
|
+
};
|
|
317
|
+
|
|
318
|
+
expect(new TextDecoder().decode(new Uint8Array([0xff]), options)).toBe('\ufffd');
|
|
319
|
+
expect(reads).toBe(1);
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
it('handles incomplete and malformed sequences across every chunk boundary', () => {
|
|
323
|
+
const NativeTextDecoder = require('node:util').TextDecoder;
|
|
324
|
+
const cases = [
|
|
325
|
+
[0xef, 0xbb, 0xbf, 0x61],
|
|
326
|
+
[0xc2, 0xa2, 0xe6, 0xb0, 0xb4, 0xf0, 0x9d, 0x84, 0x9e],
|
|
327
|
+
[0xe0, 0x80, 0x80, 0x61],
|
|
328
|
+
[0xed, 0xa0, 0x80, 0x61],
|
|
329
|
+
[0xf4, 0x90, 0x80, 0x80, 0x61],
|
|
330
|
+
[0xe1, 0x80, 0x41, 0xc2],
|
|
331
|
+
[0xff, 0xef, 0xbb, 0xbf],
|
|
332
|
+
[0x61, 0xef, 0xbb, 0xbf],
|
|
333
|
+
];
|
|
334
|
+
|
|
335
|
+
for (const input of cases) {
|
|
336
|
+
for (let split = 0; split <= input.length; split++) {
|
|
337
|
+
const decoder = new TextDecoder();
|
|
338
|
+
const nativeDecoder = new NativeTextDecoder();
|
|
339
|
+
const first = new Uint8Array(input.slice(0, split));
|
|
340
|
+
const second = new Uint8Array(input.slice(split));
|
|
341
|
+
const actual =
|
|
342
|
+
decoder.decode(first, { stream: true }) +
|
|
343
|
+
decoder.decode(second, { stream: true }) +
|
|
344
|
+
decoder.decode();
|
|
345
|
+
const expected =
|
|
346
|
+
nativeDecoder.decode(first, { stream: true }) +
|
|
347
|
+
nativeDecoder.decode(second, { stream: true }) +
|
|
348
|
+
nativeDecoder.decode();
|
|
349
|
+
|
|
350
|
+
expect(actual).toBe(expected);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
it('decodes large ASCII and multi-byte inputs', () => {
|
|
356
|
+
const value = 'a'.repeat(9000) + '水𝄞'.repeat(9000);
|
|
357
|
+
const encoded = new TextEncoder().encode(value);
|
|
358
|
+
|
|
359
|
+
expect(new TextDecoder().decode(encoded)).toBe(value);
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
it.each([0, 1, 31, 32, 33, 4095, 4096, 4097, 8193])(
|
|
363
|
+
'decodes %i ASCII bytes across conversion boundaries',
|
|
364
|
+
(length) => {
|
|
365
|
+
const bytes = new Uint8Array(length);
|
|
366
|
+
for (let i = 0; i < length; i++) bytes[i] = 32 + (i % 95);
|
|
367
|
+
|
|
368
|
+
const NativeTextDecoder = require('node:util').TextDecoder;
|
|
369
|
+
expect(new TextDecoder().decode(bytes)).toBe(new NativeTextDecoder().decode(bytes));
|
|
370
|
+
}
|
|
371
|
+
);
|
|
372
|
+
|
|
373
|
+
it('agrees with the native decoder for every two-byte input', () => {
|
|
374
|
+
const NativeTextDecoder = require('node:util').TextDecoder;
|
|
375
|
+
const bytes = new Uint8Array(2);
|
|
376
|
+
for (let first = 0; first <= 0xff; first++) {
|
|
377
|
+
bytes[0] = first;
|
|
378
|
+
for (let second = 0; second <= 0xff; second++) {
|
|
379
|
+
bytes[1] = second;
|
|
380
|
+
expect(new TextDecoder().decode(bytes)).toBe(new NativeTextDecoder().decode(bytes));
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
it('agrees with the native decoder for malformed inputs and all streaming splits', () => {
|
|
386
|
+
const NativeTextDecoder = require('node:util').TextDecoder;
|
|
387
|
+
let random = 0x12345678;
|
|
388
|
+
const nextByte = () => {
|
|
389
|
+
random = (Math.imul(random, 1664525) + 1013904223) >>> 0;
|
|
390
|
+
return random >>> 24;
|
|
391
|
+
};
|
|
392
|
+
|
|
393
|
+
for (let test = 0; test < 2000; test++) {
|
|
394
|
+
const input = new Uint8Array(test % 9);
|
|
395
|
+
for (let i = 0; i < input.length; i++) input[i] = nextByte();
|
|
396
|
+
expect(new TextDecoder().decode(input)).toBe(new NativeTextDecoder().decode(input));
|
|
397
|
+
|
|
398
|
+
for (let split = 0; split <= input.length; split++) {
|
|
399
|
+
const decoder = new TextDecoder();
|
|
400
|
+
const nativeDecoder = new NativeTextDecoder();
|
|
401
|
+
const actual =
|
|
402
|
+
decoder.decode(input.subarray(0, split), { stream: true }) +
|
|
403
|
+
decoder.decode(input.subarray(split), { stream: true }) +
|
|
404
|
+
decoder.decode();
|
|
405
|
+
const expected =
|
|
406
|
+
nativeDecoder.decode(input.subarray(0, split), { stream: true }) +
|
|
407
|
+
nativeDecoder.decode(input.subarray(split), { stream: true }) +
|
|
408
|
+
nativeDecoder.decode();
|
|
409
|
+
expect(actual).toBe(expected);
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
});
|
|
413
|
+
|
|
414
|
+
it('starts a fresh BOM session after a completed decode', () => {
|
|
415
|
+
const decoder = new TextDecoder();
|
|
416
|
+
expect(decoder.decode(new Uint8Array([0x61]))).toBe('a');
|
|
417
|
+
expect(decoder.decode(new Uint8Array([0xef, 0xbb, 0xbf, 0x62]))).toBe('b');
|
|
418
|
+
});
|
|
419
|
+
|
|
420
|
+
it('recovers cleanly after a fatal streaming error', () => {
|
|
421
|
+
const decoder = new TextDecoder('utf-8', { fatal: true });
|
|
422
|
+
expect(decoder.decode(new Uint8Array([0xe2]), { stream: true })).toBe('');
|
|
423
|
+
expect(() => decoder.decode(new Uint8Array([0x41]), { stream: true })).toThrow(TypeError);
|
|
424
|
+
expect(decoder.decode(new Uint8Array([0x42]))).toBe('B');
|
|
425
|
+
});
|
|
426
|
+
});
|
|
427
|
+
|
|
286
428
|
// https://github.com/inexorabletash/text-encoding/blob/master/test/test-utf.js
|
|
287
429
|
describe('UTF-8 - Encode/Decode - reference sample', () => {
|
|
288
430
|
// z, cent, CJK water, G-Clef, Private-use character
|
package/template.tgz
CHANGED
|
Binary file
|