chrome.jss

; Copyright 2014-2023 by Freedom Scientific BLV Group, LLC
;Freedom Scientific Script file For Google Chrome

Include "HJConst.jsh"
Include "HJGlobal.jsh"
Include "HJHelp.jsh"
Include "common.jsm"
Include "Chrome.jsh"
Include "Chrome.jsm"
Include "MSAAConst.jsh"
Include "UIA.jsh"
Include "ie.jsm"

Import "UIA.jsd"
Use "IA2Browser.jsb"

Const
    Edge="msedge.exe",
    ShiftKey = 0x800000,
    CTRLKey = 0x10000,
    key_LeftArrow = 0x4b,
    key_RightArrow = 0x4d,
    key_Home = 0x47,
    key_End = 0x4f

Globals
    IntArray restrictedIDs,
    Int gChromeVersion,
    Int gChromeMinorVersion,
    Int gChromeUpdateVersion,
    String appName,
    Int lastFocusType,
    Int lastKeyWasSelecting,
    Int lastKeyWasScripted,
    String chromeAutoCompleteAddress

;gbIsBrowserUIDialog accesses the global from IA2Browser:
Globals Int gbIsBrowserUIDialog

Const
    scTabSearchTopChromeIdentifyer = "chrome://tab-search.top-chrome/" ;address of the page which gains focus when Control+Shift + A is pressed

Globals
    Int gsDocID, ;copied from IA2Browser.jss, used in SayNewDocumentTab
    Int gsPrevDocID ;copied from IA2Browser.jss, used in SayNewDocumentTab

;For scheduling announcement of descriptionChangedEvent While typing in the Tab Search edit field:
Const
    DescriptionChangedEventAnnouncementWaitTime = 3 ;tenths of a second
Globals
    Int ScheduleIDDescriptionChangedEventAnnouncement,
    String gsDescriptionChangedEventText

;For scheduling SelectionChangedEvent when typing On the address bar,
;and For detecting when To announce a value change when tabbing:
Const
    SayAutoCompleteAddressAfterPauseWaitTime = 6 ;tenths of a second
Globals
    Int ScheduleIDSayAutoCompleteAddressAfterPause

;For managing announcement of New tab page in BrowserTabChangedEvent:
Globals
    Int gChromeBrowserTabID,
    Handle gChromeBrowserTabHWnd

;For detecting radio buttons in the chrome area of Chrome which generate no event when toggled:
Globals
    Int ScheduleIDUpdateAndSayNewStateOfRadioButton 

Void Function autoStartEvent()
GetFixedProductVersion(GetAppFilePath (), gChromeVersion, gChromeMinorVersion, gChromeUpdateVersion, 0)
appName = GetAppFileName ()
LoadNonJCFOptions ()
lastFocusType = wt_UNKNOWN
lastKeyWasSelecting = False
EndFunction

Int Function ShouldNotifyIfContextHelp()
Return Off
EndFunction

Int Function BrailleCallbackObjectIdentify()
If IsTouchCursor() Return BrailleCallbackObjectIdentify() EndIf
If GetObjectSubtypeCode() == wt_unknown
&& GetObjectRole() == Role_System_PageTabList
    Return wt_tabControl
EndIf
Return BrailleCallbackObjectIdentify()
EndFunction

Int Function BrailleAddObjectDescription(Int nSubtypeCode)
If nSubtypeCode == wt_button
&& StringLower(GetObjectname()) == StringLower(GetObjectDescription())
    ;Avoid information duplication, add nothing.
    Return True
ElIf nSubtypeCode == wt_TabControl
&& GetObjectRole() == Role_System_PageTabList
    ;Description duplicates information from name, add nothing.
    Return True
EndIf
Return BrailleAddObjectDescription(nSubtypeCode)
EndFunction

Int Function BrailleAddObjectContextHelp(Int nSubtypeCode)
Return True ; added no String, corrects later versions of Google 
EndFunction

Int Function IsBrowserContentWindow(Handle hwnd)
Return GetWindowClass(hwnd) == wc_ChromeWindowClass
EndFunction

Int Function IsBrowserUIDialog()
Return !UserBufferIsActive()
    && GetWindowClass(getFocus()) == wc_Chrome_WidgetWin_1
    && FindAncestorOfType(wt_dialog) != -1
EndFunction

Int Function ShouldUseDoSayObjectFromLevel( Handle focusWindow, Handle prevWindow )
Return !gbIsBrowserUIDialog
    && !IsOnAddressBarEdit()
