Есть ли простой метод преобразования для преобразования передачи сообщений Chrome в синтаксис передачи сообщений Safari?

Расширение My Chrome использует передачу сообщений для извлечения различных значений из встроенной области локального хранилища расширения на фоновой странице.

Что мне нравится в передаче сообщений Chrome, так это то, что он позволяет включать функцию обратного вызова внутри вызова sendMessage, например:

chrome.runtime.sendMessage({greeting: "hello"}, function(response) {
    console.log(response.farewell);   
});

и соответствующий код получения сообщения будет выглядеть следующим образом (пример кода из документации по расширениям Chrome):

chrome.runtime.onMessage.addListener(
  function(request, sender, sendResponse) {
    console.log(sender.tab ?
                "from a content script:" + sender.tab.url :
                "from the extension");
    if (request.greeting == "hello")
      sendResponse({farewell: "goodbye"});
  });

Я пытаюсь преобразовать свое расширение в формат расширения Safari, но не могу понять, как сопоставить функции Chrome sendMessage/onMessage с функциями обработки сообщений Safari safari.self.tab.dispatchMessage(name, data)

Возможно ли вообще включить функции обратного вызова в вызов функции SafariMessage dispatchMessage? Если нет, то как мне обойти это ограничение?


person user280109    schedule 14.12.2013    source источник


Ответы (1)


В средах, которые обеспечивают только одностороннюю систему передачи сообщений, вы всегда можете реализовать сообщение с обратным вызовом самостоятельно, связав уникальный идентификатор с каждым сообщением и сохранив обратные вызовы в словаре, который сопоставляет эти идентификаторы с обратными вызовами.

В конце этого ответа я скопировал и вставил модуль хранения для Safari из исходного кода моего кросс-браузера Расширение Lyrics Here, предоставляющее следующий API:

  • config.getItem(key, callback)
  • config.setItem(key, value, callback)
  • config.removeItem(key, callback)
  • config.clear(callback)

Обратный вызов getItem содержит значение, связанное с ключом (если он найден). Другие обратные вызовы получают логическое значение, указывающее, была ли операция успешной или нет.

Исходный код аннотирован и содержит фрагменты для обработки нескольких крайних случаев. Не стесняйтесь задавать мне несколько вопросов, если что-то непонятно.

config-safari.js (AMD)

// Adapter for maintaining preferences (Safari 5+)
// All methods are ASYNCHRONOUS
define('config-safari', function() {
    var config = {};
    var callbacks = {};
    ['getItem', 'setItem', 'removeItem', 'clear'].forEach(function(methodName) {
        config[methodName] = function() {
            var args = [].slice.call(arguments);
            var callback = args.pop();
            var messageID = Math.random();
            callbacks[messageID] = callback;
            
            var message = {
                type: methodName,
                messageID: messageID,
                args: args
            };
            safari.self.tab.dispatchMessage('config-request', message);
        };
    });
    config.init = function() {
        if (typeof safari === 'undefined') {
            // Safari bug: safari is undefined when current context is an iframe
            // and src="javascript:''"
            // This error is only expected on YouTube.
            // config.getItem is triggered in main, so we just redefine
            // it. Don't overwrite setItem, etc., so that errors are thrown
            // when these methods are used.
            config.getItem = function(key, callback){
                callback();
            };
            return;
        }
        safari.self.addEventListener('message', function(event) {
            if (event.name === 'config-reply') {
                var messageID = event.message.messageID;
                var callback = callbacks[messageID];
                // Check if callback exists. It may not exist when the script is
                // activated in multiple frames, because every frame receives the message
                if (callback) {
                    delete callbacks[messageID];
                    callback(event.message.result);
                }
            }
        }, true);
    };
    return config;
});

Фрагмент global.html:

<script>
(function(exports) {
    var config = {};
    config.getItem = function(key, callback) {
        var result = safari.extension.settings.getItem(key);
        if (typeof result === 'string') {
            try {
                result = JSON.parse(result);
            } catch (e) {
                // Extremely unlikely to happen, but don't neglect the possibility
                console.log('config.getItem error: ' + e);
                result = undefined;
            }
        }
        callback(result);
    };
    // callback's argument: true on success, false otherwise
    config.setItem = function(key, value, callback) {
        var success = false;
        try {
            value = JSON.stringify(value);
            // Safari (5.1.5) does not enforce the database quota,
            // let's enforce it manually (ok, set the quota per key, since
            // the performance issue only occur when a specific key has an outrageous high value)
            // 1 MB should be sufficient.
            if (value.length > 1e6) {
                throw new Error('QUOTA_EXCEEDED_ERR: length=' + value.length);
            }
            safari.extension.settings.setItem(key, value);
            success = true;
        } catch (e) {
            console.log('config.setItem error: ' + e);
        }
        callback(success);
    };
    // callback's argument: true on success, false otherwise
    config.removeItem = function(key, callback) {
        safari.extension.settings.removeItem(key);
        callback(true);
    };
    // callback's argument: true on success, false otherwise
    config.clear = function(callback) {
        safari.extension.settings.clear();
        callback(true);
    };

    // config's message handler
    function handleConfigRequest(event) {
        var args = event.message.args;
        // Last argument: Always a callback
        args.push(function(result) {
            // Note: All of the config methods send a callback only once
            // Behavior for calling the callback twice is undefined.
            
            // Send a reply to trigger the callback at the sender's end
            event.target.page.dispatchMessage('config-reply', {
                messageID: event.message.messageID,
                result: result
            });
        });
        config[event.message.type].apply(config, args);
    }
    
    // Export
    exports.handleConfigRequest = handleConfigRequest;
})(window);
</script>
<script>
safari.application.addEventListener('message', function(event) {
    switch (event.name) {
        case 'config-request':
            handleConfigRequest(event);
            break;
        /* ... other things removed ... */
    }
}, true);
</script>
person Rob W    schedule 14.12.2013
comment
Спасибо! Замечательно! :) я придумал похожее решение. все еще работая над этим, мне каким-то образом удалось создать бесконечный цикл передачи сообщений, дох! о хорошо я уверен, что я выясню это в конце концов. хотя я думаю, что мне, вероятно, следует попробовать ваш проверенный и надежный метод, а не мой собственный. - person user280109; 14.12.2013
comment
ОБНОВЛЕНИЕ ага, мне удалось вырваться из бесконечного цикла, ура. еще раз спасибо. - person user280109; 14.12.2013