Fixed: <rdar://problem/3880387> REGRESSION: www.shockplay.com site gives "Unexpected...
[WebKit-https.git] / WebKit / Plugins.subproj / WebBaseNetscapePluginView.m
1 /*      
2         WebBaseNetscapePluginView.m
3         Copyright 2002, Apple, Inc. All rights reserved.
4 */
5
6 #import <WebKit/WebBaseNetscapePluginView.h>
7
8 #import <WebKit/WebAssertions.h>
9 #import <WebKit/WebBridge.h>
10 #import <WebKit/WebDataSource.h>
11 #import <WebKit/WebDefaultUIDelegate.h>
12 #import <WebKit/WebFrameInternal.h> 
13 #import <WebKit/WebFrameView.h>
14 #import <WebKit/WebKitLogging.h>
15 #import <WebKit/WebKitNSStringExtras.h>
16 #import <WebKit/WebNetscapePluginStream.h>
17 #import <WebKit/WebNullPluginView.h>
18 #import <WebKit/WebNSObjectExtras.h>
19 #import <WebKit/WebNSURLExtras.h>
20 #import <WebKit/WebNSViewExtras.h>
21 #import <WebKit/WebNetscapePluginPackage.h>
22 #import <WebKit/WebPreferences.h>
23 #import <WebKit/WebViewPrivate.h>
24 #import <WebKit/WebUIDelegate.h>
25
26 #import <Foundation/NSData_NSURLExtras.h>
27 #import <Foundation/NSDictionary_NSURLExtras.h>
28 #import <Foundation/NSString_NSURLExtras.h>
29 #import <Foundation/NSURL_NSURLExtras.h>
30 #import <Foundation/NSURLRequestPrivate.h>
31
32 #import <AppKit/NSEvent_Private.h>
33 #import <Carbon/Carbon.h>
34 #import <CoreGraphics/CoreGraphicsPrivate.h>
35 #import <HIToolbox/TextServicesPriv.h>
36 #import <QD/QuickdrawPriv.h>
37
38 // This is not yet in QuickdrawPriv.h, although it's supposed to be.
39 void CallDrawingNotifications(CGrafPtr port, Rect *mayDrawIntoThisRect, int drawingType);
40
41 // Send null events 50 times a second when active, so plug-ins like Flash get high frame rates.
42 #define NullEventIntervalActive         0.02
43 #define NullEventIntervalNotActive      0.25
44
45 #define LoginWindowDidSwitchFromUserNotification    @"WebLoginWindowDidSwitchFromUserNotification"
46 #define LoginWindowDidSwitchToUserNotification      @"WebLoginWindowDidSwitchToUserNotification"
47
48
49 static WebBaseNetscapePluginView *currentPluginView = nil;
50
51 typedef struct {
52     GrafPtr oldPort;
53     Point oldOrigin;
54     RgnHandle oldClipRegion;
55     RgnHandle oldVisibleRegion;
56     RgnHandle clipRegion;
57     BOOL forUpdate;
58 } PortState;
59
60 @interface WebPluginRequest : NSObject
61 {
62     NSURLRequest *_request;
63     NSString *_frameName;
64     void *_notifyData;
65     BOOL _sendNotification;
66 }
67
68 - (id)initWithRequest:(NSURLRequest *)request frameName:(NSString *)frameName notifyData:(void *)notifyData sendNotification:(BOOL)sendNotification;
69
70 - (NSURLRequest *)request;
71 - (NSString *)frameName;
72 - (void *)notifyData;
73 - (BOOL)sendNotification;
74
75 @end
76
77 @interface NSData (WebPluginDataExtras)
78 - (BOOL)_web_startsWithBlankLine;
79 - (unsigned)_web_locationAfterFirstBlankLine;
80 @end
81
82 static OSStatus TSMEventHandler(EventHandlerCallRef inHandlerRef, EventRef inEvent, void *pluginView);
83 void ConsoleConnectionChangeNotifyProc(CGSNotificationType type, CGSNotificationData data, CGSByteCount dataLength, CGSNotificationArg arg);
84
85 @interface WebBaseNetscapePluginView (ForwardDeclarations)
86 - (void)setWindowIfNecessary;
87 @end
88
89 @implementation WebBaseNetscapePluginView
90
91 + (void)initialize
92 {
93     CGSRegisterNotifyProc(ConsoleConnectionChangeNotifyProc, kCGSessionConsoleConnect, NULL);
94     CGSRegisterNotifyProc(ConsoleConnectionChangeNotifyProc, kCGSessionConsoleDisconnect, NULL);
95 }
96
97 #pragma mark EVENTS
98
99 + (void)getCarbonEvent:(EventRecord *)carbonEvent
100 {
101     carbonEvent->what = nullEvent;
102     carbonEvent->message = 0;
103     carbonEvent->when = TickCount();
104     GetGlobalMouse(&carbonEvent->where);
105     carbonEvent->modifiers = GetCurrentKeyModifiers();
106     if (!Button())
107         carbonEvent->modifiers |= btnState;
108 }
109
110 - (void)getCarbonEvent:(EventRecord *)carbonEvent
111 {
112     [[self class] getCarbonEvent:carbonEvent];
113 }
114
115 - (EventModifiers)modifiersForEvent:(NSEvent *)event
116 {
117     EventModifiers modifiers;
118     unsigned int modifierFlags = [event modifierFlags];
119     NSEventType eventType = [event type];
120     
121     modifiers = 0;
122     
123     if (eventType != NSLeftMouseDown && eventType != NSRightMouseDown)
124         modifiers |= btnState;
125     
126     if (modifierFlags & NSCommandKeyMask)
127         modifiers |= cmdKey;
128     
129     if (modifierFlags & NSShiftKeyMask)
130         modifiers |= shiftKey;
131
132     if (modifierFlags & NSAlphaShiftKeyMask)
133         modifiers |= alphaLock;
134
135     if (modifierFlags & NSAlternateKeyMask)
136         modifiers |= optionKey;
137
138     if (modifierFlags & NSControlKeyMask || eventType == NSRightMouseDown)
139         modifiers |= controlKey;
140     
141     return modifiers;
142 }
143
144 - (void)getCarbonEvent:(EventRecord *)carbonEvent withEvent:(NSEvent *)cocoaEvent
145 {
146     if ([cocoaEvent _eventRef] && ConvertEventRefToEventRecord([cocoaEvent _eventRef], carbonEvent)) {
147         return;
148     }
149     
150     NSPoint where = [[cocoaEvent window] convertBaseToScreen:[cocoaEvent locationInWindow]];
151         
152     carbonEvent->what = nullEvent;
153     carbonEvent->message = 0;
154     carbonEvent->when = (UInt32)([cocoaEvent timestamp] * 60); // seconds to ticks
155     carbonEvent->where.h = (short)where.x;
156     carbonEvent->where.v = (short)(NSMaxY([[[NSScreen screens] objectAtIndex:0] frame]) - where.y);
157     carbonEvent->modifiers = [self modifiersForEvent:cocoaEvent];
158 }
159
160 - (PortState)saveAndSetPortStateForUpdate:(BOOL)forUpdate
161 {
162     ASSERT([self currentWindow]);
163     
164     WindowRef windowRef = [[self currentWindow] windowRef];
165     CGrafPtr port = GetWindowPort(windowRef);
166
167     Rect portBounds;
168     GetPortBounds(port, &portBounds);
169
170     // Use AppKit to convert view coordinates to NSWindow coordinates.
171
172     NSRect boundsInWindow = [self convertRect:[self bounds] toView:nil];
173     NSRect visibleRectInWindow = [self convertRect:[self visibleRect] toView:nil];
174     
175     // Flip Y to convert NSWindow coordinates to top-left-based window coordinates.
176
177     float borderViewHeight = [[self currentWindow] frame].size.height;
178     boundsInWindow.origin.y = borderViewHeight - NSMaxY(boundsInWindow);
179     visibleRectInWindow.origin.y = borderViewHeight - NSMaxY(visibleRectInWindow);
180     
181     // Look at the Carbon port to convert top-left-based window coordinates into top-left-based content coordinates.
182
183     PixMap *pix = *GetPortPixMap(port);
184     boundsInWindow.origin.x += pix->bounds.left - portBounds.left;
185     boundsInWindow.origin.y += pix->bounds.top - portBounds.top;
186     visibleRectInWindow.origin.x += pix->bounds.left - portBounds.left;
187     visibleRectInWindow.origin.y += pix->bounds.top - portBounds.top;
188     
189     // Set up NS_Port.
190     
191     nPort.port = port;
192     nPort.portx = (int32)-boundsInWindow.origin.x;
193     nPort.porty = (int32)-boundsInWindow.origin.y;
194     
195     // Set up NPWindow.
196     
197     window.window = &nPort;
198     
199     window.x = (int32)boundsInWindow.origin.x; 
200     window.y = (int32)boundsInWindow.origin.y;
201     window.width = NSWidth(boundsInWindow);
202     window.height = NSHeight(boundsInWindow);
203
204     window.clipRect.top = (uint16)visibleRectInWindow.origin.y;
205     window.clipRect.left = (uint16)visibleRectInWindow.origin.x;
206     window.clipRect.bottom = (uint16)(visibleRectInWindow.origin.y + visibleRectInWindow.size.height);
207     window.clipRect.right = (uint16)(visibleRectInWindow.origin.x + visibleRectInWindow.size.width);
208
209     // Clip out the plug-in when it's not really in a window or off screen or has no height or width.
210     // The "big negative number" technique is how WebCore expresses off-screen widgets.
211     NSWindow *realWindow = [self window];
212     if (window.width <= 0 || window.height <= 0 || window.x < -100000 || realWindow == nil || [realWindow isMiniaturized] || [NSApp isHidden]) {
213         // The following code tries to give plug-ins the same size they will eventually have.
214         // The specifiedWidth and specifiedHeight variables are used to predict the size that
215         // WebCore will eventually resize us to.
216
217         // The QuickTime plug-in has problems if you give it a width or height of 0.
218         // Since other plug-ins also might have the same sort of trouble, we make sure
219         // to always give plug-ins a size other than 0,0.
220
221         if (window.width <= 0) {
222             window.width = specifiedWidth > 0 ? specifiedWidth : 100;
223         }
224         if (window.height <= 0) {
225             window.height = specifiedHeight > 0 ? specifiedHeight : 100;
226         }
227
228         window.clipRect.bottom = window.clipRect.top;
229         window.clipRect.left = window.clipRect.right;
230     }
231
232     window.type = NPWindowTypeWindow;
233     
234     // Save the port state.
235
236     PortState portState;
237     
238     GetPort(&portState.oldPort);    
239
240     portState.oldOrigin.h = portBounds.left;
241     portState.oldOrigin.v = portBounds.top;
242
243     portState.oldClipRegion = NewRgn();
244     GetPortClipRegion(port, portState.oldClipRegion);
245     
246     portState.oldVisibleRegion = NewRgn();
247     GetPortVisibleRegion(port, portState.oldVisibleRegion);
248     
249     RgnHandle clipRegion = NewRgn();
250     portState.clipRegion = clipRegion;
251     
252     MacSetRectRgn(clipRegion,
253         window.clipRect.left + nPort.portx, window.clipRect.top + nPort.porty,
254         window.clipRect.right + nPort.portx, window.clipRect.bottom + nPort.porty);
255     
256     portState.forUpdate = forUpdate;
257     
258     // Switch to the port and set it up.
259
260     SetPort(port);
261
262     PenNormal();
263     ForeColor(blackColor);
264     BackColor(whiteColor);
265     
266     SetOrigin(nPort.portx, nPort.porty);
267
268     SetPortClipRegion(nPort.port, clipRegion);
269
270     if (forUpdate) {
271         // AppKit may have tried to help us by doing a BeginUpdate.
272         // But the invalid region at that level didn't include AppKit's notion of what was not valid.
273         // We reset the port's visible region to counteract what BeginUpdate did.
274         SetPortVisibleRegion(nPort.port, clipRegion);
275
276         // Some plugins do their own BeginUpdate/EndUpdate.
277         // For those, we must make sure that the update region contains the area we want to draw.
278         InvalWindowRgn(windowRef, clipRegion);
279     }
280     
281     return portState;
282 }
283
284 - (PortState)saveAndSetPortState
285 {
286     return [self saveAndSetPortStateForUpdate:NO];
287 }
288
289 - (void)restorePortState:(PortState)portState
290 {
291     ASSERT([self currentWindow]);
292     
293     WindowRef windowRef = [[self currentWindow] windowRef];
294     CGrafPtr port = GetWindowPort(windowRef);
295
296     if (portState.forUpdate) {
297         ValidWindowRgn(windowRef, portState.clipRegion);
298     }
299     
300     SetOrigin(portState.oldOrigin.h, portState.oldOrigin.v);
301
302     SetPortClipRegion(port, portState.oldClipRegion);
303     if (portState.forUpdate) {
304         SetPortVisibleRegion(port, portState.oldVisibleRegion);
305     }
306
307     DisposeRgn(portState.oldClipRegion);
308     DisposeRgn(portState.oldVisibleRegion);
309     DisposeRgn(portState.clipRegion);
310
311     SetPort(portState.oldPort);
312 }
313
314 - (BOOL)sendEvent:(EventRecord *)event
315 {
316     ASSERT([self window]);
317
318     suspendKeyUpEvents = NO;
319     
320     if (!isStarted) {
321         return NO;
322     }
323
324     ASSERT(NPP_HandleEvent);
325     
326     // Make sure we don't call NPP_HandleEvent while we're inside NPP_SetWindow.
327     // We probably don't want more general reentrancy protection; we are really
328     // protecting only against this one case, which actually comes up when
329     // you first install the SVG viewer plug-in.
330     if (inSetWindow) {
331         return NO;
332     }
333
334     BOOL defers = [[self webView] defersCallbacks];
335     if (!defers) {
336         [[self webView] setDefersCallbacks:YES];
337     }
338
339     PortState portState = [self saveAndSetPortStateForUpdate:event->what == updateEvt];
340     
341     // We may have changed the window, so inform the plug-in.
342     [self setWindowIfNecessary];
343
344 #ifndef NDEBUG
345     // Draw green to help debug.
346     // If we see any green we know something's wrong.
347     if (event->what == updateEvt) {
348         ForeColor(greenColor);
349         const Rect bigRect = { -10000, -10000, 10000, 10000 };
350         PaintRect(&bigRect);
351         ForeColor(blackColor);
352     }
353 #endif
354     
355     // Temporarily retain self in case the plug-in view is released while sending an event. 
356     [self retain];
357
358     BOOL acceptedEvent = NPP_HandleEvent(instance, event);
359
360     if ([self currentWindow]) {
361         [self restorePortState:portState];
362     }
363
364     if (!defers) {
365         [[self webView] setDefersCallbacks:NO];
366     }
367     
368     [self release];
369     
370     return acceptedEvent;
371 }
372
373 - (void)sendActivateEvent:(BOOL)activate
374 {
375     EventRecord event;
376     
377     [self getCarbonEvent:&event];
378     event.what = activateEvt;
379     WindowRef windowRef = [[self window] windowRef];
380     event.message = (UInt32)windowRef;
381     if (activate) {
382         event.modifiers |= activeFlag;
383     }
384     
385     BOOL acceptedEvent;
386     acceptedEvent = [self sendEvent:&event]; 
387     
388     LOG(PluginEvents, "NPP_HandleEvent(activateEvent): %d  isActive: %d", acceptedEvent, activate);
389 }
390
391 - (BOOL)sendUpdateEvent
392 {
393     EventRecord event;
394     
395     [self getCarbonEvent:&event];
396     event.what = updateEvt;
397     WindowRef windowRef = [[self window] windowRef];
398     event.message = (UInt32)windowRef;
399
400     BOOL acceptedEvent = [self sendEvent:&event];
401
402     LOG(PluginEvents, "NPP_HandleEvent(updateEvt): %d", acceptedEvent);
403
404     return acceptedEvent;
405 }
406
407 -(void)sendNullEvent
408 {
409     EventRecord event;
410
411     [self getCarbonEvent:&event];
412
413     // Plug-in should not react to cursor position when not active or when a menu is down.
414     MenuTrackingData trackingData;
415     OSStatus error = GetMenuTrackingData(NULL, &trackingData);
416
417     // Plug-in should not react to cursor position when the actual window is not key.
418     if (![[self window] isKeyWindow] || (error == noErr && trackingData.menu)) {
419         // FIXME: Does passing a v and h of -1 really prevent it from reacting to the cursor position?
420         event.where.v = -1;
421         event.where.h = -1;
422     }
423
424     [self sendEvent:&event];
425 }
426
427 - (void)stopNullEvents
428 {
429     [nullEventTimer invalidate];
430     [nullEventTimer release];
431     nullEventTimer = nil;
432 }
433
434 - (void)restartNullEvents
435 {
436     ASSERT([self window]);
437     
438     if (nullEventTimer) {
439         [self stopNullEvents];
440     }
441     
442     if ([[self window] isMiniaturized]) {
443         return;
444     }
445
446     NSTimeInterval interval;
447
448     // Send null events less frequently when the actual window is not key.  Also, allow the DB
449     // to override this behavior and send full speed events to non key windows.
450     if ([[self window] isKeyWindow] || [[self webView] _dashboardBehavior:WebDashboardBehaviorAlwaysSendActiveNullEventsToPlugIns]) {
451         interval = NullEventIntervalActive;
452     } else {
453         interval = NullEventIntervalNotActive;
454     }
455     
456     nullEventTimer = [[NSTimer scheduledTimerWithTimeInterval:interval
457                                                        target:self
458                                                      selector:@selector(sendNullEvent)
459                                                      userInfo:nil
460                                                       repeats:YES] retain];
461 }
462
463 - (BOOL)acceptsFirstResponder
464 {
465     return YES;
466 }
467
468 - (void)installKeyEventHandler
469 {
470     static const EventTypeSpec sTSMEvents[] =
471     {
472     { kEventClassTextInput, kEventTextInputUnicodeForKeyEvent }
473     };
474     
475     if (!keyEventHandler) {
476         InstallEventHandler(GetWindowEventTarget([[self window] windowRef]),
477                             NewEventHandlerUPP(TSMEventHandler),
478                             GetEventTypeCount(sTSMEvents),
479                             sTSMEvents,
480                             self,
481                             &keyEventHandler);
482     }
483 }
484
485 - (void)removeKeyEventHandler
486 {
487     if (keyEventHandler) {
488         RemoveEventHandler(keyEventHandler);
489         keyEventHandler = NULL;
490     }
491 }
492
493 - (void)setHasFocus:(BOOL)flag
494 {
495     if (hasFocus != flag) {
496         hasFocus = flag;
497         EventRecord event;
498         [self getCarbonEvent:&event];
499         BOOL acceptedEvent;
500         if (hasFocus) {
501             event.what = getFocusEvent;
502             acceptedEvent = [self sendEvent:&event]; 
503             LOG(PluginEvents, "NPP_HandleEvent(getFocusEvent): %d", acceptedEvent);
504             [self installKeyEventHandler];
505         } else {
506             event.what = loseFocusEvent;
507             acceptedEvent = [self sendEvent:&event]; 
508             LOG(PluginEvents, "NPP_HandleEvent(loseFocusEvent): %d", acceptedEvent);
509             [self removeKeyEventHandler];
510         }
511     }
512 }
513
514 - (BOOL)becomeFirstResponder
515 {
516     [self setHasFocus:YES];
517     return YES;
518 }
519
520 - (BOOL)resignFirstResponder
521 {
522     [self setHasFocus:NO];    
523     return YES;
524 }
525
526 // AppKit doesn't call mouseDown or mouseUp on right-click. Simulate control-click
527 // mouseDown and mouseUp so plug-ins get the right-click event as they do in Carbon (3125743).
528 - (void)rightMouseDown:(NSEvent *)theEvent
529 {
530     [self mouseDown:theEvent];
531 }
532
533 - (void)rightMouseUp:(NSEvent *)theEvent
534 {
535     [self mouseUp:theEvent];
536 }
537
538 - (void)mouseDown:(NSEvent *)theEvent
539 {
540     EventRecord event;
541
542     [self getCarbonEvent:&event withEvent:theEvent];
543     event.what = mouseDown;
544
545     BOOL acceptedEvent;
546     acceptedEvent = [self sendEvent:&event]; 
547     
548     LOG(PluginEvents, "NPP_HandleEvent(mouseDown): %d pt.v=%d, pt.h=%d", acceptedEvent, event.where.v, event.where.h);
549 }
550
551 - (void)mouseUp:(NSEvent *)theEvent
552 {
553     EventRecord event;
554     
555     [self getCarbonEvent:&event withEvent:theEvent];
556     event.what = mouseUp;
557
558     BOOL acceptedEvent;
559     acceptedEvent = [self sendEvent:&event]; 
560     
561     LOG(PluginEvents, "NPP_HandleEvent(mouseUp): %d pt.v=%d, pt.h=%d", acceptedEvent, event.where.v, event.where.h);
562 }
563
564 - (void)mouseEntered:(NSEvent *)theEvent
565 {
566     EventRecord event;
567     
568     [self getCarbonEvent:&event withEvent:theEvent];
569     event.what = adjustCursorEvent;
570
571     BOOL acceptedEvent;
572     acceptedEvent = [self sendEvent:&event]; 
573     
574     LOG(PluginEvents, "NPP_HandleEvent(mouseEntered): %d", acceptedEvent);
575 }
576
577 - (void)mouseExited:(NSEvent *)theEvent
578 {
579     EventRecord event;
580         
581     [self getCarbonEvent:&event withEvent:theEvent];
582     event.what = adjustCursorEvent;
583
584     BOOL acceptedEvent;
585     acceptedEvent = [self sendEvent:&event]; 
586     
587     LOG(PluginEvents, "NPP_HandleEvent(mouseExited): %d", acceptedEvent);
588     
589     // Set cursor back to arrow cursor.
590     [[NSCursor arrowCursor] set];
591 }
592
593 - (void)mouseDragged:(NSEvent *)theEvent
594 {
595     // Do nothing so that other responders don't respond to the drag that initiated in this view.
596 }
597
598 - (UInt32)keyMessageForEvent:(NSEvent *)event
599 {
600     NSData *data = [[event characters] dataUsingEncoding:CFStringConvertEncodingToNSStringEncoding(CFStringGetSystemEncoding())];
601     if (!data) {
602         return 0;
603     }
604     UInt8 characterCode;
605     [data getBytes:&characterCode length:1];
606     UInt16 keyCode = [event keyCode];
607     return keyCode << 8 | characterCode;
608 }
609
610 - (void)keyUp:(NSEvent *)theEvent
611 {
612     TSMProcessRawKeyEvent([theEvent _eventRef]);
613     
614     // TSM won't send keyUp events so we have to send them ourselves.
615     // Only send keyUp events after we receive the TSM callback because this is what plug-in expect from OS 9.
616     if (!suspendKeyUpEvents) {
617         EventRecord event;
618         
619         [self getCarbonEvent:&event withEvent:theEvent];
620         event.what = keyUp;
621         
622         if (event.message == 0) {
623             event.message = [self keyMessageForEvent:theEvent];
624         }
625         
626         [self sendEvent:&event];
627     }
628 }
629
630 - (void)keyDown:(NSEvent *)theEvent
631 {
632     suspendKeyUpEvents = YES;
633     TSMProcessRawKeyEvent([theEvent _eventRef]);
634 }
635
636 static OSStatus TSMEventHandler(EventHandlerCallRef inHandlerRef, EventRef inEvent, void *pluginView)
637 {    
638     EventRef rawKeyEventRef;
639     OSStatus status = GetEventParameter(inEvent, kEventParamTextInputSendKeyboardEvent, typeEventRef, NULL, sizeof(EventRef), NULL, &rawKeyEventRef);
640     if (status != noErr) {
641         ERROR("GetEventParameter failed with error: %d", status);
642         return noErr;
643     }
644     
645     // Two-pass read to allocate/extract Mac charCodes
646     UInt32 numBytes;    
647     status = GetEventParameter(rawKeyEventRef, kEventParamKeyMacCharCodes, typeChar, NULL, 0, &numBytes, NULL);
648     if (status != noErr) {
649         ERROR("GetEventParameter failed with error: %d", status);
650         return noErr;
651     }
652     char *buffer = malloc(numBytes);
653     status = GetEventParameter(rawKeyEventRef, kEventParamKeyMacCharCodes, typeChar, NULL, numBytes, NULL, buffer);
654     if (status != noErr) {
655         ERROR("GetEventParameter failed with error: %d", status);
656         free(buffer);
657         return noErr;
658     }
659     
660     EventRef cloneEvent = CopyEvent(rawKeyEventRef);
661     unsigned i;
662     for (i = 0; i < numBytes; i++) {
663         status = SetEventParameter(cloneEvent, kEventParamKeyMacCharCodes, typeChar, 1 /* one char code */, &buffer[i]);
664         if (status != noErr) {
665             ERROR("SetEventParameter failed with error: %d", status);
666             free(buffer);
667             return noErr;
668         }
669         
670         EventRecord eventRec;
671         if (ConvertEventRefToEventRecord(cloneEvent, &eventRec)) {
672             BOOL acceptedEvent;
673             acceptedEvent = [(WebBaseNetscapePluginView *)pluginView sendEvent:&eventRec];
674             
675             LOG(PluginEvents, "NPP_HandleEvent(keyDown): %d charCode:%c keyCode:%lu",
676                 acceptedEvent, (char) (eventRec.message & charCodeMask), (eventRec.message & keyCodeMask));
677             
678             // We originally thought that if the plug-in didn't accept this event,
679             // we should pass it along so that keyboard scrolling, for example, will work.
680             // In practice, this is not a good idea, because plug-ins tend to eat the event but return false.
681             // MacIE handles each key event twice because of this, but we will emulate the other browsers instead.
682         }
683     }
684     ReleaseEvent(cloneEvent);
685     
686     free(buffer);
687     return noErr;
688 }
689
690 // Fake up command-modified events so cut, copy, paste and select all menus work.
691 - (void)sendModifierEventWithKeyCode:(int)keyCode character:(char)character
692 {
693     EventRecord event;
694     [self getCarbonEvent:&event];
695     event.what = keyDown;
696     event.modifiers |= cmdKey;
697     event.message = keyCode << 8 | character;
698     [self sendEvent:&event];
699 }
700
701 - (void)cut:(id)sender
702 {
703     [self sendModifierEventWithKeyCode:7 character:'x'];
704 }
705
706 - (void)copy:(id)sender
707 {
708     [self sendModifierEventWithKeyCode:8 character:'c'];
709 }
710
711 - (void)paste:(id)sender
712 {
713     [self sendModifierEventWithKeyCode:9 character:'v'];
714 }
715
716 - (void)selectAll:(id)sender
717 {
718     [self sendModifierEventWithKeyCode:0 character:'a'];
719 }
720
721 #pragma mark WEB_NETSCAPE_PLUGIN
722
723 - (BOOL)isNewWindowEqualToOldWindow
724 {
725     if (window.x != lastSetWindow.x) {
726         return NO;
727     }
728     if (window.y != lastSetWindow.y) {
729         return NO;
730     }
731     if (window.width != lastSetWindow.width) {
732         return NO;
733     }
734     if (window.height != lastSetWindow.height) {
735         return NO;
736     }
737     if (window.clipRect.top != lastSetWindow.clipRect.top) {
738         return NO;
739     }
740     if (window.clipRect.left != lastSetWindow.clipRect.left) {
741         return NO;
742     }
743     if (window.clipRect.bottom  != lastSetWindow.clipRect.bottom ) {
744         return NO;
745     }
746     if (window.clipRect.right != lastSetWindow.clipRect.right) {
747         return NO;
748     }
749     if (window.type != lastSetWindow.type) {
750         return NO;
751     }
752     if (nPort.portx != lastSetPort.portx) {
753         return NO;
754     }
755     if (nPort.porty != lastSetPort.porty) {
756         return NO;
757     }
758     if (nPort.port != lastSetPort.port) {
759         return NO;
760     }
761     
762     return YES;
763 }
764
765 - (void)updateAndSetWindow
766 {    
767     PortState portState = [self saveAndSetPortState];
768     [self setWindowIfNecessary];
769     [self restorePortState:portState];
770 }
771
772 - (void)setWindowIfNecessary
773 {
774     if (!isStarted) {
775         return;
776     }
777     
778     if (![self isNewWindowEqualToOldWindow]) {        
779         // Make sure we don't call NPP_HandleEvent while we're inside NPP_SetWindow.
780         // We probably don't want more general reentrancy protection; we are really
781         // protecting only against this one case, which actually comes up when
782         // you first install the SVG viewer plug-in.
783         NPError npErr;
784         ASSERT(!inSetWindow);
785         
786         inSetWindow = YES;
787         npErr = NPP_SetWindow(instance, &window);
788         inSetWindow = NO;
789
790         LOG(Plugins, "NPP_SetWindow: %d, port=0x%08x, window.x:%d window.y:%d",
791             npErr, (int)nPort.port, (int)window.x, (int)window.y);
792
793         lastSetWindow = window;
794         lastSetPort = nPort;
795     }
796 }
797
798 - (void)removeTrackingRect
799 {
800     if (trackingTag) {
801         [self removeTrackingRect:trackingTag];
802         trackingTag = 0;
803
804         // Must release the window to balance the retain in resetTrackingRect.
805         // But must do it after setting trackingTag to 0 so we don't re-enter.
806         [[self window] release];
807     }
808 }
809
810 - (void)resetTrackingRect
811 {
812     [self removeTrackingRect];
813     if (isStarted) {
814         // Must retain the window so that removeTrackingRect can work after the window is closed.
815         [[self window] retain];
816         trackingTag = [self addTrackingRect:[self bounds] owner:self userData:nil assumeInside:NO];
817     }
818 }
819
820 + (void)setCurrentPluginView:(WebBaseNetscapePluginView *)view
821 {
822     currentPluginView = view;
823 }
824
825 + (WebBaseNetscapePluginView *)currentPluginView
826 {
827     return currentPluginView;
828 }
829
830 - (BOOL)canStart
831 {
832     return YES;
833 }
834
835 - (void)didStart
836 {
837     // Do nothing. Overridden by subclasses.
838 }
839
840 - (void)addWindowObservers
841 {
842     ASSERT([self window]);
843
844     NSWindow *theWindow = [self window];
845     
846     NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
847     NSView *view;
848     for (view = self; view; view = [view superview]) {
849         [notificationCenter addObserver:self selector:@selector(viewHasMoved:)
850                                    name:NSViewFrameDidChangeNotification object:view];
851         [notificationCenter addObserver:self selector:@selector(viewHasMoved:)
852                                    name:NSViewBoundsDidChangeNotification object:view];
853     }
854     [notificationCenter addObserver:self selector:@selector(windowWillClose:)
855                                name:NSWindowWillCloseNotification object:theWindow];
856     [notificationCenter addObserver:self selector:@selector(windowBecameKey:)
857                                name:NSWindowDidBecomeKeyNotification object:theWindow];
858     [notificationCenter addObserver:self selector:@selector(windowResignedKey:)
859                                name:NSWindowDidResignKeyNotification object:theWindow];
860     [notificationCenter addObserver:self selector:@selector(windowDidMiniaturize:)
861                                name:NSWindowDidMiniaturizeNotification object:theWindow];
862     [notificationCenter addObserver:self selector:@selector(windowDidDeminiaturize:)
863                                name:NSWindowDidDeminiaturizeNotification object:theWindow];
864     
865     [notificationCenter addObserver:self selector:@selector(loginWindowDidSwitchFromUser:)
866                                name:LoginWindowDidSwitchFromUserNotification object:nil];
867     [notificationCenter addObserver:self selector:@selector(loginWindowDidSwitchToUser:)
868                                name:LoginWindowDidSwitchToUserNotification object:nil];
869 }
870
871 - (void)removeWindowObservers
872 {
873     NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
874     [notificationCenter removeObserver:self name:NSViewFrameDidChangeNotification     object:nil];
875     [notificationCenter removeObserver:self name:NSViewBoundsDidChangeNotification    object:nil];
876     [notificationCenter removeObserver:self name:NSWindowWillCloseNotification        object:nil];
877     [notificationCenter removeObserver:self name:NSWindowDidBecomeKeyNotification     object:nil];
878     [notificationCenter removeObserver:self name:NSWindowDidResignKeyNotification     object:nil];
879     [notificationCenter removeObserver:self name:NSWindowDidMiniaturizeNotification   object:nil];
880     [notificationCenter removeObserver:self name:NSWindowDidDeminiaturizeNotification object:nil];
881     [notificationCenter removeObserver:self name:LoginWindowDidSwitchFromUserNotification   object:nil];
882     [notificationCenter removeObserver:self name:LoginWindowDidSwitchToUserNotification     object:nil];
883 }
884
885 - (BOOL)start
886 {
887     ASSERT([self currentWindow]);
888     
889     if (isStarted) {
890         return YES;
891     }
892
893     if (![[WebPreferences standardPreferences] arePlugInsEnabled] || ![self canStart]) {
894         return NO;
895     }
896
897     ASSERT(NPP_New);
898
899     [[self class] setCurrentPluginView:self];
900     NPError npErr = NPP_New((char *)[MIMEType cString], instance, mode, argsCount, cAttributes, cValues, NULL);
901     [[self class] setCurrentPluginView:nil];
902     
903     LOG(Plugins, "NPP_New: %d", npErr);
904     if (npErr != NPERR_NO_ERROR) {
905         ERROR("NPP_New failed with error: %d", npErr);
906         return NO;
907     }
908
909     isStarted = YES;
910         
911     [self updateAndSetWindow];
912
913     if ([self window]) {
914         [self addWindowObservers];
915         if ([[self window] isKeyWindow]) {
916             [self sendActivateEvent:YES];
917         }
918         [self restartNullEvents];
919     }
920
921     [self resetTrackingRect];
922     
923     [self didStart];
924     
925     return YES;
926 }
927
928 - (void)stop
929 {
930     [self removeTrackingRect];
931
932     if (!isStarted) {
933         return;
934     }
935     
936     isStarted = NO;
937     
938     // Stop any active streams
939     [streams makeObjectsPerformSelector:@selector(stop)];
940     
941     // Stop the null events
942     [self stopNullEvents];
943
944     // Set cursor back to arrow cursor
945     [[NSCursor arrowCursor] set];
946     
947     // Stop notifications and callbacks.
948     [self removeWindowObservers];
949     [[pendingFrameLoads allKeys] makeObjectsPerformSelector:@selector(_setInternalLoadDelegate:) withObject:nil];
950     [NSObject cancelPreviousPerformRequestsWithTarget:self];
951
952     // Setting the window type to 0 ensures that NPP_SetWindow will be called if the plug-in is restarted.
953     lastSetWindow.type = 0;
954     
955     NPError npErr;
956     npErr = NPP_Destroy(instance, NULL);
957     LOG(Plugins, "NPP_Destroy: %d", npErr);
958
959     instance->pdata = NULL;
960     
961     // We usually remove the key event handler in resignFirstResponder but it is possible that resignFirstResponder 
962     // may never get called so we can't completely rely on it.
963     [self removeKeyEventHandler];
964 }
965
966 - (BOOL)isStarted
967 {
968     return isStarted;
969 }
970
971 - (WebDataSource *)dataSource
972 {
973     // Do nothing. Overridden by subclasses.
974     return nil;
975 }
976
977 - (WebFrame *)webFrame
978 {
979     return [[self dataSource] webFrame];
980 }
981
982 - (WebView *)webView
983 {
984     return [[self webFrame] webView];
985 }
986
987 - (NSWindow *)currentWindow
988 {
989     return [self window] ? [self window] : [[self webView] hostWindow];
990 }
991
992 - (NPP)pluginPointer
993 {
994     return instance;
995 }
996
997 - (WebNetscapePluginPackage *)plugin
998 {
999     return plugin;
1000 }
1001
1002 - (void)setPlugin:(WebNetscapePluginPackage *)thePlugin;
1003 {
1004     [thePlugin retain];
1005     [plugin release];
1006     plugin = thePlugin;
1007
1008     NPP_New =           [plugin NPP_New];
1009     NPP_Destroy =       [plugin NPP_Destroy];
1010     NPP_SetWindow =     [plugin NPP_SetWindow];
1011     NPP_NewStream =     [plugin NPP_NewStream];
1012     NPP_WriteReady =    [plugin NPP_WriteReady];
1013     NPP_Write =         [plugin NPP_Write];
1014     NPP_StreamAsFile =  [plugin NPP_StreamAsFile];
1015     NPP_DestroyStream = [plugin NPP_DestroyStream];
1016     NPP_HandleEvent =   [plugin NPP_HandleEvent];
1017     NPP_URLNotify =     [plugin NPP_URLNotify];
1018     NPP_GetValue =      [plugin NPP_GetValue];
1019     NPP_SetValue =      [plugin NPP_SetValue];
1020     NPP_Print =         [plugin NPP_Print];
1021 }
1022
1023 - (void)setMIMEType:(NSString *)theMIMEType
1024 {
1025     NSString *type = [theMIMEType copy];
1026     [MIMEType release];
1027     MIMEType = type;
1028 }
1029
1030 - (void)setBaseURL:(NSURL *)theBaseURL
1031 {
1032     [theBaseURL retain];
1033     [baseURL release];
1034     baseURL = theBaseURL;
1035 }
1036
1037 - (void)setAttributeKeys:(NSArray *)keys andValues:(NSArray *)values;
1038 {
1039     ASSERT([keys count] == [values count]);
1040     
1041     // Convert the attributes to 2 C string arrays.
1042     // These arrays are passed to NPP_New, but the strings need to be
1043     // modifiable and live the entire life of the plugin.
1044
1045     // The Java plug-in requires the first argument to be the base URL
1046     if ([MIMEType isEqualToString:@"application/x-java-applet"]) {
1047         cAttributes = (char **)malloc(([keys count] + 1) * sizeof(char *));
1048         cValues = (char **)malloc(([values count] + 1) * sizeof(char *));
1049         cAttributes[0] = strdup("DOCBASE");
1050         cValues[0] = strdup([baseURL _web_URLCString]);
1051         argsCount++;
1052     } else {
1053         cAttributes = (char **)malloc([keys count] * sizeof(char *));
1054         cValues = (char **)malloc([values count] * sizeof(char *));
1055     }
1056
1057     BOOL isWMP = [[[plugin bundle] bundleIdentifier] isEqualToString:@"com.microsoft.WMP.defaultplugin"];
1058     
1059     unsigned i;
1060     unsigned count = [keys count];
1061     for (i = 0; i < count; i++) {
1062         NSString *key = [keys objectAtIndex:i];
1063         NSString *value = [values objectAtIndex:i];
1064         if ([key _web_isCaseInsensitiveEqualToString:@"height"]) {
1065             specifiedHeight = [value intValue];
1066         } else if ([key _web_isCaseInsensitiveEqualToString:@"width"]) {
1067             specifiedWidth = [value intValue];
1068         }
1069         // Avoid Window Media Player crash when these attributes are present.
1070         if (isWMP && ([key _web_isCaseInsensitiveEqualToString:@"SAMIStyle"] || [key _web_isCaseInsensitiveEqualToString:@"SAMILang"])) {
1071             continue;
1072         }
1073         cAttributes[argsCount] = strdup([key UTF8String]);
1074         cValues[argsCount] = strdup([value UTF8String]);
1075         LOG(Plugins, "%@ = %@", key, value);
1076         argsCount++;
1077     }
1078 }
1079
1080 - (void)setMode:(int)theMode
1081 {
1082     mode = theMode;
1083 }
1084
1085 #pragma mark NSVIEW
1086
1087 - initWithFrame:(NSRect)frame
1088 {
1089     [super initWithFrame:frame];
1090
1091     instance = &instanceStruct;
1092     instance->ndata = self;
1093
1094     streams = [[NSMutableArray alloc] init];
1095     pendingFrameLoads = [[NSMutableDictionary alloc] init];
1096
1097     [[NSNotificationCenter defaultCenter] addObserver:self
1098                                              selector:@selector(preferencesHaveChanged:)
1099                                                  name:WebPreferencesChangedNotification
1100                                                object:nil];
1101
1102     return self;
1103 }
1104
1105 - (void)freeAttributeKeysAndValues
1106 {
1107     unsigned i;
1108     for (i = 0; i < argsCount; i++) {
1109         free(cAttributes[i]);
1110         free(cValues[i]);
1111     }
1112     free(cAttributes);
1113     free(cValues);
1114 }
1115
1116 - (void)dealloc
1117 {
1118     [[NSNotificationCenter defaultCenter] removeObserver:self];
1119     
1120     [self stop];
1121
1122     [plugin release];
1123     [streams release];
1124     [MIMEType release];
1125     [baseURL release];
1126     [pendingFrameLoads release];
1127
1128     [self freeAttributeKeysAndValues];
1129
1130     [super dealloc];
1131 }
1132
1133 - (void)finalize
1134 {
1135     [[NSNotificationCenter defaultCenter] removeObserver:self];
1136
1137     // FIXME: Bad to stop at finalize time. Need to restructure code
1138     // so that we're already stopped before we get to this point.
1139     [self stop];
1140
1141     [self freeAttributeKeysAndValues];
1142
1143     [super finalize];
1144 }
1145
1146 - (void)drawRect:(NSRect)rect
1147 {
1148     if (!isStarted) {
1149         return;
1150     }
1151     
1152     if ([NSGraphicsContext currentContextDrawingToScreen]) {
1153         [self sendUpdateEvent];
1154     } else {
1155         // Printing 2862383
1156     }
1157 }
1158
1159 - (BOOL)isFlipped
1160 {
1161     return YES;
1162 }
1163
1164 -(void)tellQuickTimeToChill
1165 {
1166     // Make a call to the secret QuickDraw API that makes QuickTime calm down.
1167     WindowRef windowRef = [[self window] windowRef];
1168     if (!windowRef) {
1169         return;
1170     }
1171     CGrafPtr port = GetWindowPort(windowRef);
1172     Rect bounds;
1173     GetPortBounds(port, &bounds);
1174     CallDrawingNotifications(port, &bounds, kBitsProc);
1175 }
1176
1177 - (void)viewWillMoveToWindow:(NSWindow *)newWindow
1178 {
1179     [self tellQuickTimeToChill];
1180
1181     // We must remove the tracking rect before we move to the new window.
1182     // Once we move to the new window, it will be too late.
1183     [self removeTrackingRect];
1184     [self removeWindowObservers];
1185     
1186     // Workaround for: <rdar://problem/3822871> resignFirstResponder is not sent to first responder view when it is removed from the window
1187     [self setHasFocus:NO];
1188
1189     if (!newWindow) {
1190         if ([[self webView] hostWindow]) {
1191             // View will be moved out of the actual window but it still has a host window.
1192             [self stopNullEvents];
1193         } else {
1194             // View will have no associated windows.
1195             [self stop];
1196         }
1197     }
1198 }
1199
1200 - (void)viewDidMoveToWindow
1201 {
1202     [self resetTrackingRect];
1203     
1204     if ([self window]) {
1205         // View moved to an actual window. Start it if not already started.
1206         [self start];
1207         [self restartNullEvents];
1208         [self addWindowObservers];
1209     } else if ([[self webView] hostWindow]) {
1210         // View moved out of an actual window, but still has a host window.
1211         // Call setWindow to explicitly "clip out" the plug-in from sight.
1212         // FIXME: It would be nice to do this where we call stopNullEvents in viewWillMoveToWindow.
1213         [self updateAndSetWindow];
1214     }
1215 }
1216
1217 - (void)viewWillMoveToHostWindow:(NSWindow *)hostWindow
1218 {
1219     if (!hostWindow && ![self window]) {
1220         // View will have no associated windows.
1221         [self stop];
1222     }
1223 }
1224
1225 - (void)viewDidMoveToHostWindow
1226 {
1227     if ([[self webView] hostWindow]) {
1228         // View now has an associated window. Start it if not already started.
1229         [self start];
1230     }
1231 }
1232
1233 #pragma mark NOTIFICATIONS
1234
1235 - (void)viewHasMoved:(NSNotification *)notification
1236 {
1237     [self tellQuickTimeToChill];
1238     [self updateAndSetWindow];
1239     [self resetTrackingRect];
1240 }
1241
1242 - (void)windowWillClose:(NSNotification *)notification
1243 {
1244     [self stop];
1245 }
1246
1247 - (void)windowBecameKey:(NSNotification *)notification
1248 {
1249     [self sendActivateEvent:YES];
1250     [self setNeedsDisplay:YES];
1251     [self restartNullEvents];
1252     SetUserFocusWindow([[self window] windowRef]);
1253 }
1254
1255 - (void)windowResignedKey:(NSNotification *)notification
1256 {
1257     [self sendActivateEvent:NO];
1258     [self setNeedsDisplay:YES];
1259     [self restartNullEvents];
1260 }
1261
1262 - (void)windowDidMiniaturize:(NSNotification *)notification
1263 {
1264     [self stopNullEvents];
1265 }
1266
1267 - (void)windowDidDeminiaturize:(NSNotification *)notification
1268 {
1269     [self restartNullEvents];
1270 }
1271
1272 - (void)loginWindowDidSwitchFromUser:(NSNotification *)notification
1273 {
1274     [self stopNullEvents];
1275 }
1276
1277 -(void)loginWindowDidSwitchToUser:(NSNotification *)notification
1278 {
1279     [self restartNullEvents];
1280 }
1281
1282 - (void)preferencesHaveChanged:(NSNotification *)notification
1283 {
1284     WebPreferences *preferences = [[self webView] preferences];
1285     BOOL arePlugInsEnabled = [preferences arePlugInsEnabled];
1286     
1287     if ([notification object] == preferences && isStarted != arePlugInsEnabled) {
1288         if (arePlugInsEnabled) {
1289             if ([self currentWindow]) {
1290                 [self start];
1291             }
1292         } else {
1293             [self stop];
1294             [self setNeedsDisplay:YES];
1295         }
1296     }
1297 }
1298
1299 - (void *)pluginScriptableObject
1300 {
1301     if (NPP_GetValue) {
1302         void *value = 0;
1303         NPError npErr = NPP_GetValue (instance, NPPVpluginScriptableNPObject, (void *)&value);
1304         if (npErr == NPERR_NO_ERROR) {
1305             return value;
1306         }
1307     }
1308     return (void *)0;
1309 }
1310
1311
1312 @end
1313
1314 @implementation WebBaseNetscapePluginView (WebNPPCallbacks)
1315
1316 - (NSMutableURLRequest *)requestWithURLCString:(const char *)URLCString
1317 {
1318     if (!URLCString) {
1319         return nil;
1320     }
1321     
1322     CFStringRef string = CFStringCreateWithCString(kCFAllocatorDefault, URLCString, kCFStringEncodingWindowsLatin1);
1323     NSString *URLString = [(NSString *)string _web_stringByStrippingReturnCharacters];
1324     NSURL *URL = [NSURL _web_URLWithDataAsString:URLString relativeToURL:baseURL];
1325     CFRelease(string);
1326     if (!URL) {
1327         return nil;
1328     }
1329     NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:URL];
1330     [request setHTTPReferrer:[[[[self dataSource] request] URL] _web_originalDataAsString]];
1331     return request;
1332 }
1333
1334 - (void)evaluateJavaScriptPluginRequest:(WebPluginRequest *)JSPluginRequest
1335 {
1336     // FIXME: Is this isStarted check needed here? evaluateJavaScriptPluginRequest should not be called
1337     // if we are stopped since this method is called after a delay and we call 
1338     // cancelPreviousPerformRequestsWithTarget inside of stop.
1339     if (!isStarted) {
1340         return;
1341     }
1342     
1343     NSURL *URL = [[JSPluginRequest request] URL];
1344     NSString *JSString = [URL _web_scriptIfJavaScriptURL];
1345     ASSERT(JSString);
1346     
1347     NSString *result = [[[self webFrame] _bridge] stringByEvaluatingJavaScriptFromString:JSString];
1348     
1349     // Don't continue if stringByEvaluatingJavaScriptFromString caused the plug-in to stop.
1350     if (!isStarted) {
1351         return;
1352     }
1353         
1354     if ([JSPluginRequest frameName] != nil) {
1355         // FIXME: If the result is a string, we probably want to put that string into the frame, just
1356         // like we do in KHTMLPartBrowserExtension::openURLRequest.
1357         if ([JSPluginRequest sendNotification]) {
1358             NPP_URLNotify(instance, [URL _web_URLCString], NPRES_DONE, [JSPluginRequest notifyData]);
1359         }
1360     } else if ([result length] > 0) {
1361         // Don't call NPP_NewStream and other stream methods if there is no JS result to deliver. This is what Mozilla does.
1362         NSData *JSData = [result dataUsingEncoding:NSUTF8StringEncoding];
1363         WebBaseNetscapePluginStream *stream = [[WebBaseNetscapePluginStream alloc] initWithRequestURL:URL
1364                                                                                         pluginPointer:instance
1365                                                                                            notifyData:[JSPluginRequest notifyData]
1366                                                                                      sendNotification:[JSPluginRequest sendNotification]];
1367         [stream startStreamResponseURL:URL
1368                  expectedContentLength:[JSData length]
1369                       lastModifiedDate:nil
1370                               MIMEType:@"text/plain"];
1371         [stream receivedData:JSData];
1372         [stream finishedLoadingWithData:JSData];
1373         [stream release];
1374     }
1375 }
1376
1377 - (void)webFrame:(WebFrame *)webFrame didFinishLoadWithReason:(NPReason)reason
1378 {
1379     ASSERT(isStarted);
1380     
1381     WebPluginRequest *pluginRequest = [pendingFrameLoads objectForKey:webFrame];
1382     ASSERT(pluginRequest != nil);
1383     ASSERT([pluginRequest sendNotification]);
1384         
1385     NPP_URLNotify(instance, [[[pluginRequest request] URL] _web_URLCString], reason, [pluginRequest notifyData]);
1386     
1387     [pendingFrameLoads removeObjectForKey:webFrame];
1388     [webFrame _setInternalLoadDelegate:nil];
1389 }
1390
1391 - (void)webFrame:(WebFrame *)webFrame didFinishLoadWithError:(NSError *)error
1392 {
1393     NPReason reason = NPRES_DONE;
1394     if (error != nil) {
1395         reason = [WebBaseNetscapePluginStream reasonForError:error];
1396     }    
1397     [self webFrame:webFrame didFinishLoadWithReason:reason];
1398 }
1399
1400 - (void)loadPluginRequest:(WebPluginRequest *)pluginRequest
1401 {
1402     NSURLRequest *request = [pluginRequest request];
1403     NSString *frameName = [pluginRequest frameName];
1404     WebFrame *frame = nil;
1405     
1406     NSURL *URL = [request URL];
1407     NSString *JSString = [URL _web_scriptIfJavaScriptURL];
1408     
1409     ASSERT(frameName || JSString);
1410     
1411     if (frameName) {
1412         // FIXME - need to get rid of this window creation which
1413         // bypasses normal targeted link handling
1414         frame = [[self webFrame] findFrameNamed:frameName];
1415     
1416         if (frame == nil) {
1417             WebView *newWebView = nil;
1418             WebView *currentWebView = [self webView];
1419             id wd = [currentWebView UIDelegate];
1420             if ([wd respondsToSelector:@selector(webView:createWebViewWithRequest:)]) {
1421                 newWebView = [wd webView:currentWebView createWebViewWithRequest:nil];
1422             } else {
1423                 newWebView = [[WebDefaultUIDelegate sharedUIDelegate] webView:currentWebView createWebViewWithRequest:nil];
1424             }
1425             
1426             [newWebView _setTopLevelFrameName:frameName];
1427             [[newWebView _UIDelegateForwarder] webViewShow:newWebView];
1428             frame = [newWebView mainFrame];
1429         }
1430     }
1431
1432     if (JSString) {
1433         ASSERT(frame == nil || [self webFrame] == frame);
1434         [self evaluateJavaScriptPluginRequest:pluginRequest];
1435     } else {
1436         [frame loadRequest:request];
1437         if ([pluginRequest sendNotification]) {
1438             // Check if another plug-in view or even this view is waiting for the frame to load.
1439             // If it is, tell it that the load was cancelled because it will be anyway.
1440             WebBaseNetscapePluginView *view = [frame _internalLoadDelegate];
1441             if (view != nil) {
1442                 ASSERT([view isKindOfClass:[WebBaseNetscapePluginView class]]);
1443                 [view webFrame:frame didFinishLoadWithReason:NPRES_USER_BREAK];
1444             }
1445             [pendingFrameLoads _web_setObject:pluginRequest forUncopiedKey:frame];
1446             [frame _setInternalLoadDelegate:self];
1447         }
1448     }
1449 }
1450
1451 - (NPError)loadRequest:(NSMutableURLRequest *)request inTarget:(const char *)cTarget withNotifyData:(void *)notifyData sendNotification:(BOOL)sendNotification
1452 {
1453     NSURL *URL = [request URL];
1454
1455     if (!URL) {
1456         return NPERR_INVALID_URL;
1457     }
1458     
1459     NSString *JSString = [URL _web_scriptIfJavaScriptURL];
1460     if (JSString != nil) {
1461         if (![[[self webView] preferences] isJavaScriptEnabled]) {
1462             // Return NPERR_GENERIC_ERROR if JS is disabled. This is what Mozilla does.
1463             return NPERR_GENERIC_ERROR;
1464         } else if (cTarget == NULL && mode == NP_FULL) {
1465             // Don't allow a JavaScript request from a standalone plug-in that is self-targetted
1466             // because this can cause the user to be redirected to a blank page (3424039).
1467             return NPERR_INVALID_PARAM;
1468         }
1469     }
1470         
1471     if (cTarget || JSString) {
1472         // Make when targetting a frame or evaluating a JS string, perform the request after a delay because we don't
1473         // want to potentially kill the plug-in inside of its URL request.
1474         NSString *target = nil;
1475         if (cTarget) {
1476             // Find the frame given the target string.
1477             target = (NSString *)CFStringCreateWithCString(kCFAllocatorDefault, cTarget, kCFStringEncodingWindowsLatin1);
1478         }
1479         
1480         WebFrame *frame = [self webFrame];
1481         if (JSString != nil && target != nil && [frame findFrameNamed:target] != frame) {
1482             // For security reasons, only allow JS requests to be made on the frame that contains the plug-in.
1483             CFRelease(target);
1484             return NPERR_INVALID_PARAM;
1485         }
1486         
1487         WebPluginRequest *pluginRequest = [[WebPluginRequest alloc] initWithRequest:request frameName:target notifyData:notifyData sendNotification:sendNotification];
1488         [self performSelector:@selector(loadPluginRequest:) withObject:pluginRequest afterDelay:0];
1489         [pluginRequest release];
1490         if (target) {
1491             CFRelease(target);
1492         }
1493     } else {
1494         WebNetscapePluginStream *stream = [[WebNetscapePluginStream alloc] initWithRequest:request 
1495                                                                              pluginPointer:instance 
1496                                                                                 notifyData:notifyData 
1497                                                                           sendNotification:sendNotification];
1498         if (!stream) {
1499             return NPERR_INVALID_URL;
1500         }
1501         [streams addObject:stream];
1502         [stream start];
1503         [stream release];
1504     }
1505     
1506     return NPERR_NO_ERROR;
1507 }
1508
1509 -(NPError)getURLNotify:(const char *)URLCString target:(const char *)cTarget notifyData:(void *)notifyData
1510 {
1511     LOG(Plugins, "NPN_GetURLNotify: %s target: %s", URLCString, cTarget);
1512
1513     NSMutableURLRequest *request = [self requestWithURLCString:URLCString];
1514     return [self loadRequest:request inTarget:cTarget withNotifyData:notifyData sendNotification:YES];
1515 }
1516
1517 -(NPError)getURL:(const char *)URLCString target:(const char *)cTarget
1518 {
1519     LOG(Plugins, "NPN_GetURL: %s target: %s", URLCString, cTarget);
1520
1521     NSMutableURLRequest *request = [self requestWithURLCString:URLCString];
1522     return [self loadRequest:request inTarget:cTarget withNotifyData:NULL sendNotification:NO];
1523 }
1524
1525 - (NPError)_postURL:(const char *)URLCString
1526              target:(const char *)target
1527                 len:(UInt32)len
1528                 buf:(const char *)buf
1529                file:(NPBool)file
1530          notifyData:(void *)notifyData
1531    sendNotification:(BOOL)sendNotification
1532        allowHeaders:(BOOL)allowHeaders
1533 {
1534     if (!URLCString || !len || !buf) {
1535         return NPERR_INVALID_PARAM;
1536     }
1537     
1538     NSData *postData = nil;
1539
1540     if (file) {
1541         // If we're posting a file, buf is either a file URL or a path to the file.
1542         NSString *bufString = (NSString *)CFStringCreateWithCString(kCFAllocatorDefault, buf, kCFStringEncodingWindowsLatin1);
1543         if (!bufString) {
1544             return NPERR_INVALID_PARAM;
1545         }
1546         NSURL *fileURL = [NSURL _web_URLWithDataAsString:bufString];
1547         NSString *path;
1548         if ([fileURL isFileURL]) {
1549             path = [fileURL path];
1550         } else {
1551             path = bufString;
1552         }
1553         postData = [NSData dataWithContentsOfFile:[path _web_fixedCarbonPOSIXPath]];
1554         CFRelease(bufString);
1555         if (!postData) {
1556             return NPERR_FILE_NOT_FOUND;
1557         }
1558     } else {
1559         postData = [NSData dataWithBytes:buf length:len];
1560     }
1561
1562     if ([postData length] == 0) {
1563         return NPERR_INVALID_PARAM;
1564     }
1565
1566     NSMutableURLRequest *request = [self requestWithURLCString:URLCString];
1567     [request setHTTPMethod:@"POST"];
1568     
1569     if (allowHeaders) {
1570         if ([postData _web_startsWithBlankLine]) {
1571             postData = [postData subdataWithRange:NSMakeRange(1, [postData length] - 1)];
1572         } else {
1573             unsigned location = [postData _web_locationAfterFirstBlankLine];
1574             if (location != NSNotFound) {
1575                 // If the blank line is somewhere in the middle of postData, everything before is the header.
1576                 NSData *headerData = [postData subdataWithRange:NSMakeRange(0, location)];
1577                 NSMutableDictionary *header = [headerData _web_parseRFC822HeaderFields];
1578                 unsigned dataLength = [postData length] - location;
1579
1580                 // Sometimes plugins like to set Content-Length themselves when they post,
1581                 // but WebFoundation does not like that. So we will remove the header
1582                 // and instead truncate the data to the requested length.
1583                 NSString *contentLength = [header objectForKey:@"Content-Length"];
1584
1585                 if (contentLength != nil) {
1586                     dataLength = MIN((unsigned)[contentLength intValue], dataLength);
1587                 }
1588                 [header removeObjectForKey:@"Content-Length"];
1589
1590                 if ([header count] > 0) {
1591                     [request setAllHTTPHeaderFields:header];
1592                 }
1593                 // Everything after the blank line is the actual content of the POST.
1594                 postData = [postData subdataWithRange:NSMakeRange(location, dataLength)];
1595
1596             }
1597         }
1598         if ([postData length] == 0) {
1599             return NPERR_INVALID_PARAM;
1600         }
1601     }
1602
1603     // Plug-ins expect to receive uncached data when doing a POST (3347134).
1604     [request setCachePolicy:NSURLRequestReloadIgnoringCacheData];
1605     [request setHTTPBody:postData];
1606     
1607     return [self loadRequest:request inTarget:target withNotifyData:notifyData sendNotification:sendNotification];
1608 }
1609
1610 - (NPError)postURLNotify:(const char *)URLCString
1611                   target:(const char *)target
1612                      len:(UInt32)len
1613                      buf:(const char *)buf
1614                     file:(NPBool)file
1615               notifyData:(void *)notifyData
1616 {
1617     LOG(Plugins, "NPN_PostURLNotify: %s", URLCString);
1618     return [self _postURL:URLCString target:target len:len buf:buf file:file notifyData:notifyData sendNotification:YES allowHeaders:YES];
1619 }
1620
1621 -(NPError)postURL:(const char *)URLCString
1622            target:(const char *)target
1623               len:(UInt32)len
1624               buf:(const char *)buf
1625              file:(NPBool)file
1626 {
1627     LOG(Plugins, "NPN_PostURL: %s", URLCString);        
1628     // As documented, only allow headers to be specified via NPP_PostURL when using a file.
1629     return [self _postURL:URLCString target:target len:len buf:buf file:file notifyData:NULL sendNotification:NO allowHeaders:file];
1630 }
1631
1632 -(NPError)newStream:(NPMIMEType)type target:(const char *)target stream:(NPStream**)stream
1633 {
1634     LOG(Plugins, "NPN_NewStream");
1635     return NPERR_GENERIC_ERROR;
1636 }
1637
1638 -(NPError)write:(NPStream*)stream len:(SInt32)len buffer:(void *)buffer
1639 {
1640     LOG(Plugins, "NPN_Write");
1641     return NPERR_GENERIC_ERROR;
1642 }
1643
1644 -(NPError)destroyStream:(NPStream*)stream reason:(NPReason)reason
1645 {
1646     LOG(Plugins, "NPN_DestroyStream");
1647     if (!stream->ndata) {
1648         return NPERR_INVALID_INSTANCE_ERROR;
1649     }
1650     [(WebBaseNetscapePluginStream *)stream->ndata cancelWithReason:reason];
1651     return NPERR_NO_ERROR;
1652 }
1653
1654 - (const char *)userAgent
1655 {
1656     return [[[self webView] userAgentForURL:baseURL] lossyCString];
1657 }
1658
1659 -(void)status:(const char *)message
1660 {    
1661     if (!message) {
1662         ERROR("NPN_Status passed a NULL status message");
1663         return;
1664     }
1665
1666     CFStringRef status = CFStringCreateWithCString(NULL, message, kCFStringEncodingWindowsLatin1);
1667     LOG(Plugins, "NPN_Status: %@", status);
1668     WebView *wv = [self webView];
1669     [[wv _UIDelegateForwarder] webView:wv setStatusText:(NSString *)status];
1670     CFRelease(status);
1671 }
1672
1673 -(void)invalidateRect:(NPRect *)invalidRect
1674 {
1675     LOG(Plugins, "NPN_InvalidateRect");
1676     [self setNeedsDisplayInRect:NSMakeRect(invalidRect->left, invalidRect->top,
1677         (float)invalidRect->right - invalidRect->left, (float)invalidRect->bottom - invalidRect->top)];
1678 }
1679
1680 -(void)invalidateRegion:(NPRegion)invalidRegion
1681 {
1682     LOG(Plugins, "NPN_InvalidateRegion");
1683     Rect invalidRect;
1684     GetRegionBounds(invalidRegion, &invalidRect);
1685     [self setNeedsDisplayInRect:NSMakeRect(invalidRect.left, invalidRect.top,
1686         (float)invalidRect.right - invalidRect.left, (float)invalidRect.bottom - invalidRect.top)];
1687 }
1688
1689 -(void)forceRedraw
1690 {
1691     LOG(Plugins, "forceRedraw");
1692     [self setNeedsDisplay:YES];
1693     [[self window] displayIfNeeded];
1694 }
1695
1696 - (NPError)getVariable:(NPNVariable)variable value:(void *)value
1697 {
1698     if (variable == NPNVWindowNPObject) {
1699         void **v = (void **)value;
1700         *v = [[[self webFrame] _bridge] windowScriptNPObject];
1701         return NPERR_NO_ERROR;
1702     }
1703     return NPERR_GENERIC_ERROR;
1704 }
1705
1706 @end
1707
1708 @implementation WebPluginRequest
1709
1710 - (id)initWithRequest:(NSURLRequest *)request frameName:(NSString *)frameName notifyData:(void *)notifyData sendNotification:(BOOL)sendNotification
1711 {
1712     [super init];
1713     _request = [request retain];
1714     _frameName = [frameName retain];
1715     _notifyData = notifyData;
1716     _sendNotification = sendNotification;
1717     return self;
1718 }
1719
1720 - (void)dealloc
1721 {
1722     [_request release];
1723     [_frameName release];
1724     [super dealloc];
1725 }
1726
1727 - (NSURLRequest *)request
1728 {
1729     return _request;
1730 }
1731
1732 - (NSString *)frameName
1733 {
1734     return _frameName;
1735 }
1736
1737 - (BOOL)sendNotification
1738 {
1739     return _sendNotification;
1740 }
1741
1742 - (void *)notifyData
1743 {
1744     return _notifyData;
1745 }
1746
1747 @end
1748
1749 @implementation NSData (PluginExtras)
1750
1751 - (BOOL)_web_startsWithBlankLine
1752 {
1753     return [self length] > 0 && ((const char *)[self bytes])[0] == '\n';
1754 }
1755
1756 // Returns the position after 2 CRLF's or 1 CRLF if it is the first line.
1757 - (unsigned)_web_locationAfterFirstBlankLine
1758 {
1759     const char *bytes = (const char *)[self bytes];
1760     unsigned length = [self length];
1761     
1762     unsigned i;
1763     for (i = 0; i < length - 4; i++) {
1764         if (bytes[i] == '\r' && bytes[i+1] == '\n') {
1765             i += 2;
1766             if (i == 2) {
1767                 return i;
1768             } else if (bytes[i] == '\n') {
1769                 // Support for Director. It sends "\r\n\n" (3880387).
1770                 return i+1;
1771             } else if (bytes[i] == '\r' && bytes[i+1] == '\n') {
1772                 // Support for Flash. It sends "\r\n\r\n" (3758113).
1773                 return i+2;
1774             }
1775         }
1776     }
1777     return NSNotFound;
1778 }
1779
1780 @end
1781
1782 void ConsoleConnectionChangeNotifyProc(CGSNotificationType type, CGSNotificationData data, CGSByteCount dataLength, CGSNotificationArg arg)
1783 {
1784     NSString *notificationName = nil;
1785     if (type == kCGSessionConsoleConnect) {
1786         notificationName = LoginWindowDidSwitchToUserNotification;
1787     } else if (type == kCGSessionConsoleDisconnect) {
1788         notificationName = LoginWindowDidSwitchFromUserNotification;
1789     } else {
1790         ASSERT_NOT_REACHED();
1791     }
1792     [[NSNotificationCenter defaultCenter] postNotificationName:notificationName object:nil];
1793 }