EndFunction

String Function GetDocumentNameFromRealWindow()
Var
    String name
;No dom, so must get document name via RealWindowName (MSAA gets it)
name = GetWindowName(GetRealWindow(GetFocus()))
If StringContains(name, scTrimName_ChromeTitleBar)
    Return StringChopRight(name, StringLength(scTrimName_ChromeTitleBar))
EndIf
Return cscNull
EndFunction

String Function GetBrowserName(optional Int includeMaker)
If includeMaker
    Return msgGoogleChromeAppName
else
    Return msgChromeAppName
EndIf
EndFunction

Script ScreenSensitiveHelp ()
If IsSameScript () Then
    AppFileTopic(topic_Chrome)
    Return
EndIf
PerformScript ScreenSensitiveHelp()
EndScript

Int Function ShouldHandleParagraphNavigation()
Var Int isVirtualCursorTabSpecific = GetJCFOption(OPT_VirtualCursorIsTabSpecific )
Var Int vCursorSetting 
If isVirtualCursorTabSpecific Then
    vCursorSetting = GetVirtualCursorSettingForTab(GetTabID())
else
    vCursorSetting = GetJCFOption (OPT_VIRTUAL_PC_CURSOR)
EndIf
    
If (vCursorSetting == 0) Then
    Return True
EndIf

If IsFormsModeActive() || IsInsideARIAApplication () Then
    Return True
EndIf

Return False
EndFunction

Void Function CaretMovedEvent( Int movementUnit, optional Int source)
;In Chrome multiline edits, movementUnit is reported as 0.
If !movementUnit
&& lastKeyWasScripted
    Var String scriptName = GetScriptAssignedTo(GetCurrentScriptKeyName())
    If scriptName == "ControlUpArrow"
        Return CaretMovedEvent(Unit_Paragraph_Prior, source)
    ElIf scriptName == "ControlDownArrow"
        Return CaretMovedEvent(Unit_Paragraph_Next, source)
    EndIf
EndIf
CaretMovedEvent( movementUnit, source)
EndFunction

Void Function UnitMoveControlNav (Int UnitMovement)
If ShouldHandleParagraphNavigation() Then 
    Var Int objType=GetObjectSubtypeCode ()
    If SupportsEditCallbacks ()
    && objType != wt_multiline_edit
        ;NextParagraph and PriorParagraph do Not properly navigate in Chrome multiline edits.
        If UnitMovement == UnitMove_Next Then
            NextParagraph()
        ElIf UnitMovement == UnitMove_Prior Then
            PriorParagraph()
        EndIf
        Return
    EndIf

    If UnitMovement == UnitMove_Next Then
        TypeKey(cksControlDownArrow) 
    ElIf UnitMovement == UnitMove_Prior Then
        TypeKey(cksControlUpArrow)
    EndIf

    pause()
    If objType == WT_TABCONTROL Then
        sayWord()
    EndIf
    Return
EndIf
Return UnitMoveControlNav (UnitMovement)
EndFunction

Int Function IsOnAddressBarEdit()
If GetWindowClass(GetFocus()) != wc_Chrome_WidgetWin_1 Return False EndIf
Return lastFocusType == wt_edit
EndFunction

Void Function SayLineFromCaretMovedEvent(Int movementUnit)
If IsOnAddressBarEdit()
    ;The value change and selection change events will speak the address bar change:
    Return
EndIf
SayLineFromCaretMovedEvent(movementUnit)
EndFunction

Int Function ContractedBrailleInputAllowedNow ()
;event Function designed To Return False On controls that would otherwise support contracted input.
;Example would be an application whose edit controls may also support quick navigation keys, once said keys are On Return False from this Function To turn them Off.
;This Function is only called when the item with focus supports contracted input To begin with.
If IsOnAddressBarEdit() Then
    Return False
EndIf
Return ContractedBrailleInputAllowedNow () ; Fall back To default.
EndFunction

Void Function ActiveItemChangedEvent (Handle curHwnd, Int curObjectId, Int curChildId,
    Handle prevHwnd, Int prevObjectId, Int prevChildId)
If IsBrowserContentWindow(curHwnd) && GetObjectSubtypeCode()==wt_comboBox Then
    say(GetObjectValue(), OT_LINE)
    Return
EndIf

