├── LICENSE ├── README.md ├── phantomjs.go └── phantomjs_test.go /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 Ben Johnson 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## deprecation warning 2 | active phantomjs development has ended, in favor of using Chrome's new headless functionality ([reference](https://groups.google.com/forum/#!msg/phantomjs/9aI5d-LDuNE/5Z3SMZrqAQAJ)). Instead of using this library, consider using a go package that uses this new api such as [chromedp](https://github.com/chromedp/chromedp). 3 | 4 | phantomjs [![godoc](https://godoc.org/github.com/benbjohnson/phantomjs?status.svg)](https://godoc.org/github.com/benbjohnson/phantomjs) ![Status](https://img.shields.io/badge/status-beta-yellow.svg) 5 | ========= 6 | 7 | This is a Go wrapper for the [`phantomjs`][phantomjs] command line program. It 8 | provides the full `webpage` API and has a strongly typed API. The wrapper 9 | provides an idiomatic Go interface while allowing you to communicate with the 10 | underlying WebKit and JavaScript engine in a seamless way. 11 | 12 | [phantomjs]: http://phantomjs.org/ 13 | 14 | 15 | ## Installing 16 | 17 | First, install `phantomjs` on your machine. This can be done using your package 18 | manager (such as `apt-get` or `brew`). Then install this package using the Go 19 | toolchain: 20 | 21 | ```sh 22 | $ go get -u github.com/benbjohnson/phantomjs 23 | ``` 24 | 25 | 26 | ## Usage 27 | 28 | ### Starting the process 29 | 30 | This wrapper works by communicating with a separate `phantomjs` process over 31 | HTTP. The process can take several seconds to start up and shut down so you 32 | should do that once and then share the process. There is a package-level 33 | variable called `phantomjs.DefaultProcess` that exists for this purpose. 34 | 35 | ```go 36 | package main 37 | 38 | import ( 39 | "github.com/benbjohnson/phantomjs" 40 | ) 41 | 42 | func main() { 43 | // Start the process once. 44 | if err := phantomjs.DefaultProcess.Open(); err != nil { 45 | fmt.Println(err) 46 | os.Exit(1) 47 | } 48 | defer phantomjs.DefaultProcess.Close() 49 | 50 | // Do other stuff in your program. 51 | doStuff() 52 | } 53 | ``` 54 | 55 | You can have multiple processes, however, you will need to change the port used 56 | for each one so they do not conflict. This library uses port `20202` by default. 57 | 58 | 59 | ### Working with WebPage 60 | 61 | The `WebPage` will be the primary object you work with in `phantomjs`. Typically 62 | you will create a web page from a `Process` and then either open a URL or you 63 | can set the content directly: 64 | 65 | ```go 66 | // Create a web page. 67 | // IMPORTANT: Always make sure you close your pages! 68 | page, err := p.CreateWebPage() 69 | if err != nil { 70 | return err 71 | } 72 | defer page.Close() 73 | 74 | // Open a URL. 75 | if err := page.Open("https://google.com"); err != nil { 76 | return err 77 | } 78 | ``` 79 | 80 | The HTTP API uses a reference map to track references between the Go library 81 | and the `phantomjs` process. Because of this, it is important to always 82 | `Close()` your web pages or else you can experience memory leaks. 83 | 84 | 85 | 86 | ### Executing JavaScript 87 | 88 | You can synchronously execute JavaScript within the context of a web page by 89 | by using the `Evaluate()` function. This example below opens Hacker News, 90 | retrieves the text and URL from the first link, and prints it to the terminal. 91 | 92 | ```go 93 | // Open a URL. 94 | if err := page.Open("https://news.ycombinator.com"); err != nil { 95 | return err 96 | } 97 | 98 | // Read first link. 99 | info, err := page.Evaluate(`function() { 100 | var link = document.body.querySelector('.itemlist .title a'); 101 | return { title: link.innerText, url: link.href }; 102 | }`) 103 | if err != nil { 104 | return err 105 | } 106 | 107 | // Print title and URL. 108 | link := info.(map[string]interface{}) 109 | fmt.Println("Hacker News Top Link:") 110 | fmt.Println(link["title"]) 111 | fmt.Println(link["url"]) 112 | fmt.Println() 113 | ``` 114 | 115 | You can pass back any object from `Evaluate()` that can be marshaled over JSON. 116 | 117 | 118 | 119 | ### Rendering web pages 120 | 121 | Another common task with PhantomJS is to render a web page to an image. Once 122 | you have opened your web page, simply set the viewport size and call the 123 | `Render()` method: 124 | 125 | ```go 126 | // Open a URL. 127 | if err := page.Open("https://news.ycombinator.com"); err != nil { 128 | return err 129 | } 130 | 131 | // Setup the viewport and render the results view. 132 | if err := page.SetViewportSize(1024, 800); err != nil { 133 | return err 134 | } 135 | if err := page.Render("hackernews.png", "png", 100); err != nil { 136 | return err 137 | } 138 | ``` 139 | 140 | You can also use the `RenderBase64()` to return a base64 encoded image to your 141 | program instead of writing the file to disk. 142 | 143 | -------------------------------------------------------------------------------- /phantomjs.go: -------------------------------------------------------------------------------- 1 | package phantomjs 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "errors" 7 | "fmt" 8 | "io" 9 | "io/ioutil" 10 | "net/http" 11 | "os" 12 | "os/exec" 13 | "path/filepath" 14 | "time" 15 | ) 16 | 17 | var ( 18 | // ErrInjectionFailed is returned by InjectJS when injection fails. 19 | ErrInjectionFailed = errors.New("injection failed") 20 | ) 21 | 22 | // Keyboard modifiers. 23 | const ( 24 | ShiftKey = 0x02000000 25 | CtrlKey = 0x04000000 26 | AltKey = 0x08000000 27 | MetaKey = 0x10000000 28 | Keypad = 0x20000000 29 | ) 30 | 31 | // Default settings. 32 | const ( 33 | DefaultPort = 20202 34 | DefaultBinPath = "phantomjs" 35 | ) 36 | 37 | // Process represents a PhantomJS process. 38 | type Process struct { 39 | path string 40 | cmd *exec.Cmd 41 | 42 | // Path to the 'phantomjs' binary. 43 | BinPath string 44 | 45 | // HTTP port used to communicate with phantomjs. 46 | Port int 47 | 48 | // Output from the process. 49 | Stdout io.Writer 50 | Stderr io.Writer 51 | } 52 | 53 | // NewProcess returns a new instance of Process. 54 | func NewProcess() *Process { 55 | return &Process{ 56 | BinPath: DefaultBinPath, 57 | Port: DefaultPort, 58 | Stdout: os.Stdout, 59 | Stderr: os.Stderr, 60 | } 61 | } 62 | 63 | // Path returns a temporary path that the process is run from. 64 | func (p *Process) Path() string { 65 | return p.path 66 | } 67 | 68 | // Open start the phantomjs process with the shim script. 69 | func (p *Process) Open() error { 70 | if err := func() error { 71 | // Generate temporary path to run script from. 72 | path, err := ioutil.TempDir("", "phantomjs-") 73 | if err != nil { 74 | return err 75 | } 76 | p.path = path 77 | 78 | // Write shim script. 79 | scriptPath := filepath.Join(path, "shim.js") 80 | if err := ioutil.WriteFile(scriptPath, []byte(shim), 0600); err != nil { 81 | return err 82 | } 83 | 84 | // Start external process. 85 | cmd := exec.Command(p.BinPath, scriptPath) 86 | cmd.Env = []string{fmt.Sprintf("PORT=%d", p.Port)} 87 | cmd.Stdout = p.Stdout 88 | cmd.Stderr = p.Stderr 89 | if err := cmd.Start(); err != nil { 90 | return err 91 | } 92 | p.cmd = cmd 93 | 94 | // Wait until process is available. 95 | if err := p.wait(); err != nil { 96 | return err 97 | } 98 | return nil 99 | 100 | }(); err != nil { 101 | p.Close() 102 | return err 103 | } 104 | 105 | return nil 106 | } 107 | 108 | // Close stops the process. 109 | func (p *Process) Close() (err error) { 110 | // Kill process. 111 | if p.cmd != nil { 112 | if e := p.cmd.Process.Kill(); e != nil && err == nil { 113 | err = e 114 | } 115 | p.cmd.Wait() 116 | } 117 | 118 | // Remove shim file. 119 | if p.path != "" { 120 | if e := os.RemoveAll(p.path); e != nil && err == nil { 121 | err = e 122 | } 123 | } 124 | 125 | return err 126 | } 127 | 128 | // URL returns the process' API URL. 129 | func (p *Process) URL() string { 130 | return fmt.Sprintf("http://localhost:%d", p.Port) 131 | } 132 | 133 | // wait continually checks the process until it gets a response or times out. 134 | func (p *Process) wait() error { 135 | ticker := time.NewTicker(1000 * time.Millisecond) 136 | defer ticker.Stop() 137 | 138 | timer := time.NewTimer(30 * time.Second) 139 | defer ticker.Stop() 140 | 141 | for { 142 | select { 143 | case <-timer.C: 144 | return errors.New("timeout") 145 | case <-ticker.C: 146 | if err := p.ping(); err == nil { 147 | return nil 148 | } 149 | } 150 | } 151 | } 152 | 153 | // ping checks the process to see if it is up. 154 | func (p *Process) ping() error { 155 | // Send request. 156 | resp, err := http.Get(p.URL() + "/ping") 157 | if err != nil { 158 | return err 159 | } 160 | resp.Body.Close() 161 | 162 | // Verify successful status code. 163 | if resp.StatusCode != http.StatusOK { 164 | return fmt.Errorf("unexpected status: %d", resp.StatusCode) 165 | } 166 | return nil 167 | } 168 | 169 | // CreateWebPage returns a new instance of a "webpage". 170 | func (p *Process) CreateWebPage() (*WebPage, error) { 171 | var resp struct { 172 | Ref refJSON `json:"ref"` 173 | } 174 | if err := p.doJSON("POST", "/webpage/Create", nil, &resp); err != nil { 175 | return nil, err 176 | } 177 | return &WebPage{ref: newRef(p, resp.Ref.ID)}, nil 178 | } 179 | 180 | // doJSON sends an HTTP request to url and encodes and decodes the req/resp as JSON. 181 | func (p *Process) doJSON(method, path string, req, resp interface{}) error { 182 | // Encode request. 183 | var r io.Reader 184 | if req != nil { 185 | buf, err := json.Marshal(req) 186 | if err != nil { 187 | return err 188 | } 189 | r = bytes.NewReader(buf) 190 | } 191 | 192 | // Create request. 193 | httpRequest, err := http.NewRequest(method, p.URL()+path, r) 194 | if err != nil { 195 | return err 196 | } 197 | 198 | // Send request. 199 | httpResponse, err := http.DefaultClient.Do(httpRequest) 200 | if err != nil { 201 | return err 202 | } 203 | defer httpResponse.Body.Close() 204 | 205 | // Read response body. 206 | body, err := ioutil.ReadAll(httpResponse.Body) 207 | if err != nil { 208 | return err 209 | } 210 | 211 | // Check response code. 212 | if httpResponse.StatusCode == http.StatusNotFound { 213 | return fmt.Errorf("not found: %s", path) 214 | } 215 | 216 | // If an error was returned then return it. 217 | var errResp errorResponse 218 | if err := json.Unmarshal(body, &errResp); err != nil { 219 | return errors.New("phantomjs.Process: " + string(body)) 220 | } else if errResp.Error != "" { 221 | return errors.New(errResp.Error) 222 | } 223 | 224 | // Decode response if reference passed in. 225 | if resp != nil { 226 | if err := json.Unmarshal(body, resp); err != nil { 227 | return fmt.Errorf("unmarshal error: err=%s, body=%s", err, body) 228 | } 229 | } 230 | 231 | return nil 232 | } 233 | 234 | type errorResponse struct { 235 | Error string `json:"error"` 236 | } 237 | 238 | // DefaultProcess is a global, shared process. 239 | // It must be opened before use. 240 | var DefaultProcess = NewProcess() 241 | 242 | // CreateWebPage returns a new instance of a "webpage" using the default process. 243 | func CreateWebPage() (*WebPage, error) { 244 | return DefaultProcess.CreateWebPage() 245 | } 246 | 247 | // WebPage represents an object returned from "webpage.create()". 248 | type WebPage struct { 249 | ref *Ref 250 | } 251 | 252 | // Open opens a URL. 253 | func (p *WebPage) Open(url string) error { 254 | req := map[string]interface{}{ 255 | "ref": p.ref.id, 256 | "url": url, 257 | } 258 | var resp struct { 259 | Status string `json:"status"` 260 | } 261 | if err := p.ref.process.doJSON("POST", "/webpage/Open", req, &resp); err != nil { 262 | return err 263 | } 264 | 265 | if resp.Status != "success" { 266 | return errors.New("failed") 267 | } 268 | return nil 269 | } 270 | 271 | // CanGoBack returns true if the page can be navigated back. 272 | func (p *WebPage) CanGoBack() (bool, error) { 273 | var resp struct { 274 | Value bool `json:"value"` 275 | } 276 | if err := p.ref.process.doJSON("POST", "/webpage/CanGoBack", map[string]interface{}{"ref": p.ref.id}, &resp); err != nil { 277 | return false, err 278 | } 279 | return resp.Value, nil 280 | } 281 | 282 | // CanGoForward returns true if the page can be navigated forward. 283 | func (p *WebPage) CanGoForward() (bool, error) { 284 | var resp struct { 285 | Value bool `json:"value"` 286 | } 287 | if err := p.ref.process.doJSON("POST", "/webpage/CanGoForward", map[string]interface{}{"ref": p.ref.id}, &resp); err != nil { 288 | return false, err 289 | } 290 | return resp.Value, nil 291 | } 292 | 293 | // ClipRect returns the clipping rectangle used when rendering. 294 | // Returns nil if no clipping rectangle is set. 295 | func (p *WebPage) ClipRect() (Rect, error) { 296 | var resp struct { 297 | Value rectJSON `json:"value"` 298 | } 299 | if err := p.ref.process.doJSON("POST", "/webpage/ClipRect", map[string]interface{}{"ref": p.ref.id}, &resp); err != nil { 300 | return Rect{}, err 301 | } 302 | return Rect{ 303 | Top: resp.Value.Top, 304 | Left: resp.Value.Left, 305 | Width: resp.Value.Width, 306 | Height: resp.Value.Height, 307 | }, nil 308 | } 309 | 310 | // SetClipRect sets the clipping rectangle used when rendering. 311 | // Set to nil to render the entire webpage. 312 | func (p *WebPage) SetClipRect(rect Rect) error { 313 | req := map[string]interface{}{ 314 | "ref": p.ref.id, 315 | "rect": rectJSON{ 316 | Top: rect.Top, 317 | Left: rect.Left, 318 | Width: rect.Width, 319 | Height: rect.Height, 320 | }, 321 | } 322 | return p.ref.process.doJSON("POST", "/webpage/SetClipRect", req, nil) 323 | } 324 | 325 | // Content returns content of the webpage enclosed in an HTML/XML element. 326 | func (p *WebPage) Content() (string, error) { 327 | var resp struct { 328 | Value string `json:"value"` 329 | } 330 | if err := p.ref.process.doJSON("POST", "/webpage/Content", map[string]interface{}{"ref": p.ref.id}, &resp); err != nil { 331 | return "", err 332 | } 333 | return resp.Value, nil 334 | } 335 | 336 | // SetContent sets the content of the webpage. 337 | func (p *WebPage) SetContent(content string) error { 338 | return p.ref.process.doJSON("POST", "/webpage/SetContent", map[string]interface{}{"ref": p.ref.id, "content": content}, nil) 339 | } 340 | 341 | // Cookies returns a list of cookies visible to the current URL. 342 | func (p *WebPage) Cookies() ([]*http.Cookie, error) { 343 | var resp struct { 344 | Value []cookieJSON `json:"value"` 345 | } 346 | if err := p.ref.process.doJSON("POST", "/webpage/Cookies", map[string]interface{}{"ref": p.ref.id}, &resp); err != nil { 347 | return nil, err 348 | } 349 | 350 | a := make([]*http.Cookie, len(resp.Value)) 351 | for i := range resp.Value { 352 | a[i] = decodeCookieJSON(resp.Value[i]) 353 | } 354 | return a, nil 355 | } 356 | 357 | // SetCookies sets a list of cookies visible to the current URL. 358 | func (p *WebPage) SetCookies(cookies []*http.Cookie) error { 359 | a := make([]cookieJSON, len(cookies)) 360 | for i := range cookies { 361 | a[i] = encodeCookieJSON(cookies[i]) 362 | } 363 | req := map[string]interface{}{"ref": p.ref.id, "cookies": a} 364 | return p.ref.process.doJSON("POST", "/webpage/SetCookies", req, nil) 365 | } 366 | 367 | // CustomHeaders returns a list of additional headers sent with the web page. 368 | func (p *WebPage) CustomHeaders() (http.Header, error) { 369 | var resp struct { 370 | Value map[string]string `json:"value"` 371 | } 372 | if err := p.ref.process.doJSON("POST", "/webpage/CustomHeaders", map[string]interface{}{"ref": p.ref.id}, &resp); err != nil { 373 | return nil, err 374 | } 375 | 376 | // Convert to a header object. 377 | hdr := make(http.Header) 378 | for key, value := range resp.Value { 379 | hdr.Set(key, value) 380 | } 381 | return hdr, nil 382 | } 383 | 384 | // SetCustomHeaders sets a list of additional headers sent with the web page. 385 | // 386 | // This function does not support multiple headers with the same name. Only 387 | // the first value for a header key will be used. 388 | func (p *WebPage) SetCustomHeaders(header http.Header) error { 389 | m := make(map[string]string) 390 | for key := range header { 391 | m[key] = header.Get(key) 392 | } 393 | req := map[string]interface{}{"ref": p.ref.id, "headers": m} 394 | return p.ref.process.doJSON("POST", "/webpage/SetCustomHeaders", req, nil) 395 | } 396 | 397 | // FocusedFrameName returns the name of the currently focused frame. 398 | func (p *WebPage) FocusedFrameName() (string, error) { 399 | var resp struct { 400 | Value string `json:"value"` 401 | } 402 | if err := p.ref.process.doJSON("POST", "/webpage/FocusedFrameName", map[string]interface{}{"ref": p.ref.id}, &resp); err != nil { 403 | return "", err 404 | } 405 | return resp.Value, nil 406 | } 407 | 408 | // FrameContent returns the content of the current frame. 409 | func (p *WebPage) FrameContent() (string, error) { 410 | var resp struct { 411 | Value string `json:"value"` 412 | } 413 | if err := p.ref.process.doJSON("POST", "/webpage/FrameContent", map[string]interface{}{"ref": p.ref.id}, &resp); err != nil { 414 | return "", err 415 | } 416 | return resp.Value, nil 417 | } 418 | 419 | // SetFrameContent sets the content of the current frame. 420 | func (p *WebPage) SetFrameContent(content string) error { 421 | return p.ref.process.doJSON("POST", "/webpage/SetFrameContent", map[string]interface{}{"ref": p.ref.id, "content": content}, nil) 422 | } 423 | 424 | // FrameName returns the name of the current frame. 425 | func (p *WebPage) FrameName() (string, error) { 426 | var resp struct { 427 | Value string `json:"value"` 428 | } 429 | if err := p.ref.process.doJSON("POST", "/webpage/FrameName", map[string]interface{}{"ref": p.ref.id}, &resp); err != nil { 430 | return "", err 431 | } 432 | return resp.Value, nil 433 | } 434 | 435 | // FramePlainText returns the plain text representation of the current frame content. 436 | func (p *WebPage) FramePlainText() (string, error) { 437 | var resp struct { 438 | Value string `json:"value"` 439 | } 440 | if err := p.ref.process.doJSON("POST", "/webpage/FramePlainText", map[string]interface{}{"ref": p.ref.id}, &resp); err != nil { 441 | return "", err 442 | } 443 | return resp.Value, nil 444 | } 445 | 446 | // FrameTitle returns the title of the current frame. 447 | func (p *WebPage) FrameTitle() (string, error) { 448 | var resp struct { 449 | Value string `json:"value"` 450 | } 451 | if err := p.ref.process.doJSON("POST", "/webpage/FrameTitle", map[string]interface{}{"ref": p.ref.id}, &resp); err != nil { 452 | return "", err 453 | } 454 | return resp.Value, nil 455 | } 456 | 457 | // FrameURL returns the URL of the current frame. 458 | func (p *WebPage) FrameURL() (string, error) { 459 | var resp struct { 460 | Value string `json:"value"` 461 | } 462 | if err := p.ref.process.doJSON("POST", "/webpage/FrameURL", map[string]interface{}{"ref": p.ref.id}, &resp); err != nil { 463 | return "", err 464 | } 465 | return resp.Value, nil 466 | } 467 | 468 | // FrameCount returns the total number of frames. 469 | func (p *WebPage) FrameCount() (int, error) { 470 | var resp struct { 471 | Value int `json:"value"` 472 | } 473 | if err := p.ref.process.doJSON("POST", "/webpage/FrameCount", map[string]interface{}{"ref": p.ref.id}, &resp); err != nil { 474 | return 0, err 475 | } 476 | return resp.Value, nil 477 | } 478 | 479 | // FrameNames returns an list of frame names. 480 | func (p *WebPage) FrameNames() ([]string, error) { 481 | var resp struct { 482 | Value []string `json:"value"` 483 | } 484 | if err := p.ref.process.doJSON("POST", "/webpage/FrameNames", map[string]interface{}{"ref": p.ref.id}, &resp); err != nil { 485 | return nil, err 486 | } 487 | return resp.Value, nil 488 | } 489 | 490 | // LibraryPath returns the path used by InjectJS() to resolve scripts. 491 | // Initially it is set to Process.Path(). 492 | func (p *WebPage) LibraryPath() (string, error) { 493 | var resp struct { 494 | Value string `json:"value"` 495 | } 496 | if err := p.ref.process.doJSON("POST", "/webpage/LibraryPath", map[string]interface{}{"ref": p.ref.id}, &resp); err != nil { 497 | return "", err 498 | } 499 | return resp.Value, nil 500 | } 501 | 502 | // SetLibraryPath sets the library path used by InjectJS(). 503 | func (p *WebPage) SetLibraryPath(path string) error { 504 | return p.ref.process.doJSON("POST", "/webpage/SetLibraryPath", map[string]interface{}{"ref": p.ref.id, "path": path}, nil) 505 | } 506 | 507 | // NavigationLocked returns true if the navigation away from the page is disabled. 508 | func (p *WebPage) NavigationLocked() (bool, error) { 509 | var resp struct { 510 | Value bool `json:"value"` 511 | } 512 | if err := p.ref.process.doJSON("POST", "/webpage/NavigationLocked", map[string]interface{}{"ref": p.ref.id}, &resp); err != nil { 513 | return false, err 514 | } 515 | return resp.Value, nil 516 | } 517 | 518 | // SetNavigationLocked sets whether navigation away from the page should be disabled. 519 | func (p *WebPage) SetNavigationLocked(value bool) error { 520 | return p.ref.process.doJSON("POST", "/webpage/SetNavigationLocked", map[string]interface{}{"ref": p.ref.id, "value": value}, nil) 521 | } 522 | 523 | // OfflineStoragePath returns the path used by offline storage. 524 | func (p *WebPage) OfflineStoragePath() (string, error) { 525 | var resp struct { 526 | Value string `json:"value"` 527 | } 528 | if err := p.ref.process.doJSON("POST", "/webpage/OfflineStoragePath", map[string]interface{}{"ref": p.ref.id}, &resp); err != nil { 529 | return "", err 530 | } 531 | return resp.Value, nil 532 | } 533 | 534 | // OfflineStorageQuota returns the number of bytes that can be used for offline storage. 535 | func (p *WebPage) OfflineStorageQuota() (int, error) { 536 | var resp struct { 537 | Value int `json:"value"` 538 | } 539 | if err := p.ref.process.doJSON("POST", "/webpage/OfflineStorageQuota", map[string]interface{}{"ref": p.ref.id}, &resp); err != nil { 540 | return 0, err 541 | } 542 | return resp.Value, nil 543 | } 544 | 545 | // OwnsPages returns true if this page owns pages opened in other windows. 546 | func (p *WebPage) OwnsPages() (bool, error) { 547 | var resp struct { 548 | Value bool `json:"value"` 549 | } 550 | if err := p.ref.process.doJSON("POST", "/webpage/OwnsPages", map[string]interface{}{"ref": p.ref.id}, &resp); err != nil { 551 | return false, err 552 | } 553 | return resp.Value, nil 554 | } 555 | 556 | // SetOwnsPages sets whether this page owns pages opened in other windows. 557 | func (p *WebPage) SetOwnsPages(v bool) error { 558 | return p.ref.process.doJSON("POST", "/webpage/SetOwnsPages", map[string]interface{}{"ref": p.ref.id, "value": v}, nil) 559 | } 560 | 561 | // PageWindowNames returns an list of owned window names. 562 | func (p *WebPage) PageWindowNames() ([]string, error) { 563 | var resp struct { 564 | Value []string `json:"value"` 565 | } 566 | if err := p.ref.process.doJSON("POST", "/webpage/PageWindowNames", map[string]interface{}{"ref": p.ref.id}, &resp); err != nil { 567 | return nil, err 568 | } 569 | return resp.Value, nil 570 | } 571 | 572 | // Pages returns a list of owned pages. 573 | func (p *WebPage) Pages() ([]*WebPage, error) { 574 | var resp struct { 575 | Refs []refJSON `json:"refs"` 576 | } 577 | if err := p.ref.process.doJSON("POST", "/webpage/Pages", map[string]interface{}{"ref": p.ref.id}, &resp); err != nil { 578 | return nil, err 579 | } 580 | 581 | // Convert reference IDs to web pages. 582 | a := make([]*WebPage, len(resp.Refs)) 583 | for i, ref := range resp.Refs { 584 | a[i] = &WebPage{ref: newRef(p.ref.process, ref.ID)} 585 | } 586 | return a, nil 587 | } 588 | 589 | // PaperSize returns the size of the web page when rendered as a PDF. 590 | func (p *WebPage) PaperSize() (PaperSize, error) { 591 | var resp struct { 592 | Value paperSizeJSON `json:"value"` 593 | } 594 | if err := p.ref.process.doJSON("POST", "/webpage/PaperSize", map[string]interface{}{"ref": p.ref.id}, &resp); err != nil { 595 | return PaperSize{}, err 596 | } 597 | return decodePaperSizeJSON(resp.Value), nil 598 | } 599 | 600 | // SetPaperSize sets the size of the web page when rendered as a PDF. 601 | func (p *WebPage) SetPaperSize(size PaperSize) error { 602 | req := map[string]interface{}{"ref": p.ref.id, "size": encodePaperSizeJSON(size)} 603 | return p.ref.process.doJSON("POST", "/webpage/SetPaperSize", req, nil) 604 | } 605 | 606 | // PlainText returns the plain text representation of the page. 607 | func (p *WebPage) PlainText() (string, error) { 608 | var resp struct { 609 | Value string `json:"value"` 610 | } 611 | if err := p.ref.process.doJSON("POST", "/webpage/PlainText", map[string]interface{}{"ref": p.ref.id}, &resp); err != nil { 612 | return "", err 613 | } 614 | return resp.Value, nil 615 | } 616 | 617 | // ScrollPosition returns the current scroll position of the page. 618 | func (p *WebPage) ScrollPosition() (Position, error) { 619 | var resp struct { 620 | Top int `json:"top"` 621 | Left int `json:"left"` 622 | } 623 | if err := p.ref.process.doJSON("POST", "/webpage/ScrollPosition", map[string]interface{}{"ref": p.ref.id}, &resp); err != nil { 624 | return Position{}, err 625 | } 626 | return Position{Top: resp.Top, Left: resp.Left}, nil 627 | } 628 | 629 | // SetScrollPosition sets the current scroll position of the page. 630 | func (p *WebPage) SetScrollPosition(pos Position) error { 631 | return p.ref.process.doJSON("POST", "/webpage/SetScrollPosition", map[string]interface{}{"ref": p.ref.id, "top": pos.Top, "left": pos.Left}, nil) 632 | } 633 | 634 | // Settings returns the settings used on the web page. 635 | func (p *WebPage) Settings() (WebPageSettings, error) { 636 | var resp struct { 637 | Settings webPageSettingsJSON `json:"settings"` 638 | } 639 | if err := p.ref.process.doJSON("POST", "/webpage/Settings", map[string]interface{}{"ref": p.ref.id}, &resp); err != nil { 640 | return WebPageSettings{}, err 641 | } 642 | return WebPageSettings{ 643 | JavascriptEnabled: resp.Settings.JavascriptEnabled, 644 | LoadImages: resp.Settings.LoadImages, 645 | LocalToRemoteURLAccessEnabled: resp.Settings.LocalToRemoteURLAccessEnabled, 646 | UserAgent: resp.Settings.UserAgent, 647 | Username: resp.Settings.Username, 648 | Password: resp.Settings.Password, 649 | XSSAuditingEnabled: resp.Settings.XSSAuditingEnabled, 650 | WebSecurityEnabled: resp.Settings.WebSecurityEnabled, 651 | ResourceTimeout: time.Duration(resp.Settings.ResourceTimeout) * time.Millisecond, 652 | }, nil 653 | } 654 | 655 | // SetSettings sets various settings on the web page. 656 | // 657 | // The settings apply only during the initial call to the page.open function. 658 | // Subsequent modification of the settings object will not have any impact. 659 | func (p *WebPage) SetSettings(settings WebPageSettings) error { 660 | req := map[string]interface{}{ 661 | "ref": p.ref.id, 662 | "settings": webPageSettingsJSON{ 663 | JavascriptEnabled: settings.JavascriptEnabled, 664 | LoadImages: settings.LoadImages, 665 | LocalToRemoteURLAccessEnabled: settings.LocalToRemoteURLAccessEnabled, 666 | UserAgent: settings.UserAgent, 667 | Username: settings.Username, 668 | Password: settings.Password, 669 | XSSAuditingEnabled: settings.XSSAuditingEnabled, 670 | WebSecurityEnabled: settings.WebSecurityEnabled, 671 | ResourceTimeout: int(settings.ResourceTimeout / time.Millisecond), 672 | }, 673 | } 674 | return p.ref.process.doJSON("POST", "/webpage/SetSettings", req, nil) 675 | } 676 | 677 | // Title returns the title of the web page. 678 | func (p *WebPage) Title() (string, error) { 679 | var resp struct { 680 | Value string `json:"value"` 681 | } 682 | if err := p.ref.process.doJSON("POST", "/webpage/Title", map[string]interface{}{"ref": p.ref.id}, &resp); err != nil { 683 | return "", err 684 | } 685 | return resp.Value, nil 686 | } 687 | 688 | // URL returns the current URL of the web page. 689 | func (p *WebPage) URL() (string, error) { 690 | var resp struct { 691 | Value string `json:"value"` 692 | } 693 | if err := p.ref.process.doJSON("POST", "/webpage/URL", map[string]interface{}{"ref": p.ref.id}, &resp); err != nil { 694 | return "", err 695 | } 696 | return resp.Value, nil 697 | } 698 | 699 | // ViewportSize returns the size of the viewport on the browser. 700 | func (p *WebPage) ViewportSize() (width, height int, err error) { 701 | var resp struct { 702 | Width int `json:"width"` 703 | Height int `json:"height"` 704 | } 705 | if err := p.ref.process.doJSON("POST", "/webpage/ViewportSize", map[string]interface{}{"ref": p.ref.id}, &resp); err != nil { 706 | return 0, 0, err 707 | } 708 | return resp.Width, resp.Height, nil 709 | } 710 | 711 | // SetViewportSize sets the size of the viewport. 712 | func (p *WebPage) SetViewportSize(width, height int) error { 713 | return p.ref.process.doJSON("POST", "/webpage/SetViewportSize", map[string]interface{}{"ref": p.ref.id, "width": width, "height": height}, nil) 714 | } 715 | 716 | // WindowName returns the window name of the web page. 717 | func (p *WebPage) WindowName() (string, error) { 718 | var resp struct { 719 | Value string `json:"value"` 720 | } 721 | if err := p.ref.process.doJSON("POST", "/webpage/WindowName", map[string]interface{}{"ref": p.ref.id}, &resp); err != nil { 722 | return "", err 723 | } 724 | return resp.Value, nil 725 | } 726 | 727 | // ZoomFactor returns zoom factor when rendering the page. 728 | func (p *WebPage) ZoomFactor() (float64, error) { 729 | var resp struct { 730 | Value float64 `json:"value"` 731 | } 732 | if err := p.ref.process.doJSON("POST", "/webpage/ZoomFactor", map[string]interface{}{"ref": p.ref.id}, &resp); err != nil { 733 | return 0, err 734 | } 735 | return resp.Value, nil 736 | } 737 | 738 | // SetZoomFactor sets the zoom factor when rendering the page. 739 | func (p *WebPage) SetZoomFactor(factor float64) error { 740 | return p.ref.process.doJSON("POST", "/webpage/SetZoomFactor", map[string]interface{}{"ref": p.ref.id, "value": factor}, nil) 741 | } 742 | 743 | // AddCookie adds a cookie to the page. 744 | // Returns true if the cookie was successfully added. 745 | func (p *WebPage) AddCookie(cookie *http.Cookie) (bool, error) { 746 | var resp struct { 747 | ReturnValue bool `json:"returnValue"` 748 | } 749 | req := map[string]interface{}{"ref": p.ref.id, "cookie": encodeCookieJSON(cookie)} 750 | if err := p.ref.process.doJSON("POST", "/webpage/AddCookie", req, &resp); err != nil { 751 | return false, err 752 | } 753 | return resp.ReturnValue, nil 754 | } 755 | 756 | // ClearCookies deletes all cookies visible to the current URL. 757 | func (p *WebPage) ClearCookies() error { 758 | return p.ref.process.doJSON("POST", "/webpage/ClearCookies", map[string]interface{}{"ref": p.ref.id}, nil) 759 | } 760 | 761 | // Close releases the web page and its resources. 762 | func (p *WebPage) Close() error { 763 | return p.ref.process.doJSON("POST", "/webpage/Close", map[string]interface{}{"ref": p.ref.id}, nil) 764 | } 765 | 766 | // DeleteCookie removes a cookie with a matching name. 767 | // Returns true if the cookie was successfully deleted. 768 | func (p *WebPage) DeleteCookie(name string) (bool, error) { 769 | var resp struct { 770 | ReturnValue bool `json:"returnValue"` 771 | } 772 | req := map[string]interface{}{"ref": p.ref.id, "name": name} 773 | if err := p.ref.process.doJSON("POST", "/webpage/DeleteCookie", req, &resp); err != nil { 774 | return false, err 775 | } 776 | return resp.ReturnValue, nil 777 | } 778 | 779 | // EvaluateAsync executes a JavaScript function and returns immediately. 780 | // Execution is delayed by delay. No value is returned. 781 | func (p *WebPage) EvaluateAsync(script string, delay time.Duration) error { 782 | return p.ref.process.doJSON("POST", "/webpage/EvaluateAsync", map[string]interface{}{"ref": p.ref.id, "script": script, "delay": int(delay / time.Millisecond)}, nil) 783 | } 784 | 785 | // EvaluateJavaScript executes a JavaScript function. 786 | // Returns the value returned by the function. 787 | func (p *WebPage) EvaluateJavaScript(script string) (interface{}, error) { 788 | var resp struct { 789 | ReturnValue interface{} `json:"returnValue"` 790 | } 791 | if err := p.ref.process.doJSON("POST", "/webpage/EvaluateJavaScript", map[string]interface{}{"ref": p.ref.id, "script": script}, &resp); err != nil { 792 | return nil, err 793 | } 794 | return resp.ReturnValue, nil 795 | } 796 | 797 | // Evaluate executes a JavaScript function in the context of the web page. 798 | // Returns the value returned by the function. 799 | func (p *WebPage) Evaluate(script string) (interface{}, error) { 800 | var resp struct { 801 | ReturnValue interface{} `json:"returnValue"` 802 | } 803 | if err := p.ref.process.doJSON("POST", "/webpage/Evaluate", map[string]interface{}{"ref": p.ref.id, "script": script}, &resp); err != nil { 804 | return nil, err 805 | } 806 | return resp.ReturnValue, nil 807 | } 808 | 809 | // Page returns an owned page by window name. 810 | // Returns nil if the page cannot be found. 811 | func (p *WebPage) Page(name string) (*WebPage, error) { 812 | var resp struct { 813 | Ref refJSON `json:"ref"` 814 | } 815 | if err := p.ref.process.doJSON("POST", "/webpage/Page", map[string]interface{}{"ref": p.ref.id, "name": name}, &resp); err != nil { 816 | return nil, err 817 | } 818 | if resp.Ref.ID == "" { 819 | return nil, nil 820 | } 821 | return &WebPage{ref: newRef(p.ref.process, resp.Ref.ID)}, nil 822 | } 823 | 824 | // GoBack navigates back to the previous page. 825 | func (p *WebPage) GoBack() error { 826 | return p.ref.process.doJSON("POST", "/webpage/GoBack", map[string]interface{}{"ref": p.ref.id}, nil) 827 | } 828 | 829 | // GoForward navigates to the next page. 830 | func (p *WebPage) GoForward() error { 831 | return p.ref.process.doJSON("POST", "/webpage/GoForward", map[string]interface{}{"ref": p.ref.id}, nil) 832 | } 833 | 834 | // Go navigates to the page in history by relative offset. 835 | // A positive index moves forward, a negative index moves backwards. 836 | func (p *WebPage) Go(index int) error { 837 | return p.ref.process.doJSON("POST", "/webpage/Go", map[string]interface{}{"ref": p.ref.id, "index": index}, nil) 838 | } 839 | 840 | // IncludeJS includes an external script from url. 841 | // Returns after the script has been loaded. 842 | func (p *WebPage) IncludeJS(url string) error { 843 | return p.ref.process.doJSON("POST", "/webpage/IncludeJS", map[string]interface{}{"ref": p.ref.id, "url": url}, nil) 844 | } 845 | 846 | // InjectJS injects an external script from the local filesystem. 847 | // 848 | // The script will be loaded from the Process.Path() directory. If it cannot be 849 | // found then it is loaded from the library path. 850 | func (p *WebPage) InjectJS(filename string) error { 851 | var resp struct { 852 | ReturnValue bool `json:"returnValue"` 853 | } 854 | if err := p.ref.process.doJSON("POST", "/webpage/InjectJS", map[string]interface{}{"ref": p.ref.id, "filename": filename}, &resp); err != nil { 855 | return err 856 | } 857 | if !resp.ReturnValue { 858 | return ErrInjectionFailed 859 | } 860 | return nil 861 | } 862 | 863 | // Reload reloads the current web page. 864 | func (p *WebPage) Reload() error { 865 | return p.ref.process.doJSON("POST", "/webpage/Reload", map[string]interface{}{"ref": p.ref.id}, nil) 866 | } 867 | 868 | // RenderBase64 renders the web page to a base64 encoded string. 869 | func (p *WebPage) RenderBase64(format string) (string, error) { 870 | var resp struct { 871 | ReturnValue string `json:"returnValue"` 872 | } 873 | if err := p.ref.process.doJSON("POST", "/webpage/RenderBase64", map[string]interface{}{"ref": p.ref.id, "format": format}, &resp); err != nil { 874 | return "", err 875 | } 876 | return resp.ReturnValue, nil 877 | } 878 | 879 | // Render renders the web page to a file with the given format and quality settings. 880 | // This supports the "PDF", "PNG", "JPEG", "BMP", "PPM", and "GIF" formats. 881 | func (p *WebPage) Render(filename, format string, quality int) error { 882 | req := map[string]interface{}{"ref": p.ref.id, "filename": filename, "format": format, "quality": quality} 883 | return p.ref.process.doJSON("POST", "/webpage/Render", req, nil) 884 | } 885 | 886 | // SendMouseEvent sends a mouse event as if it came from the user. 887 | // It is not a synthetic event. 888 | // 889 | // The eventType can be "mouseup", "mousedown", "mousemove", "doubleclick", 890 | // or "click". The mouseX and mouseY specify the position of the mouse on the 891 | // screen. The button argument specifies the mouse button clicked (e.g. "left"). 892 | func (p *WebPage) SendMouseEvent(eventType string, mouseX, mouseY int, button string) error { 893 | return p.ref.process.doJSON("POST", "/webpage/SendMouseEvent", map[string]interface{}{"ref": p.ref.id, "eventType": eventType, "mouseX": mouseX, "mouseY": mouseY, "button": button}, nil) 894 | } 895 | 896 | // SendKeyboardEvent sends a keyboard event as if it came from the user. 897 | // It is not a synthetic event. 898 | // 899 | // The eventType can be "keyup", "keypress", or "keydown". 900 | // 901 | // The key argument is a string or a key listed here: 902 | // https://github.com/ariya/phantomjs/commit/cab2635e66d74b7e665c44400b8b20a8f225153a 903 | // 904 | // Keyboard modifiers can be joined together using the bitwise OR operator. 905 | func (p *WebPage) SendKeyboardEvent(eventType string, key string, modifier int) error { 906 | return p.ref.process.doJSON("POST", "/webpage/SendKeyboardEvent", map[string]interface{}{"ref": p.ref.id, "eventType": eventType, "key": key, "modifier": modifier}, nil) 907 | } 908 | 909 | // SetContentAndURL sets the content and URL of the page. 910 | func (p *WebPage) SetContentAndURL(content, url string) error { 911 | return p.ref.process.doJSON("POST", "/webpage/SetContentAndURL", map[string]interface{}{"ref": p.ref.id, "content": content, "url": url}, nil) 912 | } 913 | 914 | // Stop stops the web page. 915 | func (p *WebPage) Stop() error { 916 | return p.ref.process.doJSON("POST", "/webpage/Stop", map[string]interface{}{"ref": p.ref.id}, nil) 917 | } 918 | 919 | // SwitchToFocusedFrame changes the current frame to the frame that is in focus. 920 | func (p *WebPage) SwitchToFocusedFrame() error { 921 | return p.ref.process.doJSON("POST", "/webpage/SwitchToFocusedFrame", map[string]interface{}{"ref": p.ref.id}, nil) 922 | } 923 | 924 | // SwitchToFrameName changes the current frame to a frame with a given name. 925 | func (p *WebPage) SwitchToFrameName(name string) error { 926 | return p.ref.process.doJSON("POST", "/webpage/SwitchToFrameName", map[string]interface{}{"ref": p.ref.id, "name": name}, nil) 927 | } 928 | 929 | // SwitchToFramePosition changes the current frame to the frame at the given position. 930 | func (p *WebPage) SwitchToFramePosition(pos int) error { 931 | return p.ref.process.doJSON("POST", "/webpage/SwitchToFramePosition", map[string]interface{}{"ref": p.ref.id, "position": pos}, nil) 932 | } 933 | 934 | // SwitchToMainFrame switches the current frame to the main frame. 935 | func (p *WebPage) SwitchToMainFrame() error { 936 | return p.ref.process.doJSON("POST", "/webpage/SwitchToMainFrame", map[string]interface{}{"ref": p.ref.id}, nil) 937 | } 938 | 939 | // SwitchToParentFrame switches the current frame to the parent of the current frame. 940 | func (p *WebPage) SwitchToParentFrame() error { 941 | return p.ref.process.doJSON("POST", "/webpage/SwitchToParentFrame", map[string]interface{}{"ref": p.ref.id}, nil) 942 | } 943 | 944 | // UploadFile uploads a file to a form element specified by selector. 945 | func (p *WebPage) UploadFile(selector, filename string) error { 946 | return p.ref.process.doJSON("POST", "/webpage/UploadFile", map[string]interface{}{"ref": p.ref.id, "selector": selector, "filename": filename}, nil) 947 | } 948 | 949 | // OpenWebPageSettings represents the settings object passed to WebPage.Open(). 950 | type OpenWebPageSettings struct { 951 | Method string `json:"method"` 952 | } 953 | 954 | // Ref represents a reference to an object in phantomjs. 955 | type Ref struct { 956 | process *Process 957 | id string 958 | } 959 | 960 | // newRef returns a new instance of a referenced object within the process. 961 | func newRef(p *Process, id string) *Ref { 962 | return &Ref{process: p, id: id} 963 | } 964 | 965 | // ID returns the reference identifier. 966 | func (r *Ref) ID() string { 967 | return r.id 968 | } 969 | 970 | // refJSON is a struct for encoding refs as JSON. 971 | type refJSON struct { 972 | ID string `json:"id"` 973 | } 974 | 975 | // Rect represents a rectangle used by WebPage.ClipRect(). 976 | type Rect struct { 977 | Top int 978 | Left int 979 | Width int 980 | Height int 981 | } 982 | 983 | // rectJSON is a struct for encoding rects as JSON. 984 | type rectJSON struct { 985 | Top int `json:"top"` 986 | Left int `json:"left"` 987 | Width int `json:"width"` 988 | Height int `json:"height"` 989 | } 990 | 991 | // cookieJSON is a struct for encoding http.Cookie objects as JSON. 992 | type cookieJSON struct { 993 | Domain string `json:"domain"` 994 | Expires string `json:"expires"` 995 | Expiry int `json:"expiry"` 996 | HTTPOnly bool `json:"httponly"` 997 | Name string `json:"name"` 998 | Path string `json:"path"` 999 | Secure bool `json:"secure"` 1000 | Value string `json:"value"` 1001 | } 1002 | 1003 | func encodeCookieJSON(v *http.Cookie) cookieJSON { 1004 | out := cookieJSON{ 1005 | Domain: v.Domain, 1006 | HTTPOnly: v.HttpOnly, 1007 | Name: v.Name, 1008 | Path: v.Path, 1009 | Secure: v.Secure, 1010 | Value: v.Value, 1011 | } 1012 | 1013 | if !v.Expires.IsZero() { 1014 | out.Expires = v.Expires.UTC().Format(http.TimeFormat) 1015 | } 1016 | return out 1017 | } 1018 | 1019 | func decodeCookieJSON(v cookieJSON) *http.Cookie { 1020 | out := &http.Cookie{ 1021 | Domain: v.Domain, 1022 | RawExpires: v.Expires, 1023 | HttpOnly: v.HTTPOnly, 1024 | Name: v.Name, 1025 | Path: v.Path, 1026 | Secure: v.Secure, 1027 | Value: v.Value, 1028 | } 1029 | 1030 | if v.Expires != "" { 1031 | expires, _ := time.Parse(http.TimeFormat, v.Expires) 1032 | out.Expires = expires 1033 | out.RawExpires = v.Expires 1034 | } 1035 | 1036 | return out 1037 | } 1038 | 1039 | // PaperSize represents the size of a webpage when rendered as a PDF. 1040 | // 1041 | // Units can be specified in "mm", "cm", "in", or "px". 1042 | // If no unit is specified then "px" is used. 1043 | type PaperSize struct { 1044 | // Dimensions of the paper. 1045 | // This can also be specified via Format. 1046 | Width string 1047 | Height string 1048 | 1049 | // Supported formats: "A3", "A4", "A5", "Legal", "Letter", "Tabloid". 1050 | Format string 1051 | 1052 | // Margins around the paper. 1053 | Margin *PaperSizeMargin 1054 | 1055 | // Supported orientations: "portrait", "landscape". 1056 | Orientation string 1057 | } 1058 | 1059 | // PaperSizeMargin represents the margins around the paper. 1060 | type PaperSizeMargin struct { 1061 | Top string 1062 | Bottom string 1063 | Left string 1064 | Right string 1065 | } 1066 | 1067 | type paperSizeJSON struct { 1068 | Width string `json:"width,omitempty"` 1069 | Height string `json:"height,omitempty"` 1070 | Format string `json:"format,omitempty"` 1071 | Margin *paperSizeMarginJSON `json:"margin,omitempty"` 1072 | Orientation string `json:"orientation,omitempty"` 1073 | } 1074 | 1075 | type paperSizeMarginJSON struct { 1076 | Top string `json:"top,omitempty"` 1077 | Bottom string `json:"bottom,omitempty"` 1078 | Left string `json:"left,omitempty"` 1079 | Right string `json:"right,omitempty"` 1080 | } 1081 | 1082 | func encodePaperSizeJSON(v PaperSize) paperSizeJSON { 1083 | out := paperSizeJSON{ 1084 | Width: v.Width, 1085 | Height: v.Height, 1086 | Format: v.Format, 1087 | Orientation: v.Orientation, 1088 | } 1089 | if v.Margin != nil { 1090 | out.Margin = &paperSizeMarginJSON{ 1091 | Top: v.Margin.Top, 1092 | Bottom: v.Margin.Bottom, 1093 | Left: v.Margin.Left, 1094 | Right: v.Margin.Right, 1095 | } 1096 | } 1097 | return out 1098 | } 1099 | 1100 | func decodePaperSizeJSON(v paperSizeJSON) PaperSize { 1101 | out := PaperSize{ 1102 | Width: v.Width, 1103 | Height: v.Height, 1104 | Format: v.Format, 1105 | Orientation: v.Orientation, 1106 | } 1107 | if v.Margin != nil { 1108 | out.Margin = &PaperSizeMargin{ 1109 | Top: v.Margin.Top, 1110 | Bottom: v.Margin.Bottom, 1111 | Left: v.Margin.Left, 1112 | Right: v.Margin.Right, 1113 | } 1114 | } 1115 | return out 1116 | } 1117 | 1118 | // Position represents a coordinate on the page, in pixels. 1119 | type Position struct { 1120 | Top int 1121 | Left int 1122 | } 1123 | 1124 | // WebPageSettings represents various settings on a web page. 1125 | type WebPageSettings struct { 1126 | JavascriptEnabled bool 1127 | LoadImages bool 1128 | LocalToRemoteURLAccessEnabled bool 1129 | UserAgent string 1130 | Username string 1131 | Password string 1132 | XSSAuditingEnabled bool 1133 | WebSecurityEnabled bool 1134 | ResourceTimeout time.Duration 1135 | } 1136 | 1137 | type webPageSettingsJSON struct { 1138 | JavascriptEnabled bool `json:"javascriptEnabled"` 1139 | LoadImages bool `json:"loadImages"` 1140 | LocalToRemoteURLAccessEnabled bool `json:"localToRemoteUrlAccessEnabled"` 1141 | UserAgent string `json:"userAgent"` 1142 | Username string `json:"username"` 1143 | Password string `json:"password"` 1144 | XSSAuditingEnabled bool `json:"XSSAuditingEnabled"` 1145 | WebSecurityEnabled bool `json:"webSecurityEnabled"` 1146 | ResourceTimeout int `json:"resourceTimeout"` 1147 | } 1148 | 1149 | // shim is the included javascript used to communicate with PhantomJS. 1150 | const shim = ` 1151 | var system = require("system") 1152 | var webpage = require('webpage'); 1153 | var webserver = require('webserver'); 1154 | 1155 | /* 1156 | * HTTP API 1157 | */ 1158 | 1159 | // Serves RPC API. 1160 | var server = webserver.create(); 1161 | server.listen(system.env["PORT"], function(request, response) { 1162 | try { 1163 | switch (request.url) { 1164 | case '/ping': return handlePing(request, response); 1165 | case '/webpage/CanGoBack': return handleWebpageCanGoBack(request, response); 1166 | case '/webpage/CanGoForward': return handleWebpageCanGoForward(request, response); 1167 | case '/webpage/ClipRect': return handleWebpageClipRect(request, response); 1168 | case '/webpage/SetClipRect': return handleWebpageSetClipRect(request, response); 1169 | case '/webpage/Cookies': return handleWebpageCookies(request, response); 1170 | case '/webpage/SetCookies': return handleWebpageSetCookies(request, response); 1171 | case '/webpage/CustomHeaders': return handleWebpageCustomHeaders(request, response); 1172 | case '/webpage/SetCustomHeaders': return handleWebpageSetCustomHeaders(request, response); 1173 | case '/webpage/Create': return handleWebpageCreate(request, response); 1174 | case '/webpage/Content': return handleWebpageContent(request, response); 1175 | case '/webpage/SetContent': return handleWebpageSetContent(request, response); 1176 | case '/webpage/FocusedFrameName': return handleWebpageFocusedFrameName(request, response); 1177 | case '/webpage/FrameContent': return handleWebpageFrameContent(request, response); 1178 | case '/webpage/SetFrameContent': return handleWebpageSetFrameContent(request, response); 1179 | case '/webpage/FrameName': return handleWebpageFrameName(request, response); 1180 | case '/webpage/FramePlainText': return handleWebpageFramePlainText(request, response); 1181 | case '/webpage/FrameTitle': return handleWebpageFrameTitle(request, response); 1182 | case '/webpage/FrameURL': return handleWebpageFrameURL(request, response); 1183 | case '/webpage/FrameCount': return handleWebpageFrameCount(request, response); 1184 | case '/webpage/FrameNames': return handleWebpageFrameNames(request, response); 1185 | case '/webpage/LibraryPath': return handleWebpageLibraryPath(request, response); 1186 | case '/webpage/SetLibraryPath': return handleWebpageSetLibraryPath(request, response); 1187 | case '/webpage/NavigationLocked': return handleWebpageNavigationLocked(request, response); 1188 | case '/webpage/SetNavigationLocked': return handleWebpageSetNavigationLocked(request, response); 1189 | case '/webpage/OfflineStoragePath': return handleWebpageOfflineStoragePath(request, response); 1190 | case '/webpage/OfflineStorageQuota': return handleWebpageOfflineStorageQuota(request, response); 1191 | case '/webpage/OwnsPages': return handleWebpageOwnsPages(request, response); 1192 | case '/webpage/SetOwnsPages': return handleWebpageSetOwnsPages(request, response); 1193 | case '/webpage/PageWindowNames': return handleWebpagePageWindowNames(request, response); 1194 | case '/webpage/Pages': return handleWebpagePages(request, response); 1195 | case '/webpage/PaperSize': return handleWebpagePaperSize(request, response); 1196 | case '/webpage/SetPaperSize': return handleWebpageSetPaperSize(request, response); 1197 | case '/webpage/PlainText': return handleWebpagePlainText(request, response); 1198 | case '/webpage/ScrollPosition': return handleWebpageScrollPosition(request, response); 1199 | case '/webpage/SetScrollPosition': return handleWebpageSetScrollPosition(request, response); 1200 | case '/webpage/Settings': return handleWebpageSettings(request, response); 1201 | case '/webpage/SetSettings': return handleWebpageSetSettings(request, response); 1202 | case '/webpage/Title': return handleWebpageTitle(request, response); 1203 | case '/webpage/URL': return handleWebpageURL(request, response); 1204 | case '/webpage/ViewportSize': return handleWebpageViewportSize(request, response); 1205 | case '/webpage/SetViewportSize': return handleWebpageSetViewportSize(request, response); 1206 | case '/webpage/WindowName': return handleWebpageWindowName(request, response); 1207 | case '/webpage/ZoomFactor': return handleWebpageZoomFactor(request, response); 1208 | case '/webpage/SetZoomFactor': return handleWebpageSetZoomFactor(request, response); 1209 | 1210 | case '/webpage/AddCookie': return handleWebpageAddCookie(request, response); 1211 | case '/webpage/ClearCookies': return handleWebpageClearCookies(request, response); 1212 | case '/webpage/DeleteCookie': return handleWebpageDeleteCookie(request, response); 1213 | case '/webpage/Open': return handleWebpageOpen(request, response); 1214 | case '/webpage/Close': return handleWebpageClose(request, response); 1215 | case '/webpage/EvaluateAsync': return handleWebpageEvaluateAsync(request, response); 1216 | case '/webpage/EvaluateJavaScript': return handleWebpageEvaluateJavaScript(request, response); 1217 | case '/webpage/Evaluate': return handleWebpageEvaluate(request, response); 1218 | case '/webpage/Page': return handleWebpagePage(request, response); 1219 | case '/webpage/GoBack': return handleWebpageGoBack(request, response); 1220 | case '/webpage/GoForward': return handleWebpageGoForward(request, response); 1221 | case '/webpage/Go': return handleWebpageGo(request, response); 1222 | case '/webpage/IncludeJS': return handleWebpageIncludeJS(request, response); 1223 | case '/webpage/InjectJS': return handleWebpageInjectJS(request, response); 1224 | case '/webpage/Reload': return handleWebpageReload(request, response); 1225 | case '/webpage/RenderBase64': return handleWebpageRenderBase64(request, response); 1226 | case '/webpage/Render': return handleWebpageRender(request, response); 1227 | case '/webpage/SendMouseEvent': return handleWebpageSendMouseEvent(request, response); 1228 | case '/webpage/SendKeyboardEvent': return handleWebpageSendKeyboardEvent(request, response); 1229 | case '/webpage/SetContentAndURL': return handleWebpageSetContentAndURL(request, response); 1230 | case '/webpage/Stop': return handleWebpageStop(request, response); 1231 | case '/webpage/SwitchToFocusedFrame': return handleWebpageSwitchToFocusedFrame(request, response); 1232 | case '/webpage/SwitchToFrameName': return handleWebpageSwitchToFrameName(request, response); 1233 | case '/webpage/SwitchToFramePosition': return handleWebpageSwitchToFramePosition(request, response); 1234 | case '/webpage/SwitchToMainFrame': return handleWebpageSwitchToMainFrame(request, response); 1235 | case '/webpage/SwitchToParentFrame': return handleWebpageSwitchToParentFrame(request, response); 1236 | case '/webpage/UploadFile': return handleWebpageUploadFile(request, response); 1237 | default: return handleNotFound(request, response); 1238 | } 1239 | } catch(e) { 1240 | response.statusCode = 500; 1241 | response.write(JSON.stringify({url: request.url, error: e.message})); 1242 | response.closeGracefully(); 1243 | } 1244 | }); 1245 | 1246 | function handlePing(request, response) { 1247 | response.statusCode = 200; 1248 | response.write('ok'); 1249 | response.closeGracefully(); 1250 | } 1251 | 1252 | function handleWebpageCanGoBack(request, response) { 1253 | var page = ref(JSON.parse(request.post).ref); 1254 | response.write(JSON.stringify({value: page.canGoBack})); 1255 | response.closeGracefully(); 1256 | } 1257 | 1258 | function handleWebpageCanGoForward(request, response) { 1259 | var page = ref(JSON.parse(request.post).ref); 1260 | response.write(JSON.stringify({value: page.canGoForward})); 1261 | response.closeGracefully(); 1262 | } 1263 | 1264 | function handleWebpageClipRect(request, response) { 1265 | var page = ref(JSON.parse(request.post).ref); 1266 | response.write(JSON.stringify({value: page.clipRect})); 1267 | response.closeGracefully(); 1268 | } 1269 | 1270 | function handleWebpageSetClipRect(request, response) { 1271 | var msg = JSON.parse(request.post); 1272 | var page = ref(msg.ref); 1273 | page.clipRect = msg.rect; 1274 | response.write(JSON.stringify({})); 1275 | response.closeGracefully(); 1276 | } 1277 | 1278 | function handleWebpageCookies(request, response) { 1279 | var page = ref(JSON.parse(request.post).ref); 1280 | response.write(JSON.stringify({value: page.cookies})); 1281 | response.closeGracefully(); 1282 | } 1283 | 1284 | function handleWebpageSetCookies(request, response) { 1285 | var msg = JSON.parse(request.post); 1286 | var page = ref(msg.ref); 1287 | page.cookies = msg.cookies; 1288 | response.write(JSON.stringify({})); 1289 | response.closeGracefully(); 1290 | } 1291 | 1292 | function handleWebpageCustomHeaders(request, response) { 1293 | var page = ref(JSON.parse(request.post).ref); 1294 | response.write(JSON.stringify({value: page.customHeaders})); 1295 | response.closeGracefully(); 1296 | } 1297 | 1298 | function handleWebpageSetCustomHeaders(request, response) { 1299 | var msg = JSON.parse(request.post); 1300 | var page = ref(msg.ref); 1301 | page.customHeaders = msg.headers; 1302 | response.write(JSON.stringify({})); 1303 | response.closeGracefully(); 1304 | } 1305 | 1306 | function handleWebpageCreate(request, response) { 1307 | var ref = createRef(webpage.create()); 1308 | response.statusCode = 200; 1309 | response.write(JSON.stringify({ref: ref})); 1310 | response.closeGracefully(); 1311 | } 1312 | 1313 | function handleWebpageOpen(request, response) { 1314 | var msg = JSON.parse(request.post) 1315 | var page = ref(msg.ref) 1316 | page.open(msg.url, function(status) { 1317 | response.write(JSON.stringify({status: status})); 1318 | response.closeGracefully(); 1319 | }) 1320 | } 1321 | 1322 | function handleWebpageContent(request, response) { 1323 | var page = ref(JSON.parse(request.post).ref); 1324 | response.write(JSON.stringify({value: page.content})); 1325 | response.closeGracefully(); 1326 | } 1327 | 1328 | function handleWebpageSetContent(request, response) { 1329 | var msg = JSON.parse(request.post); 1330 | var page = ref(msg.ref); 1331 | page.content = msg.content; 1332 | response.write(JSON.stringify({})); 1333 | response.closeGracefully(); 1334 | } 1335 | 1336 | function handleWebpageFocusedFrameName(request, response) { 1337 | var page = ref(JSON.parse(request.post).ref); 1338 | response.write(JSON.stringify({value: page.focusedFrameName})); 1339 | response.closeGracefully(); 1340 | } 1341 | 1342 | function handleWebpageFrameContent(request, response) { 1343 | var page = ref(JSON.parse(request.post).ref); 1344 | response.write(JSON.stringify({value: page.frameContent})); 1345 | response.closeGracefully(); 1346 | } 1347 | 1348 | function handleWebpageSetFrameContent(request, response) { 1349 | var msg = JSON.parse(request.post); 1350 | var page = ref(msg.ref); 1351 | page.frameContent = msg.content; 1352 | response.write(JSON.stringify({})); 1353 | response.closeGracefully(); 1354 | } 1355 | 1356 | function handleWebpageFrameName(request, response) { 1357 | var page = ref(JSON.parse(request.post).ref); 1358 | response.write(JSON.stringify({value: page.frameName})); 1359 | response.closeGracefully(); 1360 | } 1361 | 1362 | function handleWebpageFramePlainText(request, response) { 1363 | var page = ref(JSON.parse(request.post).ref); 1364 | response.write(JSON.stringify({value: page.framePlainText})); 1365 | response.closeGracefully(); 1366 | } 1367 | 1368 | function handleWebpageFrameTitle(request, response) { 1369 | var page = ref(JSON.parse(request.post).ref); 1370 | response.write(JSON.stringify({value: page.frameTitle})); 1371 | response.closeGracefully(); 1372 | } 1373 | 1374 | function handleWebpageFrameURL(request, response) { 1375 | var page = ref(JSON.parse(request.post).ref); 1376 | response.write(JSON.stringify({value: page.frameUrl})); 1377 | response.closeGracefully(); 1378 | } 1379 | 1380 | function handleWebpageFrameCount(request, response) { 1381 | var page = ref(JSON.parse(request.post).ref); 1382 | response.write(JSON.stringify({value: page.framesCount})); 1383 | response.closeGracefully(); 1384 | } 1385 | 1386 | function handleWebpageFrameNames(request, response) { 1387 | var page = ref(JSON.parse(request.post).ref); 1388 | response.write(JSON.stringify({value: page.framesName})); 1389 | response.closeGracefully(); 1390 | } 1391 | 1392 | function handleWebpageLibraryPath(request, response) { 1393 | var page = ref(JSON.parse(request.post).ref); 1394 | response.write(JSON.stringify({value: page.libraryPath})); 1395 | response.closeGracefully(); 1396 | } 1397 | 1398 | function handleWebpageSetLibraryPath(request, response) { 1399 | var msg = JSON.parse(request.post); 1400 | var page = ref(msg.ref); 1401 | page.libraryPath = msg.path; 1402 | response.write(JSON.stringify({})); 1403 | response.closeGracefully(); 1404 | } 1405 | 1406 | function handleWebpageNavigationLocked(request, response) { 1407 | var page = ref(JSON.parse(request.post).ref); 1408 | response.write(JSON.stringify({value: page.navigationLocked})); 1409 | response.closeGracefully(); 1410 | } 1411 | 1412 | function handleWebpageSetNavigationLocked(request, response) { 1413 | var msg = JSON.parse(request.post); 1414 | var page = ref(msg.ref); 1415 | page.navigationLocked = msg.value; 1416 | response.write(JSON.stringify({})); 1417 | response.closeGracefully(); 1418 | } 1419 | 1420 | function handleWebpageOfflineStoragePath(request, response) { 1421 | var page = ref(JSON.parse(request.post).ref); 1422 | response.write(JSON.stringify({value: page.offlineStoragePath})); 1423 | response.closeGracefully(); 1424 | } 1425 | 1426 | function handleWebpageOfflineStorageQuota(request, response) { 1427 | var page = ref(JSON.parse(request.post).ref); 1428 | response.write(JSON.stringify({value: page.offlineStorageQuota})); 1429 | response.closeGracefully(); 1430 | } 1431 | 1432 | function handleWebpageOwnsPages(request, response) { 1433 | var page = ref(JSON.parse(request.post).ref); 1434 | response.write(JSON.stringify({value: page.ownsPages})); 1435 | response.closeGracefully(); 1436 | } 1437 | 1438 | function handleWebpageSetOwnsPages(request, response) { 1439 | var msg = JSON.parse(request.post); 1440 | var page = ref(msg.ref); 1441 | page.ownsPages = msg.value; 1442 | response.write(JSON.stringify({})); 1443 | response.closeGracefully(); 1444 | } 1445 | 1446 | function handleWebpagePageWindowNames(request, response) { 1447 | var page = ref(JSON.parse(request.post).ref); 1448 | response.write(JSON.stringify({value: page.pagesWindowName})); 1449 | response.closeGracefully(); 1450 | } 1451 | 1452 | function handleWebpagePages(request, response) { 1453 | var page = ref(JSON.parse(request.post).ref); 1454 | var refs = page.pages.map(function(p) { return createRef(p); }) 1455 | response.write(JSON.stringify({refs: refs})); 1456 | response.closeGracefully(); 1457 | } 1458 | 1459 | function handleWebpagePaperSize(request, response) { 1460 | var page = ref(JSON.parse(request.post).ref); 1461 | response.write(JSON.stringify({value: page.paperSize})); 1462 | response.closeGracefully(); 1463 | } 1464 | 1465 | function handleWebpageSetPaperSize(request, response) { 1466 | var msg = JSON.parse(request.post); 1467 | var page = ref(msg.ref); 1468 | page.paperSize = msg.size; 1469 | response.write(JSON.stringify({})); 1470 | response.closeGracefully(); 1471 | } 1472 | 1473 | function handleWebpagePlainText(request, response) { 1474 | var page = ref(JSON.parse(request.post).ref); 1475 | response.write(JSON.stringify({value: page.plainText})); 1476 | response.closeGracefully(); 1477 | } 1478 | 1479 | function handleWebpageScrollPosition(request, response) { 1480 | var page = ref(JSON.parse(request.post).ref); 1481 | var pos = page.scrollPosition; 1482 | response.write(JSON.stringify({top: pos.top, left: pos.left})); 1483 | response.closeGracefully(); 1484 | } 1485 | 1486 | function handleWebpageSetScrollPosition(request, response) { 1487 | var msg = JSON.parse(request.post); 1488 | var page = ref(msg.ref); 1489 | page.scrollPosition = {top: msg.top, left: msg.left}; 1490 | response.write(JSON.stringify({})); 1491 | response.closeGracefully(); 1492 | } 1493 | 1494 | function handleWebpageSettings(request, response) { 1495 | var page = ref(JSON.parse(request.post).ref); 1496 | response.write(JSON.stringify({settings: page.settings})); 1497 | response.closeGracefully(); 1498 | } 1499 | 1500 | function handleWebpageSetSettings(request, response) { 1501 | var msg = JSON.parse(request.post); 1502 | var page = ref(msg.ref); 1503 | page.settings = msg.settings; 1504 | response.write(JSON.stringify({})); 1505 | response.closeGracefully(); 1506 | } 1507 | 1508 | function handleWebpageTitle(request, response) { 1509 | var page = ref(JSON.parse(request.post).ref); 1510 | response.write(JSON.stringify({value: page.title})); 1511 | response.closeGracefully(); 1512 | } 1513 | 1514 | function handleWebpageURL(request, response) { 1515 | var page = ref(JSON.parse(request.post).ref); 1516 | response.write(JSON.stringify({value: page.url})); 1517 | response.closeGracefully(); 1518 | } 1519 | 1520 | function handleWebpageViewportSize(request, response) { 1521 | var page = ref(JSON.parse(request.post).ref); 1522 | var viewport = page.viewportSize; 1523 | response.write(JSON.stringify({width: viewport.width, height: viewport.height})); 1524 | response.closeGracefully(); 1525 | } 1526 | 1527 | function handleWebpageSetViewportSize(request, response) { 1528 | var msg = JSON.parse(request.post); 1529 | var page = ref(msg.ref); 1530 | page.viewportSize = {width: msg.width, height: msg.height}; 1531 | response.write(JSON.stringify({})); 1532 | response.closeGracefully(); 1533 | } 1534 | 1535 | function handleWebpageWindowName(request, response) { 1536 | var page = ref(JSON.parse(request.post).ref); 1537 | response.write(JSON.stringify({value: page.windowName})); 1538 | response.closeGracefully(); 1539 | } 1540 | 1541 | function handleWebpageZoomFactor(request, response) { 1542 | var page = ref(JSON.parse(request.post).ref); 1543 | response.write(JSON.stringify({value: page.zoomFactor})); 1544 | response.closeGracefully(); 1545 | } 1546 | 1547 | function handleWebpageSetZoomFactor(request, response) { 1548 | var msg = JSON.parse(request.post); 1549 | var page = ref(msg.ref); 1550 | page.zoomFactor = msg.value; 1551 | response.write(JSON.stringify({})); 1552 | response.closeGracefully(); 1553 | } 1554 | 1555 | 1556 | function handleWebpageAddCookie(request, response) { 1557 | var msg = JSON.parse(request.post); 1558 | var page = ref(msg.ref); 1559 | var returnValue = page.addCookie(msg.cookie); 1560 | response.write(JSON.stringify({returnValue: returnValue})); 1561 | response.closeGracefully(); 1562 | } 1563 | 1564 | function handleWebpageClearCookies(request, response) { 1565 | var msg = JSON.parse(request.post); 1566 | var page = ref(msg.ref); 1567 | page.clearCookies(); 1568 | response.write(JSON.stringify({})); 1569 | response.closeGracefully(); 1570 | } 1571 | 1572 | function handleWebpageDeleteCookie(request, response) { 1573 | var msg = JSON.parse(request.post); 1574 | var page = ref(msg.ref); 1575 | var returnValue = page.deleteCookie(msg.name); 1576 | response.write(JSON.stringify({returnValue: returnValue})); 1577 | response.closeGracefully(); 1578 | } 1579 | 1580 | function handleWebpageClose(request, response) { 1581 | var msg = JSON.parse(request.post); 1582 | 1583 | // Close page. 1584 | var page = ref(msg.ref); 1585 | page.close(); 1586 | delete(refs, msg.ref); 1587 | 1588 | // Close and dereference owned pages. 1589 | for (var i = 0; i < page.pages.length; i++) { 1590 | page.pages[i].close(); 1591 | deleteRef(page.pages[i]); 1592 | } 1593 | 1594 | response.write(JSON.stringify({})); 1595 | response.closeGracefully(); 1596 | } 1597 | 1598 | function handleWebpageEvaluateAsync(request, response) { 1599 | var msg = JSON.parse(request.post); 1600 | var page = ref(msg.ref); 1601 | page.evaluateAsync(msg.script, msg.delay); 1602 | response.write(JSON.stringify({})); 1603 | response.closeGracefully(); 1604 | } 1605 | 1606 | function handleWebpageEvaluateJavaScript(request, response) { 1607 | var msg = JSON.parse(request.post); 1608 | var page = ref(msg.ref); 1609 | var returnValue = page.evaluateJavaScript(msg.script); 1610 | response.write(JSON.stringify({returnValue: returnValue})); 1611 | response.closeGracefully(); 1612 | } 1613 | 1614 | function handleWebpageEvaluate(request, response) { 1615 | var msg = JSON.parse(request.post); 1616 | var page = ref(msg.ref); 1617 | var returnValue = page.evaluate(msg.script); 1618 | response.write(JSON.stringify({returnValue: returnValue})); 1619 | response.closeGracefully(); 1620 | } 1621 | 1622 | function handleWebpagePage(request, response) { 1623 | var msg = JSON.parse(request.post); 1624 | var page = ref(msg.ref); 1625 | var p = page.getPage(msg.name); 1626 | 1627 | if (p === null) { 1628 | response.write(JSON.stringify({})); 1629 | } else { 1630 | response.write(JSON.stringify({ref: createRef(p)})); 1631 | } 1632 | response.closeGracefully(); 1633 | } 1634 | 1635 | function handleWebpageGoBack(request, response) { 1636 | var msg = JSON.parse(request.post); 1637 | var page = ref(msg.ref); 1638 | page.goBack(); 1639 | response.write(JSON.stringify({})); 1640 | response.closeGracefully(); 1641 | } 1642 | 1643 | function handleWebpageGoForward(request, response) { 1644 | var msg = JSON.parse(request.post); 1645 | var page = ref(msg.ref); 1646 | page.goForward(); 1647 | response.write(JSON.stringify({})); 1648 | response.closeGracefully(); 1649 | } 1650 | 1651 | function handleWebpageGo(request, response) { 1652 | var msg = JSON.parse(request.post); 1653 | var page = ref(msg.ref); 1654 | page.go(msg.index); 1655 | response.write(JSON.stringify({})); 1656 | response.closeGracefully(); 1657 | } 1658 | 1659 | function handleWebpageIncludeJS(request, response) { 1660 | var msg = JSON.parse(request.post); 1661 | var page = ref(msg.ref); 1662 | page.includeJs(msg.url, function() { 1663 | response.write(JSON.stringify({})); 1664 | response.closeGracefully(); 1665 | }); 1666 | } 1667 | 1668 | function handleWebpageInjectJS(request, response) { 1669 | var msg = JSON.parse(request.post); 1670 | var page = ref(msg.ref); 1671 | var returnValue = page.injectJs(msg.filename); 1672 | response.write(JSON.stringify({returnValue: returnValue})); 1673 | response.closeGracefully(); 1674 | } 1675 | 1676 | function handleWebpageReload(request, response) { 1677 | var msg = JSON.parse(request.post); 1678 | var page = ref(msg.ref); 1679 | page.reload(); 1680 | response.write(JSON.stringify({})); 1681 | response.closeGracefully(); 1682 | } 1683 | 1684 | function handleWebpageRenderBase64(request, response) { 1685 | var msg = JSON.parse(request.post); 1686 | var page = ref(msg.ref); 1687 | var returnValue = page.renderBase64(msg.format); 1688 | response.write(JSON.stringify({returnValue: returnValue})); 1689 | response.closeGracefully(); 1690 | } 1691 | 1692 | function handleWebpageRender(request, response) { 1693 | var msg = JSON.parse(request.post); 1694 | var page = ref(msg.ref); 1695 | page.render(msg.filename, {format: msg.format, quality: msg.quality}); 1696 | response.write(JSON.stringify({})); 1697 | response.closeGracefully(); 1698 | } 1699 | 1700 | function handleWebpageSendMouseEvent(request, response) { 1701 | var msg = JSON.parse(request.post); 1702 | var page = ref(msg.ref); 1703 | page.sendEvent(msg.eventType, msg.mouseX, msg.mouseY, msg.button); 1704 | response.write(JSON.stringify({})); 1705 | response.closeGracefully(); 1706 | } 1707 | 1708 | function handleWebpageSendKeyboardEvent(request, response) { 1709 | var msg = JSON.parse(request.post); 1710 | var page = ref(msg.ref); 1711 | page.sendEvent(msg.eventType, msg.key, null, null, msg.modifier); 1712 | response.write(JSON.stringify({})); 1713 | response.closeGracefully(); 1714 | } 1715 | 1716 | function handleWebpageSetContentAndURL(request, response) { 1717 | var msg = JSON.parse(request.post); 1718 | var page = ref(msg.ref); 1719 | page.setContent(msg.content, msg.url); 1720 | response.write(JSON.stringify({})); 1721 | response.closeGracefully(); 1722 | } 1723 | 1724 | function handleWebpageStop(request, response) { 1725 | var msg = JSON.parse(request.post); 1726 | var page = ref(msg.ref); 1727 | page.stop(); 1728 | response.write(JSON.stringify({})); 1729 | response.closeGracefully(); 1730 | } 1731 | 1732 | function handleWebpageSwitchToFocusedFrame(request, response) { 1733 | var msg = JSON.parse(request.post); 1734 | var page = ref(msg.ref); 1735 | page.switchToFocusedFrame(); 1736 | response.write(JSON.stringify({})); 1737 | response.closeGracefully(); 1738 | } 1739 | 1740 | function handleWebpageSwitchToFrameName(request, response) { 1741 | var msg = JSON.parse(request.post); 1742 | var page = ref(msg.ref); 1743 | page.switchToFrame(msg.name); 1744 | response.write(JSON.stringify({})); 1745 | response.closeGracefully(); 1746 | } 1747 | 1748 | function handleWebpageSwitchToFramePosition(request, response) { 1749 | var msg = JSON.parse(request.post); 1750 | var page = ref(msg.ref); 1751 | page.switchToFrame(msg.position); 1752 | response.write(JSON.stringify({})); 1753 | response.closeGracefully(); 1754 | } 1755 | 1756 | function handleWebpageSwitchToMainFrame(request, response) { 1757 | var msg = JSON.parse(request.post); 1758 | var page = ref(msg.ref); 1759 | page.switchToMainFrame(); 1760 | response.write(JSON.stringify({})); 1761 | response.closeGracefully(); 1762 | } 1763 | 1764 | function handleWebpageSwitchToParentFrame(request, response) { 1765 | var msg = JSON.parse(request.post); 1766 | var page = ref(msg.ref); 1767 | page.switchToParentFrame(); 1768 | response.write(JSON.stringify({})); 1769 | response.closeGracefully(); 1770 | } 1771 | 1772 | function handleWebpageUploadFile(request, response) { 1773 | var msg = JSON.parse(request.post); 1774 | var page = ref(msg.ref); 1775 | page.uploadFile(msg.selector, msg.filename); 1776 | response.write(JSON.stringify({})); 1777 | response.closeGracefully(); 1778 | } 1779 | 1780 | 1781 | function handleNotFound(request, response) { 1782 | response.statusCode = 404; 1783 | response.write(JSON.stringify({error:"not found"})); 1784 | response.closeGracefully(); 1785 | } 1786 | 1787 | 1788 | /* 1789 | * REFS 1790 | */ 1791 | 1792 | // Holds references to remote objects. 1793 | var refID = 0; 1794 | var refs = {}; 1795 | 1796 | // Adds an object to the reference map and a ref object. 1797 | function createRef(value) { 1798 | // Return existing reference, if one exists. 1799 | for (var key in refs) { 1800 | if (refs.hasOwnProperty(key)) { 1801 | if (refs[key] === value) { 1802 | return key 1803 | } 1804 | } 1805 | } 1806 | 1807 | // Generate a new id for new references. 1808 | refID++; 1809 | refs[refID.toString()] = value; 1810 | return {id: refID.toString()}; 1811 | } 1812 | 1813 | // Removes a reference to a value, if any. 1814 | function deleteRef(value) { 1815 | for (var key in refs) { 1816 | if (refs.hasOwnProperty(key)) { 1817 | if (refs[key] === value) { 1818 | delete(refs, key); 1819 | } 1820 | } 1821 | } 1822 | } 1823 | 1824 | // Returns a reference object by ID. 1825 | function ref(id) { 1826 | return refs[id]; 1827 | } 1828 | ` 1829 | -------------------------------------------------------------------------------- /phantomjs_test.go: -------------------------------------------------------------------------------- 1 | package phantomjs_test 2 | 3 | import ( 4 | "bytes" 5 | "encoding/base64" 6 | "fmt" 7 | "image/png" 8 | "io/ioutil" 9 | "net/http" 10 | "net/http/httptest" 11 | "path/filepath" 12 | "reflect" 13 | "testing" 14 | "time" 15 | 16 | "github.com/benbjohnson/phantomjs" 17 | ) 18 | 19 | // Ensure web page can return whether it can navigate forward. 20 | func TestWebPage_CanGoForward(t *testing.T) { 21 | p := MustOpenNewProcess() 22 | defer p.MustClose() 23 | 24 | page := p.MustCreateWebPage() 25 | defer MustClosePage(page) 26 | if v, err := page.CanGoForward(); err != nil { 27 | t.Fatal(err) 28 | } else if v { 29 | t.Fatal("expected false") 30 | } 31 | } 32 | 33 | // Ensure process can return if the page can navigate back. 34 | func TestWebPage_CanGoBack(t *testing.T) { 35 | p := MustOpenNewProcess() 36 | defer p.MustClose() 37 | 38 | page := p.MustCreateWebPage() 39 | defer MustClosePage(page) 40 | if v, err := page.CanGoBack(); err != nil { 41 | t.Fatal(err) 42 | } else if v { 43 | t.Fatal("expected false") 44 | } 45 | } 46 | 47 | // Ensure process can set and retrieve the clipping rectangle. 48 | func TestWebPage_ClipRect(t *testing.T) { 49 | p := MustOpenNewProcess() 50 | defer p.MustClose() 51 | 52 | page := p.MustCreateWebPage() 53 | defer MustClosePage(page) 54 | 55 | // Clipping rectangle should be empty initially. 56 | if v, err := page.ClipRect(); err != nil { 57 | t.Fatal(err) 58 | } else if v != (phantomjs.Rect{}) { 59 | t.Fatalf("expected empty rect: %#v", v) 60 | } 61 | 62 | // Set a rectangle. 63 | rect := phantomjs.Rect{Top: 1, Left: 2, Width: 3, Height: 4} 64 | if err := page.SetClipRect(rect); err != nil { 65 | t.Fatal(err) 66 | } 67 | if v, err := page.ClipRect(); err != nil { 68 | t.Fatal(err) 69 | } else if !reflect.DeepEqual(v, rect) { 70 | t.Fatalf("unexpected value: %#v", v) 71 | } 72 | } 73 | 74 | // Ensure process can set and retrieve cookies. 75 | func TestWebPage_Cookies(t *testing.T) { 76 | p := MustOpenNewProcess() 77 | defer p.MustClose() 78 | 79 | page := p.MustCreateWebPage() 80 | defer MustClosePage(page) 81 | 82 | // Test data. 83 | cookies := []*http.Cookie{ 84 | { 85 | Domain: ".example1.com", 86 | HttpOnly: true, 87 | Name: "NAME1", 88 | Path: "/", 89 | Secure: true, 90 | Value: "VALUE1", 91 | }, 92 | { 93 | Domain: ".example2.com", 94 | Expires: time.Date(2020, time.January, 2, 3, 4, 5, 0, time.UTC), 95 | HttpOnly: false, 96 | Name: "NAME2", 97 | Path: "/path", 98 | Secure: false, 99 | Value: "VALUE2", 100 | }, 101 | } 102 | 103 | // Set the cookies. 104 | if err := page.SetCookies(cookies); err != nil { 105 | t.Fatal(err) 106 | } 107 | 108 | // Cookie with expiration should have string version set on return. 109 | cookies[1].RawExpires = "Thu, 02 Jan 2020 03:04:05 GMT" 110 | 111 | // Retrieve and verify the cookies. 112 | if other, err := page.Cookies(); err != nil { 113 | t.Fatal(err) 114 | } else if len(other) != 2 { 115 | t.Fatalf("unexpected cookie count: %d", len(other)) 116 | } else if !reflect.DeepEqual(other[0], cookies[0]) { 117 | t.Fatalf("unexpected cookie(0): %#v", other[0]) 118 | } else if !reflect.DeepEqual(other[1], cookies[1]) { 119 | t.Fatalf("unexpected cookie(1): %#v\n%#v", other[1], cookies[1]) 120 | } 121 | } 122 | 123 | // Ensure process can set and retrieve custom headers. 124 | func TestWebPage_CustomHeaders(t *testing.T) { 125 | p := MustOpenNewProcess() 126 | defer p.MustClose() 127 | 128 | page := p.MustCreateWebPage() 129 | defer MustClosePage(page) 130 | 131 | // Test data. 132 | hdr := make(http.Header) 133 | hdr.Set("FOO", "BAR") 134 | hdr.Set("BAZ", "BAT") 135 | 136 | // Set the headers. 137 | if err := page.SetCustomHeaders(hdr); err != nil { 138 | t.Fatal(err) 139 | } 140 | 141 | // Retrieve and verify the headers. 142 | if other, err := page.CustomHeaders(); err != nil { 143 | t.Fatal(err) 144 | } else if !reflect.DeepEqual(other, hdr) { 145 | t.Fatalf("unexpected value: %#v", other) 146 | } 147 | } 148 | 149 | // Ensure web page can return the name of the currently focused frame. 150 | func TestWebPage_FocusedFrameName(t *testing.T) { 151 | // Mock external HTTP server. 152 | srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 153 | switch r.URL.Path { 154 | case "/": 155 | w.Write([]byte(``)) 156 | case "/frame1.html": 157 | w.Write([]byte(`FRAME 1`)) 158 | case "/frame2.html": 159 | w.Write([]byte(``)) 160 | default: 161 | http.NotFound(w, r) 162 | } 163 | })) 164 | defer srv.Close() 165 | 166 | // Start process. 167 | p := MustOpenNewProcess() 168 | defer p.MustClose() 169 | 170 | // Create & open page. 171 | page := p.MustCreateWebPage() 172 | defer MustClosePage(page) 173 | if err := page.Open(srv.URL); err != nil { 174 | t.Fatal(err) 175 | } 176 | 177 | // Retrieve the focused frame. 178 | if other, err := page.FocusedFrameName(); err != nil { 179 | t.Fatal(err) 180 | } else if other != "FRAME2" { 181 | t.Fatalf("unexpected value: %#v", other) 182 | } 183 | } 184 | 185 | // Ensure web page can set and retrieve frame content. 186 | func TestWebPage_FrameContent(t *testing.T) { 187 | // Mock external HTTP server. 188 | srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 189 | switch r.URL.Path { 190 | case "/": 191 | w.Write([]byte(``)) 192 | case "/frame1.html": 193 | w.Write([]byte(`FOO`)) 194 | case "/frame2.html": 195 | w.Write([]byte(`BAR`)) 196 | default: 197 | http.NotFound(w, r) 198 | } 199 | })) 200 | defer srv.Close() 201 | 202 | // Start process. 203 | p := MustOpenNewProcess() 204 | defer p.MustClose() 205 | 206 | // Create & open page. 207 | page := p.MustCreateWebPage() 208 | defer MustClosePage(page) 209 | if err := page.Open(srv.URL); err != nil { 210 | t.Fatal(err) 211 | } 212 | 213 | // Switch to frame and update content. 214 | if err := page.SwitchToFrameName("FRAME2"); err != nil { 215 | t.Fatal(err) 216 | } 217 | if err := page.SetFrameContent(`NEW CONTENT`); err != nil { 218 | t.Fatal(err) 219 | } 220 | 221 | if other, err := page.FrameContent(); err != nil { 222 | t.Fatal(err) 223 | } else if other != `NEW CONTENT` { 224 | t.Fatalf("unexpected value: %#v", other) 225 | } 226 | } 227 | 228 | // Ensure web page can retrieve the current frame name. 229 | func TestWebPage_FrameName(t *testing.T) { 230 | // Mock external HTTP server. 231 | srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 232 | switch r.URL.Path { 233 | case "/": 234 | w.Write([]byte(``)) 235 | case "/frame1.html": 236 | w.Write([]byte(`FOO`)) 237 | case "/frame2.html": 238 | w.Write([]byte(`BAR`)) 239 | default: 240 | http.NotFound(w, r) 241 | } 242 | })) 243 | defer srv.Close() 244 | 245 | // Start process. 246 | p := MustOpenNewProcess() 247 | defer p.MustClose() 248 | 249 | // Create & open page. 250 | page := p.MustCreateWebPage() 251 | defer MustClosePage(page) 252 | if err := page.Open(srv.URL); err != nil { 253 | t.Fatal(err) 254 | } 255 | 256 | // Switch to frame and retrieve name. 257 | if err := page.SwitchToFrameName("FRAME2"); err != nil { 258 | t.Fatal(err) 259 | } 260 | if other, err := page.FrameName(); err != nil { 261 | t.Fatal(err) 262 | } else if other != `FRAME2` { 263 | t.Fatalf("unexpected value: %#v", other) 264 | } 265 | } 266 | 267 | // Ensure web page can retrieve frame content as plain text. 268 | func TestWebPage_FramePlainText(t *testing.T) { 269 | // Mock external HTTP server. 270 | srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 271 | switch r.URL.Path { 272 | case "/": 273 | w.Write([]byte(``)) 274 | case "/frame1.html": 275 | w.Write([]byte(`FOO`)) 276 | case "/frame2.html": 277 | w.Write([]byte(`BAR`)) 278 | default: 279 | http.NotFound(w, r) 280 | } 281 | })) 282 | defer srv.Close() 283 | 284 | // Start process. 285 | p := MustOpenNewProcess() 286 | defer p.MustClose() 287 | 288 | // Create & open page. 289 | page := p.MustCreateWebPage() 290 | defer MustClosePage(page) 291 | if err := page.Open(srv.URL); err != nil { 292 | t.Fatal(err) 293 | } 294 | 295 | // Switch to frame and update content. 296 | if err := page.SwitchToFrameName("FRAME2"); err != nil { 297 | t.Fatal(err) 298 | } 299 | if other, err := page.FramePlainText(); err != nil { 300 | t.Fatal(err) 301 | } else if other != `BAR` { 302 | t.Fatalf("unexpected value: %#v", other) 303 | } 304 | } 305 | 306 | // Ensure web page can retrieve the frame title. 307 | func TestWebPage_FrameTitle(t *testing.T) { 308 | // Mock external HTTP server. 309 | srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 310 | switch r.URL.Path { 311 | case "/": 312 | w.Write([]byte(``)) 313 | case "/frame1.html": 314 | w.Write([]byte(`FOO`)) 315 | case "/frame2.html": 316 | w.Write([]byte(`TEST TITLEBAR`)) 317 | default: 318 | http.NotFound(w, r) 319 | } 320 | })) 321 | defer srv.Close() 322 | 323 | // Start process. 324 | p := MustOpenNewProcess() 325 | defer p.MustClose() 326 | 327 | // Create & open page. 328 | page := p.MustCreateWebPage() 329 | defer MustClosePage(page) 330 | if err := page.Open(srv.URL); err != nil { 331 | t.Fatal(err) 332 | } 333 | 334 | // Switch to frame and verify title. 335 | if err := page.SwitchToFrameName("FRAME2"); err != nil { 336 | t.Fatal(err) 337 | } 338 | if other, err := page.FrameTitle(); err != nil { 339 | t.Fatal(err) 340 | } else if other != `TEST TITLE` { 341 | t.Fatalf("unexpected value: %#v", other) 342 | } 343 | } 344 | 345 | // Ensure web page can retrieve the frame URL. 346 | func TestWebPage_FrameURL(t *testing.T) { 347 | // Mock external HTTP server. 348 | srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 349 | switch r.URL.Path { 350 | case "/": 351 | w.Write([]byte(``)) 352 | case "/frame1.html": 353 | w.Write([]byte(`FOO`)) 354 | case "/frame2.html": 355 | w.Write([]byte(`BAR`)) 356 | default: 357 | http.NotFound(w, r) 358 | } 359 | })) 360 | defer srv.Close() 361 | 362 | // Start process. 363 | p := MustOpenNewProcess() 364 | defer p.MustClose() 365 | 366 | // Create & open page. 367 | page := p.MustCreateWebPage() 368 | defer MustClosePage(page) 369 | if err := page.Open(srv.URL); err != nil { 370 | t.Fatal(err) 371 | } 372 | 373 | // Switch to frame and verify title. 374 | if err := page.SwitchToFramePosition(1); err != nil { 375 | t.Fatal(err) 376 | } 377 | if other, err := page.FrameURL(); err != nil { 378 | t.Fatal(err) 379 | } else if other != srv.URL+`/frame2.html` { 380 | t.Fatalf("unexpected value: %#v", other) 381 | } 382 | } 383 | 384 | // Ensure web page can retrieve the total frame count. 385 | func TestWebPage_FrameCount(t *testing.T) { 386 | // Mock external HTTP server. 387 | srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 388 | switch r.URL.Path { 389 | case "/": 390 | w.Write([]byte(``)) 391 | case "/frame1.html": 392 | w.Write([]byte(`FOO`)) 393 | case "/frame2.html": 394 | w.Write([]byte(`BAR`)) 395 | default: 396 | http.NotFound(w, r) 397 | } 398 | })) 399 | defer srv.Close() 400 | 401 | // Start process. 402 | p := MustOpenNewProcess() 403 | defer p.MustClose() 404 | 405 | // Create & open page. 406 | page := p.MustCreateWebPage() 407 | defer MustClosePage(page) 408 | if err := page.Open(srv.URL); err != nil { 409 | t.Fatal(err) 410 | } 411 | 412 | // Verify frame count. 413 | if n, err := page.FrameCount(); err != nil { 414 | t.Fatal(err) 415 | } else if n != 2 { 416 | t.Fatalf("unexpected value: %#v", n) 417 | } 418 | } 419 | 420 | // Ensure web page can retrieve a list of frame names. 421 | func TestWebPage_FrameNames(t *testing.T) { 422 | // Mock external HTTP server. 423 | srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 424 | switch r.URL.Path { 425 | case "/": 426 | w.Write([]byte(``)) 427 | case "/frame1.html": 428 | w.Write([]byte(`FOO`)) 429 | case "/frame2.html": 430 | w.Write([]byte(`BAR`)) 431 | default: 432 | http.NotFound(w, r) 433 | } 434 | })) 435 | defer srv.Close() 436 | 437 | // Start process. 438 | p := MustOpenNewProcess() 439 | defer p.MustClose() 440 | 441 | // Create & open page. 442 | page := p.MustCreateWebPage() 443 | defer MustClosePage(page) 444 | if err := page.Open(srv.URL); err != nil { 445 | t.Fatal(err) 446 | } 447 | 448 | // Verify frame count. 449 | if other, err := page.FrameNames(); err != nil { 450 | t.Fatal(err) 451 | } else if !reflect.DeepEqual(other, []string{"FRAME1", "FRAME2"}) { 452 | t.Fatalf("unexpected value: %#v", other) 453 | } 454 | } 455 | 456 | // Ensure process can set and retrieve the library path. 457 | func TestWebPage_LibraryPath(t *testing.T) { 458 | p := MustOpenNewProcess() 459 | defer p.MustClose() 460 | 461 | page := p.MustCreateWebPage() 462 | defer MustClosePage(page) 463 | 464 | // Verify initial path is equal to process path. 465 | if v, err := page.LibraryPath(); err != nil { 466 | t.Fatal(err) 467 | } else if v != p.Path() { 468 | t.Fatalf("unexpected path: %s", v) 469 | } 470 | 471 | // Set the library path & verify it changed. 472 | if err := page.SetLibraryPath("/tmp"); err != nil { 473 | t.Fatal(err) 474 | } 475 | if v, err := page.LibraryPath(); err != nil { 476 | t.Fatal(err) 477 | } else if v != `/tmp` { 478 | t.Fatalf("unexpected path: %s", v) 479 | } 480 | } 481 | 482 | // Ensure process can set and retrieve whether the navigation is locked. 483 | func TestWebPage_NavigationLocked(t *testing.T) { 484 | p := MustOpenNewProcess() 485 | defer p.MustClose() 486 | 487 | page := p.MustCreateWebPage() 488 | defer MustClosePage(page) 489 | 490 | // Set the navigation lock & verify it changed. 491 | if err := page.SetNavigationLocked(true); err != nil { 492 | t.Fatal(err) 493 | } 494 | if v, err := page.NavigationLocked(); err != nil { 495 | t.Fatal(err) 496 | } else if !v { 497 | t.Fatal("expected navigation locked") 498 | } 499 | } 500 | 501 | // Ensure process can retrieve the offline storage path. 502 | func TestWebPage_OfflineStoragePath(t *testing.T) { 503 | p := MustOpenNewProcess() 504 | defer p.MustClose() 505 | 506 | page := p.MustCreateWebPage() 507 | defer MustClosePage(page) 508 | 509 | // Retrieve storage path and ensure it's not blank. 510 | if v, err := page.OfflineStoragePath(); err != nil { 511 | t.Fatal(err) 512 | } else if v == `` { 513 | t.Fatal("expected path") 514 | } 515 | } 516 | 517 | // Ensure process can set and retrieve the offline storage quota. 518 | func TestWebPage_OfflineStorageQuota(t *testing.T) { 519 | p := MustOpenNewProcess() 520 | defer p.MustClose() 521 | 522 | page := p.MustCreateWebPage() 523 | defer MustClosePage(page) 524 | 525 | // Retrieve storage quota and ensure it's non-zero. 526 | if v, err := page.OfflineStorageQuota(); err != nil { 527 | t.Fatal(err) 528 | } else if v == 0 { 529 | t.Fatal("expected quota") 530 | } 531 | } 532 | 533 | // Ensure process can set and retrieve whether the page owns other opened pages. 534 | func TestWebPage_OwnsPages(t *testing.T) { 535 | p := MustOpenNewProcess() 536 | defer p.MustClose() 537 | 538 | page := p.MustCreateWebPage() 539 | defer MustClosePage(page) 540 | 541 | // Set value & verify it changed. 542 | if err := page.SetOwnsPages(true); err != nil { 543 | t.Fatal(err) 544 | } 545 | if v, err := page.OwnsPages(); err != nil { 546 | t.Fatal(err) 547 | } else if !v { 548 | t.Fatal("expected true") 549 | } 550 | } 551 | 552 | // Ensure process can retrieve a list of window names opened by the page. 553 | func TestWebPage_PageWindowNames(t *testing.T) { 554 | p := MustOpenNewProcess() 555 | defer p.MustClose() 556 | 557 | page := p.MustCreateWebPage() 558 | defer MustClosePage(page) 559 | 560 | // Set content to open windows. 561 | if err := page.SetOwnsPages(true); err != nil { 562 | t.Fatal(err) 563 | } 564 | if err := page.SetContent(`CLICK ME`); err != nil { 565 | t.Fatal(err) 566 | } 567 | 568 | // Click the link. 569 | if _, err := page.EvaluateJavaScript(`function() { document.body.querySelector("#link").click() }`); err != nil { 570 | t.Fatal(err) 571 | } 572 | 573 | // Retrieve a list of window names. 574 | if names, err := page.PageWindowNames(); err != nil { 575 | t.Fatal(err) 576 | } else if !reflect.DeepEqual(names, []string{"win1"}) { 577 | t.Fatalf("unexpected names: %+v", names) 578 | } 579 | } 580 | 581 | // Ensure process can retrieve a list of owned web pages. 582 | func TestWebPage_Pages(t *testing.T) { 583 | // Mock external HTTP server. 584 | srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 585 | switch r.URL.Path { 586 | case "/": 587 | w.Write([]byte(`CLICK ME`)) 588 | case "/win1.html": 589 | w.Write([]byte(`FOO`)) 590 | default: 591 | http.NotFound(w, r) 592 | } 593 | })) 594 | defer srv.Close() 595 | 596 | p := MustOpenNewProcess() 597 | defer p.MustClose() 598 | 599 | page := p.MustCreateWebPage() 600 | defer MustClosePage(page) 601 | 602 | // Open root page. 603 | if err := page.SetOwnsPages(true); err != nil { 604 | t.Fatal(err) 605 | } 606 | if err := page.Open(srv.URL); err != nil { 607 | t.Fatal(err) 608 | } 609 | 610 | // Click the link. 611 | if _, err := page.EvaluateJavaScript(`function() { document.body.querySelector("#link").click() }`); err != nil { 612 | t.Fatal(err) 613 | } 614 | 615 | // Retrieve a list of window names. 616 | if pages, err := page.Pages(); err != nil { 617 | t.Fatal(err) 618 | } else if len(pages) != 1 { 619 | t.Fatalf("unexpected count: %d", len(pages)) 620 | } else if u, err := pages[0].URL(); err != nil { 621 | t.Fatal(err) 622 | } else if u != srv.URL+`/win1.html` { 623 | t.Fatalf("unexpected url: %s", u) 624 | } else if name, err := pages[0].WindowName(); err != nil { 625 | t.Fatal(err) 626 | } else if name != `win1` { 627 | t.Fatalf("unexpected window name: %s", name) 628 | } 629 | } 630 | 631 | // Ensure process can set and retrieve the sizing options used for printing. 632 | func TestWebPage_PaperSize(t *testing.T) { 633 | p := MustOpenNewProcess() 634 | defer p.MustClose() 635 | 636 | // Ensure initial size is the zero value. 637 | t.Run("Initial", func(t *testing.T) { 638 | page := p.MustCreateWebPage() 639 | defer MustClosePage(page) 640 | 641 | if sz, err := page.PaperSize(); err != nil { 642 | t.Fatal(err) 643 | } else if !reflect.DeepEqual(sz, phantomjs.PaperSize{}) { 644 | t.Fatalf("unexpected size: %#v", sz) 645 | } 646 | }) 647 | 648 | // Ensure width/height can be set. 649 | t.Run("WidthHeight", func(t *testing.T) { 650 | page := p.MustCreateWebPage() 651 | defer MustClosePage(page) 652 | 653 | sz := phantomjs.PaperSize{Width: "5in", Height: "10in"} 654 | if err := page.SetPaperSize(sz); err != nil { 655 | t.Fatal(err) 656 | } 657 | if other, err := page.PaperSize(); err != nil { 658 | t.Fatal(err) 659 | } else if !reflect.DeepEqual(other, sz) { 660 | t.Fatalf("unexpected size: %#v", other) 661 | } 662 | }) 663 | 664 | // Ensure format can be set. 665 | t.Run("Format", func(t *testing.T) { 666 | page := p.MustCreateWebPage() 667 | defer MustClosePage(page) 668 | 669 | sz := phantomjs.PaperSize{Format: "A4"} 670 | if err := page.SetPaperSize(sz); err != nil { 671 | t.Fatal(err) 672 | } 673 | if other, err := page.PaperSize(); err != nil { 674 | t.Fatal(err) 675 | } else if !reflect.DeepEqual(other, sz) { 676 | t.Fatalf("unexpected size: %#v", other) 677 | } 678 | }) 679 | 680 | // Ensure orientation can be set. 681 | t.Run("Orientation", func(t *testing.T) { 682 | page := p.MustCreateWebPage() 683 | defer MustClosePage(page) 684 | 685 | sz := phantomjs.PaperSize{Orientation: "landscape"} 686 | if err := page.SetPaperSize(sz); err != nil { 687 | t.Fatal(err) 688 | } 689 | if other, err := page.PaperSize(); err != nil { 690 | t.Fatal(err) 691 | } else if !reflect.DeepEqual(other, sz) { 692 | t.Fatalf("unexpected size: %#v", other) 693 | } 694 | }) 695 | 696 | // Ensure margins can be set. 697 | t.Run("Margin", func(t *testing.T) { 698 | page := p.MustCreateWebPage() 699 | defer MustClosePage(page) 700 | 701 | sz := phantomjs.PaperSize{ 702 | Margin: &phantomjs.PaperSizeMargin{ 703 | Top: "1in", 704 | Bottom: "2in", 705 | Left: "3in", 706 | Right: "4in", 707 | }, 708 | } 709 | if err := page.SetPaperSize(sz); err != nil { 710 | t.Fatal(err) 711 | } 712 | if other, err := page.PaperSize(); err != nil { 713 | t.Fatal(err) 714 | } else if !reflect.DeepEqual(other, sz) { 715 | t.Fatalf("unexpected size: %#v", other) 716 | } 717 | }) 718 | } 719 | 720 | // Ensure process can retrieve the plain text representation of a page. 721 | func TestWebPage_PlainText(t *testing.T) { 722 | p := MustOpenNewProcess() 723 | defer p.MustClose() 724 | 725 | page := p.MustCreateWebPage() 726 | defer MustClosePage(page) 727 | 728 | // Set content & verify plain text. 729 | if err := page.SetContent(`FOO`); err != nil { 730 | t.Fatal(err) 731 | } 732 | if v, err := page.PlainText(); err != nil { 733 | t.Fatal(err) 734 | } else if v != `FOO` { 735 | t.Fatalf("unexpected plain text: %s", v) 736 | } 737 | } 738 | 739 | // Ensure process can set and retrieve the scroll position of the page. 740 | func TestWebPage_ScrollPosition(t *testing.T) { 741 | p := MustOpenNewProcess() 742 | defer p.MustClose() 743 | 744 | page := p.MustCreateWebPage() 745 | defer MustClosePage(page) 746 | 747 | // Set and verify position. 748 | pos := phantomjs.Position{Top: 10, Left: 20} 749 | if err := page.SetScrollPosition(pos); err != nil { 750 | t.Fatal(err) 751 | } 752 | if other, err := page.ScrollPosition(); err != nil { 753 | t.Fatal(err) 754 | } else if !reflect.DeepEqual(other, pos) { 755 | t.Fatalf("unexpected position: %#v", pos) 756 | } 757 | } 758 | 759 | // Ensure process can set and retrieve page settings. 760 | func TestWebPage_Settings(t *testing.T) { 761 | p := MustOpenNewProcess() 762 | defer p.MustClose() 763 | 764 | page := p.MustCreateWebPage() 765 | defer MustClosePage(page) 766 | 767 | // Set and verify settings. 768 | settings := phantomjs.WebPageSettings{ 769 | JavascriptEnabled: true, 770 | LoadImages: true, 771 | LocalToRemoteURLAccessEnabled: true, 772 | UserAgent: "Mozilla/5.0", 773 | Username: "susy", 774 | Password: "pass", 775 | XSSAuditingEnabled: true, 776 | WebSecurityEnabled: true, 777 | ResourceTimeout: 10 * time.Second, 778 | } 779 | if err := page.SetSettings(settings); err != nil { 780 | t.Fatal(err) 781 | } 782 | if other, err := page.Settings(); err != nil { 783 | t.Fatal(err) 784 | } else if !reflect.DeepEqual(other, settings) { 785 | t.Fatalf("unexpected settings: %#v", other) 786 | } 787 | } 788 | 789 | // Ensure process can retrieve the title of a page. 790 | func TestWebPage_Title(t *testing.T) { 791 | p := MustOpenNewProcess() 792 | defer p.MustClose() 793 | 794 | page := p.MustCreateWebPage() 795 | defer MustClosePage(page) 796 | 797 | // Set & verify title. 798 | if err := page.SetContent(`FOOBAR`); err != nil { 799 | t.Fatal(err) 800 | } 801 | if v, err := page.Title(); err != nil { 802 | t.Fatal(err) 803 | } else if v != `FOO` { 804 | t.Fatalf("unexpected plain text: %s", v) 805 | } 806 | } 807 | 808 | // Ensure process can set and retrieve the viewport size. 809 | func TestWebPage_ViewportSize(t *testing.T) { 810 | p := MustOpenNewProcess() 811 | defer p.MustClose() 812 | 813 | page := p.MustCreateWebPage() 814 | defer MustClosePage(page) 815 | 816 | // Set and verify size. 817 | if err := page.SetViewportSize(100, 200); err != nil { 818 | t.Fatal(err) 819 | } 820 | if w, h, err := page.ViewportSize(); err != nil { 821 | t.Fatal(err) 822 | } else if w != 100 || h != 200 { 823 | t.Fatalf("unexpected size: w=%d, h=%d", w, h) 824 | } 825 | } 826 | 827 | // Ensure process can set and retrieve the zoom factor on the page. 828 | func TestWebPage_ZoomFactor(t *testing.T) { 829 | p := MustOpenNewProcess() 830 | defer p.MustClose() 831 | 832 | page := p.MustCreateWebPage() 833 | defer MustClosePage(page) 834 | 835 | // Set factor & verify it changed. 836 | if err := page.SetZoomFactor(2.5); err != nil { 837 | t.Fatal(err) 838 | } 839 | if v, err := page.ZoomFactor(); err != nil { 840 | t.Fatal(err) 841 | } else if v != 2.5 { 842 | t.Fatalf("unexpected zoom factor: %f", v) 843 | } 844 | } 845 | 846 | // Ensure process can add a cookie to the page. 847 | func TestWebPage_AddCookie(t *testing.T) { 848 | p := MustOpenNewProcess() 849 | defer p.MustClose() 850 | 851 | page := p.MustCreateWebPage() 852 | defer MustClosePage(page) 853 | 854 | // Test data. 855 | cookie := &http.Cookie{ 856 | Domain: ".example1.com", 857 | HttpOnly: true, 858 | Name: "NAME1", 859 | Path: "/", 860 | Secure: true, 861 | Value: "VALUE1", 862 | } 863 | 864 | // Add the cookie. 865 | if v, err := page.AddCookie(cookie); err != nil { 866 | t.Fatal(err) 867 | } else if !v { 868 | t.Fatal("could not add cookie") 869 | } 870 | 871 | // Retrieve and verify the cookies. 872 | if other, err := page.Cookies(); err != nil { 873 | t.Fatal(err) 874 | } else if len(other) != 1 { 875 | t.Fatalf("unexpected cookie count: %d", len(other)) 876 | } else if !reflect.DeepEqual(other[0], cookie) { 877 | t.Fatalf("unexpected cookie(0): %#v", other) 878 | } 879 | } 880 | 881 | // Ensure process can clear all cookies on the page. 882 | func TestWebPage_ClearCookies(t *testing.T) { 883 | p := MustOpenNewProcess() 884 | defer p.MustClose() 885 | 886 | page := p.MustCreateWebPage() 887 | defer MustClosePage(page) 888 | 889 | // Add a cookie. 890 | if v, err := page.AddCookie(&http.Cookie{Domain: ".example1.com", Name: "NAME1", Path: "/", Value: "VALUE1"}); err != nil { 891 | t.Fatal(err) 892 | } else if !v { 893 | t.Fatal("could not add cookie") 894 | } else if cookies, err := page.Cookies(); err != nil { 895 | t.Fatal(err) 896 | } else if len(cookies) != 1 { 897 | t.Fatalf("unexpected cookie count: %d", len(cookies)) 898 | } 899 | 900 | // Clear cookies and verify they are gone. 901 | if err := page.ClearCookies(); err != nil { 902 | t.Fatal(err) 903 | } 904 | if cookies, err := page.Cookies(); err != nil { 905 | t.Fatal(err) 906 | } else if len(cookies) != 0 { 907 | t.Fatalf("unexpected cookie count: %d", len(cookies)) 908 | } 909 | } 910 | 911 | // Ensure process can delete a single cookie on the page. 912 | func TestWebPage_DeleteCookie(t *testing.T) { 913 | p := MustOpenNewProcess() 914 | defer p.MustClose() 915 | 916 | page := p.MustCreateWebPage() 917 | defer MustClosePage(page) 918 | 919 | // Add a cookies. 920 | if v, err := page.AddCookie(&http.Cookie{Domain: ".example1.com", Name: "NAME1", Path: "/", Value: "VALUE1"}); err != nil { 921 | t.Fatal(err) 922 | } else if !v { 923 | t.Fatal("could not add cookie") 924 | } 925 | if v, err := page.AddCookie(&http.Cookie{Domain: ".example1.com", Name: "NAME2", Path: "/", Value: "VALUE2"}); err != nil { 926 | t.Fatal(err) 927 | } else if !v { 928 | t.Fatal("could not add cookie") 929 | } 930 | if cookies, err := page.Cookies(); err != nil { 931 | t.Fatal(err) 932 | } else if len(cookies) != 2 { 933 | t.Fatalf("unexpected cookie count: %d", len(cookies)) 934 | } 935 | 936 | // Delete first cookie. 937 | if v, err := page.DeleteCookie("NAME1"); err != nil { 938 | t.Fatal(err) 939 | } else if !v { 940 | t.Fatal("could not delete cookie") 941 | } 942 | if cookies, err := page.Cookies(); err != nil { 943 | t.Fatal(err) 944 | } else if len(cookies) != 1 { 945 | t.Fatalf("unexpected cookie count: %d", len(cookies)) 946 | } else if cookies[0].Name != "NAME2" { 947 | t.Fatalf("unexpected cookie(0) name: %s", cookies[0].Name) 948 | } 949 | } 950 | 951 | // Ensure process can execute JavaScript asynchronously. 952 | // This function relies on time so it is inherently flakey. 953 | func TestWebPage_EvaluateAsync(t *testing.T) { 954 | p := MustOpenNewProcess() 955 | defer p.MustClose() 956 | 957 | page := p.MustCreateWebPage() 958 | defer MustClosePage(page) 959 | 960 | // Execute after one second. 961 | if err := page.EvaluateAsync(`function() { window.testValue = "OK" }`, 1*time.Second); err != nil { 962 | t.Fatal(err) 963 | } 964 | 965 | // Value should not be set immediately. 966 | if value, err := page.EvaluateJavaScript(`function() { return window.testValue }`); err != nil { 967 | t.Fatal(err) 968 | } else if value != nil { 969 | t.Fatalf("unexpected value: %#v", value) 970 | } 971 | 972 | // Wait a bit. 973 | time.Sleep(2 * time.Second) 974 | 975 | // Value should hopefully be set now. 976 | if value, err := page.EvaluateJavaScript(`function() { return window.testValue }`); err != nil { 977 | t.Fatal(err) 978 | } else if value != "OK" { 979 | t.Fatalf("unexpected value: %#v", value) 980 | } 981 | } 982 | 983 | // Ensure process can execute JavaScript in the context of a web page. 984 | func TestWebPage_Evaluate(t *testing.T) { 985 | p := MustOpenNewProcess() 986 | defer p.MustClose() 987 | 988 | page := p.MustCreateWebPage() 989 | defer MustClosePage(page) 990 | if err := page.SetContent(`FOOBAR`); err != nil { 991 | t.Fatal(err) 992 | } 993 | 994 | // Retrieve title. 995 | if value, err := page.EvaluateJavaScript(`function() { return document.title }`); err != nil { 996 | t.Fatal(err) 997 | } else if value != "FOO" { 998 | t.Fatalf("unexpected value: %#v", value) 999 | } 1000 | } 1001 | 1002 | // Ensure process can retrieve a page by window name. 1003 | func TestWebPage_Page(t *testing.T) { 1004 | p := MustOpenNewProcess() 1005 | defer p.MustClose() 1006 | 1007 | page := p.MustCreateWebPage() 1008 | defer MustClosePage(page) 1009 | 1010 | // Set content to open windows. 1011 | if err := page.SetOwnsPages(true); err != nil { 1012 | t.Fatal(err) 1013 | } 1014 | if err := page.SetContent(`CLICK ME`); err != nil { 1015 | t.Fatal(err) 1016 | } 1017 | 1018 | // Click the link. 1019 | if _, err := page.EvaluateJavaScript(`function() { document.body.querySelector("#link").click() }`); err != nil { 1020 | t.Fatal(err) 1021 | } 1022 | 1023 | // Retrieve a window by name. 1024 | if childPage, err := page.Page("win1"); err != nil { 1025 | t.Fatal(err) 1026 | } else if childPage == nil { 1027 | t.Fatalf("unexpected page: %#v", childPage) 1028 | } else if name, err := childPage.WindowName(); err != nil { 1029 | t.Fatal(err) 1030 | } else if name != "win1" { 1031 | t.Fatalf("unexpected page: %#v", childPage) 1032 | } 1033 | 1034 | // Non-existent pages should return nil. 1035 | if childPage, err := page.Page("bad_page"); err != nil { 1036 | t.Fatal(err) 1037 | } else if childPage != nil { 1038 | t.Fatalf("expected nil page: %#v", childPage) 1039 | } 1040 | } 1041 | 1042 | // Ensure process can moves forward and back in history. 1043 | func TestWebPage_GoBackForward(t *testing.T) { 1044 | // Mock external HTTP server. 1045 | srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 1046 | switch r.URL.Path { 1047 | case "/": 1048 | w.Write([]byte(`CLICK ME`)) 1049 | case "/page1.html": 1050 | w.Write([]byte(`FOO`)) 1051 | default: 1052 | http.NotFound(w, r) 1053 | } 1054 | })) 1055 | defer srv.Close() 1056 | 1057 | p := MustOpenNewProcess() 1058 | defer p.MustClose() 1059 | 1060 | page := p.MustCreateWebPage() 1061 | defer MustClosePage(page) 1062 | 1063 | // Open root page. 1064 | if err := page.Open(srv.URL); err != nil { 1065 | t.Fatal(err) 1066 | } 1067 | 1068 | // Click the link and verify location. 1069 | if _, err := page.EvaluateJavaScript(`function() { document.body.querySelector("#link").click() }`); err != nil { 1070 | t.Fatal(err) 1071 | } 1072 | if u, err := page.URL(); err != nil { 1073 | t.Fatal(err) 1074 | } else if u != srv.URL+"/page1.html" { 1075 | t.Fatalf("unexpected page: %s", u) 1076 | } 1077 | 1078 | // Navigate back & verify location. 1079 | if err := page.GoBack(); err != nil { 1080 | t.Fatal(err) 1081 | } 1082 | if u, err := page.URL(); err != nil { 1083 | t.Fatal(err) 1084 | } else if u != srv.URL+"/" { 1085 | t.Fatalf("unexpected page: %s", u) 1086 | } 1087 | 1088 | // Navigate forward & verify location. 1089 | if err := page.GoForward(); err != nil { 1090 | t.Fatal(err) 1091 | } 1092 | if u, err := page.URL(); err != nil { 1093 | t.Fatal(err) 1094 | } else if u != srv.URL+"/page1.html" { 1095 | t.Fatalf("unexpected page: %s", u) 1096 | } 1097 | } 1098 | 1099 | // Ensure process can move by relative index. 1100 | func TestWebPage_Go(t *testing.T) { 1101 | // Mock external HTTP server. 1102 | srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 1103 | switch r.URL.Path { 1104 | case "/": 1105 | w.Write([]byte(`CLICK ME`)) 1106 | case "/page1.html": 1107 | w.Write([]byte(`CLICK ME`)) 1108 | case "/page2.html": 1109 | w.Write([]byte(`FOO`)) 1110 | default: 1111 | http.NotFound(w, r) 1112 | } 1113 | })) 1114 | defer srv.Close() 1115 | 1116 | p := MustOpenNewProcess() 1117 | defer p.MustClose() 1118 | 1119 | page := p.MustCreateWebPage() 1120 | defer MustClosePage(page) 1121 | 1122 | // Open root page. 1123 | if err := page.Open(srv.URL); err != nil { 1124 | t.Fatal(err) 1125 | } 1126 | 1127 | // Click the links on two pages and verify location. 1128 | if _, err := page.EvaluateJavaScript(`function() { document.body.querySelector("#link").click() }`); err != nil { 1129 | t.Fatal(err) 1130 | } 1131 | if _, err := page.EvaluateJavaScript(`function() { document.body.querySelector("#link").click() }`); err != nil { 1132 | t.Fatal(err) 1133 | } 1134 | if u, err := page.URL(); err != nil { 1135 | t.Fatal(err) 1136 | } else if u != srv.URL+"/page2.html" { 1137 | t.Fatalf("unexpected page: %s", u) 1138 | } 1139 | 1140 | // Navigate back & verify location. 1141 | if err := page.Go(-2); err != nil { 1142 | t.Fatal(err) 1143 | } 1144 | if u, err := page.URL(); err != nil { 1145 | t.Fatal(err) 1146 | } else if u != srv.URL+"/" { 1147 | t.Fatalf("unexpected page: %s", u) 1148 | } 1149 | 1150 | // Navigate forward & verify location. 1151 | if err := page.Go(1); err != nil { 1152 | t.Fatal(err) 1153 | } 1154 | if u, err := page.URL(); err != nil { 1155 | t.Fatal(err) 1156 | } else if u != srv.URL+"/page1.html" { 1157 | t.Fatalf("unexpected page: %s", u) 1158 | } 1159 | } 1160 | 1161 | // Ensure process include external scripts. 1162 | func TestWebPage_IncludeJS(t *testing.T) { 1163 | // Mock external HTTP server. 1164 | srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 1165 | switch r.URL.Path { 1166 | case "/": 1167 | w.Write([]byte(`FOO`)) 1168 | case "/script.js": 1169 | w.Write([]byte(`window.testValue = 'INCLUDED'`)) 1170 | default: 1171 | http.NotFound(w, r) 1172 | } 1173 | })) 1174 | defer srv.Close() 1175 | 1176 | p := MustOpenNewProcess() 1177 | defer p.MustClose() 1178 | 1179 | page := p.MustCreateWebPage() 1180 | defer MustClosePage(page) 1181 | 1182 | // Open root page. 1183 | if err := page.Open(srv.URL); err != nil { 1184 | t.Fatal(err) 1185 | } 1186 | 1187 | // Include external script. 1188 | if err := page.IncludeJS(srv.URL + "/script.js"); err != nil { 1189 | t.Fatal(err) 1190 | } 1191 | 1192 | // Verify that script ran. 1193 | if v, err := page.Evaluate(`function() { return window.testValue }`); err != nil { 1194 | t.Fatal(err) 1195 | } else if v != "INCLUDED" { 1196 | t.Fatalf("unexpected test value: %#v", v) 1197 | } 1198 | } 1199 | 1200 | // Ensure process include local scripts. 1201 | func TestWebPage_InjectJS(t *testing.T) { 1202 | p := MustOpenNewProcess() 1203 | defer p.MustClose() 1204 | 1205 | page := p.MustCreateWebPage() 1206 | defer MustClosePage(page) 1207 | 1208 | // Write local script. 1209 | if err := ioutil.WriteFile(filepath.Join(p.Path(), "script.js"), []byte(`window.testValue = 'INCLUDED'`), 0600); err != nil { 1210 | t.Fatal(err) 1211 | } 1212 | 1213 | // Include local script. 1214 | if err := page.InjectJS("script.js"); err != nil { 1215 | t.Fatal(err) 1216 | } 1217 | 1218 | // Verify that script ran. 1219 | if v, err := page.Evaluate(`function() { return window.testValue }`); err != nil { 1220 | t.Fatal(err) 1221 | } else if v != "INCLUDED" { 1222 | t.Fatalf("unexpected test value: %#v", v) 1223 | } 1224 | } 1225 | 1226 | // Ensure web page can open a URL. 1227 | func TestWebPage_Open(t *testing.T) { 1228 | // Serve web page. 1229 | srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 1230 | w.Write([]byte("OK")) 1231 | })) 1232 | defer srv.Close() 1233 | 1234 | // Start process. 1235 | p := MustOpenNewProcess() 1236 | defer p.MustClose() 1237 | 1238 | // Create & open page. 1239 | page := p.MustCreateWebPage() 1240 | defer MustClosePage(page) 1241 | if err := page.Open(srv.URL); err != nil { 1242 | t.Fatal(err) 1243 | } else if content, err := page.Content(); err != nil { 1244 | t.Fatal(err) 1245 | } else if content != `OK` { 1246 | t.Fatalf("unexpected content: %q", content) 1247 | } 1248 | } 1249 | 1250 | // Ensure web page can reload a web page. 1251 | func TestWebPage_Reload(t *testing.T) { 1252 | // Serve web page. 1253 | var counter int 1254 | srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 1255 | counter++ 1256 | fmt.Fprintf(w, "%d", counter) 1257 | })) 1258 | defer srv.Close() 1259 | 1260 | // Start process. 1261 | p := MustOpenNewProcess() 1262 | defer p.MustClose() 1263 | 1264 | // Create & open page. 1265 | page := p.MustCreateWebPage() 1266 | defer MustClosePage(page) 1267 | if err := page.Open(srv.URL); err != nil { 1268 | t.Fatal(err) 1269 | } 1270 | 1271 | // First time the counter should be 1. 1272 | if content, err := page.Content(); err != nil { 1273 | t.Fatal(err) 1274 | } else if content != `1` { 1275 | t.Fatalf("unexpected content: %q", content) 1276 | } 1277 | 1278 | // Reload the page and the counter should increment. 1279 | if err := page.Reload(); err != nil { 1280 | t.Fatal(err) 1281 | } 1282 | if content, err := page.Content(); err != nil { 1283 | t.Fatal(err) 1284 | } else if content != `2` { 1285 | t.Fatalf("unexpected content: %q", content) 1286 | } 1287 | } 1288 | 1289 | // Ensure web page can render to a base64 string. 1290 | func TestWebPage_RenderBase64(t *testing.T) { 1291 | // Start process. 1292 | p := MustOpenNewProcess() 1293 | defer p.MustClose() 1294 | 1295 | // Create & open page. 1296 | page := p.MustCreateWebPage() 1297 | defer MustClosePage(page) 1298 | if err := page.SetContent(`TEST`); err != nil { 1299 | t.Fatal(err) 1300 | } 1301 | if err := page.SetViewportSize(100, 200); err != nil { 1302 | t.Fatal(err) 1303 | } 1304 | 1305 | // Render page. 1306 | data, err := page.RenderBase64("png") 1307 | if err != nil { 1308 | t.Fatal(err) 1309 | } 1310 | 1311 | // Decode data. 1312 | buf, err := base64.StdEncoding.DecodeString(data) 1313 | if err != nil { 1314 | t.Fatal(err) 1315 | } 1316 | 1317 | // Parse image and verify dimensions. 1318 | img, err := png.Decode(bytes.NewReader(buf)) 1319 | if err != nil { 1320 | t.Fatal(err) 1321 | } else if bounds := img.Bounds(); bounds.Max.X != 100 || bounds.Max.Y != 200 { 1322 | t.Fatalf("unexpected image dimesions: %dx%d", bounds.Max.X, bounds.Max.Y) 1323 | } 1324 | } 1325 | 1326 | // Ensure web page can render to a file. 1327 | func TestWebPage_Render(t *testing.T) { 1328 | // Start process. 1329 | p := MustOpenNewProcess() 1330 | defer p.MustClose() 1331 | 1332 | // Create & open page. 1333 | page := p.MustCreateWebPage() 1334 | defer MustClosePage(page) 1335 | if err := page.SetContent(`TEST`); err != nil { 1336 | t.Fatal(err) 1337 | } 1338 | if err := page.SetViewportSize(100, 200); err != nil { 1339 | t.Fatal(err) 1340 | } 1341 | 1342 | // Render page. 1343 | filename := filepath.Join(p.Path(), "test.png") 1344 | if err := page.Render(filename, "png", 100); err != nil { 1345 | t.Fatal(err) 1346 | } 1347 | 1348 | // Read file. 1349 | buf, err := ioutil.ReadFile(filename) 1350 | if err != nil { 1351 | t.Fatal(err) 1352 | } 1353 | 1354 | // Parse image and verify dimensions. 1355 | img, err := png.Decode(bytes.NewReader(buf)) 1356 | if err != nil { 1357 | t.Fatal(err) 1358 | } else if bounds := img.Bounds(); bounds.Max.X != 100 || bounds.Max.Y != 200 { 1359 | t.Fatalf("unexpected image dimesions: %dx%d", bounds.Max.X, bounds.Max.Y) 1360 | } 1361 | } 1362 | 1363 | // Ensure web page can receive mouse events. 1364 | func TestWebPage_SendMouseEvent(t *testing.T) { 1365 | // Start process. 1366 | p := MustOpenNewProcess() 1367 | defer p.MustClose() 1368 | 1369 | // Create & open page. 1370 | page := p.MustCreateWebPage() 1371 | defer MustClosePage(page) 1372 | if err := page.SetContent(``); err != nil { 1373 | t.Fatal(err) 1374 | } 1375 | 1376 | // Send mouse event. 1377 | if err := page.SendMouseEvent("click", 100, 200, "middle"); err != nil { 1378 | t.Fatal(err) 1379 | } 1380 | 1381 | // Verify test variables. 1382 | if x, err := page.Evaluate(`function() { return window.testX }`); err != nil { 1383 | t.Fatal(err) 1384 | } else if x != float64(100) { 1385 | t.Fatalf("unexpected x: %d", x) 1386 | } 1387 | if y, err := page.Evaluate(`function() { return window.testY }`); err != nil { 1388 | t.Fatal(err) 1389 | } else if y != float64(200) { 1390 | t.Fatalf("unexpected y: %d", y) 1391 | } 1392 | if button, err := page.Evaluate(`function() { return window.testButton }`); err != nil { 1393 | t.Fatal(err) 1394 | } else if button != float64(1) { 1395 | t.Fatalf("unexpected button: %d", button) 1396 | } 1397 | } 1398 | 1399 | // Ensure web page can receive keyboard events. 1400 | func TestWebPage_SendKeyboardEvent(t *testing.T) { 1401 | // Start process. 1402 | p := MustOpenNewProcess() 1403 | defer p.MustClose() 1404 | 1405 | // Create & open page. 1406 | page := p.MustCreateWebPage() 1407 | defer MustClosePage(page) 1408 | if err := page.SetContent(``); err != nil { 1409 | t.Fatal(err) 1410 | } 1411 | 1412 | // Send event. 1413 | if err := page.SendKeyboardEvent("keydown", "A", phantomjs.AltKey|phantomjs.CtrlKey|phantomjs.MetaKey|phantomjs.ShiftKey); err != nil { 1414 | t.Fatal(err) 1415 | } 1416 | 1417 | // Verify test variables. 1418 | if key, err := page.Evaluate(`function() { return window.testKey }`); err != nil { 1419 | t.Fatal(err) 1420 | } else if key != float64(65) { 1421 | t.Fatalf("unexpected key: %s", key) 1422 | } 1423 | if altKey, err := page.Evaluate(`function() { return window.testAlt }`); err != nil { 1424 | t.Fatal(err) 1425 | } else if altKey != true { 1426 | t.Fatalf("unexpected alt key: %v", altKey) 1427 | } 1428 | if ctrlKey, err := page.Evaluate(`function() { return window.testCtrl }`); err != nil { 1429 | t.Fatal(err) 1430 | } else if ctrlKey != true { 1431 | t.Fatalf("unexpected ctrl key: %v", ctrlKey) 1432 | } 1433 | if metaKey, err := page.Evaluate(`function() { return window.testMeta }`); err != nil { 1434 | t.Fatal(err) 1435 | } else if metaKey != true { 1436 | t.Fatalf("unexpected meta key: %v", metaKey) 1437 | } 1438 | if shiftKey, err := page.Evaluate(`function() { return window.testShift }`); err != nil { 1439 | t.Fatal(err) 1440 | } else if shiftKey != true { 1441 | t.Fatalf("unexpected shift key: %v", shiftKey) 1442 | } 1443 | } 1444 | 1445 | // Ensure web page can set content and URL at the same time. 1446 | func TestWebPage_SetContentAndURL(t *testing.T) { 1447 | // Start process. 1448 | p := MustOpenNewProcess() 1449 | defer p.MustClose() 1450 | 1451 | // Create & open page. 1452 | page := p.MustCreateWebPage() 1453 | defer MustClosePage(page) 1454 | if err := page.SetContentAndURL(`FOO`, "http://google.com"); err != nil { 1455 | t.Fatal(err) 1456 | } 1457 | 1458 | // Verify content & URL. 1459 | if content, err := page.Content(); err != nil { 1460 | t.Fatal(err) 1461 | } else if content != `FOO` { 1462 | t.Fatalf("unexpected content: %s", content) 1463 | } 1464 | if u, err := page.URL(); err != nil { 1465 | t.Fatal(err) 1466 | } else if u != `http://google.com/` { 1467 | t.Fatalf("unexpected URL: %s", u) 1468 | } 1469 | } 1470 | 1471 | // Ensure web page can call stop(). 1472 | func TestWebPage_Stop(t *testing.T) { 1473 | // Start process. 1474 | p := MustOpenNewProcess() 1475 | defer p.MustClose() 1476 | 1477 | // Create & open page. 1478 | page := p.MustCreateWebPage() 1479 | defer MustClosePage(page) 1480 | 1481 | // Call stop and ensure it doesn't blow up. 1482 | if err := page.Stop(); err != nil { 1483 | t.Fatal(err) 1484 | } 1485 | } 1486 | 1487 | // Ensure web page can switch to the focused frame. 1488 | func TestWebPage_SwitchToFocusedFrame(t *testing.T) { 1489 | // Mock external HTTP server. 1490 | srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 1491 | switch r.URL.Path { 1492 | case "/": 1493 | w.Write([]byte(``)) 1494 | case "/frame1.html": 1495 | w.Write([]byte(``)) 1496 | case "/frame2.html": 1497 | w.Write([]byte(``)) 1498 | default: 1499 | http.NotFound(w, r) 1500 | } 1501 | })) 1502 | defer srv.Close() 1503 | 1504 | // Start process. 1505 | p := MustOpenNewProcess() 1506 | defer p.MustClose() 1507 | 1508 | // Create & open page. 1509 | page := p.MustCreateWebPage() 1510 | defer MustClosePage(page) 1511 | if err := page.Open(srv.URL); err != nil { 1512 | t.Fatal(err) 1513 | } 1514 | 1515 | // Check initial current frame. 1516 | if other, err := page.FrameName(); err != nil { 1517 | t.Fatal(err) 1518 | } else if other != `` { 1519 | t.Fatalf("unexpected value: %#v", other) 1520 | } 1521 | 1522 | // Switch to focused frame and verify. 1523 | if err := page.SwitchToFocusedFrame(); err != nil { 1524 | t.Fatal(err) 1525 | } 1526 | if other, err := page.FrameName(); err != nil { 1527 | t.Fatal(err) 1528 | } else if other != `FRAME2` { 1529 | t.Fatalf("unexpected value: %#v", other) 1530 | } 1531 | } 1532 | 1533 | // Ensure web page can upload a file to a form field. 1534 | func TestWebPage_UploadFile(t *testing.T) { 1535 | // Mock external HTTP server. 1536 | uploadData := make(chan []byte, 0) 1537 | srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 1538 | switch r.Method { 1539 | case "GET": 1540 | w.Write([]byte(`
`)) 1541 | case "POST": 1542 | f, _, err := r.FormFile("myfile") 1543 | if err != nil { 1544 | t.Fatal(err) 1545 | } 1546 | 1547 | buf, err := ioutil.ReadAll(f) 1548 | if err != nil { 1549 | t.Fatal(err) 1550 | } 1551 | uploadData <- buf 1552 | 1553 | default: 1554 | http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) 1555 | } 1556 | })) 1557 | defer srv.Close() 1558 | 1559 | // Start process. 1560 | p := MustOpenNewProcess() 1561 | defer p.MustClose() 1562 | 1563 | // Create & open page. 1564 | page := p.MustCreateWebPage() 1565 | defer MustClosePage(page) 1566 | if err := page.Open(srv.URL); err != nil { 1567 | t.Fatal(err) 1568 | } 1569 | 1570 | // Write file to disk. 1571 | path := filepath.Join(p.Path(), "testfile") 1572 | if err := ioutil.WriteFile(path, []byte("TESTDATA"), 0600); err != nil { 1573 | t.Fatal(err) 1574 | } 1575 | 1576 | // Upload to field 1577 | if err := page.UploadFile("input[name=myfile]", path); err != nil { 1578 | t.Fatal(err) 1579 | } 1580 | 1581 | // Submit form. 1582 | if _, err := page.Evaluate(`function() { document.body.querySelector("#myForm").submit() }`); err != nil { 1583 | t.Fatal(err) 1584 | } 1585 | 1586 | // Wait for upload. 1587 | buf := <-uploadData 1588 | if string(buf) != "TESTDATA" { 1589 | t.Fatalf("unexpected upload data: %s", buf) 1590 | } 1591 | } 1592 | 1593 | // Process is a test wrapper for phantomjs.Process. 1594 | type Process struct { 1595 | *phantomjs.Process 1596 | } 1597 | 1598 | // NewProcess returns a new, open Process. 1599 | func NewProcess() *Process { 1600 | return &Process{Process: phantomjs.NewProcess()} 1601 | } 1602 | 1603 | // MustOpenNewProcess returns a new, open Process. Panic on error. 1604 | func MustOpenNewProcess() *Process { 1605 | p := NewProcess() 1606 | if err := p.Open(); err != nil { 1607 | panic(err) 1608 | } 1609 | return p 1610 | } 1611 | 1612 | // MustClose closes the process. Panic on error. 1613 | func (p *Process) MustClose() { 1614 | if err := p.Close(); err != nil { 1615 | panic(err) 1616 | } 1617 | } 1618 | 1619 | // MustCreateWebPage creates a web page. Panic on error. 1620 | func (p *Process) MustCreateWebPage() *phantomjs.WebPage { 1621 | page, err := p.CreateWebPage() 1622 | if err != nil { 1623 | panic(err) 1624 | } 1625 | return page 1626 | } 1627 | 1628 | // MustClosePage closes page. Panic on error. 1629 | func MustClosePage(page *phantomjs.WebPage) { 1630 | if err := page.Close(); err != nil { 1631 | panic(err) 1632 | } 1633 | } 1634 | --------------------------------------------------------------------------------