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