@zip.js/zip.js 2.6.25 → 2.6.28

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 (60) hide show
  1. package/README.md +16 -17
  2. package/dist/z-worker-fflate.js +1 -1
  3. package/dist/z-worker-pako.js +1 -1
  4. package/dist/z-worker.js +1 -1
  5. package/dist/zip-fs-full.js +5593 -5582
  6. package/dist/zip-fs-full.min.js +1 -1
  7. package/dist/zip-fs.js +536 -514
  8. package/dist/zip-fs.min.js +1 -1
  9. package/dist/zip-full.js +4110 -4099
  10. package/dist/zip-full.min.js +1 -1
  11. package/dist/zip-no-worker-deflate.min.js +1 -1
  12. package/dist/zip-no-worker-inflate.min.js +1 -1
  13. package/dist/zip-no-worker.min.js +1 -1
  14. package/dist/zip.js +536 -514
  15. package/dist/zip.min.js +1 -1
  16. package/index.d.ts +46 -23
  17. package/index.js +2 -2
  18. package/lib/core/codec-interface.js +17 -9
  19. package/lib/core/codec-pool-worker.js +2 -3
  20. package/lib/core/codec-pool.js +1 -1
  21. package/lib/core/configuration.js +29 -8
  22. package/lib/core/io.js +36 -46
  23. package/lib/core/streams/aes-crypto-stream.js +87 -62
  24. package/lib/core/streams/codec-stream.js +4 -3
  25. package/lib/core/streams/codecs/crc32.js +3 -1
  26. package/lib/core/streams/codecs/deflate.js +1792 -1796
  27. package/lib/core/streams/codecs/inflate.js +1828 -1831
  28. package/lib/core/streams/common-crypto.js +3 -1
  29. package/lib/core/streams/crc32-stream.js +7 -7
  30. package/lib/core/streams/zip-crypto-stream.js +13 -14
  31. package/lib/core/streams/zip-entry-stream.js +42 -58
  32. package/lib/core/util/cp437-decode.js +6 -2
  33. package/lib/core/util/decode-text.js +4 -2
  34. package/lib/core/util/encode-text.js +3 -1
  35. package/lib/core/util/stream-codec-shim.js +6 -2
  36. package/lib/core/z-worker-core.js +9 -9
  37. package/lib/core/zip-entry.js +6 -2
  38. package/lib/core/zip-reader.js +15 -6
  39. package/lib/core/zip-writer.js +74 -50
  40. package/lib/z-worker-bootstrap-fflate.js +1 -1
  41. package/lib/z-worker-bootstrap-pako.js +1 -1
  42. package/lib/z-worker-inline-template.js +9 -7
  43. package/lib/z-worker-inline.js +1 -1
  44. package/lib/z-worker.js +2 -2
  45. package/lib/zip-fflate-shim.js +1 -1
  46. package/lib/zip-fs.js +2 -2
  47. package/lib/zip-full-fflate.js +2 -2
  48. package/lib/zip-full.js +4 -4
  49. package/lib/zip-no-worker-deflate.js +1 -1
  50. package/lib/zip-no-worker-inflate.js +1 -1
  51. package/lib/zip-no-worker.js +2 -2
  52. package/lib/zip.js +2 -2
  53. package/package.json +5 -2
  54. package/rollup-fflate.config.dev.js +2 -2
  55. package/rollup-fflate.config.js +2 -2
  56. package/rollup.config.dev.js +2 -2
  57. package/rollup.config.js +2 -2
  58. package/tests/all/test-streams.js +29 -0
  59. package/tests/tests-data.js +1 -0
  60. package/lib/z-worker-inline-template-base64.js +0 -36
@@ -35,10 +35,12 @@ import {
35
35
  } from "./codecs/sjcl.js";
36
36
 
37
37
  const ERR_INVALID_PASSWORD = "Invalid password";
38
+ const ERR_INVALID_SIGNATURE = "Invalid signature";
38
39
 
39
40
  export {
40
41
  getRandomValues,
41
- ERR_INVALID_PASSWORD
42
+ ERR_INVALID_PASSWORD,
43
+ ERR_INVALID_SIGNATURE
42
44
  };
43
45
 
