|
|
Chromium Code Reviews
DescriptionAdding null check of |layoutObject()| before calling |visiblePositionOfHitTestResult()|
BUG=667567
Committed: https://crrev.com/4807e64756edb7f9f8febaf2ae1ce45124b4c75e
Cr-Commit-Position: refs/heads/master@{#439073}
Patch Set 1 #
Total comments: 3
Patch Set 2 : moved null check out of setCaretAtHitTestResult #Patch Set 3 : Rebased #
Total comments: 1
Patch Set 4 : pulling hitTest sanity check out of selection controller #
Messages
Total messages: 38 (21 generated)
The CQ bit was checked by amaralp@chromium.org to run a CQ dry run
Dry run: CQ is trying da patch. Follow status at https://chromium-cq-status.appspot.com/v2/patch-status/codereview.chromium.or...
The CQ bit was unchecked by commit-bot@chromium.org
Dry run: This issue passed the CQ dry run.
amaralp@chromium.org changed reviewers: + yosin@chromium.org
Description was changed from ========== Adding |Node::layoutObject()| null check Adding null check of |layoutObject()| before calling |visiblePositionOfHitTestResult()| BUG=667567 ========== to ========== Adding null check of |layoutObject()| before calling |visiblePositionOfHitTestResult()| BUG=667567 ==========
PTAL
https://codereview.chromium.org/2537243002/diff/1/third_party/WebKit/Source/c... File third_party/WebKit/Source/core/editing/SelectionController.cpp (right): https://codereview.chromium.org/2537243002/diff/1/third_party/WebKit/Source/c... third_party/WebKit/Source/core/editing/SelectionController.cpp:655: if (!innerNode || !innerNode->layoutObject()) It is strange that HitTestResult picks up "display:none" node. Also, I'm not sure why |innerNode| is null. I think it should be DCHECK(innerNode); DCHECK(inner->layoutObject()); Could you add a test case for this situation? Call sites of setCaretAtHitTestResult() should make sure provide sane |HitTestResult|.
The CQ bit was checked by amaralp@chromium.org to run a CQ dry run
Dry run: CQ is trying da patch. Follow status at https://chromium-cq-status.appspot.com/v2/patch-status/codereview.chromium.or...
The CQ bit was unchecked by commit-bot@chromium.org
Dry run: Try jobs failed on following builders: mac_chromium_compile_dbg_ng on master.tryserver.chromium.mac (JOB_FAILED, http://build.chromium.org/p/tryserver.chromium.mac/builders/mac_chromium_comp...)
The CQ bit was checked by amaralp@chromium.org to run a CQ dry run
Dry run: CQ is trying da patch. Follow status at https://chromium-cq-status.appspot.com/v2/patch-status/codereview.chromium.or...
The CQ bit was unchecked by commit-bot@chromium.org
Dry run: This issue passed the CQ dry run.
https://codereview.chromium.org/2537243002/diff/1/third_party/WebKit/Source/c... File third_party/WebKit/Source/core/editing/SelectionController.cpp (right): https://codereview.chromium.org/2537243002/diff/1/third_party/WebKit/Source/c... third_party/WebKit/Source/core/editing/SelectionController.cpp:655: if (!innerNode || !innerNode->layoutObject()) On 2016/11/30 at 02:01:48, Yosi_UTC9 wrote: > It is strange that HitTestResult picks up "display:none" node. I didn't think it was possible for the HitTestResult to have the |LayoutObject| be null. It looks like all the other call sites of |visiblePositionOfHitTestResult()| do this check so I am thinking that this is probably what is causing the segmentation faults. > Also, I'm not sure why |innerNode| is null. I think it should be DCHECK(innerNode); DCHECK(inner->layoutObject()); > Maybe these DCHECKS should be added to |visiblePositionOfHitTestResult()| since that is what is probably causing the crashes. > Could you add a test case for this situation? > How would I test this? Long-pressing based on the id of a display:none element? > Call sites of setCaretAtHitTestResult() should make sure provide sane |HitTestResult|. I moved the check to the call sites although it looks like |selectClosestWordFromHitTestResult()| and |selectClosestMisspellingFromHitTestResult()| do the check themselves.
https://codereview.chromium.org/2537243002/diff/1/third_party/WebKit/Source/c... File third_party/WebKit/Source/core/editing/SelectionController.cpp (right): https://codereview.chromium.org/2537243002/diff/1/third_party/WebKit/Source/c... third_party/WebKit/Source/core/editing/SelectionController.cpp:655: if (!innerNode || !innerNode->layoutObject()) On 2016/12/01 at 01:25:34, amaralp wrote: > On 2016/11/30 at 02:01:48, Yosi_UTC9 wrote: > > It is strange that HitTestResult picks up "display:none" node. > I didn't think it was possible for the HitTestResult to have the |LayoutObject| be null. It looks like all the other call sites of |visiblePositionOfHitTestResult()| do this check so I am thinking that this is probably what is causing the segmentation faults. > > > Also, I'm not sure why |innerNode| is null. I think it should be DCHECK(innerNode); DCHECK(inner->layoutObject()); > > > Maybe these DCHECKS should be added to |visiblePositionOfHitTestResult()| since that is what is probably causing the crashes. > > > Could you add a test case for this situation? > > > How would I test this? Long-pressing based on the id of a display:none element? We need to find where we call HitTestResult::setInnerNode() with node->layoutObject() == nullptr. Then we can assemble such HTML and simulate events in gTest. Below is short investigation: Insane HitTestReulst is calculated in GestureManager::handleGestureLongPress() with IntPoint hitTestPoint = m_frame->view()->rootFrameToContents(gestureEvent.position()); HitTestResult hitTestResult = m_frame->eventHandler().hitTestResultAtPoint(hitTestPoint); Expect for <body style="display:none">, all touch event should have visible node. So, suspicious is rootFrameToContents() returns unexpected point then hitTestResultAtPoint() returns insane HitTestResult. In EventHandler::hitTestResultAtPoint(), following code fragment can return insane HitTestResult: // LayoutView::hitTest causes a layout, and we don't want to hit that until // the first layout because until then, there is nothing shown on the screen - // the user can't have intentionally clicked on something belonging to this // page. Furthermore, mousemove events before the first layout should not // lead to a premature layout() happening, which could show a flash of white. // See also the similar code in Document::performMouseEventHitTest. if (m_frame->contentLayoutItem().isNull() || !m_frame->view() || !m_frame->view()->didFirstLayout()) return result; **** innerNode == nullptr m_frame->contentLayoutItem().hitTest(result); But this isn't innerNode()->layoutObject() == nullptr case. Fumm... https://codereview.chromium.org/2537243002/diff/40001/third_party/WebKit/Sour... File third_party/WebKit/Source/core/editing/SelectionController.cpp (right): https://codereview.chromium.org/2537243002/diff/40001/third_party/WebKit/Sour... third_party/WebKit/Source/core/editing/SelectionController.cpp:892: if (!innerNode || !innerNode->layoutObject()) Could you hoist call site of this function or father? Member functions of SelectionController should take sane HitTestResult, e.g. innerNode() != nullptr && innerNode()->layoutObject() != nullptr
> Expect for <body style="display:none">, all touch event should have visible node. I think there is a potential race-condition where a |Node| is long-pressed and so we get it when we do the hit test in |GestureManager::handleGestureLongPress()|. Then that Node's style is updated to "display:none" and the call to |Document::updateStyleAndLayoutTree()| in SelectionController::handleGestureLongPress()| causes the node to detach from the layout tree and set its |LayoutObject| to null. The fix would be to do the hit test after |Document::updateStyleAndLayoutTree()|. Do you think this is a plausible explanation?
On 2016/12/02 at 01:18:46, amaralp wrote:
> > Expect for <body style="display:none">, all touch event should have visible
node.
>
> I think there is a potential race-condition where a |Node| is long-pressed and
so we get it when we do the hit test in
> |GestureManager::handleGestureLongPress()|. Then that Node's style is updated
to "display:none" and the call to
> |Document::updateStyleAndLayoutTree()| in
SelectionController::handleGestureLongPress()| causes the node to detach from
> the layout tree and set its |LayoutObject| to null.
>
> The fix would be to do the hit test after
|Document::updateStyleAndLayoutTree()|.
>
> Do you think this is a plausible explanation?
No. Let's identify which function make layout tree dirty. To change style of
node, we may dispatch event then JS event
handler does something.
Since The |MouseManager::handleDragDropIfPossible()| is the function called
between hitTest and SelectionController::handleGestureLongPress().
|MouseManager::handleDragDropIfPossible()| may update layout tree. However, I
could not locate dispatch event yet.
HitTestResult hitTestResult =
m_frame->eventHandler().hitTestResultAtPoint(hitTestPoint);
if (!hitTestContainsLinks &&
m_mouseEventManager->handleDragDropIfPossible(targetedEvent)) {
m_longTapShouldInvokeContextMenu = true;
return WebInputEventResult::HandledSystem;
}
if (m_selectionController->handleGestureLongPress(gestureEvent,
hitTestResult)) {
Could you utilize |ScriptForbiddenScope| where we call JS event handler during
processing gesture long press?
On 2016/12/02 at 01:38:44, yosin wrote:
> On 2016/12/02 at 01:18:46, amaralp wrote:
> > > Expect for <body style="display:none">, all touch event should have
visible node.
> >
> > I think there is a potential race-condition where a |Node| is long-pressed
and so we get it when we do the hit test in
> > |GestureManager::handleGestureLongPress()|. Then that Node's style is
updated to "display:none" and the call to
> > |Document::updateStyleAndLayoutTree()| in
SelectionController::handleGestureLongPress()| causes the node to detach from
> > the layout tree and set its |LayoutObject| to null.
> >
> > The fix would be to do the hit test after
|Document::updateStyleAndLayoutTree()|.
> >
> > Do you think this is a plausible explanation?
>
> No. Let's identify which function make layout tree dirty. To change style of
node, we may dispatch event then JS event
> handler does something.
>
> Since The |MouseManager::handleDragDropIfPossible()| is the function called
between hitTest and SelectionController::handleGestureLongPress().
> |MouseManager::handleDragDropIfPossible()| may update layout tree. However, I
could not locate dispatch event yet.
>
> HitTestResult hitTestResult =
> m_frame->eventHandler().hitTestResultAtPoint(hitTestPoint);
>
> if (!hitTestContainsLinks &&
> m_mouseEventManager->handleDragDropIfPossible(targetedEvent)) {
> m_longTapShouldInvokeContextMenu = true;
> return WebInputEventResult::HandledSystem;
> }
>
>
> if (m_selectionController->handleGestureLongPress(gestureEvent,
> hitTestResult)) {
>
>
>
> Could you utilize |ScriptForbiddenScope| where we call JS event handler during
processing gesture long press?
I took a closer look at |handleDragDropIfPossible()| and from I can tell if it
dispatches a javascript event then
it returns |true| meaning that |SelectionController.handleGestureLongPress()|
never gets called.
This means that the |HitTestResult| had a null |LayoutObject| from when it was
created. I'll continue to investigate
scenarios where such a |HitTestResult| could be created.
On 2016/12/06 at 02:46:57, amaralp wrote:
> On 2016/12/02 at 01:38:44, yosin wrote:
> > On 2016/12/02 at 01:18:46, amaralp wrote:
> > > > Expect for <body style="display:none">, all touch event should have
visible node.
> > >
> > > I think there is a potential race-condition where a |Node| is long-pressed
and so we get it when we do the hit test in
> > > |GestureManager::handleGestureLongPress()|. Then that Node's style is
updated to "display:none" and the call to
> > > |Document::updateStyleAndLayoutTree()| in
SelectionController::handleGestureLongPress()| causes the node to detach from
> > > the layout tree and set its |LayoutObject| to null.
> > >
> > > The fix would be to do the hit test after
|Document::updateStyleAndLayoutTree()|.
> > >
> > > Do you think this is a plausible explanation?
> >
> > No. Let's identify which function make layout tree dirty. To change style of
node, we may dispatch event then JS event
> > handler does something.
> >
> > Since The |MouseManager::handleDragDropIfPossible()| is the function called
between hitTest and SelectionController::handleGestureLongPress().
> > |MouseManager::handleDragDropIfPossible()| may update layout tree. However,
I could not locate dispatch event yet.
> >
> > HitTestResult hitTestResult =
> > m_frame->eventHandler().hitTestResultAtPoint(hitTestPoint);
> >
> > if (!hitTestContainsLinks &&
> > m_mouseEventManager->handleDragDropIfPossible(targetedEvent)) {
> > m_longTapShouldInvokeContextMenu = true;
> > return WebInputEventResult::HandledSystem;
> > }
> >
> >
> > if (m_selectionController->handleGestureLongPress(gestureEvent,
> > hitTestResult)) {
> >
> >
> >
> > Could you utilize |ScriptForbiddenScope| where we call JS event handler
during processing gesture long press?
>
> I took a closer look at |handleDragDropIfPossible()| and from I can tell if it
dispatches a javascript event then
> it returns |true| meaning that |SelectionController.handleGestureLongPress()|
never gets called.
>
> This means that the |HitTestResult| had a null |LayoutObject| from when it was
created. I'll continue to investigate
> scenarios where such a |HitTestResult| could be created.
As you had suggested going to "data:text/html,<html
style="display:none"></html>" and longpressing reproduces the crash.
I traced the problem to |LayoutView.updateHitTestResult()|. I think we'd need a
null check for this scenario.
amaralp@chromium.org changed reviewers: + aelias@chromium.org, yukawa@chromium.org
amaralp@chromium.org changed reviewers: + yutak@chromium.org - yukawa@chromium.org
Adding yukawa@ since yosin@ is OOO. Here is a quick recap: This CL is to fix the regression (crbug.com/667567) introduced by CL (crrev.com/2480353006). I found a way to reproduce the crash. On an Android device running M57 go to "data:text/html,<html style="display:none"></html>" and long press. This should cause chrome to crash. This causes a nullptr exception because the document node doesn't have a LayoutObject since it is "display:none". My solution to this is adding a null check.
On 2016/12/08 at 23:51:07, amaralp wrote:
> Adding yukawa@ since yosin@ is OOO.
>
> Here is a quick recap:
>
> This CL is to fix the regression (crbug.com/667567) introduced by CL
(crrev.com/2480353006). I found a way to reproduce the crash. On an Android
device running M57 go to "data:text/html,<html style="display:none"></html>" and
long press. This should cause chrome to crash. This causes a nullptr exception
because the document node doesn't have a LayoutObject since it is
"display:none". My solution to this is adding a null check.
Thanks for investigating root cause!
It seems touch event is dispatched for document not yet layout.
Repro case |<html style="display:none"></html>| is unusual and web author
doesn't write so.
One possibility is user tap on IFRAME being loaded.
Please do null check as much as bottom of call stack.
It seems EventHandler::handleGestureEvent() calls
GestureManager::handleGestureEventInFrame()
calls null frame case. Do we really need to call GM::handleGestureEventInFrame()
without local frame?
See the end of EventHandler::handleGestureEvent()
WebInputEventResult EventHandler::handleGestureEvent(
const GestureEventWithHitTestResults& targetedEvent) {
TRACE_EVENT0("input", "EventHandler::handleGestureEvent");
...
// Route to the correct frame.
if (LocalFrame* innerFrame = targetedEvent.hitTestResult().innerNodeFrame())
return innerFrame->eventHandler().handleGestureEventInFrame(targetedEvent);
// No hit test result, handle in root instance. Perhaps we should just return
// false instead?
return m_gestureManager->handleGestureEventInFrame(targetedEvent);
}
On 2016/12/09 at 02:03:01, yosin wrote:
> Thanks for investigating root cause!
>
> It seems touch event is dispatched for document not yet layout.
> Repro case |<html style="display:none"></html>| is unusual and web author
doesn't write so.
> One possibility is user tap on IFRAME being loaded.
>
> Please do null check as much as bottom of call stack.
>
Pulled to GestureManager.
> It seems EventHandler::handleGestureEvent() calls
GestureManager::handleGestureEventInFrame()
> calls null frame case. Do we really need to call
GM::handleGestureEventInFrame() without local frame?
> See the end of EventHandler::handleGestureEvent()
>
> WebInputEventResult EventHandler::handleGestureEvent(
> const GestureEventWithHitTestResults& targetedEvent) {
> TRACE_EVENT0("input", "EventHandler::handleGestureEvent");
>
> ...
>
> // Route to the correct frame.
> if (LocalFrame* innerFrame = targetedEvent.hitTestResult().innerNodeFrame())
> return
innerFrame->eventHandler().handleGestureEventInFrame(targetedEvent);
>
> // No hit test result, handle in root instance. Perhaps we should just
return
> // false instead?
> return m_gestureManager->handleGestureEventInFrame(targetedEvent);
> }
The callstacks from clusterfuzz
(https://cluster-fuzz.appspot.com/v2/testcase-detail/4784817788157952) and the
chrome crash report:
https://crash.corp.google.com/browse?q=custom_data.ChromeCrashProto.ptype%3D%...
seem to indicate that the if statement at the end of |EH::handleGestureEvent()|
was true and so we actually did have a local frame.
amaralp@chromium.org changed reviewers: + bokan@chromium.org
Adding bokan@ for core/input OWNERS. yutak@, do you think you could review this? yosin@ is OOO until the beginning of January and this is a M57 blocker.
lgtm but please wait for yutak@'s thumbs up.
LGTM
The CQ bit was checked by amaralp@chromium.org
CQ is trying da patch. Follow status at https://chromium-cq-status.appspot.com/v2/patch-status/codereview.chromium.or...
CQ is committing da patch.
Bot data: {"patchset_id": 60001, "attempt_start_ts": 1481873650550500,
"parent_rev": "d3149dd85e8d4cf6acd5357d0e9aa5940d268a7a", "commit_rev":
"4bae92a3db37327214fb863c11cb5c6549b96408"}
Message was sent while issue was closed.
Description was changed from ========== Adding null check of |layoutObject()| before calling |visiblePositionOfHitTestResult()| BUG=667567 ========== to ========== Adding null check of |layoutObject()| before calling |visiblePositionOfHitTestResult()| BUG=667567 Review-Url: https://codereview.chromium.org/2537243002 ==========
Message was sent while issue was closed.
Committed patchset #4 (id:60001)
Message was sent while issue was closed.
Description was changed from ========== Adding null check of |layoutObject()| before calling |visiblePositionOfHitTestResult()| BUG=667567 Review-Url: https://codereview.chromium.org/2537243002 ========== to ========== Adding null check of |layoutObject()| before calling |visiblePositionOfHitTestResult()| BUG=667567 Committed: https://crrev.com/4807e64756edb7f9f8febaf2ae1ce45124b4c75e Cr-Commit-Position: refs/heads/master@{#439073} ==========
Message was sent while issue was closed.
Patchset 4 (id:??) landed as https://crrev.com/4807e64756edb7f9f8febaf2ae1ce45124b4c75e Cr-Commit-Position: refs/heads/master@{#439073} |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
