summaryrefslogtreecommitdiff
path: root/nacl/pepper_main.c
blob: c0e497b387ec0db30d10009eb21b51cb386831bf (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
/******************************************************************************
 Copyright 2012 Google Inc. All Rights Reserved.
 Author: yugui@google.com (Yugui Sonoda)
 ******************************************************************************/

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <pthread.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <pthread.h>
#include "ppapi/c/pp_errors.h"
#include "ppapi/c/pp_module.h"
#include "ppapi/c/pp_var.h"
#include "ppapi/c/ppb.h"
#include "ppapi/c/ppb_core.h"
#include "ppapi/c/ppb_file_ref.h"
#include "ppapi/c/ppb_instance.h"
#include "ppapi/c/ppb_messaging.h"
#include "ppapi/c/ppb_url_loader.h"
#include "ppapi/c/ppb_url_request_info.h"
#include "ppapi/c/ppb_url_response_info.h"
#include "ppapi/c/ppb_var.h"
#include "ppapi/c/ppp.h"
#include "ppapi/c/ppp_instance.h"
#include "ppapi/c/ppp_messaging.h"

#include "verconf.h"
#include "ruby/ruby.h"
#include "version.h"
#include "gc.h"

#ifdef HAVE_STRUCT_PPB_CORE
typedef struct PPB_Core PPB_Core;
#endif
#ifdef HAVE_STRUCT_PPB_MESSAGING
typedef struct PPB_Messaging PPB_Messaging;
#endif
#ifdef HAVE_STRUCT_PPB_VAR
typedef struct PPB_Var PPB_Var;
#endif
#ifdef HAVE_STRUCT_PPB_URLLOADER
typedef struct PPB_URLLoader PPB_URLLoader;
#endif
#ifdef HAVE_STRUCT_PPB_URLREQUESTINFO
typedef struct PPB_URLRequestInfo PPB_URLRequestInfo;
#endif
#ifdef HAVE_STRUCT_PPB_URLRESPONSEINFO
typedef struct PPB_URLResponseInfo PPB_URLResponseInfo;
#endif
#ifdef HAVE_STRUCT_PPP_INSTANCE
typedef struct PPP_Instance PPP_Instance;
#endif

static PP_Module module_id = 0;
static PPB_Core* core_interface = NULL;
static PPB_Messaging* messaging_interface = NULL;
static PPB_Var* var_interface = NULL;
static PPB_URLLoader* loader_interface = NULL;
static PPB_URLRequestInfo* request_interface = NULL;
static PPB_URLResponseInfo* response_interface = NULL;
static PPB_FileRef* fileref_interface = NULL;
static struct st_table* instance_data = NULL;

static VALUE instance_table = Qundef;

static PP_Instance current_instance = 0;

/******************************************************************************
 * State of instance
 ******************************************************************************/

static void inst_mark(void *const ptr);
static void inst_free(void *const ptr);
static size_t inst_memsize(void *const ptr);
static const rb_data_type_t pepper_instance_data_type = {
  "PepperInstance",
  { inst_mark, inst_free, inst_memsize }
};

struct PepperInstance {
  PP_Instance instance;
  PP_Resource url_loader;
  VALUE self;
  void* async_call_args;
  union {
    int32_t as_int;
    const char* as_str;
    VALUE as_value;
  } async_call_result;
  char buf[1000];

  pthread_t th;
  pthread_mutex_t mutex;
  pthread_cond_t cond;
};

struct PepperInstance*
pruby_get_instance(PP_Instance instance)
{
  VALUE self = rb_hash_aref(instance_table, INT2FIX(instance));
  if (RTEST(self)) {
    struct PepperInstance *inst;
    TypedData_Get_Struct(self, struct PepperInstance, &pepper_instance_data_type, inst);
    return inst;
  }
  else {
    return NULL;
  }
}

#define GET_PEPPER_INSTANCE() (pruby_get_instance(current_instance))

struct PepperInstance*
pruby_register_instance(PP_Instance instance)
{
  VALUE obj;
  struct PepperInstance *data;
  obj = TypedData_Make_Struct(rb_cData, struct PepperInstance, &pepper_instance_data_type, data);
  data->self = obj;
  data->instance = instance;
  data->url_loader = 0;

  pthread_mutex_init(&data->mutex, NULL);
  pthread_cond_init(&data->cond, NULL);

  rb_hash_aset(instance_table, INT2FIX(instance), obj);
  return data;
}

int
pruby_unregister_instance(PP_Instance instance)
{
  VALUE inst = rb_hash_delete(instance_table, INT2FIX(instance));
  return RTEST(inst);
}

static void
inst_mark(void *const ptr)
{
  RUBY_MARK_ENTER("PepperInstance"0);
  if (ptr) {
    const struct PepperInstance* inst = (struct PepperInstance*)ptr;
    RUBY_MARK_UNLESS_NULL(inst->async_call_result.as_value);
  }
  RUBY_MARK_LEAVE("PepperInstance"0);
}

static void
inst_free(void *const ptr)
{
  ruby_xfree(ptr);
}

static size_t
inst_memsize(void *const ptr)
{
  if (ptr) {
    const struct PepperInstance* inst = (struct PepperInstance*)ptr;
    return sizeof(*inst);
  } else {
    return 0;
  }
}

void
pruby_async_return_int(void* data, int32_t result)
{
  /* PPAPI main thread */
  struct PepperInstance* const instance = (struct PepperInstance*)data;
  instance->async_call_result.as_int = result;
  if (pthread_cond_signal(&instance->cond)) {
    perror("pepper-ruby:pthread_cond_signal");
  }
}

void
pruby_async_return_str(void* data, const char *result)
{
  /* PPAPI main thread */
  struct PepperInstance* const instance = (struct PepperInstance*)data;
  instance->async_call_result.as_str = result;
  if (pthread_cond_signal(&instance->cond)) {
    perror("pepper-ruby:pthread_cond_signal");
  }
}

void
pruby_async_return_value(void* data, VALUE value)
{
  /* PPAPI main thread */
  struct PepperInstance* const instance = (struct PepperInstance*)data;
  instance->async_call_result.as_value = value;
  if (pthread_cond_signal(&instance->cond)) {
    perror("pepper-ruby:pthread_cond_signal");
  }
}
/******************************************************************************
 * Conversion between Ruby's VALUE, Pepper's Var and C string
 ******************************************************************************/

/**
 * Creates a new string PP_Var from C string. The resulting object will be a
 * refcounted string object. It will be AddRef()ed for the caller. When the
 * caller is done with it, it should be Release()d.
 * @param[in] str C string to be converted to PP_Var
 * @return PP_Var containing string.
 */
static struct PP_Var
pruby_cstr_to_var(const char* str)
{
#ifdef PPB_VAR_INTERFACE_1_0
  if (var_interface != NULL)
    return var_interface->VarFromUtf8(module_id, str, strlen(str));
  return PP_MakeUndefined();
#else
  return var_interface->VarFromUtf8(str, strlen(str));
#endif
}

/**
 * Returns a mutable C string contained in the @a var or NULL if @a var is not
 * string.  This makes a copy of the string in the @a var and adds a NULL
 * terminator.  Note that VarToUtf8() does not guarantee the NULL terminator on
 * the returned string.  See the comments for VarToUtf8() in ppapi/c/ppb_var.h
 * for more info.  The caller is responsible for freeing the returned memory.
 * @param[in] var PP_Var containing string.
 * @return a mutable C string representation of @a var.
 * @note The caller is responsible for freeing the returned string.
 */
static char*
pruby_var_to_cstr(struct PP_Var var)
{
  uint32_t len = 0;
  if (var_interface != NULL) {
    const char* var_c_str = var_interface->VarToUtf8(var, &len);
    if (len > 0) {
      char* c_str = (char*)malloc(len + 1);
      memcpy(c_str, var_c_str, len);
      c_str[len] = '\0';
      return c_str;
    }
  }
  return NULL;
}

static struct PP_Var
pruby_str_to_var(volatile VALUE str)
{
  if (!RB_TYPE_P(str, T_STRING)) {
    fprintf(stderr, "[BUG] Unexpected object type: %x\n", TYPE(str));
    exit(EXIT_FAILURE);
  }
#ifdef PPB_VAR_INTERFACE_1_0
  if (var_interface != NULL) {
    return var_interface->VarFromUtf8(module_id, RSTRING_PTR(str), RSTRING_LEN(str));
  }
#else
  return var_interface->VarFromUtf8(RSTRING_PTR(str), RSTRING_LEN(str));
#endif
  return PP_MakeUndefined();
}

static struct PP_Var
pruby_obj_to_var(volatile VALUE obj)
{
  static const char* const error =
      "throw 'Failed to convert the result to a JavaScript object';";
  int state;
  obj = rb_protect(&rb_obj_as_string, obj, &state);
  if (!state) {
      return pruby_str_to_var(obj);
  }
  else {
      return pruby_cstr_to_var(error);
  }
}

int
pruby_var_equal_to_cstr_p(struct PP_Var lhs, const char* rhs)
{
  uint32_t len = 0;
  if (var_interface == NULL) {
    return 0;
  }
  else {
    const char* const cstr = var_interface->VarToUtf8(lhs, &len);
    return strncmp(cstr, rhs, len) == 0;
  }
}

int
pruby_var_prefixed_p(struct PP_Var var, const char* prefix)
{
  uint32_t len = 0;
  if (var_interface == NULL) {
    return 0;
  }
  else {
    const char* const cstr = var_interface->VarToUtf8(var, &len);
    const size_t prefix_len = strlen(prefix);
    return len >= prefix_len && memcmp(cstr, prefix, len) == 0;
  }
}


/******************************************************************************
 * Messaging
 ******************************************************************************/

/* Posts the given C string as a message.
 * @param data pointer to a NULL-terminated string */
void
pruby_post_cstr(void* data)
{
  /* PPAPI main thread */
  struct PepperInstance* const instance = (struct PepperInstance*)data;
  const char* const msg = (const char*)instance->async_call_args;
  messaging_interface->PostMessage(instance->instance,
                                   pruby_cstr_to_var(msg));
}

/* Posts the given Ruby VALUE as a message.
 * @param data a VALUE casted to void* */
void
pruby_post_value(void* data)
{
  /* PPAPI main thread */
  struct PepperInstance* const instance = (struct PepperInstance*)data;
  volatile VALUE value = (VALUE)instance->async_call_args;
  messaging_interface->PostMessage(instance->instance, pruby_obj_to_var(value));
}



/******************************************************************************
 * Ruby initialization
 ******************************************************************************/

static void
init_loadpath(void)
{
  ruby_incpush("lib/ruby/"RUBY_LIB_VERSION);
  ruby_incpush("lib/ruby/"RUBY_LIB_VERSION"/"RUBY_PLATFORM);
  ruby_incpush(".");
}

static VALUE
init_libraries_internal(VALUE unused)
{
  extern void Init_enc();
  extern void Init_ext();

  init_loadpath();
  Init_enc();
  Init_ext();
  return Qnil;
}

static void*
init_libraries(void* data)
{
  int state;
  struct PepperInstance* const instance = (struct PepperInstance*)data;
  current_instance = instance->instance;

  if (pthread_mutex_lock(&instance->mutex)) {
    perror("pepper-ruby:pthread_mutex_lock");
    return 0;
  }
  rb_protect(&init_libraries_internal, Qnil, &state);
  pthread_mutex_unlock(&instance->mutex);

  if (state) {
    volatile VALUE err = rb_errinfo();
    err = rb_obj_as_string(err);
  } else {
    instance->async_call_args = (void*)"rubyReady";
    core_interface->CallOnMainThread(
        0, PP_MakeCompletionCallback(pruby_post_cstr, instance), 0);
  }
  return NULL;
}

static int
init_libraries_if_necessary(void)
{
  static int initialized = 0;
  if (!initialized) {
    struct PepperInstance* const instance = GET_PEPPER_INSTANCE();
    int err;
    initialized = 1;
    err = pthread_create(&instance->th, NULL, &init_libraries, instance);
    if (err) {
      fprintf(stderr, "pepper_ruby:pthread_create: %s\n", strerror(err));
      exit(EXIT_FAILURE);
    }
    pthread_detach(instance->th);
  }
  return 0;
}

static int
pruby_init(void)
{
  RUBY_INIT_STACK;
  ruby_init();

  instance_table = rb_hash_new();
  rb_gc_register_mark_object(instance_table);

  return 0;
}


/******************************************************************************
 * Ruby evaluation
 ******************************************************************************/

static void*
pruby_eval(void* data)
{
  extern VALUE ruby_eval_string_from_file_protect(const char* src, const char* path, int* state);
  struct PepperInstance* const instance = (struct PepperInstance*)data;
  volatile VALUE src = (VALUE)instance->async_call_args;
  volatile VALUE result = Qnil;
  volatile int state;

  RUBY_INIT_STACK;

  if (pthread_mutex_lock(&instance->mutex)) {
    perror("pepper-ruby:pthread_mutex_lock");
    return 0;
  }
  result = ruby_eval_string_from_file_protect(
      RSTRING_PTR(src), "(pepper-ruby)", &state);
  pthread_mutex_unlock(&instance->mutex);

  if (!state) {
      instance->async_call_args =
          rb_str_concat(rb_usascii_str_new_cstr("return:"),
                        rb_obj_as_string(result));
      core_interface->CallOnMainThread(
          0, PP_MakeCompletionCallback(pruby_post_value, instance), 0);
      return NULL;
  }
  else {
      rb_set_errinfo(Qnil);
      instance->async_call_args =
          rb_str_concat(rb_usascii_str_new_cstr("error:"),
                        rb_obj_as_string(result));
      core_interface->CallOnMainThread(
          0, PP_MakeCompletionCallback(pruby_post_value, instance), 0);
      return NULL;
  }
}


/******************************************************************************
 * Pepper Module callbacks
 ******************************************************************************/

/**
 * Called when the NaCl module is instantiated on the web page. The identifier
 * of the new instance will be passed in as the first argument (this value is
 * generated by the browser and is an opaque handle).  This is called for each
 * instantiation of the NaCl module, which is each time the <embed> tag for
 * this module is encountered.
 *
 * If this function reports a failure (by returning @a PP_FALSE), the NaCl
 * module will be deleted and DidDestroy will be called.
 * @param[in] instance The identifier of the new instance representing this
 *     NaCl module.
 * @param[in] argc The number of arguments contained in @a argn and @a argv.
 * @param[in] argn An array of argument names.  These argument names are
 *     supplied in the <embed> tag, for example:
 *       <embed id="nacl_module" dimensions="2">
 *     will produce two arguments, one named "id" and one named "dimensions".
 * @param[in] argv An array of argument values.  These are the values of the
 *     arguments listed in the <embed> tag.  In the above example, there will
 *     be two elements in this array, "nacl_module" and "2".  The indices of
 *     these values match the indices of the corresponding names in @a argn.
 * @return @a PP_TRUE on success.
 */
static PP_Bool
Instance_DidCreate(PP_Instance instance,
                   uint32_t argc, const char* argn[], const char* argv[])
{
  struct PepperInstance* data = pruby_register_instance(instance);
  current_instance = instance;
  return init_libraries_if_necessary() ? PP_FALSE : PP_TRUE;
}

/**
 * Called when the NaCl module is destroyed. This will always be called,
 * even if DidCreate returned failure. This routine should deallocate any data
 * associated with the instance.
 * @param[in] instance The identifier of the instance representing this NaCl
 *     module.
 */
static void Instance_DidDestroy(PP_Instance instance) {
  struct PepperInstance* data = pruby_get_instance(instance);
  core_interface->ReleaseResource(data->url_loader);
  pruby_unregister_instance(instance);
}

/**
 * Called when the position, the size, or the clip rect of the element in the
 * browser that corresponds to this NaCl module has changed.
 * @param[in] instance The identifier of the instance representing this NaCl
 *     module.
 * @param[in] position The location on the page of this NaCl module. This is
 *     relative to the top left corner of the viewport, which changes as the
 *     page is scrolled.
 * @param[in] clip The visible region of the NaCl module. This is relative to
 *     the top left of the plugin's coordinate system (not the page).  If the
 *     plugin is invisible, @a clip will be (0, 0, 0, 0).
 */
#ifdef PPP_INSTANCE_INTERFACE_1_0
static void
Instance_DidChangeView(PP_Instance instance,
                       const struct PP_Rect* position,
                       const struct PP_Rect* clip)
{
}
#else
static void
Instance_DidChangeView(PP_Instance instance, PP_Resource view_resource)
{
}
#endif

/**
 * Notification that the given NaCl module has gained or lost focus.
 * Having focus means that keyboard events will be sent to the NaCl module
 * represented by @a instance. A NaCl module's default condition is that it
 * will not have focus.
 *
 * Note: clicks on NaCl modules will give focus only if you handle the
 * click event. You signal if you handled it by returning @a true from
 * HandleInputEvent. Otherwise the browser will bubble the event and give
 * focus to the element on the page that actually did end up consuming it.
 * If you're not getting focus, check to make sure you're returning true from
 * the mouse click in HandleInputEvent.
 * @param[in] instance The identifier of the instance representing this NaCl
 *     module.
 * @param[in] has_focus Indicates whether this NaCl module gained or lost
 *     event focus.
 */
static void
Instance_DidChangeFocus(PP_Instance instance, PP_Bool has_focus)
{
}

/**
 * Handler that gets called after a full-frame module is instantiated based on
 * registered MIME types.  This function is not called on NaCl modules.  This
 * function is essentially a place-holder for the required function pointer in
 * the PPP_Instance structure.
 * @param[in] instance The identifier of the instance representing this NaCl
 *     module.
 * @param[in] url_loader A PP_Resource an open PPB_URLLoader instance.
 * @return PP_FALSE.
 */
static PP_Bool
Instance_HandleDocumentLoad(PP_Instance instance, PP_Resource url_loader)
{
  /* NaCl modules do not need to handle the document load function. */
  return PP_FALSE;
}


/**
 * Handler for messages coming in from the browser via postMessage.  The
 * @a var_message can contain anything: a JSON string; a string that encodes
 * method names and arguments; etc.  For example, you could use JSON.stringify
 * in the browser to create a message that contains a method name and some
 * parameters, something like this:
 *   var json_message = JSON.stringify({ "myMethod" : "3.14159" });
 *   nacl_module.postMessage(json_message);
 * On receipt of this message in @a var_message, you could parse the JSON to
 * retrieve the method name, match it to a function call, and then call it with
 * the parameter.
 * @param[in] instance The instance ID.
 * @param[in] message The contents, copied by value, of the message sent from
 *     browser via postMessage.
 */
void
Messaging_HandleMessage(PP_Instance instance, struct PP_Var var_message)
{
  char* const message = pruby_var_to_cstr(var_message);
  size_t message_len = strlen(message);
  current_instance = instance;

  if (strstr(message, "eval:") != NULL) {
    volatile VALUE src;
    struct PepperInstance* const instance_data = GET_PEPPER_INSTANCE();
    int err;
#define EVAL_PREFIX_LEN 5
    src = rb_str_new(message + EVAL_PREFIX_LEN, message_len - EVAL_PREFIX_LEN);
    instance_data->async_call_args = (void*)src;
    err = pthread_create(&instance_data->th, NULL, &pruby_eval, instance_data);
    if (err) {
      fprintf(stderr, "pepper_ruby:pthread_create: %s\n", strerror(err));
      exit(EXIT_FAILURE);
    }
    pthread_detach(instance_data->th);
  }
  free(message);
}

/**
 * Entry points for the module.
 * Initialize instance interface and scriptable object class.
 * @param[in] a_module_id Module ID
 * @param[in] get_browser_interface Pointer to PPB_GetInterface
 * @return PP_OK on success, any other value on failure.
 */
PP_EXPORT int32_t
PPP_InitializeModule(PP_Module a_module_id, PPB_GetInterface get_browser_interface)
{
  module_id = a_module_id;
  core_interface = (PPB_Core*)(get_browser_interface(PPB_CORE_INTERFACE));
  if (core_interface == NULL) return PP_ERROR_NOINTERFACE;

  var_interface = (PPB_Var*)(get_browser_interface(PPB_VAR_INTERFACE));
  if (var_interface == NULL) return PP_ERROR_NOINTERFACE;

  messaging_interface = (PPB_Messaging*)(get_browser_interface(PPB_MESSAGING_INTERFACE));
  if (messaging_interface == NULL) return PP_ERROR_NOINTERFACE;

  loader_interface = (PPB_URLLoader*)(get_browser_interface(PPB_URLLOADER_INTERFACE));
  if (loader_interface == NULL) return PP_ERROR_NOINTERFACE;

  request_interface = (PPB_URLRequestInfo*)(get_browser_interface(PPB_URLREQUESTINFO_INTERFACE));
  if (request_interface == NULL) return PP_ERROR_NOINTERFACE;

  response_interface = (PPB_URLResponseInfo*)(get_browser_interface(PPB_URLRESPONSEINFO_INTERFACE));
  if (response_interface == NULL) return PP_ERROR_NOINTERFACE;

  fileref_interface = (PPB_FileRef*)(get_browser_interface(PPB_FILEREF_INTERFACE));
  if (fileref_interface == NULL) return PP_ERROR_NOINTERFACE;

  return pruby_init() ? PP_ERROR_FAILED : PP_OK;
}

/**
 * Returns an interface pointer for the interface of the given name, or NULL
 * if the interface is not supported.
 * @param[in] interface_name name of the interface
 * @return pointer to the interface
 */
PP_EXPORT const void*
PPP_GetInterface(const char* interface_name)
{
  if (strcmp(interface_name, PPP_INSTANCE_INTERFACE) == 0) {
    static PPP_Instance instance_interface = {
      &Instance_DidCreate,
      &Instance_DidDestroy,
      &Instance_DidChangeView,
      &Instance_DidChangeFocus,
      &Instance_HandleDocumentLoad
    };
    return &instance_interface;
  } else if (strcmp(interface_name, PPP_MESSAGING_INTERFACE) == 0) {
    static PPP_Messaging messaging_interface = {
      &Messaging_HandleMessage
    };
    return &messaging_interface;
  }
  return NULL;
}

/**
 * Called before the plugin module is unloaded.
 */
PP_EXPORT void
PPP_ShutdownModule()
{
  ruby_cleanup(0);
}

/******************************************************************************
 * Overwrites rb_file_load_ok
 ******************************************************************************/

static void
load_ok_internal(void* data, int32_t unused)
{
  /* PPAPI main thread */
  struct PepperInstance* const instance = (struct PepperInstance*)data;
  const char *const path = (const char*)instance->async_call_args;
  PP_Resource req;
  int result;

  instance->url_loader = loader_interface->Create(instance->instance);
  req = request_interface->Create(instance->instance);
  request_interface->SetProperty(
      req, PP_URLREQUESTPROPERTY_METHOD, pruby_cstr_to_var("HEAD"));
  request_interface->SetProperty(
      req, PP_URLREQUESTPROPERTY_URL, pruby_cstr_to_var(path));

  result = loader_interface->Open(
      instance->url_loader, req,
      PP_MakeCompletionCallback(pruby_async_return_int, instance));
  if (result != PP_OK_COMPLETIONPENDING) {
    pruby_async_return_int(instance, result);
  }
}

static void
pruby_file_fetch_check_response(void* data, int32_t unused)
{
  /* PPAPI main thread */
  PP_Resource res;
  struct PepperInstance* const instance = (struct PepperInstance*)data;

  res = loader_interface->GetResponseInfo(instance->url_loader);
  if (res) {
    struct PP_Var status =
        response_interface->GetProperty(res, PP_URLRESPONSEPROPERTY_STATUSCODE);
    if (status.type == PP_VARTYPE_INT32) {
      pruby_async_return_int(instance, status.value.as_int / 100 == 2 ? PP_OK : PP_ERROR_FAILED);
      return;
    }
    else {
      messaging_interface->PostMessage(
          instance->instance, pruby_cstr_to_var("Unexpected type: ResponseInfoInterface::GetProperty"));
    }
  }
  else {
    messaging_interface->PostMessage(
        instance->instance, pruby_cstr_to_var("Failed to open URL: URLLoaderInterface::GetResponseInfo"));
  }
  pruby_async_return_int(instance, PP_ERROR_FAILED);
}


int
rb_file_load_ok(const char *path)
{
  struct PepperInstance* const instance = GET_PEPPER_INSTANCE();
  if (path[0] == '.' && path[1] == '/') path += 2;

  instance->async_call_args = (void*)path;
  core_interface->CallOnMainThread(
      0, PP_MakeCompletionCallback(load_ok_internal, instance), 0);
  if (pthread_cond_wait(&instance->cond, &instance->mutex)) {
    perror("pepper-ruby:pthread_cond_wait");
    return 0;
  }
  if (instance->async_call_result.as_int != PP_OK) {
    fprintf(stderr, "Failed to open URL: %d: %s\n",
            instance->async_call_result.as_int, path);
    return 0;
  }

  core_interface->CallOnMainThread(
      0, PP_MakeCompletionCallback(pruby_file_fetch_check_response, instance), 0);
  if (pthread_cond_wait(&instance->cond, &instance->mutex)) {
    perror("pepper-ruby:pthread_cond_wait");
    return 0;
  }
  return instance->async_call_result.as_int == PP_OK;
}

/******************************************************************************
 * Overwrites rb_load_file
 ******************************************************************************/

static void
load_file_internal(void* data, int32_t unused)
{
  /* PPAPI main thread */
  struct PepperInstance* const instance = (struct PepperInstance*)data;
  const char *const path = (const char*)instance->async_call_args;
  PP_Resource req;
  int result;

  instance->url_loader = loader_interface->Create(instance->instance);
  req = request_interface->Create(instance->instance);
  request_interface->SetProperty(
      req, PP_URLREQUESTPROPERTY_METHOD, pruby_cstr_to_var("GET"));
  request_interface->SetProperty(
      req, PP_URLREQUESTPROPERTY_URL, pruby_cstr_to_var(path));

  result = loader_interface->Open(
      instance->url_loader, req,
      PP_MakeCompletionCallback(pruby_async_return_int, instance));
  if (result != PP_OK_COMPLETIONPENDING) {
    pruby_async_return_int(instance, result);
  }
}

static void
load_file_read_contents_callback(void *data, int result)
{
  struct PepperInstance* const instance = (struct PepperInstance*)data;
  if (result > 0) {
    rb_str_buf_cat(instance->async_call_result.as_value,
                   instance->buf, result);
    loader_interface->ReadResponseBody(
        instance->url_loader, instance->buf, 1000, PP_MakeCompletionCallback(load_file_read_contents_callback, instance));
  }
  else if (result == 0) {
    pruby_async_return_value(data, instance->async_call_result.as_value);
  }
  else {
    pruby_async_return_value(data, INT2FIX(result));
  }
}

static void
load_file_read_contents(void *data, int result)
{
  struct PepperInstance* const instance = (struct PepperInstance*)data;
  instance->async_call_result.as_value = rb_str_new(0, 0);
  loader_interface->ReadResponseBody(
      instance->url_loader, instance->buf, 1000, PP_MakeCompletionCallback(load_file_read_contents_callback, instance));
}

void*
rb_load_file(const char *path)
{
  const char *real_path;
  struct PepperInstance* instance;
  if (path[0] != '.' || path[1] != '/') path += 2;

  instance = GET_PEPPER_INSTANCE();

  instance->async_call_args = (void*)path;
  core_interface->CallOnMainThread(
      0, PP_MakeCompletionCallback(load_file_internal, instance), 0);
  if (pthread_cond_wait(&instance->cond, &instance->mutex)) {
    perror("pepper-ruby:pthread_cond_wait");
    return 0;
  }
  if (instance->async_call_result.as_int != PP_OK) {
    fprintf(stderr, "Failed to open URL: %d: %s\n",
            instance->async_call_result.as_int, path);
    return 0;
  }

  core_interface->CallOnMainThread(
      0, PP_MakeCompletionCallback(pruby_file_fetch_check_response, instance), 0);
  if (pthread_cond_wait(&instance->cond, &instance->mutex)) {
    perror("pepper-ruby:pthread_cond_wait");
    return 0;
  }
  if (instance->async_call_result.as_int != PP_OK) return 0;

  core_interface->CallOnMainThread(
      0, PP_MakeCompletionCallback(load_file_read_contents, instance), 0);
  if (pthread_cond_wait(&instance->cond, &instance->mutex)) {
    perror("pepper-ruby:pthread_cond_wait");
    return 0;
  }
  if (FIXNUM_P(instance->async_call_result.as_value)) {
    return 0;
  }
  else if (RB_TYPE_P(instance->async_call_result.as_value, T_STRING)) {
    VALUE str = instance->async_call_result.as_value;
    extern void* rb_compile_cstr(const char *f, const char *s, int len, int line);
    return rb_compile_cstr(path, RSTRING_PTR(str), RSTRING_LEN(str), 0);
  }
  else {
    return 0;
  }
}