HIWebView.mm   [plain text]

/*
* Copyright (C) 2005-2017 Apple Inc. All rights reserved.
*
* 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.
* 3. Neither the name of Apple Inc. ("Apple") nor the names of
* its contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "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 OR ITS 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.
*/

#ifndef __LP64__

#import "HIWebView.h"

#import "CarbonWindowAdapter.h"
#import "HIViewAdapter.h"
#import "WebHTMLViewInternal.h"
#import "WebKit.h"
#import <pal/spi/mac/NSEventSPI.h>
#import <pal/spi/mac/NSGraphicsSPI.h>
#import <pal/spi/mac/QuickDrawSPI.h>
#import <wtf/Assertions.h>
#import <wtf/ObjcRuntimeExtras.h>
#import <wtf/cf/TypeCastsCF.h>

WTF_DECLARE_CF_TYPE_TRAIT(CFRunLoop);

@interface NSWindow (AppKitSecretsHIWebViewKnows)
- (void)_removeWindowRef;
@end

@interface NSView (AppKitSecretsHIWebViewKnows)
- (void)_clearDirtyRectsForTree;
@end

extern "C" void HIWebViewRegisterClass();

@interface MenuItemProxy : NSObject <NSValidatedUserInterfaceItem>
{
int _tag;
SEL _action;
}

- (id)initWithAction:(SEL)action;
- (SEL)action;
- (int)tag;

@end

@implementation MenuItemProxy

- (id)initWithAction:(SEL)action
{
[super init];
if (self == nil) return nil;

_action = action;

return self;
}

- (SEL)action
{
return _action;
}

- (int)tag
{
return 0;
}

@end

@interface NSWindowGraphicsContext (HIWebView)
- (void)_web_setGraphicsPort:(CGContextRef)context;
@end

@implementation NSWindowGraphicsContext (HIWebView)

- (void)_web_setGraphicsPort:(CGContextRef)context
{
CGContextRetain(context);
CGContextRelease(_cgsContext);
_cgsContext = context;
}

@end

struct HIWebView
{
HIViewRef fViewRef;

WebView* fWebView;
NSView* fFirstResponder;
CarbonWindowAdapter* fKitWindow;
bool fIsComposited;
CFRunLoopObserverRef fUpdateObserver;
};
typedef struct HIWebView HIWebView;

static const OSType NSAppKitPropertyCreator = 'akit';
/*
These constants are not used. Commented out to make the compiler happy.
static const OSType NSViewCarbonControlViewPropertyTag = 'view';
static const OSType NSViewCarbonControlAutodisplayPropertyTag = 'autd';
static const OSType NSViewCarbonControlFirstResponderViewPropertyTag = 'frvw';
*/
static const OSType NSCarbonWindowPropertyTag = 'win ';

static SEL _NSSelectorForHICommand(const HICommand*);

static const EventTypeSpec kEvents[] = {
{ kEventClassHIObject, kEventHIObjectConstruct },
{ kEventClassHIObject, kEventHIObjectDestruct },

{ kEventClassMouse, kEventMouseUp },
{ kEventClassMouse, kEventMouseMoved },
{ kEventClassMouse, kEventMouseDragged },
{ kEventClassMouse, kEventMouseWheelMoved },

{ kEventClassKeyboard, kEventRawKeyDown },
{ kEventClassKeyboard, kEventRawKeyRepeat },

{ kEventClassCommand, kEventCommandProcess },
{ kEventClassCommand, kEventCommandUpdateStatus },

{ kEventClassControl, kEventControlInitialize },
{ kEventClassControl, kEventControlDraw },
{ kEventClassControl, kEventControlHitTest },
{ kEventClassControl, kEventControlGetPartRegion },
{ kEventClassControl, kEventControlGetData },
{ kEventClassControl, kEventControlBoundsChanged },
{ kEventClassControl, kEventControlActivate },
{ kEventClassControl, kEventControlDeactivate },
{ kEventClassControl, kEventControlOwningWindowChanged },
{ kEventClassControl, kEventControlClick },
{ kEventClassControl, kEventControlContextualMenuClick },
{ kEventClassControl, kEventControlSetFocusPart }
};

#define kHIViewBaseClassID CFSTR("com.apple.hiview")
#define kHIWebViewClassID CFSTR("com.apple.HIWebView")

static HIWebView* HIWebViewConstructor(HIViewRef inView);
static void HIWebViewDestructor(HIWebView*);

static OSStatus HIWebViewEventHandler(EventHandlerCallRef inCallRef, EventRef inEvent, void *inUserData);

static UInt32 GetBehaviors();
static ControlKind GetKind();
static void Draw(HIWebView* inView, RgnHandle limitRgn, CGContextRef inContext);
static ControlPartCode HitTest(HIWebView*, const HIPoint* where);
static OSStatus GetRegion(HIWebView*, ControlPartCode inPart, RgnHandle outRgn);
static void BoundsChanged(HIWebView* inView, UInt32 inOptions, const HIRect* inOriginalBounds, const HIRect* inCurrentBounds);
static void OwningWindowChanged(HIWebView*, WindowRef oldWindow, WindowRef newWindow);
static void ActiveStateChanged(HIWebView*);

static OSStatus Click(HIWebView* inView, EventRef inEvent);
static OSStatus ContextMenuClick(HIWebView* inView, EventRef inEvent);
static OSStatus MouseUp(HIWebView* inView, EventRef inEvent);
static OSStatus MouseMoved(HIWebView* inView, EventRef inEvent);
static OSStatus MouseDragged(HIWebView* inView, EventRef inEvent);
static OSStatus MouseWheelMoved(HIWebView* inView, EventRef inEvent);

static OSStatus ProcessCommand(HIWebView* inView, const HICommand* inCommand);
static OSStatus UpdateCommandStatus(HIWebView* inView, const HICommand* inCommand);

static OSStatus SetFocusPart(HIWebView*, ControlPartCode desiredFocus, RgnHandle invalidRgn, Boolean focusEverything, ControlPartCode* actualFocus);
static NSView* AdvanceFocus(HIWebView*, bool forward);
static void RelinquishFocus(HIWebView*, bool inAutodisplay);

static WindowRef GetWindowRef(HIWebView* inView);
static void SyncFrame(HIWebView* inView);

static OSStatus WindowHandler(EventHandlerCallRef inCallRef, EventRef inEvent, void* inUserData);

static void StartUpdateObserver(HIWebView*);
static void StopUpdateObserver(HIWebView*);