ActiveItemChangedEvent (curHwnd, curObjectId, curChildId, prevHwnd, prevObjectId, prevChildId)
EndFunction

Void Function SayAutoCompleteAddressAfterPause ()
ScheduleIDSayAutoCompleteAddressAfterPause = 0
Say (chromeAutoCompleteAddress, OT_LINE, True)
SayMessage (ot_select, cmsg215_l)
chromeAutoCompleteAddress = cscNull
EndFunction

Void Function SelectionChangedEvent( String text, Int wasTextSelected, optional Int source )
If ScheduleIDSayAutoCompleteAddressAfterPause
    UnscheduleFunction(ScheduleIDSayAutoCompleteAddressAfterPause)
EndIf
If IsOnAddressBarEdit() 
&& !lastKeyWasSelecting
    If !StringLength(getSelectedText())
        ;This happens when successive presses of Control+T For a New tab occurs.
        ;We get an irroneous selection change of text "address and".
        Return
    EndIf
    If wasTextSelected
        ;When the first letter is entered into the search edit field,
        ;the SecondaryFocusChangedEvent will probably, but Not reliably, speak when the first letter is entered,
        ;and at this point announcing the selection here would be spammy.
        ;however, continuing To type in the search edit results in this event firing and Not the SecondaryFocusChangedEvent.
        ;We schedule a delay both To avoid spamming While typing,
        ;and To give SecondaryFocusChangedEvent a chance To unschedule speaking of the selection change If that event fires.
        chromeAutoCompleteAddress = text
        ScheduleIDSayAutoCompleteAddressAfterPause = ScheduleFunction ("SayAutoCompleteAddressAfterPause", SayAutoCompleteAddressAfterPauseWaitTime, True)
    EndIf
    ;This event fires To remove the selection as you tab from the editable text.
    ;Do nothing here when this event fires For the deselection, To avoid hearing the deselection when no longer in the editable text.
    Return
EndIf
;this is To keep selection event On an address bar from speaking when switching tabs
SelectionChangedEvent( text, wasTextSelected, source )
EndFunction

Void Function SecondaryFocusChangedEvent()
If ScheduleIDSayAutoCompleteAddressAfterPause
    UnscheduleFunction(ScheduleIDSayAutoCompleteAddressAfterPause)
EndIf
If IsOnAddressBarEdit()
    ;Speaking is handled by ValueChangedEvent
    Return
EndIf
SecondaryFocusChangedEvent()
EndFunction

Void Function ValueChangedEvent (Handle hwnd, Int objId, Int childId, Int nObjType, String sObjName, String sObjValue, optional Int bIsFocusObject)
If IsOnAddressBarEdit()
&& bIsFocusObject
    ;We want To announce the value change On the address bar when navigating,
    ;and Not when typing or deleting text.
    ;SelectionChangedEvent will announce If the address bar text changes due To typing in New text.
    Var String scriptName = GetScriptAssignedTo(GetCurrentScriptKeyName())
    If lastKeyWasScripted
    && (scriptName == "SayNextLine"
    || scriptName == "SayPriorLine"
    || scriptName == "Tab"
    || scriptName == "ShiftTab")
        Say(sObjValue, ot_line)
    EndIf
    Return
EndIf
If (gChromeVersion < 69 
&& getObjectSubtypeCode()==wt_combobox) Then
    Return ;spoken from activeItemChanged event, filter here To avoid multiple speaking.
EndIf
ValueChangedEvent (hwnd, objId, childId, nObjType, sObjName, sObjValue, bIsFocusObject)
EndFunction

Void Function SayLineUnit(Int unitMovement, optional Int bMoved)
If !IsVirtualPCCursor() && GetObjectSubtypeCode()==WT_COMBOBOX Then
    Return; ActiveItemChangedEvent will speak this.
EndIf
SayLineUnit(unitMovement, bMoved)
EndFunction

Int Function IsValidForTraverseBrowserUIDialogAndReadControls(String dlgName, Object element)
Return element.name != dlgName
EndFunction

Script readBoxInTabOrder()
If gbIsBrowserUIDialog 
    ;GetTypeAndTextStringsForWindow often retrieves text with duplications,
    ;so we first try To Use UIA To traverse and read the elements.
    If !ReadBrowserUIDialogBox()
        Say(GetTypeAndTextStringsForWindow(getFocus()), ot_USER_REQUESTED_INFORMATION)
    EndIf
    Return
EndIf
performScript ReadBoxInTabOrder()
EndScript

