From b8a14b3d6af920694a2bf0837d2e741a6ae13d19 Mon Sep 17 00:00:00 2001 From: Jan Vennemann Date: Wed, 10 Aug 2022 23:00:35 +0200 Subject: [PATCH] feat(ios): redesigned error view (#12163) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(ios): redesigned error view * chore: lower min-deployment-target again for now Co-authored-by: Hans Knöchel Co-authored-by: Hans Knöchel --- .../TitaniumKit.xcodeproj/project.pbxproj | 20 +- .../TitaniumKit/Sources/API/ErrorView.swift | 333 ++++++++++++++++++ .../TitaniumKit/Sources/API/TiApp.h | 5 + .../TitaniumKit/Sources/API/TiApp.m | 21 ++ .../Sources/API/TiBindingTiValue.m | 3 +- .../Sources/API/TiErrorController.h | 5 +- .../Sources/API/TiErrorController.m | 26 +- .../Sources/API/TiExceptionHandler.h | 5 + .../Sources/API/TiExceptionHandler.m | 44 ++- .../TitaniumKit/Sources/API/TiUtils.m | 3 + .../iphone/Titanium.xcodeproj/project.pbxproj | 4 + 11 files changed, 447 insertions(+), 22 deletions(-) create mode 100644 iphone/TitaniumKit/TitaniumKit/Sources/API/ErrorView.swift diff --git a/iphone/TitaniumKit/TitaniumKit.xcodeproj/project.pbxproj b/iphone/TitaniumKit/TitaniumKit.xcodeproj/project.pbxproj index 2ae4b66e4f0..da20426c939 100644 --- a/iphone/TitaniumKit/TitaniumKit.xcodeproj/project.pbxproj +++ b/iphone/TitaniumKit/TitaniumKit.xcodeproj/project.pbxproj @@ -23,18 +23,19 @@ /* Begin PBXBuildFile section */ 4A175276218B64E70094C7B6 /* KrollTimerManager.h in Headers */ = {isa = PBXBuildFile; fileRef = 4A175274218B64E70094C7B6 /* KrollTimerManager.h */; }; 4A175277218B64E70094C7B6 /* KrollTimerManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 4A175275218B64E70094C7B6 /* KrollTimerManager.m */; }; + 4A1FF4432523262A00A0F923 /* ErrorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A1FF4422523262A00A0F923 /* ErrorView.swift */; }; B106A6FD24535CB800B1305E /* JSValue+Addons.h in Headers */ = {isa = PBXBuildFile; fileRef = B106A6FB24535CB800B1305E /* JSValue+Addons.h */; settings = {ATTRIBUTES = (Public, ); }; }; B106A6FE24535CB800B1305E /* JSValue+Addons.m in Sources */ = {isa = PBXBuildFile; fileRef = B106A6FC24535CB800B1305E /* JSValue+Addons.m */; }; B10E8D502408559300578E8F /* APSHTTPClient.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = B10E8D4F2408559300578E8F /* APSHTTPClient.xcframework */; }; B10E8D5824085E1E00578E8F /* APSAnalytics.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = B10E8D5724085E1E00578E8F /* APSAnalytics.xcframework */; }; + B70ABB8424B79C9C0007D07A /* KrollPromise.h in Headers */ = {isa = PBXBuildFile; fileRef = B70ABB8324B79C9C0007D07A /* KrollPromise.h */; settings = {ATTRIBUTES = (Public, ); }; }; + B70ABB8624B79DCF0007D07A /* KrollPromise.m in Sources */ = {isa = PBXBuildFile; fileRef = B70ABB8524B79DCF0007D07A /* KrollPromise.m */; }; B7385F50246593B7007DB1F2 /* ScriptModule.m in Sources */ = {isa = PBXBuildFile; fileRef = B7385F4F246593B7007DB1F2 /* ScriptModule.m */; }; B7385F52246593BF007DB1F2 /* ScriptModule.h in Headers */ = {isa = PBXBuildFile; fileRef = B7385F51246593BF007DB1F2 /* ScriptModule.h */; }; B7385F5524659F83007DB1F2 /* AssetsModule.m in Sources */ = {isa = PBXBuildFile; fileRef = B7385F5324659F83007DB1F2 /* AssetsModule.m */; }; B7385F5624659F83007DB1F2 /* AssetsModule.h in Headers */ = {isa = PBXBuildFile; fileRef = B7385F5424659F83007DB1F2 /* AssetsModule.h */; }; B7385F592465A4B3007DB1F2 /* KrollModule.m in Sources */ = {isa = PBXBuildFile; fileRef = B7385F572465A4B3007DB1F2 /* KrollModule.m */; }; B7385F5A2465A4B3007DB1F2 /* KrollModule.h in Headers */ = {isa = PBXBuildFile; fileRef = B7385F582465A4B3007DB1F2 /* KrollModule.h */; }; - B70ABB8424B79C9C0007D07A /* KrollPromise.h in Headers */ = {isa = PBXBuildFile; fileRef = B70ABB8324B79C9C0007D07A /* KrollPromise.h */; settings = {ATTRIBUTES = (Public, ); }; }; - B70ABB8624B79DCF0007D07A /* KrollPromise.m in Sources */ = {isa = PBXBuildFile; fileRef = B70ABB8524B79DCF0007D07A /* KrollPromise.m */; }; B7624B6C216663C7000C365A /* ObjcProxy.h in Headers */ = {isa = PBXBuildFile; fileRef = B7624B6A216663C7000C365A /* ObjcProxy.h */; settings = {ATTRIBUTES = (Public, ); }; }; B7624B6D216663C7000C365A /* ObjcProxy.m in Sources */ = {isa = PBXBuildFile; fileRef = B7624B6B216663C7000C365A /* ObjcProxy.m */; }; B7D114DB2465F48600B95107 /* Module.h in Headers */ = {isa = PBXBuildFile; fileRef = B7D114DA2465F48600B95107 /* Module.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -192,6 +193,7 @@ /* Begin PBXFileReference section */ 4A175274218B64E70094C7B6 /* KrollTimerManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = KrollTimerManager.h; sourceTree = ""; }; 4A175275218B64E70094C7B6 /* KrollTimerManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = KrollTimerManager.m; sourceTree = ""; }; + 4A1FF4422523262A00A0F923 /* ErrorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorView.swift; sourceTree = ""; }; B106A6FB24535CB800B1305E /* JSValue+Addons.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "JSValue+Addons.h"; sourceTree = ""; }; B106A6FC24535CB800B1305E /* JSValue+Addons.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "JSValue+Addons.m"; sourceTree = ""; }; B10E8D4F2408559300578E8F /* APSHTTPClient.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = APSHTTPClient.xcframework; path = TitaniumKit/Libraries/APSHTTPClient/APSHTTPClient.xcframework; sourceTree = ""; }; @@ -204,8 +206,6 @@ B7385F5424659F83007DB1F2 /* AssetsModule.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AssetsModule.h; sourceTree = ""; }; B7385F572465A4B3007DB1F2 /* KrollModule.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = KrollModule.m; sourceTree = ""; }; B7385F582465A4B3007DB1F2 /* KrollModule.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = KrollModule.h; sourceTree = ""; }; - B70ABB8324B79C9C0007D07A /* KrollPromise.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KrollPromise.h; sourceTree = ""; }; - B70ABB8524B79DCF0007D07A /* KrollPromise.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KrollPromise.m; sourceTree = ""; }; B7624B6A216663C7000C365A /* ObjcProxy.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ObjcProxy.h; sourceTree = ""; }; B7624B6B216663C7000C365A /* ObjcProxy.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ObjcProxy.m; sourceTree = ""; }; B7D114DA2465F48600B95107 /* Module.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Module.h; sourceTree = ""; }; @@ -477,8 +477,6 @@ DB258D1A1F097680000D0D8D /* Bridge.m */, DB15FCC61F0A879B00A82C45 /* ImageLoader.h */, DB15FCC71F0A879B00A82C45 /* ImageLoader.m */, - B106A6FB24535CB800B1305E /* JSValue+Addons.h */, - B106A6FC24535CB800B1305E /* JSValue+Addons.m */, B7385F582465A4B3007DB1F2 /* KrollModule.h */, B7385F572465A4B3007DB1F2 /* KrollModule.m */, DB15FC9E1F0A863B00A82C45 /* LayoutConstraint.h */, @@ -587,6 +585,9 @@ DB15FC5E1F0A84C900A82C45 /* Webcolor.m */, DB15FC471F0A83A700A82C45 /* WebFont.h */, DB15FC481F0A83A700A82C45 /* WebFont.m */, + B106A6FB24535CB800B1305E /* JSValue+Addons.h */, + B106A6FC24535CB800B1305E /* JSValue+Addons.m */, + 4A1FF4422523262A00A0F923 /* ErrorView.swift */, ); name = API; path = Sources/API; @@ -773,6 +774,7 @@ TargetAttributes = { DB258CCC1F09757E000D0D8D = { CreatedOnToolsVersion = 8.3.3; + LastSwiftMigration = 1200; }; DB9635BE1F0AB4AC00C41B20 = { CreatedOnToolsVersion = 8.3.3; @@ -857,6 +859,7 @@ DB15FD0D1F0A894000A82C45 /* TiWindowProxy.m in Sources */, DB15FC991F0A862000A82C45 /* TiPoint.m in Sources */, B106A6FE24535CB800B1305E /* JSValue+Addons.m in Sources */, + 4A1FF4432523262A00A0F923 /* ErrorView.swift in Sources */, DB1596632075461400292B19 /* SBJSON.m in Sources */, DB15FCA11F0A863B00A82C45 /* LayoutConstraint.m in Sources */, DB15FC9D1F0A862D00A82C45 /* ListenerEntry.m in Sources */, @@ -1029,6 +1032,7 @@ isa = XCBuildConfiguration; buildSettings = { CLANG_ALLOW_NON_MODULAR_INCLUDES_IN_FRAMEWORK_MODULES = NO; + CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = NO; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; @@ -1071,6 +1075,8 @@ PROVISIONING_PROFILE_SPECIFIER = ""; "PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = ""; SKIP_INSTALL = YES; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; VALID_ARCHS = "$(ARCHS_STANDARD)"; }; name = Debug; @@ -1079,6 +1085,7 @@ isa = XCBuildConfiguration; buildSettings = { CLANG_ALLOW_NON_MODULAR_INCLUDES_IN_FRAMEWORK_MODULES = NO; + CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = NO; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; @@ -1119,6 +1126,7 @@ PROVISIONING_PROFILE_SPECIFIER = ""; "PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = ""; SKIP_INSTALL = YES; + SWIFT_VERSION = 5.0; VALID_ARCHS = "$(ARCHS_STANDARD)"; }; name = Release; diff --git a/iphone/TitaniumKit/TitaniumKit/Sources/API/ErrorView.swift b/iphone/TitaniumKit/TitaniumKit/Sources/API/ErrorView.swift new file mode 100644 index 00000000000..4bc59dc7d82 --- /dev/null +++ b/iphone/TitaniumKit/TitaniumKit/Sources/API/ErrorView.swift @@ -0,0 +1,333 @@ +/** +* Axway Titanium Mobile +* Copyright (c) 2020-Present by Axway, Inc. All Rights Reserved. +* Licensed under the terms of the Apache Public License +* Please see the LICENSE included with this distribution for details. +*/ + +import Foundation +import SwiftUI + +// MARK: View controller + +@available(iOS 13.0, *) +public class TiErrorViewController : UIViewController { + let error: TiScriptError + + var model: ErrorModel! + + @objc + public init(error: TiScriptError) { + self.error = error + super.init(nibName: nil, bundle: nil) + self.model = ErrorModel(error: error, modalVC: self) + } + + required public override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) { + fatalError("init(nibName:bundle:) has not been implemented") + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + public override func viewDidLoad() { + super.viewDidLoad() + + addErrorView() + } + + func addErrorView() { + let errorView = ErrorView(error: model) + let hostingController = UIHostingController(rootView: errorView) + hostingController.view.translatesAutoresizingMaskIntoConstraints = false + self.addChild(hostingController) + self.view.addSubview(hostingController.view) + hostingController.didMove(toParent: self) + self.parent?.navigationController?.navigationBar.titleTextAttributes = [NSAttributedString.Key.foregroundColor: ErrorTheme.error] + + NSLayoutConstraint.activate([ + hostingController.view.widthAnchor.constraint(equalTo: view.widthAnchor), + hostingController.view.heightAnchor.constraint(equalTo: view.heightAnchor), + hostingController.view.centerXAnchor.constraint(equalTo: view.centerXAnchor), + hostingController.view.centerYAnchor.constraint(equalTo: view.centerYAnchor), + ]) + } +} + +// MARK: SwiftUI + +@available(iOS 13.0, *) +struct ErrorView : View { + var error: ErrorModel + + var body: some View { + VStack { + ScrollView { + VStack(alignment: .leading) { + HStack { + VStack(alignment: .leading, spacing: 8) { + Text(error.name) + .font(.system(size: 20, weight: .medium)) + .foregroundColor(Color.init(ErrorTheme.error)) + Text(error.message) + .font(.subheadline) + } + .padding() + Spacer() + } + .background(Color.init(ErrorTheme.darkBg)) + + ErrorTraitView(trait: error.scriptTrait) + ErrorTraitView(trait: error.nativeTrait); + } + } + + Spacer() + + Button(action: { + TiApp.sharedApp()?.hideModalController(error.modalVC.parent?.navigationController, animated: true) + }) { + Text("Dismiss") + .frame(minWidth:0, maxWidth: .infinity, minHeight: 50) + .background(Color.init(ErrorTheme.error)) + .foregroundColor(.white) + .font(.system(size: 18)) + } + } + .edgesIgnoringSafeArea(.bottom) + .background(Color.init(ErrorTheme.primaryBg)) + } +} + +@available(iOS 13, *) +struct ErrorTraitView: View { + var trait: ErrorTrait + var body: some View { + VStack(alignment: .leading) { + Text(trait.label) + .font(.system(size: 22, weight: .medium)) + + if let sourceLine = trait.sourceLine, let location = trait.location { + Text("Source") + .font(.system(size: 17)) + .padding(.top, 1) + VStack(alignment:.leading) { + ScrollView(.horizontal, showsIndicators: false) { + HStack { + Text(sourceLine) + .font(.system(.subheadline, design: .monospaced)) + Spacer() + } + } + }.padding() + .background(Color.init(ErrorTheme.darkBg)) + Text(location) + .font(.caption) + .foregroundColor(Color.init(ErrorTheme.lightText)) + } + StackView(stack: trait.stack) + } + .padding(.bottom) + .padding(.horizontal) + } +} + +struct ErrorTrait { + var label: String + var sourceLine: String? + var location: String? + var stack: [StackEntry] +} + +@available(iOS 13, *) +struct StackView: View { + var stack: [StackEntry] + + var body: some View { + VStack(alignment: .leading) { + Text("Stack") + .padding(.top, 1) + ScrollView(.horizontal, showsIndicators: false) { + VStack(alignment: .leading, spacing: 10) { + ForEach(stack, id: \.self) { entry in + VStack(alignment:.leading) { + Text(entry.symbol) + .font(.system(.subheadline, design: .monospaced)) + .foregroundColor(Color.init(ErrorTheme.text)) + Text(entry.source) + .font(.system(.caption, design: .monospaced)) + .foregroundColor(Color.init(ErrorTheme.lightText)) + } + } + } + } + .padding(.horizontal) + } + } +} + +struct StackEntry: Hashable { + var symbol: String + var source: String +} + +@available(iOS 13.0, *) +struct ErrorModel { + var name: String + var message: String + var scriptTrait: ErrorTrait + var nativeTrait: ErrorTrait + var modalVC: UIViewController + + init(error: TiScriptError, modalVC: UIViewController) { + let errorDict = error.dictionaryValue + self.name = errorDict?["type"] as? String ?? "Error" + + self.message = error.message + + let scriptSourceLine = error.sourceLine ?? "Could not load source" + let scriptStack = error.parsedJsStack!.map({ (entry: [AnyHashable : Any]) -> StackEntry in + return StackEntry(symbol: entry["symbol"] as! String, source: entry["source"] as! String) + }) + let firstScriptSourceIndex = scriptStack.firstIndex { (entry) -> Bool in + return entry.source != "[native code]" + } ?? 0 + let scriptLocation = scriptStack[firstScriptSourceIndex].source + self.scriptTrait = ErrorTrait(label: "JavaScript", + sourceLine: scriptSourceLine, + location: scriptLocation, + stack: scriptStack) + + let nativeStack = error.formattedNativeStack!.map({ (line) -> StackEntry in + let components = line.split(separator: " ") + let symbol = components[2...].joined(separator: " "); + let source = components[...1].joined(separator: " "); + return StackEntry(symbol: symbol, source: source) + }) + var nativeSourceLine: String? + var nativeLocation: String? + if let rawLocation = errorDict?["nativeLocation"] as? String { + let regex = try! NSRegularExpression(pattern: #"(.*)\s\((.*:\d+)\)"#, options: []) + let nsrange = NSRange(rawLocation.startIndex.. UIColor in + if traitCollection.userInterfaceStyle == .dark { + return MaterialUI.redLighten1 + } else { + return MaterialUI.red + } + } + } + static var primaryBg: UIColor { + return UIColor { (traitCollection: UITraitCollection) -> UIColor in + if traitCollection.userInterfaceStyle == .dark { + return UIColor(red: 0x39 / 0xff, + green: 0x39 / 0xff, + blue: 0x39 / 0xff, + alpha: 1) + } else { + return UIColor.white + } + } + } + static var darkBg: UIColor { + return UIColor { (traitCollection: UITraitCollection) -> UIColor in + if traitCollection.userInterfaceStyle == .dark { + return MaterialUI.greyDarken4 + } else { + return MaterialUI.greyLighten4 + } + } + } + static var text: UIColor { + return UIColor { (traitCollection: UITraitCollection) -> UIColor in + if traitCollection.userInterfaceStyle == .dark { + return MaterialUI.greyLighten4 + } else { + return MaterialUI.greyDarken4 + } + } + } + static var lightText: UIColor { + return UIColor { (traitCollection: UITraitCollection) -> UIColor in + if traitCollection.userInterfaceStyle == .dark { + return MaterialUI.greyLighten1 + } else { + return MaterialUI.greyDarken1 + } + } + } +} diff --git a/iphone/TitaniumKit/TitaniumKit/Sources/API/TiApp.h b/iphone/TitaniumKit/TitaniumKit/Sources/API/TiApp.h index 255a7220b46..a2178550351 100644 --- a/iphone/TitaniumKit/TitaniumKit/Sources/API/TiApp.h +++ b/iphone/TitaniumKit/TitaniumKit/Sources/API/TiApp.h @@ -213,6 +213,11 @@ TI_INLINE void waitForMemoryPanicCleared() //WARNING: This must never be run on */ - (void)showModalError:(NSString *)message; +/** + Opens a modal view with detailed error information + */ +- (void)showDetailedModalError:(TiScriptError *)error; + /** Tells application to display modal view controller. diff --git a/iphone/TitaniumKit/TitaniumKit/Sources/API/TiApp.m b/iphone/TitaniumKit/TitaniumKit/Sources/API/TiApp.m index 64e2126bdd4..209a573aac8 100644 --- a/iphone/TitaniumKit/TitaniumKit/Sources/API/TiApp.m +++ b/iphone/TitaniumKit/TitaniumKit/Sources/API/TiApp.m @@ -23,6 +23,8 @@ #import #import +#import + TiApp *sharedApp; NSString *TITANIUM_VERSION; @@ -1145,6 +1147,25 @@ - (void)showModalError:(NSString *)message [[[self controller] topPresentedController] presentViewController:nav animated:YES completion:nil]; } +- (void)showDetailedModalError:(TiScriptError *)error +{ + if ([[TiSharedConfig defaultConfig] showErrorController] == NO) { + NSLog(@"[ERROR] Application received error: %@", error.message); + return; + } + ENSURE_UI_THREAD(showDetailedModalError, error); + + if (@available(iOS 13, *)) { + TiErrorController *errorVC = [[TiErrorController alloc] initWithScriptError:error]; + TiErrorNavigationController *nav = [[[TiErrorNavigationController alloc] initWithRootViewController:errorVC] autorelease]; + RELEASE_TO_NIL(errorVC); + + [[[self controller] topPresentedController] presentViewController:nav animated:YES completion:nil]; + } else { + [self showModalError:error.description]; + } +} + - (void)showModalController:(UIViewController *)modalController animated:(BOOL)animated { [controller showControllerModal:modalController animated:animated]; diff --git a/iphone/TitaniumKit/TitaniumKit/Sources/API/TiBindingTiValue.m b/iphone/TitaniumKit/TitaniumKit/Sources/API/TiBindingTiValue.m index 6c40d671f4c..6b05c8bfedf 100644 --- a/iphone/TitaniumKit/TitaniumKit/Sources/API/TiBindingTiValue.m +++ b/iphone/TitaniumKit/TitaniumKit/Sources/API/TiBindingTiValue.m @@ -295,7 +295,8 @@ JSValueRef TiBindingTiValueFromNSObject(JSContextRef jsContext, NSObject *obj) // Add "nativeStack" key NSArray *nativeStack = [(NSException *)obj callStackSymbols]; - NSInteger startIndex = 0; + NSInteger startIndex = 0; // Starting at index = 4 to not include the script-error API's + if (nativeStack == nil) { // the exception was created, but never thrown, so grab the current stack frames nativeStack = [NSThread callStackSymbols]; // this happens when we construct an exception manually in our ENSURE macros for obj-c proxies startIndex = 2; // drop ObjcProxy.throwException and this method from frames diff --git a/iphone/TitaniumKit/TitaniumKit/Sources/API/TiErrorController.h b/iphone/TitaniumKit/TitaniumKit/Sources/API/TiErrorController.h index 1dba08f5a62..3091e09da7f 100644 --- a/iphone/TitaniumKit/TitaniumKit/Sources/API/TiErrorController.h +++ b/iphone/TitaniumKit/TitaniumKit/Sources/API/TiErrorController.h @@ -7,6 +7,8 @@ #import +@class TiScriptError; + @interface TiErrorNavigationController : UINavigationController @end @@ -14,12 +16,13 @@ @interface TiErrorController : UIViewController { NSString *error; - + TiScriptError *scriptError; UIScrollView *scrollView; UITextView *messageView; UIButton *continueButton; } - (id)initWithError:(NSString *)error_; +- (id)initWithScriptError:(TiScriptError *)scriptError_; @end diff --git a/iphone/TitaniumKit/TitaniumKit/Sources/API/TiErrorController.m b/iphone/TitaniumKit/TitaniumKit/Sources/API/TiErrorController.m index d3ad3e44d52..ca26e8b25ee 100644 --- a/iphone/TitaniumKit/TitaniumKit/Sources/API/TiErrorController.m +++ b/iphone/TitaniumKit/TitaniumKit/Sources/API/TiErrorController.m @@ -10,9 +10,12 @@ #import "TiErrorController.h" #import "TiApp.h" #import "TiBase.h" +#import "TiExceptionHandler.h" #import "TiUtils.h" #import +#import + @implementation TiErrorNavigationController - (UIViewController *)childViewControllerForHomeIndicatorAutoHidden @@ -32,11 +35,21 @@ - (id)initWithError:(NSString *)error_ return self; } +- (instancetype)initWithScriptError:(TiScriptError *)scriptError_ +{ + if (self = [super init]) { + error = [scriptError_.description retain]; + scriptError = [scriptError_ retain]; + } + return self; +} + - (void)dealloc { RELEASE_TO_NIL(scrollView); RELEASE_TO_NIL(messageView); RELEASE_TO_NIL(error); + RELEASE_TO_NIL(scriptError); [super dealloc]; } @@ -54,7 +67,18 @@ - (void)loadView self.navigationItem.title = NSLocalizedString(@"Application Error", nil); self.navigationController.navigationBar.titleTextAttributes = @{ NSForegroundColorAttributeName : errorColor }; - [self.view setBackgroundColor:[TiUtils isIOSVersionOrGreater:@"13.0"] ? UIColor.systemBackgroundColor : UIColor.whiteColor]; + if (@available(iOS 13.0, *)) { + if (scriptError != nil) { + TiErrorViewController *errorVC = [[TiErrorViewController alloc] initWithError:scriptError]; + [self addChildViewController:errorVC]; + [self.view addSubview:errorVC.view]; + [errorVC didMoveToParentViewController:self]; + [errorVC release]; + return; + } + } + + [self.view setBackgroundColor:UIColor.whiteColor]; // release previous allocations RELEASE_TO_NIL(scrollView); diff --git a/iphone/TitaniumKit/TitaniumKit/Sources/API/TiExceptionHandler.h b/iphone/TitaniumKit/TitaniumKit/Sources/API/TiExceptionHandler.h index ed820893e74..adfbb2898b4 100644 --- a/iphone/TitaniumKit/TitaniumKit/Sources/API/TiExceptionHandler.h +++ b/iphone/TitaniumKit/TitaniumKit/Sources/API/TiExceptionHandler.h @@ -54,6 +54,11 @@ */ @property (nonatomic, readonly) NSString *backtrace; +/** + * Returns the parsed JavaScript stack trace entries consisting of smybol name and source location. + */ +@property (nonatomic, readonly) NSArray *parsedJsStack; + /** * Returns the native stack as a static string. */ diff --git a/iphone/TitaniumKit/TitaniumKit/Sources/API/TiExceptionHandler.m b/iphone/TitaniumKit/TitaniumKit/Sources/API/TiExceptionHandler.m index 8fad5f10ff7..0f160fbc04e 100644 --- a/iphone/TitaniumKit/TitaniumKit/Sources/API/TiExceptionHandler.m +++ b/iphone/TitaniumKit/TitaniumKit/Sources/API/TiExceptionHandler.m @@ -98,7 +98,7 @@ - (void)showScriptError:(TiScriptError *)error [errorDict setObject:[NSNumber numberWithLong:error.lineNo] forKey:@"line"]; NSString *stackTrace = [error.formattedNativeStack componentsJoinedByString:@"\n"]; [errorDict setObject:stackTrace forKey:@"nativeStack"]; - [[TiApp app] showModalError:[error description]]; + [[TiApp app] showDetailedModalError:error]; [[NSNotificationCenter defaultCenter] postNotificationName:kTiErrorNotification object:self userInfo:errorDict]; } @@ -125,6 +125,7 @@ @implementation TiScriptError @synthesize column = _column; @synthesize dictionaryValue = _dictionaryValue; @synthesize backtrace = _backtrace; +@synthesize parsedJsStack = _parsedJsStack; @synthesize nativeStack = _nativeStack; @synthesize formattedNativeStack = _formattedNativeStack; @@ -172,6 +173,7 @@ - (void)dealloc RELEASE_TO_NIL(_sourceURL); RELEASE_TO_NIL(_sourceLine); RELEASE_TO_NIL(_backtrace); + RELEASE_TO_NIL(_parsedJsStack); RELEASE_TO_NIL(_dictionaryValue); RELEASE_TO_NIL(_nativeStack); RELEASE_TO_NIL(_formattedNativeStack); @@ -192,10 +194,31 @@ - (NSString *)description NSString *type = self.dictionaryValue[@"type"] != nil ? self.dictionaryValue[@"type"] : @"Error"; [message appendFormat:@"%@: %@", type, self.message]; + NSMutableString *formattedJsStack = [[NSMutableString new] autorelease]; + for (NSDictionary *entry in self.parsedJsStack) { + [formattedJsStack appendFormat:@"\n at %@ (%@)", entry[@"symbol"], entry[@"source"]]; + } + [message appendString:formattedJsStack]; + [message appendFormat:@"\n\n %@", [self.formattedNativeStack componentsJoinedByString:@"\n "]]; + + return message; +} + +- (NSString *)detailedDescription +{ + return _dictionaryValue != nil ? [_dictionaryValue description] : [self description]; +} + +- (NSArray *)parsedJsStack +{ + if (_parsedJsStack != nil) { + return _parsedJsStack; + } + NSString *encodedBundlePath = [NSString stringWithFormat:@"file://%@", [[NSBundle mainBundle].bundlePath stringByReplacingOccurrencesOfString:@" " withString:@"%20"]]; NSString *jsStack = [self.backtrace stringByReplacingOccurrencesOfString:encodedBundlePath withString:@""]; NSArray *jsStackLines = [jsStack componentsSeparatedByCharactersInSet:NSCharacterSet.newlineCharacterSet]; - NSMutableString *formattedJsStack = [[NSMutableString new] autorelease]; + NSMutableArray *entries = [[NSMutableArray new] autorelease]; for (NSString *line in jsStackLines) { NSRange atSymbolRange = [line rangeOfString:@"@"]; NSInteger atSymbolIndex = atSymbolRange.location == NSNotFound ? -1 : atSymbolRange.location; @@ -208,18 +231,11 @@ - (NSString *)description if ([symbolName isEqualToString:@"global code"]) { continue; } - [formattedJsStack appendFormat:@"\n at %@ (%@)", symbolName, source]; + [entries addObject:@{ @"symbol" : symbolName, @"source" : source }]; } - [message appendString:formattedJsStack]; - - [message appendFormat:@"\n\n %@", [self.formattedNativeStack componentsJoinedByString:@"\n "]]; - - return message; -} + _parsedJsStack = [entries copy]; -- (NSString *)detailedDescription -{ - return _dictionaryValue != nil ? [_dictionaryValue description] : [self description]; + return entries; } - (NSArray *)formattedNativeStack @@ -235,7 +251,9 @@ - (NSString *)detailedDescription // starting at index = 2 to not include this method and callee startIndex = 2; } - NSMutableArray *formattedStackTrace = [[[NSMutableArray alloc] init] autorelease]; + + NSMutableArray *formattedStackTrace = [[NSMutableArray new] autorelease]; + NSUInteger stackTraceLength = MIN([stackTrace count], 20 + startIndex); // re-size stack trace and format results. for (NSInteger i = startIndex; i < stackTraceLength; i++) { diff --git a/iphone/TitaniumKit/TitaniumKit/Sources/API/TiUtils.m b/iphone/TitaniumKit/TitaniumKit/Sources/API/TiUtils.m index 3664073035e..e7f71ef694f 100644 --- a/iphone/TitaniumKit/TitaniumKit/Sources/API/TiUtils.m +++ b/iphone/TitaniumKit/TitaniumKit/Sources/API/TiUtils.m @@ -1366,6 +1366,9 @@ + (TiScriptError *)scriptErrorFromValueRef:(JSValueRef)valueRef inContext:(JSGlo NSArray *nativeStack = [callStack componentsSeparatedByCharactersInSet:NSCharacterSet.newlineCharacterSet]; [errorDict setObject:nativeStack forKey:@"nativeStack"]; } + if ([error hasProperty:@"nativeLocation"]) { + [errorDict setObject:[error[@"nativeLocation"] toString] forKey:@"nativeLocation"]; + } return [[[TiScriptError alloc] initWithDictionary:errorDict] autorelease]; } diff --git a/iphone/iphone/Titanium.xcodeproj/project.pbxproj b/iphone/iphone/Titanium.xcodeproj/project.pbxproj index 983878f0bb0..28960190e35 100644 --- a/iphone/iphone/Titanium.xcodeproj/project.pbxproj +++ b/iphone/iphone/Titanium.xcodeproj/project.pbxproj @@ -2398,6 +2398,8 @@ OTHER_LDFLAGS = ( "$(inherited)", "-lxml2", + "-weak_framework", + SwiftUI, ); "OTHER_LDFLAGS[sdk=iphoneos*]" = "$(inherited)"; "OTHER_LDFLAGS[sdk=iphonesimulator*]" = "$(inherited)"; @@ -2455,6 +2457,8 @@ OTHER_LDFLAGS = ( "$(inherited)", "-lxml2", + "-weak_framework", + SwiftUI, ); "OTHER_LDFLAGS[sdk=iphoneos*]" = "$(inherited)"; "OTHER_LDFLAGS[sdk=iphonesimulator*]" = "$(inherited)";