/* * 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 Apple Inc. All rights reserved. * Copyright (C) 2005 Alexey Proskuryakov * Copyright (C) 2007 Trolltech ASA * * 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 "FramePrivate.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 "FrameLoader.h" #include "FrameView.h" #include "GraphicsContext.h" #include "HitTestResult.h" #include "HTMLDocument.h" #include "HTMLFormElement.h" #include "HTMLFrameElementBase.h" #include "HTMLGenericFormElement.h" #include "HTMLNames.h" #include "HTMLTableCellElement.h" #include "Logging.h" #include "MediaFeatureNames.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 "Settings.h" #include "SystemTime.h" #include "TextIterator.h" #include "TextResourceDecoder.h" #include "XMLNames.h" #include "bindings/NP_jsobject.h" #include "bindings/npruntime_impl.h" #include "bindings/runtime_root.h" #include "kjs_proxy.h" #include "kjs_window.h" #include "visible_units.h" #if ENABLE(SVG) #include "SVGDocument.h" #include "SVGDocumentExtensions.h" #include "SVGNames.h" #include "XLinkNames.h" #endif using namespace std; using KJS::JSLock; namespace WebCore { using namespace EventNames; using namespace HTMLNames; double Frame::s_currentPaintTimeStamp = 0.0; class UserStyleSheetLoader : public CachedResourceClient { public: UserStyleSheetLoader(PassRefPtr document, const String& url) : m_document(document) , m_cachedSheet(m_document->docLoader()->requestUserCSSStyleSheet(url, "")) { if (m_cachedSheet) { m_document->addPendingSheet(); m_cachedSheet->ref(this); } } ~UserStyleSheetLoader() { if (m_cachedSheet) { if (!m_cachedSheet->isLoaded()) m_document->removePendingSheet(); m_cachedSheet->deref(this); } } private: virtual void setCSSStyleSheet(const String& /*URL*/, const String& /*charset*/, const String& sheet) { m_document->removePendingSheet(); if (Frame* frame = m_document->frame()) frame->setUserStyleSheet(sheet); } RefPtr m_document; CachedCSSStyleSheet* m_cachedSheet; }; #ifndef NDEBUG WTFLogChannel LogWebCoreFrameLeaks = { 0x00000000, "", WTFLogChannelOn }; struct FrameCounter { static int count; ~FrameCounter() { if (count) LOG(WebCoreFrameLeaks, "LEAK: %d Frame\n", count); } }; int FrameCounter::count = 0; static FrameCounter frameCounter; #endif static inline Frame* parentFromOwnerElement(HTMLFrameOwnerElement* ownerElement) { if (!ownerElement) return 0; return ownerElement->document()->frame(); } Frame::Frame(Page* page, HTMLFrameOwnerElement* ownerElement, FrameLoaderClient* frameLoaderClient) : d(new FramePrivate(page, parentFromOwnerElement(ownerElement), this, ownerElement, frameLoaderClient)) { AtomicString::init(); EventNames::init(); HTMLNames::init(); QualifiedName::init(); MediaFeatureNames::init(); #if ENABLE(SVG) SVGNames::init(); XLinkNames::init(); #endif XMLNames::init(); if (!ownerElement) page->setMainFrame(this); else { // FIXME: Frames were originally created with a refcount of 1. // Leave this ref call here until we can straighten that out. ref(); page->incrementFrameCount(); ownerElement->m_contentFrame = this; } #ifndef NDEBUG ++FrameCounter::count; #endif } Frame::~Frame() { setView(0); loader()->clearRecordedFormValues(); #if PLATFORM(MAC) setBridge(0); #endif loader()->cancelAndClear(); // FIXME: We should not be doing all this work inside the destructor ASSERT(!d->m_lifeSupportTimer.isActive()); #ifndef NDEBUG --FrameCounter::count; #endif if (d->m_jscript && d->m_jscript->haveGlobalObject()) static_cast(d->m_jscript->globalObject())->disconnectFrame(); disconnectOwnerElement(); if (d->m_domWindow) d->m_domWindow->disconnectFrame(); if (d->m_view) { d->m_view->hide(); d->m_view->clearFrame(); } ASSERT(!d->m_lifeSupportTimer.isActive()); delete d->m_userStyleSheetLoader; delete d; d = 0; } void Frame::init() { d->m_loader->init(); } FrameLoader* Frame::loader() const { return d->m_loader; } FrameView* Frame::view() const { return d->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 && d->m_doc && d->m_doc->attached() && !d->m_doc->inPageCache()) { // FIXME: We don't call willRemove here. Why is that OK? d->m_doc->detach(); if (d->m_view) d->m_view->unscheduleRelayout(); } eventHandler()->clear(); d->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(); } KJSProxy *Frame::scriptProxy() { if (!d->m_jscript) { Settings* settings = this->settings(); if (settings && settings->isJavaScriptEnabled()) d->m_jscript = new KJSProxy(this); } return d->m_jscript; } Document *Frame::document() const { if (d) return d->m_doc.get(); return 0; } void Frame::setDocument(PassRefPtr newDoc) { if (d->m_doc && d->m_doc->attached() && !d->m_doc->inPageCache()) { // FIXME: We don't call willRemove here. Why is that OK? d->m_doc->detach(); } d->m_doc = newDoc; if (d->m_doc && d->m_isActive) setUseSecureKeyboardEntry(d->m_doc->useSecureKeyboardEntryWhenActive()); if (d->m_doc && !d->m_doc->attached()) d->m_doc->attach(); // Remove the cached 'document' property, which is now stale. if (d->m_jscript) d->m_jscript->clearDocumentWrapper(); } Settings* Frame::settings() const { return d->m_page ? d->m_page->settings() : 0; } void Frame::setUserStyleSheetLocation(const KURL& url) { delete d->m_userStyleSheetLoader; d->m_userStyleSheetLoader = 0; if (d->m_doc && d->m_doc->docLoader()) d->m_userStyleSheetLoader = new UserStyleSheetLoader(d->m_doc, url.string()); } void Frame::setUserStyleSheet(const String& styleSheet) { delete d->m_userStyleSheetLoader; d->m_userStyleSheetLoader = 0; if (d->m_doc) d->m_doc->setUserStyleSheet(styleSheet); } String Frame::selectedText() const { return plainText(selectionController()->toRange().get()); } IntRect Frame::firstRectForRange(Range* range) const { int extraWidthToEndOfLine = 0; ExceptionCode ec = 0; ASSERT(range->startContainer(ec)); ASSERT(range->endContainer(ec)); IntRect startCaretRect = range->startContainer(ec)->renderer()->caretRect(range->startOffset(ec), DOWNSTREAM, &extraWidthToEndOfLine); ASSERT(!ec); IntRect endCaretRect = range->endContainer(ec)->renderer()->caretRect(range->endOffset(ec), UPSTREAM); ASSERT(!ec); 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::selectionController() const { return &d->m_selectionController; } Editor* Frame::editor() const { return &d->m_editor; } TextGranularity Frame::selectionGranularity() const { return d->m_selectionGranularity; } void Frame::setSelectionGranularity(TextGranularity granularity) const { d->m_selectionGranularity = granularity; } SelectionController* Frame::dragCaretController() const { return d->m_page->dragCaretController(); } AnimationController* Frame::animationController() const { return &d->m_animationController; } // Either get cached regexp or build one that matches any of the labels. // The regexp we build is of the form: (STR1|STR2|STRN) static RegularExpression *regExpForLabels(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. static RegularExpression wordRegExp = RegularExpression("\\w"); DeprecatedString pattern("("); unsigned int numLabels = labels.size(); unsigned int i; for (i = 0; i < numLabels; i++) { DeprecatedString label = labels[i].deprecatedString(); bool startsWithWordChar = false; bool endsWithWordChar = false; if (label.length() != 0) { startsWithWordChar = wordRegExp.search(label.at(0)) >= 0; endsWithWordChar = wordRegExp.search(label.at(label.length() - 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, false); } String Frame::searchForLabelsAboveCell(RegularExpression* regExp, HTMLTableCellElement* cell) { RenderTableCell* cellRenderer = static_cast(cell->renderer()); if (cellRenderer && cellRenderer->isTableCell()) { RenderTableCell* cellAboveRenderer = cellRenderer->table()->cellAbove(cellRenderer); if (cellAboveRenderer) { HTMLTableCellElement* aboveCell = static_cast(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 DeprecatedString nodeString = n->nodeValue().deprecatedString(); int pos = regExp->searchRev(nodeString); if (pos >= 0) return nodeString.mid(pos, regExp->matchedLength()); } } } } } // Any reason in practice to search all cells in that are above cell? return String(); } String Frame::searchForLabelsBeforeElement(const Vector& labels, Element* element) { RegularExpression* regExp = regExpForLabels(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(n)->isGenericFormElement())) { // 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, 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 DeprecatedString nodeString = n->nodeValue().deprecatedString(); // 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.mid(pos, regExp->matchedLength()); else 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, startingTableCell); } return String(); } String Frame::matchLabelsAgainstElement(const Vector& labels, Element* element) { DeprecatedString name = element->getAttribute(nameAttr).deprecatedString(); // Make numbers and _'s in field names behave like word boundaries, e.g., "address2" name.replace(RegularExpression("\\d"), " "); name.replace('_', ' '); RegularExpression* regExp = regExpForLabels(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->search(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.mid(bestPos, bestLength); return String(); } const Selection& Frame::mark() const { return d->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()); d->m_mark = s; } void Frame::notifyRendererOfSelectionChange(bool userTriggered) { RenderObject* renderer = 0; if (selectionController()->rootEditableElement()) renderer = selectionController()->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(renderer)->selectionChanged(userTriggered); } void Frame::invalidateSelection() { selectionController()->setNeedsLayout(); selectionLayoutChanged(); } void Frame::setCaretVisible(bool flag) { if (d->m_caretVisible == flag) return; clearCaretRectIfNeeded(); d->m_caretVisible = flag; selectionLayoutChanged(); } void Frame::clearCaretRectIfNeeded() { if (d->m_caretPaint) { d->m_caretPaint = false; selectionController()->invalidateCaretRect(); } } // Helper function that tells whether a particular node is an element that has an entire // Frame and FrameView, a ,