fix(ios): stop share extension hijacking shared .txt files (#4917)

The iOS share extension is a web-article URL clipper, but its activation
rule enabled NSExtensionActivationSupportsText. A .txt file is
public.plain-text (conforms to public.text), so that key made the
URL-only extension activate for plain-text files it cannot handle: the
share sheet hung instead of the file taking the main app's
CFBundleDocumentTypes "Copy to Readest" import path, which handles txt
fine (like EPUB and PDF, which never matched the extension).

Drop NSExtensionActivationSupportsText so the extension activates only
for web URLs. Shared .txt files now route to the working document-open
import path; sharing a web page URL from Safari or Chrome still works.

Add a regression guard asserting project.yml (the xcodegen source of
truth for the generated, skip-worktree Info.plist) never re-enables text
activation.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Huang Xin
2026-07-04 11:05:15 +09:00
committed by GitHub
parent 967a7833ca
commit 1d3dfd395f
2 changed files with 48 additions and 1 deletions
@@ -146,9 +146,13 @@ targets:
NSExtensionPointIdentifier: com.apple.share-services
NSExtensionPrincipalClass: $(PRODUCT_MODULE_NAME).ShareViewController
NSExtensionAttributes:
# Web URLs only. Do NOT add NSExtensionActivationSupportsText: a
# .txt file is public.plain-text (conforms to public.text), so that
# key makes this article-URL clipper activate for plain-text FILES
# it can't handle (the share sheet hangs). Book files must fall
# through to the main app's CFBundleDocumentTypes import path.
NSExtensionActivationRule:
NSExtensionActivationSupportsWebURLWithMaxCount: 1
NSExtensionActivationSupportsText: true
# Entitlements referenced via CODE_SIGN_ENTITLEMENTS build setting
# rather than xcodegen's `entitlements:` block — otherwise xcodegen
# may overwrite the file (even with no properties declared) during
@@ -0,0 +1,43 @@
import { describe, it, expect } from 'vitest';
import { readFileSync } from 'fs';
import { resolve } from 'path';
/**
* Regression guard: sharing a `.txt` file to Readest on iOS got stuck.
*
* The Share Extension (added in #4256 / #4267) is an article-URL clipper — its
* `ShareViewController` only ever extracts an http(s) URL from the shared item.
* Its activation rule enabled `NSExtensionActivationSupportsText`, but a `.txt`
* file is UTI `public.plain-text` (which conforms to `public.text`), so that key
* made the extension activate for plain-text FILES it cannot handle: the share
* sheet hung instead of the file taking the main app's CFBundleDocumentTypes
* "Copy to Readest" open-in-place path (which imports txt fine, exactly like the
* EPUB/PDF that never matched the extension).
*
* Fix: the extension activates only for web URLs, never for text — so `.txt`
* (and any plain-text file) routes to the working document-open import path.
*
* We assert on `project.yml` only. It is the xcodegen source of truth: Tauri's
* iOS CLI runs `xcodegen` at build time, which regenerates each target's
* `Info.plist` from this file. The committed `ShareExtension/Info.plist` is a
* generated artifact (marked `skip-worktree`) and must not be relied on.
*/
// `NSExtensionActivationSupportsText` is legitimately named in a `#` comment
// warning contributors not to re-add it. Strip comments so the check targets
// active config only.
const stripYamlComments = (text: string): string => text.replace(/#.*$/gm, '');
const projectYml = stripYamlComments(
readFileSync(resolve(process.cwd(), 'src-tauri/gen/apple/project.yml'), 'utf-8'),
);
describe('iOS Share Extension activation rule (txt share stuck)', () => {
it('does not enable NSExtensionActivationSupportsText (would capture .txt files)', () => {
expect(projectYml).not.toContain('NSExtensionActivationSupportsText');
});
it('still activates for web URLs (article clipping preserved)', () => {
expect(projectYml).toContain('NSExtensionActivationSupportsWebURLWithMaxCount');
});
});