1 // Copyright (c) 2012 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 var lastError = require('lastError'); 6 var logging = requireNative('logging'); 7 var natives = requireNative('sendRequest'); 8 var processNatives = requireNative('process'); 9 var validate = require('schemaUtils').validate; 10 11 // All outstanding requests from sendRequest(). 12 var requests = {}; 13 14 // Used to prevent double Activity Logging for API calls that use both custom 15 // bindings and ExtensionFunctions (via sendRequest). 16 var calledSendRequest = false; 17 18 // Runs a user-supplied callback safely. 19 function safeCallbackApply(name, request, callback, args) { 20 try { 21 $Function.apply(callback, request, args); 22 } catch (e) { 23 var errorMessage = "Error in response to " + name + ": " + e; 24 if (request.stack && request.stack != '') 25 errorMessage += "\n" + request.stack; 26 console.error(errorMessage); 27 } 28 } 29 30 // Callback handling. 31 function handleResponse(requestId, name, success, responseList, error) { 32 // The chrome objects we will set lastError on. Really we should only be 33 // setting this on the callback's chrome object, but set on ours too since 34 // it's conceivable that something relies on that. 35 var chromesForLastError = [chrome]; 36 37 try { 38 var request = requests[requestId]; 39 logging.DCHECK(request != null); 40 41 // lastError needs to be set on the caller's chrome object no matter what, 42 // though chances are it's the same as ours (it will be different when 43 // calling API methods on other contexts). 44 if (request.callback) { 45 var chromeForCallback = natives.GetGlobal(request.callback).chrome; 46 if (chromeForCallback != chrome) 47 $Array.push(chromesForLastError, chromeForCallback); 48 } 49 50 $Array.forEach(chromesForLastError, function(c) {lastError.clear(c)}); 51 if (!success) { 52 if (!error) 53 error = "Unknown error."; 54 $Array.forEach(chromesForLastError, function(c) { 55 lastError.set(name, error, request.stack, c) 56 }); 57 } 58 59 if (request.customCallback) { 60 safeCallbackApply(name, 61 request, 62 request.customCallback, 63 $Array.concat([name, request], responseList)); 64 } 65 66 if (request.callback) { 67 // Validate callback in debug only -- and only when the 68 // caller has provided a callback. Implementations of api 69 // calls may not return data if they observe the caller 70 // has not provided a callback. 71 if (logging.DCHECK_IS_ON() && !error) { 72 if (!request.callbackSchema.parameters) 73 throw new Error(name + ": no callback schema defined"); 74 validate(responseList, request.callbackSchema.parameters); 75 } 76 safeCallbackApply(name, request, request.callback, responseList); 77 } 78 } finally { 79 delete requests[requestId]; 80 $Array.forEach(chromesForLastError, function(c) {lastError.clear(c)}); 81 } 82 }; 83 84 function getExtensionStackTrace(call_name) { 85 var stack = $String.split(new Error().stack, '\n'); 86 87 // Remove stack frames before and after that weren't associated with the 88 // extension. 89 var id = processNatives.GetExtensionId(); 90 while (stack.length > 0 && stack[0].indexOf(id) == -1) 91 stack.shift(); 92 while (stack.length > 0 && stack[stack.length - 1].indexOf(id) == -1) 93 stack.pop(); 94 95 return stack.join('\n'); 96 } 97 98 function prepareRequest(args, argSchemas) { 99 var request = {}; 100 var argCount = args.length; 101 102 // Look for callback param. 103 if (argSchemas.length > 0 && 104 argSchemas[argSchemas.length - 1].type == "function") { 105 request.callback = args[args.length - 1]; 106 request.callbackSchema = argSchemas[argSchemas.length - 1]; 107 --argCount; 108 } 109 110 request.args = []; 111 for (var k = 0; k < argCount; k++) { 112 request.args[k] = args[k]; 113 } 114 115 return request; 116 } 117 118 // Send an API request and optionally register a callback. 119 // |optArgs| is an object with optional parameters as follows: 120 // - customCallback: a callback that should be called instead of the standard 121 // callback. 122 // - nativeFunction: the v8 native function to handle the request, or 123 // StartRequest if missing. 124 // - forIOThread: true if this function should be handled on the browser IO 125 // thread. 126 // - preserveNullInObjects: true if it is safe for null to be in objects. 127 function sendRequest(functionName, args, argSchemas, optArgs) { 128 calledSendRequest = true; 129 if (!optArgs) 130 optArgs = {}; 131 var request = prepareRequest(args, argSchemas); 132 request.stack = getExtensionStackTrace(); 133 if (optArgs.customCallback) { 134 request.customCallback = optArgs.customCallback; 135 } 136 137 var nativeFunction = optArgs.nativeFunction || natives.StartRequest; 138 139 var requestId = natives.GetNextRequestId(); 140 request.id = requestId; 141 requests[requestId] = request; 142 143 var hasCallback = request.callback || optArgs.customCallback; 144 return nativeFunction(functionName, 145 request.args, 146 requestId, 147 hasCallback, 148 optArgs.forIOThread, 149 optArgs.preserveNullInObjects); 150 } 151 152 function getCalledSendRequest() { 153 return calledSendRequest; 154 } 155 156 function clearCalledSendRequest() { 157 calledSendRequest = false; 158 } 159 160 exports.sendRequest = sendRequest; 161 exports.getCalledSendRequest = getCalledSendRequest; 162 exports.clearCalledSendRequest = clearCalledSendRequest; 163 164 // Called by C++. 165 exports.handleResponse = handleResponse; 166