Bug 1553603 - Support "capture" attribute in Gecko and expose in GV. r=geckoview-reviewers,smaug,snorp,agi

Differential Revision: https://phabricator.services.mozilla.com/D38746

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Dylan Roeh 2019-07-30 18:13:05 +00:00
parent 68a314f460
commit 1e945a8dd6
24 changed files with 140 additions and 52 deletions

View File

@ -195,6 +195,14 @@ static const nsAttrValue::EnumTable kInputInputmodeTable[] = {
static const nsAttrValue::EnumTable* kInputDefaultInputmode =
&kInputInputmodeTable[0];
static const nsAttrValue::EnumTable kCaptureTable[] = {
{"user", static_cast<int16_t>(nsIFilePicker::captureUser)},
{"environment", static_cast<int16_t>(nsIFilePicker::captureEnv)},
{"", static_cast<int16_t>(nsIFilePicker::captureDefault)},
{nullptr, static_cast<int16_t>(nsIFilePicker::captureNone)}};
static const nsAttrValue::EnumTable* kCaptureDefault = &kCaptureTable[2];
const Decimal HTMLInputElement::kStepScaleFactorDate = Decimal(86400000);
const Decimal HTMLInputElement::kStepScaleFactorNumberRange = Decimal(1);
const Decimal HTMLInputElement::kStepScaleFactorTime = Decimal(1000);
@ -762,6 +770,14 @@ nsresult HTMLInputElement::InitFilePicker(FilePickerType aType) {
if (HasAttr(kNameSpaceID_None, nsGkAtoms::accept) &&
aType != FILE_PICKER_DIRECTORY) {
SetFilePickerFiltersFromAccept(filePicker);
if (StaticPrefs::dom_capture_enabled()) {
const nsAttrValue* captureVal = GetParsedAttr(nsGkAtoms::capture,
kNameSpaceID_None);
if (captureVal) {
filePicker->SetCapture(captureVal->GetEnumValue());
}
}
} else {
filePicker->AppendFilters(nsIFilePicker::filterAll);
}
@ -1359,6 +1375,10 @@ void HTMLInputElement::GetAutocompleteInfo(Nullable<AutocompleteInfo>& aInfo) {
attributeVal, aInfo.SetValue(), mAutocompleteInfoState, true);
}
void HTMLInputElement::GetCapture(nsAString& aValue) {
GetEnumAttr(nsGkAtoms::capture, kCaptureDefault->tag, aValue);
}
void HTMLInputElement::GetFormEnctype(nsAString& aValue) {
GetEnumAttr(nsGkAtoms::formenctype, "", kFormDefaultEnctype->tag, aValue);
}
@ -5212,6 +5232,9 @@ bool HTMLInputElement::ParseAttribute(int32_t aNamespaceID, nsAtom* aAttribute,
if (aAttribute == nsGkAtoms::inputmode) {
return aResult.ParseEnumValue(aValue, kInputInputmodeTable, false);
}
if (aAttribute == nsGkAtoms::capture) {
return aResult.ParseEnumValue(aValue, kCaptureTable, false, kCaptureDefault);
}
if (ParseImageAttribute(aAttribute, aValue, aResult)) {
// We have to call |ParseImageAttribute| unconditionally since we
// don't know if we're going to have a type="image" attribute yet,

View File

@ -471,6 +471,11 @@ class HTMLInputElement final : public nsGenericHTMLFormElementWithState,
SetHTMLBoolAttr(nsGkAtoms::autofocus, aValue, aRv);
}
void GetCapture(nsAString& aValue);
void SetCapture(const nsAString& aValue, ErrorResult& aRv) {
SetHTMLAttr(nsGkAtoms::capture, aValue, aRv);
}
bool DefaultChecked() const {
return HasAttr(kNameSpaceID_None, nsGkAtoms::checked);
}

View File

@ -241,7 +241,8 @@ mozilla::ipc::IPCResult FilePickerParent::RecvOpen(
const nsString& aDefaultFile, const nsString& aDefaultExtension,
nsTArray<nsString>&& aFilters, nsTArray<nsString>&& aFilterNames,
nsTArray<nsString>&& aRawFilters, const nsString& aDisplayDirectory,
const nsString& aDisplaySpecialDirectory, const nsString& aOkButtonLabel) {
const nsString& aDisplaySpecialDirectory, const nsString& aOkButtonLabel,
const int16_t& aCapture) {
if (!CreateFilePicker()) {
Unused << Send__delete__(this, void_t(), nsIFilePicker::returnCancel);
return IPC_OK();
@ -261,6 +262,7 @@ mozilla::ipc::IPCResult FilePickerParent::RecvOpen(
mFilePicker->SetDefaultExtension(aDefaultExtension);
mFilePicker->SetFilterIndex(aSelectedType);
mFilePicker->SetOkButtonLabel(aOkButtonLabel);
mFilePicker->SetCapture(aCapture);
if (!aDisplayDirectory.IsEmpty()) {
nsCOMPtr<nsIFile> localFile = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID);

View File

@ -42,7 +42,8 @@ class FilePickerParent : public PFilePickerParent {
const nsString& aDefaultFile, const nsString& aDefaultExtension,
nsTArray<nsString>&& aFilters, nsTArray<nsString>&& aFilterNames,
nsTArray<nsString>&& aRawFilters, const nsString& aDisplayDirectory,
const nsString& aDisplaySpecialDirectory, const nsString& aOkButtonLabel);
const nsString& aDisplaySpecialDirectory, const nsString& aOkButtonLabel,
const int16_t& aCapture);
virtual void ActorDestroy(ActorDestroyReason aWhy) override;

View File

@ -42,7 +42,8 @@ parent:
async Open(int16_t selectedType, bool addToRecentDocs, nsString defaultFile,
nsString defaultExtension, nsString[] filters, nsString[] filterNames,
nsString[] rawFilters, nsString displayDirectory,
nsString displaySpecialDirectory, nsString okButtonLabel);
nsString displaySpecialDirectory, nsString okButtonLabel,
int16_t capture);
child:
async __delete__(MaybeInputData data, int16_t result);

View File

@ -32,6 +32,8 @@ interface HTMLInputElement : HTMLElement {
attribute DOMString autocomplete;
[CEReactions, Pure, SetterThrows]
attribute boolean autofocus;
[CEReactions, Pure, SetterThrows, Pref="dom.capture.enabled"]
attribute DOMString capture;
[CEReactions, Pure, SetterThrows]
attribute boolean defaultChecked;
[Pure]

View File

@ -26,6 +26,9 @@ pref("geckoview.logging", "Warn");
pref("geckoview.logging", "Debug");
#endif
// Enable capture attribute for file input.
pref("dom.capture.enabled", true);
// Disable Web Push until we get it working
pref("dom.push.enabled", false);

View File

@ -975,6 +975,7 @@ FilePickerDelegate.prototype = {
};
this._mode = aMode;
this._mimeTypes = [];
this._capture = 0;
},
get mode() {
@ -991,6 +992,7 @@ FilePickerDelegate.prototype = {
open: function(aFilePickerShownCallback) {
this._msg.mimeTypes = this._mimeTypes;
this._msg.capture = this._capture;
this._prompt.asyncShowPrompt(this._msg, result => {
// OK: result
// Cancel: !result
@ -1090,6 +1092,14 @@ FilePickerDelegate.prototype = {
},
set okButtonLabel(aValue) {},
get capture() {
return this._capture;
},
set capture(aValue) {
this._capture = aValue;
},
};
function ColorPickerDelegate() {}

View File

@ -646,12 +646,16 @@ package org.mozilla.geckoview {
method @UiThread default public void onChoicePrompt(@NonNull GeckoSession, @Nullable String, @Nullable String, int, @NonNull GeckoSession.PromptDelegate.Choice[], @NonNull GeckoSession.PromptDelegate.ChoiceCallback);
method @UiThread default public void onColorPrompt(@NonNull GeckoSession, @Nullable String, @Nullable String, @NonNull GeckoSession.PromptDelegate.TextCallback);
method @UiThread default public void onDateTimePrompt(@NonNull GeckoSession, @Nullable String, int, @Nullable String, @Nullable String, @Nullable String, @NonNull GeckoSession.PromptDelegate.TextCallback);
method @UiThread default public void onFilePrompt(@NonNull GeckoSession, @Nullable String, int, @Nullable String[], @NonNull GeckoSession.PromptDelegate.FileCallback);
method @UiThread default public void onFilePrompt(@NonNull GeckoSession, @Nullable String, int, @Nullable String[], int, @NonNull GeckoSession.PromptDelegate.FileCallback);
method @UiThread @Nullable default public GeckoResult<AllowOrDeny> onPopupRequest(@NonNull GeckoSession, @Nullable String);
method @UiThread default public void onTextPrompt(@NonNull GeckoSession, @Nullable String, @Nullable String, @Nullable String, @NonNull GeckoSession.PromptDelegate.TextCallback);
field public static final int BUTTON_TYPE_NEGATIVE = 2;
field public static final int BUTTON_TYPE_NEUTRAL = 1;
field public static final int BUTTON_TYPE_POSITIVE = 0;
field public static final int CAPTURE_TYPE_ANY = 1;
field public static final int CAPTURE_TYPE_ENVIRONMENT = 3;
field public static final int CAPTURE_TYPE_NONE = 0;
field public static final int CAPTURE_TYPE_USER = 2;
field public static final int DATETIME_TYPE_DATE = 1;
field public static final int DATETIME_TYPE_DATETIME_LOCAL = 5;
field public static final int DATETIME_TYPE_MONTH = 2;

View File

@ -240,11 +240,11 @@ class PromptDelegateTest : BaseSessionTest() {
sessionRule.waitUntilCalled(object : Callbacks.PromptDelegate {
@AssertCalled(count = 1)
override fun onFilePrompt(session: GeckoSession, title: String?, type: Int, mimeTypes: Array<out String>?, callback: GeckoSession.PromptDelegate.FileCallback) {
override fun onFilePrompt(session: GeckoSession, title: String?, type: Int, mimeTypes: Array<out String>?, capture: Int, callback: GeckoSession.PromptDelegate.FileCallback) {
assertThat("Length of mimeTypes should match", 2, equalTo(mimeTypes!!.size))
assertThat("First accept attribute should match", "image/*", equalTo(mimeTypes[0]))
assertThat("Second accept attribute should match", ".pdf", equalTo(mimeTypes[1]))
// TODO: Test capture attribute when implemented.
assertThat("Capture attribute should match", GeckoSession.PromptDelegate.CAPTURE_TYPE_USER, equalTo(capture))
}
})
}

View File

@ -2760,7 +2760,8 @@ public class GeckoSession implements Parcelable {
return;
}
String[] mimeTypes = message.getStringArray("mimeTypes");
delegate.onFilePrompt(session, title, intMode, mimeTypes, cb);
int capture = message.getInt("capture");
delegate.onFilePrompt(session, title, intMode, mimeTypes, capture, cb);
break;
}
case "popup": {
@ -4061,6 +4062,27 @@ public class GeckoSession implements Parcelable {
static final int FILE_TYPE_SINGLE = 1;
static final int FILE_TYPE_MULTIPLE = 2;
// These values should match the corresponding values in nsIFilePicker.idl
/**
* No capture attribute has been supplied by content.
*/
static final int CAPTURE_TYPE_NONE = 0;
/**
* The capture attribute was supplied with a missing or invalid value.
*/
static final int CAPTURE_TYPE_ANY = 1;
/**
* The "user" capture attribute has been supplied by content.
*/
static final int CAPTURE_TYPE_USER = 2;
/**
* The "environment" capture attribute has been supplied by content.
*/
static final int CAPTURE_TYPE_ENVIRONMENT = 3;
/**
* Display a file prompt.
*
@ -4071,12 +4093,15 @@ public class GeckoSession implements Parcelable {
* files. MIME types are of the form "type/subtype", where "type"
* and/or "subtype" can be "*" to indicate any value. Extensions
* are of the form ".ext".
* @param capture One of {@link #CAPTURE_TYPE_NONE CAPTURE_TYPE_*} indicating if the
* file is expected to be captured by a device camera and, if so, which
* type of camera.
* @param callback Callback interface.
*/
@UiThread
default void onFilePrompt(@NonNull GeckoSession session, @Nullable String title,
@FileType int type, @Nullable String[] mimeTypes,
@NonNull FileCallback callback) {
@CaptureType int capture, @NonNull FileCallback callback) {
callback.dismiss();
}
@ -4101,6 +4126,11 @@ public class GeckoSession implements Parcelable {
@IntDef({PromptDelegate.FILE_TYPE_SINGLE, PromptDelegate.FILE_TYPE_MULTIPLE})
/* package */ @interface FileType {}
@Retention(RetentionPolicy.SOURCE)
@IntDef({PromptDelegate.CAPTURE_TYPE_NONE, PromptDelegate.CAPTURE_TYPE_ANY,
PromptDelegate.CAPTURE_TYPE_USER, PromptDelegate.CAPTURE_TYPE_ENVIRONMENT})
/* package */ @interface CaptureType {}
@Retention(RetentionPolicy.SOURCE)
@IntDef({PromptDelegate.DATETIME_TYPE_DATE, PromptDelegate.DATETIME_TYPE_MONTH,
PromptDelegate.DATETIME_TYPE_WEEK, PromptDelegate.DATETIME_TYPE_TIME,

View File

@ -26,6 +26,8 @@ exclude: true
when the URI we're loading originates from another page. A common example of this would be long pressing
a link and then opening that in a new `GeckoSession`.
- Added capture parameter to `onFilePrompt` and corresponding `CAPTURE_TYPE_*` constants.
## v69
- Modified behavior of ['setAutomaticFontSizeAdjustment'][69.1] so that it no
longer has any effect on ['setFontInflationEnabled'][69.2]
@ -372,4 +374,4 @@ exclude: true
[65.24]: ../CrashReporter.html#sendCrashReport-android.content.Context-android.os.Bundle-java.lang.String-
[65.25]: ../GeckoResult.html
[api-version]: d17e48f35d11ff368320919c6213e72d9f138da1
[api-version]: ce03a46188d89b6d2ed5ebc58bdcd640eda216c9

View File

@ -711,7 +711,7 @@ final class BasicGeckoViewPrompt implements GeckoSession.PromptDelegate {
@TargetApi(19)
public void onFilePrompt(GeckoSession session, String title, int type,
String[] mimeTypes, FileCallback callback)
String[] mimeTypes, int capture, FileCallback callback)
{
final Activity activity = mActivity;
if (activity == null) {

View File

@ -1109,6 +1109,12 @@
value: false
mirror: always
# Disable capture attribute for input elements; only supported on GeckoView.
- name: dom.capture.enabled
type: bool
value: false
mirror: always
# Whether Mozilla specific "text" event should be dispatched only in the
# system group or not in content.
- name: dom.compositionevent.text.dispatch_only_system_group_in_content

View File

@ -1,31 +1,2 @@
[capture_reflect.html]
[Element input should have own property capture]
expected: FAIL
[input.capture is false when the capture attribute is absent]
expected: FAIL
[input.capture is true when the capture attribute is present]
expected: FAIL
[input.capture is true when the capture attribute is present as empty string]
expected: FAIL
[input.capture is true when the capture attribute is present as canonical name]
expected: FAIL
[input.capture is "" when the capture attribute is absent]
expected: FAIL
[input.capture is "" when the capture attribute is missing value default]
expected: FAIL
[input.capture is "user" when the capture attribute is user]
expected: FAIL
[input.capture is "invalid" when the capture attribute is invalid value default]
expected: FAIL
[input.capture is "environment" when the capture attribute is environment]
expected: FAIL
prefs: ["dom.capture.enabled:true"]

View File

@ -1,7 +1,2 @@
[idlharness.window.html]
[HTMLInputElement interface: input must inherit property "capture" with the proper type]
expected: FAIL
[HTMLInputElement interface: attribute capture]
expected: FAIL
prefs: ["dom.capture.enabled:true"]

View File

@ -20,7 +20,7 @@ if ('capture' in HTMLInputElement.prototype) {
instance['capture'] = 'user';
const logEntries = element.takeLog();
assert_array_equals(logEntries.types(), ['attributeChanged']);
assert_attribute_log_entry(logEntries.last(), {name: 'capture', oldValue: '', newValue: 'user', namespace: null});
assert_attribute_log_entry(logEntries.last(), {name: 'capture', oldValue: null, newValue: 'user', namespace: null});
}, 'capture on HTMLInputElement must enqueue an attributeChanged reaction when adding new attribute');
test(() => {
@ -43,7 +43,7 @@ if ('capture' in HTMLInputElement.prototype) {
instance['capture'] = 'asdf';
const logEntries = element.takeLog();
assert_array_equals(logEntries.types(), ['attributeChanged']);
assert_attribute_log_entry(logEntries.last(), {name: 'capture', oldValue: '', newValue: 'asdf', namespace: null});
assert_attribute_log_entry(logEntries.last(), {name: 'capture', oldValue: null, newValue: 'asdf', namespace: null});
}, 'capture on HTMLInputElement must enqueue an attributeChanged reaction when adding invalid value default');
test(() => {

View File

@ -53,8 +53,8 @@
}, 'input.capture is "user" when the capture attribute is user');
test(function() {
assert_equals(document.querySelector('#present-invalid').capture, "invalid");
}, 'input.capture is "invalid" when the capture attribute is invalid value default');
assert_equals(document.querySelector('#present-invalid').capture, "");
}, 'input.capture is "" when the capture attribute is invalid value default');
test(function() {
assert_equals(document.querySelector('#present-environment').capture, "environment");

View File

@ -236,6 +236,13 @@ NS_IMETHODIMP nsBaseFilePicker::AppendRawFilter(const nsAString& aFilter) {
return NS_OK;
}
NS_IMETHODIMP nsBaseFilePicker::GetCapture(int16_t* aCapture) {
*aCapture = 0;
return NS_OK;
}
NS_IMETHODIMP nsBaseFilePicker::SetCapture(int16_t aCapture) { return NS_OK; }
// Set the filter index
NS_IMETHODIMP nsBaseFilePicker::GetFilterIndex(int32_t* aFilterIndex) {
*aFilterIndex = 0;

View File

@ -30,6 +30,8 @@ class nsBaseFilePicker : public nsIFilePicker {
NS_IMETHOD Open(nsIFilePickerShownCallback* aCallback) override;
NS_IMETHOD AppendFilters(int32_t filterMask) override;
NS_IMETHOD AppendRawFilter(const nsAString& aFilter) override;
NS_IMETHOD GetCapture(int16_t* aCapture) override;
NS_IMETHOD SetCapture(int16_t aCapture) override;
NS_IMETHOD GetFilterIndex(int32_t* aFilterIndex) override;
NS_IMETHOD SetFilterIndex(int32_t aFilterIndex) override;
NS_IMETHOD GetFiles(nsISimpleEnumerator** aFiles) override;

View File

@ -17,7 +17,8 @@ using namespace mozilla::dom;
NS_IMPL_ISUPPORTS(nsFilePickerProxy, nsIFilePicker)
nsFilePickerProxy::nsFilePickerProxy() : mSelectedType(0), mIPCActive(false) {}
nsFilePickerProxy::nsFilePickerProxy()
: mSelectedType(0), mCapture(captureNone), mIPCActive(false) {}
nsFilePickerProxy::~nsFilePickerProxy() {}
@ -51,6 +52,18 @@ nsFilePickerProxy::AppendFilter(const nsAString& aTitle,
return NS_OK;
}
NS_IMETHODIMP
nsFilePickerProxy::GetCapture(int16_t* aCapture) {
*aCapture = mCapture;
return NS_OK;
}
NS_IMETHODIMP
nsFilePickerProxy::SetCapture(int16_t aCapture) {
mCapture = aCapture;
return NS_OK;
}
NS_IMETHODIMP
nsFilePickerProxy::GetDefaultString(nsAString& aDefaultString) {
aDefaultString = mDefault;
@ -126,7 +139,7 @@ nsFilePickerProxy::Open(nsIFilePickerShownCallback* aCallback) {
SendOpen(mSelectedType, mAddToRecentDocs, mDefault, mDefaultExtension,
mFilters, mFilterNames, mRawFilters, displayDirectory,
mDisplaySpecialDirectory, mOkButtonLabel);
mDisplaySpecialDirectory, mOkButtonLabel, mCapture);
return NS_OK;
}

View File

@ -37,6 +37,8 @@ class nsFilePickerProxy : public nsBaseFilePicker,
int16_t aMode) override;
NS_IMETHOD AppendFilter(const nsAString& aTitle,
const nsAString& aFilter) override;
NS_IMETHOD GetCapture(int16_t* aCapture) override;
NS_IMETHOD SetCapture(int16_t aCapture) override;
NS_IMETHOD GetDefaultString(nsAString& aDefaultString) override;
NS_IMETHOD SetDefaultString(const nsAString& aDefaultString) override;
NS_IMETHOD GetDefaultExtension(nsAString& aDefaultExtension) override;
@ -71,6 +73,7 @@ class nsFilePickerProxy : public nsBaseFilePicker,
nsString mFile;
nsString mDefault;
nsString mDefaultExtension;
int16_t mCapture;
bool mIPCActive;

View File

@ -58,6 +58,11 @@ interface nsIFilePicker : nsISupports
// *.rm; *.rmvb; *.smil; *.webm;
// *.wmv; *.xvid
const short captureNone = 0; // No capture target specified.
const short captureDefault = 1; // Missing/invalid value default.
const short captureUser = 2; // "user" capture target specified.
const short captureEnv = 3; // "environment" capture target specified.
/**
* Initialize the file picker widget. The file picker is not valid until this
* method is called.
@ -205,4 +210,6 @@ interface nsIFilePicker : nsISupports
* file selection.
*/
attribute AString okButtonLabel;
attribute short capture;
};

View File

@ -202,6 +202,7 @@ STATIC_ATOMS = [
Atom("canvas", "canvas"),
Atom("caption", "caption"),
Atom("captionBox", "caption-box"),
Atom("capture", "capture"),
Atom("caseOrder", "case-order"),
Atom("cdataSectionElements", "cdata-section-elements"),
Atom("ceiling", "ceiling"),