From 99c01fb393d65910281746662bfcd6d943a354d1 Mon Sep 17 00:00:00 2001 From: Dimitris - Rafail Katsampas Date: Fri, 31 Jan 2025 17:03:25 +0200 Subject: [PATCH 1/6] fix(core): Added safety checks to prevent possible navigation exceltions --- .../ui/frame/fragment.transitions.android.ts | 15 +- packages/core/ui/frame/frame-common.ts | 31 ++- tools/notes/CodingConvention.md | 208 +++++++++--------- 3 files changed, 135 insertions(+), 119 deletions(-) diff --git a/packages/core/ui/frame/fragment.transitions.android.ts b/packages/core/ui/frame/fragment.transitions.android.ts index 57bb455951..70ce2aeb9f 100644 --- a/packages/core/ui/frame/fragment.transitions.android.ts +++ b/packages/core/ui/frame/fragment.transitions.android.ts @@ -119,7 +119,7 @@ export function _setAndroidFragmentTransitions(animated: boolean, navigationTran curve: transition.getCurve(), }, newEntry, - transition + transition, ); if (currentFragmentNeedsDifferentAnimation) { setupCurrentFragmentCustomTransition( @@ -128,7 +128,7 @@ export function _setAndroidFragmentTransitions(animated: boolean, navigationTran curve: transition.getCurve(), }, currentEntry, - transition + transition, ); } } else if (name === 'default') { @@ -356,7 +356,10 @@ function getTransitionListener(entry: ExpandedEntry, transition: androidx.transi @Interfaces([(androidx).transition.Transition.TransitionListener]) class TransitionListenerImpl extends java.lang.Object implements androidx.transition.Transition.TransitionListener { public backEntry?: BackstackEntry; - constructor(public entry: ExpandedEntry, public transition: androidx.transition.Transition) { + constructor( + public entry: ExpandedEntry, + public transition: androidx.transition.Transition, + ) { super(); return global.__native(this); @@ -702,7 +705,7 @@ function transitionOrAnimationCompleted(entry: ExpandedEntry, backEntry: Backsta if (entries.size === 0) { // We have 0 or 1 entry per frameId in completedEntries // So there is no need to make it to Set like waitingQueue - const previousCompletedAnimationEntry = completedEntries.get(frameId); + const previousCompletedEntry = completedEntries.get(frameId); completedEntries.delete(frameId); waitingQueue.delete(frameId); @@ -716,8 +719,8 @@ function transitionOrAnimationCompleted(entry: ExpandedEntry, backEntry: Backsta const navigationContext = frame._executingContext || { navigationType: NavigationType.back, }; - let current = frame.isCurrent(entry) ? previousCompletedAnimationEntry : entry; - current = current || entry; + const current = frame.isCurrent(entry) && previousCompletedEntry ? previousCompletedEntry : entry; + // Will be null if Frame is shown modally... // transitionOrAnimationCompleted fires again (probably bug in android). if (current) { diff --git a/packages/core/ui/frame/frame-common.ts b/packages/core/ui/frame/frame-common.ts index d05542f5dc..77e48573f1 100644 --- a/packages/core/ui/frame/frame-common.ts +++ b/packages/core/ui/frame/frame-common.ts @@ -41,7 +41,7 @@ export class FrameBase extends CustomLayoutView { private _animated: boolean; private _transition: NavigationTransition; private _backStack = new Array(); - _navigationQueue = new Array(); + private _navigationQueue = new Array(); public actionBarVisibility: 'auto' | 'never' | 'always'; public _currentEntry: BackstackEntry; @@ -300,7 +300,16 @@ export class FrameBase extends CustomLayoutView { this._backStack.pop(); } else if (!isReplace) { if (entry.entry.clearHistory) { - this._backStack.forEach((e) => this._removeEntry(e)); + this._backStack.forEach((e) => { + if (e !== entry) { + this._removeEntry(e); + } else { + // This case is extremely rare but can occur when fragment resumes + if (Trace.isEnabled()) { + 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); @@ -429,16 +438,20 @@ export class FrameBase extends CustomLayoutView { @profile performGoBack(navigationContext: NavigationContext) { - let backstackEntry = navigationContext.entry; const backstack = this._backStack; - if (!backstackEntry) { - backstackEntry = backstack[backstack.length - 1]; + const backstackEntry = navigationContext.entry || backstack[backstack.length - 1]; + + if (backstackEntry) { navigationContext.entry = backstackEntry; - } - this._executingContext = navigationContext; - this._onNavigatingTo(backstackEntry, true); - this._goBackCore(backstackEntry); + this._executingContext = navigationContext; + this._onNavigatingTo(backstackEntry, true); + this._goBackCore(backstackEntry); + } else { + if (Trace.isEnabled()) { + Trace.write(`No backstack entry found to navigate back to`, Trace.categories.Navigation, Trace.messageType.warn); + } + } } public _goBackCore(backstackEntry: BackstackEntry) { diff --git a/tools/notes/CodingConvention.md b/tools/notes/CodingConvention.md index 1f8ceebe46..6ee7fd957b 100644 --- a/tools/notes/CodingConvention.md +++ b/tools/notes/CodingConvention.md @@ -2,11 +2,11 @@ ## Tabs vs Spaces -Use 4 spaces indentation. +Use 2 spaces indentation. ## Line length -Try to limit your lines to 80 characters. +Try to limit your lines to 600 characters. ## Semicolons, statement Termination @@ -26,18 +26,18 @@ let x = 1 ## Quotes -Use double quotes for strings: +Use single quotes for strings: *Right:* ```TypeScript -let foo = "bar"; +let foo = 'bar'; ``` *Wrong:* ```TypeScript -let foo = 'bar'; +let foo = "bar"; ``` ## Braces @@ -48,7 +48,7 @@ Your opening braces go on the same line as the statement. ```TypeScript if (true) { - console.log("winning"); + console.log("winning"); } ``` @@ -57,7 +57,7 @@ if (true) { ```TypeScript if (true) { - console.log("losing"); + console.log("losing"); } ``` @@ -69,9 +69,9 @@ Follow the JavaScript convention of stacking `else/catch` clauses on the same li ```TypeScript if (i % 2 === 0) { - console.log("even"); + console.log("even"); } else { - console.log("odd"); + console.log("odd"); } ``` @@ -79,10 +79,10 @@ if (i % 2 === 0) { ```TypeScript if (i % 2 === 0) { - console.log("even"); + console.log("even"); } else { - console.log("odd"); + console.log("odd"); } ``` @@ -96,7 +96,7 @@ Declare variables with `let` instead of `var`. Use `const` when possible. const button = new Button(); for (let i = 0; i < items.length; i++) { - // do something + // do something } ``` @@ -106,7 +106,7 @@ for (let i = 0; i < items.length; i++) { var button = new Button(); for (var i = 0; i < items.length; i++) { - // do something + // do something } ``` @@ -196,14 +196,14 @@ let b = {"good": "code" ## Equality operator -Use the [strict comparison operators][comparisonoperators]. The triple equality operator helps to maintain data type integrity throughout the code. +Use the [strict comparison operators][comparisonoperators] when needed. The triple equality operator helps to maintain data type integrity throughout the code. *Right:* ```TypeScript let a = 0; if (a === "") { - console.log("winning"); + console.log("winning"); } ``` @@ -213,7 +213,7 @@ if (a === "") { ```TypeScript let a = 0; if (a == "") { - console.log("losing"); + console.log("losing"); } ``` @@ -247,7 +247,7 @@ Always use curly braces even in the cases of one line conditional operations. ```TypeScript if (a) { - return "winning"; + return "winning"; } ``` @@ -257,7 +257,7 @@ if (a) { ```TypeScript if (a) - return "winning"; + return "winning"; if (a) return "winning"; ``` @@ -271,11 +271,11 @@ if (a) return "winning"; ```TypeScript if (condition) { - console.log("winning"); + console.log("winning"); } if (!condition) { - console.log("winning"); + console.log("winning"); } ``` @@ -285,15 +285,15 @@ if (!condition) { ```TypeScript if (condition === true) { - console.log("losing"); + console.log("losing"); } if (condition !== true) { - console.log("losing"); + console.log("losing"); } if (condition !== false) { - console.log("losing"); + console.log("losing"); } ``` @@ -306,7 +306,7 @@ Do not use the **Yoda Conditions** when writing boolean expressions: ```TypeScript let num; if (num >= 0) { - console.log("winning"); + console.log("winning"); } ``` @@ -315,14 +315,14 @@ if (num >= 0) { ```TypeScript let num; if (0 <= num) { - console.log("losing"); + console.log("losing"); } ``` **NOTE** It is OK to use constants on the left when comparing for a range. ```TypeScript if (0 <= num && num <= 100) { - console.log("winning"); + console.log("winning"); } ``` @@ -341,22 +341,22 @@ as possible. In certain routines, once you know the answer, you want to return i ```TypeScript function getSomething(val) { - if (val < 0) { - return false; - } - - if (val > 100) { - return false; - } - - let res1 = doOne(); - let res2 = doTwo(); - let options = { - a: 1, - b: 2 - }; - let result = doThree(res1, res2, options); - return result; + if (val < 0) { + return false; + } + + if (val > 100) { + return false; + } + + let res1 = doOne(); + let res2 = doTwo(); + let options = { + a: 1, + b: 2 + }; + let result = doThree(res1, res2, options); + return result; } ``` @@ -364,24 +364,24 @@ function getSomething(val) { ```TypeScript function getSomething(val) { - if (val >= 0) { - if (val < 100) { - let res1 = doOne(); - let res2 = doTwo(); - let options = { - a: 1, - b: 2 - }; - let result = doThree(res1, res2, options); - return result; - } - else { - return false; - } + if (val >= 0) { + if (val < 100) { + let res1 = doOne(); + let res2 = doTwo(); + let options = { + a: 1, + b: 2 + }; + let result = doThree(res1, res2, options); + return result; } else { - return false; + return false; } + } + else { + return false; + } } ``` @@ -393,9 +393,9 @@ Use arrow functions over anonymous function expressions. Typescript will take ca ```TypeScript req.on("end", () => { - exp1(); - exp2(); - this.doSomething(); + exp1(); + exp2(); + this.doSomething(); }); ``` @@ -404,9 +404,9 @@ req.on("end", () => { ```TypeScript let that = this; req.on("end", function () { - exp1(); - exp2(); - that.doSomething(); + exp1(); + exp2(); + that.doSomething(); }); ``` @@ -449,16 +449,16 @@ When you **need** to keep a reference to **this** use **that** as the name of th *Right:* ```TypeScript let that = this; -doSomething(function(){ - that.doNothing(); +doSomething(function() { + that.doNothing(); }); ``` *Wrong:* ```TypeScript let me = this; -doSomething(function(){ - me.doNothing(); +doSomething(function() { + me.doNothing(); }); ``` @@ -468,34 +468,34 @@ Although there is the **private** keyword in TypeScript, it is only a syntax sug *Right:* ```TypeScript class Foo { - private _myBoolean: boolean; - - public publicAPIMethod() { - } - - public _frameworkMethod() { - // this method is for internal use only - } - - private _doSomething() { - } + private _myBoolean: boolean; + + public publicAPIMethod() { + } + + public _frameworkMethod() { + // this method is for internal use only + } + + private _doSomething() { + } } ``` *Wrong:* ```TypeScript class Foo { - private myBoolean: boolean; - - public _publicAPIMethod() { - } - - public frameworkMethod() { - // this method is for internal use only - } - - private doSomething() { - } + private myBoolean: boolean; + + public _publicAPIMethod() { + } + + public frameworkMethod() { + // this method is for internal use only + } + + private doSomething() { + } } ``` @@ -509,14 +509,14 @@ export declare function concat(...categories: string[]): string; // implementation export function concat(): string { - let i; - let result: string; - // use the arguments object to iterate the parameters - for (i = 0; i < arguments.length; i++) { - // do something - } + let i; + let result: string; + // use the arguments object to iterate the parameters + for (i = 0; i < arguments.length; i++) { + // do something + } - return result; + return result; } ``` @@ -527,14 +527,14 @@ export declare function concat(...categories: string[]): string; // implementation export function concat(...categories: string[]): string { - let i; - let result: string; - // use the arguments object to iterate the parameters - for (i = 0; i < categories.length; i++) { - // do something - } + let i; + let result: string; + // use the arguments object to iterate the parameters + for (i = 0; i < categories.length; i++) { + // do something + } - return result; + return result; } ``` @@ -544,6 +544,6 @@ Name your test function with `test_` so that our test runner can find them and a *Right:* ```TypeScript export function test_goToVisualState_NoState_ShouldResetStyledProperties() { - // Test code here. + // Test code here. } ``` From 5f0dc459232d70d088bcc6ce3807ef8566552a38 Mon Sep 17 00:00:00 2001 From: Dimitris - Rafail Katsampas Date: Fri, 31 Jan 2025 17:18:02 +0200 Subject: [PATCH 2/6] chore: Make sure to always display trace messages and on alert of the bug occurence --- packages/core/ui/frame/frame-common.ts | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/packages/core/ui/frame/frame-common.ts b/packages/core/ui/frame/frame-common.ts index 77e48573f1..ecc9adc277 100644 --- a/packages/core/ui/frame/frame-common.ts +++ b/packages/core/ui/frame/frame-common.ts @@ -305,9 +305,7 @@ export class FrameBase extends CustomLayoutView { this._removeEntry(e); } else { // This case is extremely rare but can occur when fragment resumes - if (Trace.isEnabled()) { - Trace.write(`Failed to dispose backstack entry ${entry}. This entry is the one frame is navigating to.`, Trace.categories.Navigation, Trace.messageType.warn); - } + 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; @@ -448,9 +446,7 @@ export class FrameBase extends CustomLayoutView { this._onNavigatingTo(backstackEntry, true); this._goBackCore(backstackEntry); } else { - if (Trace.isEnabled()) { - Trace.write(`No backstack entry found to navigate back to`, Trace.categories.Navigation, Trace.messageType.warn); - } + Trace.write('Frame.performGoBack: No backstack entry found to navigate back to', Trace.categories.Navigation, Trace.messageType.warn); } } From 5ea53eff7b51f6305825e3527324fa5c60f4d81d Mon Sep 17 00:00:00 2001 From: Dimitris - Rafail Katsampas Date: Fri, 31 Jan 2025 17:43:28 +0200 Subject: [PATCH 3/6] ref: Avoid making external use of frame navigationQueue --- packages/core/ui/frame/frame-common.ts | 10 ++++++++++ packages/core/ui/frame/index.d.ts | 4 ++++ packages/core/ui/page/index.ios.ts | 10 ++-------- 3 files changed, 16 insertions(+), 8 deletions(-) diff --git a/packages/core/ui/frame/frame-common.ts b/packages/core/ui/frame/frame-common.ts index ecc9adc277..067abb817d 100644 --- a/packages/core/ui/frame/frame-common.ts +++ b/packages/core/ui/frame/frame-common.ts @@ -377,6 +377,16 @@ export class FrameBase extends CustomLayoutView { 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; } diff --git a/packages/core/ui/frame/index.d.ts b/packages/core/ui/frame/index.d.ts index de5e064850..d305ba6e45 100644 --- a/packages/core/ui/frame/index.d.ts +++ b/packages/core/ui/frame/index.d.ts @@ -200,6 +200,10 @@ export class Frame extends FrameBase { * @param navigationType */ setCurrent(entry: BackstackEntry, navigationType: NavigationType): void; + /** + * @private + */ + getNavigationQueueContextByEntry(entry: BackstackEntry): NavigationContext; /** * @private */ diff --git a/packages/core/ui/page/index.ios.ts b/packages/core/ui/page/index.ios.ts index 83a7c30b66..de4e9d1491 100644 --- a/packages/core/ui/page/index.ios.ts +++ b/packages/core/ui/page/index.ios.ts @@ -37,14 +37,8 @@ function isBackNavigationTo(page: Page, entry): boolean { return true; } - const navigationQueue = (frame)._navigationQueue; - for (let i = 0; i < navigationQueue.length; i++) { - if (navigationQueue[i].entry === entry) { - return navigationQueue[i].navigationType === NavigationType.back; - } - } - - return false; + const queueContext = frame.getNavigationQueueContextByEntry(entry); + return queueContext && queueContext.navigationType === NavigationType.back; } function isBackNavigationFrom(controller: UIViewControllerImpl, page: Page): boolean { From e52a528e01c3493b1ecbede0fbbc13b55cf522e3 Mon Sep 17 00:00:00 2001 From: Dimitris - Rafail Katsampas Date: Fri, 31 Jan 2025 17:46:06 +0200 Subject: [PATCH 4/6] chore: Added missing entry type --- packages/core/ui/page/index.ios.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/core/ui/page/index.ios.ts b/packages/core/ui/page/index.ios.ts index de4e9d1491..8313c5c18a 100644 --- a/packages/core/ui/page/index.ios.ts +++ b/packages/core/ui/page/index.ios.ts @@ -18,7 +18,7 @@ const DELEGATE = '_delegate'; const TRANSITION = '_transition'; const NON_ANIMATED_TRANSITION = 'non-animated'; -function isBackNavigationTo(page: Page, entry): boolean { +function isBackNavigationTo(page: Page, entry: BackstackEntry): boolean { const frame = page.frame; if (!frame) { return false; @@ -115,7 +115,7 @@ class UIViewControllerImpl extends UIViewController { } const frame: Frame = this.navigationController ? (this.navigationController).owner : null; - const newEntry = this[ENTRY]; + const newEntry: BackstackEntry = this[ENTRY]; // Don't raise event if currentPage was showing modal page. if (!owner._presentedViewController && newEntry && (!frame || frame.currentPage !== owner)) { From cd35c777e1cbecc6fc0763d6d1ce35af9b6209be Mon Sep 17 00:00:00 2001 From: Dimitris - Rafail Katsampas Date: Fri, 31 Jan 2025 17:49:27 +0200 Subject: [PATCH 5/6] chore: Small coding conventions markdown changes --- tools/notes/CodingConvention.md | 70 ++++++++++++++++----------------- 1 file changed, 35 insertions(+), 35 deletions(-) diff --git a/tools/notes/CodingConvention.md b/tools/notes/CodingConvention.md index 6ee7fd957b..4d3b5b6732 100644 --- a/tools/notes/CodingConvention.md +++ b/tools/notes/CodingConvention.md @@ -48,7 +48,7 @@ Your opening braces go on the same line as the statement. ```TypeScript if (true) { - console.log("winning"); + console.log('winning'); } ``` @@ -57,7 +57,7 @@ if (true) { ```TypeScript if (true) { - console.log("losing"); + console.log('losing'); } ``` @@ -69,9 +69,9 @@ Follow the JavaScript convention of stacking `else/catch` clauses on the same li ```TypeScript if (i % 2 === 0) { - console.log("even"); + console.log('even'); } else { - console.log("odd"); + console.log('odd'); } ``` @@ -79,10 +79,10 @@ if (i % 2 === 0) { ```TypeScript if (i % 2 === 0) { - console.log("even"); + console.log('even'); } else { - console.log("odd"); + console.log('odd'); } ``` @@ -120,13 +120,13 @@ uncommon abbreviations should generally be avoided unless it is something well k *Right:* ```TypeScript -let adminUser = db.query("SELECT * FROM users ..."); +let adminUser = db.query('SELECT * FROM users ...'); ``` *Wrong:* ```TypeScript -let admin_user = db.query("SELECT * FROM users ..."); +let admin_user = db.query('SELECT * FROM users ...'); ``` [camelcase]: https://en.wikipedia.org/wiki/camelCase#Variations_and_synonyms @@ -139,7 +139,7 @@ Type names should be capitalized using [upper camel case][camelcase]. ```TypeScript class UserAccount() { - this.field = "a"; + this.field = 'a'; } ``` @@ -147,7 +147,7 @@ class UserAccount() { ```TypeScript class userAccount() { - this.field = "a"; + this.field = 'a'; } ``` @@ -176,10 +176,10 @@ keys when your interpreter complains: *Right:* ```TypeScript -let a = ["hello", "world"]; +let a = ['hello', 'world']; let b = { - good: "code", - "is generally": "pretty", + good: 'code', + 'is generally': 'pretty', }; ``` @@ -187,10 +187,10 @@ let b = { ```TypeScript let a = [ - "hello", "world" + 'hello', 'world' ]; -let b = {"good": "code" - , is generally: "pretty" +let b = {'good': 'code' + , is generally: 'pretty' }; ``` @@ -202,8 +202,8 @@ Use the [strict comparison operators][comparisonoperators] when needed. The trip ```TypeScript let a = 0; -if (a === "") { - console.log("winning"); +if (a === '') { + console.log('winning'); } ``` @@ -212,8 +212,8 @@ if (a === "") { ```TypeScript let a = 0; -if (a == "") { - console.log("losing"); +if (a == '') { + console.log('losing'); } ``` @@ -227,8 +227,8 @@ Try to avoid short-hand operators except in very simple scenarios. ```TypeScript let default = x || 50; -let extraLarge = "xxl"; -let small = "s" +let extraLarge = 'xxl'; +let small = 's' let big = (x > 10) ? extraLarge : small; ``` @@ -247,7 +247,7 @@ Always use curly braces even in the cases of one line conditional operations. ```TypeScript if (a) { - return "winning"; + return 'winning'; } ``` @@ -257,9 +257,9 @@ if (a) { ```TypeScript if (a) - return "winning"; + return 'winning'; -if (a) return "winning"; +if (a) return 'winning'; ``` ## Boolean comparisons @@ -271,11 +271,11 @@ if (a) return "winning"; ```TypeScript if (condition) { - console.log("winning"); + console.log('winning'); } if (!condition) { - console.log("winning"); + console.log('winning'); } ``` @@ -285,15 +285,15 @@ if (!condition) { ```TypeScript if (condition === true) { - console.log("losing"); + console.log('losing'); } if (condition !== true) { - console.log("losing"); + console.log('losing'); } if (condition !== false) { - console.log("losing"); + console.log('losing'); } ``` @@ -306,7 +306,7 @@ Do not use the **Yoda Conditions** when writing boolean expressions: ```TypeScript let num; if (num >= 0) { - console.log("winning"); + console.log('winning'); } ``` @@ -315,14 +315,14 @@ if (num >= 0) { ```TypeScript let num; if (0 <= num) { - console.log("losing"); + console.log('losing'); } ``` **NOTE** It is OK to use constants on the left when comparing for a range. ```TypeScript if (0 <= num && num <= 100) { - console.log("winning"); + console.log('winning'); } ``` @@ -392,7 +392,7 @@ Use arrow functions over anonymous function expressions. Typescript will take ca *Right:* ```TypeScript -req.on("end", () => { +req.on('end', () => { exp1(); exp2(); this.doSomething(); @@ -403,7 +403,7 @@ req.on("end", () => { ```TypeScript let that = this; -req.on("end", function () { +req.on('end', function () { exp1(); exp2(); that.doSomething(); From 5276efec9e641c1b04eb91a40b7bf7fe2263c09b Mon Sep 17 00:00:00 2001 From: Dimitris - Rafail Katsampas Date: Fri, 31 Jan 2025 18:00:31 +0200 Subject: [PATCH 6/6] chore: Small markdown correction --- tools/notes/CodingConvention.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/notes/CodingConvention.md b/tools/notes/CodingConvention.md index 4d3b5b6732..b6c289c2e9 100644 --- a/tools/notes/CodingConvention.md +++ b/tools/notes/CodingConvention.md @@ -2,7 +2,7 @@ ## Tabs vs Spaces -Use 2 spaces indentation. +Use tab width 2 indentation. ## Line length