//----------------------------------------------------------------------------------
// HIWebViewCreate
//----------------------------------------------------------------------------------
//
OSStatus HIWebViewCreate(HIViewRef* outControl)
{
HIWebViewRegisterClass();
return HIObjectCreate(kHIWebViewClassID, nullptr, (HIObjectRef*)outControl);
}

//----------------------------------------------------------------------------------
// HIWebViewGetWebView
//----------------------------------------------------------------------------------
//
WebView* HIWebViewGetWebView(HIViewRef inView)
{
HIWebView* view = (HIWebView*)HIObjectDynamicCast((HIObjectRef)inView, kHIWebViewClassID);
WebView* result = nullptr;

if (view)
result = view->fWebView;

return result;
}

//----------------------------------------------------------------------------------
// HIWebViewConstructor
//----------------------------------------------------------------------------------
//

static HIWebView* HIWebViewConstructor(HIViewRef inView)
{
HIWebView* view = (HIWebView*)malloc(sizeof(HIWebView));

if (view) {
NSRect frame = { { 0, 0 }, { 400, 400 } };

view->fViewRef = inView;

WebView *webView = [[WebView alloc] initWithFrame: frame];
CFRetain(webView);
[webView release];
view->fWebView = webView;
[HIViewAdapter bindHIViewToNSView:inView nsView:view->fWebView];

view->fFirstResponder = nullptr;
view->fKitWindow = nullptr;
view->fIsComposited = false;
view->fUpdateObserver = nullptr;
}

return view;
}

//----------------------------------------------------------------------------------
// HIWebViewDestructor
//----------------------------------------------------------------------------------
//
static void HIWebViewDestructor(HIWebView* inView)
{
[HIViewAdapter unbindNSView:inView->fWebView];
CFRelease(inView->fWebView);

free(inView);
}

//----------------------------------------------------------------------------------
// HIWebViewRegisterClass
//----------------------------------------------------------------------------------
//
void HIWebViewRegisterClass()
{
static bool sRegistered;

if (!sRegistered) {
HIObjectRegisterSubclass(kHIWebViewClassID, kHIViewBaseClassID, 0, HIWebViewEventHandler,
GetEventTypeCount(kEvents), kEvents, 0, nullptr);
sRegistered = true;
}
}

//----------------------------------------------------------------------------------
// GetBehaviors
//----------------------------------------------------------------------------------
//
static UInt32 GetBehaviors()
{
return kControlSupportsDataAccess | kControlSupportsGetRegion | kControlGetsFocusOnClick;
}

static CGContextRef overrideCGContext(NSWindow *window, CGContextRef context)
{
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
NSWindowGraphicsContext *graphicsContext = (NSWindowGraphicsContext *)window.graphicsContext;
CGContextRef savedContext = (CGContextRef)graphicsContext.graphicsPort;
#pragma clang diagnostic pop
CGContextRetain(savedContext);
[graphicsContext _web_setGraphicsPort:context];
return savedContext;
}

static void restoreCGContext(NSWindow *window, CGContextRef savedContext)
{
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
NSWindowGraphicsContext *graphicsContext = (NSWindowGraphicsContext *)window.graphicsContext;
#pragma clang diagnostic pop
[graphicsContext _web_setGraphicsPort:savedContext];
CGContextRelease(savedContext);
}

//----------------------------------------------------------------------------------
// Draw
//----------------------------------------------------------------------------------
//
static void Draw(HIWebView* inView, RgnHandle limitRgn, CGContextRef inContext)
{
HIRect bounds;
Rect drawRect;
HIRect hiRect;
bool createdContext = false;

if (!inView->fIsComposited) {
GrafPtr port;
Rect portRect;

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
GetPort(&port);
GetPortBounds(port, &portRect);
CreateCGContextForPort(port, &inContext);
#pragma clang diagnostic push
SyncCGContextOriginWithPort(inContext, port);
CGContextTranslateCTM(inContext, 0, (portRect.bottom - portRect.top));
CGContextScaleCTM(inContext, 1, -1);
createdContext = true;
}

HIViewGetBounds(inView->fViewRef, &bounds);

CGContextRef savedContext = overrideCGContext(inView->fKitWindow, inContext);
[NSGraphicsContext setCurrentContext:[inView->fKitWindow graphicsContext]];

GetRegionBounds(limitRgn, &drawRect);

if (!inView->fIsComposited)
OffsetRect(&drawRect, (SInt16)-bounds.origin.x, (SInt16)-bounds.origin.y);

hiRect.origin.x = drawRect.left;
hiRect.origin.y = bounds.size.height - drawRect.bottom; // flip y
hiRect.size.width = drawRect.right - drawRect.left;
hiRect.size.height = drawRect.bottom - drawRect.top;

// printf("Drawing: drawRect is (%g %g) (%g %g)\n", hiRect.origin.x, hiRect.origin.y,
// hiRect.size.width, hiRect.size.height);

// FIXME: We need to do layout before Carbon has decided what region needs drawn.
// In Cocoa we make sure to do layout and invalidate any new regions before draw, so everything
// can be drawn in one pass. Doing a layout here will cause new regions to be invalidated, but they
// will not all be drawn in this pass since we already have a fixed rect we are going to display.

NSView <WebDocumentView> *documentView = [[[inView->fWebView mainFrame] frameView] documentView];
if ([documentView isKindOfClass:[WebHTMLView class]])
[(WebHTMLView *)documentView _web_updateLayoutAndStyleIfNeededRecursive];

if (inView->fIsComposited)
[inView->fWebView displayIfNeededInRect: *(NSRect*)&hiRect];
else
[inView->fWebView displayRect:*(NSRect*)&hiRect];

restoreCGContext(inView->fKitWindow, savedContext);

if (!inView->fIsComposited)
{
HIViewRef view;
HIViewFindByID(HIViewGetRoot(GetControlOwner(inView->fViewRef)), kHIViewWindowGrowBoxID, &view);
if (view)
{
HIRect frame;

HIViewGetBounds(view, &frame);
HIViewConvertRect(&frame, view, nullptr);

hiRect.origin.x = drawRect.left;
hiRect.origin.y = drawRect.top;
hiRect.size.width = drawRect.right - drawRect.left;
hiRect.size.height = drawRect.bottom - drawRect.top;

HIViewConvertRect(&hiRect, inView->fViewRef, nullptr);

if (CGRectIntersectsRect(frame, hiRect))
HIViewSetNeedsDisplay(view, true);
}
}

if (createdContext)
{
CGContextSynchronize(inContext);
CGContextRelease(inContext);
}
}

