diff --git a/browser_patches/buildbots/README.md b/browser_patches/buildbots/README.md
index 9491d41637..54ce5bf244 100644
--- a/browser_patches/buildbots/README.md
+++ b/browser_patches/buildbots/README.md
@@ -2,13 +2,13 @@
We currently have 4 build bots that produce the following builds
- **[buildbot-linux]** Ubuntu 18.04 machine
- - builds: `Webkit-Linux`, `Firefox-Linux`
+ - builds: `webkit-gtk`, `webkit-wpe`, `firefox-linux`
- **[buildbot-mac-10.14]** Mac 10.14 machine
- - builds: `WebKit-mac-10.14`, `Firefox-Mac`
+ - builds: `webKit-mac-10.14`, `firefox-mac`
- **[buildbot-mac-10.15]** machine
- - builds: `WebKit-mac-10.15`
+ - builds: `webkit-mac-10.15`
- **[buildbot-windows]** Windows 10 machine
- - builds: `Firefox-win32`, `Firefox-win64`, `webkit-win64`
+ - builds: `firefox-win32`, `firefox-win64`, `webkit-win64`
This document describes setting up bots infrastructure to produce
browser builds.
@@ -105,6 +105,18 @@ The `core.longpaths` is needed for webkit since it has some very long layout pat
Run `c:\mozilla-build\start-shell.bat` and checkout PlayWright repo to `/c/playwright`.
+### 7. Create a c:\WEBKIT_WIN64_LIBS\ directory with win64 dlls
+
+
+Create a new `c:\WEBKIT_WIN64_LIBS` folder and copy the following libraries from `C:\Windows\System32` into it:
+- `msvcp140.dll`
+- `vcruntime140.dll`
+- `vcruntime140_1.dll`
+
+> **NOTE**: these libraries are expected by `//browser_patches/webkit/archive.sh`.
+
+This is necessary since mingw is a 32-bit application and cannot access the `C:\Windows\System32` folder due to [Windows FileSystem Redirector](https://docs.microsoft.com/en-us/windows/win32/winprog64/file-system-redirector?redirectedfrom=MSDN). ([StackOverflow question](https://stackoverflow.com/questions/18982551/is-mingw-caching-windows-directory-contents))
+
## Running Build Loop
1. Launch `c:\mozilla-build/start-shell.bat`
diff --git a/browser_patches/buildbots/buildbot-linux.sh b/browser_patches/buildbots/buildbot-linux.sh
index 8bff4eff16..d560b8084b 100755
--- a/browser_patches/buildbots/buildbot-linux.sh
+++ b/browser_patches/buildbots/buildbot-linux.sh
@@ -55,7 +55,9 @@ if [[ -n $(git status -s) ]]; then
fi
git pull origin master
-../checkout_build_archive_upload.sh firefox >/tmp/$(basename $0)-firefox-log.log || true
+../checkout_build_archive_upload.sh firefox-linux >/tmp/$(basename $0)--firefox-linux.log || true
git pull origin master
-../checkout_build_archive_upload.sh webkit >/tmp/$(basename $0)-webkit-log.log || true
+../checkout_build_archive_upload.sh webkit-gtk >/tmp/$(basename $0)--webkit-gtk.log || true
+../checkout_build_archive_upload.sh webkit-wpe >/tmp/$(basename $0)--webkit-wpe.log || true
+../checkout_build_archive_upload.sh webkit-gtk-wpe >/tmp/$(basename $0)--webkit-gtk-wpe.log || true
diff --git a/browser_patches/buildbots/buildbot-mac-10.14.sh b/browser_patches/buildbots/buildbot-mac-10.14.sh
index 02996d5e99..edc727d239 100755
--- a/browser_patches/buildbots/buildbot-mac-10.14.sh
+++ b/browser_patches/buildbots/buildbot-mac-10.14.sh
@@ -61,7 +61,7 @@ if [[ -n $(git status -s) ]]; then
fi
git pull origin master
-../checkout_build_archive_upload.sh firefox >/tmp/$(basename $0)-firefox-log.log || true
+../checkout_build_archive_upload.sh firefox-mac >/tmp/$(basename $0)--firefox-mac.log || true
git pull origin master
-../checkout_build_archive_upload.sh webkit >/tmp/$(basename $0)-webkit-log.log || true
+../checkout_build_archive_upload.sh webkit-mac-10.14 >/tmp/$(basename $0)--webkit-mac-10.14.log || true
diff --git a/browser_patches/buildbots/buildbot-mac-10.15.sh b/browser_patches/buildbots/buildbot-mac-10.15.sh
index 1aed974cdb..61d64c959f 100755
--- a/browser_patches/buildbots/buildbot-mac-10.15.sh
+++ b/browser_patches/buildbots/buildbot-mac-10.15.sh
@@ -62,4 +62,4 @@ if [[ -n $(git status -s) ]]; then
fi
git pull origin master
-../checkout_build_archive_upload.sh webkit >/tmp/$(basename $0)-webkit-log.log || true
+../checkout_build_archive_upload.sh webkit-mac-10.15 >/tmp/$(basename $0)--webkit-mac-10.15.log || true
diff --git a/browser_patches/buildbots/buildbot-windows.sh b/browser_patches/buildbots/buildbot-windows.sh
index a6b96e4082..662bd886b7 100755
--- a/browser_patches/buildbots/buildbot-windows.sh
+++ b/browser_patches/buildbots/buildbot-windows.sh
@@ -45,9 +45,9 @@ while true; do
iteration=$(( iteration + 1 ))
echo "== ITERATION ${iteration} =="
git pull origin master
- ../checkout_build_archive_upload.sh webkit || true
+ ../checkout_build_archive_upload.sh webkit-win64 || true
git pull origin master
- ../checkout_build_archive_upload.sh firefox || true
+ ../checkout_build_archive_upload.sh firefox-win32 || true
git pull origin master
../checkout_build_archive_upload.sh firefox-win64 || true
newTimestamp=$(date +%s)
diff --git a/browser_patches/checkout_build_archive_upload.sh b/browser_patches/checkout_build_archive_upload.sh
index be2aff386b..86cda817d2 100755
--- a/browser_patches/checkout_build_archive_upload.sh
+++ b/browser_patches/checkout_build_archive_upload.sh
@@ -3,7 +3,7 @@ set -e
set +x
if [[ ($1 == '--help') || ($1 == '-h') ]]; then
- echo "usage: $(basename $0) [firefox|firefox-win64|webkit] [-f|--force]"
+ echo "usage: $(basename $0) [firefox-linux|firefox-win32|firefox-win64|webkit-gtk|webkit-wpe|webkit-gtk-wpe|webkit-win64|webkit-mac-10.14|webkit-mac-10.15] [-f|--force]"
echo
echo "Prepares checkout under browser folder, applies patches, builds, archives, and uploades if build is missing."
echo "Script will bail out early if the build for the browser version is already present."
@@ -15,22 +15,75 @@ if [[ ($1 == '--help') || ($1 == '-h') ]]; then
fi
if [[ $# == 0 ]]; then
- echo "missing browser: 'firefox' or 'webkit'"
+ echo "missing build flavor!"
echo "try './$(basename $0) --help' for more information"
exit 1
fi
+CURRENT_HOST_OS="$(uname)"
+CURRENT_HOST_OS_VERSION=""
+if [[ "$CURRENT_HOST_OS" == "Darwin" ]]; then
+ CURRENT_HOST_OS_VERSION=$(sw_vers -productVersion | grep -o '^\d\+.\d\+')
+fi
+
BROWSER_NAME=""
EXTRA_BUILD_ARGS=""
-if [[ ("$1" == "firefox") || ("$1" == "firefox/") ]]; then
+EXTRA_ARCHIVE_ARGS=""
+BUILD_FLAVOR="$1"
+EXPECTED_HOST_OS=""
+EXPECTED_HOST_OS_VERSION=""
+if [[ "$BUILD_FLAVOR" == "firefox-linux" ]]; then
BROWSER_NAME="firefox"
-elif [[ ("$1" == "firefox-win64") || ("$1" == "firefox-win64/") ]]; then
+ EXPECTED_HOST_OS="Linux"
+elif [[ "$BUILD_FLAVOR" == "firefox-mac" ]]; then
+ BROWSER_NAME="firefox"
+ EXPECTED_HOST_OS="Darwin"
+ EXPECTED_HOST_OS_VERSION="10.14"
+elif [[ "$BUILD_FLAVOR" == "firefox-win32" ]]; then
+ BROWSER_NAME="firefox"
+ EXPECTED_HOST_OS="MINGW"
+elif [[ "$BUILD_FLAVOR" == "firefox-win64" ]]; then
BROWSER_NAME="firefox"
EXTRA_BUILD_ARGS="--win64"
-elif [[ ("$1" == "webkit") || ("$1" == "webkit/") ]]; then
+ EXPECTED_HOST_OS="MINGW"
+elif [[ "$BUILD_FLAVOR" == "webkit-gtk" ]]; then
BROWSER_NAME="webkit"
+ EXPECTED_HOST_OS="Linux"
+elif [[ "$BUILD_FLAVOR" == "webkit-wpe" ]]; then
+ BROWSER_NAME="webkit"
+ EXTRA_BUILD_ARGS="--wpe"
+ EXTRA_ARCHIVE_ARGS="--wpe"
+ EXPECTED_HOST_OS="Linux"
+elif [[ "$BUILD_FLAVOR" == "webkit-gtk-wpe" ]]; then
+ BROWSER_NAME="webkit"
+ EXPECTED_HOST_OS="Linux"
+elif [[ "$BUILD_FLAVOR" == "webkit-win64" ]]; then
+ BROWSER_NAME="webkit"
+ EXPECTED_HOST_OS="MINGW"
+elif [[ "$BUILD_FLAVOR" == "webkit-mac-10.14" ]]; then
+ BROWSER_NAME="webkit"
+ EXPECTED_HOST_OS="Darwin"
+ EXPECTED_HOST_OS_VERSION="10.14"
+elif [[ "$BUILD_FLAVOR" == "webkit-mac-10.15" ]]; then
+ BROWSER_NAME="webkit"
+ EXPECTED_HOST_OS="Darwin"
+ EXPECTED_HOST_OS_VERSION="10.15"
else
- echo ERROR: unknown browser - "$1"
+ echo ERROR: unknown build flavor - "$BUILD_FLAVOR"
+ exit 1
+fi
+
+if [[ "$CURRENT_HOST_OS" != $EXPECTED_HOST_OS* ]]; then
+ echo "ERROR: cannot build $BUILD_FLAVOR"
+ echo " -- expected OS: $EXPECTED_HOST_OS"
+ echo " -- current OS: $CURRENT_HOST_OS"
+ exit 1
+fi
+
+if [[ "$CURRENT_HOST_OS_VERSION" != "$EXPECTED_HOST_OS_VERSION" ]]; then
+ echo "ERROR: cannot build $BUILD_FLAVOR"
+ echo " -- expected OS Version: $EXPECTED_HOST_OS_VERSION"
+ echo " -- current OS Version: $CURRENT_HOST_OS_VERSION"
exit 1
fi
@@ -50,7 +103,7 @@ BUILD_NUMBER=$(cat ./$BROWSER_NAME/BUILD_NUMBER)
# pull from upstream and check if a new build has to be uploaded.
if ! [[ ($2 == '-f') || ($2 == '--force') ]]; then
- if ./upload.sh $1 --check; then
+ if ./upload.sh $BUILD_FLAVOR --check; then
echo "Build is already uploaded - no changes."
exit 0
else
@@ -69,36 +122,46 @@ cd -
source ./buildbots/send_telegram_message.sh
LAST_COMMIT_MESSAGE=$(git log --format=%s -n 1 HEAD -- ./$BROWSER_NAME/BUILD_NUMBER)
-BUILD_ALIAS="[[$(./upload.sh $1 --show-alias)]] $LAST_COMMIT_MESSAGE"
+BUILD_ALIAS="[[$BUILD_FLAVOR r$BUILD_NUMBER]] $LAST_COMMIT_MESSAGE"
+
send_telegram_message "$BUILD_ALIAS -- started ⏳"
-echo "-- preparing checkout"
-if ! ./prepare_checkout.sh $BROWSER_NAME; then
- send_telegram_message "$BUILD_ALIAS -- ./prepare_checkout.sh failed! ❌"
- exit 1
-fi
+if [[ "$BUILD_FLAVOR" == "webkit-gtk-wpe" ]]; then
+ echo "-- combining binaries together"
+ if ! ./webkit/download_gtk_and_wpe_and_zip_together.sh $ZIP_PATH; then
+ send_telegram_message "$BUILD_ALIAS -- ./download_gtk_and_wpe_and_zip_together.sh failed! ❌"
+ exit 1
+ fi
+else
+ echo "-- preparing checkout"
+ if ! ./prepare_checkout.sh $BROWSER_NAME; then
+ send_telegram_message "$BUILD_ALIAS -- ./prepare_checkout.sh failed! ❌"
+ exit 1
+ fi
-echo "-- cleaning"
-if ! ./$BROWSER_NAME/clean.sh; then
- send_telegram_message "$BUILD_ALIAS -- ./clean.sh failed! ❌"
- exit 1
-fi
+ echo "-- cleaning"
+ if ! ./$BROWSER_NAME/clean.sh; then
+ send_telegram_message "$BUILD_ALIAS -- ./clean.sh failed! ❌"
+ exit 1
+ fi
-echo "-- building"
-if ! ./$BROWSER_NAME/build.sh "$EXTRA_BUILD_ARGS"; then
- send_telegram_message "$BUILD_ALIAS -- ./build.sh failed! ❌"
- exit 1
-fi
+ echo "-- building"
+ if ! ./$BROWSER_NAME/build.sh "$EXTRA_BUILD_ARGS"; then
+ send_telegram_message "$BUILD_ALIAS -- ./build.sh failed! ❌"
+ exit 1
+ fi
-echo "-- archiving to $ZIP_PATH"
-if ! ./$BROWSER_NAME/archive.sh $ZIP_PATH; then
- send_telegram_message "$BUILD_ALIAS -- ./archive.sh failed! ❌"
- exit 1
+ echo "-- archiving to $ZIP_PATH"
+ if ! ./$BROWSER_NAME/archive.sh $ZIP_PATH "$EXTRA_ARCHIVE_ARGS"; then
+ send_telegram_message "$BUILD_ALIAS -- ./archive.sh failed! ❌"
+ exit 1
+ fi
fi
echo "-- uploading"
-if ! ./upload.sh $1 $ZIP_PATH; then
+if ! ./upload.sh $BUILD_FLAVOR $ZIP_PATH; then
send_telegram_message "$BUILD_ALIAS -- ./upload.sh failed! ❌"
exit 1
fi
-send_telegram_message "$BUILD_ALIAS -- uploaded ✅"
+UPLOAD_SIZE=$(du -h "$ZIP_PATH" | awk '{print $1}')
+send_telegram_message "$BUILD_ALIAS -- $UPLOAD_SIZE uploaded ✅"
diff --git a/browser_patches/download.sh b/browser_patches/download.sh
new file mode 100755
index 0000000000..c45be6a847
--- /dev/null
+++ b/browser_patches/download.sh
@@ -0,0 +1,70 @@
+#!/bin/bash
+set -e
+set +x
+
+trap "cd $(pwd -P)" EXIT
+cd "$(dirname "$0")"
+
+if [[ ($1 == '--help') || ($1 == '-h') ]]; then
+ echo "usage: $(basename $0) [webkit-gtk|webkit-wpe] [zip-path]"
+ echo
+ echo "Download .zip of a browser build."
+ echo
+ echo "NOTE: \$AZ_ACCOUNT_KEY (azure account name) and \$AZ_ACCOUNT_NAME (azure account name)"
+ echo "env variables are required to download builds from CDN."
+ exit 0
+fi
+
+if [[ (-z $AZ_ACCOUNT_KEY) || (-z $AZ_ACCOUNT_NAME) ]]; then
+ echo "ERROR: Either \$AZ_ACCOUNT_KEY or \$AZ_ACCOUNT_NAME environment variable is missing."
+ echo " 'Azure Account Name' and 'Azure Account Key' secrets that are required"
+ echo " to download builds from Azure CDN."
+ exit 1
+fi
+
+if [[ $# < 1 ]]; then
+ echo "missing build flavor"
+ echo "try '$(basename $0) --help' for more information"
+ exit 1
+fi
+
+BUILD_FLAVOR="$1"
+BROWSER_NAME=""
+BLOB_NAME=""
+if [[ "$BUILD_FLAVOR" == "webkit-gtk" ]]; then
+ BROWSER_NAME="webkit"
+ BLOB_NAME="minibrowser-gtk.zip"
+elif [[ "$BUILD_FLAVOR" == "webkit-wpe" ]]; then
+ BROWSER_NAME="webkit"
+ BLOB_NAME="minibrowser-wpe.zip"
+else
+ echo ERROR: unsupported build flavor - "$BUILD_FLAVOR"
+ exit 1
+fi
+
+BUILD_NUMBER=$(cat ./$BROWSER_NAME/BUILD_NUMBER)
+BLOB_PATH="$BROWSER_NAME/$BUILD_NUMBER/$BLOB_NAME"
+
+if [[ $# < 2 ]]; then
+ echo "missing path to zip archive to download to"
+ echo "try '$(basename $0) --help' for more information"
+ exit 1
+fi
+
+ZIP_PATH="$2"
+
+if [[ -f $ZIP_PATH ]]; then
+ echo "ERROR: $ZIP_PATH exists"
+ exit 1
+fi
+if ! [[ $ZIP_PATH == *.zip ]]; then
+ echo "ERROR: $ZIP_PATH is not a zip archive (must have a .zip extension)"
+ exit 1
+fi
+az storage blob download -c builds --account-key $AZ_ACCOUNT_KEY --account-name $AZ_ACCOUNT_NAME -f $ZIP_PATH -n "$BLOB_PATH"
+
+echo "DOWNLOAD SUCCESSFUL!"
+echo "-- SRC: $ZIP_PATH"
+echo "-- SIZE: $(du -h "$ZIP_PATH" | awk '{print $1}')"
+echo "-- DST: $BLOB_PATH"
+
diff --git a/browser_patches/firefox/BUILD_NUMBER b/browser_patches/firefox/BUILD_NUMBER
index 7cf69b1871..8618e6130b 100644
--- a/browser_patches/firefox/BUILD_NUMBER
+++ b/browser_patches/firefox/BUILD_NUMBER
@@ -1 +1 @@
-1014
+1016
diff --git a/browser_patches/firefox/archive.sh b/browser_patches/firefox/archive.sh
index 1c845e4b1a..e173a93721 100755
--- a/browser_patches/firefox/archive.sh
+++ b/browser_patches/firefox/archive.sh
@@ -10,12 +10,6 @@ if [[ ("$1" == "-h") || ("$1" == "--help") ]]; then
exit 0
fi
-if [[ $# != 1 ]]; then
- echo "error: missing zip output path"
- echo "try '$(basename $0) --help' for details"
- exit 1
-fi
-
ZIP_PATH=$1
if [[ $ZIP_PATH != /* ]]; then
echo "ERROR: path $ZIP_PATH is not absolute"
diff --git a/browser_patches/firefox/patches/bootstrap.diff b/browser_patches/firefox/patches/bootstrap.diff
index 3930e87f3a..62055f11d3 100644
--- a/browser_patches/firefox/patches/bootstrap.diff
+++ b/browser_patches/firefox/patches/bootstrap.diff
@@ -1824,10 +1824,10 @@ index 0000000000000000000000000000000000000000..2508cce41565023b7fee9c7b85afe8ec
+
diff --git a/testing/juggler/content/PageAgent.js b/testing/juggler/content/PageAgent.js
new file mode 100644
-index 0000000000000000000000000000000000000000..03c4c9717148169110f7e7d19306a76984ed4860
+index 0000000000000000000000000000000000000000..37ab5f56739cfd16200a4ada9f4cf83436688eba
--- /dev/null
+++ b/testing/juggler/content/PageAgent.js
-@@ -0,0 +1,721 @@
+@@ -0,0 +1,843 @@
+"use strict";
+const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
+const Ci = Components.interfaces;
@@ -1839,11 +1839,28 @@ index 0000000000000000000000000000000000000000..03c4c9717148169110f7e7d19306a769
+
+const helper = new Helper();
+
++const registeredWorkerListeners = new Map();
++const workerListener = {
++ QueryInterface: ChromeUtils.generateQI([Ci.nsIWorkerDebuggerListener]),
++ onMessage: (wrapped) => {
++ const message = JSON.parse(wrapped);
++ const listener = registeredWorkerListeners.get(message.workerId);
++ if (listener)
++ listener(message);
++ },
++ onClose: () => {
++ },
++ onError: (filename, lineno, message) => {
++ dump(`Error in worker: ${message} @${filename}:${lineno}\n`);
++ },
++};
++
+class FrameData {
+ constructor(agent, frame) {
+ this._agent = agent;
+ this._frame = frame;
+ this._isolatedWorlds = new Map();
++ this._workers = new Map();
+ this.reset();
+ }
+
@@ -1913,6 +1930,59 @@ index 0000000000000000000000000000000000000000..03c4c9717148169110f7e7d19306a769
+ }
+ throw new Error('Cannot find object with id = ' + objectId);
+ }
++
++ workerCreated(workerDebugger) {
++ const workerId = helper.generateId();
++ this._workers.set(workerId, workerDebugger);
++ this._agent._session.emitEvent('Page.workerCreated', {
++ workerId,
++ frameId: this._frame.id(),
++ url: workerDebugger.url,
++ });
++ // Note: this does not interoperate with firefox devtools.
++ if (!workerDebugger.isInitialized) {
++ workerDebugger.initialize('chrome://juggler/content/content/WorkerMain.js');
++ workerDebugger.addListener(workerListener);
++ }
++ registeredWorkerListeners.set(workerId, message => {
++ if (message.command === 'dispatch') {
++ this._agent._session.emitEvent('Page.dispatchMessageFromWorker', {
++ workerId,
++ message: message.message,
++ });
++ }
++ if (message.command === 'console')
++ this._agent._runtime.filterConsoleMessage(message.hash);
++ });
++ workerDebugger.postMessage(JSON.stringify({command: 'connect', workerId}));
++ }
++
++ workerDestroyed(wd) {
++ for (const [workerId, workerDebugger] of this._workers) {
++ if (workerDebugger === wd) {
++ this._agent._session.emitEvent('Page.workerDestroyed', {
++ workerId,
++ });
++ this._workers.delete(workerId);
++ registeredWorkerListeners.delete(workerId);
++ }
++ }
++ }
++
++ sendMessageToWorker(workerId, message) {
++ const workerDebugger = this._workers.get(workerId);
++ if (!workerDebugger)
++ throw new Error('Cannot find worker with id "' + workerId + '"');
++ workerDebugger.postMessage(JSON.stringify({command: 'dispatch', workerId, message}));
++ }
++
++ dispose() {
++ for (const [workerId, workerDebugger] of this._workers) {
++ workerDebugger.postMessage(JSON.stringify({command: 'disconnect', workerId}));
++ registeredWorkerListeners.delete(workerId);
++ }
++ this._workers.clear();
++ }
+}
+
+class PageAgent {
@@ -1934,6 +2004,24 @@ index 0000000000000000000000000000000000000000..03c4c9717148169110f7e7d19306a769
+ this._docShell = docShell;
+ this._initialDPPX = docShell.contentViewer.overrideDPPX;
+ this._customScrollbars = null;
++
++ this._wdm = Cc["@mozilla.org/dom/workers/workerdebuggermanager;1"].createInstance(Ci.nsIWorkerDebuggerManager);
++ this._wdmListener = {
++ QueryInterface: ChromeUtils.generateQI([Ci.nsIWorkerDebuggerManagerListener]),
++ onRegister: this._onWorkerCreated.bind(this),
++ onUnregister: this._onWorkerDestroyed.bind(this),
++ };
++
++ this._runtime.setOnErrorFromWorker((domWindow, message, stack) => {
++ const frame = this._frameTree.frameForDocShell(domWindow.docShell);
++ if (!frame)
++ return;
++ this._session.emitEvent('Page.uncaughtError', {
++ frameId: frame.id(),
++ message,
++ stack,
++ });
++ });
+ }
+
+ async awaitViewportDimensions({width, height}) {
@@ -2042,12 +2130,43 @@ index 0000000000000000000000000000000000000000..03c4c9717148169110f7e7d19306a769
+ helper.on(this._frameTree, 'navigationaborted', this._onNavigationAborted.bind(this)),
+ helper.on(this._frameTree, 'samedocumentnavigation', this._onSameDocumentNavigation.bind(this)),
+ ];
++
++ this._wdm.addListener(this._wdmListener);
++ for (const workerDebugger of this._wdm.getWorkerDebuggerEnumerator())
++ this._onWorkerCreated(workerDebugger);
+ }
+
+ setInterceptFileChooserDialog({enabled}) {
+ this._docShell.fileInputInterceptionEnabled = !!enabled;
+ }
+
++ _frameForWorker(workerDebugger) {
++ if (workerDebugger.type !== Ci.nsIWorkerDebugger.TYPE_DEDICATED)
++ return null;
++ const docShell = workerDebugger.window.docShell;
++ const frame = this._frameTree.frameForDocShell(docShell);
++ return frame ? this._frameData.get(frame) : null;
++ }
++
++ _onWorkerCreated(workerDebugger) {
++ const frameData = this._frameForWorker(workerDebugger);
++ if (frameData)
++ frameData.workerCreated(workerDebugger);
++ }
++
++ _onWorkerDestroyed(workerDebugger) {
++ const frameData = this._frameForWorker(workerDebugger);
++ if (frameData)
++ frameData.workerDestroyed(workerDebugger);
++ }
++
++ sendMessageToWorker({frameId, workerId, message}) {
++ const frame = this._frameTree.frame(frameId);
++ if (!frame)
++ throw new Error('Failed to find frame with id = ' + frameId);
++ this._frameData.get(frame).sendMessageToWorker(workerId, message);
++ }
++
+ _filePickerShown(inputElement) {
+ if (inputElement.ownerGlobal.docShell !== this._docShell)
+ return;
@@ -2166,7 +2285,10 @@ index 0000000000000000000000000000000000000000..03c4c9717148169110f7e7d19306a769
+ }
+
+ dispose() {
++ for (const frameData of this._frameData.values())
++ frameData.dispose();
+ helper.removeListeners(this._eventListeners);
++ this._wdm.removeListener(this._wdmListener);
+ }
+
+ async navigate({frameId, url, referer}) {
@@ -2551,20 +2673,24 @@ index 0000000000000000000000000000000000000000..03c4c9717148169110f7e7d19306a769
+
diff --git a/testing/juggler/content/RuntimeAgent.js b/testing/juggler/content/RuntimeAgent.js
new file mode 100644
-index 0000000000000000000000000000000000000000..262011d8fda346078a6cfcb7aae5dac357fb9b60
+index 0000000000000000000000000000000000000000..5765d5c3b1de7b9383a80435b37b034d6951d981
--- /dev/null
+++ b/testing/juggler/content/RuntimeAgent.js
-@@ -0,0 +1,478 @@
+@@ -0,0 +1,545 @@
+"use strict";
-+const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
-+const {Helper} = ChromeUtils.import('chrome://juggler/content/Helper.js');
-+const {addDebuggerToGlobal} = ChromeUtils.import("resource://gre/modules/jsdebugger.jsm", {});
++// Note: this file should be loadabale with eval() into worker environment.
++// Avoid Components.*, ChromeUtils and global const variables.
+
-+const Ci = Components.interfaces;
-+const Cr = Components.results;
-+const Cu = Components.utils;
-+addDebuggerToGlobal(Cu.getGlobalForObject(this));
-+const helper = new Helper();
++if (!this.Debugger) {
++ // Worker has a Debugger defined already.
++ const {addDebuggerToGlobal} = ChromeUtils.import("resource://gre/modules/jsdebugger.jsm", {});
++ addDebuggerToGlobal(Components.utils.getGlobalForObject(this));
++}
++
++let lastId = 0;
++function generateId() {
++ return 'id-' + (++lastId);
++}
+
+const consoleLevelToProtocolType = {
+ 'dir': 'dir',
@@ -2603,13 +2729,39 @@ index 0000000000000000000000000000000000000000..262011d8fda346078a6cfcb7aae5dac3
+]);
+
+class RuntimeAgent {
-+ constructor(session) {
++ constructor(session, onWorkerConsoleMessage) {
+ this._debugger = new Debugger();
+ this._pendingPromises = new Map();
+ this._session = session;
+ this._executionContexts = new Map();
+ this._windowToExecutionContext = new Map();
-+ this._consoleServiceListener = {
++ this._eventListeners = [];
++ this._enabled = false;
++ this._filteredConsoleMessageHashes = new Set();
++ this._onErrorFromWorker = null;
++ this._onWorkerConsoleMessage = onWorkerConsoleMessage;
++ }
++
++ enable() {
++ if (this._enabled)
++ return;
++ this._enabled = true;
++ for (const executionContext of this._executionContexts.values())
++ this._notifyExecutionContextCreated(executionContext);
++
++ const isWorker = !!this._onWorkerConsoleMessage;
++ if (isWorker) {
++ this._registerConsoleEventHandler();
++ } else {
++ const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
++ this._registerConsoleServiceListener(Services);
++ this._registerConsoleObserver(Services);
++ }
++ }
++
++ _registerConsoleServiceListener(Services) {
++ const Ci = Components.interfaces;
++ const consoleServiceListener = {
+ QueryInterface: ChromeUtils.generateQI([Ci.nsIConsoleListener]),
+
+ observe: message => {
@@ -2618,6 +2770,11 @@ index 0000000000000000000000000000000000000000..262011d8fda346078a6cfcb7aae5dac3
+ return;
+ }
+ const errorWindow = Services.wm.getOuterWindowWithId(message.outerWindowID);
++ if (message.category === 'Web Worker' && (message.flags & Ci.nsIScriptError.exceptionFlag)) {
++ if (this._onErrorFromWorker)
++ this._onErrorFromWorker(errorWindow, message.message, '' + message.stack);
++ return;
++ }
+ const executionContext = this._windowToExecutionContext.get(errorWindow);
+ if (!executionContext)
+ return;
@@ -2641,47 +2798,67 @@ index 0000000000000000000000000000000000000000..262011d8fda346078a6cfcb7aae5dac3
+ });
+ },
+ };
-+
-+ this._eventListeners = [];
-+ this._enabled = false;
++ Services.console.registerListener(consoleServiceListener);
++ this._eventListeners.push(() => Services.console.unregisterListener(consoleServiceListener));
+ }
+
-+ _consoleAPICalled({wrappedJSObject}, topic, data) {
-+ const type = consoleLevelToProtocolType[wrappedJSObject.level];
++ _registerConsoleObserver(Services) {
++ const consoleObserver = ({wrappedJSObject}, topic, data) => {
++ const hash = this._consoleMessageHash(wrappedJSObject);
++ if (this._filteredConsoleMessageHashes.has(hash)) {
++ this._filteredConsoleMessageHashes.delete(hash);
++ return;
++ }
++ const executionContext = Array.from(this._executionContexts.values()).find(context => {
++ const domWindow = context._domWindow;
++ return domWindow && domWindow.windowUtils.currentInnerWindowID === wrappedJSObject.innerID;
++ });
++ if (!executionContext)
++ return;
++ this._onConsoleMessage(executionContext, wrappedJSObject);
++ };
++ Services.obs.addObserver(consoleObserver, "console-api-log-event");
++ this._eventListeners.push(() => Services.obs.removeObserver(consoleObserver, "console-api-log-event"));
++ }
++
++ _registerConsoleEventHandler() {
++ setConsoleEventHandler(message => {
++ this._onWorkerConsoleMessage(this._consoleMessageHash(message));
++ const executionContext = Array.from(this._executionContexts.values())[0];
++ this._onConsoleMessage(executionContext, message);
++ });
++ this._eventListeners.push(() => setConsoleEventHandler(null));
++ }
++
++ filterConsoleMessage(messageHash) {
++ this._filteredConsoleMessageHashes.add(messageHash);
++ }
++
++ setOnErrorFromWorker(onErrorFromWorker) {
++ this._onErrorFromWorker = onErrorFromWorker;
++ }
++
++ _consoleMessageHash(message) {
++ return `${message.timeStamp}/${message.filename}/${message.lineNumber}/${message.columnNumber}/${message.sourceId}/${message.level}`;
++ }
++
++ _onConsoleMessage(executionContext, message) {
++ const type = consoleLevelToProtocolType[message.level];
+ if (!type)
+ return;
-+ const executionContext = Array.from(this._executionContexts.values()).find(context => {
-+ const domWindow = context._domWindow;
-+ return domWindow && domWindow.windowUtils.currentInnerWindowID === wrappedJSObject.innerID;
-+ });
-+ if (!executionContext)
-+ return;
-+ const args = wrappedJSObject.arguments.map(arg => executionContext.rawValueToRemoteObject(arg));
++ const args = message.arguments.map(arg => executionContext.rawValueToRemoteObject(arg));
+ this._session.emitEvent('Runtime.console', {
+ args,
+ type,
+ executionContextId: executionContext.id(),
+ location: {
-+ lineNumber: wrappedJSObject.lineNumber - 1,
-+ columnNumber: wrappedJSObject.columnNumber - 1,
-+ url: wrappedJSObject.filename,
++ lineNumber: message.lineNumber - 1,
++ columnNumber: message.columnNumber - 1,
++ url: message.filename,
+ },
+ });
+ }
+
-+ enable() {
-+ if (this._enabled)
-+ return;
-+ this._enabled = true;
-+ for (const executionContext of this._executionContexts.values())
-+ this._notifyExecutionContextCreated(executionContext);
-+ Services.console.registerListener(this._consoleServiceListener);
-+ this._eventListeners = [
-+ () => Services.console.unregisterListener(this._consoleServiceListener),
-+ helper.addObserver(this._consoleAPICalled.bind(this), "console-api-log-event"),
-+ ];
-+ }
-+
+ _notifyExecutionContextCreated(executionContext) {
+ if (!this._enabled)
+ return;
@@ -2700,7 +2877,9 @@ index 0000000000000000000000000000000000000000..262011d8fda346078a6cfcb7aae5dac3
+ }
+
+ dispose() {
-+ helper.removeListeners(this._eventListeners);
++ for (const tearDown of this._eventListeners)
++ tearDown.call(null);
++ this._eventListeners = [];
+ }
+
+ async _awaitPromise(executionContext, obj, exceptionDetails = {}) {
@@ -2742,9 +2921,11 @@ index 0000000000000000000000000000000000000000..262011d8fda346078a6cfcb7aae5dac3
+ }
+
+ createExecutionContext(domWindow, contextGlobal, auxData) {
-+ const context = new ExecutionContext(this, domWindow, this._debugger.addDebuggee(contextGlobal), auxData);
++ // Note: domWindow is null for workers.
++ const context = new ExecutionContext(this, domWindow, contextGlobal, this._debugger.addDebuggee(contextGlobal), auxData);
+ this._executionContexts.set(context._id, context);
-+ this._windowToExecutionContext.set(domWindow, context);
++ if (domWindow)
++ this._windowToExecutionContext.set(domWindow, context);
+ this._notifyExecutionContextCreated(context);
+ return context;
+ }
@@ -2765,9 +2946,10 @@ index 0000000000000000000000000000000000000000..262011d8fda346078a6cfcb7aae5dac3
+ }
+ if (!this._pendingPromises.size)
+ this._debugger.onPromiseSettled = undefined;
-+ this._debugger.removeDebuggee(destroyedContext._domWindow);
++ this._debugger.removeDebuggee(destroyedContext._contextGlobal);
+ this._executionContexts.delete(destroyedContext._id);
-+ this._windowToExecutionContext.delete(destroyedContext._domWindow);
++ if (destroyedContext._domWindow)
++ this._windowToExecutionContext.delete(destroyedContext._domWindow);
+ this._notifyExecutionContextDestroyed(destroyedContext);
+ }
+
@@ -2813,12 +2995,13 @@ index 0000000000000000000000000000000000000000..262011d8fda346078a6cfcb7aae5dac3
+}
+
+class ExecutionContext {
-+ constructor(runtime, domWindow, global, auxData) {
++ constructor(runtime, domWindow, contextGlobal, global, auxData) {
+ this._runtime = runtime;
+ this._domWindow = domWindow;
++ this._contextGlobal = contextGlobal;
+ this._global = global;
+ this._remoteObjects = new Map();
-+ this._id = helper.generateId();
++ this._id = generateId();
+ this._auxData = auxData;
+ this._jsonStringifyObject = this._global.executeInGlobal(`((stringify, dateProto, object) => {
+ const oldToJson = dateProto.toJSON;
@@ -2839,9 +3022,9 @@ index 0000000000000000000000000000000000000000..262011d8fda346078a6cfcb7aae5dac3
+ }
+
+ async evaluateScript(script, exceptionDetails = {}) {
-+ const userInputHelper = this._domWindow.windowUtils.setHandlingUserInput(true);
++ const userInputHelper = this._domWindow ? this._domWindow.windowUtils.setHandlingUserInput(true) : null;
+ let {success, obj} = this._getResult(this._global.executeInGlobal(script), exceptionDetails);
-+ userInputHelper.destruct();
++ userInputHelper && userInputHelper.destruct();
+ if (!success)
+ return null;
+ if (obj && obj.isPromise) {
@@ -2873,9 +3056,9 @@ index 0000000000000000000000000000000000000000..262011d8fda346078a6cfcb7aae5dac3
+ default: return this._toDebugger(arg.value);
+ }
+ });
-+ const userInputHelper = this._domWindow.windowUtils.setHandlingUserInput(true);
++ const userInputHelper = this._domWindow ? this._domWindow.windowUtils.setHandlingUserInput(true) : null;
+ let {success, obj} = this._getResult(funEvaluation.obj.apply(null, args), exceptionDetails);
-+ userInputHelper.destruct();
++ userInputHelper && userInputHelper.destruct();
+ if (!success)
+ return null;
+ if (obj && obj.isPromise) {
@@ -2898,9 +3081,15 @@ index 0000000000000000000000000000000000000000..262011d8fda346078a6cfcb7aae5dac3
+ return this._createRemoteObject(debuggerObj);
+ }
+
++ _instanceOf(debuggerObj, rawObj, className) {
++ if (this._domWindow)
++ return rawObj instanceof this._domWindow[className];
++ return this._global.executeInGlobalWithBindings('o instanceof this[className]', {o: debuggerObj, className: this._global.makeDebuggeeValue(className)}).return;
++ }
++
+ _createRemoteObject(debuggerObj) {
+ if (debuggerObj instanceof Debugger.Object) {
-+ const objectId = helper.generateId();
++ const objectId = generateId();
+ this._remoteObjects.set(objectId, debuggerObj);
+ const rawObj = debuggerObj.unsafeDereference();
+ const type = typeof rawObj;
@@ -2911,35 +3100,35 @@ index 0000000000000000000000000000000000000000..262011d8fda346078a6cfcb7aae5dac3
+ subtype = 'array';
+ else if (Object.is(rawObj, null))
+ subtype = 'null';
-+ else if (rawObj instanceof this._domWindow.Node)
++ else if (this._instanceOf(debuggerObj, rawObj, 'Node'))
+ subtype = 'node';
-+ else if (rawObj instanceof this._domWindow.RegExp)
++ else if (this._instanceOf(debuggerObj, rawObj, 'RegExp'))
+ subtype = 'regexp';
-+ else if (rawObj instanceof this._domWindow.Date)
++ else if (this._instanceOf(debuggerObj, rawObj, 'Date'))
+ subtype = 'date';
-+ else if (rawObj instanceof this._domWindow.Map)
++ else if (this._instanceOf(debuggerObj, rawObj, 'Map'))
+ subtype = 'map';
-+ else if (rawObj instanceof this._domWindow.Set)
++ else if (this._instanceOf(debuggerObj, rawObj, 'Set'))
+ subtype = 'set';
-+ else if (rawObj instanceof this._domWindow.WeakMap)
++ else if (this._instanceOf(debuggerObj, rawObj, 'WeakMap'))
+ subtype = 'weakmap';
-+ else if (rawObj instanceof this._domWindow.WeakSet)
++ else if (this._instanceOf(debuggerObj, rawObj, 'WeakSet'))
+ subtype = 'weakset';
-+ else if (rawObj instanceof this._domWindow.Error)
++ else if (this._instanceOf(debuggerObj, rawObj, 'Error'))
+ subtype = 'error';
-+ else if (rawObj instanceof this._domWindow.Promise)
++ else if (this._instanceOf(debuggerObj, rawObj, 'Promise'))
+ subtype = 'promise';
-+ else if ((rawObj instanceof this._domWindow.Int8Array) || (rawObj instanceof this._domWindow.Uint8Array) ||
-+ (rawObj instanceof this._domWindow.Uint8ClampedArray) || (rawObj instanceof this._domWindow.Int16Array) ||
-+ (rawObj instanceof this._domWindow.Uint16Array) || (rawObj instanceof this._domWindow.Int32Array) ||
-+ (rawObj instanceof this._domWindow.Uint32Array) || (rawObj instanceof this._domWindow.Float32Array) ||
-+ (rawObj instanceof this._domWindow.Float64Array)) {
++ else if ((this._instanceOf(debuggerObj, rawObj, 'Int8Array')) || (this._instanceOf(debuggerObj, rawObj, 'Uint8Array')) ||
++ (this._instanceOf(debuggerObj, rawObj, 'Uint8ClampedArray')) || (this._instanceOf(debuggerObj, rawObj, 'Int16Array')) ||
++ (this._instanceOf(debuggerObj, rawObj, 'Uint16Array')) || (this._instanceOf(debuggerObj, rawObj, 'Int32Array')) ||
++ (this._instanceOf(debuggerObj, rawObj, 'Uint32Array')) || (this._instanceOf(debuggerObj, rawObj, 'Float32Array')) ||
++ (this._instanceOf(debuggerObj, rawObj, 'Float64Array'))) {
+ subtype = 'typedarray';
+ }
+ return {objectId, type, subtype};
+ }
+ if (typeof debuggerObj === 'symbol') {
-+ const objectId = helper.generateId();
++ const objectId = generateId();
+ this._remoteObjects.set(objectId, debuggerObj);
+ return {objectId, type: 'symbol'};
+ }
@@ -3124,6 +3313,79 @@ index 0000000000000000000000000000000000000000..caee4df323d0a526ed7e38947c41c643
+var EXPORTED_SYMBOLS = ['ScrollbarManager'];
+this.ScrollbarManager = ScrollbarManager;
+
+diff --git a/testing/juggler/content/WorkerMain.js b/testing/juggler/content/WorkerMain.js
+new file mode 100644
+index 0000000000000000000000000000000000000000..73cdce649608f068e59e1ff7808883c4482bff7e
+--- /dev/null
++++ b/testing/juggler/content/WorkerMain.js
+@@ -0,0 +1,67 @@
++"use strict";
++loadSubScript('chrome://juggler/content/content/RuntimeAgent.js');
++
++class WorkerSession {
++ constructor(workerId) {
++ this._workerId = workerId;
++ this._agents = {
++ Runtime: new RuntimeAgent(this, hash => this._send({command: 'console', hash})),
++ };
++ this._agents.Runtime.enable();
++ this._agents.Runtime.createExecutionContext(null /* domWindow */, global, {});
++ }
++
++ _send(command) {
++ postMessage(JSON.stringify({...command, workerId: this._workerId}));
++ }
++
++ _dispatchProtocolMessage(protocolMessage) {
++ this._send({command: 'dispatch', message: JSON.stringify(protocolMessage)});
++ }
++
++ emitEvent(eventName, params) {
++ this._dispatchProtocolMessage({method: eventName, params});
++ }
++
++ async _onMessage(message) {
++ const object = JSON.parse(message);
++ const id = object.id;
++ try {
++ const [domainName, methodName] = object.method.split('.');
++ const agent = this._agents[domainName];
++ if (!agent)
++ throw new Error(`unknown domain: ${domainName}`);
++ const handler = agent[methodName];
++ if (!handler)
++ throw new Error(`unknown method: ${domainName}.${methodName}`);
++ const result = await handler.call(agent, object.params);
++ this._dispatchProtocolMessage({id, result});
++ } catch (e) {
++ this._dispatchProtocolMessage({id, error: e.message + '\n' + e.stack});
++ }
++ }
++
++ dispose() {
++ for (const agent of Object.values(this._agents))
++ agent.dispose();
++ }
++}
++
++const workerSessions = new Map();
++
++this.addEventListener('message', event => {
++ const data = JSON.parse(event.data);
++ if (data.command === 'connect') {
++ const session = new WorkerSession(data.workerId);
++ workerSessions.set(data.workerId, session);
++ }
++ if (data.command === 'disconnect') {
++ const session = workerSessions.get(data.workerId);
++ session.dispose();
++ workerSessions.delete(data.workerId);
++ }
++ if (data.command === 'dispatch') {
++ const session = workerSessions.get(data.workerId);
++ session._onMessage(data.message);
++ }
++});
diff --git a/testing/juggler/content/floating-scrollbars.css b/testing/juggler/content/floating-scrollbars.css
new file mode 100644
index 0000000000000000000000000000000000000000..7709bdd34c65062fc63684ef17fc792d3991d965
@@ -3243,10 +3505,10 @@ index 0000000000000000000000000000000000000000..8585092e04e7e763a0c115c28363e505
+
diff --git a/testing/juggler/jar.mn b/testing/juggler/jar.mn
new file mode 100644
-index 0000000000000000000000000000000000000000..27f5a15fd7f14385bb1f080d5965d90e60423d1a
+index 0000000000000000000000000000000000000000..76377927a8c9af3cac3b028ff754491966d03ba3
--- /dev/null
+++ b/testing/juggler/jar.mn
-@@ -0,0 +1,29 @@
+@@ -0,0 +1,30 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
@@ -3272,6 +3534,7 @@ index 0000000000000000000000000000000000000000..27f5a15fd7f14385bb1f080d5965d90e
+ content/content/NetworkMonitor.js (content/NetworkMonitor.js)
+ content/content/PageAgent.js (content/PageAgent.js)
+ content/content/RuntimeAgent.js (content/RuntimeAgent.js)
++ content/content/WorkerMain.js (content/WorkerMain.js)
+ content/content/ScrollbarManager.js (content/ScrollbarManager.js)
+ content/content/floating-scrollbars.css (content/floating-scrollbars.css)
+ content/content/hidden-scrollbars.css (content/hidden-scrollbars.css)
@@ -3392,10 +3655,10 @@ index 0000000000000000000000000000000000000000..708059a95b3a01f3d9c7b7ef4714ee6f
+this.BrowserHandler = BrowserHandler;
diff --git a/testing/juggler/protocol/Dispatcher.js b/testing/juggler/protocol/Dispatcher.js
new file mode 100644
-index 0000000000000000000000000000000000000000..7b3a6fa4fe7a2b50a78ed446fbf5537504994798
+index 0000000000000000000000000000000000000000..956988738079272be8d3998dcbbaa91abc415fcc
--- /dev/null
+++ b/testing/juggler/protocol/Dispatcher.js
-@@ -0,0 +1,255 @@
+@@ -0,0 +1,254 @@
+const {TargetRegistry} = ChromeUtils.import("chrome://juggler/content/TargetRegistry.js");
+const {protocol, checkScheme} = ChromeUtils.import("chrome://juggler/content/protocol/Protocol.js");
+const {Helper} = ChromeUtils.import('chrome://juggler/content/Helper.js');
@@ -3630,7 +3893,6 @@ index 0000000000000000000000000000000000000000..7b3a6fa4fe7a2b50a78ed446fbf55375
+
+ _onMessage({data}) {
+ if (data.id) {
-+ let id = data.id;
+ const {resolve, reject} = this._pendingMessages.get(data.id);
+ this._pendingMessages.delete(data.id);
+ if (data.error)
@@ -3813,10 +4075,10 @@ index 0000000000000000000000000000000000000000..f5e7e919594b3778fd3046bf69d34878
+this.NetworkHandler = NetworkHandler;
diff --git a/testing/juggler/protocol/PageHandler.js b/testing/juggler/protocol/PageHandler.js
new file mode 100644
-index 0000000000000000000000000000000000000000..bf59b2afa8692d02fd0ce664eec2e9827a8209d2
+index 0000000000000000000000000000000000000000..23a32be2200e90e2e05d31aec85874a829cb1bbe
--- /dev/null
+++ b/testing/juggler/protocol/PageHandler.js
-@@ -0,0 +1,281 @@
+@@ -0,0 +1,285 @@
+"use strict";
+
+const {Helper} = ChromeUtils.import('chrome://juggler/content/Helper.js');
@@ -4039,6 +4301,10 @@ index 0000000000000000000000000000000000000000..bf59b2afa8692d02fd0ce664eec2e982
+ async handleFileChooser(options) {
+ return await this._contentSession.send('Page.handleFileChooser', options);
+ }
++
++ async sendMessageToWorker(options) {
++ return await this._contentSession.send('Page.sendMessageToWorker', options);
++ }
+}
+
+class Dialog {
@@ -4249,10 +4515,10 @@ index 0000000000000000000000000000000000000000..78b6601b91d0b7fcda61114e6846aa07
+this.EXPORTED_SYMBOLS = ['t', 'checkScheme'];
diff --git a/testing/juggler/protocol/Protocol.js b/testing/juggler/protocol/Protocol.js
new file mode 100644
-index 0000000000000000000000000000000000000000..75b5276d085bd4217389cd05099895ebec2438b6
+index 0000000000000000000000000000000000000000..1eecb6120f101cb7506fcf8d40c177089e62671b
--- /dev/null
+++ b/testing/juggler/protocol/Protocol.js
-@@ -0,0 +1,712 @@
+@@ -0,0 +1,731 @@
+const {t, checkScheme} = ChromeUtils.import('chrome://juggler/content/protocol/PrimitiveTypes.js');
+
+// Protocol-specific types.
@@ -4738,6 +5004,18 @@ index 0000000000000000000000000000000000000000..75b5276d085bd4217389cd05099895eb
+ executionContextId: t.String,
+ element: runtimeTypes.RemoteObject
+ },
++ 'workerCreated': {
++ workerId: t.String,
++ frameId: t.String,
++ url: t.String,
++ },
++ 'workerDestroyed': {
++ workerId: t.String,
++ },
++ 'dispatchMessageFromWorker': {
++ workerId: t.String,
++ message: t.String,
++ },
+ },
+
+ methods: {
@@ -4940,6 +5218,13 @@ index 0000000000000000000000000000000000000000..75b5276d085bd4217389cd05099895eb
+ enabled: t.Boolean,
+ },
+ },
++ 'sendMessageToWorker': {
++ params: {
++ frameId: t.String,
++ workerId: t.String,
++ message: t.String,
++ },
++ },
+ },
+};
+
diff --git a/browser_patches/tools/check_cdn.sh b/browser_patches/tools/check_cdn.sh
index 4bb1e2c355..1343c4ff4e 100755
--- a/browser_patches/tools/check_cdn.sh
+++ b/browser_patches/tools/check_cdn.sh
@@ -36,13 +36,15 @@ FFOX_ALIASES=(
WK_REVISION=$(cat ../webkit/BUILD_NUMBER)
WK_ARCHIVES=(
- "$HOST/webkit/%s/minibrowser-linux.zip"
+ "$HOST/webkit/%s/minibrowser-gtk.zip"
+ "$HOST/webkit/%s/minibrowser-wpe.zip"
"$HOST/webkit/%s/minibrowser-mac-10.14.zip"
"$HOST/webkit/%s/minibrowser-mac-10.15.zip"
"$HOST/webkit/%s/minibrowser-win64.zip"
)
WK_ALIASES=(
- "WK-LINUX"
+ "WK-GTK"
+ "WK-WPE"
"WK-MAC-10.14"
"WK-MAC-10.15"
"WK-WIN64"
diff --git a/browser_patches/upload.sh b/browser_patches/upload.sh
index e69810bdd3..38d54a2ea0 100755
--- a/browser_patches/upload.sh
+++ b/browser_patches/upload.sh
@@ -6,7 +6,7 @@ trap "cd $(pwd -P)" EXIT
cd "$(dirname "$0")"
if [[ ($1 == '--help') || ($1 == '-h') ]]; then
- echo "usage: $(basename $0) [firefox|firefox-win64|webkit] [--check] [zip-path]"
+ echo "usage: $(basename $0) [firefox-linux|firefox-win32|firefox-win64|webkit-gtk|webkit-wpe|webkit-gtk-wpe|webkit-win64|webkit-mac-10.14|webkit-mac-10.15] [--check] [zip-path]"
echo
echo "Upload .zip as a browser build."
echo
@@ -30,65 +30,48 @@ if [[ $# < 1 ]]; then
echo "try '$(basename $0) --help' for more information"
exit 1
fi
-BROWSER_NAME=""
-BUILD_NUMBER=""
-BLOB_NAME=""
-ALIAS=""
-if [[ ("$1" == "firefox") || ("$1" == "firefox/") ]]; then
- BUILD_NUMBER=$(cat "$PWD/firefox/BUILD_NUMBER")
+BUILD_FLAVOR="$1"
+BROWSER_NAME=""
+BLOB_NAME=""
+if [[ "$BUILD_FLAVOR" == "firefox-linux" ]]; then
BROWSER_NAME="firefox"
- if [[ "$(uname)" == "Darwin" ]]; then
- BLOB_NAME="firefox-mac.zip"
- ALIAS="firefox-mac r$BUILD_NUMBER"
- elif [[ "$(uname)" == "Linux" ]]; then
- BLOB_NAME="firefox-linux.zip"
- ALIAS="ff-linux r$BUILD_NUMBER"
- elif [[ "$(uname)" == MINGW* ]]; then
- BLOB_NAME="firefox-win32.zip"
- ALIAS="ff-win32 r$BUILD_NUMBER"
- else
- echo "ERROR: unsupported platform - $(uname)"
- exit 1
- fi
-elif [[ ("$1" == "firefox-win64") || ("$1" == "firefox-win64/") ]]; then
- BUILD_NUMBER=$(cat "$PWD/firefox/BUILD_NUMBER")
+ BLOB_NAME="firefox-linux.zip"
+elif [[ "$BUILD_FLAVOR" == "firefox-mac" ]]; then
BROWSER_NAME="firefox"
- if [[ "$(uname)" == MINGW* ]]; then
- BLOB_NAME="firefox-win64.zip"
- ALIAS="ff-win64 r$BUILD_NUMBER"
- else
- echo "ERROR: unsupported platform for browser '$1' - $(uname)"
- exit 1
- fi
-elif [[ ("$1" == "webkit") || ("$1" == "webkit/") ]]; then
- BUILD_NUMBER=$(cat "$PWD/webkit/BUILD_NUMBER")
+ BLOB_NAME="firefox-mac.zip"
+elif [[ "$BUILD_FLAVOR" == "firefox-win32" ]]; then
+ BROWSER_NAME="firefox"
+ BLOB_NAME="firefox-win32.zip"
+elif [[ "$BUILD_FLAVOR" == "firefox-win64" ]]; then
+ BROWSER_NAME="firefox"
+ BLOB_NAME="firefox-win64.zip"
+elif [[ "$BUILD_FLAVOR" == "webkit-gtk" ]]; then
BROWSER_NAME="webkit"
- if [[ "$(uname)" == "Darwin" ]]; then
- MAC_MAJOR_MINOR_VERSION=$(sw_vers -productVersion | grep -o '^\d\+.\d\+')
- BLOB_NAME="minibrowser-mac-$MAC_MAJOR_MINOR_VERSION.zip"
- ALIAS="webkit-mac-$MAC_MAJOR_MINOR_VERSION r$BUILD_NUMBER"
- elif [[ "$(uname)" == "Linux" ]]; then
- BLOB_NAME="minibrowser-linux.zip"
- ALIAS="webkit-linux r$BUILD_NUMBER"
- elif [[ "$(uname)" == MINGW* ]]; then
- BLOB_NAME="minibrowser-win64.zip"
- ALIAS="webkit-win64 r$BUILD_NUMBER"
- else
- echo "ERROR: unsupported platform - $(uname)"
- exit 1
- fi
+ BLOB_NAME="minibrowser-gtk.zip"
+elif [[ "$BUILD_FLAVOR" == "webkit-wpe" ]]; then
+ BROWSER_NAME="webkit"
+ BLOB_NAME="minibrowser-wpe.zip"
+elif [[ "$BUILD_FLAVOR" == "webkit-gtk-wpe" ]]; then
+ BROWSER_NAME="webkit"
+ BLOB_NAME="minibrowser-gtk-wpe.zip"
+elif [[ "$BUILD_FLAVOR" == "webkit-win64" ]]; then
+ BROWSER_NAME="webkit"
+ BLOB_NAME="minibrowser-win64.zip"
+elif [[ "$BUILD_FLAVOR" == "webkit-mac-10.14" ]]; then
+ BROWSER_NAME="webkit"
+ BLOB_NAME="minibrowser-mac-10.14.zip"
+elif [[ "$BUILD_FLAVOR" == "webkit-mac-10.15" ]]; then
+ BROWSER_NAME="webkit"
+ BLOB_NAME="minibrowser-mac-10.15.zip"
else
- echo ERROR: unknown browser to export - "$1"
+ echo ERROR: unknown build flavor - "$BUILD_FLAVOR"
exit 1
fi
-if [[ ("$2" == '--show-alias') || ("$3" == '--show-alias') ]]; then
- echo $ALIAS
- exit 0
-fi
-
+BUILD_NUMBER=$(cat ./$BROWSER_NAME/BUILD_NUMBER)
BLOB_PATH="$BROWSER_NAME/$BUILD_NUMBER/$BLOB_NAME"
+
if [[ ("$2" == '--check') || ("$3" == '--check') ]]; then
EXISTS=$(az storage blob exists -c builds --account-key $AZ_ACCOUNT_KEY --account-name $AZ_ACCOUNT_NAME -n "$BLOB_PATH" --query "exists")
if [[ $EXISTS == "true" ]]; then
@@ -103,7 +86,9 @@ if [[ $# < 2 ]]; then
echo "try '$(basename $0) --help' for more information"
exit 1
fi
-ZIP_PATH=$2
+
+ZIP_PATH="$2"
+
if ! [[ -f $ZIP_PATH ]]; then
echo "ERROR: $ZIP_PATH does not exist"
exit 1
diff --git a/browser_patches/webkit/BUILD_NUMBER b/browser_patches/webkit/BUILD_NUMBER
index 54ba9c1112..d878b321af 100644
--- a/browser_patches/webkit/BUILD_NUMBER
+++ b/browser_patches/webkit/BUILD_NUMBER
@@ -1 +1 @@
-1097
+1100
diff --git a/browser_patches/webkit/archive.sh b/browser_patches/webkit/archive.sh
index b97a1db8c2..161aeed18a 100755
--- a/browser_patches/webkit/archive.sh
+++ b/browser_patches/webkit/archive.sh
@@ -3,20 +3,15 @@ set -e
set +x
if [[ ("$1" == "-h") || ("$1" == "--help") ]]; then
- echo "usage: $(basename $0) [output-absolute-path]"
+ echo "usage: $(basename $0) [output-absolute-path] [--wpe]"
echo
echo "Generate distributable .zip archive from ./checkout folder that was previously built."
echo
exit 0
fi
-if [[ $# != 1 ]]; then
- echo "error: missing zip output path"
- echo "try '$(basename $0) --help' for details"
- exit 1
-fi
-
ZIP_PATH=$1
+USE_WPE=$2
if [[ $ZIP_PATH != /* ]]; then
echo "ERROR: path $ZIP_PATH is not absolute"
exit 1
@@ -55,20 +50,35 @@ createZipForLinux() {
local tmpdir=$(mktemp -d -t webkit-deploy-XXXXXXXXXX)
mkdir -p $tmpdir
- # copy all relevant binaries
- cp -t $tmpdir ./WebKitBuild/Release/bin/MiniBrowser ./WebKitBuild/Release/bin/WebKit*Process
# copy runner
cp -t $tmpdir ../pw_run.sh
# copy protocol
node ../concat_protocol.js > $tmpdir/protocol.json
- # copy all relevant shared objects
- LD_LIBRARY_PATH="$PWD/WebKitBuild/DependenciesGTK/Root/lib" ldd WebKitBuild/Release/bin/MiniBrowser | grep -o '[^ ]*WebKitBuild/[^ ]*' | xargs cp -t $tmpdir
- # we failed to nicely build libgdk_pixbuf - expect it in the env
- rm $tmpdir/libgdk_pixbuf*
+ if [[ -n $USE_WPE ]]; then
+ # copy all relevant binaries
+ cp -t $tmpdir ./WebKitBuild/Release/bin/MiniBrowser ./WebKitBuild/Release/bin/WPE*Process
+ # copy all relevant shared objects
+ LD_LIBRARY_PATH="$PWD/WebKitBuild/DependenciesWPE/Root/lib" ldd WebKitBuild/Release/bin/MiniBrowser | grep -o '[^ ]*WebKitBuild/[^ ]*' | xargs cp -t $tmpdir
+ LD_LIBRARY_PATH="$PWD/WebKitBuild/DependenciesWPE/Root/lib" ldd WebKitBuild/Release/bin/WPENetworkProcess | grep -o '[^ ]*WebKitBuild/[^ ]*' | xargs cp -t $tmpdir
+ LD_LIBRARY_PATH="$PWD/WebKitBuild/DependenciesWPE/Root/lib" ldd WebKitBuild/Release/bin/WPEWebProcess | grep -o '[^ ]*WebKitBuild/[^ ]*' | xargs cp -t $tmpdir
+ cd $tmpdir
+ ln -s libWPEBackend-fdo-1.0.so.1 libWPEBackend-fdo-1.0.so
+ cd -
+ else
+ # copy all relevant binaries
+ cp -t $tmpdir ./WebKitBuild/Release/bin/MiniBrowser ./WebKitBuild/Release/bin/WebKit*Process
+ # copy all relevant shared objects
+ LD_LIBRARY_PATH="$PWD/WebKitBuild/DependenciesGTK/Root/lib" ldd WebKitBuild/Release/bin/MiniBrowser | grep -o '[^ ]*WebKitBuild/[^ ]*' | xargs cp -t $tmpdir
+
+ # we failed to nicely build libgdk_pixbuf - expect it in the env
+ rm $tmpdir/libgdk_pixbuf*
+ fi
# tar resulting directory and cleanup TMP.
- zip -jr $ZIP_PATH $tmpdir
+ cd $tmpdir
+ zip --symlinks -r $ZIP_PATH ./
+ cd -
rm -rf $tmpdir
}
@@ -80,9 +90,12 @@ createZipForWindows() {
cp -t $tmpdir ./WebKitLibraries/win/bin64/*.dll
cd WebKitBuild/Release/bin64
cp -r -t $tmpdir WebKit.resources
- cp -t $tmpdir JavaScriptCore.dll MiniBrowserLib.dll WTF.dll WebKit.dll WebKit2.dll libEGL.dll libGLESv2.dll
+ cp -t $tmpdir JavaScriptCore.dll MiniBrowserLib.dll WTF.dll WebKit2.dll libEGL.dll libGLESv2.dll
cp -t $tmpdir MiniBrowser.exe WebKitNetworkProcess.exe WebKitWebProcess.exe
cd -
+ cd /c/WEBKIT_WIN64_LIBS
+ cp -t $tmpdir msvcp140.dll vcruntime140.dll vcruntime140_1.dll
+ cd -
# copy protocol
node ../concat_protocol.js > $tmpdir/protocol.json
diff --git a/browser_patches/webkit/build.sh b/browser_patches/webkit/build.sh
index 05be9233ad..1981176165 100755
--- a/browser_patches/webkit/build.sh
+++ b/browser_patches/webkit/build.sh
@@ -10,11 +10,17 @@ if [[ "$(uname)" == "Darwin" ]]; then
./Tools/Scripts/build-webkit --release --touch-events
elif [[ "$(uname)" == "Linux" ]]; then
cd "checkout"
- # Check that WebKitBuild exists and is not empty.
- if ! [[ (-d ./WebKitBuild) && (-n $(ls -1 ./WebKitBuild/)) ]]; then
- yes | DEBIAN_FRONTEND=noninteractive ./Tools/Scripts/update-webkitgtk-libs
+ if [[ "$1" == "--wpe" ]]; then
+ if ! [[ -d ./WebKitBuild/DependenciesWPE ]]; then
+ yes | DEBIAN_FRONTEND=noninteractive ./Tools/Scripts/update-webkitwpe-libs
+ fi
+ ./Tools/Scripts/build-webkit --wpe --release --touch-events MiniBrowser
+ else
+ if ! [[ -d ./WebKitBuild/DependenciesGTK ]]; then
+ yes | DEBIAN_FRONTEND=noninteractive ./Tools/Scripts/update-webkitgtk-libs
+ fi
+ ./Tools/Scripts/build-webkit --gtk --release --touch-events MiniBrowser
fi
- ./Tools/Scripts/build-webkit --gtk --release --touch-events MiniBrowser
elif [[ "$(uname)" == MINGW* ]]; then
/c/Windows/System32/cmd.exe "/c buildwin.bat"
else
diff --git a/browser_patches/webkit/download_gtk_and_wpe_and_zip_together.sh b/browser_patches/webkit/download_gtk_and_wpe_and_zip_together.sh
new file mode 100755
index 0000000000..05897e67a2
--- /dev/null
+++ b/browser_patches/webkit/download_gtk_and_wpe_and_zip_together.sh
@@ -0,0 +1,79 @@
+#!/bin/bash
+set -e
+set +x
+
+if [[ ("$1" == "-h") || ("$1" == "--help") ]]; then
+ echo "usage: $(basename $0) [ZIP-PATH]"
+ echo
+ echo "Generate a single .zip archive that contains both gtk and wpe builds"
+ echo
+ exit 0
+fi
+
+if [[ "$(uname)" != "Linux" ]]; then
+ echo "ERROR: this script works only on linux"
+ echo
+ exit 1
+fi
+
+ZIP_PATH="$1"
+if [[ $ZIP_PATH != /* ]]; then
+ echo "ERROR: path $ZIP_PATH is not absolute"
+ exit 1
+fi
+if [[ $ZIP_PATH != *.zip ]]; then
+ echo "ERROR: path $ZIP_PATH must have .zip extension"
+ exit 1
+fi
+if [[ -f $ZIP_PATH ]]; then
+ echo "ERROR: path $ZIP_PATH exists; can't do anything."
+ exit 1
+fi
+if ! [[ -d $(dirname $ZIP_PATH) ]]; then
+ echo "ERROR: folder for path $($ZIP_PATH) does not exist."
+ exit 1
+fi
+
+trap "cd $(pwd -P)" EXIT
+cd "$(dirname "$0")"
+
+# create a TMP directory to copy all necessary files
+TMPDIR=$(mktemp -d -t webkit-deploy-XXXXXXXXXX)
+GTK_ZIP_PATH=$(mktemp -t -u minibrowser-gtk-XXXXXX.zip)
+WPE_ZIP_PATH=$(mktemp -t -u minibrowser-wpe-XXXXXX.zip)
+../download.sh webkit-gtk $GTK_ZIP_PATH
+../download.sh webkit-wpe $WPE_ZIP_PATH
+
+# Create directory
+mkdir -p $TMPDIR
+
+# copy runner
+cp -t $TMPDIR ./pw_run.sh
+
+pushd $TMPDIR
+
+# Copy MiniBrowser-GTK
+mkdir minibrowser-gtk
+pushd minibrowser-gtk
+cp $GTK_ZIP_PATH archive.zip
+unzip archive.zip
+rm archive.zip
+popd
+
+# Copy MiniBrowser-WPE
+mkdir minibrowser-wpe
+pushd minibrowser-wpe
+cp $WPE_ZIP_PATH archive.zip
+unzip archive.zip
+rm archive.zip
+popd
+
+mv minibrowser-gtk/protocol.json .
+rm minibrowser-wpe/protocol.json
+
+zip --symlinks -r $ZIP_PATH ./
+popd
+
+rm -rf $TMPDIR
+rm -rf $WPE_ZIP_PATH
+rm -rf $GTK_ZIP_PATH
diff --git a/browser_patches/webkit/patches/bootstrap.diff b/browser_patches/webkit/patches/bootstrap.diff
index aaf1b22a5f..c9ad30be39 100644
--- a/browser_patches/webkit/patches/bootstrap.diff
+++ b/browser_patches/webkit/patches/bootstrap.diff
@@ -11147,11 +11147,31 @@ index 4c41864d89f40567ed81ed2efefb501f6753db84..b9cab7c400c0c129ea4a9851dc397682
// For backwards compatibility with the WebBackForwardList API, we honor both
// a per-WebView and a per-preferences setting for whether to use the back/forward cache.
+diff --git a/Source/cmake/OptionsGTK.cmake b/Source/cmake/OptionsGTK.cmake
+index ba7f352a2adcf7180511cd0a404a015c138adfd9..c27a7f1a2faa8189dbf6c53a915f44e2abdffdb0 100644
+--- a/Source/cmake/OptionsGTK.cmake
++++ b/Source/cmake/OptionsGTK.cmake
+@@ -3,6 +3,7 @@ include(VersioningUtils)
+
+ SET_PROJECT_VERSION(2 27 3)
+ set(WEBKITGTK_API_VERSION 4.0)
++set(ENABLE_WEBKIT_LEGACY OFF)
+
+ CALCULATE_LIBRARY_VERSIONS_FROM_LIBTOOL_TRIPLE(WEBKIT 79 0 42)
+ CALCULATE_LIBRARY_VERSIONS_FROM_LIBTOOL_TRIPLE(JAVASCRIPTCORE 33 2 15)
diff --git a/Source/cmake/OptionsWPE.cmake b/Source/cmake/OptionsWPE.cmake
-index 2fbbb581c02b6f4834ae8affa554df0fb2e311e1..1dd53b7970105b3e1191dbfb9545f406ef097646 100644
+index 2fbbb581c02b6f4834ae8affa554df0fb2e311e1..d4e2956db6b1ff83ddf5641436770e09fe0c2df7 100644
--- a/Source/cmake/OptionsWPE.cmake
+++ b/Source/cmake/OptionsWPE.cmake
-@@ -49,6 +49,7 @@ WEBKIT_OPTION_DEFAULT_PORT_VALUE(ENABLE_XSLT PUBLIC ON)
+@@ -3,6 +3,7 @@ include(VersioningUtils)
+
+ SET_PROJECT_VERSION(2 27 3)
+ set(WPE_API_VERSION 1.0)
++set(ENABLE_WEBKIT_LEGACY OFF)
+
+ CALCULATE_LIBRARY_VERSIONS_FROM_LIBTOOL_TRIPLE(WEBKIT 11 0 8)
+
+@@ -49,6 +50,7 @@ WEBKIT_OPTION_DEFAULT_PORT_VALUE(ENABLE_XSLT PUBLIC ON)
# Changing these options is completely unsupported.
WEBKIT_OPTION_DEFAULT_PORT_VALUE(ENABLE_ASYNC_SCROLLING PRIVATE ON)
WEBKIT_OPTION_DEFAULT_PORT_VALUE(ENABLE_CONTENT_EXTENSIONS PRIVATE ON)
@@ -11160,10 +11180,21 @@ index 2fbbb581c02b6f4834ae8affa554df0fb2e311e1..1dd53b7970105b3e1191dbfb9545f406
WEBKIT_OPTION_DEFAULT_PORT_VALUE(ENABLE_MHTML PRIVATE ON)
WEBKIT_OPTION_DEFAULT_PORT_VALUE(ENABLE_NETSCAPE_PLUGIN_API PRIVATE OFF)
diff --git a/Source/cmake/OptionsWin.cmake b/Source/cmake/OptionsWin.cmake
-index 1019fce94d5389a1f7b15675199dc02ccc68fcc3..5335aed3b8fba48b92407d988d5c7b49b94bc0ce 100644
+index 1019fce94d5389a1f7b15675199dc02ccc68fcc3..785cc43328c8f306e73382a7aaba619f8fc91fe4 100644
--- a/Source/cmake/OptionsWin.cmake
+++ b/Source/cmake/OptionsWin.cmake
-@@ -79,6 +79,8 @@ if (${WTF_PLATFORM_WIN_CAIRO})
+@@ -7,8 +7,9 @@ add_definitions(-D_WINDOWS -DWINVER=0x601 -D_WIN32_WINNT=0x601)
+ add_definitions(-DNOMINMAX)
+ add_definitions(-DUNICODE -D_UNICODE)
+
++set(ENABLE_WEBKIT_LEGACY OFF)
++
+ if ((NOT DEFINED ENABLE_WEBKIT_LEGACY) OR ENABLE_WEBKIT_LEGACY)
+- set(ENABLE_WEBKIT_LEGACY ON)
+ set(ENABLE_WEBKIT OFF)
+ endif ()
+
+@@ -79,6 +80,8 @@ if (${WTF_PLATFORM_WIN_CAIRO})
WEBKIT_OPTION_DEFAULT_PORT_VALUE(ENABLE_PUBLIC_SUFFIX_LIST PRIVATE ON)
WEBKIT_OPTION_DEFAULT_PORT_VALUE(ENABLE_USER_MESSAGE_HANDLERS PRIVATE ON)
WEBKIT_OPTION_DEFAULT_PORT_VALUE(ENABLE_WEBGL PUBLIC ON)
diff --git a/browser_patches/webkit/pw_run.sh b/browser_patches/webkit/pw_run.sh
index 95c3debba1..426860ea99 100755
--- a/browser_patches/webkit/pw_run.sh
+++ b/browser_patches/webkit/pw_run.sh
@@ -2,11 +2,11 @@
function runOSX() {
# if script is run as-is
- if [ -d $SCRIPT_PATH/checkout/WebKitBuild/Release/MiniBrowser.app ]; then
+ if [[ -d $SCRIPT_PATH/checkout/WebKitBuild/Release/MiniBrowser.app ]]; then
DYLIB_PATH="$SCRIPT_PATH/checkout/WebKitBuild/Release"
- elif [ -d $SCRIPT_PATH/MiniBrowser.app ]; then
+ elif [[ -d $SCRIPT_PATH/MiniBrowser.app ]]; then
DYLIB_PATH="$SCRIPT_PATH"
- elif [ -d $SCRIPT_PATH/WebKitBuild/Release/MiniBrowser.app ]; then
+ elif [[ -d $SCRIPT_PATH/WebKitBuild/Release/MiniBrowser.app ]]; then
DYLIB_PATH="$SCRIPT_PATH/WebKitBuild/Release"
else
echo "Cannot find a MiniBrowser.app in neither location" 1>&2
@@ -18,14 +18,23 @@ function runOSX() {
function runLinux() {
# if script is run as-is
- if [ -d $SCRIPT_PATH/checkout/WebKitBuild ]; then
- LD_PATH="$SCRIPT_PATH/checkout/WebKitBuild/DependenciesGTK/Root/lib:$SCRIPT_PATH/checkout/WebKitBuild/Release/bin"
+ DEPENDENCIES_FOLDER="DependenciesGTK"
+ MINIBROWSER_FOLDER="minibrowser-gtk";
+ if [[ "$*" == *--headless* ]]; then
+ DEPENDENCIES_FOLDER="DependenciesWPE";
+ MINIBROWSER_FOLDER="minibrowser-wpe";
+ fi
+ if [[ -d $SCRIPT_PATH/$MINIBROWSER_FOLDER ]]; then
+ LD_PATH="$SCRIPT_PATH/$MINIBROWSER_FOLDER"
+ MINIBROWSER="$SCRIPT_PATH/$MINIBROWSER_FOLDER/MiniBrowser"
+ elif [[ -d $SCRIPT_PATH/checkout/WebKitBuild ]]; then
+ LD_PATH="$SCRIPT_PATH/checkout/WebKitBuild/$DEPENDENCIES_FOLDER/Root/lib:$SCRIPT_PATH/checkout/WebKitBuild/Release/bin"
MINIBROWSER="$SCRIPT_PATH/checkout/WebKitBuild/Release/bin/MiniBrowser"
- elif [ -f $SCRIPT_PATH/MiniBrowser ]; then
+ elif [[ -f $SCRIPT_PATH/MiniBrowser ]]; then
LD_PATH="$SCRIPT_PATH"
MINIBROWSER="$SCRIPT_PATH/MiniBrowser"
- elif [ -d $SCRIPT_PATH/WebKitBuild ]; then
- LD_PATH="$SCRIPT_PATH/WebKitBuild/DependenciesGTK/Root/lib:$SCRIPT_PATH/WebKitBuild/Release/bin"
+ elif [[ -d $SCRIPT_PATH/WebKitBuild ]]; then
+ LD_PATH="$SCRIPT_PATH/WebKitBuild/$DEPENDENCIES_FOLDER/Root/lib:$SCRIPT_PATH/WebKitBuild/Release/bin"
MINIBROWSER="$SCRIPT_PATH/WebKitBuild/Release/bin/MiniBrowser"
else
echo "Cannot find a MiniBrowser.app in neither location" 1>&2
@@ -35,9 +44,9 @@ function runLinux() {
}
SCRIPT_PATH="$(cd "$(dirname "$0")" ; pwd -P)"
-if [ "$(uname)" == "Darwin" ]; then
+if [[ "$(uname)" == "Darwin" ]]; then
runOSX "$@"
-elif [ "$(uname)" == "Linux" ]; then
+elif [[ "$(uname)" == "Linux" ]]; then
runLinux "$@"
else
echo "ERROR: cannot run on this platform!" 1>&2
diff --git a/docs/api.md b/docs/api.md
index f5e531f9b7..6a915b18b8 100644
--- a/docs/api.md
+++ b/docs/api.md
@@ -3565,6 +3565,8 @@ Browser websocket endpoint which can be used as an argument to [firefoxPlaywrigh
#### webkitPlaywright.defaultArgs([options])
- `options` <[Object]> Set of configurable options to set on the browser. Can have the following fields:
+ - `headless` <[boolean]> Whether to run WebKit in headless mode. Defaults to `true`.
+ - `userDataDir` <[string]> Path to a User Data Directory, which stores browser session data like cookies and local storage.
- `args` <[Array]<[string]>> Additional arguments to pass to the browser instance.
- returns: <[Array]<[string]>>
@@ -3575,6 +3577,7 @@ The default flags that WebKit will be launched with.
- `headless` <[boolean]> Whether to run WebKit in headless mode. Defaults to `true`.
- `executablePath` <[string]> Path to a WebKit executable to run instead of the bundled WebKit. If `executablePath` is a relative path, then it is resolved relative to [current working directory](https://nodejs.org/api/process.html#process_process_cwd). **BEWARE**: Playwright is only guaranteed to work with the bundled WebKit, use at your own risk.
- `slowMo` <[number]> Slows down Playwright operations by the specified amount of milliseconds. Useful so that you can see what is going on.
+ - `userDataDir` <[string]> Path to a User Data Directory, which stores browser session data like cookies and local storage.
- `args` <[Array]<[string]>> Additional arguments to pass to the browser instance.
- `ignoreDefaultArgs` <[boolean]|[Array]<[string]>> If `true`, then do not use [`webKitPlaywright.defaultArgs()`](#webkitplaywrightdefaultargsoptions). If an array is given, then filter out the given default arguments. Dangerous option; use with care. Defaults to `false`.
- `handleSIGINT` <[boolean]> Close the browser process on Ctrl-C. Defaults to `true`.
@@ -3598,6 +3601,7 @@ const browser = await playwright.launch({
- `headless` <[boolean]> Whether to run WebKit in headless mode. Defaults to `true`.
- `executablePath` <[string]> Path to a WebKit executable to run instead of the bundled WebKit. If `executablePath` is a relative path, then it is resolved relative to [current working directory](https://nodejs.org/api/process.html#process_process_cwd). **BEWARE**: Playwright is only guaranteed to work with the bundled WebKit, use at your own risk.
- `slowMo` <[number]> Slows down Playwright operations by the specified amount of milliseconds. Useful so that you can see what is going on.
+ - `userDataDir` <[string]> Path to a User Data Directory, which stores browser session data like cookies and local storage.
- `args` <[Array]<[string]>> Additional arguments to pass to the browser instance.
- `ignoreDefaultArgs` <[boolean]|[Array]<[string]>> If `true`, then do not use [`webKitPlaywright.defaultArgs()`](#webkitplaywrightdefaultargsoptions). If an array is given, then filter out the given default arguments. Dangerous option; use with care. Defaults to `false`.
- `handleSIGINT` <[boolean]> Close the browser process on Ctrl-C. Defaults to `true`.
diff --git a/package.json b/package.json
index a80716d9b0..a52f12b684 100644
--- a/package.json
+++ b/package.json
@@ -9,8 +9,8 @@
"main": "index.js",
"playwright": {
"chromium_revision": "724623",
- "firefox_revision": "1015",
- "webkit_revision": "1097"
+ "firefox_revision": "1016",
+ "webkit_revision": "1099"
},
"scripts": {
"unit": "node test/test.js",
diff --git a/src/frames.ts b/src/frames.ts
index fccb725512..dc8546adbf 100644
--- a/src/frames.ts
+++ b/src/frames.ts
@@ -191,16 +191,19 @@ export class FrameManager {
for (const watcher of this._lifecycleWatchers)
watcher._onNavigationRequest(frame, request);
}
- this._page.emit(Events.Page.Request, request);
+ if (!request._isFavicon)
+ this._page.emit(Events.Page.Request, request);
}
requestReceivedResponse(response: network.Response) {
- this._page.emit(Events.Page.Response, response);
+ if (!response.request()._isFavicon)
+ this._page.emit(Events.Page.Response, response);
}
requestFinished(request: network.Request) {
this._inflightRequestFinished(request);
- this._page.emit(Events.Page.RequestFinished, request);
+ if (!request._isFavicon)
+ this._page.emit(Events.Page.RequestFinished, request);
}
requestFailed(request: network.Request, canceled: boolean) {
@@ -216,7 +219,8 @@ export class FrameManager {
watcher._onAbortedNewDocumentNavigation(frame, request._documentId, errorText);
}
}
- this._page.emit(Events.Page.RequestFailed, request);
+ if (!request._isFavicon)
+ this._page.emit(Events.Page.RequestFailed, request);
}
provisionalLoadFailed(documentId: string, error: string) {
@@ -236,7 +240,7 @@ export class FrameManager {
private _inflightRequestFinished(request: network.Request) {
const frame = request.frame();
- if (!frame || request.url().endsWith('favicon.ico'))
+ if (!frame || request._isFavicon)
return;
if (!frame._inflightRequests.has(request))
return;
@@ -249,7 +253,7 @@ export class FrameManager {
private _inflightRequestStarted(request: network.Request) {
const frame = request.frame();
- if (!frame || request.url().endsWith('favicon.ico'))
+ if (!frame || request._isFavicon)
return;
frame._inflightRequests.add(request);
if (frame._inflightRequests.size === 1)
diff --git a/src/network.ts b/src/network.ts
index 16312e732d..b4bea283f5 100644
--- a/src/network.ts
+++ b/src/network.ts
@@ -97,6 +97,7 @@ export class Request {
_redirectChain: Request[];
_finalRequest: Request;
readonly _documentId?: string;
+ readonly _isFavicon: boolean;
private _failureText: string | null = null;
private _url: string;
private _resourceType: string;
@@ -127,6 +128,7 @@ export class Request {
this._headers = headers;
this._waitForResponsePromise = new Promise(f => this._waitForResponsePromiseCallback = f);
this._waitForFinishedPromise = new Promise(f => this._waitForFinishedPromiseCallback = f);
+ this._isFavicon = url.endsWith('/favicon.ico');
}
_setFailureText(failureText: string) {
diff --git a/src/platform.ts b/src/platform.ts
index c3d67a76e7..c464f04777 100644
--- a/src/platform.ts
+++ b/src/platform.ts
@@ -1,5 +1,18 @@
-// Copyright (c) Microsoft Corporation.
-// Licensed under the MIT license.
+/**
+ * Copyright (c) Microsoft Corporation.
+ *
+ * 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.
+ */
// Note: this is the only file outside of src/server which can import external dependencies.
// All dependencies must be listed in web.webpack.config.js to avoid bundling them.
diff --git a/src/server/wkPlaywright.ts b/src/server/wkPlaywright.ts
index 28ee17f359..0acd504386 100644
--- a/src/server/wkPlaywright.ts
+++ b/src/server/wkPlaywright.ts
@@ -195,7 +195,7 @@ export class WKPlaywright implements Playwright {
_createBrowserFetcher(options?: BrowserFetcherOptions): BrowserFetcher {
const downloadURLs = {
- linux: '%s/builds/webkit/%s/minibrowser-linux.zip',
+ linux: '%s/builds/webkit/%s/minibrowser-gtk-wpe.zip',
mac: '%s/builds/webkit/%s/minibrowser-mac-%s.zip',
win64: '%s/builds/webkit/%s/minibrowser-win64.zip',
};
diff --git a/src/webkit/wkNetworkManager.ts b/src/webkit/wkNetworkManager.ts
index 94b9bb65ea..353f36e7b3 100644
--- a/src/webkit/wkNetworkManager.ts
+++ b/src/webkit/wkNetworkManager.ts
@@ -46,11 +46,11 @@ export class WKNetworkManager {
helper.removeEventListeners(this._sessionListeners);
this._session = session;
this._sessionListeners = [
- helper.addEventListener(this._session, 'Network.requestWillBeSent', this._onRequestWillBeSent.bind(this)),
- helper.addEventListener(this._session, 'Network.requestIntercepted', this._onRequestIntercepted.bind(this)),
- helper.addEventListener(this._session, 'Network.responseReceived', this._onResponseReceived.bind(this)),
- helper.addEventListener(this._session, 'Network.loadingFinished', this._onLoadingFinished.bind(this)),
- helper.addEventListener(this._session, 'Network.loadingFailed', this._onLoadingFailed.bind(this)),
+ helper.addEventListener(session, 'Network.requestWillBeSent', e => this._onRequestWillBeSent(session, e)),
+ helper.addEventListener(session, 'Network.requestIntercepted', e => this._onRequestIntercepted(e)),
+ helper.addEventListener(session, 'Network.responseReceived', e => this._onResponseReceived(e)),
+ helper.addEventListener(session, 'Network.loadingFinished', e => this._onLoadingFinished(e)),
+ helper.addEventListener(session, 'Network.loadingFailed', e => this._onLoadingFailed(e)),
];
}
@@ -77,13 +77,13 @@ export class WKNetworkManager {
await this._session.send('Network.setInterceptionEnabled', { enabled, interceptRequests: enabled });
}
- async _updateProtocolCacheDisabled() {
+ private async _updateProtocolCacheDisabled() {
await this._session.send('Network.setResourceCachingDisabled', {
disabled: this._userCacheDisabled
});
}
- _onRequestWillBeSent(event: Protocol.Network.requestWillBeSentPayload) {
+ _onRequestWillBeSent(session: WKSession, event: Protocol.Network.requestWillBeSentPayload) {
if (event.request.url.startsWith('data:'))
return;
let redirectChain: network.Request[] = [];
@@ -99,7 +99,7 @@ export class WKNetworkManager {
// TODO(einbinder) this will fail if we are an XHR document request
const isNavigationRequest = event.type === 'Document';
const documentId = isNavigationRequest ? event.loaderId : undefined;
- const request = new InterceptableRequest(this._session, !!this._page._state.interceptNetwork, frame, event, redirectChain, documentId);
+ const request = new InterceptableRequest(session, !!this._page._state.interceptNetwork, frame, event, redirectChain, documentId);
this._requestIdToRequest.set(event.requestId, request);
this._page._frameManager.requestStarted(request.request);
}
@@ -110,17 +110,17 @@ export class WKNetworkManager {
request._interceptedCallback();
}
- _createResponse(request: InterceptableRequest, responsePayload: Protocol.Network.Response): network.Response {
+ private static _createResponse(request: InterceptableRequest, responsePayload: Protocol.Network.Response): network.Response {
const remoteAddress: network.RemoteAddress = { ip: '', port: 0 };
const getResponseBody = async () => {
- const response = await this._session.send('Network.getResponseBody', { requestId: request._requestId });
+ const response = await request._session.send('Network.getResponseBody', { requestId: request._requestId });
return platform.Buffer.from(response.body, response.base64Encoded ? 'base64' : 'utf8');
};
return new network.Response(request.request, responsePayload.status, responsePayload.statusText, headersObject(responsePayload.headers), remoteAddress, getResponseBody);
}
- _handleRequestRedirect(request: InterceptableRequest, responsePayload: Protocol.Network.Response) {
- const response = this._createResponse(request, responsePayload);
+ private _handleRequestRedirect(request: InterceptableRequest, responsePayload: Protocol.Network.Response) {
+ const response = WKNetworkManager._createResponse(request, responsePayload);
request.request._redirectChain.push(request.request);
response._requestFinished(new Error('Response body is unavailable for redirect responses'));
this._requestIdToRequest.delete(request._requestId);
@@ -133,7 +133,7 @@ export class WKNetworkManager {
// FileUpload sends a response without a matching request.
if (!request)
return;
- const response = this._createResponse(request, event.response);
+ const response = WKNetworkManager._createResponse(request, event.response);
this._page._frameManager.requestReceivedResponse(response);
}
@@ -194,7 +194,7 @@ const errorReasons: { [reason: string]: string } = {
};
class InterceptableRequest implements network.RequestDelegate {
- private _session: WKSession;
+ readonly _session: WKSession;
readonly request: network.Request;
_requestId: string;
_documentId: string | undefined;
diff --git a/src/webkit/wkPage.ts b/src/webkit/wkPage.ts
index c3848adf3e..c82a031015 100644
--- a/src/webkit/wkPage.ts
+++ b/src/webkit/wkPage.ts
@@ -43,10 +43,9 @@ export class WKPage implements PageDelegate {
_session: WKSession;
readonly _page: Page;
private readonly _pageProxySession: WKSession;
- private readonly _networkManager: WKNetworkManager;
+ readonly _networkManager: WKNetworkManager;
private readonly _workers: WKWorkers;
private readonly _contextIdToContext: Map;
- private _isolatedWorlds: Set;
private _sessionListeners: RegisteredListener[] = [];
private readonly _bootstrapScripts: string[] = [];
@@ -55,14 +54,13 @@ export class WKPage implements PageDelegate {
this.rawKeyboard = new RawKeyboardImpl(pageProxySession);
this.rawMouse = new RawMouseImpl(pageProxySession);
this._contextIdToContext = new Map();
- this._isolatedWorlds = new Set();
this._page = new Page(this, browserContext);
this._networkManager = new WKNetworkManager(this._page, pageProxySession);
this._workers = new WKWorkers(this._page);
this._session = undefined as any as WKSession;
}
- async _initializePageProxySession() {
+ private async _initializePageProxySession() {
const promises : Promise[] = [
this._pageProxySession.send('Dialog.enable'),
this._networkManager.initializePageProxySession(this._page._state.credentials)
@@ -83,38 +81,46 @@ export class WKPage implements PageDelegate {
this._addSessionListeners();
this._networkManager.setSession(session);
this._workers.setSession(session);
- this._isolatedWorlds = new Set();
// New bootstrap scripts may have been added during provisional load, push them
// again to be on the safe side.
if (this._bootstrapScripts.length)
this._setBootstrapScripts(session).catch(e => debugError(e));
}
+ async initialize() {
+ await Promise.all([
+ this._initializePageProxySession(),
+ this._initializeSession(this._session, ({frameTree}) => this._handleFrameTree(frameTree)),
+ ]);
+ }
+
// This method is called for provisional targets as well. The session passed as the parameter
// may be different from the current session and may be destroyed without becoming current.
- async _initializeSession(session: WKSession, isProvisional: boolean) {
+ async _initializeSession(session: WKSession, resourceTreeHandler: (r: Protocol.Page.getResourceTreeReturnValue) => void) {
+ const isProvisional = this._session !== session;
const promises : Promise[] = [
// Page agent must be enabled before Runtime.
session.send('Page.enable'),
- session.send('Page.getResourceTree').then(({frameTree}) => this._handleFrameTree(frameTree)),
+ session.send('Page.getResourceTree').then(resourceTreeHandler),
// Resource tree should be received before first execution context.
- session.send('Runtime.enable').then(() => this._ensureIsolatedWorld(UTILITY_WORLD_NAME)),
+ session.send('Runtime.enable'),
+ session.send('Page.createIsolatedWorld', { name: UTILITY_WORLD_NAME, source: `//# sourceURL=${EVALUATION_SCRIPT_URL}` }),
session.send('Console.enable'),
session.send('Page.setInterceptFileChooserDialog', { enabled: true }),
this._networkManager.initializeSession(session, this._page._state.interceptNetwork, this._page._state.offlineMode),
- this._workers.initializeSession(session),
+ this._workers.initializeSession(session)
];
const contextOptions = this._page.browserContext()._options;
if (contextOptions.userAgent)
promises.push(session.send('Page.overrideUserAgent', { value: contextOptions.userAgent }));
if (this._page._state.mediaType || this._page._state.colorScheme)
- promises.push(this._setEmulateMedia(session, this._page._state.mediaType, this._page._state.colorScheme));
+ promises.push(WKPage._setEmulateMedia(session, this._page._state.mediaType, this._page._state.colorScheme));
if (isProvisional)
promises.push(this._setBootstrapScripts(session));
if (contextOptions.bypassCSP)
promises.push(session.send('Page.setBypassCSP', { enabled: true }));
if (this._page._state.extraHTTPHeaders !== null)
- promises.push(this._setExtraHTTPHeaders(session, this._page._state.extraHTTPHeaders));
+ promises.push(WKPage._setExtraHTTPHeaders(session, this._page._state.extraHTTPHeaders));
if (this._page._state.hasTouch)
promises.push(session.send('Page.setTouchEmulationEnabled', { enabled: true }));
await Promise.all(promises).catch(e => {
@@ -285,21 +291,11 @@ export class WKPage implements PageDelegate {
this._page._onFileChooserOpened(handle);
}
- async _ensureIsolatedWorld(name: string) {
- if (this._isolatedWorlds.has(name))
- return;
- this._isolatedWorlds.add(name);
- await this._session.send('Page.createIsolatedWorld', {
- name,
- source: `//# sourceURL=${EVALUATION_SCRIPT_URL}`
- });
- }
-
- private async _setExtraHTTPHeaders(session: WKSession, headers: network.Headers): Promise {
+ private static async _setExtraHTTPHeaders(session: WKSession, headers: network.Headers): Promise {
await session.send('Network.setExtraHTTPHeaders', { headers });
}
- private async _setEmulateMedia(session: WKSession, mediaType: types.MediaType | null, colorScheme: types.ColorScheme | null): Promise {
+ private static async _setEmulateMedia(session: WKSession, mediaType: types.MediaType | null, colorScheme: types.ColorScheme | null): Promise {
const promises = [];
promises.push(session.send('Page.setEmulatedMedia', { media: mediaType || '' }));
if (colorScheme !== null) {
@@ -314,11 +310,11 @@ export class WKPage implements PageDelegate {
}
async setExtraHTTPHeaders(headers: network.Headers): Promise {
- await this._setExtraHTTPHeaders(this._session, headers);
+ await WKPage._setExtraHTTPHeaders(this._session, headers);
}
async setEmulateMedia(mediaType: types.MediaType | null, colorScheme: types.ColorScheme | null): Promise {
- await this._setEmulateMedia(this._session, mediaType, colorScheme);
+ await WKPage._setEmulateMedia(this._session, mediaType, colorScheme);
}
async setViewport(viewport: types.Viewport): Promise {
diff --git a/src/webkit/wkPageProxy.ts b/src/webkit/wkPageProxy.ts
index a314b1c25f..a516145f32 100644
--- a/src/webkit/wkPageProxy.ts
+++ b/src/webkit/wkPageProxy.ts
@@ -1,5 +1,18 @@
-// Copyright (c) Microsoft Corporation.
-// Licensed under the MIT license.
+/**
+ * Copyright (c) Microsoft Corporation.
+ *
+ * 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.
+ */
import { BrowserContext } from '../browserContext';
@@ -9,17 +22,16 @@ import { WKSession } from './wkConnection';
import { WKPage } from './wkPage';
import { RegisteredListener, helper, assert, debugError } from '../helper';
import { Events } from '../events';
+import { WKProvisionalPage } from './wkProvisionalPage';
-// We keep provisional messages on the session instace until provisional
-// target is committed. Non-provisional target (there should be just one)
-// has undefined instead.
-const provisionalMessagesSymbol = Symbol('provisionalMessages');
+const isPovisionalSymbol = Symbol('isPovisional');
export class WKPageProxy {
private readonly _pageProxySession: WKSession;
readonly _browserContext: BrowserContext;
private _pagePromise: Promise | null = null;
private _wkPage: WKPage | null = null;
+ private _provisionalPage: WKProvisionalPage | null = null;
private readonly _firstTargetPromise: Promise;
private _firstTargetCallback?: () => void;
private readonly _sessions = new Map();
@@ -56,6 +68,10 @@ export class WKPageProxy {
for (const session of this._sessions.values())
session.dispose();
this._sessions.clear();
+ if (this._provisionalPage) {
+ this._provisionalPage.dispose();
+ this._provisionalPage = null;
+ }
if (this._wkPage)
this._wkPage.didDisconnect();
}
@@ -66,7 +82,7 @@ export class WKPageProxy {
private _isProvisionalCrossProcessLoadInProgress() : boolean {
for (const anySession of this._sessions.values()) {
- if ((anySession as any)[provisionalMessagesSymbol])
+ if ((anySession as any)[isPovisionalSymbol])
return true;
}
return false;
@@ -100,7 +116,7 @@ export class WKPageProxy {
await this._firstTargetPromise;
let session: WKSession | undefined;
for (const anySession of this._sessions.values()) {
- if (!(anySession as any)[provisionalMessagesSymbol]) {
+ if (!(anySession as any)[isPovisionalSymbol]) {
session = anySession;
break;
}
@@ -108,10 +124,7 @@ export class WKPageProxy {
assert(session, 'One non-provisional target session must exist');
this._wkPage = new WKPage(this._browserContext, this._pageProxySession);
this._wkPage.setSession(session!);
- await Promise.all([
- this._wkPage._initializePageProxySession(),
- this._wkPage._initializeSession(session!, false),
- ]);
+ await this._wkPage.initialize();
return this._wkPage._page;
}
@@ -131,9 +144,11 @@ export class WKPageProxy {
this._firstTargetCallback = undefined;
}
if (targetInfo.isProvisional)
- (session as any)[provisionalMessagesSymbol] = [];
- if (targetInfo.isProvisional && this._wkPage)
- this._wkPage._initializeSession(session, true);
+ (session as any)[isPovisionalSymbol] = true;
+ if (targetInfo.isProvisional && this._wkPage) {
+ assert(!this._provisionalPage);
+ this._provisionalPage = new WKProvisionalPage(session, this._wkPage);
+ }
if (targetInfo.isPaused)
this._pageProxySession.send('Target.resume', { targetId: targetInfo.targetId }).catch(debugError);
}
@@ -144,6 +159,10 @@ export class WKPageProxy {
if (session)
session.dispose();
this._sessions.delete(targetId);
+ if (this._provisionalPage && this._provisionalPage._session === session) {
+ this._provisionalPage.dispose();
+ this._provisionalPage = null;
+ }
if (this._wkPage && this._wkPage._session === session && crashed)
this._wkPage.didClose(crashed);
}
@@ -152,11 +171,7 @@ export class WKPageProxy {
const { targetId, message } = event;
const session = this._sessions.get(targetId);
assert(session, 'Unknown target: ' + targetId);
- const provisionalMessages = (session as any)[provisionalMessagesSymbol];
- if (provisionalMessages)
- provisionalMessages.push(message);
- else
- session!.dispatchMessage(JSON.parse(message));
+ session!.dispatchMessage(JSON.parse(message));
}
private _onDidCommitProvisionalTarget(event: Protocol.Target.didCommitProvisionalTargetPayload) {
@@ -167,11 +182,13 @@ export class WKPageProxy {
assert(oldSession, 'Unknown old target: ' + oldTargetId);
// TODO: make some calls like screenshot catch swapped out error and retry.
oldSession!.errorText = 'Target was swapped out.';
- const provisionalMessages = (newSession as any)[provisionalMessagesSymbol];
- assert(provisionalMessages, 'Committing target must be provisional');
- (newSession as any)[provisionalMessagesSymbol] = undefined;
- for (const message of provisionalMessages)
- newSession!.dispatchMessage(JSON.parse(message));
- this._wkPage!.setSession(newSession!);
+ (newSession as any)[isPovisionalSymbol] = undefined;
+ if (this._provisionalPage) {
+ this._provisionalPage.commit();
+ this._provisionalPage.dispose();
+ this._provisionalPage = null;
+ }
+ if (this._wkPage)
+ this._wkPage.setSession(newSession!);
}
}
diff --git a/src/webkit/wkProvisionalPage.ts b/src/webkit/wkProvisionalPage.ts
new file mode 100644
index 0000000000..5c81b580e4
--- /dev/null
+++ b/src/webkit/wkProvisionalPage.ts
@@ -0,0 +1,67 @@
+/**
+ * Copyright (c) Microsoft Corporation.
+ *
+ * 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.
+ */
+
+import { WKSession } from './wkConnection';
+import { WKPage } from './wkPage';
+import { RegisteredListener, helper, assert } from '../helper';
+import { Protocol } from './protocol';
+
+export class WKProvisionalPage {
+ readonly _session: WKSession;
+ private readonly _wkPage: WKPage;
+ private _sessionListeners: RegisteredListener[] = [];
+ private _mainFrameId: string | null = null;
+
+ constructor(session: WKSession, page: WKPage) {
+ this._session = session;
+ this._wkPage = page;
+
+ const overrideFrameId = (handler: (p: any) => void) => {
+ return (payload: any) => {
+ // Pretend that the events happened in the same process.
+ if (payload.frameId)
+ payload.frameId = this._wkPage._page._frameManager.mainFrame()._id;
+ handler(payload);
+ };
+ };
+ const networkManager = this._wkPage._networkManager;
+
+ this._sessionListeners = [
+ helper.addEventListener(session, 'Network.requestWillBeSent', overrideFrameId(e => networkManager._onRequestWillBeSent(session, e))),
+ helper.addEventListener(session, 'Network.requestIntercepted', overrideFrameId(e => networkManager._onRequestIntercepted(e))),
+ helper.addEventListener(session, 'Network.responseReceived', overrideFrameId(e => networkManager._onResponseReceived(e))),
+ helper.addEventListener(session, 'Network.loadingFinished', overrideFrameId(e => networkManager._onLoadingFinished(e))),
+ helper.addEventListener(session, 'Network.loadingFailed', overrideFrameId(e => networkManager._onLoadingFailed(e))),
+ ];
+
+
+ this._wkPage._initializeSession(session, ({frameTree}) => this._handleFrameTree(frameTree));
+ }
+
+ dispose() {
+ helper.removeEventListeners(this._sessionListeners);
+ }
+
+ commit() {
+ assert(this._mainFrameId);
+ this._wkPage._onFrameAttached(this._mainFrameId!, null);
+ }
+
+ private _handleFrameTree(frameTree: Protocol.Page.FrameResourceTree) {
+ assert(!frameTree.frame.parentId);
+ this._mainFrameId = frameTree.frame.id;
+ }
+}
\ No newline at end of file
diff --git a/test/interception.spec.js b/test/interception.spec.js
index 3a3c6f2e91..b11d0309d1 100644
--- a/test/interception.spec.js
+++ b/test/interception.spec.js
@@ -28,10 +28,6 @@ module.exports.describe = function({testRunner, expect, defaultBrowserOptions, p
it('should intercept', async({page, server}) => {
await page.setRequestInterception(true);
page.on('request', request => {
- if (utils.isFavicon(request)) {
- request.continue();
- return;
- }
expect(request.url()).toContain('empty.html');
expect(request.headers()['user-agent']).toBeTruthy();
expect(request.method()).toBe('GET');
@@ -94,8 +90,7 @@ module.exports.describe = function({testRunner, expect, defaultBrowserOptions, p
await page.setRequestInterception(true);
const requests = [];
page.on('request', request => {
- if (!utils.isFavicon(request))
- requests.push(request);
+ requests.push(request);
request.continue();
});
await page.goto(server.PREFIX + '/one-style.html');
@@ -217,8 +212,7 @@ module.exports.describe = function({testRunner, expect, defaultBrowserOptions, p
const requests = [];
page.on('request', request => {
request.continue();
- if (!utils.isFavicon(request))
- requests.push(request);
+ requests.push(request);
});
server.setRedirect('/non-existing-page.html', '/non-existing-page-2.html');
server.setRedirect('/non-existing-page-2.html', '/non-existing-page-3.html');
@@ -245,8 +239,7 @@ module.exports.describe = function({testRunner, expect, defaultBrowserOptions, p
const requests = [];
page.on('request', request => {
request.continue();
- if (!utils.isFavicon(request))
- requests.push(request);
+ requests.push(request);
});
server.setRedirect('/one-style.css', '/two-style.css');
server.setRedirect('/two-style.css', '/three-style.css');
@@ -274,10 +267,6 @@ module.exports.describe = function({testRunner, expect, defaultBrowserOptions, p
let spinner = false;
// Cancel 2nd request.
page.on('request', request => {
- if (utils.isFavicon(request)) {
- request.continue();
- return;
- }
spinner ? request.abort() : request.continue();
spinner = !spinner;
});
@@ -292,8 +281,7 @@ module.exports.describe = function({testRunner, expect, defaultBrowserOptions, p
await page.setRequestInterception(true);
const requests = [];
page.on('request', request => {
- if (!utils.isFavicon(request))
- requests.push(request);
+ requests.push(request);
request.continue();
});
const dataURL = 'data:text/html,yo
';
@@ -306,8 +294,7 @@ module.exports.describe = function({testRunner, expect, defaultBrowserOptions, p
await page.setRequestInterception(true);
const requests = [];
page.on('request', request => {
- if (!utils.isFavicon(request))
- requests.push(request);
+ requests.push(request);
request.continue();
});
const dataURL = 'data:text/html,yo
';
@@ -319,8 +306,7 @@ module.exports.describe = function({testRunner, expect, defaultBrowserOptions, p
await page.setRequestInterception(true);
const requests = [];
page.on('request', request => {
- if (!utils.isFavicon(request))
- requests.push(request);
+ requests.push(request);
request.continue();
});
const response = await page.goto(server.EMPTY_PAGE + '#hash');
@@ -351,8 +337,7 @@ module.exports.describe = function({testRunner, expect, defaultBrowserOptions, p
const requests = [];
page.on('request', request => {
request.continue();
- if (!utils.isFavicon(request))
- requests.push(request);
+ requests.push(request);
});
const response = await page.goto(`data:text/html,`);
expect(response).toBe(null);
@@ -386,7 +371,7 @@ module.exports.describe = function({testRunner, expect, defaultBrowserOptions, p
await page.goto(server.EMPTY_PAGE);
expect(error.message).toContain('Request Interception is not enabled');
});
- it.skip(WEBKIT)('should intercept main resource during cross-process navigation', async({page, server}) => {
+ it('should intercept main resource during cross-process navigation', async({page, server}) => {
await page.goto(server.EMPTY_PAGE);
await page.setRequestInterception(true);
let intercepted = false;
@@ -414,7 +399,7 @@ module.exports.describe = function({testRunner, expect, defaultBrowserOptions, p
const notAnError = await request.continue().then(() => null).catch(e => e);
expect(notAnError).toBe(null);
});
- it.skip(WEBKIT)('should not throw when continued after cross-process navigation', async({page, server}) => {
+ it('should not throw when continued after cross-process navigation', async({page, server}) => {
await page.setRequestInterception(true);
page.on('request', request => {
if (request.url() !== server.PREFIX + '/one-style.css')
diff --git a/test/navigation.spec.js b/test/navigation.spec.js
index 69c07126e4..a02d9ebf11 100644
--- a/test/navigation.spec.js
+++ b/test/navigation.spec.js
@@ -125,7 +125,7 @@ module.exports.describe = function({testRunner, expect, playwright, FFOX, CHROMI
const response = await page.goto(server.EMPTY_PAGE, {waitUntil: 'domcontentloaded'});
expect(response.status()).toBe(200);
});
- it.skip(WEBKIT)('should work when page calls history API in beforeunload', async({page, server}) => {
+ it('should work when page calls history API in beforeunload', async({page, server}) => {
await page.goto(server.EMPTY_PAGE);
await page.evaluate(() => {
window.addEventListener('beforeunload', () => history.replaceState(null, 'initial', window.location.href), false);
@@ -280,7 +280,7 @@ module.exports.describe = function({testRunner, expect, playwright, FFOX, CHROMI
});
it('should navigate to dataURL and not fire dataURL requests', async({page, server}) => {
const requests = [];
- page.on('request', request => !utils.isFavicon(request) && requests.push(request));
+ page.on('request', request => requests.push(request));
const dataURL = 'data:text/html,yo
';
const response = await page.goto(dataURL);
expect(response).toBe(null);
@@ -288,7 +288,7 @@ module.exports.describe = function({testRunner, expect, playwright, FFOX, CHROMI
});
it('should navigate to URL with hash and fire requests without hash', async({page, server}) => {
const requests = [];
- page.on('request', request => !utils.isFavicon(request) && requests.push(request));
+ page.on('request', request => requests.push(request));
const response = await page.goto(server.EMPTY_PAGE + '#hash');
expect(response.status()).toBe(200);
expect(response.url()).toBe(server.EMPTY_PAGE);
diff --git a/test/network.spec.js b/test/network.spec.js
index 6bcfce3c36..8ffaf8d997 100644
--- a/test/network.spec.js
+++ b/test/network.spec.js
@@ -27,20 +27,20 @@ module.exports.describe = function({testRunner, expect, FFOX, CHROMIUM, WEBKIT})
describe('Page.Events.Request', function() {
it('should fire for navigation requests', async({page, server}) => {
const requests = [];
- page.on('request', request => !utils.isFavicon(request) && requests.push(request));
+ page.on('request', request => requests.push(request));
await page.goto(server.EMPTY_PAGE);
expect(requests.length).toBe(1);
});
it('should fire for iframes', async({page, server}) => {
const requests = [];
- page.on('request', request => !utils.isFavicon(request) && requests.push(request));
+ page.on('request', request => requests.push(request));
await page.goto(server.EMPTY_PAGE);
await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE);
expect(requests.length).toBe(2);
});
it('should fire for fetches', async({page, server}) => {
const requests = [];
- page.on('request', request => !utils.isFavicon(request) && requests.push(request));
+ page.on('request', request => requests.push(request));
await page.goto(server.EMPTY_PAGE);
await page.evaluate(() => fetch('/empty.html'));
expect(requests.length).toBe(2);
@@ -50,7 +50,7 @@ module.exports.describe = function({testRunner, expect, FFOX, CHROMIUM, WEBKIT})
describe('Request.frame', function() {
it('should work for main frame navigation request', async({page, server}) => {
const requests = [];
- page.on('request', request => !utils.isFavicon(request) && requests.push(request));
+ page.on('request', request => requests.push(request));
await page.goto(server.EMPTY_PAGE);
expect(requests.length).toBe(1);
expect(requests[0].frame()).toBe(page.mainFrame());
@@ -58,7 +58,7 @@ module.exports.describe = function({testRunner, expect, FFOX, CHROMIUM, WEBKIT})
it('should work for subframe navigation request', async({page, server}) => {
await page.goto(server.EMPTY_PAGE);
const requests = [];
- page.on('request', request => !utils.isFavicon(request) && requests.push(request));
+ page.on('request', request => requests.push(request));
await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE);
expect(requests.length).toBe(1);
expect(requests[0].frame()).toBe(page.frames()[1]);
@@ -66,7 +66,7 @@ module.exports.describe = function({testRunner, expect, FFOX, CHROMIUM, WEBKIT})
it('should work for fetch requests', async({page, server}) => {
await page.goto(server.EMPTY_PAGE);
let requests = [];
- page.on('request', request => !utils.isFavicon(request) && requests.push(request));
+ page.on('request', request => requests.push(request));
await page.evaluate(() => fetch('/digits/1.png'));
requests = requests.filter(request => !request.url().includes('favicon'));
expect(requests.length).toBe(1);
@@ -151,7 +151,7 @@ module.exports.describe = function({testRunner, expect, FFOX, CHROMIUM, WEBKIT})
page.on('requestfinished', r => requestFinished = requestFinished || r.url().includes('/get'));
// send request and wait for server response
const [pageResponse] = await Promise.all([
- page.waitForEvent('response', { predicate: r => !utils.isFavicon(r.request()) }),
+ page.waitForEvent('response'),
page.evaluate(() => fetch('./get', { method: 'GET'})),
server.waitForRequest('/get'),
]);
@@ -207,7 +207,7 @@ module.exports.describe = function({testRunner, expect, FFOX, CHROMIUM, WEBKIT})
describe('Network Events', function() {
it('Page.Events.Request', async({page, server}) => {
const requests = [];
- page.on('request', request => !utils.isFavicon(request) && requests.push(request));
+ page.on('request', request => requests.push(request));
await page.goto(server.EMPTY_PAGE);
expect(requests.length).toBe(1);
expect(requests[0].url()).toBe(server.EMPTY_PAGE);
@@ -220,7 +220,7 @@ module.exports.describe = function({testRunner, expect, FFOX, CHROMIUM, WEBKIT})
// FIXME: WebKit doesn't provide remoteIPAddress in the response.
it.skip(WEBKIT)('Page.Events.Response', async({page, server}) => {
const responses = [];
- page.on('response', response => !utils.isFavicon(response.request()) && responses.push(response));
+ page.on('response', response => responses.push(response));
await page.goto(server.EMPTY_PAGE);
expect(responses.length).toBe(1);
expect(responses[0].url()).toBe(server.EMPTY_PAGE);
@@ -261,7 +261,7 @@ module.exports.describe = function({testRunner, expect, FFOX, CHROMIUM, WEBKIT})
});
it('Page.Events.RequestFinished', async({page, server}) => {
const requests = [];
- page.on('requestfinished', request => !utils.isFavicon(request) && requests.push(request));
+ page.on('requestfinished', request => requests.push(request));
await page.goto(server.EMPTY_PAGE);
expect(requests.length).toBe(1);
expect(requests[0].url()).toBe(server.EMPTY_PAGE);
@@ -271,18 +271,18 @@ module.exports.describe = function({testRunner, expect, FFOX, CHROMIUM, WEBKIT})
});
it('should fire events in proper order', async({page, server}) => {
const events = [];
- page.on('request', request => !utils.isFavicon(request) && events.push('request'));
- page.on('response', response => !utils.isFavicon(response.request()) && events.push('response'));
- page.on('requestfinished', request => !utils.isFavicon(request) && events.push('requestfinished'));
+ page.on('request', request => events.push('request'));
+ page.on('response', response => events.push('response'));
+ page.on('requestfinished', request => events.push('requestfinished'));
await page.goto(server.EMPTY_PAGE);
expect(events).toEqual(['request', 'response', 'requestfinished']);
});
it('should support redirects', async({page, server}) => {
const events = [];
- page.on('request', request => !utils.isFavicon(request) && events.push(`${request.method()} ${request.url()}`));
- page.on('response', response => !utils.isFavicon(response.request()) && events.push(`${response.status()} ${response.url()}`));
- page.on('requestfinished', request => !utils.isFavicon(request) && events.push(`DONE ${request.url()}`));
- page.on('requestfailed', request => !utils.isFavicon(request) && events.push(`FAIL ${request.url()}`));
+ page.on('request', request => events.push(`${request.method()} ${request.url()}`));
+ page.on('response', response => events.push(`${response.status()} ${response.url()}`));
+ page.on('requestfinished', request => events.push(`DONE ${request.url()}`));
+ page.on('requestfailed', request => events.push(`FAIL ${request.url()}`));
server.setRedirect('/foo.html', '/empty.html');
const FOO_URL = server.PREFIX + '/foo.html';
const response = await page.goto(FOO_URL);
diff --git a/test/page.spec.js b/test/page.spec.js
index ea2ae6a10d..afb4ef7408 100644
--- a/test/page.spec.js
+++ b/test/page.spec.js
@@ -172,7 +172,7 @@ module.exports.describe = function({testRunner, expect, headless, playwright, FF
expect(await page.evaluate(() => !!window.opener)).toBe(false);
expect(await popup.evaluate(() => !!window.opener)).toBe(false);
});
- it.skip(WEBKIT || FFOX)('should not treat navigations as new popups', async({page, server}) => {
+ it.skip(FFOX)('should not treat navigations as new popups', async({page, server}) => {
await page.goto(server.EMPTY_PAGE);
await page.setContent('yo');
const [popup] = await Promise.all([
diff --git a/test/utils.js b/test/utils.js
index 6d0e601a73..8103a1b637 100644
--- a/test/utils.js
+++ b/test/utils.js
@@ -122,19 +122,11 @@ const utils = module.exports = {
frame.src = url;
frame.id = frameId;
document.body.appendChild(frame);
- // TODO(einbinder) do this right
- // Access a scriptable global object to ensure JS context is
- // initialized. WebKit will create it lazily only as need be.
- frame.contentWindow;
await new Promise(x => frame.onload = x);
return frame;
}
},
- isFavicon: function(request) {
- return request.url().includes('favicon.ico');
- },
-
/**
* @param {!Page} page
* @param {string} frameId
diff --git a/utils/testrunner/SourceMapSupport.js b/utils/testrunner/SourceMapSupport.js
index 458ae312d7..ae8933c55e 100644
--- a/utils/testrunner/SourceMapSupport.js
+++ b/utils/testrunner/SourceMapSupport.js
@@ -1,5 +1,18 @@
-// Copyright (c) Microsoft Corporation.
-// Licensed under the MIT license.
+/**
+ * Copyright (c) Microsoft Corporation.
+ *
+ * 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.
+ */
const fs = require('fs');
const path = require('path');