Merge autoland to mozilla-central. a=merge

This commit is contained in:
Bogdan Szekely 2022-05-17 12:26:46 +03:00
Родитель 5416ab37fb b637180d67
Коммит 4ef183d00f
172 изменённых файлов: 4839 добавлений и 3051 удалений

Просмотреть файл

@ -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"

10
Cargo.lock сгенерированный
Просмотреть файл

@ -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"}

5
third_party/rust/autocfg/Cargo.lock сгенерированный поставляемый
Просмотреть файл

@ -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"

Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше