paulmillr / es6-shim

ECMAScript 6 compatibility shims for legacy JS engines
http://paulmillr.com
MIT License
3.11k stars 387 forks source link

Map/Set iterator #387

Closed Xotic750 closed 7 years ago

Xotic750 commented 8 years ago

I am detecting MapIterator and SetIterator for my inpect-x module. On native ES6 Map and Set, the following code will alert true and TypeError: Method Map Iterator.prototype.next called on incompatible receiver.

var m = new Map();
var im = m.values();
var nm = im.next;
try {
  alert(nm.call(new Map([
    [1, true]
  ]).values()).value);
} catch (e) {
  alert(e);
}
try {
  alert(nm.call(new Set([true]).values()).value);
} catch (e) {
  alert(e);
}

http://jsfiddle.net/Xotic750/xw2Lpbwz/

But on es6-shim iterators it alerts true and true.

(I think it is the shim and not the native versions, but I have not tested thoroughly yet)

Safari8, FF26, IE10, node0.10 and 0.8 are examples.

I just did a toString of the next function and it is the shim version.

ljharb commented 8 years ago

You'd need to do Function.toString.call to get the actual source of the function, just in case - the es6-shim definitely modifies the toString of many of its shims.

Xotic750 commented 8 years ago

Ok, but I didn't think that node 0.10 and 0.8 had native Map/Set.

ljharb commented 8 years ago

They don't :-) that comes in 0.12.

Xotic750 commented 8 years ago

https://saucelabs.com/beta/tests/98e7c0128021479795a1c6cf0de06b46/watch

cscott commented 8 years ago

The code in question seems a very strange way to detect the presence of MapIterator and SetIterator. But regardless, #391 contains a fix.

Xotic750 commented 8 years ago

Thanks, I haven't tested it yet. The code I posted was just to demonstrate what I was seeing. The actual code is more like

var SET = typeof Set === 'function' && isSet(new Set()) && Set;
var testSet = SET && new SET(['SetSentinel']);
var sValues = SET && SET.prototype.values;

function isSetIterator(value) {
  if (!SET || !isObjectLike(value) || !ES.IsCallable(value.next)) {
    return false;
  }
  try {
    return ES.Call(
      value.next,
      ES.Call(sValues, testSet)
    ).value === 'SetSentinel';
  } catch (ignore) {}
  return false;
}

It's fairly expensive, I know, but accuracy is more important than performance for me and I couldn't figure out a better cross environment/realm way of doing it?

cscott commented 8 years ago

This seems like it would be sufficient:

var SetIteratorProto = Object.getPrototypeOf(new Set().values());
var isSetIterator(value) { return Object.getPrototypeOf(value) === SetIteratorProto; }
Xotic750 commented 8 years ago

Does not work cross frame.

function getXSet() {
  var iframe = document.createElement('iframe');
  var parent = document.body || document.documentElement;
  var set;
  iframe.style.display = 'none';
  parent.appendChild(iframe);
  iframe.src = 'javascript:';
  set = iframe.contentWindow.Set;
  parent.removeChild(iframe);
  iframe = null;
  return set;
};

var XSet = getXSet();

var SetIteratorProto = Object.getPrototypeOf(new Set().values());

function isSetIterator1(value) {
  return Object.getPrototypeOf(value) === SetIteratorProto;
}

var testSet = new Set(['SetSentinel']);
var sValues = Set.prototype.values;

function isSetIterator2(value) {
  if (typeof value !== 'object' || typeof value.next !== 'function') {
    return false;
  }
  try {
    return value.next.call(sValues.call(testSet)).value === 'SetSentinel';
  } catch (ignore) {}
  return false;
}

var setIt1 = new Set().values();
var setIt2 = new XSet().values();

console.log(setIt1, setIt2);
console.log(isSetIterator1(setIt1), isSetIterator1(setIt2));
console.log(isSetIterator2(setIt1), isSetIterator2(setIt2));

https://jsfiddle.net/Xotic750/prvLzvm2/

ljharb commented 8 years ago

@Xotic750 there's simply not enough machinery on non-globally-exposed items, like GeneratorFunction, or IteratorPrototype, etc, for you to determine them cross-realm. They don't have methods that pivot on internal slots nor is toString reliable.

Xotic750 commented 8 years ago

Is there a situation where isSetIterator2 gets it wrong (I guess something could be fabricated to make it fail, but you'd really be wanting to), I haven't found one yet? Node uses some internal machinery for its inspect module, but of course that is not available cross-browser for my inspect-x. This was the best that I could come up with, and it has passed every test that I can throw at it, so far (until adding es6-shim). Otherwise I may as well go back to toString.call(value)==="[object Set Iterator]", which also works cross-frame on all native implementations that I have tested, but es6-shim will also fail, and it could be a modified value. Of course I could leave the detection and tests out completely, but I'd like to port it as faithfully as I can and would like it to work with es6-shim.