Skip to content

Commit

Permalink
Browser: Disable the network support by default and add a UI control …
Browse files Browse the repository at this point in the history
…to enable it (#812)

Disables the network support by default and adds a UI control to
enable it.

Why?

#724 introduced
support for wp_safe_remote_get() request and enabled it by default on
playground.wordpress.net. The problem is, all the requests block
rendering of WordPress pages and noticeably slow down the site. Let's
disable it by default for a lightweight user experience, and then add an
easy way to enable it, for example in the configuration modal.

Closes #755

## Testing Instructions

* Go to http://localhost:5400/website-server/?url=/wp-admin/plugin-install.php
* Confirm the plugins aren't loaded and you see a communicative error message
* Go to the settings modal
* Enable the network support
* Confirm the plugins are loaded now

<img width="1121" alt="CleanShot 2023-11-27 at 17 10 45@2x"
src="https://github.com/WordPress/wordpress-playground/assets/205419/ea95b783-d7a1-45c6-ab95-90b5c8ec6ce4">
  • Loading branch information
adamziel authored Nov 27, 2023
1 parent 72d74fe commit 0bdb332
Show file tree
Hide file tree
Showing 13 changed files with 175 additions and 86 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,14 @@ jobs:
- uses: actions/checkout@v3
- uses: ./.github/actions/prepare-playground
- run: ./node_modules/.bin/cypress install --force
- run: npx nx affected --target=e2e --configuration=ci
- run: npx nx affected --target=e2e --configuration=ci --verbose
build:
runs-on: ubuntu-latest
needs: [lint-and-typecheck]
steps:
- uses: actions/checkout@v3
- uses: ./.github/actions/prepare-playground
- run: npx nx affected --target=build --parallel=3
- run: npx nx affected --target=build --parallel=3 --verbose

# Deploy documentation job
deploy_docs:
Expand Down
2 changes: 1 addition & 1 deletion packages/docs/site/docs/08-query-api/01-index.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ You can go ahead and try it out. The Playground will automatically install the t
| `wp` | `latest` | Loads the specified WordPress version. Supported values: `5.9`, `6.0`, `6.1`, `6.2`, `6.3`, `latest`, `nightly`, `beta` |
| `blueprint-url` | | The URL of the Blueprint that will be used to configure this Playground instance. |
| `php-extension-bundle` | | Loads a bundle of PHP extensions. Supported bundles: `kitchen-sink` (for gd, mbstring, iconv, libxml, xml, dom, simplexml, xmlreader, xmlwriter) |
| `networking` | `yes` or `no` | Enables or disables the networking support for Playground. Defaults to `yes` |
| `networking` | `yes` or `no` | Enables or disables the networking support for Playground. Defaults to `no` |
| `plugin` | | Installs the specified plugin. Use the plugin name from the plugins directory URL, e.g. for a URL like `https://wordpress.org/plugins/wp-lazy-loading/`, the plugin name would be `wp-lazy-loading`. You can pre-install multiple plugins by saying `plugin=coblocks&plugin=wp-lazy-loading&…`. Installing a plugin automatically logs the user in as an admin |
| `theme` | | Installs the specified theme. Use the theme name from the themes directory URL, e.g. for a URL like `https://wordpress.org/themes/disco/`, the theme name would be `disco`. Installing a theme automatically logs the user in as an admin |
| `url` | `/wp-admin/` | Load the specified initial page displaying WordPress |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,4 +63,4 @@ The `phpExtensionBundles` property is an array of PHP extension bundles to load.

The `features` property is used to enable or disable certain features of the Playground. It can contain the following properties:

- `networking`: Defaults to `true`. Enables or disables the networking support for Playground. If enabled, `wp_safe_remote_get` and similar WordPress functions will actually use `fetch()` to make HTTP requests. If disabled, they will immediately fail instead.
- `networking`: Defaults to `false`. Enables or disables the networking support for Playground. If enabled, `wp_safe_remote_get` and similar WordPress functions will actually use `fetch()` to make HTTP requests. If disabled, they will immediately fail instead.
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,4 @@ When it comes to networking, WebAssembly programs are limited to calling JavaScr

In Node.js, the answer involves a WebSocket to TCP socket proxy, [Asyncify](https://emscripten.org/docs/porting/asyncify.html), and patching deep PHP internals like php_select. It's complex, but there's a reward. The Node.js-targeted PHP build can request web APIs, install composer packages, and even connect to a MySQL server.

In the browser, networking is not supported yet. Initiating a HTTPS connection involves opening a raw TCP socket which is not possible in the browser. There is an [open GitHub issue](https://github.com/WordPress/wordpress-playground/issues/85) that explores possible ways of addressing this problem.
In the browser, networking is supported to a limited extent. Network calls initiated using `wp_safe_remote_get`, like the ones in the plugin directory or the font library, are translated into `fetch()` calls and succeed if the remote server sends the correct CORS headers. However, a full support for arbitrary HTTPS connection involves opening a raw TCP socket which is not possible in the browser. There is an [open GitHub issue](https://github.com/WordPress/wordpress-playground/issues/85) that explores possible ways of addressing this problem.
4 changes: 2 additions & 2 deletions packages/playground/blueprints/src/lib/compile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -167,8 +167,8 @@ export function compileBlueprint(
blueprint.phpExtensionBundles || []
),
features: {
// Enable networking by default
networking: blueprint.features?.networking ?? true,
// Disable networking by default
networking: blueprint.features?.networking ?? false,
},
run: async (playground: UniversalPHP) => {
try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
if($res instanceof WP_Error) {
$res = new WP_Error(
'plugins_api_failed',
'Playground <a href="https://github.com/WordPress/wordpress-playground/issues/85">does not yet support</a> connecting to the plugin directory yet. You can still upload plugins or install them using the <a href="https://wordpress.github.io/wordpress-playground/query-api">Query API</a> (e.g. ?plugin=coblocks).'
'Enable networking support in Playground settings to access the Plugins directory. Network access is an <a href="https://github.com/WordPress/wordpress-playground/issues/85">experimental, opt-in feature</a>. If you don\'t want to use it, you can still upload plugins or install them using the <a href="https://wordpress.github.io/wordpress-playground/query-api">Query API</a> (e.g. ?plugin=coblocks).'
);
}
return $res;
Expand All @@ -29,7 +29,7 @@
}

if($translation === 'An unexpected error occurred. Something may be wrong with WordPress.org or this server&#8217;s configuration. If you continue to have problems, please try the <a href="%s">support forums</a>.') {
return 'Playground <a href="https://github.com/WordPress/wordpress-playground/issues/85">does not yet support</a> connecting to the themes directory yet. You can still upload a theme or install it using the <a href="https://wordpress.github.io/wordpress-playground/query-api">Query API</a> (e.g. ?theme=pendant).';
return 'Enable networking support in Playground settings to access the Themes directory. Network access is an <a href="https://github.com/WordPress/wordpress-playground/issues/85">experimental, opt-in feature</a>. If you don\'t want to use it, you can still upload themes or install them using the <a href="https://wordpress.github.io/wordpress-playground/query-api">Query API</a> (e.g. ?theme=pendant).';
}
return $translation;
} );
191 changes: 122 additions & 69 deletions packages/playground/website/cypress/e2e/app.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ describe('Query API', () => {

describe('option `wp`', () => {
it('should load WordPress latest by default', () => {
cy.visit('/?networking=no&url=/wp-admin/');
cy.visit('/?url=/wp-admin/');
const expectedBodyClass =
'branch-' + LatestSupportedWordPressVersion.replace('.', '-');
cy.wordPressDocument()
Expand All @@ -42,16 +42,14 @@ describe('Query API', () => {
});

it('should load WordPress 6.3 when requested', () => {
cy.visit('/?networking=no&wp=6.3&url=/wp-admin');
cy.visit('/?wp=6.3&url=/wp-admin');
cy.wordPressDocument().find(`body.branch-6-3`).should('exist');
});
});

describe('option `php-extension-bundle`', () => {
it('should load the specified PHP extensions', () => {
cy.visit(
'/?networking=no&php-extension-bundle=kitchen-sink&url=/phpinfo.php'
);
cy.visit('/?php-extension-bundle=kitchen-sink&url=/phpinfo.php');
cy.wordPressDocument()
.its('body')
.should('contain', '--enable-xmlwriter');
Expand All @@ -60,13 +58,10 @@ describe('Query API', () => {

describe('option `networking`', () => {
it('should disable networking when requested', () => {
cy.visit('/?networking=no&url=/wp-admin/plugin-install.php');
cy.visit('/?url=/wp-admin/plugin-install.php');
cy.wordPressDocument()
.find('.notice.error')
.should(
'contain',
'does not yet support connecting to the plugin directory'
);
.should('contain', 'Enable networking support in Playground');
});

it('should enable networking when requested', () => {
Expand Down Expand Up @@ -97,7 +92,7 @@ describe('Query API', () => {

describe('option `url`', () => {
it('should load the specified URL', () => {
cy.visit('/?url=/wp-admin/&networking=no');
cy.visit('/?url=/wp-admin/');
cy.wordpressPath().should('contain', '/wp-admin/');
cy.wordPressDocument()
.find('#adminmenu')
Expand All @@ -107,11 +102,11 @@ describe('Query API', () => {

describe('option `mode`', () => {
it('lack of mode=seamless should a WordPress in a simulated browser UI', () => {
cy.visit('/?networking=no');
cy.visit('/');
cy.get('[data-cy="simulated-browser"]').should('exist');
});
it('mode=seamless should load a fullscreen WordPress', () => {
cy.visit('/?networking=no&mode=seamless');
cy.visit('/?mode=seamless');
cy.get('[data-cy="simulated-browser"]').should('not.exist');
});
});
Expand Down Expand Up @@ -187,91 +182,149 @@ describe('Query API', () => {
});

describe('playground-website', () => {
beforeEach(() => cy.visit('/?networking=no'));
beforeEach(() => cy.visit('/'));

it('should reflect the URL update from the navigation bar in the WordPress site', () => {
cy.setWordPressUrl('/wp-admin');
cy.wordpressPath().should('contain', '/wp-admin');
});

// Test all PHP versions for completeness
SupportedPHPVersions.forEach((version) => {
it('should switch PHP version to ' + version, () => {
describe('PHP version switcher', () => {
SupportedPHPVersions.forEach((version) => {
it('should switch PHP version to ' + version, () => {
// Update settings in Playground configurator
cy.get('button#configurator').click();
cy.get('select#php-version').select(version);
cy.get('#modal-content button[type=submit]').click();
// Wait for the page to finish reloading
cy.url().should('contain', `php=${version}`);
cy.document().should('exist');

// Go to phpinfo
cy.setWordPressUrl('/phpinfo.php');
cy.wordPressDocument()
.find('h1')
.should('contain', 'PHP Version ' + version);
});
});
});

// Only test the latest PHP version to save time
describe('PHP extensions bundle', () => {
it('should load additional PHP extensions when requested', () => {
// Update settings in Playground configurator
cy.get('button#configurator').click();
cy.get('select#php-version').select(version);
cy.get('select#php-version').select(LatestSupportedPHPVersion);
cy.get('input[name=with-extensions]').check();
cy.get('#modal-content button[type=submit]').click();
// Wait for the page to finish reloading
cy.url().should('contain', `&php=${version}`);
// Wait for the page to finish loading
cy.document().should('exist');

// Go to phpinfo
cy.setWordPressUrl('/phpinfo.php');
cy.wordPressDocument()
.find('h1')
.should('contain', 'PHP Version ' + version);
.its('body')
.should('contain', '--enable-xmlwriter');
});

it('should not load additional PHP extensions when not requested', () => {
// Update settings in Playground configurator
cy.get('button#configurator').click();
cy.get('select#php-version').select(LatestSupportedPHPVersion);
cy.get('#modal-content button[type=submit]').click();
// Wait for the page to finish loading
cy.document().should('exist');

// Go to phpinfo
cy.setWordPressUrl('/phpinfo.php');
cy.wordPressDocument()
.its('body')
.should('contain', '--without-libxml');
});
});

// Only test the latest PHP version to save time
it('should load additional PHP extensions when requested', () => {
// Update settings in Playground configurator
// Test all WordPress versions for completeness
describe('WordPress version selector', () => {
for (const version in SupportedWordPressVersions) {
if (version === 'beta') {
continue;
}
// @ts-ignore
let versionMessage = 'Version ' + version;
if (version === 'nightly') {
versionMessage = 'You are using a development version';
}

it('should switch WordPress version to ' + version, () => {
// Update settings in Playground configurator
cy.get('button#configurator').click();
cy.get(`select#wp-version option[value="${version}"]`).should(
'exist'
);
cy.get('select#wp-version').select(`${version}`);
cy.get('#modal-content button[type=submit]').click();
// Wait for the page to finish loading
cy.url().should('contain', `&wp=${version}`);

// Go to phpinfo
cy.setWordPressUrl('/wp-admin');
cy.wordPressDocument()
.find('#footer-upgrade')
.should('contain', versionMessage);
});
}
});
});

/**
* These tests only check if the modal UI updates the URL correctly.
* The actual networking functionality is tested in the Query API tests.
*/
describe('Website UI – Networking support', () => {
it('should display an unchecked networking checkbox by default', () => {
cy.visit('/');

cy.get('button#configurator').click();
cy.get('select#php-version').select(LatestSupportedPHPVersion);
cy.get('input[name=with-extensions]').check();
cy.get('#modal-content button[type=submit]').click();
// Wait for the page to finish loading
cy.document().should('exist');
cy.get('input[name=with-networking]').should('not.be.checked');
});

// Go to phpinfo
cy.setWordPressUrl('/phpinfo.php');
cy.wordPressDocument()
.its('body')
.should('contain', '--enable-xmlwriter');
it('should display a checked networking checkbox when networking is enabled', () => {
cy.visit('/?networking=yes');

cy.get('button#configurator').click();
cy.get('input[name=with-networking]').should('be.checked');
});

it('should not load additional PHP extensions when not requested', () => {
it('should enable networking when requested', () => {
cy.visit('/');

// Update settings in Playground configurator
cy.get('button#configurator').click();
cy.get('select#php-version').select(LatestSupportedPHPVersion);
cy.get('input[name=with-networking]').check();
cy.get('#modal-content button[type=submit]').click();
// Wait for the page to finish loading

// Wait for the page to reload
cy.document().should('exist');
cy.get('#modal-content button[type=submit]').should('not.exist');

// Go to phpinfo
cy.setWordPressUrl('/phpinfo.php');
cy.wordPressDocument()
.its('body')
.should('contain', '--without-libxml');
// Confirm the URL was updated correctly
cy.relativeUrl().should('contain', 'networking=yes');
});

// Test all WordPress versions for completeness
for (const version in SupportedWordPressVersions) {
if (version === 'beta') {
continue;
}
// @ts-ignore
let versionMessage = 'Version ' + version;
if (version === 'nightly') {
versionMessage = 'You are using a development version';
}
it('should disable networking when requested', () => {
cy.visit('/?networking=yes');

it('should switch WordPress version to ' + version, () => {
// Update settings in Playground configurator
cy.get('button#configurator').click();
cy.get(`select#wp-version option[value="${version}"]`).should(
'exist'
);
cy.get('select#wp-version').select(`${version}`);
cy.get('#modal-content button[type=submit]').click();
// Wait for the page to finish loading
cy.url().should('contain', `&wp=${version}`);
// Update settings in Playground configurator
cy.get('button#configurator').click();
cy.get('input[name=with-networking]').uncheck();
cy.get('#modal-content button[type=submit]').click();

// Go to phpinfo
cy.setWordPressUrl('/wp-admin');
cy.wordPressDocument()
.find('#footer-upgrade')
.should('contain', versionMessage);
});
}
// Wait for the page to reload
cy.document().should('exist');
cy.get('#modal-content button[type=submit]').should('not.exist');

// Confirm the URL was updated correctly
cy.relativeUrl().should('not.contain', 'networking=yes');
});
});
10 changes: 10 additions & 0 deletions packages/playground/website/cypress/support/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ declare namespace Cypress {
setWordPressUrl(url: string): void;
wordPressDocument(): Chainable<Element>;
wordpressPath(): Chainable<string>;
relativeUrl(): Chainable<string>;
}
}

Expand Down Expand Up @@ -45,3 +46,12 @@ Cypress.Commands.add('wordpressPath', () => {
.find('#wp')
.its('0.contentWindow.location.pathname');
});

Cypress.Commands.add('relativeUrl', () => {
// relative part of the current top-level URL
return cy.url().then((href) => {
const url = new URL(href);
console.log({ href });
return href.substring(url.origin.length);
});
});
3 changes: 2 additions & 1 deletion packages/playground/website/project.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@
"mode": "development"
},
"production": {
"mode": "production"
"mode": "production",
"logLevel": "info"
}
},
"dependsOn": ["^build"]
Expand Down
Loading

0 comments on commit 0bdb332

Please sign in to comment.