mirror of
				https://github.com/NativeScript/NativeScript.git
				synced 2025-11-04 04:18:52 +08:00 
			
		
		
		
	
		
			
				
	
	
		
			813 lines
		
	
	
		
			22 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			813 lines
		
	
	
		
			22 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
import type { BackstackEntry, NavigationContext, NavigationEntry, NavigationTransition } from './frame-interfaces';
 | 
						|
import { NavigationType } from './frame-interfaces';
 | 
						|
import { Page } from '../page';
 | 
						|
import { View, CustomLayoutView, CSSType } from '../core/view';
 | 
						|
import { Property } from '../core/properties';
 | 
						|
import { Trace } from '../../trace';
 | 
						|
import { frameStack, topmost as frameStackTopmost, _pushInFrameStack, _popFromFrameStack, _removeFromFrameStack, _isFrameStackEmpty } from './frame-stack';
 | 
						|
import { viewMatchesModuleContext } from '../core/view/view-common';
 | 
						|
import { getAncestor } from '../core/view-base';
 | 
						|
import { Builder } from '../builder';
 | 
						|
import { sanitizeModuleName } from '../../utils/common';
 | 
						|
import { profile } from '../../profiling';
 | 
						|
import { FRAME_SYMBOL } from './frame-helpers';
 | 
						|
import { SharedTransition } from '../transition/shared-transition';
 | 
						|
import { NavigationData } from '.';
 | 
						|
 | 
						|
export { NavigationType } from './frame-interfaces';
 | 
						|
export type { AndroidActivityCallbacks, AndroidFragmentCallbacks, AndroidFrame, BackstackEntry, NavigationContext, NavigationEntry, NavigationTransition, TransitionState, ViewEntry, iOSFrame } from './frame-interfaces';
 | 
						|
 | 
						|
function buildEntryFromArgs(arg: any): NavigationEntry {
 | 
						|
	let entry: NavigationEntry;
 | 
						|
	if (typeof arg === 'string') {
 | 
						|
		entry = {
 | 
						|
			moduleName: arg,
 | 
						|
		};
 | 
						|
	} else if (typeof arg === 'function') {
 | 
						|
		entry = {
 | 
						|
			create: arg,
 | 
						|
		};
 | 
						|
	} else {
 | 
						|
		entry = arg;
 | 
						|
	}
 | 
						|
 | 
						|
	return entry;
 | 
						|
}
 | 
						|
 | 
						|
@CSSType('Frame')
 | 
						|
export class FrameBase extends CustomLayoutView {
 | 
						|
	public static navigatingToEvent = 'navigatingTo';
 | 
						|
	public static navigatedToEvent = 'navigatedTo';
 | 
						|
 | 
						|
	private _animated: boolean;
 | 
						|
	private _transition: NavigationTransition;
 | 
						|
	private _backStack = new Array<BackstackEntry>();
 | 
						|
	private _navigationQueue = new Array<NavigationContext>();
 | 
						|
 | 
						|
	public actionBarVisibility: 'auto' | 'never' | 'always';
 | 
						|
	public _currentEntry: BackstackEntry;
 | 
						|
 | 
						|
	/**
 | 
						|
	 * A reference of current page that is set earlier than current entry.
 | 
						|
	 * Using this property, methods like 'eachChildView' and '_childrenCount' gain access to page view
 | 
						|
	 * just in time for calls like '_addView' to perform view-tree iterations.
 | 
						|
	 */
 | 
						|
	public _resolvedPage: Page;
 | 
						|
 | 
						|
	public _animationInProgress = false;
 | 
						|
	public _executingContext: NavigationContext;
 | 
						|
	public _isInFrameStack = false;
 | 
						|
	public static defaultAnimatedNavigation = true;
 | 
						|
	public static defaultTransition: NavigationTransition;
 | 
						|
 | 
						|
	static getFrameById(id: string): FrameBase {
 | 
						|
		return frameStack.find((frame) => frame.id && frame.id === id);
 | 
						|
	}
 | 
						|
 | 
						|
	static topmost(): FrameBase {
 | 
						|
		return frameStackTopmost();
 | 
						|
	}
 | 
						|
 | 
						|
	static goBack(): boolean {
 | 
						|
		const top = FrameBase.topmost();
 | 
						|
		if (top && top.canGoBack()) {
 | 
						|
			top.goBack();
 | 
						|
 | 
						|
			return true;
 | 
						|
		} else if (top) {
 | 
						|
			let parentFrameCanGoBack = false;
 | 
						|
			let parentFrame = getAncestor(top, 'Frame');
 | 
						|
 | 
						|
			while (parentFrame && !parentFrameCanGoBack) {
 | 
						|
				if (parentFrame && parentFrame.canGoBack()) {
 | 
						|
					parentFrameCanGoBack = true;
 | 
						|
				} else {
 | 
						|
					parentFrame = getAncestor(parentFrame, 'Frame');
 | 
						|
				}
 | 
						|
			}
 | 
						|
 | 
						|
			if (parentFrame && parentFrameCanGoBack) {
 | 
						|
				parentFrame.goBack();
 | 
						|
 | 
						|
				return true;
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		if (frameStack.length > 1) {
 | 
						|
			top._popFromFrameStack();
 | 
						|
		}
 | 
						|
 | 
						|
		return false;
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * @private
 | 
						|
	 */
 | 
						|
	static reloadPage(): void {
 | 
						|
		// Implemented in plat-specific file - only for android.
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * @private
 | 
						|
	 */
 | 
						|
	static _stack(): Array<FrameBase> {
 | 
						|
		return frameStack;
 | 
						|
	}
 | 
						|
 | 
						|
	// TODO: Currently our navigation will not be synchronized in case users directly call native navigation methods like Activity.startActivity.
 | 
						|
	public _addChildFromBuilder(name: string, value: any) {
 | 
						|
		throw new Error(`Frame should not have a view. Use 'defaultPage' property instead.`);
 | 
						|
	}
 | 
						|
 | 
						|
	@profile
 | 
						|
	public onLoaded() {
 | 
						|
		super.onLoaded();
 | 
						|
		this._processNextNavigationEntry();
 | 
						|
	}
 | 
						|
 | 
						|
	public canGoBack(): boolean {
 | 
						|
		let backstack = this._backStack.length;
 | 
						|
		let previousForwardNotInBackstack = false;
 | 
						|
		this._navigationQueue.forEach((item) => {
 | 
						|
			const entry = item.entry;
 | 
						|
			const isBackNavigation = item.navigationType === NavigationType.back;
 | 
						|
			if (isBackNavigation) {
 | 
						|
				previousForwardNotInBackstack = false;
 | 
						|
				if (!entry) {
 | 
						|
					backstack--;
 | 
						|
				} else {
 | 
						|
					const backstackIndex = this._backStack.indexOf(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 (entry.entry.clearHistory) {
 | 
						|
				previousForwardNotInBackstack = false;
 | 
						|
				backstack = 0;
 | 
						|
			} else {
 | 
						|
				backstack++;
 | 
						|
				if (previousForwardNotInBackstack) {
 | 
						|
					backstack--;
 | 
						|
				}
 | 
						|
 | 
						|
				previousForwardNotInBackstack = entry.entry.backstackVisible === false;
 | 
						|
			}
 | 
						|
		});
 | 
						|
 | 
						|
		// this is our first navigation which is not completed yet.
 | 
						|
		if (this._navigationQueue.length > 0 && !this._currentEntry) {
 | 
						|
			backstack--;
 | 
						|
		}
 | 
						|
 | 
						|
		return backstack > 0;
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * Navigates to the previous entry (if any) in the back stack.
 | 
						|
	 * @param to The backstack entry to navigate back to.
 | 
						|
	 */
 | 
						|
	public goBack(backstackEntry?: BackstackEntry): void {
 | 
						|
		if (Trace.isEnabled()) {
 | 
						|
			Trace.write(`GO BACK`, Trace.categories.Navigation);
 | 
						|
		}
 | 
						|
 | 
						|
		if (!this.canGoBack()) {
 | 
						|
			return;
 | 
						|
		}
 | 
						|
 | 
						|
		if (backstackEntry) {
 | 
						|
			const index = this._backStack.indexOf(backstackEntry);
 | 
						|
			if (index < 0) {
 | 
						|
				return;
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		const navigationContext: NavigationContext = {
 | 
						|
			entry: backstackEntry,
 | 
						|
			isBackNavigation: true,
 | 
						|
			navigationType: NavigationType.back,
 | 
						|
		};
 | 
						|
 | 
						|
		this._navigationQueue.push(navigationContext);
 | 
						|
		this._processNextNavigationEntry();
 | 
						|
	}
 | 
						|
 | 
						|
	public _removeEntry(removed: BackstackEntry): void {
 | 
						|
		const page = removed.resolvedPage;
 | 
						|
		if (page) {
 | 
						|
			const frame = page.frame;
 | 
						|
			if (frame) {
 | 
						|
				frame._removeView(page);
 | 
						|
			} else {
 | 
						|
				page._tearDownUI(true);
 | 
						|
			}
 | 
						|
		} else {
 | 
						|
			if (Trace.isEnabled()) {
 | 
						|
				Trace.write(`_removeEntry: backstack entry missing page`, Trace.categories.Navigation);
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		removed.resolvedPage = null;
 | 
						|
	}
 | 
						|
 | 
						|
	protected _disposeBackstackEntry(entry: BackstackEntry): void {
 | 
						|
		const page = entry.resolvedPage;
 | 
						|
		if (page) {
 | 
						|
			page._tearDownUI(true);
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	public navigate(param: any) {
 | 
						|
		if (Trace.isEnabled()) {
 | 
						|
			Trace.write(`NAVIGATE`, Trace.categories.Navigation);
 | 
						|
		}
 | 
						|
 | 
						|
		this._pushInFrameStack();
 | 
						|
 | 
						|
		const entry = buildEntryFromArgs(param);
 | 
						|
		const page = Builder.createViewFromEntry(entry) as Page;
 | 
						|
 | 
						|
		const backstackEntry: BackstackEntry = {
 | 
						|
			entry: entry,
 | 
						|
			resolvedPage: page,
 | 
						|
			navDepth: undefined,
 | 
						|
			fragmentTag: undefined,
 | 
						|
		};
 | 
						|
 | 
						|
		const navigationContext: NavigationContext = {
 | 
						|
			entry: backstackEntry,
 | 
						|
			isBackNavigation: false,
 | 
						|
			navigationType: NavigationType.forward,
 | 
						|
		};
 | 
						|
 | 
						|
		this._navigationQueue.push(navigationContext);
 | 
						|
		this._processNextNavigationEntry();
 | 
						|
	}
 | 
						|
 | 
						|
	public isCurrent(entry: BackstackEntry): boolean {
 | 
						|
		return this._currentEntry === entry;
 | 
						|
	}
 | 
						|
 | 
						|
	public setCurrent(entry: BackstackEntry, navigationType: NavigationType): void {
 | 
						|
		const fromEntry = this._currentEntry;
 | 
						|
		const newPage = entry.resolvedPage;
 | 
						|
 | 
						|
		// In case we navigated forward to a page that was in the backstack
 | 
						|
		// with clearHistory: true
 | 
						|
		if (!newPage.frame) {
 | 
						|
			this._resolvedPage = newPage;
 | 
						|
 | 
						|
			this._addView(newPage);
 | 
						|
		}
 | 
						|
 | 
						|
		this._currentEntry = entry;
 | 
						|
 | 
						|
		const isBack = navigationType === NavigationType.back;
 | 
						|
		if (isBack) {
 | 
						|
			this._pushInFrameStack();
 | 
						|
		}
 | 
						|
 | 
						|
		newPage.onNavigatedTo(isBack);
 | 
						|
		this.notify<NavigationData>({
 | 
						|
			eventName: FrameBase.navigatedToEvent,
 | 
						|
			object: this,
 | 
						|
			isBack,
 | 
						|
			entry,
 | 
						|
			fromEntry,
 | 
						|
		});
 | 
						|
 | 
						|
		// Reset executing context after NavigatedTo is raised;
 | 
						|
		// we do not want to execute two navigations in parallel in case
 | 
						|
		// additional navigation is triggered from the NavigatedTo handler.
 | 
						|
		this._executingContext = null;
 | 
						|
	}
 | 
						|
 | 
						|
	public _updateBackstack(entry: BackstackEntry, navigationType: NavigationType): void {
 | 
						|
		const isBack = navigationType === NavigationType.back;
 | 
						|
		const isReplace = navigationType === NavigationType.replace;
 | 
						|
		this.raiseCurrentPageNavigatedEvents(isBack);
 | 
						|
		const current = this._currentEntry;
 | 
						|
 | 
						|
		// Do nothing for Hot Module Replacement
 | 
						|
		if (isBack) {
 | 
						|
			const index = this._backStack.indexOf(entry);
 | 
						|
			this._backStack.splice(index + 1).forEach((e) => this._removeEntry(e));
 | 
						|
			this._backStack.pop();
 | 
						|
		} else if (!isReplace) {
 | 
						|
			if (entry.entry.clearHistory) {
 | 
						|
				this._backStack.forEach((e) => {
 | 
						|
					if (e !== entry) {
 | 
						|
						this._removeEntry(e);
 | 
						|
					} else {
 | 
						|
						// This case is extremely rare but can occur when fragment resumes
 | 
						|
						Trace.write(`Failed to dispose backstack entry ${entry}. This entry is the one frame is navigating to.`, Trace.categories.Navigation, Trace.messageType.warn);
 | 
						|
					}
 | 
						|
				});
 | 
						|
				this._backStack.length = 0;
 | 
						|
			} else if (FrameBase._isEntryBackstackVisible(current)) {
 | 
						|
				this._backStack.push(current);
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		if (current && this._backStack.indexOf(current) < 0) {
 | 
						|
			this._removeEntry(current);
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	private isNestedWithin(parentFrameCandidate: FrameBase): boolean {
 | 
						|
		let frameAncestor = this as FrameBase;
 | 
						|
 | 
						|
		while (frameAncestor) {
 | 
						|
			frameAncestor = getAncestor(frameAncestor, FrameBase);
 | 
						|
			if (frameAncestor === parentFrameCandidate) {
 | 
						|
				return true;
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		return false;
 | 
						|
	}
 | 
						|
 | 
						|
	private raiseCurrentPageNavigatedEvents(isBack: boolean) {
 | 
						|
		const page = this.currentPage;
 | 
						|
		if (page) {
 | 
						|
			if (page.isLoaded) {
 | 
						|
				// Forward navigation does not remove page from frame so we raise unloaded manually.
 | 
						|
				page.callUnloaded();
 | 
						|
			}
 | 
						|
			page.onNavigatedFrom(isBack);
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	public _processNavigationQueue(page: Page) {
 | 
						|
		if (this._navigationQueue.length === 0) {
 | 
						|
			// This could happen when showing recreated page after activity has been destroyed.
 | 
						|
			return;
 | 
						|
		}
 | 
						|
 | 
						|
		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;
 | 
						|
		}
 | 
						|
 | 
						|
		// remove completed operation.
 | 
						|
		this._navigationQueue.shift();
 | 
						|
		this._processNextNavigationEntry();
 | 
						|
		this._updateActionBar();
 | 
						|
	}
 | 
						|
 | 
						|
	public _findEntryForTag(fragmentTag: string): BackstackEntry {
 | 
						|
		let entry: BackstackEntry;
 | 
						|
		if (this._currentEntry && this._currentEntry.fragmentTag === fragmentTag) {
 | 
						|
			entry = this._currentEntry;
 | 
						|
		} else {
 | 
						|
			entry = this._backStack.find((value) => value.fragmentTag === fragmentTag);
 | 
						|
			// on API 26 fragments are recreated lazily after activity is destroyed.
 | 
						|
			if (!entry) {
 | 
						|
				const navigationItem = this._navigationQueue.find((value) => value.entry.fragmentTag === fragmentTag);
 | 
						|
				entry = navigationItem ? navigationItem.entry : undefined;
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		return entry;
 | 
						|
	}
 | 
						|
 | 
						|
	public getNavigationQueueContextByEntry(entry: BackstackEntry): NavigationContext {
 | 
						|
		for (const context of this._navigationQueue) {
 | 
						|
			if (context.entry === entry) {
 | 
						|
				return context;
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		return null;
 | 
						|
	}
 | 
						|
 | 
						|
	public navigationQueueIsEmpty(): boolean {
 | 
						|
		return this._navigationQueue.length === 0;
 | 
						|
	}
 | 
						|
 | 
						|
	public static _isEntryBackstackVisible(entry: BackstackEntry): boolean {
 | 
						|
		if (!entry) {
 | 
						|
			return false;
 | 
						|
		}
 | 
						|
 | 
						|
		const backstackVisibleValue = entry.entry.backstackVisible;
 | 
						|
		const backstackHidden = backstackVisibleValue !== undefined && !backstackVisibleValue;
 | 
						|
 | 
						|
		return !backstackHidden;
 | 
						|
	}
 | 
						|
 | 
						|
	public _updateActionBar(page?: Page, disableNavBarAnimation?: boolean) {
 | 
						|
		//Trace.write("calling _updateActionBar on Frame", Trace.categories.Navigation);
 | 
						|
	}
 | 
						|
 | 
						|
	protected _processNextNavigationEntry() {
 | 
						|
		if (!this.isLoaded || this._executingContext) {
 | 
						|
			return;
 | 
						|
		}
 | 
						|
 | 
						|
		if (this._navigationQueue.length > 0) {
 | 
						|
			const navigationContext = this._navigationQueue[0];
 | 
						|
			const isBackNavigation = navigationContext.navigationType === NavigationType.back;
 | 
						|
			if (isBackNavigation) {
 | 
						|
				this.performGoBack(navigationContext);
 | 
						|
			} else {
 | 
						|
				this.performNavigation(navigationContext);
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	@profile
 | 
						|
	public performNavigation(navigationContext: NavigationContext) {
 | 
						|
		this._executingContext = navigationContext;
 | 
						|
 | 
						|
		const backstackEntry = navigationContext.entry;
 | 
						|
		const isBackNavigation = navigationContext.navigationType === NavigationType.back;
 | 
						|
		this._onNavigatingTo(backstackEntry, isBackNavigation);
 | 
						|
		const navigationTransition = this._getNavigationTransition(backstackEntry.entry);
 | 
						|
		if (navigationTransition?.instance) {
 | 
						|
			const state = SharedTransition.getState(navigationTransition?.instance.id);
 | 
						|
			SharedTransition.updateState(navigationTransition?.instance.id, {
 | 
						|
				// Allow setting custom page context to override default (from) page
 | 
						|
				// helpful for deeply nested frame navigation setups (eg: Nested Tab Navigation)
 | 
						|
				// when sharing elements in this condition, the (from) page would
 | 
						|
				// get overridden on each frame preventing shared element matching
 | 
						|
				page: state?.page || this.currentPage,
 | 
						|
				toPage: this,
 | 
						|
			});
 | 
						|
		}
 | 
						|
		this._navigateCore(backstackEntry);
 | 
						|
	}
 | 
						|
 | 
						|
	@profile
 | 
						|
	performGoBack(navigationContext: NavigationContext) {
 | 
						|
		const backstack = this._backStack;
 | 
						|
		const backstackEntry = navigationContext.entry || backstack[backstack.length - 1];
 | 
						|
 | 
						|
		if (backstackEntry) {
 | 
						|
			navigationContext.entry = backstackEntry;
 | 
						|
 | 
						|
			this._executingContext = navigationContext;
 | 
						|
			this._onNavigatingTo(backstackEntry, true);
 | 
						|
			this._goBackCore(backstackEntry);
 | 
						|
		} else {
 | 
						|
			Trace.write('Frame.performGoBack: No backstack entry found to navigate back to', Trace.categories.Navigation, Trace.messageType.warn);
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	public _goBackCore(backstackEntry: BackstackEntry) {
 | 
						|
		if (Trace.isEnabled()) {
 | 
						|
			Trace.write(`GO BACK CORE(${this._backstackEntryTrace(backstackEntry)}); currentPage: ${this.currentPage}`, Trace.categories.Navigation);
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	public _navigateCore(backstackEntry: BackstackEntry) {
 | 
						|
		if (Trace.isEnabled()) {
 | 
						|
			Trace.write(`NAVIGATE CORE(${this._backstackEntryTrace(backstackEntry)}); currentPage: ${this.currentPage}`, Trace.categories.Navigation);
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	public _onNavigatingTo(backstackEntry: BackstackEntry, isBack: boolean) {
 | 
						|
		if (this.currentPage) {
 | 
						|
			this.currentPage.onNavigatingFrom(isBack);
 | 
						|
		}
 | 
						|
 | 
						|
		backstackEntry.resolvedPage.onNavigatingTo(backstackEntry.entry.context, isBack, backstackEntry.entry.bindingContext);
 | 
						|
		this.notify<NavigationData>({
 | 
						|
			eventName: FrameBase.navigatingToEvent,
 | 
						|
			object: this,
 | 
						|
			isBack,
 | 
						|
			entry: backstackEntry,
 | 
						|
			fromEntry: this._currentEntry,
 | 
						|
		});
 | 
						|
	}
 | 
						|
 | 
						|
	public get animated(): boolean {
 | 
						|
		return this._animated;
 | 
						|
	}
 | 
						|
 | 
						|
	public set animated(value: boolean) {
 | 
						|
		this._animated = value;
 | 
						|
	}
 | 
						|
 | 
						|
	public get transition(): NavigationTransition {
 | 
						|
		return this._transition;
 | 
						|
	}
 | 
						|
 | 
						|
	public set transition(value: NavigationTransition) {
 | 
						|
		this._transition = value;
 | 
						|
	}
 | 
						|
 | 
						|
	get backStack(): Array<BackstackEntry> {
 | 
						|
		return this._backStack.slice();
 | 
						|
	}
 | 
						|
 | 
						|
	get currentPage(): Page {
 | 
						|
		if (this._currentEntry) {
 | 
						|
			return this._currentEntry.resolvedPage;
 | 
						|
		}
 | 
						|
		return null;
 | 
						|
	}
 | 
						|
 | 
						|
	get currentEntry(): NavigationEntry {
 | 
						|
		if (this._currentEntry) {
 | 
						|
			return this._currentEntry.entry;
 | 
						|
		}
 | 
						|
 | 
						|
		return null;
 | 
						|
	}
 | 
						|
 | 
						|
	public _pushInFrameStackRecursive() {
 | 
						|
		this._pushInFrameStack();
 | 
						|
 | 
						|
		// make sure nested frames order is kept intact i.e. the nested one should always be on top;
 | 
						|
		// see https://github.com/NativeScript/nativescript-angular/issues/1596 for more information
 | 
						|
		const framesToPush = [];
 | 
						|
		for (const frame of frameStack) {
 | 
						|
			if (frame.isNestedWithin(this)) {
 | 
						|
				framesToPush.push(frame);
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		for (const frame of framesToPush) {
 | 
						|
			frame._pushInFrameStack();
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	public _isFrameStackEmpty() {
 | 
						|
		return _isFrameStackEmpty();
 | 
						|
	}
 | 
						|
 | 
						|
	public _pushInFrameStack() {
 | 
						|
		_pushInFrameStack(this);
 | 
						|
	}
 | 
						|
 | 
						|
	public _popFromFrameStack() {
 | 
						|
		_popFromFrameStack(this);
 | 
						|
	}
 | 
						|
 | 
						|
	public _removeFromFrameStack() {
 | 
						|
		_removeFromFrameStack(this);
 | 
						|
	}
 | 
						|
 | 
						|
	public _dialogClosed(): void {
 | 
						|
		// No super call as we do not support nested frames to clean up
 | 
						|
		this._removeFromFrameStack();
 | 
						|
	}
 | 
						|
 | 
						|
	public _onRootViewReset(): void {
 | 
						|
		super._onRootViewReset();
 | 
						|
		this._removeFromFrameStack();
 | 
						|
	}
 | 
						|
 | 
						|
	get _childrenCount(): number {
 | 
						|
		if (this._resolvedPage) {
 | 
						|
			return 1;
 | 
						|
		}
 | 
						|
 | 
						|
		return 0;
 | 
						|
	}
 | 
						|
 | 
						|
	public eachChildView(callback: (child: View) => boolean) {
 | 
						|
		const page = this._resolvedPage;
 | 
						|
		if (page) {
 | 
						|
			callback(page);
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	public _getIsAnimatedNavigation(entry: NavigationEntry): boolean {
 | 
						|
		if (entry && entry.animated !== undefined) {
 | 
						|
			return entry.animated;
 | 
						|
		}
 | 
						|
 | 
						|
		if (this.animated !== undefined) {
 | 
						|
			return this.animated;
 | 
						|
		}
 | 
						|
 | 
						|
		return FrameBase.defaultAnimatedNavigation;
 | 
						|
	}
 | 
						|
 | 
						|
	public _getNavigationTransition(entry: NavigationEntry): NavigationTransition {
 | 
						|
		if (entry) {
 | 
						|
			if (__APPLE__ && entry.transitioniOS !== undefined) {
 | 
						|
				return entry.transitioniOS;
 | 
						|
			}
 | 
						|
 | 
						|
			if (__ANDROID__ && entry.transitionAndroid !== undefined) {
 | 
						|
				return entry.transitionAndroid;
 | 
						|
			}
 | 
						|
 | 
						|
			if (entry.transition !== undefined) {
 | 
						|
				return entry.transition;
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		if (this.transition !== undefined) {
 | 
						|
			return this.transition;
 | 
						|
		}
 | 
						|
 | 
						|
		return FrameBase.defaultTransition;
 | 
						|
	}
 | 
						|
 | 
						|
	public get navigationBarHeight(): number {
 | 
						|
		return 0;
 | 
						|
	}
 | 
						|
 | 
						|
	public _getNavBarVisible(page: Page): boolean {
 | 
						|
		throw new Error();
 | 
						|
	}
 | 
						|
 | 
						|
	// We don't need to put Page as visual child. Don't call super.
 | 
						|
	public _addViewToNativeVisualTree(child: View): boolean {
 | 
						|
		return true;
 | 
						|
	}
 | 
						|
 | 
						|
	// We don't need to put Page as visual child. Don't call super.
 | 
						|
	public _removeViewFromNativeVisualTree(child: View): void {
 | 
						|
		child._isAddedToNativeVisualTree = false;
 | 
						|
	}
 | 
						|
 | 
						|
	public _printFrameBackStack() {
 | 
						|
		const length = this.backStack.length;
 | 
						|
		let i = length - 1;
 | 
						|
		console.log(`Frame Back Stack: `);
 | 
						|
		while (i >= 0) {
 | 
						|
			const backstackEntry = <BackstackEntry>this.backStack[i--];
 | 
						|
			console.log(`\t${backstackEntry.resolvedPage}`);
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	public _backstackEntryTrace(b: BackstackEntry): string {
 | 
						|
		let result = `${b.resolvedPage}`;
 | 
						|
 | 
						|
		const backstackVisible = FrameBase._isEntryBackstackVisible(b);
 | 
						|
		if (!backstackVisible) {
 | 
						|
			result += ` | INVISIBLE`;
 | 
						|
		}
 | 
						|
 | 
						|
		if (b.entry.clearHistory) {
 | 
						|
			result += ` | CLEAR HISTORY`;
 | 
						|
		}
 | 
						|
 | 
						|
		const animated = this._getIsAnimatedNavigation(b.entry);
 | 
						|
		if (!animated) {
 | 
						|
			result += ` | NOT ANIMATED`;
 | 
						|
		}
 | 
						|
 | 
						|
		const t = this._getNavigationTransition(b.entry);
 | 
						|
		if (t) {
 | 
						|
			result += ` | Transition[${JSON.stringify(t)}]`;
 | 
						|
		}
 | 
						|
 | 
						|
		return result;
 | 
						|
	}
 | 
						|
 | 
						|
	public _onLivesync(context?: ModuleContext): boolean {
 | 
						|
		if (super._onLivesync(context)) {
 | 
						|
			return true;
 | 
						|
		}
 | 
						|
 | 
						|
		// Fallback
 | 
						|
		if (!context) {
 | 
						|
			return this._onLivesyncWithoutContext();
 | 
						|
		}
 | 
						|
 | 
						|
		return false;
 | 
						|
	}
 | 
						|
 | 
						|
	public _handleLivesync(context?: ModuleContext): boolean {
 | 
						|
		if (super._handleLivesync(context)) {
 | 
						|
			return true;
 | 
						|
		}
 | 
						|
 | 
						|
		// Handle markup/script changes in currentPage
 | 
						|
		if (this.currentPage && viewMatchesModuleContext(this.currentPage, context, ['markup', 'script'])) {
 | 
						|
			Trace.write(`Change Handled: Replacing page ${context.path}`, Trace.categories.Livesync);
 | 
						|
 | 
						|
			// Replace current page with a default fade transition
 | 
						|
			this.replacePage({
 | 
						|
				moduleName: context.path,
 | 
						|
				transition: {
 | 
						|
					name: 'fade',
 | 
						|
					duration: 100,
 | 
						|
				},
 | 
						|
			});
 | 
						|
 | 
						|
			return true;
 | 
						|
		}
 | 
						|
 | 
						|
		return false;
 | 
						|
	}
 | 
						|
 | 
						|
	private _onLivesyncWithoutContext(): boolean {
 | 
						|
		// Reset activity/window content when:
 | 
						|
		// + Changes are not handled on View
 | 
						|
		// + There is no ModuleContext
 | 
						|
		if (Trace.isEnabled()) {
 | 
						|
			Trace.write(`${this}._onLivesyncWithoutContext()`, Trace.categories.Livesync);
 | 
						|
		}
 | 
						|
 | 
						|
		if (!this._currentEntry || !this._currentEntry.entry) {
 | 
						|
			return false;
 | 
						|
		}
 | 
						|
 | 
						|
		const currentEntry = this._currentEntry.entry;
 | 
						|
 | 
						|
		// If create returns the same page instance we can't recreate it.
 | 
						|
		// Instead of navigation set activity content.
 | 
						|
		// This could happen if current page was set in XML as a Page instance.
 | 
						|
		if (currentEntry.create) {
 | 
						|
			const page = currentEntry.create();
 | 
						|
			if (page === this.currentPage) {
 | 
						|
				return false;
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		// Replace current page with a default fade transition
 | 
						|
		this.replacePage({
 | 
						|
			moduleName: currentEntry.moduleName,
 | 
						|
			create: currentEntry.create,
 | 
						|
			transition: {
 | 
						|
				name: 'fade',
 | 
						|
				duration: 100,
 | 
						|
			},
 | 
						|
		});
 | 
						|
 | 
						|
		return true;
 | 
						|
	}
 | 
						|
 | 
						|
	public replacePage(entry: string | NavigationEntry): void {
 | 
						|
		const currentBackstackEntry = this._currentEntry;
 | 
						|
 | 
						|
		if (typeof entry === 'string') {
 | 
						|
			const contextModuleName = sanitizeModuleName(entry);
 | 
						|
			entry = { moduleName: contextModuleName };
 | 
						|
		}
 | 
						|
 | 
						|
		const newPage = Builder.createViewFromEntry(entry) as Page;
 | 
						|
 | 
						|
		const newBackstackEntry: BackstackEntry = {
 | 
						|
			entry: Object.assign({}, currentBackstackEntry.entry, entry),
 | 
						|
			resolvedPage: newPage,
 | 
						|
			navDepth: currentBackstackEntry.navDepth,
 | 
						|
			fragmentTag: currentBackstackEntry.fragmentTag,
 | 
						|
			frameId: currentBackstackEntry.frameId,
 | 
						|
		};
 | 
						|
 | 
						|
		const navigationContext: NavigationContext = {
 | 
						|
			entry: newBackstackEntry,
 | 
						|
			isBackNavigation: false,
 | 
						|
			navigationType: NavigationType.replace,
 | 
						|
		};
 | 
						|
 | 
						|
		this._navigationQueue.push(navigationContext);
 | 
						|
		this._processNextNavigationEntry();
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// Mark as a Frame with an unique Symbol
 | 
						|
FrameBase.prototype[FRAME_SYMBOL] = true;
 | 
						|
 | 
						|
export function getFrameById(id: string): FrameBase {
 | 
						|
	console.log('getFrameById() is deprecated. Use Frame.getFrameById() instead.');
 | 
						|
 | 
						|
	return FrameBase.getFrameById(id);
 | 
						|
}
 | 
						|
 | 
						|
export function topmost(): FrameBase {
 | 
						|
	console.log('topmost() is deprecated. Use Frame.topmost() instead.');
 | 
						|
 | 
						|
	return FrameBase.topmost();
 | 
						|
}
 | 
						|
 | 
						|
export function goBack(): boolean {
 | 
						|
	console.log('goBack() is deprecated. Use Frame.goBack() instead.');
 | 
						|
 | 
						|
	return FrameBase.goBack();
 | 
						|
}
 | 
						|
 | 
						|
export function _stack(): Array<FrameBase> {
 | 
						|
	console.log('_stack() is deprecated. Use Frame._stack() instead.');
 | 
						|
 | 
						|
	return FrameBase._stack();
 | 
						|
}
 | 
						|
 | 
						|
export const defaultPageProperty = new Property<FrameBase, string>({
 | 
						|
	name: 'defaultPage',
 | 
						|
	valueChanged: (frame: FrameBase, oldValue: string, newValue: string) => {
 | 
						|
		frame.navigate({ moduleName: newValue });
 | 
						|
	},
 | 
						|
});
 | 
						|
defaultPageProperty.register(FrameBase);
 | 
						|
 | 
						|
export const actionBarVisibilityProperty = new Property<FrameBase, 'auto' | 'never' | 'always'>({ name: 'actionBarVisibility', defaultValue: 'auto', affectsLayout: __APPLE__ });
 | 
						|
actionBarVisibilityProperty.register(FrameBase);
 |