Add screenshot support for webcompat reports

This commit is contained in:
Darnell Andries
2024-03-11 10:42:20 -07:00
parent 29c72ffa82
commit 6e43ca324e
14 changed files with 272 additions and 116 deletions
@@ -57,7 +57,6 @@ class WebcompatReporterDialogDelegate : public ui::WebDialogDelegate {
void OnDialogClosed(const std::string& json_retval) override;
void OnCloseContents(WebContents* source, bool* out_close_dialog) override;
bool ShouldShowDialogTitle() const override;
private:
base::Value::Dict params_;
};
@@ -11,8 +11,9 @@
#include "base/metrics/histogram_macros.h"
#include "base/strings/string_util.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/thread_pool.h"
#include "brave/browser/brave_browser_process.h"
#include "brave/browser/ui/brave_shields_data_controller.h"
#include "brave/browser/ui/webui/brave_webui_source.h"
#include "brave/browser/ui/webui/webcompat_reporter/webcompat_reporter_dialog.h"
#include "brave/components/brave_shields/content/browser/ad_block_service.h"
@@ -20,18 +21,19 @@
#include "brave/components/brave_shields/core/browser/filter_list_catalog_entry.h"
#include "brave/components/brave_shields/core/common/pref_names.h"
#include "brave/components/brave_vpn/common/buildflags/buildflags.h"
#include "brave/components/constants/webui_url_constants.h"
#include "brave/components/webcompat_reporter/browser/fields.h"
#include "brave/components/webcompat_reporter/browser/webcompat_report_uploader.h"
#include "brave/components/webcompat_reporter/resources/grit/webcompat_reporter_generated_map.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_finder.h"
#include "components/grit/brave_components_resources.h"
#include "components/language/core/browser/pref_names.h"
#include "components/prefs/pref_service.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/render_widget_host_view.h"
#include "content/public/browser/web_ui_data_source.h"
#include "content/public/browser/web_ui_message_handler.h"
#include "services/network/public/cpp/shared_url_loader_factory.h"
#include "ui/web_dialogs/web_dialog_delegate.h"
#include "ui/gfx/codec/png_codec.h"
#include "url/gurl.h"
#if BUILDFLAG(ENABLE_BRAVE_VPN)
@@ -43,34 +45,28 @@ namespace webcompat_reporter {
namespace {
constexpr const char kUISourceHistogramName[] = "Brave.Webcompat.UISource";
constexpr char kUISourceHistogramName[] = "Brave.Webcompat.UISource";
constexpr int kMaxScreenshotPixelCount = 1280 * 720;
class WebcompatReporterDOMHandler : public content::WebUIMessageHandler {
public:
explicit WebcompatReporterDOMHandler(Profile* profile);
WebcompatReporterDOMHandler(const WebcompatReporterDOMHandler&) = delete;
WebcompatReporterDOMHandler& operator=(const WebcompatReporterDOMHandler&) =
delete;
~WebcompatReporterDOMHandler() override;
// WebUIMessageHandler implementation.
void RegisterMessages() override;
private:
void InitAdditionalParameters(Profile* profile);
void HandleSubmitReport(const base::Value::List& args);
std::unique_ptr<webcompat_reporter::WebcompatReportUploader> uploader_;
std::string ad_block_list_names_;
std::string languages_;
bool language_farbling_enabled_ = true;
bool brave_vpn_connected_ = false;
};
} // namespace
WebcompatReporterDOMHandler::WebcompatReporterDOMHandler(Profile* profile)
: uploader_(std::make_unique<webcompat_reporter::WebcompatReportUploader>(
: ui_task_runner_(base::SequencedTaskRunner::GetCurrentDefault()),
uploader_(std::make_unique<webcompat_reporter::WebcompatReportUploader>(
profile->GetURLLoaderFactory())) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
InitAdditionalParameters(profile);
auto* browser = chrome::FindLastActiveWithProfile(profile);
if (!browser) {
return;
}
auto* web_contents = browser->tab_strip_model()->GetActiveWebContents();
if (!web_contents) {
return;
}
render_widget_host_view_ = web_contents->GetTopLevelRenderWidgetHostView();
}
void WebcompatReporterDOMHandler::InitAdditionalParameters(Profile* profile) {
@@ -91,19 +87,21 @@ void WebcompatReporterDOMHandler::InitAdditionalParameters(Profile* profile) {
}
}
ad_block_list_names_ = base::JoinString(ad_block_list_names, ",");
pending_report_.ad_block_list_names =
base::JoinString(ad_block_list_names, ",");
#if BUILDFLAG(ENABLE_BRAVE_VPN)
brave_vpn::BraveVpnService* vpn_service =
brave_vpn::BraveVpnServiceFactory::GetForProfile(profile);
if (vpn_service != nullptr) {
brave_vpn_connected_ = vpn_service->IsConnected();
pending_report_.brave_vpn_connected = vpn_service->IsConnected();
}
#endif
PrefService* profile_prefs = profile->GetPrefs();
languages_ = profile_prefs->GetString(language::prefs::kAcceptLanguages);
language_farbling_enabled_ =
pending_report_.languages =
profile_prefs->GetString(language::prefs::kAcceptLanguages);
pending_report_.language_farbling =
profile_prefs->GetBoolean(brave_shields::prefs::kReduceLanguageEnabled);
}
@@ -116,6 +114,58 @@ void WebcompatReporterDOMHandler::RegisterMessages() {
base::Unretained(this)));
}
void WebcompatReporterDOMHandler::CaptureScreenshot() {
CHECK(render_widget_host_view_);
auto output_size = render_widget_host_view_->GetVisibleViewportSize();
auto original_area = output_size.GetArea();
if (original_area > kMaxScreenshotPixelCount) {
// Scale image down if it's too big
float output_scale =
std::sqrt(static_cast<float>(kMaxScreenshotPixelCount) / original_area);
output_size = gfx::ScaleToRoundedSize(output_size, output_scale);
}
render_widget_host_view_->CopyFromSurface(
{}, output_size,
base::BindOnce(
[](base::WeakPtr<WebcompatReporterDOMHandler> handler,
scoped_refptr<base::SequencedTaskRunner> ui_task_runner,
const SkBitmap& bitmap) {
ui_task_runner->PostTask(
FROM_HERE, base::BindOnce(&WebcompatReporterDOMHandler::
HandleCapturedScreenshotBitmap,
handler, bitmap));
},
weak_ptr_factory_.GetWeakPtr(), ui_task_runner_));
}
void WebcompatReporterDOMHandler::HandleCapturedScreenshotBitmap(
SkBitmap bitmap) {
if (bitmap.drawsNothing()) {
uploader_->SubmitReport(pending_report_);
}
base::ThreadPool::PostTaskAndReplyWithResult(
FROM_HERE,
base::BindOnce(
[](SkBitmap bitmap) {
std::vector<unsigned char> png_output;
return gfx::PNGCodec::EncodeBGRASkBitmap(bitmap, true, &png_output)
? std::optional(png_output)
: std::nullopt;
},
bitmap),
base::BindOnce(&WebcompatReporterDOMHandler::HandleEncodedScreenshotPNG,
weak_ptr_factory_.GetWeakPtr()));
}
void WebcompatReporterDOMHandler::HandleEncodedScreenshotPNG(
std::optional<std::vector<unsigned char>> encoded_png) {
pending_report_.screenshot_png = encoded_png;
uploader_->SubmitReport(pending_report_);
}
void WebcompatReporterDOMHandler::HandleSubmitReport(
const base::Value::List& args) {
DCHECK_EQ(args.size(), 1U);
@@ -125,6 +175,8 @@ void WebcompatReporterDOMHandler::HandleSubmitReport(
const base::Value::Dict& submission_args = args[0].GetDict();
const auto attach_screenshot =
submission_args.FindBool(kAttachScreenshotField);
const std::string* url_arg = submission_args.FindString(kSiteURLField);
const std::string* ad_block_setting_arg =
submission_args.FindString(kAdBlockSettingField);
@@ -132,44 +184,39 @@ void WebcompatReporterDOMHandler::HandleSubmitReport(
submission_args.FindString(kFPBlockSettingField);
const base::Value* details_arg = submission_args.Find(kDetailsField);
const base::Value* contact_arg = submission_args.Find(kContactField);
bool shields_enabled =
pending_report_.shields_enabled =
submission_args.FindBool(kShieldsEnabledField).value_or(false);
auto ui_source_int = submission_args.FindInt(kUISourceField);
const auto ui_source_int = submission_args.FindInt(kUISourceField);
if (ui_source_int) {
UISource ui_source = static_cast<UISource>(*ui_source_int);
UMA_HISTOGRAM_ENUMERATION(kUISourceHistogramName, ui_source);
}
std::string url;
std::string ad_block_setting;
std::string fp_block_setting;
base::Value details;
base::Value contact;
if (url_arg != nullptr) {
url = *url_arg;
pending_report_.report_url = GURL(*url_arg);
}
if (ad_block_setting_arg != nullptr) {
ad_block_setting = *ad_block_setting_arg;
pending_report_.ad_block_setting = *ad_block_setting_arg;
}
if (fp_block_setting_arg != nullptr) {
fp_block_setting = *fp_block_setting_arg;
pending_report_.fp_block_setting = *fp_block_setting_arg;
}
if (details_arg != nullptr) {
details = details_arg->Clone();
pending_report_.details = details_arg->Clone();
}
if (contact_arg != nullptr) {
contact = contact_arg->Clone();
pending_report_.contact = contact_arg->Clone();
}
uploader_->SubmitReport(GURL(url), shields_enabled, ad_block_setting,
fp_block_setting, ad_block_list_names_, languages_,
language_farbling_enabled_, brave_vpn_connected_,
details, contact);
}
if (attach_screenshot && *attach_screenshot && render_widget_host_view_) {
CaptureScreenshot();
// Screenshot callbacks will handle upload.
return;
}
} // namespace
uploader_->SubmitReport(pending_report_);
}
WebcompatReporterUI::WebcompatReporterUI(content::WebUI* web_ui,
const std::string& name)
@@ -6,16 +6,54 @@
#ifndef BRAVE_BROWSER_UI_WEBUI_WEBCOMPAT_REPORTER_WEBCOMPAT_REPORTER_UI_H_
#define BRAVE_BROWSER_UI_WEBUI_WEBCOMPAT_REPORTER_WEBCOMPAT_REPORTER_UI_H_
#include <memory>
#include <string>
#include <vector>
#include "base/memory/weak_ptr.h"
#include "base/task/sequenced_task_runner.h"
#include "brave/components/webcompat_reporter/browser/webcompat_report_uploader.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/webui/constrained_web_dialog_ui.h"
#include "content/public/browser/web_ui_message_handler.h"
namespace content {
class RenderWidgetHostView;
class WebUI;
}
namespace webcompat_reporter {
class WebcompatReporterDOMHandler : public content::WebUIMessageHandler {
public:
explicit WebcompatReporterDOMHandler(Profile* profile);
WebcompatReporterDOMHandler(const WebcompatReporterDOMHandler&) = delete;
WebcompatReporterDOMHandler& operator=(const WebcompatReporterDOMHandler&) =
delete;
~WebcompatReporterDOMHandler() override;
// WebUIMessageHandler implementation.
void RegisterMessages() override;
private:
void InitAdditionalParameters(Profile* profile);
void CaptureScreenshot();
void HandleCapturedScreenshotBitmap(SkBitmap bitmap);
void HandleEncodedScreenshotPNG(
std::optional<std::vector<unsigned char>> encoded_png);
void HandleSubmitReport(const base::Value::List& args);
raw_ptr<content::RenderWidgetHostView> render_widget_host_view_;
scoped_refptr<base::SequencedTaskRunner> ui_task_runner_;
std::unique_ptr<webcompat_reporter::WebcompatReportUploader> uploader_;
Report pending_report_;
base::WeakPtrFactory<WebcompatReporterDOMHandler> weak_ptr_factory_{this};
};
class WebcompatReporterUI : public ConstrainedWebDialogUI {
public:
WebcompatReporterUI(content::WebUI* web_ui, const std::string& host);
@@ -437,6 +437,7 @@
<message name="IDS_BRAVE_WEBCOMPATREPORTER_REPORT_DETAILS" desc="Placeholder text for the optional details field in the broken website report">Additional details (optional)</message>
<message name="IDS_BRAVE_WEBCOMPATREPORTER_REPORT_CONTACT_LABEL" desc="Label for the optional contact information field in the broken website report">Contact me at: (optional)</message>
<message name="IDS_BRAVE_WEBCOMPATREPORTER_REPORT_CONTACT_PLACEHOLDER" desc="Placeholder text for the optional contact information field in the broken website report">Email, Twitter, etc.</message>
<message name="IDS_BRAVE_WEBCOMPATREPORTER_ATTACH_SCREENSHOT_LABEL" desc="Label for the checkbox for attaching a screenshot with the broken website report">Include screenshot of the current page</message>
<message name="IDS_BRAVE_WEBCOMPATREPORTER_CANCEL" desc="Text for button to cancel sending a broken website report">Cancel</message>
<message name="IDS_BRAVE_WEBCOMPATREPORTER_SUBMIT" desc="Text for button to submit a broken website report">Submit</message>
<message name="IDS_BRAVE_WEBCOMPATREPORTER_THANK_YOU" desc="Thanks the user for submitting a report of a broken website">Thank you!</message>
@@ -17,6 +17,7 @@ inline constexpr char kShieldsEnabledField[] = "shieldsEnabled";
inline constexpr char kLanguagesField[] = "languages";
inline constexpr char kLanguageFarblingField[] = "languageFarblingEnabled";
inline constexpr char kBraveVPNEnabledField[] = "braveVPNEnabled";
inline constexpr char kAttachScreenshotField[] = "attachScreenshot";
inline constexpr char kSiteURLField[] = "url";
inline constexpr char kDomainField[] = "domain";
@@ -9,16 +9,13 @@
#include <string>
#include <utility>
#include "base/environment.h"
#include "base/json/json_writer.h"
#include "brave/components/brave_referrals/browser/brave_referrals_service.h"
#include "brave/components/brave_stats/browser/brave_stats_updater_util.h"
#include "brave/components/webcompat_reporter/browser/fields.h"
#include "brave/components/webcompat_reporter/buildflags/buildflags.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "net/base/load_flags.h"
#include "net/base/privacy_mode.h"
#include "net/base/mime_util.h"
#include "services/network/public/cpp/resource_request.h"
#include "services/network/public/cpp/shared_url_loader_factory.h"
#include "services/network/public/cpp/simple_url_loader.h"
@@ -28,57 +25,86 @@
namespace webcompat_reporter {
namespace {
constexpr char kJsonContentType[] = "application/json";
constexpr char kPngContentType[] = "image/png";
constexpr char kMultipartContentTypePrefix[] = "multipart/form-data; boundary=";
constexpr char kReportDetailsMultipartName[] = "report-details";
constexpr char kScreenshotMultipartName[] = "screenshot";
constexpr char kScreenshotMultipartFilename[] = "screenshot.png";
} // namespace
Report::Report() = default;
Report::~Report() = default;
WebcompatReportUploader::WebcompatReportUploader(
scoped_refptr<network::SharedURLLoaderFactory> factory)
: shared_url_loader_factory_(std::move(factory)) {}
WebcompatReportUploader::~WebcompatReportUploader() = default;
void WebcompatReportUploader::SubmitReport(
const GURL& report_url,
const bool shields_enabled,
const std::string& ad_block_setting,
const std::string& fp_block_setting,
const std::string& ad_block_list_names,
const std::string& languages,
const bool language_farbling,
const bool brave_vpn_connected,
const base::Value& details,
const base::Value& contact) {
void WebcompatReportUploader::SubmitReport(const Report& report) {
std::string api_key = brave_stats::GetAPIKey();
const GURL upload_url(BUILDFLAG(WEBCOMPAT_REPORT_ENDPOINT));
url::Origin report_url_origin = url::Origin::Create(report_url);
url::Origin report_url_origin = url::Origin::Create(report.report_url);
base::Value::Dict post_data_obj;
post_data_obj.Set(kSiteURLField, report_url.spec());
post_data_obj.Set(kDomainField, report_url_origin.Serialize());
post_data_obj.Set(kDetailsField, details.Clone());
post_data_obj.Set(kContactField, contact.Clone());
base::Value::Dict report_details_dict;
report_details_dict.Set(kSiteURLField, report.report_url.spec());
report_details_dict.Set(kDomainField, report_url_origin.Serialize());
report_details_dict.Set(kDetailsField, report.details.Clone());
report_details_dict.Set(kContactField, report.contact.Clone());
post_data_obj.Set(kShieldsEnabledField, shields_enabled);
post_data_obj.Set(kAdBlockSettingField, ad_block_setting);
post_data_obj.Set(kFPBlockSettingField, fp_block_setting);
post_data_obj.Set(kAdBlockListsField, ad_block_list_names);
post_data_obj.Set(kLanguagesField, languages);
post_data_obj.Set(kLanguageFarblingField, language_farbling);
post_data_obj.Set(kBraveVPNEnabledField, brave_vpn_connected);
report_details_dict.Set(kShieldsEnabledField, report.shields_enabled);
report_details_dict.Set(kAdBlockSettingField, report.ad_block_setting);
report_details_dict.Set(kFPBlockSettingField, report.fp_block_setting);
report_details_dict.Set(kAdBlockListsField, report.ad_block_list_names);
report_details_dict.Set(kLanguagesField, report.languages);
report_details_dict.Set(kLanguageFarblingField, report.language_farbling);
report_details_dict.Set(kBraveVPNEnabledField, report.brave_vpn_connected);
post_data_obj.Set(kApiKeyField, base::Value(api_key));
report_details_dict.Set(kApiKeyField, base::Value(api_key));
std::string post_data;
base::JSONWriter::Write(post_data_obj, &post_data);
std::string report_details_json;
base::JSONWriter::Write(report_details_dict, &report_details_json);
WebcompatReportUploader::CreateAndStartURLLoader(upload_url, post_data);
if (report.screenshot_png && !report.screenshot_png->empty()) {
std::string multipart_boundary = net::GenerateMimeMultipartBoundary();
std::string content_type = kMultipartContentTypePrefix + multipart_boundary;
std::string multipart_data;
net::AddMultipartValueForUpload(kReportDetailsMultipartName,
report_details_json, multipart_boundary,
kJsonContentType, &multipart_data);
std::string screenshot_png_str;
screenshot_png_str = std::string(report.screenshot_png->begin(),
report.screenshot_png->end());
net::AddMultipartValueForUploadWithFileName(
kScreenshotMultipartName, kScreenshotMultipartFilename,
screenshot_png_str, multipart_boundary, kPngContentType,
&multipart_data);
net::AddMultipartFinalDelimiterForUpload(multipart_boundary,
&multipart_data);
WebcompatReportUploader::CreateAndStartURLLoader(upload_url, content_type,
multipart_data);
return;
}
WebcompatReportUploader::CreateAndStartURLLoader(upload_url, kJsonContentType,
report_details_json);
}
void WebcompatReportUploader::CreateAndStartURLLoader(
const GURL& upload_url,
const std::string& content_type,
const std::string& post_data) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
std::string content_type = "application/json";
auto resource_request = std::make_unique<network::ResourceRequest>();
// upload_url only includes the origin and path, and not the fragment or
// query. The fragment and query are removed from the URL in
@@ -100,8 +126,8 @@ void WebcompatReportUploader::CreateAndStartURLLoader(
"Though the 'Report a Broken Site' option of the help menu or"
"the Brave Shields panel."
data: "Broken URL, IP address, Shields settings, language settings,"
"Brave VPN connection status; user provided additional details"
"and contact information."
"Brave VPN connection status, user-provided additional details,"
"optional screenshot and contact information."
destination: OTHER
destination_other: "Brave developers"
}
@@ -8,6 +8,7 @@
#include <memory>
#include <string>
#include <vector>
#include "base/memory/ref_counted.h"
#include "base/values.h"
@@ -21,6 +22,24 @@ class SimpleURLLoader;
namespace webcompat_reporter {
struct Report {
Report();
~Report();
GURL report_url;
bool shields_enabled;
std::string ad_block_setting;
std::string fp_block_setting;
std::string ad_block_list_names;
std::string languages;
bool language_farbling;
bool brave_vpn_connected;
base::Value details;
base::Value contact;
std::optional<std::vector<unsigned char>> screenshot_png;
};
class WebcompatReportUploader {
public:
explicit WebcompatReportUploader(
@@ -29,21 +48,13 @@ class WebcompatReportUploader {
WebcompatReportUploader& operator=(const WebcompatReportUploader&) = delete;
~WebcompatReportUploader();
void SubmitReport(const GURL& report_url,
const bool shields_enabled,
const std::string& ad_block_setting,
const std::string& fp_block_setting,
const std::string& ad_block_list_names,
const std::string& languages,
const bool language_farbling,
const bool brave_vpn_connected,
const base::Value& details,
const base::Value& contact);
void SubmitReport(const Report& report);
private:
std::unique_ptr<network::SimpleURLLoader> simple_url_loader_;
scoped_refptr<network::SharedURLLoaderFactory> shared_url_loader_factory_;
void CreateAndStartURLLoader(const GURL& upload_url,
const std::string& content_type,
const std::string& post_data);
void OnSimpleURLLoaderComplete(std::unique_ptr<std::string> response_body);
};
@@ -14,10 +14,11 @@ export const setDialogArgs = (dialogArgs: WebcompatReporter.DialogArgs) => {
})
}
export const onSubmitReport = (details: string, contact: string) =>
export const onSubmitReport = (details: string, contact: string, attachScreenshot: boolean) =>
action(types.WEBCOMPATREPORTER_ON_SUBMIT_REPORT, {
details,
contact
contact,
attachScreenshot
})
export const onClose = () =>
@@ -16,6 +16,8 @@ import {
SideBySideButtons,
PaddedButton,
Input,
Checkbox,
CheckboxLabel,
TextArea,
FieldCtr,
InputLabel
@@ -26,19 +28,20 @@ import { getLocale } from '../../../common/locale'
interface Props {
siteUrl: string
onSubmitReport: (details: string, contact: string) => void
onSubmitReport: (details: string, contact: string, attachScreenshot: boolean) => void
onClose: () => void
}
interface State {
details: string
contact: string
attachScreenshot: boolean
}
export default class ReportView extends React.PureComponent<Props, State> {
constructor (props: Props) {
super(props)
this.state = { details: '', contact: '' }
this.state = { details: '', contact: '', attachScreenshot: true }
}
render () {
@@ -47,7 +50,7 @@ export default class ReportView extends React.PureComponent<Props, State> {
onSubmitReport,
onClose
} = this.props
const { details, contact } = this.state
const { details, contact, attachScreenshot } = this.state
return (
<ModalLayout>
<TextSection>
@@ -78,6 +81,17 @@ export default class ReportView extends React.PureComponent<Props, State> {
id='contact-info'
/>
</FieldCtr>
<FieldCtr>
<Checkbox
onChange={(ev) => this.setState({ attachScreenshot: ev.target.checked })}
type='checkbox'
checked={attachScreenshot}
id='attach-screenshot'
/>
<CheckboxLabel htmlFor='attach-screenshot'>
{getLocale('attachScreenshotLabel')}
</CheckboxLabel>
</FieldCtr>
<SideBySideButtons>
<PaddedButton
text={getLocale('cancel')}
@@ -91,7 +105,7 @@ export default class ReportView extends React.PureComponent<Props, State> {
level={'primary'}
type={'accent'}
size={'small'}
onClick={() => onSubmitReport(details, contact)}
onClick={() => onSubmitReport(details, contact, attachScreenshot)}
/>
</SideBySideButtons>
</ModalLayout>
@@ -30,17 +30,19 @@ export default class WebcompatReportModal extends React.PureComponent<Props, {}>
onClose
} = this.props
return (
<RectangularCard>
{submitted ? (
<ConfirmationView/>
) : (
<ReportView
siteUrl={siteUrl}
onSubmitReport={onSubmitReport}
onClose={onClose}
/>
)}
</RectangularCard>
<div onContextMenu={(e: React.MouseEvent<HTMLDivElement>) => e.preventDefault()}>
<RectangularCard>
{submitted ? (
<ConfirmationView/>
) : (
<ReportView
siteUrl={siteUrl}
onSubmitReport={onSubmitReport}
onClose={onClose}
/>
)}
</RectangularCard>
</div>
)
}
}
@@ -57,6 +57,7 @@ export const SuccessIcon = styled(CheckCircleIcon)`
export const RectangularCard = styled(Card)<CardProps>`
border-radius: 0;
user-select: none;
`
export const Input = styled.input`
@@ -83,6 +84,12 @@ export const Input = styled.input`
}
`
export const Checkbox = styled(Input)`
width: unset;
margin-right: 6px;
vertical-align: middle;
`
export const InputLabel = styled.label`
display: block;
box-sizing: border-box;
@@ -92,6 +99,10 @@ export const InputLabel = styled.label`
padding-bottom: 4px;
`
export const CheckboxLabel = styled(InputLabel)`
display: unset;
`
export const TextArea = styled.textarea`
box-sizing: border-box;
width: 100%;
@@ -20,6 +20,8 @@ export {
NonInteractiveURL,
SuccessIcon,
Input,
Checkbox,
CheckboxLabel,
TextArea,
InputLabel
} from './display'
@@ -25,7 +25,8 @@ const webcompatReporterReducer: Reducer<WebcompatReporter.State | undefined> = (
{
...state.dialogArgs,
additionalDetails: action.payload.details || null,
contactInfo: action.payload.contact || null
contactInfo: action.payload.contact || null,
attachScreenshot: action.payload.attachScreenshot || false
}
])
state = {
+2
View File
@@ -1050,6 +1050,8 @@ base::span<const webui::LocalizedString> GetWebUILocalizedStrings(
IDS_BRAVE_WEBCOMPATREPORTER_REPORT_CONTACT_PLACEHOLDER},
{"reportContactLabel",
IDS_BRAVE_WEBCOMPATREPORTER_REPORT_CONTACT_LABEL},
{"attachScreenshotLabel",
IDS_BRAVE_WEBCOMPATREPORTER_ATTACH_SCREENSHOT_LABEL},
{"cancel", IDS_BRAVE_WEBCOMPATREPORTER_CANCEL},
{"submit", IDS_BRAVE_WEBCOMPATREPORTER_SUBMIT},
// Confirmation modal