diff --git a/experimental/README.md b/experimental/README.md deleted file mode 100644 index 046124cdded..00000000000 --- a/experimental/README.md +++ /dev/null @@ -1,8 +0,0 @@ -#### What's this? - -This is a **highly experimental** Puppeteer API to drive Firefox browser. - -Beware, alligators live here. - -- `/juggler` - firefox-puppeteer backend -- `/puppeteer-firefox` - puppeteer API for Firefox diff --git a/experimental/juggler/.cirrus.yml b/experimental/juggler/.cirrus.yml deleted file mode 100644 index cb7403eb86e..00000000000 --- a/experimental/juggler/.cirrus.yml +++ /dev/null @@ -1,38 +0,0 @@ -task: - timeout_in: 120m - env: - CIRRUS_WORKING_DIR: /usr/local/src - SOURCE: /usr/local/src/ - GS_AUTH: ENCRYPTED[c4b5b0404f5bfdc1b663a1eb5b70f3187b5d470d02eec3265b06b8e0d30226781523630931c1da6db06714c0d359f71f] - PATH: /root/.cargo/bin:$PATH:$SOURCE/gcloud/google-cloud-sdk/bin - SHELL: /bin/bash - container: - dockerfile: Dockerfile - # image: ubuntu - cpu: 8 - memory: 24 - name: linux - # install_deps_script: apt-get update && apt-get install -y wget python clang llvm git curl - install_gcloud_script: ./scripts/install_gcloud.sh - check_gcloud_script: - - echo "REVISION: $(git rev-parse HEAD)" - - gsutil cp FIREFOX_SHA gs://juggler-builds/$(git rev-parse HEAD)/ - clone_firefox_script: ./scripts/fetch_firefox.sh - apply_patches_script: - - cd $SOURCE/firefox - - git config --global user.name "cirrus-ci-builder" - - git config --global user.email "aslushnikov@gmail.com" - - git am ../patches/* - - ln -s $PWD/../juggler testing/juggler - bootstrap_firefox_script: - - cd $SOURCE/firefox - - ./mach bootstrap --application-choice=browser --no-interactive - build_firefox_script: - - cd $SOURCE/firefox - - ./mach build - package_firefox_script: - - cd $SOURCE/firefox - - ./mach package - upload_build_to_gcloud_script: - - bash $SOURCE/scripts/upload_linux.sh - diff --git a/experimental/juggler/.gitignore b/experimental/juggler/.gitignore deleted file mode 100644 index 678332bc068..00000000000 --- a/experimental/juggler/.gitignore +++ /dev/null @@ -1 +0,0 @@ -firefox/ diff --git a/experimental/juggler/Dockerfile b/experimental/juggler/Dockerfile deleted file mode 100644 index 578c7804888..00000000000 --- a/experimental/juggler/Dockerfile +++ /dev/null @@ -1,27 +0,0 @@ -FROM ubuntu:trusty - -MAINTAINER Andrey Lushnikov -ENV SHELL=/bin/bash - -# Install generic deps -RUN apt-get update -y && apt-get install -y wget python clang llvm git curl - -# Install gcc7 (mach requires 6.1+) -RUN apt-get update -y && \ - apt-get upgrade -y && \ - apt-get dist-upgrade -y && \ - apt-get install build-essential software-properties-common -y && \ - add-apt-repository ppa:ubuntu-toolchain-r/test -y && \ - apt-get update -y && \ - apt-get install gcc-7 g++-7 -y && \ - update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-7 60 --slave /usr/bin/g++ g++ /usr/bin/g++-7 && \ - update-alternatives --config gcc - -# Install llvm 3.9.0 (mach requires 3.9.0+) -RUN echo "deb http://apt.llvm.org/trusty/ llvm-toolchain-trusty-3.9 main" >> /etc/apt/sources.list && \ - echo "deb-src http://apt.llvm.org/trusty/ llvm-toolchain-trusty-3.9 main" >> /etc/apt/sources.list && \ - apt-get install clang-3.9 lldb-3.9 -y - -# Install python 3.6 (mach requires 3.5+) -RUN add-apt-repository ppa:deadsnakes/ppa -y && \ - apt-get update -y && apt-get install python3.6 -y diff --git a/experimental/juggler/FIREFOX_SHA b/experimental/juggler/FIREFOX_SHA deleted file mode 100644 index 577133a2477..00000000000 --- a/experimental/juggler/FIREFOX_SHA +++ /dev/null @@ -1 +0,0 @@ -120450a2c56c25e7c410a909192b5b9ad7b0dff2 diff --git a/experimental/juggler/README.md b/experimental/juggler/README.md deleted file mode 100644 index 640b4633a40..00000000000 --- a/experimental/juggler/README.md +++ /dev/null @@ -1,119 +0,0 @@ -# Juggler - -> Juggler - Firefox Automation Protocol for implementing the Puppeteer API. - -## Protocol - -See [`//src/Protocol.js`](https://github.com/GoogleChrome/puppeteer/blob/master/experimental/juggler/src/Protocol.js). - -## Building FF with Juggler - -1. Clone Juggler repository -```bash -git clone https://github.com/aslushnikov/juggler -cd juggler -``` - -2. Checkout [pinned Firefox revision](https://github.com/aslushnikov/juggler/blob/master/FIREFOX_SHA) from mozilla [github mirror](https://github.com/mozilla/gecko-dev) into `//firefox` folder. - -```bash -SOURCE=$PWD bash scripts/fetch_firefox.sh -``` - -3. Apply juggler patches to Firefox source code - -```bash -cd firefox -git am ../patches/* -``` - -4. Add Juggler to Firefox. NOTE: On Linux, symlinks work. On OSX, files have to be copied. - -```bash -# LINUX: -ln -s $PWD/../src $PWD/testing/juggler -# OSX: -cp -r $PWD/../src $PWD/testing/juggler -``` - -5. Bootstrap host environment for Firefox build and compile firefox locally - -```bash -# OPTIONAL - bootstrap host environment. -./mach bootstrap --application-choice=browser --no-interactive -# Compile browser -./mach build -``` - -### Troubleshooting when building FF on Mac -#### Black screen after FF Build -As of Jan. 2019 there is a known [bug](https://bugzilla.mozilla.org/show_bug.cgi?id=1493330) that will cause an entirely black screen when running the nightly build of firefox built with the **MacOSX SDK version 10.14.** - -The easiest fix right now is downgrading your MacOSX SDK. - -To do so: - -1) Go to [this repo](https://github.com/phracker/MacOSX-SDKs) and install any **SDK version < 10.14** (e.g. 10.13 works fine) - -2) In the `juggler/firefox` folder: - -```bash -echo "ac_add_options --with-macos-sdk=path/to/sdk" >> .mozconfig -# your SDK might be located at -# /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/ -``` - -3) run `./mach build` again - - -#### Missing headers in /usr/include - -On MacOS 10.14 (Mojave) you might run into issues when building FF. - -The error is related to [a change in the xcode-select installation](https://bugzilla.mozilla.org/show_bug.cgi?id=1487552) - -To workaround this issue you can simply run: - -```bash -# Write missing headers to /usr/include -sudo installer -pkg /Library/Developer/CommandLineTools/Packages/macOS_SDK_headers_for_macOS_10.14.pkg -target / -``` - -## Running Firefox with Juggler - -Juggle adds a `-juggler` CLI flag that accepts a port to expose a remote protocol on. -Pass `0` to pick a random port - Juggler will print its port to STDOUT. - -``` -./mach run -- -juggler 0 -``` - -## Uploading builds to Google Storage - -Firefox builds with Juggler support are uploaded to gs://juggler-builds/ bucket. - -Project maintainers can upload builds. -To upload a build, do the following: - -1. Install [gcloud](https://cloud.google.com/sdk/install) if you haven't yet. -2. Authenticate in the cloud and select project - -```bash -gcloud auth login -gcloud config set project juggler-builds -``` - -3. Make sure **firefox is compiled**; after that, package a build for a redistribution: - -```bash -cd firefox -./mach package -``` - -4. Archive build and copy to the gbucket - -We want to ship `*.zip` archives so that it's easy to decompress them on the node-side. - -- Linux: `./scripts/upload_linux.sh` -- Mac: `./scripts/upload_mac.sh` - diff --git a/experimental/juggler/patches/0001-Introduce-nsIWebProgressListener2-onFrameLocationCha.patch b/experimental/juggler/patches/0001-Introduce-nsIWebProgressListener2-onFrameLocationCha.patch deleted file mode 100644 index 648e43c1574..00000000000 --- a/experimental/juggler/patches/0001-Introduce-nsIWebProgressListener2-onFrameLocationCha.patch +++ /dev/null @@ -1,155 +0,0 @@ -From 5082f80b83290be204cd80124d292d1c563d2d13 Mon Sep 17 00:00:00 2001 -From: Andrey Lushnikov -Date: Thu, 24 Jan 2019 11:13:22 -0500 -Subject: [PATCH] Introduce nsIWebProgressListener2::onFrameLocationChange - -The event is fired when subframes commit navigation. -Juggler uses this event to track same-document iframe navigations. ---- - docshell/base/nsDocShell.cpp | 1 + - .../statusfilter/nsBrowserStatusFilter.cpp | 8 +++++++ - uriloader/base/nsDocLoader.cpp | 18 +++++++++++++++ - uriloader/base/nsDocLoader.h | 5 ++++ - uriloader/base/nsIWebProgress.idl | 7 +++++- - uriloader/base/nsIWebProgressListener2.idl | 23 +++++++++++++++++++ - 6 files changed, 61 insertions(+), 1 deletion(-) - -diff --git a/docshell/base/nsDocShell.cpp b/docshell/base/nsDocShell.cpp -index ef2e46b33a..31471e3465 100644 ---- a/docshell/base/nsDocShell.cpp -+++ b/docshell/base/nsDocShell.cpp -@@ -1198,6 +1198,7 @@ bool nsDocShell::SetCurrentURI(nsIURI* aURI, nsIRequest* aRequest, - isSubFrame = mLSHE->GetIsSubFrame(); - } - -+ FireOnFrameLocationChange(this, aRequest, aURI, aLocationFlags); - if (!isSubFrame && !isRoot) { - /* - * We don't want to send OnLocationChange notifications when -diff --git a/toolkit/components/statusfilter/nsBrowserStatusFilter.cpp b/toolkit/components/statusfilter/nsBrowserStatusFilter.cpp -index 61fcfef258..264f9c1e61 100644 ---- a/toolkit/components/statusfilter/nsBrowserStatusFilter.cpp -+++ b/toolkit/components/statusfilter/nsBrowserStatusFilter.cpp -@@ -170,6 +170,14 @@ nsBrowserStatusFilter::OnStateChange(nsIWebProgress *aWebProgress, - return NS_OK; - } - -+NS_IMETHODIMP -+nsBrowserStatusFilter::OnFrameLocationChange(nsIWebProgress *aWebProgress, -+ nsIRequest *aRequest, -+ nsIURI *aLocation, -+ uint32_t aFlags) { -+ return NS_OK; -+} -+ - NS_IMETHODIMP - nsBrowserStatusFilter::OnProgressChange(nsIWebProgress *aWebProgress, - nsIRequest *aRequest, -diff --git a/uriloader/base/nsDocLoader.cpp b/uriloader/base/nsDocLoader.cpp -index a3bc24e603..67b3d3eaeb 100644 ---- a/uriloader/base/nsDocLoader.cpp -+++ b/uriloader/base/nsDocLoader.cpp -@@ -1252,6 +1252,24 @@ void nsDocLoader::FireOnLocationChange(nsIWebProgress* aWebProgress, - } - } - -+void nsDocLoader::FireOnFrameLocationChange(nsIWebProgress* aWebProgress, -+ nsIRequest* aRequest, -+ nsIURI *aUri, -+ uint32_t aFlags) { -+ NOTIFY_LISTENERS(nsIWebProgress::NOTIFY_FRAME_LOCATION, -+ nsCOMPtr listener2 = -+ do_QueryReferent(info.mWeakListener); -+ if (!listener2) -+ continue; -+ listener2->OnFrameLocationChange(aWebProgress, aRequest, aUri, aFlags); -+ ); -+ -+ // Pass the notification up to the parent... -+ if (mParent) { -+ mParent->FireOnFrameLocationChange(aWebProgress, aRequest, aUri, aFlags); -+ } -+} -+ - void nsDocLoader::FireOnStatusChange(nsIWebProgress* aWebProgress, - nsIRequest* aRequest, nsresult aStatus, - const char16_t* aMessage) { -diff --git a/uriloader/base/nsDocLoader.h b/uriloader/base/nsDocLoader.h -index 45f0d3d88e..7848878b70 100644 ---- a/uriloader/base/nsDocLoader.h -+++ b/uriloader/base/nsDocLoader.h -@@ -153,6 +153,11 @@ class nsDocLoader : public nsIDocumentLoader, - void FireOnLocationChange(nsIWebProgress* aWebProgress, nsIRequest* aRequest, - nsIURI* aUri, uint32_t aFlags); - -+ void FireOnFrameLocationChange(nsIWebProgress* aWebProgress, -+ nsIRequest* aRequest, -+ nsIURI *aUri, -+ uint32_t aFlags); -+ - MOZ_MUST_USE bool RefreshAttempted(nsIWebProgress* aWebProgress, nsIURI* aURI, - int32_t aDelay, bool aSameURI); - -diff --git a/uriloader/base/nsIWebProgress.idl b/uriloader/base/nsIWebProgress.idl -index 0549f32e1e..3078e35d7a 100644 ---- a/uriloader/base/nsIWebProgress.idl -+++ b/uriloader/base/nsIWebProgress.idl -@@ -84,17 +84,22 @@ interface nsIWebProgress : nsISupports - * NOTIFY_REFRESH - * Receive onRefreshAttempted events. - * This is defined on nsIWebProgressListener2. -+ * -+ * NOTIFY_FRAME_LOCATION -+ * Receive onFrameLocationChange events. -+ * This is defined on nsIWebProgressListener2. - */ - const unsigned long NOTIFY_PROGRESS = 0x00000010; - const unsigned long NOTIFY_STATUS = 0x00000020; - const unsigned long NOTIFY_SECURITY = 0x00000040; - const unsigned long NOTIFY_LOCATION = 0x00000080; - const unsigned long NOTIFY_REFRESH = 0x00000100; -+ const unsigned long NOTIFY_FRAME_LOCATION = 0x00000200; - - /** - * This flag enables all notifications. - */ -- const unsigned long NOTIFY_ALL = 0x000001ff; -+ const unsigned long NOTIFY_ALL = 0x000002ff; - - /** - * Registers a listener to receive web progress events. -diff --git a/uriloader/base/nsIWebProgressListener2.idl b/uriloader/base/nsIWebProgressListener2.idl -index 87701f8d2c..ae1aa85c01 100644 ---- a/uriloader/base/nsIWebProgressListener2.idl -+++ b/uriloader/base/nsIWebProgressListener2.idl -@@ -66,4 +66,27 @@ interface nsIWebProgressListener2 : nsIWebProgressListener { - in nsIURI aRefreshURI, - in long aMillis, - in boolean aSameURI); -+ -+ /** -+ * Called when the location of the window or its subframes changes. This is not -+ * when a load is requested, but rather once it is verified that the load is -+ * going to occur in the given window. For instance, a load that starts in a -+ * window might send progress and status messages for the new site, but it -+ * will not send the onLocationChange until we are sure that we are loading -+ * this new page here. -+ * -+ * @param aWebProgress -+ * The nsIWebProgress instance that fired the notification. -+ * @param aRequest -+ * The associated nsIRequest. This may be null in some cases. -+ * @param aLocation -+ * The URI of the location that is being loaded. -+ * @param aFlags -+ * This is a value which explains the situation or the reason why -+ * the location has changed. -+ */ -+ void onFrameLocationChange(in nsIWebProgress aWebProgress, -+ in nsIRequest aRequest, -+ in nsIURI aLocation, -+ [optional] in unsigned long aFlags); - }; --- -2.19.0.605.g01d371f741-goog - diff --git a/experimental/juggler/patches/0002-Add-Juggler-to-gecko-build-system.patch b/experimental/juggler/patches/0002-Add-Juggler-to-gecko-build-system.patch deleted file mode 100644 index 3e9b94d873d..00000000000 --- a/experimental/juggler/patches/0002-Add-Juggler-to-gecko-build-system.patch +++ /dev/null @@ -1,24 +0,0 @@ -From c6f975dbc28b902cc271f79dedc42073ab1bde7d Mon Sep 17 00:00:00 2001 -From: Andrey Lushnikov -Date: Tue, 27 Nov 2018 13:39:00 -0800 -Subject: [PATCH 2/3] Add Juggler to gecko build system - ---- - toolkit/toolkit.mozbuild | 1 + - 1 file changed, 1 insertion(+) - -diff --git a/toolkit/toolkit.mozbuild b/toolkit/toolkit.mozbuild -index 4a0e5f172..b8abc1e72 100644 ---- a/toolkit/toolkit.mozbuild -+++ b/toolkit/toolkit.mozbuild -@@ -163,6 +163,7 @@ if CONFIG['ENABLE_MARIONETTE']: - DIRS += [ - '/testing/firefox-ui', - '/testing/marionette', -+ '/testing/juggler', - ] - - if CONFIG['ENABLE_GECKODRIVER'] and not CONFIG['MOZ_TSAN']: --- -2.19.0.605.g01d371f741-goog - diff --git a/experimental/juggler/patches/0003-Add-Juggler-to-mozilla-packaging-script.patch b/experimental/juggler/patches/0003-Add-Juggler-to-mozilla-packaging-script.patch deleted file mode 100644 index 4b8880d629d..00000000000 --- a/experimental/juggler/patches/0003-Add-Juggler-to-mozilla-packaging-script.patch +++ /dev/null @@ -1,43 +0,0 @@ -From 1449495af094fbc5e1bb351f8387c3a341977763 Mon Sep 17 00:00:00 2001 -From: Andrey Lushnikov -Date: Thu, 29 Nov 2018 11:40:32 -0800 -Subject: [PATCH 3/3] Add Juggler to mozilla packaging script - ---- - browser/installer/allowed-dupes.mn | 6 ++++++ - browser/installer/package-manifest.in | 5 +++++ - 2 files changed, 11 insertions(+) - -diff --git a/browser/installer/allowed-dupes.mn b/browser/installer/allowed-dupes.mn -index 5685a30d9..32ba241b8 100644 ---- a/browser/installer/allowed-dupes.mn -+++ b/browser/installer/allowed-dupes.mn -@@ -154,3 +154,9 @@ browser/defaults/settings/main/example.json - # Bug 1463748 - Fork and pref-off the new error pages - browser/chrome/browser/content/browser/aboutNetError-new.xhtml - browser/chrome/browser/content/browser/aboutNetError.xhtml -+ -+# Juggler/marionette files -+chrome/juggler/content/content/floating-scrollbars.css -+browser/chrome/devtools/skin/floating-scrollbars-responsive-design.css -+chrome/juggler/content/server/stream-utils.js -+chrome/marionette/content/stream-utils.js -diff --git a/browser/installer/package-manifest.in b/browser/installer/package-manifest.in -index 5b828784a..a5d9f9741 100644 ---- a/browser/installer/package-manifest.in -+++ b/browser/installer/package-manifest.in -@@ -338,6 +338,11 @@ - @RESPATH@/defaults/pref/marionette.js - #endif - -+@RESPATH@/chrome/juggler@JAREXT@ -+@RESPATH@/chrome/juggler.manifest -+@RESPATH@/components/juggler.manifest -+@RESPATH@/components/juggler.js -+ - @RESPATH@/components/nsAsyncShutdown.manifest - @RESPATH@/components/nsAsyncShutdown.js - --- -2.19.0.605.g01d371f741-goog - diff --git a/experimental/juggler/scripts/fetch_firefox.sh b/experimental/juggler/scripts/fetch_firefox.sh deleted file mode 100755 index b69a3d92e56..00000000000 --- a/experimental/juggler/scripts/fetch_firefox.sh +++ /dev/null @@ -1,18 +0,0 @@ -set -e -set -x - -if [ -d $SOURCE/firefox ]; then - echo ERROR! Directory "${SOURCE}/firefox" exists. Remove it and re-run this script. - exit 1; -fi -mkdir -p $SOURCE/firefox -cd $SOURCE/firefox -git init -git remote add origin https://github.com/mozilla/gecko-dev.git -git fetch --depth 50 origin release -git reset --hard $(cat $SOURCE/FIREFOX_SHA) -if [[ $? == 0 ]]; then - echo SUCCESS -else - echo FAILED TO CHECKOUT PINNED REVISION -fi diff --git a/experimental/juggler/scripts/install_gcloud.sh b/experimental/juggler/scripts/install_gcloud.sh deleted file mode 100755 index becb8b68a2c..00000000000 --- a/experimental/juggler/scripts/install_gcloud.sh +++ /dev/null @@ -1,9 +0,0 @@ -# auth -echo $GS_AUTH > $SOURCE/gsauth -# install gcloud sdk -curl https://dl.google.com/dl/cloudsdk/release/google-cloud-sdk.tar.gz > /tmp/google-cloud-sdk.tar.gz -mkdir -p $SOURCE/gcloud \ - && tar -C $SOURCE/gcloud -xvf /tmp/google-cloud-sdk.tar.gz \ - && CLOUDSDK_CORE_DISABLE_PROMPTS=1 $SOURCE/gcloud/google-cloud-sdk/install.sh -gcloud auth activate-service-account --key-file=$SOURCE/gsauth -gcloud config set project juggler-builds diff --git a/experimental/juggler/scripts/upload_linux.sh b/experimental/juggler/scripts/upload_linux.sh deleted file mode 100755 index 6bacdca59e8..00000000000 --- a/experimental/juggler/scripts/upload_linux.sh +++ /dev/null @@ -1,13 +0,0 @@ -set -e - -if [ -e ./FIREFOX_SHA ]; then - echo Checking Juggler root - OK -else - echo Please run this script from the Juggler root - exit 1; -fi -cd firefox/obj-x86_64-pc-linux-gnu/dist/ -zip -r firefox-linux.zip firefox -mv firefox-linux.zip ../../../ -cd - -gsutil mv firefox-linux.zip gs://juggler-builds/$(git rev-parse HEAD)/ diff --git a/experimental/juggler/scripts/upload_mac.sh b/experimental/juggler/scripts/upload_mac.sh deleted file mode 100755 index 1f4d0d37e03..00000000000 --- a/experimental/juggler/scripts/upload_mac.sh +++ /dev/null @@ -1,13 +0,0 @@ -set -e - -if [ -e ./FIREFOX_SHA ]; then - echo Checking Juggler root - OK -else - echo Please run this script from the Juggler root - exit 1; -fi -cd firefox/obj-x86_64-apple-darwin17.7.0/dist/ -zip -r firefox-mac.zip firefox -mv firefox-mac.zip ../../../ -cd - -gsutil mv firefox-mac.zip gs://juggler-builds/$(git rev-parse HEAD)/ diff --git a/experimental/juggler/src/BrowserHandler.jsm b/experimental/juggler/src/BrowserHandler.jsm deleted file mode 100644 index 62539429b80..00000000000 --- a/experimental/juggler/src/BrowserHandler.jsm +++ /dev/null @@ -1,149 +0,0 @@ -"use strict"; - -const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm"); -const {PageHandler} = ChromeUtils.import("chrome://juggler/content/PageHandler.jsm"); -const {InsecureSweepingOverride} = ChromeUtils.import("chrome://juggler/content/InsecureSweepingOverride.js"); - -class BrowserHandler { - constructor(session) { - this._session = session; - this._mainWindowPromise = waitForBrowserWindow(); - this._pageHandlers = new Map(); - this._tabsToPageHandlers = new Map(); - this._initializePages(); - this._sweepingOverride = null; - } - - async setIgnoreHTTPSErrors({enabled}) { - if (!enabled && this._sweepingOverride) { - this._sweepingOverride.unregister(); - this._sweepingOverride = null; - Services.prefs.setBoolPref('security.mixed_content.block_active_content', true); - } else if (enabled && !this._sweepingOverride) { - this._sweepingOverride = new InsecureSweepingOverride(); - this._sweepingOverride.register(); - Services.prefs.setBoolPref('security.mixed_content.block_active_content', false); - } - } - - async getInfo() { - const win = await this._mainWindowPromise; - const version = Components.classes["@mozilla.org/xre/app-info;1"] - .getService(Components.interfaces.nsIXULAppInfo) - .version; - const userAgent = Components.classes["@mozilla.org/network/protocol;1?name=http"] - .getService(Components.interfaces.nsIHttpProtocolHandler) - .userAgent; - return {version: 'Firefox/' + version, userAgent}; - } - - async _initializePages() { - const win = await this._mainWindowPromise; - const tabs = win.gBrowser.tabs; - for (const tab of win.gBrowser.tabs) - this._ensurePageHandler(tab); - win.gBrowser.tabContainer.addEventListener('TabOpen', event => { - this._ensurePageHandler(event.target); - }); - win.gBrowser.tabContainer.addEventListener('TabClose', event => { - this._removePageHandlerForTab(event.target); - }); - } - - pageForId(pageId) { - return this._pageHandlers.get(pageId) || null; - } - - _ensurePageHandler(tab) { - if (this._tabsToPageHandlers.has(tab)) - return this._tabsToPageHandlers.get(tab); - const pageHandler = new PageHandler(this._session, tab); - this._pageHandlers.set(pageHandler.id(), pageHandler); - this._tabsToPageHandlers.set(tab, pageHandler); - this._session.emitEvent('Browser.tabOpened', { - url: pageHandler.url(), - pageId: pageHandler.id() - }); - return pageHandler; - } - - _removePageHandlerForTab(tab) { - const pageHandler = this._tabsToPageHandlers.get(tab); - this._tabsToPageHandlers.delete(tab); - this._pageHandlers.delete(pageHandler.id()); - pageHandler.dispose(); - this._session.emitEvent('Browser.tabClosed', {pageId: pageHandler.id()}); - } - - async newPage() { - const win = await this._mainWindowPromise; - const tab = win.gBrowser.addTab('about:blank', { - triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(), - }); - win.gBrowser.selectedTab = tab; - // Await navigation to about:blank - await new Promise(resolve => { - const wpl = { - onLocationChange: function(aWebProgress, aRequest, aLocation) { - tab.linkedBrowser.removeProgressListener(wpl); - resolve(); - }, - QueryInterface: ChromeUtils.generateQI([ - Ci.nsIWebProgressListener, - Ci.nsISupportsWeakReference, - ]), - }; - tab.linkedBrowser.addProgressListener(wpl); - }); - const pageHandler = this._ensurePageHandler(tab); - return {pageId: pageHandler.id()}; - } - - async closePage({pageId}) { - const win = await this._mainWindowPromise; - const pageHandler = this._pageHandlers.get(pageId); - await win.gBrowser.removeTab(pageHandler.tab()); - } -} - -/** - * @return {!Promise} - */ -async function waitForBrowserWindow() { - const windowsIt = Services.wm.getEnumerator('navigator:browser'); - if (windowsIt.hasMoreElements()) - return waitForWindowLoaded(windowsIt.getNext().QueryInterface(Ci.nsIDOMChromeWindow)); - - let fulfill; - let promise = new Promise(x => fulfill = x); - - const listener = { - onOpenWindow: window => { - if (window instanceof Ci.nsIDOMChromeWindow) { - Services.wm.removeListener(listener); - fulfill(waitForWindowLoaded(window)); - } - }, - onCloseWindow: () => {} - }; - Services.wm.addListener(listener); - return promise; - - /** - * @param {!Ci.nsIDOMChromeWindow} window - * @return {!Promise} - */ - function waitForWindowLoaded(window) { - if (window.document.readyState === 'complete') - return window; - return new Promise(fulfill => { - window.addEventListener('load', function listener() { - window.removeEventListener('load', listener); - fulfill(window); - }); - }); - } -} - -var EXPORTED_SYMBOLS = ['BrowserHandler']; -this.BrowserHandler = BrowserHandler; diff --git a/experimental/juggler/src/ChromeSession.js b/experimental/juggler/src/ChromeSession.js deleted file mode 100644 index 562706c0e80..00000000000 --- a/experimental/juggler/src/ChromeSession.js +++ /dev/null @@ -1,72 +0,0 @@ -const {BrowserHandler} = ChromeUtils.import("chrome://juggler/content/BrowserHandler.jsm"); -const {protocol, checkScheme} = ChromeUtils.import("chrome://juggler/content/Protocol.js"); - -class ChromeSession { - constructor(connection) { - this._connection = connection; - this._connection.onmessage = this._dispatch.bind(this); - - this._browserHandler = new BrowserHandler(this); - } - - emitEvent(eventName, params) { - const scheme = protocol.events[eventName]; - if (!scheme) - throw new Error(`ERROR: event '${eventName}' is not supported`); - const details = {}; - if (!checkScheme(scheme, params || {}, details)) - throw new Error(`ERROR: event '${eventName}' is called with ${details.errorType} parameter '${details.propertyName}': ${details.propertyValue}`); - this._connection.send({method: eventName, params}); - } - - async _dispatch(data) { - const id = data.id; - try { - const method = data.method; - const params = data.params || {}; - if (!id) - throw new Error(`ERROR: every message must have an 'id' parameter`); - if (!method) - throw new Error(`ERROR: every message must have a 'method' parameter`); - - const descriptor = protocol.methods[method]; - if (!descriptor) - throw new Error(`ERROR: method '${method}' is not supported`); - let details = {}; - if (!checkScheme(descriptor.params || {}, params, details)) - throw new Error(`ERROR: method '${method}' is called with ${details.errorType} parameter '${details.propertyName}': ${details.propertyValue}`); - - const result = await this._innerDispatch(method, params); - - details = {}; - if ((descriptor.returns || result) && !checkScheme(descriptor.returns, result, details)) - throw new Error(`ERROR: method '${method}' returned ${details.errorType} parameter '${details.propertyName}': ${details.propertyValue}`); - - this._connection.send({id, result}); - } catch (e) { - this._connection.send({id, error: { - message: e.message, - data: e.stack - }}); - } - } - - async _innerDispatch(method, params) { - const [domainName, methodName] = method.split('.'); - if (domainName === 'Browser') - return await this._browserHandler[methodName](params); - if (domainName === 'Page') { - if (!params.pageId) - throw new Error('Parameter "pageId" must be present for Page.* methods'); - const pageHandler = this._browserHandler.pageForId(params.pageId); - if (!pageHandler) - throw new Error('Failed to find page for id = ' + pageId); - return await pageHandler[methodName](params); - } - throw new Error(`INTERNAL ERROR: failed to dispatch '${method}'`); - } -} - -this.EXPORTED_SYMBOLS = ['ChromeSession']; -this.ChromeSession = ChromeSession; - diff --git a/experimental/juggler/src/Helper.js b/experimental/juggler/src/Helper.js deleted file mode 100644 index 47036b22332..00000000000 --- a/experimental/juggler/src/Helper.js +++ /dev/null @@ -1,46 +0,0 @@ -const uuidGen = Cc["@mozilla.org/uuid-generator;1"].getService(Ci.nsIUUIDGenerator); -const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm"); - -class Helper { - addObserver(handler, topic) { - Services.obs.addObserver(handler, topic); - return () => Services.obs.removeObserver(handler, topic); - } - - addMessageListener(receiver, eventName, handler) { - receiver.addMessageListener(eventName, handler); - return () => receiver.removeMessageListener(eventName, handler); - } - - addEventListener(receiver, eventName, handler) { - receiver.addEventListener(eventName, handler); - return () => receiver.removeEventListener(eventName, handler); - } - - on(receiver, eventName, handler) { - // The toolkit/modules/EventEmitter.jsm dispatches event name as a first argument. - // Fire event listeners without it for convenience. - const handlerWrapper = (_, ...args) => handler(...args); - receiver.on(eventName, handlerWrapper); - return () => receiver.off(eventName, handlerWrapper); - } - - addProgressListener(progress, listener, flags) { - progress.addProgressListener(listener, flags); - return () => progress.removeProgressListener(listener); - } - - removeListeners(listeners) { - for (const tearDown of listeners) - tearDown.call(null); - listeners.splice(0, listeners.length); - } - - generateId() { - return uuidGen.generateUUID().toString(); - } -} - -var EXPORTED_SYMBOLS = [ "Helper" ]; -this.Helper = Helper; - diff --git a/experimental/juggler/src/InsecureSweepingOverride.js b/experimental/juggler/src/InsecureSweepingOverride.js deleted file mode 100644 index 3a95e7398f8..00000000000 --- a/experimental/juggler/src/InsecureSweepingOverride.js +++ /dev/null @@ -1,78 +0,0 @@ -/* 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/. */ - -"use strict"; - -ChromeUtils.import("resource://gre/modules/Preferences.jsm"); -ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm"); - -const registrar = - Components.manager.QueryInterface(Ci.nsIComponentRegistrar); -const sss = Cc["@mozilla.org/ssservice;1"] - .getService(Ci.nsISiteSecurityService); - -const CERT_PINNING_ENFORCEMENT_PREF = "security.cert_pinning.enforcement_level"; -const CID = Components.ID("{4b67cce0-a51c-11e6-9598-0800200c9a66}"); -const CONTRACT_ID = "@mozilla.org/security/certoverride;1"; -const DESC = "All-encompassing cert service that matches on a bitflag"; -const HSTS_PRELOAD_LIST_PREF = "network.stricttransportsecurity.preloadlist"; - -const Error = { - Untrusted: 1, - Mismatch: 2, - Time: 4, -}; - -/** - * Certificate override service that acts in an all-inclusive manner - * on TLS certificates. - * - * @throws {Components.Exception} - * If there are any problems registering the service. - */ -function InsecureSweepingOverride() { - // This needs to be an old-style class with a function constructor - // and prototype assignment because... XPCOM. Any attempt at - // modernisation will be met with cryptic error messages which will - // make your life miserable. - let service = function() {}; - service.prototype = { - hasMatchingOverride( - aHostName, aPort, aCert, aOverrideBits, aIsTemporary) { - aIsTemporary.value = false; - aOverrideBits.value = Error.Untrusted | Error.Mismatch | Error.Time; - - return true; - }, - - QueryInterface: ChromeUtils.generateQI([Ci.nsICertOverrideService]), - }; - let factory = XPCOMUtils.generateSingletonFactory(service); - - return { - register() { - // make it possible to register certificate overrides for domains - // that use HSTS or HPKP - Preferences.set(HSTS_PRELOAD_LIST_PREF, false); - Preferences.set(CERT_PINNING_ENFORCEMENT_PREF, 0); - - registrar.registerFactory(CID, DESC, CONTRACT_ID, factory); - }, - - unregister() { - registrar.unregisterFactory(CID, factory); - - Preferences.reset(HSTS_PRELOAD_LIST_PREF); - Preferences.reset(CERT_PINNING_ENFORCEMENT_PREF); - - // clear collected HSTS and HPKP state - // through the site security service - sss.clearAll(); - sss.clearPreloads(); - }, - }; -} - -this.EXPORTED_SYMBOLS = ["InsecureSweepingOverride"]; -this.InsecureSweepingOverride = InsecureSweepingOverride; diff --git a/experimental/juggler/src/PageHandler.jsm b/experimental/juggler/src/PageHandler.jsm deleted file mode 100644 index 8b65c969578..00000000000 --- a/experimental/juggler/src/PageHandler.jsm +++ /dev/null @@ -1,337 +0,0 @@ -"use strict"; - -const {Helper} = ChromeUtils.import('chrome://juggler/content/Helper.js'); - -const Cc = Components.classes; -const Ci = Components.interfaces; -const Cu = Components.utils; -const XUL_NS = 'http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul'; -const FRAME_SCRIPT = "chrome://juggler/content/content/ContentSession.js"; -const helper = new Helper(); - -class PageHandler { - constructor(chromeSession, tab) { - this._pageId = helper.generateId(); - this._chromeSession = chromeSession; - this._tab = tab; - this._browser = tab.linkedBrowser; - this._enabled = false; - this.QueryInterface = ChromeUtils.generateQI([ - Ci.nsIWebProgressListener, - Ci.nsISupportsWeakReference, - ]); - this._browser.addProgressListener(this, Ci.nsIWebProgress.NOTIFY_LOCATION); - this._dialogs = new Map(); - - // First navigation always happens to about:blank - do not report it. - this._skipNextNavigation = true; - } - - async setViewport({viewport}) { - if (viewport) { - const {width, height} = viewport; - this._browser.style.setProperty('min-width', width + 'px'); - this._browser.style.setProperty('min-height', height + 'px'); - this._browser.style.setProperty('max-width', width + 'px'); - this._browser.style.setProperty('max-height', height + 'px'); - } else { - this._browser.style.removeProperty('min-width'); - this._browser.style.removeProperty('min-height'); - this._browser.style.removeProperty('max-width'); - this._browser.style.removeProperty('max-height'); - } - const dimensions = this._browser.getBoundingClientRect(); - await Promise.all([ - this._contentSession.send('setViewport', { - deviceScaleFactor: viewport ? viewport.deviceScaleFactor : 0, - isMobile: viewport && viewport.isMobile, - hasTouch: viewport && viewport.hasTouch, - }), - this._contentSession.send('awaitViewportDimensions', { - width: dimensions.width, - height: dimensions.height - }), - ]); - } - - _initializeDialogEvents() { - this._browser.addEventListener('DOMWillOpenModalDialog', async (event) => { - // wait for the dialog to be actually added to DOM. - await Promise.resolve(); - this._updateModalDialogs(); - }); - this._browser.addEventListener('DOMModalDialogClosed', (event) => { - this._updateModalDialogs(); - }); - this._updateModalDialogs(); - } - - _updateModalDialogs() { - const elements = new Set(this._browser.parentNode.getElementsByTagNameNS(XUL_NS, "tabmodalprompt")); - for (const dialog of this._dialogs.values()) { - if (!elements.has(dialog.element())) { - this._dialogs.delete(dialog.id()); - this._chromeSession.emitEvent('Page.dialogClosed', { - pageId: this._pageId, - dialogId: dialog.id(), - }); - } else { - elements.delete(dialog.element()); - } - } - for (const element of elements) { - const dialog = Dialog.createIfSupported(element); - if (!dialog) - continue; - this._dialogs.set(dialog.id(), dialog); - this._chromeSession.emitEvent('Page.dialogOpened', { - pageId: this._pageId, - dialogId: dialog.id(), - type: dialog.type(), - message: dialog.message(), - defaultValue: dialog.defaultValue(), - }); - } - } - - onLocationChange(aWebProgress, aRequest, aLocation) { - if (this._skipNextNavigation) { - this._skipNextNavigation = false; - return; - } - this._chromeSession.emitEvent('Browser.tabNavigated', { - pageId: this._pageId, - url: aLocation.spec - }); - } - - url() { - return this._browser.currentURI.spec; - } - - tab() { - return this._tab; - } - - id() { - return this._pageId; - } - - async enable() { - if (this._enabled) - return; - this._enabled = true; - this._initializeDialogEvents(); - this._contentSession = new ContentSession(this._chromeSession, this._browser, this._pageId); - await this._contentSession.send('enable'); - } - - async screenshot(options) { - return await this._contentSession.send('screenshot', options); - } - - async getBoundingBox(options) { - return await this._contentSession.send('getBoundingBox', options); - } - - async getContentQuads(options) { - return await this._contentSession.send('getContentQuads', options); - } - - /** - * @param {{frameId: string, url: string}} options - */ - async navigate(options) { - return await this._contentSession.send('navigate', options); - } - - /** - * @param {{frameId: string, url: string}} options - */ - async goBack(options) { - return await this._contentSession.send('goBack', options); - } - - /** - * @param {{frameId: string, url: string}} options - */ - async goForward(options) { - return await this._contentSession.send('goForward', options); - } - - /** - * @param {{frameId: string, url: string}} options - */ - async reload(options) { - return await this._contentSession.send('reload', options); - } - - /** - * @param {{functionText: String, frameId: String}} options - * @return {!Promise<*>} - */ - async evaluate(options) { - return await this._contentSession.send('evaluate', options); - } - - async getObjectProperties(options) { - return await this._contentSession.send('getObjectProperties', options); - } - - async addScriptToEvaluateOnNewDocument(options) { - return await this._contentSession.send('addScriptToEvaluateOnNewDocument', options); - } - - async removeScriptToEvaluateOnNewDocument(options) { - return await this._contentSession.send('removeScriptToEvaluateOnNewDocument', options); - } - - async disposeObject(options) { - return await this._contentSession.send('disposeObject', options); - } - - async dispatchKeyEvent(options) { - return await this._contentSession.send('dispatchKeyEvent', options); - } - - async dispatchMouseEvent(options) { - return await this._contentSession.send('dispatchMouseEvent', options); - } - - async insertText(options) { - return await this._contentSession.send('insertText', options); - } - - async handleDialog({dialogId, accept, promptText}) { - const dialog = this._dialogs.get(dialogId); - if (!dialog) - throw new Error('Failed to find dialog with id = ' + dialogId); - if (accept) - dialog.accept(promptText); - else - dialog.dismiss(); - } - - dispose() { - this._browser.removeProgressListener(this); - if (this._contentSession) { - this._contentSession.dispose(); - this._contentSession = null; - } - } -} - -class ContentSession { - constructor(chromeSession, browser, pageId) { - this._chromeSession = chromeSession; - this._browser = browser; - this._pageId = pageId; - this._messageId = 0; - this._pendingMessages = new Map(); - this._sessionId = helper.generateId(); - this._browser.messageManager.sendAsyncMessage('juggler:create-content-session', this._sessionId); - this._eventListeners = [ - helper.addMessageListener(this._browser.messageManager, this._sessionId, { - receiveMessage: message => this._onMessage(message) - }), - ]; - } - - dispose() { - helper.removeListeners(this._eventListeners); - for (const {resolve, reject} of this._pendingMessages.values()) - reject(new Error('Page closed.')); - this._pendingMessages.clear(); - } - - /** - * @param {string} methodName - * @param {*} params - * @return {!Promise<*>} - */ - send(methodName, params) { - const id = ++this._messageId; - const promise = new Promise((resolve, reject) => { - this._pendingMessages.set(id, {resolve, reject}); - }); - this._browser.messageManager.sendAsyncMessage(this._sessionId, {id, methodName, params}); - return promise; - } - - _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) - reject(new Error(data.error)); - else - resolve(data.result); - } else { - const { - eventName, - params = {} - } = data; - params.pageId = this._pageId; - this._chromeSession.emitEvent(eventName, params); - } - } -} - -class Dialog { - static createIfSupported(element) { - const type = element.Dialog.args.promptType; - switch (type) { - case 'alert': - case 'prompt': - case 'confirm': - return new Dialog(element, type); - case 'confirmEx': - return new Dialog(element, 'beforeunload'); - default: - return null; - }; - } - - constructor(element, type) { - this._id = helper.generateId(); - this._type = type; - this._element = element; - } - - id() { - return this._id; - } - - message() { - return this._element.ui.infoBody.textContent; - } - - type() { - return this._type; - } - - element() { - return this._element; - } - - dismiss() { - if (this._element.ui.button1) - this._element.ui.button1.click(); - else - this._element.ui.button0.click(); - } - - defaultValue() { - return this._element.ui.loginTextbox.value; - } - - accept(promptValue) { - if (typeof promptValue === 'string' && this._type === 'prompt') - this._element.ui.loginTextbox.value = promptValue; - this._element.ui.button0.click(); - } -} - -var EXPORTED_SYMBOLS = ['PageHandler']; -this.PageHandler = PageHandler; diff --git a/experimental/juggler/src/Protocol.js b/experimental/juggler/src/Protocol.js deleted file mode 100644 index c4b40fe571b..00000000000 --- a/experimental/juggler/src/Protocol.js +++ /dev/null @@ -1,371 +0,0 @@ -const t = { - String: x => typeof x === 'string' || typeof x === 'String', - Number: x => typeof x === 'number', - Boolean: x => typeof x === 'boolean', - Null: x => Object.is(x, null), - Enum: values => x => values.indexOf(x) !== -1, - Undefined: x => Object.is(x, undefined), - Or: (...schemes) => x => schemes.some(scheme => checkScheme(scheme, x)), - Either: (...schemes) => x => schemes.map(scheme => checkScheme(scheme, x)).reduce((acc, x) => acc + (x ? 1 : 0)) === 1, - Array: scheme => x => Array.isArray(x) && x.every(element => checkScheme(scheme, element)), - Nullable: scheme => x => Object.is(x, null) || checkScheme(scheme, x), - Optional: scheme => x => Object.is(x, undefined) || checkScheme(scheme, x), - Any: x => true, -} - -const RemoteObject = t.Either( - { - type: t.Enum(['object', 'function', 'undefined', 'string', 'number', 'boolean', 'symbol', 'bigint']), - subtype: t.Optional(t.Enum(['array', 'null', 'node', 'regexp', 'date', 'map', 'set', 'weakmap', 'weakset', 'error', 'proxy', 'promise', 'typedarray'])), - objectId: t.String, - }, - { - unserializableValue: t.Enum(['Infinity', '-Infinity', '-0', 'NaN']), - }, - { - value: t.Any - }, -); - -const DOMPoint = { - x: t.Number, - y: t.Number, -}; - -const DOMQuad = { - p1: DOMPoint, - p2: DOMPoint, - p3: DOMPoint, - p4: DOMPoint -}; - -const protocol = { - methods: { - 'Browser.getInfo': { - returns: { - userAgent: t.String, - version: t.String, - }, - }, - 'Browser.setIgnoreHTTPSErrors': { - params: { - enabled: t.Boolean, - }, - }, - 'Browser.newPage': { - returns: { - pageId: t.String, - } - }, - 'Browser.closePage': { - params: { - pageId: t.String, - }, - }, - 'Page.enable': { - params: { - pageId: t.String, - }, - }, - 'Page.setViewport': { - params: { - pageId: t.String, - viewport: t.Nullable({ - width: t.Number, - height: t.Number, - deviceScaleFactor: t.Number, - isMobile: t.Boolean, - hasTouch: t.Boolean, - isLandscape: t.Boolean, - }), - }, - }, - 'Page.evaluate': { - params: t.Either({ - pageId: t.String, - frameId: t.String, - functionText: t.String, - returnByValue: t.Optional(t.Boolean), - args: t.Array(t.Either( - { objectId: t.String }, - { unserializableValue: t.Enum(['Infinity', '-Infinity', '-0', 'NaN']) }, - { value: t.Any }, - )), - }, { - pageId: t.String, - frameId: t.String, - script: t.String, - returnByValue: t.Optional(t.Boolean), - }), - - returns: { - result: t.Optional(RemoteObject), - exceptionDetails: t.Optional({ - text: t.Optional(t.String), - stack: t.Optional(t.String), - value: t.Optional(t.Any), - }), - } - }, - 'Page.addScriptToEvaluateOnNewDocument': { - params: { - pageId: t.String, - script: t.String, - }, - returns: { - scriptId: t.String, - } - }, - 'Page.removeScriptToEvaluateOnNewDocument': { - params: { - pageId: t.String, - scriptId: t.String, - }, - }, - 'Page.disposeObject': { - params: { - pageId: t.String, - frameId: t.String, - objectId: t.String, - }, - }, - - 'Page.getObjectProperties': { - params: { - pageId: t.String, - frameId: t.String, - objectId: t.String, - }, - - returns: { - properties: t.Array({ - name: t.String, - value: RemoteObject, - }), - } - }, - 'Page.navigate': { - params: { - pageId: t.String, - frameId: t.String, - url: t.String, - }, - returns: { - navigationId: t.Nullable(t.String), - navigationURL: t.Nullable(t.String), - } - }, - 'Page.goBack': { - params: { - pageId: t.String, - frameId: t.String, - }, - returns: { - navigationId: t.Nullable(t.String), - navigationURL: t.Nullable(t.String), - } - }, - 'Page.goForward': { - params: { - pageId: t.String, - frameId: t.String, - }, - returns: { - navigationId: t.Nullable(t.String), - navigationURL: t.Nullable(t.String), - } - }, - 'Page.reload': { - params: { - pageId: t.String, - frameId: t.String, - }, - returns: { - navigationId: t.String, - navigationURL: t.String, - } - }, - 'Page.getBoundingBox': { - params: { - pageId: t.String, - frameId: t.String, - objectId: t.String, - }, - returns: t.Nullable({ - x: t.Number, - y: t.Number, - width: t.Number, - height: t.Number, - }), - }, - 'Page.screenshot': { - params: { - pageId: t.String, - mimeType: t.Enum(['image/png', 'image/jpeg']), - fullPage: t.Optional(t.Boolean), - clip: t.Optional({ - x: t.Number, - y: t.Number, - width: t.Number, - height: t.Number, - }) - }, - returns: { - data: t.String, - } - }, - 'Page.getContentQuads': { - params: { - pageId: t.String, - frameId: t.String, - objectId: t.String, - }, - returns: { - quads: t.Array(DOMQuad), - }, - }, - 'Page.dispatchKeyEvent': { - params: { - pageId: t.String, - type: t.String, - key: t.String, - keyCode: t.Number, - location: t.Number, - code: t.String, - repeat: t.Boolean, - } - }, - 'Page.dispatchMouseEvent': { - params: { - pageId: t.String, - type: t.String, - button: t.Number, - x: t.Number, - y: t.Number, - modifiers: t.Number, - clickCount: t.Optional(t.Number), - buttons: t.Number, - } - }, - 'Page.insertText': { - params: { - pageId: t.String, - text: t.String, - } - }, - 'Page.handleDialog': { - params: { - pageId: t.String, - dialogId: t.String, - accept: t.Boolean, - promptText: t.Optional(t.String), - }, - }, - }, - events: { - 'Browser.tabOpened': { - pageId: t.String, - url: t.String, - }, - 'Browser.tabClosed': { pageId: t.String, }, - 'Browser.tabNavigated': { - pageId: t.String, - url: t.String - }, - 'Page.eventFired': { - pageId: t.String, - frameId: t.String, - name: t.Enum(['load', 'DOMContentLoaded']), - }, - 'Page.uncaughtError': { - pageId: t.String, - frameId: t.String, - message: t.String, - stack: t.String, - }, - 'Page.frameAttached': { - pageId: t.String, - frameId: t.String, - parentFrameId: t.Optional(t.String), - }, - 'Page.frameDetached': { - pageId: t.String, - frameId: t.String, - }, - 'Page.navigationStarted': { - pageId: t.String, - frameId: t.String, - navigationId: t.String, - url: t.String, - }, - 'Page.navigationCommitted': { - pageId: t.String, - frameId: t.String, - navigationId: t.String, - url: t.String, - // frame.id or frame.name - name: t.String, - }, - 'Page.navigationAborted': { - pageId: t.String, - frameId: t.String, - navigationId: t.String, - errorText: t.String, - }, - 'Page.sameDocumentNavigation': { - pageId: t.String, - frameId: t.String, - url: t.String, - }, - 'Page.consoleAPICalled': { - pageId: t.String, - frameId: t.String, - args: t.Array(RemoteObject), - type: t.String, - }, - 'Page.dialogOpened': { - pageId: t.String, - dialogId: t.String, - type: t.Enum(['prompt', 'alert', 'confirm', 'beforeunload']), - message: t.String, - defaultValue: t.Optional(t.String), - }, - 'Page.dialogClosed': { - pageId: t.String, - dialogId: t.String, - }, - }, -} - -function checkScheme(scheme, x, details = {}, path = []) { - if (typeof scheme === 'object') { - for (const [propertyName, check] of Object.entries(scheme)) { - path.push(propertyName); - const result = checkScheme(check, x[propertyName], details, path); - path.pop(); - if (!result) - return false; - } - for (const propertyName of Object.keys(x)) { - if (!scheme[propertyName]) { - path.push(propertyName); - details.propertyName = path.join('.'); - details.propertyValue = x[propertyName]; - details.errorType = 'extra'; - return false; - } - } - return true; - } - const result = scheme(x); - if (!result) { - details.propertyName = path.join('.'); - details.propertyValue = x; - details.errorType = 'unsupported'; - } - return result; -} - -this.protocol = protocol; -this.checkScheme = checkScheme; -this.EXPORTED_SYMBOLS = ['protocol', 'checkScheme']; diff --git a/experimental/juggler/src/components/juggler.js b/experimental/juggler/src/components/juggler.js deleted file mode 100644 index 6a1258cf9a4..00000000000 --- a/experimental/juggler/src/components/juggler.js +++ /dev/null @@ -1,63 +0,0 @@ -const {XPCOMUtils} = ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm"); -const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm"); -const {TCPListener} = ChromeUtils.import("chrome://juggler/content/server/server.js"); -const {ChromeSession} = ChromeUtils.import("chrome://juggler/content/ChromeSession.js"); - -const Cc = Components.classes; -const Ci = Components.interfaces; -const Cu = Components.utils; - -const FRAME_SCRIPT = "chrome://juggler/content/content/main.js"; - -// Command Line Handler -function CommandLineHandler() { - this._port = 0; -}; - -CommandLineHandler.prototype = { - classDescription: "Sample command-line handler", - classID: Components.ID('{f7a74a33-e2ab-422d-b022-4fb213dd2639}'), - contractID: "@mozilla.org/remote/juggler;1", - _xpcom_categories: [{ - category: "command-line-handler", - entry: "m-juggler" - }], - - /* nsICommandLineHandler */ - handle: async function(cmdLine) { - const jugglerFlag = cmdLine.handleFlagWithParam("juggler", false); - if (!jugglerFlag || isNaN(jugglerFlag)) - return; - this._port = parseInt(jugglerFlag, 10); - Services.obs.addObserver(this, 'sessionstore-windows-restored'); - }, - - observe: function(subject, topic) { - Services.obs.removeObserver(this, 'sessionstore-windows-restored'); - - this._server = new TCPListener(); - this._sessions = new Map(); - this._server.onconnectioncreated = connection => { - this._sessions.set(connection, new ChromeSession(connection)); - } - this._server.onconnectionclosed = connection => { - this._sessions.delete(connection); - } - const runningPort = this._server.start(this._port); - Services.mm.loadFrameScript(FRAME_SCRIPT, true /* aAllowDelayedLoad */); - dump('Juggler listening on ' + runningPort + '\n'); - }, - - QueryInterface: ChromeUtils.generateQI([ Ci.nsICommandLineHandler ]), - - // CHANGEME: change the help info as appropriate, but - // follow the guidelines in nsICommandLineHandler.idl - // specifically, flag descriptions should start at - // character 24, and lines should be wrapped at - // 72 characters with embedded newlines, - // and finally, the string should end with a newline - helpInfo : " --juggler Enable Juggler automation\n" -}; - -var NSGetFactory = XPCOMUtils.generateNSGetFactory([CommandLineHandler]); - diff --git a/experimental/juggler/src/components/juggler.manifest b/experimental/juggler/src/components/juggler.manifest deleted file mode 100644 index 50f89302075..00000000000 --- a/experimental/juggler/src/components/juggler.manifest +++ /dev/null @@ -1,3 +0,0 @@ -component {f7a74a33-e2ab-422d-b022-4fb213dd2639} juggler.js -contract @mozilla.org/remote/juggler;1 {f7a74a33-e2ab-422d-b022-4fb213dd2639} -category command-line-handler m-juggler @mozilla.org/remote/juggler;1 diff --git a/experimental/juggler/src/components/moz.build b/experimental/juggler/src/components/moz.build deleted file mode 100644 index 268fbc361d8..00000000000 --- a/experimental/juggler/src/components/moz.build +++ /dev/null @@ -1,9 +0,0 @@ -# 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/. - -EXTRA_COMPONENTS += [ - "juggler.js", - "juggler.manifest", -] - diff --git a/experimental/juggler/src/content/ContentSession.js b/experimental/juggler/src/content/ContentSession.js deleted file mode 100644 index 343a695be06..00000000000 --- a/experimental/juggler/src/content/ContentSession.js +++ /dev/null @@ -1,53 +0,0 @@ -const {Helper} = ChromeUtils.import('chrome://juggler/content/Helper.js'); -const {RuntimeAgent} = ChromeUtils.import('chrome://juggler/content/content/RuntimeAgent.js'); -const {PageAgent} = ChromeUtils.import('chrome://juggler/content/content/PageAgent.js'); - -const helper = new Helper(); - -class ContentSession { - /** - * @param {string} sessionId - * @param {!ContentFrameMessageManager} messageManager - * @param {!FrameTree} frameTree - */ - constructor(sessionId, messageManager, frameTree, scrollbarManager) { - this._sessionId = sessionId; - this._runtimeAgent = new RuntimeAgent(); - this._messageManager = messageManager; - this._pageAgent = new PageAgent(this, this._runtimeAgent, frameTree, scrollbarManager); - this._eventListeners = [ - helper.addMessageListener(messageManager, this._sessionId, this._onMessage.bind(this)), - ]; - } - - emitEvent(eventName, params) { - this._messageManager.sendAsyncMessage(this._sessionId, {eventName, params}); - } - - mm() { - return this._messageManager; - } - - async _onMessage(msg) { - const id = msg.data.id; - try { - const handler = this._pageAgent[msg.data.methodName]; - if (!handler) - throw new Error('unknown method: "' + msg.data.methodName + '"'); - const result = await handler.call(this._pageAgent, msg.data.params); - this._messageManager.sendAsyncMessage(this._sessionId, {id, result}); - } catch (e) { - this._messageManager.sendAsyncMessage(this._sessionId, {id, error: e.message + '\n' + e.stack}); - } - } - - dispose() { - helper.removeListeners(this._eventListeners); - this._pageAgent.dispose(); - this._runtimeAgent.dispose(); - } -} - -var EXPORTED_SYMBOLS = ['ContentSession']; -this.ContentSession = ContentSession; - diff --git a/experimental/juggler/src/content/FrameTree.js b/experimental/juggler/src/content/FrameTree.js deleted file mode 100644 index b420618ef5b..00000000000 --- a/experimental/juggler/src/content/FrameTree.js +++ /dev/null @@ -1,287 +0,0 @@ -"use strict"; -const Ci = Components.interfaces; -const Cr = Components.results; -const Cu = Components.utils; - -const {Helper} = ChromeUtils.import('chrome://juggler/content/Helper.js'); -const {EventEmitter} = ChromeUtils.import('resource://gre/modules/EventEmitter.jsm'); - -const helper = new Helper(); - -class FrameTree { - constructor(rootDocShell) { - EventEmitter.decorate(this); - this._docShellToFrame = new Map(); - this._frameIdToFrame = new Map(); - this._mainFrame = this._createFrame(rootDocShell); - const webProgress = rootDocShell.QueryInterface(Ci.nsIInterfaceRequestor) - .getInterface(Ci.nsIWebProgress); - this.QueryInterface = ChromeUtils.generateQI([ - Ci.nsIWebProgressListener, - Ci.nsIWebProgressListener2, - Ci.nsISupportsWeakReference, - ]); - - const flags = Ci.nsIWebProgress.NOTIFY_STATE_DOCUMENT | - Ci.nsIWebProgress.NOTIFY_FRAME_LOCATION; - this._eventListeners = [ - helper.addObserver(subject => this._onDocShellCreated(subject.QueryInterface(Ci.nsIDocShell)), 'webnavigation-create'), - helper.addObserver(subject => this._onDocShellDestroyed(subject.QueryInterface(Ci.nsIDocShell)), 'webnavigation-destroy'), - helper.addProgressListener(webProgress, this, flags), - ]; - } - - frameForDocShell(docShell) { - return this._docShellToFrame.get(docShell) || null; - } - - frame(frameId) { - return this._frameIdToFrame.get(frameId) || null; - } - - frames() { - let result = []; - collect(this._mainFrame); - return result; - - function collect(frame) { - result.push(frame); - for (const subframe of frame._children) - collect(subframe); - } - } - - mainFrame() { - return this._mainFrame; - } - - dispose() { - helper.removeListeners(this._eventListeners); - } - - onStateChange(progress, request, flag, status) { - if (!(request instanceof Ci.nsIChannel)) - return; - const channel = request.QueryInterface(Ci.nsIChannel); - const docShell = progress.DOMWindow.docShell; - const frame = this._docShellToFrame.get(docShell); - if (!frame) { - dump(`ERROR: got a state changed event for un-tracked docshell!\n`); - return; - } - - const isStart = flag & Ci.nsIWebProgressListener.STATE_START; - const isTransferring = flag & Ci.nsIWebProgressListener.STATE_TRANSFERRING; - const isStop = flag & Ci.nsIWebProgressListener.STATE_STOP; - - if (isStart) { - // Starting a new navigation. - frame._pendingNavigationId = helper.generateId(); - frame._pendingNavigationURL = channel.URI.spec; - this.emit(FrameTree.Events.NavigationStarted, frame); - } else if (isTransferring || (isStop && frame._pendingNavigationId && !status)) { - // Navigation is committed. - for (const subframe of frame._children) - this._detachFrame(subframe); - const navigationId = frame._pendingNavigationId; - frame._pendingNavigationId = null; - frame._pendingNavigationURL = null; - frame._lastCommittedNavigationId = navigationId; - frame._url = channel.URI.spec; - this.emit(FrameTree.Events.NavigationCommitted, frame); - } else if (isStop && frame._pendingNavigationId && status) { - // Navigation is aborted. - const navigationId = frame._pendingNavigationId; - frame._pendingNavigationId = null; - frame._pendingNavigationURL = null; - this.emit(FrameTree.Events.NavigationAborted, frame, navigationId, getErrorStatusText(status)); - } - } - - onFrameLocationChange(progress, request, location, flags) { - const docShell = progress.DOMWindow.docShell; - const frame = this._docShellToFrame.get(docShell); - const sameDocumentNavigation = !!(flags & Ci.nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT); - if (frame && sameDocumentNavigation) { - frame._url = location.spec; - this.emit(FrameTree.Events.SameDocumentNavigation, frame); - } - } - - _onDocShellCreated(docShell) { - // Bug 1142752: sometimes, the docshell appears to be immediately - // destroyed, bailout early to prevent random exceptions. - if (docShell.isBeingDestroyed()) - return; - // If this docShell doesn't belong to our frame tree - do nothing. - let root = docShell; - while (root.parent) - root = root.parent; - if (root === this._mainFrame._docShell) - this._createFrame(docShell); - } - - _createFrame(docShell) { - const parentFrame = this._docShellToFrame.get(docShell.parent) || null; - const frame = new Frame(this, docShell, parentFrame); - this._docShellToFrame.set(docShell, frame); - this._frameIdToFrame.set(frame.id(), frame); - this.emit(FrameTree.Events.FrameAttached, frame); - return frame; - } - - _onDocShellDestroyed(docShell) { - const frame = this._docShellToFrame.get(docShell); - if (frame) - this._detachFrame(frame); - } - - _detachFrame(frame) { - // Detach all children first - for (const subframe of frame._children) - this._detachFrame(subframe); - this._docShellToFrame.delete(frame._docShell); - this._frameIdToFrame.delete(frame.id()); - if (frame._parentFrame) - frame._parentFrame._children.delete(frame); - frame._parentFrame = null; - this.emit(FrameTree.Events.FrameDetached, frame); - } -} - -FrameTree.Events = { - FrameAttached: 'frameattached', - FrameDetached: 'framedetached', - NavigationStarted: 'navigationstarted', - NavigationCommitted: 'navigationcommitted', - NavigationAborted: 'navigationaborted', - SameDocumentNavigation: 'samedocumentnavigation', -}; - -class Frame { - constructor(frameTree, docShell, parentFrame) { - this._frameTree = frameTree; - this._docShell = docShell; - this._children = new Set(); - this._frameId = helper.generateId(); - this._parentFrame = null; - this._url = ''; - if (parentFrame) { - this._parentFrame = parentFrame; - parentFrame._children.add(this); - } - - this._lastCommittedNavigationId = null; - this._pendingNavigationId = null; - this._pendingNavigationURL = null; - - this._textInputProcessor = null; - } - - textInputProcessor() { - if (!this._textInputProcessor) { - this._textInputProcessor = Cc["@mozilla.org/text-input-processor;1"].createInstance(Ci.nsITextInputProcessor); - this._textInputProcessor.beginInputTransactionForTests(this._docShell.DOMWindow); - } - return this._textInputProcessor; - } - - pendingNavigationId() { - return this._pendingNavigationId; - } - - pendingNavigationURL() { - return this._pendingNavigationURL; - } - - lastCommittedNavigationId() { - return this._lastCommittedNavigationId; - } - - docShell() { - return this._docShell; - } - - domWindow() { - return this._docShell.DOMWindow; - } - - name() { - const frameElement = this._docShell.domWindow.frameElement; - let name = ''; - if (frameElement) - name = frameElement.getAttribute('name') || frameElement.getAttribute('id') || ''; - return name; - } - - parentFrame() { - return this._parentFrame; - } - - id() { - return this._frameId; - } - - url() { - return this._url; - } -} - -function getErrorStatusText(status) { - if (!status) - return null; - for (const key of Object.keys(Cr)) { - if (Cr[key] === status) - return key; - } - // Security module. The following is taken from - // https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/How_to_check_the_secruity_state_of_an_XMLHTTPRequest_over_SSL - if ((status & 0xff0000) === 0x5a0000) { - // NSS_SEC errors (happen below the base value because of negative vals) - if ((status & 0xffff) < Math.abs(Ci.nsINSSErrorsService.NSS_SEC_ERROR_BASE)) { - // The bases are actually negative, so in our positive numeric space, we - // need to subtract the base off our value. - const nssErr = Math.abs(Ci.nsINSSErrorsService.NSS_SEC_ERROR_BASE) - (status & 0xffff); - switch (nssErr) { - case 11: - return 'SEC_ERROR_EXPIRED_CERTIFICATE'; - case 12: - return 'SEC_ERROR_REVOKED_CERTIFICATE'; - case 13: - return 'SEC_ERROR_UNKNOWN_ISSUER'; - case 20: - return 'SEC_ERROR_UNTRUSTED_ISSUER'; - case 21: - return 'SEC_ERROR_UNTRUSTED_CERT'; - case 36: - return 'SEC_ERROR_CA_CERT_INVALID'; - case 90: - return 'SEC_ERROR_INADEQUATE_KEY_USAGE'; - case 176: - return 'SEC_ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED'; - default: - return 'SEC_ERROR_UNKNOWN'; - } - } - const sslErr = Math.abs(Ci.nsINSSErrorsService.NSS_SSL_ERROR_BASE) - (status & 0xffff); - switch (sslErr) { - case 3: - return 'SSL_ERROR_NO_CERTIFICATE'; - case 4: - return 'SSL_ERROR_BAD_CERTIFICATE'; - case 8: - return 'SSL_ERROR_UNSUPPORTED_CERTIFICATE_TYPE'; - case 9: - return 'SSL_ERROR_UNSUPPORTED_VERSION'; - case 12: - return 'SSL_ERROR_BAD_CERT_DOMAIN'; - default: - return 'SSL_ERROR_UNKNOWN'; - } - } - return ''; -} - -var EXPORTED_SYMBOLS = ['FrameTree']; -this.FrameTree = FrameTree; - diff --git a/experimental/juggler/src/content/PageAgent.js b/experimental/juggler/src/content/PageAgent.js deleted file mode 100644 index 98f7777bc84..00000000000 --- a/experimental/juggler/src/content/PageAgent.js +++ /dev/null @@ -1,460 +0,0 @@ -"use strict"; -const Ci = Components.interfaces; -const Cr = Components.results; -const Cu = Components.utils; - -const {Helper} = ChromeUtils.import('chrome://juggler/content/Helper.js'); -const {NetUtil} = ChromeUtils.import('resource://gre/modules/NetUtil.jsm'); - -const helper = new Helper(); - -class PageAgent { - constructor(session, runtimeAgent, frameTree, scrollbarManager) { - this._session = session; - this._runtime = runtimeAgent; - this._frameTree = frameTree; - this._scrollbarManager = scrollbarManager; - - this._frameToExecutionContext = new Map(); - this._scriptsToEvaluateOnNewDocument = new Map(); - - this._eventListeners = []; - this._enabled = false; - - const docShell = frameTree.mainFrame().docShell(); - this._initialDPPX = docShell.contentViewer.overrideDPPX; - this._customScrollbars = null; - } - - async awaitViewportDimensions({width, height}) { - const win = this._frameTree.mainFrame().domWindow(); - if (win.innerWidth === width && win.innerHeight === height) - return; - await new Promise(resolve => { - const listener = helper.addEventListener(win, 'resize', () => { - if (win.innerWidth === width && win.innerHeight === height) { - helper.removeListeners([listener]); - resolve(); - } - }); - }); - } - - async setViewport({deviceScaleFactor, isMobile, hasTouch}) { - const docShell = this._frameTree.mainFrame().docShell(); - docShell.contentViewer.overrideDPPX = deviceScaleFactor || this._initialDPPX; - docShell.deviceSizeIsPageSize = isMobile; - docShell.touchEventsOverride = hasTouch ? Ci.nsIDocShell.TOUCHEVENTS_OVERRIDE_ENABLED : Ci.nsIDocShell.TOUCHEVENTS_OVERRIDE_NONE; - this._scrollbarManager.setFloatingScrollbars(isMobile); - } - - addScriptToEvaluateOnNewDocument({script}) { - const scriptId = helper.generateId(); - this._scriptsToEvaluateOnNewDocument.set(scriptId, script); - return {scriptId}; - } - - removeScriptToEvaluateOnNewDocument({scriptId}) { - this._scriptsToEvaluateOnNewDocument.delete(scriptId); - } - - enable() { - if (this._enabled) - return; - - this._enabled = true; - this._eventListeners = [ - helper.addObserver(this._consoleAPICalled.bind(this), "console-api-log-event"), - helper.addEventListener(this._session.mm(), 'DOMContentLoaded', this._onDOMContentLoaded.bind(this)), - helper.addEventListener(this._session.mm(), 'pageshow', this._onLoad.bind(this)), - helper.addEventListener(this._session.mm(), 'DOMWindowCreated', this._onDOMWindowCreated.bind(this)), - helper.addEventListener(this._session.mm(), 'error', this._onError.bind(this)), - helper.on(this._frameTree, 'frameattached', this._onFrameAttached.bind(this)), - helper.on(this._frameTree, 'framedetached', this._onFrameDetached.bind(this)), - helper.on(this._frameTree, 'navigationstarted', this._onNavigationStarted.bind(this)), - helper.on(this._frameTree, 'navigationcommitted', this._onNavigationCommitted.bind(this)), - helper.on(this._frameTree, 'navigationaborted', this._onNavigationAborted.bind(this)), - helper.on(this._frameTree, 'samedocumentnavigation', this._onSameDocumentNavigation.bind(this)), - ]; - - // Dispatch frameAttached events for all initial frames - for (const frame of this._frameTree.frames()) { - this._onFrameAttached(frame); - if (frame.url()) - this._onNavigationCommitted(frame); - if (frame.pendingNavigationId()) - this._onNavigationStarted(frame); - } - } - - _onDOMContentLoaded(event) { - const docShell = event.target.ownerGlobal.docShell; - const frame = this._frameTree.frameForDocShell(docShell); - if (!frame) - return; - this._session.emitEvent('Page.eventFired', { - frameId: frame.id(), - name: 'DOMContentLoaded', - }); - } - - _onError(errorEvent) { - const docShell = errorEvent.target.ownerGlobal.docShell; - const frame = this._frameTree.frameForDocShell(docShell); - if (!frame) - return; - this._session.emitEvent('Page.uncaughtError', { - frameId: frame.id(), - message: errorEvent.message, - stack: errorEvent.error.stack - }); - } - - _onLoad(event) { - const docShell = event.target.ownerGlobal.docShell; - const frame = this._frameTree.frameForDocShell(docShell); - if (!frame) - return; - this._session.emitEvent('Page.eventFired', { - frameId: frame.id(), - name: 'load' - }); - } - - _onNavigationStarted(frame) { - this._session.emitEvent('Page.navigationStarted', { - frameId: frame.id(), - navigationId: frame.pendingNavigationId(), - url: frame.pendingNavigationURL(), - }); - } - - _onNavigationAborted(frame, navigationId, errorText) { - this._session.emitEvent('Page.navigationAborted', { - frameId: frame.id(), - navigationId, - errorText, - }); - } - - _onSameDocumentNavigation(frame) { - this._session.emitEvent('Page.sameDocumentNavigation', { - frameId: frame.id(), - url: frame.url(), - }); - } - - _onNavigationCommitted(frame) { - const context = this._frameToExecutionContext.get(frame); - if (context) { - this._runtime.destroyExecutionContext(context); - this._frameToExecutionContext.delete(frame); - } - this._session.emitEvent('Page.navigationCommitted', { - frameId: frame.id(), - navigationId: frame.lastCommittedNavigationId(), - url: frame.url(), - name: frame.name(), - }); - } - - _onDOMWindowCreated(event) { - if (!this._scriptsToEvaluateOnNewDocument.size) - return; - const docShell = event.target.ownerGlobal.docShell; - const frame = this._frameTree.frameForDocShell(docShell); - if (!frame) - return; - const executionContext = this._ensureExecutionContext(frame); - for (const script of this._scriptsToEvaluateOnNewDocument.values()) { - try { - let result = executionContext.evaluateScript(script); - if (result && result.objectId) - executionContext.disposeObject(result.objectId); - } catch (e) { - } - } - } - - _onFrameAttached(frame) { - this._session.emitEvent('Page.frameAttached', { - frameId: frame.id(), - parentFrameId: frame.parentFrame() ? frame.parentFrame().id() : undefined, - }); - } - - _onFrameDetached(frame) { - this._session.emitEvent('Page.frameDetached', { - frameId: frame.id(), - }); - } - - _ensureExecutionContext(frame) { - let executionContext = this._frameToExecutionContext.get(frame); - if (!executionContext) { - executionContext = this._runtime.createExecutionContext(frame.domWindow()); - this._frameToExecutionContext.set(frame, executionContext); - } - return executionContext; - } - - dispose() { - helper.removeListeners(this._eventListeners); - } - - _consoleAPICalled({wrappedJSObject}, topic, data) { - const levelToType = { - 'dir': 'dir', - 'log': 'log', - 'debug': 'debug', - 'info': 'info', - 'error': 'error', - 'warn': 'warning', - 'dirxml': 'dirxml', - 'table': 'table', - 'trace': 'trace', - 'clear': 'clear', - 'group': 'startGroup', - 'groupCollapsed': 'startGroupCollapsed', - 'groupEnd': 'endGroup', - 'assert': 'assert', - 'profile': 'profile', - 'profileEnd': 'profileEnd', - 'count': 'count', - 'countReset': 'countReset', - 'time': null, - 'timeLog': 'timeLog', - 'timeEnd': 'timeEnd', - 'timeStamp': 'timeStamp', - }; - const type = levelToType[wrappedJSObject.level]; - if (!type) return; - let messageFrame = null; - for (const frame of this._frameTree.frames()) { - const domWindow = frame.domWindow(); - if (domWindow && domWindow.windowUtils.currentInnerWindowID === wrappedJSObject.innerID) { - messageFrame = frame; - break; - } - } - if (!messageFrame) - return; - const executionContext = this._ensureExecutionContext(messageFrame); - const args = wrappedJSObject.arguments.map(arg => executionContext.rawValueToRemoteObject(arg)); - this._session.emitEvent('Page.consoleAPICalled', {args, type, frameId: messageFrame.id()}); - } - - async navigate({frameId, url}) { - try { - const uri = NetUtil.newURI(url); - } catch (e) { - throw new Error(`Invalid url: "${url}"`); - } - const frame = this._frameTree.frame(frameId); - const docShell = frame.docShell(); - docShell.loadURI(url, Ci.nsIWebNavigation.LOAD_FLAGS_NONE, null /* referrer */, null /* postData */, null /* headers */); - return {navigationId: frame.pendingNavigationId(), navigationURL: frame.pendingNavigationURL()}; - } - - async reload({frameId, url}) { - const frame = this._frameTree.frame(frameId); - const docShell = frame.docShell(); - docShell.reload(Ci.nsIWebNavigation.LOAD_FLAGS_NONE); - return {navigationId: frame.pendingNavigationId(), navigationURL: frame.pendingNavigationURL()}; - } - - async goBack({frameId, url}) { - const frame = this._frameTree.frame(frameId); - const docShell = frame.docShell(); - if (!docShell.canGoBack) - return {navigationId: null, navigationURL: null}; - docShell.goBack(); - return {navigationId: frame.pendingNavigationId(), navigationURL: frame.pendingNavigationURL()}; - } - - async goForward({frameId, url}) { - const frame = this._frameTree.frame(frameId); - const docShell = frame.docShell(); - if (!docShell.canGoForward) - return {navigationId: null, navigationURL: null}; - docShell.goForward(); - return {navigationId: frame.pendingNavigationId(), navigationURL: frame.pendingNavigationURL()}; - } - - async disposeObject({frameId, objectId}) { - const frame = this._frameTree.frame(frameId); - if (!frame) - throw new Error('Failed to find frame with id = ' + frameId); - const executionContext = this._ensureExecutionContext(frame); - return executionContext.disposeObject(objectId); - } - - getContentQuads({objectId, frameId}) { - const frame = this._frameTree.frame(frameId); - if (!frame) - throw new Error('Failed to find frame with id = ' + frameId); - const executionContext = this._ensureExecutionContext(frame); - const unsafeObject = executionContext.unsafeObject(objectId); - if (!unsafeObject.getBoxQuads) - throw new Error('RemoteObject is not a node'); - const quads = unsafeObject.getBoxQuads({relativeTo: this._frameTree.mainFrame().domWindow().document}).map(quad => { - return { - p1: {x: quad.p1.x, y: quad.p1.y}, - p2: {x: quad.p2.x, y: quad.p2.y}, - p3: {x: quad.p3.x, y: quad.p3.y}, - p4: {x: quad.p4.x, y: quad.p4.y}, - }; - }); - return {quads}; - } - - async getBoundingBox({frameId, objectId}) { - const frame = this._frameTree.frame(frameId); - if (!frame) - throw new Error('Failed to find frame with id = ' + frameId); - const executionContext = this._ensureExecutionContext(frame); - const unsafeObject = executionContext.unsafeObject(objectId); - if (!unsafeObject.getBoxQuads) - throw new Error('RemoteObject is not a node'); - const quads = unsafeObject.getBoxQuads({relativeTo: this._frameTree.mainFrame().domWindow().document}); - if (!quads.length) - return null; - let x1 = Infinity; - let y1 = Infinity; - let x2 = -Infinity; - let y2 = -Infinity; - for (const quad of quads) { - const boundingBox = quad.getBounds(); - x1 = Math.min(boundingBox.x, x1); - y1 = Math.min(boundingBox.y, y1); - x2 = Math.max(boundingBox.x + boundingBox.width, x2); - y2 = Math.max(boundingBox.y + boundingBox.height, y2); - } - return {x: x1 + frame.domWindow().scrollX, y: y1 + frame.domWindow().scrollY, width: x2 - x1, height: y2 - y1}; - } - - async evaluate({frameId, functionText, args, script, returnByValue}) { - const frame = this._frameTree.frame(frameId); - if (!frame) - throw new Error('Failed to find frame with id = ' + frameId); - const executionContext = this._ensureExecutionContext(frame); - const exceptionDetails = {}; - let result = null; - if (script) - result = await executionContext.evaluateScript(script, exceptionDetails); - else - result = await executionContext.evaluateFunction(functionText, args, exceptionDetails); - if (!result) - return {exceptionDetails}; - let isNode = undefined; - if (returnByValue) - result = executionContext.ensureSerializedToValue(result); - return {result}; - } - - async getObjectProperties({frameId, objectId}) { - const frame = this._frameTree.frame(frameId); - if (!frame) - throw new Error('Failed to find frame with id = ' + frameId); - const executionContext = this._ensureExecutionContext(frame); - return {properties: executionContext.getObjectProperties(objectId)}; - } - - async screenshot({mimeType, fullPage, frameId, objectId, clip}) { - const content = this._session.mm().content; - if (clip) { - const data = takeScreenshot(content, clip.x, clip.y, clip.width, clip.height, mimeType); - return {data}; - } - if (fullPage) { - const rect = content.document.documentElement.getBoundingClientRect(); - const width = content.innerWidth + content.scrollMaxX - content.scrollMinX; - const height = content.innerHeight + content.scrollMaxY - content.scrollMinY; - const data = takeScreenshot(content, 0, 0, width, height, mimeType); - return {data}; - } - const data = takeScreenshot(content, content.scrollX, content.scrollY, content.innerWidth, content.innerHeight, mimeType); - return {data}; - } - - async dispatchKeyEvent({type, keyCode, code, key, repeat, location}) { - const frame = this._frameTree.mainFrame(); - const tip = frame.textInputProcessor(); - let keyEvent = new (frame.domWindow().KeyboardEvent)("", { - key, - code, - location, - repeat, - keyCode - }); - const flags = 0; - if (type === 'keydown') - tip.keydown(keyEvent, flags); - else if (type === 'keyup') - tip.keyup(keyEvent, flags); - else - throw new Error(`Unknown type ${type}`); - } - - async dispatchMouseEvent({type, x, y, button, clickCount, modifiers, buttons}) { - const frame = this._frameTree.mainFrame(); - frame.domWindow().windowUtils.sendMouseEvent( - type, - x, - y, - button, - clickCount, - modifiers, - false /*aIgnoreRootScrollFrame*/, - undefined /*pressure*/, - undefined /*inputSource*/, - undefined /*isDOMEventSynthesized*/, - undefined /*isWidgetEventSynthesized*/, - buttons); - if (type === 'mousedown' && button === 2) { - frame.domWindow().windowUtils.sendMouseEvent( - 'contextmenu', - x, - y, - button, - clickCount, - modifiers, - false /*aIgnoreRootScrollFrame*/, - undefined /*pressure*/, - undefined /*inputSource*/, - undefined /*isDOMEventSynthesized*/, - undefined /*isWidgetEventSynthesized*/, - buttons); - } - } - - async insertText({text}) { - const frame = this._frameTree.mainFrame(); - frame.textInputProcessor().commitCompositionWith(text); - } -} - -function takeScreenshot(win, left, top, width, height, mimeType) { - const MAX_SKIA_DIMENSIONS = 32767; - - const scale = win.devicePixelRatio; - const canvasWidth = width * scale; - const canvasHeight = height * scale; - - if (canvasWidth > MAX_SKIA_DIMENSIONS || canvasHeight > MAX_SKIA_DIMENSIONS) - throw new Error('Cannot take screenshot larger than ' + MAX_SKIA_DIMENSIONS); - - const canvas = win.document.createElementNS('http://www.w3.org/1999/xhtml', 'canvas'); - canvas.width = canvasWidth; - canvas.height = canvasHeight; - - let ctx = canvas.getContext('2d'); - ctx.scale(scale, scale); - ctx.drawWindow(win, left, top, width, height, 'rgb(255,255,255)', ctx.DRAWWINDOW_DRAW_CARET); - const dataURL = canvas.toDataURL(mimeType); - return dataURL.substring(dataURL.indexOf(',') + 1); -}; - -var EXPORTED_SYMBOLS = ['PageAgent']; -this.PageAgent = PageAgent; - diff --git a/experimental/juggler/src/content/RuntimeAgent.js b/experimental/juggler/src/content/RuntimeAgent.js deleted file mode 100644 index 6c8493558aa..00000000000 --- a/experimental/juggler/src/content/RuntimeAgent.js +++ /dev/null @@ -1,275 +0,0 @@ -"use strict"; -const {Helper} = ChromeUtils.import('chrome://juggler/content/Helper.js'); -const {addDebuggerToGlobal} = ChromeUtils.import("resource://gre/modules/jsdebugger.jsm", {}); - -const Ci = Components.interfaces; -const Cr = Components.results; -const Cu = Components.utils; -addDebuggerToGlobal(Cu.getGlobalForObject(this)); -const helper = new Helper(); - -class RuntimeAgent { - constructor() { - this._debugger = new Debugger(); - this._pendingPromises = new Map(); - } - - dispose() {} - - async _awaitPromise(executionContext, obj, exceptionDetails = {}) { - if (obj.promiseState === 'fulfilled') - return {success: true, obj: obj.promiseValue}; - if (obj.promiseState === 'rejected') { - const global = executionContext._global; - exceptionDetails.text = global.executeInGlobalWithBindings('e.message', {e: obj.promiseReason}).return; - exceptionDetails.stack = global.executeInGlobalWithBindings('e.stack', {e: obj.promiseReason}).return; - return {success: false, obj: null}; - } - let resolve, reject; - const promise = new Promise((a, b) => { - resolve = a; - reject = b; - }); - this._pendingPromises.set(obj.promiseID, {resolve, reject, executionContext, exceptionDetails}); - if (this._pendingPromises.size === 1) - this._debugger.onPromiseSettled = this._onPromiseSettled.bind(this); - return await promise; - } - - _onPromiseSettled(obj) { - const pendingPromise = this._pendingPromises.get(obj.promiseID); - if (!pendingPromise) - return; - this._pendingPromises.delete(obj.promiseID); - if (!this._pendingPromises.size) - this._debugger.onPromiseSettled = undefined; - - if (obj.promiseState === 'fulfilled') { - pendingPromise.resolve({success: true, obj: obj.promiseValue}); - return; - }; - const global = pendingPromise.executionContext._global; - pendingPromise.exceptionDetails.text = global.executeInGlobalWithBindings('e.message', {e: obj.promiseReason}).return; - pendingPromise.exceptionDetails.stack = global.executeInGlobalWithBindings('e.stack', {e: obj.promiseReason}).return; - pendingPromise.resolve({success: false, obj: null}); - } - - createExecutionContext(domWindow) { - return new ExecutionContext(this, domWindow, this._debugger.addDebuggee(domWindow)); - } - - destroyExecutionContext(destroyedContext) { - for (const [promiseID, {reject, executionContext}] of this._pendingPromises) { - if (executionContext === destroyedContext) { - reject(new Error('Execution context was destroyed!')); - this._pendingPromises.delete(promiseID); - } - } - if (!this._pendingPromises.size) - this._debugger.onPromiseSettled = undefined; - this._debugger.removeDebuggee(destroyedContext._domWindow); - } -} - -class ExecutionContext { - constructor(runtime, DOMWindow, global) { - this._runtime = runtime; - this._domWindow = DOMWindow; - this._global = global; - this._remoteObjects = new Map(); - } - - async evaluateScript(script, exceptionDetails = {}) { - const userInputHelper = this._domWindow.windowUtils.setHandlingUserInput(true); - let {success, obj} = this._getResult(this._global.executeInGlobal(script), exceptionDetails); - userInputHelper.destruct(); - if (!success) - return null; - if (obj && obj.isPromise) { - const awaitResult = await this._runtime._awaitPromise(this, obj, exceptionDetails); - if (!awaitResult.success) - return null; - obj = awaitResult.obj; - } - return this._createRemoteObject(obj); - } - - async evaluateFunction(functionText, args, exceptionDetails = {}) { - const funEvaluation = this._getResult(this._global.executeInGlobal('(' + functionText + ')'), exceptionDetails); - if (!funEvaluation.success) - return null; - if (!funEvaluation.obj.callable) - throw new Error('functionText does not evaluate to a function!'); - args = args.map(arg => { - if (arg.objectId) { - if (!this._remoteObjects.has(arg.objectId)) - throw new Error('Cannot find object with id = ' + arg.objectId); - return this._remoteObjects.get(arg.objectId); - } - switch (arg.unserializableValue) { - case 'Infinity': return Infinity; - case '-Infinity': return -Infinity; - case '-0': return -0; - case 'NaN': return NaN; - default: return this._toDebugger(arg.value); - } - }); - const userInputHelper = this._domWindow.windowUtils.setHandlingUserInput(true); - let {success, obj} = this._getResult(funEvaluation.obj.apply(null, args), exceptionDetails); - userInputHelper.destruct(); - if (!success) - return null; - if (obj && obj.isPromise) { - const awaitResult = await this._runtime._awaitPromise(this, obj, exceptionDetails); - if (!awaitResult.success) - return null; - obj = awaitResult.obj; - } - return this._createRemoteObject(obj); - } - - unsafeObject(objectId) { - if (!this._remoteObjects.has(objectId)) - throw new Error('Cannot find object with id = ' + objectId); - return this._remoteObjects.get(objectId).unsafeDereference(); - } - - rawValueToRemoteObject(rawValue) { - const debuggerObj = this._global.makeDebuggeeValue(rawValue); - return this._createRemoteObject(debuggerObj); - } - - _createRemoteObject(debuggerObj) { - if (debuggerObj instanceof Debugger.Object) { - const objectId = helper.generateId(); - this._remoteObjects.set(objectId, debuggerObj); - const rawObj = debuggerObj.unsafeDereference(); - const type = typeof rawObj; - let subtype = undefined; - if (debuggerObj.isProxy) - subtype = 'proxy'; - else if (Array.isArray(rawObj)) - subtype = 'array'; - else if (Object.is(rawObj, null)) - subtype = 'null'; - else if (rawObj instanceof this._domWindow.Node) - subtype = 'node'; - else if (rawObj instanceof this._domWindow.RegExp) - subtype = 'regexp'; - else if (rawObj instanceof this._domWindow.Date) - subtype = 'date'; - else if (rawObj instanceof this._domWindow.Map) - subtype = 'map'; - else if (rawObj instanceof this._domWindow.Set) - subtype = 'set'; - else if (rawObj instanceof this._domWindow.WeakMap) - subtype = 'weakmap'; - else if (rawObj instanceof this._domWindow.WeakSet) - subtype = 'weakset'; - else if (rawObj instanceof this._domWindow.Error) - subtype = 'error'; - else if (rawObj instanceof this._domWindow.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)) { - subtype = 'typedarray'; - } - const isNode = debuggerObj.unsafeDereference() instanceof this._domWindow.Node; - return {objectId, type, subtype}; - } - if (typeof debuggerObj === 'symbol') { - const objectId = helper.generateId(); - this._remoteObjects.set(objectId, debuggerObj); - return {objectId, type: 'symbol'}; - } - - let unserializableValue = undefined; - if (Object.is(debuggerObj, NaN)) - unserializableValue = 'NaN'; - else if (Object.is(debuggerObj, -0)) - unserializableValue = '-0'; - else if (Object.is(debuggerObj, Infinity)) - unserializableValue = 'Infinity'; - else if (Object.is(debuggerObj, -Infinity)) - unserializableValue = '-Infinity'; - return unserializableValue ? {unserializableValue} : {value: debuggerObj}; - } - - ensureSerializedToValue(protocolObject) { - if (!protocolObject.objectId) - return protocolObject; - const obj = this._remoteObjects.get(protocolObject.objectId); - this._remoteObjects.delete(protocolObject.objectId); - return {value: this._serialize(obj)}; - } - - _toDebugger(obj) { - if (typeof obj !== 'object') - return obj; - const properties = {}; - for (let [key, value] of Object.entries(obj)) { - properties[key] = { - writable: true, - enumerable: true, - value: this._toDebugger(value), - }; - } - const baseObject = Array.isArray(obj) ? '([])' : '({})'; - const debuggerObj = this._global.executeInGlobal(baseObject).return; - debuggerObj.defineProperties(properties); - return debuggerObj; - } - - _serialize(obj) { - const result = this._global.executeInGlobalWithBindings('JSON.stringify(e)', {e: obj}); - if (result.throw) - throw new Error('Object is not serializable'); - return JSON.parse(result.return); - } - - disposeObject(objectId) { - this._remoteObjects.delete(objectId); - } - - getObjectProperties(objectId) { - if (!this._remoteObjects.has(objectId)) - throw new Error('Cannot find object with id = ' + arg.objectId); - const result = []; - for (let obj = this._remoteObjects.get(objectId); obj; obj = obj.proto) { - for (const propertyName of obj.getOwnPropertyNames()) { - const descriptor = obj.getOwnPropertyDescriptor(propertyName); - if (!descriptor.enumerable) - continue; - result.push({ - name: propertyName, - value: this._createRemoteObject(descriptor.value), - }); - } - } - return result; - } - - _getResult(completionValue, exceptionDetails = {}) { - if (!completionValue) { - exceptionDetails.text = 'Evaluation terminated!'; - exceptionDetails.stack = ''; - return {success: false, obj: null}; - } - if (completionValue.throw) { - if (this._global.executeInGlobalWithBindings('e instanceof Error', {e: completionValue.throw}).return) { - exceptionDetails.text = this._global.executeInGlobalWithBindings('e.message', {e: completionValue.throw}).return; - exceptionDetails.stack = this._global.executeInGlobalWithBindings('e.stack', {e: completionValue.throw}).return; - } else { - exceptionDetails.value = this._serialize(completionValue.throw); - } - return {success: false, obj: null}; - } - return {success: true, obj: completionValue.return}; - } -} - -var EXPORTED_SYMBOLS = ['RuntimeAgent']; -this.RuntimeAgent = RuntimeAgent; diff --git a/experimental/juggler/src/content/ScrollbarManager.js b/experimental/juggler/src/content/ScrollbarManager.js deleted file mode 100644 index a54219fb8d3..00000000000 --- a/experimental/juggler/src/content/ScrollbarManager.js +++ /dev/null @@ -1,63 +0,0 @@ -const Ci = Components.interfaces; -const Cr = Components.results; -const Cu = Components.utils; -const Cc = Components.classes; - -const {Helper} = ChromeUtils.import('chrome://juggler/content/Helper.js'); -const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm"); - -const HIDDEN_SCROLLBARS = Services.io.newURI('chrome://juggler/content/content/hidden-scrollbars.css'); -const FLOATING_SCROLLBARS = Services.io.newURI('chrome://juggler/content/content/floating-scrollbars.css'); - -const isHeadless = Cc["@mozilla.org/gfx/info;1"].getService(Ci.nsIGfxInfo).isHeadless; -const helper = new Helper(); - -class ScrollbarManager { - constructor(mm, docShell) { - this._docShell = docShell; - this._customScrollbars = null; - - if (isHeadless) - this._setCustomScrollbars(HIDDEN_SCROLLBARS); - - this._eventListeners = [ - helper.addEventListener(mm, 'DOMWindowCreated', this._onDOMWindowCreated.bind(this)), - ]; - } - - setFloatingScrollbars(enabled) { - if (this._customScrollbars === HIDDEN_SCROLLBARS) - return; - this._setCustomScrollbars(enabled ? FLOATING_SCROLLBARS : null); - } - - _setCustomScrollbars(customScrollbars) { - if (this._customScrollbars === customScrollbars) - return; - const windowUtils = this._docShell.domWindow.windowUtils; - if (this._customScrollbars) - windowUtils.removeSheet(this._customScrollbars, windowUtils.AGENT_SHEET); - this._customScrollbars = customScrollbars; - if (this._customScrollbars) - windowUtils.loadSheet(this._customScrollbars, windowUtils.AGENT_SHEET); - } - - dispose() { - this._setCustomScrollbars(null); - helper.removeListeners(this._eventListeners); - } - - _onDOMWindowCreated(event) { - const docShell = event.target.ownerGlobal.docShell; - if (docShell === this._docShell) - return; - const windowUtils = docShell.domWindow.windowUtils; - if (this._customScrollbars) { - windowUtils.loadSheet(this._customScrollbars, windowUtils.AGENT_SHEET); - } - } -} - -var EXPORTED_SYMBOLS = ['ScrollbarManager']; -this.ScrollbarManager = ScrollbarManager; - diff --git a/experimental/juggler/src/content/floating-scrollbars.css b/experimental/juggler/src/content/floating-scrollbars.css deleted file mode 100644 index 7709bdd34c6..00000000000 --- a/experimental/juggler/src/content/floating-scrollbars.css +++ /dev/null @@ -1,47 +0,0 @@ -@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"); -@namespace html url("http://www.w3.org/1999/xhtml"); - -/* Restrict all styles to `*|*:not(html|select) > scrollbar` so that scrollbars - inside a . */ -*|*:not(html|select) > scrollbar { - -moz-appearance: none !important; - position: relative; - background-color: transparent; - background-image: none; - z-index: 2147483647; - padding: 2px; - border: none; -} - -/* Scrollbar code will reset the margin to the correct side depending on - where layout actually puts the scrollbar */ -*|*:not(html|select) > scrollbar[orient="vertical"] { - margin-left: -10px; - min-width: 10px; - max-width: 10px; -} - -*|*:not(html|select) > scrollbar[orient="horizontal"] { - margin-top: -10px; - min-height: 10px; - max-height: 10px; -} - -*|*:not(html|select) > scrollbar slider { - -moz-appearance: none !important; -} - -*|*:not(html|select) > scrollbar thumb { - -moz-appearance: none !important; - background-color: rgba(0,0,0,0.2); - border-width: 0px !important; - border-radius: 3px !important; -} - -*|*:not(html|select) > scrollbar scrollbarbutton, -*|*:not(html|select) > scrollbar gripper { - display: none; -} diff --git a/experimental/juggler/src/content/hidden-scrollbars.css b/experimental/juggler/src/content/hidden-scrollbars.css deleted file mode 100644 index 3a386425d37..00000000000 --- a/experimental/juggler/src/content/hidden-scrollbars.css +++ /dev/null @@ -1,13 +0,0 @@ -@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"); -@namespace html url("http://www.w3.org/1999/xhtml"); - -/* Restrict all styles to `*|*:not(html|select) > scrollbar` so that scrollbars - inside a . */ -*|*:not(html|select) > scrollbar { - -moz-appearance: none !important; - display: none; -} - diff --git a/experimental/juggler/src/content/main.js b/experimental/juggler/src/content/main.js deleted file mode 100644 index d882ea9f3b1..00000000000 --- a/experimental/juggler/src/content/main.js +++ /dev/null @@ -1,27 +0,0 @@ -const {Helper} = ChromeUtils.import('chrome://juggler/content/Helper.js'); -const {ContentSession} = ChromeUtils.import('chrome://juggler/content/content/ContentSession.js'); -const {FrameTree} = ChromeUtils.import('chrome://juggler/content/content/FrameTree.js'); -const {ScrollbarManager} = ChromeUtils.import('chrome://juggler/content/content/ScrollbarManager.js'); - -const sessions = new Map(); -const frameTree = new FrameTree(docShell); -const scrollbarManager = new ScrollbarManager(this, docShell); - -const helper = new Helper(); - -const gListeners = [ - helper.addMessageListener(this, 'juggler:create-content-session', msg => { - const sessionId = msg.data; - sessions.set(sessionId, new ContentSession(sessionId, this, frameTree, scrollbarManager)); - }), - - helper.addEventListener(this, 'unload', msg => { - helper.removeListeners(gListeners); - for (const session of sessions.values()) - session.dispose(); - sessions.clear(); - scrollbarManager.dispose(); - frameTree.dispose(); - }), -]; - diff --git a/experimental/juggler/src/jar.mn b/experimental/juggler/src/jar.mn deleted file mode 100644 index 8f69d540736..00000000000 --- a/experimental/juggler/src/jar.mn +++ /dev/null @@ -1,25 +0,0 @@ -# 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/. - -juggler.jar: -% content juggler %content/ - content/ChromeSession.js (ChromeSession.js) - content/InsecureSweepingOverride.js (InsecureSweepingOverride.js) - content/Protocol.js (Protocol.js) - content/Helper.js (Helper.js) - content/PageHandler.jsm (PageHandler.jsm) - content/BrowserHandler.jsm (BrowserHandler.jsm) - content/content/main.js (content/main.js) - content/content/ContentSession.js (content/ContentSession.js) - content/content/FrameTree.js (content/FrameTree.js) - content/content/PageAgent.js (content/PageAgent.js) - content/content/RuntimeAgent.js (content/RuntimeAgent.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) - content/server/server.js (server/server.js) - content/server/transport.js (server/transport.js) - content/server/stream-utils.js (server/stream-utils.js) - content/server/packets.js (server/packets.js) - diff --git a/experimental/juggler/src/moz.build b/experimental/juggler/src/moz.build deleted file mode 100644 index 1a0a3130bf9..00000000000 --- a/experimental/juggler/src/moz.build +++ /dev/null @@ -1,15 +0,0 @@ -# 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/. - -DIRS += ["components"] - -JAR_MANIFESTS += ["jar.mn"] -#JS_PREFERENCE_FILES += ["prefs/marionette.js"] - -#MARIONETTE_UNIT_MANIFESTS += ["harness/marionette_harness/tests/unit/unit-tests.ini"] -#XPCSHELL_TESTS_MANIFESTS += ["test/unit/xpcshell.ini"] - -with Files("**"): - BUG_COMPONENT = ("Testing", "Juggler") - diff --git a/experimental/juggler/src/server/packets.js b/experimental/juggler/src/server/packets.js deleted file mode 100644 index 5fc6eba67b9..00000000000 --- a/experimental/juggler/src/server/packets.js +++ /dev/null @@ -1,407 +0,0 @@ -/* 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/. */ - -"use strict"; - -/** - * Packets contain read / write functionality for the different packet types - * supported by the debugging protocol, so that a transport can focus on - * delivery and queue management without worrying too much about the specific - * packet types. - * - * They are intended to be "one use only", so a new packet should be - * instantiated for each incoming or outgoing packet. - * - * A complete Packet type should expose at least the following: - * * read(stream, scriptableStream) - * Called when the input stream has data to read - * * write(stream) - * Called when the output stream is ready to write - * * get done() - * Returns true once the packet is done being read / written - * * destroy() - * Called to clean up at the end of use - */ - -const {StreamUtils} = - ChromeUtils.import("chrome://juggler/content/server/stream-utils.js", {}); - -const unicodeConverter = Cc["@mozilla.org/intl/scriptableunicodeconverter"] - .createInstance(Ci.nsIScriptableUnicodeConverter); -unicodeConverter.charset = "UTF-8"; - -const defer = function() { - let deferred = { - promise: new Promise((resolve, reject) => { - deferred.resolve = resolve; - deferred.reject = reject; - }), - }; - return deferred; -}; - -this.EXPORTED_SYMBOLS = ["RawPacket", "Packet", "JSONPacket", "BulkPacket"]; - -// The transport's previous check ensured the header length did not -// exceed 20 characters. Here, we opt for the somewhat smaller, but still -// large limit of 1 TiB. -const PACKET_LENGTH_MAX = Math.pow(2, 40); - -/** - * A generic Packet processing object (extended by two subtypes below). - * - * @class - */ -function Packet(transport) { - this._transport = transport; - this._length = 0; -} - -/** - * Attempt to initialize a new Packet based on the incoming packet header - * we've received so far. We try each of the types in succession, trying - * JSON packets first since they are much more common. - * - * @param {string} header - * Packet header string to attempt parsing. - * @param {DebuggerTransport} transport - * Transport instance that will own the packet. - * - * @return {Packet} - * Parsed packet of the matching type, or null if no types matched. - */ -Packet.fromHeader = function(header, transport) { - return JSONPacket.fromHeader(header, transport) || - BulkPacket.fromHeader(header, transport); -}; - -Packet.prototype = { - - get length() { - return this._length; - }, - - set length(length) { - if (length > PACKET_LENGTH_MAX) { - throw new Error("Packet length " + length + - " exceeds the max length of " + PACKET_LENGTH_MAX); - } - this._length = length; - }, - - destroy() { - this._transport = null; - }, -}; - -/** - * With a JSON packet (the typical packet type sent via the transport), - * data is transferred as a JSON packet serialized into a string, - * with the string length prepended to the packet, followed by a colon - * ([length]:[packet]). The contents of the JSON packet are specified in - * the Remote Debugging Protocol specification. - * - * @param {DebuggerTransport} transport - * Transport instance that will own the packet. - */ -function JSONPacket(transport) { - Packet.call(this, transport); - this._data = ""; - this._done = false; -} - -/** - * Attempt to initialize a new JSONPacket based on the incoming packet - * header we've received so far. - * - * @param {string} header - * Packet header string to attempt parsing. - * @param {DebuggerTransport} transport - * Transport instance that will own the packet. - * - * @return {JSONPacket} - * Parsed packet, or null if it's not a match. - */ -JSONPacket.fromHeader = function(header, transport) { - let match = this.HEADER_PATTERN.exec(header); - - if (!match) { - return null; - } - - let packet = new JSONPacket(transport); - packet.length = +match[1]; - return packet; -}; - -JSONPacket.HEADER_PATTERN = /^(\d+):$/; - -JSONPacket.prototype = Object.create(Packet.prototype); - -Object.defineProperty(JSONPacket.prototype, "object", { - /** - * Gets the object (not the serialized string) being read or written. - */ - get() { - return this._object; - }, - - /** - * Sets the object to be sent when write() is called. - */ - set(object) { - this._object = object; - let data = JSON.stringify(object); - this._data = unicodeConverter.ConvertFromUnicode(data); - this.length = this._data.length; - }, -}); - -JSONPacket.prototype.read = function(stream, scriptableStream) { - - // Read in more packet data. - this._readData(stream, scriptableStream); - - if (!this.done) { - // Don't have a complete packet yet. - return; - } - - let json = this._data; - try { - json = unicodeConverter.ConvertToUnicode(json); - this._object = JSON.parse(json); - } catch (e) { - let msg = "Error parsing incoming packet: " + json + " (" + e + - " - " + e.stack + ")"; - console.error(msg); - dump(msg + "\n"); - return; - } - - this._transport._onJSONObjectReady(this._object); -}; - -JSONPacket.prototype._readData = function(stream, scriptableStream) { - let bytesToRead = Math.min( - this.length - this._data.length, - stream.available()); - this._data += scriptableStream.readBytes(bytesToRead); - this._done = this._data.length === this.length; -}; - -JSONPacket.prototype.write = function(stream) { - - if (this._outgoing === undefined) { - // Format the serialized packet to a buffer - this._outgoing = this.length + ":" + this._data; - } - - let written = stream.write(this._outgoing, this._outgoing.length); - this._outgoing = this._outgoing.slice(written); - this._done = !this._outgoing.length; -}; - -Object.defineProperty(JSONPacket.prototype, "done", { - get() { - return this._done; - }, -}); - -JSONPacket.prototype.toString = function() { - return JSON.stringify(this._object, null, 2); -}; - -/** - * With a bulk packet, data is transferred by temporarily handing over - * the transport's input or output stream to the application layer for - * writing data directly. This can be much faster for large data sets, - * and avoids various stages of copies and data duplication inherent in - * the JSON packet type. The bulk packet looks like: - * - * bulk [actor] [type] [length]:[data] - * - * The interpretation of the data portion depends on the kind of actor and - * the packet's type. See the Remote Debugging Protocol Stream Transport - * spec for more details. - * - * @param {DebuggerTransport} transport - * Transport instance that will own the packet. - */ -function BulkPacket(transport) { - Packet.call(this, transport); - this._done = false; - this._readyForWriting = defer(); -} - -/** - * Attempt to initialize a new BulkPacket based on the incoming packet - * header we've received so far. - * - * @param {string} header - * Packet header string to attempt parsing. - * @param {DebuggerTransport} transport - * Transport instance that will own the packet. - * - * @return {BulkPacket} - * Parsed packet, or null if it's not a match. - */ -BulkPacket.fromHeader = function(header, transport) { - let match = this.HEADER_PATTERN.exec(header); - - if (!match) { - return null; - } - - let packet = new BulkPacket(transport); - packet.header = { - actor: match[1], - type: match[2], - length: +match[3], - }; - return packet; -}; - -BulkPacket.HEADER_PATTERN = /^bulk ([^: ]+) ([^: ]+) (\d+):$/; - -BulkPacket.prototype = Object.create(Packet.prototype); - -BulkPacket.prototype.read = function(stream) { - // Temporarily pause monitoring of the input stream - this._transport.pauseIncoming(); - - let deferred = defer(); - - this._transport._onBulkReadReady({ - actor: this.actor, - type: this.type, - length: this.length, - copyTo: (output) => { - let copying = StreamUtils.copyStream(stream, output, this.length); - deferred.resolve(copying); - return copying; - }, - stream, - done: deferred, - }); - - // Await the result of reading from the stream - deferred.promise.then(() => { - this._done = true; - this._transport.resumeIncoming(); - }, this._transport.close); - - // Ensure this is only done once - this.read = () => { - throw new Error("Tried to read() a BulkPacket's stream multiple times."); - }; -}; - -BulkPacket.prototype.write = function(stream) { - if (this._outgoingHeader === undefined) { - // Format the serialized packet header to a buffer - this._outgoingHeader = "bulk " + this.actor + " " + this.type + " " + - this.length + ":"; - } - - // Write the header, or whatever's left of it to write. - if (this._outgoingHeader.length) { - let written = stream.write(this._outgoingHeader, - this._outgoingHeader.length); - this._outgoingHeader = this._outgoingHeader.slice(written); - return; - } - - // Temporarily pause the monitoring of the output stream - this._transport.pauseOutgoing(); - - let deferred = defer(); - - this._readyForWriting.resolve({ - copyFrom: (input) => { - let copying = StreamUtils.copyStream(input, stream, this.length); - deferred.resolve(copying); - return copying; - }, - stream, - done: deferred, - }); - - // Await the result of writing to the stream - deferred.promise.then(() => { - this._done = true; - this._transport.resumeOutgoing(); - }, this._transport.close); - - // Ensure this is only done once - this.write = () => { - throw new Error("Tried to write() a BulkPacket's stream multiple times."); - }; -}; - -Object.defineProperty(BulkPacket.prototype, "streamReadyForWriting", { - get() { - return this._readyForWriting.promise; - }, -}); - -Object.defineProperty(BulkPacket.prototype, "header", { - get() { - return { - actor: this.actor, - type: this.type, - length: this.length, - }; - }, - - set(header) { - this.actor = header.actor; - this.type = header.type; - this.length = header.length; - }, -}); - -Object.defineProperty(BulkPacket.prototype, "done", { - get() { - return this._done; - }, -}); - -BulkPacket.prototype.toString = function() { - return "Bulk: " + JSON.stringify(this.header, null, 2); -}; - -/** - * RawPacket is used to test the transport's error handling of malformed - * packets, by writing data directly onto the stream. - * @param transport DebuggerTransport - * The transport instance that will own the packet. - * @param data string - * The raw string to send out onto the stream. - */ -function RawPacket(transport, data) { - Packet.call(this, transport); - this._data = data; - this.length = data.length; - this._done = false; -} - -RawPacket.prototype = Object.create(Packet.prototype); - -RawPacket.prototype.read = function() { - // this has not yet been needed for testing - throw new Error("Not implemented"); -}; - -RawPacket.prototype.write = function(stream) { - let written = stream.write(this._data, this._data.length); - this._data = this._data.slice(written); - this._done = !this._data.length; -}; - -Object.defineProperty(RawPacket.prototype, "done", { - get() { - return this._done; - }, -}); diff --git a/experimental/juggler/src/server/server.js b/experimental/juggler/src/server/server.js deleted file mode 100644 index 9ec7f871c36..00000000000 --- a/experimental/juggler/src/server/server.js +++ /dev/null @@ -1,97 +0,0 @@ -/* 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/. */ - -"use strict"; - -const CC = Components.Constructor; - -const ServerSocket = CC( - "@mozilla.org/network/server-socket;1", - "nsIServerSocket", - "initSpecialConnection"); - -ChromeUtils.import("resource://gre/modules/Services.jsm"); -ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm"); - -const {DebuggerTransport} = ChromeUtils.import("chrome://juggler/content/server/transport.js", {}); - -const {KeepWhenOffline, LoopbackOnly} = Ci.nsIServerSocket; - -this.EXPORTED_SYMBOLS = [ - "TCPConnection", - "TCPListener", -]; - -class TCPListener { - constructor() { - this._socket = null; - this._nextConnID = 0; - this.onconnectioncreated = null; - this.onconnectionclosed = null; - } - - start(port) { - if (this._socket) - return; - try { - const flags = KeepWhenOffline | LoopbackOnly; - const backlog = 1; - this._socket = new ServerSocket(port, flags, backlog); - } catch (e) { - throw new Error(`Could not bind to port ${port} (${e.name})`); - } - this._socket.asyncListen(this); - return this._socket.port; - } - - stop() { - if (!this._socket) - return; - // Note that closing the server socket will not close currently active - // connections. - this._socket.close(); - this._socket = null; - } - - onSocketAccepted(serverSocket, clientSocket) { - const input = clientSocket.openInputStream(0, 0, 0); - const output = clientSocket.openOutputStream(0, 0, 0); - const transport = new DebuggerTransport(input, output); - - const connection = new TCPConnection(this._nextConnID++, transport, () => { - if (this.onconnectionclosed) - this.onconnectionclosed.call(null, connection); - }); - transport.ready(); - if (this.onconnectioncreated) - this.onconnectioncreated.call(null, connection); - } -} -this.TCPListener = TCPListener; - -class TCPConnection { - constructor(id, transport, closeCallback) { - this._id = id; - this._transport = transport; - // transport hooks are TCPConnection#onPacket - // and TCPConnection#onClosed - this._transport.hooks = this; - this._closeCallback = closeCallback; - this.onmessage = null; - } - - send(msg) { - this._transport.send(msg); - } - - onClosed() { - this._closeCallback.call(null); - } - - async onPacket(data) { - if (this.onmessage) - this.onmessage.call(null, data); - } -} -this.TCPConnection = TCPConnection; diff --git a/experimental/juggler/src/server/stream-utils.js b/experimental/juggler/src/server/stream-utils.js deleted file mode 100644 index 3d3068230e5..00000000000 --- a/experimental/juggler/src/server/stream-utils.js +++ /dev/null @@ -1,247 +0,0 @@ -/* 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/. */ - -"use strict"; - -const CC = Components.Constructor; - -ChromeUtils.import("resource://gre/modules/EventEmitter.jsm"); -ChromeUtils.import("resource://gre/modules/Services.jsm"); - -const IOUtil = Cc["@mozilla.org/io-util;1"].getService(Ci.nsIIOUtil); -const ScriptableInputStream = CC("@mozilla.org/scriptableinputstream;1", - "nsIScriptableInputStream", "init"); - -this.EXPORTED_SYMBOLS = ["StreamUtils"]; - -const BUFFER_SIZE = 0x8000; - -/** - * This helper function (and its companion object) are used by bulk - * senders and receivers to read and write data in and out of other streams. - * Functions that make use of this tool are passed to callers when it is - * time to read or write bulk data. It is highly recommended to use these - * copier functions instead of the stream directly because the copier - * enforces the agreed upon length. Since bulk mode reuses an existing - * stream, the sender and receiver must write and read exactly the agreed - * upon amount of data, or else the entire transport will be left in a - * invalid state. Additionally, other methods of stream copying (such as - * NetUtil.asyncCopy) close the streams involved, which would terminate - * the debugging transport, and so it is avoided here. - * - * Overall, this *works*, but clearly the optimal solution would be - * able to just use the streams directly. If it were possible to fully - * implement nsIInputStream/nsIOutputStream in JS, wrapper streams could - * be created to enforce the length and avoid closing, and consumers could - * use familiar stream utilities like NetUtil.asyncCopy. - * - * The function takes two async streams and copies a precise number - * of bytes from one to the other. Copying begins immediately, but may - * complete at some future time depending on data size. Use the returned - * promise to know when it's complete. - * - * @param {nsIAsyncInputStream} input - * Stream to copy from. - * @param {nsIAsyncOutputStream} output - * Stream to copy to. - * @param {number} length - * Amount of data that needs to be copied. - * - * @return {Promise} - * Promise is resolved when copying completes or rejected if any - * (unexpected) errors occur. - */ -function copyStream(input, output, length) { - let copier = new StreamCopier(input, output, length); - return copier.copy(); -} - -/** @class */ -function StreamCopier(input, output, length) { - EventEmitter.decorate(this); - this._id = StreamCopier._nextId++; - this.input = input; - // Save off the base output stream, since we know it's async as we've - // required - this.baseAsyncOutput = output; - if (IOUtil.outputStreamIsBuffered(output)) { - this.output = output; - } else { - this.output = Cc["@mozilla.org/network/buffered-output-stream;1"] - .createInstance(Ci.nsIBufferedOutputStream); - this.output.init(output, BUFFER_SIZE); - } - this._length = length; - this._amountLeft = length; - this._deferred = { - promise: new Promise((resolve, reject) => { - this._deferred.resolve = resolve; - this._deferred.reject = reject; - }), - }; - - this._copy = this._copy.bind(this); - this._flush = this._flush.bind(this); - this._destroy = this._destroy.bind(this); - - // Copy promise's then method up to this object. - // - // Allows the copier to offer a promise interface for the simple succeed - // or fail scenarios, but also emit events (due to the EventEmitter) - // for other states, like progress. - this.then = this._deferred.promise.then.bind(this._deferred.promise); - this.then(this._destroy, this._destroy); - - // Stream ready callback starts as |_copy|, but may switch to |_flush| - // at end if flushing would block the output stream. - this._streamReadyCallback = this._copy; -} -StreamCopier._nextId = 0; - -StreamCopier.prototype = { - - copy() { - // Dispatch to the next tick so that it's possible to attach a progress - // event listener, even for extremely fast copies (like when testing). - Services.tm.currentThread.dispatch(() => { - try { - this._copy(); - } catch (e) { - this._deferred.reject(e); - } - }, 0); - return this; - }, - - _copy() { - let bytesAvailable = this.input.available(); - let amountToCopy = Math.min(bytesAvailable, this._amountLeft); - this._debug("Trying to copy: " + amountToCopy); - - let bytesCopied; - try { - bytesCopied = this.output.writeFrom(this.input, amountToCopy); - } catch (e) { - if (e.result == Cr.NS_BASE_STREAM_WOULD_BLOCK) { - this._debug("Base stream would block, will retry"); - this._debug("Waiting for output stream"); - this.baseAsyncOutput.asyncWait(this, 0, 0, Services.tm.currentThread); - return; - } - throw e; - } - - this._amountLeft -= bytesCopied; - this._debug("Copied: " + bytesCopied + - ", Left: " + this._amountLeft); - this._emitProgress(); - - if (this._amountLeft === 0) { - this._debug("Copy done!"); - this._flush(); - return; - } - - this._debug("Waiting for input stream"); - this.input.asyncWait(this, 0, 0, Services.tm.currentThread); - }, - - _emitProgress() { - this.emit("progress", { - bytesSent: this._length - this._amountLeft, - totalBytes: this._length, - }); - }, - - _flush() { - try { - this.output.flush(); - } catch (e) { - if (e.result == Cr.NS_BASE_STREAM_WOULD_BLOCK || - e.result == Cr.NS_ERROR_FAILURE) { - this._debug("Flush would block, will retry"); - this._streamReadyCallback = this._flush; - this._debug("Waiting for output stream"); - this.baseAsyncOutput.asyncWait(this, 0, 0, Services.tm.currentThread); - return; - } - throw e; - } - this._deferred.resolve(); - }, - - _destroy() { - this._destroy = null; - this._copy = null; - this._flush = null; - this.input = null; - this.output = null; - }, - - // nsIInputStreamCallback - onInputStreamReady() { - this._streamReadyCallback(); - }, - - // nsIOutputStreamCallback - onOutputStreamReady() { - this._streamReadyCallback(); - }, - - _debug() { - }, - -}; - -/** - * Read from a stream, one byte at a time, up to the next - * delimiter character, but stopping if we've read |count| - * without finding it. Reading also terminates early if there are less - * than count bytes available on the stream. In that case, - * we only read as many bytes as the stream currently has to offer. - * - * @param {nsIInputStream} stream - * Input stream to read from. - * @param {string} delimiter - * Character we're trying to find. - * @param {number} count - * Max number of characters to read while searching. - * - * @return {string} - * Collected data. If the delimiter was found, this string will - * end with it. - */ -// TODO: This implementation could be removed if bug 984651 is fixed, -// which provides a native version of the same idea. -function delimitedRead(stream, delimiter, count) { - let scriptableStream; - if (stream instanceof Ci.nsIScriptableInputStream) { - scriptableStream = stream; - } else { - scriptableStream = new ScriptableInputStream(stream); - } - - let data = ""; - - // Don't exceed what's available on the stream - count = Math.min(count, stream.available()); - - if (count <= 0) { - return data; - } - - let char; - while (char !== delimiter && count > 0) { - char = scriptableStream.readBytes(1); - count--; - data += char; - } - - return data; -} - -this.StreamUtils = { - copyStream, - delimitedRead, -}; diff --git a/experimental/juggler/src/server/transport.js b/experimental/juggler/src/server/transport.js deleted file mode 100644 index 8228fc362f1..00000000000 --- a/experimental/juggler/src/server/transport.js +++ /dev/null @@ -1,523 +0,0 @@ -/* 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/. */ - -"use strict"; - -/* global Pipe, ScriptableInputStream */ - -const CC = Components.Constructor; - -ChromeUtils.import("resource://gre/modules/Services.jsm"); -ChromeUtils.import("resource://gre/modules/EventEmitter.jsm"); -const {StreamUtils} = - ChromeUtils.import("chrome://juggler/content/server/stream-utils.js", {}); -const {Packet, JSONPacket, BulkPacket} = - ChromeUtils.import("chrome://juggler/content/server/packets.js", {}); - -const executeSoon = function(func) { - Services.tm.dispatchToMainThread(func); -}; - -const flags = {wantVerbose: false, wantLogging: false}; - -const dumpv = - flags.wantVerbose ? - function(msg) { dump(msg + "\n"); } : - function() {}; - -const Pipe = CC("@mozilla.org/pipe;1", "nsIPipe", "init"); - -const ScriptableInputStream = CC("@mozilla.org/scriptableinputstream;1", - "nsIScriptableInputStream", "init"); - -this.EXPORTED_SYMBOLS = ["DebuggerTransport"]; - -const PACKET_HEADER_MAX = 200; - -/** - * An adapter that handles data transfers between the debugger client - * and server. It can work with both nsIPipe and nsIServerSocket - * transports so long as the properly created input and output streams - * are specified. (However, for intra-process connections, - * LocalDebuggerTransport, below, is more efficient than using an nsIPipe - * pair with DebuggerTransport.) - * - * @param {nsIAsyncInputStream} input - * The input stream. - * @param {nsIAsyncOutputStream} output - * The output stream. - * - * Given a DebuggerTransport instance dt: - * 1) Set dt.hooks to a packet handler object (described below). - * 2) Call dt.ready() to begin watching for input packets. - * 3) Call dt.send() / dt.startBulkSend() to send packets. - * 4) Call dt.close() to close the connection, and disengage from - * the event loop. - * - * A packet handler is an object with the following methods: - * - * - onPacket(packet) - called when we have received a complete packet. - * |packet| is the parsed form of the packet --- a JavaScript value, not - * a JSON-syntax string. - * - * - onBulkPacket(packet) - called when we have switched to bulk packet - * receiving mode. |packet| is an object containing: - * * actor: Name of actor that will receive the packet - * * type: Name of actor's method that should be called on receipt - * * length: Size of the data to be read - * * stream: This input stream should only be used directly if you - * can ensure that you will read exactly |length| bytes and - * will not close the stream when reading is complete - * * done: If you use the stream directly (instead of |copyTo| - * below), you must signal completion by resolving/rejecting - * this deferred. If it's rejected, the transport will - * be closed. If an Error is supplied as a rejection value, - * it will be logged via |dump|. If you do use |copyTo|, - * resolving is taken care of for you when copying completes. - * * copyTo: A helper function for getting your data out of the - * stream that meets the stream handling requirements above, - * and has the following signature: - * - * @param nsIAsyncOutputStream {output} - * The stream to copy to. - * - * @return {Promise} - * The promise is resolved when copying completes or - * rejected if any (unexpected) errors occur. This object - * also emits "progress" events for each chunk that is - * copied. See stream-utils.js. - * - * - onClosed(reason) - called when the connection is closed. |reason| - * is an optional nsresult or object, typically passed when the - * transport is closed due to some error in a underlying stream. - * - * See ./packets.js and the Remote Debugging Protocol specification for - * more details on the format of these packets. - * - * @class - */ -function DebuggerTransport(input, output) { - EventEmitter.decorate(this); - - this._input = input; - this._scriptableInput = new ScriptableInputStream(input); - this._output = output; - - // The current incoming (possibly partial) header, which will determine - // which type of Packet |_incoming| below will become. - this._incomingHeader = ""; - // The current incoming Packet object - this._incoming = null; - // A queue of outgoing Packet objects - this._outgoing = []; - - this.hooks = null; - this.active = false; - - this._incomingEnabled = true; - this._outgoingEnabled = true; - - this.close = this.close.bind(this); -} - -DebuggerTransport.prototype = { - /** - * Transmit an object as a JSON packet. - * - * This method returns immediately, without waiting for the entire - * packet to be transmitted, registering event handlers as needed to - * transmit the entire packet. Packets are transmitted in the order they - * are passed to this method. - */ - send(object) { - this.emit("send", object); - - let packet = new JSONPacket(this); - packet.object = object; - this._outgoing.push(packet); - this._flushOutgoing(); - }, - - /** - * Transmit streaming data via a bulk packet. - * - * This method initiates the bulk send process by queuing up the header - * data. The caller receives eventual access to a stream for writing. - * - * N.B.: Do *not* attempt to close the stream handed to you, as it - * will continue to be used by this transport afterwards. Most users - * should instead use the provided |copyFrom| function instead. - * - * @param {Object} header - * This is modeled after the format of JSON packets above, but does - * not actually contain the data, but is instead just a routing - * header: - * - * - actor: Name of actor that will receive the packet - * - type: Name of actor's method that should be called on receipt - * - length: Size of the data to be sent - * - * @return {Promise} - * The promise will be resolved when you are allowed to write to - * the stream with an object containing: - * - * - stream: This output stream should only be used directly - * if you can ensure that you will write exactly - * |length| bytes and will not close the stream when - * writing is complete. - * - done: If you use the stream directly (instead of - * |copyFrom| below), you must signal completion by - * resolving/rejecting this deferred. If it's - * rejected, the transport will be closed. If an - * Error is supplied as a rejection value, it will - * be logged via |dump|. If you do use |copyFrom|, - * resolving is taken care of for you when copying - * completes. - * - copyFrom: A helper function for getting your data onto the - * stream that meets the stream handling requirements - * above, and has the following signature: - * - * @param {nsIAsyncInputStream} input - * The stream to copy from. - * - * @return {Promise} - * The promise is resolved when copying completes - * or rejected if any (unexpected) errors occur. - * This object also emits "progress" events for - * each chunkthat is copied. See stream-utils.js. - */ - startBulkSend(header) { - this.emit("startbulksend", header); - - let packet = new BulkPacket(this); - packet.header = header; - this._outgoing.push(packet); - this._flushOutgoing(); - return packet.streamReadyForWriting; - }, - - /** - * Close the transport. - * - * @param {(nsresult|object)=} reason - * The status code or error message that corresponds to the reason - * for closing the transport (likely because a stream closed - * or failed). - */ - close(reason) { - this.emit("close", reason); - - this.active = false; - this._input.close(); - this._scriptableInput.close(); - this._output.close(); - this._destroyIncoming(); - this._destroyAllOutgoing(); - if (this.hooks) { - this.hooks.onClosed(reason); - this.hooks = null; - } - if (reason) { - dumpv("Transport closed: " + reason); - } else { - dumpv("Transport closed."); - } - }, - - /** - * The currently outgoing packet (at the top of the queue). - */ - get _currentOutgoing() { - return this._outgoing[0]; - }, - - /** - * Flush data to the outgoing stream. Waits until the output - * stream notifies us that it is ready to be written to (via - * onOutputStreamReady). - */ - _flushOutgoing() { - if (!this._outgoingEnabled || this._outgoing.length === 0) { - return; - } - - // If the top of the packet queue has nothing more to send, remove it. - if (this._currentOutgoing.done) { - this._finishCurrentOutgoing(); - } - - if (this._outgoing.length > 0) { - let threadManager = Cc["@mozilla.org/thread-manager;1"].getService(); - this._output.asyncWait(this, 0, 0, threadManager.currentThread); - } - }, - - /** - * Pause this transport's attempts to write to the output stream. - * This is used when we've temporarily handed off our output stream for - * writing bulk data. - */ - pauseOutgoing() { - this._outgoingEnabled = false; - }, - - /** - * Resume this transport's attempts to write to the output stream. - */ - resumeOutgoing() { - this._outgoingEnabled = true; - this._flushOutgoing(); - }, - - // nsIOutputStreamCallback - /** - * This is called when the output stream is ready for more data to - * be written. The current outgoing packet will attempt to write some - * amount of data, but may not complete. - */ - onOutputStreamReady(stream) { - if (!this._outgoingEnabled || this._outgoing.length === 0) { - return; - } - - try { - this._currentOutgoing.write(stream); - } catch (e) { - if (e.result != Cr.NS_BASE_STREAM_WOULD_BLOCK) { - this.close(e.result); - return; - } - throw e; - } - - this._flushOutgoing(); - }, - - /** - * Remove the current outgoing packet from the queue upon completion. - */ - _finishCurrentOutgoing() { - if (this._currentOutgoing) { - this._currentOutgoing.destroy(); - this._outgoing.shift(); - } - }, - - /** - * Clear the entire outgoing queue. - */ - _destroyAllOutgoing() { - for (let packet of this._outgoing) { - packet.destroy(); - } - this._outgoing = []; - }, - - /** - * Initialize the input stream for reading. Once this method has been - * called, we watch for packets on the input stream, and pass them to - * the appropriate handlers via this.hooks. - */ - ready() { - this.active = true; - this._waitForIncoming(); - }, - - /** - * Asks the input stream to notify us (via onInputStreamReady) when it is - * ready for reading. - */ - _waitForIncoming() { - if (this._incomingEnabled) { - let threadManager = Cc["@mozilla.org/thread-manager;1"].getService(); - this._input.asyncWait(this, 0, 0, threadManager.currentThread); - } - }, - - /** - * Pause this transport's attempts to read from the input stream. - * This is used when we've temporarily handed off our input stream for - * reading bulk data. - */ - pauseIncoming() { - this._incomingEnabled = false; - }, - - /** - * Resume this transport's attempts to read from the input stream. - */ - resumeIncoming() { - this._incomingEnabled = true; - this._flushIncoming(); - this._waitForIncoming(); - }, - - // nsIInputStreamCallback - /** - * Called when the stream is either readable or closed. - */ - onInputStreamReady(stream) { - try { - while (stream.available() && this._incomingEnabled && - this._processIncoming(stream, stream.available())) { - // Loop until there is nothing more to process - } - this._waitForIncoming(); - } catch (e) { - if (e.result != Cr.NS_BASE_STREAM_WOULD_BLOCK) { - this.close(e.result); - } else { - throw e; - } - } - }, - - /** - * Process the incoming data. Will create a new currently incoming - * Packet if needed. Tells the incoming Packet to read as much data - * as it can, but reading may not complete. The Packet signals that - * its data is ready for delivery by calling one of this transport's - * _on*Ready methods (see ./packets.js and the _on*Ready methods below). - * - * @return {boolean} - * Whether incoming stream processing should continue for any - * remaining data. - */ - _processIncoming(stream, count) { - dumpv("Data available: " + count); - - if (!count) { - dumpv("Nothing to read, skipping"); - return false; - } - - try { - if (!this._incoming) { - dumpv("Creating a new packet from incoming"); - - if (!this._readHeader(stream)) { - // Not enough data to read packet type - return false; - } - - // Attempt to create a new Packet by trying to parse each possible - // header pattern. - this._incoming = Packet.fromHeader(this._incomingHeader, this); - if (!this._incoming) { - throw new Error("No packet types for header: " + - this._incomingHeader); - } - } - - if (!this._incoming.done) { - // We have an incomplete packet, keep reading it. - dumpv("Existing packet incomplete, keep reading"); - this._incoming.read(stream, this._scriptableInput); - } - } catch (e) { - dump(`Error reading incoming packet: (${e} - ${e.stack})\n`); - - // Now in an invalid state, shut down the transport. - this.close(); - return false; - } - - if (!this._incoming.done) { - // Still not complete, we'll wait for more data. - dumpv("Packet not done, wait for more"); - return true; - } - - // Ready for next packet - this._flushIncoming(); - return true; - }, - - /** - * Read as far as we can into the incoming data, attempting to build - * up a complete packet header (which terminates with ":"). We'll only - * read up to PACKET_HEADER_MAX characters. - * - * @return {boolean} - * True if we now have a complete header. - */ - _readHeader() { - let amountToRead = PACKET_HEADER_MAX - this._incomingHeader.length; - this._incomingHeader += - StreamUtils.delimitedRead(this._scriptableInput, ":", amountToRead); - if (flags.wantVerbose) { - dumpv("Header read: " + this._incomingHeader); - } - - if (this._incomingHeader.endsWith(":")) { - if (flags.wantVerbose) { - dumpv("Found packet header successfully: " + this._incomingHeader); - } - return true; - } - - if (this._incomingHeader.length >= PACKET_HEADER_MAX) { - throw new Error("Failed to parse packet header!"); - } - - // Not enough data yet. - return false; - }, - - /** - * If the incoming packet is done, log it as needed and clear the buffer. - */ - _flushIncoming() { - if (!this._incoming.done) { - return; - } - if (flags.wantLogging) { - dumpv("Got: " + this._incoming); - } - this._destroyIncoming(); - }, - - /** - * Handler triggered by an incoming JSONPacket completing it's |read| - * method. Delivers the packet to this.hooks.onPacket. - */ - _onJSONObjectReady(object) { - executeSoon(() => { - // Ensure the transport is still alive by the time this runs. - if (this.active) { - this.emit("packet", object); - this.hooks.onPacket(object); - } - }); - }, - - /** - * Handler triggered by an incoming BulkPacket entering the |read| - * phase for the stream portion of the packet. Delivers info about the - * incoming streaming data to this.hooks.onBulkPacket. See the main - * comment on the transport at the top of this file for more details. - */ - _onBulkReadReady(...args) { - executeSoon(() => { - // Ensure the transport is still alive by the time this runs. - if (this.active) { - this.emit("bulkpacket", ...args); - this.hooks.onBulkPacket(...args); - } - }); - }, - - /** - * Remove all handlers and references related to the current incoming - * packet, either because it is now complete or because the transport - * is closing. - */ - _destroyIncoming() { - if (this._incoming) { - this._incoming.destroy(); - } - this._incomingHeader = ""; - this._incoming = null; - }, -}; diff --git a/experimental/puppeteer-firefox/README.md b/experimental/puppeteer-firefox/README.md index 51f68746f49..24bc48f9f91 100644 --- a/experimental/puppeteer-firefox/README.md +++ b/experimental/puppeteer-firefox/README.md @@ -17,7 +17,7 @@ npm i puppeteer-firefox # or "yarn add puppeteer-firefox" ``` -Note: When you install puppeteer-firefox, it downloads a [custom-built Firefox](https://github.com/GoogleChrome/puppeteer/tree/master/experimental/juggler) (Firefox/63.0.4) that is guaranteed to work with the API. +Note: When you install puppeteer-firefox, it downloads a [custom-built Firefox](https://github.com/puppeteer/juggler) (Firefox/63.0.4) that is guaranteed to work with the API. ### Usage @@ -46,152 +46,9 @@ node example.js ### API Status -Big lacking parts: +Current tip-of-tree status of Puppeteer-Firefox is availabe at [isPuppeteerFirefoxReady?](https://aslushnikov.github.io/ispuppeteerfirefoxready/) -- `page.emulate` -- `page.pdf` -- all network-related APIs: `page.on('request')`, `page.on('response')`, and request interception - -Supported API: - -- class: Puppeteer - * puppeteer.executablePath() - * puppeteer.launch([options]) -- class: Browser - * event: 'targetchanged' - * event: 'targetcreated' - * event: 'targetdestroyed' - * browser.close() - * browser.newPage() - * browser.pages() - * browser.process() - * browser.targets() - * browser.userAgent() - * browser.version() - * browser.waitForTarget(predicate[, options]) -- class: Target - * target.browser() - * target.page() - * target.type() - * target.url() -- class: Page - * event: 'close' - * event: 'console' - * event: 'dialog' - * event: 'domcontentloaded' - * event: 'frameattached' - * event: 'framedetached' - * event: 'framenavigated' - * event: 'load' - * event: 'pageerror' - * page.$(selector) - * page.$$(selector) - * page.$$eval(selector, pageFunction[, ...args]) - * page.$eval(selector, pageFunction[, ...args]) - * page.$x(expression) - * page.addScriptTag(options) - * page.addStyleTag(options) - * page.browser() - * page.click(selector[, options]) - * page.close(options) - * page.content() - * page.evaluate(pageFunction, ...args) - * page.evaluateOnNewDocument(pageFunction, ...args) - * page.focus(selector) - * page.frames() - * page.goBack(options) - * page.goForward(options) - * page.goto(url, options) - * page.hover(selector) - * page.isClosed() - * page.keyboard - * page.mainFrame() - * page.mouse - * page.reload(options) - * page.screenshot([options]) - * page.select(selector, ...values) - * page.setContent(html) - * page.setViewport(viewport) - * page.target() - * page.title() - * page.type(selector, text[, options]) - * page.url() - * page.viewport() - * page.waitFor(selectorOrFunctionOrTimeout[, options[, ...args]]) - * page.waitForFunction(pageFunction[, options[, ...args]]) - * page.waitForNavigation(options) - * page.waitForSelector(selector[, options]) - * page.waitForXPath(xpath[, options]) -- class: Frame - * frame.$(selector) - * frame.$$(selector) - * frame.$$eval(selector, pageFunction[, ...args]) - * frame.$eval(selector, pageFunction[, ...args]) - * frame.$x(expression) - * frame.addScriptTag(options) - * frame.addStyleTag(options) - * frame.childFrames() - * frame.click(selector[, options]) - * frame.content() - * frame.evaluate(pageFunction, ...args) - * frame.focus(selector) - * frame.hover(selector) - * frame.isDetached() - * frame.name() - * frame.parentFrame() - * frame.select(selector, ...values) - * frame.setContent(html) - * frame.title() - * frame.type(selector, text[, options]) - * frame.url() - * frame.waitFor(selectorOrFunctionOrTimeout[, options[, ...args]]) - * frame.waitForFunction(pageFunction[, options[, ...args]]) - * frame.waitForSelector(selector[, options]) - * frame.waitForXPath(xpath[, options]) -- class: JSHandle - * jsHandle.asElement() - * jsHandle.dispose() - * jsHandle.getProperties() - * jsHandle.getProperty(propertyName) - * jsHandle.jsonValue() - * jsHandle.toString() -- class: ElementHandle - * elementHandle.$(selector) - * elementHandle.$$(selector) - * elementHandle.$$eval(selector, pageFunction, ...args) - * elementHandle.$eval(selector, pageFunction, ...args) - * elementHandle.$x(expression) - * elementHandle.boundingBox() - * elementHandle.click([options]) - * elementHandle.dispose() - * elementHandle.focus() - * elementHandle.hover() - * elementHandle.isIntersectingViewport() - * elementHandle.press(key[, options]) - * elementHandle.screenshot([options]) - * elementHandle.type(text[, options]) -- class: Keyboard - * keyboard.down(key[, options]) - * keyboard.press(key[, options]) - * keyboard.sendCharacter(char) - * keyboard.type(text, options) - * keyboard.up(key) -- class: Mouse - * mouse.click(x, y, [options]) - * mouse.down([options]) - * mouse.move(x, y, [options]) - * mouse.up([options]) -- class: Dialog - * dialog.accept([promptText]) - * dialog.defaultValue() - * dialog.dismiss() - * dialog.message() - * dialog.type() -- class: ConsoleMessage - * consoleMessage.args() - * consoleMessage.text() - * consoleMessage.type() -- class: TimeoutError +### Credits Special thanks to [Amine Bouhlali](https://bitbucket.org/aminerop/) who volunteered the [`puppeteer-firefox`](https://www.npmjs.com/package/puppeteer-firefox) NPM package.