diff options
Diffstat (limited to 'WebCore/page/mac/WebCoreFrameBridge.mm')
-rw-r--r-- | WebCore/page/mac/WebCoreFrameBridge.mm | 1242 |
1 files changed, 1242 insertions, 0 deletions
diff --git a/WebCore/page/mac/WebCoreFrameBridge.mm b/WebCore/page/mac/WebCoreFrameBridge.mm new file mode 100644 index 0000000..3f20706 --- /dev/null +++ b/WebCore/page/mac/WebCoreFrameBridge.mm @@ -0,0 +1,1242 @@ +/* + * Copyright (C) 2004, 2005, 2006, 2008 Apple Inc. All rights reserved. + * Copyright (C) 2005, 2006 Alexey Proskuryakov (ap@nypop.com) + * Copyright (C) 2006 David Smith (catfish.man@gmail.com) + * + * 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 "WebCoreFrameBridge.h" + +#import "AXObjectCache.h" +#import "CSSHelper.h" +#import "Cache.h" +#import "ClipboardMac.h" +#import "ColorMac.h" +#import "DOMImplementation.h" +#import "DOMInternal.h" +#import "DOMWindow.h" +#import "DeleteSelectionCommand.h" +#import "DocLoader.h" +#import "DocumentFragment.h" +#import "DocumentLoader.h" +#import "DocumentType.h" +#import "Editor.h" +#import "EditorClient.h" +#import "EventHandler.h" +#import "FloatRect.h" +#import "FormDataStreamMac.h" +#import "Frame.h" +#import "FrameLoader.h" +#import "FrameLoaderClient.h" +#import "FrameTree.h" +#import "FrameView.h" +#import "GraphicsContext.h" +#import "HTMLDocument.h" +#import "HTMLFormElement.h" +#import "HTMLInputElement.h" +#import "HTMLNames.h" +#import "HitTestResult.h" +#import "Image.h" +#import "LoaderNSURLExtras.h" +#import "MoveSelectionCommand.h" +#import "Page.h" +#import "PlatformMouseEvent.h" +#import "PlatformScreen.h" +#import "PluginInfoStore.h" +#import "RenderImage.h" +#import "RenderPart.h" +#import "RenderTreeAsText.h" +#import "RenderView.h" +#import "RenderWidget.h" +#import "ReplaceSelectionCommand.h" +#import "ResourceRequest.h" +#import "SelectionController.h" +#import "SimpleFontData.h" +#import "SmartReplace.h" +#import "SubresourceLoader.h" +#import "SystemTime.h" +#import "Text.h" +#import "TextEncoding.h" +#import "TextIterator.h" +#import "TextResourceDecoder.h" +#import "TypingCommand.h" +#import "WebCoreViewFactory.h" +#import "XMLTokenizer.h" +#import "htmlediting.h" +#import "kjs_proxy.h" +#import "kjs_window.h" +#import "markup.h" +#import "visible_units.h" +#import <OpenScripting/ASRegistry.h> +#import <JavaScriptCore/array_instance.h> +#import <JavaScriptCore/date_object.h> +#import <JavaScriptCore/runtime_root.h> +#import <wtf/RetainPtr.h> + +@class NSView; + +using namespace std; +using namespace WebCore; +using namespace HTMLNames; + +using KJS::ArrayInstance; +using KJS::BooleanType; +using KJS::DateInstance; +using KJS::ExecState; +using KJS::GetterSetterType; +using KJS::JSImmediate; +using KJS::JSLock; +using KJS::JSObject; +using KJS::JSValue; +using KJS::NullType; +using KJS::NumberType; +using KJS::ObjectType; +using KJS::SavedBuiltins; +using KJS::SavedProperties; +using KJS::StringType; +using KJS::UndefinedType; +using KJS::UnspecifiedType; +using KJS::Window; + +using KJS::Bindings::RootObject; + +static PassRefPtr<RootObject> createRootObject(void* nativeHandle) +{ + NSView *view = (NSView *)nativeHandle; + WebCoreFrameBridge *bridge = [[WebCoreViewFactory sharedFactory] bridgeForView:view]; + if (!bridge) + return 0; + + Frame* frame = [bridge _frame]; + return frame->createRootObject(nativeHandle, frame->scriptProxy()->globalObject()); +} + +static pthread_t mainThread = 0; + +static void updateRenderingForBindings(ExecState* exec, JSObject* rootObject) +{ + if (pthread_self() != mainThread) + return; + + if (!rootObject) + return; + + Window* window = static_cast<Window*>(rootObject); + if (!window) + return; + + if (Frame* frame = window->impl()->frame()) + if (Document* doc = frame->document()) + doc->updateRendering(); +} + +static NSAppleEventDescriptor* aeDescFromJSValue(ExecState* exec, JSValue* jsValue) +{ + NSAppleEventDescriptor* aeDesc = 0; + switch (jsValue->type()) { + case BooleanType: + aeDesc = [NSAppleEventDescriptor descriptorWithBoolean:jsValue->getBoolean()]; + break; + case StringType: + aeDesc = [NSAppleEventDescriptor descriptorWithString:String(jsValue->getString())]; + break; + case NumberType: { + double value = jsValue->getNumber(); + int intValue = (int)value; + if (value == intValue) + aeDesc = [NSAppleEventDescriptor descriptorWithDescriptorType:typeSInt32 bytes:&intValue length:sizeof(intValue)]; + else + aeDesc = [NSAppleEventDescriptor descriptorWithDescriptorType:typeIEEE64BitFloatingPoint bytes:&value length:sizeof(value)]; + break; + } + case ObjectType: { + JSObject* object = jsValue->getObject(); + if (object->inherits(&DateInstance::info)) { + DateInstance* date = static_cast<DateInstance*>(object); + double ms = 0; + int tzOffset = 0; + if (date->getTime(ms, tzOffset)) { + CFAbsoluteTime utcSeconds = ms / 1000 - kCFAbsoluteTimeIntervalSince1970; + LongDateTime ldt; + if (noErr == UCConvertCFAbsoluteTimeToLongDateTime(utcSeconds, &ldt)) + aeDesc = [NSAppleEventDescriptor descriptorWithDescriptorType:typeLongDateTime bytes:&ldt length:sizeof(ldt)]; + } + } + else if (object->inherits(&ArrayInstance::info)) { + static HashSet<JSObject*> visitedElems; + if (!visitedElems.contains(object)) { + visitedElems.add(object); + + ArrayInstance* array = static_cast<ArrayInstance*>(object); + aeDesc = [NSAppleEventDescriptor listDescriptor]; + unsigned numItems = array->getLength(); + for (unsigned i = 0; i < numItems; ++i) + [aeDesc insertDescriptor:aeDescFromJSValue(exec, array->getItem(i)) atIndex:0]; + + visitedElems.remove(object); + } + } + if (!aeDesc) { + JSValue* primitive = object->toPrimitive(exec); + if (exec->hadException()) { + exec->clearException(); + return [NSAppleEventDescriptor nullDescriptor]; + } + return aeDescFromJSValue(exec, primitive); + } + break; + } + case UndefinedType: + aeDesc = [NSAppleEventDescriptor descriptorWithTypeCode:cMissingValue]; + break; + default: + LOG_ERROR("Unknown JavaScript type: %d", jsValue->type()); + // no break; + case UnspecifiedType: + case NullType: + case GetterSetterType: + aeDesc = [NSAppleEventDescriptor nullDescriptor]; + break; + } + + return aeDesc; +} + +@implementation WebCoreFrameBridge + +static inline WebCoreFrameBridge *bridge(Frame *frame) +{ + if (!frame) + return nil; + return frame->bridge(); +} + +- (NSString *)domain +{ + Document *doc = m_frame->document(); + if (doc) + return doc->domain(); + return nil; +} + ++ (WebCoreFrameBridge *)bridgeForDOMDocument:(DOMDocument *)document +{ + return bridge([document _document]->frame()); +} + +- (id)init +{ + static bool initializedKJS; + if (!initializedKJS) { + initializedKJS = true; + + mainThread = pthread_self(); + RootObject::setCreateRootObject(createRootObject); + KJS::Bindings::Instance::setDidExecuteFunction(updateRenderingForBindings); + } + + if (!(self = [super init])) + return nil; + + _shouldCreateRenderers = YES; + return self; +} + +- (void)dealloc +{ + ASSERT(_closed); + [super dealloc]; +} + +- (void)finalize +{ + ASSERT(_closed); + [super finalize]; +} + +- (void)close +{ + [self clearFrame]; + _closed = YES; +} + +- (void)addData:(NSData *)data +{ + Document *doc = m_frame->document(); + + // Document may be nil if the part is about to redirect + // as a result of JS executing during load, i.e. one frame + // changing another's location before the frame's document + // has been created. + if (doc) { + doc->setShouldCreateRenderers(_shouldCreateRenderers); + m_frame->loader()->addData((const char *)[data bytes], [data length]); + } +} + +- (BOOL)scrollOverflowInDirection:(WebScrollDirection)direction granularity:(WebScrollGranularity)granularity +{ + if (!m_frame) + return NO; + return m_frame->eventHandler()->scrollOverflow((ScrollDirection)direction, (ScrollGranularity)granularity); +} + +- (void)clearFrame +{ + m_frame = 0; +} + +- (void)createFrameViewWithNSView:(NSView *)view marginWidth:(int)mw marginHeight:(int)mh +{ + // If we own the view, delete the old one - otherwise the render m_frame will take care of deleting the view. + if (m_frame) + m_frame->setView(0); + + FrameView* frameView = new FrameView(m_frame); + m_frame->setView(frameView); + frameView->deref(); + + frameView->setView(view); + if (mw >= 0) + frameView->setMarginWidth(mw); + if (mh >= 0) + frameView->setMarginHeight(mh); +} + +- (NSString *)_stringWithDocumentTypeStringAndMarkupString:(NSString *)markupString +{ + return m_frame->documentTypeString() + markupString; +} + +- (NSArray *)nodesFromList:(Vector<Node*> *)nodesVector +{ + size_t size = nodesVector->size(); + NSMutableArray *nodes = [NSMutableArray arrayWithCapacity:size]; + for (size_t i = 0; i < size; ++i) + [nodes addObject:[DOMNode _wrapNode:(*nodesVector)[i]]]; + return nodes; +} + +- (NSString *)markupStringFromNode:(DOMNode *)node nodes:(NSArray **)nodes +{ + // FIXME: This is never "for interchange". Is that right? See the next method. + Vector<Node*> nodeList; + NSString *markupString = createMarkup([node _node], IncludeNode, nodes ? &nodeList : 0); + if (nodes) + *nodes = [self nodesFromList:&nodeList]; + + return [self _stringWithDocumentTypeStringAndMarkupString:markupString]; +} + +- (NSString *)markupStringFromRange:(DOMRange *)range nodes:(NSArray **)nodes +{ + // FIXME: This is always "for interchange". Is that right? See the previous method. + Vector<Node*> nodeList; + NSString *markupString = createMarkup([range _range], nodes ? &nodeList : 0, AnnotateForInterchange); + if (nodes) + *nodes = [self nodesFromList:&nodeList]; + + return [self _stringWithDocumentTypeStringAndMarkupString:markupString]; +} + +- (NSString *)selectedString +{ + String text = m_frame->selectedText(); + text.replace('\\', m_frame->backslashAsCurrencySymbol()); + return text; +} + +- (NSString *)stringForRange:(DOMRange *)range +{ + // This will give a system malloc'd buffer that can be turned directly into an NSString + unsigned length; + UChar* buf = plainTextToMallocAllocatedBuffer([range _range], length); + + if (!buf) + return [NSString string]; + + UChar backslashAsCurrencySymbol = m_frame->backslashAsCurrencySymbol(); + if (backslashAsCurrencySymbol != '\\') + for (unsigned n = 0; n < length; n++) + if (buf[n] == '\\') + buf[n] = backslashAsCurrencySymbol; + + // Transfer buffer ownership to NSString + return [[[NSString alloc] initWithCharactersNoCopy:buf length:length freeWhenDone:YES] autorelease]; +} + +- (void)reapplyStylesForDeviceType:(WebCoreDeviceType)deviceType +{ + if (m_frame->view()) + m_frame->view()->setMediaType(deviceType == WebCoreDeviceScreen ? "screen" : "print"); + Document *doc = m_frame->document(); + if (doc) + doc->setPrinting(deviceType == WebCoreDevicePrinter); + m_frame->reapplyStyles(); +} + +- (void)forceLayoutAdjustingViewSize:(BOOL)flag +{ + m_frame->forceLayout(!flag); + if (flag) + m_frame->view()->adjustViewSize(); +} + +- (void)forceLayoutWithMinimumPageWidth:(float)minPageWidth maximumPageWidth:(float)maxPageWidth adjustingViewSize:(BOOL)flag +{ + m_frame->forceLayoutWithPageWidthRange(minPageWidth, maxPageWidth, flag); +} + +- (void)sendScrollEvent +{ + m_frame->sendScrollEvent(); +} + +- (void)drawRect:(NSRect)rect +{ + PlatformGraphicsContext* platformContext = static_cast<PlatformGraphicsContext*>([[NSGraphicsContext currentContext] graphicsPort]); + ASSERT([[NSGraphicsContext currentContext] isFlipped]); + GraphicsContext context(platformContext); + + m_frame->paint(&context, enclosingIntRect(rect)); +} + +// Used by pagination code called from AppKit when a standalone web page is printed. +- (NSArray*)computePageRectsWithPrintWidthScaleFactor:(float)printWidthScaleFactor printHeight:(float)printHeight +{ + NSMutableArray* pages = [NSMutableArray arrayWithCapacity:5]; + if (printWidthScaleFactor <= 0) { + LOG_ERROR("printWidthScaleFactor has bad value %.2f", printWidthScaleFactor); + return pages; + } + + if (printHeight <= 0) { + LOG_ERROR("printHeight has bad value %.2f", printHeight); + return pages; + } + + if (!m_frame || !m_frame->document() || !m_frame->view()) return pages; + RenderView* root = static_cast<RenderView *>(m_frame->document()->renderer()); + if (!root) return pages; + + FrameView* view = m_frame->view(); + if (!view) + return pages; + + NSView* documentView = view->getDocumentView(); + if (!documentView) + return pages; + + float currPageHeight = printHeight; + float docHeight = root->layer()->height(); + float docWidth = root->layer()->width(); + float printWidth = docWidth/printWidthScaleFactor; + + // We need to give the part the opportunity to adjust the page height at each step. + for (float i = 0; i < docHeight; i += currPageHeight) { + float proposedBottom = min(docHeight, i + printHeight); + m_frame->adjustPageHeight(&proposedBottom, i, proposedBottom, i); + currPageHeight = max(1.0f, proposedBottom - i); + for (float j = 0; j < docWidth; j += printWidth) { + NSValue* val = [NSValue valueWithRect: NSMakeRect(j, i, printWidth, currPageHeight)]; + [pages addObject: val]; + } + } + + return pages; +} + +// This is to support the case where a webview is embedded in the view that's being printed +- (void)adjustPageHeightNew:(float *)newBottom top:(float)oldTop bottom:(float)oldBottom limit:(float)bottomLimit +{ + m_frame->adjustPageHeight(newBottom, oldTop, oldBottom, bottomLimit); +} + +- (NSObject *)copyRenderNode:(RenderObject *)node copier:(id <WebCoreRenderTreeCopier>)copier +{ + NSMutableArray *children = [[NSMutableArray alloc] init]; + for (RenderObject *child = node->firstChild(); child; child = child->nextSibling()) { + [children addObject:[self copyRenderNode:child copier:copier]]; + } + + NSString *name = [[NSString alloc] initWithUTF8String:node->renderName()]; + + RenderWidget* renderWidget = node->isWidget() ? static_cast<RenderWidget*>(node) : 0; + Widget* widget = renderWidget ? renderWidget->widget() : 0; + NSView *view = widget ? widget->getView() : nil; + + int nx, ny; + node->absolutePosition(nx, ny); + NSObject *copiedNode = [copier nodeWithName:name + position:NSMakePoint(nx,ny) + rect:NSMakeRect(node->xPos(), node->yPos(), node->width(), node->height()) + view:view + children:children]; + + [name release]; + [children release]; + + return copiedNode; +} + +- (NSObject *)copyRenderTree:(id <WebCoreRenderTreeCopier>)copier +{ + RenderObject *renderer = m_frame->renderer(); + if (!renderer) { + return nil; + } + return [self copyRenderNode:renderer copier:copier]; +} + +- (void)installInFrame:(NSView *)view +{ + // If this isn't the main frame, it must have a render m_frame set, or it + // won't ever get installed in the view hierarchy. + ASSERT(m_frame == m_frame->page()->mainFrame() || m_frame->ownerElement()); + + m_frame->view()->setView(view); + // FIXME: frame tries to do this too, is it needed? + if (m_frame->ownerRenderer()) { + m_frame->ownerRenderer()->setWidget(m_frame->view()); + // Now the render part owns the view, so we don't any more. + } + + m_frame->view()->initScrollbars(); +} + +static HTMLInputElement* inputElementFromDOMElement(DOMElement* element) +{ + Node* node = [element _node]; + if (node->hasTagName(inputTag)) + return static_cast<HTMLInputElement*>(node); + return nil; +} + +static HTMLFormElement *formElementFromDOMElement(DOMElement *element) +{ + Node *node = [element _node]; + // This should not be necessary, but an XSL file on + // maps.google.com crashes otherwise because it is an xslt file + // that contains <form> elements that aren't in any namespace, so + // they come out as generic CML elements + if (node && node->hasTagName(formTag)) { + return static_cast<HTMLFormElement *>(node); + } + return nil; +} + +- (DOMElement *)elementWithName:(NSString *)name inForm:(DOMElement *)form +{ + HTMLFormElement *formElement = formElementFromDOMElement(form); + if (formElement) { + Vector<HTMLGenericFormElement*>& elements = formElement->formElements; + AtomicString targetName = name; + for (unsigned int i = 0; i < elements.size(); i++) { + HTMLGenericFormElement *elt = elements[i]; + // Skip option elements, other duds + if (elt->name() == targetName) + return [DOMElement _wrapElement:elt]; + } + } + return nil; +} + +- (BOOL)elementDoesAutoComplete:(DOMElement *)element +{ + HTMLInputElement *inputElement = inputElementFromDOMElement(element); + return inputElement != nil + && inputElement->inputType() == HTMLInputElement::TEXT + && inputElement->autoComplete(); +} + +- (BOOL)elementIsPassword:(DOMElement *)element +{ + HTMLInputElement *inputElement = inputElementFromDOMElement(element); + return inputElement != nil + && inputElement->inputType() == HTMLInputElement::PASSWORD; +} + +- (DOMElement *)formForElement:(DOMElement *)element; +{ + HTMLInputElement *inputElement = inputElementFromDOMElement(element); + if (inputElement) { + HTMLFormElement *formElement = inputElement->form(); + if (formElement) { + return [DOMElement _wrapElement:formElement]; + } + } + return nil; +} + +- (DOMElement *)currentForm +{ + return [DOMElement _wrapElement:m_frame->currentForm()]; +} + +- (NSArray *)controlsInForm:(DOMElement *)form +{ + NSMutableArray *results = nil; + HTMLFormElement *formElement = formElementFromDOMElement(form); + if (formElement) { + Vector<HTMLGenericFormElement*>& elements = formElement->formElements; + for (unsigned int i = 0; i < elements.size(); i++) { + if (elements.at(i)->isEnumeratable()) { // Skip option elements, other duds + DOMElement *de = [DOMElement _wrapElement:elements.at(i)]; + if (!results) { + results = [NSMutableArray arrayWithObject:de]; + } else { + [results addObject:de]; + } + } + } + } + return results; +} + +- (NSString *)searchForLabels:(NSArray *)labels beforeElement:(DOMElement *)element +{ + return m_frame->searchForLabelsBeforeElement(labels, [element _element]); +} + +- (NSString *)matchLabels:(NSArray *)labels againstElement:(DOMElement *)element +{ + return m_frame->matchLabelsAgainstElement(labels, [element _element]); +} + +- (NSURL *)URLWithAttributeString:(NSString *)string +{ + Document* doc = m_frame->document(); + if (!doc) + return nil; + // FIXME: is parseURL appropriate here? + return doc->completeURL(parseURL(string)); +} + +- (BOOL)searchFor:(NSString *)string direction:(BOOL)forward caseSensitive:(BOOL)caseFlag wrap:(BOOL)wrapFlag startInSelection:(BOOL)startInSelection +{ + return m_frame->findString(string, forward, caseFlag, wrapFlag, startInSelection); +} + +- (unsigned)markAllMatchesForText:(NSString *)string caseSensitive:(BOOL)caseFlag limit:(unsigned)limit +{ + return m_frame->markAllMatchesForText(string, caseFlag, limit); +} + +- (BOOL)markedTextMatchesAreHighlighted +{ + return m_frame->markedTextMatchesAreHighlighted(); +} + +- (void)setMarkedTextMatchesAreHighlighted:(BOOL)doHighlight +{ + m_frame->setMarkedTextMatchesAreHighlighted(doHighlight); +} + +- (void)unmarkAllTextMatches +{ + Document *doc = m_frame->document(); + if (!doc) { + return; + } + doc->removeMarkers(DocumentMarker::TextMatch); +} + +- (NSArray *)rectsForTextMatches +{ + Document *doc = m_frame->document(); + if (!doc) + return [NSArray array]; + + NSMutableArray *result = [NSMutableArray array]; + Vector<IntRect> rects = doc->renderedRectsForMarkers(DocumentMarker::TextMatch); + unsigned count = rects.size(); + for (unsigned index = 0; index < count; ++index) + [result addObject:[NSValue valueWithRect:rects[index]]]; + + return result; +} + +- (void)setTextSizeMultiplier:(float)multiplier +{ + int newZoomFactor = (int)rint(multiplier * 100); + if (m_frame->zoomFactor() == newZoomFactor) { + return; + } + m_frame->setZoomFactor(newZoomFactor); +} + +- (NSString *)stringByEvaluatingJavaScriptFromString:(NSString *)string +{ + return [self stringByEvaluatingJavaScriptFromString:string forceUserGesture:true]; +} + +- (NSString *)stringByEvaluatingJavaScriptFromString:(NSString *)string forceUserGesture:(BOOL)forceUserGesture +{ + ASSERT(m_frame->document()); + + JSValue* result = m_frame->loader()->executeScript(string, forceUserGesture); + + if (!m_frame) // In case the script removed our frame from the page. + return @""; + + // This bizarre set of rules matches behavior from WebKit for Safari 2.0. + // If you don't like it, use -[WebScriptObject evaluateWebScript:] or + // JSEvaluateScript instead, since they have less surprising semantics. + if (!result || !result->isBoolean() && !result->isString() && !result->isNumber()) + return @""; + + JSLock lock; + return String(result->toString(m_frame->scriptProxy()->globalObject()->globalExec())); +} + +- (NSAppleEventDescriptor *)aeDescByEvaluatingJavaScriptFromString:(NSString *)string +{ + ASSERT(m_frame->document()); + ASSERT(m_frame == m_frame->page()->mainFrame()); + JSValue* result = m_frame->loader()->executeScript(string, true); + if (!result) // FIXME: pass errors + return 0; + JSLock lock; + return aeDescFromJSValue(m_frame->scriptProxy()->globalObject()->globalExec(), result); +} + +- (NSRect)caretRectAtNode:(DOMNode *)node offset:(int)offset affinity:(NSSelectionAffinity)affinity +{ + return [node _node]->renderer()->caretRect(offset, static_cast<EAffinity>(affinity)); +} + +- (NSRect)firstRectForDOMRange:(DOMRange *)range +{ + return m_frame->firstRectForRange([range _range]); +} + +- (void)scrollDOMRangeToVisible:(DOMRange *)range +{ + NSRect rangeRect = [self firstRectForDOMRange:range]; + Node *startNode = [[range startContainer] _node]; + + if (startNode && startNode->renderer()) { + RenderLayer *layer = startNode->renderer()->enclosingLayer(); + if (layer) + layer->scrollRectToVisible(enclosingIntRect(rangeRect), RenderLayer::gAlignToEdgeIfNeeded, RenderLayer::gAlignToEdgeIfNeeded); + } +} + +- (NSURL *)baseURL +{ + return m_frame->document()->baseURL(); +} + +- (NSString *)stringWithData:(NSData *)data +{ + Document* doc = m_frame->document(); + if (!doc) + return nil; + TextResourceDecoder* decoder = doc->decoder(); + if (!decoder) + return nil; + return decoder->encoding().decode(reinterpret_cast<const char*>([data bytes]), [data length]); +} + ++ (NSString *)stringWithData:(NSData *)data textEncodingName:(NSString *)textEncodingName +{ + WebCore::TextEncoding encoding(textEncodingName); + if (!encoding.isValid()) + encoding = WindowsLatin1Encoding(); + return encoding.decode(reinterpret_cast<const char*>([data bytes]), [data length]); +} + +- (BOOL)needsLayout +{ + return m_frame->view() ? m_frame->view()->needsLayout() : false; +} + +- (NSString *)renderTreeAsExternalRepresentation +{ + return externalRepresentation(m_frame->renderer()); +} + +- (void)setShouldCreateRenderers:(BOOL)f +{ + _shouldCreateRenderers = f; +} + +- (id)accessibilityTree +{ + AXObjectCache::enableAccessibility(); + if (!m_frame || !m_frame->document()) + return nil; + RenderView* root = static_cast<RenderView *>(m_frame->document()->renderer()); + if (!root) + return nil; + return m_frame->document()->axObjectCache()->get(root); +} + +- (void)setBaseBackgroundColor:(NSColor *)backgroundColor +{ + if (m_frame && m_frame->view()) { + Color color = colorFromNSColor([backgroundColor colorUsingColorSpaceName:NSDeviceRGBColorSpace]); + m_frame->view()->setBaseBackgroundColor(color); + } +} + +- (void)setDrawsBackground:(BOOL)drawsBackground +{ + if (m_frame && m_frame->view()) + m_frame->view()->setTransparent(!drawsBackground); +} + +- (DOMRange *)rangeByAlteringCurrentSelection:(SelectionController::EAlteration)alteration direction:(SelectionController::EDirection)direction granularity:(TextGranularity)granularity +{ + if (m_frame->selectionController()->isNone()) + return nil; + + SelectionController selectionController; + selectionController.setSelection(m_frame->selectionController()->selection()); + selectionController.modify(alteration, direction, granularity); + return [DOMRange _wrapRange:selectionController.toRange().get()]; +} + +- (TextGranularity)selectionGranularity +{ + return m_frame->selectionGranularity(); +} + +- (NSRange)convertToNSRange:(Range *)range +{ + int exception = 0; + + if (!range || range->isDetached()) + return NSMakeRange(NSNotFound, 0); + + Element* selectionRoot = m_frame->selectionController()->rootEditableElement(); + Element* scope = selectionRoot ? selectionRoot : m_frame->document()->documentElement(); + + // Mouse events may cause TSM to attempt to create an NSRange for a portion of the view + // that is not inside the current editable region. These checks ensure we don't produce + // potentially invalid data when responding to such requests. + if (range->startContainer(exception) != scope && !range->startContainer(exception)->isDescendantOf(scope)) + return NSMakeRange(NSNotFound, 0); + if(range->endContainer(exception) != scope && !range->endContainer(exception)->isDescendantOf(scope)) + return NSMakeRange(NSNotFound, 0); + + RefPtr<Range> testRange = new Range(scope->document(), scope, 0, range->startContainer(exception), range->startOffset(exception)); + ASSERT(testRange->startContainer(exception) == scope); + int startPosition = TextIterator::rangeLength(testRange.get()); + + testRange->setEnd(range->endContainer(exception), range->endOffset(exception), exception); + ASSERT(testRange->startContainer(exception) == scope); + int endPosition = TextIterator::rangeLength(testRange.get()); + + return NSMakeRange(startPosition, endPosition - startPosition); +} + +- (PassRefPtr<Range>)convertToDOMRange:(NSRange)nsrange +{ + if (nsrange.location > INT_MAX) + return 0; + if (nsrange.length > INT_MAX || nsrange.location + nsrange.length > INT_MAX) + nsrange.length = INT_MAX - nsrange.location; + + // our critical assumption is that we are only called by input methods that + // concentrate on a given area containing the selection + // We have to do this because of text fields and textareas. The DOM for those is not + // directly in the document DOM, so serialization is problematic. Our solution is + // to use the root editable element of the selection start as the positional base. + // That fits with AppKit's idea of an input context. + Element* selectionRoot = m_frame->selectionController()->rootEditableElement(); + Element* scope = selectionRoot ? selectionRoot : m_frame->document()->documentElement(); + return TextIterator::rangeFromLocationAndLength(scope, nsrange.location, nsrange.length); +} + +- (DOMRange *)convertNSRangeToDOMRange:(NSRange)nsrange +{ + return [DOMRange _wrapRange:[self convertToDOMRange:nsrange].get()]; +} + +- (NSRange)convertDOMRangeToNSRange:(DOMRange *)range +{ + return [self convertToNSRange:[range _range]]; +} + +- (void)selectNSRange:(NSRange)range +{ + RefPtr<Range> domRange = [self convertToDOMRange:range]; + if (domRange) + m_frame->selectionController()->setSelection(Selection(domRange.get(), SEL_DEFAULT_AFFINITY)); +} + +- (NSRange)selectedNSRange +{ + return [self convertToNSRange:m_frame->selectionController()->toRange().get()]; +} + +- (DOMRange *)markDOMRange +{ + return [DOMRange _wrapRange:m_frame->mark().toRange().get()]; +} + +- (NSRange)markedTextNSRange +{ + return [self convertToNSRange:m_frame->editor()->compositionRange().get()]; +} + +// Given proposedRange, returns an extended range that includes adjacent whitespace that should +// be deleted along with the proposed range in order to preserve proper spacing and punctuation of +// the text surrounding the deletion. +- (DOMRange *)smartDeleteRangeForProposedRange:(DOMRange *)proposedRange +{ + Node *startContainer = [[proposedRange startContainer] _node]; + Node *endContainer = [[proposedRange endContainer] _node]; + if (startContainer == nil || endContainer == nil) + return nil; + + ASSERT(startContainer->document() == endContainer->document()); + + m_frame->document()->updateLayoutIgnorePendingStylesheets(); + + Position start(startContainer, [proposedRange startOffset]); + Position end(endContainer, [proposedRange endOffset]); + Position newStart = start.upstream().leadingWhitespacePosition(DOWNSTREAM, true); + if (newStart.isNull()) + newStart = start; + Position newEnd = end.downstream().trailingWhitespacePosition(DOWNSTREAM, true); + if (newEnd.isNull()) + newEnd = end; + + newStart = rangeCompliantEquivalent(newStart); + newEnd = rangeCompliantEquivalent(newEnd); + + RefPtr<Range> range = m_frame->document()->createRange(); + int exception = 0; + range->setStart(newStart.node(), newStart.offset(), exception); + range->setEnd(newStart.node(), newStart.offset(), exception); + return [DOMRange _wrapRange:range.get()]; +} + +// Determines whether whitespace needs to be added around aString to preserve proper spacing and +// punctuation when it’s inserted into the receiver’s text over charRange. Returns by reference +// in beforeString and afterString any whitespace that should be added, unless either or both are +// nil. Both are returned as nil if aString is nil or if smart insertion and deletion are disabled. +- (void)smartInsertForString:(NSString *)pasteString replacingRange:(DOMRange *)rangeToReplace beforeString:(NSString **)beforeString afterString:(NSString **)afterString +{ + // give back nil pointers in case of early returns + if (beforeString) + *beforeString = nil; + if (afterString) + *afterString = nil; + + // inspect destination + Node *startContainer = [[rangeToReplace startContainer] _node]; + Node *endContainer = [[rangeToReplace endContainer] _node]; + + Position startPos(startContainer, [rangeToReplace startOffset]); + Position endPos(endContainer, [rangeToReplace endOffset]); + + VisiblePosition startVisiblePos = VisiblePosition(startPos, VP_DEFAULT_AFFINITY); + VisiblePosition endVisiblePos = VisiblePosition(endPos, VP_DEFAULT_AFFINITY); + + // this check also ensures startContainer, startPos, endContainer, and endPos are non-null + if (startVisiblePos.isNull() || endVisiblePos.isNull()) + return; + + bool addLeadingSpace = startPos.leadingWhitespacePosition(VP_DEFAULT_AFFINITY, true).isNull() && !isStartOfParagraph(startVisiblePos); + if (addLeadingSpace) + if (UChar previousChar = startVisiblePos.previous().characterAfter()) + addLeadingSpace = !isCharacterSmartReplaceExempt(previousChar, true); + + bool addTrailingSpace = endPos.trailingWhitespacePosition(VP_DEFAULT_AFFINITY, true).isNull() && !isEndOfParagraph(endVisiblePos); + if (addTrailingSpace) + if (UChar thisChar = endVisiblePos.characterAfter()) + addTrailingSpace = !isCharacterSmartReplaceExempt(thisChar, false); + + // inspect source + bool hasWhitespaceAtStart = false; + bool hasWhitespaceAtEnd = false; + unsigned pasteLength = [pasteString length]; + if (pasteLength > 0) { + NSCharacterSet *whiteSet = [NSCharacterSet whitespaceAndNewlineCharacterSet]; + + if ([whiteSet characterIsMember:[pasteString characterAtIndex:0]]) { + hasWhitespaceAtStart = YES; + } + if ([whiteSet characterIsMember:[pasteString characterAtIndex:(pasteLength - 1)]]) { + hasWhitespaceAtEnd = YES; + } + } + + // issue the verdict + if (beforeString && addLeadingSpace && !hasWhitespaceAtStart) + *beforeString = @" "; + if (afterString && addTrailingSpace && !hasWhitespaceAtEnd) + *afterString = @" "; +} + +- (DOMDocumentFragment *)documentFragmentWithMarkupString:(NSString *)markupString baseURLString:(NSString *)baseURLString +{ + if (!m_frame || !m_frame->document()) + return 0; + + return [DOMDocumentFragment _wrapDocumentFragment:createFragmentFromMarkup(m_frame->document(), markupString, baseURLString).get()]; +} + +- (DOMDocumentFragment *)documentFragmentWithText:(NSString *)text inContext:(DOMRange *)context +{ + return [DOMDocumentFragment _wrapDocumentFragment:createFragmentFromText([context _range], text).get()]; +} + +- (DOMDocumentFragment *)documentFragmentWithNodesAsParagraphs:(NSArray *)nodes +{ + if (!m_frame || !m_frame->document()) + return 0; + + NSEnumerator *nodeEnum = [nodes objectEnumerator]; + Vector<Node*> nodesVector; + DOMNode *node; + while ((node = [nodeEnum nextObject])) + nodesVector.append([node _node]); + + return [DOMDocumentFragment _wrapDocumentFragment:createFragmentFromNodes(m_frame->document(), nodesVector).get()]; +} + +- (void)replaceSelectionWithFragment:(DOMDocumentFragment *)fragment selectReplacement:(BOOL)selectReplacement smartReplace:(BOOL)smartReplace matchStyle:(BOOL)matchStyle +{ + if (m_frame->selectionController()->isNone() || !fragment) + return; + + applyCommand(new ReplaceSelectionCommand(m_frame->document(), [fragment _documentFragment], selectReplacement, smartReplace, matchStyle)); + m_frame->revealSelection(RenderLayer::gAlignToEdgeIfNeeded); +} + +- (void)replaceSelectionWithNode:(DOMNode *)node selectReplacement:(BOOL)selectReplacement smartReplace:(BOOL)smartReplace matchStyle:(BOOL)matchStyle +{ + DOMDocumentFragment *fragment = [DOMDocumentFragment _wrapDocumentFragment:m_frame->document()->createDocumentFragment().get()]; + [fragment appendChild:node]; + [self replaceSelectionWithFragment:fragment selectReplacement:selectReplacement smartReplace:smartReplace matchStyle:matchStyle]; +} + +- (void)replaceSelectionWithMarkupString:(NSString *)markupString baseURLString:(NSString *)baseURLString selectReplacement:(BOOL)selectReplacement smartReplace:(BOOL)smartReplace +{ + DOMDocumentFragment *fragment = [self documentFragmentWithMarkupString:markupString baseURLString:baseURLString]; + [self replaceSelectionWithFragment:fragment selectReplacement:selectReplacement smartReplace:smartReplace matchStyle:NO]; +} + +- (void)replaceSelectionWithText:(NSString *)text selectReplacement:(BOOL)selectReplacement smartReplace:(BOOL)smartReplace +{ + [self replaceSelectionWithFragment:[self documentFragmentWithText:text + inContext:[DOMRange _wrapRange:m_frame->selectionController()->toRange().get()]] + selectReplacement:selectReplacement smartReplace:smartReplace matchStyle:YES]; +} + +- (void)insertParagraphSeparatorInQuotedContent +{ + if (m_frame->selectionController()->isNone()) + return; + + TypingCommand::insertParagraphSeparatorInQuotedContent(m_frame->document()); + m_frame->revealSelection(RenderLayer::gAlignToEdgeIfNeeded); +} + +- (VisiblePosition)_visiblePositionForPoint:(NSPoint)point +{ + IntPoint outerPoint(point); + HitTestResult result = m_frame->eventHandler()->hitTestResultAtPoint(outerPoint, true); + Node* node = result.innerNode(); + if (!node) + return VisiblePosition(); + RenderObject* renderer = node->renderer(); + if (!renderer) + return VisiblePosition(); + VisiblePosition visiblePos = renderer->positionForCoordinates(result.localPoint().x(), result.localPoint().y()); + if (visiblePos.isNull()) + visiblePos = VisiblePosition(Position(node, 0)); + return visiblePos; +} + +- (DOMRange *)characterRangeAtPoint:(NSPoint)point +{ + VisiblePosition position = [self _visiblePositionForPoint:point]; + if (position.isNull()) + return nil; + + VisiblePosition previous = position.previous(); + if (previous.isNotNull()) { + DOMRange *previousCharacterRange = [DOMRange _wrapRange:makeRange(previous, position).get()]; + NSRect rect = [self firstRectForDOMRange:previousCharacterRange]; + if (NSPointInRect(point, rect)) + return previousCharacterRange; + } + + VisiblePosition next = position.next(); + if (next.isNotNull()) { + DOMRange *nextCharacterRange = [DOMRange _wrapRange:makeRange(position, next).get()]; + NSRect rect = [self firstRectForDOMRange:nextCharacterRange]; + if (NSPointInRect(point, rect)) + return nextCharacterRange; + } + + return nil; +} + +- (DOMCSSStyleDeclaration *)typingStyle +{ + if (!m_frame || !m_frame->typingStyle()) + return nil; + return [DOMCSSStyleDeclaration _wrapCSSStyleDeclaration:m_frame->typingStyle()->copy().get()]; +} + +- (void)setTypingStyle:(DOMCSSStyleDeclaration *)style withUndoAction:(EditAction)undoAction +{ + if (!m_frame) + return; + m_frame->computeAndSetTypingStyle([style _CSSStyleDeclaration], undoAction); +} + +- (NSFont *)fontForSelection:(BOOL *)hasMultipleFonts +{ + bool multipleFonts = false; + NSFont *font = nil; + if (m_frame) { + const SimpleFontData* fd = m_frame->editor()->fontForSelection(multipleFonts); + if (fd) + font = fd->getNSFont(); + } + + if (hasMultipleFonts) + *hasMultipleFonts = multipleFonts; + return font; +} + +- (void)dragSourceMovedTo:(NSPoint)windowLoc +{ + if (m_frame) { + // FIXME: Fake modifier keys here. + PlatformMouseEvent event(IntPoint(windowLoc), globalPoint(windowLoc, [self window]), + LeftButton, MouseEventMoved, 0, false, false, false, false, currentTime()); + m_frame->eventHandler()->dragSourceMovedTo(event); + } +} + +- (void)dragSourceEndedAt:(NSPoint)windowLoc operation:(NSDragOperation)operation +{ + if (m_frame) { + // FIXME: Fake modifier keys here. + PlatformMouseEvent event(IntPoint(windowLoc), globalPoint(windowLoc, [self window]), + LeftButton, MouseEventMoved, 0, false, false, false, false, currentTime()); + m_frame->eventHandler()->dragSourceEndedAt(event, (DragOperation)operation); + } +} + +- (BOOL)getData:(NSData **)data andResponse:(NSURLResponse **)response forURL:(NSString *)url +{ + Document* doc = m_frame->document(); + if (!doc) + return NO; + + CachedResource* resource = doc->docLoader()->cachedResource(url); + if (!resource) + return NO; + + SharedBuffer* buffer = resource->data(); + if (buffer) + *data = [buffer->createNSData() autorelease]; + else + *data = nil; + + *response = resource->response().nsURLResponse(); + return YES; +} + +- (void)getAllResourceDatas:(NSArray **)datas andResponses:(NSArray **)responses +{ + Document* doc = m_frame->document(); + if (!doc) { + NSArray* emptyArray = [NSArray array]; + *datas = emptyArray; + *responses = emptyArray; + return; + } + + const HashMap<String, CachedResource*>& allResources = doc->docLoader()->allCachedResources(); + + NSMutableArray *d = [[NSMutableArray alloc] initWithCapacity:allResources.size()]; + NSMutableArray *r = [[NSMutableArray alloc] initWithCapacity:allResources.size()]; + + HashMap<String, CachedResource*>::const_iterator end = allResources.end(); + for (HashMap<String, CachedResource*>::const_iterator it = allResources.begin(); it != end; ++it) { + SharedBuffer* buffer = it->second->data(); + NSData *data; + if (buffer) + data = buffer->createNSData(); + else + data = [[NSData alloc] init]; + [d addObject:data]; + [data release]; + [r addObject:it->second->response().nsURLResponse()]; + } + + *datas = [d autorelease]; + *responses = [r autorelease]; +} + +- (BOOL)canProvideDocumentSource +{ + String mimeType = m_frame->loader()->responseMIMEType(); + + if (WebCore::DOMImplementation::isTextMIMEType(mimeType) || + Image::supportsType(mimeType) || + PluginInfoStore::supportsMIMEType(mimeType)) + return NO; + + return YES; +} + +- (BOOL)canSaveAsWebArchive +{ + // Currently, all documents that we can view source for + // (HTML and XML documents) can also be saved as web archives + return [self canProvideDocumentSource]; +} + +- (void)receivedData:(NSData *)data textEncodingName:(NSString *)textEncodingName +{ + // Set the encoding. This only needs to be done once, but it's harmless to do it again later. + String encoding; + if (m_frame) + encoding = m_frame->loader()->documentLoader()->overrideEncoding(); + bool userChosen = !encoding.isNull(); + if (encoding.isNull()) + encoding = textEncodingName; + m_frame->loader()->setEncoding(encoding, userChosen); + [self addData:data]; +} + +// ------------------- + +- (Frame*)_frame +{ + return m_frame; +} + +@end |