Void Function GetWindowTitleForApplication(Handle hAppWnd, Handle hRealWnd, Handle hCurWnd, Int iTypeCode,
    String ByRef sTitle, String ByRef sSubTitle, Int ByRef bHasPage)
If IsBrowserContentWindow(hCurWnd)
&& StringCompare (StringSegment (GetOwningAppName (GetFocus ()), ".", 1), "chrome") == 0
    sTitle=GetObjectName(SOURCE_CACHED_DATA, GetAncestorCount ())
EndIf
If StringIsBlank (sTitle)
    GetWindowTitleForApplication(hAppWnd, hRealWnd, hCurWnd, iTypeCode, sTitle, sSubTitle, bHasPage)
EndIf
EndFunction

Int Function HandleCustomAppWindows (Handle hWnd)
; Alt tabbing To Chrome.
If getWindowClass(hWnd)==wc_Chrome_WidgetWin_1 Then
    say(GetObjectName(SOURCE_CACHED_DATA, GetAncestorCount ()), ot_window_name)
    Return True
EndIf
Return HandleCustomAppWindows (hWnd)
EndFunction

Script PictureSmartWithControl (optional Int serviceOptions)
Return PictureSmartWithControlShared (PSServiceOptions_Single | serviceOptions)
EndScript

Script PictureSmartWithControlMultiService (optional Int serviceOptions)
Return PictureSmartWithControlShared (PSServiceOptions_Multi | serviceOptions)
EndScript

Int Function ShouldSpeakItemAtLevel(Int level, Int type, Int parentType, Int focusRole, Int focusType)
Var
    Int ancestorRole = GetObjectRole(level),
    Int ancestorCount = GetAncestorCount(),
    Int dialogAncestor = FindAncestorOfType(wt_dialog),
    String ancestorName = cscNull,
    String dialogName = cscNull
If (ancestorRole == ROLE_SYSTEM_WINDOW && level == ancestorCount)
    If (dialogAncestor != -1)
        ancestorName = GetObjectName(SOURCE_CACHED_DATA, level)
        dialogName = GetObjectName(SOURCE_CACHED_DATA, dialogAncestor)
        If (ancestorName == dialogName)
            Return False
        EndIf
    EndIf
    Return True
EndIf
If (ancestorRole == ROLE_SYSTEM_WINDOW || ancestorRole == ROLE_SYSTEM_PANE)
    Return False
EndIf
Return ShouldSpeakItemAtLevel(level, type, parentType, focusRole, focusType)
EndFunction

Int Function NewBrowserTabIsNewPage(Handle hWnd)
Return gChromeBrowserTabHWnd != hWnd
EndFunction

Void Function AnnounceNewTabOrPageFromBrowserTabChangedEvent(Handle hWnd)
;When Control+N is used, a New window opens and the focus change will speak the focused Object.
;But when Control+T is used, a New tab is created but the focus does Not change so we should also announce the focused Object.
If !NewBrowserTabIsNewPage(hWnd)
    SayFormattedMessage(ot_screen_message, msgNewTabPage)
    SayObjectTypeAndText()
else
    SayFormattedMessage(ot_screen_message, msgNewWindow)
EndIf
EndFunction

Void Function BrowserTabChangedEvent(Int tabID, Handle hWnd)
;fires when changing browser tabs.
If gChromeBrowserTabID != tabID
    ;Ensure this announcement only happens If the New tab event is generated While On the address bar:
    If GetWindowClass(GlobalPrevFocus) == cwc_Chrome_WidgetWin_1
        AnnounceNewTabOrPageFromBrowserTabChangedEvent(hWnd)
    EndIf
EndIf
gChromeBrowserTabID = tabID
gChromeBrowserTabHWnd = hWnd
EndFunction

Prototype Int Function GetTabID()
Prototype Int Function GetVirtualCursorSettingForTab(Int tabID)
Prototype Void Function SetVirtualCursorSettingForTab(Int tabID, Int vCursorSetting)

Script VirtualPCCursorToggle ()
Var Int isVirtualCursorTabSpecific = GetJCFOption(OPT_VirtualCursorIsTabSpecific )
If (!isVirtualCursorTabSpecific ) Then
    PerformScript VirtualPCCursorToggle() 
    Return
EndIf
Var Int vCursorSetting = GetVirtualCursorSettingForTab(GetTabID())
If vCursorSetting == 0 Then
    vCursorSetting = 1