//----------------------------------------------------------------------------------
// HitTest
//----------------------------------------------------------------------------------
//
static ControlPartCode HitTest(HIWebView* view, const HIPoint* where)
{
HIRect bounds;

HIViewGetBounds(view->fViewRef, &bounds);

if (CGRectContainsPoint(bounds, *where))
return 1;
return kControlNoPart;
}

//----------------------------------------------------------------------------------
// GetRegion
//----------------------------------------------------------------------------------
//
static OSStatus GetRegion(HIWebView* inView, ControlPartCode inPart, RgnHandle outRgn)
{
OSStatus err = eventNotHandledErr;

if (inPart == -3 && [inView->fWebView isOpaque]) {
// kControlOpaqueMetaPart:
HIRect bounds;
Rect temp;

HIViewGetBounds(inView->fViewRef, &bounds);

temp.top = (SInt16)bounds.origin.y;
temp.left = (SInt16)bounds.origin.x;
temp.bottom = (SInt16)CGRectGetMaxY(bounds);
temp.right = (SInt16)CGRectGetMaxX(bounds);

RectRgn(outRgn, &temp);
err = noErr;
}

return err;
}

static WindowRef GetWindowRef(HIWebView* inView)
{
return GetControlOwner(inView->fViewRef);
}

static NSEvent *CreateNSEventAdoptingCGEvent(CGEventRef cgEvent, EventRef eventRef)
{
NSEvent *result = [[NSEvent alloc] _initWithCGEvent:cgEvent eventRef:eventRef];
CFRelease(cgEvent);
return result;
}

static Boolean
CopyEventCGEvent(EventRef event, CGEventRef* cgEvent)
{
if ((*cgEvent = CopyEventCGEvent(event)))
return true;

// This event might not have been created directly from a CGS event, and might not
// have a CGEventRef associated with it. In that case, try using the event most
// recently dispatched by the event dispatcher, which is likely to be the original
// user-input event containing a CGEventRef.
event = GetCurrentEvent();
if (event && (*cgEvent = CopyEventCGEvent(event)))
return true;
return false;
}

static NSEvent *CreateNSEventWithCarbonClickEvent(EventRef inEvent, WindowRef windowRef)
{
EventRef newEvent;
Point where;
OSStatus err;
UInt32 modifiers;
Rect windRect;

CGEventRef cgEvent = nullptr;
if (!CopyEventCGEvent(inEvent, &cgEvent))
return nil;

// We need to make the event be a kEventMouseDown event, or the webkit might trip up when
// we click on a Netscape plugin. It calls ConvertEventRefToEventRecord, assuming
// that mouseDown was passed an event with a real mouse down eventRef. We just need a
// minimal one here.

err = CreateEvent(nullptr, kEventClassMouse, kEventMouseDown, GetEventTime(inEvent), 0, &newEvent);
if (err != noErr)
return nil;

GetEventParameter(inEvent, kEventParamWindowMouseLocation, typeQDPoint, nullptr, sizeof(Point), nullptr, &where);
GetWindowBounds(windowRef, kWindowStructureRgn, &windRect);
where.h += windRect.left;
where.v += windRect.top;

GetEventParameter(inEvent, kEventParamKeyModifiers, typeUInt32, nullptr, sizeof(UInt32), nullptr, &modifiers);
SetEventParameter(newEvent, kEventParamMouseLocation, typeQDPoint, sizeof(Point), &where);
SetEventParameter(newEvent, kEventParamKeyModifiers, typeUInt32, sizeof(UInt32), &modifiers);

return CreateNSEventAdoptingCGEvent(cgEvent, newEvent);
}

//----------------------------------------------------------------------------------
// Click
//----------------------------------------------------------------------------------
//
static OSStatus Click(HIWebView* inView, EventRef inEvent)
{
NSEvent *kitEvent = CreateNSEventWithCarbonClickEvent(inEvent, GetWindowRef(inView));

if (!inView->fIsComposited)
StartUpdateObserver(inView);

[inView->fKitWindow sendEvent:kitEvent];

if (!inView->fIsComposited)
StopUpdateObserver(inView);

[kitEvent release];

return noErr;
}

static NSEvent *CreateNSEventWithCarbonEvent(EventRef inEvent)
{
NSEvent *event = [NSEvent eventWithEventRef:inEvent];
if (!event)
event = [NSEvent eventWithEventRef:GetCurrentEvent()];

return [event retain];
}

//----------------------------------------------------------------------------------
// MouseUp
//----------------------------------------------------------------------------------
//
static OSStatus MouseUp(HIWebView* inView, EventRef inEvent)
{
NSEvent* kitEvent = CreateNSEventWithCarbonEvent(inEvent);

[inView->fKitWindow sendEvent:kitEvent];

[kitEvent release];

return noErr;
}

static NSEvent *CreateNSEventWithCarbonMouseMoveEvent(EventRef inEvent, NSWindow *window)
{
NSEvent* kitEvent = [NSEvent eventWithEventRef:inEvent];

// FIXME: Works around bug 3585644. Can remove it once that bug is fixed.
// We preflight here and don't do any work when the window is already correct
// because _eventRelativeToWindow will malfunction if the event's window method
// has been hijacked by the bug workaround used by Contribute. It's fine to just
// leave the event alone if the window is already correct.

if ([kitEvent window] != window)
kitEvent = [kitEvent _eventRelativeToWindow:window];

return [kitEvent retain];
}

//----------------------------------------------------------------------------------
// MouseMoved
//----------------------------------------------------------------------------------
//
static OSStatus MouseMoved(HIWebView* inView, EventRef inEvent)
{
NSEvent *kitEvent = CreateNSEventWithCarbonMouseMoveEvent(inEvent, inView->fKitWindow);
[inView->fKitWindow sendEvent:kitEvent];
[kitEvent release];

return noErr;
}

//----------------------------------------------------------------------------------
// MouseDragged
//----------------------------------------------------------------------------------
//
static OSStatus MouseDragged(HIWebView* inView, EventRef inEvent)
{
NSEvent* kitEvent = CreateNSEventWithCarbonEvent(inEvent);

[inView->fKitWindow sendEvent:kitEvent];

[kitEvent release];

return noErr;
}

//----------------------------------------------------------------------------------
// MouseDragged
//----------------------------------------------------------------------------------
//
static OSStatus MouseWheelMoved(HIWebView* inView, EventRef inEvent)
{
NSEvent* kitEvent = CreateNSEventWithCarbonEvent(inEvent);

[inView->fKitWindow sendEvent:kitEvent];

[kitEvent release];

return noErr;
}

