I think an additional option is missing, thus will fall under "Other". That is using the
RunJsFromChromeAddressBar function. This has a lot of potential as a minimalist and all AHK coding method, that doesn't require 3rd party extensions. References to this option:
https://www.autohotkey.com/boards/viewtopic.php?p=326962#p326962
(Detect clickable link in Chrome)
https://www.autohotkey.com/boards/viewtopic.php?p=362634#p362634
(AHK with Chrome extension (No Selenium, no debug mode, no web connectors)
A major factor is you have to learn some JavaScript (js), in order to do different tasks or have pre-coded JavaScript in a function for a particular task dealing with web pages. There is almost no way around needing to learn some JavaScript because that is the default language of web browsers and often the code you need to automate a specific web page can be different.
You could use image search, OCR, and or shortcut keys for the web browser instead. Some competing automation tools are entirely image based. These methods are somewhat doable and usable, if the task is simple. However, those methods will be problematic for more complicated tasks (like getting certain data out of web pages), greater precision, and when trying to use the script on multiple computers.
Even if we had an Open-Source AutoHotkey extension for Chrome and Edge (this includes the 3rd party extension AutoControl), it would still require that the AHK Coder know some JavaScript and about CSS Selectors. Though I do want to make it clear that I'm in no way against a possible AutoHotkey extension, which could make things easier. AutoControl is 3rd party, Closed-Source, and requires various permissions. So the privacy concerns have to be weighed against its benefits (which I agree it has some).
Another extension/add-on to look at for Chrome, Edge, and Firefox is SelectorsHub,
https://selectorshub.com/ or find it more directly like at the
Chrome web store. This tool makes it much easier to find the CSS Selector, XPath, or JS Path for a visual object on a web page to be used with a script. This is similar to a feature that many RPA tools have built-in. The CSS Selector for what is wanted on the web page can then be more easily placed into the JavaScript portion of the script.
Example of the RunJsFromChromeAddressBar function
Code: Select all
ClickLink("Gmail")
Return
ClickLink(LinkText, exe := "msedge.exe")
{
js =
(LTrim
(() =>
{
const links = document.links;
for (let i = 0; i < links.length; i++)
{
if (links[i].innerText == '%LinkText%')
{
links[i].click();
break
}
}
})();
)
RunJsFromChromeAddressBar(js, exe)
}
RunJsFromChromeAddressBar(js, exe := "msedge.exe")
{
static WM_GETOBJECT := 0x3D
, ROLE_SYSTEM_TEXT := 0x2A
, STATE_SYSTEM_FOCUSABLE := 0x100000
, SELFLAG_TAKEFOCUS := 0x1
, AccAddrBar
if !AccAddrBar {
window := "ahk_class Chrome_WidgetWin_1 ahk_exe " . exe
SendMessage, WM_GETOBJECT, 0, 1, Chrome_RenderWidgetHostHWND1, % window
AccChrome := AccObjectFromWindow( WinExist(window) )
AccAddrBar := SearchElement(AccChrome, {Role: ROLE_SYSTEM_TEXT, State: STATE_SYSTEM_FOCUSABLE})
}
AccAddrBar.accValue(0) := "javascript:" . js
AccAddrBar.accSelect(SELFLAG_TAKEFOCUS, 0)
ControlSend,, {Enter}, % window, Chrome Legacy Window
}
SearchElement(parentElement, params)
{
found := true
for k, v in params {
try {
if (k = "ChildCount")
(parentElement.accChildCount != v && found := false)
else if (k = "State")
(!(parentElement.accState(0) & v) && found := false)
else
(parentElement["acc" . k](0) != v && found := false)
}
catch
found := false
} until !found
if found
Return parentElement
for k, v in AccChildren(parentElement)
if obj := SearchElement(v, params)
Return obj
}
AccObjectFromWindow(hWnd, idObject = 0)
{
static IID_IDispatch := "{00020400-0000-0000-C000-000000000046}"
, IID_IAccessible := "{618736E0-3C3D-11CF-810C-00AA00389B71}"
, OBJID_NATIVEOM := 0xFFFFFFF0, VT_DISPATCH := 9, F_OWNVALUE := 1
, h := DllCall("LoadLibrary", "Str", "oleacc", "Ptr")
VarSetCapacity(IID, 16), idObject &= 0xFFFFFFFF
DllCall("ole32\CLSIDFromString", "Str", idObject = OBJID_NATIVEOM ? IID_IDispatch : IID_IAccessible, "Ptr", &IID)
if DllCall("oleacc\AccessibleObjectFromWindow", "Ptr", hWnd, "UInt", idObject, "Ptr", &IID, "PtrP", pAcc) = 0
Return ComObject(VT_DISPATCH, pAcc, F_OWNVALUE)
}
AccChildren(Acc)
{
static VT_DISPATCH := 9
Loop 1 {
if ComObjType(Acc, "Name") != "IAccessible" {
error := "Invalid IAccessible Object"
break
}
try cChildren := Acc.accChildCount
catch
Return ""
Children := []
VarSetCapacity(varChildren, cChildren*(8 + A_PtrSize*2), 0)
res := DllCall("oleacc\AccessibleChildren", "Ptr", ComObjValue(Acc), "Int", 0
, "Int", cChildren, "Ptr", &varChildren, "IntP", cChildren)
if (res != 0) {
error := "AccessibleChildren DllCall Failed"
break
}
Loop % cChildren {
i := (A_Index - 1)*(A_PtrSize*2 + 8)
child := NumGet(varChildren, i + 8)
Children.Push( (b := NumGet(varChildren, i) = VT_DISPATCH) ? AccQuery(child) : child )
( b && ObjRelease(child) )
}
}
if error
ErrorLevel := error
else
Return Children.MaxIndex() ? Children : ""
}
AccQuery(Acc)
{
static IAccessible := "{618736e0-3c3d-11cf-810c-00aa00389b71}", VT_DISPATCH := 9, F_OWNVALUE := 1
try Return ComObject(VT_DISPATCH, ComObjQuery(Acc, IAccessible), F_OWNVALUE)
}