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 GetAvailability = requireNative('v8_context').GetAvailability;
6var GetGlobal = requireNative('sendRequest').GetGlobal;
7
8// Utility for setting chrome.*.lastError.
9//
10// A utility here is useful for two reasons:
11//  1. For backwards compatibility we need to set chrome.extension.lastError,
12//     but not all contexts actually have access to the extension namespace.
13//  2. When calling across contexts, the global object that gets lastError set
14//     needs to be that of the caller. We force callers to explicitly specify
15//     the chrome object to try to prevent bugs here.
16
17/**
18 * Sets the last error for |name| on |targetChrome| to |message| with an
19 * optional |stack|.
20 */
21function set(name, message, stack, targetChrome) {
22  var errorMessage = name + ': ' + message;
23  if (stack != null && stack != '')
24    errorMessage += '\n' + stack;
25
26  if (!targetChrome)
27    throw new Error('No chrome object to set error: ' + errorMessage);
28  clear(targetChrome);  // in case somebody has set a sneaky getter/setter
29
30  var errorObject = { message: message };
31  if (GetAvailability('extension.lastError').is_available)
32    targetChrome.extension.lastError = errorObject;
33
34  assertRuntimeIsAvailable();
35
36  // We check to see if developers access runtime.lastError in order to decide
37  // whether or not to log it in the (error) console.
38  privates(targetChrome.runtime).accessedLastError = false;
39  $Object.defineProperty(targetChrome.runtime, 'lastError', {
40      configurable: true,
41      get: function() {
42        privates(targetChrome.runtime).accessedLastError = true;
43        return errorObject;
44      },
45      set: function(error) {
46        errorObject = errorObject;
47      }});
48};
49
50/**
51 * Check if anyone has checked chrome.runtime.lastError since it was set.
52 * @param {Object} targetChrome the Chrome object to check.
53 * @return boolean True if the lastError property was set.
54 */
55function hasAccessed(targetChrome) {
56  assertRuntimeIsAvailable();
57  return privates(targetChrome.runtime).accessedLastError === true;
58}
59
60/**
61 * Check whether there is an error set on |targetChrome| without setting
62 * |accessedLastError|.
63 * @param {Object} targetChrome the Chrome object to check.
64 * @return boolean Whether lastError has been set.
65 */
66function hasError(targetChrome) {
67  if (!targetChrome)
68    throw new Error('No target chrome to check');
69
70  assertRuntimeIsAvailable();
71  if ('lastError' in targetChrome.runtime)
72    return true;
73
74  return false;
75};
76
77/**
78 * Clears the last error on |targetChrome|.
79 */
80function clear(targetChrome) {
81  if (!targetChrome)
82    throw new Error('No target chrome to clear error');
83
84  if (GetAvailability('extension.lastError').is_available)
85   delete targetChrome.extension.lastError;
86
87  assertRuntimeIsAvailable();
88  delete targetChrome.runtime.lastError;
89  delete privates(targetChrome.runtime).accessedLastError;
90};
91
92function assertRuntimeIsAvailable() {
93  // chrome.runtime should always be available, but maybe it's disappeared for
94  // some reason? Add debugging for http://crbug.com/258526.
95  var runtimeAvailability = GetAvailability('runtime.lastError');
96  if (!runtimeAvailability.is_available) {
97    throw new Error('runtime.lastError is not available: ' +
98                    runtimeAvailability.message);
99  }
100  if (!chrome.runtime)
101    throw new Error('runtime namespace is null or undefined');
102}
103
104/**
105 * Runs |callback(args)| with last error args as in set().
106 *
107 * The target chrome object is the global object's of the callback, so this
108 * method won't work if the real callback has been wrapped (etc).
109 */
110function run(name, message, stack, callback, args) {
111  var targetChrome = GetGlobal(callback).chrome;
112  set(name, message, stack, targetChrome);
113  try {
114    $Function.apply(callback, undefined, args);
115  } finally {
116    clear(targetChrome);
117  }
118}
119
120exports.clear = clear;
121exports.hasAccessed = hasAccessed;
122exports.hasError = hasError;
123exports.set = set;
124exports.run = run;
125