//----------------------------------------------------------------------------------
// ContextMenuClick
//----------------------------------------------------------------------------------
//
static OSStatus ContextMenuClick(HIWebView* inView, EventRef inEvent)
{
NSView *webView = inView->fWebView;
NSWindow *window = [webView window];

// Get the point out of the event.
HIPoint point;
GetEventParameter(inEvent, kEventParamMouseLocation, typeHIPoint, nullptr, sizeof(point), nullptr, &point);
HIViewConvertPoint(&point, inView->fViewRef, nullptr);

// Flip the Y coordinate, since Carbon is flipped relative to the AppKit.
NSPoint location = NSMakePoint(point.x, [window frame].size.height - point.y);

// Make up an event with the point and send it to the window.
NSEvent *kitEvent = [NSEvent mouseEventWithType:NSEventTypeRightMouseDown
location:location
modifierFlags:0
timestamp:GetEventTime(inEvent)
windowNumber:[window windowNumber]
context:0
eventNumber:0
clickCount:1
pressure:0];
[inView->fKitWindow sendEvent:kitEvent];
return noErr;
}

//----------------------------------------------------------------------------------
// GetKind
//----------------------------------------------------------------------------------
//
static ControlKind GetKind()
{
const ControlKind kMyKind = { 'appl', 'wbvw' };

return kMyKind;
}

//----------------------------------------------------------------------------------
// BoundsChanged
//----------------------------------------------------------------------------------
//
static void BoundsChanged(HIWebView* inView, UInt32 inOptions, const HIRect* inOriginalBounds, const HIRect* inCurrentBounds)
{
if (inView->fWebView)
SyncFrame(inView);
}

//----------------------------------------------------------------------------------
// OwningWindowChanged
//----------------------------------------------------------------------------------
//
static void
OwningWindowChanged(HIWebView* view, WindowRef oldWindow, WindowRef newWindow)
{
if (newWindow) {
WindowAttributes attrs;

OSStatus err = GetWindowProperty(newWindow, NSAppKitPropertyCreator, NSCarbonWindowPropertyTag, sizeof(NSWindow *), nullptr, &view->fKitWindow);
if (err != noErr) {
const EventTypeSpec kWindowEvents[] = {
{ kEventClassWindow, kEventWindowClosed },
{ kEventClassMouse, kEventMouseMoved },
{ kEventClassMouse, kEventMouseUp },
{ kEventClassMouse, kEventMouseDragged },
{ kEventClassMouse, kEventMouseWheelMoved },
{ kEventClassKeyboard, kEventRawKeyDown },
{ kEventClassKeyboard, kEventRawKeyRepeat },
{ kEventClassKeyboard, kEventRawKeyUp },
{ kEventClassControl, kEventControlClick },
};

view->fKitWindow = [[CarbonWindowAdapter alloc] initWithCarbonWindowRef: newWindow takingOwnership: NO disableOrdering:NO carbon:YES];
SetWindowProperty(newWindow, NSAppKitPropertyCreator, NSCarbonWindowPropertyTag, sizeof(NSWindow *), &view->fKitWindow);

InstallWindowEventHandler(newWindow, WindowHandler, GetEventTypeCount(kWindowEvents), kWindowEvents, newWindow, nullptr);
}

[[view->fKitWindow contentView] addSubview:view->fWebView];

GetWindowAttributes(newWindow, &attrs);
view->fIsComposited = attrs & kWindowCompositingAttribute;

SyncFrame(view);
} else {
// Be sure to detach the cocoa view, too.
if (view->fWebView)
[view->fWebView removeFromSuperview];

view->fKitWindow = nullptr; // break the ties that bind
}
}

//-------------------------------------------------------------------------------------
// WindowHandler
//-------------------------------------------------------------------------------------
// Redirect mouse events to the views beneath them. This is required for WebKit to work
// properly. We install it once per window. We also tap into window close to release
// the NSWindow that shadows our Carbon window.
//
static OSStatus WindowHandler(EventHandlerCallRef inCallRef, EventRef inEvent, void* inUserData)
{
WindowRef window = (WindowRef)inUserData;
OSStatus result = eventNotHandledErr;

NSWindow* kitWindow;
OSStatus err = noErr;
NSEvent* kitEvent;

switch (GetEventClass(inEvent)) {
case kEventClassControl:
switch (GetEventKind(inEvent)) {
case kEventControlClick:
CarbonWindowAdapter *kitWindow;

err = GetWindowProperty(window, NSAppKitPropertyCreator, NSCarbonWindowPropertyTag, sizeof(NSWindow *), nullptr, &kitWindow);

// We must be outside the HIWebView, relinquish focus.
[kitWindow relinquishFocus];
break;
}
break;

case kEventClassKeyboard:
// if the first responder in the kit window is something other than the
// window, we assume a subview of the webview is focused. we must send
// the event to the window so that it goes through the kit's normal TSM
// logic, and -- more importantly -- allows any delegates associated
// with the first responder to have a chance at the event.

err = GetWindowProperty(window, NSAppKitPropertyCreator, NSCarbonWindowPropertyTag, sizeof(NSWindow *), nullptr, &kitWindow);
if (err == noErr) {
NSResponder* responder = [kitWindow firstResponder];
if (responder != kitWindow) {
kitEvent = CreateNSEventWithCarbonEvent(inEvent);

[kitWindow sendEvent:kitEvent];
[kitEvent release];

result = noErr;
}
}
break;

case kEventClassWindow:

err = GetWindowProperty(window, NSAppKitPropertyCreator, NSCarbonWindowPropertyTag, sizeof(NSWindow *), nullptr, &kitWindow);
if (err == noErr) {
[kitWindow _removeWindowRef];
[kitWindow close];
}
result = noErr;
break;

case kEventClassMouse:
switch (GetEventKind(inEvent)) {
case kEventMouseMoved:
Point where;
GetEventParameter(inEvent, kEventParamMouseLocation, typeQDPoint, nullptr, sizeof(Point), nullptr, &where);

WindowRef temp;
FindWindow(where, &temp);
if (temp == window) {
Rect bounds;
GetWindowBounds(window, kWindowStructureRgn, &bounds);
where.h -= bounds.left;
where.v -= bounds.top;
SetEventParameter(inEvent, kEventParamWindowRef, typeWindowRef, sizeof(WindowRef), &window);
SetEventParameter(inEvent, kEventParamWindowMouseLocation, typeQDPoint, sizeof(Point), &where);

HIViewRef view = nullptr;

err = HIViewGetViewForMouseEvent(HIViewGetRoot(window), inEvent, &view);
if (err == noErr && view && HIObjectIsOfClass((HIObjectRef)view, kHIWebViewClassID))
result = SendEventToEventTargetWithOptions(inEvent, HIObjectGetEventTarget((HIObjectRef)view), kEventTargetDontPropagate);
}
break;

case kEventMouseUp:
case kEventMouseDragged:
case kEventMouseWheelMoved:
HIViewRef view = nullptr;

err = HIViewGetViewForMouseEvent(HIViewGetRoot(window), inEvent, &view);
if (err == noErr && view && HIObjectIsOfClass((HIObjectRef)view, kHIWebViewClassID))
result = SendEventToEventTargetWithOptions(inEvent, HIObjectGetEventTarget((HIObjectRef)view), kEventTargetDontPropagate);
break;
}
break;
}

return result;
}

