From 2335603ff5120d881f803c3600680a857ea67b52 Mon Sep 17 00:00:00 2001 From: Patrick Schiller
Date: Fri, 14 Aug 2026 09:57:53 +0200 Subject: [PATCH] Fix Safari share preprocessing Signed-off-by: Patrick Schiller
--- ios/SourceBraidShare/SharePreprocessing.js | 7 +-- tests/test_ios_share_preprocessing.py | 58 ++++++++++++++++++++++ 2 files changed, 62 insertions(+), 3 deletions(-) create mode 100644 tests/test_ios_share_preprocessing.py diff --git a/ios/SourceBraidShare/SharePreprocessing.js b/ios/SourceBraidShare/SharePreprocessing.js index e34b7e7..fe479d5 100644 --- a/ios/SourceBraidShare/SharePreprocessing.js +++ b/ios/SourceBraidShare/SharePreprocessing.js @@ -1,6 +1,7 @@ -var ExtensionPreprocessingJS = function() {}; - -ExtensionPreprocessingJS.prototype = { +// Safari calls `run` on this global object before presenting the Share +// Extension. It must be an object, rather than a constructor whose `run` +// method only exists on its prototype. +var ExtensionPreprocessingJS = { run: function(arguments) { var selection = window.getSelection ? window.getSelection().toString() : ""; var article = document.querySelector("article") || document.querySelector("main") || document.body; diff --git a/tests/test_ios_share_preprocessing.py b/tests/test_ios_share_preprocessing.py new file mode 100644 index 0000000..be6b904 --- /dev/null +++ b/tests/test_ios_share_preprocessing.py @@ -0,0 +1,58 @@ +import json +import subprocess +import unittest +from pathlib import Path + + +REPOSITORY_ROOT = Path(__file__).resolve().parents[1] +SCRIPT = REPOSITORY_ROOT / "ios" / "SourceBraidShare" / "SharePreprocessing.js" + + +class SafariSharePreprocessingTests(unittest.TestCase): + def test_exposes_a_global_object_that_returns_page_details(self): + program = r""" +const fs = require("fs"); +const vm = require("vm"); +const source = fs.readFileSync(process.argv[1], "utf8"); +const sandbox = { + window: { + getSelection: () => ({ toString: () => "Selected excerpt" }), + location: { href: "https://example.com/article" } + }, + document: { + title: "Example article", + location: { href: "https://example.com/article" }, + querySelector: (selector) => selector === "article" + ? { innerText: "Readable page text" } + : null + } +}; +vm.createContext(sandbox); +vm.runInContext(source, sandbox); +if (typeof sandbox.ExtensionPreprocessingJS?.run !== "function") { + throw new Error("ExtensionPreprocessingJS must expose run()"); +} +sandbox.ExtensionPreprocessingJS.run({ + completionFunction: (value) => process.stdout.write(JSON.stringify(value)) +}); +""" + result = subprocess.run( + ["node", "-e", program, str(SCRIPT)], + check=True, + capture_output=True, + text=True, + ) + + self.assertEqual( + json.loads(result.stdout), + { + "url": "https://example.com/article", + "title": "Example article", + "selectedText": "Selected excerpt", + "articleText": "Readable page text", + }, + ) + + +if __name__ == "__main__": + unittest.main()