1/*
2 * Copyright (C) 2008 Apple Inc. All Rights Reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 *    notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 *    notice, this list of conditions and the following disclaimer in the
11 *    documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26#if USE(PLUGIN_HOST_PROCESS) && ENABLE(NETSCAPE_PLUGIN_API)
27
28#import "WebHostedNetscapePluginView.h"
29
30#import "HostedNetscapePluginStream.h"
31#import "NetscapePluginInstanceProxy.h"
32#import "NetscapePluginHostManager.h"
33#import "NetscapePluginHostProxy.h"
34#import "WebTextInputWindowController.h"
35#import "WebFrameInternal.h"
36#import "WebView.h"
37#import "WebViewInternal.h"
38#import "WebUIDelegate.h"
39
40#import <CoreFoundation/CoreFoundation.h>
41#import <WebCore/BridgeJSC.h>
42#import <WebCore/Frame.h>
43#import <WebCore/FrameLoaderTypes.h>
44#import <WebCore/FrameView.h>
45#import <WebCore/HTMLPlugInElement.h>
46#import <WebCore/RenderEmbeddedObject.h>
47#import <WebCore/WebCoreObjCExtras.h>
48#import <WebCore/runtime_root.h>
49#import <runtime/InitializeThreading.h>
50#import <wtf/Assertions.h>
51#import <wtf/Threading.h>
52
53using namespace WebCore;
54using namespace WebKit;
55
56extern "C" {
57#include "WebKitPluginClientServer.h"
58#include "WebKitPluginHost.h"
59}
60
61@implementation WebHostedNetscapePluginView
62
63+ (void)initialize
64{
65    JSC::initializeThreading();
66    WTF::initializeMainThreadToProcessMainThread();
67#ifndef BUILDING_ON_TIGER
68    WebCoreObjCFinalizeOnMainThread(self);
69#endif
70    WKSendUserChangeNotifications();
71}
72
73- (id)initWithFrame:(NSRect)frame
74      pluginPackage:(WebNetscapePluginPackage *)pluginPackage
75                URL:(NSURL *)URL
76            baseURL:(NSURL *)baseURL
77           MIMEType:(NSString *)MIME
78      attributeKeys:(NSArray *)keys
79    attributeValues:(NSArray *)values
80       loadManually:(BOOL)loadManually
81            element:(PassRefPtr<WebCore::HTMLPlugInElement>)element
82{
83    self = [super initWithFrame:frame pluginPackage:pluginPackage URL:URL baseURL:baseURL MIMEType:MIME attributeKeys:keys attributeValues:values loadManually:loadManually element:element];
84    if (!self)
85        return nil;
86
87    return self;
88}
89
90- (void)handleMouseMoved:(NSEvent *)event
91{
92    if (_isStarted && _proxy)
93        _proxy->mouseEvent(self, event, NPCocoaEventMouseMoved);
94}
95
96- (void)setAttributeKeys:(NSArray *)keys andValues:(NSArray *)values
97{
98    ASSERT(!_attributeKeys);
99    ASSERT(!_attributeValues);
100
101    _attributeKeys.adoptNS([keys copy]);
102    _attributeValues.adoptNS([values copy]);
103}
104
105- (BOOL)createPlugin
106{
107    ASSERT(!_proxy);
108
109    NSString *userAgent = [[self webView] userAgentForURL:_baseURL.get()];
110    BOOL accleratedCompositingEnabled = false;
111#if USE(ACCELERATED_COMPOSITING)
112    accleratedCompositingEnabled = [[[self webView] preferences] acceleratedCompositingEnabled];
113#endif
114
115    _proxy = NetscapePluginHostManager::shared().instantiatePlugin([_pluginPackage.get() path], [_pluginPackage.get() pluginHostArchitecture], [_pluginPackage.get() bundleIdentifier], self, _MIMEType.get(), _attributeKeys.get(), _attributeValues.get(), userAgent, _sourceURL.get(),
116                                                                   _mode == NP_FULL, _isPrivateBrowsingEnabled, accleratedCompositingEnabled);
117    if (!_proxy)
118        return NO;
119
120    if (_proxy->rendererType() == UseSoftwareRenderer)
121        _softwareRenderer = WKSoftwareCARendererCreate(_proxy->renderContextID());
122    else {
123        _pluginLayer = WKMakeRenderLayer(_proxy->renderContextID());
124
125        if (accleratedCompositingEnabled && _proxy->rendererType() == UseAcceleratedCompositing) {
126            // FIXME: This code can be shared between WebHostedNetscapePluginView and WebNetscapePluginView.
127#ifndef BUILDING_ON_LEOPARD
128            // Since this layer isn't going to be inserted into a view, we need to create another layer and flip its geometry
129            // in order to get the coordinate system right.
130            RetainPtr<CALayer> realPluginLayer(AdoptNS, _pluginLayer.releaseRef());
131
132            _pluginLayer.adoptNS([[CALayer alloc] init]);
133            _pluginLayer.get().bounds = realPluginLayer.get().bounds;
134            _pluginLayer.get().geometryFlipped = YES;
135
136            realPluginLayer.get().autoresizingMask = kCALayerWidthSizable | kCALayerHeightSizable;
137            [_pluginLayer.get() addSublayer:realPluginLayer.get()];
138#endif
139
140            // Eagerly enter compositing mode, since we know we'll need it. This avoids firing setNeedsStyleRecalc()
141            // for iframes that contain composited plugins at bad times. https://bugs.webkit.org/show_bug.cgi?id=39033
142            core([self webFrame])->view()->enterCompositingMode();
143            [self element]->setNeedsStyleRecalc(SyntheticStyleChange);
144        } else
145            self.wantsLayer = YES;
146    }
147
148    // Update the window frame.
149    _proxy->windowFrameChanged([[self window] frame]);
150
151    return YES;
152}
153
154// FIXME: This method is an ideal candidate to move up to the base class
155- (CALayer *)pluginLayer
156{
157    return _pluginLayer.get();
158}
159
160- (void)setLayer:(CALayer *)newLayer
161{
162    // FIXME: This should use the same implementation as WebNetscapePluginView (and move to the base class).
163    [super setLayer:newLayer];
164
165    if (_pluginLayer)
166        [newLayer addSublayer:_pluginLayer.get()];
167}
168
169- (void)privateBrowsingModeDidChange
170{
171    if (_proxy)
172        _proxy->privateBrowsingModeDidChange(_isPrivateBrowsingEnabled);
173}
174
175- (void)loadStream
176{
177}
178
179- (void)updateAndSetWindow
180{
181    if (!_proxy)
182        return;
183
184    // The base coordinates of a window and it's contentView happen to be the equal at a userSpaceScaleFactor
185    // of 1. For non-1.0 scale factors this assumption is false.
186    NSView *windowContentView = [[self window] contentView];
187    NSRect boundsInWindow = [self convertRect:[self bounds] toView:windowContentView];
188
189    NSRect visibleRectInWindow;
190
191    // Core Animation plug-ins need to be updated (with a 0,0,0,0 clipRect) when
192    // moved to a background tab. We don't do this for Core Graphics plug-ins as
193    // older versions of Flash have historical WebKit-specific code that isn't
194    // compatible with this behavior.
195    BOOL shouldClipOutPlugin = _pluginLayer && [self shouldClipOutPlugin];
196    if (!shouldClipOutPlugin)
197        visibleRectInWindow = [self actualVisibleRectInWindow];
198    else
199        visibleRectInWindow = NSZeroRect;
200
201    // Flip Y to convert NSWindow coordinates to top-left-based window coordinates.
202    float borderViewHeight = [[self currentWindow] frame].size.height;
203    boundsInWindow.origin.y = borderViewHeight - NSMaxY(boundsInWindow);
204
205    if (!shouldClipOutPlugin)
206        visibleRectInWindow.origin.y = borderViewHeight - NSMaxY(visibleRectInWindow);
207
208    _previousSize = boundsInWindow.size;
209
210    _proxy->resize(boundsInWindow, visibleRectInWindow);
211
212    CGRect bounds = NSRectToCGRect([self bounds]);
213    CGRect frame = NSRectToCGRect([self frame]);
214
215    // We're not scaled, or in a subframe
216    CATransform3D scaleTransform = CATransform3DIdentity;
217    if (CGSizeEqualToSize(bounds.size, frame.size)) {
218        // We're in a subframe. Backing store is boundsInWindow.size.
219        if (boundsInWindow.size.width && boundsInWindow.size.height)
220            scaleTransform = CATransform3DMakeScale(frame.size.width / boundsInWindow.size.width, frame.size.height / boundsInWindow.size.height, 1);
221    } else {
222        // We're in the main frame with scaling. Need to mimic the frame/bounds scaling on Widgets.
223        if (frame.size.width && frame.size.height)
224            scaleTransform = CATransform3DMakeScale(bounds.size.width / frame.size.width, bounds.size.height / frame.size.height, 1);
225    }
226
227    _pluginLayer.get().sublayerTransform = scaleTransform;
228}
229
230- (void)windowFocusChanged:(BOOL)hasFocus
231{
232    if (_proxy)
233        _proxy->windowFocusChanged(hasFocus);
234}
235
236- (BOOL)shouldStop
237{
238    if (!_proxy)
239        return YES;
240
241    return _proxy->shouldStop();
242}
243
244- (void)destroyPlugin
245{
246    if (_proxy) {
247        if (_softwareRenderer) {
248            WKSoftwareCARendererDestroy(_softwareRenderer);
249            _softwareRenderer = 0;
250        }
251
252        _proxy->destroy();
253        _proxy = 0;
254    }
255
256    _pluginLayer = 0;
257}
258
259- (void)startTimers
260{
261    if (_proxy)
262        _proxy->startTimers(_isCompletelyObscured);
263}
264
265- (void)stopTimers
266{
267    if (_proxy)
268        _proxy->stopTimers();
269}
270
271- (void)focusChanged
272{
273    if (_proxy)
274        _proxy->focusChanged(_hasFocus);
275}
276
277- (void)windowFrameDidChange:(NSNotification *)notification
278{
279    if (_proxy && [self window])
280        _proxy->windowFrameChanged([[self window] frame]);
281}
282
283- (void)addWindowObservers
284{
285    [super addWindowObservers];
286
287    ASSERT([self window]);
288
289    NSWindow *window = [self window];
290
291    NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
292    [notificationCenter addObserver:self selector:@selector(windowFrameDidChange:)
293                               name:NSWindowDidMoveNotification object:window];
294    [notificationCenter addObserver:self selector:@selector(windowFrameDidChange:)
295                               name:NSWindowDidResizeNotification object:window];
296
297    if (_proxy)
298        _proxy->windowFrameChanged([window frame]);
299    [self updateAndSetWindow];
300}
301
302- (void)removeWindowObservers
303{
304    [super removeWindowObservers];
305
306    NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
307    [notificationCenter removeObserver:self name:NSWindowDidMoveNotification object:nil];
308    [notificationCenter removeObserver:self name:NSWindowDidResizeNotification object:nil];
309}
310
311- (void)mouseDown:(NSEvent *)event
312{
313    if (_isStarted && _proxy)
314        _proxy->mouseEvent(self, event, NPCocoaEventMouseDown);
315}
316
317- (void)mouseUp:(NSEvent *)event
318{
319    if (_isStarted && _proxy)
320        _proxy->mouseEvent(self, event, NPCocoaEventMouseUp);
321}
322
323- (void)mouseDragged:(NSEvent *)event
324{
325    if (_isStarted && _proxy)
326        _proxy->mouseEvent(self, event, NPCocoaEventMouseDragged);
327}
328
329- (void)handleMouseEntered:(NSEvent *)event
330{
331    // Set cursor to arrow. Plugins often handle cursor internally, but those that don't will just get this default one.
332    [[NSCursor arrowCursor] set];
333
334    if (_isStarted && _proxy)
335        _proxy->mouseEvent(self, event, NPCocoaEventMouseEntered);
336}
337
338- (void)handleMouseExited:(NSEvent *)event
339{
340    if (_isStarted && _proxy)
341        _proxy->mouseEvent(self, event, NPCocoaEventMouseExited);
342
343    // Set cursor back to arrow cursor.  Because NSCursor doesn't know about changes that the plugin made, we could get confused about what we think the
344    // current cursor is otherwise.  Therefore we have no choice but to unconditionally reset the cursor when the mouse exits the plugin.
345    // FIXME: This should be job of plugin host, see <rdar://problem/7654434>.
346    [[NSCursor arrowCursor] set];
347}
348
349- (void)scrollWheel:(NSEvent *)event
350{
351    bool processedEvent = false;
352
353    if (_isStarted && _proxy)
354        processedEvent = _proxy->wheelEvent(self, event);
355
356    if (!processedEvent)
357        [super scrollWheel:event];
358}
359
360- (NSTextInputContext *)inputContext
361{
362    return [[WebTextInputWindowController sharedTextInputWindowController] inputContext];
363}
364
365- (void)keyDown:(NSEvent *)event
366{
367    if (!_isStarted || !_proxy)
368        return;
369
370    NSString *string = nil;
371    if ([[WebTextInputWindowController sharedTextInputWindowController] interpretKeyEvent:event string:&string]) {
372        if (string)
373            _proxy->insertText(string);
374        return;
375    }
376
377    _proxy->keyEvent(self, event, NPCocoaEventKeyDown);
378}
379
380- (void)keyUp:(NSEvent *)event
381{
382    if (_isStarted && _proxy)
383        _proxy->keyEvent(self, event, NPCocoaEventKeyUp);
384}
385
386- (void)flagsChanged:(NSEvent *)event
387{
388    if (_isStarted && _proxy)
389        _proxy->flagsChanged(event);
390}
391
392- (void)sendModifierEventWithKeyCode:(int)keyCode character:(char)character
393{
394    if (_isStarted && _proxy)
395        _proxy->syntheticKeyDownWithCommandModifier(keyCode, character);
396}
397
398- (void)pluginHostDied
399{
400    if (_element->renderer() && _element->renderer()->isEmbeddedObject()) {
401        // FIXME: The renderer could also be a RenderApplet, we should handle that.
402        RenderEmbeddedObject* renderer = toRenderEmbeddedObject(_element->renderer());
403        renderer->setShowsCrashedPluginIndicator();
404    }
405
406    _pluginLayer = nil;
407    _proxy = 0;
408
409    // No need for us to be layer backed anymore
410    self.wantsLayer = NO;
411
412    [self invalidatePluginContentRect:[self bounds]];
413}
414
415- (void)visibleRectDidChange
416{
417    [super visibleRectDidChange];
418    WKSyncSurfaceToView(self);
419}
420
421- (void)drawRect:(NSRect)rect
422{
423    if (_cachedSnapshot) {
424        NSRect sourceRect = { NSZeroPoint, [_cachedSnapshot.get() size] };
425        [_cachedSnapshot.get() drawInRect:[self bounds] fromRect:sourceRect operation:NSCompositeSourceOver fraction:1];
426        return;
427    }
428
429    if (_proxy) {
430        if (_softwareRenderer) {
431            if ([NSGraphicsContext currentContextDrawingToScreen]) {
432                WKSoftwareCARendererRender(_softwareRenderer, (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort], NSRectToCGRect(rect));
433                _proxy->didDraw();
434            } else
435                _proxy->print(reinterpret_cast<CGContextRef>([[NSGraphicsContext currentContext] graphicsPort]), [self bounds].size.width, [self bounds].size.height);
436        } else if (_snapshotting && [self supportsSnapshotting]) {
437            _proxy->snapshot(reinterpret_cast<CGContextRef>([[NSGraphicsContext currentContext] graphicsPort]), [self bounds].size.width, [self bounds].size.height);
438        }
439
440        return;
441    }
442}
443
444- (PassRefPtr<JSC::Bindings::Instance>)createPluginBindingsInstance:(PassRefPtr<JSC::Bindings::RootObject>)rootObject
445{
446    if (!_proxy)
447        return 0;
448
449    return _proxy->createBindingsInstance(rootObject);
450}
451
452- (void)pluginView:(NSView *)pluginView receivedResponse:(NSURLResponse *)response
453{
454    ASSERT(_loadManually);
455    if (!_proxy)
456        return;
457
458    ASSERT(!_proxy->manualStream());
459
460    _proxy->setManualStream(HostedNetscapePluginStream::create(_proxy.get(), core([self webFrame])->loader()));
461    _proxy->manualStream()->startStreamWithResponse(response);
462}
463
464- (void)pluginView:(NSView *)pluginView receivedData:(NSData *)data
465{
466    ASSERT(_loadManually);
467    if (!_proxy)
468        return;
469
470    if (HostedNetscapePluginStream* manualStream = _proxy->manualStream())
471        manualStream->didReceiveData(0, static_cast<const char*>([data bytes]), [data length]);
472}
473
474- (void)pluginView:(NSView *)pluginView receivedError:(NSError *)error
475{
476    ASSERT(_loadManually);
477    if (!_proxy)
478        return;
479
480    if (HostedNetscapePluginStream* manualStream = _proxy->manualStream())
481        manualStream->didFail(0, error);
482}
483
484- (void)pluginViewFinishedLoading:(NSView *)pluginView
485{
486    ASSERT(_loadManually);
487    if (!_proxy)
488        return;
489
490    if (HostedNetscapePluginStream* manualStream = _proxy->manualStream())
491        manualStream->didFinishLoading(0);
492}
493
494- (void)_webPluginContainerCancelCheckIfAllowedToLoadRequest:(id)webPluginContainerCheck
495{
496    ASSERT([webPluginContainerCheck isKindOfClass:[WebPluginContainerCheck class]]);
497
498    id contextInfo = [webPluginContainerCheck contextInfo];
499    ASSERT([contextInfo isKindOfClass:[NSNumber class]]);
500
501    if (!_proxy)
502        return;
503
504    uint32_t checkID = [(NSNumber *)contextInfo unsignedIntValue];
505    _proxy->cancelCheckIfAllowedToLoadURL(checkID);
506}
507
508- (void)_containerCheckResult:(PolicyAction)policy contextInfo:(id)contextInfo
509{
510    ASSERT([contextInfo isKindOfClass:[NSNumber class]]);
511    if (!_proxy)
512        return;
513
514    uint32_t checkID = [(NSNumber *)contextInfo unsignedIntValue];
515    _proxy->checkIfAllowedToLoadURLResult(checkID, (policy == PolicyUse));
516}
517
518- (void)webFrame:(WebFrame *)webFrame didFinishLoadWithReason:(NPReason)reason
519{
520    if (_isStarted && _proxy)
521        _proxy->webFrameDidFinishLoadWithReason(webFrame, reason);
522}
523
524- (void)webFrame:(WebFrame *)webFrame didFinishLoadWithError:(NSError *)error
525{
526    NPReason reason = NPRES_DONE;
527    if (error)
528        reason = HostedNetscapePluginStream::reasonForError(error);
529    [self webFrame:webFrame didFinishLoadWithReason:reason];
530}
531
532@end
533
534#endif // USE(PLUGIN_HOST_PROCESS) && ENABLE(NETSCAPE_PLUGIN_API)
535