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