text: /* @flow */
  
  import { _Vue } from '../install'
  import type Router from '../index'
  import { inBrowser } from '../util/dom'
  import { runQueue } from '../util/async'
  import { warn } from '../util/warn'
  import { START, isSameRoute } from '../util/route'
  import {
    flatten,
    flatMapComponents,
    resolveAsyncComponents
  } from '../util/resolve-components'
  import {
    createNavigationDuplicatedError,
    createNavigationCancelledError,
    createNavigationRedirectedError,
    createNavigationAbortedError,
    isError,
    isNavigationFailure,
    NavigationFailureType
  } from '../util/errors'
  
  export class History {
    router: Router
    base: string
    current: Route
    pending: ?Route
    cb: (r: Route) => void
    ready: boolean
    readyCbs: Array<Function>
    readyErrorCbs: Array<Function>
    errorCbs: Array<Function>
    listeners: Array<Function>
    cleanupListeners: Function
  
    // implemented by sub-classes
    +go: (n: number) => void
    +push: (loc: RawLocation, onComplete?: Function, onAbort?: Function) => void
    +replace: (
      loc: RawLocation,
      onComplete?: Function,
      onAbort?: Function
    ) => void
    +ensureURL: (push?: boolean) => void
    +getCurrentLocation: () => string
    +setupListeners: Function
  
    constructor (router: Router, base: ?string) {
      this.router = router
      // 把 base 处理成 '/xxx/yyy/zzz' 的格式, 
      // 如果配置项里没有提供 base，则找 <base href=""> 中的 href 属性，如果也没有，则默认为 ''
      this.base = normalizeBase(base)
      // start with a route object that stands for "nowhere"
      this.current = START
      this.pending = null
      this.ready = false
      this.readyCbs = []
      this.readyErrorCbs = []
      this.errorCbs = []
      this.listeners = []
    }
  
    listen (cb: Function) {
      this.cb = cb
    }
  
    onReady (cb: Function, errorCb: ?Function) {
      if (this.ready) {
        cb()
      } else {
        this.readyCbs.push(cb)
        if (errorCb) {
          this.readyErrorCbs.push(errorCb)
        }
      }
    }
  
    onError (errorCb: Function) {
      this.errorCbs.push(errorCb)
    }
  
    transitionTo (
      location: RawLocation,
      onComplete?: Function,
      onAbort?: Function
    ) {
      // location 是新的要路由到的路径，
      let route
      // catch redirect option https://github.com/vuejs/vue-router/issues/3201
      try {
        route = this.router.match(location, this.current)
      } catch (e) {
        this.errorCbs.forEach(cb => {
          cb(e)
        })
        // Exception should still be thrown
        throw e
      }
      this.confirmTransition(
        route,
        () => {
          const prev = this.current
          this.updateRoute(route)
          onComplete && onComplete(route)
          this.ensureURL()
          this.router.afterHooks.forEach(hook => {
            hook && hook(route, prev)
          })
  
          // fire ready cbs once
          if (!this.ready) {
            this.ready = true
            this.readyCbs.forEach(cb => {
              cb(route)
            })
          }
        },
        err => {
          if (onAbort) {
            onAbort(err)
          }
          if (err && !this.ready) {
            this.ready = true
            // Initial redirection should still trigger the onReady onSuccess
            // https://github.com/vuejs/vue-router/issues/3225
            if (!isNavigationFailure(err, NavigationFailureType.redirected)) {
              this.readyErrorCbs.forEach(cb => {
                cb(err)
              })
            } else {
              this.readyCbs.forEach(cb => {
                cb(route)
              })
            }
          }
        }
      )
    }
  
    confirmTransition (route: Route, onComplete: Function, onAbort?: Function) {
      const current = this.current
      const abort = err => {
        // changed after adding errors with
        // https://github.com/vuejs/vue-router/pull/3047 before that change,
        // redirect and aborted navigation would produce an err == null
        if (!isNavigationFailure(err) && isError(err)) {
          if (this.errorCbs.length) {
            this.errorCbs.forEach(cb => {
              cb(err)
            })
          } else {
            warn(false, 'uncaught error during route navigation:')
            console.error(err)
          }
        }
        onAbort && onAbort(err)
      }
      // 拿到即将要跳转到的新 Route 对象所匹配的 RouteRecord 对象
      const lastRouteIndex = route.matched.length - 1
      // 拿到当前 this.current 所匹配的 RouteRecord 对象
      const lastCurrentIndex = current.matched.length - 1
      if (
        // 1.先比较 route 和 current 是否相同，如果相同则
        isSameRoute(route, current) &&
        // in the case the route map has been dynamically appended to
        // 2.再比较 route 和 current 各自所匹配的 RouteRecord 是否在同一层级，
        //    如果在同一层级则
        lastRouteIndex === lastCurrentIndex &&
        // 3.再比较这俩 RouteRecord 是否是同一个
        route.matched[lastRouteIndex] === current.matched[lastCurrentIndex]
      ) {
        this.ensureURL()
        return abort(createNavigationDuplicatedError(current, route))
      }
  
      const { updated, deactivated, activated } = resolveQueue(
        this.current.matched,
        route.matched
      )
  
      const queue: Array<?NavigationGuard> = [].concat(
        // in-component leave guards
        // 从将要去激活的路由配置中按照从子 RouteRecord 到父 RouteRecord 的顺序
        // 返回这些去激活的组件中的 beforeRouteLeave 导航守卫函数
        extractLeaveGuards(deactivated),
        // global before hooks
        this.router.beforeHooks,
        // in-component update hooks
        // 从只需要更细而不需要去激活的路由配置中，按照从父 RouteRecord 到子 RouteRecord 的
        // 顺序，返回这些组件中的 beforeRouteUpdate 导航守卫函数
        extractUpdateHooks(updated),
        // in-config enter guards
        activated.map(m => m.beforeEnter),
        // async components
        resolveAsyncComponents(activated)
      )
  
      this.pending = route
      const iterator = (hook: NavigationGuard, next) => {
        if (this.pending !== route) {
          return abort(createNavigationCancelledError(current, route))
        }
        try {
          hook(route, current, (to: any) => {
            if (to === false) {
              // next(false) -> abort navigation, ensure current URL
              this.ensureURL(true)
              abort(createNavigationAbortedError(current, route))
            } else if (isError(to)) {
              this.ensureURL(true)
              abort(to)
            } else if (
              typeof to === 'string' ||
              (typeof to === 'object' &&
                (typeof to.path === 'string' || typeof to.name === 'string'))
            ) {
              // next('/') or next({ path: '/' }) -> redirect
              abort(createNavigationRedirectedError(current, route))
              if (typeof to === 'object' && to.replace) {
                this.replace(to)
              } else {
                this.push(to)
              }
            } else {
              // confirm transition and pass on the value
              next(to)
            }
          })
        } catch (e) {
          abort(e)
        }
      }
  
      runQueue(queue, iterator, () => {
        const postEnterCbs = []
        const isValid = () => this.current === route
        // wait until async components are resolved before
        // extracting in-component enter guards
        const enterGuards = extractEnterGuards(activated, postEnterCbs, isValid)
        const queue = enterGuards.concat(this.router.resolveHooks)
        runQueue(queue, iterator, () => {
          if (this.pending !== route) {
            return abort(createNavigationCancelledError(current, route))
          }
          this.pending = null
          onComplete(route)
          if (this.router.app) {
            this.router.app.$nextTick(() => {
              postEnterCbs.forEach(cb => {
                cb()
              })
            })
          }
        })
      })
    }
  
    updateRoute (route: Route) {
      this.current = route
      this.cb && this.cb(route)
    }
  
    setupListeners () {
      // Default implementation is empty
    }
  
    teardown () {
      // clean up event listeners
      // https://github.com/vuejs/vue-router/issues/2341
      this.listeners.forEach(cleanupListener => {
        cleanupListener()
      })
      this.listeners = []
  
      // reset current history route
      // https://github.com/vuejs/vue-router/issues/3294
      this.current = START
      this.pending = null
    }
  }
  
  function normalizeBase (base: ?string): string {
    if (!base) {
      if (inBrowser) {
        // respect <base> tag
        const baseEl = document.querySelector('base')
        base = (baseEl && baseEl.getAttribute('href')) || '/'
        // strip full URL origin
        base = base.replace(/^https?:\/\/[^\/]+/, '')
      } else {
        base = '/'
      }
    }
    // make sure there's the starting slash
    if (base.charAt(0) !== '/') {
      base = '/' + base
    }
    // remove trailing slash
    return base.replace(/\/$/, '')
  }
  
  function resolveQueue (
    current: Array<RouteRecord>,
    next: Array<RouteRecord>
  ): {
    updated: Array<RouteRecord>,
    activated: Array<RouteRecord>,
    deactivated: Array<RouteRecord>
  } {
    let i
    const max = Math.max(current.length, next.length)
    for (i = 0; i < max; i++) {
      if (current[i] !== next[i]) {
        break
      }
    }
    // 1.在 current 和 next 的相同 RouteRecord 之前的部分是路由变化前后所共用的路由配置，
    // 所以处于 updated，
    // 2.next 中的剩下部分是此次路由变化后不同于 current 的而且是即将被激活的路由配置
    // 3.current 中的剩下部分是不用于 next 的，将要被去激活的路由配置
    return {
      updated: next.slice(0, i),
      activated: next.slice(i),
      deactivated: current.slice(i)
    }
  }
  /**
   * 从具有父子关系的一组 RouteRecords 中，再从每个 RouteRecord 所关联的 vue 组件配置中
   * 提取出组件内的导航守卫函数，不过这里是经过闭包封装过的函数
   * @param {*} records 
   * @param {*} name 
   * @param {*} bind 
   * @param {*} reverse 
   */
  function extractGuards (
    records: Array<RouteRecord>,
    name: string,
    bind: Function,
    reverse?: boolean
  ): Array<?Function> {
    const guards = flatMapComponents(records, 
      /**
       * def: RouteRecord 中 components 对象中的 value，也就是每个路由配置所关联的vue组件配置
       * instance: RouteRecord 中 intances 对象中保存的 vue 组件实例
       * match: RouteRecord 对象本身
       * key: RouteRecord 中 components 对象中的 key
       */
      (def, instance, match, key) => {
      const guard = extractGuard(def, name)
      if (guard) {
        return Array.isArray(guard)
          ? guard.map(guard => bind(guard, instance, match, key))
          : bind(guard, instance, match, key)
      }
    })
    return flatten(reverse ? guards.reverse() : guards)
  }
  /**
   * 从 vue 组件实例中提取组件内的导航守卫
   * @param {*} def 路由配置中 components 对象中配置的，和每个路由配置相关联的 vue 组件配置
   * @param {*} key 'beforeRouteLeave', 'beforeRouteUpdate', 'beforeRouteEnter'
   *                这样的组件内的导航守卫名称
   */
  function extractGuard (
    def: Object | Function,
    key: string
  ): NavigationGuard | Array<NavigationGuard> {
    if (typeof def !== 'function') {
      // extend now so that global mixins are applied.
      // _Vue 是 install.js 中的全局Vue对象，经过全局继承后，
      // 就在处理后的 def（vue 组件实例）中拥有了 install.js 中
      // 全局混入的 beforeCreate() 和 destroyed()
      def = _Vue.extend(def)
    }
    return def.options[key]
  }
  /**
   * 从 deactivated 这些 RouteRecords 中提取出它们各自所关联的 vue 组件配置中所定义的
   * beforeRouteLeave 导航函数，并且是按照从子 RouteRecord 到父 RouteRecord 的顺序返回 
   * @param {*} deactivated 
   */
  function extractLeaveGuards (deactivated: Array<RouteRecord>): Array<?Function> {
    return extractGuards(deactivated, 'beforeRouteLeave', bindGuard, true)
  }
  /**
   * 从 updated 这些 RouteRecords 中提取出它们各自所关联的 vue 组件配置中所定义的
   * beforeRouteUpdate 导航函数，并且是按照从父 RouteRecord 到子 RouteRecord 的顺序返回
   * @param {*} updated 
   */
  function extractUpdateHooks (updated: Array<RouteRecord>): Array<?Function> {
    return extractGuards(updated, 'beforeRouteUpdate', bindGuard)
  }
  /**
   * 
   * @param {*} guard vue 组件内的导航守卫函数
   * @param {*} instance vue 组件实例
   */
  function bindGuard (guard: NavigationGuard, instance: ?_Vue): ?NavigationGuard {
    if (instance) {
      return function boundRouteGuard () {
        return guard.apply(instance, arguments)
      }
    }
  }
  
  function extractEnterGuards (
    activated: Array<RouteRecord>,
    cbs: Array<Function>,
    isValid: () => boolean
  ): Array<?Function> {
    return extractGuards(
      activated,
      'beforeRouteEnter',
      (guard, _, match, key) => {
        return bindEnterGuard(guard, match, key, cbs, isValid)
      }
    )
  }
  
  function bindEnterGuard (
    guard: NavigationGuard,
    match: RouteRecord,
    key: string,
    cbs: Array<Function>,
    isValid: () => boolean
  ): NavigationGuard {
    return function routeEnterGuard (to, from, next) {
      return guard(to, from, cb => {
        if (typeof cb === 'function') {
          cbs.push(() => {
            // #750
            // if a router-view is wrapped with an out-in transition,
            // the instance may not have been registered at this time.
            // we will need to poll for registration until current route
            // is no longer valid.
            poll(cb, match.instances, key, isValid)
          })
        }
        next(cb)
      })
    }
  }
  
  function poll (
    cb: any, // somehow flow cannot infer this is a function
    instances: Object,
    key: string,
    isValid: () => boolean
  ) {
    if (
      instances[key] &&
      !instances[key]._isBeingDestroyed // do not reuse being destroyed instance
    ) {
      cb(instances[key])
    } else if (isValid()) {
      setTimeout(() => {
        poll(cb, instances, key, isValid)
      }, 16)
    }
  }
  