blob: aa13323b41fdd7e58df4ae24836446675b3db563 [file] [log] [blame]
/* 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 "CSSComputedStyleDeclaration.h"
#include "CSSProperty.h"
#include "CSSPropertyNames.h"
#include "Cache.h"
#include "CachedCSSStyleSheet.h"
#include "DOMImplementation.h"
#include "DOMWindow.h"
#include "Decoder.h"
#include "DocLoader.h"
#include "DocumentType.h"
#include "EditingText.h"
#include "EventNames.h"
#include "FloatRect.h"
#include "Frame.h"
#include "GraphicsContext.h"
#include "HTMLDocument.h"
#include "HTMLFormElement.h"
#include "HTMLFrameElement.h"
#include "HTMLGenericFormElement.h"
#include "HTMLNames.h"
#include "MediaFeatureNames.h"
#include "HTMLObjectElement.h"
#include "ImageDocument.h"
#include "MouseEventWithHitTestResults.h"
#include "NodeList.h"
#include "Page.h"
#include "Plugin.h"
#include "RenderPart.h"
#include "RenderTheme.h"
#include "RenderView.h"
#include "SegmentedString.h"
#include "TextDocument.h"
#include "TextIterator.h"
#include "TransferJob.h"
#include "TypingCommand.h"
#include "cssstyleselector.h"
#include "dom2_eventsimpl.h"
#include "htmlediting.h"
#include "kjs_window.h"
#include "markup.h"
#include "visible_units.h"
#include "xml_tokenizer.h"
#include "xmlhttprequest.h"
#include <math.h>
#include <sys/types.h>
#if !WIN32
#include <unistd.h>
#endif
#if SVG_SUPPORT
#include "SVGNames.h"
#include "XLinkNames.h"
#include "XMLNames.h"
#include "SVGDocumentExtensions.h"
#include "SVGDOMImplementation.h"
#endif
using namespace std;
using KJS::JSLock;
using KJS::JSValue;
using KJS::Location;
using KJS::PausedTimeouts;
using KJS::SavedProperties;
using KJS::SavedBuiltins;
using KJS::UString;
using KJS::Window;
namespace WebCore {
using namespace EventNames;
using namespace HTMLNames;
const double caretBlinkFrequency = 0.5;
const double autoscrollInterval = 0.1;
class UserStyleSheetLoader : public CachedObjectClient {
public:
UserStyleSheetLoader(Frame* frame, const String& url, DocLoader* dl)
: m_frame(frame)
, m_cachedSheet(Cache::requestStyleSheet(dl, url, false, 0, ""))
{
m_cachedSheet->ref(this);
}
~UserStyleSheetLoader()
{
m_cachedSheet->deref(this);
}
private:
virtual void setStyleSheet(const String& /*URL*/, const String& sheet)
{
m_frame->setUserStyleSheet(sheet.deprecatedString());
}
Frame* m_frame;
CachedCSSStyleSheet* m_cachedSheet;
};
#ifndef 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()->document()->frame();
}
Frame::Frame(Page* page, RenderPart* ownerRenderer)
: d(new FramePrivate(page, parentFromOwnerRenderer(ownerRenderer), this, ownerRenderer))
{
AtomicString::init();
Cache::init();
EventNames::init();
HTMLNames::init();
QualifiedName::init();
MediaFeatureNames::init();
#if SVG_SUPPORT
SVGNames::init();
XLinkNames::init();
XMLNames::init();
#endif
// FIXME: Frames were originally created with a refcount of 1, leave this
// ref call here until we can straighten that out.
ref();
#ifndef NDEBUG
++FrameCounter::count;
#endif
if (ownerRenderer)
ownerRenderer->setFrame(this);
}
Frame::~Frame()
{
ASSERT(!d->m_lifeSupportTimer.isActive());
#ifndef 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();
// Must clear the window pointer, otherwise we will not
// garbage-collect collect the window (inside the call to
// delete d below).
w = 0;
}
if (d->m_domWindow)
d->m_domWindow->disconnectFrame();
setOpener(0);
HashSet<Frame*> openedBy = d->m_openedFrames;
HashSet<Frame*>::iterator end = openedBy.end();
for (HashSet<Frame*>::iterator it = openedBy.begin(); it != end; ++it)
(*it)->setOpener(0);
if (d->m_ownerRenderer)
d->m_ownerRenderer->setFrame(0);
ASSERT(!d->m_ownerRenderer);
if (d->m_view) {
d->m_view->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();
closeURL();
if (d->m_request.reload)
d->m_cachePolicy = KIO::CC_Refresh;
else
d->m_cachePolicy = KIO::CC_Verify;
if (d->m_request.doPost() && url.protocol().startsWith("http")) {
d->m_job = new TransferJob(this, "POST", url, d->m_request.postData);
d->m_job->addMetaData("content-type", d->m_request.contentType());
} else
d->m_job = new TransferJob(this, "GET", url);
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();
d->m_bJavaEnabled = d->m_settings->isJavaEnabled();
d->m_bPluginsEnabled = d->m_settings->isPluginsEnabled();
// 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 (Document *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();
}
void Frame::setView(FrameView* view)
{
d->m_view = view;
}
bool Frame::jScriptEnabled() const
{
return d->m_bJScriptEnabled;
}
KJSProxy *Frame::jScript()
{
if (!d->m_bJScriptEnabled)
return 0;
if (!d->m_jscript)
d->m_jscript = new KJSProxy(this);
return d->m_jscript;
}
static bool getString(JSValue* result, DeprecatedString& string)
{
if (!result)
return false;
JSLock lock;
UString ustring;
if (!result->getString(ustring))
return false;
string = ustring;
return true;
}
void Frame::replaceContentsWithScriptResult(const KURL& url)
{
JSValue* ret = executeScript(0, KURL::decode_string(url.url().mid(strlen("javascript:"))));
DeprecatedString scriptResult;
if (getString(ret, scriptResult)) {
begin();
write(scriptResult);
end();
}
}
JSValue* Frame::executeScript(Node* n, const DeprecatedString& script, bool forceUserGesture)
{
KJSProxy *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 ? DeprecatedString::null : d->m_url.url(), 0, script, n);
d->m_runningScripts--;
if (!d->m_runningScripts)
submitFormAgain();
Document::updateDocumentsRendering();
return ret;
}
bool Frame::javaEnabled() const
{
return d->m_settings->isJavaEnabled();
}
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_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 = DeprecatedString::null;
d->m_redirectReferrer = DeprecatedString::null;
d->m_redirectLockHistory = true;
d->m_redirectUserGesture = false;
d->m_bHTTPRefresh = false;
d->m_bFirstData = true;
d->m_bMousePressed = false;
if (!d->m_haveEncoding)
d->m_encoding = DeprecatedString::null;
}
Document *Frame::document() const
{
if (d)
return d->m_doc.get();
return 0;
}
void Frame::setDocument(Document* 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_doc->docLoader()->setCachePolicy(d->m_cachePolicy);
d->m_workingURL = KURL();
// When the first data arrives, the metadata has just been made available
DeprecatedString qData;
// Support for http-refresh
qData = d->m_job->queryMetaData("http-refresh").deprecatedString();
if (!qData.isEmpty()) {
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::receivedAllData(TransferJob* job)
{
d->m_job = 0;
if (job->error()) {
checkCompleted();
return;
}
d->m_workingURL = KURL();
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::setResourceRequest(const ResourceRequest& request)
{
d->m_request = request;
}
const ResourceRequest& Frame::resourceRequest() const
{
return d->m_request;
}
void Frame::begin(const KURL& url)
{
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;
KURL ref(url);
ref.setUser(DeprecatedString());
ref.setPass(DeprecatedString());
ref.setRef(DeprecatedString());
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 SVG_SUPPORT
if (d->m_request.m_responseMIMEType == "image/svg+xml")
d->m_doc = SVGDOMImplementation::instance()->createDocument(d->m_view.get());
else
#endif
if (DOMImplementation::isXMLMIMEType(d->m_request.m_responseMIMEType))
d->m_doc = DOMImplementation::instance()->createDocument(d->m_view.get());
else if (DOMImplementation::isTextMIMEType(d->m_request.m_responseMIMEType))
d->m_doc = new TextDocument(DOMImplementation::instance(), d->m_view.get());
else if (Image::supportsType(d->m_request.m_responseMIMEType))
d->m_doc = new ImageDocument(DOMImplementation::instance(), d->m_view.get());
else
d->m_doc = DOMImplementation::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());
updatePolicyBaseURL();
setAutoloadImages(d->m_settings->autoLoadImages());
const KURL& userStyleSheet = d->m_settings->userStyleSheetLocation();
if (!userStyleSheet.isEmpty())
setUserStyleSheetLocation(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 (len == 0)
return;
if (len == -1)
len = strlen(str);
if (Tokenizer* t = d->m_doc->tokenizer()) {
if (t->wantsRawData()) {
t->writeRawData(str, len);
return;
}
}
if (!d->m_decoder) {
d->m_decoder = new Decoder;
if (!d->m_encoding.isNull())
d->m_decoder->setEncodingName(d->m_encoding.latin1(),
d->m_haveEncoding ? Decoder::UserChosenEncoding : Decoder::EncodingFromHTTPHeader);
else
d->m_decoder->setEncodingName(settings()->encoding().latin1(), Decoder::DefaultEncoding);
if (d->m_doc)
d->m_doc->setDecoder(d->m_decoder.get());
}
DeprecatedString 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(Node::Force);
}
if (Tokenizer* t = d->m_doc->tokenizer()) {
ASSERT(!t->wantsRawData());
t->write(decoded, true);
}
}
void Frame::write(const DeprecatedString& str)
{
if (str.isNull())
return;
if (d->m_bFirstData) {
// determine the parse mode
d->m_doc->setParseMode(Document::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_doc) {
if (d->m_decoder) {
DeprecatedString decoded = d->m_decoder->flush();
if (d->m_bFirstData) {
d->m_doc->determineParseMode(decoded);
d->m_bFirstData = false;
}
write(decoded);
}
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::gotoAnchor()
{
// If our URL has no ref, then we have no place we need to jump to.
if (!d->m_url.hasRef())
return;
DeprecatedString 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->encoding()));
}
}
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) {
String 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();
}
String Frame::baseTarget() const
{
if (!d->m_doc)
return DeprecatedString();
return d->m_doc->baseTarget();
}
KURL Frame::completeURL(const DeprecatedString& url)
{
if (!d->m_doc)
return url;
return KURL(d->m_doc->completeURL(url));
}
void Frame::scheduleRedirection(double delay, const DeprecatedString& 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 = DeprecatedString::null;
d->m_redirectLockHistory = doLockHistory;
d->m_redirectUserGesture = false;
stopRedirectionTimer();
if (d->m_bComplete)
startRedirectionTimer();
}
}
void Frame::scheduleLocationChange(const DeprecatedString& url, const DeprecatedString& referrer, bool lockHistory, bool userGesture)
{
KURL u(url);
// If the URL we're going to navigate to is the same as the current one, except for the
// fragment part, we don't need to schedule the location change.
if (u.hasRef() && equalIgnoringRef(d->m_url, u)) {
changeLocation(url, referrer, lockHistory, userGesture);
return;
}
// 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;
}
// If the URL we're going to navigate to is the same as the current one, except for the
// fragment part, we don't need to schedule the navigation.
if (d->m_extension) {
KURL u = d->m_extension->historyURL(steps);
if (equalIgnoringRef(d->m_url, u)) {
d->m_extension->goBackOrForward(steps);
return;
}
}
d->m_scheduledRedirection = historyNavigationScheduled;
d->m_delayRedirect = 0;
d->m_redirectURL = DeprecatedString::null;
d->m_redirectReferrer = DeprecatedString::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 DeprecatedString& URL, const DeprecatedString& referrer, bool lockHistory, bool userGesture)
{
if (URL.find("javascript:", 0, false) == 0) {
DeprecatedString script = KURL::decode_string(URL.mid(11));
JSValue* result = executeScript(0, script, userGesture);
DeprecatedString scriptResult;
if (getString(result, scriptResult)) {
begin(url());
write(scriptResult);
end();
}
return;
}
ResourceRequest request(completeURL(URL));
request.setLockHistory(lockHistory);
if (!referrer.isEmpty())
request.setReferrer(referrer);
urlSelected(request, "_self");
}
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;
}
DeprecatedString URL = d->m_redirectURL;
DeprecatedString 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 = DeprecatedString::null;
d->m_redirectReferrer = DeprecatedString::null;
changeLocation(URL, referrer, lockHistory, userGesture);
}
void Frame::receivedRedirect(TransferJob*, const KURL& url)
{
d->m_workingURL = url;
}
DeprecatedString Frame::encoding() const
{
if (d->m_haveEncoding && !d->m_encoding.isEmpty())
return d->m_encoding;
if (d->m_decoder && d->m_decoder->encoding().isValid())
return d->m_decoder->encodingName();
return settings()->encoding();
}
void Frame::setUserStyleSheetLocation(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 String& styleSheet)
{
delete d->m_userStyleSheetLoader;
d->m_userStyleSheetLoader = 0;
if (d->m_doc)
d->m_doc->setUserStyleSheet(styleSheet);
}
bool Frame::gotoAnchor(const String& name)
{
if (!d->m_doc)
return false;
Node *n = d->m_doc->getElementById(AtomicString(name));
if (!n) {
HTMLCollection *anchors =
new HTMLCollection(d->m_doc.get(), HTMLCollection::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 String& name)
{
d->m_settings->setStdFontName(AtomicString(name));
}
void Frame::setFixedFont(const String& name)
{
d->m_settings->setFixedFontName(AtomicString(name));
}
String 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;
}
TextGranularity Frame::selectionGranularity() const
{
return d->m_selectionGranularity;
}
void Frame::setSelectionGranularity(TextGranularity granularity) const
{
d->m_selectionGranularity = granularity;
}
SelectionController& Frame::dragCaret() const
{
return d->m_page->dragCaret();
}
const Selection& Frame::mark() const
{
return d->m_mark;
}
void Frame::setMark(const Selection& s)
{
ASSERT(!s.base().node() || s.base().node()->document() == document());
ASSERT(!s.extent().node() || s.extent().node()->document() == document());
ASSERT(!s.start().node() || s.start().node()->document() == document());
ASSERT(!s.end().node() || s.end().node()->document() == document());
d->m_mark = s;
}
void Frame::setSelection(const SelectionController& s, bool closeTyping, bool keepTypingStyle)
{
if (d->m_selection == s)
return;
ASSERT(!s.base().node() || s.base().node()->document() == document());
ASSERT(!s.extent().node() || s.extent().node()->document() == document());
ASSERT(!s.start().node() || s.start().node()->document() == document());
ASSERT(!s.end().node() || s.end().node()->document() == document());
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)
{
d->m_page->setDragCaret(dragCaret);
}
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 Node *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;
Node *startNode = d->m_selection.start().node();
Node *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(GraphicsContext* p, const IntRect& rect) const
{
if (d->m_caretPaint)
d->m_selection.paintCaret(p, rect);
}
void Frame::paintDragCaret(GraphicsContext* p, const IntRect& rect) const
{
SelectionController& dragCaret = d->m_page->dragCaret();
assert(dragCaret.selection().isCaret());
if (dragCaret.selection().start().node()->document()->frame() == this)
dragCaret.paintCaret(p, rect);
}
void Frame::urlSelected(const DeprecatedString& url, const String& target)
{
urlSelected(ResourceRequest(completeURL(url)), target);
}
void Frame::urlSelected(const ResourceRequest& request, const String& _target)
{
String target = _target;
if (target.isEmpty() && d->m_doc)
target = d->m_doc->baseTarget();
const KURL& url = request.url();
if (url.url().startsWith("javascript:", false)) {
executeScript(0, KURL::decode_string(url.url().mid(11)), true);
return;
}
if (!url.isValid())
// ### ERROR HANDLING
return;
ResourceRequest requestCopy = request;
requestCopy.frameName = target;
if (d->m_bHTTPRefresh)
d->m_bHTTPRefresh = false;
if (!d->m_referrer.isEmpty())
requestCopy.setReferrer(d->m_referrer);
urlSelected(requestCopy);
}
bool Frame::requestFrame(RenderPart* renderer, const String& urlParam, const AtomicString& frameName)
{
DeprecatedString _url = urlParam.deprecatedString();
// 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 = tree()->child(frameName);
if (frame) {
ResourceRequest request(url);
request.setReferrer(d->m_referrer);
request.reload = (d->m_cachePolicy == KIO::CC_Reload) || (d->m_cachePolicy == KIO::CC_Refresh);
frame->openURLRequest(request);
} 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 String& url, const AtomicString& frameName,
const String& mimeType, const Vector<String>& paramNames, const Vector<String>& paramValues)
{
KURL completedURL;
if (!url.isEmpty())
completedURL = completeURL(url.deprecatedString());
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(Node* element, const KURL& url, const String& 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 String& mimeType,
const Vector<String>& paramNames, const Vector<String>& paramValues, bool useFallback)
{
if (useFallback) {
checkEmitLoadEvent();
return false;
}
Element *pluginElement;
if (renderer && renderer->node() && renderer->node()->isElementNode())
pluginElement = static_cast<Element*>(renderer->node());
else
pluginElement = 0;
Plugin* plugin = createPlugin(pluginElement, 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 String& name, const String& 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::clearRecordedFormValues()
{
d->m_formAboutToBeSubmitted = 0;
d->m_formValuesAboutToBeSubmitted.clear();
}
void Frame::recordFormValue(const String& name, const String& value, PassRefPtr<HTMLFormElement> element)
{
d->m_formAboutToBeSubmitted = element;
d->m_formValuesAboutToBeSubmitted.set(name, value);
}
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 String& url, const FormData& formData, const String& _target, const String& contentType, const String& boundary)
{
KURL u = completeURL(url.deprecatedString());
if (!u.isValid())
// ### ERROR HANDLING!
return;
DeprecatedString 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;
}
ResourceRequest request;
if (!d->m_referrer.isEmpty())
request.setReferrer(d->m_referrer);
request.frameName = _target.isEmpty() ? d->m_doc->baseTarget() : _target ;
// Handle mailto: forms
if (u.protocol() == "mailto") {
// 1) Check for attach= and strip it
DeprecatedString q = u.query().mid(1);
DeprecatedStringList nvps = DeprecatedStringList::split("&", q);
bool triedToAttach = false;
for (DeprecatedStringList::Iterator nvp = nvps.begin(); nvp != nvps.end(); ++nvp) {
DeprecatedStringList pair = DeprecatedStringList::split("=", *nvp);
if (pair.count() >= 2) {
if (pair.first().lower() == "attach") {
nvp = nvps.remove(nvp);
triedToAttach = true;
}
}
}
// 2) Append body=
DeprecatedString 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/
DeprecatedString 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(String::sprintf("body=%s", bodyEnc.latin1()).deprecatedString());
q = nvps.join("&");
u.setQuery(q);
}
if (strcmp(action, "get") == 0) {
if (u.protocol() != "mailto")
u.setQuery(formData.flattenToString());
request.setDoPost(false);
} else {
request.postData = formData;
request.setDoPost(true);
// construct some user headers if necessary
if (contentType.isNull() || contentType == "application/x-www-form-urlencoded")
request.setContentType("Content-Type: application/x-www-form-urlencoded");
else // contentType must be "multipart/form-data"
request.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 {
request.setURL(u);
submitForm(request);
}
}
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();
}
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(Node::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;
}
DeprecatedString Frame::referrer() const
{
return d->m_referrer;
}
String Frame::lastModified() const
{
return d->m_lastModified;
}
void Frame::reparseConfiguration()
{
setAutoloadImages(d->m_settings->autoLoadImages());
d->m_bJScriptEnabled = d->m_settings->isJavaScriptEnabled();
d->m_bJavaEnabled = d->m_settings->isJavaEnabled();
d->m_bPluginsEnabled = d->m_settings->isPluginsEnabled();
const KURL& userStyleSheetLocation = d->m_settings->userStyleSheetLocation();
if (!userStyleSheetLocation.isEmpty())
setUserStyleSheetLocation(userStyleSheetLocation);
else
setUserStyleSheet(String());
// FIXME: It's not entirely clear why the following is needed.
// The document automatically does this as required when you set the style sheet.
// But we had problems when this code was removed. Details are in
// <http://bugzilla.opendarwin.org/show_bug.cgi?id=8079>.
if (d->m_doc)
d->m_doc->updateStyleSelector();
}
bool Frame::shouldDragAutoNode(Node *node, const IntPoint& point) const
{
// No KDE impl yet
return false;
}
bool Frame::isPointInsideSelection(const IntPoint& point)
{
// 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, point);
Node *innerNode = nodeInfo.innerNode();
if (!innerNode || !innerNode->renderer())
return false;
Position pos(innerNode->renderer()->positionForPoint(point).deepEquivalent());
if (pos.isNull())
return false;
Node *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(const PlatformMouseEvent& mouse, Node *innerNode)
{
SelectionController selection;
if (innerNode && innerNode->renderer() && mouseDownMayStartSelect() && innerNode->renderer()->shouldSelect()) {
IntPoint vPoint = view()->viewportToContents(mouse.pos());
VisiblePosition pos(innerNode->renderer()->positionForPoint(vPoint));
if (pos.isNotNull()) {
selection.moveTo(pos);
selection.expandUsingGranularity(WordGranularity);
}
}
if (selection.isRange()) {
d->m_selectionGranularity = WordGranularity;
d->m_beganSelectingText = true;
}
if (shouldChangeSelection(selection))
setSelection(selection);
}
void Frame::handleMousePressEventDoubleClick(const 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 handleMouseReleaseEvent
// from setting caret selection.
d->m_beganSelectingText = true;
else
selectClosestWordFromMouseEvent(event.event(), event.targetNode());
}
}
void Frame::handleMousePressEventTripleClick(const MouseEventWithHitTestResults& event)
{
Node *innerNode = event.targetNode();
if (event.event().button() == LeftButton && innerNode && innerNode->renderer() &&
mouseDownMayStartSelect() && innerNode->renderer()->shouldSelect()) {
SelectionController selection;
IntPoint vPoint = view()->viewportToContents(event.event().pos());
VisiblePosition pos(innerNode->renderer()->positionForPoint(vPoint));
if (pos.isNotNull()) {
selection.moveTo(pos);
selection.expandUsingGranularity(ParagraphGranularity);
}
if (selection.isRange()) {
d->m_selectionGranularity = ParagraphGranularity;
d->m_beganSelectingText = true;
}
if (shouldChangeSelection(selection))
setSelection(selection);
}
}
void Frame::handleMousePressEventSingleClick(const MouseEventWithHitTestResults& event)
{
Node *innerNode = event.targetNode();
if (event.event().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 = event.event().shiftKey() && !event.isOverLink();
// Don't restart the selection when the mouse is pressed on an
// existing selection so we can allow for text dragging.
IntPoint vPoint = view()->viewportToContents(event.event().pos());
if (!extendSelection && isPointInsideSelection(vPoint))
return;
VisiblePosition visiblePos(innerNode->renderer()->positionForPoint(vPoint));
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 = Range::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 != CharacterGranularity)
sel.expandUsingGranularity(d->m_selectionGranularity);
d->m_beganSelectingText = true;
} else {
sel = SelectionController(visiblePos);
d->m_selectionGranularity = CharacterGranularity;
}
if (shouldChangeSelection(sel))
setSelection(sel);
}
}
}
void Frame::handleMousePressEvent(const MouseEventWithHitTestResults& event)
{
Node *innerNode = event.targetNode();
d->m_mousePressNode = innerNode;
d->m_dragStartPos = event.event().pos();
if (event.event().button() == LeftButton || event.event().button() == MiddleButton) {
d->m_bMousePressed = true;
d->m_beganSelectingText = false;
if (event.event().clickCount() == 2) {
handleMousePressEventDoubleClick(event);
return;
}
if (event.event().clickCount() >= 3) {
handleMousePressEventTripleClick(event);
return;
}
handleMousePressEventSingleClick(event);
}
}
void Frame::handleMouseMoveEvent(const MouseEventWithHitTestResults& event)
{
// Mouse not pressed. Do nothing.
if (!d->m_bMousePressed)
return;
Node *innerNode = event.targetNode();
if (event.event().button() != 0 || !innerNode || !innerNode->renderer() || !mouseDownMayStartSelect() || !innerNode->renderer()->shouldSelect())
return;
// handle making selection
IntPoint vPoint = view()->viewportToContents(event.event().pos());
VisiblePosition pos(innerNode->renderer()->positionForPoint(vPoint));
// 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 handleMousePressEvent, 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 != CharacterGranularity)
sel.expandUsingGranularity(d->m_selectionGranularity);
if (shouldChangeSelection(sel))
setSelection(sel);
}
void Frame::handleMouseReleaseEvent(const MouseEventWithHitTestResults& event)
{
stopAutoscrollTimer();
// 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;
Node *node = event.targetNode();
if (node && node->isContentEditable() && node->renderer()) {
IntPoint vPoint = view()->viewportToContents(event.event().pos());
VisiblePosition pos = node->renderer()->positionForPoint(vPoint);
selection.moveTo(pos);
}
if (shouldChangeSelection(selection))
setSelection(selection);
}
selectFrameElementInParentIfFullySelected();
}
void Frame::selectAll()
{
if (!d->m_doc)
return;
Node *startNode = d->m_selection.start().node();
Node *root = startNode && startNode->isContentEditable() ? startNode->rootEditableElement() : d->m_doc->documentElement();
selectContentsOfNode(root);
selectFrameElementInParentIfFullySelected();
}
bool Frame::selectContentsOfNode(Node* node)
{
SelectionController sel = SelectionController(Selection::selectionFromContentsOfNode(node));
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 Range *range) const
{
return true;
}
bool Frame::shouldEndEditing(const Range *range) const
{
return true;
}
bool Frame::isContentEditable() const
{
if (!d->m_doc)
return false;
return d->m_doc->inDesignMode();
}
void Frame::textFieldDidBeginEditing(Element* input)
{
}
void Frame::textFieldDidEndEditing(Element* input)
{
}
void Frame::textDidChangeInTextField(Element* input)
{
}
bool Frame::doTextFieldCommandFromEvent(Element* input, const PlatformKeyboardEvent* evt)
{
return false;
}
void Frame::textWillBeDeletedInTextField(Element* input)
{
}
void Frame::textDidChangeInTextArea(Element* input)
{
}
EditCommandPtr Frame::lastEditCommand()
{
return d->m_lastEditCommand;
}
void dispatchEditableContentChangedEvent(Node* root)
{
if (!root)
return;
ExceptionCode ec = 0;
RefPtr<Event> evt = new Event(khtmlEditableContentChangedEvent, false, false);
EventTargetNodeCast(root)->dispatchEvent(evt, ec, true);
}
void Frame::appliedEditing(EditCommandPtr& cmd)
{
SelectionController sel(cmd.endingSelection());
if (shouldChangeSelection(sel))
setSelection(sel, false);
dispatchEditableContentChangedEvent(!selection().isNone() ? selection().start().node()->rootEditableElement() : 0);
// 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);
dispatchEditableContentChangedEvent(!selection().isNone() ? selection().start().node()->rootEditableElement() : 0);
registerCommandForRedo(cmd);
respondToChangedContents();
d->m_lastEditCommand = EditCommandPtr::emptyCommand();
}
void Frame::reappliedEditing(EditCommandPtr& cmd)
{
SelectionController sel(cmd.endingSelection());
if (shouldChangeSelection(sel))
setSelection(sel, true);
dispatchEditableContentChangedEvent(!selection().isNone() ? selection().start().node()->rootEditableElement() : 0);
registerCommandForUndo(cmd);
respondToChangedContents();
d->m_lastEditCommand = EditCommandPtr::emptyCommand();
}
CSSMutableStyleDeclaration *Frame::typingStyle() const
{
return d->m_typingStyle.get();
}
void Frame::setTypingStyle(CSSMutableStyleDeclaration *style)
{
d->m_typingStyle = style;
}
void Frame::clearTypingStyle()
{
d->m_typingStyle = 0;
}
JSValue* Frame::executeScript(const String& filename, int baseLine, Node* n, const DeprecatedString& script)
{
// FIXME: This is missing stuff that the other executeScript has.
// --> d->m_runningScripts and submitFormAgain.
// Why is that OK?
KJSProxy *proxy = jScript();
if (!proxy)
return 0;
JSValue* ret = proxy->evaluate(filename, baseLine, script, n);
Document::updateDocumentsRendering();
return ret;
}
Frame *Frame::opener()
{
return d->m_opener;
}
void Frame::setOpener(Frame* opener)
{
if (d->m_opener)
d->m_opener->d->m_openedFrames.remove(this);
if (opener)
opener->d->m_openedFrames.add(this);
d->m_opener = opener;
}
bool Frame::openedByJS()
{
return d->m_openedByJS;
}
void Frame::setOpenedByJS(bool _openedByJS)
{
d->m_openedByJS = _openedByJS;
}
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(CSSStyleDeclaration *style, EditAction editingAction)
{
if (!style || style->length() == 0) {
clearTypingStyle();
return;
}
// Calculate the current typing style.
RefPtr<CSSMutableStyleDeclaration> mutableStyle = style->makeMutable();
if (typingStyle()) {
typingStyle()->merge(mutableStyle.get());
mutableStyle = typingStyle();
}
Node *node = VisiblePosition(selection().start(), selection().affinity()).deepEquivalent().node();
CSSComputedStyleDeclaration computedStyle(node);
computedStyle.diff(mutableStyle.get());
// Handle block styles, substracting these from the typing style.
RefPtr<CSSMutableStyleDeclaration> 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(CSSStyleDeclaration *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(CSSStyleDeclaration *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(CSSMutableStyleDeclaration *desiredStyle, CSSComputedStyleDeclaration *computedStyle, bool& atStart, Frame::TriState& state)
{
DeprecatedValueListConstIterator<CSSProperty> end;
for (DeprecatedValueListConstIterator<CSSProperty> it = desiredStyle->valuesIterator(); it != end; ++it) {
int propertyID = (*it).id();
String desiredProperty = desiredStyle->getPropertyValue(propertyID);
String 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::selectionListState() const
{
TriState state = falseTriState;
if (!d->m_selection.isRange()) {
Node* selectionNode = d->m_selection.selection().start().node();
if (enclosingList(selectionNode))
return trueTriState;
} else {
//FIXME: Support ranges
}
return state;
}
Frame::TriState Frame::selectionHasStyle(CSSStyleDeclaration *style) const
{
bool atStart = true;
TriState state = falseTriState;
RefPtr<CSSMutableStyleDeclaration> mutableStyle = style->makeMutable();
if (!d->m_selection.isRange()) {
Node* nodeToRemove;
RefPtr<CSSComputedStyleDeclaration> selectionStyle = selectionComputedStyle(nodeToRemove);
if (!selectionStyle)
return falseTriState;
updateState(mutableStyle.get(), selectionStyle.get(), atStart, state);
if (nodeToRemove) {
ExceptionCode ec = 0;
nodeToRemove->remove(ec);
assert(ec == 0);
}
} else {
for (Node* node = d->m_selection.start().node(); node; node = node->traverseNextNode()) {
RefPtr<CSSComputedStyleDeclaration> computedStyle = new CSSComputedStyleDeclaration(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(CSSStyleDeclaration *style) const
{
Node* nodeToRemove;
RefPtr<CSSStyleDeclaration> selectionStyle = selectionComputedStyle(nodeToRemove);
if (!selectionStyle)
return false;
RefPtr<CSSMutableStyleDeclaration> mutableStyle = style->makeMutable();
bool match = true;
DeprecatedValueListConstIterator<CSSProperty> end;
for (DeprecatedValueListConstIterator<CSSProperty> it = mutableStyle->valuesIterator(); it != end; ++it) {
int propertyID = (*it).id();
if (!equalIgnoringCase(mutableStyle->getPropertyValue(propertyID), selectionStyle->getPropertyValue(propertyID))) {
match = false;
break;
}
}
if (nodeToRemove) {
ExceptionCode ec = 0;
nodeToRemove->remove(ec);
assert(ec == 0);
}
return match;
}
String Frame::selectionStartStylePropertyValue(int stylePropertyID) const
{
Node *nodeToRemove;
RefPtr<CSSStyleDeclaration> selectionStyle = selectionComputedStyle(nodeToRemove);
if (!selectionStyle)
return String();
String value = selectionStyle->getPropertyValue(stylePropertyID);
if (nodeToRemove) {
ExceptionCode ec = 0;
nodeToRemove->remove(ec);
assert(ec == 0);
}
return value;
}
CSSComputedStyleDeclaration *Frame::selectionComputedStyle(Node *&nodeToRemove) const
{
nodeToRemove = 0;
if (!document())
return 0;
if (d->m_selection.isNone())
return 0;
RefPtr<Range> range(d->m_selection.toRange());
Position pos = range->editingStartPosition();
Element *elem = pos.element();
if (!elem)
return 0;
RefPtr<Element> styleElement = elem;
ExceptionCode ec = 0;
if (d->m_typingStyle) {
styleElement = document()->createElementNS(xhtmlNamespaceURI, "span", ec);
assert(ec == 0);
styleElement->setAttribute(styleAttr, d->m_typingStyle->cssText().impl(), ec);
assert(ec == 0);
styleElement->appendChild(document()->createEditingTextNode(""), ec);
assert(ec == 0);
if (elem->renderer() && elem->renderer()->canHaveChildren()) {
elem->appendChild(styleElement, ec);
} else {
Node *parent = elem->parent();
Node *next = elem->nextSibling();
if (next) {
parent->insertBefore(styleElement, next, ec);
} else {
parent->appendChild(styleElement, ec);
}
}
assert(ec == 0);
nodeToRemove = styleElement.get();
}
return new CSSComputedStyleDeclaration(styleElement);
}
void Frame::applyEditingStyleToBodyElement() const
{
if (!d->m_doc)
return;
RefPtr<NodeList> list = d->m_doc->getElementsByTagName("body");
unsigned len = list->length();
for (unsigned i = 0; i < len; i++) {
applyEditingStyleToElement(static_cast<Element*>(list->item(i)));
}
}
void Frame::removeEditingStyleFromBodyElement() const
{
if (!d->m_doc)
return;
RefPtr<NodeList> list = d->m_doc->getElementsByTagName("body");
unsigned len = list->length();
for (unsigned i = 0; i < len; i++) {
removeEditingStyleFromElement(static_cast<Element*>(list->item(i)));
}
}
void Frame::applyEditingStyleToElement(Element *element) const
{
if (!element || !element->isHTMLElement())
return;
static_cast<HTMLElement*>(element)->setContentEditable("true");
}
void Frame::removeEditingStyleFromElement(Element *element) const
{
if (!element || !element->isHTMLElement())
return;
static_cast<HTMLElement*>(element)->setContentEditable("false");
}
bool Frame::isCharacterSmartReplaceExempt(const QChar&, bool)
{
// no smart replace
return true;
}
#ifndef NDEBUG
static HashSet<Frame*> lifeSupportSet;
#endif
void Frame::endAllLifeSupport()
{
#ifndef 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();
#ifndef NDEBUG
lifeSupportSet.add(this);
#endif
d->m_lifeSupportTimer.startOneShot(0);
}
void Frame::endLifeSupport()
{
if (!d->m_lifeSupportTimer.isActive())
return;
d->m_lifeSupportTimer.stop();
#ifndef NDEBUG
lifeSupportSet.remove(this);
#endif
deref();
}
void Frame::lifeSupportTimerFired(Timer<Frame>*)
{
#ifndef 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.
Document *doc = document();
if (!doc)
return;
Element *ownerElement = doc->ownerElement();
if (!ownerElement)
return;
Node *ownerElementParent = ownerElement->parentNode();
if (!ownerElementParent)
return;
// This method's purpose is it to make it easier to select iframes (in order to delete them). Don't do anything if the iframe isn't deletable.
if (!ownerElementParent->isContentEditable())
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()
{
Element* owner = ownerElement();
if (!owner || !owner->hasTagName(objectTag))
return;
static_cast<HTMLObjectElement*>(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
{
Document *doc = document();
return doc ? doc->renderer() : 0;
}
Element* Frame::ownerElement()
{
RenderPart* ownerElementRenderer = d->m_ownerRenderer;
if (!ownerElementRenderer)
return 0;
return static_cast<Element*>(ownerElementRenderer->element());
}
RenderPart* Frame::ownerRenderer()
{
return d->m_ownerRenderer;
}
IntRect Frame::selectionRect() const
{
RenderView *root = static_cast<RenderView*>(renderer());
if (!root)
return IntRect();
return root->selectionRect();
}
// returns FloatRect because going through IntRect would truncate any floats
FloatRect Frame::visibleSelectionRect() const
{
if (!d->m_view)
return FloatRect();
return intersection(selectionRect(), d->m_view->visibleContentRect());
}
bool Frame::isFrameSet() const
{
Document* document = d->m_doc.get();
if (!document || !document->isHTMLDocument())
return false;
Node *body = static_cast<HTMLDocument*>(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 (d->m_submittedFormURL == URL)
d->m_submittedFormURL = KURL();
}
// Scans logically forward from "start", including any child frames
static HTMLFormElement *scanForForm(Node *start)
{
Node *n;
for (n = start; n; n = n->traverseNextNode()) {
if (n->hasTagName(formTag))
return static_cast<HTMLFormElement*>(n);
else if (n->isHTMLElement() && static_cast<HTMLElement*>(n)->isGenericFormElement())
return static_cast<HTMLGenericFormElement*>(n)->form();
else if (n->hasTagName(frameTag) || n->hasTagName(iframeTag)) {
Node *childDoc = static_cast<HTMLFrameElement*>(n)->contentDocument();
if (HTMLFormElement *frameResult = scanForForm(childDoc))
return frameResult;
}
}
return 0;
}
// We look for either the form containing the current focus, or for one immediately after it
HTMLFormElement *Frame::currentForm() const
{
// start looking either at the active (first responder) node, or where the selection is
Node *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
Node *n;
for (n = start; n; n = n->parentNode()) {
if (n->hasTagName(formTag))
return static_cast<HTMLFormElement*>(n);
else if (n->isHTMLElement()
&& static_cast<HTMLElement*>(n)->isGenericFormElement())
return static_cast<HTMLGenericFormElement*>(n)->form();
}
// try walking forward in the node tree to find a form element
return start ? scanForForm(start) : 0;
}
void Frame::setEncoding(const DeprecatedString& 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;
}
Node *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;
}
void Frame::handleAutoscroll(RenderLayer* layer)
{
if (d->m_autoscrollTimer.isActive())
return;
d->m_autoscrollLayer = layer;
startAutoscrollTimer();
}
void Frame::autoscrollTimerFired(Timer<Frame>*)
{
if (!d->m_bMousePressed){
stopAutoscrollTimer();
return;
}
if (d->m_autoscrollLayer) {
d->m_autoscrollLayer->autoscroll();
}
}
RenderObject::NodeInfo Frame::nodeInfoAtPoint(const IntPoint& point, bool allowShadowContent)
{
RenderObject::NodeInfo nodeInfo(true, true);
renderer()->layer()->hitTest(nodeInfo, point);
Node *n;
Widget *widget = 0;
IntPoint widgetPoint(point);
while (true) {
n = nodeInfo.innerNode();
if (!n || !n->renderer() || !n->renderer()->isWidget())
break;
widget = static_cast<RenderWidget*>(n->renderer())->widget();
if (!widget || !widget->isFrameView())
break;
Frame* frame = static_cast<HTMLFrameElement*>(n)->contentFrame();
if (!frame || !frame->renderer())
break;
int absX, absY;
n->renderer()->absolutePosition(absX, absY, true);
FrameView *view = static_cast<FrameView*>(widget);
widgetPoint.setX(widgetPoint.x() - absX + view->contentsX());
widgetPoint.setY(widgetPoint.y() - absY + view->contentsY());
RenderObject::NodeInfo widgetNodeInfo(true, true);
frame->renderer()->layer()->hitTest(widgetNodeInfo, widgetPoint);
nodeInfo = widgetNodeInfo;
}
if (!allowShadowContent) {
Node* node = nodeInfo.innerNode();
if (node)
node = node->shadowAncestorNode();
nodeInfo.setInnerNode(node);
node = nodeInfo.innerNonSharedNode();
if (node)
node = node->shadowAncestorNode();
nodeInfo.setInnerNonSharedNode(node);
}
return nodeInfo;
}
bool Frame::hasSelection()
{
if (selection().isNone())
return false;
// If a part has a selection, it should also have a document.
ASSERT(document());
return true;
}
void Frame::startAutoscrollTimer()
{
d->m_autoscrollTimer.startRepeating(autoscrollInterval);
}
void Frame::stopAutoscrollTimer()
{
d->m_autoscrollLayer = 0;
d->m_autoscrollTimer.stop();
}
// FIXME: why is this here instead of on the FrameView?
void Frame::paint(GraphicsContext* p, const IntRect& rect)
{
#ifndef NDEBUG
bool fillWithRed;
if (!document() || document()->printing())
fillWithRed = false; // Printing, don't fill with red (can't remember why).
else if (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 (d->m_drawSelectionOnly)
fillWithRed = false; // Selections are transparent, don't fill with red.
else if (d->m_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()) {
// d->m_elementToDraw is used to draw only one element
RenderObject *eltRenderer = d->m_elementToDraw ? d->m_elementToDraw->renderer() : 0;
renderer()->layer()->paint(p, rect, d->m_drawSelectionOnly, eltRenderer);
#if __APPLE__
// Regions may have changed as a result of the visibility/z-index of element changing.
if (renderer()->document()->dashboardRegionsDirty())
renderer()->view()->frameView()->updateDashboardRegions();
#endif
} else
LOG_ERROR("called Frame::paint with nil renderer");
}
#if __APPLE__
void Frame::adjustPageHeight(float *newBottom, float oldTop, float oldBottom, float bottomLimit)
{
RenderView *root = static_cast<RenderView*>(document()->renderer());
if (root) {
// Use a context with painting disabled.
GraphicsContext context(0);
root->setTruncatedAt((int)floorf(oldBottom));
IntRect dirtyRect(0, (int)floorf(oldTop), root->docWidth(), (int)ceilf(oldBottom - oldTop));
root->layer()->paint(&context, dirtyRect);
*newBottom = root->bestTruncatedAt();
if (*newBottom == 0)
*newBottom = oldBottom;
} else
*newBottom = oldBottom;
}
#endif
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);
Node *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(Node *node)
{
ASSERT_ARG(node, node);
return node->document()->frame();
}
Node* Frame::nodeForWidget(const Widget* widget)
{
ASSERT_ARG(widget, widget);
WidgetClient* client = widget->client();
if (!client)
return 0;
return client->element(const_cast<Widget*>(widget));
}
void Frame::clearDocumentFocus(Widget *widget)
{
Node *node = nodeForWidget(widget);
ASSERT(node);
node->document()->setFocusNode(0);
}
void Frame::updatePolicyBaseURL()
{
if (tree()->parent() && tree()->parent()->document())
setPolicyBaseURL(tree()->parent()->document()->policyBaseURL());
else
setPolicyBaseURL(d->m_url.url());
}
void Frame::setPolicyBaseURL(const String& 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(false);
// 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
RenderView *root = static_cast<RenderView*>(document()->renderer());
if (root) {
// This magic is basically copied from khtmlview::print
int pageW = (int)ceilf(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 = min(rightmostPos, (int)ceilf(maxPageWidth));
root->setWidth(pageW);
root->setNeedsLayoutAndMinMaxRecalc();
forceLayout();
}
}
}
void Frame::sendResizeEvent()
{
if (Document* doc = document())
doc->dispatchWindowEvent(EventNames::resizeEvent, false, false);
}
void Frame::sendScrollEvent()
{
FrameView *v = d->m_view.get();
if (v) {
Document *doc = document();
if (!doc)
return;
doc->dispatchHTMLEvent(scrollEvent, true, false);
}
}
bool Frame::scrollbarsVisible()
{
if (!view())
return false;
if (view()->hScrollBarMode() == ScrollBarAlwaysOff || view()->vScrollBarMode() == ScrollBarAlwaysOff)
return false;
return true;
}
void Frame::addMetaData(const String& key, const String& 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);
clearUndoRedoOperations();
return true;
}
bool Frame::canMouseDownStartSelect(Node* node)
{
if (!node || !node->renderer())
return true;
// Check to see if -webkit-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::handleMouseReleaseDoubleClickEvent(const MouseEventWithHitTestResults& event)
{
passWidgetMouseDownEventToWidget(event, true);
}
bool Frame::passWidgetMouseDownEventToWidget(const MouseEventWithHitTestResults& event, bool isDoubleClick)
{
// Figure out which view to send the event to.
RenderObject *target = event.targetNode() ? event.targetNode()->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()) {
Document* 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(Node *&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;
Node *node = pos.node();
if (!node)
return 0;
if (!d->m_typingStyle)
return node->renderer()->style();
ExceptionCode ec = 0;
RefPtr<Element> styleElement = document()->createElementNS(xhtmlNamespaceURI, "span", ec);
ASSERT(ec == 0);
styleElement->setAttribute(styleAttr, d->m_typingStyle->cssText().impl(), ec);
ASSERT(ec == 0);
styleElement->appendChild(document()->createEditingTextNode(""), ec);
ASSERT(ec == 0);
node->parentNode()->appendChild(styleElement, ec);
ASSERT(ec == 0);
nodeToRemove = styleElement.get();
return styleElement->renderer()->style();
}
void Frame::setMediaType(const String& type)
{
if (d->m_view)
d->m_view->setMediaType(type);
}
void Frame::setSelectionFromNone()
{
// Put a caret inside the body if the entire frame is editable (either the
// entire WebView is editable or designMode is on for this document).
Document *doc = document();
if (!doc || !selection().isNone() || !isContentEditable())
return;
Node* node = doc->documentElement();
while (node && !node->hasTagName(bodyTag))
node = node->traverseNextNode();
if (node)
setSelection(SelectionController(Position(node, 0), DOWNSTREAM));
}
bool Frame::displaysWithFocusAttributes() const
{
return d->m_isFocused;
}
void Frame::setDisplaysWithFocusAttributes(bool flag)
{
if (d->m_isFocused == flag)
return;
d->m_isFocused = flag;
// This method does the job of updating the view based on whether the view is "active".
// This involves three kinds of drawing updates:
// 1. The background color used to draw behind selected content (active | inactive color)
if (d->m_view)
d->m_view->updateContents(enclosingIntRect(visibleSelectionRect()));
// 2. Caret blinking (blinks | does not blink)
if (flag)
setSelectionFromNone();
setCaretVisible(flag);
// 3. The drawing of a focus ring around links in web pages.
Document *doc = document();
if (doc) {
Node *node = doc->focusNode();
if (node) {
node->setChanged();
if (node->renderer() && node->renderer()->style()->hasAppearance())
theme()->stateChanged(node->renderer(), FocusState);
}
}
}
void Frame::setWindowHasFocus(bool flag)
{
if (d->m_windowHasFocus == flag)
return;
d->m_windowHasFocus = flag;
if (Document *doc = document())
doc->dispatchWindowEvent(flag ? focusEvent : blurEvent, false, false);
}
UChar Frame::backslashAsCurrencySymbol() const
{
Document *doc = document();
if (!doc)
return '\\';
Decoder *decoder = doc->decoder();
if (!decoder)
return '\\';
return decoder->encoding().backslashAsCurrencySymbol();
}
bool Frame::markedTextUsesUnderlines() const
{
return d->m_markedTextUsesUnderlines;
}
DeprecatedValueList<MarkedTextUnderline> Frame::markedTextUnderlines() const
{
return d->m_markedTextUnderlines;
}
unsigned Frame::markAllMatchesForText(const String& target, bool caseFlag)
{
if (target.isEmpty())
return 0;
RefPtr<Range> searchRange(rangeOfContents(document()));
int exception = 0;
unsigned matchCount = 0;
do {
RefPtr<Range> resultRange(findPlainText(searchRange.get(), target, true, caseFlag));
if (resultRange->collapsed(exception))
break;
// A non-collapsed result range can in some funky whitespace cases still not
// advance the range's start position (4509328). Break to avoid infinite loop.
VisiblePosition newStart = endVisiblePosition(resultRange.get(), DOWNSTREAM);
if (newStart == startVisiblePosition(searchRange.get(), DOWNSTREAM))
break;
++matchCount;
document()->addMarker(resultRange.get(), DocumentMarker::TextMatch);
setStart(searchRange.get(), newStart);
} while (true);
return matchCount;
}
bool Frame::markedTextMatchesAreHighlighted() const
{
return d->m_highlightTextMatches;
}
void Frame::setMarkedTextMatchesAreHighlighted(bool flag)
{
if (flag == d->m_highlightTextMatches)
return;
d->m_highlightTextMatches = flag;
document()->repaintMarkers(DocumentMarker::TextMatch);
}
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.
d->m_submittedFormURL = KURL();
}
Node *Frame::mousePressNode()
{
return d->m_mousePressNode.get();
}
bool Frame::isComplete() const
{
return d->m_bComplete;
}
bool Frame::isLoadingMainResource() const
{
return d->m_bLoadingMainResource;
}
FrameTree* Frame::tree() const
{
return &d->m_treeNode;
}
DOMWindow* Frame::domWindow() const
{
if (!d->m_domWindow)
d->m_domWindow = new DOMWindow(const_cast<Frame*>(this));
return d->m_domWindow.get();
}
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()
{
Element* 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;
}
void Frame::disconnectOwnerRenderer()
{
d->m_ownerRenderer = 0;
}
String Frame::documentTypeString() const
{
if (Document *doc = document())
if (DocumentType *doctype = doc->realDocType())
return doctype->toString();
return String();
}
bool Frame::containsPlugins() const
{
return d->m_plugins.size() != 0;
}
} // namespace WebCore