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