Inject Javascript into Chrome

Post your working scripts, libraries and tools for AHK v1.1 and older
sancarn
Posts: 224
Joined: 01 Mar 2016, 14:52

Inject Javascript into Chrome

09 Oct 2017, 12:24

I recognize this is relatively simple but it could be useful to some:

Github repository.

Sadly it isn't perfect still as it appears as though I can't figure out how to execute the code through IAccessible. Ultimately that means I can inject the code but I can't run it without entering chrome and sending {F6}{enter} :facepalm:

Regardless, for those who can't install plugins it looks like the best method there is as far as I can see. Also, sadly it doesn't look like there is a 2-way link... So you can send data to chrome, but unless you enable accessibility in chrome://accessibility you won't be able to get any data out unless you use alert(data) however here you can only have around 1600 bytes of data. Sooo yeah.

Anyway I hope some people find this useful :)
User avatar
Delta Pythagorean
Posts: 627
Joined: 13 Feb 2017, 13:44
Location: Somewhere in the US
Contact:

Re: Inject Javascript into Chrome

11 Oct 2017, 19:57

As of right now, I'm working on a Chrome WebAPI to talk to and communicate with websites via Chrome, the problem is I haven't been able to do anything with it lol
As of right now, it's basically a toddler. You can visit the GitHub page in my signature below this message :D

[AHK]......: v2.0.12 | 64-bit
[OS].......: Windows 11 | 23H2 (OS Build: 22621.3296)
[GITHUB]...: github.com/DelPyth
[PAYPAL]...: paypal.me/DelPyth
[DISCORD]..: tophatcat

A_AhkUser
Posts: 1147
Joined: 06 Mar 2017, 16:18
Location: France
Contact:

Re: Inject Javascript into Chrome

13 Oct 2017, 19:42

Hi Sancarn,

Actually I didn't know one can reliably operate upon chrome search bar. Thanks for sharing. It's too bad, however, that it requires such a huge library like acc. For that matter, and as for me - Windows 8.1; chrome up-to-date - injectJS func, most of time, fails to send Enter key in particular and in such a way that I must manually press enter in order to inject the js source.

Btw, here's a example of speech recognition feature by a hacky 2-way link using injectJS and without using any plugin: chrome send the speech recognition result or its recognition state to ahk by its title (retrieved by WInGetTitle) which is set by the injected js, js triggered by WinMove (the javascript also previously set onresize-eventlisteners).

Code: Select all


; note: the script will create two files in the script's current working directory (Dictation.Config.ini and HTMLFile.html)
#NoEnv
; #Warn
; #SingleInstance force

#Include %A_ScriptDir%\acc.ahk  ; requires acc

injectJS(__js) { ; from https://github.com/sancarn/Small_AHK_Projects/blob/master/ChromeInjectJavascript/ChromeInjectJavascript.ahk

	if (__js = "")
		return false
	if (WinExist("ahk_exe chrome.exe")) {
	WinActivate
	WinWaitActive
	Acc_Get("Object", "4.1.2.2.3.5.2",, "ahk_exe chrome.exe").accValue(0) := "javascript:void((function(){" . __js . "})())"
	ControlSend, Chrome Legacy Window, {F6}{Enter}
	return !ErrorLevel
	}
	return false, ErrorLevel:=1
	
}

