blob: 947cb06751e09f13d1a3458e747c2e53afa8a263 [file] [log] [blame]
/*
* 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, 2007, 2008, 2009 Apple Inc. All rights reserved.
* Copyright (C) 2005 Alexey Proskuryakov <ap@nypop.com>
* Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies)
* Copyright (C) 2008 Eric Seidel <eric@webkit.org>
*
* 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., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "config.h"
#include "Frame.h"
#include "ApplyStyleCommand.h"
#include "BeforeUnloadEvent.h"
#include "CSSComputedStyleDeclaration.h"
#include "CSSMutableStyleDeclaration.h"
#include "CSSProperty.h"
#include "CSSPropertyNames.h"
#include "CachedCSSStyleSheet.h"
#include "Chrome.h"
#include "DOMWindow.h"
#include "DocLoader.h"
#include "DocumentType.h"
#include "EditingText.h"
#include "EditorClient.h"
#include "EventNames.h"
#include "FloatQuad.h"
#include "FocusController.h"
#include "FrameLoader.h"
#include "FrameLoaderClient.h"
#include "FrameView.h"
#include "GraphicsContext.h"
#include "HTMLDocument.h"
#include "HTMLFormControlElement.h"
#include "HTMLFormElement.h"
#include "HTMLFrameElementBase.h"
#include "HTMLNames.h"
#include "HTMLTableCellElement.h"
#include "HitTestResult.h"
#include "Logging.h"
#include "MediaFeatureNames.h"
#include "Navigator.h"
#include "NodeList.h"
#include "Page.h"
#include "PageGroup.h"
#include "RegularExpression.h"
#include "RenderPart.h"
#include "RenderTableCell.h"
#include "RenderTextControl.h"
#include "RenderTheme.h"
#include "RenderView.h"
#include "ScriptController.h"
#include "ScriptSourceCode.h"
#include "ScriptValue.h"
#include "Settings.h"
#include "TextIterator.h"
#include "TextResourceDecoder.h"
#include "UserContentURLPattern.h"
#include "XMLNSNames.h"
#include "XMLNames.h"
#include "htmlediting.h"
#include "markup.h"
#include "npruntime_impl.h"
#include "visible_units.h"
#include <wtf/RefCountedLeakCounter.h>
#include <wtf/StdLibExtras.h>
#if PLATFORM(MAC) || (PLATFORM(CHROMIUM) && OS(DARWIN))
#import <Carbon/Carbon.h>
#endif
#if USE(JSC)
#include "JSDOMWindowShell.h"
#include "runtime_root.h"
#endif
#if ENABLE(SVG)
#include "SVGDocument.h"
#include "SVGDocumentExtensions.h"
#include "SVGNames.h"
#include "XLinkNames.h"
#endif
#if PLATFORM(ANDROID)
#include "WebViewCore.h"
#endif
#if ENABLE(WML)
#include "WMLNames.h"
#endif
#if ENABLE(MATHML)
#include "MathMLNames.h"
#endif
using namespace std;
namespace WebCore {
using namespace HTMLNames;
#ifndef NDEBUG
static WTF::RefCountedLeakCounter frameCounter("Frame");
#endif
static inline Frame* parentFromOwnerElement(HTMLFrameOwnerElement* ownerElement)
{
if (!ownerElement)
return 0;
return ownerElement->document()->frame();
}
Frame::Frame(Page* page, HTMLFrameOwnerElement* ownerElement, FrameLoaderClient* frameLoaderClient)
: m_page(page)
, m_treeNode(this, parentFromOwnerElement(ownerElement))
, m_loader(this, frameLoaderClient)
, m_redirectScheduler(this)
, m_ownerElement(ownerElement)
, m_script(this)
, m_selectionGranularity(CharacterGranularity)
, m_selectionController(this)
, m_editor(this)
, m_eventHandler(this)
, m_animationController(this)
, m_lifeSupportTimer(this, &Frame::lifeSupportTimerFired)
#if ENABLE(ORIENTATION_EVENTS)
, m_orientation(0)
#endif
, m_highlightTextMatches(false)
, m_inViewSourceMode(false)
, m_needsReapplyStyles(false)
, m_isDisconnected(false)
, m_excludeFromTextSearch(false)
{
Frame* parent = parentFromOwnerElement(ownerElement);
m_zoomFactor = parent ? parent->m_zoomFactor : 1.0f;
AtomicString::init();
HTMLNames::init();
QualifiedName::init();
MediaFeatureNames::init();
#if ENABLE(SVG)
SVGNames::init();
XLinkNames::init();
#endif
#if ENABLE(WML)
WMLNames::init();
#endif
#if ENABLE(MATHML)
MathMLNames::init();
#endif
XMLNSNames::init();
XMLNames::init();
if (!ownerElement)
page->setMainFrame(this);
else {
page->incrementFrameCount();
// Make sure we will not end up with two frames referencing the same owner element.
ASSERT((!(ownerElement->m_contentFrame)) || (ownerElement->m_contentFrame->ownerElement() != ownerElement));
ownerElement->m_contentFrame = this;
}
#ifndef NDEBUG
frameCounter.increment();
#endif
}
Frame::~Frame()
{
setView(0);
loader()->cancelAndClear();
// FIXME: We should not be doing all this work inside the destructor
ASSERT(!m_lifeSupportTimer.isActive());
#ifndef NDEBUG
frameCounter.decrement();
#endif
disconnectOwnerElement();
if (m_domWindow)
m_domWindow->disconnectFrame();
script()->clearWindowShell();
HashSet<DOMWindow*>::iterator end = m_liveFormerWindows.end();
for (HashSet<DOMWindow*>::iterator it = m_liveFormerWindows.begin(); it != end; ++it)
(*it)->disconnectFrame();
if (m_view) {
m_view->hide();
m_view->clearFrame();
}
ASSERT(!m_lifeSupportTimer.isActive());
}
void Frame::init()
{
m_loader.init();
}
FrameLoader* Frame::loader() const
{
return &m_loader;
}
RedirectScheduler* Frame::redirectScheduler() const
{
return &m_redirectScheduler;
}
FrameView* Frame::view() const
{
return m_view.get();
}
void Frame::setView(PassRefPtr<FrameView> view)
{
// We the custom scroll bars as early as possible to prevent m_doc->detach()
// from messing with the view such that its scroll bars won't be torn down.
// FIXME: We should revisit this.
if (m_view)
m_view->detachCustomScrollbars();
// Detach the document now, so any onUnload handlers get run - if
// we wait until the view is destroyed, then things won't be
// hooked up enough for some JavaScript calls to work.
if (!view && m_doc && m_doc->attached() && !m_doc->inPageCache()) {
// FIXME: We don't call willRemove here. Why is that OK?
m_doc->detach();
if (m_view)
m_view->unscheduleRelayout();
}
eventHandler()->clear();
m_view = view;
// Only one form submission is allowed per view of a part.
// Since this part may be getting reused as a result of being
// pulled from the back/forward cache, reset this flag.
loader()->resetMultipleFormSubmissionProtection();
}
ScriptController* Frame::script()
{
return &m_script;
}
Document* Frame::document() const
{
return m_doc.get();
}
void Frame::setDocument(PassRefPtr<Document> newDoc)
{
if (m_doc && m_doc->attached() && !m_doc->inPageCache()) {
// FIXME: We don't call willRemove here. Why is that OK?
m_doc->detach();
}
m_doc = newDoc;
if (m_doc && selection()->isFocusedAndActive())
setUseSecureKeyboardEntry(m_doc->useSecureKeyboardEntryWhenActive());
if (m_doc && !m_doc->attached())
m_doc->attach();
// Update the cached 'document' property, which is now stale.
m_script.updateDocument();
}
#if ENABLE(ORIENTATION_EVENTS)
void Frame::sendOrientationChangeEvent(int orientation)
{
m_orientation = orientation;
if (Document* doc = document())
doc->dispatchWindowEvent(Event::create(eventNames().orientationchangeEvent, false, false));
}
#endif // ENABLE(ORIENTATION_EVENTS)
Settings* Frame::settings() const
{
return m_page ? m_page->settings() : 0;
}
String Frame::selectedText() const
{
return plainText(selection()->toNormalizedRange().get());
}
IntRect Frame::firstRectForRange(Range* range) const
{
int extraWidthToEndOfLine = 0;
ExceptionCode ec = 0;
ASSERT(range->startContainer(ec));
ASSERT(range->endContainer(ec));
InlineBox* startInlineBox;
int startCaretOffset;
range->startPosition().getInlineBoxAndOffset(DOWNSTREAM, startInlineBox, startCaretOffset);
RenderObject* startRenderer = range->startContainer(ec)->renderer();
IntRect startCaretRect = startRenderer->localCaretRect(startInlineBox, startCaretOffset, &extraWidthToEndOfLine);
if (startCaretRect != IntRect())
startCaretRect = startRenderer->localToAbsoluteQuad(FloatRect(startCaretRect)).enclosingBoundingBox();
InlineBox* endInlineBox;
int endCaretOffset;
range->endPosition().getInlineBoxAndOffset(UPSTREAM, endInlineBox, endCaretOffset);
RenderObject* endRenderer = range->endContainer(ec)->renderer();
IntRect endCaretRect = endRenderer->localCaretRect(endInlineBox, endCaretOffset);
if (endCaretRect != IntRect())
endCaretRect = endRenderer->localToAbsoluteQuad(FloatRect(endCaretRect)).enclosingBoundingBox();
if (startCaretRect.y() == endCaretRect.y()) {
// start and end are on the same line
return IntRect(min(startCaretRect.x(), endCaretRect.x()),
startCaretRect.y(),
abs(endCaretRect.x() - startCaretRect.x()),
max(startCaretRect.height(), endCaretRect.height()));
}
// start and end aren't on the same line, so go from start to the end of its line
return IntRect(startCaretRect.x(),
startCaretRect.y(),
startCaretRect.width() + extraWidthToEndOfLine,
startCaretRect.height());
}
SelectionController* Frame::selection() const
{
return &m_selectionController;
}
Editor* Frame::editor() const
{
return &m_editor;
}
TextGranularity Frame::selectionGranularity() const
{
return m_selectionGranularity;
}
void Frame::setSelectionGranularity(TextGranularity granularity)
{
m_selectionGranularity = granularity;
}
SelectionController* Frame::dragCaretController() const
{
return m_page->dragCaretController();
}
AnimationController* Frame::animation() const
{
return &m_animationController;
}
static RegularExpression* createRegExpForLabels(const Vector<String>& labels)
{
// REVIEW- version of this call in FrameMac.mm caches based on the NSArray ptrs being
// the same across calls. We can't do that.
DEFINE_STATIC_LOCAL(RegularExpression, wordRegExp, ("\\w", TextCaseSensitive));
String pattern("(");
unsigned int numLabels = labels.size();
unsigned int i;
for (i = 0; i < numLabels; i++) {
String label = labels[i];
bool startsWithWordChar = false;
bool endsWithWordChar = false;
if (label.length()) {
startsWithWordChar = wordRegExp.match(label.substring(0, 1)) >= 0;
endsWithWordChar = wordRegExp.match(label.substring(label.length() - 1, 1)) >= 0;
}
if (i)
pattern.append("|");
// Search for word boundaries only if label starts/ends with "word characters".
// If we always searched for word boundaries, this wouldn't work for languages
// such as Japanese.
if (startsWithWordChar)
pattern.append("\\b");
pattern.append(label);
if (endsWithWordChar)
pattern.append("\\b");
}
pattern.append(")");
return new RegularExpression(pattern, TextCaseInsensitive);
}
String Frame::searchForLabelsAboveCell(RegularExpression* regExp, HTMLTableCellElement* cell, size_t* resultDistanceFromStartOfCell)
{
RenderObject* cellRenderer = cell->renderer();
if (cellRenderer && cellRenderer->isTableCell()) {
RenderTableCell* tableCellRenderer = toRenderTableCell(cellRenderer);
RenderTableCell* cellAboveRenderer = tableCellRenderer->table()->cellAbove(tableCellRenderer);
if (cellAboveRenderer) {
HTMLTableCellElement* aboveCell =
static_cast<HTMLTableCellElement*>(cellAboveRenderer->node());
if (aboveCell) {
// search within the above cell we found for a match
size_t lengthSearched = 0;
for (Node* n = aboveCell->firstChild(); n; n = n->traverseNextNode(aboveCell)) {
if (n->isTextNode() && n->renderer() && n->renderer()->style()->visibility() == VISIBLE) {
// For each text chunk, run the regexp
String nodeString = n->nodeValue();
int pos = regExp->searchRev(nodeString);
if (pos >= 0) {
if (resultDistanceFromStartOfCell)
*resultDistanceFromStartOfCell = lengthSearched;
return nodeString.substring(pos, regExp->matchedLength());
}
lengthSearched += nodeString.length();
}
}
}
}
}
// Any reason in practice to search all cells in that are above cell?
if (resultDistanceFromStartOfCell)
*resultDistanceFromStartOfCell = notFound;
return String();
}
String Frame::searchForLabelsBeforeElement(const Vector<String>& labels, Element* element, size_t* resultDistance, bool* resultIsInCellAbove)
{
OwnPtr<RegularExpression> regExp(createRegExpForLabels(labels));
// We stop searching after we've seen this many chars
const unsigned int charsSearchedThreshold = 500;
// This is the absolute max we search. We allow a little more slop than
// charsSearchedThreshold, to make it more likely that we'll search whole nodes.
const unsigned int maxCharsSearched = 600;
// If the starting element is within a table, the cell that contains it
HTMLTableCellElement* startingTableCell = 0;
bool searchedCellAbove = false;
if (resultDistance)
*resultDistance = notFound;
if (resultIsInCellAbove)
*resultIsInCellAbove = false;
// walk backwards in the node tree, until another element, or form, or end of tree
int unsigned lengthSearched = 0;
Node* n;
for (n = element->traversePreviousNode();
n && lengthSearched < charsSearchedThreshold;
n = n->traversePreviousNode())
{
if (n->hasTagName(formTag)
|| (n->isHTMLElement() && static_cast<Element*>(n)->isFormControlElement()))
{
// We hit another form element or the start of the form - bail out
break;
} else if (n->hasTagName(tdTag) && !startingTableCell) {
startingTableCell = static_cast<HTMLTableCellElement*>(n);
} else if (n->hasTagName(trTag) && startingTableCell) {
String result = searchForLabelsAboveCell(regExp.get(), startingTableCell, resultDistance);
if (!result.isEmpty()) {
if (resultIsInCellAbove)
*resultIsInCellAbove = true;
return result;
}
searchedCellAbove = true;
} else if (n->isTextNode() && n->renderer() && n->renderer()->style()->visibility() == VISIBLE) {
// For each text chunk, run the regexp
String nodeString = n->nodeValue();
// add 100 for slop, to make it more likely that we'll search whole nodes
if (lengthSearched + nodeString.length() > maxCharsSearched)
nodeString = nodeString.right(charsSearchedThreshold - lengthSearched);
int pos = regExp->searchRev(nodeString);
if (pos >= 0) {
if (resultDistance)
*resultDistance = lengthSearched;
return nodeString.substring(pos, regExp->matchedLength());
}
lengthSearched += nodeString.length();
}
}
// If we started in a cell, but bailed because we found the start of the form or the
// previous element, we still might need to search the row above us for a label.
if (startingTableCell && !searchedCellAbove) {
String result = searchForLabelsAboveCell(regExp.get(), startingTableCell, resultDistance);
if (!result.isEmpty()) {
if (resultIsInCellAbove)
*resultIsInCellAbove = true;
return result;
}
}
return String();
}
static String matchLabelsAgainstString(const Vector<String>& labels, const String& stringToMatch)
{
if (stringToMatch.isEmpty())
return String();
String mutableStringToMatch = stringToMatch;
// Make numbers and _'s in field names behave like word boundaries, e.g., "address2"
replace(mutableStringToMatch, RegularExpression("\\d", TextCaseSensitive), " ");
mutableStringToMatch.replace('_', ' ');
OwnPtr<RegularExpression> regExp(createRegExpForLabels(labels));
// Use the largest match we can find in the whole string
int pos;
int length;
int bestPos = -1;
int bestLength = -1;
int start = 0;
do {
pos = regExp->match(mutableStringToMatch, start);
if (pos != -1) {
length = regExp->matchedLength();
if (length >= bestLength) {
bestPos = pos;
bestLength = length;
}
start = pos + 1;
}
} while (pos != -1);
if (bestPos != -1)
return mutableStringToMatch.substring(bestPos, bestLength);
return String();
}
String Frame::matchLabelsAgainstElement(const Vector<String>& labels, Element* element)
{
// Match against the name element, then against the id element if no match is found for the name element.
// See 7538330 for one popular site that benefits from the id element check.
// FIXME: This code is mirrored in FrameMac.mm. It would be nice to make the Mac code call the platform-agnostic
// code, which would require converting the NSArray of NSStrings to a Vector of Strings somewhere along the way.
String resultFromNameAttribute = matchLabelsAgainstString(labels, element->getAttribute(nameAttr));
if (!resultFromNameAttribute.isEmpty())
return resultFromNameAttribute;
return matchLabelsAgainstString(labels, element->getAttribute(idAttr));
}
const VisibleSelection& Frame::mark() const
{
return m_mark;
}
void Frame::setMark(const VisibleSelection& 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());
m_mark = s;
}
void Frame::notifyRendererOfSelectionChange(bool userTriggered)
{
RenderObject* renderer = 0;
if (selection()->rootEditableElement())
renderer = selection()->rootEditableElement()->shadowAncestorNode()->renderer();
// If the current selection is in a textfield or textarea, notify the renderer that the selection has changed
if (renderer && renderer->isTextControl())
toRenderTextControl(renderer)->selectionChanged(userTriggered);
}
// 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 = toRenderWidget(renderer)->widget();
return widget && widget->isFrameView();
}
void Frame::setFocusedNodeIfNeeded()
{
if (selection()->isNone() || !selection()->isFocused())
return;
bool caretBrowsing = settings() && settings()->caretBrowsingEnabled();
if (caretBrowsing) {
Node* anchor = enclosingAnchorElement(selection()->base());
if (anchor) {
page()->focusController()->setFocusedNode(anchor, this);
return;
}
}
Node* target = selection()->rootEditableElement();
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)) {
page()->focusController()->setFocusedNode(target, this);
return;
}
renderer = renderer->parent();
if (renderer)
target = renderer->node();
}
document()->setFocusedNode(0);
}
if (caretBrowsing)
page()->focusController()->setFocusedNode(0, this);
}
void Frame::paintDragCaret(GraphicsContext* p, int tx, int ty, const IntRect& clipRect) const
{
#if ENABLE(TEXT_CARET)
SelectionController* dragCaretController = m_page->dragCaretController();
ASSERT(dragCaretController->selection().isCaret());
if (dragCaretController->selection().start().node()->document()->frame() == this)
dragCaretController->paintCaret(p, tx, ty, clipRect);
#endif
}
float Frame::zoomFactor() const
{
return m_zoomFactor;
}
bool Frame::isZoomFactorTextOnly() const
{
return m_page->settings()->zoomsTextOnly();
}
bool Frame::shouldApplyTextZoom() const
{
if (m_zoomFactor == 1.0f || !isZoomFactorTextOnly())
return false;
#if ENABLE(SVG)
if (m_doc->isSVGDocument())
return false;
#endif
return true;
}
bool Frame::shouldApplyPageZoom() const
{
if (m_zoomFactor == 1.0f || isZoomFactorTextOnly())
return false;
#if ENABLE(SVG)
if (m_doc->isSVGDocument())
return false;
#endif
return true;
}
void Frame::setZoomFactor(float percent, bool isTextOnly)
{
if (m_zoomFactor == percent && isZoomFactorTextOnly() == isTextOnly)
return;
#if ENABLE(SVG)
// SVG doesn't care if the zoom factor is text only. It will always apply a
// zoom to the whole SVG.
if (m_doc->isSVGDocument()) {
if (!static_cast<SVGDocument*>(m_doc.get())->zoomAndPanEnabled())
return;
m_zoomFactor = percent;
m_page->settings()->setZoomsTextOnly(true); // We do this to avoid doing any scaling of CSS pixels, since the SVG has its own notion of zoom.
if (m_doc->renderer())
m_doc->renderer()->repaint();
return;
}
#endif
if (!isTextOnly) {
// Update the scroll position when doing a full page zoom, so the content stays in relatively the same position.
IntPoint scrollPosition = view()->scrollPosition();
float percentDifference = (percent / m_zoomFactor);
view()->setScrollPosition(IntPoint(scrollPosition.x() * percentDifference, scrollPosition.y() * percentDifference));
}
m_zoomFactor = percent;
m_page->settings()->setZoomsTextOnly(isTextOnly);
m_doc->recalcStyle(Node::Force);
for (Frame* child = tree()->firstChild(); child; child = child->tree()->nextSibling())
child->setZoomFactor(m_zoomFactor, isTextOnly);
if (m_doc->renderer() && m_doc->renderer()->needsLayout() && view()->didFirstLayout())
view()->layout();
}
void Frame::setPrinting(bool printing, float minPageWidth, float maxPageWidth, bool adjustViewSize)
{
m_doc->setPrinting(printing);
view()->setMediaType(printing ? "print" : "screen");
m_doc->updateStyleSelector();
view()->forceLayoutWithPageWidthRange(minPageWidth, maxPageWidth, adjustViewSize);
for (Frame* child = tree()->firstChild(); child; child = child->tree()->nextSibling())
child->setPrinting(printing, minPageWidth, maxPageWidth, adjustViewSize);
}
void Frame::setJSStatusBarText(const String& text)
{
ASSERT(m_doc); // Client calls shouldn't be made when the frame is in inconsistent state.
m_kjsStatusBarText = text;
if (m_page)
m_page->chrome()->setStatusbarText(this, m_kjsStatusBarText);
}
void Frame::setJSDefaultStatusBarText(const String& text)
{
ASSERT(m_doc); // Client calls shouldn't be made when the frame is in inconsistent state.
m_kjsDefaultStatusBarText = text;
if (m_page)
m_page->chrome()->setStatusbarText(this, m_kjsDefaultStatusBarText);
}
String Frame::jsStatusBarText() const
{
return m_kjsStatusBarText;
}
String Frame::jsDefaultStatusBarText() const
{
return m_kjsDefaultStatusBarText;
}
void Frame::setNeedsReapplyStyles()
{
// When the frame is not showing web content, it doesn't make sense to apply styles.
// If we tried, we'd end up doing things with the document, but the document, if one
// exists, is not currently shown and should be in the page cache.
if (!m_loader.client()->hasHTMLView())
return;
if (m_needsReapplyStyles)
return;
m_needsReapplyStyles = true;
// FrameView's "layout" timer includes reapplyStyles, so despite its
// name, it's what we want to call here.
if (view())
view()->scheduleRelayout();
}
bool Frame::needsReapplyStyles() const
{
return m_needsReapplyStyles;
}
void Frame::reapplyStyles()
{
m_needsReapplyStyles = false;
// FIXME: This call doesn't really make sense in a function called reapplyStyles.
// We should probably eventually move it into its own function.
m_doc->docLoader()->setAutoLoadImages(m_page && m_page->settings()->loadsImagesAutomatically());
// 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://bugs.webkit.org/show_bug.cgi?id=8079>.
m_doc->updateStyleSelector();
}
void Frame::injectUserScripts(UserScriptInjectionTime injectionTime)
{
if (!m_page)
return;
// Walk the hashtable. Inject by world.
const UserScriptMap* userScripts = m_page->group().userScripts();
if (!userScripts)
return;
UserScriptMap::const_iterator end = userScripts->end();
for (UserScriptMap::const_iterator it = userScripts->begin(); it != end; ++it)
injectUserScriptsForWorld(it->first.get(), *it->second, injectionTime);
}
void Frame::injectUserScriptsForWorld(DOMWrapperWorld* world, const UserScriptVector& userScripts, UserScriptInjectionTime injectionTime)
{
if (userScripts.isEmpty())
return;
Document* doc = document();
if (!doc)
return;
Vector<ScriptSourceCode> sourceCode;
unsigned count = userScripts.size();
for (unsigned i = 0; i < count; ++i) {
UserScript* script = userScripts[i].get();
if (script->injectionTime() == injectionTime && UserContentURLPattern::matchesPatterns(doc->url(), script->whitelist(), script->blacklist()))
m_script.evaluateInWorld(ScriptSourceCode(script->source(), script->url()), world);
}
}
bool Frame::shouldChangeSelection(const VisibleSelection& newSelection) const
{
return shouldChangeSelection(selection()->selection(), newSelection, newSelection.affinity(), false);
}
bool Frame::shouldChangeSelection(const VisibleSelection& oldSelection, const VisibleSelection& newSelection, EAffinity affinity, bool stillSelecting) const
{
return editor()->client()->shouldChangeSelectedRange(oldSelection.toNormalizedRange().get(), newSelection.toNormalizedRange().get(),
affinity, stillSelecting);
}
bool Frame::shouldDeleteSelection(const VisibleSelection& selection) const
{
return editor()->client()->shouldDeleteRange(selection.toNormalizedRange().get());
}
bool Frame::isContentEditable() const
{
if (m_editor.clientIsEditable())
return true;
return m_doc->inDesignMode();
}
#if PLATFORM(MAC) || (PLATFORM(CHROMIUM) && OS(DARWIN))
const short enableRomanKeyboardsOnly = -23;
#endif
void Frame::setUseSecureKeyboardEntry(bool enable)
{
#if PLATFORM(MAC) || (PLATFORM(CHROMIUM) && OS(DARWIN))
if (enable == IsSecureEventInputEnabled())
return;
if (enable) {
EnableSecureEventInput();
#ifdef BUILDING_ON_TIGER
KeyScript(enableRomanKeyboardsOnly);
#else
// WebKit substitutes nil for input context when in password field, which corresponds to null TSMDocument. So, there is
// no need to call TSMGetActiveDocument(), which may return an incorrect result when selection hasn't been yet updated
// after focusing a node.
CFArrayRef inputSources = TISCreateASCIICapableInputSourceList();
TSMSetDocumentProperty(0, kTSMDocumentEnabledInputSourcesPropertyTag, sizeof(CFArrayRef), &inputSources);
CFRelease(inputSources);
#endif
} else {
DisableSecureEventInput();
#ifdef BUILDING_ON_TIGER
KeyScript(smKeyEnableKybds);
#else
TSMRemoveDocumentProperty(0, kTSMDocumentEnabledInputSourcesPropertyTag);
#endif
}
#endif
}
void Frame::updateSecureKeyboardEntryIfActive()
{
if (selection()->isFocusedAndActive())
setUseSecureKeyboardEntry(m_doc->useSecureKeyboardEntryWhenActive());
}
CSSMutableStyleDeclaration *Frame::typingStyle() const
{
return m_typingStyle.get();
}
void Frame::setTypingStyle(CSSMutableStyleDeclaration *style)
{
m_typingStyle = style;
}
void Frame::clearTypingStyle()
{
m_typingStyle = 0;
}
void Frame::computeAndSetTypingStyle(CSSStyleDeclaration *style, EditAction editingAction)
{
if (!style || !style->length()) {
clearTypingStyle();
return;
}
// Calculate the current typing style.
RefPtr<CSSMutableStyleDeclaration> mutableStyle = style->makeMutable();
if (typingStyle()) {
typingStyle()->merge(mutableStyle.get());
mutableStyle = typingStyle();
}
RefPtr<CSSValue> unicodeBidi;
RefPtr<CSSValue> direction;
if (editingAction == EditActionSetWritingDirection) {
unicodeBidi = mutableStyle->getPropertyCSSValue(CSSPropertyUnicodeBidi);
direction = mutableStyle->getPropertyCSSValue(CSSPropertyDirection);
}
Node* node = selection()->selection().visibleStart().deepEquivalent().node();
computedStyle(node)->diff(mutableStyle.get());
if (editingAction == EditActionSetWritingDirection && unicodeBidi) {
ASSERT(unicodeBidi->isPrimitiveValue());
mutableStyle->setProperty(CSSPropertyUnicodeBidi, static_cast<CSSPrimitiveValue*>(unicodeBidi.get())->getIdent());
if (direction) {
ASSERT(direction->isPrimitiveValue());
mutableStyle->setProperty(CSSPropertyDirection, static_cast<CSSPrimitiveValue*>(direction.get())->getIdent());
}
}
// Handle block styles, substracting these from the typing style.
RefPtr<CSSMutableStyleDeclaration> blockStyle = mutableStyle->copyBlockProperties();
blockStyle->diff(mutableStyle.get());
if (blockStyle->length() > 0)
applyCommand(ApplyStyleCommand::create(document(), blockStyle.get(), editingAction));
// Set the remaining style as the typing style.
m_typingStyle = mutableStyle.release();
}
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);
}
return value;
}
PassRefPtr<CSSComputedStyleDeclaration> Frame::selectionComputedStyle(Node*& nodeToRemove) const
{
nodeToRemove = 0;
if (selection()->isNone())
return 0;
RefPtr<Range> range(selection()->toNormalizedRange());
Position pos = range->editingStartPosition();
Element *elem = pos.element();
if (!elem)
return 0;
RefPtr<Element> styleElement = elem;
ExceptionCode ec = 0;
if (m_typingStyle) {
styleElement = document()->createElement(spanTag, false);
styleElement->setAttribute(styleAttr, m_typingStyle->cssText().impl(), ec);
ASSERT(!ec);
styleElement->appendChild(document()->createEditingTextNode(""), ec);
ASSERT(!ec);
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);
nodeToRemove = styleElement.get();
}
return computedStyle(styleElement.release());
}
void Frame::textFieldDidBeginEditing(Element* e)
{
if (editor()->client())
editor()->client()->textFieldDidBeginEditing(e);
}
void Frame::textFieldDidEndEditing(Element* e)
{
if (editor()->client())
editor()->client()->textFieldDidEndEditing(e);
}
void Frame::textDidChangeInTextField(Element* e)
{
if (editor()->client())
editor()->client()->textDidChangeInTextField(e);
}
bool Frame::doTextFieldCommandFromEvent(Element* e, KeyboardEvent* ke)
{
if (editor()->client())
return editor()->client()->doTextFieldCommandFromEvent(e, ke);
return false;
}
void Frame::textWillBeDeletedInTextField(Element* input)
{
if (editor()->client())
editor()->client()->textWillBeDeletedInTextField(input);
}
void Frame::textDidChangeInTextArea(Element* e)
{
if (editor()->client())
editor()->client()->textDidChangeInTextArea(e);
}
void Frame::applyEditingStyleToBodyElement() const
{
RefPtr<NodeList> list = 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
{
RefPtr<NodeList> list = 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)
return;
CSSStyleDeclaration* style = element->style();
ASSERT(style);
ExceptionCode ec = 0;
style->setProperty(CSSPropertyWordWrap, "break-word", false, ec);
ASSERT(!ec);
style->setProperty(CSSPropertyWebkitNbspMode, "space", false, ec);
ASSERT(!ec);
style->setProperty(CSSPropertyWebkitLineBreak, "after-white-space", false, ec);
ASSERT(!ec);
}
void Frame::removeEditingStyleFromElement(Element*) const
{
}
#ifndef NDEBUG
static HashSet<Frame*>& keepAliveSet()
{
DEFINE_STATIC_LOCAL(HashSet<Frame*>, staticKeepAliveSet, ());
return staticKeepAliveSet;
}
#endif
void Frame::keepAlive()
{
if (m_lifeSupportTimer.isActive())
return;
#ifndef NDEBUG
keepAliveSet().add(this);
#endif
ref();
m_lifeSupportTimer.startOneShot(0);
}
#ifndef NDEBUG
void Frame::cancelAllKeepAlive()
{
HashSet<Frame*>::iterator end = keepAliveSet().end();
for (HashSet<Frame*>::iterator it = keepAliveSet().begin(); it != end; ++it) {
Frame* frame = *it;
frame->m_lifeSupportTimer.stop();
frame->deref();
}
keepAliveSet().clear();
}
#endif
void Frame::lifeSupportTimerFired(Timer<Frame>*)
{
#ifndef NDEBUG
keepAliveSet().remove(this);
#endif
deref();
}
void Frame::clearDOMWindow()
{
if (m_domWindow) {
m_liveFormerWindows.add(m_domWindow.get());
m_domWindow->clear();
}
m_domWindow = 0;
}
RenderView* Frame::contentRenderer() const
{
Document* doc = document();
if (!doc)
return 0;
RenderObject* object = doc->renderer();
if (!object)
return 0;
ASSERT(object->isRenderView());
return toRenderView(object);
}
HTMLFrameOwnerElement* Frame::ownerElement() const
{
return m_ownerElement;
}
RenderPart* Frame::ownerRenderer() const
{
HTMLFrameOwnerElement* ownerElement = m_ownerElement;
if (!ownerElement)
return 0;
RenderObject* object = ownerElement->renderer();
if (!object)
return 0;
// FIXME: If <object> is ever fixed to disassociate itself from frames
// that it has started but canceled, then this can turn into an ASSERT
// since m_ownerElement would be 0 when the load is canceled.
// https://bugs.webkit.org/show_bug.cgi?id=18585
if (!object->isRenderPart())
return 0;
return toRenderPart(object);
}
bool Frame::isDisconnected() const
{
return m_isDisconnected;
}
void Frame::setIsDisconnected(bool isDisconnected)
{
m_isDisconnected = isDisconnected;
}
bool Frame::excludeFromTextSearch() const
{
return m_excludeFromTextSearch;
}
void Frame::setExcludeFromTextSearch(bool exclude)
{
m_excludeFromTextSearch = exclude;
}
// returns FloatRect because going through IntRect would truncate any floats
FloatRect Frame::selectionBounds(bool clipToVisibleContent) const
{
RenderView* root = contentRenderer();
FrameView* view = m_view.get();
if (!root || !view)
return IntRect();
IntRect selectionRect = root->selectionBounds(clipToVisibleContent);
return clipToVisibleContent ? intersection(selectionRect, view->visibleContentRect()) : selectionRect;
}
void Frame::selectionTextRects(Vector<FloatRect>& rects, SelectionRectRespectTransforms respectTransforms, bool clipToVisibleContent) const
{
RenderView* root = contentRenderer();
if (!root)
return;
RefPtr<Range> selectedRange = selection()->toNormalizedRange();
FloatRect visibleContentRect = m_view->visibleContentRect();
// FIMXE: we are appending empty rects to the list for those that fall outside visibleContentRect.
// We may not want to do that.
if (respectTransforms) {
Vector<FloatQuad> quads;
selectedRange->textQuads(quads, true);
unsigned size = quads.size();
for (unsigned i = 0; i < size; ++i) {
IntRect currRect = quads[i].enclosingBoundingBox();
if (clipToVisibleContent)
rects.append(intersection(currRect, visibleContentRect));
else
rects.append(currRect);
}
} else {
Vector<IntRect> intRects;
selectedRange->textRects(intRects, true);
unsigned size = intRects.size();
for (unsigned i = 0; i < size; ++i) {
if (clipToVisibleContent)
rects.append(intersection(intRects[i], visibleContentRect));
else
rects.append(intRects[i]);
}
}
}
// 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<Element*>(n)->isFormControlElement())
return static_cast<HTMLFormControlElement*>(n)->form();
else if (n->hasTagName(frameTag) || n->hasTagName(iframeTag)) {
Node *childDoc = static_cast<HTMLFrameElementBase*>(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 = m_doc ? m_doc->focusedNode() : 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<Element*>(n)->isFormControlElement())
return static_cast<HTMLFormControlElement*>(n)->form();
}
// try walking forward in the node tree to find a form element
return start ? scanForForm(start) : 0;
}
void Frame::revealSelection(const ScrollAlignment& alignment, bool revealExtent)
{
IntRect rect;
switch (selection()->selectionType()) {
case VisibleSelection::NoSelection:
return;
case VisibleSelection::CaretSelection:
rect = selection()->absoluteCaretBounds();
break;
case VisibleSelection::RangeSelection:
rect = revealExtent ? VisiblePosition(selection()->extent()).absoluteCaretBounds() : enclosingIntRect(selectionBounds(false));
break;
}
Position start = selection()->start();
ASSERT(start.node());
if (start.node() && start.node()->renderer()) {
// FIXME: This code only handles scrolling the startContainer's layer, but
// the selection rect could intersect more than just that.
// See <rdar://problem/4799899>.
if (RenderLayer* layer = start.node()->renderer()->enclosingLayer())
layer->scrollRectToVisible(rect, false, alignment, alignment);
}
}
Frame* Frame::frameForWidget(const Widget* widget)
{
ASSERT_ARG(widget, widget);
if (RenderWidget* renderer = RenderWidget::find(widget))
if (Node* node = renderer->node())
return node->document()->frame();
// Assume all widgets are either a FrameView or owned by a RenderWidget.
// FIXME: That assumption is not right for scroll bars!
ASSERT(widget->isFrameView());
return static_cast<const FrameView*>(widget)->frame();
}
void Frame::clearTimers(FrameView *view, Document *document)
{
if (view) {
view->unscheduleRelayout();
if (view->frame()) {
view->frame()->animation()->suspendAnimations(document);
view->frame()->eventHandler()->stopAutoscrollTimer();
}
}
}
void Frame::clearTimers()
{
clearTimers(m_view.get(), document());
}
RenderStyle *Frame::styleForSelectionStart(Node *&nodeToRemove) const
{
nodeToRemove = 0;
if (selection()->isNone())
return 0;
Position pos = selection()->selection().visibleStart().deepEquivalent();
if (!pos.isCandidate())
return 0;
Node *node = pos.node();
if (!node)
return 0;
if (!m_typingStyle)
return node->renderer()->style();
RefPtr<Element> styleElement = document()->createElement(spanTag, false);
ExceptionCode ec = 0;
String styleText = m_typingStyle->cssText() + " display: inline";
styleElement->setAttribute(styleAttr, styleText.impl(), ec);
ASSERT(!ec);
styleElement->appendChild(document()->createEditingTextNode(""), ec);
ASSERT(!ec);
node->parentNode()->appendChild(styleElement, ec);
ASSERT(!ec);
nodeToRemove = styleElement.get();
return styleElement->renderer() ? styleElement->renderer()->style() : 0;
}
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();
bool caretBrowsing = settings() && settings()->caretBrowsingEnabled();
if (!selection()->isNone() || !(isContentEditable() || caretBrowsing))
return;
Node* node = doc->documentElement();
while (node && !node->hasTagName(bodyTag))
node = node->traverseNextNode();
if (node)
selection()->setSelection(VisibleSelection(Position(node, 0), DOWNSTREAM));
}
bool Frame::inViewSourceMode() const
{
return m_inViewSourceMode;
}
void Frame::setInViewSourceMode(bool mode)
{
m_inViewSourceMode = mode;
}
// Searches from the beginning of the document if nothing is selected.
bool Frame::findString(const String& target, bool forward, bool caseFlag, bool wrapFlag, bool startInSelection)
{
if (target.isEmpty())
return false;
if (excludeFromTextSearch())
return false;
// Start from an edge of the selection, if there's a selection that's not in shadow content. Which edge
// is used depends on whether we're searching forward or backward, and whether startInSelection is set.
RefPtr<Range> searchRange(rangeOfContents(document()));
VisibleSelection selection = this->selection()->selection();
if (forward)
setStart(searchRange.get(), startInSelection ? selection.visibleStart() : selection.visibleEnd());
else
setEnd(searchRange.get(), startInSelection ? selection.visibleEnd() : selection.visibleStart());
Node* shadowTreeRoot = selection.shadowTreeRootNode();
if (shadowTreeRoot) {
ExceptionCode ec = 0;
if (forward)
searchRange->setEnd(shadowTreeRoot, shadowTreeRoot->childNodeCount(), ec);
else
searchRange->setStart(shadowTreeRoot, 0, ec);
}
RefPtr<Range> resultRange(findPlainText(searchRange.get(), target, forward, caseFlag));
// If we started in the selection and the found range exactly matches the existing selection, find again.
// Build a selection with the found range to remove collapsed whitespace.
// Compare ranges instead of selection objects to ignore the way that the current selection was made.
if (startInSelection && *VisibleSelection(resultRange.get()).toNormalizedRange() == *selection.toNormalizedRange()) {
searchRange = rangeOfContents(document());
if (forward)
setStart(searchRange.get(), selection.visibleEnd());
else
setEnd(searchRange.get(), selection.visibleStart());
if (shadowTreeRoot) {
ExceptionCode ec = 0;
if (forward)
searchRange->setEnd(shadowTreeRoot, shadowTreeRoot->childNodeCount(), ec);
else
searchRange->setStart(shadowTreeRoot, 0, ec);
}
resultRange = findPlainText(searchRange.get(), target, forward, caseFlag);
}
ExceptionCode exception = 0;
// If nothing was found in the shadow tree, search in main content following the shadow tree.
if (resultRange->collapsed(exception) && shadowTreeRoot) {
searchRange = rangeOfContents(document());
if (forward)
searchRange->setStartAfter(shadowTreeRoot->shadowParentNode(), exception);
else
searchRange->setEndBefore(shadowTreeRoot->shadowParentNode(), exception);
resultRange = findPlainText(searchRange.get(), target, forward, caseFlag);
}
if (!editor()->insideVisibleArea(resultRange.get())) {
resultRange = editor()->nextVisibleRange(resultRange.get(), target, forward, caseFlag, wrapFlag);
if (!resultRange)
return false;
}
// If we didn't find anything and we're wrapping, search again in the entire document (this will
// redundantly re-search the area already searched in some cases).
if (resultRange->collapsed(exception) && wrapFlag) {
searchRange = rangeOfContents(document());
resultRange = findPlainText(searchRange.get(), target, forward, caseFlag);
// We used to return false here if we ended up with the same range that we started with
// (e.g., the selection was already the only instance of this text). But we decided that
// this should be a success case instead, so we'll just fall through in that case.
}
if (resultRange->collapsed(exception))
return false;
this->selection()->setSelection(VisibleSelection(resultRange.get(), DOWNSTREAM));
revealSelection();
return true;
}
unsigned Frame::markAllMatchesForText(const String& target, bool caseFlag, unsigned limit)
{
if (target.isEmpty())
return 0;
RefPtr<Range> searchRange(rangeOfContents(document()));
ExceptionCode exception = 0;
unsigned matchCount = 0;
do {
RefPtr<Range> resultRange(findPlainText(searchRange.get(), target, true, caseFlag));
if (resultRange->collapsed(exception)) {
if (!resultRange->startContainer()->isInShadowTree())
break;
searchRange = rangeOfContents(document());
searchRange->setStartAfter(resultRange->startContainer()->shadowAncestorNode(), exception);
continue;
}
// Only treat the result as a match if it is visible
if (editor()->insideVisibleArea(resultRange.get())) {
++matchCount;
document()->addMarker(resultRange.get(), DocumentMarker::TextMatch);
}
// Stop looking if we hit the specified limit. A limit of 0 means no limit.
if (limit > 0 && matchCount >= limit)
break;
// Set the new start for the search range to be the end of the previous
// result range. There is no need to use a VisiblePosition here,
// since findPlainText will use a TextIterator to go over the visible
// text nodes.
searchRange->setStart(resultRange->endContainer(exception), resultRange->endOffset(exception), exception);
Node* shadowTreeRoot = searchRange->shadowTreeRootNode();
if (searchRange->collapsed(exception) && shadowTreeRoot)
searchRange->setEnd(shadowTreeRoot, shadowTreeRoot->childNodeCount(), exception);
} while (true);
// Do a "fake" paint in order to execute the code that computes the rendered rect for
// each text match.
Document* doc = document();
if (m_view && contentRenderer()) {
doc->updateLayout(); // Ensure layout is up to date.
IntRect visibleRect = m_view->visibleContentRect();
if (!visibleRect.isEmpty()) {
GraphicsContext context((PlatformGraphicsContext*)0);
context.setPaintingDisabled(true);
m_view->paintContents(&context, visibleRect);
}
}
return matchCount;
}
bool Frame::markedTextMatchesAreHighlighted() const
{
return m_highlightTextMatches;
}
void Frame::setMarkedTextMatchesAreHighlighted(bool flag)
{
if (flag == m_highlightTextMatches)
return;
m_highlightTextMatches = flag;
document()->repaintMarkers(DocumentMarker::TextMatch);
}
FrameTree* Frame::tree() const
{
return &m_treeNode;
}
void Frame::setDOMWindow(DOMWindow* domWindow)
{
if (m_domWindow) {
m_liveFormerWindows.add(m_domWindow.get());
m_domWindow->clear();
}
m_domWindow = domWindow;
}
DOMWindow* Frame::domWindow() const
{
if (!m_domWindow)
m_domWindow = DOMWindow::create(const_cast<Frame*>(this));
return m_domWindow.get();
}
void Frame::clearFormerDOMWindow(DOMWindow* window)
{
m_liveFormerWindows.remove(window);
}
Page* Frame::page() const
{
return m_page;
}
void Frame::detachFromPage()
{
m_page = 0;
}
EventHandler* Frame::eventHandler() const
{
return &m_eventHandler;
}
void Frame::pageDestroyed()
{
if (Frame* parent = tree()->parent())
parent->loader()->checkLoadComplete();
// FIXME: It's unclear as to why this is called more than once, but it is,
// so page() could be NULL.
if (page() && page()->focusController()->focusedFrame() == this)
page()->focusController()->setFocusedFrame(0);
script()->clearWindowShell();
script()->clearScriptObjects();
script()->updatePlatformScriptObjects();
detachFromPage();
}
void Frame::disconnectOwnerElement()
{
if (m_ownerElement) {
if (Document* doc = document())
doc->clearAXObjectCache();
m_ownerElement->m_contentFrame = 0;
if (m_page)
m_page->decrementFrameCount();
}
m_ownerElement = 0;
}
String Frame::documentTypeString() const
{
if (DocumentType* doctype = document()->doctype())
return createMarkup(doctype);
return String();
}
void Frame::focusWindow()
{
if (!page())
return;
// If we're a top level window, bring the window to the front.
if (!tree()->parent())
#ifdef ANDROID_USER_GESTURE
// FrameLoader::isProcessingUserGesture() will be false when a
// different frame tries to focus this frame through javascript.
page()->chrome()->focus(m_loader.isProcessingUserGesture());
#else
page()->chrome()->focus();
#endif
eventHandler()->focusDocumentView();
}
void Frame::unfocusWindow()
{
if (!page())
return;
// If we're a top level window, deactivate the window.
if (!tree()->parent())
page()->chrome()->unfocus();
}
bool Frame::shouldClose()
{
Chrome* chrome = page() ? page()->chrome() : 0;
if (!chrome || !chrome->canRunBeforeUnloadConfirmPanel())
return true;
if (!m_domWindow)
return true;
RefPtr<Document> doc = document();
HTMLElement* body = doc->body();
if (!body)
return true;
RefPtr<BeforeUnloadEvent> beforeUnloadEvent = BeforeUnloadEvent::create();
m_domWindow->dispatchEvent(beforeUnloadEvent.get(), m_domWindow->document());
if (!beforeUnloadEvent->defaultPrevented())
doc->defaultEventHandler(beforeUnloadEvent.get());
if (beforeUnloadEvent->result().isNull())
return true;
String text = doc->displayStringModifiedByEncoding(beforeUnloadEvent->result());
return chrome->runBeforeUnloadConfirmPanel(text, this);
}
void Frame::scheduleClose()
{
if (!shouldClose())
return;
Chrome* chrome = page() ? page()->chrome() : 0;
if (chrome)
chrome->closeWindowSoon();
}
void Frame::respondToChangedSelection(const VisibleSelection& oldSelection, bool closeTyping)
{
bool isContinuousSpellCheckingEnabled = editor()->isContinuousSpellCheckingEnabled();
bool isContinuousGrammarCheckingEnabled = isContinuousSpellCheckingEnabled && editor()->isGrammarCheckingEnabled();
if (isContinuousSpellCheckingEnabled) {
VisibleSelection newAdjacentWords;
VisibleSelection newSelectedSentence;
bool caretBrowsing = settings() && settings()->caretBrowsingEnabled();
if (selection()->selection().isContentEditable() || caretBrowsing) {
VisiblePosition newStart(selection()->selection().visibleStart());
newAdjacentWords = VisibleSelection(startOfWord(newStart, LeftWordIfOnBoundary), endOfWord(newStart, RightWordIfOnBoundary));
if (isContinuousGrammarCheckingEnabled)
newSelectedSentence = VisibleSelection(startOfSentence(newStart), endOfSentence(newStart));
}
// When typing we check spelling elsewhere, so don't redo it here.
// If this is a change in selection resulting from a delete operation,
// oldSelection may no longer be in the document.
if (closeTyping && oldSelection.isContentEditable() && oldSelection.start().node() && oldSelection.start().node()->inDocument()) {
VisiblePosition oldStart(oldSelection.visibleStart());
VisibleSelection oldAdjacentWords = VisibleSelection(startOfWord(oldStart, LeftWordIfOnBoundary), endOfWord(oldStart, RightWordIfOnBoundary));
if (oldAdjacentWords != newAdjacentWords) {
if (isContinuousGrammarCheckingEnabled) {
VisibleSelection oldSelectedSentence = VisibleSelection(startOfSentence(oldStart), endOfSentence(oldStart));
editor()->markMisspellingsAndBadGrammar(oldAdjacentWords, oldSelectedSentence != newSelectedSentence, oldSelectedSentence);
} else
editor()->markMisspellingsAndBadGrammar(oldAdjacentWords, false, oldAdjacentWords);
}
}
// This only erases markers that are in the first unit (word or sentence) of the selection.
// Perhaps peculiar, but it matches AppKit.
if (RefPtr<Range> wordRange = newAdjacentWords.toNormalizedRange())
document()->removeMarkers(wordRange.get(), DocumentMarker::Spelling);
if (RefPtr<Range> sentenceRange = newSelectedSentence.toNormalizedRange())
document()->removeMarkers(sentenceRange.get(), DocumentMarker::Grammar);
}
// When continuous spell checking is off, existing markers disappear after the selection changes.
if (!isContinuousSpellCheckingEnabled)
document()->removeMarkers(DocumentMarker::Spelling);
if (!isContinuousGrammarCheckingEnabled)
document()->removeMarkers(DocumentMarker::Grammar);
editor()->respondToChangedSelection(oldSelection);
}
VisiblePosition Frame::visiblePositionForPoint(const IntPoint& framePoint)
{
HitTestResult result = eventHandler()->hitTestResultAtPoint(framePoint, true);
Node* node = result.innerNode();
if (!node)
return VisiblePosition();
RenderObject* renderer = node->renderer();
if (!renderer)
return VisiblePosition();
VisiblePosition visiblePos = renderer->positionForPoint(result.localPoint());
if (visiblePos.isNull())
visiblePos = VisiblePosition(Position(node, 0));
return visiblePos;
}
Document* Frame::documentAtPoint(const IntPoint& point)
{
if (!view())
return 0;
IntPoint pt = view()->windowToContents(point);
HitTestResult result = HitTestResult(pt);
if (contentRenderer())
result = eventHandler()->hitTestResultAtPoint(pt, false);
return result.innerNode() ? result.innerNode()->document() : 0;
}
void Frame::createView(const IntSize& viewportSize,
const Color& backgroundColor, bool transparent,
const IntSize& fixedLayoutSize, bool useFixedLayout,
ScrollbarMode horizontalScrollbarMode, ScrollbarMode verticalScrollbarMode)
{
ASSERT(this);
ASSERT(m_page);
bool isMainFrame = this == m_page->mainFrame();
if (isMainFrame && view())
view()->setParentVisible(false);
setView(0);
RefPtr<FrameView> frameView;
if (isMainFrame) {
frameView = FrameView::create(this, viewportSize);
frameView->setFixedLayoutSize(fixedLayoutSize);
frameView->setUseFixedLayout(useFixedLayout);
} else
frameView = FrameView::create(this);
frameView->setScrollbarModes(horizontalScrollbarMode, verticalScrollbarMode);
setView(frameView);
if (backgroundColor.isValid())
frameView->updateBackgroundRecursively(backgroundColor, transparent);
if (isMainFrame)
frameView->setParentVisible(true);
if (ownerRenderer())
ownerRenderer()->setWidget(frameView);
if (HTMLFrameOwnerElement* owner = ownerElement())
view()->setCanHaveScrollbars(owner->scrollingMode() != ScrollbarAlwaysOff);
}
} // namespace WebCore