| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427 | /******************************************************************************** Copyright (C) 2016 The Qt Company Ltd.** Copyright (C) 2016 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Milian Wolff <milian.wolff@kdab.com>** Contact: https://www.qt.io/licensing/**** This file is part of the QtWebChannel module of the Qt Toolkit.**** $QT_BEGIN_LICENSE:LGPL$** Commercial License Usage** Licensees holding valid commercial Qt licenses may use this file in** accordance with the commercial license agreement provided with the** Software or, alternatively, in accordance with the terms contained in** a written agreement between you and The Qt Company. For licensing terms** and conditions see https://www.qt.io/terms-conditions. For further** information use the contact form at https://www.qt.io/contact-us.**** GNU Lesser General Public License Usage** Alternatively, this file may be used under the terms of the GNU Lesser** General Public License version 3 as published by the Free Software** Foundation and appearing in the file LICENSE.LGPL3 included in the** packaging of this file. Please review the following information to** ensure the GNU Lesser General Public License version 3 requirements** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.**** GNU General Public License Usage** Alternatively, this file may be used under the terms of the GNU** General Public License version 2.0 or (at your option) the GNU General** Public license version 3 or any later version approved by the KDE Free** Qt Foundation. The licenses are as published by the Free Software** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3** included in the packaging of this file. Please review the following** information to ensure the GNU General Public License requirements will** be met: https://www.gnu.org/licenses/gpl-2.0.html and** https://www.gnu.org/licenses/gpl-3.0.html.**** $QT_END_LICENSE$******************************************************************************/"use strict";var QWebChannelMessageTypes = {    signal: 1,    propertyUpdate: 2,    init: 3,    idle: 4,    debug: 5,    invokeMethod: 6,    connectToSignal: 7,    disconnectFromSignal: 8,    setProperty: 9,    response: 10,};var QWebChannel = function(transport, initCallback){    if (typeof transport !== "object" || typeof transport.send !== "function") {        console.error("The QWebChannel expects a transport object with a send function and onmessage callback property." +                      " Given is: transport: " + typeof(transport) + ", transport.send: " + typeof(transport.send));        return;    }    var channel = this;    this.transport = transport;    this.send = function(data)    {        if (typeof(data) !== "string") {            data = JSON.stringify(data);        }        channel.transport.send(data);    }    this.transport.onmessage = function(message)    {        var data = message.data;        if (typeof data === "string") {            data = JSON.parse(data);        }        switch (data.type) {            case QWebChannelMessageTypes.signal:                channel.handleSignal(data);                break;            case QWebChannelMessageTypes.response:                channel.handleResponse(data);                break;            case QWebChannelMessageTypes.propertyUpdate:                channel.handlePropertyUpdate(data);                break;            default:                console.error("invalid message received:", message.data);                break;        }    }    this.execCallbacks = {};    this.execId = 0;    this.exec = function(data, callback)    {        if (!callback) {            // if no callback is given, send directly            channel.send(data);            return;        }        if (channel.execId === Number.MAX_VALUE) {            // wrap            channel.execId = Number.MIN_VALUE;        }        if (data.hasOwnProperty("id")) {            console.error("Cannot exec message with property id: " + JSON.stringify(data));            return;        }        data.id = channel.execId++;        channel.execCallbacks[data.id] = callback;        channel.send(data);    };    this.objects = {};    this.handleSignal = function(message)    {        var object = channel.objects[message.object];        if (object) {            object.signalEmitted(message.signal, message.args);        } else {            console.warn("Unhandled signal: " + message.object + "::" + message.signal);        }    }    this.handleResponse = function(message)    {        if (!message.hasOwnProperty("id")) {            console.error("Invalid response message received: ", JSON.stringify(message));            return;        }        channel.execCallbacks[message.id](message.data);        delete channel.execCallbacks[message.id];    }    this.handlePropertyUpdate = function(message)    {        for (var i in message.data) {            var data = message.data[i];            var object = channel.objects[data.object];            if (object) {                object.propertyUpdate(data.signals, data.properties);            } else {                console.warn("Unhandled property update: " + data.object + "::" + data.signal);            }        }        channel.exec({type: QWebChannelMessageTypes.idle});    }    this.debug = function(message)    {        channel.send({type: QWebChannelMessageTypes.debug, data: message});    };    channel.exec({type: QWebChannelMessageTypes.init}, function(data) {        for (var objectName in data) {            var object = new QObject(objectName, data[objectName], channel);        }        // now unwrap properties, which might reference other registered objects        for (var objectName in channel.objects) {            channel.objects[objectName].unwrapProperties();        }        if (initCallback) {            initCallback(channel);        }        channel.exec({type: QWebChannelMessageTypes.idle});    });};function QObject(name, data, webChannel){    this.__id__ = name;    webChannel.objects[name] = this;    // List of callbacks that get invoked upon signal emission    this.__objectSignals__ = {};    // Cache of all properties, updated when a notify signal is emitted    this.__propertyCache__ = {};    var object = this;    // ----------------------------------------------------------------------    this.unwrapQObject = function(response)    {        if (response instanceof Array) {            // support list of objects            var ret = new Array(response.length);            for (var i = 0; i < response.length; ++i) {                ret[i] = object.unwrapQObject(response[i]);            }            return ret;        }        if (!response            || !response["__QObject*__"]            || response.id === undefined) {            return response;        }        var objectId = response.id;        if (webChannel.objects[objectId])            return webChannel.objects[objectId];        if (!response.data) {            console.error("Cannot unwrap unknown QObject " + objectId + " without data.");            return;        }        var qObject = new QObject( objectId, response.data, webChannel );        qObject.destroyed.connect(function() {            if (webChannel.objects[objectId] === qObject) {                delete webChannel.objects[objectId];                // reset the now deleted QObject to an empty {} object                // just assigning {} though would not have the desired effect, but the                // below also ensures all external references will see the empty map                // NOTE: this detour is necessary to workaround QTBUG-40021                var propertyNames = [];                for (var propertyName in qObject) {                    propertyNames.push(propertyName);                }                for (var idx in propertyNames) {                    delete qObject[propertyNames[idx]];                }            }        });        // here we are already initialized, and thus must directly unwrap the properties        qObject.unwrapProperties();        return qObject;    }    this.unwrapProperties = function()    {        for (var propertyIdx in object.__propertyCache__) {            object.__propertyCache__[propertyIdx] = object.unwrapQObject(object.__propertyCache__[propertyIdx]);        }    }    function addSignal(signalData, isPropertyNotifySignal)    {        var signalName = signalData[0];        var signalIndex = signalData[1];        object[signalName] = {            connect: function(callback) {                if (typeof(callback) !== "function") {                    console.error("Bad callback given to connect to signal " + signalName);                    return;                }                object.__objectSignals__[signalIndex] = object.__objectSignals__[signalIndex] || [];                object.__objectSignals__[signalIndex].push(callback);                if (!isPropertyNotifySignal && signalName !== "destroyed") {                    // only required for "pure" signals, handled separately for properties in propertyUpdate                    // also note that we always get notified about the destroyed signal                    webChannel.exec({                        type: QWebChannelMessageTypes.connectToSignal,                        object: object.__id__,                        signal: signalIndex                    });                }            },            disconnect: function(callback) {                if (typeof(callback) !== "function") {                    console.error("Bad callback given to disconnect from signal " + signalName);                    return;                }                object.__objectSignals__[signalIndex] = object.__objectSignals__[signalIndex] || [];                var idx = object.__objectSignals__[signalIndex].indexOf(callback);                if (idx === -1) {                    console.error("Cannot find connection of signal " + signalName + " to " + callback.name);                    return;                }                object.__objectSignals__[signalIndex].splice(idx, 1);                if (!isPropertyNotifySignal && object.__objectSignals__[signalIndex].length === 0) {                    // only required for "pure" signals, handled separately for properties in propertyUpdate                    webChannel.exec({                        type: QWebChannelMessageTypes.disconnectFromSignal,                        object: object.__id__,                        signal: signalIndex                    });                }            }        };    }    /**     * Invokes all callbacks for the given signalname. Also works for property notify callbacks.     */    function invokeSignalCallbacks(signalName, signalArgs)    {        var connections = object.__objectSignals__[signalName];        if (connections) {            connections.forEach(function(callback) {                callback.apply(callback, signalArgs);            });        }    }    this.propertyUpdate = function(signals, propertyMap)    {        // update property cache        for (var propertyIndex in propertyMap) {            var propertyValue = propertyMap[propertyIndex];            object.__propertyCache__[propertyIndex] = propertyValue;        }        for (var signalName in signals) {            // Invoke all callbacks, as signalEmitted() does not. This ensures the            // property cache is updated before the callbacks are invoked.            invokeSignalCallbacks(signalName, signals[signalName]);        }    }    this.signalEmitted = function(signalName, signalArgs)    {        invokeSignalCallbacks(signalName, this.unwrapQObject(signalArgs));    }    function addMethod(methodData)    {        var methodName = methodData[0];        var methodIdx = methodData[1];        object[methodName] = function() {            var args = [];            var callback;            for (var i = 0; i < arguments.length; ++i) {                var argument = arguments[i];                if (typeof argument === "function")                    callback = argument;                else if (argument instanceof QObject && webChannel.objects[argument.__id__] !== undefined)                    args.push({                        "id": argument.__id__                    });                else                    args.push(argument);            }            webChannel.exec({                "type": QWebChannelMessageTypes.invokeMethod,                "object": object.__id__,                "method": methodIdx,                "args": args            }, function(response) {                if (response !== undefined) {                    var result = object.unwrapQObject(response);                    if (callback) {                        (callback)(result);                    }                }            });        };    }    function bindGetterSetter(propertyInfo)    {        var propertyIndex = propertyInfo[0];        var propertyName = propertyInfo[1];        var notifySignalData = propertyInfo[2];        // initialize property cache with current value        // NOTE: if this is an object, it is not directly unwrapped as it might        // reference other QObject that we do not know yet        object.__propertyCache__[propertyIndex] = propertyInfo[3];        if (notifySignalData) {            if (notifySignalData[0] === 1) {                // signal name is optimized away, reconstruct the actual name                notifySignalData[0] = propertyName + "Changed";            }            addSignal(notifySignalData, true);        }        Object.defineProperty(object, propertyName, {            configurable: true,            get: function () {                var propertyValue = object.__propertyCache__[propertyIndex];                if (propertyValue === undefined) {                    // This shouldn't happen                    console.warn("Undefined value in property cache for property \"" + propertyName + "\" in object " + object.__id__);                }                return propertyValue;            },            set: function(value) {                if (value === undefined) {                    console.warn("Property setter for " + propertyName + " called with undefined value!");                    return;                }                object.__propertyCache__[propertyIndex] = value;                var valueToSend = value;                if (valueToSend instanceof QObject && webChannel.objects[valueToSend.__id__] !== undefined)                    valueToSend = { "id": valueToSend.__id__ };                webChannel.exec({                    "type": QWebChannelMessageTypes.setProperty,                    "object": object.__id__,                    "property": propertyIndex,                    "value": valueToSend                });            }        });    }    // ----------------------------------------------------------------------    data.methods.forEach(addMethod);    data.properties.forEach(bindGetterSetter);    data.signals.forEach(function(signal) { addSignal(signal, false); });    for (var name in data.enums) {        object[name] = data.enums[name];    }}//required for use with nodejsif (typeof module === 'object') {    module.exports = {        QWebChannel: QWebChannel    };}
 |