js =
(LTrim Join
var A_LastWidth = window.outerWidth, A_LastHeight = window.outerHeight;

Dictation = new function() {

	document.getElementById("lang").setAttribute("onclick", "javascript:updateLang(this);return false;");
	window.addEventListener("resize", function() {
	var __width = window.outerWidth - A_LastWidth;
		Dictation.winResizeEventMonitor(__width, !__width);
	});

	return {
		
		recognitionState: 0,
		set recognitionLanguage(__LID) {
		
			if (__LID < 1) return; 
			console.info((document.getElementById("lang")[document.getElementById("lang").selectedIndex=__LID - 1]).value);
			document.getElementById("lang").click();

		},
		start: function() {

		this.recognitionState = 1, document.getElementById("btnClear").click(), document.getElementById("btn").click(), document.title = this.recognitionState;
		
		console.log(arguments.callee.name);
		
			this.titleUpdater = window.setInterval(function() {
				document.title = document.getElementById("labnol").innerText + document.getElementById("notfinal").innerText;
			}, 700);
		
		},
		stop: function() {
	
		this.recognitionState = -1, document.getElementById("btn").click(), clearInterval(this.titleUpdater), document.title = this.recognitionState;
		
		console.log(arguments.callee.name);
	
			window.setTimeout(function(__Dictation) {
				document.title = (__Dictation.recognitionState=0);
			}, 700, this);
		
		},
		setRecognitionLanguage: function(__language) {
		if (this.recognitionState) {
			console.warn(arguments[0]);
		return;
		}
			this.recognitionLanguage = __language;
		},
		
			winResizeEventMonitor: function(__width, __height) {

				if (__height)
				{
					switch(this.recognitionState) {
						case 1:
							this.stop();
						break;
						case 0:
							this.start();
						break;
						case -1:
						break;
					}
					A_LastHeight = window.outerHeight;
					
				}
				else if (__width)
				{
					this.setRecognitionLanguage(__width);
				A_LastWidth = window.outerWidth;
				}
	
			}
			
	}

};
console.log(document.title="test_dictation");
)
global SpeechRecognition, WB

if not (SpeechRecognition:=new Dictation()) { ; create a new instance of Dictation
MsgBox, 64,, % "Dictation.LastError:" . {-3:"NO_INTERNET"
								, -2:"CHROME_NOTFOUND"
								, -1:"CHROME_RUN_ERRORLEVEL"
								, 1:"INJECTJS_ERROR"
								, 2:"INJECTJS_ERROR"
								, 3:"SETRECOGNITIONLANGUAGE_ERROR"
								, 4:"SETRECOGNITIONLANGUAGE_ERROR"}[Dictation.LastError]
ExitApp
}

; speech recognition callback function:
SpeechRecognition.onInterimResult := Func("updateInterimResults")
SpeechRecognition.onResult := Func("saveToClipboard")

Gui, 1:Add, DropDownList, % "vdropDownListControl x12 y9 w150 R10 Choose" . Dictation.startLanguage . " +AltSubmit gsetRecognitionLanguage", % Dictation.languages
Gui, 1:Add, Button, vbuttonControl x172 y9 w160 h20 grecognitionToogleState, start/stop &recognition
Gui, 1:Add, ActiveX, vWB x12 y39 w710 h100, Shell.Explorer
if not (FileExist(var:=A_ScriptDir . "/HTMLFile.html")) {
FileAppend,
(
<!DOCTYPE html>
<html>
    <head>
	<meta http-equiv="X-UA-Compatible" content="IE=edge">
	<meta charset="utf-8" />
	<title>HTMLFile</title>
	<style>
	.S2 {
	vertical-align: sub;
	font-size: 9px;
	color: #666;
	}
	</style>
    </head>
<body>
<span class="X" style="display: none;"></span>
<div class="C" style="padding: 15px;">
</div>
</body>
</html>
), % var, utf-8
}
WB.Navigate("file:///" . var)
while (WB.busy or WB.readyState <> 4)
sleep, 100
Gui, 1:Add, Progress, vprogressControl x12 y149 w710 h10 range0-100, 100
Gui, 1:Show, h170 w734, %A_ScriptName%
OnExit("ExitFunc")
return ; end of the auto-execurte part of the script



setRecognitionLanguage:
GuiControl, 1:Enable0, % A_GuiControl
GuiControl, 1:Enable0, buttonControl
GuiControlGet, var,, % A_GuiControl
SpeechRecognition.setRecognitionLanguage(var)
GuiControl, 1:Enable1, % A_GuiControl
GuiControl, 1:Enable1, buttonControl
return

