1 // AsBroadcaster.cpp - AsBroadcaster AS interface
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 "AsBroadcaster.h"
22
23 #include "Array_as.h" // for _listeners construction
24 #include "log.h"
25 #include "fn_call.h"
26 #include "NativeFunction.h"
27 #include "Global_as.h"
28 #include "namedStrings.h"
29 #include "ObjectURI.h"
30
31 //#define GNASH_DEBUG_BROADCASTER 1
32
33 #ifdef GNASH_DEBUG_BROADCASTER
34 # include "Stats.h"
35 #endif
36
37 namespace gnash {
38
39 // Forward declarations.
40 namespace {
41 as_value asbroadcaster_addListener(const fn_call& fn);
42 as_value asbroadcaster_removeListener(const fn_call& fn);
43 as_value asbroadcaster_broadcastMessage(const fn_call& fn);
44 as_value asbroadcaster_initialize(const fn_call& fn);
45 }
46
47
48 /// Helper for notifying listeners
49 namespace {
50
51 #ifdef GNASH_DEBUG_BROADCASTER
52 struct BroadcasterStats {
53 typedef std::map<ObjectURI&, unsigned long int> Stat;
54 Stat stat;
55 const VM& _st;
BroadcasterStatsgnash::__anon25c706e80211::BroadcasterStats56 BroadcasterStats(const VM& vm) : _st(st) {}
checkgnash::__anon25c706e80211::BroadcasterStats57 void check(ObjectURI& k) {
58 if ( ! (++stat[k] % 100) ) dump();
59 }
dumpgnash::__anon25c706e80211::BroadcasterStats60 void dump() {
61 using namespace std;
62 typedef std::map<unsigned long int, ObjectURI&> Sorted;
63 Sorted sorted;
64 for (Stat::iterator i=stat.begin(), e=stat.end(); i!=e; ++i)
65 sorted[i->second] = i->first;
66 cerr << "Broadcaster stats follow:" << endl;
67 for (Sorted::reverse_iterator i=sorted.rbegin(), e=sorted.rend();
68 i!=e; ++i)
69 std::cerr
70 << std::setw(10)
71 << i->first
72 << ":"
73 << _st.value(i->second) << "("
74 << i->second << ")"
75 << std::endl;
76
77 }
78 };
79 #endif // GNASH_DEBUG_BROADCASTER
80
81 class BroadcasterVisitor
82 {
83 public:
84
85 /// @param eName name of event, will be converted to lowercase if needed
86 ///
87 /// @param env Environment to use for functions invocation.
88 ///
BroadcasterVisitor(const fn_call & fn)89 BroadcasterVisitor(const fn_call& fn)
90 :
91 _eventURI(getURI(getVM(fn),fn.arg(0).to_string())),
92 _dispatched(0),
93 _fn(fn)
94 {
95 _fn.drop_bottom();
96 }
97
98 /// Call a method on the given value
operator ()(const as_value & v)99 void operator()(const as_value& v)
100 {
101
102 as_object* o = toObject(v, getVM(_fn));
103 if (!o) return;
104
105 #ifdef GNASH_DEBUG_BROADCASTER
106 static stats::KeyLookup stats("BroadcasterVisitor call operator",
107 getVM(_fn), 1);
108 stats.check(_eventURI.name);
109 #endif
110 as_value method;
111 o->get_member(_eventURI, &method);
112
113 if (method.is_function()) {
114 _fn.super = o->get_super(_eventURI);
115 _fn.this_ptr = o;
116 method.to_function()->call(_fn);
117 }
118
119 ++_dispatched;
120 }
121
122 /// Return number of events dispatched.
eventsDispatched() const123 size_t eventsDispatched() const { return _dispatched; }
124
125 private:
126
127 /// Name of the event being broadcasted
128 /// appropriately cased based on SWF version
129 /// of the current VM
130 const ObjectURI _eventURI;
131
132 /// Number of event dispatches
133 size_t _dispatched;
134
135 fn_call _fn;
136
137 };
138
139 }
140
141 /// AsBroadcaster class
142
143
144 void
initialize(as_object & o)145 AsBroadcaster::initialize(as_object& o)
146 {
147 Global_as& gl = getGlobal(o);
148
149 // Find _global.AsBroadcaster.
150 as_object* asb = toObject(
151 getMember(gl, NSV::CLASS_AS_BROADCASTER), getVM(o));
152
153 // If it's not an object, these are left undefined, but they are
154 // always attached to the initialized object.
155 as_value al, rl;
156
157 const int flags = as_object::DefaultFlags;
158
159 if (asb) {
160 al = getMember(*asb, NSV::PROP_ADD_LISTENER);
161 rl = getMember(*asb, NSV::PROP_REMOVE_LISTENER);
162 }
163
164 o.set_member(NSV::PROP_ADD_LISTENER, al);
165 o.set_member(NSV::PROP_REMOVE_LISTENER, rl);
166
167 // The function returned by ASnative(101, 12) is attached, even though
168 // this may not exist (e.g. if _global.ASnative is altered)
169 const as_value& asn = callMethod(&gl, NSV::PROP_AS_NATIVE, 101, 12);
170 o.set_member(NSV::PROP_BROADCAST_MESSAGE, asn);
171
172 // This corresponds to "_listeners = [];", which is different from
173 // _listeners = new Array();
174 o.set_member(NSV::PROP_uLISTENERS, gl.createArray());
175
176 // This function should call ASSetPropFlags on these four properties.
177 o.set_member_flags(NSV::PROP_BROADCAST_MESSAGE, flags);
178 o.set_member_flags(NSV::PROP_ADD_LISTENER, flags);
179 o.set_member_flags(NSV::PROP_REMOVE_LISTENER, flags);
180 o.set_member_flags(NSV::PROP_uLISTENERS, flags);
181
182 }
183
184 void
attachAsBroadcasterStaticInterface(as_object & o)185 attachAsBroadcasterStaticInterface(as_object& o)
186 {
187 const int flags = PropFlags::dontEnum |
188 PropFlags::dontDelete |
189 PropFlags::onlySWF6Up;
190
191 Global_as& gl = getGlobal(o);
192
193 o.init_member("initialize",
194 gl.createFunction(asbroadcaster_initialize), flags);
195 o.init_member(NSV::PROP_ADD_LISTENER,
196 gl.createFunction(asbroadcaster_addListener), flags);
197 o.init_member(NSV::PROP_REMOVE_LISTENER,
198 gl.createFunction(asbroadcaster_removeListener), flags);
199
200 VM& vm = getVM(o);
201 o.init_member(NSV::PROP_BROADCAST_MESSAGE, vm.getNative(101, 12),
202 flags);
203
204 }
205
206
207 void
registerNative(as_object & global)208 AsBroadcaster::registerNative(as_object& global)
209 {
210 VM& vm = getVM(global);
211 vm.registerNative(asbroadcaster_broadcastMessage, 101, 12);
212 }
213
214
215 void
init(as_object & where,const ObjectURI & uri)216 AsBroadcaster::init(as_object& where, const ObjectURI& uri)
217 {
218 // AsBroadcaster is a class, even though it doesn't look much like one.
219 // Its prototype has no properties.
220 registerBuiltinClass(where, emptyFunction, nullptr,
221 attachAsBroadcasterStaticInterface, uri);
222 }
223
224
225 namespace {
226
227 as_value
asbroadcaster_initialize(const fn_call & fn)228 asbroadcaster_initialize(const fn_call& fn)
229 {
230 if ( fn.nargs < 1 )
231 {
232 IF_VERBOSE_ASCODING_ERRORS(
233 log_aserror(_("AsBroadcaster.initialize() requires one argument, "
234 "none given"));
235 );
236 return as_value();
237 }
238
239 // TODO: check if automatic primitive to object conversion apply here
240 const as_value& tgtval = fn.arg(0);
241 if (!tgtval.is_object()) {
242 IF_VERBOSE_ASCODING_ERRORS(
243 log_aserror(_("AsBroadcaster.initialize(%s): first arg is "
244 "not an object"), tgtval);
245 );
246 return as_value();
247 }
248
249 as_object* tgt = toObject(tgtval, getVM(fn));
250 if (!tgt) {
251 IF_VERBOSE_ASCODING_ERRORS(
252 log_aserror(_("AsBroadcaster.initialize(%s): first arg is an object"
253 " but doesn't cast to one (dangling DisplayObject ref?)"), tgtval);
254 );
255 return as_value();
256 }
257
258 AsBroadcaster::initialize(*tgt);
259
260 return as_value();
261 }
262 as_value
asbroadcaster_addListener(const fn_call & fn)263 asbroadcaster_addListener(const fn_call& fn)
264 {
265
266 as_object* obj = ensure<ValidThis>(fn);
267
268 as_value newListener; assert(newListener.is_undefined());
269 if ( fn.nargs ) newListener = fn.arg(0);
270
271 callMethod(obj, NSV::PROP_REMOVE_LISTENER, newListener);
272
273 as_value listenersValue;
274
275 // TODO: test if we're supposed to crawl the target object's
276 // inheritance chain in case it's own property _listeners
277 // has been deleted while another one is found in any base
278 // class.
279 if (!obj->get_member(NSV::PROP_uLISTENERS, &listenersValue)) {
280 IF_VERBOSE_ASCODING_ERRORS(
281 std::ostringstream ss; fn.dump_args(ss);
282 log_aserror(_("%p.addListener(%s): this object has no "
283 "_listeners member"), (void*)fn.this_ptr, ss.str());
284 );
285 return as_value(true); // odd, but seems the case..
286 }
287
288 // assuming no automatic primitive-to-object cast will return an array...
289 if (!listenersValue.is_object())
290 {
291 IF_VERBOSE_ASCODING_ERRORS(
292 std::ostringstream ss; fn.dump_args(ss);
293 log_aserror(_("%p.addListener(%s): this object's _listener isn't "
294 "an object: %s"), (void*)fn.this_ptr, ss.str(),
295 listenersValue);
296 );
297 // TODO: check this
298 return as_value(false);
299 }
300
301 as_object* listeners = toObject(listenersValue, getVM(fn));
302
303 // We checked is_object() above.
304 assert(listeners);
305
306 callMethod(listeners, NSV::PROP_PUSH, newListener);
307
308 return as_value(true);
309
310 }
311
312
313 as_value
asbroadcaster_removeListener(const fn_call & fn)314 asbroadcaster_removeListener(const fn_call& fn)
315 {
316 as_object* obj = ensure<ValidThis>(fn);
317
318 as_value listenersValue;
319
320 // TODO: test if we're supposed to crawl the target object's
321 // inheritance chain in case it's own property _listeners
322 // has been deleted while another one is found in any base
323 // class.
324 if (!obj->get_member(NSV::PROP_uLISTENERS, &listenersValue)) {
325 IF_VERBOSE_ASCODING_ERRORS(
326 std::ostringstream ss; fn.dump_args(ss);
327 log_aserror(_("%p.addListener(%s): this object has no _listeners "
328 "member"), (void*)fn.this_ptr, ss.str());
329 );
330 return as_value(false); // TODO: check this
331 }
332
333 // assuming no automatic primitive-to-object cast will return an array...
334 if ( ! listenersValue.is_object() )
335 {
336 IF_VERBOSE_ASCODING_ERRORS(
337 std::ostringstream ss; fn.dump_args(ss);
338 log_aserror(_("%p.addListener(%s): this object's _listener isn't "
339 "an object: %s"), (void*)fn.this_ptr, ss.str(),
340 listenersValue);
341 );
342 return as_value(false); // TODO: check this
343 }
344
345 as_object* listeners = toObject(listenersValue, getVM(fn));
346 assert(listeners);
347
348 as_value listenerToRemove;
349 if (fn.nargs) listenerToRemove = fn.arg(0);
350
351 // Remove the first listener matching the new value
352 // See http://www.senocular.com/flash/tutorials/
353 // listenersasbroadcaster/?page=2
354
355 // This is an ActionScript-like implementation, which is why it looks
356 // like poor C++.
357 const int length = toInt(getMember(*listeners, NSV::PROP_LENGTH),
358 getVM(fn));
359 int i = 0;
360
361 VM& vm = getVM(fn);
362 const ObjectURI& propSplice = getURI(vm, NSV::PROP_SPLICE);
363
364 while (i < length) {
365 std::ostringstream s;
366 s << i;
367 as_value el = getMember(*listeners, getURI(vm, s.str()));
368 if (equals(el, listenerToRemove, getVM(fn))) {
369 callMethod(listeners, propSplice, s.str(), 1);
370 return as_value(true);
371 }
372 ++i;
373 }
374 return as_value(false);
375
376 }
377
378
379 as_value
asbroadcaster_broadcastMessage(const fn_call & fn)380 asbroadcaster_broadcastMessage(const fn_call& fn)
381 {
382 as_object* obj = ensure<ValidThis>(fn);
383
384 as_value listenersValue;
385
386 // TODO: test if we're supposed to crawl the target object's
387 // inheritance chain in case its own property _listeners
388 // has been deleted while another one is found in any base
389 // class.
390 if (!obj->get_member(NSV::PROP_uLISTENERS, &listenersValue)) {
391 IF_VERBOSE_ASCODING_ERRORS(
392 std::ostringstream ss; fn.dump_args(ss);
393 log_aserror(_("%p.addListener(%s): this object has no "
394 "_listeners member"), obj, ss.str());
395 );
396 return as_value(); // TODO: check this
397 }
398
399 // assuming no automatic primitive-to-object cast will return an array...
400 if ( ! listenersValue.is_object() )
401 {
402 IF_VERBOSE_ASCODING_ERRORS(
403 std::ostringstream ss; fn.dump_args(ss);
404 log_aserror(_("%p.addListener(%s): this object's _listener "
405 "isn't an object: %s"), (void*)fn.this_ptr,
406 ss.str(), listenersValue);
407 );
408 return as_value(); // TODO: check this
409 }
410
411 as_object* listeners = toObject(listenersValue, getVM(fn));
412
413 if (!fn.nargs) {
414 IF_VERBOSE_ASCODING_ERRORS(
415 log_aserror(_("%p.broadcastMessage() needs an argument"),
416 (void*)fn.this_ptr);
417 );
418 return as_value();
419 }
420
421 BroadcasterVisitor visitor(fn);
422 foreachArray(*listeners, visitor);
423
424 const size_t dispatched = visitor.eventsDispatched();
425
426 if (dispatched) return as_value(true);
427
428 return as_value();
429
430 }
431
432 } // anonymous namespace
433
434 } // end of gnash namespace
435