1 2 Previous Next 18 Replies Latest reply: Jan 17, 2014 4:13 PM by Neville Hillyer
Bernard Harte Level 4 Level 4 (3,050 points)

The title says it all: Can someone tell me please how - most likely via UI scripting, since I can't see anything in the dictionary - I can get Safari to save the front most document as a Web Archive using AppleScript?

  • 1. Re: AppleScript: Safari - Save As Web Archive
    Pierre L. Level 4 Level 4 (3,860 points)

    Hi Bernard,

     

    The following script should do the trick:

     

    tell application "Safari" to activate

     

    tell application "System Events"

        tell process "Safari"

            keystroke "s" using command down -- Save As…

            repeat until sheet 1 of window 1 exists

                delay 0.1

            end repeat

            keystroke "d" using command down -- save to Desktop

            tell sheet 1 of window 1

                click pop up button 1 of group 1

                keystroke "W" & return -- Web Archive

                keystroke return -- Save

                if sheet 1 exists then -- that is "sheet 1 of sheet 1"

                    click button "Replace" of sheet 1

                end if

            end tell

        end tell

    end tell


  • 2. Re: AppleScript: Safari - Save As Web Archive
    Bernard Harte Level 4 Level 4 (3,050 points)

    Thank you, Pierre.

     

    I was hoping that the King of UI Scripting would come to my rescue!

     

    It works perfectly.

     

    -Bernard

  • 3. Re: AppleScript: Safari - Save As Web Archive
    Pierre L. Level 4 Level 4 (3,860 points)

    My pleasure, Bernard.

  • 4. Re: AppleScript: Safari - Save As Web Archive
    Bernard Harte Level 4 Level 4 (3,050 points)

    I have hit a problem:

     

    When I put your suggested script into a loop where the URL of the frontmost document is set by the script, I get stuck in the loop after the keystroke "s" that's waiting for sheet 1 of window 1 to exist.

     

    I thought at first it was because tell "System Events" etc was nested in an overall tell "Safari", but even de-nesting makes no difference.  It's as though the cmd-S to bring up the save dialogue is not happening.  (If I do cmd-S from the keyboard whilst in the loop, the script will move on.)

     

    Any idea what's happening?  (The following is a mix of AS and pseudocode just to show structure.)

     

     

    tell app "Safari" to activate

     

    repeat with theURL in a list

     

    tell app "Safari"

    set the URL to theURL

    wait until loaded

    end tell

     

    tell app "System Events"

    tell process "Safari"

    keystroke "s" using command down

    repeat until sheet 1 of window 1 exists

    delay 1

    end repeat

    end tell

    end tell

     

    ...

     

    end repeat

  • 5. Re: AppleScript: Safari - Save As Web Archive
    Bernard Harte Level 4 Level 4 (3,050 points)

    I tried sticking a short delay in after the tell process "Safari" and that fixed it.

     

    It's strange, as the previous section checks that the page is fully loaded before moving on, but I guess it's still just too quick!

  • 6. Re: AppleScript: Safari - Save As Web Archive
    Pierre L. Level 4 Level 4 (3,860 points)

    I don't know how you check that the page is fully loaded. The following method seems to work pretty well:

     

    set theList to {"http://www.apple.com/", "http://www.macrumors.com/", "http://hints.macworld.com/", "http://www.ee.surrey.ac.uk/Teaching/Unix/"}

     

    tell application "Safari"

        activate

        repeat with theURL in theList

            set the URL of front document to theURL

            tell application "System Events"

                tell process "Safari"

                    tell menu 1 of menu bar item "File" of menu bar 1

                        repeat while enabled of menu item "Save as…" is false

                            delay 0.1

                        end repeat

                    end tell

                    keystroke "s" using command down -- Save As…

                    repeat until sheet 1 of window 1 exists

                        delay 0.1

                    end repeat

                    keystroke "d" using command down -- save to Desktop

                    tell sheet 1 of window 1

                        click pop up button 1 of group 1

                        keystroke "W" & return -- Web Archive

                        keystroke return -- Save

                        if sheet 1 exists then -- that is "sheet 1 of sheet 1"

                            click button "Replace" of sheet 1

                        end if

                    end tell

                end tell

            end tell

        end repeat

    end tell

     

    Message was edited by: Pierre L. (I've replaced “Fichier” with “File”.)

  • 7. Re: AppleScript: Safari - Save As Web Archive
    twtwtw Level 5 Level 5 (4,690 points)

    to test if a page is fully loaded, test in javascript:

     

    set targetURL to "http://www.yahoo.com"

    tell application "Safari"

              tell document 1

                        set URL to targetURL

      delay 0.5 -- so it doean't catch that last loaded page; probably more elegant ways of doing this

                        repeat while ((do JavaScript "document.readyState") ≠ "complete")

                                  delay 0.1

                        end repeat

              end tell

              say "done"

    end tell

  • 8. Re: AppleScript: Safari - Save As Web Archive
    Pierre L. Level 4 Level 4 (3,860 points)

    Thanks, twtwtw, for that information.

     

    Maybe I wasn't clear in my previous post, but when I said “I don't know how you check that the page is fully loaded”, I was actually meaning “You did not explained to me how you managed to check that the page was fully loaded” and not “I don't know how to check that the page is fully loaded.”

     

    Between

     

        repeat while enabled of menu item "Save as…" is false

            delay 0.1

        end repeat

     

    and

     

        repeat while ((do JavaScript "document.readyState") ≠ "complete")

            delay 0.1

        end repeat

     

    I think I prefer the former one (that is, pure AppleScript), unless it proves to be less efficient. 

     

    Message was edited by: Pierre L.

  • 9. Re: AppleScript: Safari - Save As Web Archive
    Bernard Harte Level 4 Level 4 (3,050 points)

    I was not able to answer your question, Pierre, before twtwtw's response but my test was the JavaScript method.  Not generally being a UI scripting proponent, I had not thought of your method, but I see your point about it being 'pure AppleScript'.

     

    I suspect that the reason I need to inject the delay with the JavaScript method is that it confirms completion before the UI has had a chance to update - therefore, preventing cmd-S from being 'seen' by Safari.  In most cases that would not matter, but if one is then relying on UI scripting it does.

  • 10. Re: AppleScript: Safari - Save As Web Archive
    Alex Zavatone Level 1 Level 1 (0 points)

    Please note that the delay command, in a compiled AS app or when compiled into Xcode AS, has been seen to use 100% of one processor core. 

     

    I haven't checked in 2 years, but I ran into this in the past.

     

    Just FYI.

  • 11. Re: AppleScript: Safari - Save As Web Archive
    Pierre L. Level 4 Level 4 (3,860 points)

    Thanks for the information.

     

    I've searched the Web and found a few discussions about that issue, like this one.

    Then, in order to test the delay command on my own, I've saved the following script as an application:

     

        beep 1

        repeat 300 times

            delay 0.1

        end repeat

        beep 2

     

    Here's a screenshot of the Activiy Monitor window while running the above script:

     

          Capture d’écran 2012-03-08 à 11.57.22.png

     

    Message was edited by: Pierre L.

  • 12. Re: AppleScript: Safari - Save As Web Archive
    Alex Zavatone Level 1 Level 1 (0 points)

    What ended up surprising me Pierre, was that first sleep worked when run as an AS script.  But when run in an applet, it used 100% of a core.  Then Apple fixed that.  Then I moved my scripts into Xcode and guess what. When run as a compiled app, it again used 100% of a core.

     

    So, to avoid wasting more time, I'll just use shell sleep like so:

     

    do shell script "sleep 2"

     

    Sorry I didn't include that information before.

  • 13. Re: AppleScript: Safari - Save As Web Archive
    red_menace Level 6 Level 6 (14,610 points)

    Note that in Xcode the delay command has become a bit squirrely, and doesn't appear to work like it used to.  Normally you should use something like NSObject's performSelector:withObject:afterDelay: to avoid blocking the UI.

  • 14. Re: AppleScript: Safari - Save As Web Archive
    macGeeek Level 1 Level 1 (0 points)

    This is one I have recently written that saves all tabs in a window, it remembers the parent folder until next time, and also uses the webarchive  ending when Safari is in doubt. You can set properties for whether you like it to overwrite or not. Please look at Macscripter for any updates.

     

     

    property tlvl : me

    # Release 1.0.1

    # © 2012 McUsr and  put in Public Domain under GPL 1.0

    # Please refer to this post: http://macscripter.net/post.php?tid=30892

    property shallClose : false # set this to false if you don't want to close the windows, just saving them

    property dontOverWriteSavedTabs : false # set this to true if you don't want to overwrite already saved tabs in the folder

    script saveTabsInSafariWindowsToFolder

              property parent : AppleScript

     

              property scripttitle : "SafariSaveTabs"

              on run

                        if downloadWindowInFront() then return 0 # activates Safari

     

                        local script_cache

                        set script_cache to my storage's scriptCache()

     

                        set saveFolder to POSIX path of (getHFSFolder({theMessage:"Choose or create folder to save Safari-tabs in.", hfsPath:DefaultLocation of script_cache as alias}))

                        if saveFolder = false then return 0 -- we were obviously mistaken, about what we wanted to do.

     

                        my storage's saveParenFolderInScriptCache(saveFolder, script_cache)

     

                        tell application "Safari"

                                  tell its window 1

                                            local tabc, oldidx

                                            set tabc to count tabs of it

                                            if not tlvl's shallClose then

                                                      set oldidx to index of current tab

                                                      tell tab tabc to do JavaScript "self.focus()"

                                            end if

                                            local saveCounter

                                            set saveCounter to 1 -- regulates setting of save folder to only first time in Safari.

                                            repeat while tabc > 0

                                                      local theUrl, theIdx, theProtocol, alreadyClosed

     

                                                      set {theUrl, theIdx, alreadyClosed} to {URL of its current tab, index of its current tab, false}

     

                                                      if my isntAduplicateTab(theIdx, it) then

     

                                                                set theProtocol to my urlprotocol(theUrl)

                                                                if theProtocol is in {"http", "https"} then

                                                                          # save it

                                                                          set saveCounter to my saveCurrentTab(saveFolder, saveCounter)

                                                                else if theProtocol is "file" then

                                                                          # make an alias of it

                                                                          my makeAliasForAFurl(saveFolder, theUrl)

                                                                end if

                                                      else

                                                                if tlvl's shallClose then

                                                                          close current tab

                                                                          set alreadyClosed to true

                                                                end if

                                                      end if

     

                                                      if not alreadyClosed and tlvl's shallClose then

                                                                close current tab of it

                                                                set tabc to tabc - 1

                                                      else if not tlvl's shallClose then

                                                                set tabc to tabc - 1

                                                                if tabc > 0 then tell tab tabc to do JavaScript "self.focus()"

                                                      end if

                                            end repeat

                                            # move forwards

                                            if not tlvl's shallClose then

                                                      tell tab oldidx to do JavaScript "self.focus()"

                                            end if

                                  end tell

                        end tell

              end run

     

     

              to makeAliasForAFurl(destinationFolder, furl)

                        local ti, tids, thefilePath

                        set ti to "file://"

                        set {tids, AppleScript's text item delimiters} to {AppleScript's text item delimiters, ti}

                        set thefilePath to text item 2 of furl

                        set AppleScript's text item delimiters to tids

                        set theFile to POSIX file thefilePath as alias

                        set theFolder to POSIX file destinationFolder

                        tell application "Finder"

                                  make alias at theFolder to theFile

                                  # I don't care if there was one there from before, as it could equally

                                  # be a file with the same name.

                        end tell

              end makeAliasForAFurl

     

              to saveCurrentTab(destinationFolder, timeNumber)

                        tell application id "sfri" to activate

                        tell application "System Events"

                                  set UI elements enabled to true

                                  tell process "Safari"

                                            keystroke "s" using {command down}

                                            tell window 1

                                                      repeat until exists sheet 1

                                                                delay 0.2

                                                      end repeat

                                                      tell sheet 1

                                                                if timeNumber = 1 then -- We'll set the savepath upon first call

                                                                          keystroke "g" using {command down, shift down}

                                                                          repeat until exists sheet 1

                                                                                    delay 0.2

                                                                          end repeat

                                                                          tell sheet 1

                                                                                    set value of text field 1 to destinationFolder

                                                                                    click button 1

                                                                                    delay 0.1

                                                                          end tell

                                                                end if

                                                                keystroke return

                                                                delay 0.2

                                                                if exists sheet 1 then -- We are being asked if we want to overwrite already saved tab

                                                                          if dontOverWriteSavedTabs then

                                                                                    keystroke return # if it was already saved. We don't overwrite it

                                                                                    click button 3

                                                                          else

                                                                                    keystroke tab

                                                                                    keystroke space # we are  to overwrite

                                                                          end if

                                                                else

                                                                          try

                                                                                    set dummy to focused of sheet 1

                                                                          on error

                                                                                    # click button 1 of panel of application "Safari"

                                                                                    keystroke return

     

                                                                                    delay 0.2

                                                                                    if exists sheet 1 then -- We are being asked if we want to overwrite already saved tab

                                                                                              if dontOverWriteSavedTabs then

                                                                                                        keystroke return # if it was already saved. We don't overwrite it

                                                                                                        click button 3

                                                                                              else

                                                                                                        keystroke tab

                                                                                                        keystroke space # we are  to overwrite

                                                                                              end if

                                                                                    end if

                                                                          end try

                                                                end if

                                                      end tell

                                            end tell

                                  end tell

                        end tell

                        set timeNumber to timeNumber + 1

                        return timeNumber

              end saveCurrentTab

     

              on downloadWindowInFront()

                        tell application "Safari"

                                  activate

                                  set tabCount to count tabs of its window 1

                                  if tabCount < 1 then

                                            tell application "SystemUIServer" to activate

                                            activate

                                            return true # Downloads window or somethingelse

                                  end if

                        end tell

                        return false

              end downloadWindowInFront

     

              on isntAduplicateTab(idxOfCurrentTab, theWin)

                        using terms from application "Safari"

                                  tell theWin

                                            set curTabname to name of tab idxOfCurrentTab

                                            set curTabUrl to URL of tab idxOfCurrentTab

                                            repeat with i from (idxOfCurrentTab - 1) to 1 by -1

                                                      if name of tab i = curTabname and URL of tab i = curTabUrl then return false

                                            end repeat

                                            return true

                                  end tell

                        end using terms from

              end isntAduplicateTab

     

              on getHFSFolder(R) -- Returns hfsPathAsText

                        -- R : {Amessage:theMessage,hfsPath:aStartPath}

                        local new_path, failed

                        set failed to false

                        tell application "SystemUIServer"

                                  activate

                                  repeat while true

                                            try

                                                      set new_path to (choose folder with prompt (theMessage of R) default location (hfsPath of R) without invisibles) as text

                                            on error e number n

                                                      if n is -128 then

                                                                set failed to true

                                                                exit repeat

                                                      end if

                                            end try

                                            exit repeat

                                  end repeat

                        end tell

                        if failed is true then

                                  return false

                        else

                                  return new_path

                        end if

              end getHFSFolder

     

              on urlprotocol(anUrl)

                        # returns the protocol of an Url, i.e. http, https, file, localhost etc.

                        local tids, theProtocol

                        set {tids, AppleScript's text item delimiters} to {AppleScript's text item delimiters, "://"}

                        set theProtocol to text item 1 of anUrl

                        set AppleScript's text item delimiters to tids

                        return theProtocol

              end urlprotocol

     

              to parentfolder for aPath

                        local colons, slashes, origDelims

                        set {colons, slashes} to {false, false}

     

                        if (offset of ":" in aPath) > 0 then set colons to true

                        if (offset of "/" in aPath) > 0 then set slashes to true

     

                        if colons and slashes then

                                  return null

                        else if colons then

                                  set origDelims to ":"

                        else if slashes then

                                  set origDelims to "/"

                        else

                                  return null

                        end if

                        local tids

                        set {tids, AppleScript's text item delimiters} to {AppleScript's text item delimiters, origDelims}

                        if aPath = "/" then

                                  -- we return root when we get root

                                  set AppleScript's text item delimiters to tids

                                  return "/"

                        end if

                        local theParentFolder

                        if text -1 of aPath is in {":", "/"} then

                                  set theParentFolder to text items 1 thru -2 of text 1 thru -2 of aPath

                        else

                                  set theParentFolder to text items 1 thru -2 of aPath

                        end if

                        set theParentFolder to theParentFolder as text

                        if slashes and theParentFolder = "" then set theParentFolder to "/"

                        -- sets the root path if we got a folder one level below it

                        if colons and (":" is not in theParentFolder) then set theParentFolder to theParentFolder & ":"

                        -- we return volumename, if we are given volumename

                        set AppleScript's text item delimiters to tids

                        return theParentFolder

              end parentfolder

     

     

              script storage

                        property cachespath : ((path to library folder from user domain as text) & "caches:" & "net.mcusr." & scripttitle)

     

                        on scriptCache()

     

                                  local script_cache

                                  try

                                            set script_cache to load script alias (my cachespath)

                                  on error

                                            script newScriptCache

                                                      property DefaultLocation : (path to desktop folder as text)

                                                      # edit any of those with default values

                                            end script

     

                                            set script_cache to newScriptCache

                                  end try

                                  return script_cache

                        end scriptCache

     

                        to saveScriptCache(theCache)

                                  store script theCache in my cachespath replacing yes

                        end saveScriptCache

     

                        to saveParenFolderInScriptCache(theFolderToSaveIn, script_cache)

                                  local containingFolder

                                  set containingFolder to (parentfolder of saveTabsInSafariWindowsToFolder for theFolderToSaveIn) & "/"

                                  local theLoc

                                  set theLoc to POSIX file containingFolder as alias

                                  set DefaultLocation of script_cache to theLoc

                                  my saveScriptCache(script_cache)

                        end saveParenFolderInScriptCache

              end script

    end script

    tell saveTabsInSafariWindowsToFolder to run

1 2 Previous Next