recognitionToogleState:
GuiControl, 1:Enable0, % A_GuiControl
var := SpeechRecognition.recognizing
SpeechRecognition.recognitionToogleState()
GuiControl, 1:Enable%var%, dropDownListControl
GuiControl, 1:Enable1, % A_GuiControl
return

updateInterimResults(__dictation) {

GuiControl, 1:, progressControl, % ((__dictation.waitForInterimResultTimeRemaining*100)/__dictation.interimResultTimeout)

	if (__dictation.waitForInterimResultTimeRemaining) {
	
		VarSetCapacity(__str, 110*(__interimResultsOutputArray:=StrSplit(__dictation.lastInterimResult, A_Space)).MaxIndex())
		
			Loop % __interimResultsOutputArray.MaxIndex()
				__str .= "<span class=""S1"">" . __interimResultsOutputArray[a_index] . "</span><span class=""S2""> " . a_index . " </span>"
			
			WB.document.getElementsByClassName("C")[0].innerHTML := __str

	} else {
	__dictation.recognitionToogleState()
	GuiControl, 1:Enable, dropDownListControl
	}

}
saveToClipboard(__dictation, __result) {
clipboard := __result
TrayTip, %A_ScriptName%, Result has been copied to clipboard.
}



GuiClose:
ExitApp
ExitFunc() {
return (SpeechRecognition:=0)
}


; ====================


