Jump to content


Photo

Firefox - without using extensions


  • Please log in to reply
10 replies to this topic

#1 ajholman

ajholman
  • Members
  • 6 posts

Posted 27 March 2011 - 06:23 PM

There have been a few methods discussed for automating Firefox, notably:
<!-- m -->http://www.autohotke...topic46974.html<!-- m -->
and
<!-- m -->http://www.autohotke...topic53134.html<!-- m -->

I present here an alternative method which I have not found discussed previously. The method works without needing extensions installed and thus may be suitable for applications where assumptions cannot be made about what extensions are available - for example system administration. However it may not be as powerful as extension-based methods and is not presented as a better solution, rather as an alternative that may be more suited to certain applications.

The method is based on the following observations:

1. Firefox is somewhat resistant to automation with AHK. It does not expose UI elements in a way that AHK can recognise. Any method that interacts with the Firefox UI by, for example, sending key clicks is doing so 'blind' as it cannot directly see the results. It must therefore reduce the assumptions about what is happening in the UI to a minimum. This method only interacts with the location bar (or the Open Web Location dialog if the location bar is unavailable) and the window title.

2. Javascript can be run on the current page by using a commandlet. This is just prepending the javascript with 'javascript:' and entering it in the location bar.

3. The window title can be changed using javascript. It can be set to the value of a variable, DOM node or the return value of a function. As the window title can easily be read using AHK, this is a way that information can be passed out of Firefox.

4. All dialogs and other elements of the Firefox UI can be opened as if they are web pages using urls such as chrome://browser/content/places/places.xul. This includes the main window itself (chrome://browser/content/browser.xul).

5. chrome:// locations are xml documents. They have a DOM, contain javascript functions and can be interacted with using javascript/commandlets exactly as if they are a web page.

Using these observations it is possible to see how a deep penetration in to the Firefox UI can be achieved within a minimal 'footprint' of assumptions about what we can interact with directly from AHK.

I have written a set of utility functions that will interact with Firefox on this basis:
SetTitleMatchMode RegEx
firefoxString = Mozilla Firefox     ; The string that appears at the end of the window title
GroupAdd, firefoxWindow, .*%firefoxString% ahk_class MozillaWindowClass     ; Firefox 4
GroupAdd, firefoxWindow, .*%firefoxString% ahk_class MozillaUIWindowClass   ; Firefox 3. See notes at firefox_open(url)
GroupAdd, firefoxDialog, ahk_class MozillaDialogClass   ; This will however also affect dialogs from eg. Thunderbird
GroupAdd, firefoxOpenDialog, Open Web Location ahk_class MozillaDialogClass ; Appears if the location toolbar is hidden
firefoxUsesOpenDialog := false      ; This will be set to true if Firefox is using the Open Web Location dialog instead of the location toolbar

; Start an instance of the Firefox program, if one doesn't exist already
firefox_start()
{
    IfWinExist ahk_group firefoxWindow
    {
        firefox_checkUsesOpenDialog()
        Return      ; An instance already exists so no need to create another one
    }
    Process, Exist, firefox.exe
    If ErrorLevel
    {
        WinWait, ahk_group firefoxWindow, , 30   ; Firefox may be starting up so wait for the window to open
        If ErrorLevel
        {
            Process, Exist, firefox.exe         ; Check if the process is still there - maybe it closed
            If ErrorLevel
            {
                Process, Close, firefox.exe     ; Firefox is probably stuck so zap it
                If ErrorLevel = 0
                    firefox_throw("Firefox process appears to be stuck")
            }
        }
        Else
        {
            firefox_checkUsesOpenDialog()
            Return  ; Firefox is now open
        }
    }
    RegRead, version, HKLM, SOFTWARE\Mozilla\Mozilla Firefox, CurrentVersion        ; Get Firefox version
    RegRead, path, HKLM, SOFTWARE\Mozilla\Mozilla Firefox\%version%\Main, PathToExe ; Get Firefox exe location
    If ErrorLevel
    {
        firefox_throw("Could not find Firefox installation")
    }
    Run %path% about:blank  ; Start Firefox with a blank page
    count = 0
    Loop    ; Loop until Firefox window opens        
    {
        Sleep 50
        WinClose ahk_group firefoxDialog    ; Close any annoying dialogs that might want to get in the way
        IfWinExist ahk_group firefoxWindow
        {
            Break
        }
        count += 1
        If count > 600  ; Allow approx thirty seconds to open
        {
            firefox_throw("Could not start Firefox")
        }
    }
    firefox_checkUsesOpenDialog()
}

