Git Product home page Git Product logo

wordpress / wordpress-playground Goto Github PK

View Code? Open in Web Editor NEW
1.5K 37.0 196.0 2.83 GB

Run WordPress in the browser via WebAssembly PHP

Home Page: https://w.org/playground/

License: GNU General Public License v2.0

HTML 0.53% JavaScript 55.74% CSS 34.55% Dockerfile 0.26% C 2.57% PHP 0.26% CMake 0.03% Makefile 0.06% Roff 0.02% TypeScript 3.67% Shell 0.01% SCSS 2.29% MDX 0.01% M4 0.02%
emscripten wasm webassembly wordpress

wordpress-playground's Introduction

WordPress Playground and PHP WASM (WebAssembly)

Project Page | Live demo | Documentation and API Reference

WordPress Playground is an experimental in-browser WordPress that runs without a PHP server thanks to the magic of WebAssembly.

Why is WordPress Playground useful?

WordPress Playground exists to make WordPress instantly accessible for users, learners, extenders, and contributors by building foundational software tools developers can use to create interactive, zero-setup, JavaScript applications with WordPress.

Playground aims to facilitate:

– Learning WordPress Through Exploration – Learning WordPress Development Through Writing Code – Instant access to WordPress ecosystem

Learn more about the vision and the roadmap.

Getting started

WordPress Playground has a live demo available.

You can embed WordPress Playground in your project via an <iframe> – find out how in the documentation. Note the embed is experimental and may break or change without a warning.

You can connect to the Playground using the JavaScript client. Here's an example of how to do it in the browser using an iframe HTML element and the startPlaygroundWeb function from the @wp-playground/client package.

index.html:

<!DOCTYPE html>
<iframe id="wp-playground" style="width: 1200px; height: 800px"></iframe>
<script type="module">
	import { startPlaygroundWeb } from 'https://playground.wordpress.net/client/index.js';

	const client = await startPlaygroundWeb({
		iframe: document.getElementById('wp-playground'),
		remoteUrl: `https://playground.wordpress.net/remote.html`,
		blueprint: {
			landingPage: '/wp-admin/',
			preferredVersions: {
				php: '8.0',
				wp: 'latest',
			},
			steps: [
				{
					step: 'login',
					username: 'admin',
					password: 'password',
				},
				{
					step: 'installPlugin',
					pluginZipFile: {
						resource: 'wordpress.org/plugins',
						slug: 'friends',
					},
				},
			],
		},
	});

	const response = await client.run({
		// wp-load.php is only required if you want to interact with WordPress.
		code: '<?php require_once "/wordpress/wp-load.php"; $posts = get_posts(); echo "Post Title: " . $posts[0]->post_title;',
	});
	console.log(response.text);
</script>

WordPress Playground Tools

WordPress Playground Tools are independent applications built on top of WordPress Playground. They are located in a different repository: WordPress/playground-tools.

These tools include:

Cloning WordPress Playground repo

The vanilla git clone command will take ages. Here's a faster alternative that will only pull the latest revision of the trunk branch:

git clone -b trunk --single-branch --depth 1 [email protected]:WordPress/wordpress-playground.git

Running WordPress Playground locally

You also can run WordPress Playground locally as follows:

git clone -b trunk --single-branch --depth 1 [email protected]:WordPress/wordpress-playground.git
cd wordpress-playground
npm install
npm run dev

A browser should open and take you to your very own client-side WordPress at http://127.0.0.1:5400/!

Any changes you make to .ts files will be live-reloaded. Changes to Dockerfile require a full rebuild.

From here, the documentation will help you learn how WordPress Playground works and how to use it to build amazing things!

And here's a few more interesting CLI commands, which expect that you have nx installed globally:

# Build and run PHP.wasm CLI
nx start php-wasm-cli

# Build latest WordPress releases
nx bundle-wordpress:all playground-wordpress

# Recompile PHP 5.6 - 8.2 releases to .wasm for web
nx recompile-php:all php-wasm-web

# Recompile PHP 5.6 - 8.2 releases to .wasm for node
nx recompile-php:all php-wasm-node

# Builds the documentation site
nx build docs-site

# Builds the Playground Client npm package
nx build playground-client

# Bonus: Run PHP.wasm in your local CLI:
npx @php-wasm/cli -v
PHP=7.4 npx @php-wasm/cli -v
npx @php-wasm/cli phpcbf

How can I contribute?

WordPress Playground is an open-source project and welcomes all contributors from code to design, and from documentation to triage. If the feature you need is missing, you are more than welcome to start a discussion, open an issue, and even propose a Pull Request to implement it.

Here's a few quickstart guides to get you started:

WordCamp Contributor Day

If you're participating in a WordCamp Contributor Day, you can use the following instructions to get started: WordCamp Contributor Day.

Backwards compatibility

This experimental software may break or change without a warning. Releasing a stable API is an important future milestone that will be reached once the codebase is mature enough.

Prior art

WordPress Playground forked the original PHP to WebAssembly build published in https://github.com/oraoto/pib and modified later in https://github.com/seanmorris/php-wasm.

Another strong inspiration was the Drupal in the browser demo which proved you can run non-trivial PHP software in the browser.

A worthy mention is Wasm Labs’s closed-source WordPress in the browser shared before this project was first published. There is no public repository available, but their technical overview gives a breakdown of the technical decisions that project took. WordPress Playground draws inspiration from the same PHP in the browser projects and makes similar technical choices.

Governance

WordPress Playground is a WordPress.org project started and led by Adam Zielinski.

Playground tools like wp-now or the interactive code block are maintained by their authors in the playground-tools monorepo.

wordpress-playground's People

Contributors

adamziel avatar ajitbohra avatar akirk avatar artemiomorales avatar bgrgicak avatar bph avatar brandonpayton avatar danielbachhuber avatar dd32 avatar desrosj avatar dmsnell avatar eliot-akira avatar flexseth avatar gziolo avatar ironnysh avatar janw-me avatar jdevalk avatar katinthehatsite avatar kirjavascript avatar kozer avatar lemanschik avatar marcarmengou avatar mho22 avatar palmiak avatar seanmorris avatar sejas avatar soean avatar stevendufresne avatar t-hamano avatar wojtekn avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

wordpress-playground's Issues

Reflect the current URL in the browser navigation bar

At the moment, the top URL bar only says index.html, and all the navigation happens inside of an iframe. It would be great to have a feature that would update the top-level navigation, perhaps via pushState. It needs to be optional because updating top-level navigation doesn't make sense in contexts like runnable code examples.

Add support for zip files

The plugins and themes downloaded from the directory are all compressed as .zip files. However, the current PHP build has no zip support. Let's add it to enable easy plugin and themes installation.

NGINX configuration equivalent to .htaccess

Would be nice to document the config syntax for using NGINX.

Here's dist-web/.htaccess for Apache.

AddType application/wasm .wasm

RewriteEngine on
RewriteRule ^scope:.*?/(.*)$ $1 [NC]
RewriteRule ^plugin-proxy$ plugin-proxy.php [NC]

<FilesMatch "iframe-worker.html$">
  Header set Origin-Agent-Cluster: ?1
</FilesMatch>

Rough draft for NGINX config (untested).

types {
  application/wasm wasm;
}

location /scope:.*?/(.*) {
 rewrite ^scope:.*?/(.*)$ $1 last;
}

location /plugin-proxy {
 rewrite ^plugin-proxy$ plugin-proxy.php last;
}

location /iframe-worker.html {
  add_header Origin-Agent-Cluster ?1;
}

Reference

Remove raw WordPress files from this repository

There is a few different copies of WordPress shipped in this repo. Let's remove the physical copies and download these files as a part of the build/bundle pipeline.

In particular:

  • src/node/wordpress – this can be generated when missing during the initial npm run dev:node
  • dist-node/wordpress – this can be bundled during the node build process
  • Optionally: dist/wordpress-* – these are WordPress static files required to use the browser version via npm run dev. There should be a universal way to regenerate them on npm run dev:web.

However, let's leave dist/wp.data and dist/wp.js in place. Regenerating these files requires a docker setup and could be a significant speed bump for new developers trying to just clone and run this repo.

Can't build locally without Docker

It would be convenient to be able to build this project without needing to run the Dockerfile. It's an obstacle to development to have to run inside a proprietary system and to not be able to directly interact with the sources, assets, and builds.

E.g. a Makefile for the build which is then run inside Docker would be usable from the outside and inside of the container. Dockerfile is convenient for normalizing builds and enumerating the dependencies, but it's even more helpful to be able to look at that list of build dependencies, make sure they're available on my local system, and then directly build the project without Docker. It's also likely significantly more efficient to do that.

Consider disabling script/style concatenation

Occasionally in Safari I get a unstyled admin page, seemingly because the wp-admin/load-styles.php call failed or timed out.

If static files are simpler for it to serve, it would be beneficial to remove the concatenation, as it serves no purpose other than to reduce network round time trips and most admin pages will have a different set of styles/scripts causing the browser caches to be ignored.

This can be done by using the following, which disables concatenation and gzip'ing of static resources.

define( 'CONCATENATE_SCRIPTS', false );

Custom WordPress build with plugins

I'd like to create a custom build of wp.data with pre-installed plugins. I got pretty close to achieving it, but for some reason the admin screen shows no plugins. Here's what I did:

  • Edit wasm-build/wordpress-data/prepare-wordpress.sh to download, unzip, and move a plugin folder into ./wordpress/wp-content/plugins/
  • Run npm run build:wp && npm run publish:wp:web

I confirmed that the plugin's static assets were copied to dist-web/wp-content/plugins. I looked in the built file dist-web/wp.js, and the plugin's file paths are included from /preload/wordpress/wp-content/plugins - so I assume the files were successfullly included in wp.data. But somehow the WordPress instance in the browser is not recognizing it.

Any idea or hint would be greatly appreciated.

Run on Cloudflare Workers

This might be far out there, but here goes...

Since WordPress is working with WebAssembly and SQLite based on this project, how about expanding the scope from testing to production with Cloudflare Workers + CloudFlare D1 (SQLite database)?

Related issues

[Browser] Add cache busting during the build

Make each build generate a unique version string, and then use it to load resources, e.g. /iframe-worker.html?version=78a4, /php-web.wasm?version=78a4 etc. Otherwise the older assets tends to interfere with the newer builds.

Host site URL without `index.html` is intercepted by service worker

When deploying a demo site, I noticed it works fine when I visit the static file specifically, like example.com/index.html, but it has some unpredictable behavior with the root domain only, like example.com. Sometimes it takes a long time to load, or doesn't load at all.

I tracked down the cause (I think) to the service worker intercepting the visit to the host site and serving the WordPress site via the web worker, outside/without the iframe.

I'm still wrapping my head around how all of this works, but I figured I'd make a note of it. Will report back if I'm able to solve it.

Replace fswatch in npm script with cross-platform solution

The npm script dev:web:html currently depends on fswatch and xargs to watch files and copy to dist-web.

I ran sudo apt install fswatch, but encountered another snag.

xargs: warning: options --max-args and --replace/-I/-i are mutually exclusive, ignoring previous --max-args value

When I make a change to src/web/index.html, it goes into an infinite loop.

image