//----------------------------------------------------------------------------------
// SyncFrame
//----------------------------------------------------------------------------------
//
static void SyncFrame(HIWebView* inView)
{
HIViewRef parent = HIViewGetSuperview(inView->fViewRef);

if (parent) {
if (inView->fIsComposited) {
HIRect frame;
HIRect parentBounds;
NSPoint origin;

HIViewGetFrame(inView->fViewRef, &frame);
HIViewGetBounds(parent, &parentBounds);

origin.x = frame.origin.x;
origin.y = parentBounds.size.height - CGRectGetMaxY(frame);
// printf("syncing to (%g %g) (%g %g)\n", origin.x, origin.y,
// frame.size.width, frame.size.height);
[inView->fWebView setFrameOrigin: origin];
[inView->fWebView setFrameSize: *(NSSize*)&frame.size];
} else {
GrafPtr port = GetWindowPort(GetControlOwner(inView->fViewRef));
PixMapHandle portPix = GetPortPixMap(port);
Rect bounds;
HIRect rootFrame;
HIRect frame;

GetControlBounds(inView->fViewRef, &bounds);
OffsetRect(&bounds, -(**portPix).bounds.left, -(**portPix).bounds.top);

// printf("control lives at %d %d %d %d in window-coords\n", bounds.top, bounds.left,
// bounds.bottom, bounds.right);

HIViewGetFrame(HIViewGetRoot(GetControlOwner(inView->fViewRef)), &rootFrame);

frame.origin.x = bounds.left;
frame.origin.y = rootFrame.size.height - bounds.bottom;
frame.size.width = bounds.right - bounds.left;
frame.size.height = bounds.bottom - bounds.top;

// printf(" before frame convert (%g %g) (%g %g)\n", frame.origin.x, frame.origin.y,
// frame.size.width, frame.size.height);

[inView->fWebView convertRect:*(NSRect*)&frame fromView:nil];

// printf(" moving web view to (%g %g) (%g %g)\n", frame.origin.x, frame.origin.y,
// frame.size.width, frame.size.height);

[inView->fWebView setFrameOrigin: *(NSPoint*)&frame.origin];
[inView->fWebView setFrameSize: *(NSSize*)&frame.size];
}
}
}

//----------------------------------------------------------------------------------
// SetFocusPart
//----------------------------------------------------------------------------------
//
static OSStatus SetFocusPart(HIWebView* view, ControlPartCode desiredFocus, RgnHandle invalidRgn, Boolean focusEverything, ControlPartCode* actualFocus)
{
NSView *freshlyMadeFirstResponderView;
SInt32 partCodeToReturn;

// Do what Carbon is telling us to do.
if (desiredFocus == kControlFocusNoPart) {
// Relinquish the keyboard focus.
RelinquishFocus(view, true); // (autodisplay ? YES : NO));
freshlyMadeFirstResponderView = nil;
partCodeToReturn = kControlFocusNoPart;
// NSLog(@"Relinquished the key focus because we have no choice.");
} else if (desiredFocus == kControlFocusNextPart || desiredFocus == kControlFocusPrevPart) {
BOOL goForward = (desiredFocus == kControlFocusNextPart);

// Advance the keyboard focus, maybe right off of this view. Maybe a subview of this one already has the keyboard focus, maybe not.
freshlyMadeFirstResponderView = AdvanceFocus(view, goForward);
if (freshlyMadeFirstResponderView)
partCodeToReturn = desiredFocus;
else
partCodeToReturn = kControlFocusNoPart;
// NSLog(freshlyMadeFirstResponderView ? @"Advanced the key focus." : @"Relinquished the key focus.");
} else {
// What's this?
if (desiredFocus != kControlIndicatorPart)
ASSERT_NOT_REACHED();
freshlyMadeFirstResponderView = nil;
partCodeToReturn = desiredFocus;
}

view->fFirstResponder = freshlyMadeFirstResponderView;

*actualFocus = partCodeToReturn;

// Done.
return noErr;
}

