Home | History | Annotate | Download | only in resources
      1 // Copyright 2014 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 // Custom binding for the runtime API.
      6 
      7 var binding = require('binding').Binding.create('runtime');
      8 
      9 var messaging = require('messaging');
     10 var runtimeNatives = requireNative('runtime');
     11 var unloadEvent = require('unload_event');
     12 var process = requireNative('process');
     13 var forEach = require('utils').forEach;
     14 
     15 var backgroundPage = window;
     16 var backgroundRequire = require;
     17 var contextType = process.GetContextType();
     18 if (contextType == 'BLESSED_EXTENSION' ||
     19     contextType == 'UNBLESSED_EXTENSION') {
     20   var manifest = runtimeNatives.GetManifest();
     21   if (manifest.app && manifest.app.background) {
     22     // Get the background page if one exists. Otherwise, default to the current
     23     // window.
     24     backgroundPage = runtimeNatives.GetExtensionViews(-1, 'BACKGROUND')[0];
     25     if (backgroundPage) {
     26       var GetModuleSystem = requireNative('v8_context').GetModuleSystem;
     27       backgroundRequire = GetModuleSystem(backgroundPage).require;
     28     } else {
     29       backgroundPage = window;
     30     }
     31   }
     32 }
     33 
     34 // For packaged apps, all windows use the bindFileEntryCallback from the
     35 // background page so their FileEntry objects have the background page's context
     36 // as their own.  This allows them to be used from other windows (including the
     37 // background page) after the original window is closed.
     38 if (window == backgroundPage) {
     39   var lastError = require('lastError');
     40   var fileSystemNatives = requireNative('file_system_natives');
     41   var GetIsolatedFileSystem = fileSystemNatives.GetIsolatedFileSystem;
     42   var bindDirectoryEntryCallback = function(functionName, apiFunctions) {
     43     apiFunctions.setCustomCallback(functionName,
     44         function(name, request, response) {
     45       if (request.callback && response) {
     46         var callback = request.callback;
     47         request.callback = null;
     48 
     49         var fileSystemId = response.fileSystemId;
     50         var baseName = response.baseName;
     51         var fs = GetIsolatedFileSystem(fileSystemId);
     52 
     53         try {
     54           fs.root.getDirectory(baseName, {}, callback, function(fileError) {
     55             lastError.run('runtime.' + functionName,
     56                           'Error getting Entry, code: ' + fileError.code,
     57                           request.stack,
     58                           callback);
     59           });
     60         } catch (e) {
     61           lastError.run('runtime.' + functionName,
     62                         'Error: ' + e.stack,
     63                         request.stack,
     64                         callback);
     65         }
     66       }
     67     });
     68   };
     69 } else {
     70   // Force the runtime API to be loaded in the background page. Using
     71   // backgroundPageModuleSystem.require('runtime') is insufficient as
     72   // requireNative is only allowed while lazily loading an API.
     73   backgroundPage.chrome.runtime;
     74   var bindDirectoryEntryCallback = backgroundRequire(
     75       'runtime').bindDirectoryEntryCallback;
     76 }
     77 
     78 binding.registerCustomHook(function(binding, id, contextType) {
     79   var apiFunctions = binding.apiFunctions;
     80   var runtime = binding.compiledApi;
     81 
     82   //
     83   // Unprivileged APIs.
     84   //
     85 
     86   runtime.id = id;
     87 
     88   apiFunctions.setHandleRequest('getManifest', function() {
     89     return runtimeNatives.GetManifest();
     90   });
     91 
     92   apiFunctions.setHandleRequest('getURL', function(path) {
     93     path = String(path);
     94     if (!path.length || path[0] != '/')
     95       path = '/' + path;
     96     return 'chrome-extension://' + id + path;
     97   });
     98 
     99   var sendMessageUpdateArguments = messaging.sendMessageUpdateArguments;
    100   apiFunctions.setUpdateArgumentsPreValidate('sendMessage',
    101       $Function.bind(sendMessageUpdateArguments, null, 'sendMessage',
    102                      true /* hasOptionsArgument */));
    103   apiFunctions.setUpdateArgumentsPreValidate('sendNativeMessage',
    104       $Function.bind(sendMessageUpdateArguments, null, 'sendNativeMessage',
    105                      false /* hasOptionsArgument */));
    106 
    107   apiFunctions.setHandleRequest('sendMessage',
    108       function(targetId, message, options, responseCallback) {
    109     var connectOptions = {name: messaging.kMessageChannel};
    110     forEach(options, function(k, v) {
    111       connectOptions[k] = v;
    112     });
    113     var port = runtime.connect(targetId || runtime.id, connectOptions);
    114     messaging.sendMessageImpl(port, message, responseCallback);
    115   });
    116 
    117   apiFunctions.setHandleRequest('sendNativeMessage',
    118                                 function(targetId, message, responseCallback) {
    119     var port = runtime.connectNative(targetId);
    120     messaging.sendMessageImpl(port, message, responseCallback);
    121   });
    122 
    123   apiFunctions.setUpdateArgumentsPreValidate('connect', function() {
    124     // Align missing (optional) function arguments with the arguments that
    125     // schema validation is expecting, e.g.
    126     //   runtime.connect()   -> runtime.connect(null, null)
    127     //   runtime.connect({}) -> runtime.connect(null, {})
    128     var nextArg = 0;
    129 
    130     // targetId (first argument) is optional.
    131     var targetId = null;
    132     if (typeof(arguments[nextArg]) == 'string')
    133       targetId = arguments[nextArg++];
    134 
    135     // connectInfo (second argument) is optional.
    136     var connectInfo = null;
    137     if (typeof(arguments[nextArg]) == 'object')
    138       connectInfo = arguments[nextArg++];
    139 
    140     if (nextArg != arguments.length)
    141       throw new Error('Invalid arguments to connect.');
    142     return [targetId, connectInfo];
    143   });
    144 
    145   apiFunctions.setUpdateArgumentsPreValidate('connectNative',
    146                                              function(appName) {
    147     if (typeof(appName) !== 'string') {
    148       throw new Error('Invalid arguments to connectNative.');
    149     }
    150     return [appName];
    151   });
    152 
    153   apiFunctions.setHandleRequest('connect', function(targetId, connectInfo) {
    154     // Don't let orphaned content scripts communicate with their extension.
    155     // http://crbug.com/168263
    156     if (unloadEvent.wasDispatched)
    157       throw new Error('Error connecting to extension ' + targetId);
    158 
    159     if (!targetId)
    160       targetId = runtime.id;
    161 
    162     var name = '';
    163     if (connectInfo && connectInfo.name)
    164       name = connectInfo.name;
    165 
    166     var includeTlsChannelId =
    167       !!(connectInfo && connectInfo.includeTlsChannelId);
    168 
    169     var portId = runtimeNatives.OpenChannelToExtension(targetId, name,
    170                                                        includeTlsChannelId);
    171     if (portId >= 0)
    172       return messaging.createPort(portId, name);
    173   });
    174 
    175   //
    176   // Privileged APIs.
    177   //
    178   if (contextType != 'BLESSED_EXTENSION')
    179     return;
    180 
    181   apiFunctions.setHandleRequest('connectNative',
    182                                 function(nativeAppName) {
    183     if (!unloadEvent.wasDispatched) {
    184       var portId = runtimeNatives.OpenChannelToNativeApp(runtime.id,
    185                                                          nativeAppName);
    186       if (portId >= 0)
    187         return messaging.createPort(portId, '');
    188     }
    189     throw new Error('Error connecting to native app: ' + nativeAppName);
    190   });
    191 
    192   apiFunctions.setCustomCallback('getBackgroundPage',
    193                                  function(name, request, response) {
    194     if (request.callback) {
    195       var bg = runtimeNatives.GetExtensionViews(-1, 'BACKGROUND')[0] || null;
    196       request.callback(bg);
    197     }
    198     request.callback = null;
    199   });
    200 
    201   bindDirectoryEntryCallback('getPackageDirectoryEntry', apiFunctions);
    202 });
    203 
    204 exports.bindDirectoryEntryCallback = bindDirectoryEntryCallback;
    205 exports.binding = binding.generate();
    206