Bug 130767 - Don't handle IPC messages in event tracking run loop mode
Summary: Don't handle IPC messages in event tracking run loop mode
Status: REOPENED
Alias: None
Product: WebKit
Classification: Unclassified
Component: WebKit2 (show other bugs)
Version: 528+ (Nightly build)
Hardware: Unspecified Unspecified
: P2 Normal
Assignee: Alexey Proskuryakov
URL:
Keywords: InRadar
Depends on: 130869
Blocks:
  Show dependency treegraph
 
Reported: 2014-03-25 23:48 PDT by Alexey Proskuryakov
Modified: 2014-06-04 03:30 PDT (History)
10 users (show)

See Also:


Attachments
proposed patch (18.80 KB, patch)
2014-03-26 00:10 PDT, Alexey Proskuryakov
no flags Details | Formatted Diff | Diff
patch for landing (41.90 KB, patch)
2014-03-26 22:42 PDT, Alexey Proskuryakov
commit-queue: commit-queue-
Details | Formatted Diff | Diff
patch for landing (41.89 KB, patch)
2014-03-27 09:28 PDT, Alexey Proskuryakov
commit-queue: commit-queue-
Details | Formatted Diff | Diff

Note You need to log in before you can comment on or make changes to this bug.
Description Alexey Proskuryakov 2014-03-25 23:48:53 PDT
In MiniBrowser, hit Command-Option-N quickly several times, and hit an assertion:

ASSERT(!_data->_keyDownEventBeingResent);

What happens here is that -[NSCarbonMenuImpl performActionWithHighlightingForItemAtIndex:] calls _NSUnhighlightCarbonMenu(), which spins the run loop in NSEventTrackingRunLoopMode. And given that our IPC dispatches messages to main event loop in kCFRunLoopCommonModes, we get arbitrary reentrancy.

<rdar://problem/16307487>
Comment 1 Alexey Proskuryakov 2014-03-26 00:10:25 PDT
Created attachment 227831 [details]
proposed patch

Changed RunLoop to only wake up itself or fire timers in default and modal run loop modes, removing event tracking one and anything else other frameworks may have registered.

- This also addresses a problem with modal alerts appearing while menus are open (setTimeout("alert(0)", 3000), and open a Safari menu).

- I didn't change other instances of kCFRunLoopCommonModes that we have (notably WebCore timers, LayerFlushScheduler, iOS WebCoreThread code, page loader scheduling, callOnMainThread()).

- Tested that drag and drop of text and images within contenteditable still works.

- Tested that showModalDialog() successfully loads a page, and timers run in this page. Tested that timers are still paused in a page that opened the dialog.

- Confusingly, I couldn't see any difference in behavior of showModalDialog() whether I allowed NSModalPanelRunLoopMode or not.

- Timers in web pages behave strangely when menus are open. They keep firing - and the page keeps repainting - when I'm slowly moving the mouse between menus or menu items, but the timers stop after I move it quickly!

The last two items concern me. Thoughts? Any other reasons why this may not be a good change?
Comment 2 Darin Adler 2014-03-26 08:59:36 PDT
Comment on attachment 227831 [details]
proposed patch

View in context: https://bugs.webkit.org/attachment.cgi?id=227831&action=review

Seems OK. Tricky to decide which run loop modes we want to omit. In the past, it was important to include NSEventTrackingRunLoopMode so that web page loading didn’t stop any time the mouse was down in another window, but it’s always been a tricky tradeoff.

> Source/WTF/wtf/RunLoop.h:149
> +    RetainPtr<CFMutableSetRef> m_additionalTimerModes;

Seems a little over-general to use a set for this given that it’s always either null or just a set with a single entry, NSModalPanelRunLoopMode. I suppose it’s nice to be general purpose, but maybe supporting just a single additional mode is OK. I can even imagine hard-coding this one mode and just using a boolean.

> Source/WebKit2/Shared/WebKit2Initialize.cpp:44
> +#if PLATFORM(MAC)
> +extern "C" CFStringRef NSModalPanelRunLoopMode;
> +#endif

I don’t think this declaration is a good idea. Giving the linker the wrong type and depending on toll-free bridging is unnecessarily ugly. We should do this in a .mm file where we can access the real constant in NSApplication.h.

Might be worth considering a helper function, maybe somewhere in WebCore/platform, that people can use to do this from cpp files.

> Source/WebKit2/Shared/WebKit2Initialize.cpp:62
>      RunLoop::initializeMainRunLoop();
> +#if PLATFORM(MAC)
> +    RunLoop::main().addModeForWakeUpAndTimers(NSModalPanelRunLoopMode);
> +#endif

I suggest exporting a function that does these two things together.

