blob: 65c25af2d4809e9f20df2e64af9b627c8b6d43ed [file] [log] [blame]
/*
* Copyright 2007 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
#ifndef JNI_LINUX_JSROOTEDVALUE_H_
#define JNI_LINUX_JSROOTEDVALUE_H_
// Mozilla header files
#include "mozilla-headers.h"
#include "Tracer.h"
#include <stack>
extern "C" JSClass gwt_nativewrapper_class;
/*
* Holds a root for Javascript objects, so the JS interpreter knows not to
* garbage-collect the underlying object as long as this object exists.
* Java code will pass a pointer to this object around (as an int/long) for
* referring to the underlying Javascript object.
*
* There are also convenience routines for manipulating the underlying value.
* Note that all get* methods assume the type is correct, so the corresponding
* is* method should be called first if you aren't sure of the type.
*
* See http://developer.mozilla.org/en/docs/JS_AddRoot for details.
*
* TODO(jat): rewrite this to minimize the number of roots held and to
* improve 64-bit compatibility.
*/
class JsRootedValue
{
private:
// the JavaScript String class
static JSClass* stringClass;
// Javascript runtime
static JSRuntime* runtime;
// stack of Javascript contexts
static std::stack<JSContext*> contextStack;
// underlying Javascript value
jsval value_;
protected:
/*
* Fetch the JavaScript String class.
* Not inlined to minimize code bloat since it should only be called once.
*/
void fetchStringClass() const;
/*
* Make sure we have the JS code to identify String objects installed.
*/
void ensureStringClass() const {
if(stringClass) return;
fetchStringClass();
}
/*
* Helper for the various constructors
*/
void constructorHelper(const char* ctorDesc) {
Tracer tracer(ctorDesc, this);
if (!JS_AddNamedRootRT(runtime, &value_, ctorDesc)) {
tracer.log("JS_AddNamedRootRT failed");
// TODO(jat): handle errors
}
}
/*
* Push a new JavaScript execution context.
*/
static void pushContext(JSContext* context) {
Tracer tracer("JsRootedValue::pushContext");
tracer.log("pushed context=%08x", unsigned(context));
contextStack.push(context);
}
/*
* Pop a JavaScript execution context from the context stack.
*/
static void popContext() {
Tracer tracer("JsRootedValue::popContext");
JSContext* context = currentContext();
contextStack.pop();
tracer.log("popped context=%08x", unsigned(context));
}
public:
/*
* This is a helper class used to push/pop JSContext values automatically,
* using RAII. Simply create a ContextManager object on the stack and it
* will push the context at creation time and pop it at destruction time,
* so you don't have to worry about covering all the exit paths from a
* function.
*/
class ContextManager {
public:
explicit ContextManager(JSContext* context) {
JsRootedValue::pushContext(context);
}
~ContextManager() {
JsRootedValue::popContext();
}
};
/*
* This is a helper class to manage short-lived roots on the stack, using
* RAII to free the roots when no longer needed.
*/
class Temp {
private:
void* ptr_;
public:
explicit Temp(void* ptr) : ptr_(ptr) {
JS_AddNamedRootRT(runtime, ptr_, "temporary root");
}
~Temp() {
JS_RemoveRootRT(runtime, ptr_);
}
};
/*
* Copy constructor - make another rooted value that refers to the same
* JavaScript object (or has the same value if a primitive)
*/
JsRootedValue(const JsRootedValue& rooted_value) : value_(rooted_value.value_)
{
constructorHelper("JsRootedValue copy ctor");
}
/*
* Create a value with a given jsval value
*/
JsRootedValue(jsval value) : value_(value)
{
constructorHelper("JsRootedValue jsval ctor");
}
/*
* Create a void value
*/
JsRootedValue() : value_(JSVAL_VOID) {
constructorHelper("JsRootedValue void ctor");
}
/*
* Destroy this object.
*/
~JsRootedValue() {
Tracer tracer("~JsRootedValue", this);
// ignore error since currently it is not possible to fail
JS_RemoveRootRT(runtime, &value_);
}
/*
* Save a pointer to the JSRuntime if we don't have it yet
*/
static void ensureRuntime(JSContext* context) {
if(!runtime) runtime = JS_GetRuntime(context);
}
/*
* Return the current JavaScript execution context.
*/
static JSContext* currentContext() {
Tracer tracer("JsRootedValue::currentContext");
if (contextStack.empty()) {
// TODO(jat): better error handling?
fprintf(stderr, "JsRootedValue::currentContext - context stack empty\n");
::abort();
}
JSContext* context = contextStack.top();
tracer.log("context=%08x", unsigned(context));
return context;
}
/*
* Return the underlying JS object
*/
jsval getValue() const { return value_; }
/*
* Sets the value of the underlying JS object.
*
* Returns false if an error occurred.
*/
bool setValue(jsval new_value) {
value_ = new_value;
return true;
}
/*
* Returns true if the underlying value is of some number type.
*/
bool isNumber() const {
return JSVAL_IS_NUMBER(value_);
}
/*
* Returns the underlying value as a double.
* Result is 0.0 if the underlying value is not a number
* type.
*/
double getDouble() const {
jsdouble return_value=0.0;
// ignore return value -- if it fails, value will remain 0.0
JS_ValueToNumber(currentContext(), value_, &return_value);
return double(return_value);
}
/*
* Set the underlying value to a double value.
*
* Returns false on failure.
*/
bool setDouble(double val) {
jsval js_double;
if(!JS_NewDoubleValue(currentContext(), jsdouble(val), &js_double)) {
return false;
}
return setValue(js_double);
}
/*
* Returns the underlying value as an integer value. Note that the result
* is undefined if isInt() does not return true.
*/
int getInt() {
return JSVAL_TO_INT(value_);
}
/*
* Set the underlying value to an integer value.
*
* Returns false on failure.
*/
bool setInt(int val) {
// check if it fits in 31 bits (ie, top two bits are equal).
// if not, store it as a double
if ((val & 0x80000000) != ((val << 1) & 0x80000000)) {
return setDouble(val);
} else {
return setValue(INT_TO_JSVAL(val));
}
}
/*
* Returns true if the underlying value is a boolean.
*/
bool isBoolean() const {
return JSVAL_IS_BOOLEAN(value_);
}
/*
* Returns the underlying value as a boolean.
* Result is undefined if the value is not actually
* a boolean.
*/
bool getBoolean() const {
return value_ != JSVAL_FALSE;
}
/*
* Set the underlying value to a boolean value.
*
* Returns false on failure (impossible?).
*/
bool setBoolean(bool val) {
return setValue(val ? JSVAL_TRUE : JSVAL_FALSE);
}
/*
* Returns true if the underlying value is a string.
*/
bool isInt() const {
return JSVAL_IS_INT(value_);
}
/*
* Returns true if the underlying value is a string.
*/
bool isString() const {
return JSVAL_IS_STRING(value_);
}
/*
* Check if the value is a JavaScript String object.
*/
bool isJavaScriptStringObject() const {
if (!isObject()) return false;
ensureStringClass();
return getObjectClass() == stringClass;
}
/*
* Return this value as a string, converting as necessary.
*/
JSString* asString() const {
return JS_ValueToString(currentContext(), value_);
}
/* Returns the string as a JSString pointer.
* Result is undefined if the value is not actually a string or String object.
*/
const JSString* getString() const {
if (JSVAL_IS_STRING(value_)) {
return JSVAL_TO_STRING(value_);
}
return asString();
}
/*
* Returns the string as a zero-terminated array of UTF16 characters.
* Note that this pointer may become invalid when JS performs GC, so it
* may only be used without calling other JS functions. Result is
* undefined if the value is not actually a string.
*/
const wchar_t* getStringChars() const {
return reinterpret_cast<const wchar_t*>(JS_GetStringChars(
const_cast<JSString*>(getString())));
}
/*
* Returns the length of the underlying string. Result is undefined
* if the value is not actually a string.
*/
int getStringLength() const {
return JS_GetStringLength(const_cast<JSString*>(getString()));
}
/*
* Sets the underlying value, defined by a null-terminated array of UTF16
* chars.
*
* Returns false on failure.
*/
bool setString(const wchar_t* utf16) {
JSString* str = JS_NewUCStringCopyZ(currentContext(),
reinterpret_cast<const jschar*>(utf16));
return setValue(STRING_TO_JSVAL(str));
}
/*
* Sets the underlying value, defined by a counted array of UTF16 chars.
*
* Returns false on failure.
*/
bool setString(const wchar_t* utf16, size_t len) {
JSString* str = JS_NewUCStringCopyN(currentContext(),
reinterpret_cast<const jschar*>(utf16), len);
return setValue(STRING_TO_JSVAL(str));
}
/*
* Returns true if the underlying value is an object.
*/
bool isObject() const {
return JSVAL_IS_OBJECT(value_);
}
/*
* Returns the underlying value as an object.
* Result is undefined if it is not actually an object.
*/
JSObject* getObject() const {
return isObject() ? JSVAL_TO_OBJECT(value_) : 0;
}
/*
* Returns the class name of the underlying value.
*
* Result is undefined if it is not actually an object.
*/
const JSClass* getObjectClass() const {
return isObject() ? JS_GET_CLASS(currentContext(), getObject()) : 0;
}
/*
* Sets the underlying value to be an object.
*
* Returns false on failure.
*/
bool setObject(JSObject* obj) {
return setValue(OBJECT_TO_JSVAL(obj));
}
/*
* Returns true if the underlying value is undefined (void).
*/
bool isUndefined() const {
return JSVAL_IS_VOID(value_);
}
/*
* Sets the underlying value to be undefined (void).
*
* Returns false on failure (impossible?)
*/
bool setUndefined() {
return setValue(JSVAL_VOID);
}
/*
* Returns true if the underlying value is null.
*/
bool isNull() const {
return JSVAL_IS_NULL(value_);
}
/*
* Sets the underlying value to be null.
*
* Returns false on failure (impossible?)
*/
bool setNull() {
return setValue(JSVAL_NULL);
}
};
#endif /*JNI_LINUX_JSROOTEDVALUE_H_*/