1# Orca
2#
3# Copyright 2005-2009 Sun Microsystems Inc.
4# Copyright 2010 Orca Team.
5# Copyright 2014-2015 Igalia, S.L.
6#
7# This library is free software; you can redistribute it and/or
8# modify it under the terms of the GNU Lesser General Public
9# License as published by the Free Software Foundation; either
10# version 2.1 of the License, or (at your option) any later version.
11#
12# This library is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15# Lesser General Public License for more details.
16#
17# You should have received a copy of the GNU Lesser General Public
18# License along with this library; if not, write to the
19# Free Software Foundation, Inc., Franklin Street, Fifth Floor,
20# Boston MA  02110-1301 USA.
21
22__id__        = "$Id$"
23__version__   = "$Revision$"
24__date__      = "$Date$"
25__copyright__ = "Copyright (c) 2005-2009 Sun Microsystems Inc." \
26                "Copyright (c) 2010 Orca Team." \
27                "Copyright (c) 2014-2015 Igalia, S.L."
28__license__   = "LGPL"
29
30from gi.repository import Gtk
31import pyatspi
32import time
33
34from orca import caret_navigation
35from orca import cmdnames
36from orca import keybindings
37from orca import debug
38from orca import eventsynthesizer
39from orca import guilabels
40from orca import input_event
41from orca import liveregions
42from orca import messages
43from orca import object_properties
44from orca import orca
45from orca import orca_state
46from orca import settings
47from orca import settings_manager
48from orca import speech
49from orca import speechserver
50from orca import structural_navigation
51from orca.acss import ACSS
52from orca.scripts import default
53
54from .bookmarks import Bookmarks
55from .braille_generator import BrailleGenerator
56from .sound_generator import SoundGenerator
57from .speech_generator import SpeechGenerator
58from .tutorial_generator import TutorialGenerator
59from .script_utilities import Utilities
60
61_settingsManager = settings_manager.getManager()
62
63
64class Script(default.Script):
65
66    def __init__(self, app):
67        super().__init__(app)
68
69        self._sayAllContents = []
70        self._inSayAll = False
71        self._sayAllIsInterrupted = False
72        self._loadingDocumentContent = False
73        self._madeFindAnnouncement = False
74        self._lastCommandWasCaretNav = False
75        self._lastCommandWasStructNav = False
76        self._lastCommandWasMouseButton = False
77        self._lastMouseButtonContext = None, -1
78        self._lastMouseOverObject = None
79        self._preMouseOverContext = None, -1
80        self._inMouseOverObject = False
81        self._inFocusMode = False
82        self._focusModeIsSticky = False
83        self._browseModeIsSticky = False
84
85        if _settingsManager.getSetting('caretNavigationEnabled') is None:
86            _settingsManager.setSetting('caretNavigationEnabled', True)
87        if _settingsManager.getSetting('sayAllOnLoad') is None:
88            _settingsManager.setSetting('sayAllOnLoad', True)
89        if _settingsManager.getSetting('pageSummaryOnLoad') is None:
90            _settingsManager.setSetting('pageSummaryOnLoad', True)
91
92        self._changedLinesOnlyCheckButton = None
93        self._controlCaretNavigationCheckButton = None
94        self._minimumFindLengthAdjustment = None
95        self._minimumFindLengthLabel = None
96        self._minimumFindLengthSpinButton = None
97        self._pageSummaryOnLoadCheckButton = None
98        self._sayAllOnLoadCheckButton = None
99        self._skipBlankCellsCheckButton = None
100        self._speakCellCoordinatesCheckButton = None
101        self._speakCellHeadersCheckButton = None
102        self._speakCellSpanCheckButton = None
103        self._speakResultsDuringFindCheckButton = None
104        self._structuralNavigationCheckButton = None
105        self._autoFocusModeStructNavCheckButton = None
106        self._autoFocusModeCaretNavCheckButton = None
107        self._autoFocusModeNativeNavCheckButton = None
108        self._layoutModeCheckButton = None
109
110        self.attributeNamesDict["invalid"] = "text-spelling"
111        self.attributeNamesDict["text-align"] = "justification"
112        self.attributeNamesDict["text-indent"] = "indent"
113
114    def deactivate(self):
115        """Called when this script is deactivated."""
116
117        self._sayAllContents = []
118        self._inSayAll = False
119        self._sayAllIsInterrupted = False
120        self._loadingDocumentContent = False
121        self._madeFindAnnouncement = False
122        self._lastCommandWasCaretNav = False
123        self._lastCommandWasStructNav = False
124        self._lastCommandWasMouseButton = False
125        self._lastMouseButtonContext = None, -1
126        self._lastMouseOverObject = None
127        self._preMouseOverContext = None, -1
128        self._inMouseOverObject = False
129        self.utilities.clearCachedObjects()
130        self.removeKeyGrabs()
131
132    def getAppKeyBindings(self):
133        """Returns the application-specific keybindings for this script."""
134
135        keyBindings = keybindings.KeyBindings()
136
137        structNavBindings = self.structuralNavigation.keyBindings
138        for keyBinding in structNavBindings.keyBindings:
139            keyBindings.add(keyBinding)
140
141        caretNavBindings = self.caretNavigation.get_bindings()
142        for keyBinding in caretNavBindings.keyBindings:
143            keyBindings.add(keyBinding)
144
145        liveRegionBindings = self.liveRegionManager.keyBindings
146        for keyBinding in liveRegionBindings.keyBindings:
147            keyBindings.add(keyBinding)
148
149        keyBindings.add(
150            keybindings.KeyBinding(
151                "a",
152                keybindings.defaultModifierMask,
153                keybindings.ORCA_MODIFIER_MASK,
154                self.inputEventHandlers.get("togglePresentationModeHandler")))
155
156        keyBindings.add(
157            keybindings.KeyBinding(
158                "a",
159                keybindings.defaultModifierMask,
160                keybindings.ORCA_MODIFIER_MASK,
161                self.inputEventHandlers.get("enableStickyFocusModeHandler"),
162                2))
163
164        keyBindings.add(
165            keybindings.KeyBinding(
166                "a",
167                keybindings.defaultModifierMask,
168                keybindings.ORCA_MODIFIER_MASK,
169                self.inputEventHandlers.get("enableStickyBrowseModeHandler"),
170                3))
171
172        keyBindings.add(
173            keybindings.KeyBinding(
174                "",
175                keybindings.defaultModifierMask,
176                keybindings.NO_MODIFIER_MASK,
177                self.inputEventHandlers.get("toggleLayoutModeHandler")))
178
179
180        layout = _settingsManager.getSetting('keyboardLayout')
181        if layout == settings.GENERAL_KEYBOARD_LAYOUT_DESKTOP:
182            key = "KP_Multiply"
183        else:
184            key = "0"
185
186        keyBindings.add(
187            keybindings.KeyBinding(
188                key,
189                keybindings.defaultModifierMask,
190                keybindings.ORCA_MODIFIER_MASK,
191                self.inputEventHandlers.get("moveToMouseOverHandler")))
192
193        return keyBindings
194
195    def setupInputEventHandlers(self):
196        """Defines InputEventHandlers for this script."""
197
198        super().setupInputEventHandlers()
199        self.inputEventHandlers.update(
200            self.structuralNavigation.inputEventHandlers)
201
202        self.inputEventHandlers.update(
203            self.caretNavigation.get_handlers())
204
205        self.inputEventHandlers.update(
206            self.liveRegionManager.inputEventHandlers)
207
208        self.inputEventHandlers["sayAllHandler"] = \
209            input_event.InputEventHandler(
210                Script.sayAll,
211                cmdnames.SAY_ALL)
212
213        self.inputEventHandlers["panBrailleLeftHandler"] = \
214            input_event.InputEventHandler(
215                Script.panBrailleLeft,
216                cmdnames.PAN_BRAILLE_LEFT,
217                False) # Do not enable learn mode for this action
218
219        self.inputEventHandlers["panBrailleRightHandler"] = \
220            input_event.InputEventHandler(
221                Script.panBrailleRight,
222                cmdnames.PAN_BRAILLE_RIGHT,
223                False) # Do not enable learn mode for this action
224
225        self.inputEventHandlers["moveToMouseOverHandler"] = \
226            input_event.InputEventHandler(
227                Script.moveToMouseOver,
228                cmdnames.MOUSE_OVER_MOVE)
229
230        self.inputEventHandlers["togglePresentationModeHandler"] = \
231            input_event.InputEventHandler(
232                Script.togglePresentationMode,
233                cmdnames.TOGGLE_PRESENTATION_MODE)
234
235        self.inputEventHandlers["enableStickyFocusModeHandler"] = \
236            input_event.InputEventHandler(
237                Script.enableStickyFocusMode,
238                cmdnames.SET_FOCUS_MODE_STICKY)
239
240        self.inputEventHandlers["enableStickyBrowseModeHandler"] = \
241            input_event.InputEventHandler(
242                Script.enableStickyBrowseMode,
243                cmdnames.SET_BROWSE_MODE_STICKY)
244
245        self.inputEventHandlers["toggleLayoutModeHandler"] = \
246            input_event.InputEventHandler(
247                Script.toggleLayoutMode,
248                cmdnames.TOGGLE_LAYOUT_MODE)
249
250    def getBookmarks(self):
251        """Returns the "bookmarks" class for this script."""
252
253        try:
254            return self.bookmarks
255        except AttributeError:
256            self.bookmarks = Bookmarks(self)
257            return self.bookmarks
258
259    def getBrailleGenerator(self):
260        """Returns the braille generator for this script."""
261
262        return BrailleGenerator(self)
263
264    def getCaretNavigation(self):
265        """Returns the caret navigation support for this script."""
266
267        return caret_navigation.CaretNavigation(self)
268
269    def getEnabledStructuralNavigationTypes(self):
270        """Returns the structural navigation object types for this script."""
271
272        return [structural_navigation.StructuralNavigation.BLOCKQUOTE,
273                structural_navigation.StructuralNavigation.BUTTON,
274                structural_navigation.StructuralNavigation.CHECK_BOX,
275                structural_navigation.StructuralNavigation.CHUNK,
276                structural_navigation.StructuralNavigation.CLICKABLE,
277                structural_navigation.StructuralNavigation.COMBO_BOX,
278                structural_navigation.StructuralNavigation.CONTAINER,
279                structural_navigation.StructuralNavigation.ENTRY,
280                structural_navigation.StructuralNavigation.FORM_FIELD,
281                structural_navigation.StructuralNavigation.HEADING,
282                structural_navigation.StructuralNavigation.IMAGE,
283                structural_navigation.StructuralNavigation.LANDMARK,
284                structural_navigation.StructuralNavigation.LINK,
285                structural_navigation.StructuralNavigation.LIST,
286                structural_navigation.StructuralNavigation.LIST_ITEM,
287                structural_navigation.StructuralNavigation.LIVE_REGION,
288                structural_navigation.StructuralNavigation.PARAGRAPH,
289                structural_navigation.StructuralNavigation.RADIO_BUTTON,
290                structural_navigation.StructuralNavigation.SEPARATOR,
291                structural_navigation.StructuralNavigation.TABLE,
292                structural_navigation.StructuralNavigation.TABLE_CELL,
293                structural_navigation.StructuralNavigation.UNVISITED_LINK,
294                structural_navigation.StructuralNavigation.VISITED_LINK]
295
296    def getLiveRegionManager(self):
297        """Returns the live region support for this script."""
298
299        return liveregions.LiveRegionManager(self)
300
301    def getSoundGenerator(self):
302        """Returns the sound generator for this script."""
303
304        return SoundGenerator(self)
305
306    def getSpeechGenerator(self):
307        """Returns the speech generator for this script."""
308
309        return SpeechGenerator(self)
310
311    def getTutorialGenerator(self):
312        """Returns the tutorial generator for this script."""
313
314        return TutorialGenerator(self)
315
316    def getUtilities(self):
317        """Returns the utilites for this script."""
318
319        return Utilities(self)
320
321    def getAppPreferencesGUI(self):
322        """Return a GtkGrid containing app-unique configuration items."""
323
324        grid = Gtk.Grid()
325        grid.set_border_width(12)
326
327        generalFrame = Gtk.Frame()
328        grid.attach(generalFrame, 0, 0, 1, 1)
329
330        label = Gtk.Label(label="<b>%s</b>" % guilabels.PAGE_NAVIGATION)
331        label.set_use_markup(True)
332        generalFrame.set_label_widget(label)
333
334        generalAlignment = Gtk.Alignment.new(0.5, 0.5, 1, 1)
335        generalAlignment.set_padding(0, 0, 12, 0)
336        generalFrame.add(generalAlignment)
337        generalGrid = Gtk.Grid()
338        generalAlignment.add(generalGrid)
339
340        label = guilabels.USE_CARET_NAVIGATION
341        value = _settingsManager.getSetting('caretNavigationEnabled')
342        self._controlCaretNavigationCheckButton = \
343            Gtk.CheckButton.new_with_mnemonic(label)
344        self._controlCaretNavigationCheckButton.set_active(value)
345        generalGrid.attach(self._controlCaretNavigationCheckButton, 0, 0, 1, 1)
346
347        label = guilabels.AUTO_FOCUS_MODE_CARET_NAV
348        value = _settingsManager.getSetting('caretNavTriggersFocusMode')
349        self._autoFocusModeCaretNavCheckButton = Gtk.CheckButton.new_with_mnemonic(label)
350        self._autoFocusModeCaretNavCheckButton.set_active(value)
351        generalGrid.attach(self._autoFocusModeCaretNavCheckButton, 0, 1, 1, 1)
352
353        label = guilabels.USE_STRUCTURAL_NAVIGATION
354        value = self.structuralNavigation.enabled
355        self._structuralNavigationCheckButton = \
356            Gtk.CheckButton.new_with_mnemonic(label)
357        self._structuralNavigationCheckButton.set_active(value)
358        generalGrid.attach(self._structuralNavigationCheckButton, 0, 2, 1, 1)
359
360        label = guilabels.AUTO_FOCUS_MODE_STRUCT_NAV
361        value = _settingsManager.getSetting('structNavTriggersFocusMode')
362        self._autoFocusModeStructNavCheckButton = Gtk.CheckButton.new_with_mnemonic(label)
363        self._autoFocusModeStructNavCheckButton.set_active(value)
364        generalGrid.attach(self._autoFocusModeStructNavCheckButton, 0, 3, 1, 1)
365
366        label = guilabels.AUTO_FOCUS_MODE_NATIVE_NAV
367        value = _settingsManager.getSetting('nativeNavTriggersFocusMode')
368        self._autoFocusModeNativeNavCheckButton = Gtk.CheckButton.new_with_mnemonic(label)
369        self._autoFocusModeNativeNavCheckButton.set_active(value)
370        generalGrid.attach(self._autoFocusModeNativeNavCheckButton, 0, 4, 1, 1)
371
372        label = guilabels.READ_PAGE_UPON_LOAD
373        value = _settingsManager.getSetting('sayAllOnLoad')
374        self._sayAllOnLoadCheckButton = Gtk.CheckButton.new_with_mnemonic(label)
375        self._sayAllOnLoadCheckButton.set_active(value)
376        generalGrid.attach(self._sayAllOnLoadCheckButton, 0, 5, 1, 1)
377
378        label = guilabels.PAGE_SUMMARY_UPON_LOAD
379        value = _settingsManager.getSetting('pageSummaryOnLoad')
380        self._pageSummaryOnLoadCheckButton = Gtk.CheckButton.new_with_mnemonic(label)
381        self._pageSummaryOnLoadCheckButton.set_active(value)
382        generalGrid.attach(self._pageSummaryOnLoadCheckButton, 0, 6, 1, 1)
383
384        label = guilabels.CONTENT_LAYOUT_MODE
385        value = _settingsManager.getSetting('layoutMode')
386        self._layoutModeCheckButton = Gtk.CheckButton.new_with_mnemonic(label)
387        self._layoutModeCheckButton.set_active(value)
388        generalGrid.attach(self._layoutModeCheckButton, 0, 7, 1, 1)
389
390        tableFrame = Gtk.Frame()
391        grid.attach(tableFrame, 0, 1, 1, 1)
392
393        label = Gtk.Label(label="<b>%s</b>" % guilabels.TABLE_NAVIGATION)
394        label.set_use_markup(True)
395        tableFrame.set_label_widget(label)
396
397        tableAlignment = Gtk.Alignment.new(0.5, 0.5, 1, 1)
398        tableAlignment.set_padding(0, 0, 12, 0)
399        tableFrame.add(tableAlignment)
400        tableGrid = Gtk.Grid()
401        tableAlignment.add(tableGrid)
402
403        label = guilabels.TABLE_SPEAK_CELL_COORDINATES
404        value = _settingsManager.getSetting('speakCellCoordinates')
405        self._speakCellCoordinatesCheckButton = \
406            Gtk.CheckButton.new_with_mnemonic(label)
407        self._speakCellCoordinatesCheckButton.set_active(value)
408        tableGrid.attach(self._speakCellCoordinatesCheckButton, 0, 0, 1, 1)
409
410        label = guilabels.TABLE_SPEAK_CELL_SPANS
411        value = _settingsManager.getSetting('speakCellSpan')
412        self._speakCellSpanCheckButton = \
413            Gtk.CheckButton.new_with_mnemonic(label)
414        self._speakCellSpanCheckButton.set_active(value)
415        tableGrid.attach(self._speakCellSpanCheckButton, 0, 1, 1, 1)
416
417        label = guilabels.TABLE_ANNOUNCE_CELL_HEADER
418        value = _settingsManager.getSetting('speakCellHeaders')
419        self._speakCellHeadersCheckButton = \
420            Gtk.CheckButton.new_with_mnemonic(label)
421        self._speakCellHeadersCheckButton.set_active(value)
422        tableGrid.attach(self._speakCellHeadersCheckButton, 0, 2, 1, 1)
423
424        label = guilabels.TABLE_SKIP_BLANK_CELLS
425        value = _settingsManager.getSetting('skipBlankCells')
426        self._skipBlankCellsCheckButton = \
427            Gtk.CheckButton.new_with_mnemonic(label)
428        self._skipBlankCellsCheckButton.set_active(value)
429        tableGrid.attach(self._skipBlankCellsCheckButton, 0, 3, 1, 1)
430
431        findFrame = Gtk.Frame()
432        grid.attach(findFrame, 0, 2, 1, 1)
433
434        label = Gtk.Label(label="<b>%s</b>" % guilabels.FIND_OPTIONS)
435        label.set_use_markup(True)
436        findFrame.set_label_widget(label)
437
438        findAlignment = Gtk.Alignment.new(0.5, 0.5, 1, 1)
439        findAlignment.set_padding(0, 0, 12, 0)
440        findFrame.add(findAlignment)
441        findGrid = Gtk.Grid()
442        findAlignment.add(findGrid)
443
444        verbosity = _settingsManager.getSetting('findResultsVerbosity')
445
446        label = guilabels.FIND_SPEAK_RESULTS
447        value = verbosity != settings.FIND_SPEAK_NONE
448        self._speakResultsDuringFindCheckButton = \
449            Gtk.CheckButton.new_with_mnemonic(label)
450        self._speakResultsDuringFindCheckButton.set_active(value)
451        findGrid.attach(self._speakResultsDuringFindCheckButton, 0, 0, 1, 1)
452
453        label = guilabels.FIND_ONLY_SPEAK_CHANGED_LINES
454        value = verbosity == settings.FIND_SPEAK_IF_LINE_CHANGED
455        self._changedLinesOnlyCheckButton = \
456            Gtk.CheckButton.new_with_mnemonic(label)
457        self._changedLinesOnlyCheckButton.set_active(value)
458        findGrid.attach(self._changedLinesOnlyCheckButton, 0, 1, 1, 1)
459
460        hgrid = Gtk.Grid()
461        findGrid.attach(hgrid, 0, 2, 1, 1)
462
463        self._minimumFindLengthLabel = \
464              Gtk.Label(label=guilabels.FIND_MINIMUM_MATCH_LENGTH)
465        self._minimumFindLengthLabel.set_alignment(0, 0.5)
466        hgrid.attach(self._minimumFindLengthLabel, 0, 0, 1, 1)
467
468        self._minimumFindLengthAdjustment = \
469            Gtk.Adjustment(_settingsManager.getSetting(
470                'findResultsMinimumLength'), 0, 20, 1)
471        self._minimumFindLengthSpinButton = Gtk.SpinButton()
472        self._minimumFindLengthSpinButton.set_adjustment(
473            self._minimumFindLengthAdjustment)
474        hgrid.attach(self._minimumFindLengthSpinButton, 1, 0, 1, 1)
475        self._minimumFindLengthLabel.set_mnemonic_widget(
476            self._minimumFindLengthSpinButton)
477
478        grid.show_all()
479        return grid
480
481    def getPreferencesFromGUI(self):
482        """Returns a dictionary with the app-specific preferences."""
483
484        if not self._speakResultsDuringFindCheckButton.get_active():
485            verbosity = settings.FIND_SPEAK_NONE
486        elif self._changedLinesOnlyCheckButton.get_active():
487            verbosity = settings.FIND_SPEAK_IF_LINE_CHANGED
488        else:
489            verbosity = settings.FIND_SPEAK_ALL
490
491        return {
492            'findResultsVerbosity': verbosity,
493            'findResultsMinimumLength': self._minimumFindLengthSpinButton.get_value(),
494            'sayAllOnLoad': self._sayAllOnLoadCheckButton.get_active(),
495            'pageSummaryOnLoad': self._pageSummaryOnLoadCheckButton.get_active(),
496            'structuralNavigationEnabled': self._structuralNavigationCheckButton.get_active(),
497            'structNavTriggersFocusMode': self._autoFocusModeStructNavCheckButton.get_active(),
498            'caretNavigationEnabled': self._controlCaretNavigationCheckButton.get_active(),
499            'caretNavTriggersFocusMode': self._autoFocusModeCaretNavCheckButton.get_active(),
500            'nativeNavTriggersFocusMode': self._autoFocusModeNativeNavCheckButton.get_active(),
501            'speakCellCoordinates': self._speakCellCoordinatesCheckButton.get_active(),
502            'layoutMode': self._layoutModeCheckButton.get_active(),
503            'speakCellSpan': self._speakCellSpanCheckButton.get_active(),
504            'speakCellHeaders': self._speakCellHeadersCheckButton.get_active(),
505            'skipBlankCells': self._skipBlankCellsCheckButton.get_active()
506        }
507
508    def skipObjectEvent(self, event):
509        """Returns True if this object event should be skipped."""
510
511        if event.type.startswith('object:state-changed:focused') \
512           and event.detail1:
513            if event.source.getRole() == pyatspi.ROLE_LINK:
514                return False
515
516        if event.type.startswith('object:children-changed'):
517            try:
518                role = event.any_data.getRole()
519            except:
520                pass
521            else:
522                if role == pyatspi.ROLE_DIALOG:
523                    return False
524
525        return super().skipObjectEvent(event)
526
527    def presentationInterrupt(self):
528        super().presentationInterrupt()
529        msg = "WEB: Flushing live region messages"
530        debug.println(debug.LEVEL_INFO, msg, True)
531        self.liveRegionManager.flushMessages()
532
533    def consumesKeyboardEvent(self, keyboardEvent):
534        """Returns True if the script will consume this keyboard event."""
535
536        # We need to do this here. Orca caret and structural navigation
537        # often result in the user being repositioned without our getting
538        # a corresponding AT-SPI event. Without an AT-SPI event, script.py
539        # won't know to dump the generator cache. See bgo#618827.
540        self.generatorCache = {}
541
542        self._lastMouseButtonContext = None, -1
543
544        handler = self.keyBindings.getInputHandler(keyboardEvent)
545        if handler and self.caretNavigation.handles_navigation(handler):
546            consumes = self.useCaretNavigationModel(keyboardEvent)
547            self._lastCommandWasCaretNav = consumes
548            self._lastCommandWasStructNav = False
549            self._lastCommandWasMouseButton = False
550            return consumes
551
552        if handler and handler.function in self.structuralNavigation.functions:
553            consumes = self.useStructuralNavigationModel()
554            self._lastCommandWasCaretNav = False
555            self._lastCommandWasStructNav = consumes
556            self._lastCommandWasMouseButton = False
557            return consumes
558
559        if handler and handler.function in self.liveRegionManager.functions:
560            # This is temporary.
561            consumes = self.useStructuralNavigationModel()
562            self._lastCommandWasCaretNav = False
563            self._lastCommandWasStructNav = consumes
564            self._lastCommandWasMouseButton = False
565            return consumes
566
567        if not keyboardEvent.isModifierKey():
568            self._lastCommandWasCaretNav = False
569            self._lastCommandWasStructNav = False
570            self._lastCommandWasMouseButton = False
571
572        return super().consumesKeyboardEvent(keyboardEvent)
573
574    def getEnabledKeyBindings(self):
575        all = super().getEnabledKeyBindings()
576        ret = []
577        for b in all:
578            if b.handler and self.caretNavigation.handles_navigation(b.handler):
579                if self.useCaretNavigationModel(None):
580                    ret.append(b)
581            elif b.handler and b.handler.function in self.structuralNavigation.functions:
582                if self.useStructuralNavigationModel():
583                    ret.append(b)
584            elif b.handler and b.handler.function in self.liveRegionManager.functions:
585                # This is temporary.
586                if self.useStructuralNavigationModel():
587                    ret.append(b)
588            else:
589                ret.append(b)
590        return ret
591
592    def consumesBrailleEvent(self, brailleEvent):
593        """Returns True if the script will consume this braille event."""
594
595        self._lastCommandWasCaretNav = False
596        self._lastCommandWasStructNav = False
597        self._lastCommandWasMouseButton = False
598        return super().consumesBrailleEvent(brailleEvent)
599
600    # TODO - JD: This needs to be moved out of the scripts.
601    def textLines(self, obj, offset=None):
602        """Creates a generator that can be used to iterate document content."""
603
604        if not self.utilities.inDocumentContent():
605            msg = "WEB: textLines called for non-document content %s" % obj
606            debug.println(debug.LEVEL_INFO, msg, True)
607            super().textLines(obj, offset)
608            return
609
610        self._sayAllIsInterrupted = False
611
612        sayAllStyle = _settingsManager.getSetting('sayAllStyle')
613        sayAllBySentence = sayAllStyle == settings.SAYALL_STYLE_SENTENCE
614        if offset is None:
615            obj, characterOffset = self.utilities.getCaretContext()
616        else:
617            characterOffset = offset
618        priorObj, priorOffset = self.utilities.getPriorContext()
619
620        # TODO - JD: This is sad, but it's better than the old, broken
621        # clumpUtterances(). We really need to fix the speechservers'
622        # SayAll support. In the meantime, the generators should be
623        # providing one ACSS per string.
624        def _parseUtterances(utterances):
625            elements, voices = [], []
626            for u in utterances:
627                if isinstance(u, list):
628                    e, v = _parseUtterances(u)
629                    elements.extend(e)
630                    voices.extend(v)
631                elif isinstance(u, str):
632                    elements.append(u)
633                elif isinstance(u, ACSS):
634                    voices.append(u)
635            return elements, voices
636
637        self._inSayAll = True
638        done = False
639        while not done:
640            if sayAllBySentence:
641                contents = self.utilities.getSentenceContentsAtOffset(obj, characterOffset)
642            else:
643                contents = self.utilities.getLineContentsAtOffset(obj, characterOffset)
644            self._sayAllContents = contents
645            for i, content in enumerate(contents):
646                obj, startOffset, endOffset, text = content
647                msg = "WEB SAY ALL CONTENT: %i. %s '%s' (%i-%i)" % (i, obj, text, startOffset, endOffset)
648                debug.println(debug.LEVEL_INFO, msg, True)
649
650                if self.utilities.isInferredLabelForContents(content, contents):
651                    continue
652
653                if startOffset == endOffset:
654                    continue
655
656                if self.utilities.isLabellingInteractiveElement(obj):
657                    continue
658
659                if self.utilities.isLinkAncestorOfImageInContents(obj, contents):
660                    continue
661
662                utterances = self.speechGenerator.generateContents(
663                    [content], eliminatePauses=True, priorObj=priorObj)
664                priorObj = obj
665
666                elements, voices = _parseUtterances(utterances)
667                if len(elements) != len(voices):
668                    continue
669
670                for i, element in enumerate(elements):
671                    context = speechserver.SayAllContext(
672                        obj, element, startOffset, endOffset)
673                    msg = "WEB %s" % context
674                    debug.println(debug.LEVEL_INFO, msg, True)
675                    self._sayAllContexts.append(context)
676                    eventsynthesizer.scrollIntoView(obj, startOffset, endOffset)
677                    yield [context, voices[i]]
678
679            lastObj, lastOffset = contents[-1][0], contents[-1][2]
680            obj, characterOffset = self.utilities.findNextCaretInOrder(lastObj, lastOffset - 1)
681            if obj == lastObj and characterOffset <= lastOffset:
682                obj, characterOffset = self.utilities.findNextCaretInOrder(lastObj, lastOffset)
683            if obj == lastObj and characterOffset <= lastOffset:
684                msg = "WEB: Cycle within object detected in textLines. Last: %s, %i Next: %s, %i" \
685                    % (lastObj, lastOffset, obj, characterOffset)
686                debug.println(debug.LEVEL_INFO, msg, True)
687                break
688
689            done = obj is None
690
691        self._inSayAll = False
692        self._sayAllContents = []
693        self._sayAllContexts = []
694
695        msg = "WEB: textLines complete. Verifying SayAll status"
696        debug.println(debug.LEVEL_INFO, msg, True)
697        self.inSayAll()
698
699    def presentFindResults(self, obj, offset):
700        """Updates the context and presents the find results if appropriate."""
701
702        text = self.utilities.queryNonEmptyText(obj)
703        if not (text and text.getNSelections() > 0):
704            return
705
706        document = self.utilities.getDocumentForObject(obj)
707        if not document:
708            return
709
710        context = self.utilities.getCaretContext(documentFrame=document)
711        start, end = text.getSelection(0)
712        offset = max(offset, start)
713        self.utilities.setCaretContext(obj, offset, documentFrame=document)
714        if end - start < _settingsManager.getSetting('findResultsMinimumLength'):
715            return
716
717        verbosity = _settingsManager.getSetting('findResultsVerbosity')
718        if verbosity == settings.FIND_SPEAK_NONE:
719            return
720
721        if self._madeFindAnnouncement \
722           and verbosity == settings.FIND_SPEAK_IF_LINE_CHANGED \
723           and self.utilities.contextsAreOnSameLine(context, (obj, offset)):
724            return
725
726        contents = self.utilities.getLineContentsAtOffset(obj, offset)
727        self.speakContents(contents)
728        self.updateBraille(obj)
729
730        resultsCount = self.utilities.getFindResultsCount()
731        if resultsCount:
732            self.presentMessage(resultsCount)
733
734        self._madeFindAnnouncement = True
735
736    def sayAll(self, inputEvent, obj=None, offset=None):
737        """Speaks the contents of the document beginning with the present
738        location.  Overridden in this script because the sayAll could have
739        been started on an object without text (such as an image).
740        """
741
742        if not self.utilities.inDocumentContent():
743            msg = "WEB: SayAll called for non-document content %s" % obj
744            debug.println(debug.LEVEL_INFO, msg, True)
745            return super().sayAll(inputEvent, obj, offset)
746
747        obj = obj or orca_state.locusOfFocus
748        msg = "WEB: SayAll called for document content %s" % obj
749        debug.println(debug.LEVEL_INFO, msg, True)
750        speech.sayAll(self.textLines(obj, offset), self.__sayAllProgressCallback)
751        return True
752
753    def _rewindSayAll(self, context, minCharCount=10):
754        if not self.utilities.inDocumentContent():
755            return super()._rewindSayAll(context, minCharCount)
756
757        if not _settingsManager.getSetting('rewindAndFastForwardInSayAll'):
758            return False
759
760        try:
761            obj, start, end, string = self._sayAllContents[0]
762        except IndexError:
763            return False
764
765        orca.setLocusOfFocus(None, obj, notifyScript=False)
766        self.utilities.setCaretContext(obj, start)
767
768        prevObj, prevOffset = self.utilities.findPreviousCaretInOrder(obj, start)
769        self.sayAll(None, prevObj, prevOffset)
770        return True
771
772    def _fastForwardSayAll(self, context):
773        if not self.utilities.inDocumentContent():
774            return super()._fastForwardSayAll(context)
775
776        if not _settingsManager.getSetting('rewindAndFastForwardInSayAll'):
777            return False
778
779        try:
780            obj, start, end, string = self._sayAllContents[-1]
781        except IndexError:
782            return False
783
784        orca.setLocusOfFocus(None, obj, notifyScript=False)
785        self.utilities.setCaretContext(obj, end)
786
787        nextObj, nextOffset = self.utilities.findNextCaretInOrder(obj, end)
788        self.sayAll(None, nextObj, nextOffset)
789        return True
790
791    def __sayAllProgressCallback(self, context, progressType):
792        if not self.utilities.inDocumentContent():
793            super().__sayAllProgressCallback(context, progressType)
794            return
795
796        if progressType == speechserver.SayAllContext.INTERRUPTED:
797            if isinstance(orca_state.lastInputEvent, input_event.KeyboardEvent):
798                self._sayAllIsInterrupted = True
799                lastKey = orca_state.lastInputEvent.event_string
800                if lastKey == "Down" and self._fastForwardSayAll(context):
801                    return
802                elif lastKey == "Up" and self._rewindSayAll(context):
803                    return
804                elif not self._lastCommandWasStructNav:
805                    orca.emitRegionChanged(context.obj, context.currentOffset)
806                    self.utilities.setCaretPosition(context.obj, context.currentOffset)
807                    self.updateBraille(context.obj)
808
809            self._inSayAll = False
810            self._sayAllContents = []
811            self._sayAllContexts = []
812            return
813
814        orca.setLocusOfFocus(None, context.obj, notifyScript=False)
815        orca.emitRegionChanged(
816            context.obj, context.currentOffset, context.currentEndOffset, orca.SAY_ALL)
817        self.utilities.setCaretContext(context.obj, context.currentOffset)
818
819    def inFocusMode(self):
820        """ Returns True if we're in focus mode."""
821
822        return self._inFocusMode
823
824    def focusModeIsSticky(self):
825        """Returns True if we're in 'sticky' focus mode."""
826
827        return self._focusModeIsSticky
828
829    def browseModeIsSticky(self):
830        """Returns True if we're in 'sticky' browse mode."""
831
832        return self._browseModeIsSticky
833
834    def useFocusMode(self, obj, prevObj=None):
835        """Returns True if we should use focus mode in obj."""
836
837        if self._focusModeIsSticky:
838            msg = "WEB: Using focus mode because focus mode is sticky"
839            debug.println(debug.LEVEL_INFO, msg, True)
840            return True
841
842        if self._browseModeIsSticky:
843            msg = "WEB: Not using focus mode because browse mode is sticky"
844            debug.println(debug.LEVEL_INFO, msg, True)
845            return False
846
847        if self.inSayAll():
848            msg = "WEB: Not using focus mode because we're in SayAll."
849            debug.println(debug.LEVEL_INFO, msg, True)
850            return False
851
852        if not _settingsManager.getSetting('structNavTriggersFocusMode') \
853           and self._lastCommandWasStructNav:
854            msg = "WEB: Not using focus mode due to struct nav settings"
855            debug.println(debug.LEVEL_INFO, msg, True)
856            return False
857
858        if prevObj and self.utilities.isDead(prevObj):
859            prevObj = None
860
861        if not _settingsManager.getSetting('caretNavTriggersFocusMode') \
862           and self._lastCommandWasCaretNav \
863           and not self.utilities.isNavigableToolTipDescendant(prevObj):
864            msg = "WEB: Not using focus mode due to caret nav settings"
865            debug.println(debug.LEVEL_INFO, msg, True)
866            return False
867
868        if not _settingsManager.getSetting('nativeNavTriggersFocusMode') \
869           and not (self._lastCommandWasStructNav or self._lastCommandWasCaretNav):
870            msg = "WEB: Not changing focus/browse mode due to native nav settings"
871            debug.println(debug.LEVEL_INFO, msg, True)
872            return self._inFocusMode
873
874        if self.utilities.isFocusModeWidget(obj):
875            msg = "WEB: Using focus mode because %s is a focus mode widget" % obj
876            debug.println(debug.LEVEL_INFO, msg, True)
877            return True
878
879        doNotToggle = [pyatspi.ROLE_LINK, pyatspi.ROLE_RADIO_BUTTON]
880        if self._inFocusMode and obj and obj.getRole() in doNotToggle \
881           and self.utilities.lastInputEventWasUnmodifiedArrow():
882            msg = "WEB: Staying in focus mode due to arrowing in role of %s" % obj
883            debug.println(debug.LEVEL_INFO, msg, True)
884            return True
885
886        if self._inFocusMode and self.utilities.isWebAppDescendant(obj):
887            if self.utilities.forceBrowseModeForWebAppDescendant(obj):
888                msg = "WEB: Forcing browse mode for web app descendant %s" % obj
889                debug.println(debug.LEVEL_INFO, msg, True)
890                return False
891
892            msg = "WEB: Staying in focus mode because we're inside a web application"
893            debug.println(debug.LEVEL_INFO, msg, True)
894            return True
895
896        msg = "WEB: Not using focus mode for %s due to lack of cause" % obj
897        debug.println(debug.LEVEL_INFO, msg, True)
898        return False
899
900    def speakContents(self, contents, **args):
901        """Speaks the specified contents."""
902
903        utterances = self.speechGenerator.generateContents(contents, **args)
904        speech.speak(utterances)
905
906    def sayCharacter(self, obj):
907        """Speaks the character at the current caret position."""
908
909        if not self._lastCommandWasCaretNav \
910           and not self.utilities.isContentEditableWithEmbeddedObjects(obj):
911            super().sayCharacter(obj)
912            return
913
914        document = self.utilities.getTopLevelDocumentForObject(obj)
915        obj, offset = self.utilities.getCaretContext(documentFrame=document)
916        if not obj:
917            return
918
919        contents = None
920        if self.utilities.treatAsEndOfLine(obj, offset) and "Text" in pyatspi.listInterfaces(obj):
921            char = obj.queryText().getText(offset, offset + 1)
922            if char == self.EMBEDDED_OBJECT_CHARACTER:
923                char = ""
924            contents = [[obj, offset, offset + 1, char]]
925        else:
926            contents = self.utilities.getCharacterContentsAtOffset(obj, offset)
927
928        if not contents:
929            return
930
931        obj, start, end, string = contents[0]
932        if start > 0:
933            string = string or "\n"
934
935        if string:
936            self.speakMisspelledIndicator(obj, start)
937            self.speakCharacter(string)
938        else:
939            self.speakContents(contents)
940
941        self.pointOfReference["lastTextUnitSpoken"] = "char"
942
943    def sayWord(self, obj):
944        """Speaks the word at the current caret position."""
945
946        isEditable = self.utilities.isContentEditableWithEmbeddedObjects(obj)
947        if not self._lastCommandWasCaretNav and not isEditable:
948            super().sayWord(obj)
949            return
950
951        document = self.utilities.getTopLevelDocumentForObject(obj)
952        obj, offset = self.utilities.getCaretContext(documentFrame=document)
953        keyString, mods = self.utilities.lastKeyAndModifiers()
954        if keyString == "Right":
955            offset -= 1
956
957        wordContents = self.utilities.getWordContentsAtOffset(obj, offset, useCache=True)
958        textObj, startOffset, endOffset, word = wordContents[0]
959        self.speakMisspelledIndicator(textObj, startOffset)
960        self.speakContents(wordContents)
961        self.pointOfReference["lastTextUnitSpoken"] = "word"
962
963    def sayLine(self, obj):
964        """Speaks the line at the current caret position."""
965
966        isEditable = self.utilities.isContentEditableWithEmbeddedObjects(obj)
967        if not (self._lastCommandWasCaretNav or self._lastCommandWasStructNav) and not isEditable:
968            super().sayLine(obj)
969            return
970
971        document = self.utilities.getTopLevelDocumentForObject(obj)
972        priorObj = None
973        if self._lastCommandWasCaretNav or isEditable:
974            priorObj, priorOffset = self.utilities.getPriorContext(documentFrame=document)
975
976        obj, offset = self.utilities.getCaretContext(documentFrame=document)
977        contents = self.utilities.getLineContentsAtOffset(obj, offset, useCache=True)
978        self.speakContents(contents, priorObj=priorObj)
979        self.pointOfReference["lastTextUnitSpoken"] = "line"
980
981    def presentObject(self, obj, **args):
982        if not self.utilities.inDocumentContent(obj):
983            super().presentObject(obj, **args)
984            return
985
986        if obj.getRole() == pyatspi.ROLE_STATUS_BAR:
987            super().presentObject(obj, **args)
988            return
989
990        priorObj = args.get("priorObj")
991        if self._lastCommandWasCaretNav or args.get("includeContext"):
992            priorObj, priorOffset = self.utilities.getPriorContext()
993            args["priorObj"] = priorObj
994
995        if obj.getRole() == pyatspi.ROLE_ENTRY:
996            utterances = self.speechGenerator.generateSpeech(obj, **args)
997            speech.speak(utterances)
998            self.updateBraille(obj)
999            return
1000
1001        # We shouldn't use cache in this method, because if the last thing we presented
1002        # included this object and offset (e.g. a Say All or Mouse Review), we're in
1003        # danger of presented irrelevant context.
1004        useCache = False
1005        offset = args.get("offset", 0)
1006        contents = self.utilities.getObjectContentsAtOffset(obj, offset, useCache)
1007        self.displayContents(contents)
1008        self.speakContents(contents, **args)
1009
1010    def updateBrailleForNewCaretPosition(self, obj):
1011        """Try to reposition the cursor without having to do a full update."""
1012
1013        text = self.utilities.queryNonEmptyText(obj)
1014        if text and self.EMBEDDED_OBJECT_CHARACTER in text.getText(0, -1):
1015            self.updateBraille(obj)
1016            return
1017
1018        super().updateBrailleForNewCaretPosition(obj)
1019
1020    def updateBraille(self, obj, **args):
1021        """Updates the braille display to show the given object."""
1022
1023        if not _settingsManager.getSetting('enableBraille') \
1024           and not _settingsManager.getSetting('enableBrailleMonitor'):
1025            debug.println(debug.LEVEL_INFO, "BRAILLE: disabled", True)
1026            return
1027
1028        document = args.get("documentFrame", self.utilities.getTopLevelDocumentForObject(obj))
1029        if not document:
1030            msg = "WEB: updating braille for non-document object %s" % obj
1031            debug.println(debug.LEVEL_INFO, msg, True)
1032            super().updateBraille(obj, **args)
1033            return
1034
1035        isContentEditable = self.utilities.isContentEditableWithEmbeddedObjects(obj)
1036
1037        if not self._lastCommandWasCaretNav \
1038           and not self._lastCommandWasStructNav \
1039           and not isContentEditable \
1040           and not self.utilities.isPlainText() \
1041           and not self.utilities.lastInputEventWasCaretNavWithSelection():
1042            msg = "WEB: updating braille for unhandled navigation type %s" % obj
1043            debug.println(debug.LEVEL_INFO, msg, True)
1044            super().updateBraille(obj, **args)
1045            return
1046
1047        obj, offset = self.utilities.getCaretContext(documentFrame=document, getZombieReplicant=True)
1048        if offset > 0 and isContentEditable:
1049            text = self.utilities.queryNonEmptyText(obj)
1050            if text:
1051                offset = min(offset, text.characterCount)
1052
1053        contents = self.utilities.getLineContentsAtOffset(obj, offset)
1054        self.displayContents(contents, documentFrame=document)
1055
1056    def displayContents(self, contents, **args):
1057        """Displays contents in braille."""
1058
1059        if not _settingsManager.getSetting('enableBraille') \
1060           and not _settingsManager.getSetting('enableBrailleMonitor'):
1061            debug.println(debug.LEVEL_INFO, "BRAILLE: disabled", True)
1062            return
1063
1064        line = self.getNewBrailleLine(clearBraille=True, addLine=True)
1065        document = args.get("documentFrame")
1066        contents = self.brailleGenerator.generateContents(contents, documentFrame=document)
1067        if not contents:
1068            return
1069
1070        regions, focusedRegion = contents
1071        for region in regions:
1072            self.addBrailleRegionsToLine(region, line)
1073
1074        if line.regions:
1075            line.regions[-1].string = line.regions[-1].string.rstrip(" ")
1076
1077        self.setBrailleFocus(focusedRegion, getLinkMask=False)
1078        self.refreshBraille(panToCursor=True, getLinkMask=False)
1079
1080    def panBrailleLeft(self, inputEvent=None, panAmount=0):
1081        """Pans braille to the left."""
1082
1083        if self.flatReviewContext \
1084           or not self.utilities.inDocumentContent() \
1085           or not self.isBrailleBeginningShowing():
1086            super().panBrailleLeft(inputEvent, panAmount)
1087            return
1088
1089        contents = self.utilities.getPreviousLineContents()
1090        if not contents:
1091            return
1092
1093        obj, start, end, string = contents[0]
1094        self.utilities.setCaretPosition(obj, start)
1095        self.updateBraille(obj)
1096
1097        # Hack: When panning to the left in a document, we want to start at
1098        # the right/bottom of each new object. For now, we'll pan there.
1099        # When time permits, we'll give our braille code some smarts.
1100        while self.panBrailleInDirection(panToLeft=False):
1101            pass
1102
1103        self.refreshBraille(False)
1104        return True
1105
1106    def panBrailleRight(self, inputEvent=None, panAmount=0):
1107        """Pans braille to the right."""
1108
1109        if self.flatReviewContext \
1110           or not self.utilities.inDocumentContent() \
1111           or not self.isBrailleEndShowing():
1112            super().panBrailleRight(inputEvent, panAmount)
1113            return
1114
1115        contents = self.utilities.getNextLineContents()
1116        if not contents:
1117            return
1118
1119        obj, start, end, string = contents[0]
1120        self.utilities.setCaretPosition(obj, start)
1121        self.updateBraille(obj)
1122
1123        # Hack: When panning to the right in a document, we want to start at
1124        # the left/top of each new object. For now, we'll pan there. When time
1125        # permits, we'll give our braille code some smarts.
1126        while self.panBrailleInDirection(panToLeft=True):
1127            pass
1128
1129        self.refreshBraille(False)
1130        return True
1131
1132    def useCaretNavigationModel(self, keyboardEvent):
1133        """Returns True if caret navigation should be used."""
1134
1135        if not _settingsManager.getSetting('caretNavigationEnabled') \
1136           or self._inFocusMode:
1137            return False
1138
1139        if not self.utilities.inDocumentContent():
1140            return False
1141
1142        if keyboardEvent and keyboardEvent.modifiers & keybindings.SHIFT_MODIFIER_MASK:
1143            return False
1144
1145        return True
1146
1147    def useStructuralNavigationModel(self):
1148        """Returns True if structural navigation should be used."""
1149
1150        if not self.structuralNavigation.enabled or self._inFocusMode:
1151            return False
1152
1153        if not self.utilities.inDocumentContent():
1154            return False
1155
1156        return True
1157
1158    def getTextLineAtCaret(self, obj, offset=None, startOffset=None, endOffset=None):
1159        """To-be-removed. Returns the string, caretOffset, startOffset."""
1160
1161        if self._inFocusMode or not self.utilities.inDocumentContent(obj) \
1162           or self.utilities.isFocusModeWidget(obj):
1163            return super().getTextLineAtCaret(obj, offset, startOffset, endOffset)
1164
1165        text = self.utilities.queryNonEmptyText(obj)
1166        if offset is None:
1167            try:
1168                offset = max(0, text.caretOffset)
1169            except:
1170                offset = 0
1171
1172        if text and startOffset is not None and endOffset is not None:
1173            return text.getText(startOffset, endOffset), offset, startOffset
1174
1175        contextObj, contextOffset = self.utilities.getCaretContext(documentFrame=None)
1176        if contextObj == obj:
1177            caretOffset = contextOffset
1178        else:
1179            caretOffset = offset
1180
1181        contents = self.utilities.getLineContentsAtOffset(obj, offset)
1182        contents = list(filter(lambda x: x[0] == obj, contents))
1183        if len(contents) == 1:
1184            index = 0
1185        else:
1186            index = self.utilities.findObjectInContents(obj, offset, contents)
1187
1188        if index > -1:
1189            candidate, startOffset, endOffset, string = contents[index]
1190            if not self.EMBEDDED_OBJECT_CHARACTER in string:
1191                return string, caretOffset, startOffset
1192
1193        return "", 0, 0
1194
1195    def moveToMouseOver(self, inputEvent):
1196        """Moves the context to/from the mouseover which has just appeared."""
1197
1198        if not self._lastMouseOverObject:
1199            self.presentMessage(messages.MOUSE_OVER_NOT_FOUND)
1200            return
1201
1202        if self._inMouseOverObject:
1203            x, y = self.oldMouseCoordinates
1204            eventsynthesizer.routeToPoint(x, y)
1205            self.restorePreMouseOverContext()
1206            return
1207
1208        obj = self._lastMouseOverObject
1209        obj, offset = self.utilities.findFirstCaretContext(obj, 0)
1210        if not obj:
1211            return
1212
1213        if obj.getState().contains(pyatspi.STATE_FOCUSABLE):
1214            obj.queryComponent().grabFocus()
1215
1216        contents = self.utilities.getObjectContentsAtOffset(obj, offset)
1217        self.utilities.setCaretPosition(obj, offset)
1218        self.speakContents(contents)
1219        self.updateBraille(obj)
1220        self._inMouseOverObject = True
1221
1222    def restorePreMouseOverContext(self):
1223        """Cleans things up after a mouse-over object has been hidden."""
1224
1225        obj, offset = self._preMouseOverContext
1226        self.utilities.setCaretPosition(obj, offset)
1227        self.speakContents(self.utilities.getObjectContentsAtOffset(obj, offset))
1228        self.updateBraille(obj)
1229        self._inMouseOverObject = False
1230        self._lastMouseOverObject = None
1231
1232    def enableStickyBrowseMode(self, inputEvent, forceMessage=False):
1233        if not self._browseModeIsSticky or forceMessage:
1234            self.presentMessage(messages.MODE_BROWSE_IS_STICKY)
1235
1236        self._inFocusMode = False
1237        self._focusModeIsSticky = False
1238        self._browseModeIsSticky = True
1239        self.refreshKeyGrabs()
1240
1241    def enableStickyFocusMode(self, inputEvent, forceMessage=False):
1242        if not self._focusModeIsSticky or forceMessage:
1243            self.presentMessage(messages.MODE_FOCUS_IS_STICKY)
1244
1245        self._inFocusMode = True
1246        self._focusModeIsSticky = True
1247        self._browseModeIsSticky = False
1248        self.refreshKeyGrabs()
1249
1250    def toggleLayoutMode(self, inputEvent):
1251        layoutMode = not _settingsManager.getSetting('layoutMode')
1252        if layoutMode:
1253            self.presentMessage(messages.MODE_LAYOUT)
1254        else:
1255            self.presentMessage(messages.MODE_OBJECT)
1256        _settingsManager.setSetting('layoutMode', layoutMode)
1257
1258    def togglePresentationMode(self, inputEvent, documentFrame=None):
1259        [obj, characterOffset] = self.utilities.getCaretContext(documentFrame)
1260        if self._inFocusMode:
1261            try:
1262                parentRole = obj.parent.getRole()
1263            except:
1264                parentRole = None
1265            if parentRole == pyatspi.ROLE_LIST_BOX:
1266                self.utilities.setCaretContext(obj.parent, -1)
1267            elif parentRole == pyatspi.ROLE_MENU:
1268                self.utilities.setCaretContext(obj.parent.parent, -1)
1269            if not self._loadingDocumentContent:
1270                self.presentMessage(messages.MODE_BROWSE)
1271        else:
1272            if not self.utilities.grabFocusWhenSettingCaret(obj) \
1273               and (self._lastCommandWasCaretNav \
1274                    or self._lastCommandWasStructNav \
1275                    or inputEvent):
1276                self.utilities.grabFocus(obj)
1277
1278            self.presentMessage(messages.MODE_FOCUS)
1279        self._inFocusMode = not self._inFocusMode
1280        self._focusModeIsSticky = False
1281        self._browseModeIsSticky = False
1282        self.refreshKeyGrabs()
1283
1284    def locusOfFocusChanged(self, event, oldFocus, newFocus):
1285        """Handles changes of focus of interest to the script."""
1286
1287        if newFocus and self.utilities.isZombie(newFocus):
1288            msg = "WEB: New focus is Zombie: %s" % newFocus
1289            debug.println(debug.LEVEL_INFO, msg, True)
1290            return True
1291
1292        document = self.utilities.getTopLevelDocumentForObject(newFocus)
1293        if not document and self.utilities.isDocument(newFocus):
1294            document = newFocus
1295
1296        if not document:
1297            msg = "WEB: Locus of focus changed to non-document obj"
1298            self._madeFindAnnouncement = False
1299            self._inFocusMode = False
1300            debug.println(debug.LEVEL_INFO, msg, True)
1301            self.refreshKeyGrabs()
1302            return False
1303
1304        if self.flatReviewContext:
1305            self.toggleFlatReviewMode()
1306
1307        caretOffset = 0
1308        if self.utilities.inFindContainer(oldFocus) \
1309           or (self.utilities.isDocument(newFocus) and oldFocus == orca_state.activeWindow):
1310            contextObj, contextOffset = self.utilities.getCaretContext(documentFrame=document)
1311            if contextObj and not self.utilities.isZombie(contextObj):
1312                newFocus, caretOffset = contextObj, contextOffset
1313
1314        if newFocus.getRole() in [pyatspi.ROLE_UNKNOWN, pyatspi.ROLE_REDUNDANT_OBJECT]:
1315            msg = "WEB: Event source has bogus role. Likely browser bug."
1316            debug.println(debug.LEVEL_INFO, msg, True)
1317            newFocus, offset = self.utilities.findFirstCaretContext(newFocus, 0)
1318
1319        text = self.utilities.queryNonEmptyText(newFocus)
1320        if text and (0 <= text.caretOffset <= text.characterCount):
1321            caretOffset = text.caretOffset
1322
1323        self.utilities.setCaretContext(newFocus, caretOffset, document)
1324        self.updateBraille(newFocus, documentFrame=document)
1325        orca.emitRegionChanged(newFocus, caretOffset)
1326
1327        if self._lastCommandWasMouseButton and event \
1328             and event.type.startswith("object:text-caret-moved"):
1329            msg = "WEB: Last input event was mouse button. Generating line contents."
1330            debug.println(debug.LEVEL_INFO, msg, True)
1331            contents = self.utilities.getLineContentsAtOffset(newFocus, caretOffset)
1332            utterances = self.speechGenerator.generateContents(contents, priorObj=oldFocus)
1333        elif self.utilities.isContentEditableWithEmbeddedObjects(newFocus) \
1334           and (self._lastCommandWasCaretNav or self._lastCommandWasStructNav) \
1335           and not (newFocus.getRole() == pyatspi.ROLE_TABLE_CELL and newFocus.name):
1336            msg = "WEB: New focus %s content editable. Generating line contents." % newFocus
1337            debug.println(debug.LEVEL_INFO, msg, True)
1338            contents = self.utilities.getLineContentsAtOffset(newFocus, caretOffset)
1339            utterances = self.speechGenerator.generateContents(contents)
1340        elif self.utilities.isAnchor(newFocus):
1341            msg = "WEB: New focus %s is anchor. Generating line contents." % newFocus
1342            debug.println(debug.LEVEL_INFO, msg, True)
1343            contents = self.utilities.getLineContentsAtOffset(newFocus, 0)
1344            utterances = self.speechGenerator.generateContents(contents)
1345        elif self.utilities.lastInputEventWasPageNav() and not self.utilities.getTable(newFocus):
1346            msg = "WEB: New focus %s was scrolled to. Generating line contents." % newFocus
1347            debug.println(debug.LEVEL_INFO, msg, True)
1348            contents = self.utilities.getLineContentsAtOffset(newFocus, caretOffset)
1349            utterances = self.speechGenerator.generateContents(contents)
1350        elif self.utilities.isFocusedWithMathChild(newFocus):
1351            msg = "WEB: New focus %s has math child. Generating line contents." % newFocus
1352            debug.println(debug.LEVEL_INFO, msg, True)
1353            contents = self.utilities.getLineContentsAtOffset(newFocus, caretOffset)
1354            utterances = self.speechGenerator.generateContents(contents)
1355        elif newFocus.getRole() == pyatspi.ROLE_HEADING:
1356            msg = "WEB: New focus %s is heading. Generating object contents." % newFocus
1357            debug.println(debug.LEVEL_INFO, msg, True)
1358            contents = self.utilities.getObjectContentsAtOffset(newFocus, 0)
1359            utterances = self.speechGenerator.generateContents(contents)
1360        elif self.utilities.caretMovedToSamePageFragment(event, oldFocus):
1361            msg = "WEB: Event source %s is same page fragment. Generating line contents." % event.source
1362            debug.println(debug.LEVEL_INFO, msg, True)
1363            contents = self.utilities.getLineContentsAtOffset(newFocus, 0)
1364            utterances = self.speechGenerator.generateContents(contents)
1365        elif self.utilities.lastInputEventWasLineNav() and self.utilities.isZombie(oldFocus):
1366            msg = "WEB: Last input event was line nav; oldFocus is zombie. Generating line contents."
1367            debug.println(debug.LEVEL_INFO, msg, True)
1368            contents = self.utilities.getLineContentsAtOffset(newFocus, caretOffset)
1369            utterances = self.speechGenerator.generateContents(contents)
1370        elif self.utilities.lastInputEventWasLineNav() and event \
1371             and event.type.startswith("object:children-changed"):
1372            msg = "WEB: Last input event was line nav and children changed. Generating line contents."
1373            debug.println(debug.LEVEL_INFO, msg, True)
1374            contents = self.utilities.getLineContentsAtOffset(newFocus, caretOffset)
1375            utterances = self.speechGenerator.generateContents(contents)
1376        else:
1377            msg = "WEB: New focus %s is not a special case. Generating speech." % newFocus
1378            debug.println(debug.LEVEL_INFO, msg, True)
1379            utterances = self.speechGenerator.generateSpeech(newFocus, priorObj=oldFocus)
1380
1381        speech.speak(utterances)
1382        self._saveFocusedObjectInfo(newFocus)
1383
1384        if self.utilities.inTopLevelWebApp(newFocus) and not self._browseModeIsSticky:
1385            announce = not self.utilities.inDocumentContent(oldFocus)
1386            self.enableStickyFocusMode(None, announce)
1387            return True
1388
1389        if not self._focusModeIsSticky \
1390           and not self._browseModeIsSticky \
1391           and self.useFocusMode(newFocus, oldFocus) != self._inFocusMode:
1392            self.togglePresentationMode(None, document)
1393
1394        if not self.utilities.inDocumentContent(oldFocus):
1395            self.refreshKeyGrabs()
1396
1397        return True
1398
1399    def onActiveChanged(self, event):
1400        """Callback for object:state-changed:active accessibility events."""
1401
1402        if not self.utilities.inDocumentContent(event.source):
1403            msg = "WEB: Event source is not in document content"
1404            debug.println(debug.LEVEL_INFO, msg, True)
1405            return False
1406
1407        if not event.detail1:
1408            msg = "WEB: Ignoring because event source is now inactive"
1409            debug.println(debug.LEVEL_INFO, msg, True)
1410            return True
1411
1412        role = event.source.getRole()
1413        if role in [pyatspi.ROLE_DIALOG, pyatspi.ROLE_ALERT]:
1414            msg = "WEB: Event handled: Setting locusOfFocus to event source"
1415            debug.println(debug.LEVEL_INFO, msg, True)
1416            orca.setLocusOfFocus(event, event.source)
1417            return True
1418
1419        return False
1420
1421    def onActiveDescendantChanged(self, event):
1422        """Callback for object:active-descendant-changed accessibility events."""
1423
1424        if not self.utilities.inDocumentContent(event.source):
1425            msg = "WEB: Event source is not in document content"
1426            debug.println(debug.LEVEL_INFO, msg, True)
1427            return False
1428
1429        return True
1430
1431    def onBusyChanged(self, event):
1432        """Callback for object:state-changed:busy accessibility events."""
1433
1434        if event.detail1 and self._loadingDocumentContent:
1435            msg = "WEB: Ignoring: Already loading document content"
1436            debug.println(debug.LEVEL_INFO, msg, True)
1437            return True
1438
1439        if not self.utilities.inDocumentContent(event.source):
1440            msg = "WEB: Event source is not in document content"
1441            debug.println(debug.LEVEL_INFO, msg, True)
1442            return False
1443
1444        if event.source.getRole() != pyatspi.ROLE_DOCUMENT_WEB \
1445           and not self.utilities.isOrDescendsFrom(orca_state.locusOfFocus, event.source):
1446            msg = "WEB: Ignoring: Not document and not something we're in"
1447            debug.println(debug.LEVEL_INFO, msg, True)
1448            return True
1449
1450        self.structuralNavigation.clearCache()
1451
1452        if self.utilities.getDocumentForObject(event.source.parent):
1453            msg = "WEB: Ignoring: Event source is nested document"
1454            debug.println(debug.LEVEL_INFO, msg, True)
1455            return True
1456
1457        obj, offset = self.utilities.getCaretContext()
1458        if not obj or self.utilities.isZombie(obj):
1459            self.utilities.clearCaretContext()
1460
1461        shouldPresent = True
1462        if not self.utilities.isShowingOrVisible(event.source):
1463            shouldPresent = False
1464            msg = "WEB: Not presenting because source is not showing or visible"
1465            debug.println(debug.LEVEL_INFO, msg, True)
1466        elif not self.utilities.documentFrameURI(event.source):
1467            shouldPresent = False
1468            msg = "WEB: Not presenting because source lacks URI"
1469            debug.println(debug.LEVEL_INFO, msg, True)
1470        elif not event.detail1 and self._inFocusMode and not self.utilities.isZombie(obj):
1471            shouldPresent = False
1472            msg = "WEB: Not presenting due to focus mode for %s" % obj
1473            debug.println(debug.LEVEL_INFO, msg, True)
1474
1475        if not _settingsManager.getSetting('onlySpeakDisplayedText') and shouldPresent:
1476            if event.detail1:
1477                self.presentMessage(messages.PAGE_LOADING_START)
1478            elif event.source.name:
1479                msg = messages.PAGE_LOADING_END_NAMED % event.source.name
1480                self.presentMessage(msg, resetStyles=False)
1481            else:
1482                self.presentMessage(messages.PAGE_LOADING_END)
1483
1484        activeDocument = self.utilities.activeDocument()
1485        if activeDocument and activeDocument != event.source:
1486            msg = "WEB: Ignoring: Event source is not active document"
1487            debug.println(debug.LEVEL_INFO, msg, True)
1488            return True
1489
1490        self._loadingDocumentContent = event.detail1
1491        if event.detail1:
1492            return True
1493
1494        self.utilities.clearCachedObjects()
1495        if self.utilities.isDead(obj):
1496            obj = None
1497
1498        if not self.utilities.isDead(orca_state.locusOfFocus) \
1499           and not self.utilities.inDocumentContent(orca_state.locusOfFocus) \
1500           and orca_state.locusOfFocus.getState().contains(pyatspi.STATE_FOCUSED):
1501            msg = "WEB: Not presenting content, focus is outside of document"
1502            debug.println(debug.LEVEL_INFO, msg, True)
1503            return True
1504
1505        if _settingsManager.getSetting('pageSummaryOnLoad') and shouldPresent:
1506            obj = obj or event.source
1507            msg = "WEB: Getting page summary for obj %s" % obj
1508            debug.println(debug.LEVEL_INFO, msg, True)
1509            summary = self.utilities.getPageSummary(obj)
1510            if summary:
1511                self.presentMessage(summary)
1512
1513        obj, offset = self.utilities.getCaretContext()
1514
1515        try:
1516            sourceIsBusy = event.souce.getState().contains(pyatspi.STATE_BUSY)
1517        except:
1518            sourceIsBusy = False
1519
1520        if not sourceIsBusy and self.utilities.isTopLevelWebApp(event.source):
1521            msg = "WEB: Setting locusOfFocus to %s with sticky focus mode" % obj
1522            debug.println(debug.LEVEL_INFO, msg, True)
1523            orca.setLocusOfFocus(event, obj)
1524            self.enableStickyFocusMode(None, True)
1525            return True
1526
1527        if self.useFocusMode(obj) != self._inFocusMode:
1528            self.togglePresentationMode(None)
1529
1530        if not obj:
1531            msg = "WEB: Could not get caret context"
1532            debug.println(debug.LEVEL_INFO, msg, True)
1533            return True
1534
1535        if self.utilities.isFocusModeWidget(obj):
1536            msg = "WEB: Setting locus of focus to focusModeWidget %s" % obj
1537            debug.println(debug.LEVEL_INFO, msg, True)
1538            orca.setLocusOfFocus(event, obj)
1539            return True
1540
1541        state = obj.getState()
1542        if self.utilities.isLink(obj) and state.contains(pyatspi.STATE_FOCUSED):
1543            msg = "WEB: Setting locus of focus to focused link %s. No SayAll." % obj
1544            debug.println(debug.LEVEL_INFO, msg, True)
1545            orca.setLocusOfFocus(event, obj)
1546            return True
1547
1548        if offset > 0:
1549            msg = "WEB: Setting locus of focus to context obj %s. No SayAll" % obj
1550            debug.println(debug.LEVEL_INFO, msg, True)
1551            orca.setLocusOfFocus(event, obj)
1552            return True
1553
1554        try:
1555            focusState = orca_state.locusOfFocus.getState()
1556        except:
1557            inFocusedObject = False
1558        else:
1559            inFocusedObject = focusState.contains(pyatspi.STATE_FOCUSED)
1560
1561        if not inFocusedObject:
1562            msg = "WEB: Setting locus of focus to context obj %s (no notification)" % obj
1563            debug.println(debug.LEVEL_INFO, msg, True)
1564            orca.setLocusOfFocus(event, obj, False)
1565
1566        self.updateBraille(obj)
1567        if self.utilities.documentFragment(event.source):
1568            msg = "WEB: Not doing SayAll due to page fragment"
1569            debug.println(debug.LEVEL_INFO, msg, True)
1570        elif not _settingsManager.getSetting('sayAllOnLoad'):
1571            msg = "WEB: Not doing SayAll due to sayAllOnLoad being False"
1572            debug.println(debug.LEVEL_INFO, msg, True)
1573            self.speakContents(self.utilities.getLineContentsAtOffset(obj, offset))
1574        elif _settingsManager.getSetting('enableSpeech'):
1575            msg = "WEB: Doing SayAll"
1576            debug.println(debug.LEVEL_INFO, msg, True)
1577            self.sayAll(None)
1578        else:
1579            msg = "WEB: Not doing SayAll due to enableSpeech being False"
1580            debug.println(debug.LEVEL_INFO, msg, True)
1581
1582        return True
1583
1584    def onCaretMoved(self, event):
1585        """Callback for object:text-caret-moved accessibility events."""
1586
1587        self.utilities.sanityCheckActiveWindow()
1588
1589        if self.utilities.isZombie(event.source):
1590            msg = "WEB: Event source is Zombie"
1591            debug.println(debug.LEVEL_INFO, msg, True)
1592            return True
1593
1594        document = self.utilities.getTopLevelDocumentForObject(event.source)
1595        if not document:
1596            if self.utilities.eventIsBrowserUINoise(event):
1597                msg = "WEB: Ignoring event believed to be browser UI noise"
1598                debug.println(debug.LEVEL_INFO, msg, True)
1599                return True
1600
1601            if self.utilities.eventIsBrowserUIAutocompleteNoise(event):
1602                msg = "WEB: Ignoring event believed to be browser UI autocomplete noise"
1603                debug.println(debug.LEVEL_INFO, msg, True)
1604                return True
1605
1606            msg = "WEB: Event source is not in document content"
1607            debug.println(debug.LEVEL_INFO, msg, True)
1608            return False
1609
1610        obj, offset = self.utilities.getCaretContext(document, False, False)
1611        msg = "WEB: Context: %s, %i (focus: %s)" % (obj, offset, orca_state.locusOfFocus)
1612        debug.println(debug.LEVEL_INFO, msg, True)
1613
1614        if self._lastCommandWasCaretNav:
1615            msg = "WEB: Event ignored: Last command was caret nav"
1616            debug.println(debug.LEVEL_INFO, msg, True)
1617            return True
1618
1619        if self._lastCommandWasStructNav:
1620            msg = "WEB: Event ignored: Last command was struct nav"
1621            debug.println(debug.LEVEL_INFO, msg, True)
1622            return True
1623
1624        if self._lastCommandWasMouseButton:
1625            msg = "WEB: Last command was mouse button"
1626            debug.println(debug.LEVEL_INFO, msg, True)
1627
1628            if (event.source, event.detail1) == (obj, offset):
1629                msg = "WEB: Event is for current caret context."
1630                debug.println(debug.LEVEL_INFO, msg, True)
1631                return True
1632
1633            if (event.source, event.detail1) == self._lastMouseButtonContext:
1634                msg = "WEB: Event is for last mouse button context."
1635                debug.println(debug.LEVEL_INFO, msg, True)
1636                return True
1637
1638            self._lastMouseButtonContext = event.source, event.detail1
1639
1640            msg = "WEB: Event handled: Last command was mouse button"
1641            debug.println(debug.LEVEL_INFO, msg, True)
1642            self.utilities.setCaretContext(event.source, event.detail1)
1643            notify = not self.utilities.isEntryDescendant(event.source)
1644            orca.setLocusOfFocus(event, event.source, notify, True)
1645            if orca_state.locusOfFocus == event.source:
1646                self.updateBraille(event.source)
1647            return True
1648
1649        if self.utilities.lastInputEventWasTab():
1650            msg = "WEB: Last input event was Tab."
1651            debug.println(debug.LEVEL_INFO, msg, True)
1652
1653            if self.utilities.isDocument(event.source):
1654                msg = "WEB: Event ignored: Caret moved in document due to Tab."
1655                debug.println(debug.LEVEL_INFO, msg, True)
1656                return True
1657
1658            if event.source.getRole() in [pyatspi.ROLE_ENTRY, pyatspi.ROLE_SPIN_BUTTON] \
1659               and event.source.getState().contains(pyatspi.STATE_FOCUSED) \
1660               and event.source != orca_state.locusOfFocus:
1661                msg = "WEB: Event ignored: Entry is not (yet) the locus of focus. Waiting for focus event."
1662                debug.println(debug.LEVEL_INFO, msg, True)
1663                return True
1664
1665        if self.utilities.inFindContainer():
1666            msg = "WEB: Event handled: Presenting find results"
1667            debug.println(debug.LEVEL_INFO, msg, True)
1668            self.presentFindResults(event.source, event.detail1)
1669            self._saveFocusedObjectInfo(orca_state.locusOfFocus)
1670            return True
1671
1672        if not self.utilities.eventIsFromLocusOfFocusDocument(event):
1673            msg = "WEB: Event ignored: Not from locus of focus document"
1674            debug.println(debug.LEVEL_INFO, msg, True)
1675            return True
1676
1677        if self.utilities.textEventIsDueToInsertion(event):
1678            msg = "WEB: Event handled: Updating position due to insertion"
1679            debug.println(debug.LEVEL_INFO, msg, True)
1680            self._saveLastCursorPosition(event.source, event.detail1)
1681            return True
1682
1683        if self.utilities.textEventIsDueToDeletion(event):
1684            msg = "WEB: Event handled: Updating position due to deletion"
1685            debug.println(debug.LEVEL_INFO, msg, True)
1686            self._saveLastCursorPosition(event.source, event.detail1)
1687            return True
1688
1689        if self.utilities.eventIsAutocompleteNoise(event, document):
1690            msg = "WEB: Event ignored: Autocomplete noise"
1691            debug.println(debug.LEVEL_INFO, msg, True)
1692            return True
1693
1694        if self._inFocusMode and self.utilities.caretMovedOutsideActiveGrid(event):
1695            msg = "WEB: Event ignored: Caret moved outside active grid during focus mode"
1696            debug.println(debug.LEVEL_INFO, msg, True)
1697            return True
1698
1699        if self.utilities.treatEventAsSpinnerValueChange(event):
1700            msg = "WEB: Event handled as the value-change event we wish we'd get"
1701            debug.println(debug.LEVEL_INFO, msg, True)
1702            self.updateBraille(event.source)
1703            self._presentTextAtNewCaretPosition(event)
1704            return True
1705
1706        if not self.utilities.queryNonEmptyText(event.source) \
1707           and not event.source.getState().contains(pyatspi.STATE_EDITABLE):
1708            msg = "WEB: Event ignored: Was for non-editable object we're treating as textless"
1709            debug.println(debug.LEVEL_INFO, msg, True)
1710            return True
1711
1712        obj, offset = self.utilities.findFirstCaretContext(event.source, event.detail1)
1713        notify = force = handled = False
1714
1715        if self.utilities.lastInputEventWasPageNav():
1716            msg = "WEB: Caret moved due to scrolling."
1717            debug.println(debug.LEVEL_INFO, msg, True)
1718            notify = force = handled = True
1719
1720        elif self.utilities.caretMovedToSamePageFragment(event):
1721            msg = "WEB: Caret moved to fragment."
1722            debug.println(debug.LEVEL_INFO, msg, True)
1723            notify = force = handled = True
1724
1725        elif self.utilities.lastInputEventWasCaretNav():
1726            msg = "WEB: Caret moved due to native caret navigation."
1727            debug.println(debug.LEVEL_INFO, msg, True)
1728
1729        msg = "WEB: Setting context and focus to: %s, %i" % (obj, offset)
1730        debug.println(debug.LEVEL_INFO, msg, True)
1731        self.utilities.setCaretContext(obj, offset, document)
1732        orca.setLocusOfFocus(event, obj, notify, force)
1733        return handled
1734
1735    def onCheckedChanged(self, event):
1736        """Callback for object:state-changed:checked accessibility events."""
1737
1738        if not self.utilities.inDocumentContent(event.source):
1739            msg = "WEB: Event source is not in document content"
1740            debug.println(debug.LEVEL_INFO, msg, True)
1741            return False
1742
1743        obj, offset = self.utilities.getCaretContext()
1744        if obj != event.source:
1745            msg = "WEB: Event source is not context object"
1746            debug.println(debug.LEVEL_INFO, msg, True)
1747            return True
1748
1749        oldObj, oldState = self.pointOfReference.get('checkedChange', (None, 0))
1750        if hash(oldObj) == hash(obj) and oldState == event.detail1:
1751            msg = "WEB: Ignoring event, state hasn't changed"
1752            debug.println(debug.LEVEL_INFO, msg, True)
1753            return True
1754
1755        role = obj.getRole()
1756        if not (self._lastCommandWasCaretNav and role == pyatspi.ROLE_RADIO_BUTTON):
1757            msg = "WEB: Event is something default can handle"
1758            debug.println(debug.LEVEL_INFO, msg, True)
1759            return False
1760
1761        self.updateBraille(obj)
1762        speech.speak(self.speechGenerator.generateSpeech(obj, alreadyFocused=True))
1763        self.pointOfReference['checkedChange'] = hash(obj), event.detail1
1764        return True
1765
1766    def onChildrenAdded(self, event):
1767        """Callback for object:children-changed:add accessibility events."""
1768
1769        if self.utilities.eventIsBrowserUINoise(event):
1770            msg = "WEB: Ignoring event believed to be browser UI noise"
1771            debug.println(debug.LEVEL_INFO, msg, True)
1772            return True
1773
1774        isLiveRegion = self.utilities.isLiveRegion(event.source)
1775        document = self.utilities.getTopLevelDocumentForObject(event.source)
1776        if document and not isLiveRegion:
1777            if event.source == orca_state.locusOfFocus:
1778                msg = "WEB: Dumping cache and context: source is focus %s" % orca_state.locusOfFocus
1779                debug.println(debug.LEVEL_INFO, msg, True)
1780                self.utilities.dumpCache(document, preserveContext=False)
1781            elif self.utilities.isDead(orca_state.locusOfFocus):
1782                msg = "WEB: Dumping cache: dead focus %s" % orca_state.locusOfFocus
1783                debug.println(debug.LEVEL_INFO, msg, True)
1784                self.utilities.dumpCache(document, preserveContext=True)
1785            elif pyatspi.findAncestor(orca_state.locusOfFocus, lambda x: x == event.source):
1786                msg = "WEB: Dumping cache: source is ancestor of focus %s" % orca_state.locusOfFocus
1787                debug.println(debug.LEVEL_INFO, msg, True)
1788                self.utilities.dumpCache(document, preserveContext=True)
1789            else:
1790                msg = "WEB: Not dumping full cache. Focus is %s" % orca_state.locusOfFocus
1791                debug.println(debug.LEVEL_INFO, msg, True)
1792                self.utilities.clearCachedObjects()
1793
1794        elif isLiveRegion:
1795            if self.utilities.handleAsLiveRegion(event):
1796                msg = "WEB: Event to be handled as live region"
1797                debug.println(debug.LEVEL_INFO, msg, True)
1798                self.liveRegionManager.handleEvent(event)
1799            else:
1800                msg = "WEB: Ignoring because live region event not to be handled."
1801                debug.println(debug.LEVEL_INFO, msg, True)
1802            return True
1803        else:
1804            msg = "WEB: Could not get document for event source"
1805            debug.println(debug.LEVEL_INFO, msg, True)
1806            return False
1807
1808        if self._loadingDocumentContent:
1809            msg = "WEB: Ignoring because document content is being loaded."
1810            debug.println(debug.LEVEL_INFO, msg, True)
1811            return True
1812
1813        if self.utilities.isZombie(document):
1814            msg = "WEB: Ignoring because %s is zombified." % document
1815            debug.println(debug.LEVEL_INFO, msg, True)
1816            return True
1817
1818        try:
1819            docIsBusy = document.getState().contains(pyatspi.STATE_BUSY)
1820        except:
1821            docIsBusy = False
1822            msg = "WEB: Exception getting state of %s" % document
1823            debug.println(debug.LEVEL_INFO, msg, True)
1824        if docIsBusy:
1825            msg = "WEB: Ignoring because %s is busy." % document
1826            debug.println(debug.LEVEL_INFO, msg, True)
1827            return True
1828
1829        if not event.any_data or self.utilities.isZombie(event.any_data):
1830            msg = "WEB: Ignoring because any data is null or zombified."
1831            debug.println(debug.LEVEL_INFO, msg, True)
1832            return True
1833
1834        if self.utilities.handleEventFromContextReplicant(event, event.any_data):
1835            msg = "WEB: Event handled by updating locusOfFocus and context to child."
1836            debug.println(debug.LEVEL_INFO, msg, True)
1837            return True
1838
1839        childRole = event.any_data.getRole()
1840        if childRole == pyatspi.ROLE_ALERT:
1841            if event.any_data == self.utilities.lastQueuedLiveRegion():
1842                msg = "WEB: Ignoring %s (is last queued live region)" % event.any_data
1843                debug.println(debug.LEVEL_INFO, msg, True)
1844                return True
1845
1846            msg = "WEB: Presenting event.any_data"
1847            debug.println(debug.LEVEL_INFO, msg, True)
1848            self.presentObject(event.any_data)
1849
1850            focused = self.utilities.focusedObject(event.any_data)
1851            if focused:
1852                notify = self.utilities.queryNonEmptyText(focused) is None
1853                msg = "WEB: Setting locusOfFocus and caret context to %s" % focused
1854                debug.println(debug.LEVEL_INFO, msg)
1855                orca.setLocusOfFocus(event, focused, notify)
1856                self.utilities.setCaretContext(focused, 0)
1857            return True
1858
1859        if self.lastMouseRoutingTime and 0 < time.time() - self.lastMouseRoutingTime < 1:
1860            utterances = []
1861            utterances.append(messages.NEW_ITEM_ADDED)
1862            utterances.extend(self.speechGenerator.generateSpeech(child, force=True))
1863            speech.speak(utterances)
1864            self._lastMouseOverObject = event.any_data
1865            self.preMouseOverContext = self.utilities.getCaretContext()
1866            return True
1867
1868        return False
1869
1870    def onChildrenRemoved(self, event):
1871        """Callback for object:children-changed:removed accessibility events."""
1872
1873        if not self.utilities.inDocumentContent(event.source):
1874            msg = "WEB: Event source is not in document content."
1875            debug.println(debug.LEVEL_INFO, msg, True)
1876            return False
1877
1878        if self._loadingDocumentContent:
1879            msg = "WEB: Ignoring because document content is being loaded."
1880            debug.println(debug.LEVEL_INFO, msg, True)
1881            return True
1882
1883        if self.utilities.isLiveRegion(event.source):
1884            if self.utilities.handleEventForRemovedChild(event):
1885                msg = "WEB: Event handled for removed live-region child."
1886                debug.println(debug.LEVEL_INFO, msg, True)
1887            else:
1888                msg = "WEB: Ignoring removal from live region."
1889                debug.println(debug.LEVEL_INFO, msg, True)
1890            return True
1891
1892        document = self.utilities.getTopLevelDocumentForObject(event.source)
1893        if document:
1894            if event.source == orca_state.locusOfFocus:
1895                msg = "WEB: Dumping cache and context: source is focus %s" % orca_state.locusOfFocus
1896                debug.println(debug.LEVEL_INFO, msg, True)
1897                self.utilities.dumpCache(document, preserveContext=False)
1898            elif self.utilities.isDead(orca_state.locusOfFocus):
1899                msg = "WEB: Dumping cache: dead focus %s" % orca_state.locusOfFocus
1900                debug.println(debug.LEVEL_INFO, msg, True)
1901                self.utilities.dumpCache(document, preserveContext=True)
1902            elif pyatspi.findAncestor(orca_state.locusOfFocus, lambda x: x == event.source):
1903                msg = "WEB: Dumping cache: source is ancestor of focus %s" % orca_state.locusOfFocus
1904                debug.println(debug.LEVEL_INFO, msg, True)
1905                self.utilities.dumpCache(document, preserveContext=True)
1906            else:
1907                msg = "WEB: Not dumping full cache. Focus is %s" % orca_state.locusOfFocus
1908                debug.println(debug.LEVEL_INFO, msg, True)
1909                self.utilities.clearCachedObjects()
1910
1911        if self.utilities.handleEventForRemovedChild(event):
1912            msg = "WEB: Event handled for removed child."
1913            debug.println(debug.LEVEL_INFO, msg, True)
1914            return True
1915
1916        return False
1917
1918    def onColumnReordered(self, event):
1919        """Callback for object:column-reordered accessibility events."""
1920
1921        if not self.utilities.inDocumentContent(event.source):
1922            msg = "WEB: Event source is not in document content"
1923            debug.println(debug.LEVEL_INFO, msg, True)
1924            return False
1925
1926        if event.source != self.utilities.getTable(orca_state.locusOfFocus):
1927            msg = "WEB: locusOfFocus (%s) is not in this table" % orca_state.locusOfFocus
1928            debug.println(debug.LEVEL_INFO, msg, True)
1929            return False
1930
1931        self.pointOfReference['last-table-sort-time'] = time.time()
1932        self.presentMessage(messages.TABLE_REORDERED_COLUMNS)
1933        header = self.utilities.containingTableHeader(orca_state.locusOfFocus)
1934        if header:
1935            self.presentMessage(self.utilities.getSortOrderDescription(header, True))
1936
1937        return True
1938
1939    def onDocumentLoadComplete(self, event):
1940        """Callback for document:load-complete accessibility events."""
1941
1942        if self.utilities.getDocumentForObject(event.source.parent):
1943            msg = "WEB: Ignoring: Event source is nested document"
1944            debug.println(debug.LEVEL_INFO, msg, True)
1945            return True
1946
1947        msg = "WEB: Updating loading state and resetting live regions"
1948        debug.println(debug.LEVEL_INFO, msg, True)
1949        self._loadingDocumentContent = False
1950        self.liveRegionManager.reset()
1951        return True
1952
1953    def onDocumentLoadStopped(self, event):
1954        """Callback for document:load-stopped accessibility events."""
1955
1956        if self.utilities.getDocumentForObject(event.source.parent):
1957            msg = "WEB: Ignoring: Event source is nested document"
1958            debug.println(debug.LEVEL_INFO, msg, True)
1959            return True
1960
1961        msg = "WEB: Updating loading state"
1962        debug.println(debug.LEVEL_INFO, msg, True)
1963        self._loadingDocumentContent = False
1964        return True
1965
1966    def onDocumentReload(self, event):
1967        """Callback for document:reload accessibility events."""
1968
1969        if self.utilities.getDocumentForObject(event.source.parent):
1970            msg = "WEB: Ignoring: Event source is nested document"
1971            debug.println(debug.LEVEL_INFO, msg, True)
1972            return True
1973
1974        msg = "WEB: Updating loading state"
1975        debug.println(debug.LEVEL_INFO, msg, True)
1976        self._loadingDocumentContent = True
1977        return True
1978
1979    def onExpandedChanged(self, event):
1980        """Callback for object:state-changed:expanded accessibility events."""
1981
1982        if self.utilities.isZombie(event.source):
1983            msg = "WEB: Event source is Zombie"
1984            debug.println(debug.LEVEL_INFO, msg, True)
1985            return True
1986
1987        if not self.utilities.inDocumentContent(event.source):
1988            msg = "WEB: Event source is not in document content"
1989            debug.println(debug.LEVEL_INFO, msg, True)
1990            return False
1991
1992        obj, offset = self.utilities.getCaretContext(searchIfNeeded=False)
1993        msg = "WEB: Caret context is %s, %i (focus: %s)" % (obj, offset, orca_state.locusOfFocus)
1994        debug.println(debug.LEVEL_INFO, msg, True)
1995
1996        if not obj or self.utilities.isZombie(obj) and event.source == orca_state.locusOfFocus:
1997            msg = "WEB: Setting caret context to event source"
1998            debug.println(debug.LEVEL_INFO, msg, True)
1999            self.utilities.setCaretContext(event.source, 0)
2000
2001        return False
2002
2003    def onFocus(self, event):
2004        """Callback for focus: accessibility events."""
2005
2006        # We should get proper state-changed events for these.
2007        if self.utilities.inDocumentContent(event.source):
2008            msg = "WEB: Ignoring because object:state-changed-focused expected."
2009            debug.println(debug.LEVEL_INFO, msg, True)
2010            return True
2011
2012        return False
2013
2014    def onFocusedChanged(self, event):
2015        """Callback for object:state-changed:focused accessibility events."""
2016
2017        if not event.detail1:
2018            msg = "WEB: Ignoring because event source lost focus"
2019            debug.println(debug.LEVEL_INFO, msg, True)
2020            return True
2021
2022        if self.utilities.isZombie(event.source):
2023            msg = "WEB: Event source is Zombie"
2024            debug.println(debug.LEVEL_INFO, msg, True)
2025            return True
2026
2027        document = self.utilities.getDocumentForObject(event.source)
2028        if not document:
2029            msg = "WEB: Could not get document for event source"
2030            debug.println(debug.LEVEL_INFO, msg, True)
2031            return False
2032
2033        role = event.source.getRole()
2034        if self.utilities.isWebAppDescendant(event.source):
2035            if self._browseModeIsSticky:
2036                msg = "WEB: Web app descendant claimed focus, but browse mode is sticky"
2037                debug.println(debug.LEVEL_INFO, msg, True)
2038            elif role == pyatspi.ROLE_TOOL_TIP \
2039                 and pyatspi.findAncestor(orca_state.locusOfFocus, lambda x: x and x == event.source):
2040                msg = "WEB: Event believed to be side effect of tooltip navigation."
2041                debug.println(debug.LEVEL_INFO, msg, True)
2042                return True
2043            else:
2044                msg = "WEB: Event handled: Setting locusOfFocus to web app descendant"
2045                debug.println(debug.LEVEL_INFO, msg, True)
2046                orca.setLocusOfFocus(event, event.source)
2047                return True
2048
2049        state = event.source.getState()
2050        if state.contains(pyatspi.STATE_EDITABLE):
2051            msg = "WEB: Event source is editable"
2052            debug.println(debug.LEVEL_INFO, msg, True)
2053            return False
2054
2055        if role in [pyatspi.ROLE_DIALOG, pyatspi.ROLE_ALERT]:
2056            msg = "WEB: Event handled: Setting locusOfFocus to event source"
2057            debug.println(debug.LEVEL_INFO, msg, True)
2058            orca.setLocusOfFocus(event, event.source)
2059            return True
2060
2061        if self.utilities.handleEventFromContextReplicant(event, event.source):
2062            msg = "WEB: Event handled by updating locusOfFocus and context to source."
2063            debug.println(debug.LEVEL_INFO, msg, True)
2064            return True
2065
2066        obj, offset = self.utilities.getCaretContext()
2067        msg = "WEB: Caret context is %s, %i (focus: %s)" \
2068              % (obj, offset, orca_state.locusOfFocus)
2069        debug.println(debug.LEVEL_INFO, msg, True)
2070
2071        if not obj or self.utilities.isZombie(obj):
2072            msg = "WEB: Clearing context - obj is null or zombie"
2073            debug.println(debug.LEVEL_INFO, msg, True)
2074            self.utilities.clearCaretContext()
2075
2076            obj, offset = self.utilities.searchForCaretContext(event.source)
2077            if obj:
2078                notify = self.utilities.inFindContainer(orca_state.locusOfFocus)
2079                msg = "WEB: Updating focus and context to %s, %i" % (obj, offset)
2080                debug.println(debug.LEVEL_INFO, msg, True)
2081                orca.setLocusOfFocus(event, obj, notify)
2082                self.utilities.setCaretContext(obj, offset)
2083            else:
2084                msg = "WEB: Search for caret context failed"
2085                debug.println(debug.LEVEL_INFO, msg, True)
2086
2087        if self._lastCommandWasCaretNav:
2088            msg = "WEB: Event ignored: Last command was caret nav"
2089            debug.println(debug.LEVEL_INFO, msg, True)
2090            return True
2091
2092        if self._lastCommandWasStructNav:
2093            msg = "WEB: Event ignored: Last command was struct nav"
2094            debug.println(debug.LEVEL_INFO, msg, True)
2095            return True
2096
2097        if not state.contains(pyatspi.STATE_FOCUSABLE) \
2098           and not state.contains(pyatspi.STATE_FOCUSED):
2099            msg = "WEB: Event ignored: Source is not focusable or focused"
2100            debug.println(debug.LEVEL_INFO, msg, True)
2101            return True
2102
2103        if not role in [pyatspi.ROLE_DOCUMENT_FRAME, pyatspi.ROLE_DOCUMENT_WEB]:
2104            msg = "WEB: Deferring to other scripts for handling non-document source"
2105            debug.println(debug.LEVEL_INFO, msg, True)
2106            return False
2107
2108        if not obj:
2109            msg = "WEB: Unable to get non-null, non-zombie context object"
2110            debug.println(debug.LEVEL_INFO, msg, True)
2111            return False
2112
2113        if self.utilities.lastInputEventWasPageNav():
2114            msg = "WEB: Event handled: Focus changed due to scrolling"
2115            debug.println(debug.LEVEL_INFO, msg, True)
2116            orca.setLocusOfFocus(event, obj)
2117            self.utilities.setCaretContext(obj, offset)
2118            return True
2119
2120        wasFocused = obj.getState().contains(pyatspi.STATE_FOCUSED)
2121        obj.clearCache()
2122        isFocused = obj.getState().contains(pyatspi.STATE_FOCUSED)
2123        if wasFocused != isFocused:
2124            msg = "WEB: Focused state of %s changed to %s" % (obj, isFocused)
2125            debug.println(debug.LEVEL_INFO, msg, True)
2126            return False
2127
2128        if self.utilities.isAnchor(obj):
2129            cause = "Context is anchor"
2130        elif not (self.utilities.isLink(obj) and not isFocused):
2131            cause = "Context is not a non-focused link"
2132        elif self.utilities.isChildOfCurrentFragment(obj):
2133            cause = "Context is child of current fragment"
2134        elif document == event.source and self.utilities.documentFragment(event.source):
2135            cause = "Document URI is fragment"
2136        else:
2137            return False
2138
2139        msg = "WEB: Event handled: Setting locusOfFocus to %s (%s)" % (obj, cause)
2140        debug.println(debug.LEVEL_INFO, msg, True)
2141        orca.setLocusOfFocus(event, obj)
2142        return True
2143
2144    def onMouseButton(self, event):
2145        """Callback for mouse:button accessibility events."""
2146
2147        self._lastCommandWasCaretNav = False
2148        self._lastCommandWasStructNav = False
2149        self._lastCommandWasMouseButton = True
2150        return False
2151
2152    def onNameChanged(self, event):
2153        """Callback for object:property-change:accessible-name events."""
2154
2155        if self.utilities.eventIsBrowserUINoise(event):
2156            msg = "WEB: Ignoring event believed to be browser UI noise"
2157            debug.println(debug.LEVEL_INFO, msg, True)
2158            return True
2159
2160        return True
2161
2162    def onRowReordered(self, event):
2163        """Callback for object:row-reordered accessibility events."""
2164
2165        if not self.utilities.inDocumentContent(event.source):
2166            msg = "WEB: Event source is not in document content"
2167            debug.println(debug.LEVEL_INFO, msg, True)
2168            return False
2169
2170        if event.source != self.utilities.getTable(orca_state.locusOfFocus):
2171            msg = "WEB: locusOfFocus (%s) is not in this table" % orca_state.locusOfFocus
2172            debug.println(debug.LEVEL_INFO, msg, True)
2173            return False
2174
2175        self.pointOfReference['last-table-sort-time'] = time.time()
2176        self.presentMessage(messages.TABLE_REORDERED_ROWS)
2177        header = self.utilities.containingTableHeader(orca_state.locusOfFocus)
2178        if header:
2179            self.presentMessage(self.utilities.getSortOrderDescription(header, True))
2180
2181        return True
2182
2183    def onSelectedChanged(self, event):
2184        """Callback for object:state-changed:selected accessibility events."""
2185
2186        if self.utilities.eventIsBrowserUIAutocompleteNoise(event):
2187            msg = "WEB: Ignoring event believed to be browser UI autocomplete noise"
2188            debug.println(debug.LEVEL_INFO, msg, True)
2189            return True
2190
2191        if self.utilities.eventIsBrowserUIPageSwitch(event):
2192            msg = "WEB: Event believed to be browser UI page switch"
2193            debug.println(debug.LEVEL_INFO, msg, True)
2194            if event.detail1:
2195                self.presentObject(event.source, priorObj=orca_state.locusOfFocus)
2196            return True
2197
2198        if not self.utilities.inDocumentContent(event.source):
2199            msg = "WEB: Event source is not in document content"
2200            debug.println(debug.LEVEL_INFO, msg, True)
2201            return False
2202
2203        if orca_state.locusOfFocus != event.source:
2204            msg = "WEB: Ignoring because event source is not locusOfFocus"
2205            debug.println(debug.LEVEL_INFO, msg, True)
2206            return True
2207
2208        return False
2209
2210    def onSelectionChanged(self, event):
2211        """Callback for object:selection-changed accessibility events."""
2212
2213        if self.utilities.eventIsBrowserUIAutocompleteNoise(event):
2214            msg = "WEB: Ignoring event believed to be browser UI autocomplete noise"
2215            debug.println(debug.LEVEL_INFO, msg, True)
2216            return True
2217
2218        if self.utilities.eventIsBrowserUIPageSwitch(event):
2219            msg = "WEB: Ignoring event believed to be browser UI page switch"
2220            debug.println(debug.LEVEL_INFO, msg, True)
2221            return True
2222
2223        if not self.utilities.inDocumentContent(event.source):
2224            msg = "WEB: Event source is not in document content"
2225            debug.println(debug.LEVEL_INFO, msg, True)
2226            return False
2227
2228        if not self.utilities.inDocumentContent(orca_state.locusOfFocus):
2229            msg = "WEB: Event ignored: locusOfFocus (%s) is not in document content" \
2230                  % orca_state.locusOfFocus
2231            debug.println(debug.LEVEL_INFO, msg, True)
2232            return True
2233
2234        if not self.utilities.eventIsFromLocusOfFocusDocument(event):
2235            msg = "WEB: Event ignored: Not from locus of focus document"
2236            debug.println(debug.LEVEL_INFO, msg, True)
2237            return True
2238
2239        if self.utilities.isWebAppDescendant(event.source):
2240            if self._inFocusMode:
2241                msg = "WEB: Event source is web app descendant and we're in focus mode"
2242                debug.println(debug.LEVEL_INFO, msg, True)
2243                return False
2244
2245            msg = "WEB: Event source is web app descendant and we're in browse mode"
2246            debug.println(debug.LEVEL_INFO, msg, True)
2247            return True
2248
2249        obj, offset = self.utilities.getCaretContext()
2250        ancestor = self.utilities.commonAncestor(obj, event.source)
2251        if ancestor and self.utilities.isTextBlockElement(ancestor):
2252            msg = "WEB: Ignoring: Common ancestor of context and event source is text block"
2253            debug.println(debug.LEVEL_INFO, msg, True)
2254            return True
2255
2256        return False
2257
2258    def onShowingChanged(self, event):
2259        """Callback for object:state-changed:showing accessibility events."""
2260
2261        if event.detail1 and self.utilities.isTopLevelBrowserUIAlert(event.source):
2262            msg = "WEB: Event handled: Presenting event source"
2263            debug.println(debug.LEVEL_INFO, msg, True)
2264            self.presentObject(event.source)
2265            return True
2266
2267        if not self.utilities.inDocumentContent(event.source):
2268            msg = "WEB: Event source is not in document content"
2269            debug.println(debug.LEVEL_INFO, msg, True)
2270            return False
2271
2272        return True
2273
2274    def onTextAttributesChanged(self, event):
2275        """Callback for object:text-attributes-changed accessibility events."""
2276
2277        msg = "WEB: Clearing cached text attributes"
2278        debug.println(debug.LEVEL_INFO, msg, True)
2279        self._currentTextAttrs = {}
2280        return False
2281
2282    def onTextDeleted(self, event):
2283        """Callback for object:text-changed:delete accessibility events."""
2284
2285        if self.utilities.isZombie(event.source):
2286            msg = "WEB: Event source is Zombie"
2287            debug.println(debug.LEVEL_INFO, msg, True)
2288            return True
2289
2290        if self.utilities.lastInputEventWasPageSwitch():
2291            msg = "WEB: Deletion is believed to be due to page switch"
2292            debug.println(debug.LEVEL_INFO, msg, True)
2293            return True
2294
2295        if self.utilities.isLiveRegion(event.source):
2296            msg = "WEB: Ignoring deletion from live region"
2297            debug.println(debug.LEVEL_INFO, msg, True)
2298            return True
2299
2300        if self.utilities.eventIsBrowserUINoise(event):
2301            msg = "WEB: Ignoring event believed to be browser UI noise"
2302            debug.println(debug.LEVEL_INFO, msg, True)
2303            return True
2304
2305        if not self.utilities.inDocumentContent(event.source):
2306            msg = "WEB: Event source is not in document content"
2307            debug.println(debug.LEVEL_INFO, msg, True)
2308            return False
2309
2310        if self.utilities.eventIsSpinnerNoise(event):
2311            msg = "WEB: Ignoring: Event believed to be spinner noise"
2312            debug.println(debug.LEVEL_INFO, msg, True)
2313            return True
2314
2315        if self.utilities.eventIsAutocompleteNoise(event):
2316            msg = "WEB: Ignoring event believed to be autocomplete noise"
2317            debug.println(debug.LEVEL_INFO, msg, True)
2318            return True
2319
2320        msg = "WEB: Clearing content cache due to text deletion"
2321        debug.println(debug.LEVEL_INFO, msg, True)
2322        self.utilities.clearContentCache()
2323
2324        if self.utilities.textEventIsDueToDeletion(event):
2325            msg = "WEB: Event believed to be due to editable text deletion"
2326            debug.println(debug.LEVEL_INFO, msg, True)
2327            return False
2328
2329        if self.utilities.textEventIsDueToInsertion(event):
2330            msg = "WEB: Ignoring event believed to be due to text insertion"
2331            debug.println(debug.LEVEL_INFO, msg, True)
2332            return True
2333
2334        obj, offset = self.utilities.getCaretContext(getZombieReplicant=False)
2335        if obj and obj != event.source \
2336           and not pyatspi.findAncestor(obj, lambda x: x == event.source):
2337            msg = "WEB: Ignoring event because it isn't %s or its ancestor" % obj
2338            debug.println(debug.LEVEL_INFO, msg, True)
2339            return True
2340
2341        if self.utilities.isZombie(obj):
2342            if self.utilities.isLink(obj):
2343                msg = "WEB: Focused link deleted. Taking no further action."
2344                debug.println(debug.LEVEL_INFO, msg, True)
2345                return True
2346
2347            obj, offset = self.utilities.getCaretContext(getZombieReplicant=True)
2348            if obj:
2349                orca.setLocusOfFocus(event, obj, notifyScript=False)
2350
2351        if self.utilities.isZombie(obj):
2352            msg = "WEB: Unable to get non-null, non-zombie context object"
2353            debug.println(debug.LEVEL_INFO, msg, True)
2354
2355        document = self.utilities.getDocumentForObject(event.source)
2356        if document:
2357            msg = "WEB: Clearing structural navigation cache for %s" % document
2358            debug.println(debug.LEVEL_INFO, msg, True)
2359            self.structuralNavigation.clearCache(document)
2360
2361        if not event.source.getState().contains(pyatspi.STATE_EDITABLE) \
2362           and not self.utilities.isContentEditableWithEmbeddedObjects(event.source):
2363            if self._inMouseOverObject \
2364               and self.utilities.isZombie(self._lastMouseOverObject):
2365                msg = "WEB: Restoring pre-mouseover context"
2366                debug.println(debug.LEVEL_INFO, msg, True)
2367                self.restorePreMouseOverContext()
2368
2369            msg = "WEB: Done processing non-editable source"
2370            debug.println(debug.LEVEL_INFO, msg, True)
2371            return True
2372
2373        return False
2374
2375    def onTextInserted(self, event):
2376        """Callback for object:text-changed:insert accessibility events."""
2377
2378        if self.utilities.isZombie(event.source):
2379            msg = "WEB: Event source is Zombie"
2380            debug.println(debug.LEVEL_INFO, msg, True)
2381            return True
2382
2383        if self.utilities.lastInputEventWasPageSwitch():
2384            msg = "WEB: Insertion is believed to be due to page switch"
2385            debug.println(debug.LEVEL_INFO, msg, True)
2386            return True
2387
2388        if self.utilities.handleAsLiveRegion(event):
2389            msg = "WEB: Event to be handled as live region"
2390            debug.println(debug.LEVEL_INFO, msg, True)
2391            self.liveRegionManager.handleEvent(event)
2392            return True
2393
2394        if self.utilities.isLiveRegion(event.source):
2395            msg = "WEB: Ignoring because live region event not to be handled."
2396            debug.println(debug.LEVEL_INFO, msg, True)
2397            return True
2398
2399        if self.utilities.eventIsEOCAdded(event):
2400            msg = "WEB: Ignoring: Event was for embedded object char"
2401            debug.println(debug.LEVEL_INFO, msg, True)
2402            return True
2403
2404        if self.utilities.eventIsBrowserUINoise(event):
2405            msg = "WEB: Ignoring event believed to be browser UI noise"
2406            debug.println(debug.LEVEL_INFO, msg, True)
2407            return True
2408
2409        if not self.utilities.inDocumentContent(event.source):
2410            msg = "WEB: Event source is not in document content"
2411            debug.println(debug.LEVEL_INFO, msg, True)
2412            return False
2413
2414        if self.utilities.eventIsSpinnerNoise(event):
2415            msg = "WEB: Ignoring: Event believed to be spinner noise"
2416            debug.println(debug.LEVEL_INFO, msg, True)
2417            return True
2418
2419        if self.utilities.eventIsAutocompleteNoise(event):
2420            msg = "WEB: Ignoring: Event believed to be autocomplete noise"
2421            debug.println(debug.LEVEL_INFO, msg, True)
2422            return True
2423
2424        msg = "WEB: Clearing content cache due to text insertion"
2425        debug.println(debug.LEVEL_INFO, msg, True)
2426        self.utilities.clearContentCache()
2427
2428        document = self.utilities.getTopLevelDocumentForObject(event.source)
2429        if self.utilities.isDead(orca_state.locusOfFocus):
2430            msg = "WEB: Dumping cache: dead focus %s" % orca_state.locusOfFocus
2431            debug.println(debug.LEVEL_INFO, msg, True)
2432            self.utilities.dumpCache(document, preserveContext=True)
2433        else:
2434            msg = "WEB: Clearing structural navigation cache for %s" % document
2435            debug.println(debug.LEVEL_INFO, msg, True)
2436            self.structuralNavigation.clearCache(document)
2437
2438        text = self.utilities.queryNonEmptyText(event.source)
2439        if not text:
2440            msg = "WEB: Ignoring: Event source is not a text object"
2441            debug.println(debug.LEVEL_INFO, msg, True)
2442            return True
2443
2444        state = event.source.getState()
2445        if not state.contains(pyatspi.STATE_EDITABLE):
2446            if event.source != orca_state.locusOfFocus:
2447                msg = "WEB: Done processing non-editable, non-locusOfFocus source"
2448                debug.println(debug.LEVEL_INFO, msg, True)
2449                return True
2450
2451            if self.utilities.isClickableElement(event.source):
2452                msg = "WEB: Event handled: Re-setting locusOfFocus to changed clickable"
2453                debug.println(debug.LEVEL_INFO, msg, True)
2454                orca.setLocusOfFocus(None, event.source, force=True)
2455                return True
2456
2457        return False
2458
2459    def onTextSelectionChanged(self, event):
2460        """Callback for object:text-selection-changed accessibility events."""
2461
2462        if self.utilities.isZombie(event.source):
2463            msg = "WEB: Event source is Zombie"
2464            debug.println(debug.LEVEL_INFO, msg, True)
2465            return True
2466
2467        if self.utilities.eventIsBrowserUINoise(event):
2468            msg = "WEB: Ignoring event believed to be browser UI noise"
2469            debug.println(debug.LEVEL_INFO, msg, True)
2470            return True
2471
2472        if not self.utilities.inDocumentContent(event.source):
2473            msg = "WEB: Event source is not in document content"
2474            debug.println(debug.LEVEL_INFO, msg, True)
2475            return False
2476
2477        if not self.utilities.inDocumentContent(orca_state.locusOfFocus):
2478            msg = "WEB: Event ignored: locusOfFocus (%s) is not in document content" \
2479                  % orca_state.locusOfFocus
2480            debug.println(debug.LEVEL_INFO, msg, True)
2481            return True
2482
2483        if self.utilities.eventIsAutocompleteNoise(event):
2484            msg = "WEB: Ignoring: Event believed to be autocomplete noise"
2485            debug.println(debug.LEVEL_INFO, msg, True)
2486            return True
2487
2488        if self.utilities.eventIsSpinnerNoise(event):
2489            msg = "WEB: Ignoring: Event believed to be spinner noise"
2490            debug.println(debug.LEVEL_INFO, msg, True)
2491            return True
2492
2493        if self.utilities.textEventIsForNonNavigableTextObject(event):
2494            msg = "WEB: Ignoring event for non-navigable text object"
2495            debug.println(debug.LEVEL_INFO, msg, True)
2496            return True
2497
2498        text = self.utilities.queryNonEmptyText(event.source)
2499        if not text:
2500            msg = "WEB: Ignoring: Event source is not a text object"
2501            debug.println(debug.LEVEL_INFO, msg, True)
2502            return True
2503
2504        if self.utilities.isContentEditableWithEmbeddedObjects(event.source):
2505            msg = "WEB: In content editable with embedded objects"
2506            debug.println(debug.LEVEL_INFO, msg, True)
2507            return False
2508
2509        offset = text.caretOffset
2510        char = text.getText(offset, offset+1)
2511        if char == self.EMBEDDED_OBJECT_CHARACTER \
2512           and not self.utilities.lastInputEventWasCaretNavWithSelection() \
2513           and not self.utilities.lastInputEventWasCommand():
2514            msg = "WEB: Ignoring: Not selecting and event offset is at embedded object"
2515            debug.println(debug.LEVEL_INFO, msg, True)
2516            return True
2517
2518        return False
2519
2520    def onWindowActivated(self, event):
2521        """Callback for window:activate accessibility events."""
2522
2523        msg = "WEB: Deferring to app/toolkit script"
2524        debug.println(debug.LEVEL_INFO, msg, True)
2525        return False
2526
2527    def onWindowDeactivated(self, event):
2528        """Callback for window:deactivate accessibility events."""
2529
2530        msg = "WEB: Clearing command state"
2531        debug.println(debug.LEVEL_INFO, msg, True)
2532        self._lastCommandWasCaretNav = False
2533        self._lastCommandWasStructNav = False
2534        self._lastCommandWasMouseButton = False
2535        self._lastMouseButtonContext = None, -1
2536        self.removeKeyGrabs()
2537        return False
2538
2539    def getTransferableAttributes(self):
2540        return {"_lastCommandWasCaretNav": self._lastCommandWasCaretNav,
2541                "_lastCommandWasStructNav": self._lastCommandWasStructNav,
2542                "_lastCommandWasMouseButton": self._lastCommandWasMouseButton,
2543                "_inFocusMode": self._inFocusMode,
2544                "_focusModeIsSticky": self._focusModeIsSticky,
2545                "_browseModeIsSticky": self._browseModeIsSticky,
2546        }
2547