/* * Copyright (C) 2000-2004 Marco Pesenti Gritti * Copyright (C) 2003, 2004 Christian Persch * Copyright (C) 2004 Crispin Flowerday * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * * $Id$ */ #include "mozilla-config.h" #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "EventContext.h" #include "EphyUtils.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef ALLOW_PRIVATE_API #include #include #include #include #include #endif #define KEY_CODE 256 EventContext::EventContext () { } EventContext::~EventContext () { } nsresult EventContext::Init (EphyBrowser *browser) { mBrowser = browser; mDOMDocument = nsnull; return NS_OK; } nsresult EventContext::GatherTextUnder (nsIDOMNode* aNode, nsAString& aResult) { nsEmbedString text; nsCOMPtr node; aNode->GetFirstChild(getter_AddRefs(node)); PRUint32 depth = 1; while (node && depth) { nsCOMPtr charData(do_QueryInterface(node)); PRUint16 nodeType; node->GetNodeType(&nodeType); if (charData && nodeType == nsIDOMNode::TEXT_NODE) { /* Add this text to our collection. */ text += ' '; nsEmbedString data; charData->GetData(data); text += data; } else { nsCOMPtr img(do_QueryInterface(node)); if (img) { nsEmbedString altText; img->GetAlt(altText); if (altText.Length()) { text = altText; break; } } } /* Find the next node to test. */ PRBool hasChildNodes; node->HasChildNodes(&hasChildNodes); if (hasChildNodes) { nsCOMPtr temp = node; temp->GetFirstChild(getter_AddRefs(node)); depth++; } else { nsCOMPtr nextSibling; node->GetNextSibling(getter_AddRefs(nextSibling)); if (nextSibling) { node = nextSibling; } else { nsCOMPtr parentNode; node->GetParentNode(getter_AddRefs(parentNode)); if (!parentNode) { node = nsnull; } else { parentNode->GetNextSibling(getter_AddRefs(nextSibling)); node = nextSibling; depth--; } } } } /* FIXME we should trim spaces here */ aResult = text; return NS_OK; } nsresult EventContext::ResolveBaseURL (const nsAString &relurl, nsACString &url) { nsresult rv; nsCOMPtr node(do_QueryInterface (mDOMDocument)); nsEmbedString spec; node->GetBaseURI (spec); nsCOMPtr base; rv = EphyUtils::NewURI (getter_AddRefs(base), spec); if (!base) return NS_ERROR_FAILURE; nsEmbedCString cRelURL; NS_UTF16ToCString (relurl, NS_CSTRING_ENCODING_UTF8, cRelURL); return base->Resolve (cRelURL, url); } nsresult EventContext::Unescape (const nsACString &aEscaped, nsACString &aUnescaped) { if (!aEscaped.Length()) return NS_ERROR_FAILURE; nsCOMPtr escaper (do_CreateInstance ("@mozilla.org/intl/texttosuburi;1")); NS_ENSURE_TRUE (escaper, NS_ERROR_FAILURE); nsresult rv; nsEmbedCString encoding; rv = mBrowser->GetEncoding (encoding); NS_ENSURE_SUCCESS (rv, NS_ERROR_FAILURE); nsEmbedString unescaped; rv = escaper->UnEscapeNonAsciiURI (encoding, aEscaped, unescaped); NS_ENSURE_TRUE (NS_SUCCEEDED (rv) && unescaped.Length(), NS_ERROR_FAILURE); NS_UTF16ToCString (unescaped, NS_CSTRING_ENCODING_UTF8, aUnescaped); return NS_OK; } nsresult EventContext::GetEventContext (nsIDOMEventTarget *EventTarget, MozillaEmbedEvent *info) { nsresult rv; const PRUnichar hrefLiteral[] = {'h', 'r', 'e', 'f', '\0'}; const PRUnichar typeLiteral[] = {'t', 'y', 'p', 'e', '\0'}; const PRUnichar xlinknsLiteral[] = {'h', 't', 't', 'p', ':', '/', '/','w', 'w', 'w', '.', 'w', '3', '.', 'o', 'r', 'g', '/', '1', '9', '9', '9', '/', 'x', 'l', 'i', 'n', 'k', '\0'}; const PRUnichar bodyLiteral[] = { 'b', 'o', 'd', 'y', '\0' }; mEmbedEvent = info; info->context = EMBED_CONTEXT_DOCUMENT; nsCOMPtr node = do_QueryInterface(EventTarget, &rv); if (NS_FAILED(rv) || !node) return NS_ERROR_FAILURE; /* Is page xul ? then do not display context menus * FIXME I guess there is an easier way ... */ /* From philipl: This test needs to be here otherwise we * arrogantly assume we can QI to a HTMLElement, which is * not true for xul content. */ nsCOMPtr domDoc; rv = node->GetOwnerDocument(getter_AddRefs(domDoc)); if (NS_FAILED(rv) || !domDoc) return NS_ERROR_FAILURE; mDOMDocument = domDoc; nsCOMPtr xul_document = do_QueryInterface(domDoc); if (xul_document) { info->context = EMBED_CONTEXT_NONE; return NS_ERROR_FAILURE; } // Now we know that the page isn't a xul window, we can try and // do something useful with it. PRUint16 type; rv = node->GetNodeType(&type); if (NS_FAILED(rv)) return NS_ERROR_FAILURE; PRBool has_background = PR_FALSE; nsCOMPtr element = do_QueryInterface(node); if ((nsIDOMNode::ELEMENT_NODE == type) && element) { nsEmbedString uTag; rv = element->GetLocalName(uTag); if (NS_FAILED(rv)) return NS_ERROR_FAILURE; nsEmbedCString tag; NS_UTF16ToCString (uTag, NS_CSTRING_ENCODING_UTF8, tag); if (g_ascii_strcasecmp (tag.get(), "img") == 0) { info->context |= EMBED_CONTEXT_IMAGE; nsEmbedString img; nsCOMPtr image = do_QueryInterface(node, &rv); if (NS_FAILED(rv) || !image) return NS_ERROR_FAILURE; rv = image->GetSrc (img); if (NS_FAILED(rv)) return NS_ERROR_FAILURE; SetStringProperty ("image", img); } else if (g_ascii_strcasecmp (tag.get(), "input") == 0) { CheckInput (node); } else if (g_ascii_strcasecmp (tag.get(), "textarea") == 0) { info->context |= EMBED_CONTEXT_INPUT; } else if (g_ascii_strcasecmp (tag.get(), "object") == 0) { nsCOMPtr object; object = do_QueryInterface (node); if (!element) return NS_ERROR_FAILURE; nsEmbedString value; object->GetType(value); nsEmbedCString cValue; NS_UTF16ToCString (value, NS_CSTRING_ENCODING_UTF8, cValue); // MIME types are always lower case if (g_str_has_prefix (cValue.get(), "image/")) { info->context |= EMBED_CONTEXT_IMAGE; nsEmbedString img; rv = object->GetData (img); if (NS_FAILED(rv)) return NS_ERROR_FAILURE; nsEmbedCString cImg; rv = ResolveBaseURL (img, cImg); if (NS_FAILED (rv)) return NS_ERROR_FAILURE; SetStringProperty ("image", cImg.get()); } else { info->context = EMBED_CONTEXT_NONE; return NS_OK; } } else if (g_ascii_strcasecmp (tag.get(), "html") == 0) { /* Clicked on part of the page without a , so * look for a background image in the body tag */ nsCOMPtr nodeList; rv = mDOMDocument->GetElementsByTagName (nsEmbedString(bodyLiteral), getter_AddRefs (nodeList)); if (NS_SUCCEEDED (rv) && nodeList) { nsCOMPtr bodyNode; nodeList->Item (0, getter_AddRefs (bodyNode)); nsEmbedString cssurl; rv = GetCSSBackground (bodyNode, cssurl); if (NS_SUCCEEDED (rv)) { nsEmbedCString bgimg; rv = ResolveBaseURL (cssurl, bgimg); if (NS_FAILED (rv)) return NS_ERROR_FAILURE; SetStringProperty ("background_image", bgimg.get()); has_background = PR_TRUE; } } } } /* Is page framed ? */ PRBool framed; IsPageFramed (node, &framed); SetIntProperty ("framed_page", framed); /* Bubble out, looking for items of interest */ while (node) { nsCOMPtr dom_elem = do_QueryInterface(node); if (dom_elem) { nsEmbedString value; dom_elem->GetAttributeNS (nsEmbedString(xlinknsLiteral), nsEmbedString(typeLiteral), value); nsEmbedCString cValue; NS_UTF16ToCString (value, NS_CSTRING_ENCODING_UTF8, cValue); if (g_ascii_strcasecmp (cValue.get(), "simple") == 0) { info->context |= EMBED_CONTEXT_LINK; dom_elem->GetAttributeNS (nsEmbedString(xlinknsLiteral), nsEmbedString(hrefLiteral), value); SetStringProperty ("link", value); CheckLinkScheme (value); } } rv = node->GetNodeType(&type); if (NS_FAILED(rv)) return NS_ERROR_FAILURE; element = do_QueryInterface(node); if ((nsIDOMNode::ELEMENT_NODE == type) && element) { nsEmbedString uTag; rv = element->GetLocalName(uTag); if (NS_FAILED(rv)) return NS_ERROR_FAILURE; nsEmbedCString tag; NS_UTF16ToCString (uTag, NS_CSTRING_ENCODING_UTF8, tag); /* Link */ if (g_ascii_strcasecmp (tag.get(), "a") == 0) { nsEmbedString tmp; rv = GatherTextUnder (node, tmp); if (NS_SUCCEEDED(rv)) SetStringProperty ("linktext", tmp); nsCOMPtr anchor = do_QueryInterface(node); nsEmbedCString href; anchor->GetHref (tmp); NS_UTF16ToCString (tmp, NS_CSTRING_ENCODING_UTF8, href); if (g_str_has_prefix (href.get(), "mailto:")) { /* cut "mailto:" */ href.Cut (0, 7); // FIXME: cut any chars after "?" nsEmbedCString unescapedHref; rv = Unescape (href, unescapedHref); if (NS_SUCCEEDED (rv) && unescapedHref.Length()) { SetStringProperty ("email", unescapedHref.get()); info->context |= EMBED_CONTEXT_EMAIL_LINK; } } if (anchor && tmp.Length()) { info->context |= EMBED_CONTEXT_LINK; SetStringProperty ("link", tmp); CheckLinkScheme (tmp); rv = anchor->GetHreflang (tmp); if (NS_SUCCEEDED(rv)) SetStringProperty ("link_lang", tmp); rv = anchor->GetTarget (tmp); if (NS_SUCCEEDED(rv)) SetStringProperty ("link_target", tmp); rv = anchor->GetRel (tmp); if (NS_SUCCEEDED(rv)) SetStringProperty ("link_rel", tmp); rv = anchor->GetRev (tmp); if (NS_SUCCEEDED(rv)) SetStringProperty ("link_rev", tmp); rv = element->GetTitle (tmp); if (NS_SUCCEEDED(rv)) SetStringProperty ("link_title", tmp); rv = anchor->GetType (tmp); if (NS_SUCCEEDED(rv)) SetStringProperty ("link_type", tmp); nsEmbedCString linkType; NS_UTF16ToCString (tmp, NS_CSTRING_ENCODING_UTF8, linkType); if (g_ascii_strcasecmp (linkType.get(), "text/smartbookmark") == 0) { SetIntProperty ("link_is_smart", TRUE); nsCOMPtr childNode; node->GetFirstChild (getter_AddRefs(childNode)); if (childNode) { nsCOMPtr image = do_QueryInterface(childNode, &rv); if (image) { nsEmbedString img; rv = image->GetSrc (img); if (!NS_FAILED(rv)) { SetStringProperty ("image", img); } } } } else { SetIntProperty ("link_is_smart", FALSE); } } } else if (g_ascii_strcasecmp (tag.get(), "option") == 0) { info->context = EMBED_CONTEXT_NONE; return NS_OK; } if (g_ascii_strcasecmp (tag.get(), "area") == 0) { info->context |= EMBED_CONTEXT_LINK; nsCOMPtr area = do_QueryInterface(node, &rv); if (NS_SUCCEEDED(rv) && area) { nsEmbedString href; rv = area->GetHref (href); if (NS_FAILED(rv)) return NS_ERROR_FAILURE; SetStringProperty ("link", href); CheckLinkScheme (href); } } else if (g_ascii_strcasecmp (tag.get(), "input") == 0) { CheckInput (node); } else if (g_ascii_strcasecmp (tag.get(), "textarea") == 0) { info->context |= EMBED_CONTEXT_INPUT; } if (!has_background) { nsEmbedString cssurl; rv = GetCSSBackground (node, cssurl); if (NS_SUCCEEDED (rv)) { nsEmbedCString bgimg; rv = ResolveBaseURL (cssurl, bgimg); if (NS_FAILED (rv)) return NS_ERROR_FAILURE; SetStringProperty ("background_image", bgimg.get()); if (NS_FAILED (rv)) return NS_ERROR_FAILURE; has_background = PR_TRUE; } } } nsCOMPtr parentNode; node->GetParentNode (getter_AddRefs(parentNode)); node = parentNode; } return NS_OK; } nsresult EventContext::GetCSSBackground (nsIDOMNode *node, nsAString& url) { nsresult rv; const PRUnichar bgimage[] = {'b', 'a', 'c', 'k', 'g', 'r', 'o', 'u', 'n', 'd', '-', 'i', 'm', 'a', 'g', 'e', '\0'}; nsCOMPtr element = do_QueryInterface (node); NS_ENSURE_TRUE (element, NS_ERROR_FAILURE); nsCOMPtr docView = do_QueryInterface (mDOMDocument); NS_ENSURE_TRUE (docView, NS_ERROR_FAILURE); nsCOMPtr abstractView; docView->GetDefaultView (getter_AddRefs (abstractView)); nsCOMPtr viewCSS = do_QueryInterface (abstractView); NS_ENSURE_TRUE (viewCSS, NS_ERROR_FAILURE); nsCOMPtr decl; viewCSS->GetComputedStyle (element, nsEmbedString(), getter_AddRefs (decl)); NS_ENSURE_TRUE (decl, NS_ERROR_FAILURE); nsCOMPtr CSSValue; decl->GetPropertyCSSValue (nsEmbedString(bgimage), getter_AddRefs (CSSValue)); nsCOMPtr primitiveValue = do_QueryInterface (CSSValue); if (!primitiveValue) return NS_ERROR_FAILURE; PRUint16 type; rv = primitiveValue->GetPrimitiveType (&type); NS_ENSURE_SUCCESS (rv, NS_ERROR_FAILURE); if (type != nsIDOMCSSPrimitiveValue::CSS_URI) return NS_ERROR_FAILURE; rv = primitiveValue->GetStringValue (url); NS_ENSURE_SUCCESS (rv, NS_ERROR_FAILURE); return NS_OK; } nsresult EventContext::GetMouseEventInfo (nsIDOMMouseEvent *aMouseEvent, MozillaEmbedEvent *info) { /* FIXME: casting 32-bit guint* to PRUint16* below will break on big-endian */ PRUint16 btn; aMouseEvent->GetButton (&btn); switch (btn) { case 0: info->type = EPHY_EMBED_EVENT_MOUSE_BUTTON1; break; case 1: info->type = EPHY_EMBED_EVENT_MOUSE_BUTTON2; break; case 2: info->type = EPHY_EMBED_EVENT_MOUSE_BUTTON3; break; case (PRUint16) -1: /* when the user submits a form with Return, mozilla synthesises * a _mouse_ click event with btn=65535 (-1). */ info->type = EPHY_EMBED_EVENT_KEY; break; default: g_warning ("Unknown mouse button"); } /* OTOH, casting only between (un)signedness is safe */ aMouseEvent->GetScreenX ((PRInt32*)&info->x); aMouseEvent->GetScreenY ((PRInt32*)&info->y); /* be sure we are not clicking on the scroolbars */ nsCOMPtr nsEvent = do_QueryInterface(aMouseEvent); if (!nsEvent) return NS_ERROR_FAILURE; nsresult rv; nsCOMPtr OriginalTarget; rv = nsEvent->GetOriginalTarget(getter_AddRefs(OriginalTarget)); if (NS_FAILED (rv) || !OriginalTarget) return NS_ERROR_FAILURE; nsCOMPtr OriginalNode = do_QueryInterface(OriginalTarget); if (!OriginalNode) return NS_ERROR_FAILURE; nsEmbedString nodename; OriginalNode->GetNodeName(nodename); nsEmbedCString cNodeName; NS_UTF16ToCString (nodename, NS_CSTRING_ENCODING_UTF8, cNodeName); if (g_ascii_strcasecmp (cNodeName.get(), "xul:scrollbarbutton") == 0 || g_ascii_strcasecmp (cNodeName.get(), "xul:thumb") == 0 || g_ascii_strcasecmp (cNodeName.get(), "xul:vbox") == 0 || g_ascii_strcasecmp (cNodeName.get(), "xul:spacer") == 0 || g_ascii_strcasecmp (cNodeName.get(), "xul:slider") == 0) return NS_ERROR_FAILURE; nsCOMPtr EventTarget; rv = aMouseEvent->GetTarget(getter_AddRefs(EventTarget)); if (NS_FAILED (rv) || !EventTarget) return NS_ERROR_FAILURE; rv = GetEventContext (EventTarget, info); if (NS_FAILED (rv)) return rv; /* Get the modifier */ PRBool mod_key; info->modifier = 0; aMouseEvent->GetAltKey(&mod_key); if (mod_key) info->modifier |= GDK_MOD1_MASK; aMouseEvent->GetShiftKey(&mod_key); if (mod_key) info->modifier |= GDK_SHIFT_MASK; /* no need to check GetMetaKey, it's always PR_FALSE, * see widget/src/gtk2/nsWindow.cpp:InitMouseEvent */ aMouseEvent->GetCtrlKey(&mod_key); if (mod_key) info->modifier |= GDK_CONTROL_MASK; return NS_OK; } nsresult EventContext::GetKeyEventInfo (nsIDOMKeyEvent *aKeyEvent, MozillaEmbedEvent *info) { nsresult rv; info->type = EPHY_EMBED_EVENT_KEY; PRUint32 keyCode; rv = aKeyEvent->GetKeyCode(&keyCode); if (NS_FAILED(rv)) return rv; info->keycode = keyCode; nsCOMPtr target; rv = aKeyEvent->GetTarget(getter_AddRefs(target)); if (NS_FAILED(rv) || !target) return NS_ERROR_FAILURE; /* Calculate the node coordinates relative to the widget origin */ nsCOMPtr elem = do_QueryInterface(target, &rv); if (NS_FAILED(rv)) return rv; PRInt32 x = 0, y = 0; while (elem) { PRInt32 val; elem->GetOffsetTop(&val); y += val; elem->GetScrollTop(&val); y -= val; elem->GetOffsetLeft(&val); x += val; elem->GetScrollLeft(&val); x -= val; nsCOMPtr parent; elem->GetOffsetParent(getter_AddRefs(parent)); elem = do_QueryInterface(parent, &rv); } info->x = x; info->y = y; /* Context */ rv = GetEventContext (target, info); if (NS_FAILED(rv)) return rv; /* Get the modifier */ PRBool mod_key; info->modifier = 0; aKeyEvent->GetAltKey(&mod_key); if (mod_key) info->modifier |= GDK_MOD1_MASK; aKeyEvent->GetShiftKey(&mod_key); if (mod_key) info->modifier |= GDK_SHIFT_MASK; aKeyEvent->GetMetaKey(&mod_key); if (mod_key) info->modifier |= GDK_MOD2_MASK; aKeyEvent->GetCtrlKey(&mod_key); if (mod_key) info->modifier |= GDK_CONTROL_MASK; return NS_OK; } nsresult EventContext::IsPageFramed (nsIDOMNode *node, PRBool *Framed) { nsresult rv; nsCOMPtr mainDocument; rv = mBrowser->GetDocument (getter_AddRefs(mainDocument)); if (NS_FAILED (rv) || !mainDocument) return NS_ERROR_FAILURE; nsCOMPtr nodeDocument; rv = node->GetOwnerDocument (getter_AddRefs(nodeDocument)); if (NS_FAILED (rv) || !nodeDocument) return NS_ERROR_FAILURE; *Framed = (mainDocument != nodeDocument); return NS_OK; } nsresult EventContext::GetTargetDocument (nsIDOMDocument **domDoc) { if (!mDOMDocument) return NS_ERROR_FAILURE; *domDoc = mDOMDocument.get(); NS_IF_ADDREF(*domDoc); return NS_OK; } nsresult EventContext::CheckInput (nsIDOMNode *aNode) { const PRUnichar typeLiteral[] = { 't', 'y', 'p', 'e', '\0' }; nsCOMPtr element; element = do_QueryInterface (aNode); if (!element) return NS_ERROR_FAILURE; nsEmbedString uValue; element->GetAttribute (nsEmbedString(typeLiteral), uValue); nsEmbedCString value; NS_UTF16ToCString (uValue, NS_CSTRING_ENCODING_UTF8, value); if (g_ascii_strcasecmp (value.get(), "image") == 0) { mEmbedEvent->context |= EMBED_CONTEXT_IMAGE; nsCOMPtr input; input = do_QueryInterface (aNode); if (!input) return NS_ERROR_FAILURE; nsresult rv; nsEmbedString img; rv = input->GetSrc (img); if (NS_FAILED(rv)) return NS_ERROR_FAILURE; nsEmbedCString cImg; rv = ResolveBaseURL (img, cImg); if (NS_FAILED(rv)) return NS_ERROR_FAILURE; SetStringProperty ("image", cImg.get()); } else if (g_ascii_strcasecmp (value.get(), "radio") != 0 && g_ascii_strcasecmp (value.get(), "submit") != 0 && g_ascii_strcasecmp (value.get(), "reset") != 0 && g_ascii_strcasecmp (value.get(), "hidden") != 0 && g_ascii_strcasecmp (value.get(), "button") != 0 && g_ascii_strcasecmp (value.get(), "checkbox") != 0) { mEmbedEvent->context |= EMBED_CONTEXT_INPUT; } return NS_OK; } nsresult EventContext::CheckLinkScheme (const nsAString &link) { nsCOMPtr uri; EphyUtils::NewURI (getter_AddRefs (uri), link); if (!uri) return NS_ERROR_FAILURE; nsresult rv; nsEmbedCString scheme; rv = uri->GetScheme (scheme); if (NS_FAILED (rv)) return NS_ERROR_FAILURE; if (g_ascii_strcasecmp (scheme.get(), "http") || g_ascii_strcasecmp (scheme.get(), "https") || g_ascii_strcasecmp (scheme.get(), "ftp") || g_ascii_strcasecmp (scheme.get(), "file") || g_ascii_strcasecmp (scheme.get(), "data") || g_ascii_strcasecmp (scheme.get(), "resource") || g_ascii_strcasecmp (scheme.get(), "about") || g_ascii_strcasecmp (scheme.get(), "gopher")) { SetIntProperty ("link-has-web-scheme", TRUE); } return NS_OK; } nsresult EventContext::SetIntProperty (const char *name, int value) { GValue *val = g_new0 (GValue, 1); g_value_init (val, G_TYPE_INT); g_value_set_int (val, value); mozilla_embed_event_set_property (mEmbedEvent, name, val); return NS_OK; } nsresult EventContext::SetStringProperty (const char *name, const char *value) { GValue *val = g_new0 (GValue, 1); g_value_init (val, G_TYPE_STRING); g_value_set_string (val, value); mozilla_embed_event_set_property (mEmbedEvent, name, val); return NS_OK; } nsresult EventContext::SetStringProperty (const char *name, const nsAString &value) { nsEmbedCString cValue; NS_UTF16ToCString (value, NS_CSTRING_ENCODING_UTF8, cValue); return SetStringProperty (name, cValue.get()); }