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/WebInputElement.h" 14 #include "third_party/WebKit/public/web/WebInputEvent.h" 15 #include "third_party/WebKit/public/web/WebLocalFrame.h" 16 #include "third_party/WebKit/public/web/WebTextAreaElement.h" 17 #include "third_party/WebKit/public/web/WebView.h" 18 19 using blink::WebDOMEvent; 20 using blink::WebDOMMouseEvent; 21 using blink::WebElement; 22 using blink::WebFormControlElement; 23 using blink::WebFrame; 24 using blink::WebInputElement; 25 using blink::WebInputEvent; 26 using blink::WebMouseEvent; 27 using blink::WebNode; 28 using blink::WebString; 29 using blink::WebTextAreaElement; 30 using blink::WebView; 31 32 namespace { 33 34 // Casts |node| to a WebInputElement. 35 // Returns an empty (isNull()) WebInputElement if |node| is not a text field. 36 const WebInputElement GetTextWebInputElement(const WebNode& node) { 37 if (!node.isElementNode()) 38 return WebInputElement(); 39 const WebElement element = node.toConst<WebElement>(); 40 if (!element.hasHTMLTagName("input")) 41 return WebInputElement(); 42 const WebInputElement* input = blink::toWebInputElement(&element); 43 if (!autofill::IsTextInput(input)) 44 return WebInputElement(); 45 return *input; 46 } 47 48 // Casts |node| to a WebTextAreaElement. 49 // Returns an empty (isNull()) WebTextAreaElement if |node| is not a 50 // textarea field. 51 const WebTextAreaElement GetWebTextAreaElement(const WebNode& node) { 52 if (!node.isElementNode()) 53 return WebTextAreaElement(); 54 const WebElement element = node.toConst<WebElement>(); 55 if (!element.hasHTMLTagName("textarea")) 56 return WebTextAreaElement(); 57 return element.toConst<WebTextAreaElement>(); 58 } 59 60 // Checks to see if a text field was the previously selected node and is now 61 // losing its focus. 62 bool DidSelectedTextFieldLoseFocus(const WebNode& newly_clicked_node) { 63 blink::WebElement focused_element = 64 newly_clicked_node.document().focusedElement(); 65 66 if (focused_element.isNull() || 67 (GetTextWebInputElement(focused_element).isNull() && 68 GetWebTextAreaElement(focused_element).isNull())) 69 return false; 70 71 return focused_element != newly_clicked_node; 72 } 73 74 } // namespace 75 76 namespace autofill { 77 78 PageClickTracker::PageClickTracker(content::RenderView* render_view, 79 PageClickListener* listener) 80 : content::RenderViewObserver(render_view), 81 was_focused_(false), 82 listener_(listener) { 83 } 84 85 PageClickTracker::~PageClickTracker() { 86 // Note that even though RenderView calls FrameDetached when notified that 87 // a frame was closed, it might not always get that notification from WebKit 88 // for all frames. 89 // By the time we get here, the frame could have been destroyed so we cannot 90 // unregister listeners in frames remaining in tracked_frames_ as they might 91 // be invalid. 92 } 93 94 void PageClickTracker::DidHandleMouseEvent(const WebMouseEvent& event) { 95 if (event.type != WebInputEvent::MouseDown || 96 last_node_clicked_.isNull()) { 97 return; 98 } 99 100 // We are only interested in text field and textarea field clicks. 101 const WebInputElement input_element = 102 GetTextWebInputElement(last_node_clicked_); 103 const WebTextAreaElement textarea_element = 104 GetWebTextAreaElement(last_node_clicked_); 105 if (input_element.isNull() && textarea_element.isNull()) 106 return; 107 108 if (!input_element.isNull()) 109 listener_->FormControlElementClicked(input_element, was_focused_); 110 else if (!textarea_element.isNull()) 111 listener_->FormControlElementClicked(textarea_element, was_focused_); 112 } 113 114 void PageClickTracker::DidFinishDocumentLoad(blink::WebLocalFrame* frame) { 115 tracked_frames_.push_back(frame); 116 frame->document().addEventListener("mousedown", this, false); 117 } 118 119 void PageClickTracker::FrameDetached(blink::WebFrame* frame) { 120 std::vector<blink::WebFrame*>::iterator iter = 121 std::find(tracked_frames_.begin(), tracked_frames_.end(), frame); 122 if (iter == tracked_frames_.end()) { 123 // Some frames might never load contents so we may not have a listener on 124 // them. Calling removeEventListener() on them would trigger an assert, so 125 // we need to keep track of which frames we are listening to. 126 return; 127 } 128 tracked_frames_.erase(iter); 129 } 130 131 void PageClickTracker::handleEvent(const WebDOMEvent& event) { 132 last_node_clicked_.reset(); 133 134 if (!event.isMouseEvent()) 135 return; 136 137 const WebDOMMouseEvent mouse_event = event.toConst<WebDOMMouseEvent>(); 138 DCHECK(mouse_event.buttonDown()); 139 if (mouse_event.button() != 0) 140 return; // We are only interested in left clicks. 141 142 // Remember which node has focus before the click is processed. 143 // We'll get a notification once the mouse event has been processed 144 // (DidHandleMouseEvent), we'll notify the listener at that point. 145 WebNode node = mouse_event.target(); 146 if (node.isNull()) 147 // Node may be null if the target was an SVG instance element from a <use> 148 // tree and the tree has been rebuilt due to an earlier event. 149 return; 150 151 HandleTextFieldMaybeLosingFocus(node); 152 153 // We are only interested in text field clicks. 154 if (GetTextWebInputElement(node).isNull() && 155 GetWebTextAreaElement(node).isNull()) 156 return; 157 158 last_node_clicked_ = node; 159 was_focused_ = (node.document().focusedElement() == last_node_clicked_); 160 } 161 162 void PageClickTracker::HandleTextFieldMaybeLosingFocus( 163 const WebNode& newly_clicked_node) { 164 if (DidSelectedTextFieldLoseFocus(newly_clicked_node)) 165 listener_->FormControlElementLostFocus(); 166 } 167 168 } // namespace autofill 169