Titanium JIRA Archive
Appcelerator Community (AC)

[AC-6712] Opening again a webdialog after you've opened and closed it causes a crash on iOS

GitHub Issuen/a
TypeBug
Priorityn/a
StatusClosed
ResolutionNot Our Bug
Resolution Date2021-08-05T14:23:43.000+0000
Affected Version/sn/a
Fix Version/sn/a
ComponentsTitanium SDK & CLI
Labelscrash, ios
ReporterFerdinando Traversa
AssigneeAbir Mukherjee
Created2021-07-20T13:04:40.000+0000
Updated2021-08-05T14:23:43.000+0000

Description

Opening a webdialog triggered by a button event listener after you've opened and closed it causes a crash on iOS. On Android everything works correctly (and the Dialog opens again and again when required). Attached, you can find two different crash logs for the same problem. The source code of the app is on: https://github.com/ferdi2005/monumenti The involved controller is upload/config and the button has the id login_start.
[DEBUG] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Application tried to present modally a view controller <SFSafariViewController: 0x7f96c513be00> that is already being presented by <TiRootViewController: 0x7f96c48aac00>.'
[DEBUG] *** First throw call stack:
[DEBUG] (
[DEBUG]         0   CoreFoundation                      0x00000001124e7fba __exceptionPreprocess + 242
[DEBUG]         1   libobjc.A.dylib                     0x000000011208dff5 objc_exception_throw + 48
[DEBUG]         2   UIKitCore                           0x000000012105df32 -[UIViewController _presentViewController:withAnimationController:completion:] + 6016
[DEBUG]         3   UIKitCore                           0x000000012105e93c __63-[UIViewController _presentViewController:animated:completion:]_block_invoke + 98
[DEBUG]         4   UIKitCore                           0x000000012107949c -[_UIViewControllerTransitionCoordinator _applyBlocks:releaseBlocks:] + 294
[DEBUG]         5   UIKitCore                           0x000000012107570e -[_UIViewControllerTransitionContext _runAlongsideCompletions] + 126
[DEBUG]         6   UIKitCore                           0x0000000121075482 -[_UIViewControllerTransitionContext completeTransition:] + 106
[DEBUG]         7   SafariServices                      0x000000010db79007 __60-[SFInteractiveDismissController _presentAnimateTransition:]_block_invoke_2 + 39
[DEBUG]         8   UIKitCore                           0x0000000121cd1e7a -[UIViewAnimationBlockDelegate _didEndBlockAnimation:finished:context:] + 779
[DEBUG]         9   UIKitCore                           0x0000000121ca2831 -[UIViewAnimationState sendDelegateAnimationDidStop:finished:] + 231
[DEBUG]         10  UIKitCore                           0x0000000121ca2dd6 -[UIViewAnimationState animationDidStop:finished:] + 263
[DEBUG]         11  UIKitCore                           0x0000000121ca2f57 -[UIViewAnimationState animationDidStop:finished:] + 648
[DEBUG]         12  QuartzCore                          0x000000011127ddc0 _ZN2CA5Layer23run_animation_callbacksEPv + 308
[DEBUG]         13  libdispatch.dylib                   0x000000011622e8df _dispatch_client_callout + 8
[DEBUG]         14  libdispatch.dylib                   0x000000011623ba27 _dispatch_main_queue_callback_4CF + 1045
[DEBUG]         15  CoreFoundation                      0x00000001124558f8 __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__ + 9
[DEBUG]         16  CoreFoundation                      0x0000000112450169 __CFRunLoopRun + 2781
[DEBUG]         17  CoreFoundation                      0x000000011244f1a7 CFRunLoopRunSpecific + 567
[DEBUG]         18  GraphicsServices                    0x0000000125b8fd85 GSEventRunModal + 139
[DEBUG]         19  UIKitCore                           0x00000001217864df -[UIApplication _run] + 912
[DEBUG]         20  UIKitCore                           0x000000012178b39c UIApplicationMain + 101
[DEBUG]         21  WLM Italia                          0x000000010b6f5355 main + 1173
[DEBUG]         22  libdyld.dylib                       0x00000001162b7bbd start + 1
[DEBUG]         23  ???                                 0x0000000000000001 0x0 + 1
[DEBUG] )

Attachments

FileDateSize
crashlog.crash2021-07-20T13:01:10.000+0000101172

