Merge pull request #8243 from zen-browser/new-share-api

New share API and implementation
This commit is contained in:
mr. m
2025-05-13 02:55:26 +02:00
committed by GitHub
18 changed files with 950 additions and 92 deletions

View File

@@ -5,7 +5,8 @@
*/
.subviewbutton,
#zen-welcome-start-button {
#zen-welcome-start-button,
.zen-toast button {
-moz-context-properties: fill, fill-opacity !important;
fill: currentColor !important;
}
@@ -425,7 +426,8 @@
list-style-image: url('customize.svg') !important;
}
#appmenu-zen-share-rice {
#appmenu-zen-share-rice,
#zen-copy-current-url-button {
list-style-image: url('share.svg');
}

View File

@@ -72,7 +72,25 @@ var gZenCommonActions = {
transferable.addDataFlavor('text/plain');
transferable.setTransferData('text/plain', str);
Services.clipboard.setData(transferable, null, Ci.nsIClipboard.kGlobalClipboard);
gZenUIManager.showToast('zen-copy-current-url-confirmation');
let button;
if (Services.zen.canShare()) {
button = {
id: 'zen-copy-current-url-button',
command: (event) => {
const buttonRect = event.target.getBoundingClientRect();
Services.zen.share(
Services.io.newURI(currentUrl),
'',
'',
buttonRect.left,
window.innerHeight - buttonRect.bottom,
buttonRect.width,
buttonRect.height
);
},
};
}
gZenUIManager.showToast('zen-copy-current-url-confirmation', { button, timeout: 3000 });
}
},
copyCurrentURLAsMarkdownToClipboard() {

View File

@@ -422,6 +422,7 @@ var gZenUIManager = {
return [child, true];
}
}
const wrapper = document.createXULElement('hbox');
const element = document.createXULElement('vbox');
const label = document.createXULElement('label');
document.l10n.setAttributes(label, messageId, options);
@@ -432,26 +433,25 @@ var gZenUIManager = {
document.l10n.setAttributes(description, options.descriptionId, options);
element.appendChild(description);
}
element.classList.add('zen-toast');
element._messageId = messageId;
return [element, false];
wrapper.appendChild(element);
if (options.button) {
const button = document.createXULElement('button');
button.id = options.button.id;
button.classList.add('footer-button');
button.classList.add('primary');
button.addEventListener('command', options.button.command);
wrapper.appendChild(button);
}
wrapper.classList.add('zen-toast');
wrapper._messageId = messageId;
return [wrapper, false];
},
async showToast(messageId, options = {}) {
const [toast, reused] = this._createToastElement(messageId, options);
this._toastContainer.removeAttribute('hidden');
this._toastContainer.appendChild(toast);
if (reused) {
await this.motion.animate(toast, { scale: 0.2 }, { duration: 0.1, bounce: 0 });
}
if (!toast.style.hasOwnProperty('transform')) {
toast.style.transform = 'scale(0)';
}
await this.motion.animate(toast, { scale: 1 }, { type: 'spring', bounce: 0.2, duration: 0.5 });
if (this._toastTimeouts[messageId]) {
clearTimeout(this._toastTimeouts[messageId]);
}
this._toastTimeouts[messageId] = setTimeout(() => {
const timeoutFunction = () => {
this.motion
.animate(toast, { opacity: [1, 0], scale: [1, 0.5] }, { duration: 0.2, bounce: 0 })
.then(() => {
@@ -460,7 +460,30 @@ var gZenUIManager = {
this._toastContainer.setAttribute('hidden', true);
}
});
}, options.timeout || 2000);
};
if (reused) {
await this.motion.animate(toast, { scale: 0.2 }, { duration: 0.1, bounce: 0 });
} else {
toast.addEventListener('mouseover', () => {
if (this._toastTimeouts[messageId]) {
clearTimeout(this._toastTimeouts[messageId]);
}
});
toast.addEventListener('mouseout', () => {
if (this._toastTimeouts[messageId]) {
clearTimeout(this._toastTimeouts[messageId]);
}
this._toastTimeouts[messageId] = setTimeout(timeoutFunction, options.timeout || 2000);
});
}
if (!toast.style.hasOwnProperty('transform')) {
toast.style.transform = 'scale(0)';
}
await this.motion.animate(toast, { scale: 1 }, { type: 'spring', bounce: 0.2, duration: 0.5 });
if (this._toastTimeouts[messageId]) {
clearTimeout(this._toastTimeouts[messageId]);
}
this._toastTimeouts[messageId] = setTimeout(timeoutFunction, options.timeout || 2000);
},
get panelUIPosition() {

View File

@@ -363,8 +363,12 @@ menuitem {
translate: 100%;
}
gap: 10px;
z-index: 1000;
padding: 10px;
@media (-moz-platform: windows) {
padding: 6px;
}
border-radius: 12px;
background: linear-gradient(
170deg,
@@ -376,7 +380,7 @@ menuitem {
border: 1px solid rgba(0, 0, 0, 0.1);
display: flex;
font-weight: 600;
flex-direction: column;
align-items: center;
width: max-content;
font-size: small;
position: absolute;
@@ -386,6 +390,19 @@ menuitem {
opacity: 0.6;
font-size: smaller;
}
& label {
margin-top: 0;
margin-bottom: 0;
}
& button {
width: min-content;
padding: 0 12px !important;
min-width: unset !important;
margin: 0px !important;
border-radius: 8px !important;
}
}
}

View File

@@ -7,4 +7,5 @@ DIRS += [
"glance",
"mods",
"tests",
"toolkit",
]

View File

@@ -0,0 +1,114 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "ZenCommonUtils.h"
#include "ZenShareInternal.h"
#include "nsGlobalWindowOuter.h"
#include "nsQueryObject.h"
#include "nsIWindowMediator.h"
#include "nsServiceManagerUtils.h"
#include "nsISharePicker.h"
#if defined(XP_WIN)
# include "mozilla/WindowsVersion.h"
#endif
namespace zen {
// Use the macro to inject all of the definitions for nsISupports.
NS_IMPL_ISUPPORTS(ZenCommonUtils, nsIZenCommonUtils)
using WindowGlobalChild = mozilla::dom::WindowGlobalChild;
namespace {
/**
* @brief Helper function to fetch the most recent window proxy.
* @param aWindow The window to query.
* @returns The most recent window.
*/
static nsresult GetMostRecentWindowProxy(mozIDOMWindowProxy** aWindow) {
nsresult rv;
nsCOMPtr<nsIWindowMediator> med(
do_GetService(NS_WINDOWMEDIATOR_CONTRACTID, &rv));
if (NS_FAILED(rv)) return rv;
if (med) return med->GetMostRecentBrowserWindow(aWindow);
return NS_ERROR_FAILURE;
}
/**
* @brief Helper function to query and get a reference to the window.
* @param aWindow The window to query.
*/
static nsCOMPtr<mozIDOMWindowProxy> GetMostRecentWindow() {
nsCOMPtr<mozIDOMWindowProxy> aWindow;
nsresult rv = GetMostRecentWindowProxy(getter_AddRefs(aWindow));
if (NS_FAILED(rv) || !aWindow) {
return nullptr;
}
return aWindow;
}
}
using mozilla::dom::WindowGlobalChild;
#define NS_ZEN_CAN_SHARE_FAILURE() \
*canShare = false; \
return NS_OK;
/*
* @brief Check if the current context can share data.
* @param data The data to share.
* @returns True if the current context can share data, false otherwise.
*/
NS_IMETHODIMP
ZenCommonUtils::CanShare(bool* canShare) {
auto aWindow = GetMostRecentWindow();
if (!aWindow) {
NS_ZEN_CAN_SHARE_FAILURE();
}
*canShare = IsSharingSupported();
return NS_OK;
}
NS_IMETHODIMP
ZenCommonUtils::Share(nsIURI* url, const nsACString& title, const nsACString& text,
uint32_t aX, uint32_t aY, uint32_t aWidth, uint32_t aHeight) {
auto aWindow = GetMostRecentWindow();
if (!aWindow) {
return NS_ERROR_NOT_AVAILABLE;
}
if (!IsSharingSupported()) {
return NS_OK; // We don't want to throw an error here
}
return ShareInternal(aWindow, url, title, text, aX, aY, aWidth, aHeight);
}
nsresult ZenCommonUtils::ShareInternal(nsCOMPtr<mozIDOMWindowProxy>& aWindow, nsIURI* url,
const nsACString& title, const nsACString& text, uint32_t aX, uint32_t aY,
uint32_t aWidth, uint32_t aHeight) {
// We shoud've had done pointer checks before, so we can assume
// aWindow is valid.
#ifdef NS_ZEN_CAN_SHARE_NATIVE
return ::nsZenNativeShareInternal::ShowNativeDialog(
aWindow, url, title, text,
aX, aY, aWidth, aHeight
);
#else
return NS_ERROR_NOT_IMPLEMENTED;
#endif
}
auto ZenCommonUtils::IsSharingSupported() -> bool {
#if defined(XP_WIN) && !defined(__MINGW32__)
// The first public build that supports ShareCanceled API
return IsWindows10BuildOrLater(18956);
#elif defined(NS_ZEN_CAN_SHARE_NATIVE)
return NS_ZEN_CAN_SHARE_NATIVE;
#else
return false;
#endif
}
} // namespace: zen

View File

@@ -0,0 +1,50 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef mozilla_nsZenCommonUtils_h__
#define mozilla_nsZenCommonUtils_h__
#include "nsIZenCommonUtils.h"
#include "nsIDOMWindow.h"
#include "nsGlobalWindowOuter.h"
#include "nsIURI.h"
namespace zen {
/**
* @brief Common utility functions for Zen.
*/
class ZenCommonUtils final : public nsIZenCommonUtils {
NS_DECL_ISUPPORTS
NS_DECL_NSIZENCOMMONUTILS
public:
explicit ZenCommonUtils() = default;
private:
~ZenCommonUtils() = default;
/**
* @brief Check if the current context can share data.
* @param data The data to share.
* @returns True if the current context can share data, false otherwise.
*/
static auto IsSharingSupported() -> bool;
/**
* @brief Helper function to share data via the native dialogs.
* @param aWindow The window to use for the share dialog.
* @param url The URL to share.
* @param title The title of the share.
* @param text The text to share.
* @returns A promise that resolves when the share is complete.
*/
static auto ShareInternal(nsCOMPtr<mozIDOMWindowProxy>& aWindow, nsIURI* url,
const nsACString& title, const nsACString& text, uint32_t aX, uint32_t aY,
uint32_t aWidth, uint32_t aHeight)
-> nsresult;
};
} // namespace zen
#endif

View File

@@ -0,0 +1,41 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef mozilla_nsZenShareInternal_h__
#define mozilla_nsZenShareInternal_h__
#include "nsIZenCommonUtils.h"
#include "nsIDOMWindow.h"
#include "nsGlobalWindowOuter.h"
#include "nsIURI.h"
#if defined(XP_WIN) || defined(XP_MACOSX)
#define NS_ZEN_CAN_SHARE_NATIVE true
class nsZenNativeShareInternal final {
public:
/**
* @brief Use the native share dialog. This only works on Windows and MacOS
* since the native share dialog is not available on other platforms.
* Macos does need pointer coordinates to show the share dialog while
* Windows does not since it just displays a dialog on the middle of the
* screen.
* @param aWindow The window to use for the share dialog.
* @param aUrl The URL to share.
* @param aTitle The title of the share.
* @param aText The text to share.
* @returns void
*/
static auto ShowNativeDialog(nsCOMPtr<mozIDOMWindowProxy>& aWindow, nsIURI* aUrl,
const nsACString& aTitle, const nsACString& aText, uint32_t aX = 0, uint32_t aY = 0,
uint32_t aWidth = 0, uint32_t aHeight = 0)
-> nsresult;
nsZenNativeShareInternal() = default;
~nsZenNativeShareInternal() = default;
};
#endif
#endif

View File

@@ -0,0 +1,78 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "ZenShareInternal.h"
#include "nsCocoaUtils.h"
#include "nsPIDOMWindow.h"
#include "WidgetUtils.h"
#include "nsIWidget.h"
extern mozilla::LazyLogModule gCocoaUtilsLog;
#undef LOG
#define LOG(...) MOZ_LOG(gCocoaUtilsLog, mozilla::LogLevel::Info, (__VA_ARGS__))
#import <Foundation/Foundation.h>
#import <AppKit/AppKit.h>
namespace zen {
using ::mozilla::widget::WidgetUtils;
/**
* Get the native NSWindow pointer from a DOM window.
*
* @param a_window The DOM window to get the native window from.
* @param a_nativeWindow The pointer to the native NSWindow.
* @return NS_OK on success, or an error code on failure.
*/
static nsresult GetNativeWindowPointerFromDOMWindow(mozIDOMWindowProxy* a_window,
NSWindow** a_nativeWindow) {
*a_nativeWindow = nil;
if (!a_window) return NS_ERROR_INVALID_ARG;
nsPIDOMWindowOuter* win = nsPIDOMWindowOuter::From(a_window);
nsCOMPtr<nsIWidget> widget = WidgetUtils::DOMWindowToWidget(win);
if (!widget) {
return NS_ERROR_FAILURE;
}
*a_nativeWindow = (NSWindow*)widget->GetNativeData(NS_NATIVE_WINDOW);
return NS_OK;
}
}
auto nsZenNativeShareInternal::ShowNativeDialog(nsCOMPtr<mozIDOMWindowProxy>& aWindow,
nsIURI* aUrl, const nsACString& aTitle, const nsACString& aText, uint32_t aX, uint32_t aY,
uint32_t aWidth, uint32_t aHeight)
-> nsresult {
// Just use the URL since apple doesn't support sharing text
// and title in the share dialog
nsAutoCString pageUrlAsStringTemp;
if (aUrl) {
nsresult rv = aUrl->GetSpec(pageUrlAsStringTemp);
MOZ_ASSERT(NS_SUCCEEDED(rv));
mozilla::Unused << rv;
} else {
pageUrlAsStringTemp.SetIsVoid(true);
}
NSURL* pageUrl = nsCocoaUtils::ToNSURL(
NS_ConvertUTF8toUTF16(pageUrlAsStringTemp)
);
if (!pageUrl || (![pageUrl.scheme isEqualToString:@"https"] &&
![pageUrl.scheme isEqualToString:@"http"])) {
return NS_ERROR_FAILURE;
}
NSSharingServicePicker* sharingPicker =
[[NSSharingServicePicker alloc] initWithItems:@[ pageUrl ]];
NSWindow* cocoaMru = nil;
zen::GetNativeWindowPointerFromDOMWindow(aWindow, &cocoaMru);
if (!cocoaMru) {
LOG("ERROR: failed to get native window pointer");
return NS_ERROR_FAILURE;
}
// Create a rect for the sharing picker
NSRect rect = NSMakeRect(aX, aY, aWidth, aHeight);
[sharingPicker showRelativeToRect:rect
ofView:cocoaMru.contentView
preferredEdge:NSMaxYEdge];
return NS_OK;
}

View File

@@ -0,0 +1,12 @@
FINAL_LIBRARY = "xul"
SOURCES += [
"ZenShareInternal.mm",
]
LOCAL_INCLUDES += [
"../",
"/widget",
"/widget/cocoa",
"/xpcom/base",
]

View File

@@ -0,0 +1,11 @@
Classes = [
{
'cid': '{d034642a-43b1-4814-be1c-8ad75e337c84}',
'interfaces': ['nsIZenCommonUtils'],
'contract_ids': ['@mozilla.org/zen/common-utils;1'],
'type': 'zen::ZenCommonUtils',
'headers': ['mozilla/ZenCommonUtils.h'],
'js_name': 'zen',
'processes': ProcessSelector.MAIN_PROCESS_ONLY,
},
]

View File

@@ -0,0 +1,26 @@
XPIDL_SOURCES += [
"nsIZenCommonUtils.idl",
]
EXPORTS.mozilla += [
"ZenCommonUtils.h",
"ZenShareInternal.h",
]
SOURCES += [
"ZenCommonUtils.cpp",
]
XPCOM_MANIFESTS += [
"components.conf",
]
if CONFIG["MOZ_WIDGET_TOOLKIT"] == "windows":
DIRS += ["windows"]
if CONFIG["MOZ_WIDGET_TOOLKIT"] == "cocoa":
DIRS += ["cocoa"]
FINAL_LIBRARY = "xul"
XPIDL_MODULE = "zen"

View File

@@ -0,0 +1,32 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "nsISupports.idl"
#include "nsIURI.idl"
/**
* @brief Common utility functions for Zen.
*/
[scriptable, uuid(d034642a-43b1-4814-be1c-8ad75e337c84)]
interface nsIZenCommonUtils : nsISupports {
/*
* @brief Share using the native share dialog.
* @param window The window to use for the share dialog.
* @param url The URL to share.
* @param title The title of the share.
* @param text The text to share.
* @param x The x coordinate of the share dialog.
* @param y The y coordinate of the share dialog.
* @returns A promise that resolves when the share is complete.
*/
void share(in nsIURI url, in ACString title, in ACString text,
in uint32_t x, in uint32_t y, in uint32_t width, in uint32_t height);
/*
* @brief Check if the current context can share data.
* @param window The window to check.
* @returns True if the current context can share data, false otherwise.
*/
boolean canShare();
};

View File

@@ -0,0 +1,38 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "ZenShareInternal.h"
#include "WindowsUIUtils.h"
namespace zen {
/**
* @brief Helper function to convert UTF-8 to UTF-16.
* @param aStr The UTF-8 string to convert.
* @returns The converted UTF-16 string.
*/
inline NS_ConvertUTF8toUTF16 NS_ConvertUTF8toUTF16_MaybeVoid(
const nsACString& aStr) {
auto str = NS_ConvertUTF8toUTF16(aStr);
str.SetIsVoid(aStr.IsVoid());
return str;
}
} // namespace: zen
auto nsZenNativeShareInternal::ShowNativeDialog(
nsCOMPtr<mozIDOMWindowProxy>& aWindow, nsIURI* aUrl, const nsACString& aTitle,
const nsACString& aText, uint32_t aX, uint32_t aY, uint32_t aWidth, uint32_t aHeight)
-> nsresult {
nsAutoCString urlString;
if (aUrl) {
nsresult rv = aUrl->GetSpec(urlString);
MOZ_ASSERT(NS_SUCCEEDED(rv));
mozilla::Unused << rv;
} else {
urlString.SetIsVoid(true);
}
(void)WindowsUIUtils::Share(zen::NS_ConvertUTF8toUTF16_MaybeVoid(aTitle),
zen::NS_ConvertUTF8toUTF16_MaybeVoid(aText),
zen::NS_ConvertUTF8toUTF16_MaybeVoid(urlString));
return NS_OK;
}

View File

@@ -0,0 +1,11 @@
FINAL_LIBRARY = "xul"
SOURCES += [
"ZenShareInternal.cpp",
]
LOCAL_INCLUDES += [
"../",
"/widget",
"/widget/windows",
]

View File

@@ -0,0 +1,4 @@
DIRS += [
"common",
]