Javascript  |  163行  |  5.35 KB

// 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.

var exceptionHandler = require('uncaught_exception_handler');
var lastError = require('lastError');
var logging = requireNative('logging');
var natives = requireNative('sendRequest');
var validate = require('schemaUtils').validate;

// All outstanding requests from sendRequest().
var requests = {};

// Used to prevent double Activity Logging for API calls that use both custom
// bindings and ExtensionFunctions (via sendRequest).
var calledSendRequest = false;

// Runs a user-supplied callback safely.
function safeCallbackApply(name, request, callback, args) {
  try {
    $Function.apply(callback, request, args);
  } catch (e) {
    exceptionHandler.handle('Error in response to ' + name, e, request.stack);
  }
}

// Callback handling.
function handleResponse(requestId, name, success, responseList, error) {
  // The chrome objects we will set lastError on. Really we should only be
  // setting this on the callback's chrome object, but set on ours too since
  // it's conceivable that something relies on that.
  var callerChrome = chrome;

  try {
    var request = requests[requestId];
    logging.DCHECK(request != null);

    // lastError needs to be set on the caller's chrome object no matter what,
    // though chances are it's the same as ours (it will be different when
    // calling API methods on other contexts).
    if (request.callback)
      callerChrome = natives.GetGlobal(request.callback).chrome;

    lastError.clear(chrome);
    if (callerChrome !== chrome)
      lastError.clear(callerChrome);

    if (!success) {
      if (!error)
        error = "Unknown error.";
      lastError.set(name, error, request.stack, chrome);
      if (callerChrome !== chrome)
        lastError.set(name, error, request.stack, callerChrome);
    }

    if (request.customCallback) {
      safeCallbackApply(name,
                        request,
                        request.customCallback,
                        $Array.concat([name, request], responseList));
    }

    if (request.callback) {
      // Validate callback in debug only -- and only when the
      // caller has provided a callback. Implementations of api
      // calls may not return data if they observe the caller
      // has not provided a callback.
      if (logging.DCHECK_IS_ON() && !error) {
        if (!request.callbackSchema.parameters)
          throw new Error(name + ": no callback schema defined");
        validate(responseList, request.callbackSchema.parameters);
      }
      safeCallbackApply(name, request, request.callback, responseList);
    }

    if (error &&
        !lastError.hasAccessed(chrome) &&
        !lastError.hasAccessed(callerChrome)) {
      // The native call caused an error, but the developer didn't check
      // runtime.lastError.
      // Notify the developer of the error via the (error) console.
      console.error("Unchecked runtime.lastError while running " +
          (name || "unknown") + ": " + error +
          (request.stack ? "\n" + request.stack : ""));
    }
  } finally {
    delete requests[requestId];
    lastError.clear(chrome);
    if (callerChrome !== chrome)
      lastError.clear(callerChrome);
  }
}

function prepareRequest(args, argSchemas) {
  var request = {};
  var argCount = args.length;

  // Look for callback param.
  if (argSchemas.length > 0 &&
      argSchemas[argSchemas.length - 1].type == "function") {
    request.callback = args[args.length - 1];
    request.callbackSchema = argSchemas[argSchemas.length - 1];
    --argCount;
  }

  request.args = [];
  for (var k = 0; k < argCount; k++) {
    request.args[k] = args[k];
  }

  return request;
}

// Send an API request and optionally register a callback.
// |optArgs| is an object with optional parameters as follows:
// - customCallback: a callback that should be called instead of the standard
//   callback.
// - nativeFunction: the v8 native function to handle the request, or
//   StartRequest if missing.
// - forIOThread: true if this function should be handled on the browser IO
//   thread.
// - preserveNullInObjects: true if it is safe for null to be in objects.
function sendRequest(functionName, args, argSchemas, optArgs) {
  calledSendRequest = true;
  if (!optArgs)
    optArgs = {};
  var request = prepareRequest(args, argSchemas);
  request.stack = exceptionHandler.getExtensionStackTrace();
  if (optArgs.customCallback) {
    request.customCallback = optArgs.customCallback;
  }

  var nativeFunction = optArgs.nativeFunction || natives.StartRequest;

  var requestId = natives.GetNextRequestId();
  request.id = requestId;
  requests[requestId] = request;

  var hasCallback = request.callback || optArgs.customCallback;
  return nativeFunction(functionName,
                        request.args,
                        requestId,
                        hasCallback,
                        optArgs.forIOThread,
                        optArgs.preserveNullInObjects);
}

function getCalledSendRequest() {
  return calledSendRequest;
}

function clearCalledSendRequest() {
  calledSendRequest = false;
}

exports.sendRequest = sendRequest;
exports.getCalledSendRequest = getCalledSendRequest;
exports.clearCalledSendRequest = clearCalledSendRequest;
exports.safeCallbackApply = safeCallbackApply;

// Called by C++.
exports.handleResponse = handleResponse;