// 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/WebInputEvent.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(); 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::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(); 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::Iterator it(listeners_); PageClickListener* listener; while ((listener = it.GetNext()) != NULL) { if (listener->InputElementLostFocus()) break; } }