1 // Object.cpp: Implementation of ActionScript Object class, for Gnash.
2 //
3 // Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012
4 // Free Software Foundation, Inc
5 //
6 // This program is free software; you can redistribute it and/or modify
7 // it under the terms of the GNU General Public License as published by
8 // the Free Software Foundation; either version 3 of the License, or
9 // (at your option) any later version.
10 //
11 // This program is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 // GNU General Public License for more details.
15 //
16 // You should have received a copy of the GNU General Public License
17 // along with this program; if not, write to the Free Software
18 // Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
19 //
20
21 #include "Object.h"
22
23 #include <string>
24 #include <sstream>
25
26 #include "Movie.h"
27 #include "DisplayObject.h"
28 #include "fn_call.h"
29 #include "as_object.h"
30 #include "NativeFunction.h"
31 #include "movie_definition.h"
32 #include "sprite_definition.h"
33 #include "VM.h"
34 #include "namedStrings.h" // for NSV::PROP_TO_STRING
35 #include "Global_as.h"
36 #include "movie_root.h"
37 #include "log.h"
38
39 namespace gnash {
40
41 // Forward declarations
42 namespace {
43
44 as_value object_addproperty(const fn_call&);
45 as_value object_registerClass(const fn_call& fn);
46 as_value object_hasOwnProperty(const fn_call&);
47 as_value object_isPropertyEnumerable(const fn_call&);
48 as_value object_isPrototypeOf(const fn_call&);
49 as_value object_watch(const fn_call&);
50 as_value object_unwatch(const fn_call&);
51 as_value object_toLocaleString(const fn_call&);
52 as_value object_ctor(const fn_call& fn);
53 as_value object_valueOf(const fn_call& fn);
54 as_value object_toString(const fn_call& fn);
55
56 void attachObjectInterface(as_object& o);
57
58 }
59
60
61 void
registerObjectNative(as_object & global)62 registerObjectNative(as_object& global)
63 {
64 VM& vm = getVM(global);
65
66 vm.registerNative(object_watch, 101, 0);
67 vm.registerNative(object_unwatch, 101, 1);
68 vm.registerNative(object_addproperty, 101, 2);
69 vm.registerNative(object_valueOf, 101, 3);
70 vm.registerNative(object_toString, 101, 4);
71 vm.registerNative(object_hasOwnProperty, 101, 5);
72 vm.registerNative(object_isPrototypeOf, 101, 6);
73 vm.registerNative(object_isPropertyEnumerable, 101, 7);
74 vm.registerNative(object_registerClass, 101, 8);
75 vm.registerNative(object_ctor, 101, 9);
76 }
77
78 // extern (used by Global.cpp)
79 void
initObjectClass(as_object * proto,as_object & where,const ObjectURI & uri)80 initObjectClass(as_object* proto, as_object& where, const ObjectURI& uri)
81 {
82
83 assert(proto);
84
85 // Object is a native constructor.
86 VM& vm = getVM(where);
87 as_object* cl = vm.getNative(101, 9);
88 cl->init_member(NSV::PROP_PROTOTYPE, proto);
89 proto->init_member(NSV::PROP_CONSTRUCTOR, cl);
90
91 attachObjectInterface(*proto);
92
93 // The as_function ctor takes care of initializing these, but they
94 // are different for the Object class.
95 const int readOnly = PropFlags::readOnly;
96 cl->set_member_flags(NSV::PROP_uuPROTOuu, readOnly);
97 cl->set_member_flags(NSV::PROP_CONSTRUCTOR, readOnly);
98 cl->set_member_flags(NSV::PROP_PROTOTYPE, readOnly);
99
100 const int readOnlyFlags = as_object::DefaultFlags | PropFlags::readOnly;
101 cl->init_member("registerClass", vm.getNative(101, 8), readOnlyFlags);
102
103 // Register _global.Object (should only be visible in SWF5 up)
104 int flags = PropFlags::dontEnum;
105 where.init_member(uri, cl, flags);
106
107 }
108
109
110 namespace {
111
112 void
attachObjectInterface(as_object & o)113 attachObjectInterface(as_object& o)
114 {
115 VM& vm = getVM(o);
116
117 // We register natives despite swf version,
118 Global_as& gl = getGlobal(o);
119
120 o.init_member("valueOf", vm.getNative(101, 3));
121 o.init_member("toString", vm.getNative(101, 4));
122 o.init_member("toLocaleString", gl.createFunction(object_toLocaleString));
123
124 int swf6flags = PropFlags::dontEnum |
125 PropFlags::dontDelete |
126 PropFlags::onlySWF6Up;
127
128 o.init_member("addProperty", vm.getNative(101, 2), swf6flags);
129 o.init_member("hasOwnProperty", vm.getNative(101, 5), swf6flags);
130 o.init_member("isPropertyEnumerable", vm.getNative(101, 7), swf6flags);
131 o.init_member("isPrototypeOf", vm.getNative(101, 6), swf6flags);
132 o.init_member("watch", vm.getNative(101, 0), swf6flags);
133 o.init_member("unwatch", vm.getNative(101, 1), swf6flags);
134 }
135
136
137 as_value
object_ctor(const fn_call & fn)138 object_ctor(const fn_call& fn)
139 {
140
141 if (fn.nargs == 1) {
142 as_object* obj = toObject(fn.arg(0), getVM(fn));
143 if (obj) return as_value(obj);
144 }
145
146 if (fn.nargs > 1) {
147 IF_VERBOSE_ASCODING_ERRORS(
148 log_aserror(_("Too many args to Object constructor"));
149 );
150 }
151
152 Global_as& gl = getGlobal(fn);
153
154 if (!fn.isInstantiation()) {
155 return new as_object(gl);
156 }
157
158 return as_value();
159
160 }
161
162 /// Object.toString returns one of two values: [type Function] if it is a
163 /// function, [object Object] if it is an object. Gnash wrongly regards super
164 /// as a function, so we have to handle that too.
165 as_value
object_toString(const fn_call & fn)166 object_toString(const fn_call& fn)
167 {
168 as_object* obj = ensure<ValidThis>(fn);
169 return as_value(obj->stringValue());
170 }
171
172 as_value
object_valueOf(const fn_call & fn)173 object_valueOf(const fn_call& fn)
174 {
175 return fn.this_ptr;
176 }
177
178
179 /// The return value is not dependent on the result of add_property (though
180 /// this is always true anyway), but rather on the validity of the arguments.
181 as_value
object_addproperty(const fn_call & fn)182 object_addproperty(const fn_call& fn)
183 {
184 as_object* obj = ensure<ValidThis>(fn);
185
186 /// Extra arguments are just ignored.
187 if ( fn.nargs < 3 )
188 {
189 IF_VERBOSE_ASCODING_ERRORS(
190 std::stringstream ss;
191 fn.dump_args(ss);
192 log_aserror(_("Invalid call to Object.addProperty(%s) - "
193 "expected 3 arguments (<name>, <getter>, <setter>)"),
194 ss.str());
195 );
196
197 // if we've been given more args then needed there's
198 // no need to abort here
199 if ( fn.nargs < 3 )
200 {
201 return as_value(false);
202 }
203 }
204
205 const std::string& propname = fn.arg(0).to_string();
206 if (propname.empty())
207 {
208 IF_VERBOSE_ASCODING_ERRORS(
209 log_aserror(_("Invalid call to Object.addProperty() - "
210 "empty property name"));
211 );
212 return as_value(false);
213 }
214
215 as_function* getter = fn.arg(1).to_function();
216 if (!getter)
217 {
218 IF_VERBOSE_ASCODING_ERRORS(
219 log_aserror(_("Invalid call to Object.addProperty() - "
220 "getter is not an AS function"));
221 );
222 return as_value(false);
223 }
224
225 as_function* setter = nullptr;
226 const as_value& setterval = fn.arg(2);
227 if (!setterval.is_null())
228 {
229 setter = setterval.to_function();
230 if (!setter)
231 {
232 IF_VERBOSE_ASCODING_ERRORS(
233 log_aserror(_("Invalid call to Object.addProperty() - "
234 "setter is not null and not an AS function (%s)"),
235 setterval);
236 );
237 return as_value(false);
238 }
239 }
240
241 // Now that we checked everything, let's call the as_object
242 // interface for getter/setter properties :)
243 obj->add_property(propname, *getter, setter);
244
245 return as_value(true);
246 }
247
248
249 as_value
object_registerClass(const fn_call & fn)250 object_registerClass(const fn_call& fn)
251 {
252
253 if (fn.nargs != 2) {
254 IF_VERBOSE_ASCODING_ERRORS(
255 std::stringstream ss;
256 fn.dump_args(ss);
257 log_aserror(_("Invalid call to Object.registerClass(%s) - "
258 "expected 2 arguments (<symbol>, <constructor>)"),
259 ss.str());
260 );
261
262 // if we've been given more args then needed there's
263 // no need to abort here
264 if (fn.nargs < 2) {
265 return as_value(false);
266 }
267 }
268
269 const std::string& symbolid = fn.arg(0).to_string();
270 if (symbolid.empty()) {
271 IF_VERBOSE_ASCODING_ERRORS(
272 std::stringstream ss;
273 fn.dump_args(ss);
274 log_aserror(_("Invalid call to Object.registerClass(%s) - "
275 "first argument (symbol id) evaluates to empty string"),
276 ss.str());
277 );
278 return as_value(false);
279 }
280
281 as_function* theclass = fn.arg(1).to_function();
282 if (!theclass) {
283 IF_VERBOSE_ASCODING_ERRORS(
284 std::stringstream ss;
285 fn.dump_args(ss);
286 log_aserror(_("Invalid call to Object.registerClass(%s) - "
287 "second argument (class) is not a function)"), ss.str());
288 );
289 return as_value(false);
290 }
291
292 // Find the exported resource
293
294 // Using definition of current target fixes the youtube beta case
295 // https://savannah.gnu.org/bugs/index.php?23130
296 DisplayObject* tgt = fn.env().target();
297 if (!tgt) {
298 log_error(_("current environment has no target, wouldn't know "
299 "where to look for symbol required for registerClass"));
300 return as_value(false);
301 }
302
303 Movie* relRoot = tgt->get_root();
304 assert(relRoot);
305 const movie_definition* def = relRoot->definition();
306
307 // We only care about definitions, not other exportable resources.
308 const std::uint16_t id = def->exportID(symbolid);
309 SWF::DefinitionTag* d = def->getDefinitionTag(id);
310
311 if (!d) {
312 IF_VERBOSE_ASCODING_ERRORS(
313 log_aserror(_("Object.registerClass('%s', %s): "
314 "can't find exported symbol (id: %d)"),
315 symbolid, typeName(theclass), id);
316 );
317 return as_value(false);
318 }
319
320 // Check that the exported resource is a sprite_definition
321 // (we're looking for a MovieClip symbol)
322 sprite_definition* exp_clipdef(dynamic_cast<sprite_definition*>(d));
323
324 if (!exp_clipdef) {
325 IF_VERBOSE_ASCODING_ERRORS(
326 log_aserror(_("Object.registerClass(%s, %s): "
327 "exported symbol is not a MovieClip symbol "
328 "(sprite_definition), but a %s"),
329 symbolid, typeName(theclass), typeName(d));
330 );
331 return as_value(false);
332 }
333
334 movie_root& mr = getRoot(fn);
335 mr.registerClass(exp_clipdef, theclass);
336 return as_value(true);
337 }
338
339
340 as_value
object_hasOwnProperty(const fn_call & fn)341 object_hasOwnProperty(const fn_call& fn)
342 {
343 as_object* obj = ensure<ValidThis>(fn);
344
345 if ( fn.nargs < 1 )
346 {
347 IF_VERBOSE_ASCODING_ERRORS(
348 log_aserror(_("Object.hasOwnProperty() requires one arg"));
349 );
350 return as_value(false);
351 }
352 const as_value& arg = fn.arg(0);
353 const std::string& propname = arg.to_string();
354 if (arg.is_undefined() || propname.empty())
355 {
356 IF_VERBOSE_ASCODING_ERRORS(
357 log_aserror(_("Invalid call to Object.hasOwnProperty('%s')"), arg);
358 );
359 return as_value(false);
360 }
361
362 const bool found = hasOwnProperty(*obj, getURI(getVM(fn), propname));
363 return as_value(found);
364 }
365
366 as_value
object_isPropertyEnumerable(const fn_call & fn)367 object_isPropertyEnumerable(const fn_call& fn)
368 {
369 as_object* obj = ensure<ValidThis>(fn);
370
371 if (!fn.nargs) {
372 IF_VERBOSE_ASCODING_ERRORS(
373 log_aserror(_("Object.isPropertyEnumerable() requires one arg"));
374 );
375 return as_value(false);
376 }
377
378 const as_value& arg = fn.arg(0);
379 const std::string& propname = arg.to_string();
380 if (arg.is_undefined() || propname.empty()) {
381 IF_VERBOSE_ASCODING_ERRORS(
382 log_aserror(_("Invalid call to Object.isPropertyEnumerable('%s')"),
383 arg);
384 );
385 return as_value();
386 }
387
388 Property* prop = obj->getOwnProperty(getURI(getVM(fn),propname));
389
390 if (!prop) {
391 return as_value(false);
392 }
393
394 return as_value(!prop->getFlags().test<PropFlags::dontEnum>());
395 }
396
397
398 as_value
object_isPrototypeOf(const fn_call & fn)399 object_isPrototypeOf(const fn_call& fn)
400 {
401 as_object* obj = ensure<ValidThis>(fn);
402
403 if (fn.nargs < 1) {
404 IF_VERBOSE_ASCODING_ERRORS(
405 log_aserror(_("Object.isPrototypeOf() requires one arg"));
406 );
407 return as_value(false);
408 }
409
410 as_object* arg = toObject(fn.arg(0), getVM(fn));
411 if (!arg) {
412 IF_VERBOSE_ASCODING_ERRORS(
413 log_aserror(_("First arg to Object.isPrototypeOf(%s) is "
414 "not an object"), fn.arg(0));
415 );
416 return as_value(false);
417 }
418
419 return as_value(obj->prototypeOf(*arg));
420
421 }
422
423
424 as_value
object_watch(const fn_call & fn)425 object_watch(const fn_call& fn)
426 {
427 as_object* obj = ensure<ValidThis>(fn);
428
429 if (fn.nargs < 2) {
430 IF_VERBOSE_ASCODING_ERRORS(
431 std::stringstream ss; fn.dump_args(ss);
432 log_aserror(_("Object.watch(%s): missing arguments"));
433 );
434 return as_value(false);
435 }
436
437 const as_value& propval = fn.arg(0);
438 const as_value& funcval = fn.arg(1);
439
440 if (!funcval.is_function()) {
441 IF_VERBOSE_ASCODING_ERRORS(
442 std::stringstream ss; fn.dump_args(ss);
443 log_aserror(_("Object.watch(%s): second argument is not a "
444 "function"));
445 );
446 return as_value(false);
447 }
448
449 VM& vm = getVM(fn);
450
451 std::string propname = propval.to_string();
452 const ObjectURI& propkey = getURI(vm, propname);
453 as_function* trig = funcval.to_function();
454 const as_value cust = fn.nargs > 2 ? fn.arg(2) : as_value();
455
456 return as_value(obj->watch(propkey, *trig, cust));
457 }
458
459
460 as_value
object_unwatch(const fn_call & fn)461 object_unwatch(const fn_call& fn)
462 {
463 as_object* obj = ensure<ValidThis>(fn);
464
465 if ( fn.nargs < 1 )
466 {
467 IF_VERBOSE_ASCODING_ERRORS(
468 std::stringstream ss; fn.dump_args(ss);
469 log_aserror(_("Object.unwatch(%s): missing argument"));
470 );
471 return as_value(false);
472 }
473
474 const as_value& propval = fn.arg(0);
475
476 VM& vm = getVM(fn);
477
478 std::string propname = propval.to_string();
479 const ObjectURI& propkey = getURI(vm, propname);
480
481 return as_value(obj->unwatch(propkey));
482 }
483
484
485 as_value
object_toLocaleString(const fn_call & fn)486 object_toLocaleString(const fn_call& fn)
487 {
488 as_object* obj = ensure<ValidThis>(fn);
489 return callMethod(obj, NSV::PROP_TO_STRING);
490 }
491
492 } // anonymous namespace
493 } // namespace gnash
494