Class Dictation {
	
	static configFile := A_ScriptDir . "\Dictation.Config.ini"
		, url := "https://dictation.io"

		, LastError := 0
		
		recognizing := false
		, interimResultTimeout := 7
		, lastInterimResultElapsedTime := 0
		, lastInterimResult := ""
		, onInterimResultFunc := this.updateInterimResults
		, onResultFunc := this.saveToClipboard
		
		Init() {
			
			static __ := Dictation.Init()

				if not (FileExist(Dictation.configFile)) {
				FileAppend,
				(LTrim
				[start]
				language=62

				[languages]
				Afrikaans=1
				Bahasa Indonesia=2
				Bahasa Melayu=3
				Català=4
				Čeština=5
				Dansk=6
				Deutsch=7
				Australia=8
				Canada=9
				India=10
				New Zealand=11
				South Africa=12
				English=13
				United States=14
				Argentina=15
				Bolivia=16
				Chile=17
				Colombia=18
				Costa Rica=19
				Ecuador=20
				El Salvador=21
				Español=22
				Estados Unidos=23
				Guatemala=24
				Honduras=25
				México=26
				Nicaragua=27
				Panamá=28
				Paraguay=29
				Perú=30
				Puerto Rico=31
				República Dominicana=32
				Uruguay=33
				Venezuela=34
				Euskara=35
				Filipino=36
				Français=37
				Galego=38
				हिन्दी=39
				Hrvatski=40
				IsiZulu=41
				Íslenska=42
				Italiano=43
				Svizzera=44
				Lietuvių=45
				Magyar=46
				Nederlands=47
				Norsk bokmål=48
				Polski=49
				Brasil=50
				Portugal=51
				Română=52
				Slovenščina=53
				Slovenčina=54
				Suomi=55
				Svenska=56
				Tiếng Việt=57
				ภาษาไทย=58
				Türkçe=59
				Ελληνικά=60
				български=61
				Русский=62
				Српски=63
				Українська=64
				한국어=65
				普通话 (中国大陆)=66
				普通话 (香港)=67
				中文 (台灣)=68
				粵語 (香港)=69
				日本語=70
				
				), % Dictation.configFile, utf-16
				}
			
				IniRead, __language, % Dictation.configFile, start, language
					if (__language == "ERROR")
				ExitApp
					Dictation.startLanguage := __language
				
				IniRead, __languages, % Dictation.configFile, languages
					if (__languages == "ERROR")
				ExitApp
				Dictation.languages := RTrim(RegExReplace(__languages, "=.+?\n", "|"), "=" . StrSplit(__languages, "`n").length())
		
		}

	__New() {
		
		if not (DllCall("Wininet.dll\InternetGetConnectedState", "Str", 0x40, "Int", 0)) ; check internet connection
			return !Dictation.LastError:=-3
			
		RegRead, __regKey, HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\App Paths\Chrome.exe ; retrieves chrome.exe path via registry
		if (ErrorLevel)
			return !Dictation.LastError:=-2
		
		run % """" . __regKey . """ " . Dictation.url,, UseErrorLevel
		if (ErrorLevel)
			return !Dictation.LastError:=-1
		
		WinWait % Dictation.__Class . A_Space . "ahk_exe chrome.exe"
		this.HWND := "ahk_id " . WinExist()
		
		sleep, 3000
		
		global js
		injectJS(js)
		if (ErrorLevel)
			return !Dictation.LastError:=1
		sleep, 500
		MsgBox, Can't run the js without entering manually {Enter}...
		WinWait % "test_dictation" . A_Space . this.HWND,, 4
		if (ErrorLevel)
			return !Dictation.LastError:=2

		WinMove, % this.HWND,,,,, % A_ScreenHeight
		WinRestore, % this.HWND
	
	this.setRecognitionLanguage(Dictation.startLanguage)
	if (ErrorLevel)
		return !Dictation.LastError:=ErrorLevel
		
	
	return this
	}
	
	__Delete() {
		
		if (Dictation.LastError >= 0) {

			if (this.recognitionState())
				this.recognitionToogleState()
			
			PostMessage, % (WM_SYSCOMMAND:="0x112"), % (SC_CLOSE:="0xF060"),,, % this.HWND
			
		}
	
	}

		recognitionToogleState() {
	
		static __x := 0, __y := 1
			
			IfEqual, __x, %__y%, return
			__x := __y
			
			if (__f:=this.boundIterator) {
				
				SetTimer, % __f, off
				SetTimer, % __f, delete
				this.boundIterator := ""
				
			}				
			WinMove, % this.HWND,,,,, % A_ScreenHeight - __y
			
			WinWait % __y . A_Space . this.HWND,, 2
			if (ErrorLevel)
				ExitApp
			
			if (this.recognizing:=__y) {
				this.boundIterator := __f := this.updateResult.Bind(this)
				SetTimer, % __f, % this.iteratorPeriod
			} else this.onResultFunc.Bind(this, this.lastInterimResult).Call(), this.lastInterimResultElapsedTime := 0, this.lastInterimResult := ""
			
		__y := !__y
		
		return !ErrorLevel
		}
		recognitionState() {
		return this.recognizing
		}
		setRecognitionLanguage(__language) {
		
		static __l := StrSplit(Dictation.languages, "|").length()
		
			if not (((__language:=abs(__language)) >= 1) and __language <= __l)
		return !ErrorLevel:=3
			
			SetWinDelay, -1
			
			WinGetPos,,, __w1,, % "ahk_id " . WinExist(this.HWND)
				
				WinMove,,,,, % __w1 - (__language)
				WinGetPos,,, __w2
					sleep, 100
				WinMove,,,,, % __w1
				WinGetPos,,, __w1

		return ErrorLevel:=!((__w1 - __w2 == __language)*4)
		}
			
				iteratorPeriod {
					set {
					static ITERATOR_MIN_PERIOD := 250
						if (value < ITERATOR_MIN_PERIOD)
					return ITERATOR_MIN_PERIOD
					return value
					}
				}
			
			interimResult {
				set {				
				if (this.lastInterimResult == value)
					this.lastInterimResultElapsedTime += 0.5
				else this.lastInterimResult := value, this.lastInterimResultElapsedTime := 0
				this.waitForInterimResultTimeRemaining := this.interimResultTimeout - this.lastInterimResultElapsedTime
				this.onInterimResultFunc.Bind(this).Call()
				}
			}			
			updateResult() {				
			WinGetTitle, __winTitle, % this.HWND
			this.interimResult := InStr(__winTitle, Dictation.url) ? "" : StrSplit(__winTitle, " - Google Chrome")[1]
			}
				
				onInterimResult {
					set {
					if (value.maxParams > 0 or (value:=Func(value)).maxParams > 0) {
						this.onInterimResultFunc := value
					return !ErrorLevel:=0
					}
						this.onInterimResultFunc := this.updateInterimResults
					return !ErrorLevel:=6
					}
				}
				onResult {
					set {
					if (value.maxParams > 0 or (value:=Func(value)).maxParams > 0) {
						this.onResultFunc := value
					return !ErrorLevel:=0
					}
						this.onResultFunc := this.saveToClipboard
					return !ErrorLevel:=7
					}
				}
				
				updateInterimResults() {

					if (this.waitForInterimResultTimeRemaining) {
					TrayTip,, % this.lastInterimResult,, 0x1
					} else this.recognitionToogleState()
					
				}
				saveToClipboard(__result) {
				clipboard := __result
				}

}
my scripts
sancarn
Posts: 224
Joined: 01 Mar 2016, 14:52

