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 if (id != '') 87 runtime.id = id; 88 89 apiFunctions.setHandleRequest('getManifest', function() { 90 return runtimeNatives.GetManifest(); 91 }); 92 93 apiFunctions.setHandleRequest('getURL', function(path) { 94 path = String(path); 95 if (!path.length || path[0] != '/') 96 path = '/' + path; 97 return 'chrome-extension://' + id + path; 98 }); 99 100 var sendMessageUpdateArguments = messaging.sendMessageUpdateArguments; 101 apiFunctions.setUpdateArgumentsPreValidate('sendMessage', 102 $Function.bind(sendMessageUpdateArguments, null, 'sendMessage', 103 true /* hasOptionsArgument */)); 104 apiFunctions.setUpdateArgumentsPreValidate('sendNativeMessage', 105 $Function.bind(sendMessageUpdateArguments, null, 'sendNativeMessage', 106 false /* hasOptionsArgument */)); 107 108 apiFunctions.setHandleRequest('sendMessage', 109 function(targetId, message, options, responseCallback) { 110 var connectOptions = {name: messaging.kMessageChannel}; 111 forEach(options, function(k, v) { 112 connectOptions[k] = v; 113 }); 114 var port = runtime.connect(targetId || runtime.id, connectOptions); 115 messaging.sendMessageImpl(port, message, responseCallback); 116 }); 117 118 apiFunctions.setHandleRequest('sendNativeMessage', 119 function(targetId, message, responseCallback) { 120 var port = runtime.connectNative(targetId); 121 messaging.sendMessageImpl(port, message, responseCallback); 122 }); 123 124 apiFunctions.setUpdateArgumentsPreValidate('connect', function() { 125 // Align missing (optional) function arguments with the arguments that 126 // schema validation is expecting, e.g. 127 // runtime.connect() -> runtime.connect(null, null) 128 // runtime.connect({}) -> runtime.connect(null, {}) 129 var nextArg = 0; 130 131 // targetId (first argument) is optional. 132 var targetId = null; 133 if (typeof(arguments[nextArg]) == 'string') 134 targetId = arguments[nextArg++]; 135 136 // connectInfo (second argument) is optional. 137 var connectInfo = null; 138 if (typeof(arguments[nextArg]) == 'object') 139 connectInfo = arguments[nextArg++]; 140 141 if (nextArg != arguments.length) 142 throw new Error('Invalid arguments to connect.'); 143 return [targetId, connectInfo]; 144 }); 145 146 apiFunctions.setUpdateArgumentsPreValidate('connectNative', 147 function(appName) { 148 if (typeof(appName) !== 'string') { 149 throw new Error('Invalid arguments to connectNative.'); 150 } 151 return [appName]; 152 }); 153 154 apiFunctions.setHandleRequest('connect', function(targetId, connectInfo) { 155 // Don't let orphaned content scripts communicate with their extension. 156 // http://crbug.com/168263 157 if (unloadEvent.wasDispatched) 158 throw new Error('Error connecting to extension ' + targetId); 159 160 if (!targetId) 161 targetId = runtime.id; 162 163 var name = ''; 164 if (connectInfo && connectInfo.name) 165 name = connectInfo.name; 166 167 var includeTlsChannelId = 168 !!(connectInfo && connectInfo.includeTlsChannelId); 169 170 var portId = runtimeNatives.OpenChannelToExtension(targetId, name, 171 includeTlsChannelId); 172 if (portId >= 0) 173 return messaging.createPort(portId, name); 174 }); 175 176 // 177 // Privileged APIs. 178 // 179 if (contextType != 'BLESSED_EXTENSION') 180 return; 181 182 apiFunctions.setHandleRequest('connectNative', 183 function(nativeAppName) { 184 if (!unloadEvent.wasDispatched) { 185 var portId = runtimeNatives.OpenChannelToNativeApp(runtime.id, 186 nativeAppName); 187 if (portId >= 0) 188 return messaging.createPort(portId, ''); 189 } 190 throw new Error('Error connecting to native app: ' + nativeAppName); 191 }); 192 193 apiFunctions.setCustomCallback('getBackgroundPage', 194 function(name, request, response) { 195 if (request.callback) { 196 var bg = runtimeNatives.GetExtensionViews(-1, 'BACKGROUND')[0] || null; 197 request.callback(bg); 198 } 199 request.callback = null; 200 }); 201 202 bindDirectoryEntryCallback('getPackageDirectoryEntry', apiFunctions); 203}); 204 205exports.bindDirectoryEntryCallback = bindDirectoryEntryCallback; 206exports.binding = binding.generate(); 207