/* * This file is part of the DOM implementation for KDE. * * Copyright (C) 1999 Lars Knoll (knoll@kde.org) * (C) 1999 Antti Koivisto (koivisto@kde.org) * (C) 2001 Dirk Mueller (mueller@kde.org) * Copyright (C) 2004 Apple Computer, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library 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 * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. * */ #undef FORMS_DEBUG //#define FORMS_DEBUG #include "html/html_formimpl.h" #include "khtmlview.h" #include "khtml_part.h" #include "html/html_documentimpl.h" #include "html_imageimpl.h" #include "khtml_settings.h" #include "misc/htmlhashes.h" #include "misc/formdata.h" #include "css/cssstyleselector.h" #include "css/cssproperties.h" #include "css/csshelper.h" #include "xml/dom_textimpl.h" #include "xml/dom2_eventsimpl.h" #include "khtml_ext.h" #include "rendering/render_form.h" #include #include #include #include #include #include #include #include #include #include // for keygen #include #include #include using namespace khtml; namespace DOM { struct FormDataListItem { FormDataListItem(const QCString &data) : m_data(data) { } FormDataListItem(const QString &path) : m_path(path) { } QString m_path; QCString m_data; }; class FormDataList { public: FormDataList(QTextCodec *); void appendData(const DOMString &key, const DOMString &value) { appendString(key.string()); appendString(value.string()); } void appendData(const DOMString &key, const QString &value) { appendString(key.string()); appendString(value); } void appendData(const DOMString &key, const QCString &value) { appendString(key.string()); appendString(value); } void appendData(const DOMString &key, int value) { appendString(key.string()); appendString(QString::number(value)); } void appendFile(const DOMString &key, const DOMString &filename); QValueListConstIterator begin() const { return m_list.begin(); } QValueListConstIterator end() const { return m_list.end(); } private: void appendString(const QCString &s); void appendString(const QString &s); QTextCodec *m_codec; QValueList m_list; }; HTMLFormElementImpl::HTMLFormElementImpl(DocumentPtr *doc) : HTMLElementImpl(doc) { collectionInfo = 0; m_post = false; m_multipart = false; m_autocomplete = true; m_insubmit = false; m_doingsubmit = false; m_inreset = false; m_enctype = "application/x-www-form-urlencoded"; m_boundary = "----------0xKhTmLbOuNdArY"; m_acceptcharset = "UNKNOWN"; m_malformed = false; } HTMLFormElementImpl::~HTMLFormElementImpl() { delete collectionInfo; for (unsigned i = 0; i < formElements.count(); ++i) formElements[i]->m_form = 0; for (unsigned i = 0; i < dormantFormElements.count(); ++i) dormantFormElements[i]->m_form = 0; for (unsigned i = 0; i < imgElements.count(); ++i) imgElements[i]->m_form = 0; } NodeImpl::Id HTMLFormElementImpl::id() const { return ID_FORM; } #if APPLE_CHANGES bool HTMLFormElementImpl::formWouldHaveSecureSubmission(const DOMString &url) { if (url.isNull()) { return false; } return getDocument()->completeURL(url.string()).startsWith("https:", false); } #endif void HTMLFormElementImpl::attach() { HTMLElementImpl::attach(); if (getDocument()->isHTMLDocument()) { HTMLDocumentImpl *document = static_cast(getDocument()); document->addNamedImageOrForm(oldNameAttr); document->addNamedImageOrForm(oldIdAttr); } #if APPLE_CHANGES // note we don't deal with calling secureFormRemoved() on detach, because the timing // was such that it cleared our state too early if (formWouldHaveSecureSubmission(m_url)) getDocument()->secureFormAdded(); #endif } void HTMLFormElementImpl::detach() { if (getDocument()->isHTMLDocument()) { HTMLDocumentImpl *document = static_cast(getDocument()); document->removeNamedImageOrForm(oldNameAttr); document->removeNamedImageOrForm(oldIdAttr); } HTMLElementImpl::detach(); } long HTMLFormElementImpl::length() const { int len = 0; for (unsigned i = 0; i < formElements.count(); ++i) if (formElements[i]->isEnumeratable()) ++len; return len; } #if APPLE_CHANGES void HTMLFormElementImpl::submitClick() { bool submitFound = false; for (unsigned i = 0; i < formElements.count(); ++i) { if (formElements[i]->id() == ID_INPUT) { HTMLInputElementImpl *element = static_cast(formElements[i]); if (element->isSuccessfulSubmitButton() && element->renderer()) { submitFound = true; element->click(false); break; } } } if (!submitFound) // submit the form without a submit or image input prepareSubmit(); } #endif // APPLE_CHANGES static QCString encodeCString(const QCString& e) { // http://www.w3.org/TR/html4/interact/forms.html#h-17.13.4.1 // safe characters like NS handles them for compatibility static const char *safe = "-._*"; int elen = e.length(); QCString encoded(( elen+e.contains( '\n' ) )*3+1); int enclen = 0; //QCString orig(e.data(), e.size()); for(int pos = 0; pos < elen; pos++) { unsigned char c = e[pos]; if ( (( c >= 'A') && ( c <= 'Z')) || (( c >= 'a') && ( c <= 'z')) || (( c >= '0') && ( c <= '9')) || (strchr(safe, c)) ) encoded[enclen++] = c; else if ( c == ' ' ) encoded[enclen++] = '+'; else if ( c == '\n' || ( c == '\r' && e[pos+1] != '\n' ) ) { encoded[enclen++] = '%'; encoded[enclen++] = '0'; encoded[enclen++] = 'D'; encoded[enclen++] = '%'; encoded[enclen++] = '0'; encoded[enclen++] = 'A'; } else if ( c != '\r' ) { encoded[enclen++] = '%'; unsigned int h = c / 16; h += (h > 9) ? ('A' - 10) : '0'; encoded[enclen++] = h; unsigned int l = c % 16; l += (l > 9) ? ('A' - 10) : '0'; encoded[enclen++] = l; } } encoded[enclen++] = '\0'; encoded.truncate(enclen); return encoded; } // Change plain CR and plain LF to CRLF pairs. static QCString fixLineBreaks(const QCString &s) { // Compute the length. unsigned newLen = 0; const char *p = s.data(); while (char c = *p++) { if (c == '\r') { // Safe to look ahead because of trailing '\0'. if (*p != '\n') { // Turn CR into CRLF. newLen += 2; } } else if (c == '\n') { // Turn LF into CRLF. newLen += 2; } else { // Leave other characters alone. newLen += 1; } } if (newLen == s.length()) { return s; } // Make a copy of the string. p = s.data(); QCString result(newLen + 1); char *q = result.data(); while (char c = *p++) { if (c == '\r') { // Safe to look ahead because of trailing '\0'. if (*p != '\n') { // Turn CR into CRLF. *q++ = '\r'; *q++ = '\n'; } } else if (c == '\n') { // Turn LF into CRLF. *q++ = '\r'; *q++ = '\n'; } else { // Leave other characters alone. *q++ = c; } } return result; } #if !APPLE_CHANGES void HTMLFormElementImpl::i18nData() { QString foo1 = i18n( "You're about to send data to the Internet " "via an unencrypted connection. It might be possible " "for others to see this information.\n" "Do you want to continue?"); QString foo2 = i18n("KDE Web browser"); QString foo3 = i18n("When you send a password unencrypted to the Internet, " "it might be possible for others to capture it as plain text.\n" "Do you want to continue?"); QString foo5 = i18n("Your data submission is redirected to " "an insecure site. The data is sent unencrypted.\n" "Do you want to continue?"); QString foo6 = i18n("The page contents expired. You can repost the form" "data by using Reload"); } #endif bool HTMLFormElementImpl::formData(FormData &form_data) const { #ifdef FORMS_DEBUG kdDebug( 6030 ) << "form: formData()" << endl; #endif QCString enc_string = ""; // used for non-multipart data // find out the QTextcodec to use QString str = m_acceptcharset.string(); str.replace(',', ' '); QStringList charsets = QStringList::split(' ', str); QTextCodec* codec = 0; KHTMLPart *part = getDocument()->part(); for ( QStringList::Iterator it = charsets.begin(); it != charsets.end(); ++it ) { QString enc = (*it); if(enc.contains("UNKNOWN")) { // use standard document encoding enc = "ISO-8859-1"; if (part) enc = part->encoding(); } if((codec = KGlobal::charsets()->codecForName(enc.latin1()))) break; } if(!codec) codec = QTextCodec::codecForLocale(); #if !APPLE_CHANGES QStringList fileUploads; #endif for (unsigned i = 0; i < formElements.count(); ++i) { HTMLGenericFormElementImpl* current = formElements[i]; FormDataList lst(codec); if (!current->disabled() && current->appendFormData(lst, m_multipart)) { //kdDebug(6030) << "adding name " << current->name().string() << endl; for(QValueListConstIterator it = lst.begin(); it != lst.end(); ++it ) { if (!m_multipart) { // handle ISINDEX / special // but only if its the first entry if ( enc_string.isEmpty() && (*it).m_data == "isindex" ) { ++it; enc_string += encodeCString( (*it).m_data ); } else { if(!enc_string.isEmpty()) enc_string += '&'; enc_string += encodeCString((*it).m_data); enc_string += "="; ++it; enc_string += encodeCString((*it).m_data); } } else { QCString hstr("--"); hstr += m_boundary.string().latin1(); hstr += "\r\n"; hstr += "Content-Disposition: form-data; name=\""; hstr += (*it).m_data.data(); hstr += "\""; // if the current type is FILE, then we also need to // include the filename if (current->nodeType() == Node::ELEMENT_NODE && current->id() == ID_INPUT && static_cast(current)->inputType() == HTMLInputElementImpl::FILE) { QString path = static_cast(current)->value().string(); #if !APPLE_CHANGES if (path.length()) fileUploads << path; #endif // FIXME: This won't work if the filename includes a " mark, // or control characters like CR or LF. This also does strange // things if the filename includes characters you can't encode // in the website's character set. hstr += "; filename=\""; hstr += codec->fromUnicode(path.mid(path.findRev('/') + 1)); hstr += "\""; if(!static_cast(current)->value().isEmpty()) { #if APPLE_CHANGES QString mimeType = part ? KWQ(part)->mimeTypeForFileName(path) : QString(); #else KMimeType::Ptr ptr = KMimeType::findByURL(KURL(path)); QString mimeType = ptr->name(); #endif if (!mimeType.isEmpty()) { hstr += "\r\nContent-Type: "; hstr += mimeType.ascii(); } } } hstr += "\r\n\r\n"; ++it; // append body form_data.appendData(hstr.data(), hstr.length()); #if APPLE_CHANGES const FormDataListItem &item = *it; size_t dataSize = item.m_data.size(); if (dataSize != 0) form_data.appendData(item.m_data, dataSize - 1); else if (!item.m_path.isEmpty()) form_data.appendFile(item.m_path); #else form_data.appendData((*it).m_data, (*it).m_data.size() - 1); #endif form_data.appendData("\r\n", 2); } } } } #if !APPLE_CHANGES if (fileUploads.count()) { int result = KMessageBox::warningContinueCancelList( 0, i18n("You're about to transfer the following files from " "your local computer to the Internet.\n" "Do you really want to continue?"), fileUploads); if (result == KMessageBox::Cancel) { return false; } } #endif if (m_multipart) enc_string = ("--" + m_boundary.string() + "--\r\n").ascii(); form_data.appendData(enc_string.data(), enc_string.length()); return true; } void HTMLFormElementImpl::setEnctype( const DOMString& type ) { if(type.string().find("multipart", 0, false) != -1 || type.string().find("form-data", 0, false) != -1) { m_enctype = "multipart/form-data"; m_multipart = true; m_post = true; } else if (type.string().find("text", 0, false) != -1 || type.string().find("plain", 0, false) != -1) { m_enctype = "text/plain"; m_multipart = false; } else { m_enctype = "application/x-www-form-urlencoded"; m_multipart = false; } } void HTMLFormElementImpl::setBoundary( const DOMString& bound ) { m_boundary = bound; } bool HTMLFormElementImpl::prepareSubmit() { KHTMLPart *part = getDocument()->part(); if(m_insubmit || !part || part->onlyLocalReferences()) return m_insubmit; m_insubmit = true; m_doingsubmit = false; if ( dispatchHTMLEvent(EventImpl::SUBMIT_EVENT,false,true) && !m_doingsubmit ) m_doingsubmit = true; m_insubmit = false; if ( m_doingsubmit ) submit(true); return m_doingsubmit; } void HTMLFormElementImpl::submit( bool activateSubmitButton ) { KHTMLView *view = getDocument()->view(); KHTMLPart *part = getDocument()->part(); if (!view || !part) { return; } if ( m_insubmit ) { m_doingsubmit = true; return; } m_insubmit = true; #ifdef FORMS_DEBUG kdDebug( 6030 ) << "submitting!" << endl; #endif HTMLGenericFormElementImpl* firstSuccessfulSubmitButton = 0; bool needButtonActivation = activateSubmitButton; // do we need to activate a submit button? #if APPLE_CHANGES KWQ(part)->clearRecordedFormValues(); #endif for (unsigned i = 0; i < formElements.count(); ++i) { HTMLGenericFormElementImpl* current = formElements[i]; #if APPLE_CHANGES // Our app needs to get form values for password fields for doing password autocomplete, // so we are more lenient in pushing values, and let the app decide what to save when. if (current->id() == ID_INPUT) { HTMLInputElementImpl *input = static_cast(current); if (input->inputType() == HTMLInputElementImpl::TEXT || input->inputType() == HTMLInputElementImpl::PASSWORD || input->inputType() == HTMLInputElementImpl::SEARCH) { KWQ(part)->recordFormValue(input->name().string(), input->value().string(), this); if (input->renderer() && input->inputType() == HTMLInputElementImpl::SEARCH) static_cast(input->renderer())->addSearchResult(); } } #else if (current->id() == ID_INPUT && static_cast(current)->inputType() == HTMLInputElementImpl::TEXT && static_cast(current)->autoComplete() ) { HTMLInputElementImpl *input = static_cast(current); view->addFormCompletionItem(input->name().string(), input->value().string()); } #endif if (needButtonActivation) { if (current->isActivatedSubmit()) { needButtonActivation = false; } else if (firstSuccessfulSubmitButton == 0 && current->isSuccessfulSubmitButton()) { firstSuccessfulSubmitButton = current; } } } if (needButtonActivation && firstSuccessfulSubmitButton) { firstSuccessfulSubmitButton->setActivatedSubmit(true); } FormData form_data; if (formData(form_data)) { if(m_post) { part->submitForm( "post", m_url.string(), form_data, m_target.string(), enctype().string(), boundary().string() ); } else { part->submitForm( "get", m_url.string(), form_data, m_target.string() ); } } if (needButtonActivation && firstSuccessfulSubmitButton) { firstSuccessfulSubmitButton->setActivatedSubmit(false); } m_doingsubmit = m_insubmit = false; } void HTMLFormElementImpl::reset( ) { KHTMLPart *part = getDocument()->part(); if(m_inreset || !part) return; m_inreset = true; #ifdef FORMS_DEBUG kdDebug( 6030 ) << "reset pressed!" << endl; #endif // ### DOM2 labels this event as not cancelable, however // common browsers( sick! ) allow it be cancelled. if ( !dispatchHTMLEvent(EventImpl::RESET_EVENT,true, true) ) { m_inreset = false; return; } for (unsigned i = 0; i < formElements.count(); ++i) formElements[i]->reset(); m_inreset = false; } void HTMLFormElementImpl::parseHTMLAttribute(HTMLAttributeImpl *attr) { switch(attr->id()) { case ATTR_ACTION: #if APPLE_CHANGES { bool oldURLWasSecure = formWouldHaveSecureSubmission(m_url); #endif m_url = khtml::parseURL(attr->value()); #if APPLE_CHANGES bool newURLIsSecure = formWouldHaveSecureSubmission(m_url); if (m_attached && (oldURLWasSecure != newURLIsSecure)) if (newURLIsSecure) getDocument()->secureFormAdded(); else getDocument()->secureFormRemoved(); } #endif break; case ATTR_TARGET: m_target = attr->value(); break; case ATTR_METHOD: if ( strcasecmp( attr->value(), "post" ) == 0 ) m_post = true; else if ( strcasecmp( attr->value(), "get" ) == 0 ) m_post = false; break; case ATTR_ENCTYPE: setEnctype( attr->value() ); break; case ATTR_ACCEPT_CHARSET: // space separated list of charsets the server // accepts - see rfc2045 m_acceptcharset = attr->value(); break; case ATTR_ACCEPT: // ignore this one for the moment... break; case ATTR_AUTOCOMPLETE: m_autocomplete = strcasecmp( attr->value(), "off" ); break; case ATTR_ONSUBMIT: setHTMLEventListener(EventImpl::SUBMIT_EVENT, getDocument()->createHTMLEventListener(attr->value().string(), this)); break; case ATTR_ONRESET: setHTMLEventListener(EventImpl::RESET_EVENT, getDocument()->createHTMLEventListener(attr->value().string(), this)); break; case ATTR_NAME: { QString newNameAttr = attr->value().string(); if (attached() && getDocument()->isHTMLDocument()) { HTMLDocumentImpl *document = static_cast(getDocument()); document->removeNamedImageOrForm(oldNameAttr); document->addNamedImageOrForm(newNameAttr); } oldNameAttr = newNameAttr; } break; case ATTR_ID: { QString newIdAttr = attr->value().string(); if (attached() && getDocument()->isHTMLDocument()) { HTMLDocumentImpl *document = static_cast(getDocument()); document->removeNamedImageOrForm(oldIdAttr); document->addNamedImageOrForm(newIdAttr); } oldIdAttr = newIdAttr; } // fall through default: HTMLElementImpl::parseHTMLAttribute(attr); } } void HTMLFormElementImpl::radioClicked( HTMLGenericFormElementImpl *caller ) { for (unsigned i = 0; i < formElements.count(); ++i) { HTMLGenericFormElementImpl *current = formElements[i]; if (current->id() == ID_INPUT && static_cast(current)->inputType() == HTMLInputElementImpl::RADIO && current != caller && current->form() == caller->form() && current->name() == caller->name()) { static_cast(current)->setChecked(false); } } } template static void appendToVector(QPtrVector &vec, T *item) { unsigned size = vec.size(); unsigned count = vec.count(); if (size == count) vec.resize(size == 0 ? 8 : (int)(size * 1.5)); vec.insert(count, item); } template static void removeFromVector(QPtrVector &vec, T *item) { int pos = vec.findRef(item); int count = vec.count(); if (pos < 0) return; for (int i = pos; i < count - 1; i++) { vec.insert(i, vec[i+1]); } vec.remove(count - 1); } void HTMLFormElementImpl::registerFormElement(HTMLGenericFormElementImpl *e) { appendToVector(formElements, e); removeFromVector(dormantFormElements, e); } void HTMLFormElementImpl::removeFormElement(HTMLGenericFormElementImpl *e) { removeFromVector(formElements, e); removeFromVector(dormantFormElements, e); } void HTMLFormElementImpl::makeFormElementDormant(HTMLGenericFormElementImpl *e) { appendToVector(dormantFormElements, e); removeFromVector(formElements, e); } bool HTMLFormElementImpl::isURLAttribute(AttributeImpl *attr) const { return attr->id() == ATTR_ACTION; } void HTMLFormElementImpl::registerImgElement(HTMLImageElementImpl *e) { appendToVector(imgElements, e); } void HTMLFormElementImpl::removeImgElement(HTMLImageElementImpl *e) { removeFromVector(imgElements, e); } // ------------------------------------------------------------------------- HTMLGenericFormElementImpl::HTMLGenericFormElementImpl(DocumentPtr *doc, HTMLFormElementImpl *f) : HTMLElementImpl(doc) { m_disabled = m_readOnly = false; m_name = 0; m_dormant = false; if (f) m_form = f; else m_form = getForm(); if (m_form) m_form->registerFormElement(this); } HTMLGenericFormElementImpl::~HTMLGenericFormElementImpl() { if (m_form) m_form->removeFormElement(this); if (m_name) m_name->deref(); } void HTMLGenericFormElementImpl::parseHTMLAttribute(HTMLAttributeImpl *attr) { switch(attr->id()) { case ATTR_NAME: break; case ATTR_DISABLED: setDisabled( !attr->isNull() ); break; case ATTR_READONLY: { bool m_oldreadOnly = m_readOnly; m_readOnly = !attr->isNull(); if (m_oldreadOnly != m_readOnly) setChanged(); break; } default: HTMLElementImpl::parseHTMLAttribute(attr); } } void HTMLGenericFormElementImpl::attach() { assert(!attached()); // FIXME: This handles the case of a new form element being created by // JavaScript and inserted inside a form. What it does not handle is // a form element being moved from inside a form to outside, or from one // inside one form to another. The reason this other case is hard to fix // is that during parsing, we may have been passed a form that we are not // inside, DOM-tree-wise. If so, it's hard for us to know when we should // be removed from that form's element list. if (!m_form) { m_form = getForm(); if (m_form) m_form->registerFormElement(this); } HTMLElementImpl::attach(); // The call to updateFromElement() needs to go after the call through // to the base class's attach() because that can sometimes do a close // on the renderer. if (m_render) { m_render->updateFromElement(); // Delayed attachment in order to prevent FOUC can result in an object being // programmatically focused before it has a render object. If we have been focused // (i.e., if we are the focusNode) then go ahead and focus our corresponding native widget. // (Attach/detach can also happen as a result of display type changes, e.g., making a widget // block instead of inline, and focus should be restored in that case as well.) if (getDocument()->focusNode() == this && m_render->isWidget() && static_cast(renderer())->widget()) static_cast(renderer())->widget()->setFocus(); } } void HTMLGenericFormElementImpl::insertedIntoDocument() { if (m_form && m_dormant) m_form->registerFormElement(this); m_dormant = false; HTMLElementImpl::insertedIntoDocument(); } void HTMLGenericFormElementImpl::removedFromDocument() { if (m_form) m_form->makeFormElementDormant(this); m_dormant = true; HTMLElementImpl::removedFromDocument(); } HTMLFormElementImpl *HTMLGenericFormElementImpl::getForm() const { NodeImpl *p = parentNode(); while(p) { if( p->id() == ID_FORM ) return static_cast(p); p = p->parentNode(); } #ifdef FORMS_DEBUG kdDebug( 6030 ) << "couldn't find form!" << endl; #endif return 0; } DOMString HTMLGenericFormElementImpl::name() const { if (m_name) return m_name; // ### // DOMString n = getDocument()->htmlMode() != DocumentImpl::XHtml ? // getAttribute(ATTR_NAME) : getAttribute(ATTR_ID); DOMString n = getAttribute(ATTR_NAME); if (n.isNull()) return new DOMStringImpl(""); return n; } void HTMLGenericFormElementImpl::setName(const DOMString& name) { if (m_name) m_name->deref(); m_name = name.implementation(); if (m_name) m_name->ref(); } void HTMLGenericFormElementImpl::onSelect() { // ### make this work with new form events architecture dispatchHTMLEvent(EventImpl::SELECT_EVENT,true,false); } void HTMLGenericFormElementImpl::onChange() { // ### make this work with new form events architecture dispatchHTMLEvent(EventImpl::CHANGE_EVENT,true,false); } bool HTMLGenericFormElementImpl::disabled() const { return m_disabled; } void HTMLGenericFormElementImpl::setDisabled( bool _disabled ) { if ( m_disabled != _disabled ) { m_disabled = _disabled; setChanged(); } } void HTMLGenericFormElementImpl::recalcStyle( StyleChange ch ) { //bool changed = changed(); HTMLElementImpl::recalcStyle( ch ); if (m_render /*&& changed*/) m_render->updateFromElement(); } bool HTMLGenericFormElementImpl::isFocusable() const { if (!m_render || (m_render->style() && m_render->style()->visibility() != VISIBLE) || m_render->width() == 0 || m_render->height() == 0) return false; return true; } bool HTMLGenericFormElementImpl::isKeyboardFocusable() const { if (isFocusable()) { if (m_render->isWidget()) { return static_cast(m_render)->widget() && (static_cast(m_render)->widget()->focusPolicy() & QWidget::TabFocus); } if (getDocument()->part()) return getDocument()->part()->tabsToAllControls(); } return false; } bool HTMLGenericFormElementImpl::isMouseFocusable() const { if (isFocusable()) { if (m_render->isWidget()) { return static_cast(m_render)->widget() && (static_cast(m_render)->widget()->focusPolicy() & QWidget::ClickFocus); } #if APPLE_CHANGES // For and