| Index: safari/ext/common.js | 
| =================================================================== | 
| --- a/safari/ext/common.js | 
| +++ b/safari/ext/common.js | 
| @@ -15,160 +15,130 @@ | 
| * along with Adblock Plus.  If not, see <http://www.gnu.org/licenses/>. | 
| */ | 
|  | 
| -(function() { | 
| -  /* Events */ | 
| +(function() | 
| +{ | 
| +  /* Message passing */ | 
|  | 
| -  WrappedEventTarget = function(target, eventName, capture) | 
| +  var MessageProxy = ext._MessageProxy = function(messageDispatcher) | 
| { | 
| -    this._listeners = []; | 
| -    this._wrappedListeners = []; | 
| +    this._messageDispatcher = messageDispatcher; | 
| +    this._responseCallbacks = {__proto__: null}; | 
| +    this._responseCallbackCounter = 0; | 
| +  }; | 
| +  MessageProxy.prototype = { | 
| +    _sendResponse: function(request, message) | 
| +    { | 
| +      var response = {}; | 
| +      for (var prop in request) | 
| +        response[prop] = request[prop]; | 
| +      response.payload = message; | 
|  | 
| -    this._target = target; | 
| -    this._eventName = eventName; | 
| -    this._capture = capture; | 
| -  }; | 
| -  WrappedEventTarget.prototype = { | 
| -    addListener: function(listener) | 
| +      this._messageDispatcher.dispatchMessage("response", response); | 
| +    }, | 
| +    handleRequest: function(request, sender) | 
| { | 
| -      var wrappedListener = this._wrapListener(listener); | 
| +      var sendResponse; | 
| +      if ("callbackId" in request) | 
| +        sendResponse = this._sendResponse.bind(this, request); | 
| +      else | 
| +        sendResponse = function() {}; | 
|  | 
| -      this._listeners.push(listener); | 
| -      this._wrappedListeners.push(wrappedListener); | 
| +      ext.onMessage._dispatch(request.payload, sender, sendResponse); | 
| +    }, | 
| +    handleResponse: function(response) | 
| +    { | 
| +      var callbackId = response.callbackId; | 
| +      var callback = this._responseCallbacks[callbackId]; | 
| +      if (callback) | 
| +      { | 
| +        delete this._responseCallbacks[callbackId]; | 
| +        callback(response.payload); | 
| +      } | 
| +    }, | 
| +    sendMessage: function(message, responseCallback, extra) | 
| +    { | 
| +      var request = {payload: message}; | 
|  | 
| -      this._target.addEventListener( | 
| -        this._eventName, | 
| -        wrappedListener, | 
| -        this._capture | 
| -      ); | 
| -    }, | 
| -    removeListener: function(listener) | 
| -    { | 
| -      var idx = this._listeners.indexOf(listener); | 
| +      if (responseCallback) | 
| +      { | 
| +        request.callbackId = ++this._responseCallbackCounter; | 
| +        this._responseCallbacks[request.callbackId] = responseCallback; | 
| +      } | 
|  | 
| -      if (idx != -1) | 
| -      { | 
| -        this._target.removeEventListener( | 
| -          this._eventName, | 
| -          this._wrappedListeners[idx], | 
| -          this._capture | 
| -        ); | 
| +      for (var prop in extra) | 
| +        request[prop] = extra[prop]; | 
|  | 
| -        this._listeners.splice(idx, 1); | 
| -        this._wrappedListeners.splice(idx, 1); | 
| -      } | 
| +      this._messageDispatcher.dispatchMessage("request", request); | 
| } | 
| }; | 
|  | 
| -  MessageEventTarget = function(target) | 
| -  { | 
| -    WrappedEventTarget.call(this, target, "message", false); | 
| -  }; | 
| -  MessageEventTarget.prototype = { | 
| -    __proto__: WrappedEventTarget.prototype, | 
| -    _wrapListener: function(listener) | 
| -    { | 
| -      return function(event) | 
| -      { | 
| -        if (event.name == "request") | 
| -          listener(event.message.payload, this._getSenderDetails(event), function(message) | 
| -          { | 
| -            this._getResponseDispatcher(event).dispatchMessage("response", | 
| -            { | 
| -              requestId: event.message.requestId, | 
| -              payload: message | 
| -            }); | 
| -          }.bind(this)); | 
| -      }.bind(this); | 
| -    } | 
| -  }; | 
| - | 
| - | 
| -  /* Message passing */ | 
| - | 
| -  var requestCounter = 0; | 
| - | 
| -  _sendMessage = function(message, responseCallback, messageDispatcher, responseEventTarget, extra) | 
| -  { | 
| -    var requestId = ++requestCounter; | 
| - | 
| -    if (responseCallback) | 
| -    { | 
| -      var responseListener = function(event) | 
| -      { | 
| -        if (event.name == "response" && event.message.requestId == requestId) | 
| -        { | 
| -          responseEventTarget.removeEventListener("message", responseListener, false); | 
| -          responseCallback(event.message.payload); | 
| -        } | 
| -      }; | 
| -      responseEventTarget.addEventListener("message", responseListener, false); | 
| -    } | 
| - | 
| -    var rawMessage = {requestId: requestId, payload: message}; | 
| -    for (var k in extra) | 
| -      rawMessage[k] = extra[k]; | 
| -    messageDispatcher.dispatchMessage("request", rawMessage); | 
| -  }; | 
| +  ext.onMessage = new ext._EventTarget(); | 
|  | 
|  | 
| /* I18n */ | 
|  | 
| -  var I18n = function() | 
| +  var localeCandidates = null; | 
| +  var uiLocale; | 
| + | 
| +  var getLocaleCandidates = function() | 
| { | 
| -    this._localeCandidates = this._getLocaleCandidates(); | 
| -    this._uiLocale = this._localeCandidates[0]; | 
| +    var candidates = []; | 
| +    var defaultLocale = "en_US"; | 
| + | 
| +    var bits, i; | 
| +    for (i = (bits = navigator.language.split("-")).length; i > 0; i--) | 
| +    { | 
| +      var locale = bits.slice(0, i).join("_"); | 
| +      candidates.push(locale); | 
| + | 
| +      if (locale == defaultLocale) | 
| +        return candidates; | 
| +    } | 
| + | 
| +    candidates.push(defaultLocale); | 
| +    return candidates; | 
| }; | 
| -  I18n.prototype = { | 
| -    _getLocaleCandidates: function() | 
| + | 
| +  var getCatalog = function(locale) | 
| +  { | 
| +    var xhr = new XMLHttpRequest(); | 
| + | 
| +    xhr.open("GET", safari.extension.baseURI + "_locales/" + locale + "/messages.json", false); | 
| + | 
| +    try { | 
| +      xhr.send(); | 
| +    } | 
| +    catch (e) | 
| { | 
| -      var candidates = []; | 
| -      var defaultLocale = "en_US"; | 
| +      return null; | 
| +    } | 
|  | 
| -      var bits, i; | 
| -      for (i = (bits = navigator.language.split("-")).length; i > 0; i--) | 
| +    if (xhr.status != 200 && xhr.status != 0) | 
| +      return null; | 
| + | 
| +    return JSON.parse(xhr.responseText); | 
| +  }; | 
| + | 
| +  ext.i18n = { | 
| +    getMessage: function(msgId, substitutions) | 
| +    { | 
| +      if (!localeCandidates) | 
| { | 
| -        var locale = bits.slice(0, i).join("_"); | 
| -        candidates.push(locale); | 
| - | 
| -        if (locale == defaultLocale) | 
| -          return candidates; | 
| +        localeCandidates = getLocaleCandidates(); | 
| +        uiLocale = localeCandidates[0]; | 
| } | 
|  | 
| -      candidates.push(defaultLocale); | 
| -      return candidates; | 
| -    }, | 
| -    _getCatalog: function(locale) | 
| -    { | 
| -      var xhr = new XMLHttpRequest(); | 
| - | 
| -      xhr.open("GET", safari.extension.baseURI + "_locales/" + locale + "/messages.json", false); | 
| +      if (msgId == "@@ui_locale") | 
| +        return uiLocale; | 
|  | 
| -      try { | 
| -        xhr.send(); | 
| -      } | 
| -      catch (e) | 
| +      for (var i = 0; i < localeCandidates.length; i++) | 
| { | 
| -        return null; | 
| -      } | 
| - | 
| -      if (xhr.status != 200 && xhr.status != 0) | 
| -        return null; | 
| - | 
| -      return JSON.parse(xhr.responseText); | 
| -    }, | 
| -    getMessage: function(msgId, substitutions) | 
| -    { | 
| -      if (msgId == "@@ui_locale") | 
| -        return this._uiLocale; | 
| - | 
| -      for (var i = 0; i < this._localeCandidates.length; i++) | 
| -      { | 
| -        var catalog = this._getCatalog(this._localeCandidates[i]); | 
| +        var catalog = getCatalog(localeCandidates[i]); | 
| if (!catalog) | 
| { | 
| // if there is no catalog for this locale | 
| // candidate, don't try to load it again | 
| -          this._localeCandidates.splice(i--, 1); | 
| +          localeCandidates.splice(i--, 1); | 
| continue; | 
| } | 
|  | 
| @@ -193,7 +163,7 @@ | 
| continue; | 
|  | 
| var placeholderValue; | 
| -          if (Object.prototype.toString.call(substitutions) == "[object Array]") | 
| +          if (substitutions && substitutions.constructor == Array) | 
| placeholderValue = substitutions[placeholderIdx - 1]; | 
| else if (placeholderIdx == 1) | 
| placeholderValue = substitutions; | 
| @@ -209,13 +179,10 @@ | 
| }; | 
|  | 
|  | 
| -  /* API */ | 
| +  /* Utils */ | 
|  | 
| -  ext = { | 
| -    getURL: function(path) | 
| -    { | 
| -      return safari.extension.baseURI + path; | 
| -    }, | 
| -    i18n: new I18n() | 
| +  ext.getURL = function(path) | 
| +  { | 
| +    return safari.extension.baseURI + path; | 
| }; | 
| })(); | 
|  |