1/*
2 * Copyright (C) 2006, 2007 Apple Inc. All rights reserved.
3 * Copyright (C) 2008 Zan Dobersek <zandobersek@gmail.com>
4 * Copyright (C) 2009 Holger Hans Peter Freyther
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 * 1. Redistributions of source code must retain the above copyright
10 *    notice, this list of conditions and the following disclaimer.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 *    notice, this list of conditions and the following disclaimer in the
13 *    documentation and/or other materials provided with the distribution.
14 *
15 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
16 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
18 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
19 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
20 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
21 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
22 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
23 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
25 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 */
27
28#include "config.h"
29#include "PluginObject.h"
30#include "PluginTest.h"
31
32#include "npapi.h"
33#include "npruntime.h"
34#include "npfunctions.h"
35
36#include <stdarg.h>
37#include <stdio.h>
38#include <string.h>
39#include <stdlib.h>
40#include <X11/Xlib.h>
41#include <X11/Xutil.h>
42#include <string>
43
44using namespace std;
45
46extern "C" {
47    NPError NP_Initialize (NPNetscapeFuncs *aMozillaVTable, NPPluginFuncs *aPluginVTable);
48    NPError NP_Shutdown(void);
49    NPError NP_GetValue(void *future, NPPVariable variable, void *value);
50    char* NP_GetMIMEDescription(void);
51}
52
53static void executeScript(const PluginObject* obj, const char* script);
54
55static NPError
56webkit_test_plugin_new_instance(NPMIMEType mimetype,
57                                NPP instance,
58                                uint16_t mode,
59                                int16_t argc,
60                                char *argn[],
61                                char *argv[],
62                                NPSavedData* savedData)
63{
64    if (browser->version >= 14) {
65        PluginObject* obj = (PluginObject*)browser->createobject(instance, getPluginClass());
66        instance->pdata = obj;
67
68        string testIdentifier;
69
70        for (int i = 0; i < argc; i++) {
71            if (strcasecmp(argn[i], "test") == 0)
72                testIdentifier = argv[i];
73            else if (strcasecmp(argn[i], "onstreamload") == 0 && !obj->onStreamLoad)
74                obj->onStreamLoad = strdup(argv[i]);
75            else if (strcasecmp(argn[i], "onStreamDestroy") == 0 && !obj->onStreamDestroy)
76                obj->onStreamDestroy = strdup(argv[i]);
77            else if (strcasecmp(argn[i], "onURLNotify") == 0 && !obj->onURLNotify)
78                obj->onURLNotify = strdup(argv[i]);
79            else if (strcasecmp(argn[i], "src") == 0 &&
80                     strcasecmp(argv[i], "data:application/x-webkit-test-netscape,returnerrorfromnewstream") == 0)
81                obj->returnErrorFromNewStream = TRUE;
82            else if (!strcasecmp(argn[i], "src")
83                     && !strcasecmp(argv[i], "data:application/x-webkit-test-netscape,alertwhenloaded"))
84                executeScript(obj, "alert('Plugin Loaded!')");
85            else if (strcasecmp(argn[i], "logfirstsetwindow") == 0)
86                obj->logSetWindow = TRUE;
87            else if (strcasecmp(argn[i], "testnpruntime") == 0)
88                testNPRuntime(instance);
89            else if (strcasecmp(argn[i], "logSrc") == 0) {
90                for (int i = 0; i < argc; i++)
91                    if (strcasecmp(argn[i], "src") == 0)
92                        pluginLog(instance, "src: %s", argv[i]);
93            } else if (strcasecmp(argn[i], "cleardocumentduringnew") == 0)
94                executeScript(obj, "document.body.innerHTML = ''");
95            else if (!strcasecmp(argn[i], "ondestroy"))
96                obj->onDestroy = strdup(argv[i]);
97            else if (strcasecmp(argn[i], "testwindowopen") == 0)
98                obj->testWindowOpen = TRUE;
99            else if (strcasecmp(argn[i], "onSetWindow") == 0 && !obj->onSetWindow)
100                obj->onSetWindow = strdup(argv[i]);
101        }
102
103        browser->getvalue(instance, NPNVprivateModeBool, (void *)&obj->cachedPrivateBrowsingMode);
104
105        obj->pluginTest = PluginTest::create(instance, testIdentifier);
106
107        return obj->pluginTest->NPP_New(mimetype, mode, argc, argn, argv, savedData);
108    }
109
110    return NPERR_NO_ERROR;
111}
112
113static NPError
114webkit_test_plugin_destroy_instance(NPP instance, NPSavedData** save)
115{
116    PluginObject* obj = static_cast<PluginObject*>(instance->pdata);
117    if (obj) {
118        if (obj->onDestroy) {
119            executeScript(obj, obj->onDestroy);
120            free(obj->onDestroy);
121        }
122
123        if (obj->onStreamLoad)
124            free(obj->onStreamLoad);
125
126        if (obj->onStreamDestroy)
127            free(obj->onStreamDestroy);
128
129        if (obj->onURLNotify)
130            free(obj->onURLNotify);
131
132        if (obj->logDestroy)
133            pluginLog(instance, "NPP_Destroy");
134
135        if (obj->onSetWindow)
136            free(obj->onSetWindow);
137
138        obj->pluginTest->NPP_Destroy(save);
139
140        browser->releaseobject(&obj->header);
141    }
142
143    return NPERR_NO_ERROR;
144}
145
146static NPError
147webkit_test_plugin_set_window(NPP instance, NPWindow *window)
148{
149    PluginObject* obj = static_cast<PluginObject*>(instance->pdata);
150
151    if (obj) {
152        obj->lastWindow = *window;
153
154        if (obj->logSetWindow) {
155            pluginLog(instance, "NPP_SetWindow: %d %d", (int)window->width, (int)window->height);
156            obj->logSetWindow = false;
157        }
158        if (obj->onSetWindow)
159            executeScript(obj, obj->onSetWindow);
160
161        if (obj->testWindowOpen) {
162            testWindowOpen(instance);
163            obj->testWindowOpen = FALSE;
164        }
165
166    }
167
168    return obj->pluginTest->NPP_SetWindow(instance, window);
169}
170
171static void executeScript(const PluginObject* obj, const char* script)
172{
173    NPObject *windowScriptObject;
174    browser->getvalue(obj->npp, NPNVWindowNPObject, &windowScriptObject);
175
176    NPString npScript;
177    npScript.UTF8Characters = script;
178    npScript.UTF8Length = strlen(script);
179
180    NPVariant browserResult;
181    browser->evaluate(obj->npp, windowScriptObject, &npScript, &browserResult);
182    browser->releasevariantvalue(&browserResult);
183}
184
185static NPError
186webkit_test_plugin_new_stream(NPP instance,
187                              NPMIMEType /*type*/,
188                              NPStream *stream,
189                              NPBool /*seekable*/,
190                              uint16_t* stype)
191{
192    PluginObject* obj = static_cast<PluginObject*>(instance->pdata);
193    obj->stream = stream;
194    *stype = NP_NORMAL;
195
196    if (obj->returnErrorFromNewStream)
197        return NPERR_GENERIC_ERROR;
198
199    if (browser->version >= NPVERS_HAS_RESPONSE_HEADERS)
200        notifyStream(obj, stream->url, stream->headers);
201
202    if (obj->onStreamLoad)
203        executeScript(obj, obj->onStreamLoad);
204
205    return NPERR_NO_ERROR;
206}
207
208static NPError
209webkit_test_plugin_destroy_stream(NPP instance, NPStream* stream, NPError reason)
210{
211    PluginObject* obj = (PluginObject*)instance->pdata;
212
213    if (obj->onStreamDestroy) {
214        NPObject* windowObject = 0;
215        NPError error = browser->getvalue(instance, NPNVWindowNPObject, &windowObject);
216
217        if (error == NPERR_NO_ERROR) {
218            NPVariant onStreamDestroyVariant;
219            if (browser->getproperty(instance, windowObject, browser->getstringidentifier(obj->onStreamDestroy), &onStreamDestroyVariant)) {
220                if (NPVARIANT_IS_OBJECT(onStreamDestroyVariant)) {
221                    NPObject* onStreamDestroyFunction = NPVARIANT_TO_OBJECT(onStreamDestroyVariant);
222
223                    NPVariant reasonVariant;
224                    INT32_TO_NPVARIANT(reason, reasonVariant);
225
226                    NPVariant result;
227                    browser->invokeDefault(instance, onStreamDestroyFunction, &reasonVariant, 1, &result);
228                    browser->releasevariantvalue(&result);
229                }
230                browser->releasevariantvalue(&onStreamDestroyVariant);
231            }
232            browser->releaseobject(windowObject);
233        }
234    }
235
236    return obj->pluginTest->NPP_DestroyStream(stream, reason);
237}
238
239static void
240webkit_test_plugin_stream_as_file(NPP /*instance*/, NPStream* /*stream*/, const char* /*fname*/)
241{
242}
243
244static int32_t
245webkit_test_plugin_write_ready(NPP /*instance*/, NPStream* /*stream*/)
246{
247    return 4096;
248}
249
250static int32_t
251webkit_test_plugin_write(NPP instance,
252                         NPStream* /*stream*/,
253                         int32_t /*offset*/,
254                         int32_t len,
255                         void* /*buffer*/)
256{
257    PluginObject* obj = (PluginObject*)instance->pdata;
258
259    if (obj->returnNegativeOneFromWrite)
260        return -1;
261
262    return len;
263}
264
265static void
266webkit_test_plugin_print(NPP /*instance*/, NPPrint* /*platformPrint*/)
267{
268}
269
270static char keyEventToChar(XKeyEvent* event)
271{
272    char c = ' ';
273    XLookupString(event, &c, sizeof(c), 0, 0);
274    return c;
275}
276
277static int16_t
278webkit_test_plugin_handle_event(NPP instance, void* event)
279{
280    PluginObject* obj = static_cast<PluginObject*>(instance->pdata);
281    if (!obj->eventLogging)
282        return 0;
283
284    XEvent* evt = static_cast<XEvent*>(event);
285
286    switch (evt->type) {
287        case ButtonRelease:
288            pluginLog(instance, "mouseUp at (%d, %d)", evt->xbutton.x, evt->xbutton.y);
289            break;
290        case ButtonPress:
291            pluginLog(instance, "mouseDown at (%d, %d)", evt->xbutton.x, evt->xbutton.y);
292            break;
293        case KeyRelease:
294            pluginLog(instance, "keyUp '%c'", keyEventToChar(&evt->xkey));
295            break;
296        case KeyPress:
297            pluginLog(instance, "keyDown '%c'", keyEventToChar(&evt->xkey));
298            break;
299        case MotionNotify:
300        case EnterNotify:
301        case LeaveNotify:
302            break;
303        case FocusIn:
304            pluginLog(instance, "getFocusEvent");
305            break;
306        case FocusOut:
307            pluginLog(instance, "loseFocusEvent");
308            break;
309        default:
310            pluginLog(instance, "event %d", evt->type);
311    }
312
313    return 0;
314}
315
316static void
317webkit_test_plugin_url_notify(NPP instance, const char* url, NPReason reason, void* notifyData)
318{
319    PluginObject* obj = static_cast<PluginObject*>(instance->pdata);
320
321    if (obj->onURLNotify)
322        executeScript(obj, obj->onURLNotify);
323
324    handleCallback(obj, url, reason, notifyData);
325}
326
327static NPError
328webkit_test_plugin_get_value(NPP instance, NPPVariable variable, void *value)
329{
330    PluginObject* obj = 0;
331    if (instance)
332        obj = static_cast<PluginObject*>(instance->pdata);
333
334    // First, check if the PluginTest object supports getting this value.
335    if (obj && obj->pluginTest->NPP_GetValue(variable, value) == NPERR_NO_ERROR)
336        return NPERR_NO_ERROR;
337
338    NPError err = NPERR_NO_ERROR;
339
340    switch (variable) {
341        case NPPVpluginNameString:
342            *((char **)value) = const_cast<char*>("WebKit Test PlugIn");
343            break;
344        case NPPVpluginDescriptionString:
345            *((char **)value) = const_cast<char*>("Simple Netscape plug-in that handles test content for WebKit");
346            break;
347        case NPPVpluginNeedsXEmbed:
348            *((NPBool *)value) = TRUE;
349            break;
350        case NPPVpluginScriptableIID:
351        case NPPVpluginScriptableInstance:
352        case NPPVpluginScriptableNPObject:
353            err = NPERR_GENERIC_ERROR;
354            break;
355        default:
356            err = NPERR_GENERIC_ERROR;
357            break;
358    }
359
360    if (variable == NPPVpluginScriptableNPObject) {
361        void **v = (void **)value;
362        browser->retainobject((NPObject *)obj);
363        *v = obj;
364        err = NPERR_NO_ERROR;
365    }
366
367    return err;
368}
369
370static NPError
371webkit_test_plugin_set_value(NPP instance, NPNVariable variable, void* value)
372{
373    PluginObject* obj = static_cast<PluginObject*>(instance->pdata);
374
375    switch (variable) {
376        case NPNVprivateModeBool:
377            obj->cachedPrivateBrowsingMode = *(NPBool*)value;
378            return NPERR_NO_ERROR;
379        default:
380            return NPERR_GENERIC_ERROR;
381    }
382}
383
384char *
385NP_GetMIMEDescription(void)
386{
387    // We sentence-case the mime-type here to ensure that ports are not
388    // case-sensitive when loading plugins. See https://webkit.org/b/36815
389    return const_cast<char*>("application/x-Webkit-Test-Netscape:testnetscape:test netscape content");
390}
391
392NPError
393NP_Initialize (NPNetscapeFuncs *aMozillaVTable, NPPluginFuncs *aPluginVTable)
394{
395    if (aMozillaVTable == NULL || aPluginVTable == NULL)
396        return NPERR_INVALID_FUNCTABLE_ERROR;
397
398    if ((aMozillaVTable->version >> 8) > NP_VERSION_MAJOR)
399        return NPERR_INCOMPATIBLE_VERSION_ERROR;
400
401    if (aPluginVTable->size < sizeof (NPPluginFuncs))
402        return NPERR_INVALID_FUNCTABLE_ERROR;
403
404    browser = aMozillaVTable;
405    pluginFunctions = aPluginVTable;
406
407        aPluginVTable->size           = sizeof (NPPluginFuncs);
408        aPluginVTable->version        = (NP_VERSION_MAJOR << 8) + NP_VERSION_MINOR;
409        aPluginVTable->newp           = webkit_test_plugin_new_instance;
410        aPluginVTable->destroy        = webkit_test_plugin_destroy_instance;
411        aPluginVTable->setwindow      = webkit_test_plugin_set_window;
412        aPluginVTable->newstream      = webkit_test_plugin_new_stream;
413        aPluginVTable->destroystream  = webkit_test_plugin_destroy_stream;
414        aPluginVTable->asfile         = webkit_test_plugin_stream_as_file;
415        aPluginVTable->writeready     = webkit_test_plugin_write_ready;
416        aPluginVTable->write          = webkit_test_plugin_write;
417        aPluginVTable->print          = webkit_test_plugin_print;
418        aPluginVTable->event          = webkit_test_plugin_handle_event;
419        aPluginVTable->urlnotify      = webkit_test_plugin_url_notify;
420        aPluginVTable->javaClass      = NULL;
421        aPluginVTable->getvalue       = webkit_test_plugin_get_value;
422        aPluginVTable->setvalue       = webkit_test_plugin_set_value;
423
424    return NPERR_NO_ERROR;
425}
426
427NPError
428NP_Shutdown(void)
429{
430    return NPERR_NO_ERROR;
431}
432
433NPError
434NP_GetValue(void* /*future*/, NPPVariable variable, void *value)
435{
436    return webkit_test_plugin_get_value(NULL, variable, value);
437}
438