Re: Inject Javascript into Chrome

14 Oct 2017, 09:48

A_AhkUser wrote:Actually I didn't know one can reliably operate upon chrome search bar. Thanks for sharing. It's too bad, however, that it requires such a huge library like acc. For that matter, and as for me - Windows 8.1; chrome up-to-date - injectJS func, most of time, fails to send Enter key in particular and in such a way that I must manually press enter in order to inject the js source.
Well of course it doesn't require the whole of acc.ahk I was just too lazy to separate the functions I needed... :P Even more so, it's my custom version of acc.ahk which is even bigger... Really it only needs Acc_Parent, Acc_Child, Acc_GetChild and Acc_ObjectFromWindow.

Code: Select all

Acc_Init()
{
	Static	h := DllCall("LoadLibrary","Str","oleacc","Ptr")
}

Acc_Query(Acc) { 
	try return ComObj(9, ComObjQuery(Acc, "{618736e0-3c3d-11cf-810c-00aa00389b71}"), 1)
}

Acc_Parent(Acc) { 
	try parent := Acc.accParent
	return parent ? Acc_Query(parent) :
}

Acc_Child(Acc, ChildId=0) {
	try child := Acc.accChild(ChildId)
	return child ? Acc_Query(child) :
}

; thanks Lexikos - www.autohotkey.com/forum/viewtopic.php?t=81731&p=509530#509530
Acc_Query(Acc) { 
	try return ComObj(9, ComObjQuery(Acc, "{618736e0-3c3d-11cf-810c-00aa00389b71}"), 1)
}

Acc_GetChild(Acc_or_Hwnd, child_path) {
   Acc := WinExist("ahk_id" Acc_or_Hwnd)? Acc_ObjectFromWindow(Acc_or_Hwnd):Acc_or_Hwnd
   if ComObjType(Acc,"Name") = "IAccessible" {
      Loop Parse, child_path, csv
         Acc := A_LoopField="P"? Acc_Parent(Acc):Acc_Children(Acc)[A_LoopField]
      return Acc
   }
}

Acc_ObjectFromWindow(hWnd, idObject = -4){
	Acc_Init()
	if (DllCall("oleacc\AccessibleObjectFromWindow"
			  , "Ptr", hWnd
			  , "UInt", idObject &= 0xFFFFFFFF
			  , "Ptr", -VarSetCapacity(IID,16)
			           + NumPut(idObject == 0xFFFFFFF0
							    ? 0x46000000000000C0
								: 0x719B3800AA000C81
								, NumPut(idObject == 0xFFFFFFF0
								? 0x0000000000020400
								: 0x11CF3C3D618736E0,IID,"Int64"),"Int64")
			  , "Ptr*", pacc) = 0)
		return ComObjEnwrap(9,pacc,1)
}

