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