> Source/WebKit/mac/Carbon/CarbonWindowAdapter.mm:276
>      JSC::initializeThreading();
>      WTF::initializeMainThreadToProcessMainThread();
>      RunLoop::initializeMainRunLoop();
> +    RunLoop::main().addModeForWakeUpAndTimers((CFStringRef)NSModalPanelRunLoopMode);

I think this whole sequence should be a single helper function rather than putting up with having the code copied and pasted into 20 different classes.
Comment 3 Darin Adler 2014-03-26 09:02:11 PDT
(In reply to comment #1)
> - Confusingly, I couldn't see any difference in behavior of showModalDialog() whether I allowed NSModalPanelRunLoopMode or not.
> 
> - Timers in web pages behave strangely when menus are open. They keep firing - and the page keeps repainting - when I'm slowly moving the mouse between menus or menu items, but the timers stop after I move it quickly!
> 
> The last two items concern me.

I think that when we have time we should investigate anomalies like these. I don’t think we’ll be able to prove this is correct just by pure logic.
Comment 4 Alexey Proskuryakov 2014-03-26 22:42:45 PDT
Created attachment 227925 [details]
patch for landing

Moved initialization to WebCore/platform/MainRunLoop.h.
Comment 5 WebKit Commit Bot 2014-03-26 23:32:19 PDT
Comment on attachment 227925 [details]
patch for landing

Rejecting attachment 227925 [details] from commit-queue.

Failed to run "['/Volumes/Data/EWS/WebKit/Tools/Scripts/webkit-patch', '--status-host=webkit-queues.appspot.com', '--bot-id=webkit-cq-03', 'validate-changelog', '--check-oops', '--non-interactive', 227925, '--port=mac']" exit_code: 1 cwd: /Volumes/Data/EWS/WebKit

ChangeLog entry in Source/WebKit2/ChangeLog contains OOPS!.

Full output: http://webkit-queues.appspot.com/results/5203920452321280
Comment 6 Alexey Proskuryakov 2014-03-27 09:28:53 PDT
Created attachment 227950 [details]
patch for landing
Comment 7 WebKit Commit Bot 2014-03-27 09:29:39 PDT
Comment on attachment 227950 [details]
patch for landing

Rejecting attachment 227950 [details] from commit-queue.

Failed to run "['/Volumes/Data/EWS/WebKit/Tools/Scripts/webkit-patch', '--status-host=webkit-queues.appspot.com', '--bot-id=webkit-cq-03', 'validate-changelog', '--check-oops', '--non-interactive', 227950, '--port=mac']" exit_code: 1 cwd: /Volumes/Data/EWS/WebKit

/Volumes/Data/EWS/WebKit/Source/WebCore/ChangeLog neither lists a valid reviewer nor contains the string "Unreviewed" or "Rubber stamp" (case insensitive).

Full output: http://webkit-queues.appspot.com/results/5109491301351424
Comment 8 Alexey Proskuryakov 2014-03-27 09:48:39 PDT
Landed in <http://trac.webkit.org/r166360>.
Comment 9 Alexey Proskuryakov 2014-03-27 15:45:46 PDT
Looks like this broke PLT - Safari fails to exit while spinning a nested run loop. Strange, because the run loop is spinning in NSModalPanelRunLoopMode.
Comment 10 WebKit Commit Bot 2014-03-27 15:45:57 PDT
Re-opened since this is blocked by bug 130869
Comment 11 Alexey Proskuryakov 2014-03-27 18:49:30 PDT
Mostly figured out how this broke PLT, and it's quite scary. Here is what happens:

1. When PLT is done, it closes its windows with (essentially) -[NSWindow performClose]. This results in sending an IPC message to WebProcess, but it also spins a nested run loop *in event tracking mode* to highlight the window close button.
2. WebProcess is very quick to close the page, so a response arrives while spinning the run loop.
3. The response results in calling -[NSWindow close].
4. Then PLT tries to terminate Safari, and calls -[NSApplication terminate].
5. -[NSApplication terminate] tries to close all documents (not all windows), and while waiting for that, it spins run loop *in modal dialog mode*.
6. While in this mode, an NSEvent internal to AppKit is delivered, which confirms that all documents agreed to termination, and termination continues.

Now, if we don't deliver IPC responses in event tracking mode - or if closing the page takes longer than the close button highlight is visible - then the response is delivered later while in modal dialog mode, and calling -close doesn't convince NSApplication that the document got closed. So, NSApplication keeps waiting.

While I'm not sure how to fix this, It seems very likely that this cannot be fixed in WebKit alone.
Comment 12 Csaba Osztrogonác 2014-06-04 03:30:52 PDT
Comment on attachment 227831 [details]
proposed patch

Cleared Darin Adler's review+ from obsolete attachment 227831 [details] so that this bug does not appear in http://webkit.org/pending-commit.