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 exceptionHandler = require('uncaught_exception_handler');
6var lastError = require('lastError');
7var logging = requireNative('logging');
8var natives = requireNative('sendRequest');
9var validate = require('schemaUtils').validate;
10
11// All outstanding requests from sendRequest().
12var requests = {};
13
14// Used to prevent double Activity Logging for API calls that use both custom
15// bindings and ExtensionFunctions (via sendRequest).
16var calledSendRequest = false;
17
18// Runs a user-supplied callback safely.
19function 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.
28function 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
94function 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.
123function 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
148function getCalledSendRequest() {
149  return calledSendRequest;
150}
151
152function clearCalledSendRequest() {
153  calledSendRequest = false;
154}
155
156exports.sendRequest = sendRequest;
157exports.getCalledSendRequest = getCalledSendRequest;
158exports.clearCalledSendRequest = clearCalledSendRequest;
159exports.safeCallbackApply = safeCallbackApply;
160
161// Called by C++.
162exports.handleResponse = handleResponse;
163