summaryrefslogtreecommitdiffstats
path: root/extensions/renderer/resources/context_menus_handlers.js
blob: aef6889d7a35afefe0f8d0a936d6795db7fa2f06 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
// Copyright 2015 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.

// Implementation of custom bindings for the contextMenus API.
// This is used to implement the contextMenus API for extensions and for the
// <webview> tag (see chrome_web_view_experimental.js).

var contextMenuNatives = requireNative('context_menus');
var sendRequest = require('sendRequest').sendRequest;
var Event = require('event_bindings').Event;
var lastError = require('lastError');

// Add the bindings to the contextMenus API.
function createContextMenusHandlers(isWebview) {
  var eventName = isWebview ? 'webViewInternal.contextMenus' : 'contextMenus';
  // Some dummy value for chrome.contextMenus instances.
  // Webviews use positive integers, and 0 to denote an invalid webview ID.
  // The following constant is -1 to avoid any conflicts between webview IDs and
  // extensions.
  var INSTANCEID_NON_WEBVIEW = -1;

  // Generates a customCallback for a given method. |handleCallback| will be
  // invoked with |request.args| as parameters.
  function createCustomCallback(handleCallback) {
    return function(name, request, callback) {
      if (lastError.hasError(chrome)) {
        if (callback)
          callback();
        return;
      }
      var args = request.args;
      if (!isWebview) {
        // <webview>s have an extra item in front of the parameter list, which
        // specifies the viewInstanceId of the webview. This is used to hide
        // context menu events in one webview from another.
        // The non-webview chrome.contextMenus API is not called with such an
        // ID, so we prepend an ID to match the function signature.
        args = $Array.concat([INSTANCEID_NON_WEBVIEW], args);
      }
      $Function.apply(handleCallback, null, args);
      if (callback)
        callback();
    };
  }

  var contextMenus = {};
  contextMenus.handlers = {};
  contextMenus.event = new Event(eventName);

  contextMenus.getIdFromCreateProperties = function(createProperties) {
    if (typeof createProperties.id !== 'undefined')
      return createProperties.id;
    return createProperties.generatedId;
  };

  contextMenus.handlersForId = function(instanceId, id) {
    if (!contextMenus.handlers[instanceId]) {
      contextMenus.handlers[instanceId] = {
        generated: {},
        string: {}
      };
    }
    if (typeof id === 'number')
      return contextMenus.handlers[instanceId].generated;
    return contextMenus.handlers[instanceId].string;
  };

  contextMenus.ensureListenerSetup = function() {
    if (contextMenus.listening) {
      return;
    }
    contextMenus.listening = true;
    contextMenus.event.addListener(function(info) {
      var instanceId = INSTANCEID_NON_WEBVIEW;
      if (isWebview) {
        instanceId = info.webviewInstanceId;
        // Don't expose |webviewInstanceId| via the public API.
        delete info.webviewInstanceId;
      }

      var id = info.menuItemId;
      var onclick = contextMenus.handlersForId(instanceId, id)[id];
      if (onclick) {
        $Function.apply(onclick, null, arguments);
      }
    });
  };

  // To be used with apiFunctions.setHandleRequest
  var requestHandlers = {};
  // To be used with apiFunctions.setCustomCallback
  var callbacks = {};

  requestHandlers.create = function() {
    var createProperties = isWebview ? arguments[1] : arguments[0];
    createProperties.generatedId = contextMenuNatives.GetNextContextMenuId();
    var optArgs = {
      customCallback: this.customCallback,
    };
    sendRequest(this.name, arguments, this.definition.parameters, optArgs);
    return contextMenus.getIdFromCreateProperties(createProperties);
  };

  callbacks.create =
      createCustomCallback(function(instanceId, createProperties) {
    var id = contextMenus.getIdFromCreateProperties(createProperties);
    var onclick = createProperties.onclick;
    if (onclick) {
      contextMenus.ensureListenerSetup();
      contextMenus.handlersForId(instanceId, id)[id] = onclick;
    }
  });

  callbacks.remove = createCustomCallback(function(instanceId, id) {
    delete contextMenus.handlersForId(instanceId, id)[id];
  });

  callbacks.update =
      createCustomCallback(function(instanceId, id, updateProperties) {
    var onclick = updateProperties.onclick;
    if (onclick) {
      contextMenus.ensureListenerSetup();
      contextMenus.handlersForId(instanceId, id)[id] = onclick;
    } else if (onclick === null) {
      // When onclick is explicitly set to null, remove the event listener.
      delete contextMenus.handlersForId(instanceId, id)[id];
    }
  });

  callbacks.removeAll = createCustomCallback(function(instanceId) {
    delete contextMenus.handlers[instanceId];
  });

  return {
    requestHandlers: requestHandlers,
    callbacks: callbacks
  };
}

exports.$set('create', createContextMenusHandlers);