; Close all instances of Firefox
firefox_close()
{
    WinClose ahk_group firefoxDialog
    count = 0
    Loop    ; Loop until all tabs are closed. This avoids dialogs about closing multiple tabs.
    {
        IfWinExist ahk_group firefoxWindow
        {
            BlockInput On
            WinActivate
            SendPlay ^w    ; Closes a tab
            BlockInput Off
            Sleep 100
        }
        Else
        {
            Break
        }
        count += 1
        if count > 100
        {
            Break
        }
    }
    Process, WaitClose, firefox.exe, 10 ; Give process time to exit normally
    Loop    ; Loop until all remaining processes are killed
    {
        Process, Close, firefox.exe
        If ErrorLevel = 0
        {
            Break
        }
    }
}

; Test to see if Firefox is using the Open Web Location dialog
firefox_checkUsesOpenDialog()
{
    global firefoxUsesOpenDialog
    BlockInput On
    WinActivate ahk_group firefoxWindow
    SendPlay {F6}^l
    WinWait, ahk_group firefoxOpenDialog, , 2
    If ErrorLevel = 0
    {
        firefoxUsesOpenDialog := true
        WinClose ahk_group firefoxOpenDialog
    }
    BlockInput Off
}

; Open the url given or use the text as a search term. The url can be a chrome
; location to access parts of the user interface, eg:
;   chrome://browser/content/browser.xul                    - the main browser window*
;   chrome://browser/content/pageinfo/pageInfo.xul          - page info**
;   chrome://browser/content/places/places.xul              - bookmarks and history
;   chrome://browser/content/preferences/preferences.xul    - preferences and settings
;   chrome://browser/content/sanitize.xul                   - clear private information
;   chrome://global/content/console.xul                     - error console*
;   chrome://global/content/customizeToolbar.xul            - customise toolbar**
;   chrome://global/content/viewSource.xul                  - view source**
;   chrome://mozapps/content/downloads/downloads.xul        - download management
;   chrome://mozapps/content/extensions/extensions.xul      - add-on and extension management
;   chrome://mozapps/content/update/updates.xul             - check for updates to Firefox
; Locations marked with * will capture F6 in Firefox 3 and so will not work correctly
; with this function after they are opened.
; Locations marked with ** will not function normally without being opened with
; an appropriate context.
firefox_open(url)
{
    global firefoxUsesOpenDialog
    IfWinNotExist ahk_group firefoxWindow
        firefox_throw("Firefox window not found")
    WinClose ahk_group firefoxDialog    ; Close any annoying dialogs that might want to get in the way
    BlockInput On
    ClipSaved := ClipboardAll
    Clipboard := url
    WinActivate ahk_group firefoxWindow
    ; F6 ensures topmost window is selected if eg chrome://browser/content/browser.xul
    ; has been opened (But see note about Firefox 3 above). ^l selects the location bar.
    ; ^v pastes from clipboard. SendPlay is more reliable for Firefox than SendInput.
    SendPlay {F6}^l
    If firefoxUsesOpenDialog
    {
        WinWait ahk_group firefoxOpenDialog
        WinActivate
        SendPlay ^v{enter}
    }
    Else
    {
        SendPlay ^v{enter}
    }
    Clipboard := ClipSaved
    ClipSaved =
	BlockInput Off
}