//----------------------------------------------------------------------------------
// AdvanceFocus
//----------------------------------------------------------------------------------
//
static NSView* AdvanceFocus(HIWebView* view, bool forward)
{
NSResponder* oldFirstResponder;
NSView* currentKeyView;
NSView* viewWeMadeFirstResponder;

// Focus on some part (subview) of this control (view).
// Maybe a subview of this one already has the keyboard focus, maybe not.

oldFirstResponder = [view->fKitWindow firstResponder];

// If we tab out of our NSView, it will no longer be the responder
// when we get here. We'll try this trick for now. We might need to
// tag the view appropriately.

if (view->fFirstResponder && ((NSResponder*)view->fFirstResponder != oldFirstResponder))
return nullptr;

if ([oldFirstResponder isKindOfClass:[NSView class]]) {
NSView* tentativeNewKeyView;

// Some view in this window already has the keyboard focus. It better at least be a subview of this one.
NSView* oldFirstResponderView = (NSView *)oldFirstResponder;
ASSERT([oldFirstResponderView isDescendantOf:view->fWebView]);

if (oldFirstResponderView != view->fFirstResponder && ![oldFirstResponderView isDescendantOf:view->fFirstResponder]) {
// Despite our efforts to record what view we made the first responder
// (for use in the next paragraph) we couldn't keep up because the user
// has clicked in a text field to make it the key focus, instead of using
// the tab key. Find a control on which it's reasonable to invoke
// -[NSView nextValidKeyView], taking into account the fact that
// NSTextFields always pass on first-respondership to a temporarily-
// contained NSTextView.

NSView *viewBeingTested;
currentKeyView = oldFirstResponderView;
viewBeingTested = currentKeyView;
while (viewBeingTested != view->fWebView) {
if ([viewBeingTested isKindOfClass:[NSTextField class]]) {
currentKeyView = viewBeingTested;
break;
}
viewBeingTested = [viewBeingTested superview];
}
} else {
// We recorded which view we made into the first responder the
// last time the user hit the tab key, and nothing has invalidated
// our recorded value since.

currentKeyView = view->fFirstResponder;
}

// Try to move on to the next or previous key view. We use the laboriously
// recorded/figured currentKeyView instead of just oldFirstResponder as the
// jumping-off-point when searching for the next valid key view. This is so
// we don't get fooled if we recently made some view the first responder, but
// it passed on first-responder-ness to some temporary subview.
//
// You can't put normal views in a window with Carbon-control-wrapped views.
// Stuff like this would break. M.P. Notice - 12/2/00

tentativeNewKeyView = forward ? [currentKeyView nextValidKeyView] : [currentKeyView previousValidKeyView];
if (tentativeNewKeyView && [tentativeNewKeyView isDescendantOf:view->fWebView]) {
// The user has tabbed to another subview of this control view. Change the keyboard focus.
//NSLog(@"Tabbed to the next or previous key view.");

[view->fKitWindow makeFirstResponder:tentativeNewKeyView];
viewWeMadeFirstResponder = tentativeNewKeyView;
} else {
// The user has tabbed past the subviews of this control view. The window is the first responder now.
//NSLog(@"Tabbed past the first or last key view.");
[view->fKitWindow makeFirstResponder:view->fKitWindow];
viewWeMadeFirstResponder = nil;
}
} else {
// No view in this window has the keyboard focus. This view should
// try to select one of its key subviews. We're not interested in
// the subviews of sibling views here.

// NSLog(@"No keyboard focus in window. Attempting to set...");

NSView *tentativeNewKeyView;
ASSERT(oldFirstResponder == view->fKitWindow);
if ([view->fWebView acceptsFirstResponder])
tentativeNewKeyView = view->fWebView;
else
tentativeNewKeyView = [view->fWebView nextValidKeyView];
if (tentativeNewKeyView && [tentativeNewKeyView isDescendantOf:view->fWebView]) {
// This control view has at least one subview that can take the keyboard focus.
if (!forward) {
// The user has tabbed into this control view backwards. Find
// and select the last subview of this one that can take the
// keyboard focus. Watch out for loops of valid key views.

NSView *firstTentativeNewKeyView = tentativeNewKeyView;
NSView *nextTentativeNewKeyView = [tentativeNewKeyView nextValidKeyView];
while (nextTentativeNewKeyView && [nextTentativeNewKeyView isDescendantOf:view->fWebView] && nextTentativeNewKeyView != firstTentativeNewKeyView) {
tentativeNewKeyView = nextTentativeNewKeyView;
nextTentativeNewKeyView = [tentativeNewKeyView nextValidKeyView];
}

}

// Set the keyboard focus.
//NSLog(@"Tabbed into the first or last key view.");
[view->fKitWindow makeFirstResponder:tentativeNewKeyView];
viewWeMadeFirstResponder = tentativeNewKeyView;
} else {
// This control view has no subviews that can take the keyboard focus.
//NSLog(@"Can't tab into this view.");
viewWeMadeFirstResponder = nil;
}
}

// Done.
return viewWeMadeFirstResponder;
}

//----------------------------------------------------------------------------------
// RelinquishFocus
//----------------------------------------------------------------------------------
//
static void RelinquishFocus(HIWebView* view, bool inAutodisplay)
{
NSResponder* firstResponder;

// Apparently Carbon thinks that some subview of this control view has the keyboard focus,
// or we wouldn't be being asked to relinquish focus.

firstResponder = [view->fKitWindow firstResponder];
if ([firstResponder isKindOfClass:[NSView class]]) {
// Some subview of this control view really is the first responder right now.
ASSERT([(NSView *)firstResponder isDescendantOf:view->fWebView]);

// Make the window the first responder, so that no view is the key view.
[view->fKitWindow makeFirstResponder:view->fKitWindow];

// If this control is not allowed to do autodisplay, don't let
// it autodisplay any just-changed focus rings or text on the
// next go around the event loop. I'm probably clearing more
// dirty rects than I have to, but it doesn't seem to hurt in
// the print panel accessory view case, and I don't have time
// to figure out exactly what -[NSCell _setKeyboardFocusRingNeedsDisplay]
// is doing when invoked indirectly from -makeFirstResponder up above. M.P. Notice - 12/4/00

if (!inAutodisplay)
[[view->fWebView opaqueAncestor] _clearDirtyRectsForTree];
} else {
// The Cocoa first responder does not correspond to the Carbon
// control that has the keyboard focus. This can happen when
// you've closed a dialog by hitting return in an NSTextView
// that's a subview of this one; Cocoa closed the window, and
// now Carbon is telling this control to relinquish the focus
// as it's being disposed. There's nothing to do.

ASSERT(firstResponder == view->fKitWindow);
}
}

//----------------------------------------------------------------------------------
// ActiveStateChanged
//----------------------------------------------------------------------------------
//
static void ActiveStateChanged(HIWebView* view)
{
if ([view->fWebView respondsToSelector:@selector(setEnabled)]) {
[(NSControl*)view->fWebView setEnabled: IsControlEnabled(view->fViewRef)];
HIViewSetNeedsDisplay(view->fViewRef, true);
}
}

//----------------------------------------------------------------------------------
// ProcessCommand
//----------------------------------------------------------------------------------
//
static OSStatus ProcessCommand(HIWebView* inView, const HICommand* inCommand)
{
OSStatus result = eventNotHandledErr;
NSResponder* resp;

resp = [inView->fKitWindow firstResponder];

if ([resp isKindOfClass:[NSView class]]) {
NSView* respView = (NSView*)resp;

if (respView == inView->fWebView || [respView isDescendantOf: inView->fWebView]) {
switch (inCommand->commandID) {
case kHICommandCut:
case kHICommandCopy:
case kHICommandPaste:
case kHICommandClear:
case kHICommandSelectAll: {
SEL selector = _NSSelectorForHICommand(inCommand);
if ([respView respondsToSelector:selector]) {
[respView performSelector:selector withObject:nil];
result = noErr;
}
break;
}
}
}
}

return result;
}

