4

I have a code base type checked by Flow, and I use instanceof a lot to do type refinements.

I now need to make my code work when elements come from iframes, and instanceof doesn't work in this case, because each window has its own instances.

I tried to define a getOwnElement helper to get Element from defaultView, but if I then use the retrieved Element the instanceof check fails for Flow:

declare function getOwnElement(node: Element): Element;
function getOwnElement(node) {
  return node.ownerDocument.defaultView.Element;
}

const ownElement = getOwnElement(element);
element instanceof ownElement && element; // element is marked as `empty`

So now I'm wondering how am I supposed to update my code to keep it type safe and work with iframes?

Fez Vrasta
  • 14,110
  • 21
  • 98
  • 160

1 Answers1

3

I have two suggestions. Neither is perfectly typesafe, but the lack of safety is contained within small functions which will be easy to test and manually verify. With both, there is the risk that if you pass in an unrelated object with the node.ownerDocument.defaultView.Element property you will get incorrect results, but that seems like a very small risk. I haven't tested these at runtime, but any required tweaks should be relatively minor.

declare class Element {};

function asElement(node: any): ?Element {
  const OwnElement = node?.ownerDocument?.defaultView?.Element;
  if (OwnElement != null && node instanceof OwnElement) {
    return node;
  } else {
    return null;
  }
}

function doThingWithElement(whatever: mixed): void {
  const element = asElement(whatever);
  if (element != null) {
    (element: Element);
    // Expected error
    (element: string);
  }
}

(try)

In this example, asElement is a function which does the instanceof check with the iframes issue in mind, and returns the argument if and only if it passes the check. Then, you can refine using an ordinary nullness check.

declare class Element {};

declare function isElement(node: any): boolean %checks (node instanceof Element);
function isElement(node) {
  const OwnElement = node?.ownerDocument?.defaultView?.Element;
  return OwnElement != null && node instanceof OwnElement;
}

function doThingWithElement(whatever: mixed): void {
  if (isElement(whatever)) {
    (whatever: Element);
    // Expected error
    (whatever: string);
  }
}

(try)

This creates an isElement function which you can use directly to perform the refinement.

As an aside, Element is the type if an instance of the Element class, not the class itself. Class<Element> is the type of the class itself, but it appears that Flow doesn't perform type refinements where the right hand side has a dynamically-created class.

Nat Mote
  • 4,068
  • 18
  • 30