else
    vCursorSetting = 0
EndIf

refresh()
BrailleRefresh()

SetVirtualCursorSettingForTab(GetTabID(), vCursorSetting )
If vCursorSetting Then
    SayFormattedMessage(ot_status, cMSG291_L, cmsgOn)
else
    SayFormattedMessage(ot_status, cmsg292_L, cmsgOff)
EndIf
EndScript

Int Function SayLineInsteadOfSayAll()
Var
    Int type
Let type = GetObjectSubTypeCode(SOURCE_CACHED_DATA, 0)
If IsPcCursor()
&& !IsVirtualPcCursor()
&& DialogActive() Then
    If (type == WT_MULTILINE_EDIT) Then
        Return False
    EndIf
EndIf
Return SayLineInsteadOfSayAll() 
EndFunction
Int Function ContextMenuProcessed(Handle hWnd)
If (IsBrowserContentWindow(hwnd))
 Return
EndIf

Return ContextMenuProcessed (hwnd )
EndFunction

Void Function HandleUnknownAncestor (Int level, Int focusType, Int pageIsChanging)
If (level < 1) Then
    Return
EndIf
 
If (GetObjectSubtypeCode(SOURCE_CACHED_DATA, level) != WT_UNKNOWN) Then
    Return
EndIf
 
If ( GetObjectName(SOURCE_CACHED_DATA, level) == " " ) Then
    Return
EndIf
 
Var
    Int ancestorCount,
    String focusedElementName,
    String ancestorElementName
 
focusedElementName = stringStripAllBlanks (GetObjectName(SOURCE_CACHED_DATA, 0))
ancestorElementName = stringStripAllBlanks (GetObjectName(SOURCE_CACHED_DATA, level))
ancestorCount = GetAncestorCount ()
If (level != ancestorCount 
&& (StringContains (ancestorElementName, focusedElementName))) Then
    Return 
EndIf

If GetObjectSubTypeCode(SOURCE_CACHED_DATA, 0) == WT_UNKNOWN
    Return ;focused Object and ancestor Object are both unknown types, just speak the focused Object
EndIf

SayObjectTypeAndText (level)
EndFunction

Void Function PreProcessFocusChangedEventEx(
    Handle hwndFocus, Int nObject, Int nChild,
    Handle hwndPrevFocus, Int nPrevObject, Int nPrevChild,
    Int nChangeDepth)
lastFocusType = GetObjectSubTypeCode(False, 0)
If ScheduleIDUpdateAndSayNewStateOfRadioButton 
    UnscheduleFunction(ScheduleIDUpdateAndSayNewStateOfRadioButton )
    ScheduleIDUpdateAndSayNewStateOfRadioButton = 0
EndIf
PreProcessFocusChangedEventEx(hwndFocus, nObject, nChild, hwndPrevFocus, nPrevObject, nPrevChild, nChangeDepth)
EndFunction

Void Function ProcessSayRealWindowOnFocusChange(
     Handle AppWindow, Handle RealWindow, String RealWindowName, Handle FocusWindow)
If GetWindowClass(GlobalPrevFocus) !=wc_Chrome_WidgetWin_1
    If gbIsBrowserUIDialog 
        ;A dialog from the browser UI has just gained focus.
        SayAndCacheBrailleForBrowserUIDialogNameAndText()
        Return
    EndIf
EndIf
ProcessSayRealWindowOnFocusChange(AppWindow, RealWindow, RealWindowName, FocusWindow)
EndFunction

Int Function IsKeySelectingText(Int keyCode)
Var Int isShiftLeft = (keyCode&~ShiftKey) == key_LeftArrow
Var Int isShiftRight = (keyCode&~ShiftKey) == key_RightArrow
Var Int isShiftCTRLLeft = (keyCode&~(ShiftKey|CTRLKey)) == key_LeftArrow
Var Int isShiftCTRLRight = (keyCode&~(ShiftKey|CTRLKey)) == key_RightArrow
Var Int isShiftHome = (keyCode&~ShiftKey) == key_Home
Var Int isShiftEnd = (keyCode&~ShiftKey) == key_End
Return (isShiftLeft 
    || isShiftRight
    || isShiftCTRLLeft 
    || isShiftCTRLRight 
    || isShiftHome
    ||IsShiftEnd) 
EndFunction

