2

My problem: JavaScript calls into the plugin, which forks a thread that opens a NSOpenPanel (or NSSavePanel) dialog. Often this works, but sometimes it crashes on "runModal", on both Firefox and Chrome. The crash seems to happen randomly and more frequently on some machines (10.7 maybe) than others. The stack location of the crash varies, but generally occurs on the thread that opens the dialog.

I use a POSIX thread because it's cross-platform code, but I do spawn a NSThread to let Cocoa know:

NSThread* t = [[NSThread alloc] init];
[t start];
[t release];

The code that opens the dialog:

NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; // necessary?
NSOpenPanel* openPanel = [NSOpenPanel openPanel];
[openPanel setAllowsMultipleSelection:_allowMultipleFiles];
[openPanel setCanChooseFiles:YES];
[openPanel setCanChooseDirectories:YES];
NSInteger result = [openPanel runModal]; // crash
... handle result ...
[pool release];

A stack trace:

Process:         plugin-container [25536]
Path:            /Applications/Firefox.app/Contents/MacOS/plugin-container.app/Contents/MacOS/plugin-container
Identifier:      org.mozilla.plugincontainer
Version:         ??? (1.0)
Code Type:       X86-64 (Native)
Parent Process:  firefox-bin [25531]

Date/Time:       2011-12-09 17:12:04.271 -0800
OS Version:      Mac OS X 10.6.8 (10K549)
Report Version:  6

Exception Type:  EXC_BAD_ACCESS (SIGSEGV)
Exception Codes: KERN_INVALID_ADDRESS at 0x000000000000000d
Crashed Thread:  14

Thread 14 Crashed:
0   XUL                             0x0000000100d84ff4 mac_plugin_interposing_child_OnSetCursor + 38068
1   XUL                             0x0000000100dd38bd mac_plugin_interposing_child_OnSetCursor + 359805
2   XUL                             0x0000000100d7b247 mac_plugin_interposing_child_OnHideCursor + 1175
3   com.apple.AppKit                0x00007fff801cbfdb _NSCreateWindowWithOpaqueShape2 + 610
4   com.apple.AppKit                0x00007fff80160691 -[NSWindow _commonAwake] + 1214
5   com.apple.AppKit                0x00007fff803c282f -[NSWindow _setModal:] + 138
6   com.apple.AppKit                0x00007fff803c2183 -[NSApplication _orderFrontModalWindow:relativeToWindow:] + 482
7   com.apple.AppKit                0x00007fff803c1ce7 -[NSApplication _commonBeginModalSessionForWindow:relativeToWindow:modalDelegate:didEndSelector:contextInfo:] + 714
8   com.apple.AppKit                0x00007fff803c1a18 -[NSApplication beginModalSessionForWindow:] + 36
9   com.apple.AppKit                0x00007fff803c193a -[NSApplication runModalForWindow:] + 106
10  XUL                             0x0000000100d7af01 mac_plugin_interposing_child_OnHideCursor + 337
11  com.apple.AppKit                0x00007fff80627112 -[NSSavePanel runModal] + 318
12  com.aspera.AsperaWeb            0x000000010509f9d4 Connect::Web::MacOpenFolderDialogImpl::doShow() + 596
13  com.aspera.AsperaWeb            0x00000001050a0fa3 Connect::Web::IDialogImpl::show() + 45
14  com.aspera.AsperaWeb            0x00000001050a1e07 Connect::Web::OpenFolderDialog::showHelper(bool) + 141
15  com.aspera.AsperaWeb            0x00000001050a1ef0 Connect::Web::OpenFolderDialog::run() + 42
16  com.aspera.AsperaWeb            0x000000010509eb91 Connect::Web::SingleJobWorker::run() + 63
17  com.aspera.AsperaWeb            0x0000000105098160 start_helper + 38
18  libSystem.B.dylib               0x00007fff8593afd6 _pthread_start + 331
19  libSystem.B.dylib               0x00007fff8593ae89 thread_start + 13

