1/******************************************************************************
2 Copyright 2012 Google Inc. All Rights Reserved.
3 Author: yugui@google.com (Yugui Sonoda)
4 ******************************************************************************/
5
6#include <stdlib.h>
7#include <stdio.h>
8#include <string.h>
9#include <pthread.h>
10#include <sys/stat.h>
11#include <fcntl.h>
12#include <pthread.h>
13#include "ppapi/c/pp_errors.h"
14#include "ppapi/c/pp_module.h"
15#include "ppapi/c/pp_var.h"
16#include "ppapi/c/ppb.h"
17#include "ppapi/c/ppb_core.h"
18#include "ppapi/c/ppb_file_ref.h"
19#include "ppapi/c/ppb_instance.h"
20#include "ppapi/c/ppb_messaging.h"
21#include "ppapi/c/ppb_url_loader.h"
22#include "ppapi/c/ppb_url_request_info.h"
23#include "ppapi/c/ppb_url_response_info.h"
24#include "ppapi/c/ppb_var.h"
25#include "ppapi/c/ppp.h"
26#include "ppapi/c/ppp_instance.h"
27#include "ppapi/c/ppp_messaging.h"
28
29#include "verconf.h"
30#include "ruby/ruby.h"
31#include "version.h"
32#include "gc.h"
33
34#ifdef HAVE_STRUCT_PPB_CORE
35typedef struct PPB_Core PPB_Core;
36#endif
37#ifdef HAVE_STRUCT_PPB_MESSAGING
38typedef struct PPB_Messaging PPB_Messaging;
39#endif
40#ifdef HAVE_STRUCT_PPB_VAR
41typedef struct PPB_Var PPB_Var;
42#endif
43#ifdef HAVE_STRUCT_PPB_URLLOADER
44typedef struct PPB_URLLoader PPB_URLLoader;
45#endif
46#ifdef HAVE_STRUCT_PPB_URLREQUESTINFO
47typedef struct PPB_URLRequestInfo PPB_URLRequestInfo;
48#endif
49#ifdef HAVE_STRUCT_PPB_URLRESPONSEINFO
50typedef struct PPB_URLResponseInfo PPB_URLResponseInfo;
51#endif
52#ifdef HAVE_STRUCT_PPP_INSTANCE
53typedef struct PPP_Instance PPP_Instance;
54#endif
55
56static PP_Module module_id = 0;
57static PPB_Core* core_interface = NULL;
58static PPB_Messaging* messaging_interface = NULL;
59static PPB_Var* var_interface = NULL;
60static PPB_URLLoader* loader_interface = NULL;
61static PPB_URLRequestInfo* request_interface = NULL;
62static PPB_URLResponseInfo* response_interface = NULL;
63static PPB_FileRef* fileref_interface = NULL;
64static struct st_table* instance_data = NULL;
65
66static VALUE instance_table = Qundef;
67
68static PP_Instance current_instance = 0;
69
70/******************************************************************************
71 * State of instance
72 ******************************************************************************/
73
74static void inst_mark(void *const ptr);
75static void inst_free(void *const ptr);
76static size_t inst_memsize(void *const ptr);
77static const rb_data_type_t pepper_instance_data_type = {
78  "PepperInstance",
79  { inst_mark, inst_free, inst_memsize }
80};
81
82struct PepperInstance {
83  PP_Instance instance;
84  PP_Resource url_loader;
85  VALUE self;
86  void* async_call_args;
87  union {
88    int32_t as_int;
89    const char* as_str;
90    VALUE as_value;
91  } async_call_result;
92  char buf[1000];
93
94  pthread_t th;
95  pthread_mutex_t mutex;
96  pthread_cond_t cond;
97};
98
99struct PepperInstance*
100pruby_get_instance(PP_Instance instance)
101{
102  VALUE self = rb_hash_aref(instance_table, INT2FIX(instance));
103  if (RTEST(self)) {
104    struct PepperInstance *inst;
105    TypedData_Get_Struct(self, struct PepperInstance, &pepper_instance_data_type, inst);
106    return inst;
107  }
108  else {
109    return NULL;
110  }
111}
112
113#define GET_PEPPER_INSTANCE() (pruby_get_instance(current_instance))
114
115struct PepperInstance*
116pruby_register_instance(PP_Instance instance)
117{
118  VALUE obj;
119  struct PepperInstance *data;
120  obj = TypedData_Make_Struct(rb_cData, struct PepperInstance, &pepper_instance_data_type, data);
121  data->self = obj;
122  data->instance = instance;
123  data->url_loader = 0;
124
125  pthread_mutex_init(&data->mutex, NULL);
126  pthread_cond_init(&data->cond, NULL);
127
128  rb_hash_aset(instance_table, INT2FIX(instance), obj);
129  return data;
130}
131
132int
133pruby_unregister_instance(PP_Instance instance)
134{
135  VALUE inst = rb_hash_delete(instance_table, INT2FIX(instance));
136  return RTEST(inst);
137}
138
139static void
140inst_mark(void *const ptr)
141{
142  RUBY_MARK_ENTER("PepperInstance"0);
143  if (ptr) {
144    const struct PepperInstance* inst = (struct PepperInstance*)ptr;
145    RUBY_MARK_UNLESS_NULL(inst->async_call_result.as_value);
146  }
147  RUBY_MARK_LEAVE("PepperInstance"0);
148}
149
150static void
151inst_free(void *const ptr)
152{
153  ruby_xfree(ptr);
154}
155
156static size_t
157inst_memsize(void *const ptr)
158{
159  if (ptr) {
160    const struct PepperInstance* inst = (struct PepperInstance*)ptr;
161    return sizeof(*inst);
162  } else {
163    return 0;
164  }
165}
166
167void
168pruby_async_return_int(void* data, int32_t result)
169{
170  /* PPAPI main thread */
171  struct PepperInstance* const instance = (struct PepperInstance*)data;
172  instance->async_call_result.as_int = result;
173  if (pthread_cond_signal(&instance->cond)) {
174    perror("pepper-ruby:pthread_cond_signal");
175  }
176}
177
178void
179pruby_async_return_str(void* data, const char *result)
180{
181  /* PPAPI main thread */
182  struct PepperInstance* const instance = (struct PepperInstance*)data;
183  instance->async_call_result.as_str = result;
184  if (pthread_cond_signal(&instance->cond)) {
185    perror("pepper-ruby:pthread_cond_signal");
186  }
187}
188
189void
190pruby_async_return_value(void* data, VALUE value)
191{
192  /* PPAPI main thread */
193  struct PepperInstance* const instance = (struct PepperInstance*)data;
194  instance->async_call_result.as_value = value;
195  if (pthread_cond_signal(&instance->cond)) {
196    perror("pepper-ruby:pthread_cond_signal");
197  }
198}
199/******************************************************************************
200 * Conversion between Ruby's VALUE, Pepper's Var and C string
201 ******************************************************************************/
202
203/**
204 * Creates a new string PP_Var from C string. The resulting object will be a
205 * refcounted string object. It will be AddRef()ed for the caller. When the
206 * caller is done with it, it should be Release()d.
207 * @param[in] str C string to be converted to PP_Var
208 * @return PP_Var containing string.
209 */
210static struct PP_Var
211pruby_cstr_to_var(const char* str)
212{
213#ifdef PPB_VAR_INTERFACE_1_0
214  if (var_interface != NULL)
215    return var_interface->VarFromUtf8(module_id, str, strlen(str));
216  return PP_MakeUndefined();
217#else
218  return var_interface->VarFromUtf8(str, strlen(str));
219#endif
220}
221
222/**
223 * Returns a mutable C string contained in the @a var or NULL if @a var is not
224 * string.  This makes a copy of the string in the @a var and adds a NULL
225 * terminator.  Note that VarToUtf8() does not guarantee the NULL terminator on
226 * the returned string.  See the comments for VarToUtf8() in ppapi/c/ppb_var.h
227 * for more info.  The caller is responsible for freeing the returned memory.
228 * @param[in] var PP_Var containing string.
229 * @return a mutable C string representation of @a var.
230 * @note The caller is responsible for freeing the returned string.
231 */
232static char*
233pruby_var_to_cstr(struct PP_Var var)
234{
235  uint32_t len = 0;
236  if (var_interface != NULL) {
237    const char* var_c_str = var_interface->VarToUtf8(var, &len);
238    if (len > 0) {
239      char* c_str = (char*)malloc(len + 1);
240      memcpy(c_str, var_c_str, len);
241      c_str[len] = '\0';
242      return c_str;
243    }
244  }
245  return NULL;
246}
247
248static struct PP_Var
249pruby_str_to_var(volatile VALUE str)
250{
251  if (!RB_TYPE_P(str, T_STRING)) {
252    fprintf(stderr, "[BUG] Unexpected object type: %x\n", TYPE(str));
253    exit(EXIT_FAILURE);
254  }
255#ifdef PPB_VAR_INTERFACE_1_0
256  if (var_interface != NULL) {
257    return var_interface->VarFromUtf8(module_id, RSTRING_PTR(str), RSTRING_LEN(str));
258  }
259#else
260  return var_interface->VarFromUtf8(RSTRING_PTR(str), RSTRING_LEN(str));
261#endif
262  return PP_MakeUndefined();
263}
264
265static struct PP_Var
266pruby_obj_to_var(volatile VALUE obj)
267{
268  static const char* const error =
269      "throw 'Failed to convert the result to a JavaScript object';";
270  int state;
271  obj = rb_protect(&rb_obj_as_string, obj, &state);
272  if (!state) {
273      return pruby_str_to_var(obj);
274  }
275  else {
276      return pruby_cstr_to_var(error);
277  }
278}
279
280int
281pruby_var_equal_to_cstr_p(struct PP_Var lhs, const char* rhs)
282{
283  uint32_t len = 0;
284  if (var_interface == NULL) {
285    return 0;
286  }
287  else {
288    const char* const cstr = var_interface->VarToUtf8(lhs, &len);
289    return strncmp(cstr, rhs, len) == 0;
290  }
291}
292
293int
294pruby_var_prefixed_p(struct PP_Var var, const char* prefix)
295{
296  uint32_t len = 0;
297  if (var_interface == NULL) {
298    return 0;
299  }
300  else {
301    const char* const cstr = var_interface->VarToUtf8(var, &len);
302    const size_t prefix_len = strlen(prefix);
303    return len >= prefix_len && memcmp(cstr, prefix, len) == 0;
304  }
305}
306
307
308/******************************************************************************
309 * Messaging
310 ******************************************************************************/
311
312/* Posts the given C string as a message.
313 * @param data pointer to a NULL-terminated string */
314void
315pruby_post_cstr(void* data)
316{
317  /* PPAPI main thread */
318  struct PepperInstance* const instance = (struct PepperInstance*)data;
319  const char* const msg = (const char*)instance->async_call_args;
320  messaging_interface->PostMessage(instance->instance,
321                                   pruby_cstr_to_var(msg));
322}
323
324/* Posts the given Ruby VALUE as a message.
325 * @param data a VALUE casted to void* */
326void
327pruby_post_value(void* data)
328{
329  /* PPAPI main thread */
330  struct PepperInstance* const instance = (struct PepperInstance*)data;
331  volatile VALUE value = (VALUE)instance->async_call_args;
332  messaging_interface->PostMessage(instance->instance, pruby_obj_to_var(value));
333}
334
335
336
337/******************************************************************************
338 * Ruby initialization
339 ******************************************************************************/
340
341static void
342init_loadpath(void)
343{
344  ruby_incpush("lib/ruby/"RUBY_LIB_VERSION);
345  ruby_incpush("lib/ruby/"RUBY_LIB_VERSION"/"RUBY_PLATFORM);
346  ruby_incpush(".");
347}
348
349static VALUE
350init_libraries_internal(VALUE unused)
351{
352  extern void Init_enc();
353  extern void Init_ext();
354
355  init_loadpath();
356  Init_enc();
357  Init_ext();
358  return Qnil;
359}
360
361static void*
362init_libraries(void* data)
363{
364  int state;
365  struct PepperInstance* const instance = (struct PepperInstance*)data;
366  current_instance = instance->instance;
367
368  if (pthread_mutex_lock(&instance->mutex)) {
369    perror("pepper-ruby:pthread_mutex_lock");
370    return 0;
371  }
372  rb_protect(&init_libraries_internal, Qnil, &state);
373  pthread_mutex_unlock(&instance->mutex);
374
375  if (state) {
376    volatile VALUE err = rb_errinfo();
377    err = rb_obj_as_string(err);
378  } else {
379    instance->async_call_args = (void*)"rubyReady";
380    core_interface->CallOnMainThread(
381        0, PP_MakeCompletionCallback(pruby_post_cstr, instance), 0);
382  }
383  return NULL;
384}
385
386static int
387init_libraries_if_necessary(void)
388{
389  static int initialized = 0;
390  if (!initialized) {
391    struct PepperInstance* const instance = GET_PEPPER_INSTANCE();
392    int err;
393    initialized = 1;
394    err = pthread_create(&instance->th, NULL, &init_libraries, instance);
395    if (err) {
396      fprintf(stderr, "pepper_ruby:pthread_create: %s\n", strerror(err));
397      exit(EXIT_FAILURE);
398    }
399    pthread_detach(instance->th);
400  }
401  return 0;
402}
403
404static int
405pruby_init(void)
406{
407  RUBY_INIT_STACK;
408  ruby_init();
409
410  instance_table = rb_hash_new();
411  rb_gc_register_mark_object(instance_table);
412
413  return 0;
414}
415
416
417/******************************************************************************
418 * Ruby evaluation
419 ******************************************************************************/
420
421static void*
422pruby_eval(void* data)
423{
424  extern VALUE ruby_eval_string_from_file_protect(const char* src, const char* path, int* state);
425  struct PepperInstance* const instance = (struct PepperInstance*)data;
426  volatile VALUE src = (VALUE)instance->async_call_args;
427  volatile VALUE result = Qnil;
428  volatile int state;
429
430  RUBY_INIT_STACK;
431
432  if (pthread_mutex_lock(&instance->mutex)) {
433    perror("pepper-ruby:pthread_mutex_lock");
434    return 0;
435  }
436  result = ruby_eval_string_from_file_protect(
437      RSTRING_PTR(src), "(pepper-ruby)", &state);
438  pthread_mutex_unlock(&instance->mutex);
439
440  if (!state) {
441      instance->async_call_args =
442          rb_str_concat(rb_usascii_str_new_cstr("return:"),
443                        rb_obj_as_string(result));
444      core_interface->CallOnMainThread(
445          0, PP_MakeCompletionCallback(pruby_post_value, instance), 0);
446      return NULL;
447  }
448  else {
449      rb_set_errinfo(Qnil);
450      instance->async_call_args =
451          rb_str_concat(rb_usascii_str_new_cstr("error:"),
452                        rb_obj_as_string(result));
453      core_interface->CallOnMainThread(
454          0, PP_MakeCompletionCallback(pruby_post_value, instance), 0);
455      return NULL;
456  }
457}
458
459
460/******************************************************************************
461 * Pepper Module callbacks
462 ******************************************************************************/
463
464/**
465 * Called when the NaCl module is instantiated on the web page. The identifier
466 * of the new instance will be passed in as the first argument (this value is
467 * generated by the browser and is an opaque handle).  This is called for each
468 * instantiation of the NaCl module, which is each time the <embed> tag for
469 * this module is encountered.
470 *
471 * If this function reports a failure (by returning @a PP_FALSE), the NaCl
472 * module will be deleted and DidDestroy will be called.
473 * @param[in] instance The identifier of the new instance representing this
474 *     NaCl module.
475 * @param[in] argc The number of arguments contained in @a argn and @a argv.
476 * @param[in] argn An array of argument names.  These argument names are
477 *     supplied in the <embed> tag, for example:
478 *       <embed id="nacl_module" dimensions="2">
479 *     will produce two arguments, one named "id" and one named "dimensions".
480 * @param[in] argv An array of argument values.  These are the values of the
481 *     arguments listed in the <embed> tag.  In the above example, there will
482 *     be two elements in this array, "nacl_module" and "2".  The indices of
483 *     these values match the indices of the corresponding names in @a argn.
484 * @return @a PP_TRUE on success.
485 */
486static PP_Bool
487Instance_DidCreate(PP_Instance instance,
488                   uint32_t argc, const char* argn[], const char* argv[])
489{
490  struct PepperInstance* data = pruby_register_instance(instance);
491  current_instance = instance;
492  return init_libraries_if_necessary() ? PP_FALSE : PP_TRUE;
493}
494
495/**
496 * Called when the NaCl module is destroyed. This will always be called,
497 * even if DidCreate returned failure. This routine should deallocate any data
498 * associated with the instance.
499 * @param[in] instance The identifier of the instance representing this NaCl
500 *     module.
501 */
502static void Instance_DidDestroy(PP_Instance instance) {
503  struct PepperInstance* data = pruby_get_instance(instance);
504  core_interface->ReleaseResource(data->url_loader);
505  pruby_unregister_instance(instance);
506}
507
508/**
509 * Called when the position, the size, or the clip rect of the element in the
510 * browser that corresponds to this NaCl module has changed.
511 * @param[in] instance The identifier of the instance representing this NaCl
512 *     module.
513 * @param[in] position The location on the page of this NaCl module. This is
514 *     relative to the top left corner of the viewport, which changes as the
515 *     page is scrolled.
516 * @param[in] clip The visible region of the NaCl module. This is relative to
517 *     the top left of the plugin's coordinate system (not the page).  If the
518 *     plugin is invisible, @a clip will be (0, 0, 0, 0).
519 */
520#ifdef PPP_INSTANCE_INTERFACE_1_0
521static void
522Instance_DidChangeView(PP_Instance instance,
523                       const struct PP_Rect* position,
524                       const struct PP_Rect* clip)
525{
526}
527#else
528static void
529Instance_DidChangeView(PP_Instance instance, PP_Resource view_resource)
530{
531}
532#endif
533
534/**
535 * Notification that the given NaCl module has gained or lost focus.
536 * Having focus means that keyboard events will be sent to the NaCl module
537 * represented by @a instance. A NaCl module's default condition is that it
538 * will not have focus.
539 *
540 * Note: clicks on NaCl modules will give focus only if you handle the
541 * click event. You signal if you handled it by returning @a true from
542 * HandleInputEvent. Otherwise the browser will bubble the event and give
543 * focus to the element on the page that actually did end up consuming it.
544 * If you're not getting focus, check to make sure you're returning true from
545 * the mouse click in HandleInputEvent.
546 * @param[in] instance The identifier of the instance representing this NaCl
547 *     module.
548 * @param[in] has_focus Indicates whether this NaCl module gained or lost
549 *     event focus.
550 */
551static void
552Instance_DidChangeFocus(PP_Instance instance, PP_Bool has_focus)
553{
554}
555
556/**
557 * Handler that gets called after a full-frame module is instantiated based on
558 * registered MIME types.  This function is not called on NaCl modules.  This
559 * function is essentially a place-holder for the required function pointer in
560 * the PPP_Instance structure.
561 * @param[in] instance The identifier of the instance representing this NaCl
562 *     module.
563 * @param[in] url_loader A PP_Resource an open PPB_URLLoader instance.
564 * @return PP_FALSE.
565 */
566static PP_Bool
567Instance_HandleDocumentLoad(PP_Instance instance, PP_Resource url_loader)
568{
569  /* NaCl modules do not need to handle the document load function. */
570  return PP_FALSE;
571}
572
573
574/**
575 * Handler for messages coming in from the browser via postMessage.  The
576 * @a var_message can contain anything: a JSON string; a string that encodes
577 * method names and arguments; etc.  For example, you could use JSON.stringify
578 * in the browser to create a message that contains a method name and some
579 * parameters, something like this:
580 *   var json_message = JSON.stringify({ "myMethod" : "3.14159" });
581 *   nacl_module.postMessage(json_message);
582 * On receipt of this message in @a var_message, you could parse the JSON to
583 * retrieve the method name, match it to a function call, and then call it with
584 * the parameter.
585 * @param[in] instance The instance ID.
586 * @param[in] message The contents, copied by value, of the message sent from
587 *     browser via postMessage.
588 */
589void
590Messaging_HandleMessage(PP_Instance instance, struct PP_Var var_message)
591{
592  char* const message = pruby_var_to_cstr(var_message);
593  size_t message_len = strlen(message);
594  current_instance = instance;
595
596  if (strstr(message, "eval:") != NULL) {
597    volatile VALUE src;
598    struct PepperInstance* const instance_data = GET_PEPPER_INSTANCE();
599    int err;
600#define EVAL_PREFIX_LEN 5
601    src = rb_str_new(message + EVAL_PREFIX_LEN, message_len - EVAL_PREFIX_LEN);
602    instance_data->async_call_args = (void*)src;
603    err = pthread_create(&instance_data->th, NULL, &pruby_eval, instance_data);
604    if (err) {
605      fprintf(stderr, "pepper_ruby:pthread_create: %s\n", strerror(err));
606      exit(EXIT_FAILURE);
607    }
608    pthread_detach(instance_data->th);
609  }
610  free(message);
611}
612
613/**
614 * Entry points for the module.
615 * Initialize instance interface and scriptable object class.
616 * @param[in] a_module_id Module ID
617 * @param[in] get_browser_interface Pointer to PPB_GetInterface
618 * @return PP_OK on success, any other value on failure.
619 */
620PP_EXPORT int32_t
621PPP_InitializeModule(PP_Module a_module_id, PPB_GetInterface get_browser_interface)
622{
623  module_id = a_module_id;
624  core_interface = (PPB_Core*)(get_browser_interface(PPB_CORE_INTERFACE));
625  if (core_interface == NULL) return PP_ERROR_NOINTERFACE;
626
627  var_interface = (PPB_Var*)(get_browser_interface(PPB_VAR_INTERFACE));
628  if (var_interface == NULL) return PP_ERROR_NOINTERFACE;
629
630  messaging_interface = (PPB_Messaging*)(get_browser_interface(PPB_MESSAGING_INTERFACE));
631  if (messaging_interface == NULL) return PP_ERROR_NOINTERFACE;
632
633  loader_interface = (PPB_URLLoader*)(get_browser_interface(PPB_URLLOADER_INTERFACE));
634  if (loader_interface == NULL) return PP_ERROR_NOINTERFACE;
635
636  request_interface = (PPB_URLRequestInfo*)(get_browser_interface(PPB_URLREQUESTINFO_INTERFACE));
637  if (request_interface == NULL) return PP_ERROR_NOINTERFACE;
638
639  response_interface = (PPB_URLResponseInfo*)(get_browser_interface(PPB_URLRESPONSEINFO_INTERFACE));
640  if (response_interface == NULL) return PP_ERROR_NOINTERFACE;
641
642  fileref_interface = (PPB_FileRef*)(get_browser_interface(PPB_FILEREF_INTERFACE));
643  if (fileref_interface == NULL) return PP_ERROR_NOINTERFACE;
644
645  return pruby_init() ? PP_ERROR_FAILED : PP_OK;
646}
647
648/**
649 * Returns an interface pointer for the interface of the given name, or NULL
650 * if the interface is not supported.
651 * @param[in] interface_name name of the interface
652 * @return pointer to the interface
653 */
654PP_EXPORT const void*
655PPP_GetInterface(const char* interface_name)
656{
657  if (strcmp(interface_name, PPP_INSTANCE_INTERFACE) == 0) {
658    static PPP_Instance instance_interface = {
659      &Instance_DidCreate,
660      &Instance_DidDestroy,
661      &Instance_DidChangeView,
662      &Instance_DidChangeFocus,
663      &Instance_HandleDocumentLoad
664    };
665    return &instance_interface;
666  } else if (strcmp(interface_name, PPP_MESSAGING_INTERFACE) == 0) {
667    static PPP_Messaging messaging_interface = {
668      &Messaging_HandleMessage
669    };
670    return &messaging_interface;
671  }
672  return NULL;
673}
674
675/**
676 * Called before the plugin module is unloaded.
677 */
678PP_EXPORT void
679PPP_ShutdownModule()
680{
681  ruby_cleanup(0);
682}
683
684/******************************************************************************
685 * Overwrites rb_file_load_ok
686 ******************************************************************************/
687
688static void
689load_ok_internal(void* data, int32_t unused)
690{
691  /* PPAPI main thread */
692  struct PepperInstance* const instance = (struct PepperInstance*)data;
693  const char *const path = (const char*)instance->async_call_args;
694  PP_Resource req;
695  int result;
696
697  instance->url_loader = loader_interface->Create(instance->instance);
698  req = request_interface->Create(instance->instance);
699  request_interface->SetProperty(
700      req, PP_URLREQUESTPROPERTY_METHOD, pruby_cstr_to_var("HEAD"));
701  request_interface->SetProperty(
702      req, PP_URLREQUESTPROPERTY_URL, pruby_cstr_to_var(path));
703
704  result = loader_interface->Open(
705      instance->url_loader, req,
706      PP_MakeCompletionCallback(pruby_async_return_int, instance));
707  if (result != PP_OK_COMPLETIONPENDING) {
708    pruby_async_return_int(instance, result);
709  }
710}
711
712static void
713pruby_file_fetch_check_response(void* data, int32_t unused)
714{
715  /* PPAPI main thread */
716  PP_Resource res;
717  struct PepperInstance* const instance = (struct PepperInstance*)data;
718
719  res = loader_interface->GetResponseInfo(instance->url_loader);
720  if (res) {
721    struct PP_Var status =
722        response_interface->GetProperty(res, PP_URLRESPONSEPROPERTY_STATUSCODE);
723    if (status.type == PP_VARTYPE_INT32) {
724      pruby_async_return_int(instance, status.value.as_int / 100 == 2 ? PP_OK : PP_ERROR_FAILED);
725      return;
726    }
727    else {
728      messaging_interface->PostMessage(
729          instance->instance, pruby_cstr_to_var("Unexpected type: ResponseInfoInterface::GetProperty"));
730    }
731  }
732  else {
733    messaging_interface->PostMessage(
734        instance->instance, pruby_cstr_to_var("Failed to open URL: URLLoaderInterface::GetResponseInfo"));
735  }
736  pruby_async_return_int(instance, PP_ERROR_FAILED);
737}
738
739
740int
741rb_file_load_ok(const char *path)
742{
743  struct PepperInstance* const instance = GET_PEPPER_INSTANCE();
744  if (path[0] == '.' && path[1] == '/') path += 2;
745
746  instance->async_call_args = (void*)path;
747  core_interface->CallOnMainThread(
748      0, PP_MakeCompletionCallback(load_ok_internal, instance), 0);
749  if (pthread_cond_wait(&instance->cond, &instance->mutex)) {
750    perror("pepper-ruby:pthread_cond_wait");
751    return 0;
752  }
753  if (instance->async_call_result.as_int != PP_OK) {
754    fprintf(stderr, "Failed to open URL: %d: %s\n",
755            instance->async_call_result.as_int, path);
756    return 0;
757  }
758
759  core_interface->CallOnMainThread(
760      0, PP_MakeCompletionCallback(pruby_file_fetch_check_response, instance), 0);
761  if (pthread_cond_wait(&instance->cond, &instance->mutex)) {
762    perror("pepper-ruby:pthread_cond_wait");
763    return 0;
764  }
765  return instance->async_call_result.as_int == PP_OK;
766}
767
768/******************************************************************************
769 * Overwrites rb_load_file
770 ******************************************************************************/
771
772static void
773load_file_internal(void* data, int32_t unused)
774{
775  /* PPAPI main thread */
776  struct PepperInstance* const instance = (struct PepperInstance*)data;
777  const char *const path = (const char*)instance->async_call_args;
778  PP_Resource req;
779  int result;
780
781  instance->url_loader = loader_interface->Create(instance->instance);
782  req = request_interface->Create(instance->instance);
783  request_interface->SetProperty(
784      req, PP_URLREQUESTPROPERTY_METHOD, pruby_cstr_to_var("GET"));
785  request_interface->SetProperty(
786      req, PP_URLREQUESTPROPERTY_URL, pruby_cstr_to_var(path));
787
788  result = loader_interface->Open(
789      instance->url_loader, req,
790      PP_MakeCompletionCallback(pruby_async_return_int, instance));
791  if (result != PP_OK_COMPLETIONPENDING) {
792    pruby_async_return_int(instance, result);
793  }
794}
795
796static void
797load_file_read_contents_callback(void *data, int result)
798{
799  struct PepperInstance* const instance = (struct PepperInstance*)data;
800  if (result > 0) {
801    rb_str_buf_cat(instance->async_call_result.as_value,
802                   instance->buf, result);
803    loader_interface->ReadResponseBody(
804        instance->url_loader, instance->buf, 1000, PP_MakeCompletionCallback(load_file_read_contents_callback, instance));
805  }
806  else if (result == 0) {
807    pruby_async_return_value(data, instance->async_call_result.as_value);
808  }
809  else {
810    pruby_async_return_value(data, INT2FIX(result));
811  }
812}
813
814static void
815load_file_read_contents(void *data, int result)
816{
817  struct PepperInstance* const instance = (struct PepperInstance*)data;
818  instance->async_call_result.as_value = rb_str_new(0, 0);
819  loader_interface->ReadResponseBody(
820      instance->url_loader, instance->buf, 1000, PP_MakeCompletionCallback(load_file_read_contents_callback, instance));
821}
822
823void*
824rb_load_file(const char *path)
825{
826  const char *real_path;
827  struct PepperInstance* instance;
828  if (path[0] != '.' || path[1] != '/') path += 2;
829
830  instance = GET_PEPPER_INSTANCE();
831
832  instance->async_call_args = (void*)path;
833  core_interface->CallOnMainThread(
834      0, PP_MakeCompletionCallback(load_file_internal, instance), 0);
835  if (pthread_cond_wait(&instance->cond, &instance->mutex)) {
836    perror("pepper-ruby:pthread_cond_wait");
837    return 0;
838  }
839  if (instance->async_call_result.as_int != PP_OK) {
840    fprintf(stderr, "Failed to open URL: %d: %s\n",
841            instance->async_call_result.as_int, path);
842    return 0;
843  }
844
845  core_interface->CallOnMainThread(
846      0, PP_MakeCompletionCallback(pruby_file_fetch_check_response, instance), 0);
847  if (pthread_cond_wait(&instance->cond, &instance->mutex)) {
848    perror("pepper-ruby:pthread_cond_wait");
849    return 0;
850  }
851  if (instance->async_call_result.as_int != PP_OK) return 0;
852
853  core_interface->CallOnMainThread(
854      0, PP_MakeCompletionCallback(load_file_read_contents, instance), 0);
855  if (pthread_cond_wait(&instance->cond, &instance->mutex)) {
856    perror("pepper-ruby:pthread_cond_wait");
857    return 0;
858  }
859  if (FIXNUM_P(instance->async_call_result.as_value)) {
860    return 0;
861  }
862  else if (RB_TYPE_P(instance->async_call_result.as_value, T_STRING)) {
863    VALUE str = instance->async_call_result.as_value;
864    extern void* rb_compile_cstr(const char *f, const char *s, int len, int line);
865    return rb_compile_cstr(path, RSTRING_PTR(str), RSTRING_LEN(str), 0);
866  }
867  else {
868    return 0;
869  }
870}
871