wkBrowser.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", {
  3. value: true
  4. });
  5. exports.WKBrowserContext = exports.WKBrowser = void 0;
  6. var _browser = require("../browser");
  7. var _browserContext = require("../browserContext");
  8. var _utils = require("../../utils");
  9. var _eventsHelper = require("../../utils/eventsHelper");
  10. var network = _interopRequireWildcard(require("../network"));
  11. var _wkConnection = require("./wkConnection");
  12. var _wkPage = require("./wkPage");
  13. var _errors = require("../errors");
  14. function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
  15. function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
  16. /**
  17. * Copyright 2017 Google Inc. All rights reserved.
  18. * Modifications copyright (c) Microsoft Corporation.
  19. *
  20. * Licensed under the Apache License, Version 2.0 (the "License");
  21. * you may not use this file except in compliance with the License.
  22. * You may obtain a copy of the License at
  23. *
  24. * http://www.apache.org/licenses/LICENSE-2.0
  25. *
  26. * Unless required by applicable law or agreed to in writing, software
  27. * distributed under the License is distributed on an "AS IS" BASIS,
  28. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  29. * See the License for the specific language governing permissions and
  30. * limitations under the License.
  31. */
  32. const DEFAULT_USER_AGENT = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.4 Safari/605.1.15';
  33. const BROWSER_VERSION = '17.4';
  34. class WKBrowser extends _browser.Browser {
  35. static async connect(parent, transport, options) {
  36. const browser = new WKBrowser(parent, transport, options);
  37. if (options.__testHookOnConnectToBrowser) await options.__testHookOnConnectToBrowser();
  38. const promises = [browser._browserSession.send('Playwright.enable')];
  39. if (options.persistent) {
  40. var _options$persistent;
  41. (_options$persistent = options.persistent).userAgent || (_options$persistent.userAgent = DEFAULT_USER_AGENT);
  42. browser._defaultContext = new WKBrowserContext(browser, undefined, options.persistent);
  43. promises.push(browser._defaultContext._initialize());
  44. }
  45. await Promise.all(promises);
  46. return browser;
  47. }
  48. constructor(parent, transport, options) {
  49. super(parent, options);
  50. this._connection = void 0;
  51. this._browserSession = void 0;
  52. this._contexts = new Map();
  53. this._wkPages = new Map();
  54. this._eventListeners = void 0;
  55. this._connection = new _wkConnection.WKConnection(transport, this._onDisconnect.bind(this), options.protocolLogger, options.browserLogsCollector);
  56. this._browserSession = this._connection.browserSession;
  57. this._eventListeners = [_eventsHelper.eventsHelper.addEventListener(this._browserSession, 'Playwright.pageProxyCreated', this._onPageProxyCreated.bind(this)), _eventsHelper.eventsHelper.addEventListener(this._browserSession, 'Playwright.pageProxyDestroyed', this._onPageProxyDestroyed.bind(this)), _eventsHelper.eventsHelper.addEventListener(this._browserSession, 'Playwright.provisionalLoadFailed', event => this._onProvisionalLoadFailed(event)), _eventsHelper.eventsHelper.addEventListener(this._browserSession, 'Playwright.windowOpen', event => this._onWindowOpen(event)), _eventsHelper.eventsHelper.addEventListener(this._browserSession, 'Playwright.downloadCreated', this._onDownloadCreated.bind(this)), _eventsHelper.eventsHelper.addEventListener(this._browserSession, 'Playwright.downloadFilenameSuggested', this._onDownloadFilenameSuggested.bind(this)), _eventsHelper.eventsHelper.addEventListener(this._browserSession, 'Playwright.downloadFinished', this._onDownloadFinished.bind(this)), _eventsHelper.eventsHelper.addEventListener(this._browserSession, 'Playwright.screencastFinished', this._onScreencastFinished.bind(this)), _eventsHelper.eventsHelper.addEventListener(this._browserSession, _wkConnection.kPageProxyMessageReceived, this._onPageProxyMessageReceived.bind(this))];
  58. }
  59. _onDisconnect() {
  60. for (const wkPage of this._wkPages.values()) wkPage.didClose();
  61. this._wkPages.clear();
  62. for (const video of this._idToVideo.values()) video.artifact.reportFinished(new _errors.TargetClosedError());
  63. this._idToVideo.clear();
  64. this._didClose();
  65. }
  66. async doCreateNewContext(options) {
  67. const createOptions = options.proxy ? {
  68. // Enable socks5 hostname resolution on Windows. Workaround can be removed once fixed upstream.
  69. // See https://github.com/microsoft/playwright/issues/20451
  70. proxyServer: process.platform === 'win32' ? options.proxy.server.replace(/^socks5:\/\//, 'socks5h://') : options.proxy.server,
  71. proxyBypassList: options.proxy.bypass
  72. } : undefined;
  73. const {
  74. browserContextId
  75. } = await this._browserSession.send('Playwright.createContext', createOptions);
  76. options.userAgent = options.userAgent || DEFAULT_USER_AGENT;
  77. const context = new WKBrowserContext(this, browserContextId, options);
  78. await context._initialize();
  79. this._contexts.set(browserContextId, context);
  80. return context;
  81. }
  82. contexts() {
  83. return Array.from(this._contexts.values());
  84. }
  85. version() {
  86. return BROWSER_VERSION;
  87. }
  88. userAgent() {
  89. return DEFAULT_USER_AGENT;
  90. }
  91. _onDownloadCreated(payload) {
  92. const page = this._wkPages.get(payload.pageProxyId);
  93. if (!page) return;
  94. // In some cases, e.g. blob url download, we receive only frameScheduledNavigation
  95. // but no signals that the navigation was canceled and replaced by download. Fix it
  96. // here by simulating cancelled provisional load which matches downloads from network.
  97. //
  98. // TODO: this is racy, because download might be unrelated any navigation, and we will
  99. // abort navigation that is still running. We should be able to fix this by
  100. // instrumenting policy decision start/proceed/cancel.
  101. page._page._frameManager.frameAbortedNavigation(payload.frameId, 'Download is starting');
  102. let originPage = page._initializedPage;
  103. // If it's a new window download, report it on the opener page.
  104. if (!originPage) {
  105. // Resume the page creation with an error. The page will automatically close right
  106. // after the download begins.
  107. page._firstNonInitialNavigationCommittedReject(new Error('Starting new page download'));
  108. if (page._opener) originPage = page._opener._initializedPage;
  109. }
  110. if (!originPage) return;
  111. this._downloadCreated(originPage, payload.uuid, payload.url);
  112. }
  113. _onDownloadFilenameSuggested(payload) {
  114. this._downloadFilenameSuggested(payload.uuid, payload.suggestedFilename);
  115. }
  116. _onDownloadFinished(payload) {
  117. this._downloadFinished(payload.uuid, payload.error);
  118. }
  119. _onScreencastFinished(payload) {
  120. var _this$_takeVideo;
  121. (_this$_takeVideo = this._takeVideo(payload.screencastId)) === null || _this$_takeVideo === void 0 ? void 0 : _this$_takeVideo.reportFinished();
  122. }
  123. _onPageProxyCreated(event) {
  124. const pageProxyId = event.pageProxyId;
  125. let context = null;
  126. if (event.browserContextId) {
  127. // FIXME: we don't know about the default context id, so assume that all targets from
  128. // unknown contexts are created in the 'default' context which can in practice be represented
  129. // by multiple actual contexts in WebKit. Solving this properly will require adding context
  130. // lifecycle events.
  131. context = this._contexts.get(event.browserContextId) || null;
  132. }
  133. if (!context) context = this._defaultContext;
  134. if (!context) return;
  135. const pageProxySession = new _wkConnection.WKSession(this._connection, pageProxyId, message => {
  136. this._connection.rawSend({
  137. ...message,
  138. pageProxyId
  139. });
  140. });
  141. const opener = event.openerId ? this._wkPages.get(event.openerId) : undefined;
  142. const wkPage = new _wkPage.WKPage(context, pageProxySession, opener || null);
  143. this._wkPages.set(pageProxyId, wkPage);
  144. }
  145. _onPageProxyDestroyed(event) {
  146. const pageProxyId = event.pageProxyId;
  147. const wkPage = this._wkPages.get(pageProxyId);
  148. if (!wkPage) return;
  149. wkPage.didClose();
  150. this._wkPages.delete(pageProxyId);
  151. }
  152. _onPageProxyMessageReceived(event) {
  153. const wkPage = this._wkPages.get(event.pageProxyId);
  154. if (!wkPage) return;
  155. wkPage.dispatchMessageToSession(event.message);
  156. }
  157. _onProvisionalLoadFailed(event) {
  158. const wkPage = this._wkPages.get(event.pageProxyId);
  159. if (!wkPage) return;
  160. wkPage.handleProvisionalLoadFailed(event);
  161. }
  162. _onWindowOpen(event) {
  163. const wkPage = this._wkPages.get(event.pageProxyId);
  164. if (!wkPage) return;
  165. wkPage.handleWindowOpen(event);
  166. }
  167. isConnected() {
  168. return !this._connection.isClosed();
  169. }
  170. }
  171. exports.WKBrowser = WKBrowser;
  172. class WKBrowserContext extends _browserContext.BrowserContext {
  173. constructor(browser, browserContextId, options) {
  174. super(browser, options, browserContextId);
  175. this._validateEmulatedViewport(options.viewport);
  176. this._authenticateProxyViaHeader();
  177. }
  178. async _initialize() {
  179. (0, _utils.assert)(!this._wkPages().length);
  180. const browserContextId = this._browserContextId;
  181. const promises = [super._initialize()];
  182. promises.push(this._browser._browserSession.send('Playwright.setDownloadBehavior', {
  183. behavior: this._options.acceptDownloads === 'accept' ? 'allow' : 'deny',
  184. downloadPath: this._browser.options.downloadsPath,
  185. browserContextId
  186. }));
  187. if (this._options.ignoreHTTPSErrors) promises.push(this._browser._browserSession.send('Playwright.setIgnoreCertificateErrors', {
  188. browserContextId,
  189. ignore: true
  190. }));
  191. if (this._options.locale) promises.push(this._browser._browserSession.send('Playwright.setLanguages', {
  192. browserContextId,
  193. languages: [this._options.locale]
  194. }));
  195. if (this._options.geolocation) promises.push(this.setGeolocation(this._options.geolocation));
  196. if (this._options.offline) promises.push(this.setOffline(this._options.offline));
  197. if (this._options.httpCredentials) promises.push(this.setHTTPCredentials(this._options.httpCredentials));
  198. await Promise.all(promises);
  199. }
  200. _wkPages() {
  201. return Array.from(this._browser._wkPages.values()).filter(wkPage => wkPage._browserContext === this);
  202. }
  203. pages() {
  204. return this._wkPages().map(wkPage => wkPage._initializedPage).filter(pageOrNull => !!pageOrNull);
  205. }
  206. async newPageDelegate() {
  207. (0, _browserContext.assertBrowserContextIsNotOwned)(this);
  208. const {
  209. pageProxyId
  210. } = await this._browser._browserSession.send('Playwright.createPage', {
  211. browserContextId: this._browserContextId
  212. });
  213. return this._browser._wkPages.get(pageProxyId);
  214. }
  215. async doGetCookies(urls) {
  216. const {
  217. cookies
  218. } = await this._browser._browserSession.send('Playwright.getAllCookies', {
  219. browserContextId: this._browserContextId
  220. });
  221. return network.filterCookies(cookies.map(c => {
  222. const copy = {
  223. ...c
  224. };
  225. copy.expires = c.expires === -1 ? -1 : c.expires / 1000;
  226. delete copy.session;
  227. return copy;
  228. }), urls);
  229. }
  230. async addCookies(cookies) {
  231. const cc = network.rewriteCookies(cookies).map(c => ({
  232. ...c,
  233. session: c.expires === -1 || c.expires === undefined,
  234. expires: c.expires && c.expires !== -1 ? c.expires * 1000 : c.expires
  235. }));
  236. await this._browser._browserSession.send('Playwright.setCookies', {
  237. cookies: cc,
  238. browserContextId: this._browserContextId
  239. });
  240. }
  241. async clearCookies() {
  242. await this._browser._browserSession.send('Playwright.deleteAllCookies', {
  243. browserContextId: this._browserContextId
  244. });
  245. }
  246. async doGrantPermissions(origin, permissions) {
  247. await Promise.all(this.pages().map(page => page._delegate._grantPermissions(origin, permissions)));
  248. }
  249. async doClearPermissions() {
  250. await Promise.all(this.pages().map(page => page._delegate._clearPermissions()));
  251. }
  252. async setGeolocation(geolocation) {
  253. (0, _browserContext.verifyGeolocation)(geolocation);
  254. this._options.geolocation = geolocation;
  255. const payload = geolocation ? {
  256. ...geolocation,
  257. timestamp: Date.now()
  258. } : undefined;
  259. await this._browser._browserSession.send('Playwright.setGeolocationOverride', {
  260. browserContextId: this._browserContextId,
  261. geolocation: payload
  262. });
  263. }
  264. async setExtraHTTPHeaders(headers) {
  265. this._options.extraHTTPHeaders = headers;
  266. for (const page of this.pages()) await page._delegate.updateExtraHTTPHeaders();
  267. }
  268. async setUserAgent(userAgent) {
  269. this._options.userAgent = userAgent;
  270. for (const page of this.pages()) await page._delegate.updateUserAgent();
  271. }
  272. async setOffline(offline) {
  273. this._options.offline = offline;
  274. for (const page of this.pages()) await page._delegate.updateOffline();
  275. }
  276. async doSetHTTPCredentials(httpCredentials) {
  277. this._options.httpCredentials = httpCredentials;
  278. for (const page of this.pages()) await page._delegate.updateHttpCredentials();
  279. }
  280. async doAddInitScript(source) {
  281. for (const page of this.pages()) await page._delegate._updateBootstrapScript();
  282. }
  283. async doRemoveInitScripts() {
  284. for (const page of this.pages()) await page._delegate._updateBootstrapScript();
  285. }
  286. async doExposeBinding(binding) {
  287. for (const page of this.pages()) await page._delegate.exposeBinding(binding);
  288. }
  289. async doRemoveExposedBindings() {
  290. for (const page of this.pages()) await page._delegate.removeExposedBindings();
  291. }
  292. async doUpdateRequestInterception() {
  293. for (const page of this.pages()) await page._delegate.updateRequestInterception();
  294. }
  295. onClosePersistent() {}
  296. async clearCache() {
  297. // We use ephemeral contexts so there is no disk cache.
  298. await this._browser._browserSession.send('Playwright.clearMemoryCache', {
  299. browserContextId: this._browserContextId
  300. });
  301. }
  302. async doClose(reason) {
  303. if (!this._browserContextId) {
  304. await Promise.all(this._wkPages().map(wkPage => wkPage._stopVideo()));
  305. // Closing persistent context should close the browser.
  306. await this._browser.close({
  307. reason
  308. });
  309. } else {
  310. await this._browser._browserSession.send('Playwright.deleteContext', {
  311. browserContextId: this._browserContextId
  312. });
  313. this._browser._contexts.delete(this._browserContextId);
  314. }
  315. }
  316. async cancelDownload(uuid) {
  317. await this._browser._browserSession.send('Playwright.cancelDownload', {
  318. uuid
  319. });
  320. }
  321. _validateEmulatedViewport(viewportSize) {
  322. if (!viewportSize) return;
  323. if (process.platform === 'win32' && this._browser.options.headful && (viewportSize.width < 250 || viewportSize.height < 240)) throw new Error(`WebKit on Windows has a minimal viewport of 250x240.`);
  324. }
  325. }
  326. exports.WKBrowserContext = WKBrowserContext;