Void Function UpdateAndSayNewStateOfRadioButton()
ScheduleIDUpdateAndSayNewStateOfRadioButton = 0
If GetObjectStateCode() & CTRL_CHECKED
    ;The toggle was detected:
    Return
EndIf
MSAARefresh()
Pause() ;Allow time To update
IndicateControlState(wt_RadioButton, GetObjectStateCode())
EndFunction

Int Function ProcessSpaceBarKeyPressed(Int nKey, String strKeyName, Int nIsBrailleKey, Int nIsScriptKey)
If KeyIsSpacebar(nKey, strKeyName, nIsBrailleKey)
&& GetWindowClass(GetFocus()) == cwc_Chrome_WidgetWin_1
&& GetObjectSubtypeCode() == wt_RadioButton
    ;We may Not have received any events For updating the state:
    ScheduleIDUpdateAndSayNewStateOfRadioButton = ScheduleFunction("UpdateAndSayNewStateOfRadioButton", 3)
    Return True
EndIf
Return ProcessSpaceBarKeyPressed(nKey, strKeyName, nIsBrailleKey, nIsScriptKey)
EndFunction

Void Function KeyPressedEvent (Int nKey, String strKeyName, Int nIsBrailleKey, Int nIsScriptKey)
lastKeyWasSelecting = IsKeySelectingText(nKey)
lastKeyWasScripted = nIsScriptKey
KeyPressedEvent (nKey, strKeyName, nIsBrailleKey, nIsScriptKey)
EndFunction

Int Function IsOpenListBoxApplicable(Handle hWnd, Int iSubtype)
If (!IsFormsModeActive())
    Return IsOpenListBoxApplicable(hWnd, iSubtype)
EndIf

Return !SupportsEditCallbacks ()
EndFunction

Int Function IsCloseListBoxApplicable(Handle hWnd, Int iSubtype)
If (!IsFormsModeActive())
    Return IsCloseListBoxApplicable(hWnd, iSubtype)
EndIf
Return !SupportsEditCallbacks ()
EndFunction

Prototype IntArray Function GetDetailsIDs(Int details)
Prototype String Function GetTextForUniqueIDs(IntArray ids, Int idsCount)
Prototype Void Function RestrictToElementsWithUniqueIDs(IntArray ids, Int idsCount)
Prototype Void Function MoveToElementWithUniqueID(Int id)

Script announceComment ()
Var IntArray ids = GetDetailsIDs(True)
Var Int idsCount = ArrayLength (ids)
If (idsCount == 0)
    SayMessage (OT_ERROR, msgNoCommentAvailable)
    Return
EndIf

If isSameScript() Then
    ResetSpeechMarkupAttributes()
    MoveToElementWithUniqueID(ids[1])
    RestrictToElementsWithUniqueIDs(ids, idsCount)
    restrictedIDs = ids
    Return
EndIf

Var String text = GetTextForUniqueIDs(ids, idsCount)
If (StringIsBlank (text))
    SayMessage (OT_ERROR, msgNoCommentTextAvailable)
    Return
EndIf

text = msgComment + cscSpace + text
SayMessage (OT_JAWS_MESSAGE, text)
EndScript

Int Function IsInsideDetailsFor()
Var IntArray detailsForIDs = GetDetailsIDs(False)
Var Int idsCount = ArrayLength (detailsForIDs )
Return (idsCount > 0) 
EndFunction 

Script UpALevel()
If (!IsInsideDetailsFor())
    PerformScript UpALevel()
    Return 
EndIf

SayCurrentScriptKeyLabel ()
Var IntArray detailsForIDs = GetDetailsIDs(False)
Refresh() ;used To unrestrict the document before moving back To the commented text.
MoveToElementWithUniqueID(detailsForIDs[1])
EndScript

Void Function FormsModeEvent(Int bEntering, optional Int lastMovementUnit)
Var Int idsCount = ArrayLength (restrictedIDs)
If (bEntering == False
&& idsCount)
    ScheduleFunction ("RestrictToSavedIDs", 1)
EndIf

FormsModeEvent(bEntering, lastMovementUnit)
EndFunction

Void Function RestrictToSavedIDs()
Var Int idsCount = ArrayLength (restrictedIDs)
RestrictToElementsWithUniqueIDs(restrictedIDs, idsCount)
EndFunction

Int Function SpeakLiveRegionEvent(String text, Int suggestedOutputType, Int containsSpeechMarkup)
If SayAllInProgress () Then
    Return True ; Do Not speak notifications during skimread or sayAll.
