Skip to content

Commit 34ed498

Browse files
committed
Improve IE8 compatibility and expand test coverage
- Rewrite Promise implementation (promises.js) to a single state-machine queue so rejections propagate through then() calls without a rejection handler to a trailing catch() - RSA key usage filtering uses IE8-safe loops and msrcryptoUtilities.indexOf instead of Array.prototype.filter/indexOf - Add modern Wrap Key QUnit module (AES-KW, AES-CBC, AES-GCM raw/jwk, RSA-OAEP round-trips) replacing the legacy IE11-only interop file - Add Test.Promise.js coverage - getRandomValues error tests guard TypedArray usage with regular-array fallbacks for IE8 - Add IE8-safe 'tail -f' auto-scroll to SubtleTests.html runner - Rebuild dist bundles
1 parent a0a1cb7 commit 34ed498

10 files changed

Lines changed: 569 additions & 577 deletions

File tree

dist/msrcrypto.js

Lines changed: 105 additions & 271 deletions
Large diffs are not rendered by default.

dist/msrcrypto.min.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/rsa.js

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -477,9 +477,28 @@ if (typeof operations !== "undefined") {
477477
// Honor the caller's requested usages (like the other algorithms do),
478478
// routing each requested usage to the key half it applies to. When no
479479
// usages are requested, default to all usages valid for the algorithm.
480+
// Note: Array.prototype.filter/indexOf are ES5 (unavailable on IE8), so
481+
// use plain loops and the IE8-safe msrcryptoUtilities.indexOf helper.
480482
if (p.usages) {
481-
publicUsage = publicUsage.filter(function(usage) { return p.usages.indexOf(usage) >= 0; });
482-
privateUsage = privateUsage.filter(function(usage) { return p.usages.indexOf(usage) >= 0; });
483+
var requestedUsages = p.usages;
484+
var filteredPublic = [];
485+
var filteredPrivate = [];
486+
var usageIndex;
487+
488+
for (usageIndex = 0; usageIndex < publicUsage.length; usageIndex += 1) {
489+
if (msrcryptoUtilities.indexOf(requestedUsages, publicUsage[usageIndex]) >= 0) {
490+
filteredPublic.push(publicUsage[usageIndex]);
491+
}
492+
}
493+
494+
for (usageIndex = 0; usageIndex < privateUsage.length; usageIndex += 1) {
495+
if (msrcryptoUtilities.indexOf(requestedUsages, privateUsage[usageIndex]) >= 0) {
496+
filteredPrivate.push(privateUsage[usageIndex]);
497+
}
498+
}
499+
500+
publicUsage = filteredPublic;
501+
privateUsage = filteredPrivate;
483502
}
484503

485504
return {

src/subtle/promises.js

Lines changed: 105 additions & 122 deletions
Original file line numberDiff line numberDiff line change
@@ -63,158 +63,141 @@
6363
throw new Error("use 'new' keyword with Promise constructor");
6464
}
6565

66-
var successResult = null,
67-
failReason = null,
68-
thenResolved = [],
69-
thenRejected = [],
70-
rejectThenPromise = [],
71-
resolveThenPromise = [];
72-
73-
this.then = function(onCompleted, onRejected) {
74-
75-
var thenFunctionResult;
76-
77-
// If we already have a result because resolveFunction was synchronous,
78-
// then just call onCompleted with the result.
79-
if (successResult) {
80-
thenFunctionResult = onCompleted(successResult.result);
81-
82-
if (thenFunctionResult && thenFunctionResult.then) {
83-
return thenFunctionResult;
84-
}
85-
86-
// Create a new promise; resolve with the result;
87-
// return the resolved promise.
88-
return Promise.resolve(thenFunctionResult);
66+
// State: 0 = pending, 1 = fulfilled, 2 = rejected.
67+
var state = 0,
68+
settledValue = null,
69+
// Queue of handlers registered while pending. Each entry is
70+
// { onCompleted, onRejected, resolveNext, rejectNext } so a single
71+
// list keeps each handler aligned with its chained promise's
72+
// resolve/reject. This lets rejections propagate through then()
73+
// calls that omit a rejection handler so a trailing catch() still
74+
// receives the error. (The previous implementation tracked these in
75+
// separate arrays that fell out of alignment and dropped such
76+
// rejections, silently swallowing errors.)
77+
handlers = [];
78+
79+
// Invoke a single registered handler against the settled value and
80+
// route the outcome to its chained promise. A missing handler passes
81+
// the value through (fulfilled -> resolveNext, rejected -> rejectNext)
82+
// so a later catch() still sees an earlier rejection. A throwing
83+
// handler rejects the chained promise.
84+
function runHandler(handler) {
85+
86+
var callback = (state === 1) ? handler.onCompleted : handler.onRejected;
87+
88+
if (!callback) {
89+
(state === 1 ? handler.resolveNext : handler.rejectNext)(settledValue);
90+
return;
8991
}
9092

91-
// If we already have a fail reason from a rejected promise
92-
if (failReason) {
93-
thenFunctionResult = onRejected ? onRejected(failReason.result) : failReason.result;
94-
95-
if (thenFunctionResult && thenFunctionResult.then) {
96-
return thenFunctionResult;
97-
}
98-
99-
// Create a new promise; reject with the result;
100-
// return the resolved promise.
101-
return Promise.resolve(thenFunctionResult);
93+
var result;
94+
try {
95+
result = callback(settledValue);
96+
} catch (handlerError) {
97+
handler.rejectNext(handlerError);
98+
return;
10299
}
103100

104-
// If we do not have a result, store the onCompleted/onRejected functions
105-
// to call when we do get a result.
106-
thenResolved.push(onCompleted);
107-
if (onRejected) {
108-
thenRejected.push(onRejected);
109-
}
110-
111-
// Return a new promise object. This will allow chaining with then/catch().
112-
// tslint:disable-next-line: no-shadowed-variable
113-
return new Promise(function(resolve, reject) {
114-
resolveThenPromise.push(resolve);
115-
rejectThenPromise.push(reject);
116-
});
117-
};
101+
handler.resolveNext(result);
102+
}
118103

119-
// tslint:disable-next-line: no-string-literal
120-
this["catch"] = function(onRejected) {
104+
// Move the promise to its final state and flush any queued handlers.
105+
// When fulfilled with a thenable, adopt that thenable's eventual state
106+
// so returning a promise from then() chains as expected.
107+
function settle(newState, value) {
121108

122-
var catchFunctionResult;
109+
if (state !== 0) {
110+
return;
111+
}
123112

124-
// If we already have a result because resolveFunction was synchronous,
125-
// then just call onRejected with the result.
126-
if (failReason) {
127-
catchFunctionResult = onRejected(failReason.result);
113+
if (newState === 1 && value && (typeof value === "object" || typeof value === "function")) {
128114

129-
if (catchFunctionResult && catchFunctionResult.then) {
130-
return catchFunctionResult;
115+
var thenFunction;
116+
try {
117+
thenFunction = value.then;
118+
} catch (accessError) {
119+
settle(2, accessError);
120+
return;
131121
}
132122

133-
return Promise.resolve(catchFunctionResult);
123+
if (typeof thenFunction === "function") {
124+
var handled = false;
125+
try {
126+
thenFunction.call(
127+
value,
128+
function(result) { if (!handled) { handled = true; settle(1, result); } },
129+
function(reason) { if (!handled) { handled = true; settle(2, reason); } });
130+
} catch (thenableError) {
131+
if (!handled) { handled = true; settle(2, thenableError); }
132+
}
133+
return;
134+
}
134135
}
135136

136-
// If we do not have a result, store the onRejected function
137-
// to call when we do get a result.
138-
thenRejected.push(onRejected);
137+
state = newState;
138+
settledValue = value;
139139

140-
// Return a new promise object. This will allow chaining with then/catch().
141-
// tslint:disable-next-line: no-shadowed-variable
142-
return new Promise(function(resolve, reject) {
143-
resolveThenPromise.push(resolve);
144-
rejectThenPromise.push(reject);
145-
});
146-
};
140+
for (var i = 0; i < handlers.length; i += 1) {
141+
runHandler(handlers[i]);
142+
}
143+
handlers = [];
144+
}
147145

148146
function resolve(param) {
149147
/// <summary>
150-
/// Called by the executor function when the function has succeeded.
148+
/// Called by the executor function when the operation has succeeded.
151149
/// </summary>
152-
/// <param name="param">A result value that will be passed to the then() function.</param>
153-
154-
var result, i;
155-
156-
// Call each attached Then function with the result
157-
for (i = 0; i < thenResolved.length; i += 1) {
158-
159-
result = thenResolved[i](param);
160-
161-
// If the result of the then() function is a Promise,
162-
// set then() to call the chained resolve function.
163-
if (result && result.then) {
164-
result.then(resolveThenPromise[i]);
165-
166-
// Also set catch() if present
167-
if (rejectThenPromise[i]) {
168-
// tslint:disable-next-line: no-string-literal
169-
result["catch"](rejectThenPromise[i]);
170-
}
171-
172-
} else {
173-
174-
// If a then() promise was chained to this promise, call its resolve
175-
// function.
176-
if (resolveThenPromise[i]) {
177-
resolveThenPromise[i](result);
178-
}
179-
}
180-
}
181-
182-
// If the onCompleted function has not yet been assigned, store the result.
183-
successResult = { result: param };
184-
185-
return;
150+
/// <param name="param">A result value passed to the then() function.</param>
151+
settle(1, param);
186152
}
187153

188154
function reject(param) {
155+
/// <summary>
156+
/// Called by the executor function when the operation has failed.
157+
/// </summary>
158+
/// <param name="param">A reason value passed to the catch() function.</param>
159+
settle(2, param);
160+
}
189161

190-
var reason, i;
162+
this.then = function(onCompleted, onRejected) {
191163

192-
// Call each catch function on this promise
193-
for (i = 0; i < thenRejected.length; i += 1) {
164+
var resolveNext, rejectNext;
194165

195-
reason = thenRejected[i](param);
166+
// tslint:disable-next-line: no-shadowed-variable
167+
var nextPromise = new Promise(function(resolve, reject) {
168+
resolveNext = resolve;
169+
rejectNext = reject;
170+
});
196171

197-
// If the result of the catch() function is a Promise,
198-
// set then() to call the chained resolve function.
199-
if (reason && reason.then) {
200-
reason.then(resolveThenPromise[i], rejectThenPromise[i]);
172+
var handler = {
173+
onCompleted: (typeof onCompleted === "function") ? onCompleted : null,
174+
onRejected: (typeof onRejected === "function") ? onRejected : null,
175+
resolveNext: resolveNext,
176+
rejectNext: rejectNext
177+
};
201178

202-
} else {
203-
if (resolveThenPromise[i]) {
204-
resolveThenPromise[i](reason);
205-
}
206-
}
179+
// Run immediately if already settled, otherwise queue until it is.
180+
if (state === 0) {
181+
handlers.push(handler);
182+
} else {
183+
runHandler(handler);
207184
}
208185

209-
// If the onCompleted function has not yet been assigned, store the result.
210-
failReason = { result: param };
186+
return nextPromise;
187+
};
211188

212-
return;
213-
}
189+
// tslint:disable-next-line: no-string-literal
190+
this["catch"] = function(onRejected) {
191+
return this.then(null, onRejected);
192+
};
214193

215194
// Call the executor function passing the resolve & reject functions of
216-
// this instance.
217-
executor(resolve, reject);
195+
// this instance. A throw from the executor rejects the promise.
196+
try {
197+
executor(resolve, reject);
198+
} catch (executorError) {
199+
reject(executorError);
200+
}
218201

219202
return;
220203
};

src/subtle/subtleInterface.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -466,7 +466,7 @@ var publicMethods = {
466466
return new Promise(function(resolve, reject) {
467467

468468
if (key.extractable === false ||
469-
wrappingKey.usages.indexOf("wrapKey") < 0 ||
469+
utils.indexOf(wrappingKey.usages, "wrapKey") < 0 ||
470470
wrappingKey.algorithm.name.toUpperCase() !== wrappingKeyAlgorithm.name) {
471471
reject(utils.error("InvalidAccessError", "key cannot be wrapped with the supplied wrapping key"));
472472
return;
@@ -519,7 +519,7 @@ var publicMethods = {
519519

520520
return new Promise(function(resolve, reject) {
521521

522-
if (unwrappingKey.usages.indexOf("unwrapKey") < 0 ||
522+
if (utils.indexOf(unwrappingKey.usages, "unwrapKey") < 0 ||
523523
unwrappingKey.algorithm.name.toUpperCase() !== unwrapAlgorithm.name) {
524524
reject(utils.error("InvalidAccessError", "key cannot be unwrapped with the supplied unwrapping key"));
525525
return;

0 commit comments

Comments
 (0)