Comments

  1. Ferdinando Traversa 2021-07-20

    Workaround: use
     
       if (OS_ANDROID || !Dialog.isOpen()) {
       // Dialog opening code
       }
       
    It seems that the window doesn't close instantly even if it seems closed, so it raises a crash when reopened instantly.
  2. Ewan Harris 2021-07-20

    [~ferdi.traversa] I don't believe this is caused by the WebDialog module, the code below works ok for opening and closing a WebDialog multiple times
       const wd = require('ti.webdialog');
       const win = Ti.UI.createWindow();
       wd.addEventListener('close', () => {
       	console.log('webdialog closed');
       });
       win.addEventListener('click', () => {
       	if (!wd.isSupported()) {
       		console.log('not supported');
       		return;
       	}
       	wd.open({
       		url: 'https://axway.com',
       		dismissButtonStyle: wd.DISMISS_BUTTON_STYLE_DONE
       	});
       });
       win.open();
       
    I believe what is happening here is that every time you are calling the retrieveUserData function it is adding a new event listener to start_login [here](https://github.com/ferdi2005/monumenti/blob/7175d446272e5fae259933df029f772258656da8/app/controllers/upload/config.js#L207-L209), so there are multiple calls to startLogin that are attempting to open the WebDialog multiple times causing this error. That's why calling Dialog.isOpen() is fixing this. I've tried to reduce down your code to demonstrate this
    const wd = require('ti.webdialog');
       let loggedIn = false;
       
       const win = Ti.UI.createWindow();
       const loginBtn = Ti.UI.createButton({ title: 'Click me login', top: 100 });
       const logoutBtn = Ti.UI.createButton({ title: 'Click me to logout', top: 150 });
       logoutBtn.addEventListener('click', () => {
       	loggedIn = false;
       	setupLoginFlow();
       });
       
       function userLogin() {
       	console.log('userLogin called');
       	if (!wd.isSupported()) {
       		console.log('not supported');
       		return;
       	}
       	wd.open({
       		url: 'https://axway.com',
       		dismissButtonStyle: wd.DISMISS_BUTTON_STYLE_DONE
       	});
       	wd.addEventListener('close', () => {
       		console.log('webdialog closed');
       		loggedIn = true;
       		setupLoginFlow();
       	});
       }
       
       function setupLoginFlow() {
       	if (loggedIn) {
       		loginBtn.hide();
       	} else {
       		loginBtn.show();
       		loginBtn.addEventListener('click', () => {
       			userLogin();
       		})
       	}
       }
       
       
       win.add(loginBtn);
       win.add(logoutBtn);
       win.open();
       setupLoginFlow();
       
    What I would recommend is making your event handler a named function and then removing it after calling userLogin, as you always appear to be calling retrieveUserData that should ensure you only have one event handler on the button at a time.
       const wd = require('ti.webdialog');
       let loggedIn = false;
       
       const win = Ti.UI.createWindow();
       const loginBtn = Ti.UI.createButton({ title: 'Click me login', top: 100 });
       const logoutBtn = Ti.UI.createButton({ title: 'Click me to logout', top: 150 });
       logoutBtn.addEventListener('click', () => {
       	loggedIn = false;
       	setupLoginFlow();
       });
       
       function userLogin() {
       	console.log('userLogin called');
       	if (!wd.isSupported()) {
       		console.log('not supported');
       		return;
       	}
       	wd.open({
       		url: 'https://axway.com',
       		dismissButtonStyle: wd.DISMISS_BUTTON_STYLE_DONE
       	});
       	wd.addEventListener('close', () => {
       		console.log('webdialog closed');
       		loggedIn = true;
       		setupLoginFlow();
       	});
       }
       
       function setupLoginFlow() {
       	if (loggedIn) {
       		loginBtn.hide();
       	} else {
       		loginBtn.show();
       		loginBtn.addEventListener('click', function login () { // EDIT: changed to named function
       			userLogin();
       			loginBtn.removeEventListener('click', login); // EDIT: removing event listener now
       		});
       	}
       }
       win.add(loginBtn);
       win.add(logoutBtn);
       win.open();
       setupLoginFlow();
       
  3. Ferdinando Traversa 2021-07-22

    Woops! Thank you very much Ewan for spotting the real issue.

JSON Source