Thread 14 crashed with X86 Thread State (64-bit):
  rax: 0x0000000000000000  rbx: 0x0000000102063840  rcx: 0x0000000000000000  rdx: 0x0000000101e27ee0
  rdi: 0x0000000101e0bcc8  rsi: 0x0000000000000000  rbp: 0x0000000101e27ef0  rsp: 0x000000011d5805e0
   r8: 0x0000000101e0bcc8   r9: 0x00000001040fc0a4  r10: 0x00007fffffe001a0  r11: 0x0000000000000202
  r12: 0x0000000101e27ee0  r13: 0x0000000000000001  r14: 0x000000011d5805e0  r15: 0x000000011d580754
  rip: 0x0000000100d84ff4  rfl: 0x0000000000010246  cr2: 0x000000000000000d

I'm guessing there's a problem with opening a dialog on a secondary thread. Maybe I need to open it on the main thread? However, I do want to keep the JavaScript call asynchronous. I'm looking at NSNotificationQueue or performSelectorOnMainThread.

Peter Tseng
  • 13,613
  • 4
  • 67
  • 57

3 Answers3

4

I have a dialog abstraction I use in FireBreath here: https://gist.github.com/1368648

It turns out that you can't use runModal on a secondary thread; it has to be on the main thread of the application. This is a challenge in a NPAPI plugin because you also can't block the main thread during a NPAPI call or the browser (at least chrome and firefox 4+) is likely to kill you.

The solution is to have it run on the main thread, but not as part of the NPAPI call; you can do this by using a performSelectorOnMainThread and not waiting for it to complete. If you provide some sort of callback (an NPObject javascript function works great) you can invoke that when your dialog is done.

FireBreath uses this method for its BrowserHost::ScheduleOnMainThread function, which is what I now use in that gist I referenced. Someone (you?) posted a comment on the gist asking if it works for me; the gist at the time of the comment did not, but I have updated it with the fix (which I just figured out yesterday). Since we updated it to use the performSelectorOnMainThread to run the call later I haven't been able to reproduce the crash.

Looking back, you're already familiar with calling back on the main thread this way: How to callback plugin thread on Safari 5.1 on OSX?

You use the same technique; you'd think blocking the thread there would also be dangerous, but since during a modal dialog the system event loop continues to run you're okay; your code doesn't return, but other events still get dispatched. The browser isn't waiting for a NPAPI call to return so it doesn't seem to have any issues.

Community
  • 1
  • 1
taxilian
  • 14,229
  • 4
  • 34
  • 73
  • Thanks again. Yeah, I posted to the gist. Funny, I had forgotten that I use that function in Safari to call JavaScript from the main thread. It is interesting how modal dialogs are implemented to allow other events to occur while a dialog is up. I'm glad it works. It took quite a while to conclude that the cause of the crash was opening dialogs on the secondary thread instead of problems in my own code. – Peter Tseng Dec 13 '11 at 04:34
  • Yeah; wish you'd popped into the FireBreath IRC room, linearray and I were working on the problem just last week. He found a solution that led me to realize that this (simpler) solution would probably work. At least your timing is good -- we'd just barely confirmed that it was working =] – taxilian Dec 13 '11 at 16:42
2

All Appkit isn't thread safe and all Appkit methods should happen on the main thread. You can use the performselectoronmainthread... Methods to force the logic to be on the main thread.

Grady Player
  • 14,399
  • 2
  • 48
  • 76
0

Open panels should be asynchronous anyway, so there is no reason you can't show the open panel on the main thread. Simply don't use runModal. Ideally, show it as a sheet on the browser window.

When it gets back to you, you can return the result to the JavaScript side by whatever means you were already planning to use for that.

Peter Hosey
  • 95,783
  • 15
  • 211
  • 370
  • Oh yeah, I considered using beginSheetModalForWindow, but how do you get the browser's NSWindow? – Peter Tseng Dec 10 '11 at 02:28
  • @PeterTseng: That I have no idea about. I did a Google search and found this: https://discussions.apple.com/thread/1786090?start=0&tstart=0 but I don't know if that's the right way or if there's a better one, or how well it works in non-WebKit browsers. – Peter Hosey Dec 10 '11 at 02:59
  • 3
    Not only is that not the right way, there *is* no right way. Safari 64-bit, Firefox 64-bit, and Chrome all run plugins in another process, which means it is *impossible* to get a reference to the browser's window. The approach described there simply doesn't apply to modern browser plugin architectures. – smorgan Dec 12 '11 at 10:40