summaryrefslogtreecommitdiffstats
path: root/chrome/renderer/page_click_tracker.cc
blob: 9e4067941dbcc115d8b4e65e7a5c26bf1f88e6b9 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/renderer/page_click_tracker.h"

#include "chrome/common/render_messages.h"
#include "chrome/renderer/autofill/form_autofill_util.h"
#include "chrome/renderer/page_click_listener.h"
#include "content/public/renderer/render_view.h"
#include "third_party/WebKit/Source/WebKit/chromium/public/WebDocument.h"
#include "third_party/WebKit/Source/WebKit/chromium/public/WebDOMMouseEvent.h"
#include "third_party/WebKit/Source/WebKit/chromium/public/WebFrame.h"
#include "third_party/WebKit/Source/WebKit/chromium/public/WebInputElement.h"
#include "third_party/WebKit/Source/WebKit/chromium/public/platform/WebString.h"
#include "third_party/WebKit/Source/WebKit/chromium/public/WebView.h"

using WebKit::WebDOMEvent;
using WebKit::WebDOMMouseEvent;
using WebKit::WebElement;
using WebKit::WebFormControlElement;
using WebKit::WebFrame;
using WebKit::WebInputElement;
using WebKit::WebInputEvent;
using WebKit::WebMouseEvent;
using WebKit::WebNode;
using WebKit::WebString;
using WebKit::WebView;

namespace {

// Casts |node| to a WebInputElement.
// Returns an empty (isNull()) WebInputElement if |node| is not a text field.
const WebInputElement GetTextWebInputElement(const WebNode& node) {
  if (!node.isElementNode())
    return WebInputElement();
  const WebElement element = node.toConst<WebElement>();
  if (!element.hasTagName("input"))
    return WebInputElement();
  const WebInputElement* input = WebKit::toWebInputElement(&element);
  if (!autofill::IsTextInput(input))
    return WebInputElement();
  return *input;
}

// Checks to see if a text field was the previously selected node and is now
// losing its focus.
bool DidSelectedTextFieldLoseFocus(const WebNode& newly_clicked_node) {
  WebKit::WebNode focused_node = newly_clicked_node.document().focusedNode();

  if (focused_node.isNull() || GetTextWebInputElement(focused_node).isNull())
    return false;

  return focused_node != newly_clicked_node;
}

}  // namespace

PageClickTracker::PageClickTracker(content::RenderView* render_view)
    : content::RenderViewObserver(render_view),
      was_focused_(false) {
}

PageClickTracker::~PageClickTracker() {
  // Note that even though RenderView calls FrameDetached when notified that
  // a frame was closed, it might not always get that notification from WebKit
  // for all frames.
  // By the time we get here, the frame could have been destroyed so we cannot
  // unregister listeners in frames remaining in tracked_frames_ as they might
  // be invalid.
}

void PageClickTracker::DidHandleMouseEvent(const WebMouseEvent& event) {
  if (event.type != WebInputEvent::MouseDown ||
      last_node_clicked_.isNull()) {
    return;
  }

  // We are only interested in text field clicks.
  const WebInputElement input_element =
      GetTextWebInputElement(last_node_clicked_);
  if (input_element.isNull())
    return;

  bool is_focused = (last_node_clicked_ == render_view()->GetFocusedNode());
  ObserverListBase<PageClickListener>::Iterator it(listeners_);
  PageClickListener* listener;
  while ((listener = it.GetNext()) != NULL) {
    if (listener->InputElementClicked(input_element, was_focused_, is_focused))
      break;
  }
}

void PageClickTracker::AddListener(PageClickListener* listener) {
  listeners_.AddObserver(listener);
}

void PageClickTracker::RemoveListener(PageClickListener* listener) {
  listeners_.RemoveObserver(listener);
}

void PageClickTracker::DidFinishDocumentLoad(WebKit::WebFrame* frame) {
  tracked_frames_.push_back(frame);
  frame->document().addEventListener("mousedown", this, false);
}

void PageClickTracker::FrameDetached(WebKit::WebFrame* frame) {
  FrameList::iterator iter =
      std::find(tracked_frames_.begin(), tracked_frames_.end(), frame);
  if (iter == tracked_frames_.end()) {
    // Some frames might never load contents so we may not have a listener on
    // them.  Calling removeEventListener() on them would trigger an assert, so
    // we need to keep track of which frames we are listening to.
    return;
  }
  tracked_frames_.erase(iter);
}

void PageClickTracker::handleEvent(const WebDOMEvent& event) {
  last_node_clicked_.reset();

  if (!event.isMouseEvent())
    return;

  const WebDOMMouseEvent mouse_event = event.toConst<WebDOMMouseEvent>();
  DCHECK(mouse_event.buttonDown());
  if (mouse_event.button() != 0)
    return;  // We are only interested in left clicks.

  // Remember which node has focus before the click is processed.
  // We'll get a notification once the mouse event has been processed
  // (DidHandleMouseEvent), we'll notify the listener at that point.
  WebNode node = mouse_event.target();

  HandleTextFieldMaybeLosingFocus(node);

  // We are only interested in text field clicks.
  if (GetTextWebInputElement(node).isNull())
    return;

  last_node_clicked_ = node;
  was_focused_ = (node.document().focusedNode() == last_node_clicked_);
}

void PageClickTracker::HandleTextFieldMaybeLosingFocus(
    const WebNode& newly_clicked_node) {
  if (!DidSelectedTextFieldLoseFocus(newly_clicked_node))
    return;

  ObserverListBase<PageClickListener>::Iterator it(listeners_);
  PageClickListener* listener;
  while ((listener = it.GetNext()) != NULL) {
    if (listener->InputElementLostFocus())
      break;
  }
}