Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(999)

Side by Side Diff: dart/site/try/src/interaction_manager.dart

Issue 345553008: Fix issues that broke editing on browsers without Shadow DOM support. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge
Patch Set: Address Johnni's changes, and fix bugs found during testing. Created 6 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
1 // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file 1 // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file
2 // for details. All rights reserved. Use of this source code is governed by a 2 // for details. All rights reserved. Use of this source code is governed by a
3 // BSD-style license that can be found in the LICENSE file. 3 // BSD-style license that can be found in the LICENSE file.
4 4
5 library trydart.interaction_manager; 5 library trydart.interaction_manager;
6 6
7 import 'dart:html'; 7 import 'dart:html';
8 8
9 import 'dart:convert' show 9 import 'dart:convert' show
10 JSON; 10 JSON;
(...skipping 218 matching lines...) Expand 10 before | Expand all | Expand 10 after
229 : super.internal() { 229 : super.internal() {
230 state = new InitialState(this); 230 state = new InitialState(this);
231 heartbeat = new Timer.periodic(HEARTBEAT_INTERVAL, onHeartbeat); 231 heartbeat = new Timer.periodic(HEARTBEAT_INTERVAL, onHeartbeat);
232 } 232 }
233 233
234 void onInput(Event event) => state.onInput(event); 234 void onInput(Event event) => state.onInput(event);
235 235
236 void onKeyUp(KeyboardEvent event) => state.onKeyUp(event); 236 void onKeyUp(KeyboardEvent event) => state.onKeyUp(event);
237 237
238 void onMutation(List<MutationRecord> mutations, MutationObserver observer) { 238 void onMutation(List<MutationRecord> mutations, MutationObserver observer) {
239 workAroundFirefoxBug();
239 try { 240 try {
240 try { 241 try {
241 return state.onMutation(mutations, observer); 242 return state.onMutation(mutations, observer);
242 } finally { 243 } finally {
243 // Discard any mutations during the observer, as these can lead to 244 // Discard any mutations during the observer, as these can lead to
244 // infinite loop. 245 // infinite loop.
245 observer.takeRecords(); 246 observer.takeRecords();
246 } 247 }
247 } catch (error, stackTrace) { 248 } catch (error, stackTrace) {
248 try { 249 try {
(...skipping 52 matching lines...) Expand 10 before | Expand all | Expand 10 after
301 InteractionContext get context; 302 InteractionContext get context;
302 303
303 // TODO(ahe): Remove this. 304 // TODO(ahe): Remove this.
304 Set<AnchorElement> get oldDiagnostics { 305 Set<AnchorElement> get oldDiagnostics {
305 throw 'Use context.oldDiagnostics instead'; 306 throw 'Use context.oldDiagnostics instead';
306 } 307 }
307 308
308 void set state(InteractionState newState); 309 void set state(InteractionState newState);
309 310
310 void onStateChanged(InteractionState previous) { 311 void onStateChanged(InteractionState previous) {
311 print('State change ${previous.runtimeType} -> ${runtimeType}.');
312 } 312 }
313 313
314 void transitionToInitialState() { 314 void transitionToInitialState() {
315 state = new InitialState(context); 315 state = new InitialState(context);
316 } 316 }
317 } 317 }
318 318
319 class InitialState extends InteractionState { 319 class InitialState extends InteractionState {
320 final InteractionContext context; 320 final InteractionContext context;
321 bool requestCodeCompletion = false; 321 bool requestCodeCompletion = false;
322 322
323 InitialState(this.context); 323 InitialState(this.context);
324 324
325 void set state(InteractionState state) { 325 void set state(InteractionState state) {
326 InteractionState previous = context.state; 326 InteractionState previous = context.state;
327 if (previous != state) { 327 if (previous != state) {
328 context.state = state; 328 context.state = state;
329 state.onStateChanged(previous); 329 state.onStateChanged(previous);
330 } 330 }
331 } 331 }
332 332
333 void onInput(Event event) { 333 void onInput(Event event) {
334 state = new PendingInputState(context); 334 state = new PendingInputState(context);
335 } 335 }
336 336
337 void onKeyUp(KeyboardEvent event) { 337 void onKeyUp(KeyboardEvent event) {
338 if (computeHasModifier(event)) { 338 if (computeHasModifier(event)) {
339 print('onKeyUp (modified)');
340 onModifiedKeyUp(event); 339 onModifiedKeyUp(event);
341 } else { 340 } else {
342 print('onKeyUp (unmodified)');
343 onUnmodifiedKeyUp(event); 341 onUnmodifiedKeyUp(event);
344 } 342 }
345 } 343 }
346 344
347 void onModifiedKeyUp(KeyboardEvent event) { 345 void onModifiedKeyUp(KeyboardEvent event) {
348 if (event.getModifierState("Shift")) return onShiftedKeyUp(event); 346 if (event.getModifierState("Shift")) return onShiftedKeyUp(event);
349 switch (event.keyCode) { 347 switch (event.keyCode) {
350 case KeyCode.S: 348 case KeyCode.S:
351 // Disable Ctrl-S, Cmd-S, etc. We have observed users hitting these 349 // Disable Ctrl-S, Cmd-S, etc. We have observed users hitting these
352 // keys often when using Try Dart and getting frustrated. 350 // keys often when using Try Dart and getting frustrated.
(...skipping 53 matching lines...) Expand 10 before | Expand all | Expand 10 after
406 404
407 // This is a hack to get Safari (iOS) to send mutation events on 405 // This is a hack to get Safari (iOS) to send mutation events on
408 // contenteditable. 406 // contenteditable.
409 // TODO(ahe): Move to onInput? 407 // TODO(ahe): Move to onInput?
410 var newDiv = new DivElement(); 408 var newDiv = new DivElement();
411 hackDiv.replaceWith(newDiv); 409 hackDiv.replaceWith(newDiv);
412 hackDiv = newDiv; 410 hackDiv = newDiv;
413 } 411 }
414 412
415 void onMutation(List<MutationRecord> mutations, MutationObserver observer) { 413 void onMutation(List<MutationRecord> mutations, MutationObserver observer) {
416 print('onMutation');
417
418 removeCodeCompletion(); 414 removeCodeCompletion();
419 415
420 Selection selection = window.getSelection(); 416 Selection selection = window.getSelection();
421 TrySelection trySelection = new TrySelection(mainEditorPane, selection); 417 TrySelection trySelection = new TrySelection(mainEditorPane, selection);
422 418
423 Set<Node> normalizedNodes = new Set<Node>(); 419 Set<Node> normalizedNodes = new Set<Node>();
424 for (MutationRecord record in mutations) { 420 for (MutationRecord record in mutations) {
425 normalizeMutationRecord(record, trySelection, normalizedNodes); 421 normalizeMutationRecord(record, trySelection, normalizedNodes);
426 } 422 }
427 423
(...skipping 21 matching lines...) Expand all
449 for (String line in splitLines(currentText)) { 445 for (String line in splitLines(currentText)) {
450 List<Node> lineNodes = <Node>[]; 446 List<Node> lineNodes = <Node>[];
451 state = tokenizeAndHighlight( 447 state = tokenizeAndHighlight(
452 line, state, offset, trySelection, lineNodes); 448 line, state, offset, trySelection, lineNodes);
453 offset += line.length; 449 offset += line.length;
454 nodes.add(makeLine(lineNodes, state)); 450 nodes.add(makeLine(lineNodes, state));
455 } 451 }
456 452
457 node.parent.insertAllBefore(nodes, node); 453 node.parent.insertAllBefore(nodes, node);
458 node.remove(); 454 node.remove();
459 trySelection.adjust(selection); 455 if (mainEditorPane.contains(trySelection.anchorNode)) {
456 // Sometimes the anchor node is removed by the above call. This has
457 // only been observed in Firefox, and is hard to reproduce.
458 trySelection.adjust(selection);
459 }
460 460
461 // TODO(ahe): We know almost exactly what has changed. It could be 461 // TODO(ahe): We know almost exactly what has changed. It could be
462 // more efficient to only communicate what changed. 462 // more efficient to only communicate what changed.
463 context.currentCompilationUnit.content = getText(mainEditorPane); 463 context.currentCompilationUnit.content = getText(mainEditorPane);
464 464
465 // Discard highlighting mutations. 465 // Discard highlighting mutations.
466 observer.takeRecords(); 466 observer.takeRecords();
467 return; 467 return;
468 } 468 }
469 } 469 }
(...skipping 729 matching lines...) Expand 10 before | Expand all | Expand 10 after
1199 } 1199 }
1200 if (!record.removedNodes.isEmpty) { 1200 if (!record.removedNodes.isEmpty) {
1201 var first = record.removedNodes.first; 1201 var first = record.removedNodes.first;
1202 var line = findLine(record.target); 1202 var line = findLine(record.target);
1203 1203
1204 if (first is Text && first.data=="\n" && line.nextNode != null) { 1204 if (first is Text && first.data=="\n" && line.nextNode != null) {
1205 normalizedNodes.add(line.nextNode); 1205 normalizedNodes.add(line.nextNode);
1206 } 1206 }
1207 normalizedNodes.add(line); 1207 normalizedNodes.add(line);
1208 } 1208 }
1209 if (record.type == "characterData") { 1209 if (record.type == "characterData" && record.target.parent != null) {
1210 // At least Firefox sends a "characterData" record whose target is the
1211 // deleted text node. It also sends a record where "removedNodes" isn't
1212 // empty whose target is the parent (which we are interested in).
1210 normalizedNodes.add(findLine(record.target)); 1213 normalizedNodes.add(findLine(record.target));
1211 } 1214 }
1212 } 1215 }
1213 1216
1214 // Finds the line of [node] (a parent node with CSS class 'lineNumber'). 1217 // Finds the line of [node] (a parent node with CSS class 'lineNumber').
1215 // If no such parent exists, return mainEditorPane if it is a parent. 1218 // If no such parent exists, return mainEditorPane if it is a parent.
1216 // Otherwise return [node]. 1219 // Otherwise return [node].
1217 Node findLine(Node node) { 1220 Node findLine(Node node) {
1218 for (Node n = node; n != null; n = n.parent) { 1221 for (Node n = node; n != null; n = n.parent) {
1219 if (n is Element && n.classes.contains('lineNumber')) return n; 1222 if (n is Element && n.classes.contains('lineNumber')) return n;
(...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after
1252 bool isCompilerStageMarker(String message) { 1255 bool isCompilerStageMarker(String message) {
1253 return 1256 return
1254 message.startsWith('Package root is ') || 1257 message.startsWith('Package root is ') ||
1255 message.startsWith('Compiling ') || 1258 message.startsWith('Compiling ') ||
1256 message == "Resolving..." || 1259 message == "Resolving..." ||
1257 message.startsWith('Resolved ') || 1260 message.startsWith('Resolved ') ||
1258 message == "Inferring types..." || 1261 message == "Inferring types..." ||
1259 message == "Compiling..." || 1262 message == "Compiling..." ||
1260 message.startsWith('Compiled '); 1263 message.startsWith('Compiled ');
1261 } 1264 }
1265
1266 void workAroundFirefoxBug() {
1267 Selection selection = window.getSelection();
1268 if (!isCollapsed(selection)) return;
1269 Node node = selection.anchorNode;
1270 int offset = selection.anchorOffset;
1271 if (selection.anchorNode is Element && selection.anchorOffset != 0) {
1272 // In some cases, Firefox reports the wrong anchorOffset (always seems to
1273 // be 6) when anchorNode is an Element. Moving the cursor back and forth
1274 // adjusts the anchorOffset.
1275 // Safari can also reach this code, but the offset isn't wrong, just
1276 // inconsistent. After moving the cursor back and forth, Safari will make
1277 // the offset relative to a text node.
1278 selection
1279 ..modify('move', 'backward', 'character')
1280 ..modify('move', 'forward', 'character');
1281 print('Selection adjusted $node@$offset -> '
1282 '${selection.anchorNode}@${selection.anchorOffset}.');
1283 }
1284 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698