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 18 using blink::WebDOMEvent; 19 using blink::WebDOMMouseEvent; 20 using blink::WebElement; 21 using blink::WebFormControlElement; 22 using blink::WebFrame; 23 using blink::WebInputElement; 24 using blink::WebInputEvent; 25 using blink::WebMouseEvent; 26 using blink::WebNode; 27 using blink::WebString; 28 using blink::WebView; 29 30 namespace { 31 32 // Casts |node| to a WebInputElement. 33 // Returns an empty (isNull()) WebInputElement if |node| is not a text field. 34 const 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. 48 bool 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 59 namespace autofill { 60 61 PageClickTracker::PageClickTracker(content::RenderView* render_view, 62 PageClickListener* listener) 63 : content::RenderViewObserver(render_view), 64 was_focused_(false), 65 listener_(listener) { 66 } 67 68 PageClickTracker::~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 77 void 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 93 void PageClickTracker::DidFinishDocumentLoad(blink::WebFrame* frame) { 94 tracked_frames_.push_back(frame); 95 frame->document().addEventListener("mousedown", this, false); 96 } 97 98 void 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 110 void 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 140 void PageClickTracker::HandleTextFieldMaybeLosingFocus( 141 const WebNode& newly_clicked_node) { 142 if (DidSelectedTextFieldLoseFocus(newly_clicked_node)) 143 listener_->InputElementLostFocus(); 144 } 145 146 } // namespace autofill 147