├── 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 [](https://godoc.org/github.com/benbjohnson/phantomjs)  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(`