class Poltergeist.WebPage
@CALLBACKS = ['onAlert', 'onConsoleMessage', 'onLoadFinished', 'onInitialized', 'onLoadStarted', 'onResourceRequested', 'onResourceReceived', 'onError', 'onNavigationRequested', 'onUrlChanged', 'onPageCreated'] @DELEGATES = ['open', 'sendEvent', 'uploadFile', 'release', 'render', 'renderBase64', 'goBack', 'goForward'] @COMMANDS = ['currentUrl', 'find', 'nodeCall', 'documentSize', 'beforeUpload', 'afterUpload'] @EXTENSIONS = [] constructor: (@native) -> @native or= require('webpage').create() @_source = null @_errors = [] @_networkTraffic = {} @_temp_headers = {} @frames = [] for callback in WebPage.CALLBACKS this.bindCallback(callback) for command in @COMMANDS do (command) => this.prototype[command] = (args...) -> this.runCommand(command, args) for delegate in @DELEGATES do (delegate) => this.prototype[delegate] = -> @native[delegate].apply(@native, arguments) onInitializedNative: -> @_source = null @injectAgent() this.removeTempHeaders() this.setScrollPosition(left: 0, top: 0) injectAgent: -> if @native.evaluate(-> typeof __poltergeist) == "undefined" @native.injectJs "#{phantom.libraryPath}/agent.js" for extension in WebPage.EXTENSIONS @native.injectJs extension injectExtension: (file) -> WebPage.EXTENSIONS.push file @native.injectJs file onConsoleMessageNative: (message) -> if message == '__DOMContentLoaded' @_source = @native.content false onLoadStartedNative: -> @requestId = @lastRequestId onLoadFinishedNative: -> @_source or= @native.content onConsoleMessage: (message) -> console.log(message) onErrorNative: (message, stack) -> stackString = message stack.forEach (frame) -> stackString += "\n" stackString += " at #{frame.file}:#{frame.line}" stackString += " in #{frame.function}" if frame.function && frame.function != '' @_errors.push(message: message, stack: stackString) onResourceRequestedNative: (request) -> @lastRequestId = request.id if request.url == @redirectURL @redirectURL = null @requestId = request.id @_networkTraffic[request.id] = { request: request, responseParts: [] } onResourceReceivedNative: (response) -> @_networkTraffic[response.id]?.responseParts.push(response) if @requestId == response.id if response.redirectURL @redirectURL = response.redirectURL else @_statusCode = response.status @_responseHeaders = response.headers setHttpAuth: (user, password) -> @native.settings.userName = user @native.settings.password = password networkTraffic: -> @_networkTraffic clearNetworkTraffic: -> @_networkTraffic = {} content: -> @native.frameContent source: -> @_source title: -> @native.frameTitle errors: -> @_errors clearErrors: -> @_errors = [] statusCode: -> @_statusCode responseHeaders: -> headers = {} @_responseHeaders.forEach (item) -> headers[item.name] = item.value headers cookies: -> @native.cookies deleteCookie: (name) -> @native.deleteCookie(name) viewportSize: -> @native.viewportSize setViewportSize: (size) -> @native.viewportSize = size setZoomFactor: (zoom_factor) -> @native.zoomFactor = zoom_factor setPaperSize: (size) -> @native.paperSize = size scrollPosition: -> @native.scrollPosition setScrollPosition: (pos) -> @native.scrollPosition = pos clipRect: -> @native.clipRect setClipRect: (rect) -> @native.clipRect = rect elementBounds: (selector) -> @native.evaluate( (selector) -> document.querySelector(selector).getBoundingClientRect(), selector ) setUserAgent: (userAgent) -> @native.settings.userAgent = userAgent getCustomHeaders: -> @native.customHeaders setCustomHeaders: (headers) -> @native.customHeaders = headers addTempHeader: (header) -> for name, value of header @_temp_headers[name] = value removeTempHeaders: -> allHeaders = this.getCustomHeaders() for name, value of @_temp_headers delete allHeaders[name] this.setCustomHeaders(allHeaders) pushFrame: (name) -> if @native.switchToFrame(name) @frames.push(name) true else false pages: -> @native.pagesWindowName popFrame: -> @frames.pop() @native.switchToParentFrame() getPage: (name) -> page = @native.getPage(name) new Poltergeist.WebPage(page) if page dimensions: -> scroll = this.scrollPosition() viewport = this.viewportSize() top: scroll.top, bottom: scroll.top + viewport.height, left: scroll.left, right: scroll.left + viewport.width, viewport: viewport document: this.documentSize() # A work around for http://code.google.com/p/phantomjs/issues/detail?id=277 validatedDimensions: -> dimensions = this.dimensions() document = dimensions.document if dimensions.right > document.width dimensions.left = Math.max(0, dimensions.left - (dimensions.right - document.width)) dimensions.right = document.width if dimensions.bottom > document.height dimensions.top = Math.max(0, dimensions.top - (dimensions.bottom - document.height)) dimensions.bottom = document.height this.setScrollPosition(left: dimensions.left, top: dimensions.top) dimensions get: (id) -> new Poltergeist.Node(this, id) # Before each mouse event we make sure that the mouse is moved to where the # event will take place. This deals with e.g. :hover changes. mouseEvent: (name, x, y, button = 'left') -> this.sendEvent('mousemove', x, y) this.sendEvent(name, x, y, button) evaluate: (fn, args...) -> this.injectAgent() JSON.parse this.sanitize(@native.evaluate("function() { return PoltergeistAgent.stringify(#{this.stringifyCall(fn, args)}) }")) sanitize: (potential_string) -> if typeof(potential_string) == "string" # JSON doesn't like \r or \n in strings unless escaped potential_string.replace("\n","\\n").replace("\r","\\r") else potential_string execute: (fn, args...) -> @native.evaluate("function() { #{this.stringifyCall(fn, args)} }") stringifyCall: (fn, args) -> if args.length == 0 "(#{fn.toString()})()" else # The JSON.stringify happens twice because the second time we are essentially # escaping the string. "(#{fn.toString()}).apply(this, JSON.parse(#{JSON.stringify(JSON.stringify(args))}))" # For some reason phantomjs seems to have trouble with doing 'fat arrow' binding here, # hence the 'that' closure. bindCallback: (name) -> that = this @native[name] = -> if that[name + 'Native']? # For internal callbacks result = that[name + 'Native'].apply(that, arguments) if result != false && that[name]? # For externally set callbacks that[name].apply(that, arguments) # Any error raised here or inside the evaluate will get reported to # phantom.onError. If result is null, that means there was an error # inside the agent. runCommand: (name, args) -> result = this.evaluate( (name, args) -> __poltergeist.externalCall(name, args), name, args ) if result != null if result.error? switch result.error.message when 'PoltergeistAgent.ObsoleteNode' throw new Poltergeist.ObsoleteNode when 'PoltergeistAgent.InvalidSelector' [method, selector] = args throw new Poltergeist.InvalidSelector(method, selector) else throw new Poltergeist.BrowserError(result.error.message, result.error.stack) else result.value canGoBack: -> @native.canGoBack canGoForward: -> @native.canGoForward