Rather than try to figure out this issue, I think a better solution is to replace the script with a cross-platform solution that doesn't depend on fswatch being installed in the environment.

I looked around on npm and chokidar-cli seems suitable.

The new script would look like:

{
  "scripts": {
    "dev:web:html": "chokidar \"./src/web/*.html\" -c \"cp src/web/*.html dist-web/\""
  },
  "devDependencies": {
    "chokidar-cli": "^3.0.0"
  }
}

I confirmed that it works as expected. If the above change looks OK, I'd be happy to turn it into a pull request.

Add wp-admin login details

I navigated to /wp-admin in the demo, and I just guessed the username and password (admin / password), but it would be great if this was noted below the site frame so people would know upfront. Could even add the login details to the login form.

Make the bundle smaller than 20M

The largest files to load for the initial paint are:

  • wp.data – 13M – the WordPress installation and all its php files
  • php-web.wasm – 6.2M – the WebAssembly PHP build
  • wp.js – 182k – the emscripten-generated JS loader for wp.data
  • php-web.js – 153k – the emscripten-generated JS loader for php-web.wasm
  • php-webworker.js – 153k – same as above, but for the webworker backend

Let's shrink them as much as possible.

Here's a few ideas:

  • Minify the variable names in the PHP files
  • Minify the JSON files
  • Remove non-english translations
  • Identify PHP dependencies to remove, e.g. libxml2 doesn't seem necessary
    • libxml2 was removed from the default build in db0a477
  • Compress the .data and .wasm files using brotli compression – props to @eliot-akira. Edit: Web servers take care of it and most browsers support the brotli compression!
  • #39 – done in #43 – reduced wp.data from 43M to 13M!
  • Minify the PHP files
  • Remove themes other than twentytwentytwo
  • Remove unminified JS and CSS files
  • Remove the .eot, .gif, .htaccess, .md, .mp4, .png, .scss, .stylelintignore, .svg, .ttf, .txt, .woff, and .woff2 files

Setup monorepo to separate concerns

Currently the different parts of this project are vaguely separated. Let's use a stronger delineation to make the pieces reusable and make it easier for new contributors to get started.

A proposed list of packages:

  • php-wasm – low-level WASM PHP primitives
    • A configurable PHP build pipeline for different targets (web, node.js, standalone)
    • Pre-built binaries
    • A low-level PHP JavaScript class with eval for executing PHP code and FS utils like writeFile for runtime managing the
  • php-wasm-web – a high-level layer to efficiently run php-wasm the browser
    • A PHPServer JavaScript class for dispatching HTTP requests – both to run the PHP files AND to download static files
    • A ServiceWorker to redirect the browser traffic to PHPServer
    • A WasmWorker to offload the PHPServer to a separate process (With the existing three backends: Iframe, Webworker, SharedWorker)
    • A messaging layer to connect the above
    • A PHPBrowser JavaScript class to consume the above using an iframe
    • The required server setup to serve the correct headers for the wasm files. For starters this could be just .htaccess plus some documentation.
  • wordpress-wasm – WordPress-specific WASM PHP bindings for web and node.js
    • The required WordPress-specific setup like constants and filters
    • wp.data bundling pipeline for the web configurable to bundle custom code
    • WordPress API to ease common tasks like login, install a plugin, start a block editor with specific settings and content
    • A fetch-based transport for HTTP requests
    • A set of user-facing demos: Basic WordPress in a browser, a runeditable code snippet, an interactive tutorial

[Browser] Return a loading page from the service worker if the web worker isn't ready yet

What problem is this issue looking to solve?

The service worker passes requests to .php files to the web worker. However, the web worker may not be loaded yet or it could be busy at the moment.

How does this issue proposes to solve it?

Reply with HTTP 502 or HTTP 408 eventually somewhere here:

https://github.com/adamziel/wordpress-wasm/blob/9d04a113a97f53a1303fefba642f65f1b5a5a5f6/src/web/service-worker.js#L31-L52

Support preinstalling multiple plugins

Right now, adding a plugin zip bundle to the URL preinstalls that plugin, for example the URL below preinstalls coblocks and then redirect the user to the post-new.php page to play with the plugin:

https://wasm.wordpress.net/wordpress.html?plugin=coblocks.2.24.4.zip&url=/wp-admin/post-new.php

Let's also support an array of plugins, for example:

https://wasm.wordpress.net/wordpress.html?plugin=coblocks.2.24.4.zip&plugin=gutenberg.14.5.4.zip&url=/wp-admin/post-new.php

[Browser] Support file uploads

At the moment, uploading files e.g. in media library isn't supported by the PHP request handler. Here's one way to solve it:

  • Pass the uploaded data blob from the service worker to the WASM worker
  • Create a temporary file in the WASM worker
  • Setup the correct $_FILES entries in the WASM worker

WASM file crashes Google Chrome

What is this issue about?

The WASM PHP crashes in chrome. It does not crash in Firefox, Safari, and node.js.

See the minimal reproduction in bug-reproduction.zip. It consists of two HTML files: breaks_here.html and works_here.html. The first one demonstrates the problem in the worker and the second one shows that the issue does not occur in the main thread crashes too, although less frequently.

The issue is the most apparent inside of a webworker, but it also exists when WASM is initialized in the main browser thread. The code below is enough to trigger the crash. Note we don't even run any wasm code, just instantiate the module:

(() => {
  // src/web/web-worker.js
  console.log("[WebWorker] Spawned");
  var wasmTable = new WebAssembly.Table({
    initial: 1090,
    maximum: 1090,
    element: "anyfunc"
  });
  var WASM_PAGE_SIZE = 65536;
  var INITIAL_INITIAL_MEMORY = 1073741824;
  var wasmMemory = new WebAssembly.Memory({
    initial: INITIAL_INITIAL_MEMORY / WASM_PAGE_SIZE
  });
  var info = {
    env: {
      _zend_empty_array2: 1,
      tempDoublePtr: 2303696,
      "__memory_base": 1024,
      __table_base: 0,
      memory: wasmMemory,
      table: wasmTable
    },
    global: { NaN: NaN, Infinity: Infinity },
    asm2wasm: {
      "f64-rem"() {
      }
    }
  };
  fetch("updated.wasm").then(async (response) => {
    WebAssembly.instantiate(
      await response.arrayBuffer(),
      info
    ).then(() => {
      console.log("Instantiated!");
    });
    console.log("Called instantiate");
  });
  console.log("Called fetch", { info });
})();

Chromium debugging findings

The Chromium team shared the following stack trace proving this is an out of memory problem:

Magic Signature >> [Out of Memory] v8::internal::Zone::NewExpand

Stack Trace >>
Thread 26 ThreadPoolForegroundWorker (id: 0x005aad74)crashedMAGIC SIGNATURE THREADcontent_copy
0x00000001211dbf58(Google Chrome Framework -oom.cc:58)partition_alloc::internal::OnNoMemoryInternal(unsigned long)
0x00000001211dbf68(Google Chrome Framework -oom.cc:65)partition_alloc::TerminateBecauseOutOfMemory(unsigned long)
0x00000001211dbf85(Google Chrome Framework -oom.cc:75)partition_alloc::internal::OnNoMemory(unsigned long)
0x00000001246e95b2(Google Chrome Framework -partitions.cc:323)WTF::PartitionsOutOfMemoryUsing512M(unsigned long)
0x00000001246e948c(Google Chrome Framework -partitions.cc:448)WTF::Partitions::HandleOutOfMemory(unsigned long)
0x00000001211dd8b3(Google Chrome Framework -partition_root.cc:619)partition_alloc::PartitionRoot<true>::OutOfMemory(unsigned long)
0x00000001211dca8a(Google Chrome Framework -partition_bucket.cc:48)void partition_alloc::internal::(anonymous namespace)::PartitionOutOfMemoryMappingFailure<true>(partition_alloc::PartitionRoot<true>*, unsigned long)
0x000000011dfa61de(Google Chrome Framework -partition_bucket.cc:691)partition_alloc::internal::PartitionBucket<true>::SlowPathAlloc(partition_alloc::PartitionRoot<true>*, unsigned int, unsigned long, unsigned long, bool*)
0x000000011dfae2cc(Google Chrome Framework -partition_root.h:1072)base::AllocNonQuarantinable(unsigned long)
0x000000011df36397(Google Chrome Framework -allocation.cc:141)v8::internal::Zone::NewExpand(unsigned long)
0x0000000120a961d4(Google Chrome Framework + 0x0000000002b851d4)std::Cr::vector<v8::internal::compiler::Node*, v8::internal::ZoneAllocator<v8::internal::compiler::Node*>>::vector(std::Cr::vector<v8::internal::compiler::Node*, v8::internal::ZoneAllocator<v8::internal::compiler::Node*>> const&)
0x0000000122d2a138(Google Chrome Framework + 0x0000000004e19138)v8::internal::wasm::(anonymous namespace)::WasmGraphBuildingInterface::Split(v8::internal::Zone*, v8::internal::wasm::(anonymous namespace)::SsaEnv*)
0x0000000122d2bfc5(Google Chrome Framework + 0x0000000004e1afc5)v8::internal::wasm::(anonymous namespace)::WasmGraphBuildingInterface::BrOrRet(v8::internal::wasm::WasmFullDecoder<(v8::internal::wasm::Decoder::ValidateFlag)2, v8::internal::wasm::(anonymous namespace)::WasmGraphBuildingInterface, (v8::internal::wasm::DecodingMode)0>*, unsigned int, unsigned int)
0x0000000122d1efc4(Google Chrome Framework + 0x0000000004e0dfc4)v8::internal::wasm::WasmFullDecoder<(v8::internal::wasm::Decoder::ValidateFlag)2, v8::internal::wasm::(anonymous namespace)::WasmGraphBuildingInterface, (v8::internal::wasm::DecodingMode)0>::DecodeBrTable(v8::internal::wasm::WasmFullDecoder<(v8::internal::wasm::Decoder::ValidateFlag)2, v8::internal::wasm::(anonymous namespace)::WasmGraphBuildingInterface, (v8::internal::wasm::DecodingMode)0>*, v8::internal::wasm::WasmOpcode)
0x0000000122d1b301(Google Chrome Framework + 0x0000000004e0a301)v8::internal::wasm::WasmFullDecoder<(v8::internal::wasm::Decoder::ValidateFlag)2, v8::internal::wasm::(anonymous namespace)::WasmGraphBuildingInterface, (v8::internal::wasm::DecodingMode)0>::Decode()
0x0000000122d1ab36(Google Chrome Framework + 0x0000000004e09b36)v8::internal::wasm::BuildTFGraph(v8::internal::AccountingAllocator*, v8::internal::wasm::WasmFeatures const&, v8::internal::wasm::WasmModule const*, v8::internal::compiler::WasmGraphBuilder*, v8::internal::wasm::WasmFeatures*, v8::internal::wasm::FunctionBody const&, std::Cr::vector<v8::internal::compiler::WasmLoopInfo, std::Cr::allocator<v8::internal::compiler::WasmLoopInfo>>*, v8::internal::compiler::NodeOriginTable*, int, v8::internal::wasm::InlinedStatus)
0x0000000122ee6b35(Google Chrome Framework + 0x0000000004fd5b35)v8::internal::compiler::ExecuteTurbofanWasmCompilation(v8::internal::wasm::CompilationEnv*, v8::internal::wasm::WireBytesStorage const*, v8::internal::wasm::FunctionBody const&, int, v8::internal::Counters*, v8::internal::wasm::AssemblerBufferCache*, v8::internal::wasm::WasmFeatures*)
0x000000012082f8b6(Google Chrome Framework + 0x000000000291e8b6)v8::internal::wasm::WasmCompilationUnit::ExecuteCompilation(v8::internal::wasm::CompilationEnv*, v8::internal::wasm::WireBytesStorage const*, v8::internal::Counters*, v8::internal::wasm::AssemblerBufferCache*, v8::internal::wasm::WasmFeatures*)
0x00000001207fc41d(Google Chrome Framework + 0x00000000028eb41d)v8::internal::wasm::(anonymous namespace)::ExecuteCompilationUnits(std::Cr::weak_ptr<v8::internal::wasm::NativeModule>, v8::internal::Counters*, v8::JobDelegate*, v8::internal::wasm::(anonymous namespace)::CompileBaselineOnly)
0x0000000120a96633(Google Chrome Framework + 0x0000000002b85633)v8::internal::wasm::(anonymous namespace)::BackgroundCompileJob::Run(v8::JobDelegate*) (.886d8138751ea58144f90ddffe92ca79)
0x0000000125c6e110(Google Chrome Framework -v8_platform.cc:458)base::internal::Invoker<base::internal::BindState<gin::V8Platform::CreateJob(v8::TaskPriority, std::Cr::unique_ptr<v8::JobTask, std::Cr::default_delete<v8::JobTask>>)::$_0, std::Cr::unique_ptr<v8::JobTask, std::Cr::default_delete<v8::JobTask>>>, void (base::JobDelegate*)>::Run(base::internal::BindStateBase*, base::JobDelegate*)
0x000000012549ec24(Google Chrome Framework -callback.h:263)base::internal::Invoker<base::internal::BindState<base::internal::JobTaskSource::JobTaskSource(base::Location const&, base::TaskTraits const&, base::RepeatingCallback<void (base::JobDelegate*)>, base::RepeatingCallback<unsigned long (unsigned long)>, base::internal::PooledTaskRunnerDelegate*)::$_0, base::internal::UnretainedWrapper<base::internal::JobTaskSource>>, void ()>::Run(base::internal::BindStateBase*)
0x000000011e3e3a06(Google Chrome Framework -callback.h:145)base::internal::TaskTracker::RunSkipOnShutdown(base::internal::Task&, base::TaskTraits const&, base::internal::TaskSource*, base::SequenceToken const&)
0x000000011e58a828(Google Chrome Framework -task_tracker.cc:724)base::internal::TaskTracker::RunAndPopNextTask(base::internal::RegisteredTaskSource)
0x000000011e6e8d9d(Google Chrome Framework -worker_thread.cc:448)base::internal::WorkerThread::RunWorker()
0x00000001238739dc(Google Chrome Framework -worker_thread.cc:335)base::internal::WorkerThread::RunPooledWorker()
0x000000011f091c56(Google Chrome Framework -worker_thread.cc:315)base::internal::WorkerThread::ThreadMain()
0x000000011ee21522(Google Chrome Framework -platform_thread_posix.cc:101)base::(anonymous namespace)::ThreadFunc(void*)
0x00007ff81a49b4e0(libsystem_pthread.dylib + 0x000064e0)
0x00007ff81a496f6a(libsystem_pthread.dylib + 0x00001f6a)