; Execute the command as javascript in the current page, eg:
;   firefox_execute("document.body.innerHTML = '<H1>Hello World!</H1>'")
; The command is wrapped in an anonymous function to prevent the content
; of the page being changed to the result of the command. If that is the
; behaviour you want, use firefox_open to execute a commandlet.
firefox_execute(command)
{
    command := "javascript:(function(){" . command . "})();"
    firefox_open(command)
}

; Return the value of a DOM node or a javascript function, eg:
;   foo := firefox_query("document.width")
;   bar := firefox_query("Date()")
; Because this uses document.title to retrieve the value, you can't use this
; method to query document.title itself. You could use WinGetTitle after
; loading the page instead.
firefox_query(node)
{
    global firefoxString
    Random, rnd, 1000000000, 2147483647         ; First set the title bar to something random
    command := "document.title='" . rnd . "'"   ; to make sure that when we then change it to
    firefox_execute(command)                    ; the value we actually want, we know that we
    count = 0                                   ; aren't just getting the previous value.
    loop        ; Loop until the title bar is what we set it to
    {
        Sleep 50
        WinGetTitle, title, ahk_group firefoxWindow
        If title = %rnd% - %firefoxString%
        {
            Break
        }
        count += 1
        If count > 100  ; Allow approx five seconds
        {
            firefox_throw("Could not complete firefox_execute()")
        }
    }
    command := "document.title=" . node
    firefox_execute(command)
    count = 0
    loop        ; Loop until the title bar changes from what we set it to earlier
    {
        Sleep 50
        WinGetTitle, title, ahk_group firefoxWindow
        If title <> %rnd% - %firefoxString%
        {
            Break
        }
        count += 1
        If count > 100  ; Allow approx five seconds
        {
              firefox_throw("Could not complete firefox_execute()")
        }
    }
    StringReplace, title, title, %A_Space%- %firefoxString%
    return title
}

firefox_throw(exception)
{
    MsgBox Error: %exception%
    firefox_close()
    ExitApp
}

Warning: The two example scripts below will close all the Firefox windows you have open and the second will clear your history without confirmation! Make sure you understand what they do before running them. They also assume that the code above is in a file called firefox.ahk in the same directory.

This script will give a quick demo of what they do:
#include %A_ScriptDir%\firefox.ahk

firefox_start()
MsgBox Open chrome location
firefox_open("chrome://mozapps/content/extensions/extensions.xul")
MsgBox Open url
firefox_open("http://www.google.co.uk/")
MsgBox Execute javascript
firefox_execute("document.body.innerHTML = '<H1>Hello World!</H1>'")
MsgBox Query DOM
foo := firefox_query("document.width")
MsgBox Document Width is %foo%
MsgBox Query javascript function
foo := firefox_query("Date()")
MsgBox The date is %foo%
firefox_close()

While this is a less trivial example that will clear the Firefox history:
; Previously selected privacy options will be cleared
#include %A_ScriptDir%\firefox.ahk

firefox_start()
firefox_open("chrome://browser/content/sanitize.xul")   ; Open the Clear History dialog
Loop    ; Keep looping until the dialog has loaded.
{
    check := firefox_query("(gSanitizePromptDialog)?true:false")
    if (check = "true")
        break
    Sleep 50
}
firefox_execute("document.getElementById('sanitizeDurationChoice').value = 0")  ; The duration option in Firefox 4
firefox_execute("document.getElementById('SanitizeDialog').acceptDialog()")     ; Accept the dialog and close the window

There are some rough edges with the functions at present:

1. You may struggle to get effective results with Firefox 3 as many of the chrome locations you will want to open capture F6 and thus render most of the functions ineffective. If anyone can think of a way round this then I would love to hear it. Anyway, use Firefox 4 it is much better :)

2. They assume that 'Mozilla Firefox' appears at the end of the title bar. This might not be true for all installations.

3. You can't query document.title as they use this to pass return values back out of Firefox.

