Search code examples
javascriptgoogle-chrome-extensionchrome-native-messagingnativeapplication

In Chrome Native messaging: how long is the native app instance object lifetime?


Does a native application that talks to a Chrome extension is alive for ever? I mean should it be present e.g. before a postback happens? I could not find any configuration for that to add in the manifest file.

I have a page, there are some objects on the page. After clicking an object and sending/receving one message with the Native app, then it no longer works for the rest of objects.

My exact question: How long is the native app instance object lifetime? Should that object response e.g. forever? Or do I need a loop e.g. to read messages from stdin if this is a continuous communication?

Here is my background script:

var host_name = "files.mffta.java.nativeapp";
var port = null;
initPort();
function initPort() {
    console.log( 'Connecting to native host: ' + host_name );
    port = chrome.runtime.connectNative( host_name );
    port.onMessage.addListener( onNativeMessage );
    port.onDisconnect.addListener( onDisconnected );
}

// Listen for messages that come from the content script.
chrome.runtime.onMessage.addListener(
  function( messageData, sender, sendResponse ) {
    if( messageData ) {
        sendNativeMessage(messageData);
        sendResponse( { res: 'done!' } );
    }
  } );

// Sending a message to the port.
function sendNativeMessage(messageData) {
    if( port == null )
        initPort();
    console.log( 'Sending message to native app: ' + JSON.stringify( messageData ) );
    port.postMessage( messageData );
    console.log( 'Sent message to native app.' );
}

// Receiving a message back from the Native Client API.
function onNativeMessage( message ) {
    console.log( 'recieved message from native app: ' + JSON.stringify( message ) );

    alert( "messaged received from Native: " + JSON.stringify( message ) );
    //sending a message to Content Script to call a function
    if( message.methodName && message.methodName != "" ) {
        chrome.tabs.query( { active: true, currentWindow: true }, function( tabs ) {
            chrome.tabs.sendMessage( tabs[0].id, message, function( response ) {
                // Call native again to return JavaScript callback function results
                alert ("calc res received by extension : " + response);
                sendNativeMessage({ type: "JSCallbackRes", callbackRes: response });
            } );
        } );
    }
}

// Disconnecting the port.
function onDisconnected() {
    console.log( "ERROR: " + JSON.stringify( chrome.runtime.lastError ) );
    console.log( 'disconnected from native app.' );
    port = null;
}

My extension manifest:

{
  "name": "Files.ChromeExt.Operarations",
  "version": "1.0",
  "manifest_version": 2,
  "description": "This extension calls a Native API which that API calls some x-Files related operations.",
  "icons": {
    "128": "x-files_icon.png"
  },
  "permissions": [
    "nativeMessaging", "activeTab"
  ],
  "background": {
    "persistent": true,
    "scripts": ["main.js"]
  },
  "content_scripts" : [{"matches": ["http://localhost/*","https://localhost/*"], 
    "js": ["contentscripts/page.js"]}]
} 

Java program: [as has been asked in the comments]

import java.io.IOException;
import javax.swing.JOptionPane;

public class Applet {

    public Applet(){}

    public static void main(String[] args) {
        try {
            readMessage();
            sendMessage("{\"msg\" : \"hello\"}");

        } catch (Exception ex) {
            JOptionPane.showMessageDialog(null, ex.getMessage());
        }
    }

    public static String readMessage() {
        String msg = "";
        try {
            int c, t = 0;
            for (int i = 0; i <= 3; i++) {
                t += Math.pow(256.0f, i) * System.in.read();
            }

            for (int i = 0; i < t; i++) {
                c = System.in.read();
                msg += (char) c;
            }
        } catch (Exception e) {
            JOptionPane.showMessageDialog(null, "error in reading message from JS");
        }
        return msg;
    }

    public static void sendMessage(String msgdata) {
        try {
            int dataLength = msgdata.length();
            System.out.write((byte) (dataLength & 0xFF));
            System.out.write((byte) ((dataLength >> 8) & 0xFF));
            System.out.write((byte) ((dataLength >> 16) & 0xFF));
            System.out.write((byte) ((dataLength >> 24) & 0xFF));

            // Writing the message itself
            System.out.write(msgdata.getBytes());
            System.out.flush();
        } catch (IOException e) {
            JOptionPane.showMessageDialog(null, "error in sending message to JS");
        }
    }
}

By inspecting the chrome log I can see these messages:

  • Native Messaging host tried sending a message that is 1936028240 bytes long.
  • {"message":"Error when communicating with the native messaging host."}", source: chrome-extension://XXX

I am testing these on a Win 8.0 machine 64 bit.

Update: I am now convinced that the host is alive forever if connectNative is called and the port is not stopped by an error. So, the root cause of the error messages above must be something else than port lifetime. I mean some error in my communication is forcibly stopping the port.

I highly appreciate any comments you can give.


Solution

  • If using chrome.runtime.sendNativeMessage, the native app will be around right after receiving and before sending the message. Since reception of the message is asynchronous, you cannot assume that the app is still alive when the callback of sendNativeMessage is called.

    If you want to ensure that the native app stays around longer, use chrome.runtime.connectNative. This creates a port, and the native app will be alive until it exits or until the extension calls disconnect() on the port. If your app unexpectedly terminates early, then you have most likely made an error in the implementation of the native messaging protocol.

    For the exact format of the native messaging protocol, take a look at the documentation: https://developer.chrome.com/extensions/nativeMessaging#native-messaging-host-protocol


    As for your edit, the error message is quite clear: The length is invalid. The length is supposed to be in the system's native byte order (which could be little endian or big endian). When you get the following arror message with an excessive offset difference, then there are two possibilities:

    1. The byte order of the integer is incorrect, or
    2. Your output contains some unexpected characters, which causes the bytes to be shifted and resulted in bytes being at the incorrect location.

    To know which case you're in, view the number as a hexadecimal number. If there are many trailing zeroes, then that shows that the byte order is incorrect. For example, if your message has a length of 59, then the hexadecimal value is 3b. If the endianness is incorrect, then the following message is displayed:

    Native Messaging host tried sending a message that is 989855744 bytes long.

    1493172224 is 3b 00 00 00 in hexadecimal notation, and you can observe that the 3b is there, but at the wrong end (in other words, the byte order is reversed). The solution to this problem for your system is to edit your code to print the bytes in the reverse order.

    If the hexadecimal view of the number does not look remotely close to your number, then odds are that the message length is incorrect. Recall that stdio is used for communication, so if you output anything else (e.g. errors) to stdout (System.out) instead of stderr (System.err), then the protocol is violated and your app will be terminated.

            System.err.println(ex.getStackTrace());                     // <-- OK
            System.out.println("[ error -> " + ex.getMessage() + " ]"); // <-- BAD
    

    On Windows, also check whether you've set the mode for stdout (and stdin) to O_BINARY. Otherwise Windows will insert an extra byte before 0A (0D 0A), which causes all bytes to shift and the number to be incorrect. The Java Runtime already enables binary mode, so for your case it's not relevant.