Other Chromium findings

I did some debugging before they shared that stack trace. The list below is less relevant than the specific details in the stack trace above, but I'm still posting it here for posterity:

  • The easiest way to trigger the crash is to open devtools and keep triggering "clear cache and refresh," but it also does crash without devtools – it's just much harder to trigger it. These could be two different problems :(
  • The crash does happen when the service worker isn't being registered, which tells me it is not related to service workers at all.
  • It always crashes with error code 5 which means TERMINATION_STATUS_PROCESS_CRASHED.
  • I attached a debugger and did not get much more information than error code 5:

CleanShot 2022-09-29 at 21 01 16@2x

  • The debugger (lldb) attached to a healthy Chromium process. I didn't manage to inspect the crashing process right before the crash. I hoped valgrind would help, but it won't run on Mac.
  • Crashpad logs a minidump, but sadly does not forward it to Mac system crash browser like it does with about:crash and other crashes. This means you need to manually symbolize it to extract any useful information and I didn't get there yet. Older Chromium used breakpad where manual symbolization was needed. Modern Chromium uses crashpad which can be symbolized as follows:

minidump-stackwalk gets its symbols from google-breakpad symbol files. Symbol files are a plain-text format intended to unify the contents of various platform-specific debuginfo/unwinding formats like PE32 Unwinding Tables, Dwarf CFI, Macho Compact Unwinding Info, etc.

To generate those files from your build artifacts, use either Mozilla's dump_syms (recommended) or google-breakpad's dump_syms.

  • Chromium logs the same stacktrace after this crash. However, I wasn't able to attach a debugger on V8_Fatal as the execution wouldn't stop there. I will keep trying.