UriEncode(Uri, Enc = "UTF-8")
{
	StrPutVar(Uri, Var, Enc)
	f := A_FormatInteger
	SetFormat, IntegerFast, H
	Loop
	{
		Code := NumGet(Var, A_Index - 1, "UChar")
		If (!Code)
			Break
		If (Code >= 0x30 && Code <= 0x39 ; 0-9
			|| Code >= 0x41 && Code <= 0x5A ; A-Z
			|| Code >= 0x61 && Code <= 0x7A) ; a-z
			Res .= Chr(Code)
		Else
			Res .= "%" . SubStr(Code + 0x100, -1)
	}
	SetFormat, IntegerFast, %f%
	Return, Res
}

getSingleLineOfJS(js){
	oHTTP   := ComObjCreate("WinHttp.WinHttpRequest.5.1")
	oHTTP.Open("POST", "https://javascript-minifier.com/raw", True)
	oHTTP.SetRequestHeader("Content-Type", "application/x-www-form-urlencoded")
	data:= "input=" . UriEncode(js)
	oHTTP.send(data)
	Status := oHTTP.WaitForResponse(-1)  ; Success = -1, Timeout = 0, No response = Empty String
	if status=0
	{
		msgbox, "You are not connected to the internet."
		return
	}
	return oHTTP.ResponseText
}

injectJS(js,minify=1){
        if minify
	    js := getSingleLineOfJS(js)
	if !js 
		return
	addressBar := Acc_GetChild(WinExist("ahk_exe chrome.exe"),"4.1.2.2.3.5.2")
	winactivate,ahk_exe chrome.exe
	addressBar.accValue(0) := "javascript:void((function(){" . js . "})())"
	ControlSend,Chrome Legacy Window,{F6}{enter},ahk_exe chrome.exe
}
I think the above code should work out... However in general when working on a project I'll keep all the libraries I can. But I guess that's the difference between a major and minor projects :P I can see why on a minor project you'd want cut down code :) Also to be fair a lot of the whitespace in the acc code could be cut down. It'd be kinda cool to have an ahk version of minify :D
Btw, here's a example of speech recognition feature by a hacky 2-way link using injectJS and without using any plugin: chrome send the speech recognition result or its recognition state to ahk by its title (retrieved by WInGetTitle) which is set by the injected js, js triggered by WinMove (the javascript also previously set onresize-eventlisteners).
Damn that's cool :D Thanks for sharing :D
bodosko
Posts: 19
Joined: 05 May 2017, 10:49

Re: Inject Javascript into Chrome

24 Jun 2020, 10:40

I know this thread is very old, but if people still need this as like me...

A simple way to achieve what op needed without relying on sending {F6}{ENTER}:
1) Save a bookmark of the javascript code to be executed.
2) Use Acc lib to press/click that saved bookmark

I have a simple example here to mute/unmute your microphone in Google Meet from anywhere.
Save this JS as a bookmark:

Code: Select all

javascript:document.querySelector('[data-is-muted=false] > div').click();void(0)
And use this ahk script:

Code: Select all

#include Lib\Acc.ahk
SetTitleMatchMode, 2

#IfWinExist, Meet:
#Space::
	oo := Acc_Get("Object", "4.1.2.1.4.35", 0, "Meet:") ; Change the path to yours bookmarked JS
	oo.accDoDefaultAction(0)
Return
From that you can accomplish more advanced things like click bookmark with X or Y name, etc...
malcev
Posts: 1769
Joined: 12 Aug 2014, 12:37

Re: Inject Javascript into Chrome

25 Jun 2020, 11:22

sancarn, Your js injection may fail.
Better use this variant
https://www.autohotkey.com/boards/viewtopic.php?f=76&t=75527

Return to “Scripts and Functions (v1)”

Who is online

Users browsing this forum: No registered users and 115 guests