diff --git a/lib/Page.js b/lib/Page.js index 4efb564bc4f..4f76771b516 100644 --- a/lib/Page.js +++ b/lib/Page.js @@ -18,6 +18,7 @@ var fs = require('fs'); var EventEmitter = require('events'); var helpers = require('./helpers'); var mime = require('mime'); +var Request = require('./Request'); class Page extends EventEmitter { /** @@ -56,16 +57,35 @@ class Page extends EventEmitter { this._scriptIdToPageCallback = new Map(); /** @type {!Array} */ this._blockedURLs = []; + /** @type {?function(!Request)} */ + this._requestInterceptor = null; client.on('Debugger.paused', event => this._onDebuggerPaused(event)); client.on('Debugger.scriptParsed', event => this._onScriptParsed(event)); client.on('Network.responseReceived', event => this.emit(Page.Events.ResponseReceived, event.response)); client.on('Network.loadingFailed', event => this.emit(Page.Events.ResourceLoadingFailed, event)); + client.on('Network.requestIntercepted', event => this._onRequestIntercepted(event)); client.on('Runtime.consoleAPICalled', event => this._onConsoleAPI(event)); client.on('Page.javascriptDialogOpening', event => this._onDialog(event)); client.on('Runtime.exceptionThrown', exception => this._handleException(exception.exceptionDetails)); } + /** + * @param {?function(!Request)} interceptor + */ + async setRequestInterceptor(interceptor) { + this._requestInterceptor = interceptor; + await this._client.send('Network.enableRequestInterception', {enabled: !!interceptor}); + } + + /** + * @param {!Object} event + */ + _onRequestIntercepted(event) { + var request = new Request(this._client, event.InterceptionId, event.request); + this._requestInterceptor(request); + } + /** * @param {!Array} patterns * @return {!Promise} diff --git a/lib/Request.js b/lib/Request.js new file mode 100644 index 00000000000..03f18eb2d6f --- /dev/null +++ b/lib/Request.js @@ -0,0 +1,123 @@ +/** + * Copyright 2017 Google Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +class Request { + /** + * @param {!Connection} client + * @param {string} interceptionId + * @param {!Object} payload + */ + constructor(client, interceptionId, payload) { + this._client = client; + this._interceptionId = interceptionId; + this._url = payload.url; + this._method = payload.method; + this._headers = payload.headers; + this._postData = payload.postData; + + this._urlOverride = undefined; + this._methodOverride = undefined; + this._postDataOverride = undefined; + + this._handled = false; + } + + /** + * @return {string} + */ + url() { + return this._urlOverride || this._url; + } + + /** + * @param {string} url + */ + setUrl(url) { + this._urlOverride = url; + } + + /** + * @return {string} + */ + method() { + return this._methodOverride || this._method; + } + + /** + * @param {string} method + */ + setMethod(method) { + this._methodOverride = method; + } + + /** + * @return {!Object} + */ + headers() { + return Object.assign({}, this._headersOverride || this._headers); + } + + /** + * @param {!Object} headers + */ + setHeaders(headers) { + this._headersOverride = headers; + } + + /** + * @return {(string|undefined)} + */ + postData() { + return this._postDataOverride || this._postData; + } + + /** + * @return {(string|undefined)} + */ + setPostData(data) { + this._postDataOverride = data; + } + + abort() { + console.assert(!this._handled, 'This request is already handled!'); + this._handled = true; + this._client.send('Network.continueInterceptedRequest', { + interceptionId: this._interceptionId, + errorReason: 'Aborted' + }); + } + + continue() { + console.assert(!this._handled, 'This request is already handled!'); + this._handled = true; + this._client.send('Network.continueInterceptedRequest', { + interceptionId: this._interceptionId, + url: this._urlOverride, + method: this._methodOverride, + postData: this._postDataOverride, + headers: this._headersOverride + }); + } + + /** + * @return {boolean} + */ + handled() { + return this._handled; + } +} + +module.exports = Request; diff --git a/phantom_shim/WebPage.js b/phantom_shim/WebPage.js index 5b77cb73a0e..31432aca14e 100644 --- a/phantom_shim/WebPage.js +++ b/phantom_shim/WebPage.js @@ -52,6 +52,7 @@ class WebPage { this.libraryPath = path.dirname(scriptPath); + this._onResourceRequestedCallback = undefined; this._onConfirmCallback = undefined; this._onAlertCallback = undefined; this._onError = noop; @@ -65,6 +66,34 @@ class WebPage { this._pageEvents.on(PageEvents.Exception, (exception, stack) => (this._onError || noop).call(null, exception, stack)); } + /** + * @return {?function(!Object, !Request)} + */ + get onResourceRequested() { + return this._onResourceRequestedCallback; + } + + /** + * @return {?function(!Object, !Request)} callback + */ + set onResourceRequested(callback) { + this._onResourceRequestedCallback = callback; + this._page.setRequestInterceptor(callback ? resourceInterceptor : null); + + /** + * @param {!Request} request + */ + function resourceInterceptor(request) { + var requestData = { + url: request.url(), + headers: request.headers() + }; + callback(requestData, request); + if (!request.handled()) + request.continue(); + } + } + _onResponseReceived(response) { if (!this.onResourceReceived) return; diff --git a/test/test.js b/test/test.js index a533065594b..d6bc6097632 100644 --- a/test/test.js +++ b/test/test.js @@ -84,6 +84,29 @@ describe('Puppeteer', function() { expect(result).toBe(15); })); }); + + describe('Page.setRequestInterceptor', function() { + it('should work', SX(async function() { + page.setRequestInterceptor(request => { + expect(request.url()).toContain('empty.html'); + expect(request.headers()['User-Agent']).toBeTruthy(); + expect(request.method()).toBe('GET'); + expect(request.postData()).toBe(undefined); + request.continue(); + }); + var success = await page.navigate(EMPTY_PAGE); + })); + it('should show extraHTTPHeaders', SX(async function() { + await page.setExtraHTTPHeaders({ + foo: 'bar' + }); + page.setRequestInterceptor(request => { + expect(request.headers()['foo']).toBe('bar'); + request.continue(); + }); + var success = await page.navigate(EMPTY_PAGE); + })); + }); }); // Since Jasmine doesn't like async functions, they should be wrapped diff --git a/third_party/phantomjs/examples/loadurlwithoutcss.js b/third_party/phantomjs/examples/loadurlwithoutcss.js index 12fc28ab626..b6f13c6153a 100644 --- a/third_party/phantomjs/examples/loadurlwithoutcss.js +++ b/third_party/phantomjs/examples/loadurlwithoutcss.js @@ -23,4 +23,4 @@ page.open(address, function(status) { console.log('Unable to load the address!'); phantom.exit(); } -}); \ No newline at end of file +}); diff --git a/third_party/phantomjs/test/module/webpage/abort-network-request.js b/third_party/phantomjs/test/module/webpage/abort-network-request.js index e6718ff7435..9ec906da247 100644 --- a/third_party/phantomjs/test/module/webpage/abort-network-request.js +++ b/third_party/phantomjs/test/module/webpage/abort-network-request.js @@ -1,4 +1,3 @@ -//! unsupported var webpage = require('webpage'); async_test(function () {