//----------------------------------------------------------------------------------
// UpdateCommandStatus
//----------------------------------------------------------------------------------
//
static OSStatus
UpdateCommandStatus(HIWebView* inView, const HICommand* inCommand)
{
OSStatus result = eventNotHandledErr;
MenuItemProxy* proxy = nullptr;
NSResponder* resp;

resp = [inView->fKitWindow firstResponder];

if ([resp isKindOfClass:[NSView class]]) {
NSView* respView = (NSView*)resp;

if ((respView == inView->fWebView || [respView isDescendantOf: inView->fWebView]) && inCommand->attributes & kHICommandFromMenu) {
SEL selector = _NSSelectorForHICommand(inCommand);

if (selector && [resp respondsToSelector: selector]) {
proxy = [[MenuItemProxy alloc] initWithAction: selector];

// Can't use -performSelector:withObject: here because the method we're calling returns BOOL, while
// -performSelector:withObject:'s return value is assumed to be an id.
if (wtfObjcMsgSend<BOOL>(resp, @selector(validateUserInterfaceItem:), proxy))
EnableMenuItem(inCommand->menu.menuRef, inCommand->menu.menuItemIndex);
else
DisableMenuItem(inCommand->menu.menuRef, inCommand->menu.menuItemIndex);

result = noErr;
}
}
}

if (proxy)
[proxy release];

return result;
}

// Blatantly stolen from AppKit and cropped a bit

//----------------------------------------------------------------------------------
// _NSSelectorForHICommand
//----------------------------------------------------------------------------------
//
static SEL _NSSelectorForHICommand(const HICommand* inCommand)
{
switch (inCommand->commandID) {
case kHICommandUndo:
return @selector(undo:);
case kHICommandRedo:
return @selector(redo:);
case kHICommandCut:
return @selector(cut:);
case kHICommandCopy:
return @selector(copy:);
case kHICommandPaste:
return @selector(paste:);
case kHICommandClear:
return @selector(delete:);
case kHICommandSelectAll:
return @selector(selectAll:);
default:
return nullptr;
}

return nullptr;
}

//-----------------------------------------------------------------------------------
// HIWebViewEventHandler
//-----------------------------------------------------------------------------------
// Our object's virtual event handler method. I'm not sure if we need this these days.
// We used to do various things with it, but those days are long gone...
//
static OSStatus
HIWebViewEventHandler(EventHandlerCallRef inCallRef, EventRef inEvent, void *inUserData)
{
OSStatus result = eventNotHandledErr;
HIPoint where;
OSType tag;
void *ptr;
Size size;
UInt32 features;
RgnHandle region = nullptr;
ControlPartCode part;
HIWebView* view = (HIWebView*)inUserData;

// [NSApp setWindowsNeedUpdate:YES] must be called before events so that ActivateTSMDocument is called to set an active document.
// Without an active document, TSM will use a default document which uses a bottom-line input window which we don't want.
[NSApp setWindowsNeedUpdate:YES];

switch (GetEventClass(inEvent)) {
case kEventClassHIObject:
switch (GetEventKind(inEvent)) {
case kEventHIObjectConstruct:
HIObjectRef object;

result = GetEventParameter(inEvent, kEventParamHIObjectInstance,
typeHIObjectRef, nullptr, sizeof(HIObjectRef), nullptr, &object);
ASSERT(!result);
if (result)
return result;

// on entry for our construct event, we're passed the
// creation proc we registered with for this class.
// we use it now to create the instance, and then we
// replace the instance parameter data with said instance
// as type void.

view = HIWebViewConstructor((HIViewRef)object);

if (view)
SetEventParameter(inEvent, kEventParamHIObjectInstance, typeVoidPtr, sizeof(void *), &view);
break;

case kEventHIObjectDestruct:
HIWebViewDestructor(view);
// result is unimportant
break;
}
break;

case kEventClassKeyboard: {
NSEvent* kitEvent = CreateNSEventWithCarbonEvent(inEvent);
[view->fKitWindow sendSuperEvent:kitEvent];
[kitEvent release];
result = noErr;
break;
}

case kEventClassMouse:
switch (GetEventKind(inEvent)) {
case kEventMouseUp:
result = MouseUp(view, inEvent);
break;

case kEventMouseWheelMoved:
result = MouseWheelMoved(view, inEvent);
break;

case kEventMouseMoved:
result = MouseMoved(view, inEvent);
break;

case kEventMouseDragged:
result = MouseDragged(view, inEvent);
break;
}
break;

case kEventClassCommand:
HICommand command;

result = GetEventParameter(inEvent, kEventParamDirectObject, typeHICommand, nullptr, sizeof(HICommand), nullptr, &command);
ASSERT(!result);
if (result)
return result;

switch (GetEventKind(inEvent)) {
case kEventCommandProcess:
result = ProcessCommand(view, &command);
break;

case kEventCommandUpdateStatus:
result = UpdateCommandStatus(view, &command);
break;
}
break;

case kEventClassControl:
switch (GetEventKind(inEvent)) {
case kEventControlInitialize:
features = GetBehaviors();
SetEventParameter(inEvent, kEventParamControlFeatures, typeUInt32, sizeof(UInt32), &features);
result = noErr;
break;

case kEventControlDraw: {
CGContextRef context = nullptr;

GetEventParameter(inEvent, kEventParamRgnHandle, typeQDRgnHandle, nullptr, sizeof(RgnHandle), nullptr, &region);
GetEventParameter(inEvent, kEventParamCGContextRef, typeCGContextRef, nullptr, sizeof(CGContextRef), nullptr, &context);

Draw(view, region, context);
result = noErr;
break;
}

case kEventControlHitTest:
GetEventParameter(inEvent, kEventParamMouseLocation, typeHIPoint, nullptr, sizeof(HIPoint), nullptr, &where);
part = HitTest(view, &where);
SetEventParameter(inEvent, kEventParamControlPart, typeControlPartCode, sizeof(ControlPartCode), &part);
result = noErr;
break;

case kEventControlGetPartRegion:
GetEventParameter(inEvent, kEventParamControlPart, typeControlPartCode, nullptr, sizeof(ControlPartCode), nullptr, &part);
GetEventParameter(inEvent, kEventParamControlRegion, typeQDRgnHandle, nullptr, sizeof(RgnHandle), nullptr, &region);
result = GetRegion(view, part, region);
break;

case kEventControlGetData: {
GetEventParameter(inEvent, kEventParamControlPart, typeControlPartCode, nullptr, sizeof(ControlPartCode), nullptr, &part);
GetEventParameter(inEvent, kEventParamControlDataTag, typeEnumeration, nullptr, sizeof(OSType), nullptr, &tag);
GetEventParameter(inEvent, kEventParamControlDataBuffer, typePtr, nullptr, sizeof(Ptr), nullptr, &ptr);
GetEventParameter(inEvent, kEventParamControlDataBufferSize, typeByteCount, nullptr, sizeof(Size), nullptr, &size);

if (tag == kControlKindTag) {
Size outSize;
result = noErr;

if (ptr) {
if (size != sizeof(ControlKind))
result = errDataSizeMismatch;
else
(*(ControlKind *)ptr) = GetKind();
}

outSize = sizeof(ControlKind);
SetEventParameter(inEvent, kEventParamControlDataBufferSize, typeByteCount, sizeof(Size), &outSize);
}
break;
}

case kEventControlBoundsChanged: {
HIRect prevRect, currRect;
UInt32 attrs;

GetEventParameter(inEvent, kEventParamAttributes, typeUInt32, nullptr, sizeof(UInt32), nullptr, &attrs);
GetEventParameter(inEvent, kEventParamOriginalBounds, typeHIRect, nullptr, sizeof(HIRect), nullptr, &prevRect);
GetEventParameter(inEvent, kEventParamCurrentBounds, typeHIRect, nullptr, sizeof(HIRect), nullptr, &currRect);

BoundsChanged(view, attrs, &prevRect, &currRect);
result = noErr;
break;
}

case kEventControlActivate:
ActiveStateChanged(view);
result = noErr;
break;

case kEventControlDeactivate:
ActiveStateChanged(view);
result = noErr;
break;

case kEventControlOwningWindowChanged: {
WindowRef fromWindow, toWindow;

result = GetEventParameter(inEvent, kEventParamControlOriginalOwningWindow, typeWindowRef, nullptr, sizeof(WindowRef), nullptr, &fromWindow);
ASSERT(!result);
if (result)
return result;

result = GetEventParameter(inEvent, kEventParamControlCurrentOwningWindow, typeWindowRef, nullptr, sizeof(WindowRef), nullptr, &toWindow);
ASSERT(!result);
if (result)
return result;

OwningWindowChanged(view, fromWindow, toWindow);
result = noErr;
break;
}

case kEventControlClick:
result = Click(view, inEvent);
break;

case kEventControlContextualMenuClick:
result = ContextMenuClick(view, inEvent);
break;

case kEventControlSetFocusPart: {
ControlPartCode desiredFocus;
RgnHandle invalidRgn;
Boolean focusEverything;
ControlPartCode actualFocus;

result = GetEventParameter(inEvent, kEventParamControlPart, typeControlPartCode, nullptr, sizeof(ControlPartCode), nullptr, &desiredFocus);
ASSERT(!result);
if (result)
return result;

GetEventParameter(inEvent, kEventParamControlInvalRgn, typeQDRgnHandle, nullptr, sizeof(RgnHandle), nullptr, &invalidRgn);
focusEverything = false; // a good default in case the parameter doesn't exist
GetEventParameter(inEvent, kEventParamControlFocusEverything, typeBoolean, nullptr, sizeof(Boolean), nullptr, &focusEverything);

result = SetFocusPart(view, desiredFocus, invalidRgn, focusEverything, &actualFocus);
if (result == noErr)
ASSERT(!SetEventParameter(inEvent, kEventParamControlPart, typeControlPartCode, sizeof(ControlPartCode), &actualFocus));
break;
}

// some other kind of Control event
default:
break;
}
break;

// some other event class
default:
break;
}
return result;
}

