Fix crash on android where we queue few back navigations an exception is thrown.

Fix https://github.com/NativeScript/NativeScript/issues/4986
This commit is contained in:
Hristo Hristov
2017-11-10 13:40:38 +02:00
parent edb0eb1721
commit 28f1a5875e
9 changed files with 190 additions and 46 deletions

View File

@@ -114,17 +114,17 @@ function runAsync(testInfo: TestInfoEntry, recursiveIndex: number, testTimeout?:
duration = time() - testStartTime;
testInfo.duration = duration;
if (isDone) {
write(`--- [${testInfo.testName}] OK, duration: ${duration}`, trace.messageType.info);
write(`--- [${testInfo.testName}] OK, duration: ${duration.toFixed(2)}`, trace.messageType.info);
testInfo.isPassed = true;
runTests(testsQueue, recursiveIndex + 1);
} else if (error) {
write(`--- [${testInfo.testName}] FAILED: ${error.message}, duration: ${duration}`, trace.messageType.error);
write(`--- [${testInfo.testName}] FAILED: ${error.message}, duration: ${duration.toFixed(2)}`, trace.messageType.error);
testInfo.errorMessage = error.message;
runTests(testsQueue, recursiveIndex + 1);
} else {
const testEndTime = time();
if (testEndTime - testStartTime > timeout) {
write(`--- [${testInfo.testName}] TIMEOUT, duration: ${duration}`, trace.messageType.error);
write(`--- [${testInfo.testName}] TIMEOUT, duration: ${duration.toFixed(2)}`, trace.messageType.error);
testInfo.errorMessage = "Test timeout.";
runTests(testsQueue, recursiveIndex + 1);
} else {

View File

@@ -1,43 +1,150 @@
// >> frame-require
import * as frameModule from "tns-core-modules/ui/frame";
var topmost = frameModule.topmost();
import { topmost, NavigationEntry } from "tns-core-modules/ui/frame";
// << frame-require
import * as labelModule from "tns-core-modules/ui/label";
import * as pagesModule from "tns-core-modules/ui/page";
import { Label } from "tns-core-modules/ui/label";
import { Page } from "tns-core-modules/ui/page";
import * as helper from "../helper";
import * as TKUnit from "../../TKUnit";
export var ignore_test_DummyTestForSnippetOnly0 = function () {
export function ignore_test_DummyTestForSnippetOnly0() {
// >> frame-navigating
topmost.navigate("details-page");
const frame = topmost();
frame.navigate("details-page");
// << frame-navigating
}
export var ignore_test_DummyTestForSnippetOnly1 = function () {
export function ignore_test_DummyTestForSnippetOnly1() {
// >> frame-factory-func
var factoryFunc = function () {
var label = new labelModule.Label();
const func = function () {
const label = new Label();
label.text = "Hello, world!";
var page = new pagesModule.Page();
const page = new Page();
page.content = label;
return page;
};
topmost.navigate(factoryFunc);
const frame = topmost();
frame.navigate(func);
// <<frame-factory-func
}
export var ignore_test_DummyTestForSnippetOnly2 = function () {
export function ignore_test_DummyTestForSnippetOnly2() {
// >> frame-naventry
var navigationEntry = {
const navigationEntry = {
moduleName: "details-page",
context: { info: "something you want to pass to your page" },
animated: false
};
topmost.navigate(navigationEntry);
const frame = topmost();
frame.navigate(navigationEntry);
// << frame-naventry
}
export var ignore_test_DummyTestForSnippetOnly3 = function () {
export function ignore_test_DummyTestForSnippetOnly3() {
// >> frame-naventrycontext
const navigationEntry: NavigationEntry = {
moduleName: "details-page",
bindingContext: { info: "something you want to pass as binding context to your page" },
animated: false
};
const frame = topmost();
frame.navigate(navigationEntry);
// << frame-naventrycontext
}
export function ignore_test_DummyTestForSnippetOnly4() {
// >> frame-back
topmost.goBack();
const frame = topmost();
frame.goBack();
// << frame-back
}
export function test_can_go_back() {
const frame = topmost();
frame.navigate({ create: () => new Page(), clearHistory: true });
TKUnit.waitUntilReady(() => frame.navigationQueueIsEmpty());
frame.navigate(() => new Page());
frame.navigate(() => new Page());
frame.navigate({ create: () => new Page(), backstackVisible: false });
frame.navigate(() => new Page());
TKUnit.assertTrue(frame.canGoBack(), '1');
frame.goBack();
TKUnit.assertTrue(frame.canGoBack(), '2');
frame.goBack();
TKUnit.assertTrue(frame.canGoBack(), '3');
frame.goBack();
TKUnit.assertFalse(frame.canGoBack(), '4');
frame.goBack();
frame.navigate({ create: () => new Page(), backstackVisible: false });
frame.navigate(() => new Page());
TKUnit.assertTrue(frame.canGoBack(), '5');
frame.goBack();
TKUnit.assertFalse(frame.canGoBack(), '6');
frame.goBack();
frame.navigate(() => new Page());
frame.navigate({ create: () => new Page(), clearHistory: true });
TKUnit.assertFalse(frame.canGoBack(), '7');
frame.goBack();
frame.navigate(() => new Page());
frame.navigate({ create: () => new Page(), backstackVisible: false });
TKUnit.assertTrue(frame.canGoBack(), '8');
frame.goBack();
TKUnit.assertTrue(frame.canGoBack(), '9');
frame.goBack();
TKUnit.assertFalse(frame.canGoBack(), '10');
frame.goBack();
frame.navigate(() => new Page());
frame.navigate({ create: () => new Page(), clearHistory: true });
frame.navigate({ create: () => new Page(), backstackVisible: false });
TKUnit.assertTrue(frame.canGoBack(), '11');
frame.goBack();
TKUnit.assertFalse(frame.canGoBack(), '12');
frame.goBack();
frame.navigate({ create: () => new Page(), clearHistory: true });
frame.navigate({ create: () => new Page(), backstackVisible: false });
frame.navigate(() => new Page());
TKUnit.assertTrue(frame.canGoBack(), '13');
frame.goBack();
TKUnit.assertFalse(frame.canGoBack(), '14');
frame.goBack();
TKUnit.waitUntilReady(() => frame.navigationQueueIsEmpty());
}
export function test_go_back_to_backstack_entry() {
const frame = topmost();
frame.navigate(() => new Page());
TKUnit.waitUntilReady(() => frame.navigationQueueIsEmpty());
frame.navigate(() => new Page());
frame.navigate(() => new Page());
frame.navigate({ create: () => new Page(), backstackVisible: false });
frame.navigate(() => new Page());
TKUnit.assertTrue(frame.canGoBack(), '1');
frame.goBack(frame.backStack[0]);
TKUnit.assertFalse(frame.canGoBack(), '2');
frame.goBack();
TKUnit.waitUntilReady(() => frame.navigationQueueIsEmpty());
}

View File

@@ -2,6 +2,8 @@ import * as frameModule from "tns-core-modules/ui/frame";
import * as TKUnit from "../../TKUnit";
import { unsetValue, PercentLength } from "tns-core-modules/ui/core/view";
export * from "./frame-tests-common";
export function test_percent_width_and_height_set_to_page_support() {
let topFrame = frameModule.topmost();
let currentPage = topFrame.currentPage;

View File

@@ -0,0 +1 @@
export * from "./frame-tests-common";

View File

@@ -199,7 +199,42 @@ export class FrameBase extends CustomLayoutView implements FrameDefinition {
}
public canGoBack(): boolean {
return this._backStack.length > 0;
let backstack = this._backStack.length;
let previousForwardNotInBackstack = false;
this._navigationQueue.forEach((item, i, array) => {
if (item.entry !== this._currentEntry) {
if (item.isBackNavigation) {
previousForwardNotInBackstack = false;
if (!item.entry) {
backstack--;
} else {
const backstackIndex = this._backStack.indexOf(item.entry);
if (backstackIndex !== -1) {
backstack = backstackIndex;
} else {
// NOTE: We don't search for entries in navigationQueue because there is no way for
// developer to get reference to BackstackEntry unless transition is completed.
// At that point the entry is put in the backstack array.
// If we start to return Backstack entry from navigate method then
// here we should check also navigationQueue as well.
backstack--;
}
}
} else if (item.entry.entry.clearHistory) {
previousForwardNotInBackstack = false;
backstack = 0;
} else {
backstack++;
if (previousForwardNotInBackstack) {
backstack--;
}
previousForwardNotInBackstack = item.entry.entry.backstackVisible === false;
}
}
});
return backstack > 0;
}
/**
@@ -232,8 +267,7 @@ export class FrameBase extends CustomLayoutView implements FrameDefinition {
if (this._navigationQueue.length === 1) {
this._processNavigationContext(navigationContext);
}
else {
} else {
if (traceEnabled()) {
traceWrite(`Going back scheduled`, traceCategories.Navigation);
}
@@ -321,14 +355,22 @@ export class FrameBase extends CustomLayoutView implements FrameDefinition {
this._currentEntry = entry;
}
public _updateBackstack(entry: BackstackEntry): void {
if (entry.entry.clearHistory) {
this._backStack.length = 0;
} else if (FrameBase._isEntryBackstackVisible(this._currentEntry)) {
this._backStack.push(this._currentEntry);
}
}
public _processNavigationQueue(page: Page) {
if (this._navigationQueue.length === 0) {
// This could happen when showing recreated page after activity has been destroyed.
return;
}
let entry = this._navigationQueue[0].entry;
let currentNavigationPage = entry.resolvedPage;
const entry = this._navigationQueue[0].entry;
const currentNavigationPage = entry.resolvedPage;
if (page !== currentNavigationPage) {
// If the page is not the one that requested navigation - skip it.
return;
@@ -388,21 +430,9 @@ export class FrameBase extends CustomLayoutView implements FrameDefinition {
}
}
public _clearBackStack(): void {
this._backStack.length = 0;
}
@profile
private performNavigation(navigationContext: NavigationContext) {
let navContext = navigationContext.entry;
// TODO: This should happen once navigation is completed.
if (navigationContext.entry.entry.clearHistory) {
// Don't clear backstack immediately or we can't remove pages from frame.
} else if (FrameBase._isEntryBackstackVisible(this._currentEntry)) {
this._backStack.push(this._currentEntry);
}
const navContext = navigationContext.entry;
this._onNavigatingTo(navContext, navigationContext.isBackNavigation);
this._navigateCore(navContext);
}
@@ -419,7 +449,7 @@ export class FrameBase extends CustomLayoutView implements FrameDefinition {
this._backStack.pop();
this._removeBackstackEntries(removed);
}
this._onNavigatingTo(backstackEntry, true);
this._goBackCore(backstackEntry);
}

View File

@@ -89,6 +89,9 @@ export class Frame extends FrameBase {
public setCurrent(entry: BackstackEntry): void {
if (this._currentEntry !== entry) {
if (!this._isBack) {
this._updateBackstack(entry);
}
this.changeCurrentPage(entry);
}
@@ -172,7 +175,6 @@ export class Frame extends FrameBase {
_setAndroidFragmentTransitions(animated, navigationTransition, currentFragment, newFragment, transaction, manager);
if (clearHistory) {
destroyPages(this.backStack, true);
this._clearBackStack();
}
if (currentFragment && animated && !navigationTransition) {

View File

@@ -139,7 +139,7 @@ export class Frame extends View {
/**
* @private
*/
_clearBackStack(): void;
_updateBackstack(entry: BackstackEntry): void;
/**
* @private
*/

View File

@@ -50,7 +50,6 @@ export class Frame extends FrameBase {
let clearHistory = backstackEntry.entry.clearHistory;
if (clearHistory) {
this._clearBackStack();
navDepth = -1;
}
navDepth++;

View File

@@ -102,8 +102,6 @@ class UIViewControllerImpl extends UIViewController {
if (!owner.parent) {
if (!frame._currentEntry) {
frame._currentEntry = newEntry;
} else {
frame._navigateToEntry = newEntry;
}
owner._frame = frame;
@@ -142,14 +140,19 @@ class UIViewControllerImpl extends UIViewController {
// Skip navigation events if modal page is shown.
if (!owner._presentedViewController && frame) {
const newEntry = this[ENTRY];
let isBack = isBackNavigationTo(owner, newEntry);
let isBack: boolean;
// We are on the current page which happens when navigation is canceled so isBack should be false.
if (frame.currentPage === owner && frame._navigationQueue.length === 0) {
isBack = false;
} else {
isBack = isBackNavigationTo(owner, newEntry);
}
frame._navigateToEntry = null;
frame._currentEntry = newEntry;
if (!isBack) {
frame._updateBackstack(newEntry);
}
frame.setCurrent(newEntry);
owner.onNavigatedTo(isBack);
// If page was shown with custom animation - we need to set the navigationController.delegate to the animatedDelegate.