// Copyright 2014 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // Custom binding for the runtime API. var binding = require('binding').Binding.create('runtime'); var messaging = require('messaging'); var runtimeNatives = requireNative('runtime'); var unloadEvent = require('unload_event'); var process = requireNative('process'); var forEach = require('utils').forEach; var backgroundPage = window; var backgroundRequire = require; var contextType = process.GetContextType(); if (contextType == 'BLESSED_EXTENSION' || contextType == 'UNBLESSED_EXTENSION') { var manifest = runtimeNatives.GetManifest(); if (manifest.app && manifest.app.background) { // Get the background page if one exists. Otherwise, default to the current // window. backgroundPage = runtimeNatives.GetExtensionViews(-1, 'BACKGROUND')[0]; if (backgroundPage) { var GetModuleSystem = requireNative('v8_context').GetModuleSystem; backgroundRequire = GetModuleSystem(backgroundPage).require; } else { backgroundPage = window; } } } // For packaged apps, all windows use the bindFileEntryCallback from the // background page so their FileEntry objects have the background page's context // as their own. This allows them to be used from other windows (including the // background page) after the original window is closed. if (window == backgroundPage) { var lastError = require('lastError'); var fileSystemNatives = requireNative('file_system_natives'); var GetIsolatedFileSystem = fileSystemNatives.GetIsolatedFileSystem; var bindDirectoryEntryCallback = function(functionName, apiFunctions) { apiFunctions.setCustomCallback(functionName, function(name, request, response) { if (request.callback && response) { var callback = request.callback; request.callback = null; var fileSystemId = response.fileSystemId; var baseName = response.baseName; var fs = GetIsolatedFileSystem(fileSystemId); try { fs.root.getDirectory(baseName, {}, callback, function(fileError) { lastError.run('runtime.' + functionName, 'Error getting Entry, code: ' + fileError.code, request.stack, callback); }); } catch (e) { lastError.run('runtime.' + functionName, 'Error: ' + e.stack, request.stack, callback); } } }); }; } else { // Force the runtime API to be loaded in the background page. Using // backgroundPageModuleSystem.require('runtime') is insufficient as // requireNative is only allowed while lazily loading an API. backgroundPage.chrome.runtime; var bindDirectoryEntryCallback = backgroundRequire( 'runtime').bindDirectoryEntryCallback; } binding.registerCustomHook(function(binding, id, contextType) { var apiFunctions = binding.apiFunctions; var runtime = binding.compiledApi; // // Unprivileged APIs. // if (id != '') runtime.id = id; apiFunctions.setHandleRequest('getManifest', function() { return runtimeNatives.GetManifest(); }); apiFunctions.setHandleRequest('getURL', function(path) { path = String(path); if (!path.length || path[0] != '/') path = '/' + path; return 'chrome-extension://' + id + path; }); var sendMessageUpdateArguments = messaging.sendMessageUpdateArguments; apiFunctions.setUpdateArgumentsPreValidate('sendMessage', $Function.bind(sendMessageUpdateArguments, null, 'sendMessage', true /* hasOptionsArgument */)); apiFunctions.setUpdateArgumentsPreValidate('sendNativeMessage', $Function.bind(sendMessageUpdateArguments, null, 'sendNativeMessage', false /* hasOptionsArgument */)); apiFunctions.setHandleRequest('sendMessage', function(targetId, message, options, responseCallback) { var connectOptions = {name: messaging.kMessageChannel}; forEach(options, function(k, v) { connectOptions[k] = v; }); var port = runtime.connect(targetId || runtime.id, connectOptions); messaging.sendMessageImpl(port, message, responseCallback); }); apiFunctions.setHandleRequest('sendNativeMessage', function(targetId, message, responseCallback) { var port = runtime.connectNative(targetId); messaging.sendMessageImpl(port, message, responseCallback); }); apiFunctions.setUpdateArgumentsPreValidate('connect', function() { // Align missing (optional) function arguments with the arguments that // schema validation is expecting, e.g. // runtime.connect() -> runtime.connect(null, null) // runtime.connect({}) -> runtime.connect(null, {}) var nextArg = 0; // targetId (first argument) is optional. var targetId = null; if (typeof(arguments[nextArg]) == 'string') targetId = arguments[nextArg++]; // connectInfo (second argument) is optional. var connectInfo = null; if (typeof(arguments[nextArg]) == 'object') connectInfo = arguments[nextArg++]; if (nextArg != arguments.length) throw new Error('Invalid arguments to connect.'); return [targetId, connectInfo]; }); apiFunctions.setUpdateArgumentsPreValidate('connectNative', function(appName) { if (typeof(appName) !== 'string') { throw new Error('Invalid arguments to connectNative.'); } return [appName]; }); apiFunctions.setHandleRequest('connect', function(targetId, connectInfo) { // Don't let orphaned content scripts communicate with their extension. // http://crbug.com/168263 if (unloadEvent.wasDispatched) throw new Error('Error connecting to extension ' + targetId); if (!targetId) targetId = runtime.id; var name = ''; if (connectInfo && connectInfo.name) name = connectInfo.name; var includeTlsChannelId = !!(connectInfo && connectInfo.includeTlsChannelId); var portId = runtimeNatives.OpenChannelToExtension(targetId, name, includeTlsChannelId); if (portId >= 0) return messaging.createPort(portId, name); }); // // Privileged APIs. // if (contextType != 'BLESSED_EXTENSION') return; apiFunctions.setHandleRequest('connectNative', function(nativeAppName) { if (!unloadEvent.wasDispatched) { var portId = runtimeNatives.OpenChannelToNativeApp(runtime.id, nativeAppName); if (portId >= 0) return messaging.createPort(portId, ''); } throw new Error('Error connecting to native app: ' + nativeAppName); }); apiFunctions.setCustomCallback('getBackgroundPage', function(name, request, response) { if (request.callback) { var bg = runtimeNatives.GetExtensionViews(-1, 'BACKGROUND')[0] || null; request.callback(bg); } request.callback = null; }); bindDirectoryEntryCallback('getPackageDirectoryEntry', apiFunctions); }); exports.bindDirectoryEntryCallback = bindDirectoryEntryCallback; exports.binding = binding.generate();