#
# Fatal error in ../../v8/src/debug/debug-interface.cc, line 352
# Debug check failed: !isolate->is_execution_terminating().
#
#
#
#FailureMessage Object: 0x700009b7bc60[33312:259:0929/223909.968925:VERBOSE1:node.cc(1175)] OnUpdatePreviousPeer port: E64B3F19A9C61113.8955D49085231446 changing to AA7F7063054BDC96.DF5C9FB08811A500, port: E2387E855C7DDE33.6435B60F182B2A50 => BFD53B2279161D17.D245A7E2302529BE
[33609:18179:0929/223909.969410:VERBOSE1:node.cc(1175)] OnUpdatePreviousPeer port: BFD53B2279161D17.D245A7E2302529BE changing to 6975933DD5F27952.C20103EDCDFD9098, port: E2387E855C7DDE33.6435B60F182B2A50 => E64B3F19A9C61113.8955D49085231446
[33614:259:0929/223909.971940:VERBOSE1:paint_controller.cc(709)] PaintController::FinishCycle() completed
0   libbase.dylib                       0x000000010d65f21c base::debug::CollectStackTrace(void**, unsigned long) + 44
1   libbase.dylib                       0x000000010d2f6978 base::debug::StackTrace::StackTrace(unsigned long) + 72
2   libbase.dylib                       0x000000010d2f69fd base::debug::StackTrace::StackTrace(unsigned long) + 29
3   libbase.dylib                       0x000000010d2f69d5 base::debug::StackTrace::StackTrace() + 37
4   libgin.dylib                        0x00000001a7b79d1b gin::(anonymous namespace)::PrintStackTrace() + 59
5   libv8_libbase.dylib                 0x0000000119cd80f1 V8_Fatal(char const*, int, char const*, ...) + 337
6   libv8_libbase.dylib                 0x0000000119cd78e5 v8::base::(anonymous namespace)::DefaultDcheckHandler(char const*, int, char const*) + 21
7   libv8.dylib                         0x00000001d4423933 v8::debug::SetBreakPointsActive(v8::Isolate*, bool) + 291
8   libv8.dylib                         0x00000001d5558df3 v8_inspector::V8DebuggerAgentImpl::disable() + 419
9   libv8.dylib                         0x00000001d5587bca v8_inspector::V8InspectorSessionImpl::~V8InspectorSessionImpl() + 346
10  libv8.dylib                         0x00000001d5587d9e v8_inspector::V8InspectorSessionImpl::~V8InspectorSessionImpl() + 14
11  libblink_core.dylib                 0x00000001df137d4c std::Cr::default_delete<v8_inspector::V8InspectorSession>::operator()[abi:v16000](v8_inspector::V8InspectorSession*) const + 44
12  libblink_core.dylib                 0x00000001df12792a std::Cr::unique_ptr<v8_inspector::V8InspectorSession, std::Cr::default_delete<v8_inspector::V8InspectorSession>>::reset[abi:v16000](v8_inspector::V8InspectorSession*) + 106
13  libblink_core.dylib                 0x00000001df126c08 blink::DevToolsSession::Detach() + 1288
[33312:259:0929/223909.985578:VERBOSE1:node.cc(1175)] OnUpdatePreviousPeer port: 49CA70FD9D9D7B94.3536F4AFEA56CA2C changing to AA7F7063054BDC96.DF5C9FB08811A500, port: 64987F6ACCED8335.8529510F6C84227 => 12FA36F038112775.2F840886748B53FC
14  libblink_core.dylib                 0x00000001df10b64f blink::DevToolsAgent::Dispose() + 527
15  libblink_core.dylib                 0x00000001df508b77 blink::WorkerInspectorController::Dispose() + 183
16  libblink_core.dylib                 0x00000001e08501ce blink::WorkerThread::PerformShutdownOnWorkerThread() + 526
17  libblink_core.dylib                 0x00000001e085726a void base::internal::FunctorTraits<void (blink::WorkerThread::*)(), void>::Invoke<void (blink::WorkerThread::*)(), blink::WorkerThread*>(void (blink::WorkerThread::*)(), blink::WorkerThread*&&) + 122
18  libblink_core.dylib                 0x00000001e08571e4 void base::internal::InvokeHelper<false, void>::MakeItSo<void (blink::WorkerThread::*)(), blink::WorkerThread*>(void (blink::WorkerThread::*&&)(), blink::WorkerThread*&&) + 52
19  libblink_core.dylib                 0x00000001e0857188 void base::internal::Invoker<base::internal::BindState<void (blink::WorkerThread::*)(), WTF::CrossThreadUnretainedWrapper<blink::WorkerThread>>, void ()>::RunImpl<void (blink::WorkerThread::*)(), std::Cr::tuple<WTF::CrossThreadUnretainedWrapper<blink::WorkerThread>>, 0ul>(void (blink::WorkerThread::*&&)(), std::Cr::tuple<WTF::CrossThreadUnretainedWrapper<blink::WorkerThread>>&&, std::Cr::integer_sequence<unsigned long, 0ul>) + 72
20  libblink_core.dylib                 0x00000001e08570e7 base::internal::Invoker<base::internal::BindState<void (blink::WorkerThread::*)(), WTF::CrossThreadUnretainedWrapper<blink::WorkerThread>>, void ()>::RunOnce(base::internal::BindStateBase*) + 55
21  libbase.dylib                       0x000000010d2a2887 base::OnceCallback<void ()>::Run() && + 103
22  libbase.dylib                       0x000000010d504492 base::TaskAnnotator::RunTaskImpl(base::PendingTask&) + 418
23  libbase.dylib                       0x000000010d56feae _ZN4base13TaskAnnotator7RunTaskIJZNS_16sequence_manager8internal35ThreadControllerWithMessagePumpImpl10DoWorkImplEPNS_7LazyNowEE3$_0EEEvN8perfetto12StaticStringERNS_11PendingTaskEDpOT_ + 126
24  libbase.dylib                       0x000000010d56f9fa base::sequence_manager::internal::ThreadControllerWithMessagePumpImpl::DoWorkImpl(base::LazyNow*) + 2362
25  libbase.dylib                       0x000000010d56ebb6 base::sequence_manager::internal::ThreadControllerWithMessagePumpImpl::DoWork() + 246
26  libbase.dylib                       0x000000010d56fd13 non-virtual thunk to base::sequence_manager::internal::ThreadControllerWithMessagePumpImpl::DoWork() + 35
27  libbase.dylib                       0x000000010d389497 base::MessagePumpDefault::Run(base::MessagePump::Delegate*) + 151
28  libbase.dylib                       0x000000010d570661 base::sequence_manager::internal::ThreadControllerWithMessagePumpImpl::Run(bool, base::TimeDelta) + 705
29  libbase.dylib                       0x000000010d46b483 base::RunLoop::Run(base::Location const&) + 755
30  libblink_platform.dylib             0x00000001f0ab9b88 blink::scheduler::NonMainThreadImpl::SimpleThreadImpl::Run() + 568
31  libbase.dylib                       0x000000010d5fcf0a base::SimpleThread::ThreadMain() + 74
32  libbase.dylib                       0x000000010d68b2f2 base::(anonymous namespace)::ThreadFunc(void*) + 226
33  libsystem_pthread.dylib             0x00007ff81a49b4e1 _pthread_start + 125
34  libsystem_pthread.dylib             0x00007ff81a496f6b thread_start + 15