4. If you have Thunderbird open and it pops open a dialog, these will likely close it.

5. While SendPlay is a lot more reliable than SendInput for Firefox, I still find that it occasionally loses key clicks. This might be solved by tweaking the key delay.

6. They assume they have free reign over any and all Firefox windows that may already be open. They could be refined to open their own window and only interact with that one.

7. Probably a bunch of things I haven't even thought of.

Anyway, Hi. First-time poster - constructive criticism very welcome :)

#2 MacroMan!

MacroMan!
  • Members
  • 604 posts

Posted 27 March 2011 - 08:57 PM

Very nice and long awaited.

I think if you really determined to automate web tasks, it easier to use IE with the built in COM support. However, I can see that this script has it's place if needed.

David

#3 ajholman

ajholman
  • Members
  • 6 posts

Posted 27 March 2011 - 09:26 PM

Thanks MacroMan. I agree there are better tools if your goal is to automate a website - I personally would use Greasemonkey or similar. I see this more as a tool for automating some tasks within Firefox itself.

#4 Guests

  • Guests

Posted 28 March 2011 - 07:44 AM

Thanks for this, will have an experiment or two with it.
I am not interested in using I.E. so this is interesting to me.

#5 bruno

bruno
  • Members
  • 161 posts

Posted 28 March 2011 - 10:52 AM

How about (how good is) iOpus iMacros add-on? ... I am using it to do some activities on Etrade? :lol:

#6 ajholman

ajholman
  • Members
  • 6 posts

Posted 28 March 2011 - 12:09 PM

Thanks Guest, let me know how you get on.

I've realised it might not be very obvious that you need to call firefox_checkUsesOpenDialog() if you haven't used firefox_start() to start Firefox (ie. you have started Firefox some other way) - unless you can be sure that the user hasn't hidden the location toolbar in which case you don't need to bother.

bruno - I tried iMacros a few years back and had some success with it but I wasn't hugely taken by it. This is an area where you have quite a few options though so keep trying them out until you find something you like. The second link I posted above also discusses iMacros: <!-- m -->http://www.autohotke...topic53134.html<!-- m -->

edit: I would suggest calling firefox_start() anyway even if you know that FF is already going to be open, as it will call firefox_checkUsesOpenDialog() itself and is less typing. Plus it makes sure that FF really is already open.

#7 ajholman

ajholman
  • Members
  • 6 posts

Posted 28 March 2011 - 12:12 PM

I should have also noted that this was tested with FF3 and 4 only on XP. Can't see why it shouldn't work on later OSes but do let me know if you run in to problems.

#8 verSuS

verSuS
  • Guests

Posted 15 June 2012 - 12:32 PM

I change firefox_execute because old dosen't work.
Maybe something can be do better?

firefox_execute(command)
{
	Sleep 1000
	SendPlay {ShiftDown}{F4}{ShiftUp}
	Sleep 1000
	SendPlay javascript:(function(){
	SendPlay %command%
	SendPlay })();
	Sleep 1000
	SendPlay {CtrlDown}r{CtrlUp}
	Sleep 1000
	SendPlay {CtrlDown}w{CtrlUp}
	Sleep 1000
}


#9 ajholman

ajholman
  • Members
  • 6 posts

Posted 18 June 2012 - 10:23 AM

Thanks verSuS. I no longer use this script so haven't maintained it, and I don't think I'll have time to. Thank you for using it and updating it.

#10 doschnik

doschnik
  • Members
  • 1 posts

Posted 18 June 2012 - 02:01 PM

Where do you get these ff commandos? I'll like to make a personal ff script, therefor I'll have to delete cookies, history, ...

#11 ajholman

ajholman
  • Members
  • 6 posts

Posted 18 June 2012 - 02:07 PM

doschnik, you can examine the DOM of chrome documents using the built-in Firefox DOM inspector or an extension such as Firebug (which is excellent by the way). You will need some knowledge of HTML, XML and javascript.