0

Reland "WebShare: Implement SharingServicePicker"

This is a reland of 07700ae66e

Original change's description:
> WebShare: Implement SharingServicePicker
>
> This CL completes the implementation of webshare on mac.
> The UX review is being tracked here: crbug.com/1162971
>
> To test manually:
> -launch chromium with flag 'WebShare'
> -go to https://w3c.github.io/web-share/demos/share-files.html
> -optionally, add a file to the form
> -click on Share
> -the NSSharingServicePicker should popup in the top-middle,
> overlapping the client and non-client area
>
> Bug: 1144920
> Change-Id: Ia28ad87fac82a9704c07122b57a144ce5aa5086e
> Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2668974
> Reviewed-by: Avi Drissman <avi@chromium.org>
> Reviewed-by: Daniel Murphy <dmurph@chromium.org>
> Reviewed-by: Eric Willigers <ericwilligers@chromium.org>
> Reviewed-by: Leonard Grey <lgrey@chromium.org>
> Reviewed-by: Dominick Ng <dominickn@chromium.org>
> Commit-Queue: Eric Willigers <ericwilligers@chromium.org>
> Cr-Commit-Position: refs/heads/master@{#859624}

Bug: 1144920
Change-Id: Ied9df60b65fc19c366f9c0e038028563df976a5c
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2737234
Reviewed-by: Avi Drissman <avi@chromium.org>
Reviewed-by: Dominick Ng <dominickn@chromium.org>
Reviewed-by: Eric Willigers <ericwilligers@chromium.org>
Reviewed-by: Leonard Grey <lgrey@chromium.org>
Reviewed-by: Daniel Murphy <dmurph@chromium.org>
Commit-Queue: Hassan Talat <hatalat@microsoft.com>
Cr-Commit-Position: refs/heads/master@{#866720}
This commit is contained in:
Hassan Talat
2021-03-25 19:28:21 +00:00
committed by Chromium LUCI CQ
parent 69400eb99d
commit e7f474a35e
21 changed files with 342 additions and 5 deletions

@ -33,6 +33,7 @@ class SharingServiceOperation final : content::WebContentsObserver {
const std::vector<base::FilePath>& file_paths,
const std::string& text,
const std::string& title,
const GURL& url,
blink::mojom::ShareService::ShareCallback close_callback)>;
SharingServiceOperation(const std::string& title,
@ -59,6 +60,7 @@ class SharingServiceOperation final : content::WebContentsObserver {
const std::vector<base::FilePath>& file_paths,
const std::string& text,
const std::string& title,
const GURL& url,
blink::mojom::ShareService::ShareCallback close_callback);
static SharePickerCallback& GetSharePickerCallback();

@ -6,16 +6,20 @@
#include <AppKit/AppKit.h>
#include <utility>
#include "base/bind.h"
#include "base/guid.h"
#include "base/i18n/file_util_icu.h"
#include "base/mac/scoped_nsobject.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/browser/webshare/prepare_directory_task.h"
#include "chrome/browser/webshare/prepare_subdirectory_task.h"
#include "chrome/browser/webshare/share_service_impl.h"
#include "chrome/browser/webshare/store_files_task.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/render_widget_host_view.h"
#include "content/public/browser/storage_partition.h"
#include "content/public/browser/web_contents.h"
#include "net/base/filename_util.h"
@ -60,7 +64,7 @@ void SharingServiceOperation::Share(
if (shared_files_.size() == 0) {
GetSharePickerCallback().Run(
web_contents(), file_paths_, text_, title_,
web_contents(), file_paths_, text_, title_, url_,
base::BindOnce(&SharingServiceOperation::OnShowSharePicker,
weak_factory_.GetWeakPtr()));
return;
@ -94,6 +98,8 @@ void SharingServiceOperation::OnPrepareDirectory(
for (const auto& file : shared_files_) {
std::string file_name = file->name;
// Protecting against including paths in a file name.
base::ReplaceSubstringsAfterOffset(&file_name, 0, "/", "_");
base::i18n::ReplaceIllegalCharactersInPath(&file_name, '_');
file_paths_.push_back(
GenerateUniqueSubDirectory(directory_).Append(file_name));
@ -125,18 +131,24 @@ void SharingServiceOperation::OnPrepareSubDirectory(
void SharingServiceOperation::OnStoreFiles(blink::mojom::ShareError error) {
if (!web_contents() || error != blink::mojom::ShareError::OK) {
PrepareDirectoryTask::ScheduleSharedFileDeletion(
std::move(file_paths_), base::TimeDelta::FromMinutes(0));
std::move(callback_).Run(error);
return;
}
GetSharePickerCallback().Run(
web_contents(), file_paths_, text_, title_,
web_contents(), file_paths_, text_, title_, url_,
base::BindOnce(&SharingServiceOperation::OnShowSharePicker,
weak_factory_.GetWeakPtr()));
}
void SharingServiceOperation::OnShowSharePicker(
blink::mojom::ShareError error) {
if (file_paths_.size() > 0) {
PrepareDirectoryTask::ScheduleSharedFileDeletion(
std::move(file_paths_), base::TimeDelta::FromMinutes(0));
}
std::move(callback_).Run(error);
}
@ -146,15 +158,15 @@ void SharingServiceOperation::ShowSharePicker(
const std::vector<base::FilePath>& file_paths,
const std::string& text,
const std::string& title,
const GURL& url,
blink::mojom::ShareService::ShareCallback callback) {
std::vector<std::string> file_paths_as_utf8;
for (const auto& file_path : file_paths) {
file_paths_as_utf8.emplace_back(file_path.AsUTF8Unsafe());
}
// TODO(crbug.com/1144920): Add & invoke NSSharingServicePicker in
// remote_cocoa
std::move(callback).Run(blink::mojom::ShareError::INTERNAL_ERROR);
web_contents->GetRenderWidgetHostView()->ShowSharePicker(
title, text, url.spec(), file_paths_as_utf8, std::move(callback));
}
// static

@ -0,0 +1,61 @@
// Copyright 2021 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/webshare/mac/sharing_service_operation.h"
#include "base/test/bind.h"
#include "base/test/scoped_feature_list.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/common/chrome_features.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/base/ui_test_utils.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "third_party/blink/public/mojom/webshare/webshare.mojom.h"
#include "url/gurl.h"
namespace webshare {
class SharingServiceOperationBrowserTest : public InProcessBrowserTest {
public:
SharingServiceOperationBrowserTest() {
feature_list_.InitAndEnableFeature(features::kWebShare);
}
GURL GetAppUrl() const {
return embedded_test_server()->GetURL("/webshare/index.html");
}
private:
base::test::ScopedFeatureList feature_list_;
};
IN_PROC_BROWSER_TEST_F(SharingServiceOperationBrowserTest,
ShareIllegalFilename) {
const std::string script =
"share_multiple_custom_files_url('../sample.csv', '..sample.csv')";
ASSERT_TRUE(embedded_test_server()->Start());
ui_test_utils::NavigateToURL(browser(), GetAppUrl());
content::WebContents* const contents =
browser()->tab_strip_model()->GetActiveWebContents();
std::vector<base::FilePath> file_paths;
ui_test_utils::NavigateToURL(browser(), GetAppUrl());
SharingServiceOperation::SetSharePickerCallbackForTesting(
base::BindLambdaForTesting(
[](content::WebContents* in_contents,
const std::vector<base::FilePath>& file_paths,
const std::string& text, const std::string& title, const GURL& url,
blink::mojom::ShareService::ShareCallback close_callback) {
EXPECT_EQ(file_paths[0].BaseName().value(), "_._sample.csv");
EXPECT_EQ(file_paths[1].BaseName().value(), "_.sample.csv");
std::move(close_callback).Run(blink::mojom::ShareError::OK);
}));
EXPECT_EQ("share succeeded", content::EvalJs(contents, script));
}
} // namespace webshare

@ -67,6 +67,7 @@ class ShareServiceBrowserTest : public InProcessBrowserTest {
const std::vector<base::FilePath>& file_paths,
const std::string& text,
const std::string& title,
const GURL& url,
blink::mojom::ShareService::ShareCallback close_callback) {
std::move(close_callback).Run(blink::mojom::ShareError::OK);
}

@ -155,6 +155,7 @@ class ShareServiceUnitTest : public ChromeRenderViewHostTestHarness {
const std::vector<base::FilePath>& file_paths,
const std::string& text,
const std::string& title,
const GURL& url,
blink::mojom::ShareService::ShareCallback close_callback) {
std::move(close_callback).Run(blink::mojom::ShareError::OK);
}

@ -1861,6 +1861,11 @@ if (!is_android) {
sources += [ "../browser/webshare/share_service_browsertest.cc" ]
}
if (is_mac) {
sources +=
[ "../browser/webshare/mac/sharing_service_operation_browsertest.cc" ]
}
if (enable_dice_support) {
sources += [
"../browser/policy/cloud/user_policy_signin_service_browsertest.cc",

@ -142,6 +142,19 @@
return ('share failed: ' + error);
}
}
async function share_multiple_custom_files_url(first_filename, second_filename) {
try {
const first_file = create_file(first_filename, 'text/csv', 12);
const second_file = create_file(second_filename, 'text/csv', 15);
await navigator.share({files: [first_file, second_file],
url: "https://example.com/"});
return 'share succeeded';
} catch(error) {
return ('share failed: ' + error);
}
}
</script>
</head>
<body>

@ -31,4 +31,5 @@ include_rules = [
"+content/public/common/content_features.h",
"+content/public/common/drop_data.h",
"+content/public/common/widget_type.h",
"+third_party/blink/public/mojom/webshare/webshare.mojom.h",
]

@ -10,6 +10,7 @@
#import "base/mac/scoped_nsobject.h"
#import "content/app_shim_remote_cocoa/popup_window_mac.h"
#import "content/app_shim_remote_cocoa/render_widget_host_view_cocoa.h"
#include "content/app_shim_remote_cocoa/sharing_service_picker.h"
#include "content/common/render_widget_host_ns_view.mojom.h"
#include "content/public/common/widget_type.h"
#include "mojo/public/cpp/bindings/associated_receiver.h"
@ -68,6 +69,12 @@ class RenderWidgetHostNSViewBridge : public mojom::RenderWidgetHostNSView,
void LockKeyboard(
const base::Optional<std::vector<uint32_t>>& uint_dom_codes) override;
void UnlockKeyboard() override;
void ShowSharingServicePicker(
const std::string& title,
const std::string& text,
const std::string& url,
const std::vector<std::string>& file_paths,
ShowSharingServicePickerCallback callback) override;
private:
bool IsPopup() const { return !!popup_window_; }

@ -271,4 +271,14 @@ void RenderWidgetHostNSViewBridge::UnlockKeyboard() {
[cocoa_view_ unlockKeyboard];
}
void RenderWidgetHostNSViewBridge::ShowSharingServicePicker(
const std::string& title,
const std::string& text,
const std::string& url,
const std::vector<std::string>& file_paths,
ShowSharingServicePickerCallback callback) {
ShowSharingServicePickerForView(cocoa_view_, title, text, url, file_paths,
std::move(callback));
}
} // namespace remote_cocoa

@ -0,0 +1,26 @@
// Copyright 2021 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CONTENT_APP_SHIM_REMOTE_COCOA_SHARING_SERVICE_PICKER_H_
#define CONTENT_APP_SHIM_REMOTE_COCOA_SHARING_SERVICE_PICKER_H_
#include <string>
#include <AppKit/AppKit.h>
#include "content/common/render_widget_host_ns_view.mojom.h"
namespace remote_cocoa {
void ShowSharingServicePickerForView(
NSView* owning_view,
const std::string& title,
const std::string& text,
const std::string& url,
const std::vector<std::string>& file_paths,
mojom::RenderWidgetHostNSView::ShowSharingServicePickerCallback callback);
} // namespace remote_cocoa
#endif // CONTENT_APP_SHIM_REMOTE_COCOA_SHARING_SERVICE_PICKER_H_

@ -0,0 +1,114 @@
// Copyright 2021 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/app_shim_remote_cocoa/sharing_service_picker.h"
#include <utility>
#include "base/mac/scoped_nsobject.h"
#include "base/strings/sys_string_conversions.h"
#include "third_party/blink/public/mojom/webshare/webshare.mojom.h"
#include "url/gurl.h"
@interface SharingServicePicker
: NSObject <NSSharingServiceDelegate, NSSharingServicePickerDelegate>
// Displays the NSSharingServicePicker which is positioned center and overlaps
// WebContents and the Non Client area.
- (void)show;
@end
@implementation SharingServicePicker {
base::scoped_nsobject<NSSharingServicePicker> picker_;
remote_cocoa::mojom::RenderWidgetHostNSView::ShowSharingServicePickerCallback
callback_;
NSView* view_;
}
- (instancetype)initWithItems:(NSArray*)items
callback:(remote_cocoa::mojom::RenderWidgetHostNSView::
ShowSharingServicePickerCallback)cb
view:(NSView*)view {
if ((self = [super init])) {
picker_.reset([[NSSharingServicePicker alloc] initWithItems:items]);
picker_.get().delegate = self;
callback_ = std::move(cb);
view_ = view;
}
return self;
}
- (void)sharingServicePicker:(NSSharingServicePicker*)sharingServicePicker
didChooseSharingService:(NSSharingService*)service {
// When the NSSharingServicePicker gets invoked but then the picker gets
// dismissed, this is the only delegate method called, and it's called with a
// nil service, so run the callback.
if (!service) {
std::move(callback_).Run(blink::mojom::ShareError::CANCELED);
}
}
- (void)show {
NSRect viewFrame = [view_ frame];
CGSize size = viewFrame.size;
NSRect rect = NSMakeRect(size.width / 2, size.height, 1, 1);
[picker_ showRelativeToRect:rect ofView:view_ preferredEdge:NSMaxXEdge];
}
- (void)sharingService:(NSSharingService*)sharingService
didShareItems:(NSArray*)items {
std::move(callback_).Run(blink::mojom::ShareError::OK);
}
- (void)sharingService:(NSSharingService*)sharingService
didFailToShareItems:(NSArray*)items
error:(NSError*)error {
error.code == NSUserCancelledError
? std::move(callback_).Run(blink::mojom::ShareError::CANCELED)
: std::move(callback_).Run(blink::mojom::ShareError::INTERNAL_ERROR);
}
- (id<NSSharingServiceDelegate>)
sharingServicePicker:(NSSharingServicePicker*)sharingServicePicker
delegateForSharingService:(NSSharingService*)sharingService {
return self;
}
- (NSWindow*)sharingService:(NSSharingService*)sharingService
sourceWindowForShareItems:(NSArray*)items
sharingContentScope:(NSSharingContentScope*)sharingContentScope {
return [view_ window];
}
@end
namespace remote_cocoa {
void ShowSharingServicePickerForView(
NSView* view,
const std::string& title,
const std::string& text,
const std::string& url,
const std::vector<std::string>& file_paths,
mojom::RenderWidgetHostNSView::ShowSharingServicePickerCallback callback) {
NSString* ns_title = base::SysUTF8ToNSString(title);
NSString* ns_url = base::SysUTF8ToNSString(url);
NSString* ns_text = base::SysUTF8ToNSString(text);
NSMutableArray* items =
[NSMutableArray arrayWithArray:@[ ns_title, ns_url, ns_text ]];
for (const auto& file_path : file_paths) {
NSString* ns_file_path = base::SysUTF8ToNSString(file_path);
NSURL* file_url = [NSURL fileURLWithPath:ns_file_path];
[items addObject:file_url];
}
base::scoped_nsobject<SharingServicePicker> picker(
[[SharingServicePicker alloc] initWithItems:items
callback:std::move(callback)
view:view]);
[picker.get() show];
}
} // namespace remote_cocoa

@ -2742,6 +2742,8 @@ source_set("browser") {
"../app_shim_remote_cocoa/render_widget_host_ns_view_host_helper.h",
"../app_shim_remote_cocoa/render_widget_host_view_cocoa.h",
"../app_shim_remote_cocoa/render_widget_host_view_cocoa.mm",
"../app_shim_remote_cocoa/sharing_service_picker.h",
"../app_shim_remote_cocoa/sharing_service_picker.mm",
"../app_shim_remote_cocoa/web_contents_view_cocoa.h",
"../app_shim_remote_cocoa/web_contents_view_cocoa.mm",
]

@ -755,6 +755,13 @@ void RenderWidgetHostViewChildFrame::SpeakSelection() {}
void RenderWidgetHostViewChildFrame::SetWindowFrameInScreen(
const gfx::Rect& rect) {}
void RenderWidgetHostViewChildFrame::ShowSharePicker(
const std::string& title,
const std::string& text,
const std::string& url,
const std::vector<std::string>& file_paths,
blink::mojom::ShareService::ShareCallback callback) {}
#endif // defined(OS_MAC)
void RenderWidgetHostViewChildFrame::CopyFromSurface(

@ -35,6 +35,10 @@
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/native_widget_types.h"
#if defined(OS_MAC)
#include "third_party/blink/public/mojom/webshare/webshare.mojom.h"
#endif // defined(OS_MAC)
namespace content {
class CrossProcessFrameConnector;
class RenderWidgetHost;
@ -147,6 +151,12 @@ class CONTENT_EXPORT RenderWidgetHostViewChildFrame
void ShowDefinitionForSelection() override;
void SpeakSelection() override;
void SetWindowFrameInScreen(const gfx::Rect& rect) override;
void ShowSharePicker(
const std::string& title,
const std::string& text,
const std::string& url,
const std::vector<std::string>& file_paths,
blink::mojom::ShareService::ShareCallback callback) override;
#endif // defined(OS_MAC)
blink::mojom::InputEventResultState FilterInputEvent(

@ -24,6 +24,7 @@
#include "content/common/render_widget_host_ns_view.mojom.h"
#include "mojo/public/cpp/bindings/associated_receiver.h"
#include "mojo/public/cpp/bindings/associated_remote.h"
#include "third_party/blink/public/mojom/webshare/webshare.mojom.h"
#include "ui/accelerated_widget_mac/accelerated_widget_mac.h"
#include "ui/base/cocoa/accessibility_focus_overrider.h"
#include "ui/base/cocoa/remote_layer_api.h"
@ -479,6 +480,13 @@ class CONTENT_EXPORT RenderWidgetHostViewMac
MouseWheelPhaseHandler* GetMouseWheelPhaseHandler() override;
void ShowSharePicker(
const std::string& title,
const std::string& text,
const std::string& url,
const std::vector<std::string>& file_paths,
blink::mojom::ShareService::ShareCallback callback) override;
protected:
// This class is to be deleted through the Destroy method.
~RenderWidgetHostViewMac() override;

@ -1412,6 +1412,16 @@ MouseWheelPhaseHandler* RenderWidgetHostViewMac::GetMouseWheelPhaseHandler() {
return &mouse_wheel_phase_handler_;
}
void RenderWidgetHostViewMac::ShowSharePicker(
const std::string& title,
const std::string& text,
const std::string& url,
const std::vector<std::string>& file_paths,
blink::mojom::ShareService::ShareCallback callback) {
ns_view_->ShowSharingServicePicker(title, text, url, file_paths,
std::move(callback));
}
///////////////////////////////////////////////////////////////////////////////
// RenderWidgetHostNSViewHostHelper and mojom::RenderWidgetHostNSViewHost
// implementation:

@ -6,6 +6,7 @@ module remote_cocoa.mojom;
import "mojo/public/mojom/base/string16.mojom";
import "third_party/blink/public/mojom/input/input_handler.mojom";
import "third_party/blink/public/mojom/webshare/share_error.mojom";
import "ui/base/mojom/attributed_string.mojom";
import "ui/base/cursor/mojom/cursor.mojom";
import "ui/base/ime/mojom/ime_types.mojom";
@ -92,6 +93,14 @@ interface RenderWidgetHostNSView {
// Stop intercepting keyboard events.
UnlockKeyboard();
// Show the NSSharingServicePicker using the requested data that is passed and
// returning the result of that action.
ShowSharingServicePicker(string title,
string text,
string url,
array<string> file_paths)
=> (blink.mojom.ShareError error);
};
// The interface through which the RenderWidgetHostViewCocoa NSView in the app

@ -22,6 +22,10 @@
#include "ui/gfx/native_widget_types.h"
#include "ui/gfx/range/range.h"
#if defined(OS_MAC)
#include "third_party/blink/public/mojom/webshare/webshare.mojom.h"
#endif
namespace gfx {
class Insets;
class Point;
@ -258,6 +262,23 @@ class CONTENT_EXPORT RenderWidgetHostView {
// Allows to update the widget's screen rects when it is not attached to
// a window (e.g. in headless mode).
virtual void SetWindowFrameInScreen(const gfx::Rect& rect) = 0;
// Invoked by browser implementation of the navigator.share() to trigger the
// NSSharingServicePicker.
//
// |title|, |text|, |url| makes up the requested data that is passed to the
// picker after being converted to NSString.
// |file_paths| is the set of paths to files to be shared passed onto the
// picker after being converted to NSURL.
// |callback| returns the result from the NSSharingServicePicker depending
// upon the user's action.
virtual void ShowSharePicker(
const std::string& title,
const std::string& text,
const std::string& url,
const std::vector<std::string>& file_paths,
blink::mojom::ShareService::ShareCallback callback) = 0;
#endif // defined(OS_MAC)
// Indicates that this view should show the contents of |view| if it doesn't

@ -160,6 +160,13 @@ void TestRenderWidgetHostView::SpeakSelection() {
}
void TestRenderWidgetHostView::SetWindowFrameInScreen(const gfx::Rect& rect) {}
void TestRenderWidgetHostView::ShowSharePicker(
const std::string& title,
const std::string& text,
const std::string& url,
const std::vector<std::string>& file_paths,
blink::mojom::ShareService::ShareCallback callback) {}
#endif
gfx::Rect TestRenderWidgetHostView::GetBoundsInRootWindow() {

@ -30,6 +30,10 @@
#include "ui/aura/window.h"
#endif
#if defined(OS_MAC)
#include "third_party/blink/public/mojom/webshare/webshare.mojom.h"
#endif
// This file provides a testing framework for mocking out the RenderProcessHost
// layer. It allows you to test RenderViewHost, WebContentsImpl,
// NavigationController, and other layers above that without running an actual
@ -77,6 +81,12 @@ class TestRenderWidgetHostView : public RenderWidgetHostViewBase,
void ShowDefinitionForSelection() override {}
void SpeakSelection() override;
void SetWindowFrameInScreen(const gfx::Rect& rect) override;
void ShowSharePicker(
const std::string& title,
const std::string& text,
const std::string& url,
const std::vector<std::string>& file_paths,
blink::mojom::ShareService::ShareCallback callback) override;
#endif // defined(OS_MAC)
// Advances the fallback surface to the first surface after navigation. This