importSwiftUIimportUIKitstructMastgTest{staticfuncmastgTest(completion:@escaping(String)->Void){DispatchQueue.main.async{//Buildthealertletalert=UIAlertController(title:"Enter sensitive text",message:"Please fill in all fields.",preferredStyle:.alert)alert.addTextField{tfintf.placeholder="Name"tf.autocorrectionType=.defaulttf.spellCheckingType=.notf.accessibilityIdentifier="name_field"}alert.addTextField{tfintf.placeholder="E-Mail"tf.autocorrectionType=.notf.accessibilityIdentifier="email_field"}alert.addTextField{tfintf.placeholder="Password"tf.isSecureTextEntry=truetf.accessibilityIdentifier="password_field"}alert.addAction(UIAlertAction(title:"OK",style:.default,handler:{_inletfirst=alert.textFields?[0].text??""letsecond=alert.textFields?[1].text??""letthird=alert.textFields?[2].text??""completion("Submitted values: '\(first)', '\(second)', \(third)")}))//Presentfromthetopmostviewcontrollerifletpresenter=topViewController(){presenter.present(alert,animated:true,completion:nil)}else{completion("Failed to present alert (no active view controller).")}}}//FindsthecurrentlyvisibleviewcontrollertopresentfromprivatestaticfunctopViewController(base:UIViewController?={letscenes=UIApplication.shared.connectedScenes.compactMap{$0as?UIWindowScene}letkeyWindow=scenes.flatMap{$0.windows}.first{$0.isKeyWindow}returnkeyWindow?.rootViewController}())->UIViewController?{ifletnav=baseas?UINavigationController{returntopViewController(base:nav.visibleViewController)}iflettab=baseas?UITabBarController{returntopViewController(base:tab.selectedViewController)}ifletpresented=base?.presentedViewController{returntopViewController(base:presented)}returnbase}}
constUITextAutocorrectionType={0:".default",1:".no",2:".yes"};constUITextSpellCheckingType={0:".default",1:".no",2:".yes"};constclasses=["UITextField","UITextView","UISearchBar"];classes.forEach(inputClass=>hook(inputClass));functionhook(inputClass){constcls=ObjC.classes[inputClass];if(!cls||!cls["- resignFirstResponder"]){return;}Interceptor.attach(cls["- resignFirstResponder"].implementation,{onEnter(args){constself=newObjC.Object(args[0]);constcorrectionType=UITextAutocorrectionType[self.autocorrectionType()];constisSecure=self.isSecureTextEntry&&self.isSecureTextEntry();letaid=null;if(self.$methods.indexOf("- accessibilityIdentifier")!==-1){aid=self.accessibilityIdentifier();}letspellType=null;try{spellType=UITextSpellCheckingType[self.spellCheckingType()];}catch(e){spellType=null;}letplaceholder=null;if(self.$methods.indexOf("- placeholder")!==-1){try{placeholder=self.placeholder();}catch(e){placeholder=null;}}letcacheable="Eligible for caching";if(isSecure||(correctionType===".no"&&spellType===".no")){cacheable="Excluded from caching";}console.log(`${cacheable} [autocorrectionType=${correctionType}, spellCheckingType=${spellType}, isSecureTextEntry=${isSecure}, class=${self.$className}, aid=${aid}, placeholder=${placeholder}]: "${self.text()}"`);}});}
The script hooks into text input controls at runtime and monitors when they lose focus. For each interaction, it captures the entered text, the input field class, accessibility identifier when available, placeholder text when available, and the relevant input traits such as autocorrectionType, spellCheckingType, and isSecureTextEntry. Based on these values, it reports whether the input is eligible for keyboard caching.
The test fails because the output shows two text fields that are eligible for keyboard caching and contain sensitive information: the user's name and email address.
The name input has autocorrectionType set to default, making it eligible for keyboard caching.
The email input has autocorrectionType set to no but spellCheckingType set to default, making it eligible for keyboard caching.
The password input is protected against keyboard caching because isSecureTextEntry is set to true.