/* * 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 "UserTypingGestureIndicator.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 #if PLATFORM(ANDROID) #include "WebViewCore.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 ,