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