Skip to content
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# Changelog
### Unreleased
- Improved performance of `atob` and `btoa`, [#1503](https://github.com/zloirock/core-js/issues/1503), [#1464](https://github.com/zloirock/core-js/issues/1464)
- Forced replacement of `Iterator.{ concat, zip, zipKeyed }` in the pure version for ensuring proper wrapped `Iterator` instances as the result
- Compat data improvements:
- Added [Deno 2.6.7](https://github.com/denoland/deno/releases/tag/v2.6.7) compat data mapping
Expand Down
21 changes: 15 additions & 6 deletions packages/core-js/modules/web.atob.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,11 @@ var whitespaces = /[\t\n\f\r ]+/g;
var finalEq = /[=]{1,2}$/;

var $atob = getBuiltIn('atob');
var $Array = Array;
var fromCharCode = String.fromCharCode;
var charAt = uncurryThis(''.charAt);
var replace = uncurryThis(''.replace);
var join = uncurryThis([].join);
var exec = uncurryThis(disallowed.exec);

var BASIC = !!$atob && !fails(function () {
Expand Down Expand Up @@ -47,21 +49,28 @@ $({ global: true, bind: true, enumerable: true, forced: FORCED }, {
// `webpack` dev server bug on IE global methods - use call(fn, global, ...)
if (BASIC && !NO_SPACES_IGNORE && !NO_ENCODING_CHECK) return call($atob, globalThis, data);
var string = replace(toString(data), whitespaces, '');
var output = '';
var position = 0;
var bc = 0;
var length, chr, bs;
if (string.length % 4 === 0) {
if (!(string.length & 3)) {
string = replace(string, finalEq, '');
}
length = string.length;
if (length % 4 === 1 || exec(disallowed, string)) {
var lenmod = length & 3;
if (lenmod === 1 || exec(disallowed, string)) {
throw new (getBuiltIn('DOMException'))('The string is not correctly encoded', 'InvalidCharacterError');
}
// (length >> 2) is equivalent for length / 4 floored; * 3 then multiplies the
// number of bytes for full quanta
// lenmod is length % 4; if there's 2 or 3 bytes it's 1 or 2 bytes of extra output
// respectively, so -1, however use a ternary to ensure 0 does not get -1 onto length
var output = new $Array((length >> 2) * 3 + (lenmod ? lenmod - 1 : 0));
var outputIndex = 0;
while (position < length) {
chr = charAt(string, position++);
bs = bc % 4 ? bs * 64 + c2i[chr] : c2i[chr];
if (bc++ % 4) output += fromCharCode(255 & bs >> (-2 * bc & 6));
} return output;
bs = bc & 3 ? (bs << 6) + c2i[chr] : c2i[chr];
if (bc++ & 3) output[outputIndex++] = fromCharCode(255 & bs >> (-2 * bc & 6));
}
return join(output, '');
}
});
13 changes: 10 additions & 3 deletions packages/core-js/modules/web.btoa.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ var validateArgumentsLength = require('../internals/validate-arguments-length');
var i2c = require('../internals/base64-map').i2c;

var $btoa = getBuiltIn('btoa');
var $Array = Array;
var join = uncurryThis([].join);
var charAt = uncurryThis(''.charAt);
var charCodeAt = uncurryThis(''.charCodeAt);

Expand All @@ -35,7 +37,12 @@ $({ global: true, bind: true, enumerable: true, forced: !BASIC || NO_ARG_RECEIVI
// `webpack` dev server bug on IE global methods - use call(fn, global, ...)
if (BASIC) return call($btoa, globalThis, toString(data));
var string = toString(data);
var output = '';
// (string.length + 2) / 3) and then truncating to integer
// does the ceil automatically. << 2 will truncate the integer
// while also doing *4. ceil(length / 3) quanta, 4 bytes output
// per quanta for base64.
var output = new $Array((string.length + 2) / 3 << 2);
var outputIndex = 0;
var position = 0;
var map = i2c;
var block, charCode;
Expand All @@ -45,7 +52,7 @@ $({ global: true, bind: true, enumerable: true, forced: !BASIC || NO_ARG_RECEIVI
throw new (getBuiltIn('DOMException'))('The string contains characters outside of the Latin1 range', 'InvalidCharacterError');
}
block = block << 8 | charCode;
output += charAt(map, 63 & block >> 8 - position % 1 * 8);
} return output;
output[outputIndex++] = charAt(map, 63 & block >> 8 - position % 1 * 8);
} return join(output, '');
}
});