static void UpdateObserver(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info);

static void StartUpdateObserver(HIWebView* view)
{
CFRunLoopObserverContext context;
CFRunLoopObserverRef observer;
CFRunLoopRef mainRunLoop;

ASSERT(!view->fIsComposited);
ASSERT(!view->fUpdateObserver);

context.version = 0;
context.info = view;
context.retain = nullptr;
context.release = nullptr;
context.copyDescription = nullptr;

mainRunLoop = checked_cf_cast<CFRunLoopRef>(GetCFRunLoopFromEventLoop(GetMainEventLoop()));
observer = CFRunLoopObserverCreate(nullptr, kCFRunLoopEntry | kCFRunLoopBeforeWaiting, true, 0, UpdateObserver, &context);
CFRunLoopAddObserver(mainRunLoop, observer, kCFRunLoopCommonModes);

view->fUpdateObserver = observer;

// printf("Update observer started\n");
}

static void StopUpdateObserver(HIWebView* view)
{
ASSERT(!view->fIsComposited);
ASSERT(view->fUpdateObserver);

CFRunLoopObserverInvalidate(view->fUpdateObserver);
CFRelease(view->fUpdateObserver);
view->fUpdateObserver = nullptr;

// printf("Update observer removed\n");
}

static void
UpdateObserver(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info)
{
HIWebView* view = (HIWebView*)info;
RgnHandle region = NewRgn();

// printf("Update observer called\n");

if (region) {
GetWindowRegion(GetControlOwner(view->fViewRef), kWindowUpdateRgn, region);

if (!EmptyRgn(region)) {
RgnHandle ourRgn = NewRgn();
Rect rect;

GetWindowBounds(GetControlOwner(view->fViewRef), kWindowStructureRgn, &rect);

// printf("Update region is non-empty\n");
if (ourRgn) {
Rect rect;
GrafPtr savePort, port;
Point offset = { 0, 0 };

port = GetWindowPort(GetControlOwner(view->fViewRef));

GetPort(&savePort);
SetPort(port);

GlobalToLocal(&offset);
OffsetRgn(region, offset.h, offset.v);

GetControlBounds(view->fViewRef, &rect);
RectRgn(ourRgn, &rect);

// printf("our control is at %d %d %d %d\n", rect.top, rect.left, rect.bottom, rect.right);
GetRegionBounds(region, &rect);
// printf("region is at %d %d %d %d\n", rect.top, rect.left, rect.bottom, rect.right);

SectRgn(ourRgn, region, ourRgn);

GetRegionBounds(ourRgn, &rect);
// printf("intersection is %d %d %d %d\n", rect.top, rect.left, rect.bottom, rect.right);
if (!EmptyRgn(ourRgn)) {
RgnHandle saveVis = NewRgn();

// printf("looks like we should draw\n");
if (saveVis) {
// RGBColor kRedColor = { 0xffff, 0, 0 };
GetPortVisibleRegion(GetWindowPort(GetControlOwner(view->fViewRef)), saveVis);
SetPortVisibleRegion(GetWindowPort(GetControlOwner(view->fViewRef)), ourRgn);

// RGBForeColor(&kRedColor);
// PaintRgn(ourRgn);
// QDFlushPortBuffer(port, nullptr);
// Delay(15, nullptr);
Draw1Control(view->fViewRef);
ValidWindowRgn(GetControlOwner(view->fViewRef), ourRgn);

SetPortVisibleRegion(GetWindowPort(GetControlOwner(view->fViewRef)), saveVis);
DisposeRgn(saveVis);
}
}

SetPort(savePort);

DisposeRgn(ourRgn);
}
}

DisposeRgn(region);
}
}

#endif