// This currentContext variable will only be used if the makeSlotClass // function is called, which happens only if this is the first copy of the // @wry/context package to be imported. let currentContext = null; // This unique internal object is used to denote the absence of a value // for a given Slot, and is never exposed to outside code. const MISSING_VALUE = {}; let idCounter = 1; // Although we can't do anything about the cost of duplicated code from // accidentally bundling multiple copies of the @wry/context package, we can // avoid creating the Slot class more than once using makeSlotClass. const makeSlotClass = () => class Slot { constructor() { // If you have a Slot object, you can find out its slot.id, but you cannot // guess the slot.id of a Slot you don't have access to, thanks to the // randomized suffix. this.id = [ "slot", idCounter++, Date.now(), Math.random().toString(36).slice(2), ].join(":"); } hasValue() { for (let context = currentContext; context; context = context.parent) { // We use the Slot object iself as a key to its value, which means the // value cannot be obtained without a reference to the Slot object. if (this.id in context.slots) { const value = context.slots[this.id]; if (value === MISSING_VALUE) break; if (context !== currentContext) { // Cache the value in currentContext.slots so the next lookup will // be faster. This caching is safe because the tree of contexts and // the values of the slots are logically immutable. currentContext.slots[this.id] = value; } return true; } } if (currentContext) { // If a value was not found for this Slot, it's never going to be found // no matter how many times we look it up, so we might as well cache // the absence of the value, too. currentContext.slots[this.id] = MISSING_VALUE; } return false; } getValue() { if (this.hasValue()) { return currentContext.slots[this.id]; } } withValue(value, callback, // Given the prevalence of arrow functions, specifying arguments is likely // to be much more common than specifying `this`, hence this ordering: args, thisArg) { const slots = { __proto__: null, [this.id]: value, }; const parent = currentContext; currentContext = { parent, slots }; try { // Function.prototype.apply allows the arguments array argument to be // omitted or undefined, so args! is fine here. return callback.apply(thisArg, args); } finally { currentContext = parent; } } // Capture the current context and wrap a callback function so that it // reestablishes the captured context when called. static bind(callback) { const context = currentContext; return function () { const saved = currentContext; try { currentContext = context; return callback.apply(this, arguments); } finally { currentContext = saved; } }; } // Immediately run a callback function without any captured context. static noContext(callback, // Given the prevalence of arrow functions, specifying arguments is likely // to be much more common than specifying `this`, hence this ordering: args, thisArg) { if (currentContext) { const saved = currentContext; try { currentContext = null; // Function.prototype.apply allows the arguments array argument to be // omitted or undefined, so args! is fine here. return callback.apply(thisArg, args); } finally { currentContext = saved; } } else { return callback.apply(thisArg, args); } } }; function maybe(fn) { try { return fn(); } catch (ignored) { } } // We store a single global implementation of the Slot class as a permanent // non-enumerable property of the globalThis object. This obfuscation does // nothing to prevent access to the Slot class, but at least it ensures the // implementation (i.e. currentContext) cannot be tampered with, and all copies // of the @wry/context package (hopefully just one) will share the same Slot // implementation. Since the first copy of the @wry/context package to be // imported wins, this technique imposes a steep cost for any future breaking // changes to the Slot class. const globalKey = "@wry/context:Slot"; const host = // Prefer globalThis when available. // https://github.com/benjamn/wryware/issues/347 maybe(() => globalThis) || // Fall back to global, which works in Node.js and may be converted by some // bundlers to the appropriate identifier (window, self, ...) depending on the // bundling target. https://github.com/endojs/endo/issues/576#issuecomment-1178515224 maybe(() => global) || // Otherwise, use a dummy host that's local to this module. We used to fall // back to using the Array constructor as a namespace, but that was flagged in // https://github.com/benjamn/wryware/issues/347, and can be avoided. Object.create(null); // Whichever globalHost we're using, make TypeScript happy about the additional // globalKey property. const globalHost = host; export const Slot = globalHost[globalKey] || // Earlier versions of this package stored the globalKey property on the Array // constructor, so we check there as well, to prevent Slot class duplication. Array[globalKey] || (function (Slot) { try { Object.defineProperty(globalHost, globalKey, { value: Slot, enumerable: false, writable: false, // When it was possible for globalHost to be the Array constructor (a // legacy Slot dedup strategy), it was important for the property to be // configurable:true so it could be deleted. That does not seem to be as // important when globalHost is the global object, but I don't want to // cause similar problems again, and configurable:true seems safest. // https://github.com/endojs/endo/issues/576#issuecomment-1178274008 configurable: true }); } finally { return Slot; } })(makeSlotClass()); //# sourceMappingURL=slot.js.map