Safari Extension Stops on iOS 17.5.1 - 18

We are encountering an issue where the Safari extension we are developing stops working while in use on relatively new iOS versions (confirmed on 17.5.1, 17.6.1, and 18). Upon checking the Safari console, the content script is displayed in the extension script, so the background script or Service Worker must be stopping. The time until it stops is about 1 minute on 17.5.1 and about one day on 17.6.1 or 18.

When it stops, we would like to find a way to restart the Service Worker from the extension side, but we have not found a method to do so yet. To restart the extension, the user needs to turn off the corresponding extension in the iPhone settings and then turn it back on. As mentioned in the following thread, it is written that the above bug was fixed in 17.6, but we recognize that it has not been fixed. https://forums.developer.apple.com/forums/thread/758346

On 17.5.1, adding the following process to the background script prevents it from stopping for about the same time as on 17.6 and above.

// Will be passed into runtime.onConnect for processes that are listening for the connection event
const INTERNAL_STAYALIVE_PORT = "port.connect";
// Try wake up every 9S
const INTERVAL_WAKE_UP = 9000;
// Alive port
var alivePort = null;
// Call the function at SW(service worker) start
StayAlive();

async function StayAlive() {
  var wakeup = setInterval(() => {
    if (alivePort == null) {
      alivePort = browser.runtime.connect({ name: INTERNAL_STAYALIVE_PORT });
      alivePort.onDisconnect.addListener((p) => {
        alivePort = null;
      });
    }

    if (alivePort) {
      alivePort.postMessage({ content: "ping" });
    }
  }, INTERVAL_WAKE_UP);
}

Additionally, we considered methods to revive the Service Worker when it stops, which are listed below. None of the methods listed below resolved the issue. ① Implemented a process to create a connection again if the return value of sendMessage is null. The determination of whether the Service Worker has stopped is made by sending a message from the content script to the background script and checking whether the message return value is null as follows. sendMessageToBackground.js

let infoFromBackground = await browser.runtime.sendMessage(sendParam);
if (!infoFromBackground) {
  // If infoFromBackground is null, Service Worker should have stopped.
  browser.runtime.connect({name: 'reconnect'});  // ← reconnection process

  // Sending message again
  infoFromBackground = await browser.runtime.sendMessage(sendParam);
}
return infoFromBackground.message;

Background script

browser.runtime.onConnect.addListener((port) => {
  if (port.name !== 'reconnect') return;
 
  port.onMessage.addListener(async (request, sender, sendResponse) => {
    sendResponse({
      response: "response form background",
      message: "reconnect.",
  });
});

② Verified whether the service worker could be restarted by regenerating Background.js and content.js. sendMessageToBackground.js

export async function sendMessageToBackground(sendParam) {
  let infoFromBackground = await browser.runtime.sendMessage(sendParam);
  if (!infoFromBackground) {
    executeContentScript(); // ← executeScript

    infoFromBackground = await browser.runtime.sendMessage(sendParam);
  }
  return infoFromBackground.message;
}

async function executeContentScript() {
  browser.webNavigation.onDOMContentLoaded.addListener((details) => {
    browser.scripting.executeScript({
      target: { tabId: details.tabId },
      files: ["./content.js"]
    });
  });
}

However, browser.webNavigation.onDOMContentLoaded.addListener was not executed due to the following error.

@webkit-masked-url://hidden/:2:58295
@webkit-masked-url://hidden/:2:58539
@webkit-masked-url://hidden/:2:58539

③ Verify that ServiceWorker restarts by updating ContentScripts

async function updateContentScripts() {
  try {
    const scripts = await browser.scripting.getRegisteredContentScripts();
    const scriptIds = scripts.map(script => script.id);
    await browser.scripting.updateContentScripts(scriptIds);//update content
  } catch (e) {
    await errorLogger(e.stack);
  }
}

However, scripting.getRegisteredContentScripts was not executed due to the same error as in 2.

@webkit-masked-url://hidden/:2:58359
@webkit-masked-url://hidden/:2:58456
@webkit-masked-url://hidden/:2:58456
@webkit-masked-url://hidden/:2:58549
@webkit-masked-url://hidden/:2:58549

These are the methods we have considered. If anyone knows a solution, please let us know.

Dropping a link to a StackOverflow i authored around this exact topic. I've been trying to figure this out for months as well. https://stackoverflow.com/questions/78570837/safari-web-extension-background-script-becomes-unresponsive-after-30-seconds

Safari Extension Stops on iOS 17.5.1 - 18
 
 
Q