GeezerVisonThe World Wide Web, restored to 800 pixels of glory. |
| Home|About|Gaming cabinet|Archives|How it works|Starter sites |
GeezerVison is showing www.mediawiki.org — VisualEditor/Tutorial - MediaWiki Tutorial introductionWhat is VisualEditor?VisualEditor is an in-browser rich text editor for HTML documents. It is most widely used as the Wikipedia editor. However, the core implementation is a standalone JavaScript library that does not depend on the MediaWiki platform. Who is this tutorial for?This tutorial focuses mainly on the fundamentals of the VisualEditor core implementation. It is aimed at newcomers joining the Editing team and volunteers who wish to hack on internals, but it should be interesting to anyone who wants to understand how an in-browser rich text editor works. Some of the fundamentals depend heavily on algorithms, so a computer science background would be very helpful, but pointers will be provided so the reader can fill in necessary background learning. A word about MediaWiki integrationVisualEditor works on HTML documents — it doesn’t know wikitext. However, MediaWiki’s native storage format is wikitext. This is possible because MediaWiki’s parser (Parsoid) can automatically translate to/from HTML+RDFa format, and a lot of work has gone into ensuring diffs round-trip cleanly, so that source editors who use raw wikitext can work side-by-side with rich-text editors who use VisualEditor. VisualEditor never needs to parse wikitext directly; from its point of view, it just sees MediaWiki loading/saving HTML+RDFa documents. Tutorial 1: Dive right inLet’s dive right in, without even bothering to install a development environment on your computer. Open https://simple.wikipedia.org/w/index.php?title=Oolong&oldid=9187227&veaction=edit in your browser. Some notes.
Q1
Now open the developer tools. In Firefox and Chromium, you can press s = ve.init.target.surface.model d = s.documentModel Then We will use s and d so defined throughout this tutorial.
Now try:
The array returned is a dump of the linear model Q1a. How long is the array?
Q1b. What section within the array represents the word “traditional” in the first paragraph?
Q1c. What section within the array represents the word “Oolong” in the first paragraph?
Q1d. What difference do you notice between the representation of “traditional” vs “Oolong”?
LEARNING GOALS: We learned how to access the live VisualEditor instance running within an editing session on a Wikipedia page, and how to query the document model to see the abstract representation of the content. Q2Now we’ll take a step back and learn about how the document model fits into VisualEditor as a whole. Q2. Read over VisualEditor/Design/Software_overview#Architecture then fill in the blanks:
LEARNING GOALS: We learned about the VisualEditor architecture, and in particular what the linear model is. Now we’ll return to the live editing session, and learn about the transactions system. Q3In the developer tools console tab (see above), type:
and find the Q3a. What is the
Q3b. The
Q4Now click on the document text, press Q4a. How long is the array now?
Q4b. What are the items at offsets 2 and 3?
Q4c. Can you find content in the editing interface that corresponds to the items? Can you do something to delete that content and make those items disappear?
Q5Now try:
Then try it again after pressing Q5a. What is the response? Comparing to the linear model, what do you think “document range” might mean?
Q5b. Look closely at the linear model data again. What tag contains all the content outside the document range? What is this content?
Q5c. Why do you think such content is stored separately?
Q5d. It feels like such content should have disappeared when you deleted everything. But it didn’t. Can you explain why that is not actually a bug?
Q6In the linear model, find the words “Oolong” and “traditional” from the first paragraph again. Recall there’s an interesting difference, and expand the items to see it completely. Q6. What do you think hed5e5a34cf0f5c5b might mean?
Q7Take a look into:
Look at the Q7. Does this confirm your suspicions about hed5e5a34cf0f5c5b?
Tutorial 2: TransactionsCheck out https://gerrit.wikimedia.org/r/VisualEditor/VisualEditor.git and host the files in a local webserver (e.g. Browse to http://localhost:8000/demos/ve/desktop.html#!h1 In the browser console, do: s = ve.init.target.surface.model d = s.documentModel tx1 = ve.dm.TransactionBuilder.static.newFromRemoval( d, new ve.Range( 3, 5 ) ) Q1Q1. Look inside
Notice that the “replace” operation is symmetrical: it specifies the content to remove as well as the content to insert. This is useful for creating the reverse (“undo”) transaction: s.change( tx1 ) s.change( tx1.reversed() ) s.change( tx1 ) // error, because tx1.applied === true tx2 = ve.dm.TransactionBuilder.static.newFromInsertion( d, 6, [ 'h', 'e', 'l', 'l', 'o' ] ) Q2Q2. Select ‘ef’ with the mouse. What do you expect will happen to the selection when you apply tx2?
Q3s.change( tx2 ) s.change( tx2.reversed() ) What do you think will happen if you apply tx1 then tx2? tx1 = tx1.clone() // so the .applied flag will be false tx2 = tx2.clone() // so the .applied flag will be false s.change( tx1 ) s.change( tx2 ) // error Q3. What’s wrong with tx2? How would you change tx2 so it would make sense on top of tx1? [ tx1on2, tx2on1 ] = ve.dm.Change.static.rebaseTransactions( tx1, tx2 ) s.change( tx2on1 ) Q4Q4. Reload the page, setup s= and d= and tx1= again. Set a breakpoint inside s.change. Apply tx1 and step into everything interesting.
Tutorial 3: Synchronous tree updatesFirst, disable Open http://localhost:8000/demos/ve/desktop.html#!h1 and set a breakpoint inside Put the cursor at ‘abc|defg’ and backspace the c. The breakpoint should trigger. In the console, check Now step over once (i.e. over the ‘ Step over the Q1Q1 What are the parameters to this function?
Q2Step in again, into Q2 Read this method very carefully, and try to state in what order the following things happen: updating this node’s length, updating ancestor nodes’ lengths, notifying listeners of these changes.
Q3Q3 Make yourself 100% sure how the recursion plays out and the ordering of these changes … you’ll need this knowledge to continue. Q4Step into the emit( ‘update’ ) line. This will pass into Q4 Is this listener running synchronously or asynchronously, with respect to the emit call? How do you know?
Q5Notice we’re processing operations one by one. Each operation modifies the linear model, then we update the DM tree correspondingly, then each node change in the DM tree emits an ‘update’ event which the CE node uses to update itself correspondingly. Q5 How is this even possible? A single linear operation, in isolation, does not necessarily preserve tree validity. It can leave the linear data in a state that does not even represent a tree. For instance <heading>...</paragraph>. So how does VE update the tree incrementally?
Next time: synchronous updates originating in the model. Tutorial 4: Updates initiated in the model vs the viewOpen http://localhost:8000/demos/ve/desktop.html#!h1 and set a breakpoint at the start of Create and apply a transaction to remove the ‘c’ and ‘d’ programmatically: tx1 = ve.dm.TransactionBuilder.static.newFromRemoval( d, new ve.Range( 3, 5 ) ) s.change( tx1 ) The breakpoint in Q1Q1. Look at the call stack. How did
Q2Q2. Step carefully through
Q3Now undo the transaction:
Now apply the same change but do it by editing the contentEditable DOM directly: select the letters ‘cde’ and press ‘e’ (so the net effect will be to remove the ‘c’ and ‘d’). The breakpoint in Q3. Look at the call stack this time. Can you see where the following things happened?
Q4Q4. Describe briefly the difference in control flow between the first example (where the update was initiated in the model) and the second example (where the update was initiated in the view).
Tutorial 5: Annotation nailsOpen Q1Q1. Guess, and then test, what formatting will appear if you type text after placing the cursor:
Notice that the cursor positions above are visually ambiguous: it’s not clear whether they lie inside the italic tags or outside. Chromium normalizes ambiguous cursor positions towards the left, or more precisely, towards the document start (since it applies in right-to-left scripts too). Q2Using the console, try placing the cursor programmatically after the f and outside the italic tag: sel = window.getSelection(); textNode = document.body.firstChild.firstChild.nextSibling.nextSibling; r = document.createRange(); r.setStart( textNode, 0 ); sel.removeAllRanges(); sel.addRange( r ); Q2. Then close the inspector and start typing. What happens? Notice that the text is italicized anyway. Q3Q3. Try the same experiments from Q1-2 in Firefox. Does the result depend whether you click on the cursor position or cursor there? Notice that Firefox does NOT normalize ambiguous cursor positions. When moving the cursor with left/right arrow keys, it moves lazily (choosing the nearest of the ambiguous cursor positions to the prior position). Q4Q4. Repeat Q1-2 in Chromium but with the italic tags replaced by <a href=xxx>...</a>. What is different? Notice that Chromium has special rules whereby typing at the end boundary of a link never extends the link. Q5Q5. Open http://localhost:8000/demos/ve/desktop.html#!h1 and paste abc <a href=xxx>def</a> ghi inside. Cursor very slowly across the content. Do you notice interesting behaviour? Notice that VE adds an extra cursor step to step into/out of a link, whereby you can type text that extends the link or not, depending on your wishes. Can you think of how this might have been implemented? Bear in mind you’ve just seen Chromium’s native behaviour won’t let you extend a link by typing text at its end.
IME = Input Method Engine, a software component for typing languages with complex scripts, such as Chinese or Japanese. An IME treats a combination of keystrokes as a composite character. This might look like a dropdown of candidate text that the user can choose from as they type. On mobile browsers, the mobile keyboard is an IME and so imposes these same constraints.
Inspect the link to see how we achieve this behaviour. We call the extra <img> elements “annotation nails”. Q6Q6. Can you explain we need two at each end of the link, and not just one?
Q7Click the “Input debugging” button in VE standalone, so the link nails become visible. Now cursor carefully across the entire content again. Q7. Can you see a point where the cursor jumps two nails in one step? Why do we want this?
Q8Now select “Disable JavaScript” in the Chromium inspector settings and cursor again. Notice the cursor does not jump two nails in one step. Q8. Guess how we used javascript to fix this behaviour.
Q9Go to he.wikipedia.org and copy a single word of Hebrew (language written right to left). Paste it over the ‘def’. Now try cursoring right across the h1. Q9. Is your browser doing visual bidi cursoring or logical bidi cursoring? (Search for these terms if you’re not sure what they mean)
Q10Q10. Bearing in mind that some browsers do visual cursoring and others do logical cursoring, and there’s no easy way for our code to know which will happen, do you need to improve on your answer to Q8?
Q11Q11. What do you think “prepare-observe-fixup” means with respect to how we handle cursoring across link boundaries? Can you think of other cases where this would be a useful pattern?
Tutorial 6: DebuggingPut “throw new Error( 'foo' )” at the top of ve.ce.ClipboardHandler#afterPaste. Then open http://localhost:8000/demos/ve/desktop.html#!h1 and try to paste something with the console open. Note how the browser gives an “uncaught error” warning in the console. Next, move the “throw new Error( 'foo' )” into the .then callback at the bottom of that method. And try the paste again. Note how there is NO warning now. This is because the .then callback is called through the jQuery promise system, and there’s no good way for it to know whether the promise error is uncaught. (See https://phabricator.wikimedia.org/T233480 ). Now instead try “Promise.resolve().then( function () { throw new Error( 'foo' ); } )” Note this time you do get an “uncaught error” warning — because the native promise system does know whether the native promise error is uncaught. Q1Q1. Suppose you suspect an uncaught error is happening in a .then callback within some new code, but you can’t see exactly where. How might you temporarily use native promises to help you debug this?
Q2Read https://developer.mozilla.org/Web/API/HTML_DOM_API/Microtask_guide/In_depth to learn about microtasks.
Q2. VisualEditor’s jQuery promises are created using the helper method ve.createDeferred . It wouldn’t be a huge code change to reimplement this to use native promises instead. Why might this create subtle timing issues? (Hint: native promises use microtasks but jQuery promises don’t)
Q3Reload http://localhost:8000/demos/ve/desktop.html#!h1 and place the cursor between ‘c’ and ‘d’. Click Filibuster, press Enter, then click Filibuster again. You should see a call tree appended below the document, with each function call numbered sequentially. Q3. Can you see where the javascript started handling the ‘Enter’ keypress? And how that triggered one of the two processes you learned in tutorial 4? Is it a model-initiated change or a view-initiated change?
Q4Now reload and place the cursor between ‘c’ and ‘d’ again. Click Filibuster, press ‘x’, then click Filibuster again, to get a new call tree. Q4. Is this a model-initiated change or a view-initiated change?
Q5Now reload set a breakpoint at the start of Install a Japanese romaji input method on your operating system, activate it, and learn how to enter the Kanji ‘日本’ (“Japan”, probably by typing ‘nipon’ and selecting from a list). Try to use the Chromium debugger to put a breakpoint at the start of Q5. Does the debugger close the input method? Why do you think this might happen? “Closing the input method” means prematurely committing the candidate text.
Exactly how an input method interacts with Javascript is highly platform-specific. It depends on platform combinations: OS, browser, language, input method software, and even software version. Different input methods can send radically different sequences of events, even if they look like they’re doing exactly the same thing. For instance, as of 2023, pressing Enter in Android Gboard does completely different things depending on whether the language is English or Cantonese. Q6Now reload, and place the cursor between ‘c’ and ‘d’ again. Click Filibuster, type ‘日本’ in Japanese, then click Filibuster again, to get a call tree. Q6. Look carefully through the call tree. Can you list the Javascript events which VisualEditor observes from the input method? For many platform combinations, you’ll see interesting changes of selection and content as the input method software builds up candidate text and then commits it. Or at the other extreme, you may only see a single ‘input’ event where ‘日本’ is inserted. Filibuster works by wrapping every method in ve.ce.*, ve.ui.* and ve.dm.* with a proxy that logs the call and its return value. It slows down execution greatly and the logs are too vast to be useful for complex edit sessions. Its main use is for debugging input method behaviour — where you can’t set a breakpoint because it will disturb the IME (see Q5) — and for this purpose a few keystrokes on a document of a few words usually suffices. Tutorial 7: MiscellaneousOpen http://localhost:8000/demos/ve/desktop.html#!simple and use the debugger's element tree to select the first h2. In the debugger, do
to get the CE ContentBranchNode. Do
To get the range object. Observe it has .start / .end properties and also .from / .to properties. Q1Q1. How do start/end relate to from/to?
Q2Now use the cursor and shift keys in VE to select the entire H2, starting from the end and moving left (so that your cursor ends at the beginning of the node). Try
Q2. How do r1 and r2 differ?
Q3Move the selection to the start of the document, and press ‘x’ to insert a character. Q3. Did the properties r1 or r2 change value? What are the consequences of this answer?
Q4Now undo the ‘x’ insertion, select the entire H2 again using cursor and shift keys, and do sel = s.getSelection() sf = new ve.dm.SurfaceFragment( s, sel ) and look at the property values returned by Q4. If you insert text at the start of the document, does calling |
| You are visitor 01402503 — GeezerVison does not store the pages you look up. Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required. |