Skip to content

fix(downloads): context menu downloads and Downloads panel - #254

Closed
barto95100 wants to merge 55 commits into
the-ora:mainfrom
barto95100:fix/context-menu-downloads
Closed

barto95100 wants to merge 55 commits into
the-ora:mainfrom
barto95100:fix/context-menu-downloads

Conversation

@barto95100

Copy link
Copy Markdown
  • Context menu "Download Image / Linked File / Video" never started: implemented _webView:contextMenuDidCreateDownload: (WKNavigationDelegatePrivate, macOS 12+), the delegate method WebKit uses for them.
  • Links with the download attribute navigated instead of downloading: shouldPerformDownload → .download + webView(_:navigationAction:didBecome:).
  • Downloads panel stayed empty: TabManager created a new DownloadManager for tabs opened/activated without one; it now receives and always uses the window's (also restores the download toasts).
  • Pre-push hook builds unsigned into its own DerivedData (build/DerivedData): sharing it with Xcode broke the next signed test build ("code object is not signed at all").

Checked: Download Image, Download Linked File, <a download>, .dmg from a new tab; all listed in the panel.

barto95100 and others added 30 commits October 25, 2026 10:55
Moving across child elements of the same anchor re-sent the same URL on every mouseover and re-assigned the @published hoveredLinkURL, re-rendering the link preview. Deduplicate in the injected script and skip identical assignments in Swift. Complements the-ora#246.
urlString, used by open-tab search, was only set when the tab was created, so a tab kept matching the URL it was first opened with. Route navigation updates through Tab.updateURL(_:) which updates both.
The first suggestion could be the active tab itself, so Enter switched to the current tab and nothing happened. Never suggest the active tab, and rank the typed URL before open tabs when the input is a URL.
…s through

Clicking the sidebar or toolbar left the URL bar suggestions open, while the dimming overlay over the page swallowed clicks, so links and the context menu did not work while editing. Watch mouse-downs during editing (without consuming them) and dismiss when they land outside the field and its suggestions; keep the overlay purely visual.
…g it blindly

The WKUIDelegate method was missing the type parameter, so WebKit never called it (compiler: 'nearly matches optional requirement'). Fix the signature, forward whether camera, microphone or both are requested, and answer with WebKit's own prompt instead of an unconditional grant.
oraTests had no Info.plist and no signing team, so tests could not run locally. Generate the Info.plist and sign with the development team.
Introduce SiteOrigin (scheme, host and port, with default ports normalized), a tri-state ask/allow/deny per capability, and SitePermissionStore scoped per space like website data. Private browsing is kept in memory only. Permissions are cleared when a space is deleted. Replaces the unused SitePermissionSettings.
Capture requests are answered from the saved per-space permissions; when nothing is saved, an in-page prompt asks the user, optionally remembering the choice. Unanswered prompts are denied when the page navigates away, the tab is closed or another request replaces them. Allow has no keyboard shortcut on purpose.
…ttings

Add a Site Permissions card to each space in Settings > Spaces to change or forget saved camera and microphone choices. Share the site display rule (host for HTTPS, full origin otherwise) between the prompt and settings, with tests.
The workflow only checked lint and formatting. Build the app and run the unit tests (ad-hoc signing on CI), pin SwiftLint and SwiftFormat to the local versions, cache Swift packages and upload the test results on failure. Upstream Sync now dispatches the CI on its branch, since pull requests opened with GITHUB_TOKEN do not trigger workflows.
ci: build the app and run unit tests
Escape not handled by the page travels up the responder chain as cancelOperation(_:) and reached the window, which exits full screen. Stop it at the page host view, after WebKit, so pages still get the key. Like Safari, it now stops a page that is still loading. Fixes the-ora#179.
fix: keep full screen when Escape is pressed in a web page
The Fullscreen shortcut (Control-Command-F by default) was defined and listed in Settings but never handled, and macOS only offers Globe-F in the View menu, which keyboards without a Globe key cannot type. Handle Ora's customizable shortcut with the other global key handlers, without adding a duplicate menu item.
fix: make the Fullscreen keyboard shortcut work
The app delegate received links before any window was ready, picked an arbitrary window or built one by hand without its environment, and posted a notification the window might not be listening to yet, so links were lost. Let SwiftUI route external events: regular windows accept and prefer them and get them through onOpenURL once ready; settings and private windows refuse them. Remove the now unused manual window factory. Fixes the-ora#178.
openTab required a host, which file URLs do not have, so no tab was created; and the sandbox only lets WebKit read a local file loaded with loadFileURL(_:allowingReadAccessTo:). Use the file name as the initial title and load file URLs with that API.
Pages without a <title> report an empty one, which blanked the tab title. Fall back to the host, or the file name for local files, like Safari.
barto95100 and others added 24 commits October 25, 2026 17:57
fix(media): don't show the mini player for silent or muted media
…p with media events

