text: /* @flow */
  
  import config from "../config";
  import Watcher from "../observer/watcher";
  import { mark, measure } from "../util/perf";
  import { createEmptyVNode } from "../vdom/vnode";
  import { updateComponentListeners } from "./events";
  import { resolveSlots } from "./render-helpers/resolve-slots";
  import { toggleObserving } from "../observer/index";
  import { pushTarget, popTarget } from "../observer/dep";
  
  import {
    warn,
    noop,
    remove,
    emptyObject,
    validateProp,
    invokeWithErrorHandling,
  } from "../util/index";
  
  export let activeInstance: any = null;
  export let isUpdatingChildComponent: boolean = false;
  
  export function setActiveInstance(vm: Component) {
    const prevActiveInstance = activeInstance;
    activeInstance = vm;
    return () => {
      activeInstance = prevActiveInstance;
    };
  }
  
  export function initLifecycle(vm: Component) {
    const options = vm.$options;
  
    // locate first non-abstract parent
    let parent = options.parent;
    if (parent && !options.abstract) {
      while (parent.$options.abstract && parent.$parent) {
        parent = parent.$parent;
      }
      parent.$children.push(vm);
    }
  
    vm.$parent = parent;
    vm.$root = parent ? parent.$root : vm;
  
    vm.$children = [];
    vm.$refs = {};
  
    vm._watcher = null;
    vm._inactive = null;
    vm._directInactive = false;
    vm._isMounted = false;
    vm._isDestroyed = false;
    vm._isBeingDestroyed = false;
  }
  
  export function lifecycleMixin(Vue: Class<Component>) {
    Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) {
      const vm: Component = this;
      const prevEl = vm.$el;
      const prevVnode = vm._vnode;
      const restoreActiveInstance = setActiveInstance(vm);
      vm._vnode = vnode;
      // Vue.prototype.__patch__ is injected in entry points
      // based on the rendering backend used.
      if (!prevVnode) {
        // initial render
        vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */);
      } else {
        // updates
        vm.$el = vm.__patch__(prevVnode, vnode);
      }
      restoreActiveInstance();
      // update __vue__ reference
      if (prevEl) {
        prevEl.__vue__ = null;
      }
      if (vm.$el) {
        vm.$el.__vue__ = vm;
      }
      // if parent is an HOC, update its $el as well
      if (vm.$vnode && vm.$parent && vm.$vnode === vm.$parent._vnode) {
        vm.$parent.$el = vm.$el;
      }
      // updated hook is called by the scheduler to ensure that children are
      // updated in a parent's updated hook.
    };
  
    Vue.prototype.$forceUpdate = function () {
      const vm: Component = this;
      if (vm._watcher) {
        vm._watcher.update();
      }
    };
  
    Vue.prototype.$destroy = function () {
      const vm: Component = this;
      if (vm._isBeingDestroyed) {
        return;
      }
      callHook(vm, "beforeDestroy");
      vm._isBeingDestroyed = true;
      // remove self from parent
      const parent = vm.$parent;
      if (parent && !parent._isBeingDestroyed && !vm.$options.abstract) {
        remove(parent.$children, vm);
      }
      // teardown watchers
      if (vm._watcher) {
        vm._watcher.teardown();
      }
      let i = vm._watchers.length;
      while (i--) {
        vm._watchers[i].teardown();
      }
      // remove reference from data ob
      // frozen object may not have observer.
      if (vm._data.__ob__) {
        vm._data.__ob__.vmCount--;
      }
      // call the last hook...
      vm._isDestroyed = true;
      // invoke destroy hooks on current rendered tree
      vm.__patch__(vm._vnode, null);
      // fire destroyed hook
      callHook(vm, "destroyed");
      // turn off all instance listeners.
      vm.$off();
      // remove __vue__ reference
      if (vm.$el) {
        vm.$el.__vue__ = null;
      }
      // release circular reference (#6759)
      if (vm.$vnode) {
        vm.$vnode.parent = null;
      }
    };
  }
  
  export function mountComponent(
    vm: Component,
    el: ?Element,
    hydrating?: boolean
  ): Component {
    vm.$el = el;
    if (!vm.$options.render) {
      vm.$options.render = createEmptyVNode;
      if (process.env.NODE_ENV !== "production") {
        /* istanbul ignore if */
        if (
          (vm.$options.template && vm.$options.template.charAt(0) !== "#") ||
          vm.$options.el ||
          el
        ) {
          warn(
            "You are using the runtime-only build of Vue where the template " +
              "compiler is not available. Either pre-compile the templates into " +
              "render functions, or use the compiler-included build.",
            vm
          );
        } else {
          warn(
            "Failed to mount component: template or render function not defined.",
            vm
          );
        }
      }
    }
    callHook(vm, "beforeMount");
  
    let updateComponent;
    /* istanbul ignore if */
    if (process.env.NODE_ENV !== "production" && config.performance && mark) {
      updateComponent = () => {
        const name = vm._name;
        const id = vm._uid;
        const startTag = `vue-perf-start:${id}`;
        const endTag = `vue-perf-end:${id}`;
  
        mark(startTag);
        const vnode = vm._render();
        mark(endTag);
        measure(`vue ${name} render`, startTag, endTag);
  
        mark(startTag);
        vm._update(vnode, hydrating);
        mark(endTag);
        measure(`vue ${name} patch`, startTag, endTag);
      };
    } else {
      updateComponent = () => {
        vm._update(vm._render(), hydrating);
      };
    }
  
    // we set this to vm._watcher inside the watcher's constructor
    // since the watcher's initial patch may call $forceUpdate (e.g. inside child
    // component's mounted hook), which relies on vm._watcher being already defined
    new Watcher(
      vm,
      updateComponent,
      noop,
      {
        before() {
          if (vm._isMounted && !vm._isDestroyed) {
            callHook(vm, "beforeUpdate");
          }
        },
      },
      true /* isRenderWatcher */
    );
    hydrating = false;
  
    // manually mounted instance, call mounted on self
    // mounted is called for render-created child components in its inserted hook
    if (vm.$vnode == null) {
      vm._isMounted = true;
      callHook(vm, "mounted");
    }
    return vm;
  }
  
  export function updateChildComponent(
    vm: Component,
    propsData: ?Object,
    listeners: ?Object,
    parentVnode: MountedComponentVNode,
    renderChildren: ?Array<VNode>
  ) {
    if (process.env.NODE_ENV !== "production") {
      isUpdatingChildComponent = true;
    }
  
    // determine whether component has slot children
    // we need to do this before overwriting $options._renderChildren.
  
    // check if there are dynamic scopedSlots (hand-written or compiled but with
    // dynamic slot names). Static scoped slots compiled from template has the
    // "$stable" marker.
    const newScopedSlots = parentVnode.data.scopedSlots;
    const oldScopedSlots = vm.$scopedSlots;
    const hasDynamicScopedSlot = !!(
      (newScopedSlots && !newScopedSlots.$stable) ||
      (oldScopedSlots !== emptyObject && !oldScopedSlots.$stable) ||
      (newScopedSlots && vm.$scopedSlots.$key !== newScopedSlots.$key) ||
      (!newScopedSlots && vm.$scopedSlots.$key)
    );
  
    // Any static slot children from the parent may have changed during parent's
    // update. Dynamic scoped slots may also have changed. In such cases, a forced
    // update is necessary to ensure correctness.
    const needsForceUpdate = !!(
      renderChildren || // has new static slots
      vm.$options._renderChildren || // has old static slots
      hasDynamicScopedSlot
    );
  
    vm.$options._parentVnode = parentVnode;
    vm.$vnode = parentVnode; // update vm's placeholder node without re-render
  
    if (vm._vnode) {
      // update child tree's parent
      vm._vnode.parent = parentVnode;
    }
    vm.$options._renderChildren = renderChildren;
  
    // update $attrs and $listeners hash
    // these are also reactive so they may trigger child update if the child
    // used them during render
    vm.$attrs = parentVnode.data.attrs || emptyObject;
    vm.$listeners = listeners || emptyObject;
  
    // update props
    if (propsData && vm.$options.props) {
      toggleObserving(false);
      const props = vm._props;
      const propKeys = vm.$options._propKeys || [];
      for (let i = 0; i < propKeys.length; i++) {
        const key = propKeys[i];
        const propOptions: any = vm.$options.props; // wtf flow?
        props[key] = validateProp(key, propOptions, propsData, vm);
      }
      toggleObserving(true);
      // keep a copy of raw propsData
      vm.$options.propsData = propsData;
    }
  
    // update listeners
    listeners = listeners || emptyObject;
    const oldListeners = vm.$options._parentListeners;
    vm.$options._parentListeners = listeners;
    updateComponentListeners(vm, listeners, oldListeners);
  
    // resolve slots + force update if has children
    if (needsForceUpdate) {
      vm.$slots = resolveSlots(renderChildren, parentVnode.context);
      vm.$forceUpdate();
    }
  
    if (process.env.NODE_ENV !== "production") {
      isUpdatingChildComponent = false;
    }
  }
  
  function isInInactiveTree(vm) {
    while (vm && (vm = vm.$parent)) {
      if (vm._inactive) return true;
    }
    return false;
  }
  
  export function activateChildComponent(vm: Component, direct?: boolean) {
    if (direct) {
      vm._directInactive = false;
      if (isInInactiveTree(vm)) {
        return;
      }
    } else if (vm._directInactive) {
      return;
    }
    if (vm._inactive || vm._inactive === null) {
      vm._inactive = false;
      for (let i = 0; i < vm.$children.length; i++) {
        activateChildComponent(vm.$children[i]);
      }
      callHook(vm, "activated");
    }
  }
  
  export function deactivateChildComponent(vm: Component, direct?: boolean) {
    if (direct) {
      vm._directInactive = true;
      if (isInInactiveTree(vm)) {
        return;
      }
    }
    if (!vm._inactive) {
      vm._inactive = true;
      for (let i = 0; i < vm.$children.length; i++) {
        deactivateChildComponent(vm.$children[i]);
      }
      callHook(vm, "deactivated");
    }
  }
  
  export function callHook(vm: Component, hook: string) {
    // #7573 disable dep collection when invoking lifecycle hooks
    pushTarget();
    const handlers = vm.$options[hook];
    const info = `${hook} hook`;
    if (handlers) {
      for (let i = 0, j = handlers.length; i < j; i++) {
        invokeWithErrorHandling(handlers[i], vm, null, vm, info);
      }
    }
    if (vm._hasHookEvent) {
      vm.$emit("hook:" + hook);
    }
    popTarget();
  }
  