The Text Selection system is a key component of the Translate-It extension, responsible for detecting, managing, and processing text selection on web pages. With a Simplified Architecture based on selectionchange events, it provides an optimized user experience for translating or pronouncing selected text.
- Icon to Toolbar Evolution: The single "Translation Icon" has been refactored into a modular Selection Toolbar (pill-shaped icon group).
- Multi-Action Support: Supports both Translate and Text-to-Speech (TTS) actions directly from the initial selection trigger.
- Configurable Visibility: Users can toggle individual buttons (Translate/TTS) via settings.
- Stateful Interaction: The toolbar integrates with
useTTSSmartto provide real-time playback states (loading, playing) within the selection UI. - Smart Dismissal: TTS playback automatically stops when the toolbar is dismissed (e.g., clicking outside), ensuring audio doesn't leak after the UI is gone.
- Complexity Removal: Complete removal of complex drag detection and the
pendingSelectionsystem. - selectionchange-only: Utilizes only
selectionchangeevents for all scenarios. - Text Field Decoupling: Text field logic has been moved to the
text-field-interactionmodule. - Simple Drag Prevention: Uses basic
mousedown/mouseupdetection to prevent UI triggers during active dragging. - Performance Boost: 60-70% reduction in code complexity and improved performance.
- Maintainability: Significantly simpler and more maintainable codebase.
src/features/text-selection/handlers/SimpleTextSelectionHandler.js
- Manages the standalone
selectionchangeevent. - Simple drag detection (
mousedown/mouseup). - Prevents toolbar display within text fields.
- Communicates directly with the
SelectionManager.
src/features/text-selection/core/SelectionManager.js
- Processes text selection simply.
- Calculates UI positioning for the toolbar.
- Interacts with
WindowsManager. - Supports iframe communication.
src/features/text-selection/composables/useTextSelection.js
- Vue composable for integration.
- Reactive state management.
- Simple interaction with
SimpleTextSelectionHandler.
src/features/text-field-interaction/utils/TextFieldDetector.js
- Detects field types using site handlers.
- Determines the appropriate selection strategy.
- Correct
async/awaitimplementation for all operations. - Cache management for performance optimization.
The new system utilizes a single strategy:
// Only one event listener is required:
document.addEventListener('selectionchange', () => {
if (!isDragging && hasText && !isInTextField) {
showTranslationIcon();
}
});- Text is selected (
selectedText.trim()) - Not currently dragging (
!isDragging) - Not inside a text field (
!isInTextField) - Ctrl key requirement (if enabled)
- Select element mode is disabled (
!selectModeActive) - At least one toolbar icon is enabled (Translate or TTS)
- Page Text Selection →
SimpleTextSelectionHandler - Text Field Selection →
TextFieldDoubleClickHandler(Separate module)
// Complex and bug-prone
selectionchange → store as pendingSelection
mouseup → process pendingSelection
timeout management + complex state// Highly simple and effective
mousedown → isDragging = true
selectionchange → if (isDragging) skip
mouseup → isDragging = false + process after delayclass SimpleTextSelectionHandler {
constructor() {
this.isDragging = false;
}
handleMouseDown() {
this.isDragging = true;
}
handleMouseUp() {
this.isDragging = false;
// Process selection after short delay
setTimeout(() => {
this.processSelection();
}, 50);
}
async processSelection() {
if (this.isDragging) {
return; // Skip during drag
}
if (this.isSelectionInTextField()) {
return; // Skip text fields
}
// Process page selection
await this.showSelectionToolbar();
}
}graph TD
A[User MouseDown] --> B[isDragging = true]
B --> C[User Drags Text]
C --> D[selectionchange events]
D --> E[Skip (isDragging = true)]
E --> F[User MouseUp]
F --> G[isDragging = false]
G --> H[Process selection after 50ms]
H --> I[Show Selection Toolbar]
I --> J1[Click Translate]
I --> J2[Click TTS]
J1 --> K1[Show Translation Window]
J2 --> K2[Play Audio / Toggle State]
mousedown → isDragging = true
↓
selectionchange → skip (isDragging = true)
↓
mouseup → isDragging = false → process after 50ms → show toolbar
selectionchange (isDragging = false) → immediate processing → show toolbar
selectionchange → isSelectionInTextField() = true → skip
↓
double-click in text field → TextFieldDoubleClickHandler → show toolbar/icon
Professional editors and text fields are now managed by a separate module:
// TextFieldDoubleClickHandler for text fields
class TextFieldDoubleClickHandler {
handleDoubleClick(event) {
if (this.isTextField(event.target)) {
const selectedText = this.getSelectedText();
this.showTranslationUI(selectedText);
}
}
isTextField(element) {
// INPUT, TEXTAREA, contenteditable
return element.tagName === 'INPUT' ||
element.tagName === 'TEXTAREA' ||
element.contentEditable === 'true';
}
}- Google Docs: contenteditable detection
- Microsoft Office: iframe-based detection
- Zoho Writer: custom element detection
- Notion: block-based detection
- WPS Office: office suite detection
- Page content →
SimpleTextSelectionHandler - Text fields →
TextFieldDoubleClickHandler - Professional editors →
TextFieldDoubleClickHandler(via contenteditable)
// TextSelectionManager → WindowsManager
const position = this._calculateSelectionPosition(selectedText);
const windowsManager = this._getWindowsManager();
await windowsManager.show('selection', {
text: selectedText,
position: position
});// FeatureManager → TextSelectionHandler
const textSelectionHandler = featureManager.getFeatureHandler('textSelection');
if (textSelectionHandler?.isActive) {
const manager = textSelectionHandler.getTextSelectionManager();
// Use manager...
}// Cross-frame communication
if (window !== window.top) {
// Send selection request to parent
const message = {
type: 'SELECTION_REQUEST',
text: selectedText,
position: position
};
window.parent.postMessage(message, '*');
}try {
await this._processSelectionChangeEvent(event);
} catch (rawError) {
const error = await ErrorHandler.processError(rawError);
await this.errorHandler.handle(error, {
type: ErrorTypes.UI,
context: 'text-selection',
eventType: event?.type
});
}// Extension context validation
if (ExtensionContextManager.isContextError(error)) {
this.logger.debug('Extension context invalidated, skipping selection processing');
return;
}class TextSelectionManager extends ResourceTracker {
constructor() {
super('text-selection-manager');
// Automatic cleanup of timeouts, event listeners, etc.
}
}The Selection Toolbar (TranslationIcon.vue) calculates its width dynamically based on active settings (SHOW_TTS_ICON_IN_TOOLBAR, SHOW_TRANSLATE_ICON_IN_TOOLBAR). This ensures the usePositioning logic always aligns the "pill" correctly relative to the selection without overflow.
// Prevent duplicate processing
const isRecentDuplicate = selectedText === this.lastProcessedText &&
(currentTime - this.lastProcessedTime) < this.selectionProcessingCooldown;
if (isRecentDuplicate && this._isWindowVisible()) {
return; // Skip duplicate
}// Only process events when feature is active
if (!this.isActive || !this.textSelectionManager) return;// Debug status
getStatus() {
return {
handlerActive: this.isActive,
hasSelection: this.hasActiveSelection(),
managerAvailable: !!this.textSelectionManager,
isDragging: this.isDragging,
pendingSelection: !!this.pendingSelection
};
}// Structured logging
this.logger.debug('Selection detected', {
text: selection.toString().substring(0, 30),
fieldType: detection.fieldType,
selectionStrategy: detection.selectionStrategy,
eventStrategy: detection.selectionEventStrategy
});- Use Field Detection: Always identify the field type.
- Respect User Interaction: Wait for the user to complete their selection.
- Cross-Frame Compatibility: Account for iframes.
- Error Resilience: Always handle potential errors.
- Resource Cleanup: Ensure resources are properly disposed.
- Performance: Prevent redundant duplicate processing.
- Timeout-Based Detection: Avoid using timeouts for primary drag detection.
- Immediate Processing: Do not process
selectionchangeimmediately during a drag. - Hard-Coded Delays: Avoid using fixed/static delays.
- Memory Leaks: Do not forget to clean up resources.
- Duplicate Events: Manage duplicate events properly.
// User drags text on a regular website
// → selectionchange events ignored while dragging
// → On mouseup: process and show selection toolbar// User double-clicks in Google Docs // → handleDoubleClick triggered // → Direct processing with professional editor logic
### 3. **Keyboard Selection**
```javascript
// User presses Ctrl+A
// → selectionchange with isDragging = false
// → Immediate processing and toolbar display
- SimpleTextSelectionHandler:
src/features/text-selection/handlers/SimpleTextSelectionHandler.js - SelectionManager:
src/features/text-selection/core/SelectionManager.js - useTextSelection (Vue):
src/features/text-selection/composables/useTextSelection.js
- TextFieldHandler:
src/features/text-field-interaction/handlers/TextFieldHandler.js - TextFieldDoubleClickHandler:
src/features/text-field-interaction/handlers/TextFieldDoubleClickHandler.js - TextFieldIconManager:
src/features/text-field-interaction/managers/TextFieldIconManager.js
- WindowsManager:
docs/technical/WINDOWS_MANAGER_UI_HOST_INTEGRATION.md - Smart Handler Registration:
docs/technical/SMART_HANDLER_REGISTRATION_SYSTEM.md - Error Management:
docs/technical/ERROR_MANAGEMENT_SYSTEM.md
- 60-70% Code Reduction: Eliminated unnecessary complexities.
- selectionchange-only: Switched exclusively to
selectionchangeevents. - Simple Drag Prevention: Replaced
pendingSelectionwith simplemousedown/mouseup. - Text Field Separation: Fully decoupled text fields into an independent module.
- Performance Boost: Higher efficiency with fewer race conditions.
- Maintainability: Cleaner, more readable code.
- Cross-browser Reliability: Improved compatibility across all major browsers.