EndIf
Return SpeakLiveRegionEvent(text, suggestedOutputType, containsSpeechMarkup)
EndFunction

Int Function InPageTabArea()
If isVirtualPCCursor() Return False EndIf
If GetWindowClass(getFocus()) != wc_Chrome_WidgetWin_1 Return False EndIf
Var
    Int i,
    Int depth = GetAncestorCount()
For i = 1 To depth
    If GetObjectRole(i) == Role_System_PageTabList
        Return True
    EndIf
EndFor
Return False
EndFunction

Void Function SayObjectTypeAndText(optional Int nLevel, Int includeContainerName)
If nLevel == 0
    If InPageTabArea()
        ;suppress description announcement from builtin SayObjectTypeAndText where the description information duplicates the name:
        Var
            Int type = GetObjectSubtypeCode(),
            String name = GetObjectname()
        If type == wt_button
        && StringLower(name) == StringLower(GetObjectDescription())
            ;The New Tab button differens only by case:
            IndicateControlType(wt_button, name, cmsgSilent)
            Return
        ElIf type == wt_unknown
        && GetObjectRole() == Role_System_PageTabList
            ;We don't actually have a subtypeCode defined For this type of control yet.
            ;For now, we will call it a tab control.
                IndicateControlType(wt_TabControl, name, cmsgSilent)
                IndicateControlState(wt_TabControl, GetObjectStateCode())
                Return
        EndIf
    EndIf
EndIf
SayObjectTypeAndText(nLevel, includeContainerName)
EndFunction

Script SayLine(optional Int drawHighlights)
If IsPCCursor()
&& InPageTabArea()
&& GetObjectSubtypecode() == wt_TabControl
    SayLine()
    ;SayLine does Not Include the position in group of the tab,
    ;and SayObjectTypeAndText will have out of date information about the position If the tab was shifted and focus has Not yet moved.
    Var Object element = FSUIAGetFocusedElement()
    If !element Return EndIf
    Var
        Int x = element.positionInSet,
        Int y = element.sizeOfSet
    If x && y
        Say(FormatString(cmsgPosInGroup1, IntToString(x), IntToString(y)), ot_position)
    EndIf
    Return
EndIf
PerformScript SayLine()
EndScript

Script SayNextWord()
;Allow shifting page tabs:
If IsPCCursor()
&& InPageTabArea()
&& GetObjectSubtypeCode() == wt_TabControl
    TypeKey(cksCtrlRightArrow)
    Return
EndIf
PerformScript SayNextWord()
EndScript

Script SayPriorWord()
;Allow shifting page tabs:
If IsPCCursor()
&& InPageTabArea()
&& GetObjectSubtypeCode() == wt_TabControl
    TypeKey(cksCtrlLeftArrow)
    Return
EndIf
PerformScript SayPriorWord()
EndScript

Script SelectNextWord()
;Allow shifting page tabs:
If IsPCCursor()
&& InPageTabArea()
&& GetObjectSubtypeCode() == wt_TabControl
    TypeKey(cksShiftControlRightArrow)
    Return
EndIf
PerformScript SelectNextWord()
EndScript

Script SelectPriorWord()
;Allow shifting page tabs:
If IsPCCursor()
&& InPageTabArea()
&& GetObjectSubtypeCode() == wt_TabControl
    TypeKey(cksShiftControlLeftArrow)
    Return
EndIf
PerformScript SelectPriorWord()
EndScript

Int Function IsFocusedOnTabSearchTopChromePage()
;Control+Shift + A brings up the page chrome://tab-search.top-chrome/
Return !UserBufferIsActive()
    && GetWindowClass(GetFocus()) == wc_ChromeWindowClass
    && GetDocumentPath() == scTabSearchTopChromeIdentifyer
EndFunction

Void Function DescriptionChangedEventAnnouncement()
ScheduleIDDescriptionChangedEventAnnouncement = 0
Say (gsDescriptionChangedEventText, OT_WINDOW_INFORMATION)
gsDescriptionChangedEventText = cscNull
EndFunction