Chromium debugging resources

I built Chromium on Mac like this:

git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git
export PATH="$PATH:"`pwd`
cd ~/ && mkdir chromium && cd chromium
caffeinate fetch --no-history chromium
caffeinate autoninja -C out/Default chrome
./out/Default/Chromium.app/Contents/MacOS/Chromium --enable-logging --v=1

Then, I created a new empty xcode project and used the Debug > Attach to > Chromium from the top level menu. Finally, I paused the process and set a breakpoint on the error page handler like this:

(lldb) b SadTab

It didn't yield much information so I looked for scraps of information and set further breakpoints:

  • break on all exceptions – breakpoint wasn't triggered
  • GetTerminationStatus – breakpoint wasn't triggered
  • V8_Fatal – breakpoint wasn't triggered
  • PerformShutdownOnWorkerThread – breakpoint wasn't triggered

See more information at:

[Browser] Add a loading indicator

With 50 MB of resources to download, a loading indicator seems like a necessity. A simple spinner would be a great start, but the loading might take a while and an actual progress bar with real progress would provide a much better experience.

Support uploads to paths different than /wp-content/uploads

#48 Introduced a support for file uploads. Unfortunately it introduces an assumption that all requests to /wp-content/uploads/* should be resolved from the WASM filesystem, and all other non-PHP requests should be resolved from the server where wordpress-wasm is hosted.

Let's relax that constraint and correctly serve the static files from either the server or the WASM filesystem regardless of their path.

Expose PHP fatal errors from the WASM interpreter

Right now, PHP syntax errors cause the WASM PHP to return an exit code 2, but it doesn't return any error message. Let's make it provide a regular error message such as Fatal error: Class 'MyClass' not found in ...

Logging levels

The amount of log messages is too large and reduces the usefulness of the logged information. Let's find a solution that supports:

  • Different logging level (at least debug, info, warn, error)
  • Custom module labels (like PHP, ServiceWorker, etc.)
  • Filtering the output by level and module

Persist filesystem changes after page refresh

What problem is this issue trying to solve?

At the moment, all database changes and uploads are gone once the page is refreshed. Preserving them would be useful for courses, technical demos, even sharing a link to your changes.

What solution does this issue propose?

A few ideas:

A minimal runeditable code embed

Let's make it easy to embed runedditable code examples in the technical documentation.

A minimal implementation already exists at https://adamziel.github.io/embed.html#IkhleSB0aGVyZSEi

CleanShot 2022-09-22 at 13 16 34@2x

One problem with it is the code editor – it has much less affordances than the one on StackBlitz. I mean code completion, files explorer, linter integration etc.

Let's build a minimal "full widget" app for embedding editable code snippets.

Remove pre-built files from the repository

There are some pre-built files shipped in this repository:

Building them from scratch requires additional setup and takes a long time, which is a bad experience for a new contributor who just wants to clone the project and publish their first PR.

At the same time, shipping these files is a burden – they change, pollute the commit logs, and won't play very well with a support for multiple PHP and WordPress version.

It would be nice to have a separate package (or maybe even a repository?) for these pre-built binaries, and source them from there on the initial build.

WordPress build pipeline

What problem is this issue looking to solve?

WebAssembly WordPress applies a few WordPress patches once it's started. The method is quite peculiar – a PHP code snippet reads these files from the in-memory filesystem, runs some str_replaces, and then writes these files back:

https://github.com/adamziel/wordpress-wasm/blob/9d04a113a97f53a1303fefba642f65f1b5a5a5f6/src/shared/wordpress.mjs#L128-L137

How does this issue proposes to solve it?

The bundling pipeline could be updated to also apply a few .patch files to WordPress. This way, WebAssembly environment would already boot with the patched version.

Site editor not rendering

Navigating to /wp-admin/site-editor.php displays a blank page.

I narrowed the problem down to the Editor component in the edit-site.min.js file:

const isReady = (settings === null || settings === void 0 ? void 0 : settings.siteUrl) && templateType !== undefined && entityId !== undefined;
// ...
return (
   <Fragment>
      <URLQueryController>
          {isReady && ( /* the rest of the component */}
      </URLQueryController>
   </Fragment>
)

Inspecting the values yields this:

console.log(isReady, settings.siteUrl, templateType, entityId)
// false, 'http://127.0.0.1:8777/scope:0.0936352195928096' undefined undefined

Presumably, this is because a server-side WordPress installation redirects the user from site-editor.php to site-editor.php?postType=wp_template&postId=twentytwentythree%2F%2Fhome and the in-browser one doesn't.

[Browser] Support multiple browser tabs

What problem is this issue looking to solve?

In the browser, all the requests to WordPress are routed to a web worker by a service worker. The service worker handles the traffic for the entire domain, which means that all the open tabs are routing their WordPress requests via the same service worker.

Unfortunately, it passes them to all the running web workers instead of targeting specifically the one from the tab where the request originated. When five tabs are opened, five web workers handle all the requests, which causes all sorts of problems.

How does this issue proposes to solve it?

Find a way to route each request from service worker to only the specific web worker that initiated it. Somewhere around here:

https://github.com/adamziel/wordpress-wasm/blob/9d04a113a97f53a1303fefba642f65f1b5a5a5f6/src/web/service-worker.js#L31-L45

If there was a way to brand all requests from a specific tab somehow, that would be perfect. Perhaps each iframe could pretend to have a unique subdomain? Or each tab could attach a unique cookie or other http header to every request?

Build pipeline for multiple PHP versions and targets

What problem is this issue looking to solve?

A pipeline that builds all the listed PHP versions would pave the way to one-click switching of the PHP version:

switch php versions

How does this issue proposes to solve it?

The technical foundation was shipped in 45ba8ce. Since that commit, the docker build process can be build a specific PHP version as follows:

docker build . --no-cache --tag=wasm-wordpress-php-builder --build-arg PHP_VERSION="8.0.29" --build-arg VRZNO_FLAG="--disable-vrzno"

I confirmed it works with 7.0.0, 7.0.24 and even 8.0.29 when vrzno is disabled. It may break for versions that are not compatible with the PHP patches shipped in this repo.

Origin-Agent-Cluster header causes an error

The Origin-Agent-Cluster: ?1 is used to request a separate process for the iframe-worker.html in Google Chrome. It worked perfectly when the root app was served from wasm.wordpress.net and the iframe-worker.html was served from wasm-worker.wordpress.net.

Recently both these domains have been merged to allow direct communication between the Service Worker and the iframe Worker Thread. Now I'm observing the following error message:

The page requested an origin-keyed agent cluster using the Origin-Agent-Cluster header, but could not be origin-keyed since the origin 'https://wasm.wordpress.net' had previously been placed in a site-keyed agent cluster. Update your headers to uniformly request origin-keying for all pages on the origin.

Disable site health

MySQL is not available in the WASM runtime so /wp-admin/site-health.php displays the below error. Let's disable site-health.php entirely for now.

Fatal error: Uncaught Error: Call to undefined function mysql_get_server_info() in /preload/wordpress/wp-admin/includes/class-wp-site-health.php:2 Stack trace: #0 /preload/wordpress/wp-admin/includes/class-wp-site-health.php(2): WP_Site_Health->prepare_sql_data() #1 /preload/wordpress/wp-admin/includes/class-wp-site-health.php(2): WP_Site_Health->get_test_sql_server() #2 /preload/wordpress/wp-admin/includes/class-wp-site-health.php(2): WP_Site_Health->perform_test(Array) #3 /preload/wordpress/wp-includes/class-wp-hook.php(2): WP_Site_Health->enqueue_scripts('site-health.php') #4 /preload/wordpress/wp-includes/class-wp-hook.php(2): WP_Hook->apply_filters(NULL, Array) #5 /preload/wordpress/wp-includes/plugin.php(2): WP_Hook->do_action(Array) #6 /preload/wordpress/wp-admin/admin-header.php(17): do_action('admin_enqueue_s...', 'site-health.php') #7 /preload/wordpress/wp-admin/site-health.php(2): require_once('/preload/wordpr...') #8 php-wasm run script(90): require_once('/preload/wordpr...') #9 {main} thrown in /preload/wordpress/wp-admin/includes/class-wp-site-health.php on line 2

[PHP] Can't copy() empty files

php.wasm can copy() files with content just file:

console.log(await php.run(`<?php
     file_put_contents('with_content.txt', 'hey!');
     var_dump(copy('with_content.txt', 'with_content.txt'));
     // bool(true);
`));

However, trying to copy an empty file causes a JavaScript exception:

console.log(await php.run(`<?php
     file_put_contents('empty.txt', '');
     var_dump(copy('empty.txt', 'copied_empty.txt'));
`));

The exception:

TypeError: Cannot read properties of null (reading 'buffer')
    at Object.mmap (http://127.0.0.1:8778/php-web.js:2764:40)
    at Object.mmap (http://127.0.0.1:8778/php-web.js:3787:34)
    at syscallMmap2 (http://127.0.0.1:8778/php-web.js:5862:22)
    at ___sys_mmap2 (http://127.0.0.1:8778/php-web.js:5870:14)
    at ___syscall192 (http://127.0.0.1:8778/php-web.js:5877:10)
    at ___mmap (wasm://wasm/04421bd2:wasm-function[8927]:0x9473e2)
    at _php_stdiop_set_option (wasm://wasm/04421bd2:wasm-function[2855]:0x3209c9)
    at __php_stream_set_option (wasm://wasm/04421bd2:wasm-function[2747]:0x314b63)
    at __php_stream_mmap_range (wasm://wasm/04421bd2:wasm-function[2879]:0x325b18)
    at __php_stream_copy_to_stream_ex (wasm://wasm/04421bd2:wasm-function[2761]:0x317285)

It happens on MEMFS and WASMFS. I spent a day trying to find the root cause and work around it, but I couldn't.

As a consequence, installing plugins often fails as many plugins ship with at least one empty file.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.