- Coalesce DOM scans (at most one every 250 ms) and only report next/previous button changes: busy pages like YouTube sent hundreds of events per minute, each re-rendering the sidebar.
- Don't republish unchanged media state.
- Automatic Picture in Picture uses the video playing with sound, not the first video of the page, and leaving a tab whose video is already in Picture in Picture keeps it there.
- 'Back to tab' from the Picture in Picture window now works: the page of a tab in Picture in Picture stays attached to the window, hidden, so WebKit can bring the video back, and Ora then shows that tab like Safari.
fix(media): reliable Picture in Picture, no more media event flood
WebKit already reports them like Safari (core count capped at 8, no deviceMemory, real language and screen). Overriding them made Ora stand out from Safari users and look inconsistent — language differing from the Accept-Language header, a screen smaller than the window — which YouTube's bot detection flagged, stopping playback after a few minutes. Keep canvas, WebGL, audio and media device protections; tests guard against reintroducing the overrides.
applicationNameForUserAgent is appended by WebKit to its own Mozilla/AppleWebKit prefix but was given a full user agent, so the prefix appeared twice, with a hard-coded, outdated Safari version. Pass only Safari's suffix, using the installed Safari's version (validated, with a fallback).
Canvas and audio noise was a fixed offset on fixed samples, so Ora produced the same unique signature on every site and every session — an identifier in itself. Derive the noise from a secret drawn at each launch, mixed with the space, the browsing mode and, in the page, the top-level site and frame origin. A site reads stable values within a session; sites and sessions cannot be linked.
fix(privacy): make fingerprinting protection blend in like Safari
WebKit reports the end of a page's process (crash or memory reclaim), but Ora ignored it and left the tab blank and unresponsive. The visible page now reloads once with a notice, a background page reloads only when its tab is shown, and a page that ends again within 30 seconds of a reload shows a crash page instead of looping. Media sessions, Picture in Picture, permission prompts and autofill of the dead page are cleared.
Debug builds only. The sandbox forbids Ora to kill a web content process, so the command copies the kill -9 command for the space's pages to the clipboard, to run in Terminal.
fix: recover from web content process crashes like Safari
The fingerprinting script replaced navigator.permissions.query with a fake that always said "prompt", even for a site the user allowed, and returned a plain object without addEventListener (a TypeError for sites watching the state, and a fingerprint of its own). Without it, WebKit ignored the per-site decisions because Ora did not implement the delegate method Safari implements (_webView:queryPermission:forOrigin:completionHandler:). Camera and microphone are now answered from the site's saved decision; WebKit then applies Safari's adjustments.
A site whose camera and microphone were both set to Ask was deleted, so it vanished from Settings and could no longer be set to Don't Allow. Like in Safari, only Forget removes a site. Also tests the permission query mapping.
…nting

fix: answer navigator.permissions.query like Safari
The fingerprinting script meant to replace the label-less device list with fake devices named "ora-videoinput-1"…, which would identify Ora instead of hiding it. It never applied (navigator.mediaDevices is not exposed yet when the script runs), and WebKit already hides device names and identifiers until the site is allowed, like Safari. Also removes the defineGetter/defineValue helpers left unused since navigator and screen are no longer overridden.
…inting

fix(privacy): remove the fake media device list
WebKit already hides the GPU like Safari ("Apple GPU"). The fixed values only made Ora differ from it: vendor "Apple Inc." instead of "WebKit", a different GLSL string, and WebGL 1.0 / GLSL ES 1.0 reported for WebGL 2 contexts, with a non-native getParameter. The script now only adds canvas and audio noise.
fix(privacy): stop overriding WebGL parameters
The address bar shows a red camera/microphone button while the page uses it; clicking pauses or resumes the device without reloading (WKWebView capture state API). The sidebar tab shows the same indicator. Tabs in a call are no longer unloaded or closed by the background cleanup. Closing a tab now releases the camera and microphone right away and silences its media before the animated Picture in Picture exit, instead of when WebKit frees the page.
feat(media): show and pause camera feat(media): show and pause camera and microphone use like Safari microphone use like Safari
BrowserPage reached SwiftLint's 500-line error limit. The WebKit navigation and UI delegate conformances and the media capture code move to their own files; behavior is unchanged. The web view and navigation tracking state become engine-internal so the extensions can use them.
refactor(engine): split BrowserPage into delegate extensions
"Download Image", "Download Linked File" and "Download Video" never started: WebKit reports them through _webView:contextMenuDidCreateDownload: (WKNavigationDelegatePrivate, macOS 12+), which Ora did not implement, so nothing took care of the download. Links marked with the download attribute navigated instead of downloading (shouldPerformDownload ignored).
TabManager created a new DownloadManager when a tab was opened or activated without one, so those downloads were recorded in an instance the Downloads panel never showed, and no toast appeared. TabManager now receives the window's DownloadManager and always falls back to it.
The unsigned pre-push build shared DerivedData with Xcode and xcodebuild test, leaving unsigned products that made the next test build fail with "code object is not signed at all".
@barto95100 barto95100 closed this Oct 26, 2026
@barto95100
barto95100 deleted the fix/context-menu-downloads branch October 26, 2026 10:49
@greptile-apps

