diff -r 000000000000 -r 4f2f89ce4247 WebCore/page/Frame.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/WebCore/page/Frame.cpp Fri Sep 17 09:02:29 2010 +0300 @@ -0,0 +1,1621 @@ +/* + * Copyright (C) 1998, 1999 Torben Weis + * 1999 Lars Knoll + * 1999 Antti Koivisto + * 2000 Simon Hausmann + * 2000 Stefan Schimanski <1Stein@gmx.de> + * 2001 George Staikos + * Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009, 2010 Apple Inc. All rights reserved. + * Copyright (C) 2005 Alexey Proskuryakov + * Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies) + * Copyright (C) 2008 Eric Seidel + * + * 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 "CSSComputedStyleDeclaration.h" +#include "CSSMutableStyleDeclaration.h" +#include "CSSProperty.h" +#include "CSSPropertyNames.h" +#include "CachedCSSStyleSheet.h" +#include "Chrome.h" +#include "ChromeClient.h" +#include "DOMWindow.h" +#include "DocLoader.h" +#include "DocumentType.h" +#include "EditingText.h" +#include "EditorClient.h" +#include "EventNames.h" +#include "FloatQuad.h" +#include "FocusController.h" +#include "FrameLoader.h" +#include "FrameLoaderClient.h" +#include "FrameView.h" +#include "GraphicsContext.h" +#include "GraphicsLayer.h" +#include "HTMLDocument.h" +#include "HTMLFormControlElement.h" +#include "HTMLFormElement.h" +#include "HTMLFrameElementBase.h" +#include "HTMLNames.h" +#include "HTMLTableCellElement.h" +#include "HitTestResult.h" +#include "Logging.h" +#include "MediaFeatureNames.h" +#include "Navigator.h" +#include "NodeList.h" +#include "Page.h" +#include "PageGroup.h" +#include "RegularExpression.h" +#include "RenderLayer.h" +#include "RenderPart.h" +#include "RenderTableCell.h" +#include "RenderTextControl.h" +#include "RenderTheme.h" +#include "RenderView.h" +#include "ScriptController.h" +#include "ScriptSourceCode.h" +#include "ScriptValue.h" +#include "Settings.h" +#include "TextIterator.h" +#include "TextResourceDecoder.h" +#include "UserContentURLPattern.h" +#include "XMLNSNames.h" +#include "XMLNames.h" +#include "htmlediting.h" +#include "markup.h" +#include "npruntime_impl.h" +#include "visible_units.h" +#include +#include + +#if USE(ACCELERATED_COMPOSITING) +#include "RenderLayerCompositor.h" +#endif + +#if USE(JSC) +#include "JSDOMWindowShell.h" +#include "runtime_root.h" +#endif + +#include "MathMLNames.h" +#include "SVGNames.h" +#include "XLinkNames.h" + +#if ENABLE(SVG) +#include "SVGDocument.h" +#include "SVGDocumentExtensions.h" +#endif + +#if ENABLE(TILED_BACKING_STORE) +#include "TiledBackingStore.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(); +} + +inline Frame::Frame(Page* page, HTMLFrameOwnerElement* ownerElement, FrameLoaderClient* frameLoaderClient) + : m_page(page) + , m_treeNode(this, parentFromOwnerElement(ownerElement)) + , m_loader(this, frameLoaderClient) + , m_redirectScheduler(this) + , m_ownerElement(ownerElement) + , m_script(this) + , m_editor(this) + , m_selectionController(this) + , m_eventHandler(this) + , m_animationController(this) + , m_lifeSupportTimer(this, &Frame::lifeSupportTimerFired) +#if ENABLE(ORIENTATION_EVENTS) + , m_orientation(0) +#endif + , m_highlightTextMatches(false) + , m_inViewSourceMode(false) + , m_needsReapplyStyles(false) + , m_isDisconnected(false) + , m_excludeFromTextSearch(false) +{ + ASSERT(page); + AtomicString::init(); + HTMLNames::init(); + QualifiedName::init(); + MediaFeatureNames::init(); + SVGNames::init(); + XLinkNames::init(); + MathMLNames::init(); + XMLNSNames::init(); + XMLNames::init(); + +#if ENABLE(WML) + WMLNames::init(); +#endif + + if (!ownerElement) { +#if ENABLE(TILED_BACKING_STORE) + // Top level frame only for now. + setTiledBackingStoreEnabled(page->settings()->tiledBackingStoreEnabled()); +#endif + } else { + page->incrementFrameCount(); + + // Make sure we will not end up with two frames referencing the same owner element. + Frame*& contentFrameSlot = ownerElement->m_contentFrame; + ASSERT(!contentFrameSlot || contentFrameSlot->ownerElement() != ownerElement); + contentFrameSlot = this; + } + +#ifndef NDEBUG + frameCounter.increment(); +#endif +} + +PassRefPtr Frame::create(Page* page, HTMLFrameOwnerElement* ownerElement, FrameLoaderClient* client) +{ + RefPtr frame = adoptRef(new Frame(page, ownerElement, client)); + if (!ownerElement) + page->setMainFrame(frame); + return frame.release(); +} + +Frame::~Frame() +{ + setView(0); + loader()->cancelAndClear(); + + // FIXME: We should not be doing all this work inside the destructor + + ASSERT(!m_lifeSupportTimer.isActive()); + +#ifndef NDEBUG + frameCounter.decrement(); +#endif + + disconnectOwnerElement(); + + if (m_domWindow) + m_domWindow->disconnectFrame(); + script()->clearWindowShell(); + + HashSet::iterator end = m_liveFormerWindows.end(); + for (HashSet::iterator it = m_liveFormerWindows.begin(); it != end; ++it) + (*it)->disconnectFrame(); + + if (m_view) { + m_view->hide(); + m_view->clearFrame(); + } + + ASSERT(!m_lifeSupportTimer.isActive()); +} + +void Frame::setView(PassRefPtr view) +{ + // We the custom scroll bars as early as possible to prevent m_doc->detach() + // from messing with the view such that its scroll bars won't be torn down. + // FIXME: We should revisit this. + if (m_view) + m_view->detachCustomScrollbars(); + + // Detach the document now, so any onUnload handlers get run - if + // we wait until the view is destroyed, then things won't be + // hooked up enough for some JavaScript calls to work. + if (!view && m_doc && m_doc->attached() && !m_doc->inPageCache()) { + // FIXME: We don't call willRemove here. Why is that OK? + m_doc->detach(); + if (m_view) + m_view->unscheduleRelayout(); + } + eventHandler()->clear(); + + m_view = view; + + // Only one form submission is allowed per view of a part. + // Since this part may be getting reused as a result of being + // pulled from the back/forward cache, reset this flag. + loader()->resetMultipleFormSubmissionProtection(); + +#if ENABLE(TILED_BACKING_STORE) + if (m_view && tiledBackingStore()) + m_view->setPaintsEntireContents(true); +#endif +} + +void Frame::setDocument(PassRefPtr 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; + selection()->updateSecureKeyboardEntryIfActive(); + + if (m_doc && !m_doc->attached()) + m_doc->attach(); + + // Update the cached 'document' property, which is now stale. + m_script.updateDocument(); +} + +#if ENABLE(ORIENTATION_EVENTS) +void Frame::sendOrientationChangeEvent(int orientation) +{ + m_orientation = orientation; + if (Document* doc = document()) + doc->dispatchWindowEvent(Event::create(eventNames().orientationchangeEvent, false, false)); +} +#endif // ENABLE(ORIENTATION_EVENTS) + +Settings* Frame::settings() const +{ + return m_page ? m_page->settings() : 0; +} + +String Frame::selectedText() const +{ + return plainText(selection()->toNormalizedRange().get()); +} + +IntRect Frame::firstRectForRange(Range* range) const +{ + int extraWidthToEndOfLine = 0; + ASSERT(range->startContainer()); + ASSERT(range->endContainer()); + + InlineBox* startInlineBox; + int startCaretOffset; + Position startPosition = VisiblePosition(range->startPosition()).deepEquivalent(); + if (startPosition.isNull()) + return IntRect(); + startPosition.getInlineBoxAndOffset(DOWNSTREAM, startInlineBox, startCaretOffset); + + RenderObject* startRenderer = startPosition.node()->renderer(); + ASSERT(startRenderer); + IntRect startCaretRect = startRenderer->localCaretRect(startInlineBox, startCaretOffset, &extraWidthToEndOfLine); + if (startCaretRect != IntRect()) + startCaretRect = startRenderer->localToAbsoluteQuad(FloatRect(startCaretRect)).enclosingBoundingBox(); + + InlineBox* endInlineBox; + int endCaretOffset; + Position endPosition = VisiblePosition(range->endPosition()).deepEquivalent(); + if (endPosition.isNull()) + return IntRect(); + endPosition.getInlineBoxAndOffset(UPSTREAM, endInlineBox, endCaretOffset); + + RenderObject* endRenderer = endPosition.node()->renderer(); + ASSERT(endRenderer); + 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()); +} + +TextGranularity Frame::selectionGranularity() const +{ + return m_selectionController.granularity(); +} + +SelectionController* Frame::dragCaretController() const +{ + return m_page->dragCaretController(); +} + +static RegularExpression* createRegExpForLabels(const Vector& labels) +{ + // REVIEW- version of this call in FrameMac.mm caches based on the NSArray ptrs being + // the same across calls. We can't do that. + + DEFINE_STATIC_LOCAL(RegularExpression, wordRegExp, ("\\w", TextCaseSensitive)); + String pattern("("); + unsigned int numLabels = labels.size(); + unsigned int i; + for (i = 0; i < numLabels; i++) { + String label = labels[i]; + + bool startsWithWordChar = false; + bool endsWithWordChar = false; + if (label.length()) { + startsWithWordChar = wordRegExp.match(label.substring(0, 1)) >= 0; + endsWithWordChar = wordRegExp.match(label.substring(label.length() - 1, 1)) >= 0; + } + + if (i) + pattern.append("|"); + // Search for word boundaries only if label starts/ends with "word characters". + // If we always searched for word boundaries, this wouldn't work for languages + // such as Japanese. + if (startsWithWordChar) + pattern.append("\\b"); + pattern.append(label); + if (endsWithWordChar) + pattern.append("\\b"); + } + pattern.append(")"); + return new RegularExpression(pattern, TextCaseInsensitive); +} + +String Frame::searchForLabelsAboveCell(RegularExpression* regExp, HTMLTableCellElement* cell, size_t* resultDistanceFromStartOfCell) +{ + RenderObject* cellRenderer = cell->renderer(); + + if (cellRenderer && cellRenderer->isTableCell()) { + RenderTableCell* tableCellRenderer = toRenderTableCell(cellRenderer); + RenderTableCell* cellAboveRenderer = tableCellRenderer->table()->cellAbove(tableCellRenderer); + + if (cellAboveRenderer) { + HTMLTableCellElement* aboveCell = + static_cast(cellAboveRenderer->node()); + + if (aboveCell) { + // search within the above cell we found for a match + size_t lengthSearched = 0; + for (Node* n = aboveCell->firstChild(); n; n = n->traverseNextNode(aboveCell)) { + if (n->isTextNode() && n->renderer() && n->renderer()->style()->visibility() == VISIBLE) { + // For each text chunk, run the regexp + String nodeString = n->nodeValue(); + int pos = regExp->searchRev(nodeString); + if (pos >= 0) { + if (resultDistanceFromStartOfCell) + *resultDistanceFromStartOfCell = lengthSearched; + return nodeString.substring(pos, regExp->matchedLength()); + } + lengthSearched += nodeString.length(); + } + } + } + } + } + // Any reason in practice to search all cells in that are above cell? + if (resultDistanceFromStartOfCell) + *resultDistanceFromStartOfCell = notFound; + return String(); +} + +String Frame::searchForLabelsBeforeElement(const Vector& labels, Element* element, size_t* resultDistance, bool* resultIsInCellAbove) +{ + OwnPtr regExp(createRegExpForLabels(labels)); + // We stop searching after we've seen this many chars + const unsigned int charsSearchedThreshold = 500; + // This is the absolute max we search. We allow a little more slop than + // charsSearchedThreshold, to make it more likely that we'll search whole nodes. + const unsigned int maxCharsSearched = 600; + // If the starting element is within a table, the cell that contains it + HTMLTableCellElement* startingTableCell = 0; + bool searchedCellAbove = false; + + if (resultDistance) + *resultDistance = notFound; + if (resultIsInCellAbove) + *resultIsInCellAbove = false; + + // walk backwards in the node tree, until another element, or form, or end of tree + int unsigned lengthSearched = 0; + Node* n; + for (n = element->traversePreviousNode(); + n && lengthSearched < charsSearchedThreshold; + n = n->traversePreviousNode()) + { + if (n->hasTagName(formTag) + || (n->isHTMLElement() && static_cast(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(n); + } else if (n->hasTagName(trTag) && startingTableCell) { + String result = searchForLabelsAboveCell(regExp.get(), startingTableCell, resultDistance); + if (!result.isEmpty()) { + if (resultIsInCellAbove) + *resultIsInCellAbove = true; + return result; + } + searchedCellAbove = true; + } else if (n->isTextNode() && n->renderer() && n->renderer()->style()->visibility() == VISIBLE) { + // For each text chunk, run the regexp + String nodeString = n->nodeValue(); + // add 100 for slop, to make it more likely that we'll search whole nodes + if (lengthSearched + nodeString.length() > maxCharsSearched) + nodeString = nodeString.right(charsSearchedThreshold - lengthSearched); + int pos = regExp->searchRev(nodeString); + if (pos >= 0) { + if (resultDistance) + *resultDistance = lengthSearched; + return nodeString.substring(pos, regExp->matchedLength()); + } + lengthSearched += nodeString.length(); + } + } + + // If we started in a cell, but bailed because we found the start of the form or the + // previous element, we still might need to search the row above us for a label. + if (startingTableCell && !searchedCellAbove) { + String result = searchForLabelsAboveCell(regExp.get(), startingTableCell, resultDistance); + if (!result.isEmpty()) { + if (resultIsInCellAbove) + *resultIsInCellAbove = true; + return result; + } + } + return String(); +} + +static String matchLabelsAgainstString(const Vector& labels, const String& stringToMatch) +{ + if (stringToMatch.isEmpty()) + return String(); + + String mutableStringToMatch = stringToMatch; + + // Make numbers and _'s in field names behave like word boundaries, e.g., "address2" + replace(mutableStringToMatch, RegularExpression("\\d", TextCaseSensitive), " "); + mutableStringToMatch.replace('_', ' '); + + OwnPtr regExp(createRegExpForLabels(labels)); + // Use the largest match we can find in the whole string + int pos; + int length; + int bestPos = -1; + int bestLength = -1; + int start = 0; + do { + pos = regExp->match(mutableStringToMatch, start); + if (pos != -1) { + length = regExp->matchedLength(); + if (length >= bestLength) { + bestPos = pos; + bestLength = length; + } + start = pos + 1; + } + } while (pos != -1); + + if (bestPos != -1) + return mutableStringToMatch.substring(bestPos, bestLength); + return String(); +} + +String Frame::matchLabelsAgainstElement(const Vector& labels, Element* element) +{ + // Match against the name element, then against the id element if no match is found for the name element. + // See 7538330 for one popular site that benefits from the id element check. + // FIXME: This code is mirrored in FrameMac.mm. It would be nice to make the Mac code call the platform-agnostic + // code, which would require converting the NSArray of NSStrings to a Vector of Strings somewhere along the way. + String resultFromNameAttribute = matchLabelsAgainstString(labels, element->getAttribute(nameAttr)); + if (!resultFromNameAttribute.isEmpty()) + return resultFromNameAttribute; + + return matchLabelsAgainstString(labels, element->getAttribute(idAttr)); +} + +void Frame::notifyRendererOfSelectionChange(bool userTriggered) +{ + RenderObject* renderer = 0; + + document()->updateStyleIfNeeded(); + + if (selection()->rootEditableElement()) + renderer = selection()->rootEditableElement()->shadowAncestorNode()->renderer(); + + // If the current selection is in a textfield or textarea, notify the renderer that the selection has changed + if (renderer && renderer->isTextControl()) + toRenderTextControl(renderer)->selectionChanged(userTriggered); +} + +// Helper function that tells whether a particular node is an element that has an entire +// Frame and FrameView, a ,