1/* vim: set ts=2 sw=2 sts=2 et tw=80: */
2/* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5
6"use strict";
7
8this.EXPORTED_SYMBOLS = [ "ContentPrefServiceChild" ];
9
10const Ci = Components.interfaces;
11const Cc = Components.classes;
12const Cu = Components.utils;
13
14Cu.import("resource://gre/modules/XPCOMUtils.jsm");
15Cu.import("resource://gre/modules/ContentPrefUtils.jsm");
16Cu.import("resource://gre/modules/ContentPrefStore.jsm");
17
18// We only need one bit of information out of the context.
19function contextArg(context) {
20  return (context && context.usePrivateBrowsing) ?
21            { usePrivateBrowsing: true } :
22            null;
23}
24
25function NYI() {
26  throw new Error("Do not add any new users of these functions");
27}
28
29function CallbackCaller(callback) {
30  this._callback = callback;
31}
32
33CallbackCaller.prototype = {
34  handleResult: function(contentPref) {
35    cbHandleResult(this._callback,
36                   new ContentPref(contentPref.domain,
37                                   contentPref.name,
38                                   contentPref.value));
39  },
40
41  handleError: function(result) {
42    cbHandleError(this._callback, result);
43  },
44
45  handleCompletion: function(reason) {
46    cbHandleCompletion(this._callback, reason);
47  },
48};
49
50var ContentPrefServiceChild = {
51  QueryInterface: XPCOMUtils.generateQI([ Ci.nsIContentPrefService2 ]),
52
53  // Map from pref name -> set of observers
54  _observers: new Map(),
55
56  _mm: Cc["@mozilla.org/childprocessmessagemanager;1"]
57         .getService(Ci.nsIMessageSender),
58
59  _getRandomId: function() {
60    return Cc["@mozilla.org/uuid-generator;1"]
61             .getService(Ci.nsIUUIDGenerator).generateUUID().toString();
62  },
63
64  // Map from random ID string -> CallbackCaller, per request
65  _requests: new Map(),
66
67  init: function() {
68    this._mm.addMessageListener("ContentPrefs:HandleResult", this);
69    this._mm.addMessageListener("ContentPrefs:HandleError", this);
70    this._mm.addMessageListener("ContentPrefs:HandleCompletion", this);
71  },
72
73  receiveMessage: function(msg) {
74    let data = msg.data;
75    let callback;
76    switch (msg.name) {
77      case "ContentPrefs:HandleResult":
78        callback = this._requests.get(data.requestId);
79        callback.handleResult(data.contentPref);
80        break;
81
82      case "ContentPrefs:HandleError":
83        callback = this._requests.get(data.requestId);
84        callback.handleError(data.error);
85        break;
86
87      case "ContentPrefs:HandleCompletion":
88        callback = this._requests.get(data.requestId);
89        this._requests.delete(data.requestId);
90        callback.handleCompletion(data.reason);
91        break;
92
93      case "ContentPrefs:NotifyObservers": {
94        let observerList = this._observers.get(data.name);
95        if (!observerList)
96          break;
97
98        for (let observer of observerList) {
99          safeCallback(observer, data.callback, data.args);
100        }
101
102        break;
103      }
104    }
105  },
106
107  _callFunction: function(call, args, callback) {
108    let requestId = this._getRandomId();
109    let data = { call: call, args: args, requestId: requestId };
110
111    this._mm.sendAsyncMessage("ContentPrefs:FunctionCall", data);
112
113    this._requests.set(requestId, new CallbackCaller(callback));
114  },
115
116  getCachedByDomainAndName: NYI,
117  getCachedBySubdomainAndName: NYI,
118  getCachedGlobal: NYI,
119
120  addObserverForName: function(name, observer) {
121    let set = this._observers.get(name);
122    if (!set) {
123      set = new Set();
124      if (this._observers.size === 0) {
125        // This is the first observer of any kind. Start listening for changes.
126        this._mm.addMessageListener("ContentPrefs:NotifyObservers", this);
127      }
128
129      // This is the first observer for this name. Start listening for changes
130      // to it.
131      this._mm.sendAsyncMessage("ContentPrefs:AddObserverForName", { name: name });
132      this._observers.set(name, set);
133    }
134
135    set.add(observer);
136  },
137
138  removeObserverForName: function(name, observer) {
139    let set = this._observers.get(name);
140    if (!set)
141      return;
142
143    set.delete(observer);
144    if (set.size === 0) {
145      // This was the last observer for this name. Stop listening for changes.
146      this._mm.sendAsyncMessage("ContentPrefs:RemoveObserverForName", { name: name });
147
148      this._observers.delete(name);
149      if (this._observers.size === 0) {
150        // This was the last observer for this process. Stop listing for all
151        // changes.
152        this._mm.removeMessageListener("ContentPrefs:NotifyObservers", this);
153      }
154    }
155  },
156
157  extractDomain: NYI
158};
159
160function forwardMethodToParent(method, signature, ...args) {
161  // Ignore superfluous arguments
162  args = args.slice(0, signature.length);
163
164  // Process context argument for forwarding
165  let contextIndex = signature.indexOf("context");
166  if (contextIndex > -1) {
167    args[contextIndex] = contextArg(args[contextIndex]);
168  }
169  // Take out the callback argument, if present.
170  let callbackIndex = signature.indexOf("callback");
171  let callback = null;
172  if (callbackIndex > -1 && args.length > callbackIndex) {
173    callback = args.splice(callbackIndex, 1)[0];
174  }
175  this._callFunction(method, args, callback);
176}
177
178for (let [method, signature] of _methodsCallableFromChild) {
179  ContentPrefServiceChild[method] = forwardMethodToParent.bind(ContentPrefServiceChild, method, signature);
180}
181
182ContentPrefServiceChild.init();
183