Skip to content

Commit 7693eb5

Browse files
fix(ssr): memory leak in poll method (#2875)
Co-authored-by: Eduardo San Martin Morote <[email protected]> Co-authored-by: Eduardo San Martin Morote <[email protected]>
1 parent f597959 commit 7693eb5

File tree

7 files changed

+60
-49
lines changed

7 files changed

+60
-49
lines changed

examples/navigation-guards/app.js

+19-9
Original file line numberDiff line numberDiff line change
@@ -91,15 +91,25 @@ const Quux = {
9191
}
9292

9393
const NestedParent = {
94-
template: `<div id="nested-parent">Nested Parent <hr>
95-
<router-link to="/parent/child/1">/parent/child/1</router-link>
96-
<router-link to="/parent/child/2">/parent/child/2</router-link>
97-
<hr>
98-
<p id="bre-order">
99-
<span v-for="log in logs">{{ log }} </span>
100-
</p>
101-
102-
<router-view/></div>`,
94+
template: `
95+
<div id="nested-parent">
96+
Nested Parent
97+
<hr>
98+
<router-link to="/parent/child/1">/parent/child/1</router-link>
99+
<router-link to="/parent/child/2">/parent/child/2</router-link>
100+
<hr>
101+
<p id="bre-order">
102+
<span v-for="log in logs">{{ log }} </span>
103+
</p>
104+
<!-- #705 -->
105+
<!-- Some transitions, specifically out-in transitions, cause the view -->
106+
<!-- that is transitioning in to appear significantly later than normal, -->
107+
<!-- which can cause bugs as "vm" below in "next(vm => ...)" may not be -->
108+
<!-- available at the time the callback is called -->
109+
<transition name="fade" mode="out-in">
110+
<router-view :key="$route.path"/>
111+
</transition>
112+
</div>`,
103113
data: () => ({ logs: [] }),
104114
beforeRouteEnter (to, from, next) {
105115
next(vm => {

examples/navigation-guards/index.html

+8
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
11
<!DOCTYPE html>
22
<link rel="stylesheet" href="/global.css">
3+
<style>
4+
.fade-enter-active, .fade-leave-active {
5+
transition: opacity .5s ease;
6+
}
7+
.fade-enter, .fade-leave-active {
8+
opacity: 0;
9+
}
10+
</style>
311
<a href="/">&larr; Examples index</a>
412
<div id="app"></div>
513
<script src="/__build__/shared.chunk.js"></script>

flow/declarations.js

+1
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ declare type RouteRecord = {
6868
regex: RouteRegExp;
6969
components: Dictionary<any>;
7070
instances: Dictionary<any>;
71+
enteredCbs: Dictionary<Array<Function>>;
7172
name: ?string;
7273
parent: ?RouteRecord;
7374
redirect: ?RedirectOption;

src/components/view.js

+6
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { warn } from '../util/warn'
22
import { extend } from '../util/misc'
3+
import { handleRouteEntered } from '../util/route'
34

45
export default {
56
name: 'RouterView',
@@ -94,6 +95,11 @@ export default {
9495
) {
9596
matched.instances[name] = vnode.componentInstance
9697
}
98+
99+
// if the route transition has already been confirmed then we weren't
100+
// able to call the cbs during confirmation as the component was not
101+
// registered yet, so we call it here.
102+
handleRouteEntered(route)
97103
}
98104

99105
const configProps = matched.props && matched.props[name]

src/create-route-map.js

+1
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ function addRouteRecord (
8585
regex: compileRouteRegex(normalizedPath, pathToRegexpOptions),
8686
components: route.components || { default: route.component },
8787
instances: {},
88+
enteredCbs: {},
8889
name,
8990
parent,
9091
matchAs,

src/history/base.js

+10-40
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import type Router from '../index'
55
import { inBrowser } from '../util/dom'
66
import { runQueue } from '../util/async'
77
import { warn } from '../util/warn'
8-
import { START, isSameRoute } from '../util/route'
8+
import { START, isSameRoute, handleRouteEntered } from '../util/route'
99
import {
1010
flatten,
1111
flatMapComponents,
@@ -218,11 +218,9 @@ export class History {
218218
}
219219

220220
runQueue(queue, iterator, () => {
221-
const postEnterCbs = []
222-
const isValid = () => this.current === route
223221
// wait until async components are resolved before
224222
// extracting in-component enter guards
225-
const enterGuards = extractEnterGuards(activated, postEnterCbs, isValid)
223+
const enterGuards = extractEnterGuards(activated)
226224
const queue = enterGuards.concat(this.router.resolveHooks)
227225
runQueue(queue, iterator, () => {
228226
if (this.pending !== route) {
@@ -232,9 +230,7 @@ export class History {
232230
onComplete(route)
233231
if (this.router.app) {
234232
this.router.app.$nextTick(() => {
235-
postEnterCbs.forEach(cb => {
236-
cb()
237-
})
233+
handleRouteEntered(route)
238234
})
239235
}
240236
})
@@ -352,57 +348,31 @@ function bindGuard (guard: NavigationGuard, instance: ?_Vue): ?NavigationGuard {
352348
}
353349

354350
function extractEnterGuards (
355-
activated: Array<RouteRecord>,
356-
cbs: Array<Function>,
357-
isValid: () => boolean
351+
activated: Array<RouteRecord>
358352
): Array<?Function> {
359353
return extractGuards(
360354
activated,
361355
'beforeRouteEnter',
362356
(guard, _, match, key) => {
363-
return bindEnterGuard(guard, match, key, cbs, isValid)
357+
return bindEnterGuard(guard, match, key)
364358
}
365359
)
366360
}
367361

368362
function bindEnterGuard (
369363
guard: NavigationGuard,
370364
match: RouteRecord,
371-
key: string,
372-
cbs: Array<Function>,
373-
isValid: () => boolean
365+
key: string
374366
): NavigationGuard {
375367
return function routeEnterGuard (to, from, next) {
376368
return guard(to, from, cb => {
377369
if (typeof cb === 'function') {
378-
cbs.push(() => {
379-
// #750
380-
// if a router-view is wrapped with an out-in transition,
381-
// the instance may not have been registered at this time.
382-
// we will need to poll for registration until current route
383-
// is no longer valid.
384-
poll(cb, match.instances, key, isValid)
385-
})
370+
if (!match.enteredCbs[key]) {
371+
match.enteredCbs[key] = []
372+
}
373+
match.enteredCbs[key].push(cb)
386374
}
387375
next(cb)
388376
})
389377
}
390378
}
391-
392-
function poll (
393-
cb: any, // somehow flow cannot infer this is a function
394-
instances: Object,
395-
key: string,
396-
isValid: () => boolean
397-
) {
398-
if (
399-
instances[key] &&
400-
!instances[key]._isBeingDestroyed // do not reuse being destroyed instance
401-
) {
402-
cb(instances[key])
403-
} else if (isValid()) {
404-
setTimeout(() => {
405-
poll(cb, instances, key, isValid)
406-
}, 16)
407-
}
408-
}

src/util/route.js

+15
Original file line numberDiff line numberDiff line change
@@ -132,3 +132,18 @@ function queryIncludes (current: Dictionary<string>, target: Dictionary<string>)
132132
}
133133
return true
134134
}
135+
136+
export function handleRouteEntered (route: Route) {
137+
for (let i = 0; i < route.matched.length; i++) {
138+
const record = route.matched[i]
139+
for (const name in record.instances) {
140+
const instance = record.instances[name]
141+
const cbs = record.enteredCbs[name]
142+
if (!instance || !cbs) continue
143+
delete record.enteredCbs[name]
144+
for (let i = 0; i < cbs.length; i++) {
145+
if (!instance._isBeingDestroyed) cbs[i](instance)
146+
}
147+
}
148+
}
149+
}

0 commit comments

Comments
 (0)