mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-30 21:55:31 +00:00
1268 lines
34 KiB
C
1268 lines
34 KiB
C
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
|
|
*
|
|
* The contents of this file are subject to the Netscape Public
|
|
* License Version 1.1 (the "License"); you may not use this file
|
|
* except in compliance with the License. You may obtain a copy of
|
|
* the License at http://www.mozilla.org/NPL/
|
|
*
|
|
* Software distributed under the License is distributed on an "AS
|
|
* IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
|
|
* implied. See the License for the specific language governing
|
|
* rights and limitations under the License.
|
|
*
|
|
* The Original Code is mozilla.org code.
|
|
*
|
|
* The Initial Developer of the Original Code is Netscape
|
|
* Communications Corporation. Portions created by Netscape are
|
|
* Copyright (C) 1998 Netscape Communications Corporation. All
|
|
* Rights Reserved.
|
|
*
|
|
* Contributor(s):
|
|
*/
|
|
/*
|
|
* JS reflection of Navigator Plugins.
|
|
*
|
|
* This file contains implementations of several JS objects:
|
|
*
|
|
* + JSPluginList, a lazily-filled array of all installed
|
|
* plug-ins, based on the information exported from lib/plugin
|
|
* by the functions in np.h. The plug-in properties in the
|
|
* array are named by plug-in name, so you can do an array
|
|
* lookup by name, e.g. plugins["Shockwave"].
|
|
*
|
|
* + JSMIMETypeList, a lazily-filled array of all handled
|
|
* MIME types, based in the information exported from libnet
|
|
* by the functions in net.h. The type properties in the
|
|
* array are named by type, so you can do an array lookup by
|
|
* type, e.g. mimetypes["video/quicktime"].
|
|
*
|
|
* + JSPlugin, the reflection of an installed plug-in, with
|
|
* static properties for the plug-in's name, file name, etc.
|
|
* and dynamic properties for the MIME types supported by
|
|
* the plug-in.
|
|
*
|
|
* + JSMIMEType, the reflection of an individual MIME type,
|
|
* with properties for the type, file extensions, platform-
|
|
* specific file type, description of the type, and enabled
|
|
* plug-in. The enabled plug-in property is a weak object
|
|
* reference marked as JSPROP_BACKEDGE to break the cycle of
|
|
* references from plug-in to mime type to plug-in.
|
|
*
|
|
*/
|
|
|
|
#include "lm.h"
|
|
#include "prmem.h"
|
|
#include "np.h"
|
|
#include "net.h"
|
|
|
|
|
|
/*
|
|
* -----------------------------------------------------------------------
|
|
*
|
|
* Data types
|
|
*
|
|
* -----------------------------------------------------------------------
|
|
*/
|
|
|
|
typedef struct JSMIMEType
|
|
{
|
|
MochaDecoder *decoder;
|
|
JSObject *obj;
|
|
JSString *type; /* MIME type itself, e.g. "text/html" */
|
|
JSString *description; /* English description of MIME type */
|
|
JSString *suffixes; /* Comma-separated list of file suffixes */
|
|
JSObject *enabledPluginObj; /* Plug-in enabled for this MIME type */
|
|
void *fileType; /* Platform-specific file type */
|
|
} JSMIMEType;
|
|
|
|
typedef struct JSPlugin
|
|
{
|
|
MochaDecoder *decoder;
|
|
JSObject *obj;
|
|
JSString *name; /* Name of plug-in */
|
|
JSString *filename; /* Name of file on disk */
|
|
JSString *description; /* Descriptive HTML (version, etc.) */
|
|
NPReference plugref; /* Opaque reference to private plugin object */
|
|
uint32 length; /* Total number of mime types we have */
|
|
XP_Bool reentrant; /* Flag to halt recursion of getProperty */
|
|
} JSPlugin;
|
|
|
|
|
|
typedef struct JSPluginList
|
|
{
|
|
MochaDecoder *decoder;
|
|
JSObject *obj;
|
|
uint32 length; /* Total number of plug-ins */
|
|
XP_Bool reentrant; /* Flag to halt recursion of getProperty */
|
|
} JSPluginList;
|
|
|
|
|
|
typedef struct JSMIMETypeList
|
|
{
|
|
MochaDecoder *decoder;
|
|
JSObject *obj;
|
|
uint32 length; /* Total number of mime types */
|
|
PRPackedBool reentrant; /* Flag to halt recursion of getProperty */
|
|
} JSMIMETypeList;
|
|
|
|
|
|
|
|
/*
|
|
* -----------------------------------------------------------------------
|
|
*
|
|
* Function protos (all private to this file)
|
|
*
|
|
* -----------------------------------------------------------------------
|
|
*/
|
|
|
|
static JSMIMEType* mimetype_create_self(JSContext *cx, MochaDecoder* decoder, char* type,
|
|
char* description, char** suffixes, uint32 numExtents, void* fileType);
|
|
JSBool PR_CALLBACK mimetype_getProperty(JSContext *cx, JSObject *obj, jsval id, jsval *vp);
|
|
void PR_CALLBACK mimetype_finalize(JSContext *cx, JSObject *obj);
|
|
|
|
static JSPlugin* plugin_create_self(JSContext *cx, MochaDecoder* decoder, char* name,
|
|
char* filename, char* description, NPReference plugref);
|
|
static JSMIMEType* plugin_create_mimetype(JSContext* cx, JSPlugin* plugin,
|
|
XP_Bool byName, const char* targetName, jsint targetSlot);
|
|
JSBool PR_CALLBACK plugin_getProperty(JSContext *cx, JSObject *obj, jsval id, jsval *vp);
|
|
JSBool PR_CALLBACK plugin_resolve_name(JSContext *cx, JSObject *obj, jsval id);
|
|
void PR_CALLBACK plugin_finalize(JSContext *cx, JSObject *obj);
|
|
|
|
static JSPluginList* pluginlist_create_self(JSContext *cx, MochaDecoder* decoder, JSObject* parent_obj);
|
|
static JSPlugin* pluginlist_create_plugin(JSContext *cx, JSPluginList *pluginlist,
|
|
XP_Bool byName, const char* targetName, jsint targetSlot);
|
|
JSBool PR_CALLBACK pluginlist_getProperty(JSContext *cx, JSObject *obj, jsval id, jsval *vp);
|
|
JSBool PR_CALLBACK pluginlist_resolve_name(JSContext *cx, JSObject *obj, jsval id);
|
|
JSBool PR_CALLBACK pluginlist_enumerate(JSContext *cx, JSObject *obj);
|
|
void PR_CALLBACK pluginlist_finalize(JSContext *cx, JSObject *obj);
|
|
JSBool PR_CALLBACK pluginlist_refresh(JSContext *cx, JSObject *obj,
|
|
uint argc, jsval *argv, jsval *rval);
|
|
|
|
static JSMIMETypeList* mimetypelist_create_self(JSContext *cx, MochaDecoder* decoder);
|
|
static JSMIMEType* mimetypelist_create_mimetype(JSContext* cx, JSMIMETypeList* mimetypelist,
|
|
XP_Bool byName, const char* targetName, jsint targetSlot);
|
|
JSBool PR_CALLBACK mimetypelist_getProperty(JSContext *cx, JSObject *obj, jsval id, jsval *vp);
|
|
JSBool PR_CALLBACK mimetypelist_resolve_name(JSContext *cx, JSObject *obj, jsval id);
|
|
JSBool PR_CALLBACK mimetypelist_enumerate(JSContext *cx, JSObject *obj);
|
|
void PR_CALLBACK mimetypelist_finalize(JSContext *cx, JSObject *obj);
|
|
|
|
/*
|
|
* -----------------------------------------------------------------------
|
|
*
|
|
* Reflection of a MIME type.
|
|
*
|
|
* -----------------------------------------------------------------------
|
|
*/
|
|
|
|
enum mimetype_slot
|
|
{
|
|
MIMETYPE_TYPE = -1,
|
|
MIMETYPE_DESCRIPTION = -2,
|
|
MIMETYPE_SUFFIXES = -3,
|
|
MIMETYPE_ENABLEDPLUGIN = -4
|
|
};
|
|
|
|
|
|
static JSPropertySpec mimetype_props[] =
|
|
{
|
|
{"type", MIMETYPE_TYPE, JSPROP_ENUMERATE|JSPROP_READONLY},
|
|
{"description", MIMETYPE_DESCRIPTION, JSPROP_ENUMERATE|JSPROP_READONLY},
|
|
{"suffixes", MIMETYPE_SUFFIXES, JSPROP_ENUMERATE|JSPROP_READONLY},
|
|
{"enabledPlugin", MIMETYPE_ENABLEDPLUGIN, JSPROP_ENUMERATE|JSPROP_READONLY},
|
|
{0}
|
|
};
|
|
|
|
|
|
static JSClass mimetype_class =
|
|
{
|
|
"MimeType", JSCLASS_HAS_PRIVATE,
|
|
JS_PropertyStub, JS_PropertyStub,
|
|
mimetype_getProperty, mimetype_getProperty, JS_EnumerateStub,
|
|
JS_ResolveStub, JS_ConvertStub, mimetype_finalize
|
|
};
|
|
|
|
PR_STATIC_CALLBACK(JSBool)
|
|
MimeType(JSContext *cx, JSObject *obj, uint argc, jsval *argv, jsval *rval)
|
|
{
|
|
return JS_TRUE;
|
|
}
|
|
|
|
/* Constructor method for a JSMIMEType object */
|
|
static JSMIMEType*
|
|
mimetype_create_self(JSContext *cx, MochaDecoder* decoder,
|
|
char* type, char* description, char ** suffixes,
|
|
uint32 numExtents, void* fileType)
|
|
{
|
|
JSObject *obj;
|
|
JSMIMEType *mimetype;
|
|
|
|
mimetype = JS_malloc(cx, sizeof(JSMIMEType));
|
|
if (!mimetype)
|
|
return NULL;
|
|
XP_BZERO(mimetype, sizeof *mimetype);
|
|
|
|
obj = JS_NewObject(cx, &mimetype_class, NULL, NULL);
|
|
if (!obj || !JS_SetPrivate(cx, obj, mimetype)) {
|
|
JS_free(cx, mimetype);
|
|
return NULL;
|
|
}
|
|
|
|
if (!JS_DefineProperties(cx, obj, mimetype_props))
|
|
return NULL;
|
|
|
|
mimetype->decoder = HOLD_BACK_COUNT(decoder);
|
|
mimetype->obj = obj;
|
|
mimetype->fileType = fileType;
|
|
|
|
mimetype->type = JS_NewStringCopyZ(cx, type);
|
|
if (!mimetype->type || !JS_LockGCThing(cx, mimetype->type))
|
|
return NULL;
|
|
|
|
mimetype->description = JS_NewStringCopyZ(cx, description);
|
|
if (!mimetype->description || !JS_LockGCThing(cx, mimetype->description))
|
|
return NULL;
|
|
|
|
|
|
/*
|
|
* Assemble the list of file extensions into a string.
|
|
* The extensions are stored in an array of individual strings, so we
|
|
* first traverse the array to see how big the concatenated string will
|
|
* be, then allocate the memory and re-traverse the array to build the
|
|
* string. Each extension is seperated by trailing comma and space.
|
|
*/
|
|
{
|
|
uint32 index;
|
|
uint32 totalSize = 0;
|
|
char *extStr;
|
|
|
|
if (numExtents > 0)
|
|
{
|
|
for (index = 0; index < numExtents; index++)
|
|
totalSize += XP_STRLEN(suffixes[index]);
|
|
|
|
/* Add space for ', ' after each extension */
|
|
totalSize += (2 * (numExtents - 1));
|
|
}
|
|
|
|
/* Total size plus terminator */
|
|
extStr = XP_ALLOC(totalSize + 1);
|
|
if (! extStr)
|
|
return NULL;
|
|
|
|
extStr[0] = 0;
|
|
|
|
for (index = 0; index < numExtents; index++)
|
|
{
|
|
extStr = strcat(extStr, suffixes[index]);
|
|
if (index < numExtents - 1)
|
|
extStr = strcat(extStr, ", ");
|
|
}
|
|
mimetype->suffixes = JS_NewStringCopyZ(cx, extStr);
|
|
XP_FREE(extStr);
|
|
if (!mimetype->suffixes || !JS_LockGCThing(cx, mimetype->suffixes))
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* Conjure up the JS object for the plug-in enabled for this
|
|
* MIME type. First we get the name of the plug-in from libplugin;
|
|
* then we look up the plugin object by name in the global plug-in
|
|
* list (it's actually a "resolve", not a "lookup", so that the
|
|
* plug-in object will be created if it doesn't already exist).
|
|
*/
|
|
if (type) {
|
|
char* pluginName = NPL_FindPluginEnabledForType(type);
|
|
if (pluginName)
|
|
{
|
|
/* Look up the global plugin list object */
|
|
jsval val;
|
|
if (JS_LookupProperty(cx, decoder->navigator, "plugins", &val) &&
|
|
JSVAL_IS_OBJECT(val))
|
|
{
|
|
/* Look up the desired plugin by name in the list */
|
|
JSObject* pluginListObj = JSVAL_TO_OBJECT(val);
|
|
if (JS_GetProperty(cx, pluginListObj, pluginName, &val) &&
|
|
JSVAL_IS_OBJECT(val))
|
|
{
|
|
mimetype->enabledPluginObj = JSVAL_TO_OBJECT(val);
|
|
}
|
|
}
|
|
|
|
XP_FREE(pluginName);
|
|
}
|
|
}
|
|
|
|
return mimetype;
|
|
}
|
|
|
|
|
|
JSBool PR_CALLBACK
|
|
mimetype_getProperty(JSContext *cx, JSObject *obj, jsval id, jsval *vp)
|
|
{
|
|
JSMIMEType *mimetype;
|
|
JSString *str;
|
|
jsint slot;
|
|
|
|
if (!JSVAL_IS_INT(id))
|
|
return JS_TRUE;
|
|
|
|
slot = JSVAL_TO_INT(id);
|
|
|
|
mimetype = JS_GetInstancePrivate(cx, obj, &mimetype_class, NULL);
|
|
if (!mimetype)
|
|
return JS_TRUE;
|
|
|
|
switch (slot)
|
|
{
|
|
case MIMETYPE_TYPE:
|
|
str = mimetype->type;
|
|
break;
|
|
|
|
case MIMETYPE_DESCRIPTION:
|
|
str = mimetype->description;
|
|
break;
|
|
|
|
case MIMETYPE_SUFFIXES:
|
|
str = mimetype->suffixes;
|
|
break;
|
|
|
|
case MIMETYPE_ENABLEDPLUGIN:
|
|
*vp = OBJECT_TO_JSVAL(mimetype->enabledPluginObj);
|
|
return JS_TRUE;
|
|
|
|
default:
|
|
/* don't mess with user-defined props. */
|
|
return JS_TRUE;
|
|
}
|
|
|
|
if (str)
|
|
*vp = STRING_TO_JSVAL(str);
|
|
else
|
|
*vp = JS_GetEmptyStringValue(cx);
|
|
return JS_TRUE;
|
|
}
|
|
|
|
|
|
void PR_CALLBACK
|
|
mimetype_finalize(JSContext *cx, JSObject *obj)
|
|
{
|
|
JSMIMEType *mimetype;
|
|
|
|
mimetype = JS_GetPrivate(cx, obj);
|
|
if (!mimetype)
|
|
return;
|
|
|
|
DROP_BACK_COUNT(mimetype->decoder);
|
|
JS_UnlockGCThing(cx, mimetype->type);
|
|
JS_UnlockGCThing(cx, mimetype->description);
|
|
JS_UnlockGCThing(cx, mimetype->suffixes);
|
|
JS_free(cx, mimetype);
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
* -----------------------------------------------------------------------
|
|
*
|
|
* Reflection of an installed plug-in.
|
|
*
|
|
* -----------------------------------------------------------------------
|
|
*/
|
|
|
|
enum plugin_slot
|
|
{
|
|
PLUGIN_NAME = -1,
|
|
PLUGIN_FILENAME = -2,
|
|
PLUGIN_DESCRIPTION = -3,
|
|
PLUGIN_ARRAY_LENGTH = -4
|
|
};
|
|
|
|
|
|
static JSPropertySpec plugin_props[] =
|
|
{
|
|
{"name", PLUGIN_NAME, JSPROP_ENUMERATE | JSPROP_READONLY},
|
|
{"filename", PLUGIN_FILENAME, JSPROP_ENUMERATE | JSPROP_READONLY},
|
|
{"description", PLUGIN_DESCRIPTION, JSPROP_ENUMERATE | JSPROP_READONLY},
|
|
{"length", PLUGIN_ARRAY_LENGTH, JSPROP_ENUMERATE | JSPROP_READONLY},
|
|
{0}
|
|
};
|
|
|
|
static JSClass plugin_class =
|
|
{
|
|
"Plugin", JSCLASS_HAS_PRIVATE,
|
|
JS_PropertyStub, JS_PropertyStub,
|
|
plugin_getProperty, plugin_getProperty, JS_EnumerateStub,
|
|
plugin_resolve_name, JS_ConvertStub, plugin_finalize
|
|
};
|
|
|
|
PR_STATIC_CALLBACK(JSBool)
|
|
Plugin(JSContext *cx, JSObject *obj, uint argc, jsval *argv, jsval *rval)
|
|
{
|
|
return JS_TRUE;
|
|
}
|
|
|
|
/* Constructor method for a JSPlugin object */
|
|
static JSPlugin*
|
|
plugin_create_self(JSContext *cx, MochaDecoder* decoder, char* name,
|
|
char* filename, char* description, NPReference plugref)
|
|
{
|
|
JSObject *obj;
|
|
JSPlugin *plugin;
|
|
|
|
plugin = JS_malloc(cx, sizeof(JSPlugin));
|
|
if (!plugin)
|
|
return NULL;
|
|
XP_BZERO(plugin, sizeof *plugin);
|
|
|
|
obj = JS_NewObject(cx, &plugin_class, NULL, NULL);
|
|
if (!obj || !JS_SetPrivate(cx, obj, plugin)) {
|
|
JS_free(cx, plugin);
|
|
return NULL;
|
|
}
|
|
|
|
if (!JS_DefineProperties(cx, obj, plugin_props))
|
|
return NULL;
|
|
|
|
/* Fill out static property fields */
|
|
plugin->decoder = HOLD_BACK_COUNT(decoder);
|
|
plugin->obj = obj;
|
|
plugin->plugref = plugref;
|
|
|
|
plugin->name = JS_NewStringCopyZ(cx, name);
|
|
if (!plugin->name || !JS_LockGCThing(cx, plugin->name))
|
|
return NULL;
|
|
|
|
plugin->filename = JS_NewStringCopyZ(cx, filename);
|
|
if (!plugin->filename || !JS_LockGCThing(cx, plugin->filename))
|
|
return NULL;
|
|
|
|
plugin->description = JS_NewStringCopyZ(cx, description);
|
|
if (!plugin->description || !JS_LockGCThing(cx, plugin->description))
|
|
return NULL;
|
|
|
|
/* Count how many MIME types we have */
|
|
{
|
|
NPReference typeref = NPRefFromStart;
|
|
while (NPL_IteratePluginTypes(&typeref, plugref, NULL, NULL, NULL, NULL))
|
|
plugin->length++;
|
|
}
|
|
|
|
return plugin;
|
|
}
|
|
|
|
|
|
/* Create a mimetype property for a plugin for a specified slot or name */
|
|
static JSMIMEType*
|
|
plugin_create_mimetype(JSContext* cx, JSPlugin* plugin, XP_Bool byName,
|
|
const char* targetName, jsint targetSlot)
|
|
{
|
|
NPMIMEType type = NULL;
|
|
char** suffixes = NULL;
|
|
char* description = NULL;
|
|
void* fileType = NULL;
|
|
NPReference typeref = NPRefFromStart;
|
|
jsint slot = 0;
|
|
XP_Bool found = FALSE;
|
|
|
|
/* Search for the type (by type name or slot number) */
|
|
while (NPL_IteratePluginTypes(&typeref, plugin->plugref, &type,
|
|
&suffixes, &description, &fileType))
|
|
{
|
|
if (byName)
|
|
found = (type && (XP_STRCMP(targetName, type) == 0));
|
|
else
|
|
found = (targetSlot == slot);
|
|
|
|
if (found)
|
|
break;
|
|
|
|
slot++;
|
|
}
|
|
|
|
/* Found the mime type, so create an object and property */
|
|
if (found) {
|
|
JSMIMEType* mimetype;
|
|
uint32 numExtents = 0;
|
|
|
|
while (suffixes[numExtents])
|
|
numExtents++;
|
|
|
|
mimetype = mimetype_create_self(cx, plugin->decoder, type, description,
|
|
suffixes, numExtents, fileType);
|
|
if (mimetype) {
|
|
char *name;
|
|
jsval val;
|
|
|
|
name = JS_GetStringBytes(mimetype->type);
|
|
val = OBJECT_TO_JSVAL(mimetype->obj);
|
|
JS_DefineProperty(cx, plugin->obj, name, val, NULL, NULL,
|
|
JSPROP_ENUMERATE | JSPROP_READONLY);
|
|
JS_AliasElement(cx, plugin->obj, name, slot);
|
|
}
|
|
|
|
return mimetype;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
|
|
JSBool PR_CALLBACK
|
|
plugin_getProperty(JSContext *cx, JSObject *obj, jsval id, jsval *vp)
|
|
{
|
|
JSPlugin *plugin;
|
|
JSString *str;
|
|
jsint slot;
|
|
|
|
if (!JSVAL_IS_INT(id))
|
|
return JS_TRUE;
|
|
|
|
slot = JSVAL_TO_INT(id);
|
|
|
|
plugin = JS_GetInstancePrivate(cx, obj, &plugin_class, NULL);
|
|
if (!plugin)
|
|
return JS_TRUE;
|
|
|
|
/* Prevent infinite recursion through call to GetSlot below */
|
|
if (plugin->reentrant)
|
|
return JS_TRUE;
|
|
|
|
switch (slot)
|
|
{
|
|
case PLUGIN_NAME:
|
|
str = plugin->name;
|
|
break;
|
|
|
|
case PLUGIN_FILENAME:
|
|
str = plugin->filename;
|
|
break;
|
|
|
|
case PLUGIN_DESCRIPTION:
|
|
str = plugin->description;
|
|
break;
|
|
|
|
case PLUGIN_ARRAY_LENGTH:
|
|
*vp = INT_TO_JSVAL(plugin->length);
|
|
return JS_TRUE;
|
|
|
|
default:
|
|
/* Don't mess with a user-defined property. */
|
|
if (slot >= 0 && slot < (jsint) plugin->length) {
|
|
/* Search for an existing JSMIMEType for this slot */
|
|
JSObject* mimetypeObj = NULL;
|
|
jsval val;
|
|
|
|
plugin->reentrant = TRUE;
|
|
if (JS_GetElement(cx, obj, slot, &val) && JSVAL_IS_OBJECT(val))
|
|
mimetypeObj = JSVAL_TO_OBJECT(val);
|
|
else {
|
|
JSMIMEType* mimetype;
|
|
mimetype = plugin_create_mimetype(cx, plugin, FALSE, NULL, slot);
|
|
if (mimetype)
|
|
mimetypeObj = mimetype->obj;
|
|
}
|
|
plugin->reentrant = FALSE;
|
|
|
|
*vp = OBJECT_TO_JSVAL(mimetypeObj);
|
|
return JS_TRUE;
|
|
}
|
|
return JS_FALSE;
|
|
}
|
|
|
|
if (str)
|
|
*vp = STRING_TO_JSVAL(str);
|
|
else
|
|
*vp = JS_GetEmptyStringValue(cx);
|
|
return JS_TRUE;
|
|
}
|
|
|
|
|
|
JSBool PR_CALLBACK
|
|
plugin_resolve_name(JSContext *cx, JSObject *obj, jsval id)
|
|
{
|
|
JSPlugin* plugin;
|
|
jsval val;
|
|
const char * name;
|
|
|
|
if (!JSVAL_IS_STRING(id))
|
|
return JS_TRUE;
|
|
|
|
name = JS_GetStringBytes(JSVAL_TO_STRING(id));
|
|
|
|
plugin = JS_GetPrivate(cx, obj);
|
|
if (!plugin)
|
|
return JS_TRUE;
|
|
|
|
/* Prevent infinite recursion through call to LookupProperty below */
|
|
if (plugin->reentrant)
|
|
return JS_TRUE;
|
|
|
|
/* Look for a JSMIMEType object by this name already in our list */
|
|
plugin->reentrant = TRUE;
|
|
if (JS_LookupProperty(cx, obj, name, &val) && JSVAL_IS_OBJECT(val)) {
|
|
plugin->reentrant = FALSE;
|
|
return JS_TRUE;
|
|
}
|
|
plugin->reentrant = FALSE;
|
|
|
|
/* We don't already have the object, so make a new one */
|
|
if (plugin_create_mimetype(cx, plugin, TRUE, name, 0))
|
|
return JS_TRUE;
|
|
|
|
/* Still no match for the name? Must be some other property, or invalid. */
|
|
return JS_TRUE;
|
|
}
|
|
|
|
|
|
void PR_CALLBACK
|
|
plugin_finalize(JSContext *cx, JSObject *obj)
|
|
{
|
|
JSPlugin* plugin;
|
|
|
|
plugin = JS_GetPrivate(cx, obj);
|
|
if (!plugin)
|
|
return;
|
|
DROP_BACK_COUNT(plugin->decoder);
|
|
JS_UnlockGCThing(cx, plugin->name);
|
|
JS_UnlockGCThing(cx, plugin->filename);
|
|
JS_UnlockGCThing(cx, plugin->description);
|
|
JS_free(cx, plugin);
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
* -----------------------------------------------------------------------
|
|
*
|
|
* Reflection of the list of installed plug-ins.
|
|
* The only static property is the array length;
|
|
* the array elements (JSPlugins) are added
|
|
* lazily when referenced.
|
|
*
|
|
* -----------------------------------------------------------------------
|
|
*/
|
|
|
|
enum pluginlist_slot
|
|
{
|
|
PLUGINLIST_ARRAY_LENGTH = -1
|
|
};
|
|
|
|
|
|
static JSPropertySpec pluginlist_props[] =
|
|
{
|
|
{"length", PLUGINLIST_ARRAY_LENGTH, JSPROP_READONLY},
|
|
{0}
|
|
};
|
|
|
|
|
|
static JSClass pluginlist_class =
|
|
{
|
|
"PluginArray", JSCLASS_HAS_PRIVATE,
|
|
JS_PropertyStub, JS_PropertyStub,
|
|
pluginlist_getProperty, pluginlist_getProperty, pluginlist_enumerate,
|
|
pluginlist_resolve_name, JS_ConvertStub, pluginlist_finalize
|
|
};
|
|
|
|
|
|
static JSFunctionSpec pluginlist_methods[] =
|
|
{
|
|
{"refresh", pluginlist_refresh, 0},
|
|
{0}
|
|
};
|
|
|
|
|
|
PR_STATIC_CALLBACK(JSBool)
|
|
PluginList(JSContext *cx, JSObject *obj,
|
|
uint argc, jsval *argv, jsval *rval)
|
|
{
|
|
return JS_TRUE;
|
|
}
|
|
|
|
|
|
/* Constructor method for a JSPluginList object */
|
|
static JSPluginList*
|
|
pluginlist_create_self(JSContext *cx, MochaDecoder* decoder, JSObject* parent_obj)
|
|
{
|
|
JSObject *obj;
|
|
JSPluginList *pluginlist;
|
|
|
|
pluginlist = JS_malloc(cx, sizeof(JSPluginList));
|
|
if (!pluginlist)
|
|
return NULL;
|
|
XP_BZERO(pluginlist, sizeof *pluginlist);
|
|
|
|
obj = JS_InitClass(cx, parent_obj, NULL, &pluginlist_class,
|
|
PluginList, 0, pluginlist_props,
|
|
pluginlist_methods, NULL, NULL);
|
|
|
|
if (!obj || !JS_SetPrivate(cx, obj, pluginlist)) {
|
|
JS_free(cx, pluginlist);
|
|
return NULL;
|
|
}
|
|
|
|
pluginlist->decoder = HOLD_BACK_COUNT(decoder);
|
|
pluginlist->obj = obj;
|
|
|
|
{
|
|
/* Compute total number of plug-ins (potential slots) */
|
|
NPReference ref = NPRefFromStart;
|
|
while (NPL_IteratePluginFiles(&ref, NULL, NULL, NULL))
|
|
pluginlist->length++;
|
|
}
|
|
|
|
return pluginlist;
|
|
}
|
|
|
|
|
|
/* Look up the plugin for the specified slot of the plug-in list */
|
|
static JSPlugin*
|
|
pluginlist_create_plugin(JSContext *cx, JSPluginList *pluginlist, XP_Bool byName,
|
|
const char* targetName, jsint targetSlot)
|
|
{
|
|
char* plugname = NULL;
|
|
char* filename = NULL;
|
|
char* description = NULL;
|
|
NPReference plugref = NPRefFromStart;
|
|
jsint slot = 0;
|
|
XP_Bool found = FALSE;
|
|
|
|
/* Search for the plug-in (by name or slot number) */
|
|
while (NPL_IteratePluginFiles(&plugref, &plugname, &filename, &description)) {
|
|
if (byName)
|
|
found = (plugname && (XP_STRCMP(targetName, plugname) == 0));
|
|
else
|
|
found = (targetSlot == slot);
|
|
|
|
if (found)
|
|
break;
|
|
|
|
slot++;
|
|
}
|
|
|
|
/* Found the plug-in, so create an object and property */
|
|
if (found) {
|
|
JSPlugin* plugin;
|
|
plugin = plugin_create_self(cx, pluginlist->decoder, plugname,
|
|
filename, description, plugref);
|
|
if (plugin) {
|
|
char *name;
|
|
jsval val;
|
|
|
|
name = JS_GetStringBytes(plugin->name);
|
|
val = OBJECT_TO_JSVAL(plugin->obj);
|
|
JS_DefineProperty(cx, pluginlist->obj, name, val, NULL, NULL,
|
|
JSPROP_ENUMERATE | JSPROP_READONLY);
|
|
JS_AliasElement(cx, pluginlist->obj, name, slot);
|
|
}
|
|
|
|
return plugin;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
|
|
JSBool PR_CALLBACK
|
|
pluginlist_getProperty(JSContext *cx, JSObject *obj, jsval id, jsval *vp)
|
|
{
|
|
JSPluginList *pluginlist;
|
|
jsint slot;
|
|
|
|
if (!JSVAL_IS_INT(id))
|
|
return JS_TRUE;
|
|
|
|
slot = JSVAL_TO_INT(id);
|
|
|
|
pluginlist = JS_GetInstancePrivate(cx, obj, &pluginlist_class, NULL);
|
|
if (!pluginlist)
|
|
return JS_TRUE;
|
|
|
|
/* Prevent infinite recursion through call to GetSlot below */
|
|
if (pluginlist->reentrant)
|
|
return JS_TRUE;
|
|
|
|
switch (slot) {
|
|
case PLUGINLIST_ARRAY_LENGTH:
|
|
*vp = INT_TO_JSVAL(pluginlist->length);
|
|
break;
|
|
|
|
default:
|
|
/* Don't mess with a user-defined property. */
|
|
if (slot >= 0 && slot < (jsint) pluginlist->length) {
|
|
|
|
/* Search for an existing JSPlugin for this slot */
|
|
JSObject* pluginObj = NULL;
|
|
jsval val;
|
|
|
|
pluginlist->reentrant = TRUE;
|
|
if (JS_GetElement(cx, obj, slot, &val) && JSVAL_IS_OBJECT(val)) {
|
|
pluginObj = JSVAL_TO_OBJECT(val);
|
|
}
|
|
else {
|
|
JSPlugin* plugin;
|
|
plugin = pluginlist_create_plugin(cx, pluginlist, FALSE, NULL, slot);
|
|
if (plugin)
|
|
pluginObj = plugin->obj;
|
|
}
|
|
pluginlist->reentrant = FALSE;
|
|
|
|
*vp = OBJECT_TO_JSVAL(pluginObj);
|
|
return JS_TRUE;
|
|
}
|
|
}
|
|
|
|
return JS_TRUE;
|
|
}
|
|
|
|
|
|
JSBool PR_CALLBACK
|
|
pluginlist_resolve_name(JSContext *cx, JSObject *obj, jsval id)
|
|
{
|
|
JSPluginList* pluginlist;
|
|
jsval val;
|
|
const char * name;
|
|
|
|
if (!JSVAL_IS_STRING(id))
|
|
return JS_TRUE;
|
|
|
|
name = JS_GetStringBytes(JSVAL_TO_STRING(id));
|
|
|
|
pluginlist = JS_GetPrivate(cx, obj);
|
|
if (!pluginlist)
|
|
return JS_TRUE;
|
|
|
|
/* Prevent infinite recursion through call to LookupProperty below */
|
|
if (pluginlist->reentrant)
|
|
return JS_TRUE;
|
|
|
|
/* Look for a JSMIMEType object by this name already in our list */
|
|
pluginlist->reentrant = TRUE;
|
|
if (JS_LookupProperty(cx, obj, name, &val) && JSVAL_IS_OBJECT(val))
|
|
{
|
|
pluginlist->reentrant = FALSE;
|
|
return JS_TRUE;
|
|
}
|
|
pluginlist->reentrant = FALSE;
|
|
|
|
/* We don't already have the object, so make a new one */
|
|
if (pluginlist_create_plugin(cx, pluginlist, TRUE, name, 0))
|
|
return JS_TRUE;
|
|
|
|
/* Still no match for the name? Must be some other property, or invalid. */
|
|
return JS_TRUE;
|
|
}
|
|
|
|
JSBool PR_CALLBACK
|
|
pluginlist_enumerate(JSContext *cx, JSObject *obj)
|
|
{
|
|
return JS_TRUE;
|
|
}
|
|
|
|
void PR_CALLBACK
|
|
pluginlist_finalize(JSContext *cx, JSObject *obj)
|
|
{
|
|
JSPluginList* pluginlist;
|
|
|
|
pluginlist = JS_GetPrivate(cx, obj);
|
|
if (!pluginlist)
|
|
return;
|
|
DROP_BACK_COUNT(pluginlist->decoder);
|
|
JS_free(cx, pluginlist);
|
|
}
|
|
|
|
|
|
JSBool PR_CALLBACK
|
|
pluginlist_refresh(JSContext *cx, JSObject *obj,
|
|
uint argc, jsval *argv, jsval *rval)
|
|
{
|
|
JSBool value = JS_FALSE;
|
|
JSPluginList* pluginlist;
|
|
JSObject* navigator;
|
|
MochaDecoder* decoder;
|
|
MochaDecoder* crippled_decoder;
|
|
NPError error = NPERR_NO_ERROR;
|
|
|
|
if (!(pluginlist = JS_GetInstancePrivate(cx, obj, &pluginlist_class, argv)))
|
|
return JS_FALSE;
|
|
decoder = pluginlist->decoder;
|
|
|
|
/*
|
|
* Evaluate the parameter (if any). If the parameter
|
|
* is missing or can't be evaluated, default to FALSE.
|
|
*/
|
|
if (argc > 0)
|
|
(void) JS_ValueToBoolean(cx, argv[0], &value);
|
|
|
|
/* Synchronously execute this function on the Mozilla thread */
|
|
error = (NPError) ET_npl_RefreshPluginList(decoder->window_context, value);
|
|
if (error != NPERR_NO_ERROR)
|
|
{
|
|
/* XXX should JS_ReportError() here, but can't happen currently */
|
|
return JS_FALSE;
|
|
}
|
|
|
|
/*
|
|
* Remove references to the existing navigator object,
|
|
* and make a new one. If scripts have outstanding
|
|
* references to the old objects, they'll still be
|
|
* valid, but if they reference navigator.plugins
|
|
* anew they'll see any new plug-ins registered by
|
|
* NPL_RefreshPlugins.
|
|
*/
|
|
navigator = decoder->navigator;
|
|
decoder->navigator = NULL; /* Prevent lm_DefineNavigator from short-circuiting */
|
|
crippled_decoder = LM_GetCrippledDecoder();
|
|
crippled_decoder->navigator = NULL;
|
|
crippled_decoder->navigator = lm_DefineNavigator(decoder);
|
|
if (!decoder->navigator)
|
|
{
|
|
/*
|
|
* We failed to create a new navigator object, so
|
|
* restore the old one (saved in a local variable).
|
|
*/
|
|
decoder->navigator = navigator;
|
|
return JS_FALSE;
|
|
}
|
|
return JS_TRUE;
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
* -----------------------------------------------------------------------
|
|
*
|
|
* Reflection of the list of handled MIME types.
|
|
*
|
|
* -----------------------------------------------------------------------
|
|
*/
|
|
|
|
enum mimetypelist_slot
|
|
{
|
|
MIMETYPELIST_ARRAY_LENGTH = -1
|
|
};
|
|
|
|
|
|
static JSPropertySpec mimetypelist_props[] =
|
|
{
|
|
{"length", MIMETYPELIST_ARRAY_LENGTH, JSPROP_READONLY},
|
|
{0}
|
|
};
|
|
|
|
|
|
static JSClass mimetypelist_class =
|
|
{
|
|
"MimeTypeArray", JSCLASS_HAS_PRIVATE,
|
|
JS_PropertyStub, JS_PropertyStub,
|
|
mimetypelist_getProperty, mimetypelist_getProperty, mimetypelist_enumerate,
|
|
mimetypelist_resolve_name, JS_ConvertStub, mimetypelist_finalize
|
|
};
|
|
|
|
|
|
/* Constructor method for a JSMIMETypeList object */
|
|
static JSMIMETypeList*
|
|
mimetypelist_create_self(JSContext *cx, MochaDecoder* decoder)
|
|
{
|
|
JSObject *obj;
|
|
JSMIMETypeList *mimetypelist;
|
|
|
|
mimetypelist = JS_malloc(cx, sizeof(JSMIMETypeList));
|
|
if (!mimetypelist)
|
|
return NULL;
|
|
XP_BZERO(mimetypelist, sizeof *mimetypelist);
|
|
|
|
obj = JS_NewObject(cx, &mimetypelist_class, NULL, NULL);
|
|
|
|
if (!obj || !JS_SetPrivate(cx, obj, mimetypelist)) {
|
|
JS_free(cx, mimetypelist);
|
|
return NULL;
|
|
}
|
|
|
|
if (!JS_DefineProperties(cx, obj, mimetypelist_props))
|
|
return NULL;
|
|
|
|
mimetypelist->decoder = HOLD_BACK_COUNT(decoder);
|
|
mimetypelist->obj = obj;
|
|
|
|
/*
|
|
* Count the number of types in the list.
|
|
* We can't just go by the number of items
|
|
* in the list, since it contains encodings, too.
|
|
*/
|
|
{
|
|
XP_List* cinfoList = cinfo_MasterListPointer();
|
|
NET_cdataStruct* cdata = NULL;
|
|
mimetypelist->length = 0;
|
|
while (cinfoList)
|
|
{
|
|
cdata = cinfoList->object;
|
|
if (cdata && cdata->ci.type)
|
|
mimetypelist->length++;
|
|
cinfoList = cinfoList->next;
|
|
}
|
|
}
|
|
|
|
return mimetypelist;
|
|
}
|
|
|
|
/*
|
|
* Common code to take a cdata and create a mimetype
|
|
*/
|
|
static JSMIMEType*
|
|
define_mimetype(JSContext* cx, JSMIMETypeList* mimetypelist,
|
|
NET_cdataStruct* cdata, jsint targetSlot)
|
|
{
|
|
|
|
JSMIMEType* mimetype;
|
|
mimetype = mimetype_create_self(cx, mimetypelist->decoder, cdata->ci.type,
|
|
cdata->ci.desc, cdata->exts, cdata->num_exts, NULL);
|
|
if (mimetype) {
|
|
char *name;
|
|
name = JS_GetStringBytes(mimetype->type);
|
|
JS_DefineProperty(cx, mimetypelist->obj, name,
|
|
OBJECT_TO_JSVAL(mimetype->obj), NULL, NULL,
|
|
JSPROP_ENUMERATE | JSPROP_READONLY);
|
|
JS_AliasElement(cx, mimetypelist->obj, name, targetSlot);
|
|
}
|
|
|
|
return mimetype;
|
|
}
|
|
|
|
/* Create a mimetype property for a specified slot or name */
|
|
static JSMIMEType*
|
|
mimetypelist_create_mimetype(JSContext* cx, JSMIMETypeList* mimetypelist,
|
|
XP_Bool byName, const char* targetName, jsint targetSlot)
|
|
{
|
|
XP_List* cinfoList = cinfo_MasterListPointer();
|
|
NET_cdataStruct* cdata = NULL;
|
|
XP_Bool found = FALSE;
|
|
|
|
if (byName) {
|
|
/* Look up by name */
|
|
targetSlot = 0;
|
|
while (cinfoList) {
|
|
cdata = cinfoList->object;
|
|
if (cdata) {
|
|
char* type = cdata->ci.type;
|
|
if (type && (XP_STRCMP(type, targetName) == 0)) {
|
|
found = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
targetSlot++;
|
|
cinfoList = cinfoList->next;
|
|
}
|
|
}
|
|
else {
|
|
/*
|
|
* Look up by slot.
|
|
* The slot number does not directly correspond to list index,
|
|
* since not all items in the list correspond to properties
|
|
* (encodings are in list list as well as types).
|
|
*/
|
|
uint32 count = targetSlot + 1;
|
|
while (cinfoList) {
|
|
cdata = cinfoList->object;
|
|
if (cdata && cdata->ci.type)
|
|
count--;
|
|
|
|
if (count == 0) {
|
|
found = TRUE;
|
|
break;
|
|
}
|
|
|
|
cinfoList = cinfoList->next;
|
|
}
|
|
}
|
|
|
|
if (found)
|
|
return define_mimetype(cx, mimetypelist, cdata, targetSlot);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
JSBool PR_CALLBACK
|
|
mimetypelist_getProperty(JSContext *cx, JSObject *obj, jsval id, jsval *vp)
|
|
{
|
|
JSMIMETypeList *mimetypelist;
|
|
jsint slot;
|
|
|
|
if (!JSVAL_IS_INT(id))
|
|
return JS_TRUE;
|
|
|
|
slot = JSVAL_TO_INT(id);
|
|
|
|
mimetypelist = JS_GetInstancePrivate(cx, obj, &mimetypelist_class, NULL);
|
|
if (!mimetypelist)
|
|
return JS_TRUE;
|
|
|
|
/* Prevent infinite recursion through call to GetSlot below */
|
|
if (mimetypelist->reentrant)
|
|
return JS_TRUE;
|
|
|
|
switch (slot)
|
|
{
|
|
case MIMETYPELIST_ARRAY_LENGTH:
|
|
*vp = INT_TO_JSVAL(mimetypelist->length);
|
|
break;
|
|
|
|
default:
|
|
/* Don't mess with a user-defined property. */
|
|
if (slot >= 0 && slot < (jsint) mimetypelist->length) {
|
|
/* Search for an existing JSMIMEType for this slot */
|
|
JSObject* mimetypeObj = NULL;
|
|
jsval val = JSVAL_VOID;
|
|
|
|
mimetypelist->reentrant = TRUE;
|
|
if (JS_GetElement(cx, obj, slot, &val) && JSVAL_IS_OBJECT(val)) {
|
|
mimetypeObj = JSVAL_TO_OBJECT(val);
|
|
}
|
|
else {
|
|
JSMIMEType* mimetype;
|
|
mimetype = mimetypelist_create_mimetype(cx, mimetypelist, FALSE, NULL, slot);
|
|
if (mimetype)
|
|
mimetypeObj = mimetype->obj;
|
|
}
|
|
mimetypelist->reentrant = FALSE;
|
|
|
|
*vp = OBJECT_TO_JSVAL(mimetypeObj);
|
|
return JS_TRUE;
|
|
}
|
|
break;
|
|
}
|
|
|
|
return JS_TRUE;
|
|
}
|
|
|
|
|
|
JSBool PR_CALLBACK
|
|
mimetypelist_resolve_name(JSContext *cx, JSObject *obj, jsval id)
|
|
{
|
|
JSMIMETypeList* mimetypelist;
|
|
jsval val;
|
|
const char * name;
|
|
|
|
if (!JSVAL_IS_STRING(id))
|
|
return JS_TRUE;
|
|
|
|
name = JS_GetStringBytes(JSVAL_TO_STRING(id));
|
|
|
|
mimetypelist = JS_GetPrivate(cx, obj);
|
|
if (!mimetypelist)
|
|
return JS_TRUE;
|
|
|
|
/* Prevent infinite recursion through call to LookupProperty below */
|
|
if (mimetypelist->reentrant)
|
|
return JS_TRUE;
|
|
|
|
/* Look for a JSMIMEType object by this name already in our list */
|
|
mimetypelist->reentrant = TRUE;
|
|
if (JS_LookupProperty(cx, obj, name, &val) && JSVAL_IS_OBJECT(val))
|
|
{
|
|
mimetypelist->reentrant = FALSE;
|
|
return JS_TRUE;
|
|
}
|
|
mimetypelist->reentrant = FALSE;
|
|
|
|
/* We don't already have the object, so make a new one */
|
|
if (mimetypelist_create_mimetype(cx, mimetypelist, TRUE, name, 0))
|
|
return JS_TRUE;
|
|
|
|
/* Still no match for the name? Must be some other property, or invalid. */
|
|
return JS_TRUE;
|
|
}
|
|
|
|
JSBool PR_CALLBACK
|
|
mimetypelist_enumerate(JSContext *cx, JSObject *obj)
|
|
{
|
|
JSMIMETypeList* mimetypelist;
|
|
XP_List* cinfoList = cinfo_MasterListPointer();
|
|
NET_cdataStruct* cdata = NULL;
|
|
jsint targetSlot = 0;
|
|
|
|
mimetypelist = JS_GetPrivate(cx, obj);
|
|
if (!mimetypelist)
|
|
return JS_TRUE;
|
|
|
|
while (cinfoList) {
|
|
cdata = cinfoList->object;
|
|
if (cdata)
|
|
define_mimetype(cx, mimetypelist, cdata, targetSlot++);
|
|
cinfoList = cinfoList->next;
|
|
}
|
|
|
|
return JS_TRUE;
|
|
}
|
|
|
|
void PR_CALLBACK
|
|
mimetypelist_finalize(JSContext *cx, JSObject *obj)
|
|
{
|
|
JSMIMETypeList *mimetypelist;
|
|
|
|
mimetypelist = JS_GetPrivate(cx, obj);
|
|
if (!mimetypelist)
|
|
return;
|
|
DROP_BACK_COUNT(mimetypelist->decoder);
|
|
JS_free(cx, mimetypelist);
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
* Exported entry point, called from lm_nav.c.
|
|
* This function creates the JSPluginList object. The
|
|
* properties for the installed plug-ins are instantiated
|
|
* lazily in pluginlist_resolve_name.
|
|
*/
|
|
JSObject*
|
|
lm_NewPluginList(JSContext *cx, JSObject *parent_obj)
|
|
{
|
|
MochaDecoder* decoder;
|
|
JSPluginList* pluginlist;
|
|
|
|
decoder = JS_GetPrivate(cx, JS_GetGlobalObject(cx));
|
|
pluginlist = pluginlist_create_self(cx, decoder, parent_obj);
|
|
return (pluginlist ? pluginlist->obj : NULL);
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
* Exported entry point to this file, called from lm_nav.c.
|
|
* This function creates the JSMIMETypeList object. The
|
|
* properties for the MIME types are instantiated
|
|
* lazily in mimetypelist_resolve_name.
|
|
*/
|
|
JSObject*
|
|
lm_NewMIMETypeList(JSContext *cx)
|
|
{
|
|
MochaDecoder* decoder;
|
|
JSMIMETypeList* mimetypelist;
|
|
|
|
decoder = JS_GetPrivate(cx, JS_GetGlobalObject(cx));
|
|
mimetypelist = mimetypelist_create_self(cx, decoder);
|
|
return (mimetypelist ? mimetypelist->obj : NULL);
|
|
}
|
|
|
|
JSBool
|
|
lm_DefinePluginClasses(MochaDecoder *decoder)
|
|
{
|
|
JSContext *cx = decoder->js_context;
|
|
|
|
if (!JS_InitClass(cx, decoder->window_object, NULL, &mimetype_class,
|
|
MimeType, 0, mimetype_props, NULL, NULL, NULL))
|
|
return JS_FALSE;
|
|
|
|
if (!JS_InitClass(cx, decoder->window_object, NULL, &plugin_class,
|
|
Plugin, 0, plugin_props, NULL, NULL, NULL))
|
|
return JS_FALSE;
|
|
|
|
return JS_TRUE;
|
|
}
|