/* * Copyright (C) 2004, 2005, 2006, 2007 Apple Inc. All rights reserved. * Copyright (C) 2006 Alexey Proskuryakov (ap@nypop.com) * Copyright (C) 2007 Trolltech ASA * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #import "config.h" #import "Frame.h" #import "AXObjectCache.h" #import "BeforeUnloadEvent.h" #import "BlockExceptions.h" #import "CSSHelper.h" #import "Cache.h" #import "Chrome.h" #import "ClipboardEvent.h" #import "ClipboardMac.h" #import "Cursor.h" #import "DOMInternal.h" #import "DocumentLoader.h" #import "EditCommand.h" #import "EditorClient.h" #import "Event.h" #import "EventNames.h" #import "FloatRect.h" #import "FontData.h" #import "FoundationExtras.h" #import "FrameLoadRequest.h" #import "FrameLoader.h" #import "FrameLoaderClient.h" #import "FrameLoaderTypes.h" #import "FramePrivate.h" #import "FrameView.h" #import "GraphicsContext.h" #import "HTMLDocument.h" #import "HTMLFormElement.h" #import "HTMLGenericFormElement.h" #import "HTMLInputElement.h" #import "HTMLNames.h" #import "HTMLTableCellElement.h" #import "HitTestRequest.h" #import "HitTestResult.h" #import "KeyboardEvent.h" #import "Logging.h" #import "MouseEventWithHitTestResults.h" #import "Page.h" #import "PlatformKeyboardEvent.h" #import "PlatformScrollBar.h" #import "PlatformWheelEvent.h" #import "Plugin.h" #import "RegularExpression.h" #import "RenderImage.h" #import "RenderListItem.h" #import "RenderPart.h" #import "RenderTableCell.h" #import "RenderTheme.h" #import "RenderView.h" #import "ResourceHandle.h" #import "Settings.h" #import "SystemTime.h" #import "TextResourceDecoder.h" #import "WebCoreFrameBridge.h" #import "WebCoreSystemInterface.h" #import "WebCoreViewFactory.h" #import "WebDashboardRegion.h" #import "WebScriptObjectPrivate.h" #import "kjs_proxy.h" #import "kjs_window.h" #import "visible_units.h" #import #import #import #undef _webcore_TIMING @interface NSObject (WebPlugIn) - (id)objectForWebScript; - (NPObject *)createPluginScriptableObject; @end @interface NSView (WebCoreHTMLDocumentView) - (void)drawSingleRect:(NSRect)rect; @end using namespace std; using namespace KJS::Bindings; using KJS::JSLock; namespace WebCore { using namespace EventNames; using namespace HTMLNames; void Frame::setBridge(WebCoreFrameBridge* bridge) { if (d->m_bridge == bridge) return; if (!bridge) { [d->m_bridge clearFrame]; HardRelease(d->m_bridge); d->m_bridge = nil; return; } HardRetain(bridge); HardRelease(d->m_bridge); d->m_bridge = bridge; } WebCoreFrameBridge* Frame::bridge() const { return d->m_bridge; } // Either get cached regexp or build one that matches any of the labels. // The regexp we build is of the form: (STR1|STR2|STRN) RegularExpression* regExpForLabels(NSArray* labels) { // All the ObjC calls in this method are simple array and string // calls which we can assume do not raise exceptions // Parallel arrays that we use to cache regExps. In practice the number of expressions // that the app will use is equal to the number of locales is used in searching. static const unsigned int regExpCacheSize = 4; static NSMutableArray* regExpLabels = nil; static Vector regExps; static RegularExpression wordRegExp = RegularExpression("\\w"); RegularExpression* result; if (!regExpLabels) regExpLabels = [[NSMutableArray alloc] initWithCapacity:regExpCacheSize]; CFIndex cacheHit = [regExpLabels indexOfObject:labels]; if (cacheHit != NSNotFound) result = regExps.at(cacheHit); else { DeprecatedString pattern("("); unsigned int numLabels = [labels count]; unsigned int i; for (i = 0; i < numLabels; i++) { DeprecatedString label = DeprecatedString::fromNSString((NSString*)[labels objectAtIndex:i]); 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(")"); result = new RegularExpression(pattern, false); } // add regexp to the cache, making sure it is at the front for LRU ordering if (cacheHit != 0) { if (cacheHit != NSNotFound) { // remove from old spot [regExpLabels removeObjectAtIndex:cacheHit]; regExps.remove(cacheHit); } // add to start [regExpLabels insertObject:labels atIndex:0]; regExps.insert(0, result); // trim if too big if ([regExpLabels count] > regExpCacheSize) { [regExpLabels removeObjectAtIndex:regExpCacheSize]; RegularExpression* last = regExps.last(); regExps.removeLast(); delete last; } } return result; } NSString* Frame::searchForNSLabelsAboveCell(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()).getNSString(); } } } } } // Any reason in practice to search all cells in that are above cell? return nil; } NSString* Frame::searchForLabelsBeforeElement(NSArray* 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) { NSString* result = searchForLabelsAboveCell(regExp, startingTableCell); if (result && [result length] > 0) 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()).getNSString(); 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) { NSString* result = searchForLabelsAboveCell(regExp, startingTableCell); if (result && [result length] > 0) return result; } return nil; } NSString* Frame::matchLabelsAgainstElement(NSArray* 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("[[:digit:]]"), " "); 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).getNSString(); return nil; } NSImage* Frame::imageFromRect(NSRect rect) const { NSView* view = d->m_view->getDocumentView(); if (!view) return nil; if (![view respondsToSelector:@selector(drawSingleRect:)]) return nil; NSImage* resultImage; BEGIN_BLOCK_OBJC_EXCEPTIONS; NSRect bounds = [view bounds]; // Round image rect size in window coordinate space to avoid pixel cracks at HiDPI (4622794) rect = [view convertRect:rect toView:nil]; rect.size.height = roundf(rect.size.height); rect.size.width = roundf(rect.size.width); rect = [view convertRect:rect fromView:nil]; resultImage = [[[NSImage alloc] initWithSize:rect.size] autorelease]; if (rect.size.width != 0 && rect.size.height != 0) { [resultImage setFlipped:YES]; [resultImage lockFocus]; CGContextRef context = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort]; CGContextSaveGState(context); CGContextTranslateCTM(context, bounds.origin.x - rect.origin.x, bounds.origin.y - rect.origin.y); // Note: Must not call drawRect: here, because drawRect: assumes that it's called from AppKit's // display machinery. It calls getRectsBeingDrawn:count:, which can only be called inside // when a real AppKit display is underway. [view drawSingleRect:rect]; CGContextRestoreGState(context); [resultImage unlockFocus]; [resultImage setFlipped:NO]; } return resultImage; END_BLOCK_OBJC_EXCEPTIONS; return nil; } NSImage* Frame::selectionImage(bool forceBlackText) const { d->m_paintRestriction = forceBlackText ? PaintRestrictionSelectionOnlyBlackText : PaintRestrictionSelectionOnly; d->m_doc->updateLayout(); NSImage* result = imageFromRect(selectionRect()); d->m_paintRestriction = PaintRestrictionNone; return result; } NSImage* Frame::snapshotDragImage(Node* node, NSRect* imageRect, NSRect* elementRect) const { RenderObject* renderer = node->renderer(); if (!renderer) return nil; renderer->updateDragState(true); // mark dragged nodes (so they pick up the right CSS) d->m_doc->updateLayout(); // forces style recalc - needed since changing the drag state might // imply new styles, plus JS could have changed other things IntRect topLevelRect; NSRect paintingRect = renderer->paintingRootRect(topLevelRect); d->m_elementToDraw = node; // invoke special sub-tree drawing mode NSImage* result = imageFromRect(paintingRect); renderer->updateDragState(false); d->m_doc->updateLayout(); d->m_elementToDraw = 0; if (elementRect) *elementRect = topLevelRect; if (imageRect) *imageRect = paintingRect; return result; } NSDictionary* Frame::fontAttributesForSelectionStart() const { Node* nodeToRemove; RenderStyle* style = styleForSelectionStart(nodeToRemove); if (!style) return nil; NSMutableDictionary* result = [NSMutableDictionary dictionary]; if (style->backgroundColor().isValid() && style->backgroundColor().alpha() != 0) [result setObject:nsColor(style->backgroundColor()) forKey:NSBackgroundColorAttributeName]; if (style->font().primaryFont()->getNSFont()) [result setObject:style->font().primaryFont()->getNSFont() forKey:NSFontAttributeName]; if (style->color().isValid() && style->color() != Color::black) [result setObject:nsColor(style->color()) forKey:NSForegroundColorAttributeName]; ShadowData* shadow = style->textShadow(); if (shadow) { NSShadow* s = [[NSShadow alloc] init]; [s setShadowOffset:NSMakeSize(shadow->x, shadow->y)]; [s setShadowBlurRadius:shadow->blur]; [s setShadowColor:nsColor(shadow->color)]; [result setObject:s forKey:NSShadowAttributeName]; } int decoration = style->textDecorationsInEffect(); if (decoration & LINE_THROUGH) [result setObject:[NSNumber numberWithInt:NSUnderlineStyleSingle] forKey:NSStrikethroughStyleAttributeName]; int superscriptInt = 0; switch (style->verticalAlign()) { case BASELINE: case BOTTOM: case BASELINE_MIDDLE: case LENGTH: case MIDDLE: case TEXT_BOTTOM: case TEXT_TOP: case TOP: break; case SUB: superscriptInt = -1; break; case SUPER: superscriptInt = 1; break; } if (superscriptInt) [result setObject:[NSNumber numberWithInt:superscriptInt] forKey:NSSuperscriptAttributeName]; if (decoration & UNDERLINE) [result setObject:[NSNumber numberWithInt:NSUnderlineStyleSingle] forKey:NSUnderlineStyleAttributeName]; if (nodeToRemove) { ExceptionCode ec = 0; nodeToRemove->remove(ec); ASSERT(ec == 0); } return result; } NSWritingDirection Frame::baseWritingDirectionForSelectionStart() const { NSWritingDirection result = NSWritingDirectionLeftToRight; Position pos = selectionController()->selection().visibleStart().deepEquivalent(); Node* node = pos.node(); if (!node || !node->renderer() || !node->renderer()->containingBlock()) return result; RenderStyle* style = node->renderer()->containingBlock()->style(); if (!style) return result; switch (style->direction()) { case LTR: result = NSWritingDirectionLeftToRight; break; case RTL: result = NSWritingDirectionRightToLeft; break; } return result; } void Frame::issuePasteCommand() { [d->m_bridge issuePasteCommand]; } void Frame::issueTransposeCommand() { [d->m_bridge issueTransposeCommand]; } const short enableRomanKeyboardsOnly = -23; void Frame::setUseSecureKeyboardEntry(bool enable) { if (enable == IsSecureEventInputEnabled()) return; if (enable) { EnableSecureEventInput(); #ifdef BUILDING_ON_TIGER KeyScript(enableRomanKeyboardsOnly); #else CFArrayRef inputSources = TISCreateASCIICapableInputSourceList(); TSMSetDocumentProperty(TSMGetActiveDocument(), kTSMDocumentEnabledInputSourcesPropertyTag, sizeof(CFArrayRef), &inputSources); CFRelease(inputSources); #endif } else { DisableSecureEventInput(); #ifdef BUILDING_ON_TIGER KeyScript(smKeyEnableKybds); #else TSMRemoveDocumentProperty(TSMGetActiveDocument(), kTSMDocumentEnabledInputSourcesPropertyTag); #endif } } static void convertAttributesToUnderlines(Vector& result, const Range* markedTextRange, NSArray* attributes, NSArray* ranges) { int exception = 0; int baseOffset = markedTextRange->startOffset(exception); unsigned length = [attributes count]; ASSERT([ranges count] == length); for (unsigned i = 0; i < length; i++) { NSNumber* style = [[attributes objectAtIndex:i] objectForKey:NSUnderlineStyleAttributeName]; if (!style) continue; NSRange range = [[ranges objectAtIndex:i] rangeValue]; NSColor* color = [[attributes objectAtIndex:i] objectForKey:NSUnderlineColorAttributeName]; Color qColor = Color::black; if (color) { NSColor* deviceColor = [color colorUsingColorSpaceName:NSDeviceRGBColorSpace]; qColor = Color(makeRGBA((int)(255 * [deviceColor redComponent]), (int)(255 * [deviceColor blueComponent]), (int)(255 * [deviceColor greenComponent]), (int)(255 * [deviceColor alphaComponent]))); } result.append(MarkedTextUnderline(range.location + baseOffset, range.location + baseOffset + range.length, qColor, [style intValue] > 1)); } } void Frame::setMarkedTextRange(Range* range, NSArray* attributes, NSArray* ranges) { int exception; exception = 0; ASSERT(!range || range->startContainer(exception) == range->endContainer(exception)); ASSERT(!range || range->collapsed(exception) || range->startContainer(exception)->isTextNode()); Vector decorations; if (attributes) convertAttributesToUnderlines(decorations, range, attributes, ranges); setMarkedTextRange(range, decorations); } NSMutableDictionary* Frame::dashboardRegionsDictionary() { Document* doc = document(); if (!doc) return nil; const Vector& regions = doc->dashboardRegions(); size_t n = regions.size(); // Convert the Vector into a NSDictionary of WebDashboardRegions NSMutableDictionary* webRegions = [NSMutableDictionary dictionaryWithCapacity:n]; for (size_t i = 0; i < n; i++) { const DashboardRegionValue& region = regions[i]; if (region.type == StyleDashboardRegion::None) continue; NSString *label = region.label; WebDashboardRegionType type = WebDashboardRegionTypeNone; if (region.type == StyleDashboardRegion::Circle) type = WebDashboardRegionTypeCircle; else if (region.type == StyleDashboardRegion::Rectangle) type = WebDashboardRegionTypeRectangle; NSMutableArray *regionValues = [webRegions objectForKey:label]; if (!regionValues) { regionValues = [[NSMutableArray alloc] initWithCapacity:1]; [webRegions setObject:regionValues forKey:label]; [regionValues release]; } WebDashboardRegion *webRegion = [[WebDashboardRegion alloc] initWithRect:region.bounds clip:region.clip type:type]; [regionValues addObject:webRegion]; [webRegion release]; } return webRegions; } void Frame::dashboardRegionsChanged() { NSMutableDictionary *webRegions = dashboardRegionsDictionary(); [d->m_bridge dashboardRegionsChanged:webRegions]; } void Frame::willPopupMenu(NSMenu * menu) { [d->m_bridge willPopupMenu:menu]; } void Frame::setNeedsReapplyStyles() { [d->m_bridge setNeedsReapplyStyles]; } FloatRect Frame::customHighlightLineRect(const AtomicString& type, const FloatRect& lineRect, Node* node) { return [d->m_bridge customHighlightRect:type forLine:lineRect representedNode:node]; } void Frame::paintCustomHighlight(const AtomicString& type, const FloatRect& boxRect, const FloatRect& lineRect, bool text, bool line, Node* node) { [d->m_bridge paintCustomHighlight:type forBox:boxRect onLine:lineRect behindText:text entireLine:line representedNode:node]; } DragImageRef Frame::dragImageForSelection() { if (!selectionController()->isRange()) return nil; return selectionImage(); } KJS::Bindings::Instance* Frame::createScriptInstanceForWidget(WebCore::Widget* widget) { NSView* aView = widget->getView(); if (!aView) return 0; void* nativeHandle = aView; CreateRootObjectFunction createRootObject = RootObject::createRootObject(); RefPtr rootObject = createRootObject(nativeHandle); if ([aView respondsToSelector:@selector(objectForWebScript)]) { id objectForWebScript = [aView objectForWebScript]; if (objectForWebScript) return Instance::createBindingForLanguageInstance(Instance::ObjectiveCLanguage, objectForWebScript, rootObject.release()); return 0; } else if ([aView respondsToSelector:@selector(createPluginScriptableObject)]) { NPObject* npObject = [aView createPluginScriptableObject]; if (npObject) { Instance* instance = Instance::createBindingForLanguageInstance(Instance::CLanguage, npObject, rootObject.release()); // -createPluginScriptableObject returns a retained NPObject. The caller is expected to release it. _NPN_ReleaseObject(npObject); return instance; } return 0; } jobject applet; // Get a pointer to the actual Java applet instance. if ([d->m_bridge respondsToSelector:@selector(getAppletInView:)]) applet = [d->m_bridge getAppletInView:aView]; else applet = [d->m_bridge pollForAppletInView:aView]; if (applet) { // Wrap the Java instance in a language neutral binding and hand // off ownership to the APPLET element. Instance* instance = Instance::createBindingForLanguageInstance(Instance::JavaLanguage, applet, rootObject.release()); return instance; } return 0; } WebScriptObject* Frame::windowScriptObject() { Settings* settings = this->settings(); if (!settings || !settings->isJavaScriptEnabled()) return 0; if (!d->m_windowScriptObject) { KJS::JSLock lock; KJS::JSObject* win = KJS::Window::retrieveWindow(this); KJS::Bindings::RootObject *root = bindingRootObject(); d->m_windowScriptObject = HardRetain([WebScriptObject scriptObjectForJSObject:toRef(win) originRootObject:root rootObject:root]); } return d->m_windowScriptObject; } void Frame::cleanupPlatformScriptObjects() { HardRelease(d->m_windowScriptObject); // Explicitly remove m_windowScriptObject from the wrapper caches, otherwise // the next load might end up with a stale, cached m_windowScriptObject. // (This problem is unique to m_windowScriptObject because its JS/DOM counterparts // persist across page loads.) removeDOMWrapper(reinterpret_cast(d->m_domWindow.get())); if (d->m_jscript && d->m_jscript->haveInterpreter()) removeJSWrapper(KJS::Window::retrieveWindow(this)); d->m_windowScriptObject = 0; } } // namespace WebCore