API-Version: 1.9

dwb's javascript api documentation

dwb can be extended with different types of userscripts. This api documentation describes the javascript interface.

Scripts that use the javascript api must be located in $XDG_CONFIG_HOME/dwb/userscripts like any other script. Scripts that use the javascript api must either start with

 #!javascript

or with

//!javascript

or they must be contained in an archive, see below.

All native javascript methods can be used in scripts, however there are limitations:

  • The execution context of userscripts is completely seperated from the web execution context. Due to security concerns it is not possible to communicate with the web execution context, it is only possible to inject scripts into the web context.
  • In contrast to the global window object in the web execution context, the global object is a readonly object, i.e. it is not possible to set properties on the global object.

dwb uses an underscore-prefix for some internal functions, so it should be avoided to use function names and variable identifiers starting with a single underscore.

The api can also be used directly inside dwb, the scripting console can be found under dwb:script. Scripts can be executed by pressing Control-Return, all error messages will be printed to stderr. Scripts can also be executed from command line using the eval command.

Global data and script

All scripts share the same execution context and every script is encapsulated in a function. To avoid conflicts between different scripts it is not allowed to set properties on the global object, i.e. it is not possible to define global variables, variables can only be defined in the scope of the script.

//!javascript 

var x = 3; 
// OK
io.print(x);

y = 37;
// Will throw an error
io.print(y);

If you override a bultin namespace with another variable you can access the global object with global.

//!javascript 

var net = 33; 

Signal.connect("navigation", function(wv, frame, request) {
    // OK
    var domain = global.net.domainFromHost(request.message.uri.host);
    // Will throw an error
    var domain = net.domainFromHost(request.message.uri.host);
});

In every script the constant namespace script refers to the encapsulating function, this namepsace cannot be accessed via global.

script is useful for debugging but also defines functions for setting/getting properties on objects derived from GObject. Since object derived from GObject are only created once and shared between all scripts it should be avoided to set properties directly on the object.

To share data between scripts the global functions provide and require can be used.

Handles

Some functions return a handle that can be used to stop callback functions, e.g. the callbacks of timer.start or bind. Handles are just an object that with a remove property, i.e. a function that takes care the the callback is no longer called.

All handles can be owned by the script if the handles are passed to script.own. To remove all handles owned by a script script.removeHandles can be used. If an assert evaluates to false all handles will be removed automatically.

//!javascript 

function bindCallback() {
    ...
}
function navigationCallback() {
    ...
}
function stopScript() {
    // unbinds bindCallback, stopAll and calls Signal.disconnect(navigationCallback);
    script.removeHandles();
}
functio startScript() {
    script.own( 
        bind("someshortcut", bindCallback), 
        Signal.connect("navigation", navigationCallback)
    );
}
bind(null, startScript, "startScript"),
bind(null, stopScript, "stopscript"),

Chrome documents

Local chrome sites can be created with loadString and they must use the base-uri dwb-chrome://. They are useful e.g. for configuration sites for extensions. There is one difference between chrome sites and regular websites. The web execution context of chrome sites is still seperated from the scripting execution context but it is possible to define functions in the webcontext that are executed in the scripting context. A special function dwb is defined in the web context that calls a function in the scripting context. Note that variables cannot be shared directly between the 2 contexts. Objects can be shared by passing a second argument to the dwb function. The objects are first stringified in the web context and than parsed to an object in the scripting context.

Note that the function dwb is only defined if external resources, e.g. external scripts, are forbidden, see loadString for details.

function myDocument() {
/*HEREDOC
<head>
    <script type="text/javascript">
        function onClick(e) 
        {
            dwb(function() {
                var id = arguments[0];
                io.print(id + " was pressed!");
            }, e.target.id);
        }
    </script>

</head>
<body>
    <button id="button1" onclick="onClick(event)">Button 1</button>
    <button id="button2" onclick="onClick(event)">Button 2</button>
</body>
HEREDOC*/
}

bind(null, function() {
    var document = util.hereDoc(myDocument);
    tabs.current.loadString(document, null, null, "dwb-chrome://buttons");
}, "showButtons"); 

To define a callback that is called in the scripting context provide and require can be used:

function myDocument() {
/*HEREDOC
<head>
    <script type="text/javascript">
        function onClick(e) 
        {
            dwb(function() {
                require("buttonExport").onClick(arguments[0]);
            }, e.target.id);
        }
    </script>

</head>
<body>
    <button id="button1" onclick="onClick(event)">Button 1</button>
    <button id="button2" onclick="onClick(event)">Button 2</button>
</body>
HEREDOC*/
}


provide("buttonExport", {
    onClick : function(id) 
    {
        io.print(id + " was pressed!");
    }
});

bind(null, function() {
    var document = util.hereDoc(myDocument);
    tabs.current.loadString(document, null, null, "dwb-chrome://buttons");
}, "showButtons"); 

Archives

Complex scripts or extensions could be separated into several files and packed into an archive. Each archive must contain one (and only one) file named "main.js" which will be used as a starting point for the archive. All other files must be included with xinclude. To create an archive the extension manager dwbem can be used, the command

dwbem --archive p /path/to/archive

will create an archive called archive.exar. The archive could be unpacked with

dwbem --archive u archive.exar

The directory structure of an archive could be listed with

dwbem --archive i archive.exar 

For more options see also

dwbem --archive h

Archives could simply be merged with

cat archive1.exar archive2.exar > archive3.exar

When files are included with xinclude the relative path of the file in the archive must be used. Consider the following directory structure:

archive/main.js
archive/content/foo.js
archive/content/bar.js

foo.js and bar.js could then be included from main.js as follows:

var foo = xinclude("archive/content/foo.js");
var bar = xinclude("archive/content/foo.js");

If some files should not be inside a directory, e.g.

archive/foo.js
archive/bar.js
main.js

the files must be appended:

dwbem --archive p /path/to/archive                  # creates archive.exar
dwbem --archive a archive.exar /path/to/main.js     # appends main.js 

Debugging

By default dwb will print debugging messages that occur in the global scope of a script, to debug code that isn't executed in the global scope dwb adds a debug method to all functions, it can be used together with the script variable to get more verbose error messages in callbacks and nested functions:

Signal.connect("request", function(wv, frame, request, resource) {
   ... 
}.debug(script));

Errors in scripts that are injected in websites will not show up in the web-inspector. There are 2 possibilities to debug injected scripts, either wrapping the code in a try/catch block and printing error messages to console:

function injectMe() {
    try 
    {
        ...
    }
    catch (e)
    {
        // will show up in the web inspector
        console.error(e);
    }
}
bind("xy", function() {
    tabs.current.inject(injectMe);
});

or by passing the starting line number to inject, that will produce the same error message as if the function was executed in dwb's scripting context:

 1: //!javascript
 2: 
 3: function injectMe() {
 4:      //
 5:      var foo = arguments[0].foo;
...     ...
49: }
50: bind("xy", function() {
51:     tabs.current.inject(injectMe, { foo : "bar" }, 4);
52: });

Syntax highlighting

To highlight global variables and namespaces in vim save the following as $HOME/.vim/syntax/dwb.vim

runtime! syntax/javascript.vim

syn keyword javaScriptGlobal global session settings
syn keyword javaScriptMember clipboard data extensions gui io net script signals system tabs timer util
syn keyword javaScriptType Deferred Signal Glob

And then add

// ext:set ft=dwb:

to your scripts.