• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
7var binding = require('binding').Binding.create('runtime');
8
9var messaging = require('messaging');
10var runtimeNatives = requireNative('runtime');
11var unloadEvent = require('unload_event');
12var process = requireNative('process');
13var forEach = require('utils').forEach;
14
15var backgroundPage = window;
16var backgroundRequire = require;
17var contextType = process.GetContextType();
18if (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.
38if (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
78binding.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
204exports.bindDirectoryEntryCallback = bindDirectoryEntryCallback;
205exports.binding = binding.generate();
206