- From: Mark S. Miller <erights@google.com>
- Date: Thu, 14 Jul 2011 12:51:30 -0700
- To: David Bruant <david.bruant@labri.fr>
- Cc: es-discuss <es-discuss@mozilla.org>, public-script-coord@w3.org
- Message-ID: <CABHxS9iHxuMDDfeO=tLGHv=7t7dhzeyEL4NXB5QnKBw4K-EFMA@mail.gmail.com>
On Thu, Jul 14, 2011 at 12:46 PM, Mark S. Miller <erights@google.com> wrote: > At the thread "LazyReadCopy experiment and invariant checking for > [[Extensible]]=false" on es-discuss, > On Wed, Jul 13, 2011 at 10:29 AM, David Bruant <david.bruant@labri.fr>wrote: > >> Hi, >> >> Recently, I've been thinking about the structured clone algorithm used in >> postMessage >> > > Along with Dave Herman < > http://blog.mozilla.com/dherman/2011/05/25/im-worried-about-structured-clone/>, > I'm worried about structure clone < > http://www.w3.org/TR/html5/common-dom-interfaces.html#safe-passing-of-structured-data>. > In order to understand it better before criticizing it, I tried implementing > it in ES5 + WeakMaps. My code appears below. In writing it, I noticed some > ambiguities in the spec, so I implemented my best guess about what the spec > intended. > > Aside: Coding this so that it is successfully defensive against changes to > primordial bindings proved surprisingly tricky, and the resulting coding > patterns quite unpleasant. See the explanatory comment early in the code > below. Separately, we should think about how to better support defensive > programming for code that must operate in the face of mutable primordials. > > Ambiguities: > > 1) When the says "If input is an Object object", I assumed it meant 'if the > input's [[Class]] is "Object" '. > 2) By "for each enumerable property in input" combined with "Note: This > does not walk the prototype chain.", I assume it meant "for each enumerable > own property of input". > 3) By "the value of the property" combined with "Property descriptors, > setters, getters, and analogous features are not copied in this process.", I > assume it meant "the result of calling the [[Get]] internal method of input > with the property name", even if the enumerable own property is an accessor > property. > 4) By "corresponding to the same underlying data", I assume it meant to > imply direct sharing of read/write access, leading to shared state > concurrency between otherwise shared-nothing event loops. > 5) By "add a new property to output having the same name" combined with "in the output it would just have the default state (typically read-write, though that could depend on the scripting environment).", I assume it meant to define the property as writable: true, enumerable: true, configurable: true, rather than to call the internal [[Put]] method, in order to avoid inherited setters. > > Are the above interpretations correct? > > Given the access to shared mutability implied by #4, I'm wondering why > MessagePorts are passed separately, rather than simply being other special > case like File in the structured clone algorithm. > > I've been advising people to avoid the structured clone algorithm, and send > only JSON serializations + MessagePorts through postMessage. It's unclear to > me why structured clone wasn't instead defined to be more equivalent to > JSON, or to a well chosen subset of JSON. Given that they're going to > co-exist, it behooves us to understand their differences better, so that we > know when to advise JSON serialization/unserialization around postMessage > vs. just using structured clone directly. > > There are here a fixed set of data types recognized as special cases by > this algorithm. Unlike JSON, there are no extension points for a > user-defined abstraction to cause its own instances to effectively be > cloned, with behavior, across the boundary. But neither do we gain the > advantage of avoiding calls to user code interleaved with the structured > clone algorithm, if my resolution of #3 is correct, since these [[Get]] > calls can call getters. > > In ES6 we intend to reform [[Class]]. Allen's ES6 draft < > http://wiki.ecmascript.org/doku.php?id=harmony:specification_drafts> makes > a valiant start at this. How would we revise structured clone to account for > [[Class]] reform? > > And finally there's the issue raised by David on the es-discuss thread: > What should the structured clone algorithm do when encountering a proxy? The > algorithm as coded below will successfully "clone" proxies, for some meaning > of clone. Is that the clone behavior we wish for proxies? > > > ------------- sclone.js ------------------------ > > var sclone; > > (function () { > "use strict"; > > // The following initializations are assumed to capture initial > // bindings, so that sclone is insensitive to changes to these > // bindings between the creation of the sclone function and calls > // to it. Note that {@code call.bind} is only called here during > // initialization, so we are insensitive to whether this changes to > // something other than the original Function.prototype.bind after > // initialization. > > var Obj = Object; > var WM = WeakMap; > var Bool = Boolean; > var Num = Number; > var Str = String; > var Dat = Date; > var RE = RegExp; > var Err = Error; > var TypeErr = TypeError; > > var call = Function.prototype.call; > > var getValue = call.bind(WeakMap.prototype.get); > var setValue = call.bind(WeakMap.prototype.set); > > var getClassRE = (/\[object (.*)\]/); > var exec = call.bind(RegExp.prototype.exec); > var toClassString = call.bind(Object.prototype.toString); > function getClass(obj) { > return exec(getClassRE, toClassString(obj))[1]; > } > > var valueOfBoolean = call.bind(Boolean.prototype.valueOf); > var valueOfNumber = call.bind(Number.prototype.valueOf); > var valueOfString = call.bind(String.prototype.valueOf); > var valueOfDate = call.bind(Date.prototype.valueOf); > > var keys = Object.keys; > var forEach = call.bind(Array.prototype.forEach); > > var defProp = Object.defineProperty; > > // Below this line, we should no longer be sensitive to the current > // bindings of built-in services we rely on. > > sclone = function(input) { > > function recur(input, memory) { > if (input !== Obj(input)) { return input; } > var output = getValue(memory, input); > if (output) { return output; } > > var klass = getClass(input); > switch (klass) { > case 'Boolean': { > output = new Bool(valueOfBoolean(input)); > break; > } > case 'Number': { > output = new Num(valueOfNumber(input)); > break; > } > case 'String': { > output = new Str(valueOfString(input)); > break; > } > case 'Date': { > output = new Dat(valueOfDate(input)); > break; > } > case 'RegExp': { > var flags = (input.global ? 'g' : '') + > (input.ignoreCase ? 'i' : '') + > (input.multiline ? 'm' : ''); > output = new RE(input.source, flags); > break; > } > case 'ImageData': > case 'File': > case 'Blob': > case 'FileList': { > // TODO: implement > throw new Err('not yet implemented'); > break; > } > case 'Array': { > output = []; > break; > } > case 'Object': { > output = {}; > break; > } > default: { > throw new TypeErr('Should be DOMException(DATA_CLONE_ERR)'); > break; > } > } > setValue(memory, input, output); > > if (klass === 'Object' || klass === 'Array') { > forEach(keys(input), function(name) { > defProp(output, name, { > value: recur(input[name], memory), > writable: true, > enumerable: true, > configurable: true > }); > }); > } > return output; > } > > return recur(input, WM()); > }; > })(); > > > > > > -- Cheers, --MarkM
Received on Thursday, 14 July 2011 19:51:57 UTC