page_click_tracker.cc revision f2477e01787aa58f445919b809d89e252beef54f
1// Copyright 2013 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "components/autofill/content/renderer/page_click_tracker.h"
6
7#include "components/autofill/content/renderer/form_autofill_util.h"
8#include "components/autofill/content/renderer/page_click_listener.h"
9#include "content/public/renderer/render_view.h"
10#include "third_party/WebKit/public/platform/WebString.h"
11#include "third_party/WebKit/public/web/WebDOMMouseEvent.h"
12#include "third_party/WebKit/public/web/WebDocument.h"
13#include "third_party/WebKit/public/web/WebFrame.h"
14#include "third_party/WebKit/public/web/WebInputElement.h"
15#include "third_party/WebKit/public/web/WebInputEvent.h"
16#include "third_party/WebKit/public/web/WebView.h"
17
18using blink::WebDOMEvent;
19using blink::WebDOMMouseEvent;
20using blink::WebElement;
21using blink::WebFormControlElement;
22using blink::WebFrame;
23using blink::WebInputElement;
24using blink::WebInputEvent;
25using blink::WebMouseEvent;
26using blink::WebNode;
27using blink::WebString;
28using blink::WebView;
29
30namespace {
31
32// Casts |node| to a WebInputElement.
33// Returns an empty (isNull()) WebInputElement if |node| is not a text field.
34const WebInputElement GetTextWebInputElement(const WebNode& node) {
35  if (!node.isElementNode())
36    return WebInputElement();
37  const WebElement element = node.toConst<WebElement>();
38  if (!element.hasTagName("input"))
39    return WebInputElement();
40  const WebInputElement* input = blink::toWebInputElement(&element);
41  if (!autofill::IsTextInput(input))
42    return WebInputElement();
43  return *input;
44}
45
46// Checks to see if a text field was the previously selected node and is now
47// losing its focus.
48bool DidSelectedTextFieldLoseFocus(const WebNode& newly_clicked_node) {
49  blink::WebNode focused_node = newly_clicked_node.document().focusedNode();
50
51  if (focused_node.isNull() || GetTextWebInputElement(focused_node).isNull())
52    return false;
53
54  return focused_node != newly_clicked_node;
55}
56
57}  // namespace
58
59namespace autofill {
60
61PageClickTracker::PageClickTracker(content::RenderView* render_view,
62                                   PageClickListener* listener)
63    : content::RenderViewObserver(render_view),
64      was_focused_(false),
65      listener_(listener) {
66}
67
68PageClickTracker::~PageClickTracker() {
69  // Note that even though RenderView calls FrameDetached when notified that
70  // a frame was closed, it might not always get that notification from WebKit
71  // for all frames.
72  // By the time we get here, the frame could have been destroyed so we cannot
73  // unregister listeners in frames remaining in tracked_frames_ as they might
74  // be invalid.
75}
76
77void PageClickTracker::DidHandleMouseEvent(const WebMouseEvent& event) {
78  if (event.type != WebInputEvent::MouseDown ||
79      last_node_clicked_.isNull()) {
80    return;
81  }
82
83  // We are only interested in text field clicks.
84  const WebInputElement input_element =
85      GetTextWebInputElement(last_node_clicked_);
86  if (input_element.isNull())
87    return;
88
89  bool is_focused = (last_node_clicked_ == render_view()->GetFocusedNode());
90  listener_->InputElementClicked(input_element, was_focused_, is_focused);
91}
92
93void PageClickTracker::DidFinishDocumentLoad(blink::WebFrame* frame) {
94  tracked_frames_.push_back(frame);
95  frame->document().addEventListener("mousedown", this, false);
96}
97
98void PageClickTracker::FrameDetached(blink::WebFrame* frame) {
99  std::vector<blink::WebFrame*>::iterator iter =
100      std::find(tracked_frames_.begin(), tracked_frames_.end(), frame);
101  if (iter == tracked_frames_.end()) {
102    // Some frames might never load contents so we may not have a listener on
103    // them.  Calling removeEventListener() on them would trigger an assert, so
104    // we need to keep track of which frames we are listening to.
105    return;
106  }
107  tracked_frames_.erase(iter);
108}
109
110void PageClickTracker::handleEvent(const WebDOMEvent& event) {
111  last_node_clicked_.reset();
112
113  if (!event.isMouseEvent())
114    return;
115
116  const WebDOMMouseEvent mouse_event = event.toConst<WebDOMMouseEvent>();
117  DCHECK(mouse_event.buttonDown());
118  if (mouse_event.button() != 0)
119    return;  // We are only interested in left clicks.
120
121  // Remember which node has focus before the click is processed.
122  // We'll get a notification once the mouse event has been processed
123  // (DidHandleMouseEvent), we'll notify the listener at that point.
124  WebNode node = mouse_event.target();
125  if (node.isNull())
126    // Node may be null if the target was an SVG instance element from a <use>
127    // tree and the tree has been rebuilt due to an earlier event.
128    return;
129
130  HandleTextFieldMaybeLosingFocus(node);
131
132  // We are only interested in text field clicks.
133  if (GetTextWebInputElement(node).isNull())
134    return;
135
136  last_node_clicked_ = node;
137  was_focused_ = (node.document().focusedNode() == last_node_clicked_);
138}
139
140void PageClickTracker::HandleTextFieldMaybeLosingFocus(
141    const WebNode& newly_clicked_node) {
142  if (DidSelectedTextFieldLoseFocus(newly_clicked_node))
143    listener_->InputElementLostFocus();
144}
145
146}  // namespace autofill
147