| /* This file is part of the KDE project |
| * |
| * Copyright (C) 1998, 1999 Torben Weis <weis@kde.org> |
| * 1999 Lars Knoll <knoll@kde.org> |
| * 1999 Antti Koivisto <koivisto@kde.org> |
| * 2000 Simon Hausmann <hausmann@kde.org> |
| * 2000 Stefan Schimanski <1Stein@gmx.de> |
| * 2001 George Staikos <staikos@kde.org> |
| * Copyright (C) 2004, 2005, 2006 Apple Computer, Inc. |
| * Copyright (C) 2005 Alexey Proskuryakov <ap@nypop.com> |
| * |
| * 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. |
| */ |
| |
| #include "config.h" |
| #include "Frame.h" |
| #include "FramePrivate.h" |
| |
| #include "ApplyStyleCommand.h" |
| #include "Cache.h" |
| #include "CachedCSSStyleSheet.h" |
| #include "DOMImplementationImpl.h" |
| #include "DocLoader.h" |
| #include "EditingTextImpl.h" |
| #include "EventNames.h" |
| #include "Frame.h" |
| #include "FrameView.h" |
| #include "HTMLCollectionImpl.h" |
| #include "HTMLFormElementImpl.h" |
| #include "HTMLGenericFormElementImpl.h" |
| #include "MouseEvent.h" |
| #include "MouseEventWithHitTestResults.h" |
| #include "NodeListImpl.h" |
| #include "PlatformString.h" |
| #include "Plugin.h" |
| #include "RenderBlock.h" |
| #include "RenderText.h" |
| #include "SegmentedString.h" |
| #include "TypingCommand.h" |
| #include "VisiblePosition.h" |
| #include "css_computedstyle.h" |
| #include "css_valueimpl.h" |
| #include "cssproperties.h" |
| #include "cssstyleselector.h" |
| #include "dom2_eventsimpl.h" |
| #include "dom2_rangeimpl.h" |
| #include "html_baseimpl.h" |
| #include "html_documentimpl.h" |
| #include "html_imageimpl.h" |
| #include "html_objectimpl.h" |
| #include "htmlediting.h" |
| #include "htmlnames.h" |
| #include "khtml_settings.h" |
| #include "kjs_proxy.h" |
| #include "kjs_window.h" |
| #include "loader.h" |
| #include "markup.h" |
| #include "render_canvas.h" |
| #include "render_frames.h" |
| #include "visible_text.h" |
| #include "visible_units.h" |
| #include "xml_tokenizer.h" |
| #include "xmlhttprequest.h" |
| #include <assert.h> |
| #include <kio/global.h> |
| #include <kio/job.h> |
| #include <klocale.h> |
| #include <kxmlcore/Assertions.h> |
| #include <qptrlist.h> |
| #include <qtextcodec.h> |
| #include <sys/types.h> |
| |
| #if !WIN32 |
| #include <unistd.h> |
| #endif |
| |
| #if SVG_SUPPORT |
| #include "SVGNames.h" |
| #include "XLinkNames.h" |
| #include "SVGDocumentExtensions.h" |
| #endif |
| |
| using namespace KJS; |
| |
| namespace WebCore { |
| |
| using namespace EventNames; |
| using namespace HTMLNames; |
| |
| const double caretBlinkFrequency = 0.5; |
| |
| class UserStyleSheetLoader : public CachedObjectClient { |
| public: |
| UserStyleSheetLoader(Frame* frame, const String& url, DocLoader* dl) |
| : m_frame(frame) |
| , m_cachedSheet(Cache::requestStyleSheet(dl, url)) |
| { |
| m_cachedSheet->ref(this); |
| } |
| ~UserStyleSheetLoader() |
| { |
| m_cachedSheet->deref(this); |
| } |
| private: |
| virtual void setStyleSheet(const String&, const String &sheet) |
| { |
| m_frame->setUserStyleSheet(sheet.qstring()); |
| } |
| Frame* m_frame; |
| CachedCSSStyleSheet* m_cachedSheet; |
| }; |
| |
| #if !NDEBUG |
| struct FrameCounter { |
| static int count; |
| ~FrameCounter() { if (count != 0) fprintf(stderr, "LEAK: %d Frame\n", count); } |
| }; |
| int FrameCounter::count = 0; |
| static FrameCounter frameCounter; |
| #endif |
| |
| static inline Frame* parentFromOwnerRenderer(RenderPart* ownerRenderer) |
| { |
| if (!ownerRenderer) |
| return 0; |
| return ownerRenderer->node()->getDocument()->frame(); |
| } |
| |
| Frame::Frame(Page* page, RenderPart* ownerRenderer) |
| : d(new FramePrivate(page, parentFromOwnerRenderer(ownerRenderer), this, ownerRenderer)) |
| , _drawSelectionOnly(false) |
| , m_markedTextUsesUnderlines(false) |
| , m_windowHasFocus(false) |
| , frameCount(0) |
| { |
| AtomicString::init(); |
| Cache::init(); |
| EventNames::init(); |
| HTMLNames::init(); |
| QualifiedName::init(); |
| |
| #if SVG_SUPPORT |
| SVGNames::init(); |
| XLinkNames::init(); |
| #endif |
| |
| // FIXME: Frames were originally created with a refcount of 1, leave this |
| // ref call here until we can straighten that out. |
| ref(); |
| #if !NDEBUG |
| ++FrameCounter::count; |
| #endif |
| } |
| |
| Frame::~Frame() |
| { |
| ASSERT(!d->m_lifeSupportTimer.isActive()); |
| |
| #if !NDEBUG |
| --FrameCounter::count; |
| #endif |
| |
| cancelRedirection(); |
| |
| if (!d->m_bComplete) |
| closeURL(); |
| |
| clear(false); |
| |
| if (d->m_jscript && d->m_jscript->haveInterpreter()) |
| if (Window* w = Window::retrieveWindow(this)) |
| w->disconnectFrame(); |
| |
| if (d->m_view) { |
| d->m_view->hide(); |
| d->m_view->viewport()->hide(); |
| d->m_view->m_frame = 0; |
| } |
| |
| ASSERT(!d->m_lifeSupportTimer.isActive()); |
| |
| delete d->m_userStyleSheetLoader; |
| delete d; |
| d = 0; |
| } |
| |
| bool Frame::didOpenURL(const KURL &url) |
| { |
| if (d->m_scheduledRedirection == locationChangeScheduledDuringLoad) { |
| // A redirect was shceduled before the document was created. This can happen |
| // when one frame changes another frame's location. |
| return false; |
| } |
| |
| cancelRedirection(); |
| |
| // clear last edit command |
| d->m_lastEditCommand = EditCommandPtr(); |
| clearUndoRedoOperations(); |
| |
| URLArgs args( d->m_extension->urlArgs() ); |
| |
| if (!d->m_restored) |
| closeURL(); |
| |
| if (d->m_restored) |
| d->m_cachePolicy = KIO::CC_Cache; |
| else if (args.reload) |
| d->m_cachePolicy = KIO::CC_Refresh; |
| else |
| d->m_cachePolicy = KIO::CC_Verify; |
| |
| if ( args.doPost() && (url.protocol().startsWith("http")) ) |
| { |
| d->m_job = KIO::http_post( url, args.postData, false ); |
| d->m_job->addMetaData("content-type", args.contentType() ); |
| } |
| else |
| { |
| d->m_job = KIO::get( url, false, false ); |
| } |
| |
| d->m_job->addMetaData(args.metaData()); |
| |
| connect( d->m_job, SIGNAL( result( KIO::Job * ) ), |
| SLOT( slotFinished( KIO::Job * ) ) ); |
| |
| connect( d->m_job, SIGNAL(redirection(KIO::Job*, const KURL&) ), |
| SLOT( slotRedirection(KIO::Job*,const KURL&) ) ); |
| |
| d->m_bComplete = false; |
| d->m_bLoadingMainResource = true; |
| d->m_bLoadEventEmitted = false; |
| |
| d->m_kjsStatusBarText = String(); |
| d->m_kjsDefaultStatusBarText = String(); |
| |
| d->m_bJScriptEnabled = d->m_settings->isJavaScriptEnabled(url.host()); |
| d->m_bJavaEnabled = d->m_settings->isJavaEnabled(url.host()); |
| d->m_bPluginsEnabled = d->m_settings->isPluginsEnabled(url.host()); |
| |
| // initializing d->m_url to the new url breaks relative links when opening such a link after this call and _before_ begin() is called (when the first |
| // data arrives) (Simon) |
| d->m_url = url; |
| if(d->m_url.protocol().startsWith("http") && !d->m_url.host().isEmpty() && d->m_url.path().isEmpty()) |
| d->m_url.setPath("/"); |
| d->m_workingURL = d->m_url; |
| |
| started(); |
| |
| return true; |
| } |
| |
| void Frame::didExplicitOpen() |
| { |
| d->m_bComplete = false; |
| d->m_bLoadEventEmitted = false; |
| |
| // Prevent window.open(url) -- eg window.open("about:blank") -- from blowing away results |
| // from a subsequent window.document.open / window.document.write call. |
| // Cancelling redirection here works for all cases because document.open |
| // implicitly precedes document.write. |
| cancelRedirection(); |
| } |
| |
| void Frame::stopLoading(bool sendUnload) |
| { |
| if (d->m_doc && d->m_doc->tokenizer()) |
| d->m_doc->tokenizer()->stopParsing(); |
| |
| if (d->m_job) |
| { |
| d->m_job->kill(); |
| d->m_job = 0; |
| } |
| |
| if (sendUnload) { |
| if (d->m_doc) { |
| if (d->m_bLoadEventEmitted && !d->m_bUnloadEventEmitted ) { |
| d->m_doc->dispatchWindowEvent(unloadEvent, false, false); |
| if (d->m_doc) |
| d->m_doc->updateRendering(); |
| d->m_bUnloadEventEmitted = true; |
| } |
| } |
| |
| if (d->m_doc && !d->m_doc->inPageCache()) |
| d->m_doc->removeAllEventListenersFromAllNodes(); |
| } |
| |
| d->m_bComplete = true; // to avoid calling completed() in finishedParsing() (David) |
| d->m_bLoadingMainResource = false; |
| d->m_bLoadEventEmitted = true; // don't want that one either |
| d->m_cachePolicy = KIO::CC_Verify; // Why here? |
| |
| if (d->m_doc && d->m_doc->parsing()) { |
| finishedParsing(); |
| d->m_doc->setParsing(false); |
| } |
| |
| d->m_workingURL = KURL(); |
| |
| if (DocumentImpl *doc = d->m_doc.get()) { |
| if (DocLoader *docLoader = doc->docLoader()) |
| Cache::loader()->cancelRequests(docLoader); |
| XMLHttpRequest::cancelRequests(doc); |
| } |
| |
| // tell all subframes to stop as well |
| for (Frame* child = tree()->firstChild(); child; child = child->tree()->nextSibling()) |
| child->stopLoading(sendUnload); |
| |
| d->m_bPendingChildRedirection = false; |
| |
| cancelRedirection(); |
| } |
| |
| BrowserExtension *Frame::browserExtension() const |
| { |
| return d->m_extension; |
| } |
| |
| FrameView *Frame::view() const |
| { |
| return d->m_view.get(); |
| } |
| |
| bool Frame::jScriptEnabled() const |
| { |
| return d->m_bJScriptEnabled; |
| } |
| |
| void Frame::setMetaRefreshEnabled( bool enable ) |
| { |
| d->m_metaRefreshEnabled = enable; |
| } |
| |
| bool Frame::metaRefreshEnabled() const |
| { |
| return d->m_metaRefreshEnabled; |
| } |
| |
| KJSProxyImpl *Frame::jScript() |
| { |
| if (!d->m_bJScriptEnabled) |
| return 0; |
| |
| if (!d->m_jscript) |
| d->m_jscript = new KJSProxyImpl(this); |
| |
| return d->m_jscript; |
| } |
| |
| static bool getString(JSValue* result, QString& string) |
| { |
| if (!result) |
| return false; |
| JSLock lock; |
| UString ustring; |
| if (!result->getString(ustring)) |
| return false; |
| string = ustring.qstring(); |
| return true; |
| } |
| |
| void Frame::replaceContentsWithScriptResult(const KURL& url) |
| { |
| JSValue* ret = executeScript(0, KURL::decode_string(url.url().mid(strlen("javascript:")))); |
| QString scriptResult; |
| if (getString(ret, scriptResult)) { |
| begin(); |
| write(scriptResult); |
| end(); |
| } |
| } |
| |
| JSValue* Frame::executeScript(NodeImpl* n, const QString& script, bool forceUserGesture) |
| { |
| KJSProxyImpl *proxy = jScript(); |
| |
| if (!proxy) |
| return 0; |
| |
| d->m_runningScripts++; |
| // If forceUserGesture is true, then make the script interpreter |
| // treat it as if triggered by a user gesture even if there is no |
| // current DOM event being processed. |
| JSValue* ret = proxy->evaluate(forceUserGesture ? QString::null : d->m_url.url(), 0, script, n); |
| d->m_runningScripts--; |
| |
| if (!d->m_runningScripts) |
| submitFormAgain(); |
| |
| DocumentImpl::updateDocumentsRendering(); |
| |
| return ret; |
| } |
| |
| bool Frame::javaEnabled() const |
| { |
| #ifndef Q_WS_QWS |
| return d->m_bJavaEnabled; |
| #else |
| return false; |
| #endif |
| } |
| |
| bool Frame::pluginsEnabled() const |
| { |
| return d->m_bPluginsEnabled; |
| } |
| |
| void Frame::setAutoloadImages( bool enable ) |
| { |
| if ( d->m_doc && d->m_doc->docLoader()->autoloadImages() == enable ) |
| return; |
| |
| if ( d->m_doc ) |
| d->m_doc->docLoader()->setAutoloadImages( enable ); |
| } |
| |
| bool Frame::autoloadImages() const |
| { |
| if ( d->m_doc ) |
| return d->m_doc->docLoader()->autoloadImages(); |
| |
| return true; |
| } |
| |
| void Frame::clear(bool clearWindowProperties) |
| { |
| if ( d->m_bCleared ) |
| return; |
| d->m_bCleared = true; |
| d->m_bClearing = true; |
| d->m_mousePressNode = 0; |
| |
| if (d->m_doc) { |
| d->m_doc->cancelParsing(); |
| d->m_doc->detach(); |
| } |
| |
| // Moving past doc so that onUnload works. |
| if (clearWindowProperties && d->m_jscript) |
| d->m_jscript->clear(); |
| |
| if ( d->m_view ) |
| d->m_view->clear(); |
| |
| // do not drop the document before the jscript and view are cleared, as some destructors |
| // might still try to access the document. |
| d->m_doc = 0; |
| d->m_decoder = 0; |
| |
| d->m_plugins.clear(); |
| |
| d->m_scheduledRedirection = noRedirectionScheduled; |
| d->m_delayRedirect = 0; |
| d->m_redirectURL = QString::null; |
| d->m_redirectReferrer = QString::null; |
| d->m_redirectLockHistory = true; |
| d->m_redirectUserGesture = false; |
| d->m_bHTTPRefresh = false; |
| d->m_bClearing = false; |
| d->m_frameNameId = 1; |
| d->m_bFirstData = true; |
| |
| d->m_bMousePressed = false; |
| |
| if ( !d->m_haveEncoding ) |
| d->m_encoding = QString::null; |
| } |
| |
| bool Frame::openFile() |
| { |
| return true; |
| } |
| |
| DocumentImpl *Frame::document() const |
| { |
| if (d) |
| return d->m_doc.get(); |
| return 0; |
| } |
| |
| void Frame::setDocument(DocumentImpl* newDoc) |
| { |
| if (d) { |
| if (d->m_doc) |
| d->m_doc->detach(); |
| d->m_doc = newDoc; |
| if (newDoc) |
| newDoc->attach(); |
| } |
| } |
| |
| void Frame::receivedFirstData() |
| { |
| begin( d->m_workingURL, d->m_extension->urlArgs().xOffset, d->m_extension->urlArgs().yOffset ); |
| |
| d->m_doc->docLoader()->setCachePolicy(d->m_cachePolicy); |
| d->m_workingURL = KURL(); |
| |
| // When the first data arrives, the metadata has just been made available |
| QString qData; |
| |
| // Support for http-refresh |
| qData = d->m_job->queryMetaData("http-refresh"); |
| if(!qData.isEmpty() && d->m_metaRefreshEnabled) { |
| double delay; |
| int pos = qData.find( ';' ); |
| if ( pos == -1 ) |
| pos = qData.find( ',' ); |
| |
| if( pos == -1 ) |
| { |
| delay = qData.stripWhiteSpace().toDouble(); |
| // We want a new history item if the refresh timeout > 1 second |
| scheduleRedirection( delay, d->m_url.url(), delay <= 1); |
| } |
| else |
| { |
| int end_pos = qData.length(); |
| delay = qData.left(pos).stripWhiteSpace().toDouble(); |
| while ( qData[++pos] == ' ' ); |
| if ( qData.find( "url", pos, false ) == pos ) |
| { |
| pos += 3; |
| while (qData[pos] == ' ' || qData[pos] == '=' ) |
| pos++; |
| if ( qData[pos] == '"' ) |
| { |
| pos++; |
| int index = end_pos-1; |
| while( index > pos ) |
| { |
| if ( qData[index] == '"' ) |
| break; |
| index--; |
| } |
| if ( index > pos ) |
| end_pos = index; |
| } |
| } |
| // We want a new history item if the refresh timeout > 1 second |
| scheduleRedirection( delay, d->m_doc->completeURL( qData.mid( pos, end_pos ) ), delay <= 1); |
| } |
| d->m_bHTTPRefresh = true; |
| } |
| |
| // Support for http last-modified |
| d->m_lastModified = d->m_job->queryMetaData("modified"); |
| } |
| |
| void Frame::slotFinished( KIO::Job * job ) |
| { |
| if (job->error()) |
| { |
| d->m_job = 0L; |
| // TODO: what else ? |
| checkCompleted(); |
| return; |
| } |
| |
| d->m_workingURL = KURL(); |
| d->m_job = 0L; |
| |
| if (d->m_doc->parsing()) |
| end(); // will call completed() |
| } |
| |
| void Frame::childBegin() |
| { |
| // We need to do this when the child is created so as to avoid the parent thining the child |
| // is complete before it has even started loading. |
| // FIXME: do we really still need this? |
| d->m_bComplete = false; |
| } |
| |
| void Frame::begin( const KURL &url, int xOffset, int yOffset ) |
| { |
| if (d->m_workingURL.isEmpty()) |
| createEmptyDocument(); // Creates an empty document if we don't have one already |
| |
| clear(); |
| partClearedInBegin(); |
| |
| d->m_bCleared = false; |
| d->m_bComplete = false; |
| d->m_bLoadEventEmitted = false; |
| d->m_bLoadingMainResource = true; |
| |
| URLArgs args( d->m_extension->urlArgs() ); |
| args.xOffset = xOffset; |
| args.yOffset = yOffset; |
| d->m_extension->setURLArgs( args ); |
| |
| KURL ref(url); |
| ref.setUser(QSTRING_NULL); |
| ref.setPass(QSTRING_NULL); |
| ref.setRef(QSTRING_NULL); |
| d->m_referrer = ref.url(); |
| d->m_url = url; |
| KURL baseurl; |
| |
| // We don't need KDE chained URI handling or window caption setting |
| if (!d->m_url.isEmpty()) |
| baseurl = d->m_url; |
| |
| if (DOMImplementationImpl::isXMLMIMEType(args.serviceType)) |
| d->m_doc = DOMImplementationImpl::instance()->createDocument(d->m_view.get()); |
| else |
| d->m_doc = DOMImplementationImpl::instance()->createHTMLDocument(d->m_view.get()); |
| |
| if (!d->m_doc->attached()) |
| d->m_doc->attach( ); |
| d->m_doc->setURL( d->m_url.url() ); |
| // We prefer m_baseURL over d->m_url because d->m_url changes when we are |
| // about to load a new page. |
| d->m_doc->setBaseURL( baseurl.url() ); |
| if (d->m_decoder) |
| d->m_doc->setDecoder(d->m_decoder.get()); |
| d->m_doc->docLoader()->setShowAnimations( d->m_settings->showAnimations() ); |
| |
| updatePolicyBaseURL(); |
| |
| setAutoloadImages( d->m_settings->autoLoadImages() ); |
| QString userStyleSheet = d->m_settings->userStyleSheet(); |
| |
| if ( !userStyleSheet.isEmpty() ) |
| setUserStyleSheet( KURL( userStyleSheet ) ); |
| |
| restoreDocumentState(); |
| |
| d->m_doc->implicitOpen(); |
| // clear widget |
| if (d->m_view) |
| d->m_view->resizeContents( 0, 0 ); |
| } |
| |
| void Frame::write( const char *str, int len ) |
| { |
| if ( !d->m_decoder ) { |
| d->m_decoder = new Decoder; |
| if (!d->m_encoding.isNull()) |
| d->m_decoder->setEncoding(d->m_encoding.latin1(), |
| d->m_haveEncoding ? Decoder::UserChosenEncoding : Decoder::EncodingFromHTTPHeader); |
| else |
| d->m_decoder->setEncoding(settings()->encoding().latin1(), Decoder::DefaultEncoding); |
| |
| if (d->m_doc) |
| d->m_doc->setDecoder(d->m_decoder.get()); |
| } |
| if ( len == 0 ) |
| return; |
| |
| if ( len == -1 ) |
| len = strlen( str ); |
| |
| QString decoded = d->m_decoder->decode( str, len ); |
| |
| if(decoded.isEmpty()) return; |
| |
| if(d->m_bFirstData) { |
| // determine the parse mode |
| d->m_doc->determineParseMode( decoded ); |
| d->m_bFirstData = false; |
| |
| // ### this is still quite hacky, but should work a lot better than the old solution |
| if(d->m_decoder->visuallyOrdered()) d->m_doc->setVisuallyOrdered(); |
| d->m_doc->recalcStyle( NodeImpl::Force ); |
| } |
| |
| if (Tokenizer* t = d->m_doc->tokenizer()) |
| t->write( decoded, true ); |
| } |
| |
| void Frame::write( const QString &str ) |
| { |
| if ( str.isNull() ) |
| return; |
| |
| if(d->m_bFirstData) { |
| // determine the parse mode |
| d->m_doc->setParseMode( DocumentImpl::Strict ); |
| d->m_bFirstData = false; |
| } |
| Tokenizer* t = d->m_doc->tokenizer(); |
| if(t) |
| t->write( str, true ); |
| } |
| |
| void Frame::end() |
| { |
| d->m_bLoadingMainResource = false; |
| endIfNotLoading(); |
| } |
| |
| void Frame::endIfNotLoading() |
| { |
| if (d->m_bLoadingMainResource) |
| return; |
| |
| // make sure nothing's left in there... |
| if (d->m_decoder) |
| write(d->m_decoder->flush()); |
| if (d->m_doc) |
| d->m_doc->finishParsing(); |
| else |
| // WebKit partially uses WebCore when loading non-HTML docs. In these cases doc==nil, but |
| // WebCore is enough involved that we need to checkCompleted() in order for m_bComplete to |
| // become true. An example is when a subframe is a pure text doc, and that subframe is the |
| // last one to complete. |
| checkCompleted(); |
| } |
| |
| void Frame::stop() |
| { |
| if (d->m_doc) { |
| if (d->m_doc->tokenizer()) |
| d->m_doc->tokenizer()->stopParsing(); |
| d->m_doc->finishParsing(); |
| } else |
| // WebKit partially uses WebCore when loading non-HTML docs. In these cases doc==nil, but |
| // WebCore is enough involved that we need to checkCompleted() in order for m_bComplete to |
| // become true. An example is when a subframe is a pure text doc, and that subframe is the |
| // last one to complete. |
| checkCompleted(); |
| } |
| |
| void Frame::stopAnimations() |
| { |
| if (d->m_doc) |
| d->m_doc->docLoader()->setShowAnimations(KHTMLSettings::KAnimationDisabled); |
| |
| for (Frame* child = tree()->firstChild(); child; child = child->tree()->nextSibling()) |
| child->stopAnimations(); |
| } |
| |
| void Frame::gotoAnchor() |
| { |
| // If our URL has no ref, then we have no place we need to jump to. |
| if (!d->m_url.hasRef()) |
| return; |
| |
| QString ref = d->m_url.encodedHtmlRef(); |
| if (!gotoAnchor(ref)) { |
| // Can't use htmlRef() here because it doesn't know which encoding to use to decode. |
| // Decoding here has to match encoding in completeURL, which means it has to use the |
| // page's encoding rather than UTF-8. |
| if (d->m_decoder) |
| gotoAnchor(KURL::decode_string(ref, d->m_decoder->codec())); |
| } |
| } |
| |
| void Frame::finishedParsing() |
| { |
| RefPtr<Frame> protector(this); |
| checkCompleted(); |
| |
| if (!d->m_view) |
| return; // We are being destroyed by something checkCompleted called. |
| |
| // check if the scrollbars are really needed for the content |
| // if not, remove them, relayout, and repaint |
| |
| d->m_view->restoreScrollBar(); |
| gotoAnchor(); |
| } |
| |
| void Frame::loadDone() |
| { |
| if (d->m_doc) |
| checkCompleted(); |
| } |
| |
| void Frame::checkCompleted() |
| { |
| // Any frame that hasn't completed yet ? |
| for (Frame* child = tree()->firstChild(); child; child = child->tree()->nextSibling()) |
| if (!child->d->m_bComplete) |
| return; |
| |
| // Have we completed before? |
| if (d->m_bComplete) |
| return; |
| |
| // Are we still parsing? |
| if (d->m_doc && d->m_doc->parsing()) |
| return; |
| |
| // Still waiting for images/scripts from the loader ? |
| int requests = 0; |
| if (d->m_doc && d->m_doc->docLoader()) |
| requests = Cache::loader()->numRequests(d->m_doc->docLoader()); |
| |
| if (requests > 0) |
| return; |
| |
| // OK, completed. |
| // Now do what should be done when we are really completed. |
| d->m_bComplete = true; |
| |
| checkEmitLoadEvent(); // if we didn't do it before |
| |
| if (d->m_scheduledRedirection != noRedirectionScheduled) { |
| // Do not start redirection for frames here! That action is |
| // deferred until the parent emits a completed signal. |
| if (!tree()->parent()) |
| startRedirectionTimer(); |
| |
| completed(true); |
| } else { |
| completed(d->m_bPendingChildRedirection); |
| } |
| } |
| |
| void Frame::checkEmitLoadEvent() |
| { |
| if (d->m_bLoadEventEmitted || !d->m_doc || d->m_doc->parsing()) |
| return; |
| |
| for (Frame* child = tree()->firstChild(); child; child = child->tree()->nextSibling()) |
| if (!child->d->m_bComplete) // still got a frame running -> too early |
| return; |
| |
| // All frames completed -> set their domain to the frameset's domain |
| // This must only be done when loading the frameset initially (#22039), |
| // not when following a link in a frame (#44162). |
| if (d->m_doc) { |
| DOMString domain = d->m_doc->domain(); |
| for (Frame* child = tree()->firstChild(); child; child = child->tree()->nextSibling()) |
| if (child->d->m_doc) |
| child->d->m_doc->setDomain(domain); |
| } |
| |
| d->m_bLoadEventEmitted = true; |
| d->m_bUnloadEventEmitted = false; |
| if (d->m_doc) |
| d->m_doc->implicitClose(); |
| } |
| |
| const KHTMLSettings *Frame::settings() const |
| { |
| return d->m_settings; |
| } |
| |
| KURL Frame::baseURL() const |
| { |
| if (!d->m_doc) |
| return KURL(); |
| return d->m_doc->baseURL(); |
| } |
| |
| QString Frame::baseTarget() const |
| { |
| if (!d->m_doc) |
| return QString(); |
| return d->m_doc->baseTarget(); |
| } |
| |
| KURL Frame::completeURL( const QString &url ) |
| { |
| if (!d->m_doc) |
| return url; |
| |
| return KURL(d->m_doc->completeURL(url)); |
| } |
| |
| void Frame::scheduleRedirection( double delay, const QString &url, bool doLockHistory) |
| { |
| if (delay < 0 || delay > INT_MAX / 1000) |
| return; |
| if ( d->m_scheduledRedirection == noRedirectionScheduled || delay <= d->m_delayRedirect ) |
| { |
| d->m_scheduledRedirection = redirectionScheduled; |
| d->m_delayRedirect = delay; |
| d->m_redirectURL = url; |
| d->m_redirectReferrer = QString::null; |
| d->m_redirectLockHistory = doLockHistory; |
| d->m_redirectUserGesture = false; |
| |
| stopRedirectionTimer(); |
| if (d->m_bComplete) |
| startRedirectionTimer(); |
| } |
| } |
| |
| void Frame::scheduleLocationChange(const QString &url, const QString &referrer, bool lockHistory, bool userGesture) |
| { |
| // Handle a location change of a page with no document as a special case. |
| // This may happen when a frame changes the location of another frame. |
| d->m_scheduledRedirection = d->m_doc ? locationChangeScheduled : locationChangeScheduledDuringLoad; |
| |
| // If a redirect was scheduled during a load, then stop the current load. |
| // Otherwise when the current load transitions from a provisional to a |
| // committed state, pending redirects may be cancelled. |
| if (d->m_scheduledRedirection == locationChangeScheduledDuringLoad) { |
| stopLoading(true); |
| } |
| |
| d->m_delayRedirect = 0; |
| d->m_redirectURL = url; |
| d->m_redirectReferrer = referrer; |
| d->m_redirectLockHistory = lockHistory; |
| d->m_redirectUserGesture = userGesture; |
| stopRedirectionTimer(); |
| if (d->m_bComplete) |
| startRedirectionTimer(); |
| } |
| |
| bool Frame::isScheduledLocationChangePending() const |
| { |
| switch (d->m_scheduledRedirection) { |
| case noRedirectionScheduled: |
| case redirectionScheduled: |
| return false; |
| case historyNavigationScheduled: |
| case locationChangeScheduled: |
| case locationChangeScheduledDuringLoad: |
| return true; |
| } |
| return false; |
| } |
| |
| void Frame::scheduleHistoryNavigation( int steps ) |
| { |
| // navigation will always be allowed in the 0 steps case, which is OK because |
| // that's supposed to force a reload. |
| if (!canGoBackOrForward(steps)) { |
| cancelRedirection(); |
| return; |
| } |
| |
| d->m_scheduledRedirection = historyNavigationScheduled; |
| d->m_delayRedirect = 0; |
| d->m_redirectURL = QString::null; |
| d->m_redirectReferrer = QString::null; |
| d->m_scheduledHistoryNavigationSteps = steps; |
| stopRedirectionTimer(); |
| if (d->m_bComplete) |
| startRedirectionTimer(); |
| } |
| |
| void Frame::cancelRedirection(bool cancelWithLoadInProgress) |
| { |
| if (d) { |
| d->m_cancelWithLoadInProgress = cancelWithLoadInProgress; |
| d->m_scheduledRedirection = noRedirectionScheduled; |
| stopRedirectionTimer(); |
| } |
| } |
| |
| void Frame::changeLocation(const QString &URL, const QString &referrer, bool lockHistory, bool userGesture) |
| { |
| if (URL.find("javascript:", 0, false) == 0) { |
| QString script = KURL::decode_string(URL.mid(11)); |
| JSValue* result = executeScript(0, script, userGesture); |
| QString scriptResult; |
| if (getString(result, scriptResult)) { |
| begin(url()); |
| write(scriptResult); |
| end(); |
| } |
| return; |
| } |
| |
| URLArgs args; |
| |
| args.setLockHistory(lockHistory); |
| if (!referrer.isEmpty()) |
| args.metaData().set("referrer", referrer); |
| |
| urlSelected(URL, "_self", args); |
| } |
| |
| void Frame::redirectionTimerFired(Timer<Frame>*) |
| { |
| if (d->m_scheduledRedirection == historyNavigationScheduled) { |
| d->m_scheduledRedirection = noRedirectionScheduled; |
| |
| // Special case for go(0) from a frame -> reload only the frame |
| // go(i!=0) from a frame navigates into the history of the frame only, |
| // in both IE and NS (but not in Mozilla).... we can't easily do that |
| // in Konqueror... |
| if (d->m_scheduledHistoryNavigationSteps == 0) // add && parent() to get only frames, but doesn't matter |
| openURL( url() ); /// ## need args.reload=true? |
| else { |
| if (d->m_extension) { |
| d->m_extension->goBackOrForward(d->m_scheduledHistoryNavigationSteps); |
| } |
| } |
| return; |
| } |
| |
| QString URL = d->m_redirectURL; |
| QString referrer = d->m_redirectReferrer; |
| bool lockHistory = d->m_redirectLockHistory; |
| bool userGesture = d->m_redirectUserGesture; |
| |
| d->m_scheduledRedirection = noRedirectionScheduled; |
| d->m_delayRedirect = 0; |
| d->m_redirectURL = QString::null; |
| d->m_redirectReferrer = QString::null; |
| |
| changeLocation(URL, referrer, lockHistory, userGesture); |
| } |
| |
| void Frame::slotRedirection(KIO::Job*, const KURL& url) |
| { |
| d->m_workingURL = url; |
| } |
| |
| QString Frame::encoding() const |
| { |
| if(d->m_haveEncoding && !d->m_encoding.isEmpty()) |
| return d->m_encoding; |
| |
| if(d->m_decoder && d->m_decoder->encoding()) |
| return QString(d->m_decoder->encoding()); |
| |
| return(settings()->encoding()); |
| } |
| |
| void Frame::setUserStyleSheet(const KURL& url) |
| { |
| delete d->m_userStyleSheetLoader; |
| d->m_userStyleSheetLoader = 0; |
| if (d->m_doc && d->m_doc->docLoader()) |
| d->m_userStyleSheetLoader = new UserStyleSheetLoader(this, url.url(), d->m_doc->docLoader()); |
| } |
| |
| void Frame::setUserStyleSheet(const QString& styleSheet) |
| { |
| delete d->m_userStyleSheetLoader; |
| d->m_userStyleSheetLoader = 0; |
| if (d->m_doc) |
| d->m_doc->setUserStyleSheet(styleSheet); |
| } |
| |
| bool Frame::gotoAnchor( const QString &name ) |
| { |
| if (!d->m_doc) |
| return false; |
| |
| NodeImpl *n = d->m_doc->getElementById(name); |
| if (!n) { |
| HTMLCollectionImpl *anchors = |
| new HTMLCollectionImpl(d->m_doc.get(), HTMLCollectionImpl::DOC_ANCHORS); |
| anchors->ref(); |
| n = anchors->namedItem(name, !d->m_doc->inCompatMode()); |
| anchors->deref(); |
| } |
| |
| d->m_doc->setCSSTarget(n); // Setting to null will clear the current target. |
| |
| // Implement the rule that "" and "top" both mean top of page as in other browsers. |
| if (!n && !(name.isEmpty() || name.lower() == "top")) |
| return false; |
| |
| // We need to update the layout before scrolling, otherwise we could |
| // really mess things up if an anchor scroll comes at a bad moment. |
| if ( d->m_doc ) { |
| d->m_doc->updateRendering(); |
| // Only do a layout if changes have occurred that make it necessary. |
| if ( d->m_view && d->m_doc->renderer() && d->m_doc->renderer()->needsLayout() ) { |
| d->m_view->layout(); |
| } |
| } |
| |
| // Scroll nested layers and frames to reveal the anchor. |
| RenderObject *renderer; |
| IntRect rect; |
| if (n) { |
| renderer = n->renderer(); |
| rect = n->getRect(); |
| } else { |
| // If there's no node, we should scroll to the top of the document. |
| renderer = d->m_doc->renderer(); |
| rect = IntRect(); |
| } |
| |
| if (renderer) { |
| // Align to the top and to the closest side (this matches other browsers). |
| renderer->enclosingLayer()->scrollRectToVisible(rect, RenderLayer::gAlignToEdgeIfNeeded, RenderLayer::gAlignTopAlways); |
| } |
| |
| return true; |
| } |
| |
| void Frame::setStandardFont( const QString &name ) |
| { |
| d->m_settings->setStdFontName(name); |
| } |
| |
| void Frame::setFixedFont( const QString &name ) |
| { |
| d->m_settings->setFixedFontName(name); |
| } |
| |
| QString Frame::selectedText() const |
| { |
| return plainText(selection().toRange().get()); |
| } |
| |
| bool Frame::hasSelection() const |
| { |
| return d->m_selection.isCaretOrRange(); |
| } |
| |
| SelectionController &Frame::selection() const |
| { |
| return d->m_selection; |
| } |
| |
| ETextGranularity Frame::selectionGranularity() const |
| { |
| return d->m_selectionGranularity; |
| } |
| |
| void Frame::setSelectionGranularity(ETextGranularity granularity) const |
| { |
| d->m_selectionGranularity = granularity; |
| } |
| |
| const SelectionController &Frame::dragCaret() const |
| { |
| return d->m_dragCaret; |
| } |
| |
| const Selection& Frame::mark() const |
| { |
| return d->m_mark; |
| } |
| |
| void Frame::setMark(const Selection& s) |
| { |
| d->m_mark = s; |
| } |
| |
| void Frame::setSelection(const SelectionController &s, bool closeTyping, bool keepTypingStyle) |
| { |
| if (d->m_selection == s) { |
| return; |
| } |
| |
| clearCaretRectIfNeeded(); |
| |
| SelectionController oldSelection = d->m_selection; |
| |
| d->m_selection = s; |
| if (!s.isNone()) |
| setFocusNodeIfNeeded(); |
| |
| selectionLayoutChanged(); |
| |
| // Always clear the x position used for vertical arrow navigation. |
| // It will be restored by the vertical arrow navigation code if necessary. |
| d->m_xPosForVerticalArrowNavigation = NoXPosForVerticalArrowNavigation; |
| |
| if (closeTyping) |
| TypingCommand::closeTyping(lastEditCommand()); |
| |
| if (!keepTypingStyle) |
| clearTypingStyle(); |
| |
| respondToChangedSelection(oldSelection, closeTyping); |
| } |
| |
| void Frame::setDragCaret(const SelectionController &dragCaret) |
| { |
| if (d->m_dragCaret != dragCaret) { |
| d->m_dragCaret.needsCaretRepaint(); |
| d->m_dragCaret = dragCaret; |
| d->m_dragCaret.needsCaretRepaint(); |
| } |
| } |
| |
| void Frame::invalidateSelection() |
| { |
| clearCaretRectIfNeeded(); |
| d->m_selection.setNeedsLayout(); |
| selectionLayoutChanged(); |
| } |
| |
| void Frame::setCaretVisible(bool flag) |
| { |
| if (d->m_caretVisible == flag) |
| return; |
| clearCaretRectIfNeeded(); |
| if (flag) |
| setFocusNodeIfNeeded(); |
| d->m_caretVisible = flag; |
| selectionLayoutChanged(); |
| } |
| |
| |
| void Frame::clearCaretRectIfNeeded() |
| { |
| if (d->m_caretPaint) { |
| d->m_caretPaint = false; |
| d->m_selection.needsCaretRepaint(); |
| } |
| } |
| |
| // Helper function that tells whether a particular node is an element that has an entire |
| // Frame and FrameView, a <frame>, <iframe>, or <object>. |
| static bool isFrameElement(const NodeImpl *n) |
| { |
| if (!n) |
| return false; |
| RenderObject *renderer = n->renderer(); |
| if (!renderer || !renderer->isWidget()) |
| return false; |
| Widget *widget = static_cast<RenderWidget *>(renderer)->widget(); |
| return widget && widget->isFrameView(); |
| } |
| |
| void Frame::setFocusNodeIfNeeded() |
| { |
| if (!document() || d->m_selection.isNone() || !d->m_isFocused) |
| return; |
| |
| NodeImpl *startNode = d->m_selection.start().node(); |
| NodeImpl *target = startNode ? startNode->rootEditableElement() : 0; |
| |
| if (target) { |
| RenderObject* renderer = target->renderer(); |
| |
| // Walk up the render tree to search for a node to focus. |
| // Walking up the DOM tree wouldn't work for shadow trees, like those behind the engine-based text fields. |
| while (renderer) { |
| // We don't want to set focus on a subframe when selecting in a parent frame, |
| // so add the !isFrameElement check here. There's probably a better way to make this |
| // work in the long term, but this is the safest fix at this time. |
| if (target && target->isMouseFocusable() && !isFrameElement(target)) { |
| document()->setFocusNode(target); |
| return; |
| } |
| renderer = renderer->parent(); |
| if (renderer) |
| target = renderer->element(); |
| } |
| document()->setFocusNode(0); |
| } |
| } |
| |
| void Frame::selectionLayoutChanged() |
| { |
| // kill any caret blink timer now running |
| d->m_caretBlinkTimer.stop(); |
| |
| // see if a new caret blink timer needs to be started |
| if (d->m_caretVisible && d->m_caretBlinks && |
| d->m_selection.isCaret() && d->m_selection.start().node()->isContentEditable()) { |
| d->m_caretBlinkTimer.startRepeating(caretBlinkFrequency); |
| d->m_caretPaint = true; |
| d->m_selection.needsCaretRepaint(); |
| } |
| |
| if (d->m_doc) |
| d->m_doc->updateSelection(); |
| } |
| |
| void Frame::setXPosForVerticalArrowNavigation(int x) |
| { |
| d->m_xPosForVerticalArrowNavigation = x; |
| } |
| |
| int Frame::xPosForVerticalArrowNavigation() const |
| { |
| return d->m_xPosForVerticalArrowNavigation; |
| } |
| |
| void Frame::caretBlinkTimerFired(Timer<Frame>*) |
| { |
| // Might be better to turn the timer off during some of these circumstances |
| // and assert rather then letting the timer fire and do nothing here. |
| // Could do that in selectionLayoutChanged. |
| |
| if (!d->m_caretVisible) |
| return; |
| if (!d->m_caretBlinks) |
| return; |
| if (!d->m_selection.isCaret()) |
| return; |
| bool caretPaint = d->m_caretPaint; |
| if (d->m_bMousePressed && caretPaint) |
| return; |
| d->m_caretPaint = !caretPaint; |
| d->m_selection.needsCaretRepaint(); |
| } |
| |
| void Frame::paintCaret(QPainter *p, const IntRect &rect) const |
| { |
| if (d->m_caretPaint) |
| d->m_selection.paintCaret(p, rect); |
| } |
| |
| void Frame::paintDragCaret(QPainter *p, const IntRect &rect) const |
| { |
| d->m_dragCaret.paintCaret(p, rect); |
| } |
| |
| void Frame::urlSelected(const QString &url, const QString& _target, const URLArgs& args ) |
| { |
| QString target = _target; |
| if (target.isEmpty() && d->m_doc) |
| target = d->m_doc->baseTarget(); |
| |
| if (url.startsWith("javascript:", false)) { |
| executeScript(0, KURL::decode_string(url.mid(11)), true); |
| return; |
| } |
| |
| KURL cURL = completeURL(url); |
| if (!cURL.isValid()) |
| // ### ERROR HANDLING |
| return; |
| |
| URLArgs argsCopy = args; |
| argsCopy.frameName = target; |
| |
| if (d->m_bHTTPRefresh) { |
| d->m_bHTTPRefresh = false; |
| argsCopy.metaData().set("cache", "refresh"); |
| } |
| |
| if (!d->m_referrer.isEmpty()) |
| argsCopy.metaData().set("referrer", d->m_referrer); |
| |
| urlSelected(cURL, argsCopy); |
| } |
| |
| DOMString Frame::requestFrameName() |
| { |
| return generateFrameName(); |
| } |
| |
| bool Frame::requestFrame(RenderPart* renderer, const QString& _url, const QString& frameName) |
| { |
| // Support for <frame src="javascript:string"> |
| KURL scriptURL; |
| KURL url; |
| if (_url.startsWith("javascript:", false)) { |
| scriptURL = _url; |
| url = "about:blank"; |
| } else |
| url = completeURL(_url); |
| |
| Frame* frame = childFrameNamed(frameName); |
| if (frame) { |
| URLArgs args; |
| args.metaData().set("referrer", d->m_referrer); |
| args.reload = (d->m_cachePolicy == KIO::CC_Reload) || (d->m_cachePolicy == KIO::CC_Refresh); |
| frame->openURLRequest(url, args); |
| } else |
| frame = loadSubframe(renderer, url, frameName, d->m_referrer); |
| |
| if (!frame) |
| return false; |
| |
| if (!scriptURL.isEmpty()) |
| frame->replaceContentsWithScriptResult(scriptURL); |
| |
| return true; |
| } |
| |
| bool Frame::requestObject(RenderPart* renderer, const QString& url, const QString& frameName, |
| const QString& mimeType, const QStringList& paramNames, const QStringList& paramValues) |
| { |
| KURL completedURL; |
| if (!url.isEmpty()) |
| completedURL = completeURL(url); |
| |
| if (url.isEmpty() && mimeType.isEmpty()) |
| return true; |
| |
| bool useFallback; |
| if (shouldUsePlugin(renderer->element(), completedURL, mimeType, renderer->hasFallbackContent(), useFallback)) |
| return loadPlugin(renderer, completedURL, mimeType, paramNames, paramValues, useFallback); |
| |
| // FIXME: ok to always make a new one? when does the old frame get removed? |
| return loadSubframe(renderer, completedURL, frameName, d->m_referrer); |
| } |
| |
| bool Frame::shouldUsePlugin(NodeImpl* element, const KURL& url, const QString& mimeType, bool hasFallback, bool& useFallback) |
| { |
| useFallback = false; |
| ObjectContentType objectType = objectContentType(url, mimeType); |
| |
| // if an object's content can't be handled and it has no fallback, let |
| // it be handled as a plugin to show the broken plugin icon |
| if (objectType == ObjectContentNone && hasFallback) |
| useFallback = true; |
| |
| return objectType == ObjectContentNone || objectType == ObjectContentPlugin; |
| } |
| |
| |
| bool Frame::loadPlugin(RenderPart *renderer, const KURL &url, const QString &mimeType, |
| const QStringList& paramNames, const QStringList& paramValues, bool useFallback) |
| { |
| if (useFallback) { |
| checkEmitLoadEvent(); |
| return false; |
| } |
| |
| Plugin* plugin = createPlugin(url, paramNames, paramValues, mimeType); |
| if (!plugin) { |
| checkEmitLoadEvent(); |
| return false; |
| } |
| d->m_plugins.append(plugin); |
| |
| if (renderer && plugin->view()) |
| renderer->setWidget(plugin->view()); |
| |
| checkEmitLoadEvent(); |
| |
| return true; |
| } |
| |
| Frame* Frame::loadSubframe(RenderPart* renderer, const KURL& url, const QString& name, const DOMString& referrer) |
| { |
| Frame* frame = createFrame(url, name, renderer, referrer); |
| if (!frame) { |
| checkEmitLoadEvent(); |
| return 0; |
| } |
| |
| frame->childBegin(); |
| |
| if (renderer && frame->view()) |
| renderer->setWidget(frame->view()); |
| |
| checkEmitLoadEvent(); |
| |
| // In these cases, the synchronous load would have finished |
| // before we could connect the signals, so make sure to send the |
| // completed() signal for the child by hand |
| // FIXME: In this case the Frame will have finished loading before |
| // it's being added to the child list. It would be a good idea to |
| // create the child first, then invoke the loader separately. |
| if (url.isEmpty() || url == "about:blank") { |
| frame->completed(false); |
| frame->checkCompleted(); |
| } |
| |
| return frame; |
| } |
| |
| void Frame::submitFormAgain() |
| { |
| FramePrivate::SubmitForm* form = d->m_submitForm; |
| d->m_submitForm = 0; |
| if (d->m_doc && !d->m_doc->parsing() && form) |
| submitForm(form->submitAction, form->submitUrl, form->submitFormData, |
| form->target, form->submitContentType, form->submitBoundary); |
| delete form; |
| } |
| |
| void Frame::submitForm( const char *action, const QString &url, const FormData &formData, const QString &_target, const QString& contentType, const QString& boundary ) |
| { |
| KURL u = completeURL( url ); |
| |
| if (!u.isValid()) |
| // ### ERROR HANDLING! |
| return; |
| |
| QString urlstring = u.url(); |
| if (urlstring.startsWith("javascript:", false)) { |
| urlstring = KURL::decode_string(urlstring); |
| d->m_executingJavaScriptFormAction = true; |
| executeScript(0, urlstring.mid(11)); |
| d->m_executingJavaScriptFormAction = false; |
| return; |
| } |
| |
| URLArgs args; |
| |
| if (!d->m_referrer.isEmpty()) |
| args.metaData().set("referrer", d->m_referrer); |
| |
| args.frameName = _target.isEmpty() ? d->m_doc->baseTarget() : _target ; |
| |
| // Handle mailto: forms |
| if (u.protocol() == "mailto") { |
| // 1) Check for attach= and strip it |
| QString q = u.query().mid(1); |
| QStringList nvps = QStringList::split("&", q); |
| bool triedToAttach = false; |
| |
| for (QStringList::Iterator nvp = nvps.begin(); nvp != nvps.end(); ++nvp) { |
| QStringList pair = QStringList::split("=", *nvp); |
| if (pair.count() >= 2) { |
| if (pair.first().lower() == "attach") { |
| nvp = nvps.remove(nvp); |
| triedToAttach = true; |
| } |
| } |
| } |
| |
| |
| // 2) Append body= |
| QString bodyEnc; |
| if (contentType.lower() == "multipart/form-data") { |
| // FIXME: is this correct? I suspect not |
| bodyEnc = KURL::encode_string(formData.flattenToString()); |
| } else if (contentType.lower() == "text/plain") { |
| // Convention seems to be to decode, and s/&/\n/ |
| QString tmpbody = formData.flattenToString(); |
| tmpbody.replace('&', '\n'); |
| tmpbody.replace('+', ' '); |
| tmpbody = KURL::decode_string(tmpbody); // Decode the rest of it |
| bodyEnc = KURL::encode_string(tmpbody); // Recode for the URL |
| } else { |
| bodyEnc = KURL::encode_string(formData.flattenToString()); |
| } |
| |
| nvps.append(QString("body=%1").arg(bodyEnc)); |
| q = nvps.join("&"); |
| u.setQuery(q); |
| } |
| |
| if ( strcmp( action, "get" ) == 0 ) { |
| if (u.protocol() != "mailto") |
| u.setQuery( formData.flattenToString() ); |
| args.setDoPost( false ); |
| } |
| else { |
| args.postData = formData; |
| args.setDoPost( true ); |
| |
| // construct some user headers if necessary |
| if (contentType.isNull() || contentType == "application/x-www-form-urlencoded") |
| args.setContentType( "Content-Type: application/x-www-form-urlencoded" ); |
| else // contentType must be "multipart/form-data" |
| args.setContentType( "Content-Type: " + contentType + "; boundary=" + boundary ); |
| } |
| |
| if ( d->m_doc->parsing() || d->m_runningScripts > 0 ) { |
| if(d->m_submitForm) |
| return; |
| d->m_submitForm = new FramePrivate::SubmitForm; |
| d->m_submitForm->submitAction = action; |
| d->m_submitForm->submitUrl = url; |
| d->m_submitForm->submitFormData = formData; |
| d->m_submitForm->target = _target; |
| d->m_submitForm->submitContentType = contentType; |
| d->m_submitForm->submitBoundary = boundary; |
| } |
| else |
| submitForm(u, args); |
| } |
| |
| |
| void Frame::parentCompleted() |
| { |
| if (d->m_scheduledRedirection != noRedirectionScheduled && !d->m_redirectionTimer.isActive()) |
| startRedirectionTimer(); |
| } |
| |
| void Frame::childCompleted(bool complete) |
| { |
| if (complete && !tree()->parent()) |
| d->m_bPendingChildRedirection = true; |
| checkCompleted(); |
| } |
| |
| Frame* Frame::findFrame(const QString& f) |
| { |
| // FIXME: this only finds child frames, is it ever appropriate to use this? |
| // ### http://www.w3.org/TR/html4/appendix/notes.html#notes-frames |
| for (Frame* child = tree()->firstChild(); child; child = child->tree()->nextSibling()) |
| if (child->tree()->name() == f) |
| return child; |
| return 0; |
| } |
| |
| bool Frame::frameExists(const QString& frameName) |
| { |
| return findFrame(frameName); |
| } |
| |
| int Frame::zoomFactor() const |
| { |
| return d->m_zoomFactor; |
| } |
| |
| void Frame::setZoomFactor(int percent) |
| { |
| if (d->m_zoomFactor == percent) |
| return; |
| |
| d->m_zoomFactor = percent; |
| |
| if (d->m_doc) |
| d->m_doc->recalcStyle(NodeImpl::Force); |
| |
| for (Frame* child = tree()->firstChild(); child; child = child->tree()->nextSibling()) |
| child->setZoomFactor(d->m_zoomFactor); |
| |
| if (d->m_doc && d->m_doc->renderer() && d->m_doc->renderer()->needsLayout()) |
| view()->layout(); |
| } |
| |
| void Frame::setJSStatusBarText(const String& text) |
| { |
| d->m_kjsStatusBarText = text; |
| setStatusBarText(d->m_kjsStatusBarText); |
| } |
| |
| void Frame::setJSDefaultStatusBarText(const String& text) |
| { |
| d->m_kjsDefaultStatusBarText = text; |
| setStatusBarText(d->m_kjsDefaultStatusBarText); |
| } |
| |
| String Frame::jsStatusBarText() const |
| { |
| return d->m_kjsStatusBarText; |
| } |
| |
| String Frame::jsDefaultStatusBarText() const |
| { |
| return d->m_kjsDefaultStatusBarText; |
| } |
| |
| QString Frame::referrer() const |
| { |
| return d->m_referrer; |
| } |
| |
| QString Frame::lastModified() const |
| { |
| return d->m_lastModified; |
| } |
| |
| |
| void Frame::reparseConfiguration() |
| { |
| setAutoloadImages( d->m_settings->autoLoadImages() ); |
| if (d->m_doc) |
| d->m_doc->docLoader()->setShowAnimations( d->m_settings->showAnimations() ); |
| |
| d->m_bJScriptEnabled = d->m_settings->isJavaScriptEnabled(d->m_url.host()); |
| d->m_bJavaEnabled = d->m_settings->isJavaEnabled(d->m_url.host()); |
| d->m_bPluginsEnabled = d->m_settings->isPluginsEnabled(d->m_url.host()); |
| |
| QString userStyleSheet = d->m_settings->userStyleSheet(); |
| if ( !userStyleSheet.isEmpty() ) |
| setUserStyleSheet( KURL( userStyleSheet ) ); |
| else |
| setUserStyleSheet( QString() ); |
| |
| if(d->m_doc) d->m_doc->updateStyleSelector(); |
| } |
| |
| QStringList Frame::frameNames() const |
| { |
| QStringList res; |
| |
| for (Frame* child = tree()->firstChild(); child; child = child->tree()->nextSibling()) |
| res += child->tree()->name().qstring(); |
| |
| return res; |
| } |
| |
| QPtrList<Frame> Frame::frames() const |
| { |
| QPtrList<Frame> res; |
| |
| for (Frame* child = tree()->firstChild(); child; child = child->tree()->nextSibling()) |
| res.append(child); |
| |
| return res; |
| } |
| |
| Frame* Frame::childFrameNamed(const QString& name) const |
| { |
| for (Frame* child = tree()->firstChild(); child; child = child->tree()->nextSibling()) |
| if (child->tree()->name() == name) |
| return child; |
| return 0; |
| } |
| |
| bool Frame::shouldDragAutoNode(NodeImpl *node, int x, int y) const |
| { |
| // No KDE impl yet |
| return false; |
| } |
| |
| bool Frame::isPointInsideSelection(int x, int y) |
| { |
| // Treat a collapsed selection like no selection. |
| if (!d->m_selection.isRange()) |
| return false; |
| if (!document()->renderer()) |
| return false; |
| |
| RenderObject::NodeInfo nodeInfo(true, true); |
| document()->renderer()->layer()->hitTest(nodeInfo, x, y); |
| NodeImpl *innerNode = nodeInfo.innerNode(); |
| if (!innerNode || !innerNode->renderer()) |
| return false; |
| |
| Position pos(innerNode->renderer()->positionForCoordinates(x, y).deepEquivalent()); |
| if (pos.isNull()) |
| return false; |
| |
| NodeImpl *n = d->m_selection.start().node(); |
| while (n) { |
| if (n == pos.node()) { |
| if ((n == d->m_selection.start().node() && pos.offset() < d->m_selection.start().offset()) || |
| (n == d->m_selection.end().node() && pos.offset() > d->m_selection.end().offset())) { |
| return false; |
| } |
| return true; |
| } |
| if (n == d->m_selection.end().node()) |
| break; |
| n = n->traverseNextNode(); |
| } |
| |
| return false; |
| } |
| |
| void Frame::selectClosestWordFromMouseEvent(MouseEvent *mouse, NodeImpl *innerNode, int x, int y) |
| { |
| SelectionController selection; |
| |
| if (innerNode && innerNode->renderer() && mouseDownMayStartSelect() && innerNode->renderer()->shouldSelect()) { |
| VisiblePosition pos(innerNode->renderer()->positionForCoordinates(x, y)); |
| if (pos.isNotNull()) { |
| selection.moveTo(pos); |
| selection.expandUsingGranularity(WORD); |
| } |
| } |
| |
| if (selection.isRange()) { |
| d->m_selectionGranularity = WORD; |
| d->m_beganSelectingText = true; |
| } |
| |
| if (shouldChangeSelection(selection)) |
| setSelection(selection); |
| } |
| |
| void Frame::handleMousePressEventDoubleClick(MouseEventWithHitTestResults* event) |
| { |
| if (event->event()->button() == LeftButton) { |
| if (selection().isRange()) |
| // A double-click when range is already selected |
| // should not change the selection. So, do not call |
| // selectClosestWordFromMouseEvent, but do set |
| // m_beganSelectingText to prevent khtmlMouseReleaseEvent |
| // from setting caret selection. |
| d->m_beganSelectingText = true; |
| else { |
| int x, y; |
| view()->viewportToContents(event->event()->x(), event->event()->y(), x, y); |
| selectClosestWordFromMouseEvent(event->event(), event->innerNode(), x, y); |
| } |
| } |
| } |
| |
| void Frame::handleMousePressEventTripleClick(MouseEventWithHitTestResults* event) |
| { |
| MouseEvent *mouse = event->event(); |
| NodeImpl *innerNode = event->innerNode(); |
| |
| if (mouse->button() == LeftButton && innerNode && innerNode->renderer() && |
| mouseDownMayStartSelect() && innerNode->renderer()->shouldSelect()) { |
| SelectionController selection; |
| int x, y; |
| view()->viewportToContents(event->event()->x(), event->event()->y(), x, y); |
| VisiblePosition pos(innerNode->renderer()->positionForCoordinates(x, y)); |
| if (pos.isNotNull()) { |
| selection.moveTo(pos); |
| selection.expandUsingGranularity(PARAGRAPH); |
| } |
| if (selection.isRange()) { |
| d->m_selectionGranularity = PARAGRAPH; |
| d->m_beganSelectingText = true; |
| } |
| |
| if (shouldChangeSelection(selection)) |
| setSelection(selection); |
| } |
| } |
| |
| void Frame::handleMousePressEventSingleClick(MouseEventWithHitTestResults* event) |
| { |
| MouseEvent *mouse = event->event(); |
| NodeImpl *innerNode = event->innerNode(); |
| |
| if (mouse->button() == LeftButton) { |
| if (innerNode && innerNode->renderer() && |
| mouseDownMayStartSelect() && innerNode->renderer()->shouldSelect()) { |
| SelectionController sel; |
| |
| // Extend the selection if the Shift key is down, unless the click is in a link. |
| bool extendSelection = mouse->shiftKey() && event->url().isNull(); |
| |
| // Don't restart the selection when the mouse is pressed on an |
| // existing selection so we can allow for text dragging. |
| int x, y; |
| view()->viewportToContents(event->event()->x(), event->event()->y(), x, y); |
| if (!extendSelection && isPointInsideSelection(x, y)) |
| return; |
| |
| VisiblePosition visiblePos(innerNode->renderer()->positionForCoordinates(x, y)); |
| if (visiblePos.isNull()) |
| visiblePos = VisiblePosition(innerNode, innerNode->caretMinOffset(), DOWNSTREAM); |
| Position pos = visiblePos.deepEquivalent(); |
| |
| sel = selection(); |
| if (extendSelection && sel.isCaretOrRange()) { |
| sel.clearModifyBias(); |
| |
| // See <rdar://problem/3668157> REGRESSION (Mail): shift-click deselects when selection |
| // was created right-to-left |
| Position start = sel.start(); |
| short before = RangeImpl::compareBoundaryPoints(pos.node(), pos.offset(), start.node(), start.offset()); |
| if (before <= 0) { |
| sel.setBaseAndExtent(pos.node(), pos.offset(), sel.end().node(), sel.end().offset()); |
| } else { |
| sel.setBaseAndExtent(start.node(), start.offset(), pos.node(), pos.offset()); |
| } |
| |
| if (d->m_selectionGranularity != CHARACTER) { |
| sel.expandUsingGranularity(d->m_selectionGranularity); |
| } |
| d->m_beganSelectingText = true; |
| } else { |
| sel = SelectionController(visiblePos); |
| d->m_selectionGranularity = CHARACTER; |
| } |
| |
| if (shouldChangeSelection(sel)) |
| setSelection(sel); |
| } |
| } |
| } |
| |
| void Frame::khtmlMousePressEvent(MouseEventWithHitTestResults* event) |
| { |
| DOMString url = event->url(); |
| MouseEvent *mouse = event->event(); |
| NodeImpl *innerNode = event->innerNode(); |
| |
| d->m_mousePressNode = innerNode; |
| d->m_dragStartPos = mouse->pos(); |
| |
| if (!event->url().isNull()) { |
| d->m_strSelectedURL = d->m_strSelectedURLTarget = QString::null; |
| } else { |
| d->m_strSelectedURL = event->url().qstring(); |
| d->m_strSelectedURLTarget = event->target().qstring(); |
| } |
| |
| if (mouse->button() == LeftButton || mouse->button() == MiddleButton) { |
| d->m_bMousePressed = true; |
| d->m_beganSelectingText = false; |
| |
| if (mouse->clickCount() == 2) { |
| handleMousePressEventDoubleClick(event); |
| return; |
| } |
| if (mouse->clickCount() >= 3) { |
| handleMousePressEventTripleClick(event); |
| return; |
| } |
| handleMousePressEventSingleClick(event); |
| } |
| } |
| |
| void Frame::handleMouseMoveEventSelection(MouseEventWithHitTestResults* event) |
| { |
| // Mouse not pressed. Do nothing. |
| if (!d->m_bMousePressed) |
| return; |
| |
| MouseEvent *mouse = event->event(); |
| NodeImpl *innerNode = event->innerNode(); |
| |
| if (mouse->button() != 0 || !innerNode || !innerNode->renderer() || !mouseDownMayStartSelect() || !innerNode->renderer()->shouldSelect()) |
| return; |
| |
| // handle making selection |
| int x, y; |
| view()->viewportToContents(event->event()->x(), event->event()->y(), x, y); |
| VisiblePosition pos(innerNode->renderer()->positionForCoordinates(x, y)); |
| |
| // Don't modify the selection if we're not on a node. |
| if (pos.isNull()) |
| return; |
| |
| // Restart the selection if this is the first mouse move. This work is usually |
| // done in khtmlMousePressEvent, but not if the mouse press was on an existing selection. |
| SelectionController sel = selection(); |
| sel.clearModifyBias(); |
| |
| if (!d->m_beganSelectingText) { |
| d->m_beganSelectingText = true; |
| sel.moveTo(pos); |
| } |
| |
| sel.setExtent(pos); |
| if (d->m_selectionGranularity != CHARACTER) |
| sel.expandUsingGranularity(d->m_selectionGranularity); |
| |
| if (shouldChangeSelection(sel)) |
| setSelection(sel); |
| } |
| |
| void Frame::khtmlMouseMoveEvent(MouseEventWithHitTestResults* event) |
| { |
| handleMouseMoveEventSelection(event); |
| } |
| |
| void Frame::khtmlMouseReleaseEvent(MouseEventWithHitTestResults* event) |
| { |
| // Used to prevent mouseMoveEvent from initiating a drag before |
| // the mouse is pressed again. |
| d->m_bMousePressed = false; |
| |
| // Clear the selection if the mouse didn't move after the last mouse press. |
| // We do this so when clicking on the selection, the selection goes away. |
| // However, if we are editing, place the caret. |
| if (mouseDownMayStartSelect() && !d->m_beganSelectingText |
| && d->m_dragStartPos == event->event()->pos() |
| && d->m_selection.isRange()) { |
| SelectionController selection; |
| NodeImpl *node = event->innerNode(); |
| if (node && node->isContentEditable() && node->renderer()) { |
| int x, y; |
| view()->viewportToContents(event->event()->x(), event->event()->y(), x, y); |
| VisiblePosition pos = node->renderer()->positionForCoordinates(x, y); |
| selection.moveTo(pos); |
| } |
| if (shouldChangeSelection(selection)) |
| setSelection(selection); |
| } |
| selectFrameElementInParentIfFullySelected(); |
| } |
| |
| void Frame::selectAll() |
| { |
| if (!d->m_doc) |
| return; |
| |
| NodeImpl *startNode = d->m_selection.start().node(); |
| NodeImpl *root = startNode && startNode->isContentEditable() ? startNode->rootEditableElement() : d->m_doc->documentElement(); |
| |
| selectContentsOfNode(root); |
| selectFrameElementInParentIfFullySelected(); |
| } |
| |
| bool Frame::selectContentsOfNode(NodeImpl* node) |
| { |
| SelectionController sel = SelectionController(Position(node, 0), Position(node, maxDeepOffset(node)), DOWNSTREAM); |
| if (shouldChangeSelection(sel)) { |
| setSelection(sel); |
| return true; |
| } |
| return false; |
| } |
| |
| bool Frame::shouldChangeSelection(const SelectionController &newselection) const |
| { |
| return shouldChangeSelection(d->m_selection, newselection, newselection.affinity(), false); |
| } |
| |
| bool Frame::shouldBeginEditing(const RangeImpl *range) const |
| { |
| return true; |
| } |
| |
| bool Frame::shouldEndEditing(const RangeImpl *range) const |
| { |
| return true; |
| } |
| |
| bool Frame::isContentEditable() const |
| { |
| if (!d->m_doc) |
| return false; |
| return d->m_doc->inDesignMode(); |
| } |
| |
| EditCommandPtr Frame::lastEditCommand() |
| { |
| return d->m_lastEditCommand; |
| } |
| |
| void Frame::appliedEditing(EditCommandPtr &cmd) |
| { |
| SelectionController sel(cmd.endingSelection()); |
| if (shouldChangeSelection(sel)) { |
| setSelection(sel, false); |
| } |
| |
| // Now set the typing style from the command. Clear it when done. |
| // This helps make the case work where you completely delete a piece |
| // of styled text and then type a character immediately after. |
| // That new character needs to take on the style of the just-deleted text. |
| // FIXME: Improve typing style. |
| // See this bug: <rdar://problem/3769899> Implementation of typing style needs improvement |
| if (cmd.typingStyle()) { |
| setTypingStyle(cmd.typingStyle()); |
| cmd.setTypingStyle(0); |
| } |
| |
| // Command will be equal to last edit command only in the case of typing |
| if (d->m_lastEditCommand == cmd) { |
| assert(cmd.isTypingCommand()); |
| } |
| else { |
| // Only register a new undo command if the command passed in is |
| // different from the last command |
| registerCommandForUndo(cmd); |
| d->m_lastEditCommand = cmd; |
| } |
| respondToChangedContents(); |
| } |
| |
| void Frame::unappliedEditing(EditCommandPtr &cmd) |
| { |
| SelectionController sel(cmd.startingSelection()); |
| if (shouldChangeSelection(sel)) { |
| setSelection(sel, true); |
| } |
| registerCommandForRedo(cmd); |
| respondToChangedContents(); |
| d->m_lastEditCommand = EditCommandPtr::emptyCommand(); |
| } |
| |
| void Frame::reappliedEditing(EditCommandPtr &cmd) |
| { |
| SelectionController sel(cmd.endingSelection()); |
| if (shouldChangeSelection(sel)) { |
| setSelection(sel, true); |
| } |
| registerCommandForUndo(cmd); |
| respondToChangedContents(); |
| d->m_lastEditCommand = EditCommandPtr::emptyCommand(); |
| } |
| |
| CSSMutableStyleDeclarationImpl *Frame::typingStyle() const |
| { |
| return d->m_typingStyle.get(); |
| } |
| |
| void Frame::setTypingStyle(CSSMutableStyleDeclarationImpl *style) |
| { |
| d->m_typingStyle = style; |
| } |
| |
| void Frame::clearTypingStyle() |
| { |
| d->m_typingStyle = 0; |
| } |
| |
| JSValue* Frame::executeScript(const QString& filename, int baseLine, NodeImpl* n, const QString &script) |
| { |
| // FIXME: This is missing stuff that the other executeScript has. |
| // --> d->m_runningScripts and submitFormAgain. |
| // Why is that OK? |
| KJSProxyImpl *proxy = jScript(); |
| if (!proxy) |
| return 0; |
| JSValue* ret = proxy->evaluate(filename, baseLine, script, n); |
| DocumentImpl::updateDocumentsRendering(); |
| return ret; |
| } |
| |
| Frame *Frame::opener() |
| { |
| return d->m_opener; |
| } |
| |
| void Frame::setOpener(Frame *_opener) |
| { |
| d->m_opener = _opener; |
| } |
| |
| bool Frame::openedByJS() |
| { |
| return d->m_openedByJS; |
| } |
| |
| void Frame::setOpenedByJS(bool _openedByJS) |
| { |
| d->m_openedByJS = _openedByJS; |
| } |
| |
| void Frame::preloadStyleSheet(const QString &url, const QString &stylesheet) |
| { |
| Cache::preloadStyleSheet(url, stylesheet); |
| } |
| |
| void Frame::preloadScript(const QString &url, const QString &script) |
| { |
| Cache::preloadScript(url, script); |
| } |
| |
| bool Frame::restored() const |
| { |
| return d->m_restored; |
| } |
| |
| void Frame::incrementFrameCount() |
| { |
| // FIXME: this should be in Page |
| frameCount++; |
| if (tree()->parent()) |
| tree()->parent()->incrementFrameCount(); |
| } |
| |
| void Frame::decrementFrameCount() |
| { |
| // FIXME: this should be in Page |
| frameCount--; |
| if (tree()->parent()) |
| tree()->parent()->decrementFrameCount(); |
| } |
| |
| int Frame::topLevelFrameCount() |
| { |
| // FIXME: this should be in Page |
| if (tree()->parent()) |
| return tree()->parent()->topLevelFrameCount(); |
| return frameCount; |
| } |
| |
| bool Frame::tabsToLinks() const |
| { |
| return true; |
| } |
| |
| bool Frame::tabsToAllControls() const |
| { |
| return true; |
| } |
| |
| void Frame::copyToPasteboard() |
| { |
| issueCopyCommand(); |
| } |
| |
| void Frame::cutToPasteboard() |
| { |
| issueCutCommand(); |
| } |
| |
| void Frame::pasteFromPasteboard() |
| { |
| issuePasteCommand(); |
| } |
| |
| void Frame::pasteAndMatchStyle() |
| { |
| issuePasteAndMatchStyleCommand(); |
| } |
| |
| void Frame::transpose() |
| { |
| issueTransposeCommand(); |
| } |
| |
| void Frame::redo() |
| { |
| issueRedoCommand(); |
| } |
| |
| void Frame::undo() |
| { |
| issueUndoCommand(); |
| } |
| |
| |
| void Frame::computeAndSetTypingStyle(CSSStyleDeclarationImpl *style, EditAction editingAction) |
| { |
| if (!style || style->length() == 0) { |
| clearTypingStyle(); |
| return; |
| } |
| |
| // Calculate the current typing style. |
| RefPtr<CSSMutableStyleDeclarationImpl> mutableStyle = style->makeMutable(); |
| if (typingStyle()) { |
| typingStyle()->merge(mutableStyle.get()); |
| mutableStyle = typingStyle(); |
| } |
| |
| NodeImpl *node = VisiblePosition(selection().start(), selection().affinity()).deepEquivalent().node(); |
| CSSComputedStyleDeclarationImpl computedStyle(node); |
| computedStyle.diff(mutableStyle.get()); |
| |
| // Handle block styles, substracting these from the typing style. |
| RefPtr<CSSMutableStyleDeclarationImpl> blockStyle = mutableStyle->copyBlockProperties(); |
| blockStyle->diff(mutableStyle.get()); |
| if (document() && blockStyle->length() > 0) { |
| EditCommandPtr cmd(new ApplyStyleCommand(document(), blockStyle.get(), editingAction)); |
| cmd.apply(); |
| } |
| |
| // Set the remaining style as the typing style. |
| d->m_typingStyle = mutableStyle.release(); |
| } |
| |
| void Frame::applyStyle(CSSStyleDeclarationImpl *style, EditAction editingAction) |
| { |
| switch (selection().state()) { |
| case Selection::NONE: |
| // do nothing |
| break; |
| case Selection::CARET: { |
| computeAndSetTypingStyle(style, editingAction); |
| break; |
| } |
| case Selection::RANGE: |
| if (document() && style) { |
| EditCommandPtr cmd(new ApplyStyleCommand(document(), style, editingAction)); |
| cmd.apply(); |
| } |
| break; |
| } |
| } |
| |
| void Frame::applyParagraphStyle(CSSStyleDeclarationImpl *style, EditAction editingAction) |
| { |
| switch (selection().state()) { |
| case Selection::NONE: |
| // do nothing |
| break; |
| case Selection::CARET: |
| case Selection::RANGE: |
| if (document() && style) { |
| EditCommandPtr cmd(new ApplyStyleCommand(document(), style, editingAction, ApplyStyleCommand::ForceBlockProperties)); |
| cmd.apply(); |
| } |
| break; |
| } |
| } |
| |
| static void updateState(CSSMutableStyleDeclarationImpl *desiredStyle, CSSComputedStyleDeclarationImpl *computedStyle, bool &atStart, Frame::TriState &state) |
| { |
| QValueListConstIterator<CSSProperty> end; |
| for (QValueListConstIterator<CSSProperty> it = desiredStyle->valuesIterator(); it != end; ++it) { |
| int propertyID = (*it).id(); |
| DOMString desiredProperty = desiredStyle->getPropertyValue(propertyID); |
| DOMString computedProperty = computedStyle->getPropertyValue(propertyID); |
| Frame::TriState propertyState = equalIgnoringCase(desiredProperty, computedProperty) |
| ? Frame::trueTriState : Frame::falseTriState; |
| if (atStart) { |
| state = propertyState; |
| atStart = false; |
| } else if (state != propertyState) { |
| state = Frame::mixedTriState; |
| break; |
| } |
| } |
| } |
| |
| Frame::TriState Frame::selectionHasStyle(CSSStyleDeclarationImpl *style) const |
| { |
| bool atStart = true; |
| TriState state = falseTriState; |
| |
| RefPtr<CSSMutableStyleDeclarationImpl> mutableStyle = style->makeMutable(); |
| |
| if (!d->m_selection.isRange()) { |
| NodeImpl* nodeToRemove; |
| RefPtr<CSSComputedStyleDeclarationImpl> selectionStyle = selectionComputedStyle(nodeToRemove); |
| if (!selectionStyle) |
| return falseTriState; |
| updateState(mutableStyle.get(), selectionStyle.get(), atStart, state); |
| if (nodeToRemove) { |
| int exceptionCode = 0; |
| nodeToRemove->remove(exceptionCode); |
| assert(exceptionCode == 0); |
| } |
| } else { |
| for (NodeImpl* node = d->m_selection.start().node(); node; node = node->traverseNextNode()) { |
| RefPtr<CSSComputedStyleDeclarationImpl> computedStyle = new CSSComputedStyleDeclarationImpl(node); |
| if (computedStyle) |
| updateState(mutableStyle.get(), computedStyle.get(), atStart, state); |
| if (state == mixedTriState) |
| break; |
| if (node == d->m_selection.end().node()) |
| break; |
| } |
| } |
| |
| return state; |
| } |
| |
| bool Frame::selectionStartHasStyle(CSSStyleDeclarationImpl *style) const |
| { |
| NodeImpl* nodeToRemove; |
| RefPtr<CSSStyleDeclarationImpl> selectionStyle = selectionComputedStyle(nodeToRemove); |
| if (!selectionStyle) |
| return false; |
| |
| RefPtr<CSSMutableStyleDeclarationImpl> mutableStyle = style->makeMutable(); |
| |
| bool match = true; |
| QValueListConstIterator<CSSProperty> end; |
| for (QValueListConstIterator<CSSProperty> it = mutableStyle->valuesIterator(); it != end; ++it) { |
| int propertyID = (*it).id(); |
| if (!equalIgnoringCase(mutableStyle->getPropertyValue(propertyID), selectionStyle->getPropertyValue(propertyID))) { |
| match = false; |
| break; |
| } |
| } |
| |
| if (nodeToRemove) { |
| int exceptionCode = 0; |
| nodeToRemove->remove(exceptionCode); |
| assert(exceptionCode == 0); |
| } |
| |
| return match; |
| } |
| |
| DOMString Frame::selectionStartStylePropertyValue(int stylePropertyID) const |
| { |
| NodeImpl *nodeToRemove; |
| RefPtr<CSSStyleDeclarationImpl> selectionStyle = selectionComputedStyle(nodeToRemove); |
| if (!selectionStyle) |
| return DOMString(); |
| |
| DOMString value = selectionStyle->getPropertyValue(stylePropertyID); |
| |
| if (nodeToRemove) { |
| int exceptionCode = 0; |
| nodeToRemove->remove(exceptionCode); |
| assert(exceptionCode == 0); |
| } |
| |
| return value; |
| } |
| |
| CSSComputedStyleDeclarationImpl *Frame::selectionComputedStyle(NodeImpl *&nodeToRemove) const |
| { |
| nodeToRemove = 0; |
| |
| if (!document()) |
| return 0; |
| |
| if (d->m_selection.isNone()) |
| return 0; |
| |
| RefPtr<RangeImpl> range(d->m_selection.toRange()); |
| Position pos = range->editingStartPosition(); |
| |
| ElementImpl *elem = pos.element(); |
| if (!elem) |
| return 0; |
| |
| RefPtr<ElementImpl> styleElement = elem; |
| int exceptionCode = 0; |
| |
| if (d->m_typingStyle) { |
| styleElement = document()->createElementNS(xhtmlNamespaceURI, "span", exceptionCode); |
| assert(exceptionCode == 0); |
| |
| styleElement->setAttribute(styleAttr, d->m_typingStyle->cssText().impl(), exceptionCode); |
| assert(exceptionCode == 0); |
| |
| styleElement->appendChild(document()->createEditingTextNode(""), exceptionCode); |
| assert(exceptionCode == 0); |
| |
| if (elem->renderer() && elem->renderer()->canHaveChildren()) { |
| elem->appendChild(styleElement, exceptionCode); |
| } else { |
| NodeImpl *parent = elem->parent(); |
| NodeImpl *next = elem->nextSibling(); |
| |
| if (next) { |
| parent->insertBefore(styleElement, next, exceptionCode); |
| } else { |
| parent->appendChild(styleElement, exceptionCode); |
| } |
| } |
| assert(exceptionCode == 0); |
| |
| nodeToRemove = styleElement.get(); |
| } |
| |
| return new CSSComputedStyleDeclarationImpl(styleElement); |
| } |
| |
| static CSSMutableStyleDeclarationImpl *editingStyle() |
| { |
| static CSSMutableStyleDeclarationImpl *editingStyle = 0; |
| if (!editingStyle) { |
| editingStyle = new CSSMutableStyleDeclarationImpl; |
| int exceptionCode; |
| editingStyle->setCssText("word-wrap: break-word; -khtml-nbsp-mode: space; -khtml-line-break: after-white-space;", exceptionCode); |
| } |
| return editingStyle; |
| } |
| |
| void Frame::applyEditingStyleToBodyElement() const |
| { |
| if (!d->m_doc) |
| return; |
| |
| RefPtr<NodeListImpl> list = d->m_doc->getElementsByTagName("body"); |
| unsigned len = list->length(); |
| for (unsigned i = 0; i < len; i++) { |
| applyEditingStyleToElement(static_cast<ElementImpl *>(list->item(i))); |
| } |
| } |
| |
| void Frame::removeEditingStyleFromBodyElement() const |
| { |
| if (!d->m_doc) |
| return; |
| |
| RefPtr<NodeListImpl> list = d->m_doc->getElementsByTagName("body"); |
| unsigned len = list->length(); |
| for (unsigned i = 0; i < len; i++) { |
| removeEditingStyleFromElement(static_cast<ElementImpl *>(list->item(i))); |
| } |
| } |
| |
| void Frame::applyEditingStyleToElement(ElementImpl *element) const |
| { |
| if (!element || !element->isHTMLElement()) |
| return; |
| |
| RenderObject *renderer = element->renderer(); |
| if (!renderer || !renderer->isBlockFlow()) |
| return; |
| |
| CSSMutableStyleDeclarationImpl *currentStyle = static_cast<HTMLElementImpl *>(element)->getInlineStyleDecl(); |
| CSSMutableStyleDeclarationImpl *mergeStyle = editingStyle(); |
| if (mergeStyle) { |
| currentStyle->merge(mergeStyle); |
| element->setAttribute(styleAttr, currentStyle->cssText()); |
| } |
| } |
| |
| void Frame::removeEditingStyleFromElement(ElementImpl *element) const |
| { |
| if (!element || !element->isHTMLElement()) |
| return; |
| |
| RenderObject *renderer = element->renderer(); |
| if (!renderer || !renderer->isBlockFlow()) |
| return; |
| |
| CSSMutableStyleDeclarationImpl *currentStyle = static_cast<HTMLElementImpl *>(element)->getInlineStyleDecl(); |
| bool changed = false; |
| changed |= !currentStyle->removeProperty(CSS_PROP_WORD_WRAP, false).isNull(); |
| changed |= !currentStyle->removeProperty(CSS_PROP__KHTML_NBSP_MODE, false).isNull(); |
| changed |= !currentStyle->removeProperty(CSS_PROP__KHTML_LINE_BREAK, false).isNull(); |
| if (changed) |
| currentStyle->setChanged(); |
| |
| element->setAttribute(styleAttr, currentStyle->cssText()); |
| } |
| |
| |
| bool Frame::isCharacterSmartReplaceExempt(const QChar &, bool) |
| { |
| // no smart replace |
| return true; |
| } |
| |
| #if !NDEBUG |
| static HashSet<Frame*> lifeSupportSet; |
| #endif |
| |
| void Frame::endAllLifeSupport() |
| { |
| #if !NDEBUG |
| HashSet<Frame*> lifeSupportCopy = lifeSupportSet; |
| HashSet<Frame*>::iterator end = lifeSupportCopy.end(); |
| for (HashSet<Frame*>::iterator it = lifeSupportCopy.begin(); it != end; ++it) |
| (*it)->endLifeSupport(); |
| #endif |
| } |
| |
| void Frame::keepAlive() |
| { |
| if (d->m_lifeSupportTimer.isActive()) |
| return; |
| ref(); |
| #if !NDEBUG |
| lifeSupportSet.add(this); |
| #endif |
| d->m_lifeSupportTimer.startOneShot(0); |
| } |
| |
| void Frame::endLifeSupport() |
| { |
| if (!d->m_lifeSupportTimer.isActive()) |
| return; |
| d->m_lifeSupportTimer.stop(); |
| #if !NDEBUG |
| lifeSupportSet.remove(this); |
| #endif |
| deref(); |
| } |
| |
| void Frame::lifeSupportTimerFired(Timer<Frame>*) |
| { |
| #if !NDEBUG |
| lifeSupportSet.remove(this); |
| #endif |
| deref(); |
| } |
| |
| // Workaround for the fact that it's hard to delete a frame. |
| // Call this after doing user-triggered selections to make it easy to delete the frame you entirely selected. |
| // Can't do this implicitly as part of every setSelection call because in some contexts it might not be good |
| // for the focus to move to another frame. So instead we call it from places where we are selecting with the |
| // mouse or the keyboard after setting the selection. |
| void Frame::selectFrameElementInParentIfFullySelected() |
| { |
| // Find the parent frame; if there is none, then we have nothing to do. |
| Frame *parent = tree()->parent(); |
| if (!parent) |
| return; |
| FrameView *parentView = parent->view(); |
| if (!parentView) |
| return; |
| |
| // Check if the selection contains the entire frame contents; if not, then there is nothing to do. |
| if (!d->m_selection.isRange()) |
| return; |
| if (!isStartOfDocument(VisiblePosition(d->m_selection.start(), d->m_selection.affinity()))) |
| return; |
| if (!isEndOfDocument(VisiblePosition(d->m_selection.end(), d->m_selection.affinity()))) |
| return; |
| |
| // Get to the <iframe> or <frame> (or even <object>) element in the parent frame. |
| DocumentImpl *doc = document(); |
| if (!doc) |
| return; |
| ElementImpl *ownerElement = doc->ownerElement(); |
| if (!ownerElement) |
| return; |
| NodeImpl *ownerElementParent = ownerElement->parentNode(); |
| if (!ownerElementParent) |
| return; |
| |
| // Create compute positions before and after the element. |
| unsigned ownerElementNodeIndex = ownerElement->nodeIndex(); |
| VisiblePosition beforeOwnerElement(VisiblePosition(ownerElementParent, ownerElementNodeIndex, SEL_DEFAULT_AFFINITY)); |
| VisiblePosition afterOwnerElement(VisiblePosition(ownerElementParent, ownerElementNodeIndex + 1, VP_UPSTREAM_IF_POSSIBLE)); |
| |
| // Focus on the parent frame, and then select from before this element to after. |
| if (parent->shouldChangeSelection(SelectionController(beforeOwnerElement, afterOwnerElement))) { |
| parentView->setFocus(); |
| parent->setSelection(SelectionController(beforeOwnerElement, afterOwnerElement)); |
| } |
| } |
| |
| void Frame::handleFallbackContent() |
| { |
| ElementImpl* owner = ownerElement(); |
| if (!owner || !owner->hasTagName(objectTag)) |
| return; |
| static_cast<HTMLObjectElementImpl *>(owner)->renderFallbackContent(); |
| } |
| |
| void Frame::setSettings(KHTMLSettings *settings) |
| { |
| d->m_settings = settings; |
| } |
| |
| void Frame::provisionalLoadStarted() |
| { |
| // we don't want to wait until we get an actual http response back |
| // to cancel pending redirects, otherwise they might fire before |
| // that happens. |
| cancelRedirection(true); |
| } |
| |
| bool Frame::userGestureHint() |
| { |
| Frame *rootFrame = this; |
| while (rootFrame->tree()->parent()) |
| rootFrame = rootFrame->tree()->parent(); |
| |
| if (rootFrame->jScript()) |
| return rootFrame->jScript()->interpreter()->wasRunByUserGesture(); |
| |
| return true; // If JavaScript is disabled, a user gesture must have initiated the navigation |
| } |
| |
| RenderObject *Frame::renderer() const |
| { |
| DocumentImpl *doc = document(); |
| return doc ? doc->renderer() : 0; |
| } |
| |
| ElementImpl* Frame::ownerElement() |
| { |
| RenderPart* ownerElementRenderer = d->m_ownerRenderer; |
| if (!ownerElementRenderer) |
| return 0; |
| return static_cast<ElementImpl*>(ownerElementRenderer->element()); |
| } |
| |
| RenderPart* Frame::ownerRenderer() |
| { |
| return d->m_ownerRenderer; |
| } |
| |
| IntRect Frame::selectionRect() const |
| { |
| RenderCanvas *root = static_cast<RenderCanvas *>(renderer()); |
| if (!root) |
| return IntRect(); |
| |
| return root->selectionRect(); |
| } |
| |
| bool Frame::isFrameSet() const |
| { |
| DocumentImpl* document = d->m_doc.get(); |
| if (!document || !document->isHTMLDocument()) |
| return false; |
| NodeImpl *body = static_cast<HTMLDocumentImpl *>(document)->body(); |
| return body && body->renderer() && body->hasTagName(framesetTag); |
| } |
| |
| bool Frame::openURL(const KURL &URL) |
| { |
| ASSERT_NOT_REACHED(); |
| return true; |
| } |
| |
| void Frame::didNotOpenURL(const KURL &URL) |
| { |
| if (_submittedFormURL == URL) { |
| _submittedFormURL = KURL(); |
| } |
| } |
| |
| // Scans logically forward from "start", including any child frames |
| static HTMLFormElementImpl *scanForForm(NodeImpl *start) |
| { |
| NodeImpl *n; |
| for (n = start; n; n = n->traverseNextNode()) { |
| if (n->hasTagName(formTag)) { |
| return static_cast<HTMLFormElementImpl *>(n); |
| } else if (n->isHTMLElement() |
| && static_cast<HTMLElementImpl *>(n)->isGenericFormElement()) { |
| return static_cast<HTMLGenericFormElementImpl *>(n)->form(); |
| } else if (n->hasTagName(frameTag) || n->hasTagName(iframeTag)) { |
| NodeImpl *childDoc = static_cast<HTMLFrameElementImpl *>(n)->contentDocument(); |
| HTMLFormElementImpl *frameResult = scanForForm(childDoc); |
| if (frameResult) { |
| return frameResult; |
| } |
| } |
| } |
| return 0; |
| } |
| |
| // We look for either the form containing the current focus, or for one immediately after it |
| HTMLFormElementImpl *Frame::currentForm() const |
| { |
| // start looking either at the active (first responder) node, or where the selection is |
| NodeImpl *start = d->m_doc ? d->m_doc->focusNode() : 0; |
| if (!start) |
| start = selection().start().node(); |
| |
| // try walking up the node tree to find a form element |
| NodeImpl *n; |
| for (n = start; n; n = n->parentNode()) { |
| if (n->hasTagName(formTag)) |
| return static_cast<HTMLFormElementImpl *>(n); |
| else if (n->isHTMLElement() |
| && static_cast<HTMLElementImpl *>(n)->isGenericFormElement()) |
| return static_cast<HTMLGenericFormElementImpl *>(n)->form(); |
| } |
| |
| // try walking forward in the node tree to find a form element |
| return start ? scanForForm(start) : 0; |
| } |
| |
| void Frame::setEncoding(const QString &name, bool userChosen) |
| { |
| if (!d->m_workingURL.isEmpty()) |
| receivedFirstData(); |
| d->m_encoding = name; |
| d->m_haveEncoding = userChosen; |
| } |
| |
| void Frame::addData(const char *bytes, int length) |
| { |
| ASSERT(d->m_workingURL.isEmpty()); |
| ASSERT(d->m_doc); |
| ASSERT(d->m_doc->parsing()); |
| write(bytes, length); |
| } |
| |
| // FIXME: should this go in SelectionController? |
| void Frame::revealSelection() |
| { |
| IntRect rect; |
| |
| switch (selection().state()) { |
| case Selection::NONE: |
| return; |
| |
| case Selection::CARET: |
| rect = selection().caretRect(); |
| break; |
| |
| case Selection::RANGE: |
| rect = selectionRect(); |
| break; |
| } |
| |
| Position start = selection().start(); |
| Position end = selection().end(); |
| ASSERT(start.node()); |
| if (start.node() && start.node()->renderer()) { |
| RenderLayer *layer = start.node()->renderer()->enclosingLayer(); |
| if (layer) { |
| ASSERT(!end.node() || !end.node()->renderer() |
| || (end.node()->renderer()->enclosingLayer() == layer)); |
| layer->scrollRectToVisible(rect); |
| } |
| } |
| } |
| |
| // FIXME: should this be here? |
| bool Frame::scrollOverflow(KWQScrollDirection direction, KWQScrollGranularity granularity) |
| { |
| if (!document()) { |
| return false; |
| } |
| |
| NodeImpl *node = document()->focusNode(); |
| if (node == 0) { |
| node = d->m_mousePressNode.get(); |
| } |
| |
| if (node != 0) { |
| RenderObject *r = node->renderer(); |
| if (r != 0) { |
| return r->scroll(direction, granularity); |
| } |
| } |
| |
| return false; |
| } |
| |
| // FIXME: why is this here instead of on the FrameView? |
| void Frame::paint(QPainter *p, const IntRect& rect) |
| { |
| #if !NDEBUG |
| bool fillWithRed; |
| if (p->printing()) |
| fillWithRed = false; // Printing, don't fill with red (can't remember why). |
| else if (!document() || document()->ownerElement()) |
| fillWithRed = false; // Subframe, don't fill with red. |
| else if (view() && view()->isTransparent()) |
| fillWithRed = false; // Transparent, don't fill with red. |
| else if (_drawSelectionOnly) |
| fillWithRed = false; // Selections are transparent, don't fill with red. |
| else if (_elementToDraw) |
| fillWithRed = false; // Element images are transparent, don't fill with red. |
| else |
| fillWithRed = true; |
| |
| if (fillWithRed) |
| p->fillRect(rect, Color(0xFF, 0, 0)); |
| #endif |
| |
| if (renderer()) { |
| // _elementToDraw is used to draw only one element |
| RenderObject *eltRenderer = _elementToDraw ? _elementToDraw->renderer() : 0; |
| renderer()->layer()->paint(p, rect, _drawSelectionOnly, eltRenderer); |
| |
| #if __APPLE__ |
| // Regions may have changed as a result of the visibility/z-index of element changing. |
| if (renderer()->document()->dashboardRegionsDirty()) |
| renderer()->canvas()->view()->updateDashboardRegions(); |
| #endif |
| } else |
| LOG_ERROR("called Frame::paint with nil renderer"); |
| } |
| |
| void Frame::adjustPageHeight(float *newBottom, float oldTop, float oldBottom, float bottomLimit) |
| { |
| RenderCanvas *root = static_cast<RenderCanvas *>(document()->renderer()); |
| if (root) { |
| // Use a printer device, with painting disabled for the pagination phase |
| QPainter painter(true); |
| painter.setPaintingDisabled(true); |
| |
| root->setTruncatedAt((int)floor(oldBottom)); |
| IntRect dirtyRect(0, (int)floor(oldTop), |
| root->docWidth(), (int)ceil(oldBottom-oldTop)); |
| root->layer()->paint(&painter, dirtyRect); |
| *newBottom = root->bestTruncatedAt(); |
| if (*newBottom == 0) |
| *newBottom = oldBottom; |
| } else |
| *newBottom = oldBottom; |
| } |
| |
| PausedTimeouts *Frame::pauseTimeouts() |
| { |
| #if SVG_SUPPORT |
| if (d->m_doc && d->m_doc->svgExtensions()) |
| d->m_doc->accessSVGExtensions()->pauseAnimations(); |
| #endif |
| |
| if (d->m_doc && d->m_jscript) { |
| if (Window *w = Window::retrieveWindow(this)) |
| return w->pauseTimeouts(); |
| } |
| return 0; |
| } |
| |
| void Frame::resumeTimeouts(PausedTimeouts *t) |
| { |
| #if SVG_SUPPORT |
| if (d->m_doc && d->m_doc->svgExtensions()) |
| d->m_doc->accessSVGExtensions()->unpauseAnimations(); |
| #endif |
| |
| if (d->m_doc && d->m_jscript && d->m_bJScriptEnabled) { |
| if (Window *w = Window::retrieveWindow(this)) |
| w->resumeTimeouts(t); |
| } |
| } |
| |
| bool Frame::canCachePage() |
| { |
| // Only save page state if: |
| // 1. We're not a frame or frameset. |
| // 2. The page has no unload handler. |
| // 3. The page has no password fields. |
| // 4. The URL for the page is not https. |
| // 5. The page has no applets. |
| if (tree()->childCount() || d->m_plugins.size() || |
| tree()->parent() || |
| d->m_url.protocol().startsWith("https") || |
| (d->m_doc && (d->m_doc->applets()->length() != 0 || |
| d->m_doc->hasWindowEventListener(unloadEvent) || |
| d->m_doc->hasPasswordField()))) { |
| return false; |
| } |
| return true; |
| } |
| |
| void Frame::saveWindowProperties(KJS::SavedProperties *windowProperties) |
| { |
| Window *window = Window::retrieveWindow(this); |
| if (window) |
| window->saveProperties(*windowProperties); |
| } |
| |
| void Frame::saveLocationProperties(SavedProperties *locationProperties) |
| { |
| Window *window = Window::retrieveWindow(this); |
| if (window) { |
| JSLock lock; |
| Location *location = window->location(); |
| location->saveProperties(*locationProperties); |
| } |
| } |
| |
| void Frame::restoreWindowProperties(SavedProperties *windowProperties) |
| { |
| Window *window = Window::retrieveWindow(this); |
| if (window) |
| window->restoreProperties(*windowProperties); |
| } |
| |
| void Frame::restoreLocationProperties(SavedProperties *locationProperties) |
| { |
| Window *window = Window::retrieveWindow(this); |
| if (window) { |
| JSLock lock; |
| Location *location = window->location(); |
| location->restoreProperties(*locationProperties); |
| } |
| } |
| |
| void Frame::saveInterpreterBuiltins(SavedBuiltins &interpreterBuiltins) |
| { |
| if (jScript()) |
| jScript()->interpreter()->saveBuiltins(interpreterBuiltins); |
| } |
| |
| void Frame::restoreInterpreterBuiltins(const SavedBuiltins &interpreterBuiltins) |
| { |
| if (jScript()) |
| jScript()->interpreter()->restoreBuiltins(interpreterBuiltins); |
| } |
| |
| Frame *Frame::frameForWidget(const Widget *widget) |
| { |
| ASSERT_ARG(widget, widget); |
| |
| NodeImpl *node = nodeForWidget(widget); |
| if (node) |
| return frameForNode(node); |
| |
| // Assume all widgets are either form controls, or FrameViews. |
| ASSERT(widget->isFrameView()); |
| return static_cast<const FrameView *>(widget)->frame(); |
| } |
| |
| Frame *Frame::frameForNode(NodeImpl *node) |
| { |
| ASSERT_ARG(node, node); |
| return node->getDocument()->frame(); |
| } |
| |
| NodeImpl *Frame::nodeForWidget(const Widget *widget) |
| { |
| ASSERT_ARG(widget, widget); |
| const QObject *o = widget->eventFilterObject(); |
| return o ? static_cast<const RenderWidget *>(o)->element() : 0; |
| } |
| |
| void Frame::setDocumentFocus(Widget *widget) |
| { |
| NodeImpl *node = nodeForWidget(widget); |
| ASSERT(node); |
| node->getDocument()->setFocusNode(node); |
| } |
| |
| void Frame::clearDocumentFocus(Widget *widget) |
| { |
| NodeImpl *node = nodeForWidget(widget); |
| ASSERT(node); |
| node->getDocument()->setFocusNode(0); |
| } |
| |
| QPtrList<Frame> &Frame::mutableInstances() |
| { |
| static QPtrList<Frame> instancesList; |
| return instancesList; |
| } |
| |
| void Frame::updatePolicyBaseURL() |
| { |
| if (tree()->parent() && tree()->parent()->document()) |
| setPolicyBaseURL(tree()->parent()->document()->policyBaseURL()); |
| else |
| setPolicyBaseURL(d->m_url.url()); |
| } |
| |
| void Frame::setPolicyBaseURL(const DOMString &s) |
| { |
| if (document()) |
| document()->setPolicyBaseURL(s); |
| for (Frame* child = tree()->firstChild(); child; child = child->tree()->nextSibling()) |
| child->setPolicyBaseURL(s); |
| } |
| |
| void Frame::forceLayout() |
| { |
| FrameView *v = d->m_view.get(); |
| if (v) { |
| v->layout(); |
| // We cannot unschedule a pending relayout, since the force can be called with |
| // a tiny rectangle from a drawRect update. By unscheduling we in effect |
| // "validate" and stop the necessary full repaint from occurring. Basically any basic |
| // append/remove DHTML is broken by this call. For now, I have removed the optimization |
| // until we have a better invalidation stategy. -dwh |
| //v->unscheduleRelayout(); |
| } |
| } |
| |
| void Frame::forceLayoutWithPageWidthRange(float minPageWidth, float maxPageWidth) |
| { |
| // Dumping externalRepresentation(m_frame->renderer()).ascii() is a good trick to see |
| // the state of things before and after the layout |
| RenderCanvas *root = static_cast<RenderCanvas *>(document()->renderer()); |
| if (root) { |
| // This magic is basically copied from khtmlview::print |
| int pageW = (int)ceil(minPageWidth); |
| root->setWidth(pageW); |
| root->setNeedsLayoutAndMinMaxRecalc(); |
| forceLayout(); |
| |
| // If we don't fit in the minimum page width, we'll lay out again. If we don't fit in the |
| // maximum page width, we will lay out to the maximum page width and clip extra content. |
| // FIXME: We are assuming a shrink-to-fit printing implementation. A cropping |
| // implementation should not do this! |
| int rightmostPos = root->rightmostPosition(); |
| if (rightmostPos > minPageWidth) { |
| pageW = kMin(rightmostPos, (int)ceil(maxPageWidth)); |
| root->setWidth(pageW); |
| root->setNeedsLayoutAndMinMaxRecalc(); |
| forceLayout(); |
| } |
| } |
| } |
| |
| void Frame::sendResizeEvent() |
| { |
| if (DocumentImpl* doc = document()) |
| doc->dispatchWindowEvent(EventNames::resizeEvent, false, false); |
| } |
| |
| void Frame::sendScrollEvent() |
| { |
| FrameView *v = d->m_view.get(); |
| if (v) { |
| DocumentImpl *doc = document(); |
| if (!doc) |
| return; |
| doc->dispatchHTMLEvent(scrollEvent, true, false); |
| } |
| } |
| |
| bool Frame::scrollbarsVisible() |
| { |
| if (!view()) |
| return false; |
| |
| if (view()->hScrollBarMode() == QScrollView::AlwaysOff || view()->vScrollBarMode() == QScrollView::AlwaysOff) |
| return false; |
| |
| return true; |
| } |
| |
| void Frame::addMetaData(const QString &key, const QString &value) |
| { |
| d->m_job->addMetaData(key, value); |
| } |
| |
| // This does the same kind of work that Frame::openURL does, except it relies on the fact |
| // that a higher level already checked that the URLs match and the scrolling is the right thing to do. |
| void Frame::scrollToAnchor(const KURL &URL) |
| { |
| d->m_url = URL; |
| started(); |
| |
| gotoAnchor(); |
| |
| // It's important to model this as a load that starts and immediately finishes. |
| // Otherwise, the parent frame may think we never finished loading. |
| d->m_bComplete = false; |
| checkCompleted(); |
| } |
| |
| bool Frame::closeURL() |
| { |
| saveDocumentState(); |
| stopLoading(true); |
| return true; |
| } |
| |
| bool Frame::canMouseDownStartSelect(NodeImpl* node) |
| { |
| if (!node || !node->renderer()) |
| return true; |
| |
| // Check to see if khtml-user-select has been set to none |
| if (!node->renderer()->canSelect()) |
| return false; |
| |
| // Some controls and images can't start a select on a mouse down. |
| for (RenderObject* curr = node->renderer(); curr; curr = curr->parent()) { |
| if (curr->style()->userSelect() == SELECT_IGNORE) |
| return false; |
| } |
| |
| return true; |
| } |
| |
| void Frame::khtmlMouseDoubleClickEvent(MouseEventWithHitTestResults* event) |
| { |
| passWidgetMouseDownEventToWidget(event, true); |
| } |
| |
| bool Frame::passWidgetMouseDownEventToWidget(MouseEventWithHitTestResults* event, bool isDoubleClick) |
| { |
| // Figure out which view to send the event to. |
| RenderObject *target = event->innerNode() ? event->innerNode()->renderer() : 0; |
| if (!target) |
| return false; |
| |
| Widget* widget = RenderLayer::gScrollBar; |
| if (!widget) { |
| if (!target->isWidget()) |
| return false; |
| widget = static_cast<RenderWidget *>(target)->widget(); |
| } |
| |
| // Doubleclick events don't exist in Cocoa. Since passWidgetMouseDownEventToWidget will |
| // just pass _currentEvent down to the widget, we don't want to call it for events that |
| // don't correspond to Cocoa events. The mousedown/ups will have already been passed on as |
| // part of the pressed/released handling. |
| if (!isDoubleClick) |
| return passMouseDownEventToWidget(widget); |
| return true; |
| } |
| |
| bool Frame::passWidgetMouseDownEventToWidget(RenderWidget *renderWidget) |
| { |
| return passMouseDownEventToWidget(renderWidget->widget()); |
| } |
| |
| void Frame::clearTimers(FrameView *view) |
| { |
| if (view) { |
| view->unscheduleRelayout(); |
| if (view->frame()) { |
| DocumentImpl* document = view->frame()->document(); |
| if (document && document->renderer() && document->renderer()->layer()) |
| document->renderer()->layer()->suspendMarquees(); |
| } |
| } |
| } |
| |
| void Frame::clearTimers() |
| { |
| clearTimers(d->m_view.get()); |
| } |
| |
| // FIXME: selection controller? |
| void Frame::centerSelectionInVisibleArea() const |
| { |
| IntRect rect; |
| |
| switch (selection().state()) { |
| case Selection::NONE: |
| return; |
| |
| case Selection::CARET: |
| rect = selection().caretRect(); |
| break; |
| |
| case Selection::RANGE: |
| rect = selectionRect(); |
| break; |
| } |
| |
| Position start = selection().start(); |
| Position end = selection().end(); |
| ASSERT(start.node()); |
| if (start.node() && start.node()->renderer()) { |
| RenderLayer *layer = start.node()->renderer()->enclosingLayer(); |
| if (layer) { |
| ASSERT(!end.node() || !end.node()->renderer() |
| || (end.node()->renderer()->enclosingLayer() == layer)); |
| layer->scrollRectToVisible(rect, RenderLayer::gAlignCenterAlways, RenderLayer::gAlignCenterAlways); |
| } |
| } |
| } |
| |
| RenderStyle *Frame::styleForSelectionStart(NodeImpl *&nodeToRemove) const |
| { |
| nodeToRemove = 0; |
| |
| if (!document()) |
| return 0; |
| if (d->m_selection.isNone()) |
| return 0; |
| |
| Position pos = VisiblePosition(d->m_selection.start(), d->m_selection.affinity()).deepEquivalent(); |
| if (!pos.inRenderedContent()) |
| return 0; |
| NodeImpl *node = pos.node(); |
| if (!node) |
| return 0; |
| |
| if (!d->m_typingStyle) |
| return node->renderer()->style(); |
| |
| int exceptionCode = 0; |
| RefPtr<ElementImpl> styleElement = document()->createElementNS(xhtmlNamespaceURI, "span", exceptionCode); |
| ASSERT(exceptionCode == 0); |
| |
| styleElement->setAttribute(styleAttr, d->m_typingStyle->cssText().impl(), exceptionCode); |
| ASSERT(exceptionCode == 0); |
| |
| styleElement->appendChild(document()->createEditingTextNode(""), exceptionCode); |
| ASSERT(exceptionCode == 0); |
| |
| node->parentNode()->appendChild(styleElement, exceptionCode); |
| ASSERT(exceptionCode == 0); |
| |
| nodeToRemove = styleElement.get(); |
| return styleElement->renderer()->style(); |
| } |
| |
| void Frame::setMediaType(const QString &type) |
| { |
| if (d->m_view) |
| d->m_view->setMediaType(type); |
| } |
| |
| void Frame::setSelectionFromNone() |
| { |
| // Put the caret someplace if the selection is empty and the part is editable. |
| // This has the effect of flashing the caret in a contentEditable view automatically |
| // without requiring the programmer to set a selection explicitly. |
| DocumentImpl *doc = document(); |
| if (doc && selection().isNone() && isContentEditable()) { |
| NodeImpl *node = doc->documentElement(); |
| while (node) { |
| // Look for a block flow, but skip over the HTML element, since we really |
| // want to get at least as far as the the BODY element in a document. |
| if (node->isBlockFlow() && node->hasTagName(htmlTag)) |
| break; |
| node = node->traverseNextNode(); |
| } |
| if (node) |
| setSelection(SelectionController(Position(node, 0), DOWNSTREAM)); |
| } |
| } |
| |
| bool Frame::displaysWithFocusAttributes() const |
| { |
| return d->m_isFocused; |
| } |
| |
| void Frame::setWindowHasFocus(bool flag) |
| { |
| if (m_windowHasFocus == flag) |
| return; |
| m_windowHasFocus = flag; |
| |
| if (DocumentImpl *doc = document()) |
| doc->dispatchWindowEvent(flag ? focusEvent : blurEvent, false, false); |
| } |
| |
| QChar Frame::backslashAsCurrencySymbol() const |
| { |
| DocumentImpl *doc = document(); |
| if (!doc) |
| return '\\'; |
| Decoder *decoder = doc->decoder(); |
| if (!decoder) |
| return '\\'; |
| const QTextCodec *codec = decoder->codec(); |
| if (!codec) |
| return '\\'; |
| |
| return codec->backslashAsCurrencySymbol(); |
| } |
| |
| bool Frame::markedTextUsesUnderlines() const |
| { |
| return m_markedTextUsesUnderlines; |
| } |
| |
| QValueList<MarkedTextUnderline> Frame::markedTextUnderlines() const |
| { |
| return m_markedTextUnderlines; |
| } |
| |
| unsigned Frame::highlightAllMatchesForString(const QString &target, bool caseFlag) |
| { |
| if (target.isEmpty()) { |
| return 0; |
| } |
| |
| RefPtr<RangeImpl> searchRange(rangeOfContents(document())); |
| |
| int exception = 0; |
| unsigned matchCount = 0; |
| do { |
| RefPtr<RangeImpl> resultRange(findPlainText(searchRange.get(), target, true, caseFlag)); |
| if (resultRange->collapsed(exception)) |
| break; |
| |
| ++matchCount; |
| document()->addMarker(resultRange.get(), DocumentMarker::TextMatch); |
| |
| setStart(searchRange.get(), endVisiblePosition(resultRange.get(), DOWNSTREAM)); |
| } while (true); |
| |
| return matchCount; |
| } |
| |
| void Frame::prepareForUserAction() |
| { |
| // Reset the multiple form submission protection code. |
| // We'll let you submit the same form twice if you do two separate user actions. |
| _submittedFormURL = KURL(); |
| } |
| |
| bool Frame::isFrame() const |
| { |
| return true; |
| } |
| |
| NodeImpl *Frame::mousePressNode() |
| { |
| return d->m_mousePressNode.get(); |
| } |
| |
| bool Frame::isComplete() |
| { |
| return d->m_bComplete; |
| } |
| |
| FrameTree *Frame::tree() const |
| { |
| return &d->m_treeNode; |
| } |
| |
| void Frame::detachFromView() |
| { |
| } |
| |
| KURL Frame::url() const |
| { |
| return d->m_url; |
| } |
| |
| void Frame::startRedirectionTimer() |
| { |
| d->m_redirectionTimer.startOneShot(d->m_delayRedirect); |
| } |
| |
| void Frame::stopRedirectionTimer() |
| { |
| d->m_redirectionTimer.stop(); |
| } |
| |
| void Frame::frameDetached() |
| { |
| } |
| |
| void Frame::updateBaseURLForEmptyDocument() |
| { |
| ElementImpl* owner = ownerElement(); |
| // FIXME: Should embed be included? |
| if (owner && (owner->hasTagName(iframeTag) || owner->hasTagName(objectTag) || owner->hasTagName(embedTag))) |
| d->m_doc->setBaseURL(tree()->parent()->d->m_doc->baseURL()); |
| } |
| |
| Page* Frame::page() const |
| { |
| return d->m_page; |
| } |
| |
| void Frame::completed(bool complete) |
| { |
| ref(); |
| for (Frame* child = tree()->firstChild(); child; child = child->tree()->nextSibling()) |
| child->parentCompleted(); |
| if (Frame* parent = tree()->parent()) |
| parent->childCompleted(complete); |
| submitFormAgain(); |
| deref(); |
| } |
| |
| void Frame::setStatusBarText(const String&) |
| { |
| } |
| |
| void Frame::started() |
| { |
| for (Frame* frame = this; frame; frame = frame->tree()->parent()) |
| frame->d->m_bComplete = false; |
| } |
| |
| } // namespace WebCore |