blob: d280bcb8e48c390d6c73ff55b3e16615128c0373 [file] [log] [blame]
/*
* Copyright (C) 2004, 2005, 2006 Apple Computer, 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 "WebCoreSystemInterface.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?
DeprecatedString rel = parseURL(string).deprecatedString();
return KURL(doc->completeURL(rel)).getNSURL();
}
- (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->loader()->completeURL(m_frame->document()->baseURL()).getNSURL();
}
- (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()).getNSString();
}
- (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