greptile-apps Bot commented Oct 26, 2026 •

Copy link
Copy Markdown

RetriggerView in GreptileConfidence Score: 1/5

[High impact] The PR changes browser download, device-permission, recovery, and release workflows.

Fix release export, update-feed routing, and permissions after tab moves before merging.

Findings

  1. P1 Release export no longer matches ▶
  2. P1 Updates use the wrong feed ▶
  3. P1 Security Permission choices cross spaces ▶
  4. P2 Track buttons stay disabled ▶

Summary

Adds context-menu and download-attribute downloads and shares the window's DownloadManager with its tabs.

  • Page downloads appear in the window’s Downloads panel.
  • Sites ask before using the camera or microphone, and Ora shows when either is active.
  • Crashed pages reload when useful and stop when they keep crashing.
  • Picture in Picture can return to the tab that owns its video.
  • Fingerprinting protection adds site-specific noise and leaves more browser details alone.
  • Typed URLs, open tabs, and pages without titles get clearer treatment.
  • Build and release settings change, while browser callback code is reorganized.

Diagram

%%{init: {'theme': 'neutral'}}%%
flowchart LR
  A[Context-menu download] --> D[BrowserDownloadTask]
  B[Download-attribute link] --> D
  C[Non-displayable response] --> D
  D --> E[Tab delegate]
  E --> F[Window DownloadManager]
  F --> G[Saved file and download record]
  F --> H[Downloads panel and toast]
Loading

Reviews (1) · Last reviewed commit: "build(hooks): build the pre-push check i..." · Reviewed by Greptile

Comment thread project.yml
CURRENT_PROJECT_VERSION: 105
PRODUCT_NAME: Ora
PRODUCT_BUNDLE_IDENTIFIER: com.orabrowser.app
PRODUCT_BUNDLE_IDENTIFIER: com.barto95100.ora

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Release export no longer matches

The new release bundle ID, com.barto95100.ora, no longer matches the manual export settings. scripts/build.sh still maps the provisioning profile to com.orabrowser.app, and Release retains the existing profile. The release script cannot export the renamed app with that mapping. Keep the identity change Debug-only, or update the Release profile and export settings together.

Knowledge Base Used: Application services and build delivery

Comment thread project.yml
GENERATE_INFOPLIST_FILE: YES
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME: AccentColor
SUFeedURL: "https://the-ora.github.io/browser/appcast.xml"
SUFeedURL: "https://barto95100.github.io/ora-browser/appcast.xml"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Updates use the wrong feed

The new SUFeedURL is never used. UpdateService supplies itself as the Sparkle delegate, and feedURLString(for:) still returns the original feed. Manual update checks therefore contact the original project instead of the newly configured fork. Remove the override or read the configured value there.

Knowledge Base Used: Application services and build delivery

Comment on lines +17 to +22
SitePermissionStore.shared.setState(
granted ? .allow : .deny,
for: request.types,
origin: request.origin,
in: container.id,
isPrivate: isPrivate

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 security Permission choices cross spaces

A pending permission answer uses the tab's current container.id, not the space that requested access. A user can leave a camera prompt open, move the tab through “Move to Container,” then click Allow. Moving neither cancels the prompt nor rebuilds the page. This writes Allow into the destination space—even over an existing Deny—while the page still uses the original space's profile. Bind requests to their original space and cancel them when the tab moves.

How this was verified: The move changes only the tab's container, the answer saves to that live container, and later capture requests grant access from the saved choice.

Knowledge Base Used: Privacy controls

Comment on lines +167 to +169
if (lastCaps && lastCaps.hasNext === hasNext && lastCaps.hasPrevious === hasPrevious) return;
lastCaps = { hasNext, hasPrevious };
post({ type: 'caps', hasNext, hasPrevious });

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Track buttons stay disabled

The new lastCaps check can permanently discard the first next/previous values. If a page initially has paused media and track buttons, scan() sends caps before any session exists. MediaController drops that event. Playing later creates a session with both controls false, but unchanged button values are now suppressed on every scan. Send the current values when playback creates a session, or retain them until a session exists.

Knowledge Base Used: Media player experience

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant