зеркало из https://github.com/mozilla/gecko-dev.git
Merge autoland to mozilla-central. a=merge
This commit is contained in:
Коммит
4ef183d00f
|
@ -85,12 +85,12 @@ rev = "3484d3e3ebdc8931493aa5df4d7ee9360a90e76b"
|
||||||
[source."https://github.com/gfx-rs/wgpu"]
|
[source."https://github.com/gfx-rs/wgpu"]
|
||||||
git = "https://github.com/gfx-rs/wgpu"
|
git = "https://github.com/gfx-rs/wgpu"
|
||||||
replace-with = "vendored-sources"
|
replace-with = "vendored-sources"
|
||||||
rev = "0b61a191"
|
rev = "b51fd851"
|
||||||
|
|
||||||
[source."https://github.com/gfx-rs/naga"]
|
[source."https://github.com/gfx-rs/naga"]
|
||||||
git = "https://github.com/gfx-rs/naga"
|
git = "https://github.com/gfx-rs/naga"
|
||||||
replace-with = "vendored-sources"
|
replace-with = "vendored-sources"
|
||||||
rev = "85056524"
|
rev = "1aa91549"
|
||||||
|
|
||||||
[source."https://github.com/gfx-rs/metal-rs"]
|
[source."https://github.com/gfx-rs/metal-rs"]
|
||||||
git = "https://github.com/gfx-rs/metal-rs"
|
git = "https://github.com/gfx-rs/metal-rs"
|
||||||
|
|
|
@ -332,7 +332,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "autocfg"
|
name = "autocfg"
|
||||||
version = "1.0.1"
|
version = "1.1.0"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "baldrdash"
|
name = "baldrdash"
|
||||||
|
@ -3432,7 +3432,7 @@ checksum = "a2983372caf4480544083767bf2d27defafe32af49ab4df3a0b7fc90793a3664"
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "naga"
|
name = "naga"
|
||||||
version = "0.8.0"
|
version = "0.8.0"
|
||||||
source = "git+https://github.com/gfx-rs/naga?rev=85056524#850565243d1d0d03215f13246c94d63e1d4c51cd"
|
source = "git+https://github.com/gfx-rs/naga?rev=1aa91549#1aa9154964238af8c692cf521ff90e1f2395e147"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bit-set",
|
"bit-set",
|
||||||
"bitflags",
|
"bitflags",
|
||||||
|
@ -5876,7 +5876,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wgpu-core"
|
name = "wgpu-core"
|
||||||
version = "0.12.0"
|
version = "0.12.0"
|
||||||
source = "git+https://github.com/gfx-rs/wgpu?rev=0b61a191#0b61a191244da0f0d987d53614a6698097a7622f"
|
source = "git+https://github.com/gfx-rs/wgpu?rev=b51fd851#b51fd851be51cfe40c937ef789a44244e7dc2971"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arrayvec 0.7.2",
|
"arrayvec 0.7.2",
|
||||||
"bitflags",
|
"bitflags",
|
||||||
|
@ -5899,7 +5899,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wgpu-hal"
|
name = "wgpu-hal"
|
||||||
version = "0.12.0"
|
version = "0.12.0"
|
||||||
source = "git+https://github.com/gfx-rs/wgpu?rev=0b61a191#0b61a191244da0f0d987d53614a6698097a7622f"
|
source = "git+https://github.com/gfx-rs/wgpu?rev=b51fd851#b51fd851be51cfe40c937ef789a44244e7dc2971"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arrayvec 0.7.2",
|
"arrayvec 0.7.2",
|
||||||
"ash",
|
"ash",
|
||||||
|
@ -5936,7 +5936,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wgpu-types"
|
name = "wgpu-types"
|
||||||
version = "0.12.0"
|
version = "0.12.0"
|
||||||
source = "git+https://github.com/gfx-rs/wgpu?rev=0b61a191#0b61a191244da0f0d987d53614a6698097a7622f"
|
source = "git+https://github.com/gfx-rs/wgpu?rev=b51fd851#b51fd851be51cfe40c937ef789a44244e7dc2971"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags",
|
"bitflags",
|
||||||
"bitflags_serde_shim",
|
"bitflags_serde_shim",
|
||||||
|
|
|
@ -276,7 +276,7 @@ var StarUI = {
|
||||||
{ capture: true, once: true }
|
{ capture: true, once: true }
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
gEditItemOverlay.initPanel({
|
await gEditItemOverlay.initPanel({
|
||||||
node: aNode,
|
node: aNode,
|
||||||
onPanelReady,
|
onPanelReady,
|
||||||
hiddenRows: ["location", "keyword"],
|
hiddenRows: ["location", "keyword"],
|
||||||
|
|
|
@ -67,10 +67,6 @@ var AboutModuleFactory = {
|
||||||
return new AboutModule().QueryInterface(aIID);
|
return new AboutModule().QueryInterface(aIID);
|
||||||
},
|
},
|
||||||
|
|
||||||
lockFactory(aLock) {
|
|
||||||
throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED);
|
|
||||||
},
|
|
||||||
|
|
||||||
QueryInterface: ChromeUtils.generateQI(["nsIFactory"]),
|
QueryInterface: ChromeUtils.generateQI(["nsIFactory"]),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1905,7 +1905,7 @@ this.DiscoveryStreamFeed = class DiscoveryStreamFeed {
|
||||||
*/
|
*/
|
||||||
getHardcodedLayout = ({
|
getHardcodedLayout = ({
|
||||||
items = 21,
|
items = 21,
|
||||||
spocPositions = [2, 4, 11, 20],
|
spocPositions = [1, 5, 7, 11, 18, 20],
|
||||||
sponsoredCollectionsEnabled = false,
|
sponsoredCollectionsEnabled = false,
|
||||||
compactLayout = false,
|
compactLayout = false,
|
||||||
hybridLayout = false,
|
hybridLayout = false,
|
||||||
|
|
|
@ -288,6 +288,12 @@ var gEditItemOverlay = {
|
||||||
onPanelReady,
|
onPanelReady,
|
||||||
} = this._setPaneInfo(aInfo);
|
} = this._setPaneInfo(aInfo);
|
||||||
|
|
||||||
|
// initPanel can be called multiple times in a row,
|
||||||
|
// and awaits Promises. If the reference to `instance`
|
||||||
|
// changes, it must mean another caller has called
|
||||||
|
// initPanel again, so bail out of the initialization.
|
||||||
|
let instance = (this._instance = {});
|
||||||
|
|
||||||
// If we're creating a new item on the toolbar, show it:
|
// If we're creating a new item on the toolbar, show it:
|
||||||
if (
|
if (
|
||||||
aInfo.isNewBookmark &&
|
aInfo.isNewBookmark &&
|
||||||
|
@ -326,6 +332,11 @@ var gEditItemOverlay = {
|
||||||
|
|
||||||
if (showOrCollapse("keywordRow", isBookmark, "keyword")) {
|
if (showOrCollapse("keywordRow", isBookmark, "keyword")) {
|
||||||
await this._initKeywordField().catch(Cu.reportError);
|
await this._initKeywordField().catch(Cu.reportError);
|
||||||
|
// paneInfo can be null if paneInfo is uninitialized while
|
||||||
|
// the process above is awaiting initialization
|
||||||
|
if (instance != this._instance || this._paneInfo == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
this._keywordField.readOnly = this.readOnly;
|
this._keywordField.readOnly = this.readOnly;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -342,6 +353,9 @@ var gEditItemOverlay = {
|
||||||
// this (it's only the Star UI that shows the folderPicker)
|
// this (it's only the Star UI that shows the folderPicker)
|
||||||
if (showOrCollapse("folderRow", isItem, "folderPicker")) {
|
if (showOrCollapse("folderRow", isItem, "folderPicker")) {
|
||||||
await this._initFolderMenuList(parentGuid).catch(Cu.reportError);
|
await this._initFolderMenuList(parentGuid).catch(Cu.reportError);
|
||||||
|
if (instance != this._instance || this._paneInfo == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Selection count.
|
// Selection count.
|
||||||
|
|
|
@ -241,7 +241,7 @@ var gEditItemOverlay = {
|
||||||
* List of rows to be hidden regardless of the item edited. Possible values:
|
* List of rows to be hidden regardless of the item edited. Possible values:
|
||||||
* "title", "location", "keyword", "folderPicker".
|
* "title", "location", "keyword", "folderPicker".
|
||||||
*/
|
*/
|
||||||
initPanel(aInfo) {
|
async initPanel(aInfo) {
|
||||||
if (typeof aInfo != "object" || aInfo === null) {
|
if (typeof aInfo != "object" || aInfo === null) {
|
||||||
throw new Error("aInfo must be an object.");
|
throw new Error("aInfo must be an object.");
|
||||||
}
|
}
|
||||||
|
@ -279,6 +279,12 @@ var gEditItemOverlay = {
|
||||||
onPanelReady,
|
onPanelReady,
|
||||||
} = this._setPaneInfo(aInfo);
|
} = this._setPaneInfo(aInfo);
|
||||||
|
|
||||||
|
// initPanel can be called multiple times in a row,
|
||||||
|
// and awaits Promises. If the reference to `instance`
|
||||||
|
// changes, it must mean another caller has called
|
||||||
|
// initPanel again, so bail out of the initialization.
|
||||||
|
let instance = (this._instance = {});
|
||||||
|
|
||||||
// If we're creating a new item on the toolbar, show it:
|
// If we're creating a new item on the toolbar, show it:
|
||||||
if (
|
if (
|
||||||
aInfo.isNewBookmark &&
|
aInfo.isNewBookmark &&
|
||||||
|
@ -316,7 +322,12 @@ var gEditItemOverlay = {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (showOrCollapse("keywordRow", isBookmark, "keyword")) {
|
if (showOrCollapse("keywordRow", isBookmark, "keyword")) {
|
||||||
this._initKeywordField().catch(Cu.reportError);
|
await this._initKeywordField().catch(Cu.reportError);
|
||||||
|
// paneInfo can be null if paneInfo is uninitialized while
|
||||||
|
// the process above is awaiting initialization
|
||||||
|
if (instance != this._instance || this._paneInfo == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
this._keywordField.readOnly = this.readOnly;
|
this._keywordField.readOnly = this.readOnly;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -332,7 +343,10 @@ var gEditItemOverlay = {
|
||||||
// not cheap (we don't always have the parent), and there's no use case for
|
// not cheap (we don't always have the parent), and there's no use case for
|
||||||
// this (it's only the Star UI that shows the folderPicker)
|
// this (it's only the Star UI that shows the folderPicker)
|
||||||
if (showOrCollapse("folderRow", isItem, "folderPicker")) {
|
if (showOrCollapse("folderRow", isItem, "folderPicker")) {
|
||||||
this._initFolderMenuList(parentGuid).catch(Cu.reportError);
|
await this._initFolderMenuList(parentGuid).catch(Cu.reportError);
|
||||||
|
if (instance != this._instance || this._paneInfo == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Selection count.
|
// Selection count.
|
||||||
|
|
|
@ -777,17 +777,21 @@ var PlacesOrganizer = {
|
||||||
gEditItemOverlay.uninitPanel(false);
|
gEditItemOverlay.uninitPanel(false);
|
||||||
|
|
||||||
if (selectedNode && !PlacesUtils.nodeIsSeparator(selectedNode)) {
|
if (selectedNode && !PlacesUtils.nodeIsSeparator(selectedNode)) {
|
||||||
gEditItemOverlay.initPanel({
|
gEditItemOverlay
|
||||||
node: selectedNode,
|
.initPanel({
|
||||||
hiddenRows: ["folderPicker"],
|
node: selectedNode,
|
||||||
});
|
hiddenRows: ["folderPicker"],
|
||||||
|
})
|
||||||
|
.catch(ex => Cu.reportError(ex));
|
||||||
} else if (!selectedNode && aNodeList[0]) {
|
} else if (!selectedNode && aNodeList[0]) {
|
||||||
if (aNodeList.every(PlacesUtils.nodeIsURI)) {
|
if (aNodeList.every(PlacesUtils.nodeIsURI)) {
|
||||||
let uris = aNodeList.map(node => Services.io.newURI(node.uri));
|
let uris = aNodeList.map(node => Services.io.newURI(node.uri));
|
||||||
gEditItemOverlay.initPanel({
|
gEditItemOverlay
|
||||||
uris,
|
.initPanel({
|
||||||
hiddenRows: ["folderPicker", "location", "keyword", "name"],
|
uris,
|
||||||
});
|
hiddenRows: ["folderPicker", "location", "keyword", "name"],
|
||||||
|
})
|
||||||
|
.catch(ex => Cu.reportError(ex));
|
||||||
} else {
|
} else {
|
||||||
let selectItemDesc = document.getElementById("selectItemDescription");
|
let selectItemDesc = document.getElementById("selectItemDescription");
|
||||||
let itemsCountLabel = document.getElementById("itemsCountText");
|
let itemsCountLabel = document.getElementById("itemsCountText");
|
||||||
|
|
|
@ -54,7 +54,7 @@ add_task(async function() {
|
||||||
|
|
||||||
// Init panel.
|
// Init panel.
|
||||||
let node = await PlacesUIUtils.promiseNodeLikeFromFetchInfo(bm);
|
let node = await PlacesUIUtils.promiseNodeLikeFromFetchInfo(bm);
|
||||||
gEditItemOverlay.initPanel({ node });
|
await gEditItemOverlay.initPanel({ node });
|
||||||
|
|
||||||
// Add a tag.
|
// Add a tag.
|
||||||
await promiseTagSelectorUpdated(() =>
|
await promiseTagSelectorUpdated(() =>
|
||||||
|
@ -99,7 +99,7 @@ add_task(async function() {
|
||||||
});
|
});
|
||||||
|
|
||||||
// Init panel with multiple uris.
|
// Init panel with multiple uris.
|
||||||
gEditItemOverlay.initPanel({ uris: [TEST_URI, TEST_URI2] });
|
await gEditItemOverlay.initPanel({ uris: [TEST_URI, TEST_URI2] });
|
||||||
|
|
||||||
// Add a tag to the first uri.
|
// Add a tag to the first uri.
|
||||||
await promiseTagSelectorUpdated(() =>
|
await promiseTagSelectorUpdated(() =>
|
||||||
|
@ -166,7 +166,7 @@ add_task(async function() {
|
||||||
await checkTagsSelector([], []);
|
await checkTagsSelector([], []);
|
||||||
|
|
||||||
// Init panel with a nsIURI entry.
|
// Init panel with a nsIURI entry.
|
||||||
gEditItemOverlay.initPanel({ uris: [TEST_URI] });
|
await gEditItemOverlay.initPanel({ uris: [TEST_URI] });
|
||||||
|
|
||||||
// Add a tag.
|
// Add a tag.
|
||||||
await promiseTagSelectorUpdated(() =>
|
await promiseTagSelectorUpdated(() =>
|
||||||
|
@ -201,7 +201,7 @@ add_task(async function() {
|
||||||
await checkTagsSelector([], []);
|
await checkTagsSelector([], []);
|
||||||
|
|
||||||
// Init panel with multiple nsIURI entries.
|
// Init panel with multiple nsIURI entries.
|
||||||
gEditItemOverlay.initPanel({ uris: [TEST_URI, TEST_URI2] });
|
await gEditItemOverlay.initPanel({ uris: [TEST_URI, TEST_URI2] });
|
||||||
|
|
||||||
// Add a tag to the first entry.
|
// Add a tag to the first entry.
|
||||||
await promiseTagSelectorUpdated(() =>
|
await promiseTagSelectorUpdated(() =>
|
||||||
|
|
|
@ -2438,6 +2438,13 @@ bool JsepSessionImpl::CheckNegotiationNeeded() const {
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t level = transceiver->GetLevel();
|
size_t level = transceiver->GetLevel();
|
||||||
|
if (NS_WARN_IF(mCurrentLocalDescription->GetMediaSectionCount() <= level) ||
|
||||||
|
NS_WARN_IF(mCurrentRemoteDescription->GetMediaSectionCount() <=
|
||||||
|
level)) {
|
||||||
|
MOZ_ASSERT(false);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
const SdpMediaSection& local =
|
const SdpMediaSection& local =
|
||||||
mCurrentLocalDescription->GetMediaSection(level);
|
mCurrentLocalDescription->GetMediaSection(level);
|
||||||
const SdpMediaSection& remote =
|
const SdpMediaSection& remote =
|
||||||
|
|
|
@ -16,9 +16,6 @@ var provider = {
|
||||||
}
|
}
|
||||||
return this.QueryInterface(iid);
|
return this.QueryInterface(iid);
|
||||||
},
|
},
|
||||||
lockFactory: function eventsink_lockf(lock) {
|
|
||||||
throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED);
|
|
||||||
},
|
|
||||||
startup() {},
|
startup() {},
|
||||||
watch() {},
|
watch() {},
|
||||||
shutdown() {},
|
shutdown() {},
|
||||||
|
|
|
@ -16,9 +16,6 @@ var provider = {
|
||||||
}
|
}
|
||||||
return this.QueryInterface(iid);
|
return this.QueryInterface(iid);
|
||||||
},
|
},
|
||||||
lockFactory: function eventsink_lockf(lock) {
|
|
||||||
throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED);
|
|
||||||
},
|
|
||||||
startup() {},
|
startup() {},
|
||||||
watch() {},
|
watch() {},
|
||||||
shutdown() {},
|
shutdown() {},
|
||||||
|
|
|
@ -72,6 +72,8 @@ class CommandEncoder final : public ObjectBase, public ChildOf<Device> {
|
||||||
nsTArray<WeakPtr<CanvasContext>> mTargetContexts;
|
nsTArray<WeakPtr<CanvasContext>> mTargetContexts;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
const auto& GetDevice() const { return mParent; };
|
||||||
|
|
||||||
void EndComputePass(ffi::WGPUComputePass& aPass, ErrorResult& aRv);
|
void EndComputePass(ffi::WGPUComputePass& aPass, ErrorResult& aRv);
|
||||||
void EndRenderPass(ffi::WGPURenderPass& aPass, ErrorResult& aRv);
|
void EndRenderPass(ffi::WGPURenderPass& aPass, ErrorResult& aRv);
|
||||||
|
|
||||||
|
|
|
@ -63,15 +63,15 @@ void ComputePassEncoder::SetPipeline(const ComputePipeline& aPipeline) {
|
||||||
void ComputePassEncoder::DispatchWorkgroups(uint32_t x, uint32_t y,
|
void ComputePassEncoder::DispatchWorkgroups(uint32_t x, uint32_t y,
|
||||||
uint32_t z) {
|
uint32_t z) {
|
||||||
if (mValid) {
|
if (mValid) {
|
||||||
ffi::wgpu_compute_pass_dispatch(mPass, x, y, z);
|
ffi::wgpu_compute_pass_dispatch_workgroups(mPass, x, y, z);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ComputePassEncoder::DispatchWorkgroupsIndirect(
|
void ComputePassEncoder::DispatchWorkgroupsIndirect(
|
||||||
const Buffer& aIndirectBuffer, uint64_t aIndirectOffset) {
|
const Buffer& aIndirectBuffer, uint64_t aIndirectOffset) {
|
||||||
if (mValid) {
|
if (mValid) {
|
||||||
ffi::wgpu_compute_pass_dispatch_indirect(mPass, aIndirectBuffer.mId,
|
ffi::wgpu_compute_pass_dispatch_workgroups_indirect(
|
||||||
aIndirectOffset);
|
mPass, aIndirectBuffer.mId, aIndirectOffset);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -94,6 +94,12 @@ void Device::CleanupUnregisteredInParent() {
|
||||||
mValid = false;
|
mValid = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Device::GenerateError(const nsCString& aMessage) {
|
||||||
|
if (mBridge->CanSend()) {
|
||||||
|
mBridge->SendGenerateError(mId, aMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void Device::GetLabel(nsAString& aValue) const { aValue = mLabel; }
|
void Device::GetLabel(nsAString& aValue) const { aValue = mLabel; }
|
||||||
void Device::SetLabel(const nsAString& aLabel) { mLabel = aLabel; }
|
void Device::SetLabel(const nsAString& aLabel) { mLabel = aLabel; }
|
||||||
|
|
||||||
|
|
|
@ -111,6 +111,8 @@ class Device final : public DOMEventTargetHelper, public SupportsWeakPtr {
|
||||||
|
|
||||||
void CleanupUnregisteredInParent();
|
void CleanupUnregisteredInParent();
|
||||||
|
|
||||||
|
void GenerateError(const nsCString& aMessage);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
~Device();
|
~Device();
|
||||||
void Cleanup();
|
void Cleanup();
|
||||||
|
|
|
@ -52,7 +52,7 @@ ffi::WGPUColor ConvertColor(const dom::GPUColorDict& aColor) {
|
||||||
}
|
}
|
||||||
|
|
||||||
ffi::WGPURenderPass* BeginRenderPass(
|
ffi::WGPURenderPass* BeginRenderPass(
|
||||||
RawId aEncoderId, const dom::GPURenderPassDescriptor& aDesc) {
|
CommandEncoder* const aParent, const dom::GPURenderPassDescriptor& aDesc) {
|
||||||
ffi::WGPURenderPassDescriptor desc = {};
|
ffi::WGPURenderPassDescriptor desc = {};
|
||||||
|
|
||||||
ffi::WGPURenderPassDepthStencilAttachment dsDesc = {};
|
ffi::WGPURenderPassDepthStencilAttachment dsDesc = {};
|
||||||
|
@ -84,6 +84,12 @@ ffi::WGPURenderPass* BeginRenderPass(
|
||||||
desc.depth_stencil_attachment = &dsDesc;
|
desc.depth_stencil_attachment = &dsDesc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (aDesc.mColorAttachments.Length() > WGPUMAX_COLOR_TARGETS) {
|
||||||
|
aParent->GetDevice()->GenerateError(nsLiteralCString(
|
||||||
|
"Too many color attachments in GPURenderPassDescriptor"));
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
std::array<ffi::WGPURenderPassColorAttachment, WGPUMAX_COLOR_TARGETS>
|
std::array<ffi::WGPURenderPassColorAttachment, WGPUMAX_COLOR_TARGETS>
|
||||||
colorDescs = {};
|
colorDescs = {};
|
||||||
desc.color_attachments = colorDescs.data();
|
desc.color_attachments = colorDescs.data();
|
||||||
|
@ -124,12 +130,17 @@ ffi::WGPURenderPass* BeginRenderPass(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return ffi::wgpu_command_encoder_begin_render_pass(aEncoderId, &desc);
|
return ffi::wgpu_command_encoder_begin_render_pass(aParent->mId, &desc);
|
||||||
}
|
}
|
||||||
|
|
||||||
RenderPassEncoder::RenderPassEncoder(CommandEncoder* const aParent,
|
RenderPassEncoder::RenderPassEncoder(CommandEncoder* const aParent,
|
||||||
const dom::GPURenderPassDescriptor& aDesc)
|
const dom::GPURenderPassDescriptor& aDesc)
|
||||||
: ChildOf(aParent), mPass(BeginRenderPass(aParent->mId, aDesc)) {
|
: ChildOf(aParent), mPass(BeginRenderPass(aParent, aDesc)) {
|
||||||
|
if (!mPass) {
|
||||||
|
mValid = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
for (const auto& at : aDesc.mColorAttachments) {
|
for (const auto& at : aDesc.mColorAttachments) {
|
||||||
mUsedTextureViews.AppendElement(at.mView);
|
mUsedTextureViews.AppendElement(at.mView);
|
||||||
}
|
}
|
||||||
|
|
|
@ -71,6 +71,7 @@ parent:
|
||||||
async DevicePushErrorScope(RawId selfId);
|
async DevicePushErrorScope(RawId selfId);
|
||||||
async DevicePopErrorScope(RawId selfId) returns (MaybeScopedError maybeError);
|
async DevicePopErrorScope(RawId selfId) returns (MaybeScopedError maybeError);
|
||||||
|
|
||||||
|
async GenerateError(RawId deviceId, nsCString message);
|
||||||
child:
|
child:
|
||||||
async DeviceUncapturedError(RawId aDeviceId, nsCString message);
|
async DeviceUncapturedError(RawId aDeviceId, nsCString message);
|
||||||
async DropAction(ByteBuf buf);
|
async DropAction(ByteBuf buf);
|
||||||
|
|
|
@ -239,21 +239,25 @@ bool WebGPUParent::ForwardError(RawId aDeviceId, ErrorBuffer& aError) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ReportError(aDeviceId, cString.value());
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebGPUParent::ReportError(RawId aDeviceId, const nsCString& aMessage) {
|
||||||
// find the appropriate error scope
|
// find the appropriate error scope
|
||||||
const auto& lookup = mErrorScopeMap.find(aDeviceId);
|
const auto& lookup = mErrorScopeMap.find(aDeviceId);
|
||||||
if (lookup != mErrorScopeMap.end() && !lookup->second.mStack.IsEmpty()) {
|
if (lookup != mErrorScopeMap.end() && !lookup->second.mStack.IsEmpty()) {
|
||||||
auto& last = lookup->second.mStack.LastElement();
|
auto& last = lookup->second.mStack.LastElement();
|
||||||
if (last.isNothing()) {
|
if (last.isNothing()) {
|
||||||
last.emplace(ScopedError{false, cString.value()});
|
last.emplace(ScopedError{false, aMessage});
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// fall back to the uncaptured error handler
|
// fall back to the uncaptured error handler
|
||||||
if (!SendDeviceUncapturedError(aDeviceId, cString.value())) {
|
if (!SendDeviceUncapturedError(aDeviceId, aMessage)) {
|
||||||
NS_ERROR("Unable to SendError");
|
NS_ERROR("Unable to SendError");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ipc::IPCResult WebGPUParent::RecvInstanceRequestAdapter(
|
ipc::IPCResult WebGPUParent::RecvInstanceRequestAdapter(
|
||||||
|
@ -927,4 +931,10 @@ ipc::IPCResult WebGPUParent::RecvDevicePopErrorScope(
|
||||||
return IPC_OK();
|
return IPC_OK();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ipc::IPCResult WebGPUParent::RecvGenerateError(RawId aDeviceId,
|
||||||
|
const nsCString& aMessage) {
|
||||||
|
ReportError(aDeviceId, aMessage);
|
||||||
|
return IPC_OK();
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace mozilla::webgpu
|
} // namespace mozilla::webgpu
|
||||||
|
|
|
@ -88,6 +88,7 @@ class WebGPUParent final : public PWebGPUParent {
|
||||||
ipc::IPCResult RecvDevicePushErrorScope(RawId aSelfId);
|
ipc::IPCResult RecvDevicePushErrorScope(RawId aSelfId);
|
||||||
ipc::IPCResult RecvDevicePopErrorScope(
|
ipc::IPCResult RecvDevicePopErrorScope(
|
||||||
RawId aSelfId, DevicePopErrorScopeResolver&& aResolver);
|
RawId aSelfId, DevicePopErrorScopeResolver&& aResolver);
|
||||||
|
ipc::IPCResult RecvGenerateError(RawId aDeviceId, const nsCString& message);
|
||||||
|
|
||||||
ipc::IPCResult GetFrontBufferSnapshot(IProtocol* aProtocol,
|
ipc::IPCResult GetFrontBufferSnapshot(IProtocol* aProtocol,
|
||||||
const CompositableHandle& aHandle,
|
const CompositableHandle& aHandle,
|
||||||
|
@ -100,6 +101,7 @@ class WebGPUParent final : public PWebGPUParent {
|
||||||
virtual ~WebGPUParent();
|
virtual ~WebGPUParent();
|
||||||
void MaintainDevices();
|
void MaintainDevices();
|
||||||
bool ForwardError(RawId aDeviceID, ErrorBuffer& aError);
|
bool ForwardError(RawId aDeviceID, ErrorBuffer& aError);
|
||||||
|
void ReportError(RawId aDeviceId, const nsCString& message);
|
||||||
|
|
||||||
UniquePtr<ffi::WGPUGlobal> mContext;
|
UniquePtr<ffi::WGPUGlobal> mContext;
|
||||||
base::RepeatingTimer<WebGPUParent> mTimer;
|
base::RepeatingTimer<WebGPUParent> mTimer;
|
||||||
|
|
|
@ -197,8 +197,7 @@ HTMLEditor::HTMLEditor()
|
||||||
mDefaultParagraphSeparator(
|
mDefaultParagraphSeparator(
|
||||||
StaticPrefs::editor_use_div_for_default_newlines()
|
StaticPrefs::editor_use_div_for_default_newlines()
|
||||||
? ParagraphSeparator::div
|
? ParagraphSeparator::div
|
||||||
: ParagraphSeparator::br) {
|
: ParagraphSeparator::br) {}
|
||||||
}
|
|
||||||
|
|
||||||
HTMLEditor::~HTMLEditor() {
|
HTMLEditor::~HTMLEditor() {
|
||||||
// Collect the data of `beforeinput` event only when it's enabled because
|
// Collect the data of `beforeinput` event only when it's enabled because
|
||||||
|
|
|
@ -12,9 +12,6 @@ var factory = {
|
||||||
createInstance() {
|
createInstance() {
|
||||||
throw new Error("There is no history service");
|
throw new Error("There is no history service");
|
||||||
},
|
},
|
||||||
lockFactory() {
|
|
||||||
throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED);
|
|
||||||
},
|
|
||||||
QueryInterface: ChromeUtils.generateQI(["nsIFactory"]),
|
QueryInterface: ChromeUtils.generateQI(["nsIFactory"]),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,7 @@ default = []
|
||||||
[dependencies.wgc]
|
[dependencies.wgc]
|
||||||
package = "wgpu-core"
|
package = "wgpu-core"
|
||||||
git = "https://github.com/gfx-rs/wgpu"
|
git = "https://github.com/gfx-rs/wgpu"
|
||||||
rev = "0b61a191"
|
rev = "b51fd851"
|
||||||
#Note: "replay" shouldn't ideally be needed,
|
#Note: "replay" shouldn't ideally be needed,
|
||||||
# but it allows us to serialize everything across IPC.
|
# but it allows us to serialize everything across IPC.
|
||||||
features = ["replay", "trace", "serial-pass"]
|
features = ["replay", "trace", "serial-pass"]
|
||||||
|
@ -25,12 +25,12 @@ features = ["replay", "trace", "serial-pass"]
|
||||||
[dependencies.wgt]
|
[dependencies.wgt]
|
||||||
package = "wgpu-types"
|
package = "wgpu-types"
|
||||||
git = "https://github.com/gfx-rs/wgpu"
|
git = "https://github.com/gfx-rs/wgpu"
|
||||||
rev = "0b61a191"
|
rev = "b51fd851"
|
||||||
|
|
||||||
[dependencies.wgh]
|
[dependencies.wgh]
|
||||||
package = "wgpu-hal"
|
package = "wgpu-hal"
|
||||||
git = "https://github.com/gfx-rs/wgpu"
|
git = "https://github.com/gfx-rs/wgpu"
|
||||||
rev = "0b61a191"
|
rev = "b51fd851"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
bincode = "1"
|
bincode = "1"
|
||||||
|
|
|
@ -1092,28 +1092,6 @@ pub unsafe extern "C" fn wgpu_command_encoder_insert_debug_marker(
|
||||||
*bb = make_byte_buf(&action);
|
*bb = make_byte_buf(&action);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
|
||||||
pub unsafe extern "C" fn wgpu_render_pass_set_index_buffer(
|
|
||||||
pass: &mut wgc::command::RenderPass,
|
|
||||||
buffer: wgc::id::BufferId,
|
|
||||||
index_format: wgt::IndexFormat,
|
|
||||||
offset: wgt::BufferAddress,
|
|
||||||
size: Option<wgt::BufferSize>,
|
|
||||||
) {
|
|
||||||
pass.set_index_buffer(buffer, index_format, offset, size);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[no_mangle]
|
|
||||||
pub unsafe extern "C" fn wgpu_render_bundle_set_index_buffer(
|
|
||||||
encoder: &mut wgc::command::RenderBundleEncoder,
|
|
||||||
buffer: wgc::id::BufferId,
|
|
||||||
index_format: wgt::IndexFormat,
|
|
||||||
offset: wgt::BufferAddress,
|
|
||||||
size: Option<wgt::BufferSize>,
|
|
||||||
) {
|
|
||||||
encoder.set_index_buffer(buffer, index_format, offset, size);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub unsafe extern "C" fn wgpu_queue_write_buffer(
|
pub unsafe extern "C" fn wgpu_queue_write_buffer(
|
||||||
dst: id::BufferId,
|
dst: id::BufferId,
|
||||||
|
|
|
@ -3827,6 +3827,10 @@ pub struct CommandBufferBuilder {
|
||||||
|
|
||||||
/// List of render tasks that depend on the task that will be created for this builder
|
/// List of render tasks that depend on the task that will be created for this builder
|
||||||
pub extra_dependencies: Vec<RenderTaskId>,
|
pub extra_dependencies: Vec<RenderTaskId>,
|
||||||
|
|
||||||
|
/// If true, this represents a surface that wraps a sub-graph, and we need to look
|
||||||
|
/// higher in the surface hierarchy for the resolve surface.
|
||||||
|
pub wraps_sub_graph: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CommandBufferBuilder {
|
impl CommandBufferBuilder {
|
||||||
|
@ -3836,6 +3840,7 @@ impl CommandBufferBuilder {
|
||||||
establishes_sub_graph: false,
|
establishes_sub_graph: false,
|
||||||
resolve_source: None,
|
resolve_source: None,
|
||||||
extra_dependencies: Vec::new(),
|
extra_dependencies: Vec::new(),
|
||||||
|
wraps_sub_graph: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3850,6 +3855,7 @@ impl CommandBufferBuilder {
|
||||||
establishes_sub_graph: false,
|
establishes_sub_graph: false,
|
||||||
resolve_source: None,
|
resolve_source: None,
|
||||||
extra_dependencies: Vec::new(),
|
extra_dependencies: Vec::new(),
|
||||||
|
wraps_sub_graph: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3858,6 +3864,7 @@ impl CommandBufferBuilder {
|
||||||
render_task_id: RenderTaskId,
|
render_task_id: RenderTaskId,
|
||||||
establishes_sub_graph: bool,
|
establishes_sub_graph: bool,
|
||||||
root_task_id: Option<RenderTaskId>,
|
root_task_id: Option<RenderTaskId>,
|
||||||
|
wraps_sub_graph: bool,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
CommandBufferBuilder {
|
CommandBufferBuilder {
|
||||||
kind: CommandBufferBuilderKind::Simple {
|
kind: CommandBufferBuilderKind::Simple {
|
||||||
|
@ -3867,6 +3874,7 @@ impl CommandBufferBuilder {
|
||||||
establishes_sub_graph,
|
establishes_sub_graph,
|
||||||
resolve_source: None,
|
resolve_source: None,
|
||||||
extra_dependencies: Vec::new(),
|
extra_dependencies: Vec::new(),
|
||||||
|
wraps_sub_graph,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -457,42 +457,30 @@ struct TilePreUpdateContext {
|
||||||
frame_id: FrameId,
|
frame_id: FrameId,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Immutable context passed to picture cache tiles during post_update
|
// Immutable context passed to picture cache tiles during update_dirty_and_valid_rects
|
||||||
struct TilePostUpdateContext<'a> {
|
struct TileUpdateDirtyContext<'a> {
|
||||||
/// Maps from picture cache coords -> world space coords.
|
/// Maps from picture cache coords -> world space coords.
|
||||||
pic_to_world_mapper: SpaceMapper<PicturePixel, WorldPixel>,
|
pic_to_world_mapper: SpaceMapper<PicturePixel, WorldPixel>,
|
||||||
|
|
||||||
/// Global scale factor from world -> device pixels.
|
/// Global scale factor from world -> device pixels.
|
||||||
global_device_pixel_scale: DevicePixelScale,
|
global_device_pixel_scale: DevicePixelScale,
|
||||||
|
|
||||||
/// The local clip rect (in picture space) of the entire picture cache
|
|
||||||
local_clip_rect: PictureRect,
|
|
||||||
|
|
||||||
/// The calculated backdrop information for this cache instance.
|
|
||||||
backdrop: Option<BackdropInfo>,
|
|
||||||
|
|
||||||
/// Information about opacity bindings from the picture cache.
|
/// Information about opacity bindings from the picture cache.
|
||||||
opacity_bindings: &'a FastHashMap<PropertyBindingId, OpacityBindingInfo>,
|
opacity_bindings: &'a FastHashMap<PropertyBindingId, OpacityBindingInfo>,
|
||||||
|
|
||||||
/// Information about color bindings from the picture cache.
|
/// Information about color bindings from the picture cache.
|
||||||
color_bindings: &'a FastHashMap<PropertyBindingId, ColorBindingInfo>,
|
color_bindings: &'a FastHashMap<PropertyBindingId, ColorBindingInfo>,
|
||||||
|
|
||||||
/// Current size in device pixels of tiles for this cache
|
|
||||||
current_tile_size: DeviceIntSize,
|
|
||||||
|
|
||||||
/// The local rect of the overall picture cache
|
/// The local rect of the overall picture cache
|
||||||
local_rect: PictureRect,
|
local_rect: PictureRect,
|
||||||
|
|
||||||
/// Pre-allocated z-id to assign to tiles during post_update.
|
|
||||||
z_id: ZBufferId,
|
|
||||||
|
|
||||||
/// If true, the scale factor of the root transform for this picture
|
/// If true, the scale factor of the root transform for this picture
|
||||||
/// cache changed, so we need to invalidate the tile and re-render.
|
/// cache changed, so we need to invalidate the tile and re-render.
|
||||||
invalidate_all: bool,
|
invalidate_all: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mutable state passed to picture cache tiles during post_update
|
// Mutable state passed to picture cache tiles during update_dirty_and_valid_rects
|
||||||
struct TilePostUpdateState<'a> {
|
struct TileUpdateDirtyState<'a> {
|
||||||
/// Allow access to the texture cache for requesting tiles
|
/// Allow access to the texture cache for requesting tiles
|
||||||
resource_cache: &'a mut ResourceCache,
|
resource_cache: &'a mut ResourceCache,
|
||||||
|
|
||||||
|
@ -506,6 +494,30 @@ struct TilePostUpdateState<'a> {
|
||||||
spatial_node_comparer: &'a mut SpatialNodeComparer,
|
spatial_node_comparer: &'a mut SpatialNodeComparer,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Immutable context passed to picture cache tiles during post_update
|
||||||
|
struct TilePostUpdateContext {
|
||||||
|
/// The local clip rect (in picture space) of the entire picture cache
|
||||||
|
local_clip_rect: PictureRect,
|
||||||
|
|
||||||
|
/// The calculated backdrop information for this cache instance.
|
||||||
|
backdrop: Option<BackdropInfo>,
|
||||||
|
|
||||||
|
/// Current size in device pixels of tiles for this cache
|
||||||
|
current_tile_size: DeviceIntSize,
|
||||||
|
|
||||||
|
/// Pre-allocated z-id to assign to tiles during post_update.
|
||||||
|
z_id: ZBufferId,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mutable state passed to picture cache tiles during post_update
|
||||||
|
struct TilePostUpdateState<'a> {
|
||||||
|
/// Allow access to the texture cache for requesting tiles
|
||||||
|
resource_cache: &'a mut ResourceCache,
|
||||||
|
|
||||||
|
/// Current configuration and setup for compositing all the picture cache tiles in renderer.
|
||||||
|
composite_state: &'a mut CompositeState,
|
||||||
|
}
|
||||||
|
|
||||||
/// Information about the dependencies of a single primitive instance.
|
/// Information about the dependencies of a single primitive instance.
|
||||||
struct PrimitiveDependencyInfo {
|
struct PrimitiveDependencyInfo {
|
||||||
/// Unique content identifier of the primitive.
|
/// Unique content identifier of the primitive.
|
||||||
|
@ -723,6 +735,8 @@ pub enum InvalidationReason {
|
||||||
ValidRectChanged,
|
ValidRectChanged,
|
||||||
// The overall scale of the picture cache changed
|
// The overall scale of the picture cache changed
|
||||||
ScaleChanged,
|
ScaleChanged,
|
||||||
|
// The content of the sampling surface changed
|
||||||
|
SurfaceContentChanged,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Information about a cached tile.
|
/// Information about a cached tile.
|
||||||
|
@ -823,8 +837,8 @@ impl Tile {
|
||||||
/// Check if the content of the previous and current tile descriptors match
|
/// Check if the content of the previous and current tile descriptors match
|
||||||
fn update_dirty_rects(
|
fn update_dirty_rects(
|
||||||
&mut self,
|
&mut self,
|
||||||
ctx: &TilePostUpdateContext,
|
ctx: &TileUpdateDirtyContext,
|
||||||
state: &mut TilePostUpdateState,
|
state: &mut TileUpdateDirtyState,
|
||||||
invalidation_reason: &mut Option<InvalidationReason>,
|
invalidation_reason: &mut Option<InvalidationReason>,
|
||||||
frame_context: &FrameVisibilityContext,
|
frame_context: &FrameVisibilityContext,
|
||||||
) -> PictureRect {
|
) -> PictureRect {
|
||||||
|
@ -857,8 +871,8 @@ impl Tile {
|
||||||
/// later by changing how ComparableVec is used.
|
/// later by changing how ComparableVec is used.
|
||||||
fn update_content_validity(
|
fn update_content_validity(
|
||||||
&mut self,
|
&mut self,
|
||||||
ctx: &TilePostUpdateContext,
|
ctx: &TileUpdateDirtyContext,
|
||||||
state: &mut TilePostUpdateState,
|
state: &mut TileUpdateDirtyState,
|
||||||
frame_context: &FrameVisibilityContext,
|
frame_context: &FrameVisibilityContext,
|
||||||
) {
|
) {
|
||||||
// Check if the contents of the primitives, clips, and
|
// Check if the contents of the primitives, clips, and
|
||||||
|
@ -1061,12 +1075,12 @@ impl Tile {
|
||||||
|
|
||||||
/// Called during tile cache instance post_update. Allows invalidation and dirty
|
/// Called during tile cache instance post_update. Allows invalidation and dirty
|
||||||
/// rect calculation after primitive dependencies have been updated.
|
/// rect calculation after primitive dependencies have been updated.
|
||||||
fn post_update(
|
fn update_dirty_and_valid_rects(
|
||||||
&mut self,
|
&mut self,
|
||||||
ctx: &TilePostUpdateContext,
|
ctx: &TileUpdateDirtyContext,
|
||||||
state: &mut TilePostUpdateState,
|
state: &mut TileUpdateDirtyState,
|
||||||
frame_context: &FrameVisibilityContext,
|
frame_context: &FrameVisibilityContext,
|
||||||
) -> bool {
|
) {
|
||||||
// Register the frame id of this tile with the spatial node comparer, to ensure
|
// Register the frame id of this tile with the spatial node comparer, to ensure
|
||||||
// that it doesn't GC any spatial nodes from the comparer that are referenced
|
// that it doesn't GC any spatial nodes from the comparer that are referenced
|
||||||
// by this tile. Must be done before we early exit below, so that we retain
|
// by this tile. Must be done before we early exit below, so that we retain
|
||||||
|
@ -1077,7 +1091,7 @@ impl Tile {
|
||||||
// so don't want to invalidate, merge, split etc. The tile won't need to be drawn
|
// so don't want to invalidate, merge, split etc. The tile won't need to be drawn
|
||||||
// (and thus updated / invalidated) until it is on screen again.
|
// (and thus updated / invalidated) until it is on screen again.
|
||||||
if !self.is_visible {
|
if !self.is_visible {
|
||||||
return false;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate the overall valid rect for this tile.
|
// Calculate the overall valid rect for this tile.
|
||||||
|
@ -1116,6 +1130,22 @@ impl Tile {
|
||||||
|
|
||||||
// Invalidate the tile based on the content changing.
|
// Invalidate the tile based on the content changing.
|
||||||
self.update_content_validity(ctx, state, frame_context);
|
self.update_content_validity(ctx, state, frame_context);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Called during tile cache instance post_update. Allows invalidation and dirty
|
||||||
|
/// rect calculation after primitive dependencies have been updated.
|
||||||
|
fn post_update(
|
||||||
|
&mut self,
|
||||||
|
ctx: &TilePostUpdateContext,
|
||||||
|
state: &mut TilePostUpdateState,
|
||||||
|
frame_context: &FrameVisibilityContext,
|
||||||
|
) {
|
||||||
|
// If tile is not visible, just early out from here - we don't update dependencies
|
||||||
|
// so don't want to invalidate, merge, split etc. The tile won't need to be drawn
|
||||||
|
// (and thus updated / invalidated) until it is on screen again.
|
||||||
|
if !self.is_visible {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// If there are no primitives there is no need to draw or cache it.
|
// If there are no primitives there is no need to draw or cache it.
|
||||||
// Bug 1719232 - The final device valid rect does not always describe a non-empty
|
// Bug 1719232 - The final device valid rect does not always describe a non-empty
|
||||||
|
@ -1132,7 +1162,7 @@ impl Tile {
|
||||||
}
|
}
|
||||||
|
|
||||||
self.is_visible = false;
|
self.is_visible = false;
|
||||||
return false;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if this tile can be considered opaque. Opacity state must be updated only
|
// Check if this tile can be considered opaque. Opacity state must be updated only
|
||||||
|
@ -1278,8 +1308,6 @@ impl Tile {
|
||||||
|
|
||||||
// Store the current surface backing info for use during batching.
|
// Store the current surface backing info for use during batching.
|
||||||
self.surface = Some(surface);
|
self.surface = Some(surface);
|
||||||
|
|
||||||
true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1892,6 +1920,9 @@ pub struct TileCacheInstance {
|
||||||
current_raster_scale: f32,
|
current_raster_scale: f32,
|
||||||
/// Depth of off-screen surfaces that are currently pushed during dependency updates
|
/// Depth of off-screen surfaces that are currently pushed during dependency updates
|
||||||
current_surface_traversal_depth: usize,
|
current_surface_traversal_depth: usize,
|
||||||
|
/// A list of extra dirty invalidation tests that can only be checked once we
|
||||||
|
/// know the dirty rect of all tiles
|
||||||
|
deferred_dirty_tests: Vec<DeferredDirtyTest>,
|
||||||
}
|
}
|
||||||
|
|
||||||
enum SurfacePromotionResult {
|
enum SurfacePromotionResult {
|
||||||
|
@ -1950,6 +1981,7 @@ impl TileCacheInstance {
|
||||||
invalidate_all_tiles: true,
|
invalidate_all_tiles: true,
|
||||||
current_raster_scale: 1.0,
|
current_raster_scale: 1.0,
|
||||||
current_surface_traversal_depth: 0,
|
current_surface_traversal_depth: 0,
|
||||||
|
deferred_dirty_tests: Vec::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2068,6 +2100,7 @@ impl TileCacheInstance {
|
||||||
self.surface_index = surface_index;
|
self.surface_index = surface_index;
|
||||||
self.local_rect = pic_rect;
|
self.local_rect = pic_rect;
|
||||||
self.local_clip_rect = PictureRect::max_rect();
|
self.local_clip_rect = PictureRect::max_rect();
|
||||||
|
self.deferred_dirty_tests.clear();
|
||||||
|
|
||||||
for sub_slice in &mut self.sub_slices {
|
for sub_slice in &mut self.sub_slices {
|
||||||
sub_slice.reset();
|
sub_slice.reset();
|
||||||
|
@ -3297,6 +3330,13 @@ impl TileCacheInstance {
|
||||||
tile.sub_graphs.push((pic_coverage_rect, surface_info.clone()));
|
tile.sub_graphs.push((pic_coverage_rect, surface_info.clone()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// For backdrop-filter, we need to check if any of the dirty rects
|
||||||
|
// in tiles that are affected by the filter primitive are dirty.
|
||||||
|
self.deferred_dirty_tests.push(DeferredDirtyTest {
|
||||||
|
tile_rect: TileRect::new(p0, p1),
|
||||||
|
prim_rect: pic_coverage_rect,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
PrimitiveInstanceKind::LineDecoration { .. } |
|
PrimitiveInstanceKind::LineDecoration { .. } |
|
||||||
PrimitiveInstanceKind::NormalBorder { .. } |
|
PrimitiveInstanceKind::NormalBorder { .. } |
|
||||||
|
@ -3549,20 +3589,16 @@ impl TileCacheInstance {
|
||||||
frame_context.spatial_tree,
|
frame_context.spatial_tree,
|
||||||
);
|
);
|
||||||
|
|
||||||
let mut ctx = TilePostUpdateContext {
|
let ctx = TileUpdateDirtyContext {
|
||||||
pic_to_world_mapper,
|
pic_to_world_mapper,
|
||||||
global_device_pixel_scale: frame_context.global_device_pixel_scale,
|
global_device_pixel_scale: frame_context.global_device_pixel_scale,
|
||||||
local_clip_rect: self.local_clip_rect,
|
|
||||||
backdrop: None,
|
|
||||||
opacity_bindings: &self.opacity_bindings,
|
opacity_bindings: &self.opacity_bindings,
|
||||||
color_bindings: &self.color_bindings,
|
color_bindings: &self.color_bindings,
|
||||||
current_tile_size: self.current_tile_size,
|
|
||||||
local_rect: self.local_rect,
|
local_rect: self.local_rect,
|
||||||
z_id: ZBufferId::invalid(),
|
|
||||||
invalidate_all: self.invalidate_all_tiles,
|
invalidate_all: self.invalidate_all_tiles,
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut state = TilePostUpdateState {
|
let mut state = TileUpdateDirtyState {
|
||||||
resource_cache: frame_state.resource_cache,
|
resource_cache: frame_state.resource_cache,
|
||||||
composite_state: frame_state.composite_state,
|
composite_state: frame_state.composite_state,
|
||||||
compare_cache: &mut self.compare_cache,
|
compare_cache: &mut self.compare_cache,
|
||||||
|
@ -3571,6 +3607,60 @@ impl TileCacheInstance {
|
||||||
|
|
||||||
// Step through each tile and invalidate if the dependencies have changed. Determine
|
// Step through each tile and invalidate if the dependencies have changed. Determine
|
||||||
// the current opacity setting and whether it's changed.
|
// the current opacity setting and whether it's changed.
|
||||||
|
for sub_slice in &mut self.sub_slices {
|
||||||
|
for tile in sub_slice.tiles.values_mut() {
|
||||||
|
tile.update_dirty_and_valid_rects(&ctx, &mut state, frame_context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process any deferred dirty checks
|
||||||
|
for sub_slice in &mut self.sub_slices {
|
||||||
|
for dirty_test in self.deferred_dirty_tests.drain(..) {
|
||||||
|
// Calculate the total dirty rect from all tiles that this primitive affects
|
||||||
|
let mut total_dirty_rect = PictureRect::zero();
|
||||||
|
|
||||||
|
for y in dirty_test.tile_rect.min.y .. dirty_test.tile_rect.max.y {
|
||||||
|
for x in dirty_test.tile_rect.min.x .. dirty_test.tile_rect.max.x {
|
||||||
|
let key = TileOffset::new(x, y);
|
||||||
|
let tile = sub_slice.tiles.get_mut(&key).expect("bug: no tile");
|
||||||
|
total_dirty_rect = total_dirty_rect.union(&tile.local_dirty_rect);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If that dirty rect intersects with the local rect of the primitive
|
||||||
|
// being checked, invalidate that region in all of the affected tiles.
|
||||||
|
// TODO(gw): This is somewhat conservative, we could be more clever
|
||||||
|
// here and avoid invalidating every tile when this changes.
|
||||||
|
// We could also store the dirty rect only when the prim
|
||||||
|
// is encountered, so that we don't invalidate if something
|
||||||
|
// *after* the query in the rendering order affects invalidation.
|
||||||
|
if total_dirty_rect.intersects(&dirty_test.prim_rect) {
|
||||||
|
for y in dirty_test.tile_rect.min.y .. dirty_test.tile_rect.max.y {
|
||||||
|
for x in dirty_test.tile_rect.min.x .. dirty_test.tile_rect.max.x {
|
||||||
|
let key = TileOffset::new(x, y);
|
||||||
|
let tile = sub_slice.tiles.get_mut(&key).expect("bug: no tile");
|
||||||
|
tile.invalidate(
|
||||||
|
Some(dirty_test.prim_rect),
|
||||||
|
InvalidationReason::SurfaceContentChanged,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut ctx = TilePostUpdateContext {
|
||||||
|
local_clip_rect: self.local_clip_rect,
|
||||||
|
backdrop: None,
|
||||||
|
current_tile_size: self.current_tile_size,
|
||||||
|
z_id: ZBufferId::invalid(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut state = TilePostUpdateState {
|
||||||
|
resource_cache: frame_state.resource_cache,
|
||||||
|
composite_state: frame_state.composite_state,
|
||||||
|
};
|
||||||
|
|
||||||
for (i, sub_slice) in self.sub_slices.iter_mut().enumerate().rev() {
|
for (i, sub_slice) in self.sub_slices.iter_mut().enumerate().rev() {
|
||||||
// The backdrop is only relevant for the first sub-slice
|
// The backdrop is only relevant for the first sub-slice
|
||||||
if i == 0 {
|
if i == 0 {
|
||||||
|
@ -4302,6 +4392,8 @@ bitflags! {
|
||||||
/// This picture establishes a sub-graph, which affects how SurfaceBuilder will
|
/// This picture establishes a sub-graph, which affects how SurfaceBuilder will
|
||||||
/// set up dependencies in the render task graph
|
/// set up dependencies in the render task graph
|
||||||
const IS_SUB_GRAPH = 1 << 1;
|
const IS_SUB_GRAPH = 1 << 1;
|
||||||
|
/// This picture wraps a sub-graph, but is not the resolve source itself
|
||||||
|
const WRAPS_SUB_GRAPH = 1 << 2;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5037,6 +5129,7 @@ impl PicturePrimitive {
|
||||||
frame_state.surface_builder.push_surface(
|
frame_state.surface_builder.push_surface(
|
||||||
surface_index,
|
surface_index,
|
||||||
false,
|
false,
|
||||||
|
false,
|
||||||
surface_local_dirty_rect,
|
surface_local_dirty_rect,
|
||||||
descriptor,
|
descriptor,
|
||||||
frame_state.surfaces,
|
frame_state.surfaces,
|
||||||
|
@ -5472,10 +5565,12 @@ impl PicturePrimitive {
|
||||||
}
|
}
|
||||||
|
|
||||||
let is_sub_graph = self.flags.contains(PictureFlags::IS_SUB_GRAPH);
|
let is_sub_graph = self.flags.contains(PictureFlags::IS_SUB_GRAPH);
|
||||||
|
let wraps_sub_graph = self.flags.contains(PictureFlags::WRAPS_SUB_GRAPH);
|
||||||
|
|
||||||
frame_state.surface_builder.push_surface(
|
frame_state.surface_builder.push_surface(
|
||||||
raster_config.surface_index,
|
raster_config.surface_index,
|
||||||
is_sub_graph,
|
is_sub_graph,
|
||||||
|
wraps_sub_graph,
|
||||||
surface_rects.clipped_local,
|
surface_rects.clipped_local,
|
||||||
surface_descriptor,
|
surface_descriptor,
|
||||||
frame_state.surfaces,
|
frame_state.surfaces,
|
||||||
|
@ -5834,7 +5929,8 @@ impl PicturePrimitive {
|
||||||
// context.
|
// context.
|
||||||
let allow_snapping =
|
let allow_snapping =
|
||||||
parent_allows_snapping &&
|
parent_allows_snapping &&
|
||||||
!self.flags.contains(PictureFlags::IS_SUB_GRAPH);
|
!self.flags.contains(PictureFlags::IS_SUB_GRAPH) &&
|
||||||
|
!self.flags.contains(PictureFlags::WRAPS_SUB_GRAPH);
|
||||||
|
|
||||||
// Check if there is perspective or if an SVG filter is applied, and thus whether a new
|
// Check if there is perspective or if an SVG filter is applied, and thus whether a new
|
||||||
// rasterization root should be established.
|
// rasterization root should be established.
|
||||||
|
@ -6157,6 +6253,16 @@ impl ImageDependency {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// In some cases, we need to know the dirty rect of all tiles in order
|
||||||
|
/// to correctly invalidate a primitive.
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct DeferredDirtyTest {
|
||||||
|
/// The tile rect that the primitive being checked affects
|
||||||
|
tile_rect: TileRect,
|
||||||
|
/// The picture-cache local rect of the primitive being checked
|
||||||
|
prim_rect: PictureRect,
|
||||||
|
}
|
||||||
|
|
||||||
/// A helper struct to compare a primitive and all its sub-dependencies.
|
/// A helper struct to compare a primitive and all its sub-dependencies.
|
||||||
struct PrimitiveComparer<'a> {
|
struct PrimitiveComparer<'a> {
|
||||||
clip_comparer: CompareHelper<'a, ItemUid>,
|
clip_comparer: CompareHelper<'a, ItemUid>,
|
||||||
|
|
|
@ -637,8 +637,8 @@ impl<'a> SceneBuilder<'a> {
|
||||||
|
|
||||||
// If we're a surface, use that spatial node, otherwise the parent
|
// If we're a surface, use that spatial node, otherwise the parent
|
||||||
let spatial_node_index = match pic.composite_mode {
|
let spatial_node_index = match pic.composite_mode {
|
||||||
Some(_) => pic.spatial_node_index,
|
Some(_) if !pic.flags.contains(PictureFlags::WRAPS_SUB_GRAPH) => pic.spatial_node_index,
|
||||||
None => parent_spatial_node_index.expect("bug: no parent"),
|
Some(_) | None => parent_spatial_node_index.expect("bug: no parent"),
|
||||||
};
|
};
|
||||||
|
|
||||||
(
|
(
|
||||||
|
@ -2271,6 +2271,13 @@ impl<'a> SceneBuilder<'a> {
|
||||||
None => true,
|
None => true,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let pic_flags = if stacking_context.flags.contains(StackingContextFlags::WRAPS_BACKDROP_FILTER) {
|
||||||
|
assert!(stacking_context.blit_reason.contains(BlitReason::CLIP));
|
||||||
|
PictureFlags::WRAPS_SUB_GRAPH
|
||||||
|
} else {
|
||||||
|
PictureFlags::empty()
|
||||||
|
};
|
||||||
|
|
||||||
let mut source = match stacking_context.context_3d {
|
let mut source = match stacking_context.context_3d {
|
||||||
// TODO(gw): For now, as soon as this picture is in
|
// TODO(gw): For now, as soon as this picture is in
|
||||||
// a 3D context, we draw it to an intermediate
|
// a 3D context, we draw it to an intermediate
|
||||||
|
@ -2295,7 +2302,7 @@ impl<'a> SceneBuilder<'a> {
|
||||||
stacking_context.prim_list,
|
stacking_context.prim_list,
|
||||||
stacking_context.spatial_node_index,
|
stacking_context.spatial_node_index,
|
||||||
stacking_context.raster_space,
|
stacking_context.raster_space,
|
||||||
PictureFlags::empty(),
|
pic_flags,
|
||||||
))
|
))
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -2339,7 +2346,7 @@ impl<'a> SceneBuilder<'a> {
|
||||||
stacking_context.prim_list,
|
stacking_context.prim_list,
|
||||||
stacking_context.spatial_node_index,
|
stacking_context.spatial_node_index,
|
||||||
stacking_context.raster_space,
|
stacking_context.raster_space,
|
||||||
PictureFlags::empty(),
|
pic_flags,
|
||||||
))
|
))
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -245,6 +245,7 @@ impl SurfaceBuilder {
|
||||||
&mut self,
|
&mut self,
|
||||||
surface_index: SurfaceIndex,
|
surface_index: SurfaceIndex,
|
||||||
is_sub_graph: bool,
|
is_sub_graph: bool,
|
||||||
|
wraps_sub_graph: bool,
|
||||||
clipping_rect: PictureRect,
|
clipping_rect: PictureRect,
|
||||||
descriptor: SurfaceDescriptor,
|
descriptor: SurfaceDescriptor,
|
||||||
surfaces: &mut [SurfaceInfo],
|
surfaces: &mut [SurfaceInfo],
|
||||||
|
@ -266,6 +267,7 @@ impl SurfaceBuilder {
|
||||||
render_task_id,
|
render_task_id,
|
||||||
is_sub_graph,
|
is_sub_graph,
|
||||||
None,
|
None,
|
||||||
|
wraps_sub_graph,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
SurfaceDescriptorKind::Chained { render_task_id, root_task_id } => {
|
SurfaceDescriptorKind::Chained { render_task_id, root_task_id } => {
|
||||||
|
@ -273,6 +275,7 @@ impl SurfaceBuilder {
|
||||||
render_task_id,
|
render_task_id,
|
||||||
is_sub_graph,
|
is_sub_graph,
|
||||||
Some(root_task_id),
|
Some(root_task_id),
|
||||||
|
wraps_sub_graph,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -407,7 +410,26 @@ impl SurfaceBuilder {
|
||||||
// (c) Make the old parent surface tasks input dependencies of the resolve target
|
// (c) Make the old parent surface tasks input dependencies of the resolve target
|
||||||
// (d) Make the sub-graph output an input dependency of the new task(s).
|
// (d) Make the sub-graph output an input dependency of the new task(s).
|
||||||
|
|
||||||
match self.builder_stack.last_mut().unwrap().kind {
|
// If this surface wraps a sub-graph, we need to look up in the hierarchy to find where
|
||||||
|
// the resource source actually is. This may happen in cases where a clip-mask applies to
|
||||||
|
// a backdrop-filter and child content in the associated stacking context.
|
||||||
|
let sub_graph_source_index = self.builder_stack
|
||||||
|
.iter()
|
||||||
|
.rposition(|builder| {
|
||||||
|
!builder.wraps_sub_graph
|
||||||
|
})
|
||||||
|
.expect("bug: no parent that is not a sub graph wrapper");
|
||||||
|
|
||||||
|
let sub_graph_parent = if sub_graph_source_index == self.builder_stack.len() - 1 {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
match self.builder_stack.last().unwrap().kind {
|
||||||
|
CommandBufferBuilderKind::Simple { render_task_id, .. } => Some(render_task_id),
|
||||||
|
_ => panic!("bug: should not occur on tiled surfaces"),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
match self.builder_stack[sub_graph_source_index].kind {
|
||||||
CommandBufferBuilderKind::Tiled { ref mut tiles } => {
|
CommandBufferBuilderKind::Tiled { ref mut tiles } => {
|
||||||
let keys: Vec<TileKey> = tiles.keys().cloned().collect();
|
let keys: Vec<TileKey> = tiles.keys().cloned().collect();
|
||||||
|
|
||||||
|
@ -454,7 +476,7 @@ impl SurfaceBuilder {
|
||||||
|
|
||||||
// Make the output of the sub-graph a dependency of the new replacement tile task
|
// Make the output of the sub-graph a dependency of the new replacement tile task
|
||||||
rg_builder.add_dependency(
|
rg_builder.add_dependency(
|
||||||
new_task_id,
|
sub_graph_parent.unwrap_or(new_task_id),
|
||||||
child_root_task_id.unwrap_or(child_render_task_id),
|
child_root_task_id.unwrap_or(child_render_task_id),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -518,7 +540,7 @@ impl SurfaceBuilder {
|
||||||
|
|
||||||
// Make the output of the sub-graph a dependency of the new replacement tile task
|
// Make the output of the sub-graph a dependency of the new replacement tile task
|
||||||
rg_builder.add_dependency(
|
rg_builder.add_dependency(
|
||||||
new_task_id,
|
sub_graph_parent.unwrap_or(new_task_id),
|
||||||
child_root_task_id.unwrap_or(child_render_task_id),
|
child_root_task_id.unwrap_or(child_render_task_id),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -947,6 +947,10 @@ bitflags! {
|
||||||
/// If true, this stacking context is a blend container than contains
|
/// If true, this stacking context is a blend container than contains
|
||||||
/// mix-blend-mode children (and should thus be isolated).
|
/// mix-blend-mode children (and should thus be isolated).
|
||||||
const IS_BLEND_CONTAINER = 1 << 0;
|
const IS_BLEND_CONTAINER = 1 << 0;
|
||||||
|
/// If true, this stacking context is a wrapper around a backdrop-filter (e.g. for
|
||||||
|
/// a clip-mask). This is needed to allow the correct selection of a backdrop root
|
||||||
|
/// since a clip-mask stacking context creates a parent surface.
|
||||||
|
const WRAPS_BACKDROP_FILTER = 1 << 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
---
|
||||||
|
root:
|
||||||
|
items:
|
||||||
|
- type: rect
|
||||||
|
bounds: 0 0 256 256
|
||||||
|
color: red
|
||||||
|
- type: rect
|
||||||
|
bounds: 64 64 128 128
|
||||||
|
color: cyan
|
|
@ -0,0 +1,25 @@
|
||||||
|
# Ensure that a backdrop-filter enclosed by a stacking context with
|
||||||
|
# a clip-mask can select the correct backdrop root
|
||||||
|
---
|
||||||
|
root:
|
||||||
|
items:
|
||||||
|
- type: clip
|
||||||
|
bounds: [64, 64, 128, 128]
|
||||||
|
id: 2
|
||||||
|
image-mask:
|
||||||
|
image: solid-color(255,255,255,255,128,128)
|
||||||
|
rect: [64, 64, 128, 128]
|
||||||
|
repeat: false
|
||||||
|
- type: stacking-context
|
||||||
|
backdrop-root: true
|
||||||
|
items:
|
||||||
|
- type: rect
|
||||||
|
bounds: 0 0 256 256
|
||||||
|
color: red
|
||||||
|
- type: stacking-context
|
||||||
|
clip-node: 2
|
||||||
|
wraps-backdrop-filter: true
|
||||||
|
items:
|
||||||
|
- type: backdrop-filter
|
||||||
|
bounds: 0 0 256 256
|
||||||
|
filters: invert(1)
|
|
@ -64,6 +64,7 @@ fuzzy(1,1) platform(linux,mac) == svg-filter-drop-shadow-perspective.yaml svg-fi
|
||||||
#== backdrop-filter-basic.yaml backdrop-filter-basic-ref.yaml
|
#== backdrop-filter-basic.yaml backdrop-filter-basic-ref.yaml
|
||||||
#platform(linux,mac) == backdrop-filter-perspective.yaml backdrop-filter-perspective.png
|
#platform(linux,mac) == backdrop-filter-perspective.yaml backdrop-filter-perspective.png
|
||||||
#== backdrop-filter-on-child-surface.yaml backdrop-filter-on-child-surface-ref.yaml
|
#== backdrop-filter-on-child-surface.yaml backdrop-filter-on-child-surface-ref.yaml
|
||||||
|
#== backdrop-filter-clip-mask.yaml backdrop-filter-clip-mask-ref.yaml
|
||||||
#platform(linux,mac) == backdrop-filter-across-tiles.yaml backdrop-filter-across-tiles.png
|
#platform(linux,mac) == backdrop-filter-across-tiles.yaml backdrop-filter-across-tiles.png
|
||||||
#platform(linux,mac) == backdrop-filter-chain.yaml backdrop-filter-chain.png
|
#platform(linux,mac) == backdrop-filter-chain.yaml backdrop-filter-chain.png
|
||||||
#platform(linux,mac) == backdrop-filter-overlap.yaml backdrop-filter-overlap.png
|
#platform(linux,mac) == backdrop-filter-overlap.yaml backdrop-filter-overlap.png
|
||||||
|
|
|
@ -2025,6 +2025,7 @@ impl YamlFrameReader {
|
||||||
.as_raster_space()
|
.as_raster_space()
|
||||||
.unwrap_or(RasterSpace::Screen);
|
.unwrap_or(RasterSpace::Screen);
|
||||||
let is_blend_container = yaml["blend-container"].as_bool().unwrap_or(false);
|
let is_blend_container = yaml["blend-container"].as_bool().unwrap_or(false);
|
||||||
|
let wraps_backdrop_filter = yaml["wraps-backdrop-filter"].as_bool().unwrap_or(false);
|
||||||
|
|
||||||
if is_root {
|
if is_root {
|
||||||
if let Some(vector) = yaml["scroll-offset"].as_vector() {
|
if let Some(vector) = yaml["scroll-offset"].as_vector() {
|
||||||
|
@ -2045,6 +2046,7 @@ impl YamlFrameReader {
|
||||||
|
|
||||||
let mut flags = StackingContextFlags::empty();
|
let mut flags = StackingContextFlags::empty();
|
||||||
flags.set(StackingContextFlags::IS_BLEND_CONTAINER, is_blend_container);
|
flags.set(StackingContextFlags::IS_BLEND_CONTAINER, is_blend_container);
|
||||||
|
flags.set(StackingContextFlags::WRAPS_BACKDROP_FILTER, wraps_backdrop_filter);
|
||||||
|
|
||||||
dl.push_stacking_context(
|
dl.push_stacking_context(
|
||||||
bounds.min,
|
bounds.min,
|
||||||
|
|
|
@ -3442,8 +3442,7 @@ void nsIFrame::BuildDisplayListForStackingContext(
|
||||||
// to remove any existing content that isn't wrapped in the blend container,
|
// to remove any existing content that isn't wrapped in the blend container,
|
||||||
// and then we need to build content infront/behind the blend container
|
// and then we need to build content infront/behind the blend container
|
||||||
// to get correct positioning during merging.
|
// to get correct positioning during merging.
|
||||||
if ((aBuilder->ContainsBlendMode()) &&
|
if ((aBuilder->ContainsBlendMode()) && aBuilder->IsRetainingDisplayList()) {
|
||||||
aBuilder->IsRetainingDisplayList()) {
|
|
||||||
if (aBuilder->IsPartialUpdate()) {
|
if (aBuilder->IsPartialUpdate()) {
|
||||||
aBuilder->SetPartialBuildFailed(true);
|
aBuilder->SetPartialBuildFailed(true);
|
||||||
} else {
|
} else {
|
||||||
|
@ -3551,7 +3550,7 @@ void nsIFrame::BuildDisplayListForStackingContext(
|
||||||
: containerItemASR;
|
: containerItemASR;
|
||||||
/* List now emptied, so add the new list to the top. */
|
/* List now emptied, so add the new list to the top. */
|
||||||
resultList.AppendNewToTop<nsDisplayMasksAndClipPaths>(
|
resultList.AppendNewToTop<nsDisplayMasksAndClipPaths>(
|
||||||
aBuilder, this, &resultList, maskASR);
|
aBuilder, this, &resultList, maskASR, usingBackdropFilter);
|
||||||
createdContainer = true;
|
createdContainer = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7857,8 +7857,9 @@ static void ComputeMaskGeometry(PaintFramesParams& aParams) {
|
||||||
|
|
||||||
nsDisplayMasksAndClipPaths::nsDisplayMasksAndClipPaths(
|
nsDisplayMasksAndClipPaths::nsDisplayMasksAndClipPaths(
|
||||||
nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, nsDisplayList* aList,
|
nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, nsDisplayList* aList,
|
||||||
const ActiveScrolledRoot* aActiveScrolledRoot)
|
const ActiveScrolledRoot* aActiveScrolledRoot, bool aWrapsBackdropFilter)
|
||||||
: nsDisplayEffectsBase(aBuilder, aFrame, aList, aActiveScrolledRoot, true) {
|
: nsDisplayEffectsBase(aBuilder, aFrame, aList, aActiveScrolledRoot, true),
|
||||||
|
mWrapsBackdropFilter(aWrapsBackdropFilter) {
|
||||||
MOZ_COUNT_CTOR(nsDisplayMasksAndClipPaths);
|
MOZ_COUNT_CTOR(nsDisplayMasksAndClipPaths);
|
||||||
|
|
||||||
nsPresContext* presContext = mFrame->PresContext();
|
nsPresContext* presContext = mFrame->PresContext();
|
||||||
|
@ -8199,6 +8200,9 @@ bool nsDisplayMasksAndClipPaths::CreateWebRenderCommands(
|
||||||
wr::StackingContextParams params;
|
wr::StackingContextParams params;
|
||||||
params.clip = wr::WrStackingContextClip::ClipId(*clip);
|
params.clip = wr::WrStackingContextClip::ClipId(*clip);
|
||||||
params.opacity = opacity.ptrOr(nullptr);
|
params.opacity = opacity.ptrOr(nullptr);
|
||||||
|
if (mWrapsBackdropFilter) {
|
||||||
|
params.flags |= wr::StackingContextFlags::WRAPS_BACKDROP_FILTER;
|
||||||
|
}
|
||||||
layer.emplace(aSc, GetActiveScrolledRoot(), mFrame, this, aBuilder, params,
|
layer.emplace(aSc, GetActiveScrolledRoot(), mFrame, this, aBuilder, params,
|
||||||
bounds);
|
bounds);
|
||||||
sc = layer.ptr();
|
sc = layer.ptr();
|
||||||
|
@ -8327,6 +8331,15 @@ void nsDisplayBackdropFilters::Paint(nsDisplayListBuilder* aBuilder,
|
||||||
mFrame->PresContext()->AppUnitsPerDevPixel());
|
mFrame->PresContext()->AppUnitsPerDevPixel());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
nsRect nsDisplayBackdropFilters::GetBounds(nsDisplayListBuilder* aBuilder,
|
||||||
|
bool* aSnap) const {
|
||||||
|
nsRect childBounds = nsDisplayWrapList::GetBounds(aBuilder, aSnap);
|
||||||
|
|
||||||
|
*aSnap = false;
|
||||||
|
|
||||||
|
return mBackdropRect.Union(childBounds);
|
||||||
|
}
|
||||||
|
|
||||||
/* static */
|
/* static */
|
||||||
nsDisplayFilters::nsDisplayFilters(nsDisplayListBuilder* aBuilder,
|
nsDisplayFilters::nsDisplayFilters(nsDisplayListBuilder* aBuilder,
|
||||||
nsIFrame* aFrame, nsDisplayList* aList,
|
nsIFrame* aFrame, nsDisplayList* aList,
|
||||||
|
|
|
@ -5863,11 +5863,13 @@ class nsDisplayMasksAndClipPaths : public nsDisplayEffectsBase {
|
||||||
public:
|
public:
|
||||||
nsDisplayMasksAndClipPaths(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
|
nsDisplayMasksAndClipPaths(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
|
||||||
nsDisplayList* aList,
|
nsDisplayList* aList,
|
||||||
const ActiveScrolledRoot* aActiveScrolledRoot);
|
const ActiveScrolledRoot* aActiveScrolledRoot,
|
||||||
|
bool aWrapsBackdropFilter);
|
||||||
nsDisplayMasksAndClipPaths(nsDisplayListBuilder* aBuilder,
|
nsDisplayMasksAndClipPaths(nsDisplayListBuilder* aBuilder,
|
||||||
const nsDisplayMasksAndClipPaths& aOther)
|
const nsDisplayMasksAndClipPaths& aOther)
|
||||||
: nsDisplayEffectsBase(aBuilder, aOther),
|
: nsDisplayEffectsBase(aBuilder, aOther),
|
||||||
mDestRects(aOther.mDestRects.Clone()) {
|
mDestRects(aOther.mDestRects.Clone()),
|
||||||
|
mWrapsBackdropFilter(aOther.mWrapsBackdropFilter) {
|
||||||
MOZ_COUNT_CTOR(nsDisplayMasksAndClipPaths);
|
MOZ_COUNT_CTOR(nsDisplayMasksAndClipPaths);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5932,6 +5934,7 @@ class nsDisplayMasksAndClipPaths : public nsDisplayEffectsBase {
|
||||||
NS_DISPLAY_ALLOW_CLONING()
|
NS_DISPLAY_ALLOW_CLONING()
|
||||||
|
|
||||||
nsTArray<nsRect> mDestRects;
|
nsTArray<nsRect> mDestRects;
|
||||||
|
bool mWrapsBackdropFilter;
|
||||||
};
|
};
|
||||||
|
|
||||||
class nsDisplayBackdropFilters : public nsDisplayWrapList {
|
class nsDisplayBackdropFilters : public nsDisplayWrapList {
|
||||||
|
@ -5962,6 +5965,8 @@ class nsDisplayBackdropFilters : public nsDisplayWrapList {
|
||||||
|
|
||||||
bool CreatesStackingContextHelper() override { return true; }
|
bool CreatesStackingContextHelper() override { return true; }
|
||||||
|
|
||||||
|
nsRect GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) const override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
RefPtr<ComputedStyle> mStyle;
|
RefPtr<ComputedStyle> mStyle;
|
||||||
nsRect mBackdropRect;
|
nsRect mBackdropRect;
|
||||||
|
|
|
@ -113,7 +113,7 @@ class SPSCRingBufferBase {
|
||||||
|
|
||||||
mData = std::make_unique<T[]>(StorageCapacity());
|
mData = std::make_unique<T[]>(StorageCapacity());
|
||||||
|
|
||||||
std::atomic_thread_fence(std::memory_order::memory_order_seq_cst);
|
std::atomic_thread_fence(std::memory_order_seq_cst);
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Push `aCount` zero or default constructed elements in the array.
|
* Push `aCount` zero or default constructed elements in the array.
|
||||||
|
@ -152,8 +152,8 @@ class SPSCRingBufferBase {
|
||||||
AssertCorrectThread(mProducerId);
|
AssertCorrectThread(mProducerId);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
int rdIdx = mReadIndex.load(std::memory_order::memory_order_acquire);
|
int rdIdx = mReadIndex.load(std::memory_order_acquire);
|
||||||
int wrIdx = mWriteIndex.load(std::memory_order::memory_order_relaxed);
|
int wrIdx = mWriteIndex.load(std::memory_order_relaxed);
|
||||||
|
|
||||||
if (IsFull(rdIdx, wrIdx)) {
|
if (IsFull(rdIdx, wrIdx)) {
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -178,7 +178,7 @@ class SPSCRingBufferBase {
|
||||||
}
|
}
|
||||||
|
|
||||||
mWriteIndex.store(IncrementIndex(wrIdx, toWrite),
|
mWriteIndex.store(IncrementIndex(wrIdx, toWrite),
|
||||||
std::memory_order::memory_order_release);
|
std::memory_order_release);
|
||||||
|
|
||||||
return toWrite;
|
return toWrite;
|
||||||
}
|
}
|
||||||
|
@ -198,8 +198,8 @@ class SPSCRingBufferBase {
|
||||||
AssertCorrectThread(mConsumerId);
|
AssertCorrectThread(mConsumerId);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
int wrIdx = mWriteIndex.load(std::memory_order::memory_order_acquire);
|
int wrIdx = mWriteIndex.load(std::memory_order_acquire);
|
||||||
int rdIdx = mReadIndex.load(std::memory_order::memory_order_relaxed);
|
int rdIdx = mReadIndex.load(std::memory_order_relaxed);
|
||||||
|
|
||||||
if (IsEmpty(rdIdx, wrIdx)) {
|
if (IsEmpty(rdIdx, wrIdx)) {
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -217,8 +217,7 @@ class SPSCRingBufferBase {
|
||||||
secondPart);
|
secondPart);
|
||||||
}
|
}
|
||||||
|
|
||||||
mReadIndex.store(IncrementIndex(rdIdx, toRead),
|
mReadIndex.store(IncrementIndex(rdIdx, toRead), std::memory_order_release);
|
||||||
std::memory_order::memory_order_release);
|
|
||||||
|
|
||||||
return toRead;
|
return toRead;
|
||||||
}
|
}
|
||||||
|
@ -233,9 +232,8 @@ class SPSCRingBufferBase {
|
||||||
* @return The number of available elements for reading.
|
* @return The number of available elements for reading.
|
||||||
*/
|
*/
|
||||||
int AvailableRead() const {
|
int AvailableRead() const {
|
||||||
return AvailableReadInternal(
|
return AvailableReadInternal(mReadIndex.load(std::memory_order_relaxed),
|
||||||
mReadIndex.load(std::memory_order::memory_order_relaxed),
|
mWriteIndex.load(std::memory_order_relaxed));
|
||||||
mWriteIndex.load(std::memory_order::memory_order_relaxed));
|
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Get the number of available elements for writing.
|
* Get the number of available elements for writing.
|
||||||
|
@ -248,9 +246,8 @@ class SPSCRingBufferBase {
|
||||||
* @return The number of empty slots in the buffer, available for writing.
|
* @return The number of empty slots in the buffer, available for writing.
|
||||||
*/
|
*/
|
||||||
int AvailableWrite() const {
|
int AvailableWrite() const {
|
||||||
return AvailableWriteInternal(
|
return AvailableWriteInternal(mReadIndex.load(std::memory_order_relaxed),
|
||||||
mReadIndex.load(std::memory_order::memory_order_relaxed),
|
mWriteIndex.load(std::memory_order_relaxed));
|
||||||
mWriteIndex.load(std::memory_order::memory_order_relaxed));
|
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Get the total Capacity, for this ring buffer.
|
* Get the total Capacity, for this ring buffer.
|
||||||
|
|
|
@ -17,18 +17,20 @@ const { debug, warn } = GeckoViewUtils.initLogging("ContentDelegateParent");
|
||||||
|
|
||||||
class ContentDelegateParent extends GeckoViewActorParent {
|
class ContentDelegateParent extends GeckoViewActorParent {
|
||||||
async receiveMessage(aMsg) {
|
async receiveMessage(aMsg) {
|
||||||
debug`receiveMessage: ${aMsg.name} ${aMsg}`;
|
debug`receiveMessage: ${aMsg.name}`;
|
||||||
|
|
||||||
switch (aMsg.name) {
|
switch (aMsg.name) {
|
||||||
case "GeckoView:DOMFullscreenExit": {
|
case "GeckoView:DOMFullscreenExit": {
|
||||||
this.window.windowUtils.remoteFrameFullscreenReverted();
|
this.window.windowUtils.remoteFrameFullscreenReverted();
|
||||||
break;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
case "GeckoView:DOMFullscreenRequest": {
|
case "GeckoView:DOMFullscreenRequest": {
|
||||||
this.window.windowUtils.remoteFrameFullscreenChanged(this.browser);
|
this.window.windowUtils.remoteFrameFullscreenChanged(this.browser);
|
||||||
break;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return super.receiveMessage(aMsg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,11 +11,13 @@ const { XPCOMUtils } = ChromeUtils.import(
|
||||||
const { GeckoViewUtils } = ChromeUtils.import(
|
const { GeckoViewUtils } = ChromeUtils.import(
|
||||||
"resource://gre/modules/GeckoViewUtils.jsm"
|
"resource://gre/modules/GeckoViewUtils.jsm"
|
||||||
);
|
);
|
||||||
|
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
|
||||||
|
|
||||||
XPCOMUtils.defineLazyModuleGetters(this, {
|
XPCOMUtils.defineLazyModuleGetters(this, {
|
||||||
FormLikeFactory: "resource://gre/modules/FormLikeFactory.jsm",
|
FormLikeFactory: "resource://gre/modules/FormLikeFactory.jsm",
|
||||||
GeckoViewAutofill: "resource://gre/modules/GeckoViewAutofill.jsm",
|
GeckoViewAutofill: "resource://gre/modules/GeckoViewAutofill.jsm",
|
||||||
WebNavigationFrames: "resource://gre/modules/WebNavigationFrames.jsm",
|
WebNavigationFrames: "resource://gre/modules/WebNavigationFrames.jsm",
|
||||||
|
LoginManagerChild: "resource://gre/modules/LoginManagerChild.jsm",
|
||||||
});
|
});
|
||||||
|
|
||||||
const EXPORTED_SYMBOLS = ["GeckoViewAutoFillChild"];
|
const EXPORTED_SYMBOLS = ["GeckoViewAutoFillChild"];
|
||||||
|
@ -24,61 +26,352 @@ class GeckoViewAutoFillChild extends GeckoViewActorChild {
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
XPCOMUtils.defineLazyGetter(this, "_autofill", function() {
|
this._autofillElements = undefined;
|
||||||
return new GeckoViewAutofill(this.eventDispatcher);
|
this._autofillInfos = undefined;
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line complexity
|
// eslint-disable-next-line complexity
|
||||||
handleEvent(aEvent) {
|
handleEvent(aEvent) {
|
||||||
debug`handleEvent: ${aEvent.type}`;
|
debug`handleEvent: ${aEvent.type}`;
|
||||||
const { contentWindow } = this;
|
|
||||||
|
|
||||||
switch (aEvent.type) {
|
switch (aEvent.type) {
|
||||||
case "DOMFormHasPassword": {
|
case "DOMFormHasPassword": {
|
||||||
this._autofill.addElement(
|
this.addElement(FormLikeFactory.createFromForm(aEvent.composedTarget));
|
||||||
FormLikeFactory.createFromForm(aEvent.composedTarget)
|
|
||||||
);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "DOMInputPasswordAdded": {
|
case "DOMInputPasswordAdded": {
|
||||||
const input = aEvent.composedTarget;
|
const input = aEvent.composedTarget;
|
||||||
if (!input.form) {
|
if (!input.form) {
|
||||||
this._autofill.addElement(FormLikeFactory.createFromField(input));
|
this.addElement(FormLikeFactory.createFromField(input));
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "focusin": {
|
case "focusin": {
|
||||||
if (contentWindow.HTMLInputElement.isInstance(aEvent.composedTarget)) {
|
if (
|
||||||
this._autofill.onFocus(aEvent.composedTarget);
|
this.contentWindow.HTMLInputElement.isInstance(aEvent.composedTarget)
|
||||||
|
) {
|
||||||
|
this.onFocus(aEvent.composedTarget);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "focusout": {
|
case "focusout": {
|
||||||
if (contentWindow.HTMLInputElement.isInstance(aEvent.composedTarget)) {
|
if (
|
||||||
this._autofill.onFocus(null);
|
this.contentWindow.HTMLInputElement.isInstance(aEvent.composedTarget)
|
||||||
|
) {
|
||||||
|
this.onFocus(null);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "pagehide": {
|
case "pagehide": {
|
||||||
if (aEvent.target === contentWindow.top.document) {
|
if (aEvent.target === this.document) {
|
||||||
this._autofill.clearElements();
|
this.clearElements(this.browsingContext);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "pageshow": {
|
case "pageshow": {
|
||||||
if (aEvent.target === contentWindow.top.document && aEvent.persisted) {
|
if (aEvent.target === this.document) {
|
||||||
this._autofill.scanDocument(aEvent.target);
|
this.scanDocument(this.document);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "PasswordManager:ShowDoorhanger": {
|
case "PasswordManager:ShowDoorhanger": {
|
||||||
const { form: formLike } = aEvent.detail;
|
const { form: formLike } = aEvent.detail;
|
||||||
this._autofill.commitAutofill(formLike);
|
this.commitAutofill(formLike);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process an auto-fillable form and send the relevant details of the form
|
||||||
|
* to Java. Multiple calls within a short time period for the same form are
|
||||||
|
* coalesced, so that, e.g., if multiple inputs are added to a form in
|
||||||
|
* succession, we will only perform one processing pass. Note that for inputs
|
||||||
|
* without forms, FormLikeFactory treats the document as the "form", but
|
||||||
|
* there is no difference in how we process them.
|
||||||
|
*
|
||||||
|
* @param aFormLike A FormLike object produced by FormLikeFactory.
|
||||||
|
*/
|
||||||
|
async addElement(aFormLike) {
|
||||||
|
debug`Adding auto-fill ${aFormLike.rootElement.tagName}`;
|
||||||
|
|
||||||
|
const window = aFormLike.rootElement.ownerGlobal;
|
||||||
|
// Get password field to get better form data via LoginManagerChild.
|
||||||
|
let passwordField;
|
||||||
|
for (const field of aFormLike.elements) {
|
||||||
|
if (
|
||||||
|
ChromeUtils.getClassName(field) === "HTMLInputElement" &&
|
||||||
|
field.type == "password"
|
||||||
|
) {
|
||||||
|
passwordField = field;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const [usernameField] = LoginManagerChild.forWindow(
|
||||||
|
window
|
||||||
|
).getUserNameAndPasswordFields(passwordField || aFormLike.elements[0]);
|
||||||
|
|
||||||
|
const focusedElement = aFormLike.rootElement.ownerDocument.activeElement;
|
||||||
|
let sendFocusEvent = aFormLike.rootElement === focusedElement;
|
||||||
|
|
||||||
|
const rootInfo = this._getInfo(
|
||||||
|
aFormLike.rootElement,
|
||||||
|
null,
|
||||||
|
undefined,
|
||||||
|
null
|
||||||
|
);
|
||||||
|
|
||||||
|
rootInfo.rootUuid = rootInfo.uuid;
|
||||||
|
rootInfo.children = aFormLike.elements
|
||||||
|
.filter(
|
||||||
|
element =>
|
||||||
|
element.type != "hidden" &&
|
||||||
|
(!usernameField ||
|
||||||
|
element.type != "text" ||
|
||||||
|
element == usernameField ||
|
||||||
|
(element.getAutocompleteInfo() &&
|
||||||
|
element.getAutocompleteInfo().fieldName == "email"))
|
||||||
|
)
|
||||||
|
.map(element => {
|
||||||
|
sendFocusEvent |= element === focusedElement;
|
||||||
|
return this._getInfo(
|
||||||
|
element,
|
||||||
|
rootInfo.uuid,
|
||||||
|
rootInfo.uuid,
|
||||||
|
usernameField
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
// We don't await here so that we can send a focus event immediately
|
||||||
|
// after this as the app might not know which element is focused.
|
||||||
|
const responsePromise = this.sendQuery("Add", {
|
||||||
|
node: rootInfo,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (sendFocusEvent) {
|
||||||
|
// We might have missed sending a focus event for the active element.
|
||||||
|
this.onFocus(aFormLike.ownerDocument.activeElement);
|
||||||
|
}
|
||||||
|
|
||||||
|
const responses = await responsePromise;
|
||||||
|
// `responses` is an object with global IDs as keys.
|
||||||
|
debug`Performing auto-fill ${Object.keys(responses)}`;
|
||||||
|
|
||||||
|
const AUTOFILL_STATE = "autofill";
|
||||||
|
const winUtils = window.windowUtils;
|
||||||
|
|
||||||
|
for (const uuid in responses) {
|
||||||
|
const entry =
|
||||||
|
this._autofillElements && this._autofillElements.get(uuid);
|
||||||
|
const element = entry && entry.get();
|
||||||
|
const value = responses[uuid] || "";
|
||||||
|
|
||||||
|
if (
|
||||||
|
window.HTMLInputElement.isInstance(element) &&
|
||||||
|
!element.disabled &&
|
||||||
|
element.parentElement
|
||||||
|
) {
|
||||||
|
element.setUserInput(value);
|
||||||
|
if (winUtils && element.value === value) {
|
||||||
|
// Add highlighting for autofilled fields.
|
||||||
|
winUtils.addManuallyManagedState(element, AUTOFILL_STATE);
|
||||||
|
|
||||||
|
// Remove highlighting when the field is changed.
|
||||||
|
element.addEventListener(
|
||||||
|
"input",
|
||||||
|
_ => winUtils.removeManuallyManagedState(element, AUTOFILL_STATE),
|
||||||
|
{ mozSystemGroup: true, once: true }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else if (element) {
|
||||||
|
warn`Don't know how to auto-fill ${element.tagName}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
warn`Cannot perform autofill ${error}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_getInfo(aElement, aParent, aRoot, aUsernameField) {
|
||||||
|
if (!this._autofillInfos) {
|
||||||
|
this._autofillInfos = new WeakMap();
|
||||||
|
this._autofillElements = new Map();
|
||||||
|
}
|
||||||
|
|
||||||
|
let info = this._autofillInfos.get(aElement);
|
||||||
|
if (info) {
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
|
||||||
|
const window = aElement.ownerGlobal;
|
||||||
|
const bounds = aElement.getBoundingClientRect();
|
||||||
|
const isInputElement = window.HTMLInputElement.isInstance(aElement);
|
||||||
|
|
||||||
|
info = {
|
||||||
|
isInputElement,
|
||||||
|
uuid: Services.uuid
|
||||||
|
.generateUUID()
|
||||||
|
.toString()
|
||||||
|
.slice(1, -1), // discard the surrounding curly braces
|
||||||
|
parentUuid: aParent,
|
||||||
|
rootUuid: aRoot,
|
||||||
|
tag: aElement.tagName,
|
||||||
|
type: isInputElement ? aElement.type : null,
|
||||||
|
value: isInputElement ? aElement.value : null,
|
||||||
|
editable:
|
||||||
|
isInputElement &&
|
||||||
|
[
|
||||||
|
"color",
|
||||||
|
"date",
|
||||||
|
"datetime-local",
|
||||||
|
"email",
|
||||||
|
"month",
|
||||||
|
"number",
|
||||||
|
"password",
|
||||||
|
"range",
|
||||||
|
"search",
|
||||||
|
"tel",
|
||||||
|
"text",
|
||||||
|
"time",
|
||||||
|
"url",
|
||||||
|
"week",
|
||||||
|
].includes(aElement.type),
|
||||||
|
disabled: isInputElement ? aElement.disabled : null,
|
||||||
|
attributes: Object.assign(
|
||||||
|
{},
|
||||||
|
...Array.from(aElement.attributes)
|
||||||
|
.filter(attr => attr.localName !== "value")
|
||||||
|
.map(attr => ({ [attr.localName]: attr.value }))
|
||||||
|
),
|
||||||
|
origin: aElement.ownerDocument.location.origin,
|
||||||
|
autofillhint: "",
|
||||||
|
bounds: {
|
||||||
|
left: bounds.left,
|
||||||
|
top: bounds.top,
|
||||||
|
right: bounds.right,
|
||||||
|
bottom: bounds.bottom,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
if (aElement === aUsernameField) {
|
||||||
|
info.autofillhint = "username"; // AUTOFILL.HINT.USERNAME
|
||||||
|
} else if (isInputElement) {
|
||||||
|
// Using autocomplete attribute if it is email.
|
||||||
|
const autocompleteInfo = aElement.getAutocompleteInfo();
|
||||||
|
if (autocompleteInfo) {
|
||||||
|
const autocompleteAttr = autocompleteInfo.fieldName;
|
||||||
|
if (autocompleteAttr == "email") {
|
||||||
|
info.type = "email";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this._autofillInfos.set(aElement, info);
|
||||||
|
this._autofillElements.set(info.uuid, Cu.getWeakReference(aElement));
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
|
||||||
|
_updateInfoValues(aElements) {
|
||||||
|
if (!this._autofillInfos) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const updated = [];
|
||||||
|
for (const element of aElements) {
|
||||||
|
const info = this._autofillInfos.get(element);
|
||||||
|
|
||||||
|
if (!info?.isInputElement || info.value === element.value) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
debug`Updating value ${info.value} to ${element.value}`;
|
||||||
|
|
||||||
|
info.value = element.value;
|
||||||
|
this._autofillInfos.set(element, info);
|
||||||
|
updated.push(info);
|
||||||
|
}
|
||||||
|
return updated;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when an auto-fillable field is focused or blurred.
|
||||||
|
*
|
||||||
|
* @param aTarget Focused element, or null if an element has lost focus.
|
||||||
|
*/
|
||||||
|
onFocus(aTarget) {
|
||||||
|
debug`Auto-fill focus on ${aTarget && aTarget.tagName}`;
|
||||||
|
|
||||||
|
const info =
|
||||||
|
aTarget && this._autofillInfos && this._autofillInfos.get(aTarget);
|
||||||
|
if (!aTarget || info) {
|
||||||
|
this.sendAsyncMessage("Focus", {
|
||||||
|
node: info,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
commitAutofill(aFormLike) {
|
||||||
|
if (!aFormLike) {
|
||||||
|
throw new Error("null-form on autofill commit");
|
||||||
|
}
|
||||||
|
|
||||||
|
debug`Committing auto-fill for ${aFormLike.rootElement.tagName}`;
|
||||||
|
|
||||||
|
const updatedNodeInfos = this._updateInfoValues([
|
||||||
|
aFormLike.rootElement,
|
||||||
|
...aFormLike.elements,
|
||||||
|
]);
|
||||||
|
|
||||||
|
for (const updatedInfo of updatedNodeInfos) {
|
||||||
|
debug`Updating node ${updatedInfo}`;
|
||||||
|
this.sendAsyncMessage("Update", {
|
||||||
|
node: updatedInfo,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const info = this._getInfo(aFormLike.rootElement);
|
||||||
|
if (info) {
|
||||||
|
debug`Committing node ${info}`;
|
||||||
|
this.sendAsyncMessage("Commit", {
|
||||||
|
node: info,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear all tracked auto-fill forms and notify Java.
|
||||||
|
*/
|
||||||
|
clearElements(browsingContext) {
|
||||||
|
this._autofillInfos = undefined;
|
||||||
|
this._autofillElements = undefined;
|
||||||
|
|
||||||
|
if (browsingContext === browsingContext.top) {
|
||||||
|
this.sendAsyncMessage("Clear");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Scan for auto-fillable forms and add them if necessary. Called when a page
|
||||||
|
* is navigated to through history, in which case we don't get our typical
|
||||||
|
* "input added" notifications.
|
||||||
|
*
|
||||||
|
* @param aDoc Document to scan.
|
||||||
|
*/
|
||||||
|
scanDocument(aDoc) {
|
||||||
|
// Add forms first; only check forms with password inputs.
|
||||||
|
const inputs = aDoc.querySelectorAll("input[type=password]");
|
||||||
|
let inputAdded = false;
|
||||||
|
for (let i = 0; i < inputs.length; i++) {
|
||||||
|
if (inputs[i].form) {
|
||||||
|
// Let addElement coalesce multiple calls for the same form.
|
||||||
|
this.addElement(FormLikeFactory.createFromForm(inputs[i].form));
|
||||||
|
} else if (!inputAdded) {
|
||||||
|
// Treat inputs without forms as one unit, and process them only once.
|
||||||
|
inputAdded = true;
|
||||||
|
this.addElement(FormLikeFactory.createFromField(inputs[i]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const { debug, warn } = GeckoViewAutoFillChild.initLogging("GeckoViewAutoFill");
|
const { debug, warn } = GeckoViewAutoFillChild.initLogging("GeckoViewAutoFill");
|
||||||
|
|
|
@ -0,0 +1,100 @@
|
||||||
|
/* 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/. */
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
var EXPORTED_SYMBOLS = ["GeckoViewAutoFillParent"];
|
||||||
|
|
||||||
|
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
|
||||||
|
const { XPCOMUtils } = ChromeUtils.import(
|
||||||
|
"resource://gre/modules/XPCOMUtils.jsm"
|
||||||
|
);
|
||||||
|
const { GeckoViewActorParent } = ChromeUtils.import(
|
||||||
|
"resource://gre/modules/GeckoViewActorParent.jsm"
|
||||||
|
);
|
||||||
|
|
||||||
|
XPCOMUtils.defineLazyModuleGetters(this, {
|
||||||
|
gAutofillManager: "resource://gre/modules/GeckoViewAutofill.jsm",
|
||||||
|
});
|
||||||
|
|
||||||
|
class GeckoViewAutoFillParent extends GeckoViewActorParent {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this.sessionId = Services.uuid
|
||||||
|
.generateUUID()
|
||||||
|
.toString()
|
||||||
|
.slice(1, -1); // discard the surrounding curly braces
|
||||||
|
}
|
||||||
|
|
||||||
|
get rootActor() {
|
||||||
|
return this.browsingContext.top.currentWindowGlobal.getActor(
|
||||||
|
"GeckoViewAutoFill"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
get autofill() {
|
||||||
|
return gAutofillManager.get(this.sessionId);
|
||||||
|
}
|
||||||
|
|
||||||
|
add(node) {
|
||||||
|
// We will start a new session if the current one does not exist.
|
||||||
|
const autofill = gAutofillManager.ensure(
|
||||||
|
this.sessionId,
|
||||||
|
this.eventDispatcher
|
||||||
|
);
|
||||||
|
return autofill?.add(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
focus(node) {
|
||||||
|
this.autofill?.focus(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
commit(node) {
|
||||||
|
this.autofill?.commit(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
update(node) {
|
||||||
|
this.autofill?.update(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
clear() {
|
||||||
|
gAutofillManager.delete(this.sessionId);
|
||||||
|
}
|
||||||
|
|
||||||
|
async receiveMessage(aMessage) {
|
||||||
|
const { name } = aMessage;
|
||||||
|
debug`receiveMessage ${name}`;
|
||||||
|
|
||||||
|
// We need to re-route all messages through the root actor to ensure that we
|
||||||
|
// have a consistent sessionId for the entire browsingContext tree.
|
||||||
|
switch (name) {
|
||||||
|
case "Add": {
|
||||||
|
return this.rootActor.add(aMessage.data.node);
|
||||||
|
}
|
||||||
|
case "Focus": {
|
||||||
|
this.rootActor.focus(aMessage.data.node);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "Update": {
|
||||||
|
this.rootActor.update(aMessage.data.node);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "Commit": {
|
||||||
|
this.rootActor.commit(aMessage.data.node);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "Clear": {
|
||||||
|
if (this.browsingContext === this.browsingContext.top) {
|
||||||
|
this.clear();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const { debug, warn } = GeckoViewAutoFillParent.initLogging(
|
||||||
|
"GeckoViewAutoFill"
|
||||||
|
);
|
|
@ -0,0 +1,125 @@
|
||||||
|
/* 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/. */
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
var EXPORTED_SYMBOLS = ["GeckoViewPermissionChild"];
|
||||||
|
|
||||||
|
const { XPCOMUtils } = ChromeUtils.import(
|
||||||
|
"resource://gre/modules/XPCOMUtils.jsm"
|
||||||
|
);
|
||||||
|
const { GeckoViewActorChild } = ChromeUtils.import(
|
||||||
|
"resource://gre/modules/GeckoViewActorChild.jsm"
|
||||||
|
);
|
||||||
|
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
|
||||||
|
|
||||||
|
XPCOMUtils.defineLazyModuleGetters(this, {
|
||||||
|
E10SUtils: "resource://gre/modules/E10SUtils.jsm",
|
||||||
|
});
|
||||||
|
|
||||||
|
const PERM_ACCESS_FINE_LOCATION = "android.permission.ACCESS_FINE_LOCATION";
|
||||||
|
|
||||||
|
class GeckoViewPermissionChild extends GeckoViewActorChild {
|
||||||
|
getMediaPermission(aPermission) {
|
||||||
|
return this.eventDispatcher.sendRequestForResult({
|
||||||
|
type: "GeckoView:MediaPermission",
|
||||||
|
...aPermission,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
addCameraPermission() {
|
||||||
|
return this.sendQuery("AddCameraPermission");
|
||||||
|
}
|
||||||
|
|
||||||
|
getAppPermissions(aPermissions) {
|
||||||
|
return this.sendQuery("GetAppPermissions", aPermissions);
|
||||||
|
}
|
||||||
|
|
||||||
|
mediaRecordingStatusChanged(aDevices) {
|
||||||
|
return this.eventDispatcher.sendRequest({
|
||||||
|
type: "GeckoView:MediaRecordingStatusChanged",
|
||||||
|
devices: aDevices,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async promptPermission(aRequest) {
|
||||||
|
// Only allow exactly one permission request here.
|
||||||
|
const types = aRequest.types.QueryInterface(Ci.nsIArray);
|
||||||
|
if (types.length !== 1) {
|
||||||
|
return { allow: false };
|
||||||
|
}
|
||||||
|
|
||||||
|
const perm = types.queryElementAt(0, Ci.nsIContentPermissionType);
|
||||||
|
if (
|
||||||
|
perm.type === "desktop-notification" &&
|
||||||
|
!aRequest.hasValidTransientUserGestureActivation &&
|
||||||
|
Services.prefs.getBoolPref(
|
||||||
|
"dom.webnotifications.requireuserinteraction",
|
||||||
|
true
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
// We need user interaction and don't have it.
|
||||||
|
return { allow: false };
|
||||||
|
}
|
||||||
|
|
||||||
|
const principal =
|
||||||
|
perm.type === "storage-access"
|
||||||
|
? aRequest.principal
|
||||||
|
: aRequest.topLevelPrincipal;
|
||||||
|
|
||||||
|
let allowOrDeny;
|
||||||
|
try {
|
||||||
|
allowOrDeny = await this.eventDispatcher.sendRequestForResult({
|
||||||
|
type: "GeckoView:ContentPermission",
|
||||||
|
uri: principal.URI.displaySpec,
|
||||||
|
thirdPartyOrigin: aRequest.principal.origin,
|
||||||
|
principal: E10SUtils.serializePrincipal(principal),
|
||||||
|
perm: perm.type,
|
||||||
|
value: perm.capability,
|
||||||
|
contextId: principal.originAttributes.geckoViewSessionContextId ?? null,
|
||||||
|
privateMode: principal.privateBrowsingId != 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (allowOrDeny === Services.perms.ALLOW_ACTION) {
|
||||||
|
// Ask for app permission after asking for content permission.
|
||||||
|
if (perm.type === "geolocation") {
|
||||||
|
const granted = await this.getAppPermissions([
|
||||||
|
PERM_ACCESS_FINE_LOCATION,
|
||||||
|
]);
|
||||||
|
allowOrDeny = granted
|
||||||
|
? Services.perms.ALLOW_ACTION
|
||||||
|
: Services.perms.DENY_ACTION;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
Cu.reportError("Permission error: " + error);
|
||||||
|
allowOrDeny = Services.perms.DENY_ACTION;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Manually release the target request here to facilitate garbage collection.
|
||||||
|
aRequest = undefined;
|
||||||
|
|
||||||
|
const allow = allowOrDeny === Services.perms.ALLOW_ACTION;
|
||||||
|
|
||||||
|
// The storage access code adds itself to the perm manager; no need for us to do it.
|
||||||
|
if (perm.type === "storage-access") {
|
||||||
|
if (allow) {
|
||||||
|
return { allow, permission: { "storage-access": "allow" } };
|
||||||
|
}
|
||||||
|
return { allow };
|
||||||
|
}
|
||||||
|
|
||||||
|
Services.perms.addFromPrincipal(
|
||||||
|
principal,
|
||||||
|
perm.type,
|
||||||
|
allowOrDeny,
|
||||||
|
Services.perms.EXPIRE_NEVER
|
||||||
|
);
|
||||||
|
|
||||||
|
return { allow };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const { debug, warn } = GeckoViewPermissionChild.initLogging(
|
||||||
|
"GeckoViewPermissionChild"
|
||||||
|
);
|
|
@ -0,0 +1,75 @@
|
||||||
|
/* 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/. */
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
var EXPORTED_SYMBOLS = ["GeckoViewPermissionParent"];
|
||||||
|
|
||||||
|
const { GeckoViewUtils } = ChromeUtils.import(
|
||||||
|
"resource://gre/modules/GeckoViewUtils.jsm"
|
||||||
|
);
|
||||||
|
const { XPCOMUtils } = ChromeUtils.import(
|
||||||
|
"resource://gre/modules/XPCOMUtils.jsm"
|
||||||
|
);
|
||||||
|
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
|
||||||
|
const { GeckoViewActorParent } = ChromeUtils.import(
|
||||||
|
"resource://gre/modules/GeckoViewActorParent.jsm"
|
||||||
|
);
|
||||||
|
|
||||||
|
class GeckoViewPermissionParent extends GeckoViewActorParent {
|
||||||
|
_appPermissions = {};
|
||||||
|
|
||||||
|
async getAppPermissions(aPermissions) {
|
||||||
|
const perms = aPermissions.filter(perm => !this._appPermissions[perm]);
|
||||||
|
if (!perms.length) {
|
||||||
|
return Promise.resolve(/* granted */ true);
|
||||||
|
}
|
||||||
|
|
||||||
|
const granted = await this.eventDispatcher.sendRequestForResult({
|
||||||
|
type: "GeckoView:AndroidPermission",
|
||||||
|
perms,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (granted) {
|
||||||
|
for (const perm of perms) {
|
||||||
|
this._appPermissions[perm] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return granted;
|
||||||
|
}
|
||||||
|
|
||||||
|
addCameraPermission() {
|
||||||
|
const principal = Services.scriptSecurityManager.createContentPrincipalFromOrigin(
|
||||||
|
this.browsingContext.top.currentWindowGlobal.documentPrincipal.origin
|
||||||
|
);
|
||||||
|
|
||||||
|
// Although the lifetime is "session" it will be removed upon
|
||||||
|
// use so it's more of a one-shot.
|
||||||
|
Services.perms.addFromPrincipal(
|
||||||
|
principal,
|
||||||
|
"MediaManagerVideo",
|
||||||
|
Services.perms.ALLOW_ACTION,
|
||||||
|
Services.perms.EXPIRE_SESSION
|
||||||
|
);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
receiveMessage(aMessage) {
|
||||||
|
debug`receiveMessage ${aMessage.name}`;
|
||||||
|
|
||||||
|
switch (aMessage.name) {
|
||||||
|
case "GetAppPermissions": {
|
||||||
|
return this.getAppPermissions(aMessage.data);
|
||||||
|
}
|
||||||
|
case "AddCameraPermission": {
|
||||||
|
return this.addCameraPermission();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return super.receiveMessage(aMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const { debug, warn } = GeckoViewUtils.initLogging("GeckoViewPermissionParent");
|
|
@ -0,0 +1,200 @@
|
||||||
|
/* 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/. */
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
var EXPORTED_SYMBOLS = ["GeckoViewPermissionProcessChild"];
|
||||||
|
|
||||||
|
const { GeckoViewActorChild } = ChromeUtils.import(
|
||||||
|
"resource://gre/modules/GeckoViewActorChild.jsm"
|
||||||
|
);
|
||||||
|
const { GeckoViewUtils } = ChromeUtils.import(
|
||||||
|
"resource://gre/modules/GeckoViewUtils.jsm"
|
||||||
|
);
|
||||||
|
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
|
||||||
|
const { XPCOMUtils } = ChromeUtils.import(
|
||||||
|
"resource://gre/modules/XPCOMUtils.jsm"
|
||||||
|
);
|
||||||
|
|
||||||
|
XPCOMUtils.defineLazyServiceGetter(
|
||||||
|
this,
|
||||||
|
"MediaManagerService",
|
||||||
|
"@mozilla.org/mediaManagerService;1",
|
||||||
|
"nsIMediaManagerService"
|
||||||
|
);
|
||||||
|
|
||||||
|
const STATUS_RECORDING = "recording";
|
||||||
|
const STATUS_INACTIVE = "inactive";
|
||||||
|
const TYPE_CAMERA = "camera";
|
||||||
|
const TYPE_MICROPHONE = "microphone";
|
||||||
|
|
||||||
|
class GeckoViewPermissionProcessChild extends JSProcessActorChild {
|
||||||
|
getActor(window) {
|
||||||
|
return window.windowGlobalChild.getActor("GeckoViewPermission");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---------- nsIObserver ---------- */
|
||||||
|
async observe(aSubject, aTopic, aData) {
|
||||||
|
switch (aTopic) {
|
||||||
|
case "getUserMedia:ask-device-permission": {
|
||||||
|
await this.sendQuery("AskDevicePermission", aData);
|
||||||
|
Services.obs.notifyObservers(
|
||||||
|
aSubject,
|
||||||
|
"getUserMedia:got-device-permission"
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "getUserMedia:request": {
|
||||||
|
const { callID } = aSubject;
|
||||||
|
const allowedDevices = await this.handleMediaRequest(aSubject);
|
||||||
|
Services.obs.notifyObservers(
|
||||||
|
allowedDevices,
|
||||||
|
allowedDevices
|
||||||
|
? "getUserMedia:response:allow"
|
||||||
|
: "getUserMedia:response:deny",
|
||||||
|
callID
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "PeerConnection:request": {
|
||||||
|
Services.obs.notifyObservers(
|
||||||
|
null,
|
||||||
|
"PeerConnection:response:allow",
|
||||||
|
aSubject.callID
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "recording-device-events": {
|
||||||
|
this.handleRecordingDeviceEvents(aSubject);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleRecordingDeviceEvents(aRequest) {
|
||||||
|
aRequest.QueryInterface(Ci.nsIPropertyBag2);
|
||||||
|
const contentWindow = aRequest.getProperty("window");
|
||||||
|
const devices = [];
|
||||||
|
|
||||||
|
const getStatusString = function(activityStatus) {
|
||||||
|
switch (activityStatus) {
|
||||||
|
case MediaManagerService.STATE_CAPTURE_ENABLED:
|
||||||
|
case MediaManagerService.STATE_CAPTURE_DISABLED:
|
||||||
|
return STATUS_RECORDING;
|
||||||
|
case MediaManagerService.STATE_NOCAPTURE:
|
||||||
|
return STATUS_INACTIVE;
|
||||||
|
default:
|
||||||
|
throw new Error("Unexpected activityStatus value");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const hasCamera = {};
|
||||||
|
const hasMicrophone = {};
|
||||||
|
const screen = {};
|
||||||
|
const window = {};
|
||||||
|
const browser = {};
|
||||||
|
const mediaDevices = {};
|
||||||
|
MediaManagerService.mediaCaptureWindowState(
|
||||||
|
contentWindow,
|
||||||
|
hasCamera,
|
||||||
|
hasMicrophone,
|
||||||
|
screen,
|
||||||
|
window,
|
||||||
|
browser,
|
||||||
|
mediaDevices
|
||||||
|
);
|
||||||
|
var cameraStatus = getStatusString(hasCamera.value);
|
||||||
|
var microphoneStatus = getStatusString(hasMicrophone.value);
|
||||||
|
if (hasCamera.value != MediaManagerService.STATE_NOCAPTURE) {
|
||||||
|
devices.push({
|
||||||
|
type: TYPE_CAMERA,
|
||||||
|
status: cameraStatus,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (hasMicrophone.value != MediaManagerService.STATE_NOCAPTURE) {
|
||||||
|
devices.push({
|
||||||
|
type: TYPE_MICROPHONE,
|
||||||
|
status: microphoneStatus,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
this.getActor(contentWindow).mediaRecordingStatusChanged(devices);
|
||||||
|
}
|
||||||
|
|
||||||
|
async handleMediaRequest(aRequest) {
|
||||||
|
const constraints = aRequest.getConstraints();
|
||||||
|
const { devices, windowID } = aRequest;
|
||||||
|
const window = Services.wm.getOuterWindowWithId(windowID);
|
||||||
|
if (window.closed) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Release the request first
|
||||||
|
aRequest = undefined;
|
||||||
|
|
||||||
|
const sources = devices.map(device => {
|
||||||
|
device = device.QueryInterface(Ci.nsIMediaDevice);
|
||||||
|
return {
|
||||||
|
type: device.type,
|
||||||
|
id: device.rawId,
|
||||||
|
rawId: device.rawId,
|
||||||
|
name: device.rawName, // unfiltered device name to show to the user
|
||||||
|
mediaSource: device.mediaSource,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
if (
|
||||||
|
constraints.video &&
|
||||||
|
!sources.some(source => source.type === "videoinput")
|
||||||
|
) {
|
||||||
|
Cu.reportError("Media device error: no video source");
|
||||||
|
return null;
|
||||||
|
} else if (
|
||||||
|
constraints.audio &&
|
||||||
|
!sources.some(source => source.type === "audioinput")
|
||||||
|
) {
|
||||||
|
Cu.reportError("Media device error: no audio source");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await this.getActor(window).getMediaPermission({
|
||||||
|
uri: window.document.documentURI,
|
||||||
|
video: constraints.video
|
||||||
|
? sources.filter(source => source.type === "videoinput")
|
||||||
|
: null,
|
||||||
|
audio: constraints.audio
|
||||||
|
? sources.filter(source => source.type === "audioinput")
|
||||||
|
: null,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response) {
|
||||||
|
// Rejected.
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const allowedDevices = Cc["@mozilla.org/array;1"].createInstance(
|
||||||
|
Ci.nsIMutableArray
|
||||||
|
);
|
||||||
|
if (constraints.video) {
|
||||||
|
const video = devices.find(device => response.video === device.rawId);
|
||||||
|
if (!video) {
|
||||||
|
Cu.reportError("Media device error: invalid video id");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
await this.getActor(window).addCameraPermission();
|
||||||
|
allowedDevices.appendElement(video);
|
||||||
|
}
|
||||||
|
if (constraints.audio) {
|
||||||
|
const audio = devices.find(device => response.audio === device.rawId);
|
||||||
|
if (!audio) {
|
||||||
|
Cu.reportError("Media device error: invalid audio id");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
allowedDevices.appendElement(audio);
|
||||||
|
}
|
||||||
|
return allowedDevices;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const { debug, warn } = GeckoViewUtils.initLogging(
|
||||||
|
"GeckoViewPermissionProcessChild"
|
||||||
|
);
|
|
@ -0,0 +1,62 @@
|
||||||
|
/* 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/. */
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
var EXPORTED_SYMBOLS = ["GeckoViewPermissionProcessParent"];
|
||||||
|
|
||||||
|
const { GeckoViewUtils } = ChromeUtils.import(
|
||||||
|
"resource://gre/modules/GeckoViewUtils.jsm"
|
||||||
|
);
|
||||||
|
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
|
||||||
|
|
||||||
|
// See: http://developer.android.com/reference/android/Manifest.permission.html
|
||||||
|
const PERM_CAMERA = "android.permission.CAMERA";
|
||||||
|
const PERM_RECORD_AUDIO = "android.permission.RECORD_AUDIO";
|
||||||
|
|
||||||
|
class GeckoViewPermissionProcessParent extends JSProcessActorParent {
|
||||||
|
async askDevicePermission(aType) {
|
||||||
|
const perms = [];
|
||||||
|
if (aType === "video" || aType === "all") {
|
||||||
|
perms.push(PERM_CAMERA);
|
||||||
|
}
|
||||||
|
if (aType === "audio" || aType === "all") {
|
||||||
|
perms.push(PERM_RECORD_AUDIO);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// This looks sketchy but it's fine: Android needs the audio/video
|
||||||
|
// permission to enumerate devices, which Gecko wants to do even before
|
||||||
|
// we expose the list to web pages.
|
||||||
|
// So really it doesn't matter what the request source is, because we
|
||||||
|
// will, separately, issue a windowId-specific request to let the webpage
|
||||||
|
// actually have access to the list of devices. So even if the source of
|
||||||
|
// *this* request is incorrect, no actual harm will be done, as the user
|
||||||
|
// will always receive the request with the right origin after this.
|
||||||
|
const window = Services.wm.getMostRecentWindow("navigator:geckoview");
|
||||||
|
const windowActor = window.browsingContext.currentWindowGlobal.getActor(
|
||||||
|
"GeckoViewPermission"
|
||||||
|
);
|
||||||
|
await windowActor.getAppPermissions(perms);
|
||||||
|
} catch (error) {
|
||||||
|
// We can't really do anything here so we pretend we got the permission.
|
||||||
|
warn`Error getting app permission: ${error}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
receiveMessage(aMessage) {
|
||||||
|
debug`receiveMessage ${aMessage.name}`;
|
||||||
|
|
||||||
|
switch (aMessage.name) {
|
||||||
|
case "AskDevicePermission": {
|
||||||
|
return this.askDevicePermission(aMessage.data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return super.receiveMessage(aMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const { debug, warn } = GeckoViewUtils.initLogging(
|
||||||
|
"GeckoViewPermissionProcess"
|
||||||
|
);
|
|
@ -16,12 +16,12 @@ class GeckoViewPrompterChild extends GeckoViewActorChild {
|
||||||
this._prompts = new Map();
|
this._prompts = new Map();
|
||||||
}
|
}
|
||||||
|
|
||||||
registerPrompt(prompt) {
|
dismissPrompt(prompt) {
|
||||||
this._prompts.set(prompt.id, prompt);
|
this.eventDispatcher.sendRequest({
|
||||||
this.sendAsyncMessage("RegisterPrompt", {
|
type: "GeckoView:Prompt:Dismiss",
|
||||||
id: prompt.id,
|
id: prompt.id,
|
||||||
promptType: prompt.getPromptType(),
|
|
||||||
});
|
});
|
||||||
|
this.unregisterPrompt(prompt);
|
||||||
}
|
}
|
||||||
|
|
||||||
unregisterPrompt(prompt) {
|
unregisterPrompt(prompt) {
|
||||||
|
@ -31,10 +31,23 @@ class GeckoViewPrompterChild extends GeckoViewActorChild {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
notifyPromptShow(prompt) {
|
prompt(prompt, message) {
|
||||||
|
this._prompts.set(prompt.id, prompt);
|
||||||
|
this.sendAsyncMessage("RegisterPrompt", {
|
||||||
|
id: prompt.id,
|
||||||
|
promptType: prompt.getPromptType(),
|
||||||
|
});
|
||||||
|
// We intentionally do not await here as we want to fire NotifyPromptShow
|
||||||
|
// immediately rather than waiting until the user accepts/dismisses the
|
||||||
|
// prompt.
|
||||||
|
const result = this.eventDispatcher.sendRequestForResult({
|
||||||
|
type: "GeckoView:Prompt",
|
||||||
|
prompt: message,
|
||||||
|
});
|
||||||
this.sendAsyncMessage("NotifyPromptShow", {
|
this.sendAsyncMessage("NotifyPromptShow", {
|
||||||
id: prompt.id,
|
id: prompt.id,
|
||||||
});
|
});
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -76,3 +89,5 @@ class GeckoViewPrompterChild extends GeckoViewActorChild {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { debug, warn } = GeckoViewPrompterChild.initLogging("Prompter");
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
/* 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/. */
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
var EXPORTED_SYMBOLS = ["LoadURIDelegateParent"];
|
||||||
|
|
||||||
|
const { GeckoViewActorParent } = ChromeUtils.import(
|
||||||
|
"resource://gre/modules/GeckoViewActorParent.jsm"
|
||||||
|
);
|
||||||
|
|
||||||
|
// For this.eventDispatcher in the child
|
||||||
|
class LoadURIDelegateParent extends GeckoViewActorParent {}
|
|
@ -0,0 +1,13 @@
|
||||||
|
/* 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/. */
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
var EXPORTED_SYMBOLS = ["MediaControlDelegateParent"];
|
||||||
|
|
||||||
|
const { GeckoViewActorParent } = ChromeUtils.import(
|
||||||
|
"resource://gre/modules/GeckoViewActorParent.jsm"
|
||||||
|
);
|
||||||
|
|
||||||
|
// For this.eventDispatcher in the child
|
||||||
|
class MediaControlDelegateParent extends GeckoViewActorParent {}
|
|
@ -0,0 +1,13 @@
|
||||||
|
/* 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/. */
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
var EXPORTED_SYMBOLS = ["ScrollDelegateParent"];
|
||||||
|
|
||||||
|
const { GeckoViewActorParent } = ChromeUtils.import(
|
||||||
|
"resource://gre/modules/GeckoViewActorParent.jsm"
|
||||||
|
);
|
||||||
|
|
||||||
|
// For this.eventDispatcher in the child
|
||||||
|
class ScrollDelegateParent extends GeckoViewActorParent {}
|
|
@ -21,7 +21,7 @@ class SelectionActionDelegateChild extends GeckoViewActorChild {
|
||||||
constructor(aModuleName, aMessageManager) {
|
constructor(aModuleName, aMessageManager) {
|
||||||
super(aModuleName, aMessageManager);
|
super(aModuleName, aMessageManager);
|
||||||
|
|
||||||
this._seqNo = 0;
|
this._actionCallback = () => {};
|
||||||
this._isActive = false;
|
this._isActive = false;
|
||||||
this._previousMessage = "";
|
this._previousMessage = "";
|
||||||
|
|
||||||
|
@ -133,6 +133,16 @@ class SelectionActionDelegateChild extends GeckoViewActorChild {
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
receiveMessage({ name, data }) {
|
||||||
|
debug`receiveMessage ${name}`;
|
||||||
|
|
||||||
|
switch (name) {
|
||||||
|
case "ExecuteSelectionAction": {
|
||||||
|
this._actionCallback(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
_performPaste() {
|
_performPaste() {
|
||||||
this.handleEvent({ type: "pagehide" });
|
this.handleEvent({ type: "pagehide" });
|
||||||
this.docShell.doCommand("cmd_paste");
|
this.docShell.doCommand("cmd_paste");
|
||||||
|
@ -307,8 +317,6 @@ class SelectionActionDelegateChild extends GeckoViewActorChild {
|
||||||
const password = this._isPasswordField(aEvent);
|
const password = this._isPasswordField(aEvent);
|
||||||
|
|
||||||
const msg = {
|
const msg = {
|
||||||
type: "GeckoView:ShowSelectionAction",
|
|
||||||
seqNo: this._seqNo,
|
|
||||||
collapsed: aEvent.collapsed,
|
collapsed: aEvent.collapsed,
|
||||||
editable: aEvent.selectionEditable,
|
editable: aEvent.selectionEditable,
|
||||||
password,
|
password,
|
||||||
|
@ -333,29 +341,21 @@ class SelectionActionDelegateChild extends GeckoViewActorChild {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
msg.seqNo = ++this._seqNo;
|
|
||||||
this._isActive = true;
|
this._isActive = true;
|
||||||
this._previousMessage = JSON.stringify(msg);
|
this._previousMessage = JSON.stringify(msg);
|
||||||
|
|
||||||
this.eventDispatcher.sendRequest(msg, {
|
// We can't just listen to the response of the message because we accept
|
||||||
onSuccess: response => {
|
// multiple callbacks.
|
||||||
if (response.seqNo !== this._seqNo) {
|
this._actionCallback = data => {
|
||||||
// Stale action.
|
const action = actions.find(action => action.id === data.id);
|
||||||
warn`Stale response ${response.id}`;
|
if (action) {
|
||||||
return;
|
debug`Performing ${data.id}`;
|
||||||
}
|
action.perform.call(this, aEvent);
|
||||||
const action = actions.find(action => action.id === response.id);
|
} else {
|
||||||
if (action) {
|
warn`Invalid action ${data.id}`;
|
||||||
debug`Performing ${response.id}`;
|
}
|
||||||
action.perform.call(this, aEvent, response);
|
};
|
||||||
} else {
|
this.sendAsyncMessage("ShowSelectionAction", msg);
|
||||||
warn`Invalid action ${response.id}`;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onError: _ => {
|
|
||||||
// Do nothing; we can get here if the delegate was just unregistered.
|
|
||||||
},
|
|
||||||
});
|
|
||||||
} else if (
|
} else if (
|
||||||
[
|
[
|
||||||
"invisibleselection",
|
"invisibleselection",
|
||||||
|
@ -367,7 +367,6 @@ class SelectionActionDelegateChild extends GeckoViewActorChild {
|
||||||
if (!this._isActive) {
|
if (!this._isActive) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this._isActive = false;
|
this._isActive = false;
|
||||||
|
|
||||||
// Mark previous actions as stale. Don't do this for "invisibleselection"
|
// Mark previous actions as stale. Don't do this for "invisibleselection"
|
||||||
|
@ -377,10 +376,7 @@ class SelectionActionDelegateChild extends GeckoViewActorChild {
|
||||||
this._seqNo++;
|
this._seqNo++;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.eventDispatcher.sendRequest({
|
this.sendAsyncMessage("HideSelectionAction", { reason });
|
||||||
type: "GeckoView:HideSelectionAction",
|
|
||||||
reason,
|
|
||||||
});
|
|
||||||
} else if (reason == "dragcaret") {
|
} else if (reason == "dragcaret") {
|
||||||
// nothing for selection action
|
// nothing for selection action
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -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/. */
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
var EXPORTED_SYMBOLS = ["SelectionActionDelegateParent"];
|
||||||
|
|
||||||
|
const { GeckoViewActorParent } = ChromeUtils.import(
|
||||||
|
"resource://gre/modules/GeckoViewActorParent.jsm"
|
||||||
|
);
|
||||||
|
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
|
||||||
|
|
||||||
|
class SelectionActionDelegateParent extends GeckoViewActorParent {
|
||||||
|
respondTo = null;
|
||||||
|
actionId = null;
|
||||||
|
|
||||||
|
get rootActor() {
|
||||||
|
return this.browsingContext.top.currentWindowGlobal.getActor(
|
||||||
|
"SelectionActionDelegate"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
receiveMessage(aMessage) {
|
||||||
|
const { data, name } = aMessage;
|
||||||
|
switch (name) {
|
||||||
|
case "ShowSelectionAction": {
|
||||||
|
this.rootActor.showSelectionAction(this, data);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case "HideSelectionAction": {
|
||||||
|
this.rootActor.hideSelectionAction(this, data.reason);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
default: {
|
||||||
|
super.receiveMessage(aMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
hideSelectionAction(aRespondTo, reason) {
|
||||||
|
// Mark previous actions as stale. Don't do this for "invisibleselection"
|
||||||
|
// or "scroll" because previous actions should still be valid even after
|
||||||
|
// these events occur.
|
||||||
|
if (reason !== "invisibleselection" && reason !== "scroll") {
|
||||||
|
this.actionId = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.eventDispatcher?.sendRequest({
|
||||||
|
type: "GeckoView:HideSelectionAction",
|
||||||
|
reason,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
showSelectionAction(aRespondTo, aData) {
|
||||||
|
this.actionId = Services.uuid.generateUUID().toString();
|
||||||
|
this.respondTo = aRespondTo;
|
||||||
|
|
||||||
|
this.eventDispatcher?.sendRequest({
|
||||||
|
type: "GeckoView:ShowSelectionAction",
|
||||||
|
actionId: this.actionId,
|
||||||
|
...aData,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
executeSelectionAction(aData) {
|
||||||
|
if (this.actionId === null || aData.actionId != this.actionId) {
|
||||||
|
warn`Stale response ${aData.id} ${aData.actionId}`;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.respondTo.sendAsyncMessage("ExecuteSelectionAction", aData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const { debug, warn } = SelectionActionDelegateParent.initLogging(
|
||||||
|
"SelectionActionDelegate"
|
||||||
|
);
|
|
@ -9,17 +9,26 @@ FINAL_TARGET_FILES.actors += [
|
||||||
"ContentDelegateChild.jsm",
|
"ContentDelegateChild.jsm",
|
||||||
"ContentDelegateParent.jsm",
|
"ContentDelegateParent.jsm",
|
||||||
"GeckoViewAutoFillChild.jsm",
|
"GeckoViewAutoFillChild.jsm",
|
||||||
|
"GeckoViewAutoFillParent.jsm",
|
||||||
"GeckoViewContentChild.jsm",
|
"GeckoViewContentChild.jsm",
|
||||||
"GeckoViewContentParent.jsm",
|
"GeckoViewContentParent.jsm",
|
||||||
"GeckoViewFormValidationChild.jsm",
|
"GeckoViewFormValidationChild.jsm",
|
||||||
|
"GeckoViewPermissionChild.jsm",
|
||||||
|
"GeckoViewPermissionParent.jsm",
|
||||||
|
"GeckoViewPermissionProcessChild.jsm",
|
||||||
|
"GeckoViewPermissionProcessParent.jsm",
|
||||||
"GeckoViewPromptChild.jsm",
|
"GeckoViewPromptChild.jsm",
|
||||||
"GeckoViewPrompterChild.jsm",
|
"GeckoViewPrompterChild.jsm",
|
||||||
"GeckoViewPrompterParent.jsm",
|
"GeckoViewPrompterParent.jsm",
|
||||||
"GeckoViewSettingsChild.jsm",
|
"GeckoViewSettingsChild.jsm",
|
||||||
"LoadURIDelegateChild.jsm",
|
"LoadURIDelegateChild.jsm",
|
||||||
|
"LoadURIDelegateParent.jsm",
|
||||||
"MediaControlDelegateChild.jsm",
|
"MediaControlDelegateChild.jsm",
|
||||||
|
"MediaControlDelegateParent.jsm",
|
||||||
"ProgressDelegateChild.jsm",
|
"ProgressDelegateChild.jsm",
|
||||||
"ProgressDelegateParent.jsm",
|
"ProgressDelegateParent.jsm",
|
||||||
"ScrollDelegateChild.jsm",
|
"ScrollDelegateChild.jsm",
|
||||||
|
"ScrollDelegateParent.jsm",
|
||||||
"SelectionActionDelegateChild.jsm",
|
"SelectionActionDelegateChild.jsm",
|
||||||
|
"SelectionActionDelegateParent.jsm",
|
||||||
]
|
]
|
||||||
|
|
|
@ -468,7 +468,12 @@ class ModuleInfo {
|
||||||
throw new Error(`No impl for message: ${aMessage.name}.`);
|
throw new Error(`No impl for message: ${aMessage.name}.`);
|
||||||
}
|
}
|
||||||
|
|
||||||
this._impl.receiveMessage(aMessage);
|
try {
|
||||||
|
this._impl.receiveMessage(aMessage);
|
||||||
|
} catch (error) {
|
||||||
|
warn`this._impl.receiveMessage failed ${aMessage.name}`;
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onContentModuleLoaded() {
|
onContentModuleLoaded() {
|
||||||
|
@ -613,6 +618,9 @@ function startup() {
|
||||||
onEnable: {
|
onEnable: {
|
||||||
actors: {
|
actors: {
|
||||||
ScrollDelegate: {
|
ScrollDelegate: {
|
||||||
|
parent: {
|
||||||
|
moduleURI: "resource:///actors/ScrollDelegateParent.jsm",
|
||||||
|
},
|
||||||
child: {
|
child: {
|
||||||
moduleURI: "resource:///actors/ScrollDelegateChild.jsm",
|
moduleURI: "resource:///actors/ScrollDelegateChild.jsm",
|
||||||
events: {
|
events: {
|
||||||
|
@ -627,8 +635,12 @@ function startup() {
|
||||||
{
|
{
|
||||||
name: "GeckoViewSelectionAction",
|
name: "GeckoViewSelectionAction",
|
||||||
onEnable: {
|
onEnable: {
|
||||||
|
resource: "resource://gre/modules/GeckoViewSelectionAction.jsm",
|
||||||
actors: {
|
actors: {
|
||||||
SelectionActionDelegate: {
|
SelectionActionDelegate: {
|
||||||
|
parent: {
|
||||||
|
moduleURI: "resource:///actors/SelectionActionDelegateParent.jsm",
|
||||||
|
},
|
||||||
child: {
|
child: {
|
||||||
moduleURI: "resource:///actors/SelectionActionDelegateChild.jsm",
|
moduleURI: "resource:///actors/SelectionActionDelegateChild.jsm",
|
||||||
events: {
|
events: {
|
||||||
|
@ -679,6 +691,9 @@ function startup() {
|
||||||
onInit: {
|
onInit: {
|
||||||
actors: {
|
actors: {
|
||||||
GeckoViewAutoFill: {
|
GeckoViewAutoFill: {
|
||||||
|
parent: {
|
||||||
|
moduleURI: "resource:///actors/GeckoViewAutoFillParent.jsm",
|
||||||
|
},
|
||||||
child: {
|
child: {
|
||||||
moduleURI: "resource:///actors/GeckoViewAutoFillChild.jsm",
|
moduleURI: "resource:///actors/GeckoViewAutoFillChild.jsm",
|
||||||
events: {
|
events: {
|
||||||
|
@ -721,6 +736,9 @@ function startup() {
|
||||||
resource: "resource://gre/modules/GeckoViewMediaControl.jsm",
|
resource: "resource://gre/modules/GeckoViewMediaControl.jsm",
|
||||||
actors: {
|
actors: {
|
||||||
MediaControlDelegate: {
|
MediaControlDelegate: {
|
||||||
|
parent: {
|
||||||
|
moduleURI: "resource:///actors/MediaControlDelegateParent.jsm",
|
||||||
|
},
|
||||||
child: {
|
child: {
|
||||||
moduleURI: "resource:///actors/MediaControlDelegateChild.jsm",
|
moduleURI: "resource:///actors/MediaControlDelegateChild.jsm",
|
||||||
events: {
|
events: {
|
||||||
|
|
|
@ -8,301 +8,38 @@ var EXPORTED_SYMBOLS = ["GeckoViewPermission"];
|
||||||
const { XPCOMUtils } = ChromeUtils.import(
|
const { XPCOMUtils } = ChromeUtils.import(
|
||||||
"resource://gre/modules/XPCOMUtils.jsm"
|
"resource://gre/modules/XPCOMUtils.jsm"
|
||||||
);
|
);
|
||||||
|
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
|
||||||
XPCOMUtils.defineLazyModuleGetters(this, {
|
const { GeckoViewUtils } = ChromeUtils.import(
|
||||||
E10SUtils: "resource://gre/modules/E10SUtils.jsm",
|
"resource://gre/modules/GeckoViewUtils.jsm"
|
||||||
GeckoViewUtils: "resource://gre/modules/GeckoViewUtils.jsm",
|
);
|
||||||
Services: "resource://gre/modules/Services.jsm",
|
|
||||||
});
|
|
||||||
|
|
||||||
// See: http://developer.android.com/reference/android/Manifest.permission.html
|
|
||||||
const PERM_ACCESS_FINE_LOCATION = "android.permission.ACCESS_FINE_LOCATION";
|
|
||||||
const PERM_CAMERA = "android.permission.CAMERA";
|
|
||||||
const PERM_RECORD_AUDIO = "android.permission.RECORD_AUDIO";
|
|
||||||
|
|
||||||
class GeckoViewPermission {
|
class GeckoViewPermission {
|
||||||
constructor() {
|
constructor() {
|
||||||
this.wrappedJSObject = this;
|
this.wrappedJSObject = this;
|
||||||
}
|
}
|
||||||
|
|
||||||
_appPermissions = {};
|
async prompt(aRequest) {
|
||||||
|
const window = aRequest.window
|
||||||
|
? aRequest.window
|
||||||
|
: aRequest.element.ownerGlobal;
|
||||||
|
|
||||||
/* ---------- nsIObserver ---------- */
|
const actor = window.windowGlobalChild.getActor("GeckoViewPermission");
|
||||||
observe(aSubject, aTopic, aData) {
|
const result = await actor.promptPermission(aRequest);
|
||||||
switch (aTopic) {
|
if (!result.allow) {
|
||||||
case "getUserMedia:ask-device-permission": {
|
aRequest.cancel();
|
||||||
this.handleMediaAskDevicePermission(aData, aSubject);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case "getUserMedia:request": {
|
|
||||||
this.handleMediaRequest(aSubject);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case "PeerConnection:request": {
|
|
||||||
this.handlePeerConnectionRequest(aSubject);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
receiveMessage(aMsg) {
|
|
||||||
switch (aMsg.name) {
|
|
||||||
case "GeckoView:AddCameraPermission": {
|
|
||||||
const principal = Services.scriptSecurityManager.createContentPrincipalFromOrigin(
|
|
||||||
aMsg.data.origin
|
|
||||||
);
|
|
||||||
|
|
||||||
// Although the lifetime is "session" it will be removed upon
|
|
||||||
// use so it's more of a one-shot.
|
|
||||||
Services.perms.addFromPrincipal(
|
|
||||||
principal,
|
|
||||||
"MediaManagerVideo",
|
|
||||||
Services.perms.ALLOW_ACTION,
|
|
||||||
Services.perms.EXPIRE_SESSION
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
handleMediaAskDevicePermission(aType, aCallback) {
|
|
||||||
const perms = [];
|
|
||||||
if (aType === "video" || aType === "all") {
|
|
||||||
perms.push(PERM_CAMERA);
|
|
||||||
}
|
|
||||||
if (aType === "audio" || aType === "all") {
|
|
||||||
perms.push(PERM_RECORD_AUDIO);
|
|
||||||
}
|
|
||||||
|
|
||||||
const [dispatcher] = GeckoViewUtils.getActiveDispatcherAndWindow();
|
|
||||||
const callback = _ => {
|
|
||||||
Services.obs.notifyObservers(
|
|
||||||
aCallback,
|
|
||||||
"getUserMedia:got-device-permission"
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
if (dispatcher) {
|
|
||||||
this.getAppPermissions(dispatcher, perms).then(callback, callback);
|
|
||||||
} else {
|
} else {
|
||||||
// No dispatcher; just bail.
|
// Note: permission could be undefined, that's what aRequest expects.
|
||||||
callback();
|
const { permission } = result;
|
||||||
|
aRequest.allow(permission);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleMediaRequest(aRequest) {
|
|
||||||
const constraints = aRequest.getConstraints();
|
|
||||||
const { callID, devices } = aRequest;
|
|
||||||
const denyRequest = _ => {
|
|
||||||
Services.obs.notifyObservers(null, "getUserMedia:response:deny", callID);
|
|
||||||
};
|
|
||||||
|
|
||||||
const win = Services.wm.getOuterWindowWithId(aRequest.windowID);
|
|
||||||
// Release the request first.
|
|
||||||
aRequest = undefined;
|
|
||||||
Promise.resolve()
|
|
||||||
.then(() => {
|
|
||||||
if (win.closed) {
|
|
||||||
return Promise.resolve();
|
|
||||||
}
|
|
||||||
|
|
||||||
const sources = devices.map(device => {
|
|
||||||
device = device.QueryInterface(Ci.nsIMediaDevice);
|
|
||||||
return {
|
|
||||||
type: device.type,
|
|
||||||
id: device.rawId,
|
|
||||||
rawId: device.rawId,
|
|
||||||
name: device.rawName, // unfiltered device name to show to the user
|
|
||||||
mediaSource: device.mediaSource,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
if (
|
|
||||||
constraints.video &&
|
|
||||||
!sources.some(source => source.type === "videoinput")
|
|
||||||
) {
|
|
||||||
throw new Error("no video source");
|
|
||||||
} else if (
|
|
||||||
constraints.audio &&
|
|
||||||
!sources.some(source => source.type === "audioinput")
|
|
||||||
) {
|
|
||||||
throw new Error("no audio source");
|
|
||||||
}
|
|
||||||
|
|
||||||
const dispatcher = GeckoViewUtils.getDispatcherForWindow(win);
|
|
||||||
const uri = win.top.document.documentURIObject;
|
|
||||||
return dispatcher
|
|
||||||
.sendRequestForResult({
|
|
||||||
type: "GeckoView:MediaPermission",
|
|
||||||
uri: uri.displaySpec,
|
|
||||||
video: constraints.video
|
|
||||||
? sources.filter(source => source.type === "videoinput")
|
|
||||||
: null,
|
|
||||||
audio: constraints.audio
|
|
||||||
? sources.filter(source => source.type === "audioinput")
|
|
||||||
: null,
|
|
||||||
})
|
|
||||||
.then(response => {
|
|
||||||
if (!response) {
|
|
||||||
// Rejected.
|
|
||||||
denyRequest();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const allowedDevices = Cc["@mozilla.org/array;1"].createInstance(
|
|
||||||
Ci.nsIMutableArray
|
|
||||||
);
|
|
||||||
if (constraints.video) {
|
|
||||||
const video = devices.find(
|
|
||||||
device => response.video === device.rawId
|
|
||||||
);
|
|
||||||
if (!video) {
|
|
||||||
throw new Error("invalid video id");
|
|
||||||
}
|
|
||||||
Services.cpmm.sendAsyncMessage("GeckoView:AddCameraPermission", {
|
|
||||||
origin: win.top.document.nodePrincipal.origin,
|
|
||||||
});
|
|
||||||
allowedDevices.appendElement(video);
|
|
||||||
}
|
|
||||||
if (constraints.audio) {
|
|
||||||
const audio = devices.find(
|
|
||||||
device => response.audio === device.rawId
|
|
||||||
);
|
|
||||||
if (!audio) {
|
|
||||||
throw new Error("invalid audio id");
|
|
||||||
}
|
|
||||||
allowedDevices.appendElement(audio);
|
|
||||||
}
|
|
||||||
Services.obs.notifyObservers(
|
|
||||||
allowedDevices,
|
|
||||||
"getUserMedia:response:allow",
|
|
||||||
callID
|
|
||||||
);
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
Cu.reportError("Media device error: " + error);
|
|
||||||
denyRequest();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
handlePeerConnectionRequest(aRequest) {
|
|
||||||
Services.obs.notifyObservers(
|
|
||||||
null,
|
|
||||||
"PeerConnection:response:allow",
|
|
||||||
aRequest.callID
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
checkAppPermissions(aPerms) {
|
|
||||||
return aPerms.every(perm => this._appPermissions[perm]);
|
|
||||||
}
|
|
||||||
|
|
||||||
getAppPermissions(aDispatcher, aPerms) {
|
|
||||||
const perms = aPerms.filter(perm => !this._appPermissions[perm]);
|
|
||||||
if (!perms.length) {
|
|
||||||
return Promise.resolve(/* granted */ true);
|
|
||||||
}
|
|
||||||
return aDispatcher
|
|
||||||
.sendRequestForResult({
|
|
||||||
type: "GeckoView:AndroidPermission",
|
|
||||||
perms,
|
|
||||||
})
|
|
||||||
.then(granted => {
|
|
||||||
if (granted) {
|
|
||||||
for (const perm of perms) {
|
|
||||||
this._appPermissions[perm] = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return granted;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
prompt(aRequest) {
|
|
||||||
// Only allow exactly one permission request here.
|
|
||||||
const types = aRequest.types.QueryInterface(Ci.nsIArray);
|
|
||||||
if (types.length !== 1) {
|
|
||||||
aRequest.cancel();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const perm = types.queryElementAt(0, Ci.nsIContentPermissionType);
|
|
||||||
if (
|
|
||||||
perm.type === "desktop-notification" &&
|
|
||||||
!aRequest.hasValidTransientUserGestureActivation &&
|
|
||||||
Services.prefs.getBoolPref(
|
|
||||||
"dom.webnotifications.requireuserinteraction",
|
|
||||||
true
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
// We need user interaction and don't have it.
|
|
||||||
aRequest.cancel();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const dispatcher = GeckoViewUtils.getDispatcherForWindow(
|
|
||||||
aRequest.window ? aRequest.window : aRequest.element.ownerGlobal
|
|
||||||
);
|
|
||||||
const principal =
|
|
||||||
perm.type == "storage-access"
|
|
||||||
? aRequest.principal
|
|
||||||
: aRequest.topLevelPrincipal;
|
|
||||||
dispatcher
|
|
||||||
.sendRequestForResult({
|
|
||||||
type: "GeckoView:ContentPermission",
|
|
||||||
uri: principal.URI.displaySpec,
|
|
||||||
thirdPartyOrigin: aRequest.principal.origin,
|
|
||||||
principal: E10SUtils.serializePrincipal(principal),
|
|
||||||
perm: perm.type,
|
|
||||||
value: perm.capability,
|
|
||||||
contextId: principal.originAttributes.geckoViewSessionContextId ?? null,
|
|
||||||
privateMode: principal.privateBrowsingId != 0,
|
|
||||||
})
|
|
||||||
.then(value => {
|
|
||||||
if (value == Services.perms.ALLOW_ACTION) {
|
|
||||||
// Ask for app permission after asking for content permission.
|
|
||||||
if (perm.type === "geolocation") {
|
|
||||||
return this.getAppPermissions(dispatcher, [
|
|
||||||
PERM_ACCESS_FINE_LOCATION,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return value;
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
Cu.reportError("Permission error: " + error);
|
|
||||||
return /* value */ Services.perms.DENY_ACTION;
|
|
||||||
})
|
|
||||||
.then(value => {
|
|
||||||
// The storage access code adds itself to the perm manager; no need for us to do it.
|
|
||||||
if (perm.type == "storage-access") {
|
|
||||||
if (value == Services.perms.ALLOW_ACTION) {
|
|
||||||
aRequest.allow({ "storage-access": "allow" });
|
|
||||||
} else {
|
|
||||||
aRequest.cancel();
|
|
||||||
}
|
|
||||||
aRequest = undefined;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
(value == Services.perms.ALLOW_ACTION
|
|
||||||
? aRequest.allow
|
|
||||||
: aRequest.cancel)();
|
|
||||||
Services.perms.addFromPrincipal(
|
|
||||||
principal,
|
|
||||||
perm.type,
|
|
||||||
value,
|
|
||||||
Services.perms.EXPIRE_NEVER
|
|
||||||
);
|
|
||||||
// Manually release the target request here to facilitate garbage collection.
|
|
||||||
aRequest = undefined;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
GeckoViewPermission.prototype.classID = Components.ID(
|
GeckoViewPermission.prototype.classID = Components.ID(
|
||||||
"{42f3c238-e8e8-4015-9ca2-148723a8afcf}"
|
"{42f3c238-e8e8-4015-9ca2-148723a8afcf}"
|
||||||
);
|
);
|
||||||
GeckoViewPermission.prototype.QueryInterface = ChromeUtils.generateQI([
|
GeckoViewPermission.prototype.QueryInterface = ChromeUtils.generateQI([
|
||||||
"nsIObserver",
|
|
||||||
"nsIContentPermissionPrompt",
|
"nsIContentPermissionPrompt",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
const { debug, warn } = GeckoViewUtils.initLogging("GeckoViewPermission");
|
||||||
|
|
|
@ -34,15 +34,8 @@ class GeckoViewPrompter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this._domWin) {
|
if (!this._domWin) {
|
||||||
this._dispatcher = GeckoViewUtils.getDispatcherForWindow(this._domWin);
|
this._domWin = Services.wm.getMostRecentWindow("navigator:geckoview");
|
||||||
}
|
|
||||||
|
|
||||||
if (!this._dispatcher) {
|
|
||||||
[
|
|
||||||
this._dispatcher,
|
|
||||||
this._domWin,
|
|
||||||
] = GeckoViewUtils.getActiveDispatcherAndWindow();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this._innerWindowId = this._domWin?.browsingContext.currentWindowContext.innerWindowId;
|
this._innerWindowId = this._domWin?.browsingContext.currentWindowContext.innerWindowId;
|
||||||
|
@ -88,8 +81,7 @@ class GeckoViewPrompter {
|
||||||
}
|
}
|
||||||
|
|
||||||
_dismissUi() {
|
_dismissUi() {
|
||||||
this.prompterActor?.unregisterPrompt(this);
|
this.prompterActor?.dismissPrompt(this);
|
||||||
this._dispatcher.dispatch("GeckoView:Prompt:Dismiss", { id: this.id });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
accept(aInputText = this.inputText) {
|
accept(aInputText = this.inputText) {
|
||||||
|
@ -186,46 +178,35 @@ class GeckoViewPrompter {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
asyncShowPrompt(aMsg, aCallback) {
|
async asyncShowPrompt(aMsg, aCallback) {
|
||||||
let handled = false;
|
|
||||||
this.message = aMsg;
|
this.message = aMsg;
|
||||||
this.inputText = aMsg.value;
|
this.inputText = aMsg.value;
|
||||||
this.callback = aCallback;
|
this.callback = aCallback;
|
||||||
this.prompterActor?.registerPrompt(this);
|
|
||||||
|
|
||||||
const onResponse = response => {
|
|
||||||
if (handled) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!this.checkInnerWindow()) {
|
|
||||||
// Page has navigated away, let's dismiss the prompt
|
|
||||||
aCallback(null);
|
|
||||||
} else {
|
|
||||||
aCallback(response);
|
|
||||||
}
|
|
||||||
// This callback object is tied to the Java garbage collector because
|
|
||||||
// it is invoked from Java. Manually release the target callback
|
|
||||||
// here; otherwise we may hold onto resources for too long, because
|
|
||||||
// we would be relying on both the Java and the JS garbage collectors
|
|
||||||
// to run.
|
|
||||||
aMsg = undefined;
|
|
||||||
aCallback = undefined;
|
|
||||||
handled = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!this._dispatcher || !this.checkInnerWindow()) {
|
|
||||||
onResponse(null);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
aMsg.id = this.id;
|
aMsg.id = this.id;
|
||||||
this._dispatcher.dispatch("GeckoView:Prompt", aMsg, {
|
|
||||||
onSuccess: onResponse,
|
let response = null;
|
||||||
onError: error => {
|
try {
|
||||||
Cu.reportError("Prompt error: " + error);
|
if (this.checkInnerWindow()) {
|
||||||
onResponse(null);
|
response = await this.prompterActor.prompt(this, aMsg);
|
||||||
},
|
}
|
||||||
});
|
} catch (error) {
|
||||||
this.prompterActor?.notifyPromptShow(this);
|
// Nothing we can do really, we will treat this as a dismiss.
|
||||||
|
warn`Error while prompting: ${error}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.checkInnerWindow()) {
|
||||||
|
// Page has navigated away, let's dismiss the prompt
|
||||||
|
aCallback(null);
|
||||||
|
} else {
|
||||||
|
aCallback(response);
|
||||||
|
}
|
||||||
|
// This callback object is tied to the Java garbage collector because
|
||||||
|
// it is invoked from Java. Manually release the target callback
|
||||||
|
// here; otherwise we may hold onto resources for too long, because
|
||||||
|
// we would be relying on both the Java and the JS garbage collectors
|
||||||
|
// to run.
|
||||||
|
aMsg = undefined;
|
||||||
|
aCallback = undefined;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,13 +29,43 @@ function InitLater(fn, object, name) {
|
||||||
return DelayedInit.schedule(fn, object, name, 15000 /* 15s max wait */);
|
return DelayedInit.schedule(fn, object, name, 15000 /* 15s max wait */);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const JSPROCESSACTORS = {
|
||||||
|
GeckoViewPermissionProcess: {
|
||||||
|
parent: {
|
||||||
|
moduleURI: "resource:///actors/GeckoViewPermissionProcessParent.jsm",
|
||||||
|
},
|
||||||
|
child: {
|
||||||
|
moduleURI: "resource:///actors/GeckoViewPermissionProcessChild.jsm",
|
||||||
|
observers: [
|
||||||
|
"getUserMedia:ask-device-permission",
|
||||||
|
"getUserMedia:request",
|
||||||
|
"recording-device-events",
|
||||||
|
"PeerConnection:request",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
const JSWINDOWACTORS = {
|
const JSWINDOWACTORS = {
|
||||||
LoadURIDelegate: {
|
LoadURIDelegate: {
|
||||||
|
parent: {
|
||||||
|
moduleURI: "resource:///actors/LoadURIDelegateParent.jsm",
|
||||||
|
},
|
||||||
child: {
|
child: {
|
||||||
moduleURI: "resource:///actors/LoadURIDelegateChild.jsm",
|
moduleURI: "resource:///actors/LoadURIDelegateChild.jsm",
|
||||||
},
|
},
|
||||||
messageManagerGroups: ["browsers"],
|
messageManagerGroups: ["browsers"],
|
||||||
},
|
},
|
||||||
|
GeckoViewPermission: {
|
||||||
|
parent: {
|
||||||
|
moduleURI: "resource:///actors/GeckoViewPermissionParent.jsm",
|
||||||
|
},
|
||||||
|
child: {
|
||||||
|
moduleURI: "resource:///actors/GeckoViewPermissionChild.jsm",
|
||||||
|
},
|
||||||
|
allFrames: true,
|
||||||
|
includeChrome: true,
|
||||||
|
},
|
||||||
GeckoViewPrompt: {
|
GeckoViewPrompt: {
|
||||||
child: {
|
child: {
|
||||||
moduleURI: "resource:///actors/GeckoViewPromptChild.jsm",
|
moduleURI: "resource:///actors/GeckoViewPromptChild.jsm",
|
||||||
|
@ -70,55 +100,10 @@ class GeckoViewStartup {
|
||||||
switch (aTopic) {
|
switch (aTopic) {
|
||||||
case "content-process-ready-for-script":
|
case "content-process-ready-for-script":
|
||||||
case "app-startup": {
|
case "app-startup": {
|
||||||
// Parent and content process.
|
|
||||||
GeckoViewUtils.addLazyGetter(this, "GeckoViewPermission", {
|
|
||||||
service: "@mozilla.org/content-permission/prompt;1",
|
|
||||||
observers: [
|
|
||||||
"getUserMedia:ask-device-permission",
|
|
||||||
"getUserMedia:request",
|
|
||||||
"PeerConnection:request",
|
|
||||||
],
|
|
||||||
ppmm: ["GeckoView:AddCameraPermission"],
|
|
||||||
});
|
|
||||||
|
|
||||||
GeckoViewUtils.addLazyGetter(this, "GeckoViewRecordingMedia", {
|
|
||||||
module: "resource://gre/modules/GeckoViewMedia.jsm",
|
|
||||||
observers: ["recording-device-events"],
|
|
||||||
});
|
|
||||||
|
|
||||||
GeckoViewUtils.addLazyGetter(this, "GeckoViewConsole", {
|
GeckoViewUtils.addLazyGetter(this, "GeckoViewConsole", {
|
||||||
module: "resource://gre/modules/GeckoViewConsole.jsm",
|
module: "resource://gre/modules/GeckoViewConsole.jsm",
|
||||||
});
|
});
|
||||||
|
|
||||||
GeckoViewUtils.addLazyGetter(this, "GeckoViewWebExtension", {
|
|
||||||
module: "resource://gre/modules/GeckoViewWebExtension.jsm",
|
|
||||||
ged: [
|
|
||||||
"GeckoView:ActionDelegate:Attached",
|
|
||||||
"GeckoView:BrowserAction:Click",
|
|
||||||
"GeckoView:PageAction:Click",
|
|
||||||
"GeckoView:RegisterWebExtension",
|
|
||||||
"GeckoView:UnregisterWebExtension",
|
|
||||||
"GeckoView:WebExtension:CancelInstall",
|
|
||||||
"GeckoView:WebExtension:Disable",
|
|
||||||
"GeckoView:WebExtension:Enable",
|
|
||||||
"GeckoView:WebExtension:EnsureBuiltIn",
|
|
||||||
"GeckoView:WebExtension:Get",
|
|
||||||
"GeckoView:WebExtension:Install",
|
|
||||||
"GeckoView:WebExtension:InstallBuiltIn",
|
|
||||||
"GeckoView:WebExtension:List",
|
|
||||||
"GeckoView:WebExtension:PortDisconnect",
|
|
||||||
"GeckoView:WebExtension:PortMessageFromApp",
|
|
||||||
"GeckoView:WebExtension:SetPBAllowed",
|
|
||||||
"GeckoView:WebExtension:Uninstall",
|
|
||||||
"GeckoView:WebExtension:Update",
|
|
||||||
],
|
|
||||||
observers: [
|
|
||||||
"devtools-installed-addon",
|
|
||||||
"testing-installed-addon",
|
|
||||||
"testing-uninstalled-addon",
|
|
||||||
],
|
|
||||||
});
|
|
||||||
|
|
||||||
GeckoViewUtils.addLazyGetter(this, "GeckoViewStorageController", {
|
GeckoViewUtils.addLazyGetter(this, "GeckoViewStorageController", {
|
||||||
module: "resource://gre/modules/GeckoViewStorageController.jsm",
|
module: "resource://gre/modules/GeckoViewStorageController.jsm",
|
||||||
ged: [
|
ged: [
|
||||||
|
@ -153,6 +138,36 @@ class GeckoViewStartup {
|
||||||
Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_DEFAULT
|
Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_DEFAULT
|
||||||
) {
|
) {
|
||||||
ActorManagerParent.addJSWindowActors(JSWINDOWACTORS);
|
ActorManagerParent.addJSWindowActors(JSWINDOWACTORS);
|
||||||
|
ActorManagerParent.addJSProcessActors(JSPROCESSACTORS);
|
||||||
|
|
||||||
|
GeckoViewUtils.addLazyGetter(this, "GeckoViewWebExtension", {
|
||||||
|
module: "resource://gre/modules/GeckoViewWebExtension.jsm",
|
||||||
|
ged: [
|
||||||
|
"GeckoView:ActionDelegate:Attached",
|
||||||
|
"GeckoView:BrowserAction:Click",
|
||||||
|
"GeckoView:PageAction:Click",
|
||||||
|
"GeckoView:RegisterWebExtension",
|
||||||
|
"GeckoView:UnregisterWebExtension",
|
||||||
|
"GeckoView:WebExtension:CancelInstall",
|
||||||
|
"GeckoView:WebExtension:Disable",
|
||||||
|
"GeckoView:WebExtension:Enable",
|
||||||
|
"GeckoView:WebExtension:EnsureBuiltIn",
|
||||||
|
"GeckoView:WebExtension:Get",
|
||||||
|
"GeckoView:WebExtension:Install",
|
||||||
|
"GeckoView:WebExtension:InstallBuiltIn",
|
||||||
|
"GeckoView:WebExtension:List",
|
||||||
|
"GeckoView:WebExtension:PortDisconnect",
|
||||||
|
"GeckoView:WebExtension:PortMessageFromApp",
|
||||||
|
"GeckoView:WebExtension:SetPBAllowed",
|
||||||
|
"GeckoView:WebExtension:Uninstall",
|
||||||
|
"GeckoView:WebExtension:Update",
|
||||||
|
],
|
||||||
|
observers: [
|
||||||
|
"devtools-installed-addon",
|
||||||
|
"testing-installed-addon",
|
||||||
|
"testing-uninstalled-addon",
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
GeckoViewUtils.addLazyGetter(this, "ChildCrashHandler", {
|
GeckoViewUtils.addLazyGetter(this, "ChildCrashHandler", {
|
||||||
module: "resource://gre/modules/ChildCrashHandler.jsm",
|
module: "resource://gre/modules/ChildCrashHandler.jsm",
|
||||||
|
|
|
@ -310,7 +310,15 @@ package org.mozilla.geckoview {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static interface Autofill.Delegate {
|
public static interface Autofill.Delegate {
|
||||||
method @UiThread default public void onAutofill(@NonNull GeckoSession, int, @Nullable Autofill.Node);
|
method @Deprecated @DeprecationSchedule(id="autofill-node",version=104) @UiThread default public void onAutofill(@NonNull GeckoSession, int, @Nullable Autofill.Node);
|
||||||
|
method @UiThread default public void onNodeAdd(@NonNull GeckoSession, @NonNull Autofill.Node, @NonNull Autofill.NodeData);
|
||||||
|
method @UiThread default public void onNodeBlur(@NonNull GeckoSession, @NonNull Autofill.Node, @NonNull Autofill.NodeData);
|
||||||
|
method @UiThread default public void onNodeFocus(@NonNull GeckoSession, @NonNull Autofill.Node, @NonNull Autofill.NodeData);
|
||||||
|
method @UiThread default public void onNodeRemove(@NonNull GeckoSession, @NonNull Autofill.Node, @NonNull Autofill.NodeData);
|
||||||
|
method @UiThread default public void onNodeUpdate(@NonNull GeckoSession, @NonNull Autofill.Node, @NonNull Autofill.NodeData);
|
||||||
|
method @UiThread default public void onSessionCancel(@NonNull GeckoSession);
|
||||||
|
method @UiThread default public void onSessionCommit(@NonNull GeckoSession, @NonNull Autofill.Node, @NonNull Autofill.NodeData);
|
||||||
|
method @UiThread default public void onSessionStart(@NonNull GeckoSession);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final class Autofill.Hint {
|
public static final class Autofill.Hint {
|
||||||
|
@ -331,7 +339,7 @@ package org.mozilla.geckoview {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final class Autofill.Node {
|
public static final class Autofill.Node {
|
||||||
method @UiThread public void fillViewStructure(@NonNull View, @NonNull ViewStructure, int);
|
method @Deprecated @DeprecationSchedule(id="autofill-node",version=104) @UiThread public void fillViewStructure(@NonNull View, @NonNull ViewStructure, int);
|
||||||
method @AnyThread @Nullable public String getAttribute(@NonNull String);
|
method @AnyThread @Nullable public String getAttribute(@NonNull String);
|
||||||
method @AnyThread @NonNull public Map<String,String> getAttributes();
|
method @AnyThread @NonNull public Map<String,String> getAttributes();
|
||||||
method @AnyThread @NonNull public Collection<Autofill.Node> getChildren();
|
method @AnyThread @NonNull public Collection<Autofill.Node> getChildren();
|
||||||
|
@ -339,16 +347,21 @@ package org.mozilla.geckoview {
|
||||||
method @AnyThread @NonNull public String getDomain();
|
method @AnyThread @NonNull public String getDomain();
|
||||||
method @AnyThread public boolean getEnabled();
|
method @AnyThread public boolean getEnabled();
|
||||||
method @AnyThread public boolean getFocusable();
|
method @AnyThread public boolean getFocusable();
|
||||||
method @AnyThread public boolean getFocused();
|
method @AnyThread @Deprecated @DeprecationSchedule(id="autofill-node",version=104) public boolean getFocused();
|
||||||
method @AnyThread public int getHint();
|
method @AnyThread public int getHint();
|
||||||
method @AnyThread public int getId();
|
method @AnyThread @Deprecated @DeprecationSchedule(id="autofill-node",version=104) public int getId();
|
||||||
method @AnyThread public int getInputType();
|
method @AnyThread public int getInputType();
|
||||||
method @AnyThread @NonNull public String getTag();
|
method @AnyThread @NonNull public String getTag();
|
||||||
method @AnyThread @NonNull public String getValue();
|
method @AnyThread @Deprecated @DeprecationSchedule(id="autofill-node",version=104) @NonNull public String getValue();
|
||||||
method @AnyThread public boolean getVisible();
|
method @AnyThread @Deprecated @DeprecationSchedule(id="autofill-node",version=104) public boolean getVisible();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final class Autofill.Notify {
|
public static class Autofill.NodeData {
|
||||||
|
method @AnyThread public int getId();
|
||||||
|
method @AnyThread @Nullable public String getValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Deprecated @DeprecationSchedule(id="autofill-node",version=104) public static final class Autofill.Notify {
|
||||||
method @AnyThread @Nullable public static String toString(int);
|
method @AnyThread @Nullable public static String toString(int);
|
||||||
field public static final int NODE_ADDED = 3;
|
field public static final int NODE_ADDED = 3;
|
||||||
field public static final int NODE_BLURRED = 7;
|
field public static final int NODE_BLURRED = 7;
|
||||||
|
@ -361,9 +374,14 @@ package org.mozilla.geckoview {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final class Autofill.Session {
|
public static final class Autofill.Session {
|
||||||
|
method @NonNull @UiThread public Autofill.NodeData dataFor(@NonNull Autofill.Node);
|
||||||
method @UiThread public void fillViewStructure(@NonNull View, @NonNull ViewStructure, int);
|
method @UiThread public void fillViewStructure(@NonNull View, @NonNull ViewStructure, int);
|
||||||
method @AnyThread @NonNull public Rect getDefaultDimensions();
|
method @UiThread public void fillViewStructure(@NonNull Autofill.Node, @NonNull View, @NonNull ViewStructure, int);
|
||||||
|
method @NonNull @UiThread public Rect getDefaultDimensions();
|
||||||
|
method @Nullable @UiThread public Autofill.Node getFocused();
|
||||||
|
method @Nullable @UiThread public Autofill.NodeData getFocusedData();
|
||||||
method @AnyThread @NonNull public Autofill.Node getRoot();
|
method @AnyThread @NonNull public Autofill.Node getRoot();
|
||||||
|
method @UiThread public boolean isVisible(@NonNull Autofill.Node);
|
||||||
}
|
}
|
||||||
|
|
||||||
@UiThread public class BasicSelectionActionDelegate implements ActionMode.Callback GeckoSession.SelectionActionDelegate {
|
@UiThread public class BasicSelectionActionDelegate implements ActionMode.Callback GeckoSession.SelectionActionDelegate {
|
||||||
|
|
|
@ -12,13 +12,6 @@
|
||||||
<input type="password" id="pass1" value="foo" autofocus>
|
<input type="password" id="pass1" value="foo" autofocus>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
</form>
|
</form>
|
||||||
<iframe id="iframe"></iframe>
|
<iframe id="iframe" src="forms2_iframe.html"></iframe>
|
||||||
<script>
|
|
||||||
addEventListener("load", function(e) {
|
|
||||||
if (window.parent === window) {
|
|
||||||
document.getElementById("iframe").contentWindow.location.href = window.location.href;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>Forms2 iframe</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<form>
|
||||||
|
<fieldset>
|
||||||
|
<input type="text" id="firstname">
|
||||||
|
<input type="text" id="lastname">
|
||||||
|
<input type="text" id="user1" value="foo">
|
||||||
|
<input type="password" id="pass1" value="foo" autofocus>
|
||||||
|
</fieldset>
|
||||||
|
</form>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -11,13 +11,6 @@
|
||||||
<input type="password">
|
<input type="password">
|
||||||
<input type="submit" value="submit">
|
<input type="submit" value="submit">
|
||||||
</form>
|
</form>
|
||||||
<iframe id="iframe"></iframe>
|
<iframe id="iframe" src="forms_autocomplete_iframe.html"></iframe>
|
||||||
<script>
|
|
||||||
addEventListener("load", function(e) {
|
|
||||||
if (window.parent === window) {
|
|
||||||
document.getElementById("iframe").contentWindow.location.href = window.location.href;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>Forms</title>
|
||||||
|
<meta name="viewport" content="minimum-scale=1,width=device-width">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<form>
|
||||||
|
<input type="text" autocomplete="email" autofocus>
|
||||||
|
<input type="text" autocomplete="username">
|
||||||
|
<input type="password">
|
||||||
|
<input type="submit" value="submit">
|
||||||
|
</form>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,61 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>Forms iframe</title>
|
||||||
|
<meta name="viewport" content="minimum-scale=1,width=device-width">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<form id="form1">
|
||||||
|
<input type="text" id="user1" value="foo">
|
||||||
|
<input type="password" id="pass1" value="foo">
|
||||||
|
<input type="email" id="email1" value="@">
|
||||||
|
<input type="number" id="number1" value="0">
|
||||||
|
<input type="tel" id="tel1" value="0">
|
||||||
|
<input type="submit" value="submit">
|
||||||
|
</form>
|
||||||
|
<input type="Text" id="user2" value="foo">
|
||||||
|
<input type="PassWord" id="pass2" maxlength="8" value="foo">
|
||||||
|
<input type="button" id="button1" value="foo"/>
|
||||||
|
<input type="checkbox" id="checkbox1"/>
|
||||||
|
<input type="search" id="search1">
|
||||||
|
<input type="url" id="url1">
|
||||||
|
<input type="hidden" id="hidden1" value="foo"/>
|
||||||
|
</body>
|
||||||
|
<script>
|
||||||
|
const params = (new URL(document.location)).searchParams;
|
||||||
|
|
||||||
|
function getEventInterface(event) {
|
||||||
|
if (event instanceof document.defaultView.InputEvent) {
|
||||||
|
return "InputEvent";
|
||||||
|
}
|
||||||
|
if (event instanceof document.defaultView.UIEvent) {
|
||||||
|
return "UIEvent";
|
||||||
|
}
|
||||||
|
if (event instanceof document.defaultView.Event) {
|
||||||
|
return "Event";
|
||||||
|
}
|
||||||
|
return "Unknown";
|
||||||
|
}
|
||||||
|
|
||||||
|
function getData(key, value) {
|
||||||
|
return new Promise(resolve =>
|
||||||
|
document.querySelector(key)
|
||||||
|
.addEventListener('input', event => {
|
||||||
|
resolve([
|
||||||
|
key,
|
||||||
|
event.target.value,
|
||||||
|
value,
|
||||||
|
getEventInterface(event),
|
||||||
|
]);
|
||||||
|
}, { once: true }))
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener('message', async event => {
|
||||||
|
const { data, source, origin } = event;
|
||||||
|
source.postMessage(
|
||||||
|
await getData(data.key, data.value),
|
||||||
|
origin);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</html>
|
|
@ -0,0 +1,68 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>Forms</title>
|
||||||
|
<meta name="viewport" content="minimum-scale=1,width=device-width">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<form id="form1">
|
||||||
|
<input type="text" id="user1" value="foo">
|
||||||
|
<input type="password" id="pass1" value="foo">
|
||||||
|
<input type="email" id="email1" value="@">
|
||||||
|
<input type="number" id="number1" value="0">
|
||||||
|
<input type="tel" id="tel1" value="0">
|
||||||
|
<input type="submit" value="submit">
|
||||||
|
</form>
|
||||||
|
<input type="Text" id="user2" value="foo">
|
||||||
|
<input type="PassWord" id="pass2" maxlength="8" value="foo">
|
||||||
|
<input type="button" id="button1" value="foo"/>
|
||||||
|
<input type="checkbox" id="checkbox1"/>
|
||||||
|
<input type="search" id="search1">
|
||||||
|
<input type="url" id="url1">
|
||||||
|
<input type="hidden" id="hidden1" value="foo"/>
|
||||||
|
|
||||||
|
<iframe id="iframe" src="http://example.org/tests/junit/forms_iframe.html"></iframe>
|
||||||
|
</body>
|
||||||
|
<script>
|
||||||
|
const params = (new URL(document.location)).searchParams;
|
||||||
|
const iframe = document.getElementById('iframe').contentWindow;
|
||||||
|
|
||||||
|
function getEventInterface(event) {
|
||||||
|
if (event instanceof document.defaultView.InputEvent) {
|
||||||
|
return "InputEvent";
|
||||||
|
}
|
||||||
|
if (event instanceof document.defaultView.UIEvent) {
|
||||||
|
return "UIEvent";
|
||||||
|
}
|
||||||
|
if (event instanceof document.defaultView.Event) {
|
||||||
|
return "Event";
|
||||||
|
}
|
||||||
|
return "Unknown";
|
||||||
|
}
|
||||||
|
|
||||||
|
function getData(key, value) {
|
||||||
|
return new Promise(resolve =>
|
||||||
|
document.querySelector(key)
|
||||||
|
.addEventListener('input', event => {
|
||||||
|
resolve([
|
||||||
|
key,
|
||||||
|
event.target.value,
|
||||||
|
value,
|
||||||
|
getEventInterface(event),
|
||||||
|
]);
|
||||||
|
}, { once: true }))
|
||||||
|
}
|
||||||
|
|
||||||
|
window.getDataForAllFrames = function(key, value) {
|
||||||
|
const data = [];
|
||||||
|
data.push(new Promise(resolve =>
|
||||||
|
window.addEventListener('message', event => {
|
||||||
|
resolve(event.data);
|
||||||
|
}, { once: true })));
|
||||||
|
iframe.postMessage({key, value}, "*");
|
||||||
|
data.push(getData(key, value));
|
||||||
|
return Promise.all(data);
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
</html>
|
|
@ -4,50 +4,58 @@
|
||||||
|
|
||||||
package org.mozilla.geckoview.test
|
package org.mozilla.geckoview.test
|
||||||
|
|
||||||
import android.graphics.Matrix
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.os.LocaleList
|
|
||||||
import androidx.test.filters.MediumTest
|
import androidx.test.filters.MediumTest
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
|
||||||
import android.util.Pair
|
|
||||||
import android.util.SparseArray
|
import android.util.SparseArray
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewStructure
|
|
||||||
import android.view.autofill.AutofillId
|
|
||||||
import android.view.autofill.AutofillValue
|
|
||||||
import org.hamcrest.Matchers.*
|
import org.hamcrest.Matchers.*
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
|
import org.junit.runners.Parameterized
|
||||||
import org.mozilla.geckoview.Autofill
|
import org.mozilla.geckoview.Autofill
|
||||||
import org.mozilla.geckoview.GeckoSession
|
import org.mozilla.geckoview.GeckoSession
|
||||||
import org.mozilla.geckoview.test.rule.GeckoSessionTestRule
|
import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.*
|
||||||
import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.AssertCalled
|
|
||||||
import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.WithDisplay
|
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4::class)
|
@RunWith(Parameterized::class)
|
||||||
@MediumTest
|
@MediumTest
|
||||||
class AutofillDelegateTest : BaseSessionTest() {
|
class AutofillDelegateTest : BaseSessionTest() {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
@get:Parameterized.Parameters(name = "{0}")
|
||||||
|
@JvmStatic
|
||||||
|
val parameters: List<Array<out Any>> = listOf(
|
||||||
|
arrayOf("#inProcess"), arrayOf("#oop"))
|
||||||
|
}
|
||||||
|
|
||||||
|
@field:Parameterized.Parameter(0) @JvmField var iframe: String = ""
|
||||||
|
|
||||||
|
// Whether the iframe is loaded in-process (i.e. with the same origin as the
|
||||||
|
// outer html page) or out-of-process.
|
||||||
|
private val pageUrl by lazy {
|
||||||
|
when (iframe) {
|
||||||
|
"#inProcess" -> "http://example.org/tests/junit/forms_xorigin.html"
|
||||||
|
"#oop" -> createTestUrl(FORMS_XORIGIN_HTML_PATH)
|
||||||
|
else -> throw IllegalStateException()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test fun autofillCommit() {
|
@Test fun autofillCommit() {
|
||||||
sessionRule.setPrefsUntilTestEnd(mapOf(
|
sessionRule.setPrefsUntilTestEnd(mapOf(
|
||||||
"signon.rememberSignons" to true,
|
"signon.rememberSignons" to true,
|
||||||
"signon.userInputRequiredToCapture.enabled" to false))
|
"signon.userInputRequiredToCapture.enabled" to false))
|
||||||
|
|
||||||
mainSession.loadTestPath(FORMS_HTML_PATH)
|
mainSession.loadUri(pageUrl)
|
||||||
// Wait for the auto-fill nodes to populate.
|
// Wait for the auto-fill nodes to populate.
|
||||||
sessionRule.waitUntilCalled(object : Autofill.Delegate {
|
sessionRule.waitUntilCalled(object : Autofill.Delegate, GeckoSession.ProgressDelegate {
|
||||||
// For the root document and the iframe document, each has a form group and
|
// We expect to get a call to onSessionStart and many calls to onNodeAdd depending
|
||||||
// a group for inputs outside of forms, so the total count is 4.
|
// on timing.
|
||||||
@AssertCalled(count = 4)
|
@AssertCalled(count = 1)
|
||||||
override fun onAutofill(session: GeckoSession,
|
override fun onSessionStart(session: GeckoSession) {}
|
||||||
notification: Int,
|
@AssertCalled(count = -1)
|
||||||
node: Autofill.Node?) {
|
override fun onNodeAdd(session: GeckoSession,
|
||||||
assertThat("Should be starting auto-fill",
|
node: Autofill.Node,
|
||||||
notification,
|
data: Autofill.NodeData) {}
|
||||||
equalTo(forEachCall(
|
@AssertCalled(count = 1)
|
||||||
Autofill.Notify.SESSION_STARTED,
|
override fun onPageStop(session: GeckoSession, success: Boolean) {}
|
||||||
Autofill.Notify.NODE_ADDED)))
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// Assign node values.
|
// Assign node values.
|
||||||
|
@ -60,34 +68,33 @@ class AutofillDelegateTest : BaseSessionTest() {
|
||||||
mainSession.evaluateJS("document.querySelector('#form1').submit()")
|
mainSession.evaluateJS("document.querySelector('#form1').submit()")
|
||||||
|
|
||||||
sessionRule.waitUntilCalled(object : Autofill.Delegate {
|
sessionRule.waitUntilCalled(object : Autofill.Delegate {
|
||||||
@AssertCalled(count = 5)
|
@AssertCalled(order = [1,2,3,4])
|
||||||
override fun onAutofill(session: GeckoSession,
|
override fun onNodeUpdate(session: GeckoSession,
|
||||||
notification: Int,
|
node: Autofill.Node,
|
||||||
node: Autofill.Node?) {
|
data: Autofill.NodeData) {
|
||||||
val info = sessionRule.currentCall
|
}
|
||||||
|
|
||||||
if (info.counter < 5) {
|
@AssertCalled(order = [5])
|
||||||
assertThat("Should be an update notification",
|
override fun onSessionCommit(session: GeckoSession,
|
||||||
notification,
|
node: Autofill.Node,
|
||||||
equalTo(Autofill.Notify.NODE_UPDATED))
|
data: Autofill.NodeData) {
|
||||||
} else {
|
val autofillSession = mainSession.autofillSession
|
||||||
assertThat("Should be a commit notification",
|
assertThat("Values should match",
|
||||||
notification,
|
countAutofillNodes({
|
||||||
equalTo(Autofill.Notify.SESSION_COMMITTED))
|
autofillSession.dataFor(it).value == "user1x" }),
|
||||||
|
equalTo(1))
|
||||||
assertThat("Values should match",
|
assertThat("Values should match",
|
||||||
countAutofillNodes({ it.value == "user1x" }),
|
countAutofillNodes({
|
||||||
equalTo(1))
|
autofillSession.dataFor(it).value == "pass1x" }),
|
||||||
assertThat("Values should match",
|
equalTo(1))
|
||||||
countAutofillNodes({ it.value == "pass1x" }),
|
assertThat("Values should match",
|
||||||
equalTo(1))
|
countAutofillNodes({
|
||||||
assertThat("Values should match",
|
autofillSession.dataFor(it).value == "e@mail.com" }),
|
||||||
countAutofillNodes({ it.value == "e@mail.com" }),
|
equalTo(1))
|
||||||
equalTo(1))
|
assertThat("Values should match",
|
||||||
assertThat("Values should match",
|
countAutofillNodes({
|
||||||
countAutofillNodes({ it.value == "1" }),
|
autofillSession.dataFor(it).value == "1" }),
|
||||||
equalTo(1))
|
equalTo(1))
|
||||||
}
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -99,17 +106,15 @@ class AutofillDelegateTest : BaseSessionTest() {
|
||||||
|
|
||||||
mainSession.loadTestPath(FORMS_ID_VALUE_HTML_PATH)
|
mainSession.loadTestPath(FORMS_ID_VALUE_HTML_PATH)
|
||||||
// Wait for the auto-fill nodes to populate.
|
// Wait for the auto-fill nodes to populate.
|
||||||
sessionRule.waitUntilCalled(object : Autofill.Delegate {
|
sessionRule.waitUntilCalled(object : Autofill.Delegate, GeckoSession.ProgressDelegate {
|
||||||
@AssertCalled(count = 1)
|
@AssertCalled(count = 1)
|
||||||
override fun onAutofill(session: GeckoSession,
|
override fun onSessionStart(session: GeckoSession) {}
|
||||||
notification: Int,
|
@AssertCalled(count = -1)
|
||||||
node: Autofill.Node?) {
|
override fun onNodeAdd(session: GeckoSession,
|
||||||
assertThat("Should be starting auto-fill",
|
node: Autofill.Node,
|
||||||
notification,
|
data: Autofill.NodeData) {}
|
||||||
equalTo(forEachCall(
|
@AssertCalled(count = 1)
|
||||||
Autofill.Notify.SESSION_STARTED,
|
override fun onPageStop(session: GeckoSession, success: Boolean) {}
|
||||||
Autofill.Notify.NODE_ADDED)))
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// Assign node values.
|
// Assign node values.
|
||||||
|
@ -119,25 +124,20 @@ class AutofillDelegateTest : BaseSessionTest() {
|
||||||
mainSession.evaluateJS("document.querySelector('#form1').submit()")
|
mainSession.evaluateJS("document.querySelector('#form1').submit()")
|
||||||
|
|
||||||
sessionRule.waitUntilCalled(object : Autofill.Delegate {
|
sessionRule.waitUntilCalled(object : Autofill.Delegate {
|
||||||
@AssertCalled(count = 2)
|
@AssertCalled(order = [1])
|
||||||
override fun onAutofill(session: GeckoSession,
|
override fun onNodeUpdate(session: GeckoSession,
|
||||||
notification: Int,
|
node: Autofill.Node,
|
||||||
node: Autofill.Node?) {
|
data: Autofill.NodeData) {
|
||||||
val info = sessionRule.currentCall
|
}
|
||||||
|
@AssertCalled(order = [2])
|
||||||
if (info.counter < 2) {
|
override fun onSessionCommit(session: GeckoSession,
|
||||||
assertThat("Should be an update notification",
|
node: Autofill.Node,
|
||||||
notification,
|
data: Autofill.NodeData) {
|
||||||
equalTo(Autofill.Notify.NODE_UPDATED))
|
assertThat("Values should match",
|
||||||
} else {
|
countAutofillNodes({
|
||||||
assertThat("Should be a commit notification",
|
mainSession.autofillSession.dataFor(it).value == "pass1x"
|
||||||
notification,
|
}),
|
||||||
equalTo(Autofill.Notify.SESSION_COMMITTED))
|
equalTo(1))
|
||||||
|
|
||||||
assertThat("Values should match",
|
|
||||||
countAutofillNodes({ it.value == "pass1x" }),
|
|
||||||
equalTo(1))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -145,16 +145,16 @@ class AutofillDelegateTest : BaseSessionTest() {
|
||||||
@Test fun autofill() {
|
@Test fun autofill() {
|
||||||
// Test parts of the Oreo auto-fill API; there is another autofill test in
|
// Test parts of the Oreo auto-fill API; there is another autofill test in
|
||||||
// SessionAccessibility for a11y auto-fill support.
|
// SessionAccessibility for a11y auto-fill support.
|
||||||
mainSession.loadTestPath(FORMS_HTML_PATH)
|
mainSession.loadUri(pageUrl)
|
||||||
// Wait for the auto-fill nodes to populate.
|
// Wait for the auto-fill nodes to populate.
|
||||||
sessionRule.waitUntilCalled(object : Autofill.Delegate {
|
sessionRule.waitUntilCalled(object : Autofill.Delegate, GeckoSession.ProgressDelegate {
|
||||||
// For the root document and the iframe document, each has a form group and
|
// We expect many call to onNodeAdd while loading the page
|
||||||
// a group for inputs outside of forms, so the total count is 4.
|
@AssertCalled(count = -1)
|
||||||
@AssertCalled(count = 4)
|
override fun onNodeAdd(session: GeckoSession,
|
||||||
override fun onAutofill(session: GeckoSession,
|
node: Autofill.Node,
|
||||||
notification: Int,
|
data: Autofill.NodeData) {}
|
||||||
node: Autofill.Node?) {
|
@AssertCalled(count = 1)
|
||||||
}
|
override fun onPageStop(session: GeckoSession, success: Boolean) {}
|
||||||
})
|
})
|
||||||
|
|
||||||
val autofills = mapOf(
|
val autofills = mapOf(
|
||||||
|
@ -163,45 +163,36 @@ class AutofillDelegateTest : BaseSessionTest() {
|
||||||
"#number1" to "24", "#tel1" to "42")
|
"#number1" to "24", "#tel1" to "42")
|
||||||
|
|
||||||
// Set up promises to monitor the values changing.
|
// Set up promises to monitor the values changing.
|
||||||
val promises = autofills.flatMap { entry ->
|
val promises = autofills.map { entry ->
|
||||||
// Repeat each test with both the top document and the iframe document.
|
// Repeat each test with both the top document and the iframe document.
|
||||||
arrayOf("document", "document.querySelector('#iframe').contentDocument").map { doc ->
|
mainSession.evaluatePromiseJS("""
|
||||||
mainSession.evaluatePromiseJS("""new Promise(resolve =>
|
window.getDataForAllFrames('${entry.key}', '${entry.value}')
|
||||||
$doc.querySelector('${entry.key}').addEventListener(
|
""")
|
||||||
'input', event => {
|
|
||||||
let eventInterface =
|
|
||||||
event instanceof $doc.defaultView.InputEvent ? "InputEvent" :
|
|
||||||
event instanceof $doc.defaultView.UIEvent ? "UIEvent" :
|
|
||||||
event instanceof $doc.defaultView.Event ? "Event" : "Unknown";
|
|
||||||
resolve([
|
|
||||||
'${entry.key}',
|
|
||||||
event.target.value,
|
|
||||||
'${entry.value}',
|
|
||||||
eventInterface
|
|
||||||
]);
|
|
||||||
}, { once: true }))""")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val autofillValues = SparseArray<CharSequence>()
|
val autofillValues = SparseArray<CharSequence>()
|
||||||
|
|
||||||
// Perform auto-fill and return number of auto-fills performed.
|
// Perform auto-fill and return number of auto-fills performed.
|
||||||
fun checkAutofillChild(child: Autofill.Node) {
|
fun checkAutofillChild(child: Autofill.Node, domain: String) {
|
||||||
// Seal the node info instance so we can perform actions on it.
|
// Seal the node info instance so we can perform actions on it.
|
||||||
if (child.children.count() > 0) {
|
if (child.children.isNotEmpty()) {
|
||||||
for (c in child.children) {
|
for (c in child.children) {
|
||||||
checkAutofillChild(c!!)
|
checkAutofillChild(c!!, child.domain)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (child.id == View.NO_ID) {
|
if (child == mainSession.autofillSession.root) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
assertThat("Should have HTML tag",
|
assertThat("Should have HTML tag",
|
||||||
child.tag, not(isEmptyOrNullString()))
|
child.tag, not(isEmptyOrNullString()))
|
||||||
assertThat("Web domain should match",
|
if (domain != "") {
|
||||||
child.domain, equalTo(GeckoSessionTestRule.TEST_ENDPOINT))
|
assertThat(
|
||||||
|
"Web domain should match its parent.",
|
||||||
|
child.domain, equalTo(domain)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
if (child.inputType == Autofill.InputType.TEXT) {
|
if (child.inputType == Autofill.InputType.TEXT) {
|
||||||
assertThat("Input should be enabled", child.enabled, equalTo(true))
|
assertThat("Input should be enabled", child.enabled, equalTo(true))
|
||||||
|
@ -212,7 +203,8 @@ class AutofillDelegateTest : BaseSessionTest() {
|
||||||
assertThat("Should have ID attribute", child.attributes.get("id"), not(isEmptyOrNullString()))
|
assertThat("Should have ID attribute", child.attributes.get("id"), not(isEmptyOrNullString()))
|
||||||
}
|
}
|
||||||
|
|
||||||
autofillValues.append(child.id, when (child.inputType) {
|
val childId = mainSession.autofillSession.dataFor(child).id
|
||||||
|
autofillValues.append(childId, when (child.inputType) {
|
||||||
Autofill.InputType.NUMBER -> "24"
|
Autofill.InputType.NUMBER -> "24"
|
||||||
Autofill.InputType.PHONE -> "42"
|
Autofill.InputType.PHONE -> "42"
|
||||||
Autofill.InputType.TEXT -> when (child.hint) {
|
Autofill.InputType.TEXT -> when (child.hint) {
|
||||||
|
@ -225,28 +217,37 @@ class AutofillDelegateTest : BaseSessionTest() {
|
||||||
}
|
}
|
||||||
|
|
||||||
val nodes = mainSession.autofillSession.root
|
val nodes = mainSession.autofillSession.root
|
||||||
checkAutofillChild(nodes)
|
checkAutofillChild(nodes, "")
|
||||||
|
|
||||||
mainSession.autofill(autofillValues)
|
mainSession.autofill(autofillValues)
|
||||||
|
|
||||||
// Wait on the promises and check for correct values.
|
// Wait on the promises and check for correct values.
|
||||||
for ((key, actual, expected, eventInterface) in promises.map { it.value.asJSList<String>() }) {
|
for (values in promises.map { it.value.asJsonArray() }) {
|
||||||
assertThat("Auto-filled value must match ($key)", actual, equalTo(expected))
|
for (i in 0 until values.length()) {
|
||||||
assertThat("input event should be dispatched with InputEvent interface", eventInterface, equalTo("InputEvent"))
|
val (key, actual, expected, eventInterface) = values.get(i).asJSList<String>()
|
||||||
|
|
||||||
|
assertThat("Auto-filled value must match ($key)", actual, equalTo(expected))
|
||||||
|
assertThat(
|
||||||
|
"input event should be dispatched with InputEvent interface",
|
||||||
|
eventInterface,
|
||||||
|
equalTo("InputEvent")
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test fun autofillUnknownValue() {
|
@Test fun autofillUnknownValue() {
|
||||||
// Test parts of the Oreo auto-fill API; there is another autofill test in
|
// Test parts of the Oreo auto-fill API; there is another autofill test in
|
||||||
// SessionAccessibility for a11y auto-fill support.
|
// SessionAccessibility for a11y auto-fill support.
|
||||||
mainSession.loadTestPath(FORMS_HTML_PATH)
|
mainSession.loadUri(pageUrl)
|
||||||
// Wait for the auto-fill nodes to populate.
|
// Wait for the auto-fill nodes to populate.
|
||||||
sessionRule.waitUntilCalled(object : Autofill.Delegate {
|
sessionRule.waitUntilCalled(object : Autofill.Delegate, GeckoSession.ProgressDelegate {
|
||||||
|
@AssertCalled(count = -1)
|
||||||
|
override fun onNodeAdd(session: GeckoSession,
|
||||||
|
node: Autofill.Node,
|
||||||
|
data: Autofill.NodeData) {}
|
||||||
@AssertCalled(count = 1)
|
@AssertCalled(count = 1)
|
||||||
override fun onAutofill(session: GeckoSession,
|
override fun onPageStop(session: GeckoSession, success: Boolean) {}
|
||||||
notification: Int,
|
|
||||||
node: Autofill.Node?) {
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
val autofillValues = SparseArray<CharSequence>()
|
val autofillValues = SparseArray<CharSequence>()
|
||||||
|
@ -266,19 +267,29 @@ class AutofillDelegateTest : BaseSessionTest() {
|
||||||
@WithDisplay(width = 100, height = 100)
|
@WithDisplay(width = 100, height = 100)
|
||||||
@Test fun autofillNavigation() {
|
@Test fun autofillNavigation() {
|
||||||
// Wait for the accessibility nodes to populate.
|
// Wait for the accessibility nodes to populate.
|
||||||
mainSession.loadTestPath(FORMS_HTML_PATH)
|
mainSession.loadUri(pageUrl)
|
||||||
sessionRule.waitUntilCalled(object : Autofill.Delegate {
|
|
||||||
@AssertCalled(count = 4)
|
sessionRule.waitUntilCalled(object : Autofill.Delegate, ShouldContinue,
|
||||||
override fun onAutofill(session: GeckoSession,
|
GeckoSession.ProgressDelegate
|
||||||
notification: Int,
|
{
|
||||||
node: Autofill.Node?) {
|
var nodeCount = 0
|
||||||
assertThat("Should be starting auto-fill",
|
|
||||||
notification,
|
// Continue waiting util we get all 16 nodes
|
||||||
equalTo(forEachCall(
|
override fun shouldContinue(): Boolean = nodeCount < 16
|
||||||
Autofill.Notify.SESSION_STARTED,
|
|
||||||
Autofill.Notify.NODE_ADDED)))
|
@AssertCalled(count = 1)
|
||||||
|
override fun onSessionStart(session: GeckoSession) {}
|
||||||
|
|
||||||
|
@AssertCalled(count = -1)
|
||||||
|
override fun onNodeAdd(session: GeckoSession,
|
||||||
|
node: Autofill.Node,
|
||||||
|
data: Autofill.NodeData) {
|
||||||
assertThat("Node should be valid", node, notNullValue())
|
assertThat("Node should be valid", node, notNullValue())
|
||||||
|
nodeCount = countAutofillNodes()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@AssertCalled(count = 1)
|
||||||
|
override fun onPageStop(session: GeckoSession, success: Boolean) {}
|
||||||
})
|
})
|
||||||
|
|
||||||
assertThat("Initial auto-fill count should match",
|
assertThat("Initial auto-fill count should match",
|
||||||
|
@ -286,99 +297,101 @@ class AutofillDelegateTest : BaseSessionTest() {
|
||||||
|
|
||||||
// Now wait for the nodes to clear.
|
// Now wait for the nodes to clear.
|
||||||
mainSession.loadTestPath(HELLO_HTML_PATH)
|
mainSession.loadTestPath(HELLO_HTML_PATH)
|
||||||
sessionRule.waitUntilCalled(object : Autofill.Delegate {
|
sessionRule.waitUntilCalled(object : Autofill.Delegate, GeckoSession.ProgressDelegate {
|
||||||
@AssertCalled(count = 1)
|
@AssertCalled(count = 1)
|
||||||
override fun onAutofill(session: GeckoSession,
|
override fun onSessionCancel(session: GeckoSession) {}
|
||||||
notification: Int,
|
|
||||||
node: Autofill.Node?) {
|
@AssertCalled
|
||||||
assertThat("Should be canceling auto-fill",
|
override fun onPageStop(session: GeckoSession, success: Boolean) {}
|
||||||
notification,
|
|
||||||
equalTo(Autofill.Notify.SESSION_CANCELED))
|
|
||||||
assertThat("Node should be null", node, nullValue())
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
assertThat("Should not have auto-fill fields",
|
assertThat("Should not have auto-fill fields",
|
||||||
countAutofillNodes(), equalTo(0))
|
countAutofillNodes(), equalTo(0))
|
||||||
|
|
||||||
// Now wait for the nodes to reappear.
|
|
||||||
mainSession.waitForPageStop()
|
|
||||||
mainSession.goBack()
|
mainSession.goBack()
|
||||||
sessionRule.waitUntilCalled(object : Autofill.Delegate, GeckoSession.ProgressDelegate {
|
sessionRule.waitUntilCalled(object : Autofill.Delegate, GeckoSession.ProgressDelegate,
|
||||||
@AssertCalled(count = 4)
|
ShouldContinue
|
||||||
override fun onAutofill(session: GeckoSession,
|
{
|
||||||
notification: Int,
|
var nodeCount = 0
|
||||||
node: Autofill.Node?) {
|
override fun shouldContinue(): Boolean = nodeCount < 16
|
||||||
assertThat("Should be starting auto-fill",
|
|
||||||
notification,
|
@AssertCalled(count = 1)
|
||||||
equalTo(forEachCall(
|
override fun onSessionStart(session: GeckoSession) {}
|
||||||
Autofill.Notify.SESSION_STARTED,
|
|
||||||
Autofill.Notify.NODE_ADDED)))
|
@AssertCalled(count = -1)
|
||||||
assertThat("ID should be valid", node, notNullValue())
|
override fun onNodeAdd(session: GeckoSession,
|
||||||
|
node: Autofill.Node,
|
||||||
|
data: Autofill.NodeData) {
|
||||||
|
assertThat("Node should be valid", node, notNullValue())
|
||||||
|
nodeCount = countAutofillNodes()
|
||||||
}
|
}
|
||||||
|
|
||||||
@AssertCalled(count = 1)
|
@AssertCalled(count = 1)
|
||||||
override fun onPageStop(session: GeckoSession, success: Boolean) {
|
override fun onPageStop(session: GeckoSession, success: Boolean) {}
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
assertThat("Should have auto-fill fields again",
|
assertThat("Should have auto-fill fields again",
|
||||||
countAutofillNodes(), equalTo(16))
|
countAutofillNodes(), equalTo(16))
|
||||||
|
|
||||||
|
var focused = mainSession.autofillSession.focused
|
||||||
assertThat("Should not have focused field",
|
assertThat("Should not have focused field",
|
||||||
countAutofillNodes({ it.focused }), equalTo(0))
|
countAutofillNodes({ it == focused }), equalTo(0))
|
||||||
|
|
||||||
mainSession.evaluateJS("document.querySelector('#pass2').focus()")
|
mainSession.evaluateJS("document.querySelector('#pass2').focus()")
|
||||||
|
|
||||||
sessionRule.waitUntilCalled(object : Autofill.Delegate {
|
sessionRule.waitUntilCalled(object : Autofill.Delegate {
|
||||||
@AssertCalled(count = 1)
|
@AssertCalled(count = 1)
|
||||||
override fun onAutofill(session: GeckoSession,
|
override fun onNodeFocus(session: GeckoSession,
|
||||||
notification: Int,
|
node: Autofill.Node,
|
||||||
node: Autofill.Node?) {
|
data: Autofill.NodeData) {
|
||||||
assertThat("Should be entering auto-fill view",
|
|
||||||
notification,
|
|
||||||
equalTo(Autofill.Notify.NODE_FOCUSED))
|
|
||||||
assertThat("ID should be valid", node, notNullValue())
|
assertThat("ID should be valid", node, notNullValue())
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
focused = mainSession.autofillSession.focused
|
||||||
assertThat("Should have one focused field",
|
assertThat("Should have one focused field",
|
||||||
countAutofillNodes({ it.focused }), equalTo(1))
|
countAutofillNodes({ it == focused }), equalTo(1))
|
||||||
// The focused field, its siblings, its parent, and the root node should
|
// The focused field, its siblings, its parent, and the root node should
|
||||||
// be visible.
|
// be visible.
|
||||||
// Hidden elements are ignored.
|
// Hidden elements are ignored.
|
||||||
// TODO: Is this actually correct? Should the whole focused branch be
|
// TODO: Is this actually correct? Should the whole focused branch be
|
||||||
// visible or just the nodes as described above?
|
// visible or just the nodes as described above?
|
||||||
assertThat("Should have nine visible nodes",
|
assertThat("Should have nine visible nodes",
|
||||||
countAutofillNodes({ node -> node.visible }),
|
countAutofillNodes({ node -> mainSession.autofillSession.isVisible(node) }),
|
||||||
equalTo(8))
|
equalTo(8))
|
||||||
|
|
||||||
mainSession.evaluateJS("document.querySelector('#pass2').blur()")
|
mainSession.evaluateJS("document.querySelector('#pass2').blur()")
|
||||||
sessionRule.waitUntilCalled(object : Autofill.Delegate {
|
sessionRule.waitUntilCalled(object : Autofill.Delegate {
|
||||||
@AssertCalled(count = 1)
|
@AssertCalled(count = 1)
|
||||||
override fun onAutofill(session: GeckoSession,
|
override fun onNodeBlur(session: GeckoSession,
|
||||||
notification: Int,
|
node: Autofill.Node,
|
||||||
node: Autofill.Node?) {
|
data: Autofill.NodeData) {
|
||||||
assertThat("Should be exiting auto-fill view",
|
|
||||||
notification,
|
|
||||||
equalTo(Autofill.Notify.NODE_BLURRED))
|
|
||||||
assertThat("ID should be valid", node, notNullValue())
|
assertThat("ID should be valid", node, notNullValue())
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
focused = mainSession.autofillSession.focused
|
||||||
assertThat("Should not have focused field",
|
assertThat("Should not have focused field",
|
||||||
countAutofillNodes({ it.focused }), equalTo(0))
|
countAutofillNodes({ it == focused }), equalTo(0))
|
||||||
}
|
}
|
||||||
|
|
||||||
@WithDisplay(height = 100, width = 100)
|
@WithDisplay(height = 100, width = 100)
|
||||||
@Test fun autofillUserpass() {
|
@Test fun autofillUserpass() {
|
||||||
mainSession.loadTestPath(FORMS2_HTML_PATH)
|
mainSession.loadTestPath(FORMS2_HTML_PATH)
|
||||||
// Wait for the auto-fill nodes to populate.
|
// Wait for the auto-fill nodes to populate.
|
||||||
sessionRule.waitUntilCalled(object : Autofill.Delegate {
|
sessionRule.waitUntilCalled(object : Autofill.Delegate, GeckoSession.ProgressDelegate {
|
||||||
@AssertCalled(count = 3)
|
@AssertCalled(count = 1)
|
||||||
override fun onAutofill(session: GeckoSession,
|
override fun onSessionStart(session: GeckoSession) {}
|
||||||
notification: Int,
|
@AssertCalled(count = 1)
|
||||||
node: Autofill.Node?) {
|
override fun onNodeFocus(session: GeckoSession,
|
||||||
assertThat("Autofill notification should match", notification,
|
node: Autofill.Node,
|
||||||
equalTo(forEachCall(Autofill.Notify.SESSION_STARTED,
|
data: Autofill.NodeData) {}
|
||||||
Autofill.Notify.NODE_FOCUSED,
|
@AssertCalled(count = -1)
|
||||||
Autofill.Notify.NODE_ADDED)))
|
override fun onNodeAdd(session: GeckoSession,
|
||||||
}
|
node: Autofill.Node,
|
||||||
|
data: Autofill.NodeData) {}
|
||||||
|
@AssertCalled(count = 1)
|
||||||
|
override fun onPageStop(session: GeckoSession, success: Boolean) {}
|
||||||
})
|
})
|
||||||
|
|
||||||
// Perform auto-fill and return number of auto-fills performed.
|
// Perform auto-fill and return number of auto-fills performed.
|
||||||
|
@ -393,7 +406,8 @@ class AutofillDelegateTest : BaseSessionTest() {
|
||||||
return sum
|
return sum
|
||||||
}
|
}
|
||||||
|
|
||||||
assertThat("ID should be valid", child.id, not(equalTo(View.NO_ID)))
|
val childId = mainSession.autofillSession.dataFor(child).id
|
||||||
|
assertThat("ID should be valid", childId, not(equalTo(View.NO_ID)))
|
||||||
assertThat("Should have HTML tag", child.tag, equalTo("input"))
|
assertThat("Should have HTML tag", child.tag, equalTo("input"))
|
||||||
|
|
||||||
return sum + 1
|
return sum + 1
|
||||||
|
@ -410,48 +424,42 @@ class AutofillDelegateTest : BaseSessionTest() {
|
||||||
@Test fun autofillActiveChange() {
|
@Test fun autofillActiveChange() {
|
||||||
// We should blur the active autofill node if the session is set
|
// We should blur the active autofill node if the session is set
|
||||||
// inactive. Likewise, we should focus a node once we return.
|
// inactive. Likewise, we should focus a node once we return.
|
||||||
mainSession.loadTestPath(FORMS_HTML_PATH)
|
mainSession.loadUri(pageUrl)
|
||||||
// Wait for the auto-fill nodes to populate.
|
// Wait for the auto-fill nodes to populate.
|
||||||
sessionRule.waitUntilCalled(object : Autofill.Delegate {
|
sessionRule.waitUntilCalled(object : Autofill.Delegate, GeckoSession.ProgressDelegate {
|
||||||
// For the root document and the iframe document, each has a form group and
|
// For the root document and the iframe document, each has a form group and
|
||||||
// a group for inputs outside of forms, so the total count is 4.
|
// a group for inputs outside of forms, so the total count is 4.
|
||||||
@AssertCalled(count = 4)
|
@AssertCalled(count = 1)
|
||||||
override fun onAutofill(session: GeckoSession,
|
override fun onSessionStart(session: GeckoSession) {}
|
||||||
notification: Int,
|
@AssertCalled(count = -1)
|
||||||
node: Autofill.Node?) {
|
override fun onNodeAdd(session: GeckoSession,
|
||||||
assertThat("Should be starting auto-fill",
|
node: Autofill.Node,
|
||||||
notification,
|
data: Autofill.NodeData) {}
|
||||||
equalTo(forEachCall(
|
@AssertCalled(count = 1)
|
||||||
Autofill.Notify.SESSION_STARTED,
|
override fun onPageStop(session: GeckoSession, success: Boolean) {}
|
||||||
Autofill.Notify.NODE_ADDED)))
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
mainSession.evaluateJS("document.querySelector('#pass2').focus()")
|
mainSession.evaluateJS("document.querySelector('#pass2').focus()")
|
||||||
sessionRule.waitUntilCalled(object : Autofill.Delegate {
|
sessionRule.waitUntilCalled(object : Autofill.Delegate {
|
||||||
@AssertCalled(count = 1)
|
@AssertCalled(count = 1)
|
||||||
override fun onAutofill(session: GeckoSession,
|
override fun onNodeFocus(session: GeckoSession,
|
||||||
notification: Int,
|
node: Autofill.Node,
|
||||||
node: Autofill.Node?) {
|
data: Autofill.NodeData) {
|
||||||
assertThat("Should be entering auto-fill view",
|
|
||||||
notification,
|
|
||||||
equalTo(Autofill.Notify.NODE_FOCUSED))
|
|
||||||
assertThat("ID should be valid", node, notNullValue())
|
assertThat("ID should be valid", node, notNullValue())
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
var focused = mainSession.autofillSession.focused
|
||||||
assertThat("Should have one focused field",
|
assertThat("Should have one focused field",
|
||||||
countAutofillNodes({ it.focused }), equalTo(1))
|
countAutofillNodes({ it == focused }), equalTo(1))
|
||||||
|
|
||||||
// Make sure we get NODE_BLURRED when inactive
|
// Make sure we get NODE_BLURRED when inactive
|
||||||
mainSession.setActive(false)
|
mainSession.setActive(false)
|
||||||
sessionRule.waitUntilCalled(object : Autofill.Delegate {
|
sessionRule.waitUntilCalled(object : Autofill.Delegate {
|
||||||
@AssertCalled(count = 1)
|
@AssertCalled(count = 1)
|
||||||
override fun onAutofill(session: GeckoSession,
|
override fun onNodeBlur(session: GeckoSession,
|
||||||
notification: Int,
|
node: Autofill.Node,
|
||||||
node: Autofill.Node?) {
|
data: Autofill.NodeData) {
|
||||||
assertThat("Should be exiting auto-fill view",
|
|
||||||
notification,
|
|
||||||
equalTo(Autofill.Notify.NODE_BLURRED))
|
|
||||||
assertThat("ID should be valid", node, notNullValue())
|
assertThat("ID should be valid", node, notNullValue())
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -460,29 +468,30 @@ class AutofillDelegateTest : BaseSessionTest() {
|
||||||
mainSession.setActive(true)
|
mainSession.setActive(true)
|
||||||
sessionRule.waitUntilCalled(object : Autofill.Delegate {
|
sessionRule.waitUntilCalled(object : Autofill.Delegate {
|
||||||
@AssertCalled(count = 1)
|
@AssertCalled(count = 1)
|
||||||
override fun onAutofill(session: GeckoSession,
|
override fun onNodeFocus(session: GeckoSession,
|
||||||
notification: Int,
|
node: Autofill.Node,
|
||||||
node: Autofill.Node?) {
|
data: Autofill.NodeData) {
|
||||||
assertThat("Should be entering auto-fill view",
|
|
||||||
notification,
|
|
||||||
equalTo(Autofill.Notify.NODE_FOCUSED))
|
|
||||||
assertThat("ID should be valid", node, notNullValue())
|
assertThat("ID should be valid", node, notNullValue())
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
focused = mainSession.autofillSession.focused
|
||||||
assertThat("Should have one focused field",
|
assertThat("Should have one focused field",
|
||||||
countAutofillNodes({ it.focused }), equalTo(1))
|
countAutofillNodes({ focused == it }), equalTo(1))
|
||||||
}
|
}
|
||||||
|
|
||||||
@WithDisplay(width = 100, height = 100)
|
@WithDisplay(width = 100, height = 100)
|
||||||
@Test fun autofillAutocompleteAttribute() {
|
@Test fun autofillAutocompleteAttribute() {
|
||||||
mainSession.loadTestPath(FORMS_AUTOCOMPLETE_HTML_PATH)
|
mainSession.loadTestPath(FORMS_AUTOCOMPLETE_HTML_PATH)
|
||||||
sessionRule.waitUntilCalled(object : Autofill.Delegate {
|
sessionRule.waitUntilCalled(object : Autofill.Delegate, GeckoSession.ProgressDelegate {
|
||||||
@AssertCalled(count = 3)
|
@AssertCalled(count = -1)
|
||||||
override fun onAutofill(session: GeckoSession,
|
override fun onNodeAdd(session: GeckoSession,
|
||||||
notification: Int,
|
node: Autofill.Node,
|
||||||
node: Autofill.Node?) {
|
data: Autofill.NodeData) {}
|
||||||
}
|
|
||||||
});
|
@AssertCalled(count = 1)
|
||||||
|
override fun onPageStop(session: GeckoSession, success: Boolean) {}
|
||||||
|
})
|
||||||
|
|
||||||
fun checkAutofillChild(child: Autofill.Node): Int {
|
fun checkAutofillChild(child: Autofill.Node): Int {
|
||||||
var sum = 0
|
var sum = 0
|
||||||
|
@ -501,266 +510,4 @@ class AutofillDelegateTest : BaseSessionTest() {
|
||||||
assertThat("autofill hint count",
|
assertThat("autofill hint count",
|
||||||
checkAutofillChild(root), equalTo(6))
|
checkAutofillChild(root), equalTo(6))
|
||||||
}
|
}
|
||||||
|
|
||||||
class MockViewNode : ViewStructure() {
|
|
||||||
private var mClassName: String? = null
|
|
||||||
private var mEnabled = false
|
|
||||||
private var mVisibility = -1
|
|
||||||
private var mPackageName: String? = null
|
|
||||||
private var mTypeName: String? = null
|
|
||||||
private var mEntryName: String? = null
|
|
||||||
private var mAutofillType = -1
|
|
||||||
private var mAutofillHints: Array<String>? = null
|
|
||||||
private var mInputType = -1
|
|
||||||
private var mHtmlInfo: HtmlInfo? = null
|
|
||||||
private var mWebDomain: String? = null
|
|
||||||
private var mFocused = false
|
|
||||||
private var mFocusable = false
|
|
||||||
|
|
||||||
var children = ArrayList<MockViewNode?>()
|
|
||||||
var id = View.NO_ID
|
|
||||||
var height = 0
|
|
||||||
var width = 0
|
|
||||||
|
|
||||||
val className get() = mClassName
|
|
||||||
val htmlInfo get() = mHtmlInfo
|
|
||||||
val autofillHints get() = mAutofillHints
|
|
||||||
val autofillType get() = mAutofillType
|
|
||||||
val webDomain get() = mWebDomain
|
|
||||||
val isEnabled get() = mEnabled
|
|
||||||
val isFocused get() = mFocused
|
|
||||||
val isFocusable get() = mFocusable
|
|
||||||
val visibility get() = mVisibility
|
|
||||||
val inputType get() = mInputType
|
|
||||||
|
|
||||||
override fun setId(id: Int, packageName: String?, typeName: String?, entryName: String?) {
|
|
||||||
this.id = id
|
|
||||||
mPackageName = packageName
|
|
||||||
mTypeName = typeName
|
|
||||||
mEntryName = entryName
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun setHint(hint: CharSequence?) {
|
|
||||||
TODO("not implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun setElevation(elevation: Float) {
|
|
||||||
TODO("not implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getText(): CharSequence {
|
|
||||||
TODO("not implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun setText(text: CharSequence?) {
|
|
||||||
TODO("not implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun setText(text: CharSequence?, selectionStart: Int, selectionEnd: Int) {
|
|
||||||
TODO("not implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun asyncCommit() {
|
|
||||||
TODO("not implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getChildCount(): Int = children.size
|
|
||||||
|
|
||||||
override fun setEnabled(state: Boolean) {
|
|
||||||
mEnabled = state
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun setLocaleList(localeList: LocaleList?) {
|
|
||||||
TODO("not implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun setDimens(left: Int, top: Int, scrollX: Int, scrollY: Int, width: Int, height: Int) {
|
|
||||||
this.width = width
|
|
||||||
this.height = height
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun setChecked(state: Boolean) {
|
|
||||||
TODO("not implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun setContextClickable(state: Boolean) {
|
|
||||||
TODO("not implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun setAccessibilityFocused(state: Boolean) {
|
|
||||||
TODO("not implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun setAlpha(alpha: Float) {
|
|
||||||
TODO("not implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun setTransformation(matrix: Matrix?) {
|
|
||||||
TODO("not implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun setClassName(className: String?) {
|
|
||||||
mClassName = className
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun setLongClickable(state: Boolean) {
|
|
||||||
TODO("not implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun newChild(index: Int): ViewStructure {
|
|
||||||
val child = MockViewNode()
|
|
||||||
children[index] = child
|
|
||||||
return child
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getHint(): CharSequence {
|
|
||||||
TODO("not implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun setInputType(inputType: Int) {
|
|
||||||
mInputType = inputType
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun setWebDomain(domain: String?) {
|
|
||||||
mWebDomain = domain
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun setAutofillOptions(options: Array<out CharSequence>?) {
|
|
||||||
TODO("not implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun setTextStyle(size: Float, fgColor: Int, bgColor: Int, style: Int) {
|
|
||||||
TODO("not implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun setVisibility(visibility: Int) {
|
|
||||||
mVisibility = visibility
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getAutofillId(): AutofillId? {
|
|
||||||
TODO("not implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun setHtmlInfo(htmlInfo: HtmlInfo) {
|
|
||||||
mHtmlInfo = htmlInfo
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun setTextLines(charOffsets: IntArray?, baselines: IntArray?) {
|
|
||||||
TODO("not implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getExtras(): Bundle {
|
|
||||||
TODO("not implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun setClickable(state: Boolean) {
|
|
||||||
TODO("not implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun newHtmlInfoBuilder(tagName: String): HtmlInfo.Builder {
|
|
||||||
return MockHtmlInfoBuilder(tagName)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getTextSelectionEnd(): Int {
|
|
||||||
TODO("not implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun setAutofillId(id: AutofillId) {
|
|
||||||
TODO("not implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun setAutofillId(parentId: AutofillId, virtualId: Int) {
|
|
||||||
TODO("not implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun hasExtras(): Boolean {
|
|
||||||
TODO("not implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun addChildCount(num: Int): Int {
|
|
||||||
TODO("not implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun setAutofillType(type: Int) {
|
|
||||||
mAutofillType = type
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun setActivated(state: Boolean) {
|
|
||||||
TODO("not implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun setFocused(state: Boolean) {
|
|
||||||
mFocused = state
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getTextSelectionStart(): Int {
|
|
||||||
TODO("not implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun setChildCount(num: Int) {
|
|
||||||
children = ArrayList()
|
|
||||||
for (i in 0 until num) {
|
|
||||||
children.add(null)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun setAutofillValue(value: AutofillValue?) {
|
|
||||||
TODO("not implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun setAutofillHints(hint: Array<String>?) {
|
|
||||||
mAutofillHints = hint
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun setContentDescription(contentDescription: CharSequence?) {
|
|
||||||
TODO("not implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun setFocusable(state: Boolean) {
|
|
||||||
mFocusable = state
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun setCheckable(state: Boolean) {
|
|
||||||
TODO("not implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun asyncNewChild(index: Int): ViewStructure {
|
|
||||||
TODO("not implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun setSelected(state: Boolean) {
|
|
||||||
TODO("not implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun setDataIsSensitive(sensitive: Boolean) {
|
|
||||||
TODO("not implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun setOpaque(opaque: Boolean) {
|
|
||||||
TODO("not implemented")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class MockHtmlInfoBuilder(tagName: String) : ViewStructure.HtmlInfo.Builder() {
|
|
||||||
val mTagName = tagName
|
|
||||||
val mAttributes: MutableList<Pair<String, String>> = mutableListOf()
|
|
||||||
|
|
||||||
override fun addAttribute(name: String, value: String): ViewStructure.HtmlInfo.Builder {
|
|
||||||
mAttributes.add(Pair(name, value))
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun build(): ViewStructure.HtmlInfo {
|
|
||||||
return MockHtmlInfo(mTagName, mAttributes)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class MockHtmlInfo(tagName: String, attributes: MutableList<Pair<String, String>>)
|
|
||||||
: ViewStructure.HtmlInfo() {
|
|
||||||
private val mTagName = tagName
|
|
||||||
private val mAttributes = attributes
|
|
||||||
|
|
||||||
override fun getTag() = mTagName
|
|
||||||
override fun getAttributes() = mAttributes
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,6 +39,7 @@ open class BaseSessionTest(noErrorCollector: Boolean = false) {
|
||||||
const val DOWNLOAD_HTML_PATH = "/assets/www/download.html"
|
const val DOWNLOAD_HTML_PATH = "/assets/www/download.html"
|
||||||
const val FORM_BLANK_HTML_PATH = "/assets/www/form_blank.html"
|
const val FORM_BLANK_HTML_PATH = "/assets/www/form_blank.html"
|
||||||
const val FORMS_HTML_PATH = "/assets/www/forms.html"
|
const val FORMS_HTML_PATH = "/assets/www/forms.html"
|
||||||
|
const val FORMS_XORIGIN_HTML_PATH = "/assets/www/forms_xorigin.html"
|
||||||
const val FORMS2_HTML_PATH = "/assets/www/forms2.html"
|
const val FORMS2_HTML_PATH = "/assets/www/forms2.html"
|
||||||
const val FORMS3_HTML_PATH = "/assets/www/forms3.html"
|
const val FORMS3_HTML_PATH = "/assets/www/forms3.html"
|
||||||
const val FORMS4_HTML_PATH = "/assets/www/forms4.html"
|
const val FORMS4_HTML_PATH = "/assets/www/forms4.html"
|
||||||
|
|
|
@ -59,15 +59,15 @@ class MediaDelegateXOriginTest : BaseSessionTest() {
|
||||||
mainSession.delegateDuringNextWait(object : MediaDelegate {
|
mainSession.delegateDuringNextWait(object : MediaDelegate {
|
||||||
@GeckoSessionTestRule.AssertCalled(count = 1)
|
@GeckoSessionTestRule.AssertCalled(count = 1)
|
||||||
override fun onRecordingStatusChanged(session: GeckoSession,
|
override fun onRecordingStatusChanged(session: GeckoSession,
|
||||||
devices: Array<org.mozilla.geckoview.GeckoSession.MediaDelegate.RecordingDevice>) {
|
devices: Array<MediaDelegate.RecordingDevice>) {
|
||||||
var audioActive = false
|
var audioActive = false
|
||||||
var cameraActive = false
|
var cameraActive = false
|
||||||
for (device in devices) {
|
for (device in devices) {
|
||||||
if (device.type == org.mozilla.geckoview.GeckoSession.MediaDelegate.RecordingDevice.Type.MICROPHONE) {
|
if (device.type == MediaDelegate.RecordingDevice.Type.MICROPHONE) {
|
||||||
audioActive = device.status != org.mozilla.geckoview.GeckoSession.MediaDelegate.RecordingDevice.Status.INACTIVE
|
audioActive = device.status != MediaDelegate.RecordingDevice.Status.INACTIVE
|
||||||
}
|
}
|
||||||
if (device.type == org.mozilla.geckoview.GeckoSession.MediaDelegate.RecordingDevice.Type.CAMERA) {
|
if (device.type == MediaDelegate.RecordingDevice.Type.CAMERA) {
|
||||||
cameraActive = device.status != org.mozilla.geckoview.GeckoSession.MediaDelegate.RecordingDevice.Status.INACTIVE
|
cameraActive = device.status != MediaDelegate.RecordingDevice.Status.INACTIVE
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -76,7 +76,6 @@ class MediaDelegateXOriginTest : BaseSessionTest() {
|
||||||
|
|
||||||
assertThat("Audio is ${if (allowAudio ) { "active" } else { "inactive" }}" ,
|
assertThat("Audio is ${if (allowAudio ) { "active" } else { "inactive" }}" ,
|
||||||
audioActive, Matchers.equalTo(allowAudio))
|
audioActive, Matchers.equalTo(allowAudio))
|
||||||
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -149,9 +148,6 @@ class MediaDelegateXOriginTest : BaseSessionTest() {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test fun testDeviceRecordingEventAudioAndVideoInXOriginIframe() {
|
@Test fun testDeviceRecordingEventAudioAndVideoInXOriginIframe() {
|
||||||
// TODO: Bug 1648153
|
|
||||||
assumeThat(sessionRule.env.isFission, Matchers.equalTo(false))
|
|
||||||
|
|
||||||
// TODO: needs bug 1700243
|
// TODO: needs bug 1700243
|
||||||
assumeThat(sessionRule.env.isIsolatedProcess, Matchers.equalTo(false))
|
assumeThat(sessionRule.env.isIsolatedProcess, Matchers.equalTo(false))
|
||||||
|
|
||||||
|
@ -167,9 +163,6 @@ class MediaDelegateXOriginTest : BaseSessionTest() {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test fun testDeviceRecordingEventAudioAndVideoInXOriginIframeNoAllow() {
|
@Test fun testDeviceRecordingEventAudioAndVideoInXOriginIframeNoAllow() {
|
||||||
// TODO: Bug 1648153
|
|
||||||
assumeThat(sessionRule.env.isFission, Matchers.equalTo(false))
|
|
||||||
|
|
||||||
mainSession.loadTestPath(GETUSERMEDIA_XORIGIN_CONTAINER_HTML_PATH)
|
mainSession.loadTestPath(GETUSERMEDIA_XORIGIN_CONTAINER_HTML_PATH)
|
||||||
mainSession.waitForPageStop()
|
mainSession.waitForPageStop()
|
||||||
|
|
||||||
|
|
|
@ -151,7 +151,7 @@ class PermissionDelegateTest : BaseSessionTest() {
|
||||||
window.navigator.mediaDevices.getUserMedia({ video: true })""")
|
window.navigator.mediaDevices.getUserMedia({ video: true })""")
|
||||||
} else {
|
} else {
|
||||||
mainSession.waitForJS("""
|
mainSession.waitForJS("""
|
||||||
window.navigator.mediaDevices.getUserMedia({ audio: true: video: true })""")
|
window.navigator.mediaDevices.getUserMedia({ audio: true, video: true })""")
|
||||||
}
|
}
|
||||||
fail("Request should have failed")
|
fail("Request should have failed")
|
||||||
} catch (e: RejectedPromiseException) {
|
} catch (e: RejectedPromiseException) {
|
||||||
|
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -831,6 +831,7 @@ public class GeckoSession {
|
||||||
final String event,
|
final String event,
|
||||||
final GeckoBundle message,
|
final GeckoBundle message,
|
||||||
final EventCallback callback) {
|
final EventCallback callback) {
|
||||||
|
Log.d(LOGTAG, "handleMessage: " + event);
|
||||||
if (delegate == null) {
|
if (delegate == null) {
|
||||||
callback.sendSuccess(/* granted */ false);
|
callback.sendSuccess(/* granted */ false);
|
||||||
return;
|
return;
|
||||||
|
@ -900,7 +901,7 @@ public class GeckoSession {
|
||||||
final @SelectionActionDelegateAction HashSet<String> actionsSet =
|
final @SelectionActionDelegateAction HashSet<String> actionsSet =
|
||||||
new HashSet<>(Arrays.asList(message.getStringArray("actions")));
|
new HashSet<>(Arrays.asList(message.getStringArray("actions")));
|
||||||
final SelectionActionDelegate.Selection selection =
|
final SelectionActionDelegate.Selection selection =
|
||||||
new SelectionActionDelegate.Selection(message, actionsSet, callback);
|
new SelectionActionDelegate.Selection(message, actionsSet, mEventDispatcher);
|
||||||
|
|
||||||
delegate.onShowActionRequest(GeckoSession.this, selection);
|
delegate.onShowActionRequest(GeckoSession.this, selection);
|
||||||
|
|
||||||
|
@ -1318,14 +1319,12 @@ public class GeckoSession {
|
||||||
@Override
|
@Override
|
||||||
public void handleMessage(
|
public void handleMessage(
|
||||||
final String event, final GeckoBundle message, final EventCallback callback) {
|
final String event, final GeckoBundle message, final EventCallback callback) {
|
||||||
if (DEBUG) {
|
Log.d(LOGTAG, "handleMessage " + event);
|
||||||
Log.d(LOGTAG, "handleMessage: event = " + event);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ("GeckoView:PinOnScreen".equals(event)) {
|
if ("GeckoView:PinOnScreen".equals(event)) {
|
||||||
GeckoSession.this.setShouldPinOnScreen(message.getBoolean("pinned"));
|
GeckoSession.this.setShouldPinOnScreen(message.getBoolean("pinned"));
|
||||||
} else if ("GeckoView:Prompt".equals(event)) {
|
} else if ("GeckoView:Prompt".equals(event)) {
|
||||||
mPromptController.handleEvent(GeckoSession.this, message, callback);
|
mPromptController.handleEvent(GeckoSession.this, message.getBundle("prompt"), callback);
|
||||||
} else if ("GeckoView:Prompt:Dismiss".equals(event)) {
|
} else if ("GeckoView:Prompt:Dismiss".equals(event)) {
|
||||||
mPromptController.dismissPrompt(message.getString("id"));
|
mPromptController.dismissPrompt(message.getString("id"));
|
||||||
}
|
}
|
||||||
|
@ -3406,14 +3405,14 @@ public class GeckoSession {
|
||||||
/** Set of valid actions available through {@link Selection#execute(String)} */
|
/** Set of valid actions available through {@link Selection#execute(String)} */
|
||||||
public final @NonNull @SelectionActionDelegateAction Collection<String> availableActions;
|
public final @NonNull @SelectionActionDelegateAction Collection<String> availableActions;
|
||||||
|
|
||||||
private final int mSeqNo;
|
private final String mActionId;
|
||||||
|
|
||||||
private final EventCallback mEventCallback;
|
private final WeakReference<EventDispatcher> mEventDispatcher;
|
||||||
|
|
||||||
/* package */ Selection(
|
/* package */ Selection(
|
||||||
final GeckoBundle bundle,
|
final GeckoBundle bundle,
|
||||||
final @NonNull @SelectionActionDelegateAction Set<String> actions,
|
final @NonNull @SelectionActionDelegateAction Set<String> actions,
|
||||||
final EventCallback callback) {
|
final EventDispatcher eventDispatcher) {
|
||||||
flags =
|
flags =
|
||||||
(bundle.getBoolean("collapsed") ? SelectionActionDelegate.FLAG_IS_COLLAPSED : 0)
|
(bundle.getBoolean("collapsed") ? SelectionActionDelegate.FLAG_IS_COLLAPSED : 0)
|
||||||
| (bundle.getBoolean("editable") ? SelectionActionDelegate.FLAG_IS_EDITABLE : 0)
|
| (bundle.getBoolean("editable") ? SelectionActionDelegate.FLAG_IS_EDITABLE : 0)
|
||||||
|
@ -3421,8 +3420,8 @@ public class GeckoSession {
|
||||||
text = bundle.getString("selection");
|
text = bundle.getString("selection");
|
||||||
clientRect = bundle.getRectF("clientRect");
|
clientRect = bundle.getRectF("clientRect");
|
||||||
availableActions = actions;
|
availableActions = actions;
|
||||||
mSeqNo = bundle.getInt("seqNo");
|
mActionId = bundle.getString("actionId");
|
||||||
mEventCallback = callback;
|
mEventDispatcher = new WeakReference<>(eventDispatcher);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Empty constructor for tests. */
|
/** Empty constructor for tests. */
|
||||||
|
@ -3431,8 +3430,8 @@ public class GeckoSession {
|
||||||
text = "";
|
text = "";
|
||||||
clientRect = null;
|
clientRect = null;
|
||||||
availableActions = new HashSet<>();
|
availableActions = new HashSet<>();
|
||||||
mSeqNo = 0;
|
mActionId = null;
|
||||||
mEventCallback = null;
|
mEventDispatcher = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -3458,10 +3457,16 @@ public class GeckoSession {
|
||||||
if (!isActionAvailable(action)) {
|
if (!isActionAvailable(action)) {
|
||||||
throw new IllegalStateException("Action not available");
|
throw new IllegalStateException("Action not available");
|
||||||
}
|
}
|
||||||
|
final EventDispatcher eventDispatcher = mEventDispatcher.get();
|
||||||
|
if (eventDispatcher == null) {
|
||||||
|
// The session is not available anymore, nothing really to do
|
||||||
|
Log.w(LOGTAG, "Calling execute on a stale Selection.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
final GeckoBundle response = new GeckoBundle(2);
|
final GeckoBundle response = new GeckoBundle(2);
|
||||||
response.putString("id", action);
|
response.putString("id", action);
|
||||||
response.putInt("seqNo", mSeqNo);
|
response.putString("actionId", mActionId);
|
||||||
mEventCallback.sendSuccess(response);
|
eventDispatcher.dispatch("GeckoView:ExecuteSelectionAction", response);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -23,7 +23,6 @@ import android.os.Build;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.util.AttributeSet;
|
import android.util.AttributeSet;
|
||||||
import android.util.DisplayMetrics;
|
import android.util.DisplayMetrics;
|
||||||
import android.util.Log;
|
|
||||||
import android.util.SparseArray;
|
import android.util.SparseArray;
|
||||||
import android.util.TypedValue;
|
import android.util.TypedValue;
|
||||||
import android.view.DisplayCutout;
|
import android.view.DisplayCutout;
|
||||||
|
@ -50,6 +49,7 @@ import androidx.annotation.UiThread;
|
||||||
import androidx.core.view.ViewCompat;
|
import androidx.core.view.ViewCompat;
|
||||||
import java.lang.annotation.Retention;
|
import java.lang.annotation.Retention;
|
||||||
import java.lang.annotation.RetentionPolicy;
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.util.Objects;
|
||||||
import org.mozilla.gecko.AndroidGamepadManager;
|
import org.mozilla.gecko.AndroidGamepadManager;
|
||||||
import org.mozilla.gecko.EventDispatcher;
|
import org.mozilla.gecko.EventDispatcher;
|
||||||
import org.mozilla.gecko.InputMethods;
|
import org.mozilla.gecko.InputMethods;
|
||||||
|
@ -272,7 +272,12 @@ public class GeckoView extends FrameLayout {
|
||||||
mSelectionActionDelegate = new BasicSelectionActionDelegate(activity);
|
mSelectionActionDelegate = new BasicSelectionActionDelegate(activity);
|
||||||
}
|
}
|
||||||
|
|
||||||
mAutofillDelegate = new AndroidAutofillDelegate();
|
if (Build.VERSION.SDK_INT >= 26) {
|
||||||
|
mAutofillDelegate = new AndroidAutofillDelegate();
|
||||||
|
} else {
|
||||||
|
// We don't support Autofill on SDK < 26
|
||||||
|
mAutofillDelegate = new Autofill.Delegate() {};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -915,10 +920,26 @@ public class GeckoView extends FrameLayout {
|
||||||
return mAutofillEnabled;
|
return mAutofillEnabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TargetApi(26)
|
||||||
private class AndroidAutofillDelegate implements Autofill.Delegate {
|
private class AndroidAutofillDelegate implements Autofill.Delegate {
|
||||||
|
AutofillManager mAutofillManager;
|
||||||
|
boolean mDisabled = false;
|
||||||
|
|
||||||
|
private void ensureAutofillManager() {
|
||||||
|
if (mDisabled || mAutofillManager != null) {
|
||||||
|
// Nothing to do
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
mAutofillManager = GeckoView.this.getContext().getSystemService(AutofillManager.class);
|
||||||
|
if (mAutofillManager == null) {
|
||||||
|
// If we can't get a reference to the autofill manager, we cannot run the autofill service
|
||||||
|
mDisabled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private Rect displayRectForId(
|
private Rect displayRectForId(
|
||||||
@NonNull final GeckoSession session, @NonNull final Autofill.Node node) {
|
@NonNull final GeckoSession session, @Nullable final Autofill.Node node) {
|
||||||
if (node == null) {
|
if (node == null) {
|
||||||
return new Rect(0, 0, 0, 0);
|
return new Rect(0, 0, 0, 0);
|
||||||
}
|
}
|
||||||
|
@ -934,43 +955,95 @@ public class GeckoView extends FrameLayout {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onAutofill(
|
public void onNodeBlur(
|
||||||
@NonNull final GeckoSession session, final int notification, final Autofill.Node node) {
|
final @NonNull GeckoSession session,
|
||||||
ThreadUtils.assertOnUiThread();
|
final @NonNull Autofill.Node prev,
|
||||||
if (Build.VERSION.SDK_INT < 26) {
|
final @NonNull Autofill.NodeData data) {
|
||||||
|
ensureAutofillManager();
|
||||||
|
if (mAutofillManager != null) {
|
||||||
|
mAutofillManager.notifyViewExited(GeckoView.this, data.getId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onNodeAdd(
|
||||||
|
final @NonNull GeckoSession session,
|
||||||
|
final @NonNull Autofill.Node node,
|
||||||
|
final @NonNull Autofill.NodeData data) {
|
||||||
|
if (!mSession.getAutofillSession().isVisible(node)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
final Autofill.Node focused = mSession.getAutofillSession().getFocused();
|
||||||
|
// We must have a focused node because |node| is visible
|
||||||
|
Objects.requireNonNull(focused);
|
||||||
|
|
||||||
final AutofillManager manager =
|
final Autofill.NodeData focusedData = mSession.getAutofillSession().dataFor(focused);
|
||||||
GeckoView.this.getContext().getSystemService(AutofillManager.class);
|
Objects.requireNonNull(focusedData);
|
||||||
if (manager == null) {
|
|
||||||
return;
|
ensureAutofillManager();
|
||||||
|
if (mAutofillManager != null) {
|
||||||
|
mAutofillManager.notifyViewExited(GeckoView.this, focusedData.getId());
|
||||||
|
mAutofillManager.notifyViewEntered(
|
||||||
|
GeckoView.this, focusedData.getId(), displayRectForId(session, focused));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
@Override
|
||||||
switch (notification) {
|
public void onNodeFocus(
|
||||||
case Autofill.Notify.SESSION_STARTED:
|
final @NonNull GeckoSession session,
|
||||||
// This line seems necessary for auto-fill to work on the initial page.
|
final @NonNull Autofill.Node focused,
|
||||||
case Autofill.Notify.SESSION_CANCELED:
|
final @NonNull Autofill.NodeData data) {
|
||||||
manager.cancel();
|
ensureAutofillManager();
|
||||||
break;
|
if (mAutofillManager != null) {
|
||||||
case Autofill.Notify.SESSION_COMMITTED:
|
mAutofillManager.notifyViewEntered(
|
||||||
manager.commit();
|
GeckoView.this, data.getId(), displayRectForId(session, focused));
|
||||||
break;
|
}
|
||||||
case Autofill.Notify.NODE_FOCUSED:
|
}
|
||||||
manager.notifyViewEntered(
|
|
||||||
GeckoView.this, node.getId(), displayRectForId(session, node));
|
@Override
|
||||||
break;
|
public void onNodeRemove(
|
||||||
case Autofill.Notify.NODE_BLURRED:
|
final @NonNull GeckoSession session,
|
||||||
manager.notifyViewExited(GeckoView.this, node.getId());
|
final @NonNull Autofill.Node node,
|
||||||
break;
|
final @NonNull Autofill.NodeData data) {}
|
||||||
case Autofill.Notify.NODE_UPDATED:
|
|
||||||
manager.notifyValueChanged(
|
@Override
|
||||||
GeckoView.this, node.getId(), AutofillValue.forText(node.getValue()));
|
public void onNodeUpdate(
|
||||||
break;
|
final @NonNull GeckoSession session,
|
||||||
}
|
final @NonNull Autofill.Node node,
|
||||||
} catch (final SecurityException e) {
|
final @NonNull Autofill.NodeData data) {
|
||||||
Log.e(LOGTAG, "Failed to call Autofill Manager API: ", e);
|
ensureAutofillManager();
|
||||||
|
if (mAutofillManager != null) {
|
||||||
|
mAutofillManager.notifyValueChanged(
|
||||||
|
GeckoView.this, data.getId(), AutofillValue.forText(data.getValue()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSessionCancel(final @NonNull GeckoSession session) {
|
||||||
|
ensureAutofillManager();
|
||||||
|
if (mAutofillManager != null) {
|
||||||
|
// This line seems necessary for auto-fill to work on the initial page.
|
||||||
|
mAutofillManager.cancel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSessionCommit(
|
||||||
|
final @NonNull GeckoSession session,
|
||||||
|
final @NonNull Autofill.Node node,
|
||||||
|
final @NonNull Autofill.NodeData data) {
|
||||||
|
ensureAutofillManager();
|
||||||
|
if (mAutofillManager != null) {
|
||||||
|
mAutofillManager.commit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSessionStart(final @NonNull GeckoSession session) {
|
||||||
|
ensureAutofillManager();
|
||||||
|
if (mAutofillManager != null) {
|
||||||
|
// This line seems necessary for auto-fill to work on the initial page.
|
||||||
|
mAutofillManager.cancel();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -79,6 +79,7 @@ import org.mozilla.geckoview.GeckoSession.PromptDelegate.TextPrompt;
|
||||||
|
|
||||||
public void handleEvent(
|
public void handleEvent(
|
||||||
final GeckoSession session, final GeckoBundle message, final EventCallback callback) {
|
final GeckoSession session, final GeckoBundle message, final EventCallback callback) {
|
||||||
|
Log.d(LOGTAG, "handleEvent " + message.getString("type"));
|
||||||
final PromptDelegate delegate = session.getPromptDelegate();
|
final PromptDelegate delegate = session.getPromptDelegate();
|
||||||
if (delegate == null) {
|
if (delegate == null) {
|
||||||
// Default behavior is same as calling dismiss() on callback.
|
// Default behavior is same as calling dismiss() on callback.
|
||||||
|
|
|
@ -20,6 +20,12 @@ exclude: true
|
||||||
- Added [`GeckoSession.setPriorityHint`][102.5] function to set the session to either high priority or default.
|
- Added [`GeckoSession.setPriorityHint`][102.5] function to set the session to either high priority or default.
|
||||||
- [`WebRequestError.ERROR_HTTPS_ONLY`][102.6] now has error category
|
- [`WebRequestError.ERROR_HTTPS_ONLY`][102.6] now has error category
|
||||||
`ERROR_CATEGORY_NETWORK` rather than `ERROR_CATEGORY_SECURITY`.
|
`ERROR_CATEGORY_NETWORK` rather than `ERROR_CATEGORY_SECURITY`.
|
||||||
|
- ⚠️ The Autofill.Delegate API now receives a [`AutofillNode`][102.7] object instead of
|
||||||
|
the entire [`Node`][102.8] structure. The `onAutofill` delegate method is now split
|
||||||
|
into several methods: [`onNodeAdd`][102.9], [`onNodeBlur`][102.10],
|
||||||
|
[`onNodeFocus`][102.11], [`onNodeRemove`][102.12], [`onNodeUpdate`][102.13],
|
||||||
|
[`onSessionCancel`][102.14], [`onSessionCommit`][102.15],
|
||||||
|
[`onSessionStart`][102.16].
|
||||||
|
|
||||||
[102.1]: {{javadoc_uri}}/GeckoSession.PromptDelegate.DateTimePrompt.html#stepValue
|
[102.1]: {{javadoc_uri}}/GeckoSession.PromptDelegate.DateTimePrompt.html#stepValue
|
||||||
[102.2]: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/date#step
|
[102.2]: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/date#step
|
||||||
|
@ -27,6 +33,16 @@ exclude: true
|
||||||
[102.4]: {{javadoc_uri}}/GeckoSession.NavigationDelegate.html#onLocationChange(org.mozilla.geckoview.GeckoSession,java.lang.String,java.util.List)
|
[102.4]: {{javadoc_uri}}/GeckoSession.NavigationDelegate.html#onLocationChange(org.mozilla.geckoview.GeckoSession,java.lang.String,java.util.List)
|
||||||
[102.5]: {{javadoc_uri}}/GeckoSession.html#setPriorityHint(int)
|
[102.5]: {{javadoc_uri}}/GeckoSession.html#setPriorityHint(int)
|
||||||
[102.6]: {{javadoc_uri}}/WebRequestError.html#ERROR_HTTPS_ONLY
|
[102.6]: {{javadoc_uri}}/WebRequestError.html#ERROR_HTTPS_ONLY
|
||||||
|
[102.7]: {{javadoc_uri}}/Autofill.AutofillNode.html
|
||||||
|
[102.8]: {{javadoc_uri}}/Autofill.Node.html
|
||||||
|
[102.9]: {{javadoc_uri}}/Autofill.Delegate.html#onNodeAdd(org.mozilla.geckoview.GeckoSession,org.mozilla.geckoview.Autofill.Node,org.mozilla.geckoview.Autofill.NodeData)
|
||||||
|
[102.10]: {{javadoc_uri}}/Autofill.Delegate.html#onNodeBlur(org.mozilla.geckoview.GeckoSession,org.mozilla.geckoview.Autofill.Node,org.mozilla.geckoview.Autofill.NodeData)
|
||||||
|
[102.11]: {{javadoc_uri}}/Autofill.Delegate.html#onNodeFocus(org.mozilla.geckoview.GeckoSession,org.mozilla.geckoview.Autofill.Node,org.mozilla.geckoview.Autofill.NodeData)
|
||||||
|
[102.12]: {{javadoc_uri}}/Autofill.Delegate.html#onNodeRemove(org.mozilla.geckoview.GeckoSession,org.mozilla.geckoview.Autofill.Node,org.mozilla.geckoview.Autofill.NodeData)
|
||||||
|
[102.13]: {{javadoc_uri}}/Autofill.Delegate.html#onNodeUpdate(org.mozilla.geckoview.GeckoSession,org.mozilla.geckoview.Autofill.Node,org.mozilla.geckoview.Autofill.NodeData)
|
||||||
|
[102.14]: {{javadoc_uri}}/Autofill.Delegate.html#onSessionCancel(org.mozilla.geckoview.GeckoSession)
|
||||||
|
[102.15]: {{javadoc_uri}}/Autofill.Delegate.html#onSessionCommit(org.mozilla.geckoview.GeckoSession,org.mozilla.geckoview.Autofill.Node,org.mozilla.geckoview.Autofill.NodeData)
|
||||||
|
[102.16]: {{javadoc_uri}}/Autofill.Delegate.html#onSessionStart(org.mozilla.geckoview.GeckoSession)
|
||||||
|
|
||||||
## v101
|
## v101
|
||||||
- Added [`GeckoDisplay.surfaceChanged`][101.1] function taking new type [`GeckoDisplay.SurfaceInfo`][101.2].
|
- Added [`GeckoDisplay.surfaceChanged`][101.1] function taking new type [`GeckoDisplay.SurfaceInfo`][101.2].
|
||||||
|
@ -1181,4 +1197,4 @@ to allow adding gecko profiler markers.
|
||||||
[65.24]: {{javadoc_uri}}/CrashReporter.html#sendCrashReport(android.content.Context,android.os.Bundle,java.lang.String)
|
[65.24]: {{javadoc_uri}}/CrashReporter.html#sendCrashReport(android.content.Context,android.os.Bundle,java.lang.String)
|
||||||
[65.25]: {{javadoc_uri}}/GeckoResult.html
|
[65.25]: {{javadoc_uri}}/GeckoResult.html
|
||||||
|
|
||||||
[api-version]: 2f401dd976431a7a150b8743f3949a50adbeeb4b
|
[api-version]: dbe27cda45ed1bdff264b14f4471fdbe8438ca94
|
||||||
|
|
|
@ -5,6 +5,9 @@
|
||||||
const { GeckoViewUtils } = ChromeUtils.import(
|
const { GeckoViewUtils } = ChromeUtils.import(
|
||||||
"resource://gre/modules/GeckoViewUtils.jsm"
|
"resource://gre/modules/GeckoViewUtils.jsm"
|
||||||
);
|
);
|
||||||
|
const { EventDispatcher } = ChromeUtils.import(
|
||||||
|
"resource://gre/modules/Messaging.jsm"
|
||||||
|
);
|
||||||
|
|
||||||
var EXPORTED_SYMBOLS = ["GeckoViewActorChild"];
|
var EXPORTED_SYMBOLS = ["GeckoViewActorChild"];
|
||||||
|
|
||||||
|
@ -15,9 +18,7 @@ class GeckoViewActorChild extends JSWindowActorChild {
|
||||||
}
|
}
|
||||||
|
|
||||||
actorCreated() {
|
actorCreated() {
|
||||||
this.eventDispatcher = GeckoViewUtils.getDispatcherForWindow(
|
this.eventDispatcher = EventDispatcher.forActor(this);
|
||||||
this.docShell.domWindow
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,15 +19,37 @@ class GeckoViewActorParent extends JSWindowActorParent {
|
||||||
}
|
}
|
||||||
|
|
||||||
get window() {
|
get window() {
|
||||||
return this.browser.ownerGlobal;
|
const { browsingContext } = this;
|
||||||
|
// If this is a chrome actor, the chrome window will be at
|
||||||
|
// browsingContext.window.
|
||||||
|
if (!browsingContext.isContent && browsingContext.window) {
|
||||||
|
return browsingContext.window;
|
||||||
|
}
|
||||||
|
return this.browser?.ownerGlobal;
|
||||||
}
|
}
|
||||||
|
|
||||||
get eventDispatcher() {
|
get eventDispatcher() {
|
||||||
return this.window.moduleManager.eventDispatcher;
|
return this.window?.moduleManager.eventDispatcher;
|
||||||
}
|
}
|
||||||
|
|
||||||
receiveMessage(aMessage) {
|
receiveMessage(aMessage) {
|
||||||
|
if (!this.window) {
|
||||||
|
// If we have no window, it means that this browsingContext has been
|
||||||
|
// destroyed already and there's nothing to do here for us.
|
||||||
|
debug`receiveMessage window destroyed ${aMessage.name} ${aMessage.data?.type}`;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (aMessage.name) {
|
||||||
|
case "DispatcherMessage":
|
||||||
|
return this.eventDispatcher.sendRequest(aMessage.data);
|
||||||
|
case "DispatcherQuery":
|
||||||
|
return this.eventDispatcher.sendRequestForResult(aMessage.data);
|
||||||
|
}
|
||||||
|
|
||||||
// By default messages are forwarded to the module.
|
// By default messages are forwarded to the module.
|
||||||
this.window.moduleManager.onMessageFromActor(this.name, aMessage);
|
return this.window.moduleManager.onMessageFromActor(this.name, aMessage);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { debug, warn } = GeckoViewUtils.initLogging("GeckoViewActorParent");
|
||||||
|
|
|
@ -1,356 +1,101 @@
|
||||||
// -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
|
|
||||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
/* 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,
|
* 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/. */
|
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
var EXPORTED_SYMBOLS = ["GeckoViewAutofill"];
|
|
||||||
|
|
||||||
const { XPCOMUtils } = ChromeUtils.import(
|
|
||||||
"resource://gre/modules/XPCOMUtils.jsm"
|
|
||||||
);
|
|
||||||
const { GeckoViewUtils } = ChromeUtils.import(
|
const { GeckoViewUtils } = ChromeUtils.import(
|
||||||
"resource://gre/modules/GeckoViewUtils.jsm"
|
"resource://gre/modules/GeckoViewUtils.jsm"
|
||||||
);
|
);
|
||||||
|
|
||||||
XPCOMUtils.defineLazyModuleGetters(this, {
|
var EXPORTED_SYMBOLS = ["gAutofillManager"];
|
||||||
DeferredTask: "resource://gre/modules/DeferredTask.jsm",
|
|
||||||
FormLikeFactory: "resource://gre/modules/FormLikeFactory.jsm",
|
|
||||||
LoginManagerChild: "resource://gre/modules/LoginManagerChild.jsm",
|
|
||||||
Services: "resource://gre/modules/Services.jsm",
|
|
||||||
});
|
|
||||||
|
|
||||||
const { debug, warn } = GeckoViewUtils.initLogging("Autofill");
|
class Autofill {
|
||||||
|
constructor(sessionId, eventDispatcher) {
|
||||||
class GeckoViewAutofill {
|
this.eventDispatcher = eventDispatcher;
|
||||||
constructor(aEventDispatcher) {
|
this.sessionId = sessionId;
|
||||||
this._eventDispatcher = aEventDispatcher;
|
|
||||||
this._autofillElements = undefined;
|
|
||||||
this._autofillInfos = undefined;
|
|
||||||
this._autofillTasks = undefined;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
start() {
|
||||||
* Process an auto-fillable form and send the relevant details of the form
|
this.eventDispatcher.sendRequest({
|
||||||
* to Java. Multiple calls within a short time period for the same form are
|
type: "GeckoView:StartAutofill",
|
||||||
* coalesced, so that, e.g., if multiple inputs are added to a form in
|
sessionId: this.sessionId,
|
||||||
* succession, we will only perform one processing pass. Note that for inputs
|
|
||||||
* without forms, FormLikeFactory treats the document as the "form", but
|
|
||||||
* there is no difference in how we process them.
|
|
||||||
*
|
|
||||||
* @param aFormLike A FormLike object produced by FormLikeFactory.
|
|
||||||
*/
|
|
||||||
addElement(aFormLike) {
|
|
||||||
this._addElement(aFormLike, /* fromDeferredTask */ false);
|
|
||||||
}
|
|
||||||
|
|
||||||
_getInfo(aElement, aParent, aRoot, aUsernameField) {
|
|
||||||
if (!this._autofillInfos) {
|
|
||||||
this._autofillInfos = new WeakMap();
|
|
||||||
this._autofillElements = new Map();
|
|
||||||
}
|
|
||||||
|
|
||||||
let info = this._autofillInfos.get(aElement);
|
|
||||||
if (info) {
|
|
||||||
return info;
|
|
||||||
}
|
|
||||||
|
|
||||||
const window = aElement.ownerGlobal;
|
|
||||||
const bounds = aElement.getBoundingClientRect();
|
|
||||||
const isInputElement = window.HTMLInputElement.isInstance(aElement);
|
|
||||||
|
|
||||||
info = {
|
|
||||||
isInputElement,
|
|
||||||
uuid: Services.uuid
|
|
||||||
.generateUUID()
|
|
||||||
.toString()
|
|
||||||
.slice(1, -1), // discard the surrounding curly braces
|
|
||||||
parentUuid: aParent,
|
|
||||||
rootUuid: aRoot,
|
|
||||||
tag: aElement.tagName,
|
|
||||||
type: isInputElement ? aElement.type : null,
|
|
||||||
value: isInputElement ? aElement.value : null,
|
|
||||||
editable:
|
|
||||||
isInputElement &&
|
|
||||||
[
|
|
||||||
"color",
|
|
||||||
"date",
|
|
||||||
"datetime-local",
|
|
||||||
"email",
|
|
||||||
"month",
|
|
||||||
"number",
|
|
||||||
"password",
|
|
||||||
"range",
|
|
||||||
"search",
|
|
||||||
"tel",
|
|
||||||
"text",
|
|
||||||
"time",
|
|
||||||
"url",
|
|
||||||
"week",
|
|
||||||
].includes(aElement.type),
|
|
||||||
disabled: isInputElement ? aElement.disabled : null,
|
|
||||||
attributes: Object.assign(
|
|
||||||
{},
|
|
||||||
...Array.from(aElement.attributes)
|
|
||||||
.filter(attr => attr.localName !== "value")
|
|
||||||
.map(attr => ({ [attr.localName]: attr.value }))
|
|
||||||
),
|
|
||||||
origin: aElement.ownerDocument.location.origin,
|
|
||||||
autofillhint: "",
|
|
||||||
bounds: {
|
|
||||||
left: bounds.left,
|
|
||||||
top: bounds.top,
|
|
||||||
right: bounds.right,
|
|
||||||
bottom: bounds.bottom,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
if (aElement === aUsernameField) {
|
|
||||||
info.autofillhint = "username"; // AUTOFILL.HINT.USERNAME
|
|
||||||
} else if (isInputElement) {
|
|
||||||
// Using autocomplete attribute if it is email.
|
|
||||||
const autocompleteInfo = aElement.getAutocompleteInfo();
|
|
||||||
if (autocompleteInfo) {
|
|
||||||
const autocompleteAttr = autocompleteInfo.fieldName;
|
|
||||||
if (autocompleteAttr == "email") {
|
|
||||||
info.type = "email";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this._autofillInfos.set(aElement, info);
|
|
||||||
this._autofillElements.set(info.uuid, Cu.getWeakReference(aElement));
|
|
||||||
return info;
|
|
||||||
}
|
|
||||||
|
|
||||||
_updateInfoValues(aElements) {
|
|
||||||
if (!this._autofillInfos) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
const updated = [];
|
|
||||||
for (const element of aElements) {
|
|
||||||
const info = this._autofillInfos.get(element);
|
|
||||||
|
|
||||||
if (!info?.isInputElement || info.value === element.value) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
debug`Updating value ${info.value} to ${element.value}`;
|
|
||||||
|
|
||||||
info.value = element.value;
|
|
||||||
this._autofillInfos.set(element, info);
|
|
||||||
updated.push(info);
|
|
||||||
}
|
|
||||||
return updated;
|
|
||||||
}
|
|
||||||
|
|
||||||
_addElement(aFormLike, aFromDeferredTask) {
|
|
||||||
let task =
|
|
||||||
this._autofillTasks && this._autofillTasks.get(aFormLike.rootElement);
|
|
||||||
if (task && !aFromDeferredTask) {
|
|
||||||
// We already have a pending task; cancel that and start a new one.
|
|
||||||
debug`Canceling previous auto-fill task`;
|
|
||||||
task.disarm();
|
|
||||||
task = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!task) {
|
|
||||||
if (aFromDeferredTask) {
|
|
||||||
// Canceled before we could run the task.
|
|
||||||
debug`Auto-fill task canceled`;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Start a new task so we can coalesce adding elements in one batch.
|
|
||||||
debug`Deferring auto-fill task`;
|
|
||||||
task = new DeferredTask(() => this._addElement(aFormLike, true), 100);
|
|
||||||
task.arm();
|
|
||||||
if (!this._autofillTasks) {
|
|
||||||
this._autofillTasks = new WeakMap();
|
|
||||||
}
|
|
||||||
this._autofillTasks.set(aFormLike.rootElement, task);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
debug`Adding auto-fill ${aFormLike.rootElement.tagName}`;
|
|
||||||
|
|
||||||
this._autofillTasks.delete(aFormLike.rootElement);
|
|
||||||
|
|
||||||
const window = aFormLike.rootElement.ownerGlobal;
|
|
||||||
// Get password field to get better form data via LoginManagerChild.
|
|
||||||
let passwordField;
|
|
||||||
for (const field of aFormLike.elements) {
|
|
||||||
if (
|
|
||||||
ChromeUtils.getClassName(field) === "HTMLInputElement" &&
|
|
||||||
field.type == "password"
|
|
||||||
) {
|
|
||||||
passwordField = field;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const [usernameField] = LoginManagerChild.forWindow(
|
|
||||||
window
|
|
||||||
).getUserNameAndPasswordFields(passwordField || aFormLike.elements[0]);
|
|
||||||
|
|
||||||
const focusedElement = aFormLike.rootElement.ownerDocument.activeElement;
|
|
||||||
let sendFocusEvent = aFormLike.rootElement === focusedElement;
|
|
||||||
|
|
||||||
const rootInfo = this._getInfo(
|
|
||||||
aFormLike.rootElement,
|
|
||||||
null,
|
|
||||||
undefined,
|
|
||||||
null
|
|
||||||
);
|
|
||||||
|
|
||||||
rootInfo.rootUuid = rootInfo.uuid;
|
|
||||||
rootInfo.children = aFormLike.elements
|
|
||||||
.filter(
|
|
||||||
element =>
|
|
||||||
element.type != "hidden" &&
|
|
||||||
(!usernameField ||
|
|
||||||
element.type != "text" ||
|
|
||||||
element == usernameField ||
|
|
||||||
(element.getAutocompleteInfo() &&
|
|
||||||
element.getAutocompleteInfo().fieldName == "email"))
|
|
||||||
)
|
|
||||||
.map(element => {
|
|
||||||
sendFocusEvent |= element === focusedElement;
|
|
||||||
return this._getInfo(
|
|
||||||
element,
|
|
||||||
rootInfo.uuid,
|
|
||||||
rootInfo.uuid,
|
|
||||||
usernameField
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
this._eventDispatcher.dispatch("GeckoView:AddAutofill", rootInfo, {
|
|
||||||
onSuccess: responses => {
|
|
||||||
// `responses` is an object with global IDs as keys.
|
|
||||||
debug`Performing auto-fill ${Object.keys(responses)}`;
|
|
||||||
|
|
||||||
const AUTOFILL_STATE = "autofill";
|
|
||||||
const winUtils = window.windowUtils;
|
|
||||||
|
|
||||||
for (const uuid in responses) {
|
|
||||||
const entry =
|
|
||||||
this._autofillElements && this._autofillElements.get(uuid);
|
|
||||||
const element = entry && entry.get();
|
|
||||||
const value = responses[uuid] || "";
|
|
||||||
|
|
||||||
if (
|
|
||||||
window.HTMLInputElement.isInstance(element) &&
|
|
||||||
!element.disabled &&
|
|
||||||
element.parentElement
|
|
||||||
) {
|
|
||||||
element.setUserInput(value);
|
|
||||||
if (winUtils && element.value === value) {
|
|
||||||
// Add highlighting for autofilled fields.
|
|
||||||
winUtils.addManuallyManagedState(element, AUTOFILL_STATE);
|
|
||||||
|
|
||||||
// Remove highlighting when the field is changed.
|
|
||||||
element.addEventListener(
|
|
||||||
"input",
|
|
||||||
_ =>
|
|
||||||
winUtils.removeManuallyManagedState(element, AUTOFILL_STATE),
|
|
||||||
{ mozSystemGroup: true, once: true }
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else if (element) {
|
|
||||||
warn`Don't know how to auto-fill ${element.tagName}`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onError: error => {
|
|
||||||
warn`Cannot perform autofill ${error}`;
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (sendFocusEvent) {
|
|
||||||
// We might have missed sending a focus event for the active element.
|
|
||||||
this.onFocus(aFormLike.ownerDocument.activeElement);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
add(node) {
|
||||||
* Called when an auto-fillable field is focused or blurred.
|
return this.eventDispatcher.sendRequestForResult({
|
||||||
*
|
type: "GeckoView:AddAutofill",
|
||||||
* @param aTarget Focused element, or null if an element has lost focus.
|
node,
|
||||||
*/
|
});
|
||||||
onFocus(aTarget) {
|
|
||||||
debug`Auto-fill focus on ${aTarget && aTarget.tagName}`;
|
|
||||||
|
|
||||||
const info =
|
|
||||||
aTarget && this._autofillInfos && this._autofillInfos.get(aTarget);
|
|
||||||
if (!aTarget || info) {
|
|
||||||
this._eventDispatcher.dispatch("GeckoView:OnAutofillFocus", info);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
commitAutofill(aFormLike) {
|
focus(node) {
|
||||||
if (!aFormLike) {
|
this.eventDispatcher.sendRequest({
|
||||||
throw new Error("null-form on autofill commit");
|
type: "GeckoView:OnAutofillFocus",
|
||||||
}
|
node,
|
||||||
|
});
|
||||||
debug`Committing auto-fill for ${aFormLike.rootElement.tagName}`;
|
|
||||||
|
|
||||||
const updatedNodeInfos = this._updateInfoValues([
|
|
||||||
aFormLike.rootElement,
|
|
||||||
...aFormLike.elements,
|
|
||||||
]);
|
|
||||||
|
|
||||||
for (const updatedInfo of updatedNodeInfos) {
|
|
||||||
debug`Updating node ${updatedInfo}`;
|
|
||||||
this._eventDispatcher.dispatch("GeckoView:UpdateAutofill", updatedInfo);
|
|
||||||
}
|
|
||||||
|
|
||||||
const info = this._getInfo(aFormLike.rootElement);
|
|
||||||
if (info) {
|
|
||||||
debug`Committing node ${info}`;
|
|
||||||
this._eventDispatcher.dispatch("GeckoView:CommitAutofill", info);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
update(node) {
|
||||||
* Clear all tracked auto-fill forms and notify Java.
|
this.eventDispatcher.sendRequest({
|
||||||
*/
|
type: "GeckoView:UpdateAutofill",
|
||||||
clearElements() {
|
node,
|
||||||
debug`Clearing auto-fill`;
|
});
|
||||||
|
}
|
||||||
|
|
||||||
this._autofillTasks = undefined;
|
commit(node) {
|
||||||
this._autofillInfos = undefined;
|
this.eventDispatcher.sendRequest({
|
||||||
this._autofillElements = undefined;
|
type: "GeckoView:CommitAutofill",
|
||||||
|
node,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
this._eventDispatcher.sendRequest({
|
clear() {
|
||||||
|
this.eventDispatcher.sendRequest({
|
||||||
type: "GeckoView:ClearAutofill",
|
type: "GeckoView:ClearAutofill",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
class AutofillManager {
|
||||||
* Scan for auto-fillable forms and add them if necessary. Called when a page
|
sessions = new Set();
|
||||||
* is navigated to through history, in which case we don't get our typical
|
autofill = null;
|
||||||
* "input added" notifications.
|
|
||||||
*
|
|
||||||
* @param aDoc Document to scan.
|
|
||||||
*/
|
|
||||||
scanDocument(aDoc) {
|
|
||||||
// Add forms first; only check forms with password inputs.
|
|
||||||
const inputs = aDoc.querySelectorAll("input[type=password]");
|
|
||||||
let inputAdded = false;
|
|
||||||
for (let i = 0; i < inputs.length; i++) {
|
|
||||||
if (inputs[i].form) {
|
|
||||||
// Let _addElement coalesce multiple calls for the same form.
|
|
||||||
this._addElement(FormLikeFactory.createFromForm(inputs[i].form));
|
|
||||||
} else if (!inputAdded) {
|
|
||||||
// Treat inputs without forms as one unit, and process them only once.
|
|
||||||
inputAdded = true;
|
|
||||||
this._addElement(FormLikeFactory.createFromField(inputs[i]));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Finally add frames.
|
ensure(sessionId, eventDispatcher) {
|
||||||
const frames = aDoc.defaultView.frames;
|
if (!this.sessions.has(sessionId)) {
|
||||||
for (let i = 0; i < frames.length; i++) {
|
this.autofill = new Autofill(sessionId, eventDispatcher);
|
||||||
this.scanDocument(frames[i].document);
|
this.sessions.add(sessionId);
|
||||||
|
this.autofill.start();
|
||||||
}
|
}
|
||||||
|
// This could be called for an outdated session, in which case we will just
|
||||||
|
// ignore the autofill call.
|
||||||
|
if (sessionId !== this.autofill.sessionId) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return this.autofill;
|
||||||
|
}
|
||||||
|
|
||||||
|
get(sessionId) {
|
||||||
|
if (!this.autofill || sessionId !== this.autofill.sessionId) {
|
||||||
|
warn`Disregarding old session ${sessionId}`;
|
||||||
|
// We disregard old sessions
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return this.autofill;
|
||||||
|
}
|
||||||
|
|
||||||
|
delete(sessionId) {
|
||||||
|
this.sessions.delete(sessionId);
|
||||||
|
if (!this.autofill || sessionId !== this.autofill.sessionId) {
|
||||||
|
// this delete call might happen *after* the next session already
|
||||||
|
// started, in that case, we can safely ignore this call.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.autofill.clear();
|
||||||
|
this.autofill = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var gAutofillManager = new AutofillManager();
|
||||||
|
|
||||||
|
const { debug, warn } = GeckoViewUtils.initLogging("Autofill");
|
||||||
|
|
|
@ -1,100 +0,0 @@
|
||||||
/* 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/. */
|
|
||||||
|
|
||||||
"use strict";
|
|
||||||
|
|
||||||
var EXPORTED_SYMBOLS = ["GeckoViewRecordingMedia"];
|
|
||||||
|
|
||||||
const { GeckoViewUtils } = ChromeUtils.import(
|
|
||||||
"resource://gre/modules/GeckoViewUtils.jsm"
|
|
||||||
);
|
|
||||||
const { XPCOMUtils } = ChromeUtils.import(
|
|
||||||
"resource://gre/modules/XPCOMUtils.jsm"
|
|
||||||
);
|
|
||||||
|
|
||||||
XPCOMUtils.defineLazyServiceGetter(
|
|
||||||
this,
|
|
||||||
"MediaManagerService",
|
|
||||||
"@mozilla.org/mediaManagerService;1",
|
|
||||||
"nsIMediaManagerService"
|
|
||||||
);
|
|
||||||
|
|
||||||
const STATUS_RECORDING = "recording";
|
|
||||||
const STATUS_INACTIVE = "inactive";
|
|
||||||
const TYPE_CAMERA = "camera";
|
|
||||||
const TYPE_MICROPHONE = "microphone";
|
|
||||||
|
|
||||||
const GeckoViewRecordingMedia = {
|
|
||||||
// The event listener for this is hooked up in GeckoViewStartup.jsm
|
|
||||||
observe(aSubject, aTopic, aData) {
|
|
||||||
debug`observe: aTopic=${aTopic}`;
|
|
||||||
switch (aTopic) {
|
|
||||||
case "recording-device-events": {
|
|
||||||
this.handleRecordingDeviceEvents();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
handleRecordingDeviceEvents() {
|
|
||||||
const [dispatcher] = GeckoViewUtils.getActiveDispatcherAndWindow();
|
|
||||||
if (dispatcher) {
|
|
||||||
const windows = MediaManagerService.activeMediaCaptureWindows;
|
|
||||||
const devices = [];
|
|
||||||
|
|
||||||
const getStatusString = function(activityStatus) {
|
|
||||||
switch (activityStatus) {
|
|
||||||
case MediaManagerService.STATE_CAPTURE_ENABLED:
|
|
||||||
case MediaManagerService.STATE_CAPTURE_DISABLED:
|
|
||||||
return STATUS_RECORDING;
|
|
||||||
case MediaManagerService.STATE_NOCAPTURE:
|
|
||||||
return STATUS_INACTIVE;
|
|
||||||
default:
|
|
||||||
throw new Error("Unexpected activityStatus value");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
for (let i = 0; i < windows.length; i++) {
|
|
||||||
const win = windows.queryElementAt(i, Ci.nsIDOMWindow);
|
|
||||||
const hasCamera = {};
|
|
||||||
const hasMicrophone = {};
|
|
||||||
const screen = {};
|
|
||||||
const window = {};
|
|
||||||
const browser = {};
|
|
||||||
const mediaDevices = {};
|
|
||||||
MediaManagerService.mediaCaptureWindowState(
|
|
||||||
win,
|
|
||||||
hasCamera,
|
|
||||||
hasMicrophone,
|
|
||||||
screen,
|
|
||||||
window,
|
|
||||||
browser,
|
|
||||||
mediaDevices
|
|
||||||
);
|
|
||||||
var cameraStatus = getStatusString(hasCamera.value);
|
|
||||||
var microphoneStatus = getStatusString(hasMicrophone.value);
|
|
||||||
if (hasCamera.value != MediaManagerService.STATE_NOCAPTURE) {
|
|
||||||
devices.push({
|
|
||||||
type: TYPE_CAMERA,
|
|
||||||
status: cameraStatus,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (hasMicrophone.value != MediaManagerService.STATE_NOCAPTURE) {
|
|
||||||
devices.push({
|
|
||||||
type: TYPE_MICROPHONE,
|
|
||||||
status: microphoneStatus,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
dispatcher.sendRequestForResult({
|
|
||||||
type: "GeckoView:MediaRecordingStatusChanged",
|
|
||||||
devices,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
console.log("no dispatcher present");
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const { debug, warn } = GeckoViewUtils.initLogging("GeckoViewMedia");
|
|
|
@ -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/. */
|
||||||
|
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
var EXPORTED_SYMBOLS = ["GeckoViewSelectionAction"];
|
||||||
|
|
||||||
|
const { GeckoViewModule } = ChromeUtils.import(
|
||||||
|
"resource://gre/modules/GeckoViewModule.jsm"
|
||||||
|
);
|
||||||
|
|
||||||
|
const { XPCOMUtils } = ChromeUtils.import(
|
||||||
|
"resource://gre/modules/XPCOMUtils.jsm"
|
||||||
|
);
|
||||||
|
|
||||||
|
XPCOMUtils.defineLazyModuleGetters(this, {
|
||||||
|
Services: "resource://gre/modules/Services.jsm",
|
||||||
|
});
|
||||||
|
|
||||||
|
class GeckoViewSelectionAction extends GeckoViewModule {
|
||||||
|
onEnable() {
|
||||||
|
debug`onEnable`;
|
||||||
|
this.registerListener(["GeckoView:ExecuteSelectionAction"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
onDisable() {
|
||||||
|
debug`onDisable`;
|
||||||
|
this.unregisterListener();
|
||||||
|
}
|
||||||
|
|
||||||
|
get actor() {
|
||||||
|
return this.getActor("SelectionActionDelegate");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bundle event handler.
|
||||||
|
onEvent(aEvent, aData, aCallback) {
|
||||||
|
debug`onEvent: ${aEvent}`;
|
||||||
|
|
||||||
|
switch (aEvent) {
|
||||||
|
case "GeckoView:ExecuteSelectionAction": {
|
||||||
|
this.actor.executeSelectionAction(aData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const { debug, warn } = GeckoViewSelectionAction.initLogging(
|
||||||
|
"GeckoViewSelectionAction"
|
||||||
|
);
|
|
@ -211,47 +211,6 @@ var GeckoViewUtils = {
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
|
||||||
* Add lazy event listeners on the per-window EventDispatcher, and only load
|
|
||||||
* the actual handler when an event is being handled.
|
|
||||||
*
|
|
||||||
* @param window Window with the target EventDispatcher.
|
|
||||||
* @param events Event name as a string or array.
|
|
||||||
* @param handler If specified, function that, for a given event, returns the
|
|
||||||
* actual event handler as an object or an array of objects.
|
|
||||||
* If handler is not specified, the actual event handler is
|
|
||||||
* specified using the scope and name pair.
|
|
||||||
* @param scope See handler.
|
|
||||||
* @param name See handler.
|
|
||||||
* @param once If true, only listen to the specified events once.
|
|
||||||
*/
|
|
||||||
registerLazyWindowEventListener(
|
|
||||||
window,
|
|
||||||
events,
|
|
||||||
{ handler, scope, name, once }
|
|
||||||
) {
|
|
||||||
const dispatcher = this.getDispatcherForWindow(window);
|
|
||||||
|
|
||||||
this._addLazyListeners(
|
|
||||||
events,
|
|
||||||
handler,
|
|
||||||
scope,
|
|
||||||
name,
|
|
||||||
(events, listener) => {
|
|
||||||
dispatcher.registerListener(listener, events);
|
|
||||||
},
|
|
||||||
(handlers, listener, args) => {
|
|
||||||
if (!once) {
|
|
||||||
dispatcher.unregisterListener(listener, args[0]);
|
|
||||||
handlers.forEach(handler =>
|
|
||||||
dispatcher.registerListener(handler, args[0])
|
|
||||||
);
|
|
||||||
}
|
|
||||||
handlers.forEach(handler => handler.onEvent(...args));
|
|
||||||
}
|
|
||||||
);
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add lazy pref observers, and only load the actual handler once the pref
|
* Add lazy pref observers, and only load the actual handler once the pref
|
||||||
* value changes from default, and every time the pref value changes
|
* value changes from default, and every time the pref value changes
|
||||||
|
@ -365,23 +324,6 @@ var GeckoViewUtils = {
|
||||||
return null;
|
return null;
|
||||||
},
|
},
|
||||||
|
|
||||||
getActiveDispatcherAndWindow() {
|
|
||||||
const bc = Services.focus.activeBrowsingContext;
|
|
||||||
const win = bc ? bc.window : null; // WON'T WORK FOR OOP IFRAMES!
|
|
||||||
let dispatcher = this.getDispatcherForWindow(win);
|
|
||||||
if (dispatcher) {
|
|
||||||
return [dispatcher, win];
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const win of Services.wm.getEnumerator(/* windowType */ null)) {
|
|
||||||
dispatcher = this.getDispatcherForWindow(win);
|
|
||||||
if (dispatcher) {
|
|
||||||
return [dispatcher, win];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return [null, null];
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add logging functions to the specified scope that forward to the given
|
* Add logging functions to the specified scope that forward to the given
|
||||||
* Log.jsm logger. Currently "debug" and "warn" functions are supported. To
|
* Log.jsm logger. Currently "debug" and "warn" functions are supported. To
|
||||||
|
|
|
@ -13,12 +13,47 @@ var EXPORTED_SYMBOLS = ["EventDispatcher"];
|
||||||
const IS_PARENT_PROCESS =
|
const IS_PARENT_PROCESS =
|
||||||
Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_DEFAULT;
|
Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_DEFAULT;
|
||||||
|
|
||||||
|
class ChildActorDispatcher {
|
||||||
|
constructor(actor) {
|
||||||
|
this._actor = actor;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Bug 1658980
|
||||||
|
registerListener(aListener, aEvents) {
|
||||||
|
throw new Error("Cannot registerListener in child actor");
|
||||||
|
}
|
||||||
|
unregisterListener(aListener, aEvents) {
|
||||||
|
throw new Error("Cannot registerListener in child actor");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends a request to Java.
|
||||||
|
*
|
||||||
|
* @param aMsg Message to send; must be an object with a "type" property
|
||||||
|
*/
|
||||||
|
sendRequest(aMsg) {
|
||||||
|
this._actor.sendAsyncMessage("DispatcherMessage", aMsg);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends a request to Java, returning a Promise that resolves to the response.
|
||||||
|
*
|
||||||
|
* @param aMsg Message to send; must be an object with a "type" property
|
||||||
|
* @return A Promise resolving to the response
|
||||||
|
*/
|
||||||
|
sendRequestForResult(aMsg) {
|
||||||
|
return this._actor.sendQuery("DispatcherQuery", aMsg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function DispatcherDelegate(aDispatcher, aMessageManager) {
|
function DispatcherDelegate(aDispatcher, aMessageManager) {
|
||||||
this._dispatcher = aDispatcher;
|
this._dispatcher = aDispatcher;
|
||||||
this._messageManager = aMessageManager;
|
this._messageManager = aMessageManager;
|
||||||
|
|
||||||
if (!aDispatcher) {
|
if (!aDispatcher) {
|
||||||
// Child process.
|
// Child process.
|
||||||
|
// TODO: this doesn't work with Fission, remove this code path once every
|
||||||
|
// consumer has been migrated. Bug 1569360.
|
||||||
this._replies = new Map();
|
this._replies = new Map();
|
||||||
(aMessageManager || Services.cpmm).addMessageListener(
|
(aMessageManager || Services.cpmm).addMessageListener(
|
||||||
"GeckoView:MessagingReply",
|
"GeckoView:MessagingReply",
|
||||||
|
@ -226,6 +261,15 @@ var EventDispatcher = {
|
||||||
return new DispatcherDelegate(null, aMessageManager);
|
return new DispatcherDelegate(null, aMessageManager);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the EventDispatcher instance associated with an actor.
|
||||||
|
*
|
||||||
|
* @param aActor an actor
|
||||||
|
*/
|
||||||
|
forActor(aActor) {
|
||||||
|
return new ChildActorDispatcher(aActor);
|
||||||
|
},
|
||||||
|
|
||||||
receiveMessage(aMsg) {
|
receiveMessage(aMsg) {
|
||||||
// aMsg.data includes keys: global, event, data, uuid
|
// aMsg.data includes keys: global, event, data, uuid
|
||||||
let callback;
|
let callback;
|
||||||
|
|
|
@ -18,7 +18,6 @@ EXTRA_JS_MODULES += [
|
||||||
"GeckoViewConsole.jsm",
|
"GeckoViewConsole.jsm",
|
||||||
"GeckoViewContent.jsm",
|
"GeckoViewContent.jsm",
|
||||||
"GeckoViewContentBlocking.jsm",
|
"GeckoViewContentBlocking.jsm",
|
||||||
"GeckoViewMedia.jsm",
|
|
||||||
"GeckoViewMediaControl.jsm",
|
"GeckoViewMediaControl.jsm",
|
||||||
"GeckoViewModule.jsm",
|
"GeckoViewModule.jsm",
|
||||||
"GeckoViewNavigation.jsm",
|
"GeckoViewNavigation.jsm",
|
||||||
|
@ -26,6 +25,7 @@ EXTRA_JS_MODULES += [
|
||||||
"GeckoViewProgress.jsm",
|
"GeckoViewProgress.jsm",
|
||||||
"GeckoViewPushController.jsm",
|
"GeckoViewPushController.jsm",
|
||||||
"GeckoViewRemoteDebugger.jsm",
|
"GeckoViewRemoteDebugger.jsm",
|
||||||
|
"GeckoViewSelectionAction.jsm",
|
||||||
"GeckoViewSettings.jsm",
|
"GeckoViewSettings.jsm",
|
||||||
"GeckoViewStorageController.jsm",
|
"GeckoViewStorageController.jsm",
|
||||||
"GeckoViewTab.jsm",
|
"GeckoViewTab.jsm",
|
||||||
|
|
|
@ -72,6 +72,8 @@ DIRS += [
|
||||||
]
|
]
|
||||||
|
|
||||||
TEST_HARNESS_FILES.testing.mochitest.tests.junit += [
|
TEST_HARNESS_FILES.testing.mochitest.tests.junit += [
|
||||||
|
"geckoview/src/androidTest/assets/www/forms_iframe.html",
|
||||||
|
"geckoview/src/androidTest/assets/www/forms_xorigin.html",
|
||||||
"geckoview/src/androidTest/assets/www/hello.html",
|
"geckoview/src/androidTest/assets/www/hello.html",
|
||||||
"geckoview/src/androidTest/assets/www/iframe_http_only.html",
|
"geckoview/src/androidTest/assets/www/iframe_http_only.html",
|
||||||
"geckoview/src/androidTest/assets/www/simple_redirect.sjs",
|
"geckoview/src/androidTest/assets/www/simple_redirect.sjs",
|
||||||
|
|
|
@ -48,7 +48,6 @@ CustomProtocolHandler.prototype = {
|
||||||
}
|
}
|
||||||
return this.QueryInterface(aIID);
|
return this.QueryInterface(aIID);
|
||||||
},
|
},
|
||||||
lockFactory() {},
|
|
||||||
|
|
||||||
/** nsISupports */
|
/** nsISupports */
|
||||||
QueryInterface: ChromeUtils.generateQI(["nsIProtocolHandler", "nsIFactory"]),
|
QueryInterface: ChromeUtils.generateQI(["nsIProtocolHandler", "nsIFactory"]),
|
||||||
|
|
|
@ -24,9 +24,6 @@ var factory = {
|
||||||
}
|
}
|
||||||
return unsafeAboutModule.QueryInterface(aIID);
|
return unsafeAboutModule.QueryInterface(aIID);
|
||||||
},
|
},
|
||||||
lockFactory(aLock) {
|
|
||||||
throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED);
|
|
||||||
},
|
|
||||||
QueryInterface: ChromeUtils.generateQI(["nsIFactory"]),
|
QueryInterface: ChromeUtils.generateQI(["nsIFactory"]),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -116,7 +116,6 @@ ProtocolHandler.prototype = {
|
||||||
}
|
}
|
||||||
return this.QueryInterface(aIID);
|
return this.QueryInterface(aIID);
|
||||||
},
|
},
|
||||||
lockFactory() {},
|
|
||||||
|
|
||||||
/** nsISupports */
|
/** nsISupports */
|
||||||
QueryInterface: ChromeUtils.generateQI([
|
QueryInterface: ChromeUtils.generateQI([
|
||||||
|
|
|
@ -27,9 +27,6 @@ var sniffer = {
|
||||||
}
|
}
|
||||||
return this.QueryInterface(iid);
|
return this.QueryInterface(iid);
|
||||||
},
|
},
|
||||||
lockFactory: function sniffer_lockf(lock) {
|
|
||||||
throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED);
|
|
||||||
},
|
|
||||||
|
|
||||||
getMIMETypeFromContent(request, data, length) {
|
getMIMETypeFromContent(request, data, length) {
|
||||||
return sniffedType;
|
return sniffedType;
|
||||||
|
|
|
@ -26,9 +26,6 @@ var eventsink = {
|
||||||
}
|
}
|
||||||
return this.QueryInterface(iid);
|
return this.QueryInterface(iid);
|
||||||
},
|
},
|
||||||
lockFactory: function eventsink_lockf(lock) {
|
|
||||||
throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED);
|
|
||||||
},
|
|
||||||
|
|
||||||
asyncOnChannelRedirect: function eventsink_onredir(
|
asyncOnChannelRedirect: function eventsink_onredir(
|
||||||
oldChan,
|
oldChan,
|
||||||
|
|
|
@ -38,7 +38,6 @@ TestProtocolHandlerFactory.prototype = {
|
||||||
createInstance(delegate, iid) {
|
createInstance(delegate, iid) {
|
||||||
return new TestProtocolHandler().QueryInterface(iid);
|
return new TestProtocolHandler().QueryInterface(iid);
|
||||||
},
|
},
|
||||||
lockFactory(lock) {},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
function register_test_protocol_handler() {
|
function register_test_protocol_handler() {
|
||||||
|
|
|
@ -52,7 +52,6 @@ TestProtocolHandlerFactory.prototype = {
|
||||||
createInstance(delegate, iid) {
|
createInstance(delegate, iid) {
|
||||||
return new TestProtocolHandler().QueryInterface(iid);
|
return new TestProtocolHandler().QueryInterface(iid);
|
||||||
},
|
},
|
||||||
lockFactory(lock) {},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
function register_test_protocol_handler() {
|
function register_test_protocol_handler() {
|
||||||
|
|
|
@ -10,7 +10,7 @@ import re
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
from distutils.version import StrictVersion as Version
|
from packaging.version import Version
|
||||||
|
|
||||||
import buildconfig
|
import buildconfig
|
||||||
from mozbuild.action.util import log_build_task
|
from mozbuild.action.util import log_build_task
|
||||||
|
|
|
@ -21,7 +21,7 @@ import attr
|
||||||
import psutil
|
import psutil
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
from distutils.version import LooseVersion
|
from packaging.version import Version
|
||||||
|
|
||||||
import mozpack.path as mozpath
|
import mozpack.path as mozpath
|
||||||
|
|
||||||
|
@ -35,7 +35,7 @@ MEMORY_THRESHOLD = 7.4
|
||||||
FREESPACE_THRESHOLD = 10
|
FREESPACE_THRESHOLD = 10
|
||||||
|
|
||||||
# Latest MozillaBuild version.
|
# Latest MozillaBuild version.
|
||||||
LATEST_MOZILLABUILD_VERSION = LooseVersion("3.3")
|
LATEST_MOZILLABUILD_VERSION = Version("4.0")
|
||||||
|
|
||||||
DISABLE_LASTACCESS_WIN = """
|
DISABLE_LASTACCESS_WIN = """
|
||||||
Disable the last access time feature?
|
Disable the last access time feature?
|
||||||
|
@ -436,7 +436,7 @@ def mozillabuild(**kwargs) -> DoctorCheck:
|
||||||
display_text=["Could not get local MozillaBuild version."],
|
display_text=["Could not get local MozillaBuild version."],
|
||||||
)
|
)
|
||||||
|
|
||||||
if LooseVersion(local_version) < LATEST_MOZILLABUILD_VERSION:
|
if Version(local_version) < LATEST_MOZILLABUILD_VERSION:
|
||||||
status = CheckStatus.WARNING
|
status = CheckStatus.WARNING
|
||||||
desc = "MozillaBuild %s in use, <%s" % (
|
desc = "MozillaBuild %s in use, <%s" % (
|
||||||
local_version,
|
local_version,
|
||||||
|
|
|
@ -6,7 +6,7 @@ from __future__ import absolute_import, print_function, unicode_literals
|
||||||
|
|
||||||
import re
|
import re
|
||||||
import six
|
import six
|
||||||
from distutils.version import LooseVersion
|
from packaging.version import Version
|
||||||
from mozpack.errors import errors
|
from mozpack.errors import errors
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
|
|
||||||
|
@ -160,14 +160,14 @@ class VersionFlag(object):
|
||||||
assert definition.startswith(self.name)
|
assert definition.startswith(self.name)
|
||||||
value = definition[len(self.name) :]
|
value = definition[len(self.name) :]
|
||||||
if value.startswith("="):
|
if value.startswith("="):
|
||||||
self.values.append(("==", LooseVersion(value[1:])))
|
self.values.append(("==", Version(value[1:])))
|
||||||
elif len(value) > 1 and value[0] in ["<", ">"]:
|
elif len(value) > 1 and value[0] in ["<", ">"]:
|
||||||
if value[1] == "=":
|
if value[1] == "=":
|
||||||
if len(value) < 3:
|
if len(value) < 3:
|
||||||
return errors.fatal("Malformed flag: %s" % definition)
|
return errors.fatal("Malformed flag: %s" % definition)
|
||||||
self.values.append((value[0:2], LooseVersion(value[2:])))
|
self.values.append((value[0:2], Version(value[2:])))
|
||||||
else:
|
else:
|
||||||
self.values.append((value[0], LooseVersion(value[1:])))
|
self.values.append((value[0], Version(value[1:])))
|
||||||
else:
|
else:
|
||||||
return errors.fatal("Malformed flag: %s" % definition)
|
return errors.fatal("Malformed flag: %s" % definition)
|
||||||
|
|
||||||
|
@ -189,7 +189,7 @@ class VersionFlag(object):
|
||||||
flag.matches('1.0') returns True
|
flag.matches('1.0') returns True
|
||||||
flag.matches('0.6') returns False
|
flag.matches('0.6') returns False
|
||||||
"""
|
"""
|
||||||
value = LooseVersion(value)
|
value = Version(value)
|
||||||
if not self.values:
|
if not self.values:
|
||||||
return True
|
return True
|
||||||
for comparison, val in self.values:
|
for comparison, val in self.values:
|
||||||
|
|
|
@ -311,8 +311,8 @@ sccache:
|
||||||
fetch:
|
fetch:
|
||||||
type: git
|
type: git
|
||||||
repo: https://github.com/mozilla/sccache
|
repo: https://github.com/mozilla/sccache
|
||||||
# 0.2.15 release
|
# 0.3.0 release
|
||||||
revision: 6b6d2f7d2dceefeb4f583712aa4c221db62be0bd
|
revision: f6e36e68c6cb1e17127273956df9ebf2127dff1a
|
||||||
|
|
||||||
fxc2:
|
fxc2:
|
||||||
description: fxc2 source code
|
description: fxc2 source code
|
||||||
|
|
|
@ -68,9 +68,6 @@ var MockRegistrar = Object.freeze({
|
||||||
|
|
||||||
return wrappedMock.QueryInterface(iid);
|
return wrappedMock.QueryInterface(iid);
|
||||||
},
|
},
|
||||||
lockFactory(lock) {
|
|
||||||
throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED);
|
|
||||||
},
|
|
||||||
QueryInterface: ChromeUtils.generateQI(["nsIFactory"]),
|
QueryInterface: ChromeUtils.generateQI(["nsIFactory"]),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -12,7 +12,7 @@ from commit messages.
|
||||||
from __future__ import absolute_import, print_function
|
from __future__ import absolute_import, print_function
|
||||||
|
|
||||||
from collections.abc import Iterable
|
from collections.abc import Iterable
|
||||||
from distutils.version import StrictVersion
|
from packaging.version import Version
|
||||||
import argparse
|
import argparse
|
||||||
import os
|
import os
|
||||||
import subprocess
|
import subprocess
|
||||||
|
@ -48,12 +48,12 @@ def changelog(args):
|
||||||
for line in diff.splitlines():
|
for line in diff.splitlines():
|
||||||
if line.startswith("-PACKAGE_VERSION"):
|
if line.startswith("-PACKAGE_VERSION"):
|
||||||
try:
|
try:
|
||||||
minus_version = StrictVersion(line.split()[-1].strip("\"'"))
|
minus_version = Version(line.split()[-1].strip("\"'"))
|
||||||
except ValueError:
|
except ValueError:
|
||||||
pass
|
pass
|
||||||
elif line.startswith("+PACKAGE_VERSION"):
|
elif line.startswith("+PACKAGE_VERSION"):
|
||||||
try:
|
try:
|
||||||
plus_version = StrictVersion(line.split()[-1].strip("\"'"))
|
plus_version = Version(line.split()[-1].strip("\"'"))
|
||||||
except ValueError:
|
except ValueError:
|
||||||
break
|
break
|
||||||
|
|
||||||
|
@ -62,7 +62,7 @@ def changelog(args):
|
||||||
if not v:
|
if not v:
|
||||||
return rev
|
return rev
|
||||||
|
|
||||||
if StrictVersion(v) == plus_version:
|
if Version(v) == plus_version:
|
||||||
return rev
|
return rev
|
||||||
|
|
||||||
print(
|
print(
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
from __future__ import absolute_import, division
|
from __future__ import absolute_import, division
|
||||||
from distutils.version import LooseVersion
|
from packaging.version import Version
|
||||||
import json
|
import json
|
||||||
import math
|
import math
|
||||||
import os
|
import os
|
||||||
|
@ -23,7 +23,7 @@ from mozharness.base.script import BaseScript
|
||||||
|
|
||||||
# ensure all versions are 3 part (i.e. 99.1.0)
|
# ensure all versions are 3 part (i.e. 99.1.0)
|
||||||
# ensure all text (i.e. 'esr') is in the last part
|
# ensure all text (i.e. 'esr') is in the last part
|
||||||
class CompareVersion(LooseVersion):
|
class CompareVersion(Version):
|
||||||
version = ""
|
version = ""
|
||||||
|
|
||||||
def __init__(self, versionMap):
|
def __init__(self, versionMap):
|
||||||
|
@ -38,7 +38,7 @@ class CompareVersion(LooseVersion):
|
||||||
else:
|
else:
|
||||||
parts.append("0")
|
parts.append("0")
|
||||||
self.version = ".".join(parts)
|
self.version = ".".join(parts)
|
||||||
LooseVersion(versionMap)
|
Version(versionMap)
|
||||||
|
|
||||||
|
|
||||||
def is_triangualar(x):
|
def is_triangualar(x):
|
||||||
|
@ -549,9 +549,9 @@ class UpdateVerifyConfigCreator(BaseScript):
|
||||||
|
|
||||||
# Use new build targets for Windows, but only on compatible
|
# Use new build targets for Windows, but only on compatible
|
||||||
# versions (42+). See bug 1185456 for additional context.
|
# versions (42+). See bug 1185456 for additional context.
|
||||||
if self.config["platform"] not in ("win32", "win64") or LooseVersion(
|
if self.config["platform"] not in ("win32", "win64") or Version(
|
||||||
fromVersion
|
fromVersion
|
||||||
) < LooseVersion("42.0"):
|
) < Version("42.0"):
|
||||||
update_platform = ftp2updatePlatforms(self.config["platform"])[0]
|
update_platform = ftp2updatePlatforms(self.config["platform"])[0]
|
||||||
else:
|
else:
|
||||||
update_platform = ftp2updatePlatforms(self.config["platform"])[1]
|
update_platform = ftp2updatePlatforms(self.config["platform"])[1]
|
||||||
|
|
|
@ -32,9 +32,6 @@ var newFactory = function(window) {
|
||||||
}
|
}
|
||||||
return new MockColorPickerInstance(window).QueryInterface(aIID);
|
return new MockColorPickerInstance(window).QueryInterface(aIID);
|
||||||
},
|
},
|
||||||
lockFactory(aLock) {
|
|
||||||
throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED);
|
|
||||||
},
|
|
||||||
QueryInterface: ChromeUtils.generateQI(["nsIFactory"]),
|
QueryInterface: ChromeUtils.generateQI(["nsIFactory"]),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -37,9 +37,6 @@ var newFactory = function(window) {
|
||||||
}
|
}
|
||||||
return new MockFilePickerInstance(window).QueryInterface(aIID);
|
return new MockFilePickerInstance(window).QueryInterface(aIID);
|
||||||
},
|
},
|
||||||
lockFactory(aLock) {
|
|
||||||
throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED);
|
|
||||||
},
|
|
||||||
QueryInterface: ChromeUtils.generateQI(["nsIFactory"]),
|
QueryInterface: ChromeUtils.generateQI(["nsIFactory"]),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -20,9 +20,6 @@ var newFactory = {
|
||||||
}
|
}
|
||||||
return new MockPermissionPromptInstance().QueryInterface(aIID);
|
return new MockPermissionPromptInstance().QueryInterface(aIID);
|
||||||
},
|
},
|
||||||
lockFactory(aLock) {
|
|
||||||
throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED);
|
|
||||||
},
|
|
||||||
QueryInterface: ChromeUtils.generateQI(["nsIFactory"]),
|
QueryInterface: ChromeUtils.generateQI(["nsIFactory"]),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -308,9 +308,6 @@ var _fakeIdleService = {
|
||||||
}
|
}
|
||||||
return _fakeIdleService.QueryInterface(aIID);
|
return _fakeIdleService.QueryInterface(aIID);
|
||||||
},
|
},
|
||||||
lockFactory(aLock) {
|
|
||||||
throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED);
|
|
||||||
},
|
|
||||||
QueryInterface: ChromeUtils.generateQI(["nsIFactory"]),
|
QueryInterface: ChromeUtils.generateQI(["nsIFactory"]),
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
{"files":{"Cargo.lock":"d658acfaa27a2b30de98cf004d4d3f4ec0f1757b136610289cbbd1c847ae2e6c","Cargo.toml":"e2176be78c2989884eba4a20a58a672277b3a8a99a72b0ba7347f48eb827ae0e","LICENSE-APACHE":"a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2","LICENSE-MIT":"27995d58ad5c1145c1a8cd86244ce844886958a35eb2b78c6b772748669999ac","README.md":"2d8b6f07819ad7adfab1d153216bfdcde154ffd4a870d98794125c910b0f4593","examples/integers.rs":"589ff4271566dfa322becddf3e2c7b592e6e0bc97b02892ce75619b7e452e930","examples/paths.rs":"1b30e466b824ce8df7ad0a55334424131d9d2573d6cf9f7d5d50c09c8901d526","examples/traits.rs":"cbee6a3e1f7db60b02ae25b714926517144a77cb492021f492774cf0e1865a9e","examples/versions.rs":"38535e6d9f5bfae0de474a3db79a40e8f5da8ba9334c5ff4c363de9bc99d4d12","src/error.rs":"12de7dafea4a35d1dc2f0fa79bfa038386bbbea72bf083979f4ddf227999eeda","src/lib.rs":"9b450d90730624807979045ea7ff48374355314cd894345e1b9651485ba1b2ff","src/tests.rs":"a902fbd42b0f0b81a2830f2368fab733041b02fcb902c8e2520d07b3bff10713","src/version.rs":"175727d5f02f2fe2271ddc9b041db2a5b9c6fe0f95afd17c73a4d982612764a3","tests/rustflags.rs":"441fb0c6606e243c31d3817a5ae2240b65fcae0ea8ab583f80f8f6d6c267e614"},"package":"cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"}
|
{"files":{"Cargo.lock":"3d91565ed13de572a9ebde408a0c98e33f931d6ab52f212b0830a60b4ab26b77","Cargo.toml":"39f627122dceaad42146634719fde802fca3baa1b3908753af723074ae2a6d69","LICENSE-APACHE":"a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2","LICENSE-MIT":"27995d58ad5c1145c1a8cd86244ce844886958a35eb2b78c6b772748669999ac","README.md":"4c8f9b5016f2a0c3dbeca5bc41241f57db5568f803e58c1fa480ae2b3638d0a9","examples/integers.rs":"589ff4271566dfa322becddf3e2c7b592e6e0bc97b02892ce75619b7e452e930","examples/paths.rs":"1b30e466b824ce8df7ad0a55334424131d9d2573d6cf9f7d5d50c09c8901d526","examples/traits.rs":"cbee6a3e1f7db60b02ae25b714926517144a77cb492021f492774cf0e1865a9e","examples/versions.rs":"38535e6d9f5bfae0de474a3db79a40e8f5da8ba9334c5ff4c363de9bc99d4d12","src/error.rs":"12de7dafea4a35d1dc2f0fa79bfa038386bbbea72bf083979f4ddf227999eeda","src/lib.rs":"6fa01458e8f9258d84f83ead24fdb0cdf9aec10838b0262f1dfbdf79c530c537","src/tests.rs":"f0e6dc1ad9223c0336c02e215ea3940acb2af6c3bc8fd791e16cd4e786e6a608","src/version.rs":"175727d5f02f2fe2271ddc9b041db2a5b9c6fe0f95afd17c73a4d982612764a3","tests/rustflags.rs":"5c8169b88216055019db61b5d7baf4abdf675e3b14b54f5037bb1e3acd0a5d3f"},"package":"d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"}
|
|
@ -1,6 +1,7 @@
|
||||||
# This file is automatically @generated by Cargo.
|
# This file is automatically @generated by Cargo.
|
||||||
# It is not intended for manual editing.
|
# It is not intended for manual editing.
|
||||||
|
version = 3
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "autocfg"
|
name = "autocfg"
|
||||||
version = "1.0.1"
|
version = "1.1.0"
|
||||||
|
|
||||||
|
|
Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше
Загрузка…
Ссылка в новой задаче