Skip to content
This repository was archived by the owner on Dec 15, 2022. It is now read-only.

Conversation

@smashwilson
Copy link
Contributor

@smashwilson smashwilson commented Jan 2, 2019

Continue the work begun in #14, addressing the problems that only surfaced when I tried to use the new version in atom/atom as part of atom/atom#18611.


The problem

Recent versions of React, which we snapshot as a dependency of the GitHub package, are distributed on npm as minimized source. Unfortunately, a common pattern within minimizers is to alias a global function as a short name to shave off a few characters at each call site:

A="function"===typeof requestAnimationFrame?requestAnimationFrame:void 0

// ...

"function"!==typeof A&&get_console().error("This browser doesn't support requestAnimationFrame. Make sure that you load a polyfill in older browsers. https://fb.me/react-polyfills")

Because requestAnimationFrame is not defined while the snapshot is being produced, A is bound to undefined and any calls fail.

React's Scheduler also uses feature probing to fall back to more primitive implementations when certain global functions are unavailable:

if (
  // If Scheduler runs in a non-DOM environment, it falls back to a naive
  // implementation using setTimeout.
  typeof window === 'undefined' ||
  // Check if MessageChannel is supported, too.
  typeof MessageChannel !== 'function'
) {
  // ...
}

This means that the React scheduler will be using a less performant fallback implementation, even though requestAnimationFrame and friends are available at runtime!

The fix

To handle both of these situations, I'm introducing a set of trampolines for all global functions that are defined in a full Electron renderer process, but not within the mksnapshot environment.

First of all, I needed to find the full list of global functions that should be there in a valid browser window. I ran the following in the dev console of an atom --safe window:

const editor = atom.workspace.getActiveTextEditor()
let globalNames = Object
  .getOwnPropertyNames(global)
  .filter(globalName => typeof global[globalName] === 'function')
  .map(globalName => `"${globalName}"`)
  .join(', ')
editor.insertText(`const globalNames = [${globalNames}]`)

Next, I needed to discover which of these global functions are still defined under mksnapshot. To do that I first needed to be able to run arbitrary source from a snapshot, so I wrote @smashwilson/run-in-snapshot. Once that was working, I pointed it at a script in this repo that I wrote that attempted to eval() each name from the list and collected lists of those that passed and failed; this is available as test/samples/list-globals.js.

I added the global names we need to shim to src/blueprint.js as an Array, and added logic that would define placeholder functions for each. The placeholder functions use a variable in scope called globalFunctionTrampoline to determine if the deferred global function has been set or not, and either call it appropriately or fail.

Finally, I've modified setGlobals() to populate the trampolines with the real functions.


My first attempt at doing this broke Atom because I'd accidentally been installing the global function shims on window, rather than in a self-contained function scope within the blueprint script. As a result, the real global functions were trampled by the shims and were unable to be restored properly. I gave the IIFE its own scope to prevent this from happening.


Remaining work

  • Publish a prerelease version from this branch and verify it with an Atom build

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants