├── AUTHORS ├── LICENSE ├── README.md ├── cmd └── webkit-eval-js │ └── evaljs.go └── webkit2 ├── example_test.go ├── gasyncreadycallback.go ├── gasyncreadycallback.go.c ├── gasyncreadycallback.go.h ├── gtk_test.go ├── server_for_test.go ├── settings.go ├── settings_2_2.go ├── settings_2_2_test.go ├── settings_test.go ├── util.go ├── webcontext.go ├── webcontext_test.go ├── webview.go └── webview_test.go /AUTHORS: -------------------------------------------------------------------------------- 1 | Bryan Summersett 2 | Quinn Slack 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 The go-webkit2 AUTHORS. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above 10 | copyright notice, this list of conditions and the following disclaimer 11 | in the documentation and/or other materials provided with the 12 | distribution. 13 | * Neither the name of Sourcegraph Inc. nor the names of its 14 | contributors may be used to endorse or promote products derived from 15 | this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | 29 | ---------------------------------------------------------------------- 30 | 31 | Contains code adapted from gotk3, which is distributed under the 32 | following license: 33 | 34 | Copyright (c) 2013 Conformal Systems 35 | 36 | This file originated from: http://opensource.conformal.com/ 37 | 38 | Permission to use, copy, modify, and distribute this software for any 39 | purpose with or without fee is hereby granted, provided that the above 40 | copyright notice and this permission notice appear in all copies. 41 | 42 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 43 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 44 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 45 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 46 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 47 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 48 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 49 | 50 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # go-webkit2 2 | 3 | go-webkit2 provides [Go](http://golang.org) bindings for the 4 | [WebKitGTK+ 2 API](http://webkitgtk.org/reference/webkit2gtk/stable/index.html). It permits headless operation of WebKit 5 | as well as embedding a WebView in a GTK+ application. 6 | 7 | * [Documentation on Sourcegraph](https://sourcegraph.com/github.com/sourcegraph/go-webkit2/tree) 8 | 9 | ## Requirements 10 | 11 | * [Go](http://golang.org) >= 1.2rc1 (due to [#3250](https://code.google.com/p/go/issues/detail?id=3250)) 12 | * [GTK+](http://www.gtk.org) 3.10+ 13 | * [WebKitGTK+](http://webkitgtk.org/) >= 2.0.0 14 | 15 | You can specify Go build tags to omit bindings in 16 | [gotk3](https://github.com/conformal/gotk3) for later versions of GTK 17 | (e.g., `go build -tags gtk_3_10`). 18 | 19 | #### Ubuntu 14.04 (Trusty) 20 | ```bash 21 | sudo apt-get install libwebkit2gtk-3.0-dev 22 | ``` 23 | 24 | Pass `-tags gtk_3_10` to the go tool if you have GTK 3.10 installed. 25 | 26 | #### Ubuntu 13.10 (Saucy) 27 | ```bash 28 | sudo add-apt-repository ppa:gnome3-team/gnome3-staging 29 | sudo apt-get update 30 | sudo apt-get install libwebkit2gtk-3.0-dev 31 | ``` 32 | #### Ubuntu 13.04 (Raring) 33 | ```bash 34 | sudo add-apt-repository ppa:gnome3-team/gnome3 35 | sudo apt-get update 36 | sudo apt-get install libwebkit2gtk-3.0-dev 37 | ``` 38 | #### Arch Linux 39 | ```bash 40 | sudo pacman -S webkitgtk 41 | ``` 42 | 43 | #### Other platforms 44 | 45 | Make sure you install WebKitGTK+ 2, not version 1. After installation, you 46 | should have an include file that satisfies `#include `. 47 | 48 | 49 | ## Usage 50 | 51 | ### As a Go package 52 | 53 | ```go 54 | package webkit2_test 55 | 56 | import ( 57 | "fmt" 58 | "runtime" 59 | 60 | "github.com/gotk3/gotk3/glib" 61 | "github.com/gotk3/gotk3/gtk" 62 | "github.com/sourcegraph/go-webkit2/webkit2" 63 | "github.com/sqs/gojs" 64 | ) 65 | 66 | func Example() { 67 | runtime.LockOSThread() 68 | gtk.Init(nil) 69 | 70 | webView := webkit2.NewWebView() 71 | defer webView.Destroy() 72 | 73 | webView.Connect("load-failed", func() { 74 | fmt.Println("Load failed.") 75 | }) 76 | webView.Connect("load-changed", func(_ *glib.Object, i int) { 77 | loadEvent := webkit2.LoadEvent(i) 78 | switch loadEvent { 79 | case webkit2.LoadFinished: 80 | fmt.Println("Load finished.") 81 | fmt.Printf("Title: %q\n", webView.Title()) 82 | fmt.Printf("URI: %s\n", webView.URI()) 83 | webView.RunJavaScript("window.location.hostname", func(val *gojs.Value, err error) { 84 | if err != nil { 85 | fmt.Println("JavaScript error.") 86 | } else { 87 | fmt.Printf("Hostname (from JavaScript): %q\n", val) 88 | } 89 | gtk.MainQuit() 90 | }) 91 | } 92 | }) 93 | 94 | glib.IdleAdd(func() bool { 95 | webView.LoadURI("https://www.google.com/") 96 | return false 97 | }) 98 | 99 | gtk.Main() 100 | 101 | // output: 102 | // Load finished. 103 | // Title: "Google" 104 | // URI: https://www.google.com/ 105 | // Hostname (from JavaScript): "www.google.com" 106 | } 107 | ``` 108 | 109 | See the 110 | [documentation](https://sourcegraph.com/github.com/sourcegraph/go-webkit2) and 111 | the test files for usage information and examples. 112 | 113 | For more information about the underlying WebKitGTK+ 2 API, refer to the 114 | [WebKit2 docs](http://webkitgtk.org/reference/webkit2gtk/stable/index.html). 115 | 116 | 117 | ### As a program for evaluating JavaScript in the context of a web page 118 | 119 | The included `webkit-eval-js` program runs the contents of a JavaScript file in the context of 120 | a web page. Run with: 121 | 122 | ``` 123 | $ go get -tags gtk_3_10 github.com/sourcegraph/go-webkit2/webkit-eval-js 124 | $ webkit-eval-js https://example.com scriptfile.js 125 | ``` 126 | 127 | For example: 128 | 129 | ``` 130 | $ echo document.title | webkit-eval-js https://google.com /dev/stdin 131 | "Google" 132 | ``` 133 | 134 | 135 | ## Used in 136 | 137 | The following projects use go-webkit2: 138 | 139 | * [WebLoop](https://sourcegraph.com/github.com/sourcegraph/webloop) - headless WebKit with a Go API 140 | 141 | 142 | ## Running tests 143 | 144 | ``` 145 | go test ./webkit2 146 | ``` 147 | 148 | Note: The tests require an X display. If you are not running in a graphical 149 | environment, you can use [Xvfb](http://en.wikipedia.org/wiki/Xvfb): 150 | 151 | ``` 152 | Xvfb :1 & 153 | export DISPLAY=:1 154 | go test ./webkit2 155 | ``` 156 | 157 | 158 | ## TODO 159 | 160 | * Implement more of the WebKitGTK+ 2 API. Right now, only certain parts of it 161 | are implemented. 162 | * [Set up CI testing.](https://github.com/sourcegraph/go-webkit2/issues/1) This 163 | is difficult because all of the popular CI services run older versions of 164 | Ubuntu that make it difficult to install WebKitGTK+ >= 2.0.0. 165 | * Create example applications. 166 | * Fix memory leaks where C strings are allocated and not freed. 167 | 168 | 169 | ## Contributors 170 | 171 | See the AUTHORS file for a list of contributors. 172 | 173 | Submit contributions via GitHub pull request. Patches should include tests and 174 | should pass [golint](https://github.com/golang/lint). 175 | -------------------------------------------------------------------------------- /cmd/webkit-eval-js/evaljs.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "io/ioutil" 7 | "log" 8 | "net/url" 9 | "os" 10 | "runtime" 11 | 12 | "github.com/gotk3/gotk3/glib" 13 | "github.com/gotk3/gotk3/gtk" 14 | "github.com/sourcegraph/go-webkit2/webkit2" 15 | "github.com/sqs/gojs" 16 | ) 17 | 18 | func main() { 19 | flag.Usage = func() { 20 | fmt.Fprintln(os.Stderr) 21 | fmt.Fprintf(os.Stderr, "webkit-eval-js evaluates a JavaScript expression in the context of a web page\n") 22 | fmt.Fprintf(os.Stderr, "running in a headless instance of the WebKit browser.\n\n") 23 | fmt.Fprintf(os.Stderr, "Usage:\n\n") 24 | fmt.Fprintf(os.Stderr, "\twebkit-eval-js url script-file\n\n") 25 | fmt.Fprintf(os.Stderr, "url is the web page to execute the script in, and script-file is a local file\n") 26 | fmt.Fprintf(os.Stderr, "with the JavaScript you want to evaluate. The result is printed to stdout as JSON.\n\n") 27 | fmt.Fprintln(os.Stderr) 28 | fmt.Fprintf(os.Stderr, "Example usage:\n\n") 29 | fmt.Fprintf(os.Stderr, "\tTo return the value of `document.title` on https://google.com:\n") 30 | fmt.Fprintf(os.Stderr, "\t $ echo document.title | webkit-eval-js https://google.com /dev/stdin\n") 31 | fmt.Fprintf(os.Stderr, "\tPrints:\n") 32 | fmt.Fprintf(os.Stderr, "\t \"Google\"\n\n") 33 | fmt.Fprintf(os.Stderr, "Notes:\n\n") 34 | fmt.Fprintf(os.Stderr, "\tBecause a headless WebKit instance is used, your $DISPLAY must be set. Use\n") 35 | fmt.Fprintf(os.Stderr, "\tXvfb if you are running on a machine without an existing X server. See\n") 36 | fmt.Fprintf(os.Stderr, "\thttps://sourcegraph.com/github.com/sourcegraph/go-webkit2/readme for more info.\n") 37 | fmt.Fprintln(os.Stderr) 38 | os.Exit(1) 39 | } 40 | flag.Parse() 41 | 42 | if flag.NArg() != 2 { 43 | flag.Usage() 44 | os.Exit(1) 45 | } 46 | 47 | log := log.New(os.Stderr, "", 0) 48 | 49 | pageURL := flag.Arg(0) 50 | scriptFile := flag.Arg(1) 51 | 52 | if _, err := url.Parse(pageURL); err != nil { 53 | log.Fatalf("Failed to parse URL %q: %s", pageURL, err) 54 | } 55 | 56 | script, err := ioutil.ReadFile(scriptFile) 57 | if err != nil { 58 | log.Fatalf("Failed to open script file %q: %s", scriptFile, err) 59 | } 60 | 61 | runtime.LockOSThread() 62 | gtk.Init(nil) 63 | 64 | webView := webkit2.NewWebView() 65 | defer webView.Destroy() 66 | 67 | webView.Connect("load-failed", func() { 68 | fmt.Println("Load failed.") 69 | }) 70 | webView.Connect("load-changed", func(_ *glib.Object, loadEvent webkit2.LoadEvent) { 71 | switch loadEvent { 72 | case webkit2.LoadFinished: 73 | webView.RunJavaScript(string(script), func(val *gojs.Value, err error) { 74 | if err != nil { 75 | log.Fatalf("JavaScript error: %s", err) 76 | } else { 77 | json, err := val.JSON() 78 | if err != nil { 79 | log.Fatal("JavaScript serialization error: %s", err) 80 | } 81 | fmt.Println(string(json)) 82 | } 83 | gtk.MainQuit() 84 | }) 85 | } 86 | }) 87 | 88 | glib.IdleAdd(func() bool { 89 | webView.LoadURI(pageURL) 90 | return false 91 | }) 92 | 93 | gtk.Main() 94 | } 95 | -------------------------------------------------------------------------------- /webkit2/example_test.go: -------------------------------------------------------------------------------- 1 | package webkit2_test 2 | 3 | import ( 4 | "fmt" 5 | "runtime" 6 | 7 | "github.com/gotk3/gotk3/glib" 8 | "github.com/gotk3/gotk3/gtk" 9 | "github.com/sourcegraph/go-webkit2/webkit2" 10 | "github.com/sqs/gojs" 11 | ) 12 | 13 | func Example() { 14 | runtime.LockOSThread() 15 | gtk.Init(nil) 16 | 17 | webView := webkit2.NewWebView() 18 | defer webView.Destroy() 19 | 20 | webView.Connect("load-failed", func() { 21 | fmt.Println("Load failed.") 22 | }) 23 | webView.Connect("load-changed", func(_ *glib.Object, i int) { 24 | loadEvent := webkit2.LoadEvent(i) 25 | switch loadEvent { 26 | case webkit2.LoadFinished: 27 | fmt.Println("Load finished.") 28 | fmt.Printf("Title: %q\n", webView.Title()) 29 | fmt.Printf("URI: %s\n", webView.URI()) 30 | webView.RunJavaScript("window.location.hostname", func(val *gojs.Value, err error) { 31 | if err != nil { 32 | fmt.Println("JavaScript error.") 33 | } else { 34 | fmt.Printf("Hostname (from JavaScript): %q\n", val) 35 | } 36 | gtk.MainQuit() 37 | }) 38 | } 39 | }) 40 | 41 | glib.IdleAdd(func() bool { 42 | webView.LoadURI("https://status.github.com/") 43 | return false 44 | }) 45 | 46 | gtk.Main() 47 | 48 | // output: 49 | // Load finished. 50 | // Title: "GitHub System Status" 51 | // URI: https://status.github.com/ 52 | // Hostname (from JavaScript): "status.github.com" 53 | } 54 | -------------------------------------------------------------------------------- /webkit2/gasyncreadycallback.go: -------------------------------------------------------------------------------- 1 | package webkit2 2 | 3 | // #include 4 | // #include "gasyncreadycallback.go.h" 5 | import "C" 6 | import ( 7 | "errors" 8 | "reflect" 9 | "unsafe" 10 | ) 11 | 12 | type garCallback struct { 13 | f reflect.Value 14 | } 15 | 16 | //export _go_gasyncreadycallback_call 17 | func _go_gasyncreadycallback_call(cbinfoRaw C.gpointer, cresult unsafe.Pointer) { 18 | result := (*C.GAsyncResult)(cresult) 19 | cbinfo := (*garCallback)(unsafe.Pointer(cbinfoRaw)) 20 | cbinfo.f.Call([]reflect.Value{reflect.ValueOf(result)}) 21 | } 22 | 23 | func newGAsyncReadyCallback(f interface{}) (cCallback C.GAsyncReadyCallback, userData C.gpointer, err error) { 24 | rf := reflect.ValueOf(f) 25 | if rf.Kind() != reflect.Func { 26 | return nil, nil, errors.New("f is not a function") 27 | } 28 | data := C.malloc(C.size_t(unsafe.Sizeof(garCallback{}))) 29 | cbinfo := (*garCallback)(data) 30 | cbinfo.f = rf 31 | return C.GAsyncReadyCallback(C._gasyncreadycallback_call), C.gpointer(unsafe.Pointer(cbinfo)), nil 32 | } 33 | -------------------------------------------------------------------------------- /webkit2/gasyncreadycallback.go.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "gasyncreadycallback.go.h" 4 | 5 | void _gasyncreadycallback_call(GObject *source_object, GAsyncResult *res, gpointer user_data) { 6 | _go_gasyncreadycallback_call(user_data, res); 7 | } 8 | -------------------------------------------------------------------------------- /webkit2/gasyncreadycallback.go.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | /* Wrapper that runs the Go closure for a given context */ 4 | extern void _go_gasyncreadycallback_call(gpointer user_data, void *cresult); 5 | 6 | void _gasyncreadycallback_call(GObject *source_object, GAsyncResult *res, gpointer user_data); 7 | -------------------------------------------------------------------------------- /webkit2/gtk_test.go: -------------------------------------------------------------------------------- 1 | package webkit2 2 | 3 | import ( 4 | "runtime" 5 | 6 | "github.com/gotk3/gotk3/gtk" 7 | ) 8 | 9 | func init() { 10 | runtime.LockOSThread() 11 | gtk.Init(nil) 12 | } 13 | -------------------------------------------------------------------------------- /webkit2/server_for_test.go: -------------------------------------------------------------------------------- 1 | package webkit2 2 | 3 | import ( 4 | "net/http" 5 | "net/http/httptest" 6 | ) 7 | 8 | var ( 9 | // mux is the HTTP request multiplexer used with the test server. 10 | mux *http.ServeMux 11 | 12 | // webView is the WebView being tested. 13 | webView *WebView 14 | 15 | // server is a test HTTP server used to provide mock API responses. 16 | server *httptest.Server 17 | ) 18 | 19 | // setup sets up a test HTTP server and a WebView. Tests should register 20 | // handlers on mux which provide mock responses for the method being tested. 21 | func setup() { 22 | mux = http.NewServeMux() 23 | server = httptest.NewServer(mux) 24 | 25 | webView = NewWebView() 26 | } 27 | 28 | // teardown closes the test HTTP server and webengine.View. 29 | func teardown() { 30 | server.Close() 31 | webView.Destroy() 32 | } 33 | -------------------------------------------------------------------------------- /webkit2/settings.go: -------------------------------------------------------------------------------- 1 | package webkit2 2 | 3 | // #include 4 | import "C" 5 | import "unsafe" 6 | import "github.com/gotk3/gotk3/glib" 7 | 8 | type Settings struct { 9 | *glib.Object 10 | settings *C.WebKitSettings 11 | } 12 | 13 | // newSettings creates a new Settings with default values. 14 | // 15 | // See also: webkit_settings_new at 16 | // http://webkitgtk.org/reference/webkit2gtk/stable/WebKitSettings.html#webkit-settings-new. 17 | func newSettings(settings *C.WebKitSettings) *Settings { 18 | return &Settings{&glib.Object{glib.ToGObject(unsafe.Pointer(settings))}, settings} 19 | } 20 | 21 | // GetAutoLoadImages returns the "auto-load-images" property. 22 | // 23 | // See also: webkit_settings_get_auto_load_images at 24 | // http://webkitgtk.org/reference/webkit2gtk/stable/WebKitSettings.html#webkit-settings-get-auto-load-images 25 | func (s *Settings) GetAutoLoadImages() bool { 26 | return gobool(C.webkit_settings_get_auto_load_images(s.settings)) 27 | } 28 | 29 | // SetAutoLoadImages sets the "auto-load-images" property. 30 | // 31 | // See also: webkit_settings_get_auto_load_images at 32 | // http://webkitgtk.org/reference/webkit2gtk/stable/WebKitSettings.html#webkit-settings-set-auto-load-images 33 | func (s *Settings) SetAutoLoadImages(autoLoad bool) { 34 | C.webkit_settings_set_auto_load_images(s.settings, gboolean(autoLoad)) 35 | } 36 | 37 | // SetUserAgentWithApplicationDetails sets the "user-agent" property by 38 | // appending the application details to the default user agent. 39 | // 40 | // See also: webkit_settings_set_user_agent_with_application_details at 41 | // http://webkitgtk.org/reference/webkit2gtk/unstable/WebKitSettings.html#webkit-settings-set-user-agent-with-application-details 42 | func (s *Settings) SetUserAgentWithApplicationDetails(appName, appVersion string) { 43 | C.webkit_settings_set_user_agent_with_application_details(s.settings, (*C.gchar)(C.CString(appName)), (*C.gchar)(C.CString(appVersion))) 44 | } 45 | -------------------------------------------------------------------------------- /webkit2/settings_2_2.go: -------------------------------------------------------------------------------- 1 | // This file includes wrappers for symbols included since WebKit2GTK+ 2.2, and 2 | // and should not be included in a build intended to target any older WebKit2 3 | // versions. To target an older build, such as 2.1, use 4 | // 'go build -tags webkit2_2_1'. 5 | // +build !webkit2_2_1,!webkit2_2_0 6 | 7 | package webkit2 8 | 9 | // #include 10 | import "C" 11 | 12 | // GetEnableWriteConsoleMessagesToStdout returns the 13 | // "enable-write-console-messages-to-stdout" property. 14 | // 15 | // See also: webkit_settings_get_enable_write_console_messages_to_stdout at 16 | // http://webkitgtk.org/reference/webkit2gtk/stable/WebKitSettings.html#webkit-settings-get-enable-write-console-messages-to-stdout 17 | func (s *Settings) GetEnableWriteConsoleMessagesToStdout() bool { 18 | return gobool(C.webkit_settings_get_enable_write_console_messages_to_stdout(s.settings)) 19 | } 20 | 21 | // SetEnableWriteConsoleMessagesToStdout sets the 22 | // "enable-write-console-messages-to-stdout" property. 23 | // 24 | // See also: webkit_settings_set_enable_write_console_messages_to_stdout at 25 | // http://webkitgtk.org/reference/webkit2gtk/stable/WebKitSettings.html#webkit-settings-set-enable-write-console-messages-to-stdout 26 | func (s *Settings) SetEnableWriteConsoleMessagesToStdout(write bool) { 27 | C.webkit_settings_set_enable_write_console_messages_to_stdout(s.settings, gboolean(write)) 28 | } 29 | -------------------------------------------------------------------------------- /webkit2/settings_2_2_test.go: -------------------------------------------------------------------------------- 1 | // +build !webkit2_2_1,!webkit2_2_0 2 | 3 | package webkit2 4 | 5 | import ( 6 | "testing" 7 | ) 8 | 9 | func TestSettings_EnableWriteConsoleMessagesToStdout(t *testing.T) { 10 | s := NewWebView().Settings() 11 | 12 | write := s.GetEnableWriteConsoleMessagesToStdout() 13 | wantWrite := !write 14 | s.SetEnableWriteConsoleMessagesToStdout(wantWrite) 15 | 16 | write = s.GetEnableWriteConsoleMessagesToStdout() 17 | if wantWrite != write { 18 | t.Errorf("want changed Write == %d, got %d", wantWrite, write) 19 | } 20 | 21 | // Revert to original setting. 22 | s.SetEnableWriteConsoleMessagesToStdout(!write) 23 | } 24 | -------------------------------------------------------------------------------- /webkit2/settings_test.go: -------------------------------------------------------------------------------- 1 | package webkit2 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestSettings_AutoLoadImages(t *testing.T) { 8 | s := NewWebView().Settings() 9 | 10 | autoLoad := s.GetAutoLoadImages() 11 | wantAutoLoad := !autoLoad 12 | s.SetAutoLoadImages(wantAutoLoad) 13 | 14 | autoLoad = s.GetAutoLoadImages() 15 | if wantAutoLoad != autoLoad { 16 | t.Errorf("want changed AutoLoad == %d, got %d", wantAutoLoad, autoLoad) 17 | } 18 | 19 | // Revert to original setting. 20 | s.SetAutoLoadImages(!autoLoad) 21 | } 22 | 23 | func TestSettings_SetUserAgentWithApplicationDetails(t *testing.T) { 24 | s := NewWebView().Settings() 25 | s.SetUserAgentWithApplicationDetails("myApp", "myVersion") 26 | } 27 | -------------------------------------------------------------------------------- /webkit2/util.go: -------------------------------------------------------------------------------- 1 | package webkit2 2 | 3 | // #include 4 | import "C" 5 | 6 | func gboolean(b bool) C.gboolean { 7 | if b { 8 | return C.gboolean(1) 9 | } 10 | return C.gboolean(0) 11 | } 12 | 13 | func gobool(b C.gboolean) bool { 14 | if b != 0 { 15 | return true 16 | } 17 | return false 18 | } 19 | -------------------------------------------------------------------------------- /webkit2/webcontext.go: -------------------------------------------------------------------------------- 1 | package webkit2 2 | 3 | // #include 4 | import "C" 5 | 6 | // WebContext manages all aspects common to all WebViews. 7 | // 8 | // See also: WebKitWebContext at 9 | // http://webkitgtk.org/reference/webkit2gtk/stable/WebKitWebContext.html. 10 | type WebContext struct { 11 | webContext *C.WebKitWebContext 12 | } 13 | 14 | // DefaultWebContext returns the default WebContext. 15 | // 16 | // See also: webkit_web_context_get_default at 17 | // http://webkitgtk.org/reference/webkit2gtk/stable/WebKitWebContext.html#webkit-web-context-get-default. 18 | func DefaultWebContext() *WebContext { 19 | return &WebContext{C.webkit_web_context_get_default()} 20 | } 21 | 22 | // CacheModel describes the caching behavior. 23 | // 24 | // See also: WebKitCacheModel at 25 | // http://webkitgtk.org/reference/webkit2gtk/stable/WebKitWebContext.html#WebKitCacheModel. 26 | type CacheModel int 27 | 28 | // CacheModel enum values are described at 29 | // http://webkitgtk.org/reference/webkit2gtk/stable/WebKitWebContext.html#WebKitCacheModel. 30 | const ( 31 | DocumentViewerCacheModel CacheModel = iota 32 | WebBrowserCacheModel 33 | DocumentBrowserCacheModel 34 | ) 35 | 36 | // CacheModel returns the current cache model. 37 | // 38 | // See also: webkit_web_context_get_cache_model at 39 | // http://webkitgtk.org/reference/webkit2gtk/stable/WebKitWebContext.html#webkit-web-context-get-cache-model. 40 | func (wc *WebContext) CacheModel() CacheModel { 41 | return CacheModel(C.int(C.webkit_web_context_get_cache_model(wc.webContext))) 42 | } 43 | 44 | // SetCacheModel sets the current cache model. 45 | // 46 | // See also: webkit_web_context_set_cache_model at 47 | // http://webkitgtk.org/reference/webkit2gtk/stable/WebKitWebContext.html#webkit-web-context-set-cache-model. 48 | func (wc *WebContext) SetCacheModel(model CacheModel) { 49 | C.webkit_web_context_set_cache_model(wc.webContext, C.WebKitCacheModel(model)) 50 | } 51 | 52 | // ClearCache clears all resources currently cached. 53 | // 54 | // See also: webkit_web_context_clear_cache at 55 | // http://webkitgtk.org/reference/webkit2gtk/stable/WebKitWebContext.html#webkit-web-context-clear-cache. 56 | func (wc *WebContext) ClearCache() { 57 | C.webkit_web_context_clear_cache(wc.webContext) 58 | } 59 | -------------------------------------------------------------------------------- /webkit2/webcontext_test.go: -------------------------------------------------------------------------------- 1 | package webkit2 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestDefaultWebContext(t *testing.T) { 8 | DefaultWebContext() 9 | } 10 | 11 | func TestWebContext_CacheModel(t *testing.T) { 12 | wc := DefaultWebContext() 13 | 14 | // WebBrowserCacheModel is the default, per 15 | // http://webkitgtk.org/reference/webkit2gtk/stable/WebKitWebContext.html#webkit-web-context-set-cache-model. 16 | wantCacheModel := WebBrowserCacheModel 17 | cacheModel := wc.CacheModel() 18 | if wantCacheModel != cacheModel { 19 | t.Errorf("want default CacheModel == %d, got %d", wantCacheModel, cacheModel) 20 | } 21 | 22 | wantCacheModel = DocumentViewerCacheModel 23 | wc.SetCacheModel(DocumentViewerCacheModel) 24 | cacheModel = wc.CacheModel() 25 | if wantCacheModel != cacheModel { 26 | t.Errorf("want changed CacheModel == %d, got %d", wantCacheModel, cacheModel) 27 | } 28 | } 29 | 30 | func TestWebContext_ClearCache(t *testing.T) { 31 | DefaultWebContext().ClearCache() 32 | } 33 | -------------------------------------------------------------------------------- /webkit2/webview.go: -------------------------------------------------------------------------------- 1 | package webkit2 2 | 3 | // #include 4 | // #include 5 | // #include 6 | // 7 | // static WebKitWebView* to_WebKitWebView(GtkWidget* w) { return WEBKIT_WEB_VIEW(w); } 8 | // 9 | // #cgo pkg-config: webkit2gtk-4.0 10 | import "C" 11 | 12 | import ( 13 | "bytes" 14 | "encoding/binary" 15 | "errors" 16 | "image" 17 | "unsafe" 18 | 19 | "github.com/gotk3/gotk3/glib" 20 | "github.com/gotk3/gotk3/gtk" 21 | "github.com/sqs/gojs" 22 | ) 23 | 24 | // WebView represents a WebKit WebView. 25 | // 26 | // See also: WebView at 27 | // http://webkitgtk.org/reference/webkit2gtk/stable/WebKitWebView.html. 28 | type WebView struct { 29 | *gtk.Widget 30 | webView *C.WebKitWebView 31 | } 32 | 33 | // NewWebView creates a new WebView with the default WebContext and the default 34 | // WebViewGroup. 35 | // 36 | // See also: webkit_web_view_new at 37 | // http://webkitgtk.org/reference/webkit2gtk/stable/WebKitWebView.html#webkit-web-view-new. 38 | func NewWebView() *WebView { 39 | return newWebView(C.webkit_web_view_new()) 40 | } 41 | 42 | // NewWebViewWithContext creates a new WebView with the given WebContext and the 43 | // default WebViewGroup. 44 | // 45 | // See also: webkit_web_view_new_with_context at 46 | // http://webkitgtk.org/reference/webkit2gtk/stable/WebKitWebView.html#webkit-web-view-new-with-context. 47 | func NewWebViewWithContext(ctx *WebContext) *WebView { 48 | return newWebView(C.webkit_web_view_new_with_context(ctx.webContext)) 49 | } 50 | 51 | func newWebView(webViewWidget *C.GtkWidget) *WebView { 52 | obj := &glib.Object{glib.ToGObject(unsafe.Pointer(webViewWidget))} 53 | return &WebView{>k.Widget{glib.InitiallyUnowned{obj}}, C.to_WebKitWebView(webViewWidget)} 54 | } 55 | 56 | // Context returns the current WebContext of the WebView. 57 | // 58 | // See also: webkit_web_view_get_context at 59 | // http://webkitgtk.org/reference/webkit2gtk/stable/WebKitWebView.html#webkit-web-view-get-context. 60 | func (v *WebView) Context() *WebContext { 61 | return &WebContext{C.webkit_web_view_get_context(v.webView)} 62 | } 63 | 64 | // LoadURI requests loading of the specified URI string. 65 | // 66 | // See also: webkit_web_view_load_uri at 67 | // http://webkitgtk.org/reference/webkit2gtk/stable/WebKitWebView.html#webkit-web-view-load-uri 68 | func (v *WebView) LoadURI(uri string) { 69 | C.webkit_web_view_load_uri(v.webView, (*C.gchar)(C.CString(uri))) 70 | } 71 | 72 | // LoadHTML loads the given content string with the specified baseURI. The MIME 73 | // type of the document will be "text/html". 74 | // 75 | // See also: webkit_web_view_load_html at 76 | // http://webkitgtk.org/reference/webkit2gtk/stable/WebKitWebView.html#webkit-web-view-load-html 77 | func (v *WebView) LoadHTML(content, baseURI string) { 78 | C.webkit_web_view_load_html(v.webView, (*C.gchar)(C.CString(content)), (*C.gchar)(C.CString(baseURI))) 79 | } 80 | 81 | // Settings returns the current active settings of this WebView's WebViewGroup. 82 | // 83 | // See also: webkit_web_view_get_settings at 84 | // http://webkitgtk.org/reference/webkit2gtk/stable/WebKitWebView.html#webkit-web-view-get-settings. 85 | func (v *WebView) Settings() *Settings { 86 | return newSettings(C.webkit_web_view_get_settings(v.webView)) 87 | } 88 | 89 | // Title returns the current active title of the WebView. 90 | // 91 | // See also: webkit_web_view_get_title at 92 | // http://webkitgtk.org/reference/webkit2gtk/stable/WebKitWebView.html#webkit-web-view-get-title. 93 | func (v *WebView) Title() string { 94 | return C.GoString((*C.char)(C.webkit_web_view_get_title(v.webView))) 95 | } 96 | 97 | // URI returns the current active URI of the WebView. 98 | // 99 | // See also: webkit_web_view_get_uri at 100 | // http://webkitgtk.org/reference/webkit2gtk/stable/WebKitWebView.html#webkit-web-view-get-uri. 101 | func (v *WebView) URI() string { 102 | return C.GoString((*C.char)(C.webkit_web_view_get_uri(v.webView))) 103 | } 104 | 105 | // JavaScriptGlobalContext returns the global JavaScript context used by 106 | // WebView. 107 | // 108 | // See also: webkit_web_view_get_javascript_global_context at 109 | // http://webkitgtk.org/reference/webkit2gtk/stable/WebKitWebView.html#webkit-web-view-get-javascript-global-context 110 | func (v *WebView) JavaScriptGlobalContext() *gojs.Context { 111 | return (*gojs.Context)(gojs.NewGlobalContextFrom((gojs.RawGlobalContext)(unsafe.Pointer(C.webkit_web_view_get_javascript_global_context(v.webView))))) 112 | } 113 | 114 | // RunJavaScript runs script asynchronously in the context of the current page 115 | // in the WebView. Upon completion, resultCallback will be called with the 116 | // result of evaluating the script, or with an error encountered during 117 | // execution. To get the stack trace and other error logs, use the 118 | // ::console-message signal. 119 | // 120 | // See also: webkit_web_view_run_javascript at 121 | // http://webkitgtk.org/reference/webkit2gtk/stable/WebKitWebView.html#webkit-web-view-run-javascript 122 | func (v *WebView) RunJavaScript(script string, resultCallback func(result *gojs.Value, err error)) { 123 | var cCallback C.GAsyncReadyCallback 124 | var userData C.gpointer 125 | var err error 126 | if resultCallback != nil { 127 | callback := func(result *C.GAsyncResult) { 128 | C.free(unsafe.Pointer(userData)) 129 | var jserr *C.GError 130 | jsResult := C.webkit_web_view_run_javascript_finish(v.webView, result, &jserr) 131 | if jsResult == nil { 132 | defer C.g_error_free(jserr) 133 | msg := C.GoString((*C.char)(jserr.message)) 134 | resultCallback(nil, errors.New(msg)) 135 | return 136 | } 137 | ctxRaw := gojs.RawGlobalContext(unsafe.Pointer(C.webkit_javascript_result_get_global_context(jsResult))) 138 | jsValRaw := gojs.RawValue(unsafe.Pointer(C.webkit_javascript_result_get_value(jsResult))) 139 | ctx := (*gojs.Context)(gojs.NewGlobalContextFrom(ctxRaw)) 140 | jsVal := ctx.NewValueFrom(jsValRaw) 141 | resultCallback(jsVal, nil) 142 | } 143 | cCallback, userData, err = newGAsyncReadyCallback(callback) 144 | if err != nil { 145 | panic(err) 146 | } 147 | } 148 | C.webkit_web_view_run_javascript(v.webView, (*C.gchar)(C.CString(script)), nil, cCallback, userData) 149 | } 150 | 151 | // Destroy destroys the WebView's corresponding GtkWidget and marks its internal 152 | // WebKitWebView as nil so that it can't be accidentally reused. 153 | func (v *WebView) Destroy() { 154 | v.Widget.Destroy() 155 | v.webView = nil 156 | } 157 | 158 | // LoadEvent denotes the different events that happen during a WebView load 159 | // operation. 160 | // 161 | // See also: WebKitLoadEvent at 162 | // http://webkitgtk.org/reference/webkit2gtk/stable/WebKitWebView.html#WebKitLoadEvent. 163 | type LoadEvent int 164 | 165 | // LoadEvent enum values are described at 166 | // http://webkitgtk.org/reference/webkit2gtk/stable/WebKitWebView.html#WebKitLoadEvent. 167 | const ( 168 | LoadStarted LoadEvent = iota 169 | LoadRedirected 170 | LoadCommitted 171 | LoadFinished 172 | ) 173 | 174 | // http://cairographics.org/manual/cairo-cairo-surface-t.html#cairo-surface-type-t 175 | const cairoSurfaceTypeImage = 0 176 | 177 | // http://cairographics.org/manual/cairo-Image-Surfaces.html#cairo-format-t 178 | const cairoImageSurfaceFormatARGB32 = 0 179 | 180 | // GetSnapshot runs asynchronously, taking a snapshot of the WebView. 181 | // Upon completion, resultCallback will be called with a copy of the underlying 182 | // bitmap backing store for the frame, or with an error encountered during 183 | // execution. 184 | // 185 | // See also: webkit_web_view_get_snapshot at 186 | // http://webkitgtk.org/reference/webkit2gtk/stable/WebKitWebView.html#webkit-web-view-get-snapshot 187 | func (v *WebView) GetSnapshot(resultCallback func(result *image.RGBA, err error)) { 188 | var cCallback C.GAsyncReadyCallback 189 | var userData C.gpointer 190 | var err error 191 | if resultCallback != nil { 192 | callback := func(result *C.GAsyncResult) { 193 | C.free(unsafe.Pointer(userData)) 194 | var snapErr *C.GError 195 | snapResult := C.webkit_web_view_get_snapshot_finish(v.webView, result, &snapErr) 196 | if snapResult == nil { 197 | defer C.g_error_free(snapErr) 198 | msg := C.GoString((*C.char)(snapErr.message)) 199 | resultCallback(nil, errors.New(msg)) 200 | return 201 | } 202 | defer C.cairo_surface_destroy(snapResult) 203 | 204 | if C.cairo_surface_get_type(snapResult) != cairoSurfaceTypeImage || 205 | C.cairo_image_surface_get_format(snapResult) != cairoImageSurfaceFormatARGB32 { 206 | panic("Snapshot in unexpected format") 207 | } 208 | 209 | w := int(C.cairo_image_surface_get_width(snapResult)) 210 | h := int(C.cairo_image_surface_get_height(snapResult)) 211 | stride := int(C.cairo_image_surface_get_stride(snapResult)) 212 | data := unsafe.Pointer(C.cairo_image_surface_get_data(snapResult)) 213 | surfaceBytes := C.GoBytes(data, C.int(stride*h)) 214 | // convert from b,g,r,a or a,r,g,b(local endianness) to r,g,b,a 215 | testint, _ := binary.ReadUvarint(bytes.NewBuffer([]byte{0x1, 0})) 216 | if testint == 0x1 { 217 | // Little: b,g,r,a -> r,g,b,a 218 | for i := 0; i < w*h; i++ { 219 | b := surfaceBytes[4*i+0] 220 | r := surfaceBytes[4*i+2] 221 | surfaceBytes[4*i+0] = r 222 | surfaceBytes[4*i+2] = b 223 | } 224 | } else { 225 | // Big: a,r,g,b -> r,g,b,a 226 | for i := 0; i < w*h; i++ { 227 | a := surfaceBytes[4*i+0] 228 | r := surfaceBytes[4*i+1] 229 | g := surfaceBytes[4*i+2] 230 | b := surfaceBytes[4*i+3] 231 | surfaceBytes[4*i+0] = r 232 | surfaceBytes[4*i+1] = g 233 | surfaceBytes[4*i+2] = b 234 | surfaceBytes[4*i+3] = a 235 | } 236 | } 237 | rgba := &image.RGBA{surfaceBytes, stride, image.Rect(0, 0, w, h)} 238 | resultCallback(rgba, nil) 239 | } 240 | cCallback, userData, err = newGAsyncReadyCallback(callback) 241 | if err != nil { 242 | panic(err) 243 | } 244 | } 245 | 246 | C.webkit_web_view_get_snapshot(v.webView, 247 | (C.WebKitSnapshotRegion)(1), // FullDocument is the only working region at this point 248 | (C.WebKitSnapshotOptions)(0), 249 | nil, 250 | cCallback, 251 | userData) 252 | } 253 | -------------------------------------------------------------------------------- /webkit2/webview_test.go: -------------------------------------------------------------------------------- 1 | package webkit2 2 | 3 | import ( 4 | "errors" 5 | "image" 6 | "net/http" 7 | "reflect" 8 | "testing" 9 | 10 | "github.com/gotk3/gotk3/glib" 11 | "github.com/gotk3/gotk3/gtk" 12 | "github.com/sqs/gojs" 13 | ) 14 | 15 | func TestNewWebView(t *testing.T) { 16 | webView := NewWebView() 17 | defer webView.Destroy() 18 | } 19 | 20 | func TestNewWebViewWithContext(t *testing.T) { 21 | cx := DefaultWebContext() 22 | webView := NewWebViewWithContext(cx) 23 | defer webView.Destroy() 24 | } 25 | 26 | func TestWebView_Context(t *testing.T) { 27 | webView := NewWebView() 28 | defer webView.Destroy() 29 | webView.Context() 30 | } 31 | 32 | func TestWebView_LoadURI(t *testing.T) { 33 | setup() 34 | defer teardown() 35 | 36 | responseOk := false 37 | mux.HandleFunc("/", func(w http.ResponseWriter, _ *http.Request) { 38 | w.Write([]byte("abc")) 39 | responseOk = true 40 | }) 41 | 42 | loadFinished := false 43 | webView.Connect("load-failed", func() { 44 | t.Errorf("load failed") 45 | }) 46 | webView.Connect("load-changed", func(_ *glib.Object, loadEvent LoadEvent) { 47 | switch loadEvent { 48 | case LoadFinished: 49 | loadFinished = true 50 | gtk.MainQuit() 51 | } 52 | }) 53 | 54 | glib.IdleAdd(func() bool { 55 | webView.LoadURI(server.URL) 56 | return false 57 | }) 58 | 59 | gtk.Main() 60 | 61 | if !responseOk { 62 | t.Error("!responseOk") 63 | } 64 | if !loadFinished { 65 | t.Error("!loadFinished") 66 | } 67 | } 68 | 69 | func TestWebView_LoadURI_load_failed(t *testing.T) { 70 | webView := NewWebView() 71 | defer webView.Destroy() 72 | 73 | loadFailed := false 74 | loadFinished := false 75 | webView.Connect("load-failed", func() { 76 | loadFailed = true 77 | }) 78 | webView.Connect("load-changed", func(_ *glib.Object, loadEvent LoadEvent) { 79 | switch loadEvent { 80 | case LoadFinished: 81 | loadFinished = true 82 | gtk.MainQuit() 83 | } 84 | }) 85 | 86 | glib.IdleAdd(func() bool { 87 | // Load a bad URL to trigger load failure. 88 | webView.LoadURI("http://127.0.0.1:99999") 89 | return false 90 | }) 91 | 92 | gtk.Main() 93 | 94 | if !loadFailed { 95 | t.Error("!loadFailed") 96 | } 97 | if !loadFinished { 98 | t.Error("!loadFinished") 99 | } 100 | } 101 | 102 | func TestWebView_LoadHTML(t *testing.T) { 103 | webView := NewWebView() 104 | defer webView.Destroy() 105 | 106 | loadOk := false 107 | webView.Connect("load-failed", func() { 108 | t.Errorf("load failed") 109 | }) 110 | webView.Connect("load-changed", func(_ *glib.Object, loadEvent LoadEvent) { 111 | switch loadEvent { 112 | case LoadFinished: 113 | loadOk = true 114 | gtk.MainQuit() 115 | } 116 | }) 117 | 118 | glib.IdleAdd(func() bool { 119 | webView.LoadHTML("

hello

", "") 120 | return false 121 | }) 122 | 123 | gtk.Main() 124 | 125 | if !loadOk { 126 | t.Error("!loadOk") 127 | } 128 | } 129 | 130 | func TestWebView_Title(t *testing.T) { 131 | webView := NewWebView() 132 | defer webView.Destroy() 133 | 134 | wantTitle := "foo" 135 | var gotTitle string 136 | webView.Connect("notify::title", func() { 137 | glib.IdleAdd(func() bool { 138 | gotTitle = webView.Title() 139 | if gotTitle != "" { 140 | gtk.MainQuit() 141 | } 142 | return false 143 | }) 144 | }) 145 | 146 | glib.IdleAdd(func() bool { 147 | webView.LoadHTML(""+wantTitle+"", "") 148 | return false 149 | }) 150 | 151 | gtk.Main() 152 | 153 | if wantTitle != gotTitle { 154 | t.Errorf("want title %q, got %q", wantTitle, gotTitle) 155 | } 156 | } 157 | 158 | func TestWebView_URI(t *testing.T) { 159 | setup() 160 | defer teardown() 161 | 162 | mux.HandleFunc("/", func(w http.ResponseWriter, _ *http.Request) {}) 163 | 164 | wantURI := server.URL + "/" 165 | var gotURI string 166 | webView.Connect("notify::uri", func() { 167 | glib.IdleAdd(func() bool { 168 | gotURI = webView.URI() 169 | if gotURI != "" { 170 | gtk.MainQuit() 171 | } 172 | return false 173 | }) 174 | }) 175 | 176 | glib.IdleAdd(func() bool { 177 | webView.LoadURI(server.URL) 178 | return false 179 | }) 180 | 181 | gtk.Main() 182 | 183 | if wantURI != gotURI { 184 | t.Errorf("want URI %q, got %q", wantURI, gotURI) 185 | } 186 | } 187 | 188 | func TestWebView_Settings(t *testing.T) { 189 | webView := NewWebView() 190 | defer webView.Destroy() 191 | 192 | webView.Settings() 193 | } 194 | 195 | func TestWebView_JavaScriptGlobalContext(t *testing.T) { 196 | webView := NewWebView() 197 | defer webView.Destroy() 198 | 199 | webView.JavaScriptGlobalContext() 200 | } 201 | 202 | func TestWebView_RunJavaScript(t *testing.T) { 203 | webView := NewWebView() 204 | defer webView.Destroy() 205 | 206 | wantResultString := "abc" 207 | webView.Connect("load-changed", func(_ *glib.Object, loadEvent LoadEvent) { 208 | switch loadEvent { 209 | case LoadFinished: 210 | webView.RunJavaScript(`document.getElementById("foo").innerHTML`, func(result *gojs.Value, err error) { 211 | if err != nil { 212 | t.Errorf("RunJavaScript error: %s", err) 213 | } 214 | resultString := webView.JavaScriptGlobalContext().ToStringOrDie(result) 215 | if wantResultString != resultString { 216 | t.Errorf("want result string %q, got %q", wantResultString, resultString) 217 | } 218 | gtk.MainQuit() 219 | }) 220 | } 221 | }) 222 | 223 | glib.IdleAdd(func() bool { 224 | webView.LoadHTML(`

abc

`, "") 225 | return false 226 | }) 227 | 228 | gtk.Main() 229 | } 230 | 231 | func TestWebView_RunJavaScript_exception(t *testing.T) { 232 | webView := NewWebView() 233 | defer webView.Destroy() 234 | 235 | wantErr := errors.New("An exception was raised in JavaScript") 236 | webView.Connect("load-changed", func(_ *glib.Object, loadEvent LoadEvent) { 237 | switch loadEvent { 238 | case LoadFinished: 239 | webView.RunJavaScript(`throw new Error("foo")`, func(result *gojs.Value, err error) { 240 | if result != nil { 241 | ctx := webView.JavaScriptGlobalContext() 242 | t.Errorf("want result == nil, got %q", ctx.ToStringOrDie(result)) 243 | } 244 | if !reflect.DeepEqual(wantErr, err) { 245 | t.Errorf("want error %q, got %q", wantErr, err) 246 | } 247 | gtk.MainQuit() 248 | }) 249 | } 250 | }) 251 | 252 | glib.IdleAdd(func() bool { 253 | webView.LoadHTML(`

`, "") 254 | return false 255 | }) 256 | 257 | gtk.Main() 258 | } 259 | 260 | func TestWebView_GetSnapshot(t *testing.T) { 261 | webView := NewWebView() 262 | defer webView.Destroy() 263 | 264 | webView.Connect("load-changed", func(_ *glib.Object, loadEvent LoadEvent) { 265 | switch loadEvent { 266 | case LoadFinished: 267 | webView.GetSnapshot(func(img *image.RGBA, err error) { 268 | if err != nil { 269 | t.Errorf("GetSnapshot error: %q", err) 270 | } 271 | if img.Pix == nil { 272 | t.Error("!img.Pix") 273 | } 274 | if img.Stride == 0 || img.Rect.Max.X == 0 || img.Rect.Max.Y == 0 { 275 | t.Error("!img.Stride or !img.Rect.Max.X or !img.Rect.Max.Y") 276 | } 277 | gtk.MainQuit() 278 | }) 279 | } 280 | }) 281 | 282 | glib.IdleAdd(func() bool { 283 | webView.LoadHTML(`

abc

`, "") 284 | return false 285 | }) 286 | 287 | gtk.Main() 288 | } 289 | --------------------------------------------------------------------------------