2017-11-13 01:23:39 -05:00
|
|
|
'use strict';
|
|
|
|
|
|
|
|
// ============================================================================
|
|
|
|
// Fine Lib
|
|
|
|
// It controls React.
|
|
|
|
// ============================================================================
|
|
|
|
|
|
|
|
import {EventEmitter} from 'utilities/events';
|
|
|
|
import Module from 'utilities/module';
|
|
|
|
import {has} from 'utilities/object';
|
|
|
|
|
|
|
|
|
|
|
|
export default class Fine extends Module {
|
|
|
|
constructor(...args) {
|
|
|
|
super(...args);
|
|
|
|
|
|
|
|
this._wrappers = new Map;
|
|
|
|
this._known_classes = new Map;
|
|
|
|
this._observer = null;
|
|
|
|
this._waiting = null;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2017-11-15 14:05:58 -05:00
|
|
|
async onEnable(tries=0) {
|
2017-11-13 01:23:39 -05:00
|
|
|
// TODO: Move awaitElement to utilities/dom
|
|
|
|
if ( ! this.root_element )
|
2018-02-22 18:23:44 -05:00
|
|
|
this.root_element = await this.parent.awaitElement(this.selector || 'body #root');
|
2017-11-13 01:23:39 -05:00
|
|
|
|
2018-02-22 18:23:44 -05:00
|
|
|
if ( ! this.root_element || ! this.root_element._reactRootContainer ) {
|
2017-11-15 14:05:58 -05:00
|
|
|
if ( tries > 500 )
|
2018-02-22 18:23:44 -05:00
|
|
|
throw new Error('Unable to find React after 25 seconds');
|
|
|
|
this.root_element = null;
|
2017-11-15 14:05:58 -05:00
|
|
|
return new Promise(r => setTimeout(r, 50)).then(() => this.onEnable(tries+1));
|
|
|
|
}
|
2017-11-13 01:23:39 -05:00
|
|
|
|
2018-02-22 18:23:44 -05:00
|
|
|
this.react_root = this.root_element._reactRootContainer;
|
|
|
|
this.react = this.react_root.current.child;
|
2017-11-13 01:23:39 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
onDisable() {
|
2018-02-22 18:23:44 -05:00
|
|
|
this.react_root = this.root_element = this.react = this.accessor = null;
|
2017-11-13 01:23:39 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static findAccessor(element) {
|
|
|
|
for(const key in element)
|
|
|
|
if ( key.startsWith('__reactInternalInstance$') )
|
|
|
|
return key;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ========================================================================
|
|
|
|
// Low Level Accessors
|
|
|
|
// ========================================================================
|
|
|
|
|
|
|
|
getReactInstance(element) {
|
2018-02-22 18:23:44 -05:00
|
|
|
if ( ! this.accessor )
|
|
|
|
this.accessor = Fine.findAccessor(element);
|
|
|
|
if ( ! this.accessor )
|
|
|
|
return;
|
|
|
|
|
2017-11-13 01:23:39 -05:00
|
|
|
return element[this.accessor];
|
|
|
|
}
|
|
|
|
|
|
|
|
getOwner(instance) {
|
2018-02-22 18:23:44 -05:00
|
|
|
if ( instance._reactInternalFiber )
|
|
|
|
instance = instance._reactInternalFiber;
|
2017-11-13 01:23:39 -05:00
|
|
|
else if ( instance instanceof Node )
|
|
|
|
instance = this.getReactInstance(instance);
|
|
|
|
|
|
|
|
if ( ! instance )
|
|
|
|
return null;
|
|
|
|
|
2018-02-22 18:23:44 -05:00
|
|
|
return instance.return;
|
2017-11-13 01:23:39 -05:00
|
|
|
}
|
|
|
|
|
2018-02-22 18:23:44 -05:00
|
|
|
getHostNode(instance) {
|
|
|
|
if ( instance._reactInternalFiber )
|
|
|
|
instance = instance._reactInternalFiber;
|
2017-11-13 01:23:39 -05:00
|
|
|
else if ( instance instanceof Node )
|
|
|
|
instance = this.getReactInstance(instance);
|
|
|
|
|
|
|
|
while( instance )
|
2018-02-22 18:23:44 -05:00
|
|
|
if ( instance.stateNode instanceof Node )
|
|
|
|
return instance.stateNode
|
2017-11-13 01:23:39 -05:00
|
|
|
else
|
2018-02-22 18:23:44 -05:00
|
|
|
instance = instance.parent;
|
2017-11-13 01:23:39 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
getParent(instance) {
|
2018-02-22 18:23:44 -05:00
|
|
|
return this.getOwner(instance);
|
|
|
|
}
|
|
|
|
|
|
|
|
getFirstChild(node) {
|
|
|
|
if ( node._reactInternalFiber )
|
|
|
|
node = node._reactInternalFiber;
|
|
|
|
else if ( node instanceof Node )
|
|
|
|
node = this.getReactInstance(node);
|
|
|
|
|
|
|
|
if ( ! node )
|
|
|
|
return null;
|
|
|
|
|
|
|
|
return node.child;
|
|
|
|
}
|
|
|
|
|
|
|
|
getChildren(node) {
|
|
|
|
if ( node._reactInternalFiber )
|
|
|
|
node = node._reactInternalFiber;
|
|
|
|
else if ( node instanceof Node )
|
|
|
|
node = this.getReactInstance(node);
|
|
|
|
|
|
|
|
if ( ! node )
|
|
|
|
return null;
|
|
|
|
|
|
|
|
const children = [];
|
|
|
|
let child = node.child;
|
|
|
|
while(child) {
|
|
|
|
children.push(child);
|
|
|
|
child = child.sibling;
|
|
|
|
}
|
|
|
|
|
|
|
|
return children;
|
2017-11-13 01:23:39 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
searchParent(node, criteria, max_depth=15, depth=0) {
|
2018-02-22 18:23:44 -05:00
|
|
|
if ( node._reactInternalFiber )
|
|
|
|
node = node._reactInternalFiber;
|
2017-11-13 01:23:39 -05:00
|
|
|
else if ( node instanceof Node )
|
|
|
|
node = this.getReactInstance(node);
|
|
|
|
|
|
|
|
if ( ! node || depth > max_depth )
|
|
|
|
return null;
|
|
|
|
|
2018-02-22 18:23:44 -05:00
|
|
|
const inst = node.stateNode;
|
2017-11-13 01:23:39 -05:00
|
|
|
if ( inst && criteria(inst) )
|
|
|
|
return inst;
|
|
|
|
|
2018-02-22 18:23:44 -05:00
|
|
|
if ( node.return ) {
|
|
|
|
const result = this.searchParent(node.return, criteria, max_depth, depth+1);
|
2017-11-13 01:23:39 -05:00
|
|
|
if ( result )
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
searchTree(node, criteria, max_depth=15, depth=0) {
|
|
|
|
if ( ! node )
|
|
|
|
node = this.react;
|
2018-02-22 18:23:44 -05:00
|
|
|
else if ( node._reactInternalFiber )
|
|
|
|
node = node._reactInternalFiber;
|
2017-11-13 01:23:39 -05:00
|
|
|
else if ( node instanceof Node )
|
|
|
|
node = this.getReactInstance(node);
|
|
|
|
|
|
|
|
if ( ! node || depth > max_depth )
|
|
|
|
return null;
|
|
|
|
|
2018-02-22 18:23:44 -05:00
|
|
|
const inst = node.stateNode;
|
2017-11-13 01:23:39 -05:00
|
|
|
if ( inst && criteria(inst) )
|
|
|
|
return inst;
|
|
|
|
|
2018-02-22 18:23:44 -05:00
|
|
|
if ( node.child ) {
|
|
|
|
let child = node.child;
|
|
|
|
while(child) {
|
|
|
|
const result = this.searchTree(child, criteria, max_depth, depth+1);
|
|
|
|
if ( result )
|
|
|
|
return result;
|
|
|
|
child = child.sibling;
|
|
|
|
}
|
|
|
|
}
|
2017-11-13 01:23:39 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
searchAll(node, criterias, max_depth=15, depth=0, data) {
|
|
|
|
if ( ! node )
|
|
|
|
node = this.react;
|
2018-02-22 18:23:44 -05:00
|
|
|
else if ( node._reactInternalFiber )
|
|
|
|
node = node._reactInternalFiber;
|
2017-11-13 01:23:39 -05:00
|
|
|
else if ( node instanceof Node )
|
|
|
|
node = this.getReactInstance(node);
|
|
|
|
|
|
|
|
if ( ! data )
|
|
|
|
data = {
|
|
|
|
seen: new Set,
|
|
|
|
classes: criterias.map(() => null),
|
|
|
|
out: criterias.map(() => ({
|
|
|
|
cls: null, instances: new Set, depth: null
|
|
|
|
})),
|
|
|
|
max_depth: depth
|
|
|
|
};
|
|
|
|
|
|
|
|
if ( ! node || depth > max_depth )
|
|
|
|
return data.out;
|
|
|
|
|
|
|
|
if ( depth > data.max_depth )
|
|
|
|
data.max_depth = depth;
|
|
|
|
|
2018-02-22 18:23:44 -05:00
|
|
|
const inst = node.stateNode;
|
2017-11-13 01:23:39 -05:00
|
|
|
if ( inst ) {
|
|
|
|
const cls = inst.constructor,
|
|
|
|
idx = data.classes.indexOf(cls);
|
|
|
|
|
|
|
|
if ( idx !== -1 )
|
|
|
|
data.out[idx].instances.add(inst);
|
|
|
|
|
|
|
|
else if ( ! data.seen.has(cls) ) {
|
|
|
|
let i = criterias.length;
|
|
|
|
while(i-- > 0)
|
|
|
|
if ( criterias[i](inst) ) {
|
|
|
|
data.classes[i] = data.out[i].cls = cls;
|
|
|
|
data.out[i].instances.add(inst);
|
|
|
|
data.out[i].depth = depth;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
data.seen.add(cls);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-02-22 18:23:44 -05:00
|
|
|
if ( node.child ) {
|
|
|
|
let child = node.child;
|
|
|
|
while(child) {
|
|
|
|
this.searchAll(child, criterias, max_depth, depth+1, data);
|
|
|
|
child = child.sibling;
|
|
|
|
}
|
|
|
|
}
|
2017-11-13 01:23:39 -05:00
|
|
|
|
|
|
|
return data.out;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ========================================================================
|
|
|
|
// Class Wrapping
|
|
|
|
// ========================================================================
|
|
|
|
|
|
|
|
define(key, criteria) {
|
|
|
|
if ( this._wrappers.has(key) )
|
|
|
|
return this._wrappers.get(key);
|
|
|
|
|
|
|
|
if ( ! criteria )
|
|
|
|
throw new Error('cannot find definition and no criteria provided');
|
|
|
|
|
|
|
|
const wrapper = new FineWrapper(key, criteria, this);
|
|
|
|
this._wrappers.set(key, wrapper);
|
|
|
|
|
|
|
|
const data = this.searchAll(this.react, [criteria], 1000)[0];
|
|
|
|
if ( data.cls ) {
|
|
|
|
wrapper._set(data.cls, data.instances);
|
|
|
|
this._known_classes.set(data.cls, wrapper);
|
|
|
|
|
|
|
|
} else {
|
|
|
|
if ( ! this._waiting )
|
|
|
|
this._startWaiting();
|
|
|
|
|
|
|
|
this._waiting.push(wrapper);
|
|
|
|
this._waiting_crit.push(criteria);
|
|
|
|
}
|
|
|
|
|
|
|
|
return wrapper;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
_checkWaiters(nodes) {
|
|
|
|
if ( ! this._waiting )
|
|
|
|
return;
|
|
|
|
|
|
|
|
if ( ! Array.isArray(nodes) )
|
|
|
|
nodes = [nodes];
|
|
|
|
|
|
|
|
for(let node of nodes) {
|
|
|
|
if ( ! node )
|
|
|
|
node = this.react;
|
|
|
|
else if ( node._reactInternalInstance )
|
|
|
|
node = node._reactInternalInstance;
|
|
|
|
else if ( node instanceof Node )
|
|
|
|
node = this.getReactInstance(node);
|
|
|
|
|
|
|
|
if ( ! node || ! this._waiting.length )
|
|
|
|
continue;
|
|
|
|
|
|
|
|
const data = this.searchAll(node, this._waiting_crit, 1000);
|
|
|
|
let i = data.length;
|
|
|
|
while(i-- > 0) {
|
|
|
|
if ( data[i].cls ) {
|
|
|
|
const d = data[i],
|
|
|
|
w = this._waiting.splice(i, 1)[0];
|
|
|
|
|
|
|
|
this._waiting_crit.splice(i, 1);
|
|
|
|
this.log.info(`Found class for "${w.name}" at depth ${d.depth}`, d);
|
|
|
|
|
|
|
|
w._set(d.cls, d.instances);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( ! this._waiting.length )
|
|
|
|
this._stopWaiting();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
_startWaiting() {
|
|
|
|
this.log.info('Installing MutationObserver.');
|
|
|
|
|
|
|
|
this._waiting = [];
|
|
|
|
this._waiting_crit = [];
|
|
|
|
this._waiting_timer = setInterval(() => this._checkWaiters(), 500);
|
|
|
|
|
|
|
|
if ( ! this._observer )
|
|
|
|
this._observer = new MutationObserver(mutations =>
|
|
|
|
this._checkWaiters(mutations.map(x => x.target))
|
|
|
|
);
|
|
|
|
|
|
|
|
this._observer.observe(document.body, {
|
|
|
|
childList: true,
|
|
|
|
subtree: true
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
_stopWaiting() {
|
|
|
|
this.log.info('Stopping MutationObserver.');
|
|
|
|
|
|
|
|
if ( this._observer )
|
|
|
|
this._observer.disconnect();
|
|
|
|
|
|
|
|
if ( this._waiting_timer )
|
|
|
|
clearInterval(this._waiting_timer);
|
|
|
|
|
|
|
|
this._waiting = null;
|
|
|
|
this._waiting_crit = null;
|
|
|
|
this._waiting_timer = null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const EVENTS = {
|
|
|
|
'will-mount': 'componentWillMount',
|
|
|
|
mount: 'componentDidMount',
|
|
|
|
render: 'render',
|
|
|
|
'receive-props': 'componentWillReceiveProps',
|
|
|
|
'should-update': 'shouldComponentUpdate',
|
|
|
|
'will-update': 'componentWillUpdate',
|
|
|
|
update: 'componentDidUpdate',
|
|
|
|
unmount: 'componentWillUnmount'
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export class FineWrapper extends EventEmitter {
|
|
|
|
constructor(name, criteria, fine) {
|
|
|
|
super();
|
|
|
|
|
|
|
|
this.name = name;
|
|
|
|
this.criteria = criteria;
|
|
|
|
this.fine = fine;
|
|
|
|
|
|
|
|
this.instances = new Set;
|
|
|
|
|
|
|
|
this._wrapped = new Set;
|
|
|
|
this._class = null;
|
|
|
|
}
|
|
|
|
|
2017-11-22 03:33:34 -05:00
|
|
|
get first() {
|
|
|
|
return this.toArray()[0];
|
|
|
|
}
|
|
|
|
|
|
|
|
toArray() {
|
|
|
|
return Array.from(this.instances);
|
|
|
|
}
|
|
|
|
|
2017-11-13 01:23:39 -05:00
|
|
|
ready(fn) {
|
|
|
|
if ( this._class )
|
|
|
|
fn(this._class, this.instances);
|
|
|
|
else
|
|
|
|
this.once('set', fn);
|
|
|
|
}
|
|
|
|
|
|
|
|
_set(cls, instances) {
|
|
|
|
if ( this._class )
|
|
|
|
throw new Error('already have a class');
|
|
|
|
|
|
|
|
this._class = cls;
|
|
|
|
this._wrapped.add('componentWillMount');
|
|
|
|
this._wrapped.add('componentWillUnmount');
|
|
|
|
|
|
|
|
const t = this,
|
|
|
|
_instances = this.instances,
|
|
|
|
proto = cls.prototype,
|
|
|
|
o_mount = proto.componentWillMount,
|
|
|
|
o_unmount = proto.componentWillUnmount,
|
|
|
|
|
|
|
|
mount = proto.componentWillMount = o_mount ?
|
|
|
|
function(...args) {
|
|
|
|
this._ffz_mounted = true;
|
|
|
|
_instances.add(this);
|
|
|
|
t.emit('will-mount', this, ...args);
|
|
|
|
return o_mount.apply(this, args);
|
|
|
|
} :
|
|
|
|
function(...args) {
|
|
|
|
this._ffz_mounted = true;
|
|
|
|
_instances.add(this);
|
|
|
|
t.emit('will-mount', this, ...args);
|
|
|
|
},
|
|
|
|
|
|
|
|
unmount = proto.componentWillUnmount = o_unmount ?
|
|
|
|
function(...args) {
|
|
|
|
t.emit('unmount', this, ...args);
|
|
|
|
_instances.delete(this);
|
|
|
|
this._ffz_mounted = false;
|
|
|
|
return o_unmount.apply(this, args);
|
|
|
|
} :
|
|
|
|
function(...args) {
|
|
|
|
t.emit('unmount', this, ...args);
|
|
|
|
_instances.delete(this);
|
|
|
|
this._ffz_mounted = false;
|
|
|
|
};
|
|
|
|
|
|
|
|
this.__componentWillMount = [mount, o_mount];
|
|
|
|
this.__componentWillUnmount = [unmount, o_unmount];
|
|
|
|
|
|
|
|
for(const event of this.events())
|
|
|
|
this._maybeWrap(event);
|
|
|
|
|
|
|
|
if ( instances )
|
|
|
|
for(const inst of instances) {
|
2018-02-22 18:23:44 -05:00
|
|
|
// How do we check mounted state for fibers?
|
|
|
|
//if ( inst._reactInternalInstance && inst._reactInternalInstance._renderedComponent )
|
|
|
|
// inst._ffz_mounted = true;
|
2017-11-13 01:23:39 -05:00
|
|
|
_instances.add(inst);
|
|
|
|
}
|
|
|
|
|
|
|
|
this.emit('set', cls, _instances);
|
|
|
|
}
|
|
|
|
|
|
|
|
_add(instances) {
|
|
|
|
for(const inst of instances)
|
|
|
|
this.instances.add(inst);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
_maybeWrap(event) {
|
|
|
|
const key = EVENTS[event];
|
|
|
|
if ( ! this._class || ! key || this._wrapped.has(key) )
|
|
|
|
return;
|
|
|
|
|
|
|
|
this._wrap(event, key);
|
|
|
|
}
|
|
|
|
|
|
|
|
_wrap(event, key) {
|
|
|
|
if ( this._wrapped.has(key) )
|
|
|
|
return;
|
|
|
|
|
|
|
|
const t = this,
|
|
|
|
proto = this._class.prototype,
|
|
|
|
original = proto[key],
|
|
|
|
|
|
|
|
fn = proto[key] = original ?
|
|
|
|
function(...args) {
|
|
|
|
t.emit(event, this, ...args);
|
|
|
|
return original.apply(this, args);
|
|
|
|
} :
|
|
|
|
|
|
|
|
function(...args) {
|
|
|
|
t.emit(event, this, ...args);
|
|
|
|
};
|
|
|
|
|
|
|
|
this[`__${key}`] = [fn, original];
|
|
|
|
}
|
|
|
|
|
|
|
|
_unwrap(key) {
|
|
|
|
if ( ! this._wrapped.has(key) )
|
|
|
|
return;
|
|
|
|
|
|
|
|
const k = `__${key}`,
|
|
|
|
proto = this._class.prototype,
|
|
|
|
[fn, original] = this[k];
|
|
|
|
|
|
|
|
if ( proto[key] !== fn )
|
|
|
|
throw new Error('unable to unwrap -- prototype modified');
|
|
|
|
|
|
|
|
proto[key] = original;
|
|
|
|
this[k] = undefined;
|
|
|
|
this._wrapped.delete(key);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2017-11-15 14:05:58 -05:00
|
|
|
forceUpdate() {
|
|
|
|
for(const inst of this.instances)
|
|
|
|
inst.forceUpdate();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2017-11-13 01:23:39 -05:00
|
|
|
on(event, fn, ctx) {
|
|
|
|
this._maybeWrap(event);
|
|
|
|
return super.on(event, fn, ctx);
|
|
|
|
}
|
|
|
|
|
|
|
|
prependOn(event, fn, ctx) {
|
|
|
|
this._maybeWrap(event);
|
|
|
|
return super.prependOn(event, fn, ctx);
|
|
|
|
}
|
|
|
|
|
|
|
|
once(event, fn, ctx) {
|
|
|
|
this._maybeWrap(event);
|
|
|
|
return super.once(event, fn, ctx);
|
|
|
|
}
|
|
|
|
|
|
|
|
prependOnce(event, fn, ctx) {
|
|
|
|
this._maybeWrap(event);
|
|
|
|
return super.prependOnce(event, fn, ctx);
|
|
|
|
}
|
|
|
|
|
|
|
|
many(event, ttl, fn, ctx) {
|
|
|
|
this._maybeWrap(event);
|
|
|
|
return super.many(event, ttl, fn, ctx);
|
|
|
|
}
|
|
|
|
|
|
|
|
prependMany(event, ttl, fn, ctx) {
|
|
|
|
this._maybeWrap(event);
|
|
|
|
return super.prependMany(event, ttl, fn, ctx);
|
|
|
|
}
|
|
|
|
|
|
|
|
waitFor(event) {
|
|
|
|
this._maybeWrap(event);
|
|
|
|
return super.waitFor(event);
|
|
|
|
}
|
|
|
|
}
|