| Index: third_party/WebKit/WebCore/page/Frame.cpp
|
| ===================================================================
|
| --- third_party/WebKit/WebCore/page/Frame.cpp (revision 9391)
|
| +++ third_party/WebKit/WebCore/page/Frame.cpp (working copy)
|
| @@ -1,1835 +1,1831 @@
|
| -/*
|
| - * 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 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 "CSSProperty.h"
|
| -#include "CSSPropertyNames.h"
|
| -#include "CachedCSSStyleSheet.h"
|
| -#include "DOMWindow.h"
|
| -#include "DocLoader.h"
|
| -#include "DocumentType.h"
|
| -#include "EditingText.h"
|
| -#include "EditorClient.h"
|
| -#include "EventNames.h"
|
| -#include "FocusController.h"
|
| -#include "FloatQuad.h"
|
| -#include "FrameLoader.h"
|
| -#include "FrameView.h"
|
| -#include "GraphicsContext.h"
|
| -#include "HTMLDocument.h"
|
| -#include "HTMLFormElement.h"
|
| -#include "HTMLFrameElementBase.h"
|
| -#include "HTMLFormControlElement.h"
|
| -#include "HTMLNames.h"
|
| -#include "HTMLTableCellElement.h"
|
| -#include "HitTestResult.h"
|
| -#include "Logging.h"
|
| -#include "markup.h"
|
| -#include "MediaFeatureNames.h"
|
| -#include "Navigator.h"
|
| -#include "NodeList.h"
|
| -#include "Page.h"
|
| -#include "RegularExpression.h"
|
| -#include "RenderPart.h"
|
| -#include "RenderTableCell.h"
|
| -#include "RenderTextControl.h"
|
| -#include "RenderTheme.h"
|
| -#include "RenderView.h"
|
| -#include "ScriptController.h"
|
| -#include "Settings.h"
|
| -#include "TextIterator.h"
|
| -#include "TextResourceDecoder.h"
|
| -#include "XMLNames.h"
|
| -#include "ScriptController.h"
|
| -#include "npruntime_impl.h"
|
| -#include "visible_units.h"
|
| -#include <wtf/RefCountedLeakCounter.h>
|
| -#include <wtf/StdLibExtras.h>
|
| -
|
| -#if USE(JSC)
|
| -#include "JSDOMWindowShell.h"
|
| -#include "runtime_root.h"
|
| -#endif
|
| -
|
| -#if FRAME_LOADS_USER_STYLESHEET
|
| -#include "UserStyleSheetLoader.h"
|
| -#endif
|
| -
|
| -#if ENABLE(SVG)
|
| -#include "SVGDocument.h"
|
| -#include "SVGDocumentExtensions.h"
|
| -#include "SVGNames.h"
|
| -#include "XLinkNames.h"
|
| -#endif
|
| -
|
| -#if ENABLE(WML)
|
| -#include "WMLNames.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_ownerElement(ownerElement)
|
| - , m_script(this)
|
| - , m_selectionGranularity(CharacterGranularity)
|
| - , m_selectionController(this)
|
| - , m_caretBlinkTimer(this, &Frame::caretBlinkTimerFired)
|
| - , m_editor(this)
|
| - , m_eventHandler(this)
|
| - , m_animationController(this)
|
| - , m_lifeSupportTimer(this, &Frame::lifeSupportTimerFired)
|
| - , m_caretVisible(false)
|
| - , m_caretPaint(true)
|
| - , m_highlightTextMatches(false)
|
| - , m_inViewSourceMode(false)
|
| - , m_needsReapplyStyles(false)
|
| - , m_isDisconnected(false)
|
| - , m_excludeFromTextSearch(false)
|
| -#if FRAME_LOADS_USER_STYLESHEET
|
| - , m_userStyleSheetLoader(0)
|
| -#endif
|
| -{
|
| - 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
|
| -
|
| - 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()->clearRecordedFormValues();
|
| - loader()->cancelAndClear();
|
| -
|
| - // FIXME: We should not be doing all this work inside the destructor
|
| -
|
| - ASSERT(!m_lifeSupportTimer.isActive());
|
| -
|
| -#ifndef NDEBUG
|
| - frameCounter.decrement();
|
| -#endif
|
| -
|
| -#if USE(JSC)
|
| - if (m_script.haveWindowShell())
|
| - m_script.windowShell()->disconnectFrame();
|
| -#elif USE(V8)
|
| - m_script.disconnectFrame();
|
| -#endif
|
| -
|
| - disconnectOwnerElement();
|
| -
|
| - if (m_domWindow)
|
| - m_domWindow->disconnectFrame();
|
| -
|
| - 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());
|
| -
|
| -#if FRAME_LOADS_USER_STYLESHEET
|
| - delete m_userStyleSheetLoader;
|
| -#endif
|
| -}
|
| -
|
| -void Frame::init()
|
| -{
|
| - m_loader.init();
|
| -}
|
| -
|
| -FrameLoader* Frame::loader() const
|
| -{
|
| - return &m_loader;
|
| -}
|
| -
|
| -FrameView* Frame::view() const
|
| -{
|
| - return m_view.get();
|
| -}
|
| -
|
| -void Frame::setView(FrameView* view)
|
| -{
|
| - // 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();
|
| -}
|
| -
|
| -Settings* Frame::settings() const
|
| -{
|
| - return m_page ? m_page->settings() : 0;
|
| -}
|
| -
|
| -String Frame::selectedText() const
|
| -{
|
| - return plainText(selection()->toRange().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() != 0) {
|
| - startsWithWordChar = wordRegExp.match(label.substring(0, 1)) >= 0;
|
| - endsWithWordChar = wordRegExp.match(label.substring(label.length() - 1, 1)) >= 0;
|
| - }
|
| -
|
| - if (i != 0)
|
| - 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)
|
| -{
|
| - RenderTableCell* cellRenderer = static_cast<RenderTableCell*>(cell->renderer());
|
| -
|
| - if (cellRenderer && cellRenderer->isTableCell()) {
|
| - RenderTableCell* cellAboveRenderer = cellRenderer->table()->cellAbove(cellRenderer);
|
| -
|
| - if (cellAboveRenderer) {
|
| - HTMLTableCellElement* aboveCell =
|
| - static_cast<HTMLTableCellElement*>(cellAboveRenderer->element());
|
| -
|
| - if (aboveCell) {
|
| - // search within the above cell we found for a match
|
| - 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)
|
| - return nodeString.substring(pos, regExp->matchedLength());
|
| - }
|
| - }
|
| - }
|
| - }
|
| - }
|
| - // Any reason in practice to search all cells in that are above cell?
|
| - return String();
|
| -}
|
| -
|
| -String Frame::searchForLabelsBeforeElement(const Vector<String>& labels, Element* element)
|
| -{
|
| - 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;
|
| -
|
| - // 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);
|
| - if (!result.isEmpty())
|
| - 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)
|
| - 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) {
|
| - return searchForLabelsAboveCell(regExp.get(), startingTableCell);
|
| - }
|
| - return String();
|
| -}
|
| -
|
| -String Frame::matchLabelsAgainstElement(const Vector<String>& labels, Element* element)
|
| -{
|
| - String name = element->getAttribute(nameAttr);
|
| - if (name.isEmpty())
|
| - return String();
|
| -
|
| - // Make numbers and _'s in field names behave like word boundaries, e.g., "address2"
|
| - replace(name, RegularExpression("\\d", TextCaseSensitive), " ");
|
| - name.replace('_', ' ');
|
| -
|
| - OwnPtr<RegularExpression> regExp(createRegExpForLabels(labels));
|
| - // Use the largest match we can find in the whole name string
|
| - int pos;
|
| - int length;
|
| - int bestPos = -1;
|
| - int bestLength = -1;
|
| - int start = 0;
|
| - do {
|
| - pos = regExp->match(name, start);
|
| - if (pos != -1) {
|
| - length = regExp->matchedLength();
|
| - if (length >= bestLength) {
|
| - bestPos = pos;
|
| - bestLength = length;
|
| - }
|
| - start = pos + 1;
|
| - }
|
| - } while (pos != -1);
|
| -
|
| - if (bestPos != -1)
|
| - return name.substring(bestPos, bestLength);
|
| - return String();
|
| -}
|
| -
|
| -const Selection& Frame::mark() const
|
| -{
|
| - return 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());
|
| -
|
| - 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->isTextArea() || renderer->isTextField()))
|
| - static_cast<RenderTextControl*>(renderer)->selectionChanged(userTriggered);
|
| -}
|
| -
|
| -void Frame::invalidateSelection()
|
| -{
|
| - selection()->setNeedsLayout();
|
| - selectionLayoutChanged();
|
| -}
|
| -
|
| -void Frame::setCaretVisible(bool flag)
|
| -{
|
| - if (m_caretVisible == flag)
|
| - return;
|
| - clearCaretRectIfNeeded();
|
| - m_caretVisible = flag;
|
| - selectionLayoutChanged();
|
| -}
|
| -
|
| -void Frame::clearCaretRectIfNeeded()
|
| -{
|
| -#if ENABLE(TEXT_CARET)
|
| - if (m_caretPaint) {
|
| - m_caretPaint = false;
|
| - selection()->invalidateCaretRect();
|
| - }
|
| -#endif
|
| -}
|
| -
|
| -// 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::setFocusedNodeIfNeeded()
|
| -{
|
| - if (!document() || selection()->isNone() || !selection()->isFocusedAndActive())
|
| - 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->element();
|
| - }
|
| - document()->setFocusedNode(0);
|
| - }
|
| -}
|
| -
|
| -void Frame::selectionLayoutChanged()
|
| -{
|
| - bool caretRectChanged = selection()->recomputeCaretRect();
|
| -
|
| -#if ENABLE(TEXT_CARET)
|
| - bool shouldBlink = m_caretVisible
|
| - && selection()->isCaret() && selection()->isContentEditable();
|
| -
|
| - // If the caret moved, stop the blink timer so we can restart with a
|
| - // black caret in the new location.
|
| - if (caretRectChanged || !shouldBlink)
|
| - m_caretBlinkTimer.stop();
|
| -
|
| - // Start blinking with a black caret. Be sure not to restart if we're
|
| - // already blinking in the right location.
|
| - if (shouldBlink && !m_caretBlinkTimer.isActive()) {
|
| - if (double blinkInterval = theme()->caretBlinkInterval())
|
| - m_caretBlinkTimer.startRepeating(blinkInterval);
|
| -
|
| - if (!m_caretPaint) {
|
| - m_caretPaint = true;
|
| - selection()->invalidateCaretRect();
|
| - }
|
| - }
|
| -#else
|
| - if (!caretRectChanged)
|
| - return;
|
| -#endif
|
| -
|
| - RenderView* view = contentRenderer();
|
| - if (!view)
|
| - return;
|
| -
|
| - Selection selection = this->selection()->selection();
|
| -
|
| - if (!selection.isRange())
|
| - view->clearSelection();
|
| - else {
|
| - // Use the rightmost candidate for the start of the selection, and the leftmost candidate for the end of the selection.
|
| - // Example: foo <a>bar</a>. Imagine that a line wrap occurs after 'foo', and that 'bar' is selected. If we pass [foo, 3]
|
| - // as the start of the selection, the selection painting code will think that content on the line containing 'foo' is selected
|
| - // and will fill the gap before 'bar'.
|
| - Position startPos = selection.start();
|
| - if (startPos.downstream().isCandidate())
|
| - startPos = startPos.downstream();
|
| - Position endPos = selection.end();
|
| - if (endPos.upstream().isCandidate())
|
| - endPos = endPos.upstream();
|
| -
|
| - // We can get into a state where the selection endpoints map to the same VisiblePosition when a selection is deleted
|
| - // because we don't yet notify the SelectionController of text removal.
|
| - if (startPos.isNotNull() && endPos.isNotNull() && selection.visibleStart() != selection.visibleEnd()) {
|
| - RenderObject *startRenderer = startPos.node()->renderer();
|
| - RenderObject *endRenderer = endPos.node()->renderer();
|
| - view->setSelection(startRenderer, startPos.offset(), endRenderer, endPos.offset());
|
| - }
|
| - }
|
| -}
|
| -
|
| -void Frame::caretBlinkTimerFired(Timer<Frame>*)
|
| -{
|
| -#if ENABLE(TEXT_CARET)
|
| - ASSERT(m_caretVisible);
|
| - ASSERT(selection()->isCaret());
|
| - bool caretPaint = m_caretPaint;
|
| - if (selection()->isCaretBlinkingSuspended() && caretPaint)
|
| - return;
|
| - m_caretPaint = !caretPaint;
|
| - selection()->invalidateCaretRect();
|
| -#endif
|
| -}
|
| -
|
| -void Frame::paintCaret(GraphicsContext* p, int tx, int ty, const IntRect& clipRect) const
|
| -{
|
| -#if ENABLE(TEXT_CARET)
|
| - if (m_caretPaint && m_caretVisible)
|
| - selection()->paintCaret(p, tx, ty, clipRect);
|
| -#endif
|
| -}
|
| -
|
| -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 && 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 && 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 && 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
|
| -
|
| - m_zoomFactor = percent;
|
| - m_page->settings()->setZoomsTextOnly(isTextOnly);
|
| -
|
| - if (m_doc)
|
| - m_doc->recalcStyle(Node::Force);
|
| -
|
| - for (Frame* child = tree()->firstChild(); child; child = child->tree()->nextSibling())
|
| - child->setZoomFactor(m_zoomFactor, isTextOnly);
|
| -
|
| - if (m_doc && m_doc->renderer() && m_doc->renderer()->needsLayout() && view()->didFirstLayout())
|
| - view()->layout();
|
| -}
|
| -
|
| -void Frame::setPrinting(bool printing, float minPageWidth, float maxPageWidth, bool adjustViewSize)
|
| -{
|
| - if (!m_doc)
|
| - return;
|
| -
|
| - 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)
|
| -{
|
| - m_kjsStatusBarText = text;
|
| - if (m_page)
|
| - m_page->chrome()->setStatusbarText(this, m_kjsStatusBarText);
|
| -}
|
| -
|
| -void Frame::setJSDefaultStatusBarText(const String& text)
|
| -{
|
| - 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()
|
| -{
|
| - 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 method called
|
| - // "reapplyStyles". We should probably eventually move it into its own
|
| - // method.
|
| - if (m_doc)
|
| - m_doc->docLoader()->setAutoLoadImages(m_page && m_page->settings()->loadsImagesAutomatically());
|
| -
|
| -#if FRAME_LOADS_USER_STYLESHEET
|
| - const KURL userStyleSheetLocation = m_page ? m_page->settings()->userStyleSheetLocation() : KURL();
|
| - if (!userStyleSheetLocation.isEmpty())
|
| - setUserStyleSheetLocation(userStyleSheetLocation);
|
| - else
|
| - setUserStyleSheet(String());
|
| -#endif
|
| -
|
| - // 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>.
|
| - if (m_doc)
|
| - m_doc->updateStyleSelector();
|
| -}
|
| -
|
| -bool Frame::shouldChangeSelection(const Selection& newSelection) const
|
| -{
|
| - return shouldChangeSelection(selection()->selection(), newSelection, newSelection.affinity(), false);
|
| -}
|
| -
|
| -bool Frame::shouldChangeSelection(const Selection& oldSelection, const Selection& newSelection, EAffinity affinity, bool stillSelecting) const
|
| -{
|
| - return editor()->client()->shouldChangeSelectedRange(oldSelection.toRange().get(), newSelection.toRange().get(),
|
| - affinity, stillSelecting);
|
| -}
|
| -
|
| -bool Frame::shouldDeleteSelection(const Selection& selection) const
|
| -{
|
| - return editor()->client()->shouldDeleteRange(selection.toRange().get());
|
| -}
|
| -
|
| -bool Frame::isContentEditable() const
|
| -{
|
| - if (m_editor.clientIsEditable())
|
| - return true;
|
| - if (!m_doc)
|
| - return false;
|
| - return m_doc->inDesignMode();
|
| -}
|
| -
|
| -#if !PLATFORM(MAC)
|
| -
|
| -void Frame::setUseSecureKeyboardEntry(bool)
|
| -{
|
| -}
|
| -
|
| -#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() == 0) {
|
| - 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 (document() && 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 == 0);
|
| - }
|
| -
|
| - return value;
|
| -}
|
| -
|
| -PassRefPtr<CSSComputedStyleDeclaration> Frame::selectionComputedStyle(Node*& nodeToRemove) const
|
| -{
|
| - nodeToRemove = 0;
|
| -
|
| - if (!document())
|
| - return 0;
|
| -
|
| - if (selection()->isNone())
|
| - return 0;
|
| -
|
| - RefPtr<Range> range(selection()->toRange());
|
| - Position pos = range->editingStartPosition();
|
| -
|
| - Element *elem = pos.element();
|
| - if (!elem)
|
| - return 0;
|
| -
|
| - RefPtr<Element> styleElement = elem;
|
| - ExceptionCode ec = 0;
|
| -
|
| - if (m_typingStyle) {
|
| - styleElement = document()->createElementNS(xhtmlNamespaceURI, "span", ec);
|
| - ASSERT(ec == 0);
|
| -
|
| - styleElement->setAttribute(styleAttr, 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 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
|
| -{
|
| - if (!m_doc)
|
| - return;
|
| -
|
| - 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
|
| -{
|
| - if (!m_doc)
|
| - return;
|
| -
|
| - 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 == 0);
|
| - style->setProperty(CSSPropertyWebkitNbspMode, "space", false, ec);
|
| - ASSERT(ec == 0);
|
| - style->setProperty(CSSPropertyWebkitLineBreak, "after-white-space", false, ec);
|
| - ASSERT(ec == 0);
|
| -}
|
| -
|
| -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;
|
| -#if USE(V8)
|
| - m_script.clearPluginObjects();
|
| -#endif
|
| -}
|
| -
|
| -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 static_cast<RenderPart*>(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, bool clipToVisibleContent) const
|
| -{
|
| - RenderView* root = contentRenderer();
|
| - if (!root)
|
| - return;
|
| -
|
| - RefPtr<Range> selectedRange = selection()->toRange();
|
| -
|
| - Vector<IntRect> intRects;
|
| - selectedRange->addLineBoxRects(intRects, true);
|
| -
|
| - unsigned size = intRects.size();
|
| - FloatRect visibleContentRect = m_view->visibleContentRect();
|
| - 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;
|
| -}
|
| -
|
| -// FIXME: should this go in SelectionController?
|
| -void Frame::revealSelection(const RenderLayer::ScrollAlignment& alignment) const
|
| -{
|
| - IntRect rect;
|
| -
|
| - switch (selection()->state()) {
|
| - case Selection::NONE:
|
| - return;
|
| -
|
| - case Selection::CARET:
|
| - rect = selection()->absoluteCaretBounds();
|
| - break;
|
| -
|
| - case Selection::RANGE:
|
| - rect = 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);
|
| - }
|
| -}
|
| -
|
| -void Frame::revealCaret(const RenderLayer::ScrollAlignment& alignment) const
|
| -{
|
| - if (selection()->isNone())
|
| - return;
|
| -
|
| - Position extent = selection()->extent();
|
| - if (extent.node() && extent.node()->renderer()) {
|
| - IntRect extentRect = VisiblePosition(extent).absoluteCaretBounds();
|
| - RenderLayer* layer = extent.node()->renderer()->enclosingLayer();
|
| - if (layer)
|
| - layer->scrollRectToVisible(extentRect, 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()) {
|
| - if (document && document->renderer() && document->renderer()->hasLayer())
|
| - document->renderView()->layer()->suspendMarquees();
|
| - 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 (!document())
|
| - return 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();
|
| -
|
| - ExceptionCode ec = 0;
|
| - RefPtr<Element> styleElement = document()->createElementNS(xhtmlNamespaceURI, "span", ec);
|
| - ASSERT(ec == 0);
|
| -
|
| - String styleText = m_typingStyle->cssText() + " display: inline";
|
| - styleElement->setAttribute(styleAttr, styleText.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() ? 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();
|
| - if (!doc || !selection()->isNone() || !isContentEditable())
|
| - return;
|
| -
|
| - Node* node = doc->documentElement();
|
| - while (node && !node->hasTagName(bodyTag))
|
| - node = node->traverseNextNode();
|
| - if (node)
|
| - selection()->setSelection(Selection(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() || !document())
|
| - 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()));
|
| - Selection 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 && *Selection(resultRange.get()).toRange() == *selection.toRange()) {
|
| - 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(Selection(resultRange.get(), DOWNSTREAM));
|
| - revealSelection();
|
| - return true;
|
| -}
|
| -
|
| -unsigned Frame::markAllMatchesForText(const String& target, bool caseFlag, unsigned limit)
|
| -{
|
| - if (target.isEmpty() || !document())
|
| - 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;
|
| - }
|
| -
|
| - // 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;
|
| -
|
| - // 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;
|
| -
|
| - setStart(searchRange.get(), newStart);
|
| - 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 (doc && 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 || !document())
|
| - 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;
|
| -}
|
| -
|
| -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);
|
| -
|
| -#if USE(JSC)
|
| - // TODO(fqian): Unfork this change. It is a temporary workaround
|
| - // for this merge to pass layout tests. Once the merge is landed
|
| - // in the trunk, I am going to unfork this change and fix the issue
|
| - // in the binding code.
|
| - script()->clearWindowShell();
|
| -#endif
|
| -
|
| - // This will stop any JS timers
|
| -#if USE(JSC)
|
| - if (script()->haveWindowShell())
|
| - script()->windowShell()->disconnectFrame();
|
| -#elif USE(V8)
|
| - script()->disconnectFrame();
|
| -#endif
|
| -
|
| - script()->clearScriptObjects();
|
| - script()->updatePlatformScriptObjects();
|
| -
|
| - m_page = 0;
|
| -}
|
| -
|
| -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 (Document* doc = document()) {
|
| - if (DocumentType* doctype = doc->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())
|
| - page()->chrome()->focus();
|
| -
|
| - 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;
|
| -
|
| - RefPtr<Document> doc = document();
|
| - if (!doc)
|
| - return true;
|
| - HTMLElement* body = doc->body();
|
| - if (!body)
|
| - return true;
|
| -
|
| - loader()->setFiringUnloadEvents(true);
|
| -
|
| - RefPtr<BeforeUnloadEvent> beforeUnloadEvent = BeforeUnloadEvent::create();
|
| - beforeUnloadEvent->setTarget(doc);
|
| - doc->handleWindowEvent(beforeUnloadEvent.get(), false);
|
| -
|
| - if (!beforeUnloadEvent->defaultPrevented() && doc)
|
| - doc->defaultEventHandler(beforeUnloadEvent.get());
|
| -
|
| - loader()->setFiringUnloadEvents(false);
|
| -
|
| - 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 Selection& oldSelection, bool closeTyping)
|
| -{
|
| - if (document()) {
|
| - bool isContinuousSpellCheckingEnabled = editor()->isContinuousSpellCheckingEnabled();
|
| - bool isContinuousGrammarCheckingEnabled = isContinuousSpellCheckingEnabled && editor()->isGrammarCheckingEnabled();
|
| - if (isContinuousSpellCheckingEnabled) {
|
| - Selection newAdjacentWords;
|
| - Selection newSelectedSentence;
|
| - if (selection()->selection().isContentEditable()) {
|
| - VisiblePosition newStart(selection()->selection().visibleStart());
|
| - newAdjacentWords = Selection(startOfWord(newStart, LeftWordIfOnBoundary), endOfWord(newStart, RightWordIfOnBoundary));
|
| - if (isContinuousGrammarCheckingEnabled)
|
| - newSelectedSentence = Selection(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());
|
| - Selection oldAdjacentWords = Selection(startOfWord(oldStart, LeftWordIfOnBoundary), endOfWord(oldStart, RightWordIfOnBoundary));
|
| - if (oldAdjacentWords != newAdjacentWords) {
|
| - editor()->markMisspellings(oldAdjacentWords);
|
| - if (isContinuousGrammarCheckingEnabled) {
|
| - Selection oldSelectedSentence = Selection(startOfSentence(oldStart), endOfSentence(oldStart));
|
| - if (oldSelectedSentence != newSelectedSentence)
|
| - editor()->markBadGrammar(oldSelectedSentence);
|
| - }
|
| - }
|
| - }
|
| -
|
| - // 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.toRange())
|
| - document()->removeMarkers(wordRange.get(), DocumentMarker::Spelling);
|
| - if (RefPtr<Range> sentenceRange = newSelectedSentence.toRange())
|
| - 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->positionForCoordinates(result.localPoint().x(), result.localPoint().y());
|
| - 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);
|
| -
|
| - FrameView* frameView;
|
| - if (isMainFrame) {
|
| - frameView = new FrameView(this, viewportSize);
|
| - frameView->setFixedLayoutSize(fixedLayoutSize);
|
| - frameView->setUseFixedLayout(useFixedLayout);
|
| - } else
|
| - frameView = new FrameView(this);
|
| -
|
| - frameView->setScrollbarModes(horizontalScrollbarMode, verticalScrollbarMode);
|
| - frameView->updateDefaultScrollbarState();
|
| -
|
| - setView(frameView);
|
| - // FrameViews are created with a ref count of 1. Release this ref since we've assigned it to frame.
|
| - frameView->deref();
|
| -
|
| - 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
|
| +/*
|
| + * 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 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 "CSSProperty.h"
|
| +#include "CSSPropertyNames.h"
|
| +#include "CachedCSSStyleSheet.h"
|
| +#include "DOMWindow.h"
|
| +#include "DocLoader.h"
|
| +#include "DocumentType.h"
|
| +#include "EditingText.h"
|
| +#include "EditorClient.h"
|
| +#include "EventNames.h"
|
| +#include "FocusController.h"
|
| +#include "FloatQuad.h"
|
| +#include "FrameLoader.h"
|
| +#include "FrameView.h"
|
| +#include "GraphicsContext.h"
|
| +#include "HTMLDocument.h"
|
| +#include "HTMLFormElement.h"
|
| +#include "HTMLFrameElementBase.h"
|
| +#include "HTMLFormControlElement.h"
|
| +#include "HTMLNames.h"
|
| +#include "HTMLTableCellElement.h"
|
| +#include "HitTestResult.h"
|
| +#include "Logging.h"
|
| +#include "markup.h"
|
| +#include "MediaFeatureNames.h"
|
| +#include "Navigator.h"
|
| +#include "NodeList.h"
|
| +#include "Page.h"
|
| +#include "RegularExpression.h"
|
| +#include "RenderPart.h"
|
| +#include "RenderTableCell.h"
|
| +#include "RenderTextControl.h"
|
| +#include "RenderTheme.h"
|
| +#include "RenderView.h"
|
| +#include "ScriptController.h"
|
| +#include "Settings.h"
|
| +#include "TextIterator.h"
|
| +#include "TextResourceDecoder.h"
|
| +#include "XMLNames.h"
|
| +#include "ScriptController.h"
|
| +#include "npruntime_impl.h"
|
| +#include "visible_units.h"
|
| +#include <wtf/RefCountedLeakCounter.h>
|
| +#include <wtf/StdLibExtras.h>
|
| +
|
| +#if USE(JSC)
|
| +#include "JSDOMWindowShell.h"
|
| +#include "runtime_root.h"
|
| +#endif
|
| +
|
| +#if FRAME_LOADS_USER_STYLESHEET
|
| +#include "UserStyleSheetLoader.h"
|
| +#endif
|
| +
|
| +#if ENABLE(SVG)
|
| +#include "SVGDocument.h"
|
| +#include "SVGDocumentExtensions.h"
|
| +#include "SVGNames.h"
|
| +#include "XLinkNames.h"
|
| +#endif
|
| +
|
| +#if ENABLE(WML)
|
| +#include "WMLNames.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_ownerElement(ownerElement)
|
| + , m_script(this)
|
| + , m_selectionGranularity(CharacterGranularity)
|
| + , m_selectionController(this)
|
| + , m_caretBlinkTimer(this, &Frame::caretBlinkTimerFired)
|
| + , m_editor(this)
|
| + , m_eventHandler(this)
|
| + , m_animationController(this)
|
| + , m_lifeSupportTimer(this, &Frame::lifeSupportTimerFired)
|
| + , m_caretVisible(false)
|
| + , m_caretPaint(true)
|
| + , m_highlightTextMatches(false)
|
| + , m_inViewSourceMode(false)
|
| + , m_needsReapplyStyles(false)
|
| + , m_isDisconnected(false)
|
| + , m_excludeFromTextSearch(false)
|
| +#if FRAME_LOADS_USER_STYLESHEET
|
| + , m_userStyleSheetLoader(0)
|
| +#endif
|
| +{
|
| + 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
|
| +
|
| + 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()->clearRecordedFormValues();
|
| + loader()->cancelAndClear();
|
| +
|
| + // FIXME: We should not be doing all this work inside the destructor
|
| +
|
| + ASSERT(!m_lifeSupportTimer.isActive());
|
| +
|
| +#ifndef NDEBUG
|
| + frameCounter.decrement();
|
| +#endif
|
| +
|
| +#if USE(JSC)
|
| + if (m_script.haveWindowShell())
|
| + m_script.windowShell()->disconnectFrame();
|
| +#elif USE(V8)
|
| + m_script.disconnectFrame();
|
| +#endif
|
| +
|
| + disconnectOwnerElement();
|
| +
|
| + if (m_domWindow)
|
| + m_domWindow->disconnectFrame();
|
| +
|
| + 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());
|
| +
|
| +#if FRAME_LOADS_USER_STYLESHEET
|
| + delete m_userStyleSheetLoader;
|
| +#endif
|
| +}
|
| +
|
| +void Frame::init()
|
| +{
|
| + m_loader.init();
|
| +}
|
| +
|
| +FrameLoader* Frame::loader() const
|
| +{
|
| + return &m_loader;
|
| +}
|
| +
|
| +FrameView* Frame::view() const
|
| +{
|
| + return m_view.get();
|
| +}
|
| +
|
| +void Frame::setView(FrameView* view)
|
| +{
|
| + // 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();
|
| +}
|
| +
|
| +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() != 0) {
|
| + startsWithWordChar = wordRegExp.match(label.substring(0, 1)) >= 0;
|
| + endsWithWordChar = wordRegExp.match(label.substring(label.length() - 1, 1)) >= 0;
|
| + }
|
| +
|
| + if (i != 0)
|
| + 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)
|
| +{
|
| + RenderTableCell* cellRenderer = static_cast<RenderTableCell*>(cell->renderer());
|
| +
|
| + if (cellRenderer && cellRenderer->isTableCell()) {
|
| + RenderTableCell* cellAboveRenderer = cellRenderer->table()->cellAbove(cellRenderer);
|
| +
|
| + if (cellAboveRenderer) {
|
| + HTMLTableCellElement* aboveCell =
|
| + static_cast<HTMLTableCellElement*>(cellAboveRenderer->element());
|
| +
|
| + if (aboveCell) {
|
| + // search within the above cell we found for a match
|
| + 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)
|
| + return nodeString.substring(pos, regExp->matchedLength());
|
| + }
|
| + }
|
| + }
|
| + }
|
| + }
|
| + // Any reason in practice to search all cells in that are above cell?
|
| + return String();
|
| +}
|
| +
|
| +String Frame::searchForLabelsBeforeElement(const Vector<String>& labels, Element* element)
|
| +{
|
| + 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;
|
| +
|
| + // 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);
|
| + if (!result.isEmpty())
|
| + 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)
|
| + 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) {
|
| + return searchForLabelsAboveCell(regExp.get(), startingTableCell);
|
| + }
|
| + return String();
|
| +}
|
| +
|
| +String Frame::matchLabelsAgainstElement(const Vector<String>& labels, Element* element)
|
| +{
|
| + String name = element->getAttribute(nameAttr);
|
| + if (name.isEmpty())
|
| + return String();
|
| +
|
| + // Make numbers and _'s in field names behave like word boundaries, e.g., "address2"
|
| + replace(name, RegularExpression("\\d", TextCaseSensitive), " ");
|
| + name.replace('_', ' ');
|
| +
|
| + OwnPtr<RegularExpression> regExp(createRegExpForLabels(labels));
|
| + // Use the largest match we can find in the whole name string
|
| + int pos;
|
| + int length;
|
| + int bestPos = -1;
|
| + int bestLength = -1;
|
| + int start = 0;
|
| + do {
|
| + pos = regExp->match(name, start);
|
| + if (pos != -1) {
|
| + length = regExp->matchedLength();
|
| + if (length >= bestLength) {
|
| + bestPos = pos;
|
| + bestLength = length;
|
| + }
|
| + start = pos + 1;
|
| + }
|
| + } while (pos != -1);
|
| +
|
| + if (bestPos != -1)
|
| + return name.substring(bestPos, bestLength);
|
| + return String();
|
| +}
|
| +
|
| +const Selection& Frame::mark() const
|
| +{
|
| + return 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());
|
| +
|
| + 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->isTextArea() || renderer->isTextField()))
|
| + static_cast<RenderTextControl*>(renderer)->selectionChanged(userTriggered);
|
| +}
|
| +
|
| +void Frame::invalidateSelection()
|
| +{
|
| + selection()->setNeedsLayout();
|
| + selectionLayoutChanged();
|
| +}
|
| +
|
| +void Frame::setCaretVisible(bool flag)
|
| +{
|
| + if (m_caretVisible == flag)
|
| + return;
|
| + clearCaretRectIfNeeded();
|
| + m_caretVisible = flag;
|
| + selectionLayoutChanged();
|
| +}
|
| +
|
| +void Frame::clearCaretRectIfNeeded()
|
| +{
|
| +#if ENABLE(TEXT_CARET)
|
| + if (m_caretPaint) {
|
| + m_caretPaint = false;
|
| + selection()->invalidateCaretRect();
|
| + }
|
| +#endif
|
| +}
|
| +
|
| +// 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::setFocusedNodeIfNeeded()
|
| +{
|
| + if (!document() || selection()->isNone() || !selection()->isFocusedAndActive())
|
| + 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->element();
|
| + }
|
| + document()->setFocusedNode(0);
|
| + }
|
| +}
|
| +
|
| +void Frame::selectionLayoutChanged()
|
| +{
|
| + bool caretRectChanged = selection()->recomputeCaretRect();
|
| +
|
| +#if ENABLE(TEXT_CARET)
|
| + bool shouldBlink = m_caretVisible
|
| + && selection()->isCaret() && selection()->isContentEditable();
|
| +
|
| + // If the caret moved, stop the blink timer so we can restart with a
|
| + // black caret in the new location.
|
| + if (caretRectChanged || !shouldBlink)
|
| + m_caretBlinkTimer.stop();
|
| +
|
| + // Start blinking with a black caret. Be sure not to restart if we're
|
| + // already blinking in the right location.
|
| + if (shouldBlink && !m_caretBlinkTimer.isActive()) {
|
| + if (double blinkInterval = theme()->caretBlinkInterval())
|
| + m_caretBlinkTimer.startRepeating(blinkInterval);
|
| +
|
| + if (!m_caretPaint) {
|
| + m_caretPaint = true;
|
| + selection()->invalidateCaretRect();
|
| + }
|
| + }
|
| +#else
|
| + if (!caretRectChanged)
|
| + return;
|
| +#endif
|
| +
|
| + RenderView* view = contentRenderer();
|
| + if (!view)
|
| + return;
|
| +
|
| + Selection selection = this->selection()->selection();
|
| +
|
| + if (!selection.isRange())
|
| + view->clearSelection();
|
| + else {
|
| + // Use the rightmost candidate for the start of the selection, and the leftmost candidate for the end of the selection.
|
| + // Example: foo <a>bar</a>. Imagine that a line wrap occurs after 'foo', and that 'bar' is selected. If we pass [foo, 3]
|
| + // as the start of the selection, the selection painting code will think that content on the line containing 'foo' is selected
|
| + // and will fill the gap before 'bar'.
|
| + Position startPos = selection.start();
|
| + if (startPos.downstream().isCandidate())
|
| + startPos = startPos.downstream();
|
| + Position endPos = selection.end();
|
| + if (endPos.upstream().isCandidate())
|
| + endPos = endPos.upstream();
|
| +
|
| + // We can get into a state where the selection endpoints map to the same VisiblePosition when a selection is deleted
|
| + // because we don't yet notify the SelectionController of text removal.
|
| + if (startPos.isNotNull() && endPos.isNotNull() && selection.visibleStart() != selection.visibleEnd()) {
|
| + RenderObject *startRenderer = startPos.node()->renderer();
|
| + RenderObject *endRenderer = endPos.node()->renderer();
|
| + view->setSelection(startRenderer, startPos.offset(), endRenderer, endPos.offset());
|
| + }
|
| + }
|
| +}
|
| +
|
| +void Frame::caretBlinkTimerFired(Timer<Frame>*)
|
| +{
|
| +#if ENABLE(TEXT_CARET)
|
| + ASSERT(m_caretVisible);
|
| + ASSERT(selection()->isCaret());
|
| + bool caretPaint = m_caretPaint;
|
| + if (selection()->isCaretBlinkingSuspended() && caretPaint)
|
| + return;
|
| + m_caretPaint = !caretPaint;
|
| + selection()->invalidateCaretRect();
|
| +#endif
|
| +}
|
| +
|
| +void Frame::paintCaret(GraphicsContext* p, int tx, int ty, const IntRect& clipRect) const
|
| +{
|
| +#if ENABLE(TEXT_CARET)
|
| + if (m_caretPaint && m_caretVisible)
|
| + selection()->paintCaret(p, tx, ty, clipRect);
|
| +#endif
|
| +}
|
| +
|
| +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 && 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 && 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 && 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
|
| +
|
| + m_zoomFactor = percent;
|
| + m_page->settings()->setZoomsTextOnly(isTextOnly);
|
| +
|
| + if (m_doc)
|
| + m_doc->recalcStyle(Node::Force);
|
| +
|
| + for (Frame* child = tree()->firstChild(); child; child = child->tree()->nextSibling())
|
| + child->setZoomFactor(m_zoomFactor, isTextOnly);
|
| +
|
| + if (m_doc && m_doc->renderer() && m_doc->renderer()->needsLayout() && view()->didFirstLayout())
|
| + view()->layout();
|
| +}
|
| +
|
| +void Frame::setPrinting(bool printing, float minPageWidth, float maxPageWidth, bool adjustViewSize)
|
| +{
|
| + if (!m_doc)
|
| + return;
|
| +
|
| + 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)
|
| +{
|
| + m_kjsStatusBarText = text;
|
| + if (m_page)
|
| + m_page->chrome()->setStatusbarText(this, m_kjsStatusBarText);
|
| +}
|
| +
|
| +void Frame::setJSDefaultStatusBarText(const String& text)
|
| +{
|
| + 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()
|
| +{
|
| + 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 method called
|
| + // "reapplyStyles". We should probably eventually move it into its own
|
| + // method.
|
| + if (m_doc)
|
| + m_doc->docLoader()->setAutoLoadImages(m_page && m_page->settings()->loadsImagesAutomatically());
|
| +
|
| +#if FRAME_LOADS_USER_STYLESHEET
|
| + const KURL userStyleSheetLocation = m_page ? m_page->settings()->userStyleSheetLocation() : KURL();
|
| + if (!userStyleSheetLocation.isEmpty())
|
| + setUserStyleSheetLocation(userStyleSheetLocation);
|
| + else
|
| + setUserStyleSheet(String());
|
| +#endif
|
| +
|
| + // 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>.
|
| + if (m_doc)
|
| + m_doc->updateStyleSelector();
|
| +}
|
| +
|
| +bool Frame::shouldChangeSelection(const Selection& newSelection) const
|
| +{
|
| + return shouldChangeSelection(selection()->selection(), newSelection, newSelection.affinity(), false);
|
| +}
|
| +
|
| +bool Frame::shouldChangeSelection(const Selection& oldSelection, const Selection& newSelection, EAffinity affinity, bool stillSelecting) const
|
| +{
|
| + return editor()->client()->shouldChangeSelectedRange(oldSelection.toNormalizedRange().get(), newSelection.toNormalizedRange().get(),
|
| + affinity, stillSelecting);
|
| +}
|
| +
|
| +bool Frame::shouldDeleteSelection(const Selection& selection) const
|
| +{
|
| + return editor()->client()->shouldDeleteRange(selection.toNormalizedRange().get());
|
| +}
|
| +
|
| +bool Frame::isContentEditable() const
|
| +{
|
| + if (m_editor.clientIsEditable())
|
| + return true;
|
| + if (!m_doc)
|
| + return false;
|
| + return m_doc->inDesignMode();
|
| +}
|
| +
|
| +#if !PLATFORM(MAC)
|
| +
|
| +void Frame::setUseSecureKeyboardEntry(bool)
|
| +{
|
| +}
|
| +
|
| +#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() == 0) {
|
| + 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 (document() && 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 == 0);
|
| + }
|
| +
|
| + return value;
|
| +}
|
| +
|
| +PassRefPtr<CSSComputedStyleDeclaration> Frame::selectionComputedStyle(Node*& nodeToRemove) const
|
| +{
|
| + nodeToRemove = 0;
|
| +
|
| + if (!document())
|
| + return 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()->createElementNS(xhtmlNamespaceURI, "span", ec);
|
| + ASSERT(ec == 0);
|
| +
|
| + styleElement->setAttribute(styleAttr, 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 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
|
| +{
|
| + if (!m_doc)
|
| + return;
|
| +
|
| + 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
|
| +{
|
| + if (!m_doc)
|
| + return;
|
| +
|
| + 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 == 0);
|
| + style->setProperty(CSSPropertyWebkitNbspMode, "space", false, ec);
|
| + ASSERT(ec == 0);
|
| + style->setProperty(CSSPropertyWebkitLineBreak, "after-white-space", false, ec);
|
| + ASSERT(ec == 0);
|
| +}
|
| +
|
| +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;
|
| +#if USE(V8)
|
| + m_script.clearPluginObjects();
|
| +#endif
|
| +}
|
| +
|
| +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 static_cast<RenderPart*>(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, bool clipToVisibleContent) const
|
| +{
|
| + RenderView* root = contentRenderer();
|
| + if (!root)
|
| + return;
|
| +
|
| + RefPtr<Range> selectedRange = selection()->toNormalizedRange();
|
| +
|
| + Vector<IntRect> intRects;
|
| + selectedRange->addLineBoxRects(intRects, true);
|
| +
|
| + unsigned size = intRects.size();
|
| + FloatRect visibleContentRect = m_view->visibleContentRect();
|
| + 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 RenderLayer::ScrollAlignment& alignment) const
|
| +{
|
| + IntRect rect;
|
| +
|
| + switch (selection()->selectionType()) {
|
| + case Selection::NoSelection:
|
| + return;
|
| + case Selection::CaretSelection:
|
| + rect = selection()->absoluteCaretBounds();
|
| + break;
|
| + case Selection::RangeSelection:
|
| + rect = 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);
|
| + }
|
| +}
|
| +
|
| +void Frame::revealCaret(const RenderLayer::ScrollAlignment& alignment) const
|
| +{
|
| + if (selection()->isNone())
|
| + return;
|
| +
|
| + Position extent = selection()->extent();
|
| + if (extent.node() && extent.node()->renderer()) {
|
| + IntRect extentRect = VisiblePosition(extent).absoluteCaretBounds();
|
| + RenderLayer* layer = extent.node()->renderer()->enclosingLayer();
|
| + if (layer)
|
| + layer->scrollRectToVisible(extentRect, 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()) {
|
| + if (document && document->renderer() && document->renderer()->hasLayer())
|
| + document->renderView()->layer()->suspendMarquees();
|
| + 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 (!document())
|
| + return 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();
|
| +
|
| + ExceptionCode ec = 0;
|
| + RefPtr<Element> styleElement = document()->createElementNS(xhtmlNamespaceURI, "span", ec);
|
| + ASSERT(ec == 0);
|
| +
|
| + String styleText = m_typingStyle->cssText() + " display: inline";
|
| + styleElement->setAttribute(styleAttr, styleText.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() ? 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();
|
| + if (!doc || !selection()->isNone() || !isContentEditable())
|
| + return;
|
| +
|
| + Node* node = doc->documentElement();
|
| + while (node && !node->hasTagName(bodyTag))
|
| + node = node->traverseNextNode();
|
| + if (node)
|
| + selection()->setSelection(Selection(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() || !document())
|
| + 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()));
|
| + Selection 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 && *Selection(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(Selection(resultRange.get(), DOWNSTREAM));
|
| + revealSelection();
|
| + return true;
|
| +}
|
| +
|
| +unsigned Frame::markAllMatchesForText(const String& target, bool caseFlag, unsigned limit)
|
| +{
|
| + if (target.isEmpty() || !document())
|
| + 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;
|
| + }
|
| +
|
| + // 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;
|
| +
|
| + // 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;
|
| +
|
| + setStart(searchRange.get(), newStart);
|
| + 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 (doc && 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 || !document())
|
| + 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;
|
| +}
|
| +
|
| +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);
|
| +
|
| +#if USE(JSC)
|
| + // TODO(fqian): Unfork this change. It is a temporary workaround
|
| + // for this merge to pass layout tests. Once the merge is landed
|
| + // in the trunk, I am going to unfork this change and fix the issue
|
| + // in the binding code.
|
| + script()->clearWindowShell();
|
| +#endif
|
| +
|
| + // This will stop any JS timers
|
| +#if USE(JSC)
|
| + if (script()->haveWindowShell())
|
| + script()->windowShell()->disconnectFrame();
|
| +#elif USE(V8)
|
| + script()->disconnectFrame();
|
| +#endif
|
| +
|
| + script()->clearScriptObjects();
|
| + script()->updatePlatformScriptObjects();
|
| +
|
| + m_page = 0;
|
| +}
|
| +
|
| +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 (Document* doc = document()) {
|
| + if (DocumentType* doctype = doc->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())
|
| + page()->chrome()->focus();
|
| +
|
| + 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;
|
| +
|
| + RefPtr<Document> doc = document();
|
| + if (!doc)
|
| + return true;
|
| + HTMLElement* body = doc->body();
|
| + if (!body)
|
| + return true;
|
| +
|
| + loader()->setFiringUnloadEvents(true);
|
| +
|
| + RefPtr<BeforeUnloadEvent> beforeUnloadEvent = BeforeUnloadEvent::create();
|
| + beforeUnloadEvent->setTarget(doc);
|
| + doc->handleWindowEvent(beforeUnloadEvent.get(), false);
|
| +
|
| + if (!beforeUnloadEvent->defaultPrevented() && doc)
|
| + doc->defaultEventHandler(beforeUnloadEvent.get());
|
| +
|
| + loader()->setFiringUnloadEvents(false);
|
| +
|
| + 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 Selection& oldSelection, bool closeTyping)
|
| +{
|
| + if (document()) {
|
| + bool isContinuousSpellCheckingEnabled = editor()->isContinuousSpellCheckingEnabled();
|
| + bool isContinuousGrammarCheckingEnabled = isContinuousSpellCheckingEnabled && editor()->isGrammarCheckingEnabled();
|
| + if (isContinuousSpellCheckingEnabled) {
|
| + Selection newAdjacentWords;
|
| + Selection newSelectedSentence;
|
| + if (selection()->selection().isContentEditable()) {
|
| + VisiblePosition newStart(selection()->selection().visibleStart());
|
| + newAdjacentWords = Selection(startOfWord(newStart, LeftWordIfOnBoundary), endOfWord(newStart, RightWordIfOnBoundary));
|
| + if (isContinuousGrammarCheckingEnabled)
|
| + newSelectedSentence = Selection(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());
|
| + Selection oldAdjacentWords = Selection(startOfWord(oldStart, LeftWordIfOnBoundary), endOfWord(oldStart, RightWordIfOnBoundary));
|
| + if (oldAdjacentWords != newAdjacentWords) {
|
| + editor()->markMisspellings(oldAdjacentWords);
|
| + if (isContinuousGrammarCheckingEnabled) {
|
| + Selection oldSelectedSentence = Selection(startOfSentence(oldStart), endOfSentence(oldStart));
|
| + if (oldSelectedSentence != newSelectedSentence)
|
| + editor()->markBadGrammar(oldSelectedSentence);
|
| + }
|
| + }
|
| + }
|
| +
|
| + // 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->positionForCoordinates(result.localPoint().x(), result.localPoint().y());
|
| + 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);
|
| +
|
| + FrameView* frameView;
|
| + if (isMainFrame) {
|
| + frameView = new FrameView(this, viewportSize);
|
| + frameView->setFixedLayoutSize(fixedLayoutSize);
|
| + frameView->setUseFixedLayout(useFixedLayout);
|
| + } else
|
| + frameView = new FrameView(this);
|
| +
|
| + frameView->setScrollbarModes(horizontalScrollbarMode, verticalScrollbarMode);
|
| + frameView->updateDefaultScrollbarState();
|
| +
|
| + setView(frameView);
|
| + // FrameViews are created with a ref count of 1. Release this ref since we've assigned it to frame.
|
| + frameView->deref();
|
| +
|
| + 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
|
|
|