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"]
git = "https://github.com/gfx-rs/wgpu"
replace-with = "vendored-sources"
rev = "0b61a191"
rev = "b51fd851"
[source."https://github.com/gfx-rs/naga"]
git = "https://github.com/gfx-rs/naga"
replace-with = "vendored-sources"
rev = "85056524"
rev = "1aa91549"
[source."https://github.com/gfx-rs/metal-rs"]
git = "https://github.com/gfx-rs/metal-rs"

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

@ -332,7 +332,7 @@ dependencies = [
[[package]]
name = "autocfg"
version = "1.0.1"
version = "1.1.0"
[[package]]
name = "baldrdash"
@ -3432,7 +3432,7 @@ checksum = "a2983372caf4480544083767bf2d27defafe32af49ab4df3a0b7fc90793a3664"
[[package]]
name = "naga"
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 = [
"bit-set",
"bitflags",
@ -5876,7 +5876,7 @@ dependencies = [
[[package]]
name = "wgpu-core"
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 = [
"arrayvec 0.7.2",
"bitflags",
@ -5899,7 +5899,7 @@ dependencies = [
[[package]]
name = "wgpu-hal"
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 = [
"arrayvec 0.7.2",
"ash",
@ -5936,7 +5936,7 @@ dependencies = [
[[package]]
name = "wgpu-types"
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 = [
"bitflags",
"bitflags_serde_shim",

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

@ -276,7 +276,7 @@ var StarUI = {
{ capture: true, once: true }
);
};
gEditItemOverlay.initPanel({
await gEditItemOverlay.initPanel({
node: aNode,
onPanelReady,
hiddenRows: ["location", "keyword"],

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

@ -67,10 +67,6 @@ var AboutModuleFactory = {
return new AboutModule().QueryInterface(aIID);
},
lockFactory(aLock) {
throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED);
},
QueryInterface: ChromeUtils.generateQI(["nsIFactory"]),
};

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

@ -1905,7 +1905,7 @@ this.DiscoveryStreamFeed = class DiscoveryStreamFeed {
*/
getHardcodedLayout = ({
items = 21,
spocPositions = [2, 4, 11, 20],
spocPositions = [1, 5, 7, 11, 18, 20],
sponsoredCollectionsEnabled = false,
compactLayout = false,
hybridLayout = false,

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

@ -288,6 +288,12 @@ var gEditItemOverlay = {
onPanelReady,
} = 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 (
aInfo.isNewBookmark &&
@ -326,6 +332,11 @@ var gEditItemOverlay = {
if (showOrCollapse("keywordRow", isBookmark, "keyword")) {
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;
}
@ -342,6 +353,9 @@ var gEditItemOverlay = {
// this (it's only the Star UI that shows the folderPicker)
if (showOrCollapse("folderRow", isItem, "folderPicker")) {
await this._initFolderMenuList(parentGuid).catch(Cu.reportError);
if (instance != this._instance || this._paneInfo == null) {
return;
}
}
// Selection count.

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

@ -241,7 +241,7 @@ var gEditItemOverlay = {
* List of rows to be hidden regardless of the item edited. Possible values:
* "title", "location", "keyword", "folderPicker".
*/
initPanel(aInfo) {
async initPanel(aInfo) {
if (typeof aInfo != "object" || aInfo === null) {
throw new Error("aInfo must be an object.");
}
@ -279,6 +279,12 @@ var gEditItemOverlay = {
onPanelReady,
} = 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 (
aInfo.isNewBookmark &&
@ -316,7 +322,12 @@ var gEditItemOverlay = {
}
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;
}
@ -332,7 +343,10 @@ var gEditItemOverlay = {
// 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)
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.

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

@ -777,17 +777,21 @@ var PlacesOrganizer = {
gEditItemOverlay.uninitPanel(false);
if (selectedNode && !PlacesUtils.nodeIsSeparator(selectedNode)) {
gEditItemOverlay.initPanel({
node: selectedNode,
hiddenRows: ["folderPicker"],
});
gEditItemOverlay
.initPanel({
node: selectedNode,
hiddenRows: ["folderPicker"],
})
.catch(ex => Cu.reportError(ex));
} else if (!selectedNode && aNodeList[0]) {
if (aNodeList.every(PlacesUtils.nodeIsURI)) {
let uris = aNodeList.map(node => Services.io.newURI(node.uri));
gEditItemOverlay.initPanel({
uris,
hiddenRows: ["folderPicker", "location", "keyword", "name"],
});
gEditItemOverlay
.initPanel({
uris,
hiddenRows: ["folderPicker", "location", "keyword", "name"],
})
.catch(ex => Cu.reportError(ex));
} else {
let selectItemDesc = document.getElementById("selectItemDescription");
let itemsCountLabel = document.getElementById("itemsCountText");

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

@ -54,7 +54,7 @@ add_task(async function() {
// Init panel.
let node = await PlacesUIUtils.promiseNodeLikeFromFetchInfo(bm);
gEditItemOverlay.initPanel({ node });
await gEditItemOverlay.initPanel({ node });
// Add a tag.
await promiseTagSelectorUpdated(() =>
@ -99,7 +99,7 @@ add_task(async function() {
});
// 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.
await promiseTagSelectorUpdated(() =>
@ -166,7 +166,7 @@ add_task(async function() {
await checkTagsSelector([], []);
// Init panel with a nsIURI entry.
gEditItemOverlay.initPanel({ uris: [TEST_URI] });
await gEditItemOverlay.initPanel({ uris: [TEST_URI] });
// Add a tag.
await promiseTagSelectorUpdated(() =>
@ -201,7 +201,7 @@ add_task(async function() {
await checkTagsSelector([], []);
// 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.
await promiseTagSelectorUpdated(() =>

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

@ -2438,6 +2438,13 @@ bool JsepSessionImpl::CheckNegotiationNeeded() const {
}
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 =
mCurrentLocalDescription->GetMediaSection(level);
const SdpMediaSection& remote =

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

@ -16,9 +16,6 @@ var provider = {
}
return this.QueryInterface(iid);
},
lockFactory: function eventsink_lockf(lock) {
throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED);
},
startup() {},
watch() {},
shutdown() {},

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

@ -16,9 +16,6 @@ var provider = {
}
return this.QueryInterface(iid);
},
lockFactory: function eventsink_lockf(lock) {
throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED);
},
startup() {},
watch() {},
shutdown() {},

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

@ -72,6 +72,8 @@ class CommandEncoder final : public ObjectBase, public ChildOf<Device> {
nsTArray<WeakPtr<CanvasContext>> mTargetContexts;
public:
const auto& GetDevice() const { return mParent; };
void EndComputePass(ffi::WGPUComputePass& 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,
uint32_t z) {
if (mValid) {
ffi::wgpu_compute_pass_dispatch(mPass, x, y, z);
ffi::wgpu_compute_pass_dispatch_workgroups(mPass, x, y, z);
}
}
void ComputePassEncoder::DispatchWorkgroupsIndirect(
const Buffer& aIndirectBuffer, uint64_t aIndirectOffset) {
if (mValid) {
ffi::wgpu_compute_pass_dispatch_indirect(mPass, aIndirectBuffer.mId,
aIndirectOffset);
ffi::wgpu_compute_pass_dispatch_workgroups_indirect(
mPass, aIndirectBuffer.mId, aIndirectOffset);
}
}

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

@ -94,6 +94,12 @@ void Device::CleanupUnregisteredInParent() {
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::SetLabel(const nsAString& aLabel) { mLabel = aLabel; }

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

@ -111,6 +111,8 @@ class Device final : public DOMEventTargetHelper, public SupportsWeakPtr {
void CleanupUnregisteredInParent();
void GenerateError(const nsCString& aMessage);
private:
~Device();
void Cleanup();

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

@ -52,7 +52,7 @@ ffi::WGPUColor ConvertColor(const dom::GPUColorDict& aColor) {
}
ffi::WGPURenderPass* BeginRenderPass(
RawId aEncoderId, const dom::GPURenderPassDescriptor& aDesc) {
CommandEncoder* const aParent, const dom::GPURenderPassDescriptor& aDesc) {
ffi::WGPURenderPassDescriptor desc = {};
ffi::WGPURenderPassDepthStencilAttachment dsDesc = {};
@ -84,6 +84,12 @@ ffi::WGPURenderPass* BeginRenderPass(
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>
colorDescs = {};
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,
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) {
mUsedTextureViews.AppendElement(at.mView);
}

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

@ -71,6 +71,7 @@ parent:
async DevicePushErrorScope(RawId selfId);
async DevicePopErrorScope(RawId selfId) returns (MaybeScopedError maybeError);
async GenerateError(RawId deviceId, nsCString message);
child:
async DeviceUncapturedError(RawId aDeviceId, nsCString message);
async DropAction(ByteBuf buf);

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

@ -239,21 +239,25 @@ bool WebGPUParent::ForwardError(RawId aDeviceId, ErrorBuffer& aError) {
return false;
}
ReportError(aDeviceId, cString.value());
return true;
}
void WebGPUParent::ReportError(RawId aDeviceId, const nsCString& aMessage) {
// find the appropriate error scope
const auto& lookup = mErrorScopeMap.find(aDeviceId);
if (lookup != mErrorScopeMap.end() && !lookup->second.mStack.IsEmpty()) {
auto& last = lookup->second.mStack.LastElement();
if (last.isNothing()) {
last.emplace(ScopedError{false, cString.value()});
last.emplace(ScopedError{false, aMessage});
}
} else {
// fall back to the uncaptured error handler
if (!SendDeviceUncapturedError(aDeviceId, cString.value())) {
if (!SendDeviceUncapturedError(aDeviceId, aMessage)) {
NS_ERROR("Unable to SendError");
}
}
return true;
}
ipc::IPCResult WebGPUParent::RecvInstanceRequestAdapter(
@ -927,4 +931,10 @@ ipc::IPCResult WebGPUParent::RecvDevicePopErrorScope(
return IPC_OK();
}
ipc::IPCResult WebGPUParent::RecvGenerateError(RawId aDeviceId,
const nsCString& aMessage) {
ReportError(aDeviceId, aMessage);
return IPC_OK();
}
} // namespace mozilla::webgpu

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

@ -88,6 +88,7 @@ class WebGPUParent final : public PWebGPUParent {
ipc::IPCResult RecvDevicePushErrorScope(RawId aSelfId);
ipc::IPCResult RecvDevicePopErrorScope(
RawId aSelfId, DevicePopErrorScopeResolver&& aResolver);
ipc::IPCResult RecvGenerateError(RawId aDeviceId, const nsCString& message);
ipc::IPCResult GetFrontBufferSnapshot(IProtocol* aProtocol,
const CompositableHandle& aHandle,
@ -100,6 +101,7 @@ class WebGPUParent final : public PWebGPUParent {
virtual ~WebGPUParent();
void MaintainDevices();
bool ForwardError(RawId aDeviceID, ErrorBuffer& aError);
void ReportError(RawId aDeviceId, const nsCString& message);
UniquePtr<ffi::WGPUGlobal> mContext;
base::RepeatingTimer<WebGPUParent> mTimer;

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

@ -197,8 +197,7 @@ HTMLEditor::HTMLEditor()
mDefaultParagraphSeparator(
StaticPrefs::editor_use_div_for_default_newlines()
? ParagraphSeparator::div
: ParagraphSeparator::br) {
}
: ParagraphSeparator::br) {}
HTMLEditor::~HTMLEditor() {
// Collect the data of `beforeinput` event only when it's enabled because

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

@ -12,9 +12,6 @@ var factory = {
createInstance() {
throw new Error("There is no history service");
},
lockFactory() {
throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED);
},
QueryInterface: ChromeUtils.generateQI(["nsIFactory"]),
};

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

@ -17,7 +17,7 @@ default = []
[dependencies.wgc]
package = "wgpu-core"
git = "https://github.com/gfx-rs/wgpu"
rev = "0b61a191"
rev = "b51fd851"
#Note: "replay" shouldn't ideally be needed,
# but it allows us to serialize everything across IPC.
features = ["replay", "trace", "serial-pass"]
@ -25,12 +25,12 @@ features = ["replay", "trace", "serial-pass"]
[dependencies.wgt]
package = "wgpu-types"
git = "https://github.com/gfx-rs/wgpu"
rev = "0b61a191"
rev = "b51fd851"
[dependencies.wgh]
package = "wgpu-hal"
git = "https://github.com/gfx-rs/wgpu"
rev = "0b61a191"
rev = "b51fd851"
[dependencies]
bincode = "1"

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

@ -1092,28 +1092,6 @@ pub unsafe extern "C" fn wgpu_command_encoder_insert_debug_marker(
*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]
pub unsafe extern "C" fn wgpu_queue_write_buffer(
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
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 {
@ -3836,6 +3840,7 @@ impl CommandBufferBuilder {
establishes_sub_graph: false,
resolve_source: None,
extra_dependencies: Vec::new(),
wraps_sub_graph: false,
}
}
@ -3850,6 +3855,7 @@ impl CommandBufferBuilder {
establishes_sub_graph: false,
resolve_source: None,
extra_dependencies: Vec::new(),
wraps_sub_graph: false,
}
}
@ -3858,6 +3864,7 @@ impl CommandBufferBuilder {
render_task_id: RenderTaskId,
establishes_sub_graph: bool,
root_task_id: Option<RenderTaskId>,
wraps_sub_graph: bool,
) -> Self {
CommandBufferBuilder {
kind: CommandBufferBuilderKind::Simple {
@ -3867,6 +3874,7 @@ impl CommandBufferBuilder {
establishes_sub_graph,
resolve_source: None,
extra_dependencies: Vec::new(),
wraps_sub_graph,
}
}
}

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

@ -457,42 +457,30 @@ struct TilePreUpdateContext {
frame_id: FrameId,
}
// Immutable context passed to picture cache tiles during post_update
struct TilePostUpdateContext<'a> {
// Immutable context passed to picture cache tiles during update_dirty_and_valid_rects
struct TileUpdateDirtyContext<'a> {
/// Maps from picture cache coords -> world space coords.
pic_to_world_mapper: SpaceMapper<PicturePixel, WorldPixel>,
/// Global scale factor from world -> device pixels.
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.
opacity_bindings: &'a FastHashMap<PropertyBindingId, OpacityBindingInfo>,
/// Information about color bindings from the picture cache.
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
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
/// cache changed, so we need to invalidate the tile and re-render.
invalidate_all: bool,
}
// Mutable state passed to picture cache tiles during post_update
struct TilePostUpdateState<'a> {
// Mutable state passed to picture cache tiles during update_dirty_and_valid_rects
struct TileUpdateDirtyState<'a> {
/// Allow access to the texture cache for requesting tiles
resource_cache: &'a mut ResourceCache,
@ -506,6 +494,30 @@ struct TilePostUpdateState<'a> {
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.
struct PrimitiveDependencyInfo {
/// Unique content identifier of the primitive.
@ -723,6 +735,8 @@ pub enum InvalidationReason {
ValidRectChanged,
// The overall scale of the picture cache changed
ScaleChanged,
// The content of the sampling surface changed
SurfaceContentChanged,
}
/// Information about a cached tile.
@ -823,8 +837,8 @@ impl Tile {
/// Check if the content of the previous and current tile descriptors match
fn update_dirty_rects(
&mut self,
ctx: &TilePostUpdateContext,
state: &mut TilePostUpdateState,
ctx: &TileUpdateDirtyContext,
state: &mut TileUpdateDirtyState,
invalidation_reason: &mut Option<InvalidationReason>,
frame_context: &FrameVisibilityContext,
) -> PictureRect {
@ -857,8 +871,8 @@ impl Tile {
/// later by changing how ComparableVec is used.
fn update_content_validity(
&mut self,
ctx: &TilePostUpdateContext,
state: &mut TilePostUpdateState,
ctx: &TileUpdateDirtyContext,
state: &mut TileUpdateDirtyState,
frame_context: &FrameVisibilityContext,
) {
// 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
/// rect calculation after primitive dependencies have been updated.
fn post_update(
fn update_dirty_and_valid_rects(
&mut self,
ctx: &TilePostUpdateContext,
state: &mut TilePostUpdateState,
ctx: &TileUpdateDirtyContext,
state: &mut TileUpdateDirtyState,
frame_context: &FrameVisibilityContext,
) -> bool {
) {
// 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
// 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
// (and thus updated / invalidated) until it is on screen again.
if !self.is_visible {
return false;
return;
}
// Calculate the overall valid rect for this tile.
@ -1116,6 +1130,22 @@ impl Tile {
// Invalidate the tile based on the content changing.
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.
// Bug 1719232 - The final device valid rect does not always describe a non-empty
@ -1132,7 +1162,7 @@ impl Tile {
}
self.is_visible = false;
return false;
return;
}
// 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.
self.surface = Some(surface);
true
}
}
@ -1892,6 +1920,9 @@ pub struct TileCacheInstance {
current_raster_scale: f32,
/// Depth of off-screen surfaces that are currently pushed during dependency updates
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 {
@ -1950,6 +1981,7 @@ impl TileCacheInstance {
invalidate_all_tiles: true,
current_raster_scale: 1.0,
current_surface_traversal_depth: 0,
deferred_dirty_tests: Vec::new(),
}
}
@ -2068,6 +2100,7 @@ impl TileCacheInstance {
self.surface_index = surface_index;
self.local_rect = pic_rect;
self.local_clip_rect = PictureRect::max_rect();
self.deferred_dirty_tests.clear();
for sub_slice in &mut self.sub_slices {
sub_slice.reset();
@ -3297,6 +3330,13 @@ impl TileCacheInstance {
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::NormalBorder { .. } |
@ -3549,20 +3589,16 @@ impl TileCacheInstance {
frame_context.spatial_tree,
);
let mut ctx = TilePostUpdateContext {
let ctx = TileUpdateDirtyContext {
pic_to_world_mapper,
global_device_pixel_scale: frame_context.global_device_pixel_scale,
local_clip_rect: self.local_clip_rect,
backdrop: None,
opacity_bindings: &self.opacity_bindings,
color_bindings: &self.color_bindings,
current_tile_size: self.current_tile_size,
local_rect: self.local_rect,
z_id: ZBufferId::invalid(),
invalidate_all: self.invalidate_all_tiles,
};
let mut state = TilePostUpdateState {
let mut state = TileUpdateDirtyState {
resource_cache: frame_state.resource_cache,
composite_state: frame_state.composite_state,
compare_cache: &mut self.compare_cache,
@ -3571,6 +3607,60 @@ impl TileCacheInstance {
// Step through each tile and invalidate if the dependencies have changed. Determine
// 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() {
// The backdrop is only relevant for the first sub-slice
if i == 0 {
@ -4302,6 +4392,8 @@ bitflags! {
/// This picture establishes a sub-graph, which affects how SurfaceBuilder will
/// set up dependencies in the render task graph
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(
surface_index,
false,
false,
surface_local_dirty_rect,
descriptor,
frame_state.surfaces,
@ -5472,10 +5565,12 @@ impl PicturePrimitive {
}
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(
raster_config.surface_index,
is_sub_graph,
wraps_sub_graph,
surface_rects.clipped_local,
surface_descriptor,
frame_state.surfaces,
@ -5834,7 +5929,8 @@ impl PicturePrimitive {
// context.
let allow_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
// 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.
struct PrimitiveComparer<'a> {
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
let spatial_node_index = match pic.composite_mode {
Some(_) => pic.spatial_node_index,
None => parent_spatial_node_index.expect("bug: no parent"),
Some(_) if !pic.flags.contains(PictureFlags::WRAPS_SUB_GRAPH) => pic.spatial_node_index,
Some(_) | None => parent_spatial_node_index.expect("bug: no parent"),
};
(
@ -2271,6 +2271,13 @@ impl<'a> SceneBuilder<'a> {
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 {
// TODO(gw): For now, as soon as this picture is in
// a 3D context, we draw it to an intermediate
@ -2295,7 +2302,7 @@ impl<'a> SceneBuilder<'a> {
stacking_context.prim_list,
stacking_context.spatial_node_index,
stacking_context.raster_space,
PictureFlags::empty(),
pic_flags,
))
);
@ -2339,7 +2346,7 @@ impl<'a> SceneBuilder<'a> {
stacking_context.prim_list,
stacking_context.spatial_node_index,
stacking_context.raster_space,
PictureFlags::empty(),
pic_flags,
))
);

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

@ -245,6 +245,7 @@ impl SurfaceBuilder {
&mut self,
surface_index: SurfaceIndex,
is_sub_graph: bool,
wraps_sub_graph: bool,
clipping_rect: PictureRect,
descriptor: SurfaceDescriptor,
surfaces: &mut [SurfaceInfo],
@ -266,6 +267,7 @@ impl SurfaceBuilder {
render_task_id,
is_sub_graph,
None,
wraps_sub_graph,
)
}
SurfaceDescriptorKind::Chained { render_task_id, root_task_id } => {
@ -273,6 +275,7 @@ impl SurfaceBuilder {
render_task_id,
is_sub_graph,
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
// (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 } => {
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
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),
);
@ -518,7 +540,7 @@ impl SurfaceBuilder {
// Make the output of the sub-graph a dependency of the new replacement tile task
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),
);

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

@ -947,6 +947,10 @@ bitflags! {
/// If true, this stacking context is a blend container than contains
/// mix-blend-mode children (and should thus be isolated).
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
#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-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-chain.yaml backdrop-filter-chain.png
#platform(linux,mac) == backdrop-filter-overlap.yaml backdrop-filter-overlap.png

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

@ -2025,6 +2025,7 @@ impl YamlFrameReader {
.as_raster_space()
.unwrap_or(RasterSpace::Screen);
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 let Some(vector) = yaml["scroll-offset"].as_vector() {
@ -2045,6 +2046,7 @@ impl YamlFrameReader {
let mut flags = StackingContextFlags::empty();
flags.set(StackingContextFlags::IS_BLEND_CONTAINER, is_blend_container);
flags.set(StackingContextFlags::WRAPS_BACKDROP_FILTER, wraps_backdrop_filter);
dl.push_stacking_context(
bounds.min,

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

@ -3442,8 +3442,7 @@ void nsIFrame::BuildDisplayListForStackingContext(
// 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
// to get correct positioning during merging.
if ((aBuilder->ContainsBlendMode()) &&
aBuilder->IsRetainingDisplayList()) {
if ((aBuilder->ContainsBlendMode()) && aBuilder->IsRetainingDisplayList()) {
if (aBuilder->IsPartialUpdate()) {
aBuilder->SetPartialBuildFailed(true);
} else {
@ -3551,7 +3550,7 @@ void nsIFrame::BuildDisplayListForStackingContext(
: containerItemASR;
/* List now emptied, so add the new list to the top. */
resultList.AppendNewToTop<nsDisplayMasksAndClipPaths>(
aBuilder, this, &resultList, maskASR);
aBuilder, this, &resultList, maskASR, usingBackdropFilter);
createdContainer = true;
}

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

@ -7857,8 +7857,9 @@ static void ComputeMaskGeometry(PaintFramesParams& aParams) {
nsDisplayMasksAndClipPaths::nsDisplayMasksAndClipPaths(
nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, nsDisplayList* aList,
const ActiveScrolledRoot* aActiveScrolledRoot)
: nsDisplayEffectsBase(aBuilder, aFrame, aList, aActiveScrolledRoot, true) {
const ActiveScrolledRoot* aActiveScrolledRoot, bool aWrapsBackdropFilter)
: nsDisplayEffectsBase(aBuilder, aFrame, aList, aActiveScrolledRoot, true),
mWrapsBackdropFilter(aWrapsBackdropFilter) {
MOZ_COUNT_CTOR(nsDisplayMasksAndClipPaths);
nsPresContext* presContext = mFrame->PresContext();
@ -8199,6 +8200,9 @@ bool nsDisplayMasksAndClipPaths::CreateWebRenderCommands(
wr::StackingContextParams params;
params.clip = wr::WrStackingContextClip::ClipId(*clip);
params.opacity = opacity.ptrOr(nullptr);
if (mWrapsBackdropFilter) {
params.flags |= wr::StackingContextFlags::WRAPS_BACKDROP_FILTER;
}
layer.emplace(aSc, GetActiveScrolledRoot(), mFrame, this, aBuilder, params,
bounds);
sc = layer.ptr();
@ -8327,6 +8331,15 @@ void nsDisplayBackdropFilters::Paint(nsDisplayListBuilder* aBuilder,
mFrame->PresContext()->AppUnitsPerDevPixel());
}
nsRect nsDisplayBackdropFilters::GetBounds(nsDisplayListBuilder* aBuilder,
bool* aSnap) const {
nsRect childBounds = nsDisplayWrapList::GetBounds(aBuilder, aSnap);
*aSnap = false;
return mBackdropRect.Union(childBounds);
}
/* static */
nsDisplayFilters::nsDisplayFilters(nsDisplayListBuilder* aBuilder,
nsIFrame* aFrame, nsDisplayList* aList,

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

@ -5863,11 +5863,13 @@ class nsDisplayMasksAndClipPaths : public nsDisplayEffectsBase {
public:
nsDisplayMasksAndClipPaths(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
nsDisplayList* aList,
const ActiveScrolledRoot* aActiveScrolledRoot);
const ActiveScrolledRoot* aActiveScrolledRoot,
bool aWrapsBackdropFilter);
nsDisplayMasksAndClipPaths(nsDisplayListBuilder* aBuilder,
const nsDisplayMasksAndClipPaths& aOther)
: nsDisplayEffectsBase(aBuilder, aOther),
mDestRects(aOther.mDestRects.Clone()) {
mDestRects(aOther.mDestRects.Clone()),
mWrapsBackdropFilter(aOther.mWrapsBackdropFilter) {
MOZ_COUNT_CTOR(nsDisplayMasksAndClipPaths);
}
@ -5932,6 +5934,7 @@ class nsDisplayMasksAndClipPaths : public nsDisplayEffectsBase {
NS_DISPLAY_ALLOW_CLONING()
nsTArray<nsRect> mDestRects;
bool mWrapsBackdropFilter;
};
class nsDisplayBackdropFilters : public nsDisplayWrapList {
@ -5962,6 +5965,8 @@ class nsDisplayBackdropFilters : public nsDisplayWrapList {
bool CreatesStackingContextHelper() override { return true; }
nsRect GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) const override;
private:
RefPtr<ComputedStyle> mStyle;
nsRect mBackdropRect;

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

@ -113,7 +113,7 @@ class SPSCRingBufferBase {
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.
@ -152,8 +152,8 @@ class SPSCRingBufferBase {
AssertCorrectThread(mProducerId);
#endif
int rdIdx = mReadIndex.load(std::memory_order::memory_order_acquire);
int wrIdx = mWriteIndex.load(std::memory_order::memory_order_relaxed);
int rdIdx = mReadIndex.load(std::memory_order_acquire);
int wrIdx = mWriteIndex.load(std::memory_order_relaxed);
if (IsFull(rdIdx, wrIdx)) {
return 0;
@ -178,7 +178,7 @@ class SPSCRingBufferBase {
}
mWriteIndex.store(IncrementIndex(wrIdx, toWrite),
std::memory_order::memory_order_release);
std::memory_order_release);
return toWrite;
}
@ -198,8 +198,8 @@ class SPSCRingBufferBase {
AssertCorrectThread(mConsumerId);
#endif
int wrIdx = mWriteIndex.load(std::memory_order::memory_order_acquire);
int rdIdx = mReadIndex.load(std::memory_order::memory_order_relaxed);
int wrIdx = mWriteIndex.load(std::memory_order_acquire);
int rdIdx = mReadIndex.load(std::memory_order_relaxed);
if (IsEmpty(rdIdx, wrIdx)) {
return 0;
@ -217,8 +217,7 @@ class SPSCRingBufferBase {
secondPart);
}
mReadIndex.store(IncrementIndex(rdIdx, toRead),
std::memory_order::memory_order_release);
mReadIndex.store(IncrementIndex(rdIdx, toRead), std::memory_order_release);
return toRead;
}
@ -233,9 +232,8 @@ class SPSCRingBufferBase {
* @return The number of available elements for reading.
*/
int AvailableRead() const {
return AvailableReadInternal(
mReadIndex.load(std::memory_order::memory_order_relaxed),
mWriteIndex.load(std::memory_order::memory_order_relaxed));
return AvailableReadInternal(mReadIndex.load(std::memory_order_relaxed),
mWriteIndex.load(std::memory_order_relaxed));
}
/**
* 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.
*/
int AvailableWrite() const {
return AvailableWriteInternal(
mReadIndex.load(std::memory_order::memory_order_relaxed),
mWriteIndex.load(std::memory_order::memory_order_relaxed));
return AvailableWriteInternal(mReadIndex.load(std::memory_order_relaxed),
mWriteIndex.load(std::memory_order_relaxed));
}
/**
* Get the total Capacity, for this ring buffer.

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

@ -17,18 +17,20 @@ const { debug, warn } = GeckoViewUtils.initLogging("ContentDelegateParent");
class ContentDelegateParent extends GeckoViewActorParent {
async receiveMessage(aMsg) {
debug`receiveMessage: ${aMsg.name} ${aMsg}`;
debug`receiveMessage: ${aMsg.name}`;
switch (aMsg.name) {
case "GeckoView:DOMFullscreenExit": {
this.window.windowUtils.remoteFrameFullscreenReverted();
break;
return null;
}
case "GeckoView:DOMFullscreenRequest": {
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(
"resource://gre/modules/GeckoViewUtils.jsm"
);
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
XPCOMUtils.defineLazyModuleGetters(this, {
FormLikeFactory: "resource://gre/modules/FormLikeFactory.jsm",
GeckoViewAutofill: "resource://gre/modules/GeckoViewAutofill.jsm",
WebNavigationFrames: "resource://gre/modules/WebNavigationFrames.jsm",
LoginManagerChild: "resource://gre/modules/LoginManagerChild.jsm",
});
const EXPORTED_SYMBOLS = ["GeckoViewAutoFillChild"];
@ -24,61 +26,352 @@ class GeckoViewAutoFillChild extends GeckoViewActorChild {
constructor() {
super();
XPCOMUtils.defineLazyGetter(this, "_autofill", function() {
return new GeckoViewAutofill(this.eventDispatcher);
});
this._autofillElements = undefined;
this._autofillInfos = undefined;
}
// eslint-disable-next-line complexity
handleEvent(aEvent) {
debug`handleEvent: ${aEvent.type}`;
const { contentWindow } = this;
switch (aEvent.type) {
case "DOMFormHasPassword": {
this._autofill.addElement(
FormLikeFactory.createFromForm(aEvent.composedTarget)
);
this.addElement(FormLikeFactory.createFromForm(aEvent.composedTarget));
break;
}
case "DOMInputPasswordAdded": {
const input = aEvent.composedTarget;
if (!input.form) {
this._autofill.addElement(FormLikeFactory.createFromField(input));
this.addElement(FormLikeFactory.createFromField(input));
}
break;
}
case "focusin": {
if (contentWindow.HTMLInputElement.isInstance(aEvent.composedTarget)) {
this._autofill.onFocus(aEvent.composedTarget);
if (
this.contentWindow.HTMLInputElement.isInstance(aEvent.composedTarget)
) {
this.onFocus(aEvent.composedTarget);
}
break;
}
case "focusout": {
if (contentWindow.HTMLInputElement.isInstance(aEvent.composedTarget)) {
this._autofill.onFocus(null);
if (
this.contentWindow.HTMLInputElement.isInstance(aEvent.composedTarget)
) {
this.onFocus(null);
}
break;
}
case "pagehide": {
if (aEvent.target === contentWindow.top.document) {
this._autofill.clearElements();
if (aEvent.target === this.document) {
this.clearElements(this.browsingContext);
}
break;
}
case "pageshow": {
if (aEvent.target === contentWindow.top.document && aEvent.persisted) {
this._autofill.scanDocument(aEvent.target);
if (aEvent.target === this.document) {
this.scanDocument(this.document);
}
break;
}
case "PasswordManager:ShowDoorhanger": {
const { form: formLike } = aEvent.detail;
this._autofill.commitAutofill(formLike);
this.commitAutofill(formLike);
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");

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

@ -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();
}
registerPrompt(prompt) {
this._prompts.set(prompt.id, prompt);
this.sendAsyncMessage("RegisterPrompt", {
dismissPrompt(prompt) {
this.eventDispatcher.sendRequest({
type: "GeckoView:Prompt:Dismiss",
id: prompt.id,
promptType: prompt.getPromptType(),
});
this.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", {
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) {
super(aModuleName, aMessageManager);
this._seqNo = 0;
this._actionCallback = () => {};
this._isActive = false;
this._previousMessage = "";
@ -133,6 +133,16 @@ class SelectionActionDelegateChild extends GeckoViewActorChild {
},
];
receiveMessage({ name, data }) {
debug`receiveMessage ${name}`;
switch (name) {
case "ExecuteSelectionAction": {
this._actionCallback(data);
}
}
}
_performPaste() {
this.handleEvent({ type: "pagehide" });
this.docShell.doCommand("cmd_paste");
@ -307,8 +317,6 @@ class SelectionActionDelegateChild extends GeckoViewActorChild {
const password = this._isPasswordField(aEvent);
const msg = {
type: "GeckoView:ShowSelectionAction",
seqNo: this._seqNo,
collapsed: aEvent.collapsed,
editable: aEvent.selectionEditable,
password,
@ -333,29 +341,21 @@ class SelectionActionDelegateChild extends GeckoViewActorChild {
return;
}
msg.seqNo = ++this._seqNo;
this._isActive = true;
this._previousMessage = JSON.stringify(msg);
this.eventDispatcher.sendRequest(msg, {
onSuccess: response => {
if (response.seqNo !== this._seqNo) {
// Stale action.
warn`Stale response ${response.id}`;
return;
}
const action = actions.find(action => action.id === response.id);
if (action) {
debug`Performing ${response.id}`;
action.perform.call(this, aEvent, response);
} else {
warn`Invalid action ${response.id}`;
}
},
onError: _ => {
// Do nothing; we can get here if the delegate was just unregistered.
},
});
// We can't just listen to the response of the message because we accept
// multiple callbacks.
this._actionCallback = data => {
const action = actions.find(action => action.id === data.id);
if (action) {
debug`Performing ${data.id}`;
action.perform.call(this, aEvent);
} else {
warn`Invalid action ${data.id}`;
}
};
this.sendAsyncMessage("ShowSelectionAction", msg);
} else if (
[
"invisibleselection",
@ -367,7 +367,6 @@ class SelectionActionDelegateChild extends GeckoViewActorChild {
if (!this._isActive) {
return;
}
this._isActive = false;
// Mark previous actions as stale. Don't do this for "invisibleselection"
@ -377,10 +376,7 @@ class SelectionActionDelegateChild extends GeckoViewActorChild {
this._seqNo++;
}
this.eventDispatcher.sendRequest({
type: "GeckoView:HideSelectionAction",
reason,
});
this.sendAsyncMessage("HideSelectionAction", { reason });
} else if (reason == "dragcaret") {
// nothing for selection action
} 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",
"ContentDelegateParent.jsm",
"GeckoViewAutoFillChild.jsm",
"GeckoViewAutoFillParent.jsm",
"GeckoViewContentChild.jsm",
"GeckoViewContentParent.jsm",
"GeckoViewFormValidationChild.jsm",
"GeckoViewPermissionChild.jsm",
"GeckoViewPermissionParent.jsm",
"GeckoViewPermissionProcessChild.jsm",
"GeckoViewPermissionProcessParent.jsm",
"GeckoViewPromptChild.jsm",
"GeckoViewPrompterChild.jsm",
"GeckoViewPrompterParent.jsm",
"GeckoViewSettingsChild.jsm",
"LoadURIDelegateChild.jsm",
"LoadURIDelegateParent.jsm",
"MediaControlDelegateChild.jsm",
"MediaControlDelegateParent.jsm",
"ProgressDelegateChild.jsm",
"ProgressDelegateParent.jsm",
"ScrollDelegateChild.jsm",
"ScrollDelegateParent.jsm",
"SelectionActionDelegateChild.jsm",
"SelectionActionDelegateParent.jsm",
]

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

@ -468,7 +468,12 @@ class ModuleInfo {
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() {
@ -613,6 +618,9 @@ function startup() {
onEnable: {
actors: {
ScrollDelegate: {
parent: {
moduleURI: "resource:///actors/ScrollDelegateParent.jsm",
},
child: {
moduleURI: "resource:///actors/ScrollDelegateChild.jsm",
events: {
@ -627,8 +635,12 @@ function startup() {
{
name: "GeckoViewSelectionAction",
onEnable: {
resource: "resource://gre/modules/GeckoViewSelectionAction.jsm",
actors: {
SelectionActionDelegate: {
parent: {
moduleURI: "resource:///actors/SelectionActionDelegateParent.jsm",
},
child: {
moduleURI: "resource:///actors/SelectionActionDelegateChild.jsm",
events: {
@ -679,6 +691,9 @@ function startup() {
onInit: {
actors: {
GeckoViewAutoFill: {
parent: {
moduleURI: "resource:///actors/GeckoViewAutoFillParent.jsm",
},
child: {
moduleURI: "resource:///actors/GeckoViewAutoFillChild.jsm",
events: {
@ -721,6 +736,9 @@ function startup() {
resource: "resource://gre/modules/GeckoViewMediaControl.jsm",
actors: {
MediaControlDelegate: {
parent: {
moduleURI: "resource:///actors/MediaControlDelegateParent.jsm",
},
child: {
moduleURI: "resource:///actors/MediaControlDelegateChild.jsm",
events: {

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

@ -8,301 +8,38 @@ var EXPORTED_SYMBOLS = ["GeckoViewPermission"];
const { XPCOMUtils } = ChromeUtils.import(
"resource://gre/modules/XPCOMUtils.jsm"
);
XPCOMUtils.defineLazyModuleGetters(this, {
E10SUtils: "resource://gre/modules/E10SUtils.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";
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
const { GeckoViewUtils } = ChromeUtils.import(
"resource://gre/modules/GeckoViewUtils.jsm"
);
class GeckoViewPermission {
constructor() {
this.wrappedJSObject = this;
}
_appPermissions = {};
async prompt(aRequest) {
const window = aRequest.window
? aRequest.window
: aRequest.element.ownerGlobal;
/* ---------- nsIObserver ---------- */
observe(aSubject, aTopic, aData) {
switch (aTopic) {
case "getUserMedia:ask-device-permission": {
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);
const actor = window.windowGlobalChild.getActor("GeckoViewPermission");
const result = await actor.promptPermission(aRequest);
if (!result.allow) {
aRequest.cancel();
} else {
// No dispatcher; just bail.
callback();
// Note: permission could be undefined, that's what aRequest expects.
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(
"{42f3c238-e8e8-4015-9ca2-148723a8afcf}"
);
GeckoViewPermission.prototype.QueryInterface = ChromeUtils.generateQI([
"nsIObserver",
"nsIContentPermissionPrompt",
]);
const { debug, warn } = GeckoViewUtils.initLogging("GeckoViewPermission");

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

@ -34,15 +34,8 @@ class GeckoViewPrompter {
}
}
if (this._domWin) {
this._dispatcher = GeckoViewUtils.getDispatcherForWindow(this._domWin);
}
if (!this._dispatcher) {
[
this._dispatcher,
this._domWin,
] = GeckoViewUtils.getActiveDispatcherAndWindow();
if (!this._domWin) {
this._domWin = Services.wm.getMostRecentWindow("navigator:geckoview");
}
this._innerWindowId = this._domWin?.browsingContext.currentWindowContext.innerWindowId;
@ -88,8 +81,7 @@ class GeckoViewPrompter {
}
_dismissUi() {
this.prompterActor?.unregisterPrompt(this);
this._dispatcher.dispatch("GeckoView:Prompt:Dismiss", { id: this.id });
this.prompterActor?.dismissPrompt(this);
}
accept(aInputText = this.inputText) {
@ -186,46 +178,35 @@ class GeckoViewPrompter {
});
}
asyncShowPrompt(aMsg, aCallback) {
let handled = false;
async asyncShowPrompt(aMsg, aCallback) {
this.message = aMsg;
this.inputText = aMsg.value;
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;
this._dispatcher.dispatch("GeckoView:Prompt", aMsg, {
onSuccess: onResponse,
onError: error => {
Cu.reportError("Prompt error: " + error);
onResponse(null);
},
});
this.prompterActor?.notifyPromptShow(this);
let response = null;
try {
if (this.checkInnerWindow()) {
response = await this.prompterActor.prompt(this, aMsg);
}
} catch (error) {
// 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 */);
}
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 = {
LoadURIDelegate: {
parent: {
moduleURI: "resource:///actors/LoadURIDelegateParent.jsm",
},
child: {
moduleURI: "resource:///actors/LoadURIDelegateChild.jsm",
},
messageManagerGroups: ["browsers"],
},
GeckoViewPermission: {
parent: {
moduleURI: "resource:///actors/GeckoViewPermissionParent.jsm",
},
child: {
moduleURI: "resource:///actors/GeckoViewPermissionChild.jsm",
},
allFrames: true,
includeChrome: true,
},
GeckoViewPrompt: {
child: {
moduleURI: "resource:///actors/GeckoViewPromptChild.jsm",
@ -70,55 +100,10 @@ class GeckoViewStartup {
switch (aTopic) {
case "content-process-ready-for-script":
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", {
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", {
module: "resource://gre/modules/GeckoViewStorageController.jsm",
ged: [
@ -153,6 +138,36 @@ class GeckoViewStartup {
Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_DEFAULT
) {
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", {
module: "resource://gre/modules/ChildCrashHandler.jsm",

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

@ -310,7 +310,15 @@ package org.mozilla.geckoview {
}
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 {
@ -331,7 +339,7 @@ package org.mozilla.geckoview {
}
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 @NonNull public Map<String,String> getAttributes();
method @AnyThread @NonNull public Collection<Autofill.Node> getChildren();
@ -339,16 +347,21 @@ package org.mozilla.geckoview {
method @AnyThread @NonNull public String getDomain();
method @AnyThread public boolean getEnabled();
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 getId();
method @AnyThread @Deprecated @DeprecationSchedule(id="autofill-node",version=104) public int getId();
method @AnyThread public int getInputType();
method @AnyThread @NonNull public String getTag();
method @AnyThread @NonNull public String getValue();
method @AnyThread public boolean getVisible();
method @AnyThread @Deprecated @DeprecationSchedule(id="autofill-node",version=104) @NonNull public String getValue();
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);
field public static final int NODE_ADDED = 3;
field public static final int NODE_BLURRED = 7;
@ -361,9 +374,14 @@ package org.mozilla.geckoview {
}
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 @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 @UiThread public boolean isVisible(@NonNull Autofill.Node);
}
@UiThread public class BasicSelectionActionDelegate implements ActionMode.Callback GeckoSession.SelectionActionDelegate {

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

@ -12,13 +12,6 @@
<input type="password" id="pass1" value="foo" autofocus>
</fieldset>
</form>
<iframe id="iframe"></iframe>
<script>
addEventListener("load", function(e) {
if (window.parent === window) {
document.getElementById("iframe").contentWindow.location.href = window.location.href;
}
});
</script>
<iframe id="iframe" src="forms2_iframe.html"></iframe>
</body>
</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="submit" value="submit">
</form>
<iframe id="iframe"></iframe>
<script>
addEventListener("load", function(e) {
if (window.parent === window) {
document.getElementById("iframe").contentWindow.location.href = window.location.href;
}
});
</script>
<iframe id="iframe" src="forms_autocomplete_iframe.html"></iframe>
</body>
</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
import android.graphics.Matrix
import android.os.Bundle
import android.os.LocaleList
import androidx.test.filters.MediumTest
import androidx.test.ext.junit.runners.AndroidJUnit4
import android.util.Pair
import android.util.SparseArray
import android.view.View
import android.view.ViewStructure
import android.view.autofill.AutofillId
import android.view.autofill.AutofillValue
import org.hamcrest.Matchers.*
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
import org.mozilla.geckoview.Autofill
import org.mozilla.geckoview.GeckoSession
import org.mozilla.geckoview.test.rule.GeckoSessionTestRule
import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.AssertCalled
import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.WithDisplay
import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.*
@RunWith(AndroidJUnit4::class)
@RunWith(Parameterized::class)
@MediumTest
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() {
sessionRule.setPrefsUntilTestEnd(mapOf(
"signon.rememberSignons" to true,
"signon.userInputRequiredToCapture.enabled" to false))
mainSession.loadTestPath(FORMS_HTML_PATH)
mainSession.loadUri(pageUrl)
// Wait for the auto-fill nodes to populate.
sessionRule.waitUntilCalled(object : Autofill.Delegate {
// 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.
@AssertCalled(count = 4)
override fun onAutofill(session: GeckoSession,
notification: Int,
node: Autofill.Node?) {
assertThat("Should be starting auto-fill",
notification,
equalTo(forEachCall(
Autofill.Notify.SESSION_STARTED,
Autofill.Notify.NODE_ADDED)))
}
sessionRule.waitUntilCalled(object : Autofill.Delegate, GeckoSession.ProgressDelegate {
// We expect to get a call to onSessionStart and many calls to onNodeAdd depending
// on timing.
@AssertCalled(count = 1)
override fun onSessionStart(session: GeckoSession) {}
@AssertCalled(count = -1)
override fun onNodeAdd(session: GeckoSession,
node: Autofill.Node,
data: Autofill.NodeData) {}
@AssertCalled(count = 1)
override fun onPageStop(session: GeckoSession, success: Boolean) {}
})
// Assign node values.
@ -60,34 +68,33 @@ class AutofillDelegateTest : BaseSessionTest() {
mainSession.evaluateJS("document.querySelector('#form1').submit()")
sessionRule.waitUntilCalled(object : Autofill.Delegate {
@AssertCalled(count = 5)
override fun onAutofill(session: GeckoSession,
notification: Int,
node: Autofill.Node?) {
val info = sessionRule.currentCall
@AssertCalled(order = [1,2,3,4])
override fun onNodeUpdate(session: GeckoSession,
node: Autofill.Node,
data: Autofill.NodeData) {
}
if (info.counter < 5) {
assertThat("Should be an update notification",
notification,
equalTo(Autofill.Notify.NODE_UPDATED))
} else {
assertThat("Should be a commit notification",
notification,
equalTo(Autofill.Notify.SESSION_COMMITTED))
assertThat("Values should match",
countAutofillNodes({ it.value == "user1x" }),
equalTo(1))
assertThat("Values should match",
countAutofillNodes({ it.value == "pass1x" }),
equalTo(1))
assertThat("Values should match",
countAutofillNodes({ it.value == "e@mail.com" }),
equalTo(1))
assertThat("Values should match",
countAutofillNodes({ it.value == "1" }),
equalTo(1))
}
@AssertCalled(order = [5])
override fun onSessionCommit(session: GeckoSession,
node: Autofill.Node,
data: Autofill.NodeData) {
val autofillSession = mainSession.autofillSession
assertThat("Values should match",
countAutofillNodes({
autofillSession.dataFor(it).value == "user1x" }),
equalTo(1))
assertThat("Values should match",
countAutofillNodes({
autofillSession.dataFor(it).value == "pass1x" }),
equalTo(1))
assertThat("Values should match",
countAutofillNodes({
autofillSession.dataFor(it).value == "e@mail.com" }),
equalTo(1))
assertThat("Values should match",
countAutofillNodes({
autofillSession.dataFor(it).value == "1" }),
equalTo(1))
}
})
}
@ -99,17 +106,15 @@ class AutofillDelegateTest : BaseSessionTest() {
mainSession.loadTestPath(FORMS_ID_VALUE_HTML_PATH)
// 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 onAutofill(session: GeckoSession,
notification: Int,
node: Autofill.Node?) {
assertThat("Should be starting auto-fill",
notification,
equalTo(forEachCall(
Autofill.Notify.SESSION_STARTED,
Autofill.Notify.NODE_ADDED)))
}
override fun onSessionStart(session: GeckoSession) {}
@AssertCalled(count = -1)
override fun onNodeAdd(session: GeckoSession,
node: Autofill.Node,
data: Autofill.NodeData) {}
@AssertCalled(count = 1)
override fun onPageStop(session: GeckoSession, success: Boolean) {}
})
// Assign node values.
@ -119,25 +124,20 @@ class AutofillDelegateTest : BaseSessionTest() {
mainSession.evaluateJS("document.querySelector('#form1').submit()")
sessionRule.waitUntilCalled(object : Autofill.Delegate {
@AssertCalled(count = 2)
override fun onAutofill(session: GeckoSession,
notification: Int,
node: Autofill.Node?) {
val info = sessionRule.currentCall
if (info.counter < 2) {
assertThat("Should be an update notification",
notification,
equalTo(Autofill.Notify.NODE_UPDATED))
} else {
assertThat("Should be a commit notification",
notification,
equalTo(Autofill.Notify.SESSION_COMMITTED))
assertThat("Values should match",
countAutofillNodes({ it.value == "pass1x" }),
equalTo(1))
}
@AssertCalled(order = [1])
override fun onNodeUpdate(session: GeckoSession,
node: Autofill.Node,
data: Autofill.NodeData) {
}
@AssertCalled(order = [2])
override fun onSessionCommit(session: GeckoSession,
node: Autofill.Node,
data: Autofill.NodeData) {
assertThat("Values should match",
countAutofillNodes({
mainSession.autofillSession.dataFor(it).value == "pass1x"
}),
equalTo(1))
}
})
}
@ -145,16 +145,16 @@ class AutofillDelegateTest : BaseSessionTest() {
@Test fun autofill() {
// Test parts of the Oreo auto-fill API; there is another autofill test in
// SessionAccessibility for a11y auto-fill support.
mainSession.loadTestPath(FORMS_HTML_PATH)
mainSession.loadUri(pageUrl)
// Wait for the auto-fill nodes to populate.
sessionRule.waitUntilCalled(object : Autofill.Delegate {
// 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.
@AssertCalled(count = 4)
override fun onAutofill(session: GeckoSession,
notification: Int,
node: Autofill.Node?) {
}
sessionRule.waitUntilCalled(object : Autofill.Delegate, GeckoSession.ProgressDelegate {
// We expect many call to onNodeAdd while loading the page
@AssertCalled(count = -1)
override fun onNodeAdd(session: GeckoSession,
node: Autofill.Node,
data: Autofill.NodeData) {}
@AssertCalled(count = 1)
override fun onPageStop(session: GeckoSession, success: Boolean) {}
})
val autofills = mapOf(
@ -163,45 +163,36 @@ class AutofillDelegateTest : BaseSessionTest() {
"#number1" to "24", "#tel1" to "42")
// 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.
arrayOf("document", "document.querySelector('#iframe').contentDocument").map { doc ->
mainSession.evaluatePromiseJS("""new Promise(resolve =>
$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 }))""")
}
mainSession.evaluatePromiseJS("""
window.getDataForAllFrames('${entry.key}', '${entry.value}')
""")
}
val autofillValues = SparseArray<CharSequence>()
// 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.
if (child.children.count() > 0) {
if (child.children.isNotEmpty()) {
for (c in child.children) {
checkAutofillChild(c!!)
checkAutofillChild(c!!, child.domain)
}
}
if (child.id == View.NO_ID) {
if (child == mainSession.autofillSession.root) {
return
}
assertThat("Should have HTML tag",
child.tag, not(isEmptyOrNullString()))
assertThat("Web domain should match",
child.domain, equalTo(GeckoSessionTestRule.TEST_ENDPOINT))
if (domain != "") {
assertThat(
"Web domain should match its parent.",
child.domain, equalTo(domain)
)
}
if (child.inputType == Autofill.InputType.TEXT) {
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()))
}
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.PHONE -> "42"
Autofill.InputType.TEXT -> when (child.hint) {
@ -225,28 +217,37 @@ class AutofillDelegateTest : BaseSessionTest() {
}
val nodes = mainSession.autofillSession.root
checkAutofillChild(nodes)
checkAutofillChild(nodes, "")
mainSession.autofill(autofillValues)
// Wait on the promises and check for correct values.
for ((key, actual, expected, eventInterface) in promises.map { it.value.asJSList<String>() }) {
assertThat("Auto-filled value must match ($key)", actual, equalTo(expected))
assertThat("input event should be dispatched with InputEvent interface", eventInterface, equalTo("InputEvent"))
for (values in promises.map { it.value.asJsonArray() }) {
for (i in 0 until values.length()) {
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 parts of the Oreo auto-fill API; there is another autofill test in
// SessionAccessibility for a11y auto-fill support.
mainSession.loadTestPath(FORMS_HTML_PATH)
mainSession.loadUri(pageUrl)
// 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)
override fun onAutofill(session: GeckoSession,
notification: Int,
node: Autofill.Node?) {
}
override fun onPageStop(session: GeckoSession, success: Boolean) {}
})
val autofillValues = SparseArray<CharSequence>()
@ -266,19 +267,29 @@ class AutofillDelegateTest : BaseSessionTest() {
@WithDisplay(width = 100, height = 100)
@Test fun autofillNavigation() {
// Wait for the accessibility nodes to populate.
mainSession.loadTestPath(FORMS_HTML_PATH)
sessionRule.waitUntilCalled(object : Autofill.Delegate {
@AssertCalled(count = 4)
override fun onAutofill(session: GeckoSession,
notification: Int,
node: Autofill.Node?) {
assertThat("Should be starting auto-fill",
notification,
equalTo(forEachCall(
Autofill.Notify.SESSION_STARTED,
Autofill.Notify.NODE_ADDED)))
mainSession.loadUri(pageUrl)
sessionRule.waitUntilCalled(object : Autofill.Delegate, ShouldContinue,
GeckoSession.ProgressDelegate
{
var nodeCount = 0
// Continue waiting util we get all 16 nodes
override fun shouldContinue(): Boolean = nodeCount < 16
@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())
nodeCount = countAutofillNodes()
}
@AssertCalled(count = 1)
override fun onPageStop(session: GeckoSession, success: Boolean) {}
})
assertThat("Initial auto-fill count should match",
@ -286,99 +297,101 @@ class AutofillDelegateTest : BaseSessionTest() {
// Now wait for the nodes to clear.
mainSession.loadTestPath(HELLO_HTML_PATH)
sessionRule.waitUntilCalled(object : Autofill.Delegate {
sessionRule.waitUntilCalled(object : Autofill.Delegate, GeckoSession.ProgressDelegate {
@AssertCalled(count = 1)
override fun onAutofill(session: GeckoSession,
notification: Int,
node: Autofill.Node?) {
assertThat("Should be canceling auto-fill",
notification,
equalTo(Autofill.Notify.SESSION_CANCELED))
assertThat("Node should be null", node, nullValue())
}
override fun onSessionCancel(session: GeckoSession) {}
@AssertCalled
override fun onPageStop(session: GeckoSession, success: Boolean) {}
})
assertThat("Should not have auto-fill fields",
countAutofillNodes(), equalTo(0))
// Now wait for the nodes to reappear.
mainSession.waitForPageStop()
mainSession.goBack()
sessionRule.waitUntilCalled(object : Autofill.Delegate, GeckoSession.ProgressDelegate {
@AssertCalled(count = 4)
override fun onAutofill(session: GeckoSession,
notification: Int,
node: Autofill.Node?) {
assertThat("Should be starting auto-fill",
notification,
equalTo(forEachCall(
Autofill.Notify.SESSION_STARTED,
Autofill.Notify.NODE_ADDED)))
assertThat("ID should be valid", node, notNullValue())
sessionRule.waitUntilCalled(object : Autofill.Delegate, GeckoSession.ProgressDelegate,
ShouldContinue
{
var nodeCount = 0
override fun shouldContinue(): Boolean = nodeCount < 16
@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())
nodeCount = countAutofillNodes()
}
@AssertCalled(count = 1)
override fun onPageStop(session: GeckoSession, success: Boolean) {
}
override fun onPageStop(session: GeckoSession, success: Boolean) {}
})
assertThat("Should have auto-fill fields again",
countAutofillNodes(), equalTo(16))
var focused = mainSession.autofillSession.focused
assertThat("Should not have focused field",
countAutofillNodes({ it.focused }), equalTo(0))
countAutofillNodes({ it == focused }), equalTo(0))
mainSession.evaluateJS("document.querySelector('#pass2').focus()")
sessionRule.waitUntilCalled(object : Autofill.Delegate {
@AssertCalled(count = 1)
override fun onAutofill(session: GeckoSession,
notification: Int,
node: Autofill.Node?) {
assertThat("Should be entering auto-fill view",
notification,
equalTo(Autofill.Notify.NODE_FOCUSED))
override fun onNodeFocus(session: GeckoSession,
node: Autofill.Node,
data: Autofill.NodeData) {
assertThat("ID should be valid", node, notNullValue())
}
})
focused = mainSession.autofillSession.focused
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
// be visible.
// Hidden elements are ignored.
// TODO: Is this actually correct? Should the whole focused branch be
// visible or just the nodes as described above?
assertThat("Should have nine visible nodes",
countAutofillNodes({ node -> node.visible }),
countAutofillNodes({ node -> mainSession.autofillSession.isVisible(node) }),
equalTo(8))
mainSession.evaluateJS("document.querySelector('#pass2').blur()")
sessionRule.waitUntilCalled(object : Autofill.Delegate {
@AssertCalled(count = 1)
override fun onAutofill(session: GeckoSession,
notification: Int,
node: Autofill.Node?) {
assertThat("Should be exiting auto-fill view",
notification,
equalTo(Autofill.Notify.NODE_BLURRED))
override fun onNodeBlur(session: GeckoSession,
node: Autofill.Node,
data: Autofill.NodeData) {
assertThat("ID should be valid", node, notNullValue())
}
})
focused = mainSession.autofillSession.focused
assertThat("Should not have focused field",
countAutofillNodes({ it.focused }), equalTo(0))
countAutofillNodes({ it == focused }), equalTo(0))
}
@WithDisplay(height = 100, width = 100)
@Test fun autofillUserpass() {
mainSession.loadTestPath(FORMS2_HTML_PATH)
// Wait for the auto-fill nodes to populate.
sessionRule.waitUntilCalled(object : Autofill.Delegate {
@AssertCalled(count = 3)
override fun onAutofill(session: GeckoSession,
notification: Int,
node: Autofill.Node?) {
assertThat("Autofill notification should match", notification,
equalTo(forEachCall(Autofill.Notify.SESSION_STARTED,
Autofill.Notify.NODE_FOCUSED,
Autofill.Notify.NODE_ADDED)))
}
sessionRule.waitUntilCalled(object : Autofill.Delegate, GeckoSession.ProgressDelegate {
@AssertCalled(count = 1)
override fun onSessionStart(session: GeckoSession) {}
@AssertCalled(count = 1)
override fun onNodeFocus(session: GeckoSession,
node: Autofill.Node,
data: Autofill.NodeData) {}
@AssertCalled(count = -1)
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.
@ -393,7 +406,8 @@ class AutofillDelegateTest : BaseSessionTest() {
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"))
return sum + 1
@ -410,48 +424,42 @@ class AutofillDelegateTest : BaseSessionTest() {
@Test fun autofillActiveChange() {
// We should blur the active autofill node if the session is set
// 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.
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
// a group for inputs outside of forms, so the total count is 4.
@AssertCalled(count = 4)
override fun onAutofill(session: GeckoSession,
notification: Int,
node: Autofill.Node?) {
assertThat("Should be starting auto-fill",
notification,
equalTo(forEachCall(
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) {}
@AssertCalled(count = 1)
override fun onPageStop(session: GeckoSession, success: Boolean) {}
})
mainSession.evaluateJS("document.querySelector('#pass2').focus()")
sessionRule.waitUntilCalled(object : Autofill.Delegate {
@AssertCalled(count = 1)
override fun onAutofill(session: GeckoSession,
notification: Int,
node: Autofill.Node?) {
assertThat("Should be entering auto-fill view",
notification,
equalTo(Autofill.Notify.NODE_FOCUSED))
override fun onNodeFocus(session: GeckoSession,
node: Autofill.Node,
data: Autofill.NodeData) {
assertThat("ID should be valid", node, notNullValue())
}
})
var focused = mainSession.autofillSession.focused
assertThat("Should have one focused field",
countAutofillNodes({ it.focused }), equalTo(1))
countAutofillNodes({ it == focused }), equalTo(1))
// Make sure we get NODE_BLURRED when inactive
mainSession.setActive(false)
sessionRule.waitUntilCalled(object : Autofill.Delegate {
@AssertCalled(count = 1)
override fun onAutofill(session: GeckoSession,
notification: Int,
node: Autofill.Node?) {
assertThat("Should be exiting auto-fill view",
notification,
equalTo(Autofill.Notify.NODE_BLURRED))
override fun onNodeBlur(session: GeckoSession,
node: Autofill.Node,
data: Autofill.NodeData) {
assertThat("ID should be valid", node, notNullValue())
}
})
@ -460,29 +468,30 @@ class AutofillDelegateTest : BaseSessionTest() {
mainSession.setActive(true)
sessionRule.waitUntilCalled(object : Autofill.Delegate {
@AssertCalled(count = 1)
override fun onAutofill(session: GeckoSession,
notification: Int,
node: Autofill.Node?) {
assertThat("Should be entering auto-fill view",
notification,
equalTo(Autofill.Notify.NODE_FOCUSED))
override fun onNodeFocus(session: GeckoSession,
node: Autofill.Node,
data: Autofill.NodeData) {
assertThat("ID should be valid", node, notNullValue())
}
})
focused = mainSession.autofillSession.focused
assertThat("Should have one focused field",
countAutofillNodes({ it.focused }), equalTo(1))
countAutofillNodes({ focused == it }), equalTo(1))
}
@WithDisplay(width = 100, height = 100)
@Test fun autofillAutocompleteAttribute() {
mainSession.loadTestPath(FORMS_AUTOCOMPLETE_HTML_PATH)
sessionRule.waitUntilCalled(object : Autofill.Delegate {
@AssertCalled(count = 3)
override fun onAutofill(session: GeckoSession,
notification: Int,
node: Autofill.Node?) {
}
});
sessionRule.waitUntilCalled(object : Autofill.Delegate, GeckoSession.ProgressDelegate {
@AssertCalled(count = -1)
override fun onNodeAdd(session: GeckoSession,
node: Autofill.Node,
data: Autofill.NodeData) {}
@AssertCalled(count = 1)
override fun onPageStop(session: GeckoSession, success: Boolean) {}
})
fun checkAutofillChild(child: Autofill.Node): Int {
var sum = 0
@ -501,266 +510,4 @@ class AutofillDelegateTest : BaseSessionTest() {
assertThat("autofill hint count",
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 FORM_BLANK_HTML_PATH = "/assets/www/form_blank.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 FORMS3_HTML_PATH = "/assets/www/forms3.html"
const val FORMS4_HTML_PATH = "/assets/www/forms4.html"

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

@ -59,15 +59,15 @@ class MediaDelegateXOriginTest : BaseSessionTest() {
mainSession.delegateDuringNextWait(object : MediaDelegate {
@GeckoSessionTestRule.AssertCalled(count = 1)
override fun onRecordingStatusChanged(session: GeckoSession,
devices: Array<org.mozilla.geckoview.GeckoSession.MediaDelegate.RecordingDevice>) {
devices: Array<MediaDelegate.RecordingDevice>) {
var audioActive = false
var cameraActive = false
for (device in devices) {
if (device.type == org.mozilla.geckoview.GeckoSession.MediaDelegate.RecordingDevice.Type.MICROPHONE) {
audioActive = device.status != org.mozilla.geckoview.GeckoSession.MediaDelegate.RecordingDevice.Status.INACTIVE
if (device.type == MediaDelegate.RecordingDevice.Type.MICROPHONE) {
audioActive = device.status != MediaDelegate.RecordingDevice.Status.INACTIVE
}
if (device.type == org.mozilla.geckoview.GeckoSession.MediaDelegate.RecordingDevice.Type.CAMERA) {
cameraActive = device.status != org.mozilla.geckoview.GeckoSession.MediaDelegate.RecordingDevice.Status.INACTIVE
if (device.type == MediaDelegate.RecordingDevice.Type.CAMERA) {
cameraActive = device.status != MediaDelegate.RecordingDevice.Status.INACTIVE
}
}
@ -76,7 +76,6 @@ class MediaDelegateXOriginTest : BaseSessionTest() {
assertThat("Audio is ${if (allowAudio ) { "active" } else { "inactive" }}" ,
audioActive, Matchers.equalTo(allowAudio))
}
})
@ -149,9 +148,6 @@ class MediaDelegateXOriginTest : BaseSessionTest() {
}
@Test fun testDeviceRecordingEventAudioAndVideoInXOriginIframe() {
// TODO: Bug 1648153
assumeThat(sessionRule.env.isFission, Matchers.equalTo(false))
// TODO: needs bug 1700243
assumeThat(sessionRule.env.isIsolatedProcess, Matchers.equalTo(false))
@ -167,9 +163,6 @@ class MediaDelegateXOriginTest : BaseSessionTest() {
}
@Test fun testDeviceRecordingEventAudioAndVideoInXOriginIframeNoAllow() {
// TODO: Bug 1648153
assumeThat(sessionRule.env.isFission, Matchers.equalTo(false))
mainSession.loadTestPath(GETUSERMEDIA_XORIGIN_CONTAINER_HTML_PATH)
mainSession.waitForPageStop()

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

@ -151,7 +151,7 @@ class PermissionDelegateTest : BaseSessionTest() {
window.navigator.mediaDevices.getUserMedia({ video: true })""")
} else {
mainSession.waitForJS("""
window.navigator.mediaDevices.getUserMedia({ audio: true: video: true })""")
window.navigator.mediaDevices.getUserMedia({ audio: true, video: true })""")
}
fail("Request should have failed")
} catch (e: RejectedPromiseException) {

Разница между файлами не показана из-за своего большого размера Загрузить разницу

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

@ -831,6 +831,7 @@ public class GeckoSession {
final String event,
final GeckoBundle message,
final EventCallback callback) {
Log.d(LOGTAG, "handleMessage: " + event);
if (delegate == null) {
callback.sendSuccess(/* granted */ false);
return;
@ -900,7 +901,7 @@ public class GeckoSession {
final @SelectionActionDelegateAction HashSet<String> actionsSet =
new HashSet<>(Arrays.asList(message.getStringArray("actions")));
final SelectionActionDelegate.Selection selection =
new SelectionActionDelegate.Selection(message, actionsSet, callback);
new SelectionActionDelegate.Selection(message, actionsSet, mEventDispatcher);
delegate.onShowActionRequest(GeckoSession.this, selection);
@ -1318,14 +1319,12 @@ public class GeckoSession {
@Override
public void handleMessage(
final String event, final GeckoBundle message, final EventCallback callback) {
if (DEBUG) {
Log.d(LOGTAG, "handleMessage: event = " + event);
}
Log.d(LOGTAG, "handleMessage " + event);
if ("GeckoView:PinOnScreen".equals(event)) {
GeckoSession.this.setShouldPinOnScreen(message.getBoolean("pinned"));
} 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)) {
mPromptController.dismissPrompt(message.getString("id"));
}
@ -3406,14 +3405,14 @@ public class GeckoSession {
/** Set of valid actions available through {@link Selection#execute(String)} */
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(
final GeckoBundle bundle,
final @NonNull @SelectionActionDelegateAction Set<String> actions,
final EventCallback callback) {
final EventDispatcher eventDispatcher) {
flags =
(bundle.getBoolean("collapsed") ? SelectionActionDelegate.FLAG_IS_COLLAPSED : 0)
| (bundle.getBoolean("editable") ? SelectionActionDelegate.FLAG_IS_EDITABLE : 0)
@ -3421,8 +3420,8 @@ public class GeckoSession {
text = bundle.getString("selection");
clientRect = bundle.getRectF("clientRect");
availableActions = actions;
mSeqNo = bundle.getInt("seqNo");
mEventCallback = callback;
mActionId = bundle.getString("actionId");
mEventDispatcher = new WeakReference<>(eventDispatcher);
}
/** Empty constructor for tests. */
@ -3431,8 +3430,8 @@ public class GeckoSession {
text = "";
clientRect = null;
availableActions = new HashSet<>();
mSeqNo = 0;
mEventCallback = null;
mActionId = null;
mEventDispatcher = null;
}
/**
@ -3458,10 +3457,16 @@ public class GeckoSession {
if (!isActionAvailable(action)) {
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);
response.putString("id", action);
response.putInt("seqNo", mSeqNo);
mEventCallback.sendSuccess(response);
response.putString("actionId", mActionId);
eventDispatcher.dispatch("GeckoView:ExecuteSelectionAction", response);
}
/**

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

@ -23,7 +23,6 @@ import android.os.Build;
import android.os.Handler;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Log;
import android.util.SparseArray;
import android.util.TypedValue;
import android.view.DisplayCutout;
@ -50,6 +49,7 @@ import androidx.annotation.UiThread;
import androidx.core.view.ViewCompat;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Objects;
import org.mozilla.gecko.AndroidGamepadManager;
import org.mozilla.gecko.EventDispatcher;
import org.mozilla.gecko.InputMethods;
@ -272,7 +272,12 @@ public class GeckoView extends FrameLayout {
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;
}
@TargetApi(26)
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(
@NonNull final GeckoSession session, @NonNull final Autofill.Node node) {
@NonNull final GeckoSession session, @Nullable final Autofill.Node node) {
if (node == null) {
return new Rect(0, 0, 0, 0);
}
@ -934,43 +955,95 @@ public class GeckoView extends FrameLayout {
}
@Override
public void onAutofill(
@NonNull final GeckoSession session, final int notification, final Autofill.Node node) {
ThreadUtils.assertOnUiThread();
if (Build.VERSION.SDK_INT < 26) {
public void onNodeBlur(
final @NonNull GeckoSession session,
final @NonNull Autofill.Node prev,
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;
}
final Autofill.Node focused = mSession.getAutofillSession().getFocused();
// We must have a focused node because |node| is visible
Objects.requireNonNull(focused);
final AutofillManager manager =
GeckoView.this.getContext().getSystemService(AutofillManager.class);
if (manager == null) {
return;
final Autofill.NodeData focusedData = mSession.getAutofillSession().dataFor(focused);
Objects.requireNonNull(focusedData);
ensureAutofillManager();
if (mAutofillManager != null) {
mAutofillManager.notifyViewExited(GeckoView.this, focusedData.getId());
mAutofillManager.notifyViewEntered(
GeckoView.this, focusedData.getId(), displayRectForId(session, focused));
}
}
try {
switch (notification) {
case Autofill.Notify.SESSION_STARTED:
// This line seems necessary for auto-fill to work on the initial page.
case Autofill.Notify.SESSION_CANCELED:
manager.cancel();
break;
case Autofill.Notify.SESSION_COMMITTED:
manager.commit();
break;
case Autofill.Notify.NODE_FOCUSED:
manager.notifyViewEntered(
GeckoView.this, node.getId(), displayRectForId(session, node));
break;
case Autofill.Notify.NODE_BLURRED:
manager.notifyViewExited(GeckoView.this, node.getId());
break;
case Autofill.Notify.NODE_UPDATED:
manager.notifyValueChanged(
GeckoView.this, node.getId(), AutofillValue.forText(node.getValue()));
break;
}
} catch (final SecurityException e) {
Log.e(LOGTAG, "Failed to call Autofill Manager API: ", e);
@Override
public void onNodeFocus(
final @NonNull GeckoSession session,
final @NonNull Autofill.Node focused,
final @NonNull Autofill.NodeData data) {
ensureAutofillManager();
if (mAutofillManager != null) {
mAutofillManager.notifyViewEntered(
GeckoView.this, data.getId(), displayRectForId(session, focused));
}
}
@Override
public void onNodeRemove(
final @NonNull GeckoSession session,
final @NonNull Autofill.Node node,
final @NonNull Autofill.NodeData data) {}
@Override
public void onNodeUpdate(
final @NonNull GeckoSession session,
final @NonNull Autofill.Node node,
final @NonNull Autofill.NodeData data) {
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(
final GeckoSession session, final GeckoBundle message, final EventCallback callback) {
Log.d(LOGTAG, "handleEvent " + message.getString("type"));
final PromptDelegate delegate = session.getPromptDelegate();
if (delegate == null) {
// 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.
- [`WebRequestError.ERROR_HTTPS_ONLY`][102.6] now has error category
`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.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.5]: {{javadoc_uri}}/GeckoSession.html#setPriorityHint(int)
[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
- 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.25]: {{javadoc_uri}}/GeckoResult.html
[api-version]: 2f401dd976431a7a150b8743f3949a50adbeeb4b
[api-version]: dbe27cda45ed1bdff264b14f4471fdbe8438ca94

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

@ -5,6 +5,9 @@
const { GeckoViewUtils } = ChromeUtils.import(
"resource://gre/modules/GeckoViewUtils.jsm"
);
const { EventDispatcher } = ChromeUtils.import(
"resource://gre/modules/Messaging.jsm"
);
var EXPORTED_SYMBOLS = ["GeckoViewActorChild"];
@ -15,9 +18,7 @@ class GeckoViewActorChild extends JSWindowActorChild {
}
actorCreated() {
this.eventDispatcher = GeckoViewUtils.getDispatcherForWindow(
this.docShell.domWindow
);
this.eventDispatcher = EventDispatcher.forActor(this);
}
}

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

@ -19,15 +19,37 @@ class GeckoViewActorParent extends JSWindowActorParent {
}
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() {
return this.window.moduleManager.eventDispatcher;
return this.window?.moduleManager.eventDispatcher;
}
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.
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
* 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 = ["GeckoViewAutofill"];
const { XPCOMUtils } = ChromeUtils.import(
"resource://gre/modules/XPCOMUtils.jsm"
);
const { GeckoViewUtils } = ChromeUtils.import(
"resource://gre/modules/GeckoViewUtils.jsm"
);
XPCOMUtils.defineLazyModuleGetters(this, {
DeferredTask: "resource://gre/modules/DeferredTask.jsm",
FormLikeFactory: "resource://gre/modules/FormLikeFactory.jsm",
LoginManagerChild: "resource://gre/modules/LoginManagerChild.jsm",
Services: "resource://gre/modules/Services.jsm",
});
var EXPORTED_SYMBOLS = ["gAutofillManager"];
const { debug, warn } = GeckoViewUtils.initLogging("Autofill");
class GeckoViewAutofill {
constructor(aEventDispatcher) {
this._eventDispatcher = aEventDispatcher;
this._autofillElements = undefined;
this._autofillInfos = undefined;
this._autofillTasks = undefined;
class Autofill {
constructor(sessionId, eventDispatcher) {
this.eventDispatcher = eventDispatcher;
this.sessionId = sessionId;
}
/**
* 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.
*/
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}`;
},
start() {
this.eventDispatcher.sendRequest({
type: "GeckoView:StartAutofill",
sessionId: this.sessionId,
});
if (sendFocusEvent) {
// We might have missed sending a focus event for the active element.
this.onFocus(aFormLike.ownerDocument.activeElement);
}
}
/**
* 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._eventDispatcher.dispatch("GeckoView:OnAutofillFocus", info);
}
add(node) {
return this.eventDispatcher.sendRequestForResult({
type: "GeckoView:AddAutofill",
node,
});
}
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._eventDispatcher.dispatch("GeckoView:UpdateAutofill", updatedInfo);
}
const info = this._getInfo(aFormLike.rootElement);
if (info) {
debug`Committing node ${info}`;
this._eventDispatcher.dispatch("GeckoView:CommitAutofill", info);
}
focus(node) {
this.eventDispatcher.sendRequest({
type: "GeckoView:OnAutofillFocus",
node,
});
}
/**
* Clear all tracked auto-fill forms and notify Java.
*/
clearElements() {
debug`Clearing auto-fill`;
update(node) {
this.eventDispatcher.sendRequest({
type: "GeckoView:UpdateAutofill",
node,
});
}
this._autofillTasks = undefined;
this._autofillInfos = undefined;
this._autofillElements = undefined;
commit(node) {
this.eventDispatcher.sendRequest({
type: "GeckoView:CommitAutofill",
node,
});
}
this._eventDispatcher.sendRequest({
clear() {
this.eventDispatcher.sendRequest({
type: "GeckoView:ClearAutofill",
});
}
}
/**
* 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]));
}
}
class AutofillManager {
sessions = new Set();
autofill = null;
// Finally add frames.
const frames = aDoc.defaultView.frames;
for (let i = 0; i < frames.length; i++) {
this.scanDocument(frames[i].document);
ensure(sessionId, eventDispatcher) {
if (!this.sessions.has(sessionId)) {
this.autofill = new Autofill(sessionId, eventDispatcher);
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
* value changes from default, and every time the pref value changes
@ -365,23 +324,6 @@ var GeckoViewUtils = {
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
* Log.jsm logger. Currently "debug" and "warn" functions are supported. To

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

@ -13,12 +13,47 @@ var EXPORTED_SYMBOLS = ["EventDispatcher"];
const IS_PARENT_PROCESS =
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) {
this._dispatcher = aDispatcher;
this._messageManager = aMessageManager;
if (!aDispatcher) {
// 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();
(aMessageManager || Services.cpmm).addMessageListener(
"GeckoView:MessagingReply",
@ -226,6 +261,15 @@ var EventDispatcher = {
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) {
// aMsg.data includes keys: global, event, data, uuid
let callback;

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

@ -18,7 +18,6 @@ EXTRA_JS_MODULES += [
"GeckoViewConsole.jsm",
"GeckoViewContent.jsm",
"GeckoViewContentBlocking.jsm",
"GeckoViewMedia.jsm",
"GeckoViewMediaControl.jsm",
"GeckoViewModule.jsm",
"GeckoViewNavigation.jsm",
@ -26,6 +25,7 @@ EXTRA_JS_MODULES += [
"GeckoViewProgress.jsm",
"GeckoViewPushController.jsm",
"GeckoViewRemoteDebugger.jsm",
"GeckoViewSelectionAction.jsm",
"GeckoViewSettings.jsm",
"GeckoViewStorageController.jsm",
"GeckoViewTab.jsm",

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

@ -72,6 +72,8 @@ DIRS += [
]
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/iframe_http_only.html",
"geckoview/src/androidTest/assets/www/simple_redirect.sjs",

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

@ -48,7 +48,6 @@ CustomProtocolHandler.prototype = {
}
return this.QueryInterface(aIID);
},
lockFactory() {},
/** nsISupports */
QueryInterface: ChromeUtils.generateQI(["nsIProtocolHandler", "nsIFactory"]),

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

@ -24,9 +24,6 @@ var factory = {
}
return unsafeAboutModule.QueryInterface(aIID);
},
lockFactory(aLock) {
throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED);
},
QueryInterface: ChromeUtils.generateQI(["nsIFactory"]),
};

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

@ -116,7 +116,6 @@ ProtocolHandler.prototype = {
}
return this.QueryInterface(aIID);
},
lockFactory() {},
/** nsISupports */
QueryInterface: ChromeUtils.generateQI([

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

@ -27,9 +27,6 @@ var sniffer = {
}
return this.QueryInterface(iid);
},
lockFactory: function sniffer_lockf(lock) {
throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED);
},
getMIMETypeFromContent(request, data, length) {
return sniffedType;

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

@ -26,9 +26,6 @@ var eventsink = {
}
return this.QueryInterface(iid);
},
lockFactory: function eventsink_lockf(lock) {
throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED);
},
asyncOnChannelRedirect: function eventsink_onredir(
oldChan,

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

@ -38,7 +38,6 @@ TestProtocolHandlerFactory.prototype = {
createInstance(delegate, iid) {
return new TestProtocolHandler().QueryInterface(iid);
},
lockFactory(lock) {},
};
function register_test_protocol_handler() {

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

@ -52,7 +52,6 @@ TestProtocolHandlerFactory.prototype = {
createInstance(delegate, iid) {
return new TestProtocolHandler().QueryInterface(iid);
},
lockFactory(lock) {},
};
function register_test_protocol_handler() {

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

@ -10,7 +10,7 @@ import re
import subprocess
import sys
from distutils.version import StrictVersion as Version
from packaging.version import Version
import buildconfig
from mozbuild.action.util import log_build_task

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

@ -21,7 +21,7 @@ import attr
import psutil
import requests
from distutils.version import LooseVersion
from packaging.version import Version
import mozpack.path as mozpath
@ -35,7 +35,7 @@ MEMORY_THRESHOLD = 7.4
FREESPACE_THRESHOLD = 10
# Latest MozillaBuild version.
LATEST_MOZILLABUILD_VERSION = LooseVersion("3.3")
LATEST_MOZILLABUILD_VERSION = Version("4.0")
DISABLE_LASTACCESS_WIN = """
Disable the last access time feature?
@ -436,7 +436,7 @@ def mozillabuild(**kwargs) -> DoctorCheck:
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
desc = "MozillaBuild %s in use, <%s" % (
local_version,

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

@ -6,7 +6,7 @@ from __future__ import absolute_import, print_function, unicode_literals
import re
import six
from distutils.version import LooseVersion
from packaging.version import Version
from mozpack.errors import errors
from collections import OrderedDict
@ -160,14 +160,14 @@ class VersionFlag(object):
assert definition.startswith(self.name)
value = definition[len(self.name) :]
if value.startswith("="):
self.values.append(("==", LooseVersion(value[1:])))
self.values.append(("==", Version(value[1:])))
elif len(value) > 1 and value[0] in ["<", ">"]:
if value[1] == "=":
if len(value) < 3:
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:
self.values.append((value[0], LooseVersion(value[1:])))
self.values.append((value[0], Version(value[1:])))
else:
return errors.fatal("Malformed flag: %s" % definition)
@ -189,7 +189,7 @@ class VersionFlag(object):
flag.matches('1.0') returns True
flag.matches('0.6') returns False
"""
value = LooseVersion(value)
value = Version(value)
if not self.values:
return True
for comparison, val in self.values:

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

@ -311,8 +311,8 @@ sccache:
fetch:
type: git
repo: https://github.com/mozilla/sccache
# 0.2.15 release
revision: 6b6d2f7d2dceefeb4f583712aa4c221db62be0bd
# 0.3.0 release
revision: f6e36e68c6cb1e17127273956df9ebf2127dff1a
fxc2:
description: fxc2 source code

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

@ -68,9 +68,6 @@ var MockRegistrar = Object.freeze({
return wrappedMock.QueryInterface(iid);
},
lockFactory(lock) {
throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED);
},
QueryInterface: ChromeUtils.generateQI(["nsIFactory"]),
};

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

@ -12,7 +12,7 @@ from commit messages.
from __future__ import absolute_import, print_function
from collections.abc import Iterable
from distutils.version import StrictVersion
from packaging.version import Version
import argparse
import os
import subprocess
@ -48,12 +48,12 @@ def changelog(args):
for line in diff.splitlines():
if line.startswith("-PACKAGE_VERSION"):
try:
minus_version = StrictVersion(line.split()[-1].strip("\"'"))
minus_version = Version(line.split()[-1].strip("\"'"))
except ValueError:
pass
elif line.startswith("+PACKAGE_VERSION"):
try:
plus_version = StrictVersion(line.split()[-1].strip("\"'"))
plus_version = Version(line.split()[-1].strip("\"'"))
except ValueError:
break
@ -62,7 +62,7 @@ def changelog(args):
if not v:
return rev
if StrictVersion(v) == plus_version:
if Version(v) == plus_version:
return rev
print(

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

@ -3,7 +3,7 @@
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
from __future__ import absolute_import, division
from distutils.version import LooseVersion
from packaging.version import Version
import json
import math
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 text (i.e. 'esr') is in the last part
class CompareVersion(LooseVersion):
class CompareVersion(Version):
version = ""
def __init__(self, versionMap):
@ -38,7 +38,7 @@ class CompareVersion(LooseVersion):
else:
parts.append("0")
self.version = ".".join(parts)
LooseVersion(versionMap)
Version(versionMap)
def is_triangualar(x):
@ -549,9 +549,9 @@ class UpdateVerifyConfigCreator(BaseScript):
# Use new build targets for Windows, but only on compatible
# 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
) < LooseVersion("42.0"):
) < Version("42.0"):
update_platform = ftp2updatePlatforms(self.config["platform"])[0]
else:
update_platform = ftp2updatePlatforms(self.config["platform"])[1]

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

@ -32,9 +32,6 @@ var newFactory = function(window) {
}
return new MockColorPickerInstance(window).QueryInterface(aIID);
},
lockFactory(aLock) {
throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED);
},
QueryInterface: ChromeUtils.generateQI(["nsIFactory"]),
};
};

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

@ -37,9 +37,6 @@ var newFactory = function(window) {
}
return new MockFilePickerInstance(window).QueryInterface(aIID);
},
lockFactory(aLock) {
throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED);
},
QueryInterface: ChromeUtils.generateQI(["nsIFactory"]),
};
};

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

@ -20,9 +20,6 @@ var newFactory = {
}
return new MockPermissionPromptInstance().QueryInterface(aIID);
},
lockFactory(aLock) {
throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED);
},
QueryInterface: ChromeUtils.generateQI(["nsIFactory"]),
};

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

@ -308,9 +308,6 @@ var _fakeIdleService = {
}
return _fakeIdleService.QueryInterface(aIID);
},
lockFactory(aLock) {
throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED);
},
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.
# It is not intended for manual editing.
version = 3
[[package]]
name = "autocfg"
version = "1.0.1"
version = "1.1.0"

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