JS_SetWatchPoint(JSContext *cx, JSObject *obj, jsval id, JSWatchPointHandler handler, void *closure) { JSAtom *atom; jsid propid; JSObject *pobj; JSScopeProperty *sprop; JSRuntime *rt; JSWatchPoint *wp; JSPropertyOp watcher; if (!OBJ_IS_NATIVE(obj)) { JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_CANT_WATCH, OBJ_GET_CLASS(cx, obj)->name); return JS_FALSE; } if (JSVAL_IS_INT(id)) { propid = (jsid)id; atom = NULL; } else { atom = js_ValueToStringAtom(cx, id); if (!atom) return JS_FALSE; propid = (jsid)atom; } if (!js_LookupProperty(cx, obj, propid, &pobj, (JSProperty **)&sprop)) return JS_FALSE; rt = cx->runtime; if (!sprop) { /* Check for a deleted symbol watchpoint, which holds its property. */ sprop = js_FindWatchPoint(rt, OBJ_SCOPE(obj), propid); if (!sprop) { /* Make a new property in obj so we can watch for the first set. */ if (!js_DefineProperty(cx, obj, propid, JSVAL_VOID, NULL, NULL, JSPROP_ENUMERATE, (JSProperty **)&sprop)) { sprop = NULL; } } } else if (pobj != obj) { /* Clone the prototype property so we can watch the right object. */ jsval value; JSPropertyOp getter, setter; uintN attrs; if (OBJ_IS_NATIVE(pobj)) { value = SPROP_HAS_VALID_SLOT(sprop, OBJ_SCOPE(pobj)) ? LOCKED_OBJ_GET_SLOT(pobj, sprop->slot) : JSVAL_VOID; getter = sprop->getter; setter = sprop->setter; attrs = sprop->attrs; } else { if (!OBJ_GET_PROPERTY(cx, pobj, id, &value)) { OBJ_DROP_PROPERTY(cx, pobj, (JSProperty *)sprop); return JS_FALSE; } getter = setter = JS_PropertyStub; attrs = JSPROP_ENUMERATE; } OBJ_DROP_PROPERTY(cx, pobj, (JSProperty *)sprop); if (!js_DefineProperty(cx, obj, propid, value, getter, setter, attrs, (JSProperty **)&sprop)) { sprop = NULL; } } if (!sprop) return JS_FALSE; wp = FindWatchPoint(rt, OBJ_SCOPE(obj), propid); if (!wp) { watcher = js_WrapWatchedSetter(cx, propid, sprop->attrs, sprop->setter); if (!watcher) return JS_FALSE; wp = (JSWatchPoint *) JS_malloc(cx, sizeof *wp); if (!wp) return JS_FALSE; wp->handler = NULL; wp->closure = NULL; if (!js_AddRoot(cx, &wp->closure, "wp->closure")) { JS_free(cx, wp); return JS_FALSE; } JS_APPEND_LINK(&wp->links, &rt->watchPointList); wp->object = obj; wp->sprop = sprop; JS_ASSERT(sprop->setter != js_watch_set); wp->setter = sprop->setter; wp->nrefs = 1; sprop = js_ChangeNativePropertyAttrs(cx, obj, sprop, 0, sprop->attrs, sprop->getter, watcher); if (!sprop) return DropWatchPoint(cx, wp); } wp->handler = handler; wp->closure = closure; OBJ_DROP_PROPERTY(cx, obj, (JSProperty *)sprop); return JS_TRUE; }
JSProperty * js_SetProperty(JSContext *cx, JSObject *obj, jsval id, jsval *vp) { JSRuntime *rt; JSScope *scope, *protoScope; JSProperty *prop, *protoProp; PRHashNumber hash; JSSymbol *sym, *protoSym; JSObject *proto, *assignobj; jsval pval, aval, rval; jsint slot; JSErrorReporter older; JSString *str; rt = cx->runtime; PR_ASSERT(JS_IS_RUNTIME_LOCKED(rt)); #ifdef SCOPE_TABLE_NOTYET /* XXX hash recompute */ /* XXX protoProp->getter, protoProp->setter, protoProp->flags */ scope = js_MutateScope(cx, obj, id, getter, setter, flags, &prop); #endif scope = js_GetMutableScope(cx, obj); if (!scope) return NULL; /* Handle old bug that treated empty string as zero index. */ CHECK_FOR_FUNNY_INDEX(id); hash = js_HashValue(id); sym = scope->ops->lookup(cx, scope, id, hash); if (sym) { prop = sym_property(sym); #if JS_HAS_OBJ_WATCHPOINT if (!prop) { /* * Deleted property place-holder, could have a watchpoint that * holds the deleted-but-watched property. */ prop = js_FindWatchPoint(rt, obj, js_IdToValue(id)); } #endif } else { prop = NULL; } if (!prop) { /* Find a prototype property with the same id. */ proto = OBJ_GET_PROTO(obj); protoProp = NULL; while (proto) { protoScope = (JSScope *)proto->map; protoSym = protoScope->ops->lookup(cx, protoScope, id, hash); if (protoSym) { protoProp = sym_property(protoSym); if (protoProp) break; } proto = OBJ_GET_PROTO(proto); } /* Make a new property descriptor with the right heritage. */ if (protoProp) { prop = js_NewProperty(cx, scope, id, protoProp->getter, protoProp->setter, protoProp->flags | JSPROP_ENUMERATE); prop->id = protoProp->id; } else { prop = js_NewProperty(cx, scope, id, scope->map.clasp->getProperty, scope->map.clasp->setProperty, JSPROP_ENUMERATE); } if (!prop) return NULL; if (!obj->map->clasp->addProperty(cx, obj, prop->id, vp)) { js_DestroyProperty(cx, prop); return NULL; } /* Initialize new properties to undefined. */ obj->slots[prop->slot] = JSVAL_VOID; if (sym) { /* Null-valued symbol left behind from a delete operation. */ sym->entry.value = js_HoldProperty(cx, prop); } } if (!sym) { /* Need a new symbol as well as a new property. */ sym = scope->ops->add(cx, scope, id, prop); if (!sym) return NULL; #if JS_BUG_AUTO_INDEX_PROPS { jsval id2 = INT_TO_JSVAL(prop->slot - JSSLOT_START); if (!scope->ops->add(cx, scope, id2, prop)) { scope->ops->remove(cx, scope, id); return NULL; } PROPERTY_CACHE_FILL(cx, &rt->propertyCache, obj, id2, prop); } #endif PROPERTY_CACHE_FILL(cx, &rt->propertyCache, obj, id, prop); } /* Get the current property value from its slot. */ PR_ASSERT(prop->slot < obj->map->freeslot); slot = prop->slot; pval = obj->slots[slot]; /* Evil overloaded operator assign() hack. */ if (JSVAL_IS_OBJECT(pval)) { assignobj = JSVAL_TO_OBJECT(pval); if (assignobj) { older = JS_SetErrorReporter(cx, NULL); if (js_GetProperty(cx, assignobj, (jsval)rt->atomState.assignAtom, &aval) && JSVAL_IS_FUNCTION(aval) && js_Call(cx, assignobj, aval, 1, vp, &rval)) { *vp = rval; JS_SetErrorReporter(cx, older); prop->flags |= JSPROP_ASSIGNHACK; return prop; } JS_SetErrorReporter(cx, older); } } /* Check for readonly *after* the assign() hack. */ if (prop->flags & JSPROP_READONLY) { if (!JSVERSION_IS_ECMA(cx->version)) { str = js_ValueToSource(cx, js_IdToValue(id)); if (str) { JS_ReportError(cx, "%s is read-only", JS_GetStringBytes(str)); } } return NULL; } /* Let the setter modify vp before copying from it to obj->slots[slot]. */ if (!prop->setter(cx, obj, prop->id, vp)) return NULL; GC_POKE(cx, pval); obj->slots[slot] = *vp; /* Setting a property makes it enumerable. */ prop->flags |= JSPROP_ENUMERATE; return prop; }