Void Function DescriptionChangedEvent(Handle hwnd, Int objId, Int childId, Int nObjType, String sOldDescription, String sNewDescription, optional Int bFromFocusObject)
If bFromFocusObject
&& IsFocusedOnTabSearchTopChromePage()
    ;sNewDescription contains text saying how many matches were found For the text in the search edit field.
    ;This updates For each character typed into the edit field.
    ;To avoid spamming While typing, the announcement is scheduled rather than spoken immediately.
    If ScheduleIDDescriptionChangedEventAnnouncement
        UnscheduleFunction(ScheduleIDDescriptionChangedEventAnnouncement)
    EndIf
    gsDescriptionChangedEventText = sNewDescription
    ScheduleIDDescriptionChangedEventAnnouncement = ScheduleFunction("DescriptionChangedEventAnnouncement", DescriptionChangedEventAnnouncementWaitTime)
    Return
EndIf
DescriptionChangedEvent(hwnd, objId, childId, nObjType, sOldDescription, sNewDescription, bFromFocusObject)
EndFunction

Int Function InDialogWithDocumentDescendant()
If !dialogActive() Return False EndIf
Var
    Int i,
    Int type,
    Int role,
    Int levels
;Start at level 0, just in case the dialog itself has focus.
levels = GetAncestorCount()+1
For i = 0 To levels
    type = GetObjectSubtypeCode(False, i)
    role = GetObjectRole(i)
    If type == wt_dialog
    || role == ROLE_SYSTEM_DIALOG
        Return False
    ElIf type == WT_DOCUMENT
    || role == ROLE_SYSTEM_DOCUMENT
        Return True
    EndIf
EndFor
Return False
EndFunction

Int Function SayNewDocumentTab()
If IsFocusedOnTabSearchTopChromePage()
    Var Int documentLevel = GetDocumentLevel()
    If documentLevel > -1
        gsDocID = GetIDAtLevel(documentLevel)
        If gsDocID != gsPrevDocID
            Say(GetDocumentTitle(), ot_document_name)
            SayObjectTypeAndText()
            gsPrevDocID = gsDocID
            Return True
        EndIf
    EndIf
EndIf
Return SayNewDocumentTab()
EndFunction

Void Function DoBackSpace()
If !IsOnAddressBarEdit()
    DoBackSpace()
    Return
EndIf
Var
    String sSelectedText = GetSelectedText(),
    Int iTextSelected = !StringIsBlank(sSelectedText)
If !iTextSelected
    DoBackSpace()
    Return
EndIf
TypeKey(cksBackspace)
SayFormattedMessage(OT_MESSAGE, cMsgSelectionDeleted, cmsgSilent)
EndFunction

Void Function MSAAAlertEvent(Handle hwnd, Int nTime, String sText, Int nAlertLevel, String appName)
MSAAAlertEvent(hwnd, nTime, sText, nAlertLevel, appName)
If !nAlertLevel
&& c_BackgroundOCRRect.restrictedToCustomRect
    ;The zoom level of the browser may have changed.
    ;Set c_BackgroundOCRRect.shouldUpdateRect To True so the rectangle will be updated.
    c_BackgroundOCRRect.shouldUpdateRect = True
EndIf
EndFunction

Void Function GetARIAActionsNameAndIndexes(String ByRef actionsString, IntArray byref actionIndexes)
Var StringArray ariaActions = GetARIAActions()
If (ArrayLength(ariaActions) == 0)
    actionsString = " "
    Return
EndIf
        
Var Int i
actionIndexes = New IntArray[ArrayLength(ariaActions)]

For i = 1 To ArrayLength(ariaActions)
    Var StringArray actionItem = stringSplit(ariaActions[i], ":", True)
    actionsString = StringConcatenate(actionsString, actionItem[1], LIST_ITEM_SEPARATOR)
    actionIndexes[i] = StringToInt (actionItem[2])
EndFor
EndFunction

Script DisplayARIAActions ()
Var String actionsString 
Var IntArray actionIndexes
GetARIAActionsNameAndIndexes(actionsString, actionIndexes)

If (StringLength (actionsString) == 0)
    SayMessage (OT_ERROR, msgNoActionsAvailable)
    Return
EndIf

Var Int selectedItem = DlgSelectItemInList (actionsString, msgSelectActionTitle, False)
If (!PerformARIAActionByIndex(actionIndexes[selectedItem]))
    SayMessage (OT_ERROR, msgFailedToInvokeAction)
EndIf
EndScript

Void Function DoCloseListBoxKeyStroke()
;Using TypeKey of Alt+UpArrow in Chrome may cause the focus To move To the Chrome menu,
;so we Use Escape To close a listbox For both Chrome and Edge:
EscapeKey()
EndFunction