At the NGA wrap-up this past Thursday it was mentioned that
https://github.com/gaia-components/bridge/issues/69 was landed to deal
with latency problems with bridge.js versus postMessage and one of the
losses/wins was that structured cloning no longer happens.
In retrospect, this worried me, so I made this test:
https://gist.github.com/asutherland/79d629e2c9d1b7a16a2c and runnable at
https://clicky.visophyte.org/tests/iframe-entrainment/parent-root.html
The tl;dr is that holding onto any non-value objects provided in a
message seems to entrain the iframe's window and all of its
global/expandos (but not the DOM), which is concerning and eliminates
many of the intended benefits of the use of iframes.
== Investigation:
In the test:
- We have a child iframe.
- Script in the child iframe creates a 2 megabyte Uint8Array and saves
it on its window and a 3 megabyte Uint8Array (that may end up as a 4 meg
allocation) and saves it on a DOM node as an expando. This helps us tell
what gets entrained.
- Script in the child iframe creates an object and posts it to the
parent using MessageEvent and dispatchEvent as used by bridge.js
- The parent retains a reference to the data posted on its window.
- The parent kills off the child iframe.
And then I as a tester using firefox nightly:
- Use about:memory to force aggressive GC by hitting the "minimize
memory usage" button.
- "Measure" a "verbose" memory report.
- Search for "child-frame.html" and see that the window still exists
with its compartment in a detached window at
"top(none)/detached/window(http://localhost/t-html/iframes/child-frame.html)"
and that the compartment includes "2,097,296 B (03.15%) --
class(ArrayBuffer)"
Testing variations:
- If we do not save off the data in the parent by commenting out only
the saving line, then the child window is not entrained. (So there's
nothing I'm screwing up in the parent or child otherwise to cause the
iframe/its window to be entrained.)
- If the child saves a reference to the expando DOM node, then we have 5
MiB of ArrayBuffer still around.
- Creating the data using Object.create(null) to avoid the prototype
chain has no beneficial effect, the window/global is still entrained.
- Trying to just clobber the contents of the iframe via setting the
`src` to a data URI does not avoid the leak.
This suggests that cross-compartment wrapper-cutting does not occur in
this case (makes sense) and that although the DOM and its expandos are
able to be collected if there are no window-rooted references, the
window global is otherwise kept alive as a static root.
Since one of the main goals of the iframes seems to be to make it
impossible or at least harder for buggy code in the iframes to leak
data, this seems concerning.
Is this something that we're aware of and are there platform mitigation
bugs that exist? (I doubt wrapper-cutting is viable for the web as it
exists, but maybe the platform could reap the window global or its
expandos?) It sounded like the apparent performance of the NGA-ported
apps really depends on the
https://github.com/gaia-components/bridge/issues/69 fix to avoid
yielding to the busy event loop. Unfortunately
JSON.parse(JSON.stringify(message.data)) in the receiving window is the
only easy way that jumps out at me to try and "bless" the objects into
the current compartment using mechanism available to non-chrome code, but:
- That's not viable unless we're throwing away all the fidelity of
structured cloning.
- That means the GC engine is still on the hook to actually reap those
references out of the receiving window before the iframe can be totally
GC'ed, we're just upper-bounding their lifetime by avoiding any
possibility of the bridge.js consumers of holding onto data.
But maybe a structured clone polyfill like
https://github.com/traviskaufman/cycloneJS could work, noting that I
haven't audited that it really avoids any references to the source
object after the fact.
Andrew
_______________________________________________
dev-fxos mailing list
[email protected]
https://lists.mozilla.org/listinfo/dev-fxos