44
46
  function getRandomValues(array) {
@@ -28,26 +28,26 @@
28
28
 
29
29
  /* global TransformStream */
30
30
 
31
- import Crc32 from "./codecs/crc32.js";
31
+ import { Crc32 } from "./codecs/crc32.js";
32
32
 
33
33
  class Crc32Stream extends TransformStream {
34
34
 
35
35
  constructor() {
36
+ const crc32 = new Crc32();
36
37
  super({
37
- start() {
38
- this.crc32 = new Crc32();
39
- },
40
38
  transform(chunk) {
41
- this.crc32.append(chunk);
39
+ crc32.append(chunk);
42
40
  },
43
41
  flush(controller) {
44
42
  const value = new Uint8Array(4);
45
43
  const dataView = new DataView(value.buffer);
46
- dataView.setUint32(0, this.crc32.get());
44
+ dataView.setUint32(0, crc32.get());
47
45
  controller.enqueue(value);
48
46
  }
49
47
  });
50
48
  }
51
49
  }
52
50
 
53
- export default Crc32Stream;
51
+ export {
52
+ Crc32Stream
53
+ };
@@ -28,7 +28,7 @@
28
28
 
29
29
  /* global TransformStream */
30
30
 
31
- import Crc32 from "./codecs/crc32.js";
31
+ import { Crc32 } from "./codecs/crc32.js";
32
32
  import {
33
33
  ERR_INVALID_PASSWORD,
34
34
  getRandomValues
@@ -39,7 +39,6 @@ const HEADER_LENGTH = 12;
39
39
  class ZipCryptoDecryptionStream extends TransformStream {
40
40
 
41
41
  constructor(password, passwordVerification) {
42
- let stream;
43
42
  super({
44
43
  start() {
45
44
  Object.assign(this, {
@@ -59,12 +58,8 @@ class ZipCryptoDecryptionStream extends TransformStream {
59
58
  chunk = chunk.subarray(HEADER_LENGTH);
60
59
  }
61
60
  controller.enqueue(decrypt(zipCrypto, chunk));
62
- },
63
- flush() {
64
- stream.valid = true;
65
61
  }
66
62
  });
67
- stream = this;
68
63
  }
69
64
  }
70
65
 
@@ -128,21 +123,25 @@ function encrypt(target, input) {
128
123
  }
129
124
 
130
125
  function createKeys(target, password) {
131
- target.keys = [0x12345678, 0x23456789, 0x34567890];
132
- target.crcKey0 = new Crc32(target.keys[0]);
133
- target.crcKey2 = new Crc32(target.keys[2]);
126
+ const keys = [0x12345678, 0x23456789, 0x34567890];
127
+ Object.assign(target, {
128
+ keys,
129
+ crcKey0: new Crc32(keys[0]),
130
+ crcKey2: new Crc32(keys[2]),
131
+ });
134
132
  for (let index = 0; index < password.length; index++) {
135
133
  updateKeys(target, password.charCodeAt(index));
136
134
  }
137
135
  }
138
136
 
139
137
  function updateKeys(target, byte) {
138
+ let [key0, key1, key2] = target.keys;
140
139
  target.crcKey0.append([byte]);
141
- target.keys[0] = ~target.crcKey0.get();
142
- target.keys[1] = getInt32(target.keys[1] + getInt8(target.keys[0]));
143
- target.keys[1] = getInt32(Math.imul(target.keys[1], 134775813) + 1);
144
- target.crcKey2.append([target.keys[1] >>> 24]);
145
- target.keys[2] = ~target.crcKey2.get();
140
+ key0 = ~target.crcKey0.get();
141
+ key1 = getInt32(Math.imul(getInt32(key1 + getInt8(key0)), 134775813) + 1);
142
+ target.crcKey2.append([key1 >>> 24]);
143
+ key2 = ~target.crcKey2.get();
144
+ target.keys = [key0, key1, key2];
146
145
  }
147
146
 
148
147
  function getByte(target) {
@@ -26,60 +26,45 @@
26
26
  EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
27
  */
28
28
 
29
- /* global CompressionStream, DecompressionStream, TransformStream */
29
+ /* global TransformStream */
30
30
 
31
- import Crc32Stream from "./crc32-stream.js";
31
+ import { Crc32Stream } from "./crc32-stream.js";
32
32
  import {
33
33
  AESEncryptionStream,
34
34
  AESDecryptionStream
35
35
  } from "./aes-crypto-stream.js";
36
36
  import {
37
37
  ZipCryptoEncryptionStream,
38
- ZipCryptoDecryptionStream,
38
+ ZipCryptoDecryptionStream
39
39
  } from "./zip-crypto-stream.js";
40
40
  import {
41
- ERR_INVALID_PASSWORD
41
+ ERR_INVALID_PASSWORD,
42
+ ERR_INVALID_SIGNATURE
42
43
  } from "./common-crypto.js";
43
- import CodecStream from "./codec-stream.js";
44
44
 
45
- const ERR_INVALID_SIGNATURE = "Invalid signature";
46
45
  const COMPRESSION_FORMAT = "deflate-raw";
47
- const UNDEFINED_TYPE = "undefined";
48
- const COMPRESSION_STREAM_API_SUPPORTED = typeof CompressionStream == UNDEFINED_TYPE;
49
- const DECOMPRESSION_STREAM_API_SUPPORTED = typeof DecompressionStream == UNDEFINED_TYPE;
50
- let INFLATE_RAW_SUPPORTED = true;
51
- let DEFLATE_RAW_SUPPORTED = true;
52
46
 
53
47
  class DeflateStream extends TransformStream {
54
48
 
55
- constructor(codecConstructor, options, { chunkSize }, ...strategies) {
56
- super({}, ...strategies);
49
+ constructor(options, { chunkSize, CompressionStream, CompressionStreamNative }) {
50
+ super({});
57
51
  const { compressed, encrypted, useCompressionStream, password, passwordVerification, encryptionStrength, zipCrypto, signed, level } = options;
58
52
  const stream = this;
59
53
  let crc32Stream, encryptionStream;
60
54
  let readable = filterEmptyChunks(super.readable);
61
55
  if ((!encrypted || zipCrypto) && signed) {
62
56
  [readable, crc32Stream] = readable.tee();
63
- crc32Stream = crc32Stream.pipeThrough(new Crc32Stream());
57
+ crc32Stream = pipeThrough(crc32Stream, new Crc32Stream());
64
58
  }
65
59
  if (compressed) {
66
- if ((useCompressionStream !== undefined && !useCompressionStream) || (COMPRESSION_STREAM_API_SUPPORTED && !DEFLATE_RAW_SUPPORTED)) {
67
- readable = pipeCodecStream(codecConstructor, readable, { chunkSize, level });
68
- } else {
69
- try {
70
- readable = readable.pipeThrough(new CompressionStream(COMPRESSION_FORMAT));
71
- } catch (_error) {
72
- DEFLATE_RAW_SUPPORTED = false;
73
- readable = pipeCodecStream(codecConstructor, readable, { chunkSize, level });
74
- }
75
- }
60
+ readable = pipeThroughCommpressionStream(readable, useCompressionStream, { level, chunkSize }, CompressionStreamNative, CompressionStream);
76
61
  }
77
62
  if (encrypted) {
78
63
  if (zipCrypto) {
79
- readable = readable.pipeThrough(new ZipCryptoEncryptionStream(password, passwordVerification));
64
+ readable = pipeThrough(readable, new ZipCryptoEncryptionStream(password, passwordVerification));
80
65
  } else {
81
66
  encryptionStream = new AESEncryptionStream(password, encryptionStrength);
82
- readable = readable.pipeThrough(encryptionStream);
67
+ readable = pipeThrough(readable, encryptionStream);
83
68
  }
84
69
  }
85
70
  setReadable(stream, readable, async () => {
@@ -98,46 +83,31 @@ class DeflateStream extends TransformStream {
98
83
 
99
84
  class InflateStream extends TransformStream {
100
85
 
101
- constructor(codecConstructor, options, { chunkSize }, ...strategies) {
102
- super({}, ...strategies);
103
- const { zipCrypto, encrypted, password, passwordVerification, signed, encryptionStrength, compressed, useCompressionStream } = options;
104
- const stream = this;
86
+ constructor(options, { chunkSize, DecompressionStream, DecompressionStreamNative }) {
87
+ super({});
88
+ const { zipCrypto, encrypted, password, passwordVerification, signed, signature, encryptionStrength, compressed, useCompressionStream } = options;
105
89
  let crc32Stream, decryptionStream;
106
90
  let readable = filterEmptyChunks(super.readable);
107
91
  if (encrypted) {
108
92
  if (zipCrypto) {
109
- readable = readable.pipeThrough(new ZipCryptoDecryptionStream(password, passwordVerification));
93
+ readable = pipeThrough(readable, new ZipCryptoDecryptionStream(password, passwordVerification));
110
94
  } else {
111
95
  decryptionStream = new AESDecryptionStream(password, signed, encryptionStrength);
112
- readable = readable.pipeThrough(decryptionStream);
96
+ readable = pipeThrough(readable, decryptionStream);
113
97
  }
114
98
  }
115
99
  if (compressed) {
116
- if ((useCompressionStream !== undefined && !useCompressionStream) || (DECOMPRESSION_STREAM_API_SUPPORTED && !INFLATE_RAW_SUPPORTED)) {
117
- readable = pipeCodecStream(codecConstructor, readable, { chunkSize });
118
- } else {
119
- try {
120
- readable = readable.pipeThrough(new DecompressionStream(COMPRESSION_FORMAT));
121
- } catch (_error) {
122
- INFLATE_RAW_SUPPORTED = false;
123
- readable = pipeCodecStream(codecConstructor, readable, { chunkSize });
124
- }
125
- }
100
+ readable = pipeThroughCommpressionStream(readable, useCompressionStream, { chunkSize }, DecompressionStreamNative, DecompressionStream);
126
101
  }
127
102
  if ((!encrypted || zipCrypto) && signed) {
128
103
  [readable, crc32Stream] = readable.tee();
129
- crc32Stream = crc32Stream.pipeThrough(new Crc32Stream());
104
+ crc32Stream = pipeThrough(crc32Stream, new Crc32Stream());
130
105
  }
131
- setReadable(stream, readable, async () => {
132
- if (encrypted && !zipCrypto) {
133
- if (!decryptionStream.valid) {
134
- throw new Error(ERR_INVALID_SIGNATURE);
135
- }
136
- }
106
+ setReadable(this, readable, async () => {
137
107
  if ((!encrypted || zipCrypto) && signed) {
138
- const signature = await crc32Stream.getReader().read();
139
- const dataViewSignature = new DataView(signature.value.buffer);
140
- if (options.signature != dataViewSignature.getUint32(0, false)) {
108
+ const streamSignature = await crc32Stream.getReader().read();
109
+ const dataViewSignature = new DataView(streamSignature.value.buffer);
110
+ if (signature != dataViewSignature.getUint32(0, false)) {
141
111
  throw new Error(ERR_INVALID_SIGNATURE);
142
112
  }
143
113
  }
@@ -152,12 +122,8 @@ export {
152
122
  ERR_INVALID_SIGNATURE
153
123
  };
154
124
 
155
- function pipeCodecStream(codecConstructor, readable, options) {
156
- return readable.pipeThrough(new CodecStream(codecConstructor, options));
157
- }
158
-
159
125
  function filterEmptyChunks(readable) {
160
- return readable.pipeThrough(new TransformStream({
126
+ return pipeThrough(readable, new TransformStream({
161
127
  transform(chunk, controller) {
162
128
  if (chunk && chunk.length) {
163
129
  controller.enqueue(chunk);
@@ -167,10 +133,28 @@ function filterEmptyChunks(readable) {
167
133
  }
168
134
 
169
135
  function setReadable(stream, readable, flush) {
170
- readable = readable.pipeThrough(new TransformStream({ flush }));
136
+ readable = pipeThrough(readable, new TransformStream({ flush }));
171
137
  Object.defineProperty(stream, "readable", {
172
138
  get() {
173
139
  return readable;
174
140
  }
175
141
  });
142
+ }
143
+
144
+ function pipeThroughCommpressionStream(readable, useCompressionStream, options, CodecStreamNative, CodecStream) {
145
+ try {
146
+ const CompressionStream = useCompressionStream && CodecStreamNative ? CodecStreamNative : CodecStream;
147
+ readable = pipeThrough(readable, new CompressionStream(COMPRESSION_FORMAT, options));
148
+ } catch (error) {
149
+ if (useCompressionStream) {
150
+ readable = pipeThrough(readable, new CodecStream(COMPRESSION_FORMAT, options));
151
+ } else {
152
+ throw error;
153
+ }
154
+ }
155
+ return readable;
156
+ }
157
+
158
+ function pipeThrough(readable, transformStream) {
159
+ return readable.pipeThrough(transformStream);
176
160
  }
@@ -31,7 +31,11 @@
31
31
  const CP437 = "\0☺☻♥♦♣♠•◘○◙♂♀♪♫☼►◄↕‼¶§▬↨↑↓→←∟↔▲▼ !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~⌂ÇüéâäàåçêëèïîìÄÅÉæÆôöòûùÿÖÜ¢£¥₧ƒáíóúñѪº¿⌐¬½¼¡«»░▒▓│┤╡╢╖╕╣║╗╝╜╛┐└┴┬├─┼╞╟╚╔╩╦╠═╬╧╨╤╥╙╘╒╓╫╪┘┌█▄▌▐▀αßΓπΣσµτΦΘΩδ∞φε∩≡±≥≤⌠⌡÷≈°∙·√ⁿ²■ ".split("");
32
32
  const VALID_CP437 = CP437.length == 256;
33
33
 
34
- export default (stringValue) => {
34
+ export {
35
+ decodeCP437
36
+ };
37
+
38
+ function decodeCP437(stringValue) {
35
39
  if (VALID_CP437) {
36
40
  let result = "";
37
41
  for (let indexCharacter = 0; indexCharacter < stringValue.length; indexCharacter++) {
@@ -41,4 +45,4 @@ export default (stringValue) => {
41
45
  } else {
42
46
  return new TextDecoder().decode(stringValue);
43
47
  }
44
- };
48
+ }
@@ -28,9 +28,11 @@
28
28
 
29
29
  /* global TextDecoder */
30
30
 
31
- import decodeCP437 from "./cp437-decode.js";
31
+ import { decodeCP437 } from "./cp437-decode.js";
32
32
 
33
- export default decodeText;
33
+ export {
34
+ decodeText
35
+ };
34
36
 
35
37
  function decodeText(value, encoding) {
36
38
  if (encoding && encoding.trim().toLowerCase() == "cp437") {
@@ -28,7 +28,9 @@
28
28
 
29
29
  /* global TextEncoder */
30
30
 
31
- export default encodeText;
31
+ export {
32
+ encodeText
33
+ };
32
34
 
33
35
  function encodeText(value) {
34
36
  if (typeof TextEncoder == "undefined") {
@@ -27,12 +27,16 @@
27
27
  EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28
28
  */
29
29
 
30
- export default (library, options = {}, registerDataHandler) => {
30
+ export {
31
+ initShimAsyncCodec
32
+ };
33
+
34
+ function initShimAsyncCodec(library, options = {}, registerDataHandler) {
31
35
  return {
32
36
  Deflate: createCodecClass(library.Deflate, options.deflate, registerDataHandler),
33
37
  Inflate: createCodecClass(library.Inflate, options.inflate, registerDataHandler)
34
38
  };
35
- };
39
+ }
36
40
 
37
41
  function createCodecClass(constructor, constructorOptions, registerDataHandler) {
38
42
  return class {
@@ -30,8 +30,7 @@
30
30
 
31
31
  import {
32
32
  Codec,
33
- CODEC_DEFLATE,
34
- CODEC_INFLATE,
33
+ getCodecStreamClass,
35
34
  MESSAGE_EVENT_TYPE,
36
35
  MESSAGE_START,
37
36
  MESSAGE_PULL,
@@ -70,18 +69,19 @@ addEventListener(MESSAGE_EVENT_TYPE, async event => {
70
69
  async function init(message) {
71
70
  try {
72
71
  const { options, scripts, config } = message;
73
- const { codecType } = options;
74
72
  if (scripts && scripts.length) {
75
73
  importScripts.apply(undefined, scripts);
76
74
  }
77
75
  if (self.initCodec) {
78
76
  self.initCodec();
79
77
  }
80
- let codecConstructor;
81
- if (codecType.startsWith(CODEC_DEFLATE)) {
82
- codecConstructor = self.Deflate;
83
- } else if (codecType.startsWith(CODEC_INFLATE)) {
84
- codecConstructor = self.Inflate;
78
+ config.CompressionStreamNative = self.CompressionStream;
79
+ config.DecompressionStreamNative = self.DecompressionStream;
80
+ if (self.Deflate) {
81
+ config.CompressionStream = getCodecStreamClass(self.Deflate);
82
+ }
83
+ if (self.Inflate) {
84
+ config.DecompressionStream = getCodecStreamClass(self.Inflate);
85
85
  }
86
86
  const strategy = { highWaterMark: 1, size: () => config.chunkSize };
87
87
  const readable = message.readable || new ReadableStream({
@@ -106,7 +106,7 @@ async function init(message) {
106
106
  await ackData;
107
107
  }
108
108
  }, strategy);
109
- codec = new Codec(codecConstructor, readable, writable, options, config);
109
+ codec = new Codec(readable, writable, options, config);
110
110
  const result = await codec.run();
111
111
  if (message.writable && !options.preventClose) {
112
112
  await message.writable.close();
@@ -34,10 +34,14 @@ const PROPERTY_NAMES = [
34
34
  "extraFieldNTFS", "lastAccessDate", "creationDate", "extraFieldExtendedTimestamp",
35
35
  "version", "versionMadeBy", "msDosCompatible", "internalFileAttribute", "externalFileAttribute"];
36
36
 
37
- export default class Entry {
37
+ class Entry {
38
38
 
39
39
  constructor(data) {
40
40
  PROPERTY_NAMES.forEach(name => this[name] = data[name]);
41
41
  }
42
42
 
43
- }
43
+ }
44
+
45
+ export {
46
+ Entry
47
+ };
@@ -26,7 +26,7 @@
26
26
  EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
27
  */
28
28
 
29
- /* global BigInt, Response */
29
+ /* global BigInt, Response, ReadableStream, WritableStream */
30
30
 
31
31
  import {
32
32
  MAX_32_BITS,
@@ -70,9 +70,9 @@ import {
70
70
  initStream,
71
71
  BlobReader
72
72
  } from "./io.js";
73
- import decodeText from "./util/decode-text.js";
74
- import Crc32 from "./streams/codecs/crc32.js";
75
- import Entry from "./zip-entry.js";
73
+ import { decodeText } from "./util/decode-text.js";
74
+ import { Crc32 } from "./streams/codecs/crc32.js";
75
+ import { Entry } from "./zip-entry.js";
76
76
 
77
77
  const ERR_BAD_FORMAT = "File format is not recognized";
78
78
  const ERR_EOCDR_NOT_FOUND = "End of central directory not found";
@@ -91,6 +91,11 @@ const ZIP64_PROPERTIES = ["uncompressedSize", "compressedSize", "offset"];
91
91
  class ZipReader {
92
92
 
93
93
  constructor(reader, options = {}) {
94
+ if (reader instanceof ReadableStream) {
95
+ reader = {
96
+ readable: reader
97
+ };
98
+ }
94
99
  Object.assign(this, {
95
100
  reader,
96
101
  options,
@@ -320,6 +325,11 @@ class ZipEntry {
320
325
  const readable = reader.readable;
321
326
  readable.offset = dataOffset;
322
327
  const size = readable.size = compressedSize;
328
+ if (writer instanceof WritableStream) {
329
+ writer = {
330
+ writable: writer
331
+ };
332
+ }
323
333
  const { writable } = writer;
324
334
  if (writable.size === undefined) {
325
335
  writable.size = 0;
@@ -345,8 +355,7 @@ class ZipEntry {
345
355
  preventClose
346
356
  },
347
357
  config,
348
- streamOptions: { signal, size, onstart, onprogress, onend },
349
- codecConstructor: config.Inflate
358
+ streamOptions: { signal, size, onstart, onprogress, onend }
350
359
  };
351
360
  writable.size += (await runWorker({ readable, writable }, workerOptions)).size;
352
361
  if (!preventClose) {