4 Replies
      Latest reply on May 15, 2020 8:19 PM by RickyRomero
      sriharshaang Level 1 Level 1 (0 points)

        Hello,

         

        1) Does anyone know what is the lifetime of a Safari app extension process? Can it run as long as Safari is alive or does it get killed and restarted from time to time?

         

        2) Does anyone know how many safari app extension processes can be loaded simultaneously ? Is it one per tab or one per window or one per safari-app extension?

         

        Any help is greatly appreciated.

        • Re: Safari App Extension Process Questions
          bweinstein Apple Staff Apple Staff (100 points)

          1) There is no guarantee of the lifetime of a Safari App Extension process. Any time you try to communicate with it from Safari, an instance will be spun up. Is there something you are trying to accomplish?

           

          2) It will be one instance of the process per Safari App Extension for Safari, and if a user has Safari Technology Preview running as well, any extensions will run in a separate process from Safari.

            • Re: Safari App Extension Process Questions
              RickyRomero Level 1 Level 1 (0 points)

              Hi Brian,

               

              Safari App Extensions are automatically discovered, without launching the containing app, when downloaded through the App Store. Therefore, I must support a use case where the user installs my containing app from the App Store, goes to Safari's prefs without opening my containing app first, and turns my extension on. That means I have to have initialization logic in my app and my extension.

               

              Your post says that there's one instance of the process per App Extension, but I can't independently verify this.

               

              I'm debugging a Safari app extension in Xcode 11.4.1 (11E503a), Safari 13.1 (15609.1.20.111.8) and macOS Catalina 10.15.4 (19E287). When I choose to run my extension (not the containing app) and attach to Safari, Safari runs, then my extension's initializer is run several times in sub-millisecond succession, and runs in parallel (along with the beginRequest(with:) method). I tested the parallelization by running many NSLog statements in my init and verifying the printed order in the console.

               

              If I tried to run initialization logic in this sort of environment, I'm afraid of the kinds of bugs that might appear!

               

              In the course of writing this post, I created a blank Safari App Extension project and had the same thing happen. Simply adding this code to the SafariExtensionHandler, then attaching the app extension to Safari shows the problem:

               

                  var counter = 0
                  override init() {
                      super.init()
                      counter += 1
                      NSLog(String(counter))
                  }
              
                  deinit {
                      NSLog("Disappearing...")
                  }

               

              objc[58615]: Class AMSupportURLConnectionDelegate is implemented in both /System/Library/PrivateFrameworks/OSPersonalization.framework/Versions/A/OSPersonalization (0x7fff92e61b68) and /System/Library/PrivateFrameworks/MobileDevice.framework/Versions/A/MobileDevice (0x1048fe228). One of the two will be used. Which one is undefined.
              objc[58615]: Class AMSupportURLSession is implemented in both /System/Library/PrivateFrameworks/OSPersonalization.framework/Versions/A/OSPersonalization (0x7fff92e61bb8) and /System/Library/PrivateFrameworks/MobileDevice.framework/Versions/A/MobileDevice (0x1048fe278). One of the two will be used. Which one is undefined.
              2020-05-15 19:25:31.213733-0700 Safari[58615:5517884] [Extensions] Unable to access keychain item for the list of installed extensions because it does not exist
              2020-05-15 19:25:32.848653-0700 basicsafariextension Extension[58618:5517957] [subsystems ] Bootstrapping; external subsystem UIKit_PKSubsystem refused setup
              2020-05-15 19:25:32.878416-0700 basicsafariextension Extension[58618:5517957] 1
              2020-05-15 19:25:32.878689-0700 basicsafariextension Extension[58618:5517957] 1
              2020-05-15 19:25:32.878864-0700 basicsafariextension Extension[58618:5517957] 1
              2020-05-15 19:25:32.879073-0700 basicsafariextension Extension[58618:5517957] 1
              2020-05-15 19:25:32.879233-0700 basicsafariextension Extension[58618:5517957] 1
              2020-05-15 19:25:32.879501-0700 basicsafariextension Extension[58618:5517957] 1
              2020-05-15 19:25:32.893023-0700 basicsafariextension Extension[58618:5517957] Disappearing...
              2020-05-15 19:25:32.893492-0700 basicsafariextension Extension[58618:5517957] Disappearing...

               

              (Yes, deinit is only run twice after Safari loads...)

               

              Why is my init code being run so many times if there's only one instance of the process?

               

              EDIT: I discovered that the first number after the name of the process is the PID, which clearly shows one copy of the process. But the multiple, parallel inits are still a cause for concern. How can I handle this?

                • Re: Safari App Extension Process Questions
                  RickyRomero Level 1 Level 1 (0 points)

                  I've discovered that I can work around this parallel initialization problem through the use of a singleton, like so:

                   

                  final class Setup {
                      static var main = Setup()
                      private init() {}
                  
                      var bootstrapRun = false
                      func bootstrap() {
                          guard !bootstrapRun else {
                              return
                          }
                  
                          bootstrapRun = true
                  
                          // Put your setup logic here; it will only run once.
                          // ...
                      }
                  }
                  

                   

                  Then, call the bootstrap method from wherever your extension enters the world:

                   

                      override func beginRequest(with context: NSExtensionContext) {
                          super.beginRequest(with: context)
                  
                          Setup.main.bootstrap()
                  

                   

                  Hope this helps anyone trying to run initialization logic inside their extension.