Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Sign in
Toggle navigation
F
Front-End
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
abdullh.alsoleman
Front-End
Commits
17eb2e8a
Unverified
Commit
17eb2e8a
authored
Jan 30, 2023
by
Justin McCandless
Committed by
GitHub
Jan 30, 2023
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Ability to disable the browser's context menu on web (#118194)
Enables custom context menus on web
parent
530c3f2d
Changes
9
Hide whitespace changes
Inline
Side-by-side
Showing
9 changed files
with
272 additions
and
36 deletions
+272
-36
services.dart
packages/flutter/lib/services.dart
+1
-0
text_selection.dart
packages/flutter/lib/src/material/text_selection.dart
+1
-1
browser_context_menu.dart
packages/flutter/lib/src/services/browser_context_menu.dart
+83
-0
system_channels.dart
packages/flutter/lib/src/services/system_channels.dart
+13
-0
editable_text.dart
packages/flutter/lib/src/widgets/editable_text.dart
+18
-18
text_field_test.dart
packages/flutter/test/material/text_field_test.dart
+3
-9
browser_context_menu_test.dart
...ages/flutter/test/services/browser_context_menu_test.dart
+82
-0
text_input_test.dart
packages/flutter/test/services/text_input_test.dart
+0
-1
editable_text_test.dart
packages/flutter/test/widgets/editable_text_test.dart
+71
-7
No files found.
packages/flutter/lib/services.dart
View file @
17eb2e8a
...
...
@@ -14,6 +14,7 @@ export 'src/services/asset_bundle.dart';
export
'src/services/autofill.dart'
;
export
'src/services/binary_messenger.dart'
;
export
'src/services/binding.dart'
;
export
'src/services/browser_context_menu.dart'
;
export
'src/services/clipboard.dart'
;
export
'src/services/debug.dart'
;
export
'src/services/deferred_component.dart'
;
...
...
packages/flutter/lib/src/material/text_selection.dart
View file @
17eb2e8a
...
...
@@ -55,7 +55,7 @@ class MaterialTextSelectionControls extends TextSelectionControls {
ClipboardStatusNotifier
?
clipboardStatus
,
Offset
?
lastSecondaryTapDownPosition
,
)
{
return
_TextSelectionControlsToolbar
(
return
_TextSelectionControlsToolbar
(
globalEditableRegion:
globalEditableRegion
,
textLineHeight:
textLineHeight
,
selectionMidpoint:
selectionMidpoint
,
...
...
packages/flutter/lib/src/services/browser_context_menu.dart
0 → 100644
View file @
17eb2e8a
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import
'package:flutter/foundation.dart'
;
import
'system_channels.dart'
;
/// Controls the browser's context menu on the web platform.
///
/// The context menu is the menu that appears on right clicking or selecting
/// text in the browser, for example.
///
/// On web, by default, the browser's context menu is enabled and Flutter's
/// context menus are hidden.
///
/// On all non-web platforms, this does nothing.
class
BrowserContextMenu
{
BrowserContextMenu
.
_
();
static
final
BrowserContextMenu
_instance
=
BrowserContextMenu
.
_
();
/// Whether showing the browser's context menu is enabled.
///
/// When true, any event that the browser typically uses to trigger its
/// context menu (e.g. right click) will do so. When false, the browser's
/// context menu will not show.
///
/// It's possible for this to be true but for the browser's context menu to
/// not show due to direct manipulation of the DOM. For example, handlers for
/// the browser's `contextmenu` event could be added/removed in the browser's
/// JavaScript console, and this boolean wouldn't know about it. This boolean
/// only indicates the results of calling [disableContextMenu] and
/// [enableContextMenu] here.
///
/// Defaults to true.
static
bool
get
enabled
=>
_instance
.
_enabled
;
bool
_enabled
=
true
;
final
MethodChannel
_channel
=
SystemChannels
.
contextMenu
;
/// Disable the browser's context menu.
///
/// By default, when the app starts, the browser's context menu is already
/// enabled.
///
/// This is an asynchronous action. The context menu can be considered to be
/// disabled at the time that the Future resolves. [enabled] won't reflect the
/// change until that time.
///
/// See also:
/// * [enableContextMenu], which performs the opposite operation.
static
Future
<
void
>
disableContextMenu
()
{
assert
(
kIsWeb
,
'This has no effect on platforms other than web.'
);
return
_instance
.
_channel
.
invokeMethod
<
void
>(
'disableContextMenu'
,
).
then
((
_
)
{
_instance
.
_enabled
=
false
;
});
}
/// Enable the browser's context menu.
///
/// By default, when the app starts, the browser's context menu is already
/// enabled. Typically this method would be called after first calling
/// [disableContextMenu].
///
/// This is an asynchronous action. The context menu can be considered to be
/// enabled at the time that the Future resolves. [enabled] won't reflect the
/// change until that time.
///
/// See also:
/// * [disableContextMenu], which performs the opposite operation.
static
Future
<
void
>
enableContextMenu
()
{
assert
(
kIsWeb
,
'This has no effect on platforms other than web.'
);
return
_instance
.
_channel
.
invokeMethod
<
void
>(
'enableContextMenu'
,
).
then
((
_
)
{
_instance
.
_enabled
=
true
;
});
}
}
packages/flutter/lib/src/services/system_channels.dart
View file @
17eb2e8a
...
...
@@ -465,4 +465,17 @@ class SystemChannels {
///
/// * [DefaultPlatformMenuDelegate], which uses this channel.
static
const
MethodChannel
menu
=
OptionalMethodChannel
(
'flutter/menu'
);
/// A [MethodChannel] for configuring the browser's context menu on web.
///
/// The following outgoing methods are defined for this channel (invoked using
/// [OptionalMethodChannel.invokeMethod]):
///
/// * `enableContextMenu`: enables the browser's context menu. When a Flutter
/// app starts, the browser's context menu is already enabled.
/// * `disableContextMenu`: disables the browser's context menu.
static
const
MethodChannel
contextMenu
=
OptionalMethodChannel
(
'flutter/contextmenu'
,
JSONMethodCodec
(),
);
}
packages/flutter/lib/src/widgets/editable_text.dart
View file @
17eb2e8a
...
...
@@ -1893,7 +1893,7 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
final
GlobalKey
_editableKey
=
GlobalKey
();
/// Detects whether the clipboard can paste.
final
ClipboardStatusNotifier
?
clipboardStatus
=
kIsWeb
?
null
:
ClipboardStatusNotifier
();
final
ClipboardStatusNotifier
clipboardStatus
=
ClipboardStatusNotifier
();
TextInputConnection
?
_textInputConnection
;
bool
get
_hasInputConnection
=>
_textInputConnection
?.
attached
??
false
;
...
...
@@ -1996,8 +1996,7 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
return
widget
.
toolbarOptions
.
paste
&&
!
widget
.
readOnly
;
}
return
!
widget
.
readOnly
&&
(
clipboardStatus
==
null
||
clipboardStatus
!.
value
==
ClipboardStatus
.
pasteable
);
&&
(
clipboardStatus
.
value
==
ClipboardStatus
.
pasteable
);
}
@override
...
...
@@ -2074,7 +2073,7 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
break
;
}
}
clipboardStatus
?
.
update
();
clipboardStatus
.
update
();
}
/// Cut current selection to [Clipboard].
...
...
@@ -2099,7 +2098,7 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
});
hideToolbar
();
}
clipboardStatus
?
.
update
();
clipboardStatus
.
update
();
}
/// Paste text from [Clipboard].
...
...
@@ -2285,7 +2284,7 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
},
type:
ContextMenuButtonType
.
copy
,
),
if
(
toolbarOptions
.
paste
&&
clipboardStatus
!=
null
&&
pasteEnabled
)
if
(
toolbarOptions
.
paste
&&
pasteEnabled
)
ContextMenuButtonItem
(
onPressed:
()
{
pasteText
(
SelectionChangedCause
.
toolbar
);
...
...
@@ -2386,7 +2385,7 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
/// button Widgets for the current platform given [ContextMenuButtonItem]s.
List
<
ContextMenuButtonItem
>
get
contextMenuButtonItems
{
return
buttonItemsForToolbarOptions
()
??
EditableText
.
getEditableButtonItems
(
clipboardStatus:
clipboardStatus
?
.
value
,
clipboardStatus:
clipboardStatus
.
value
,
onCopy:
copyEnabled
?
()
=>
copySelection
(
SelectionChangedCause
.
toolbar
)
:
null
,
...
...
@@ -2407,7 +2406,7 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
@override
void
initState
()
{
super
.
initState
();
clipboardStatus
?
.
addListener
(
_onChangedClipboardStatus
);
clipboardStatus
.
addListener
(
_onChangedClipboardStatus
);
widget
.
controller
.
addListener
(
_didChangeTextEditingValue
);
widget
.
focusNode
.
addListener
(
_handleFocusChanged
);
_scrollController
.
addListener
(
_onEditableScroll
);
...
...
@@ -2531,8 +2530,8 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
final
bool
canPaste
=
widget
.
selectionControls
is
TextSelectionHandleControls
?
pasteEnabled
:
widget
.
selectionControls
?.
canPaste
(
this
)
??
false
;
if
(
widget
.
selectionEnabled
&&
pasteEnabled
&&
c
lipboardStatus
!=
null
&&
c
anPaste
)
{
clipboardStatus
!
.
update
();
if
(
widget
.
selectionEnabled
&&
pasteEnabled
&&
canPaste
)
{
clipboardStatus
.
update
();
}
}
...
...
@@ -2553,8 +2552,8 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
_selectionOverlay
=
null
;
widget
.
focusNode
.
removeListener
(
_handleFocusChanged
);
WidgetsBinding
.
instance
.
removeObserver
(
this
);
clipboardStatus
?
.
removeListener
(
_onChangedClipboardStatus
);
clipboardStatus
?
.
dispose
();
clipboardStatus
.
removeListener
(
_onChangedClipboardStatus
);
clipboardStatus
.
dispose
();
_cursorVisibilityNotifier
.
dispose
();
super
.
dispose
();
assert
(
_batchEditDepth
<=
0
,
'unfinished batch edits:
$_batchEditDepth
'
);
...
...
@@ -3688,17 +3687,18 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
@override
bool
showToolbar
()
{
// Web is using native dom elements to enable clipboard functionality of the
// toolbar: copy, paste, select, cut. It might also provide additional
// functionality depending on the browser (such as translate). Due to this
// we should not show a Flutter toolbar for the editable text elements.
if
(
kIsWeb
)
{
// context menu: copy, paste, select, cut. It might also provide additional
// functionality depending on the browser (such as translate). Due to this,
// we should not show a Flutter toolbar for the editable text elements
// unless the browser's context menu is explicitly disabled.
if
(
kIsWeb
&&
BrowserContextMenu
.
enabled
)
{
return
false
;
}
if
(
_selectionOverlay
==
null
)
{
return
false
;
}
clipboardStatus
?
.
update
();
clipboardStatus
.
update
();
_selectionOverlay
!.
showToolbar
();
return
true
;
}
...
...
@@ -3912,7 +3912,7 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
&&
(
widget
.
selectionControls
is
TextSelectionHandleControls
?
pasteEnabled
:
pasteEnabled
&&
(
widget
.
selectionControls
?.
canPaste
(
this
)
??
false
))
&&
(
clipboardStatus
==
null
||
clipboardStatus
!
.
value
==
ClipboardStatus
.
pasteable
)
&&
(
clipboardStatus
.
value
==
ClipboardStatus
.
pasteable
)
?
()
{
controls
?.
handlePaste
(
this
);
pasteText
(
SelectionChangedCause
.
toolbar
);
...
...
packages/flutter/test/material/text_field_test.dart
View file @
17eb2e8a
...
...
@@ -11914,7 +11914,7 @@ void main() {
},
);
testWidgets
(
'
Web does not check the clipboard statu
s'
,
(
WidgetTester
tester
)
async
{
testWidgets
(
'
clipboard status is checked via hasStrings without getting the full clipboard content
s'
,
(
WidgetTester
tester
)
async
{
final
TextEditingController
controller
=
TextEditingController
(
text:
'Atwater Peel Sherbrooke Bonaventure'
,
);
...
...
@@ -11958,14 +11958,8 @@ void main() {
// getData is not called unless something is pasted. hasStrings is used to
// check the status of the clipboard.
expect
(
calledGetData
,
false
);
if
(
kIsWeb
)
{
// hasStrings is not checked because web doesn't show a custom text
// selection menu.
expect
(
calledHasStrings
,
false
);
}
else
{
// hasStrings is checked in order to decide if the content can be pasted.
expect
(
calledHasStrings
,
true
);
}
// hasStrings is checked in order to decide if the content can be pasted.
expect
(
calledHasStrings
,
true
);
});
testWidgets
(
'TextField changes mouse cursor when hovered'
,
(
WidgetTester
tester
)
async
{
...
...
packages/flutter/test/services/browser_context_menu_test.dart
0 → 100644
View file @
17eb2e8a
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import
'package:flutter/foundation.dart'
;
import
'package:flutter/services.dart'
;
import
'package:flutter_test/flutter_test.dart'
;
void
main
(
)
{
TestWidgetsFlutterBinding
.
ensureInitialized
();
final
List
<
MethodCall
>
log
=
<
MethodCall
>[];
Future
<
void
>
verify
(
AsyncCallback
test
,
List
<
Object
>
expectations
)
async
{
log
.
clear
();
await
test
();
expect
(
log
,
expectations
);
}
group
(
'not on web'
,
()
{
test
(
'disableContextMenu asserts'
,
()
async
{
try
{
BrowserContextMenu
.
disableContextMenu
();
}
catch
(
error
)
{
expect
(
error
,
isAssertionError
);
}
});
test
(
'enableContextMenu asserts'
,
()
async
{
try
{
BrowserContextMenu
.
enableContextMenu
();
}
catch
(
error
)
{
expect
(
error
,
isAssertionError
);
}
});
},
skip:
kIsWeb
,
// [intended]
);
group
(
'on web'
,
()
{
group
(
'disableContextMenu'
,
()
{
// Make sure the context menu is enabled (default) after the test.
tearDown
(()
async
{
TestDefaultBinaryMessengerBinding
.
instance
!.
defaultBinaryMessenger
.
setMockMethodCallHandler
(
SystemChannels
.
contextMenu
,
(
MethodCall
methodCall
)
{
return
null
;
});
await
BrowserContextMenu
.
enableContextMenu
();
TestDefaultBinaryMessengerBinding
.
instance
!.
defaultBinaryMessenger
.
setMockMethodCallHandler
(
SystemChannels
.
contextMenu
,
null
);
});
test
(
'disableContextMenu calls its platform channel method'
,
()
async
{
TestDefaultBinaryMessengerBinding
.
instance
!.
defaultBinaryMessenger
.
setMockMethodCallHandler
(
SystemChannels
.
contextMenu
,
(
MethodCall
methodCall
)
async
{
log
.
add
(
methodCall
);
return
null
;
});
await
verify
(
BrowserContextMenu
.
disableContextMenu
,
<
Object
>[
isMethodCall
(
'disableContextMenu'
,
arguments:
null
),
]);
TestDefaultBinaryMessengerBinding
.
instance
!.
defaultBinaryMessenger
.
setMockMethodCallHandler
(
SystemChannels
.
contextMenu
,
null
);
});
});
group
(
'enableContextMenu'
,
()
{
test
(
'enableContextMenu calls its platform channel method'
,
()
async
{
TestDefaultBinaryMessengerBinding
.
instance
!.
defaultBinaryMessenger
.
setMockMethodCallHandler
(
SystemChannels
.
contextMenu
,
(
MethodCall
methodCall
)
async
{
log
.
add
(
methodCall
);
return
null
;
});
await
verify
(
BrowserContextMenu
.
enableContextMenu
,
<
Object
>[
isMethodCall
(
'enableContextMenu'
,
arguments:
null
),
]);
TestDefaultBinaryMessengerBinding
.
instance
!.
defaultBinaryMessenger
.
setMockMethodCallHandler
(
SystemChannels
.
contextMenu
,
null
);
});
});
},
skip:
!
kIsWeb
,
// [intended]
);
}
packages/flutter/test/services/text_input_test.dart
View file @
17eb2e8a
...
...
@@ -2,7 +2,6 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import
'dart:convert'
show
jsonDecode
;
import
'package:flutter/foundation.dart'
;
...
...
packages/flutter/test/widgets/editable_text_test.dart
View file @
17eb2e8a
...
...
@@ -1488,13 +1488,14 @@ void main() {
expect
(
tester
.
takeException
(),
isNull
);
});
/// Toolbar is not used in Flutter Web. Skip this check.
///
/// Web is using native DOM elements (it is also used as platform input)
/// to enable clipboard functionality of the toolbar: copy, paste, select,
/// cut. It might also provide additional functionality depending on the
/// browser (such as translation). Due to this, in browsers, we should not
/// show a Flutter toolbar for the editable text elements.
// Toolbar is not used in Flutter Web unless the browser context menu is
// explicitly disabled. Skip this check.
//
// Web is using native DOM elements (it is also used as platform input)
// to enable clipboard functionality of the toolbar: copy, paste, select,
// cut. It might also provide additional functionality depending on the
// browser (such as translation). Due to this, in browsers, we should not
// show a Flutter toolbar for the editable text elements.
testWidgets
(
'can show toolbar when there is text and a selection'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
MaterialApp
(
...
...
@@ -1542,6 +1543,69 @@ void main() {
expect
(
find
.
text
(
'Paste'
),
kIsWeb
?
findsNothing
:
findsOneWidget
);
});
group
(
'BrowserContextMenu'
,
()
{
setUp
(()
async
{
SystemChannels
.
contextMenu
.
setMockMethodCallHandler
((
MethodCall
call
)
{
// Just complete successfully, so that BrowserContextMenu thinks that
// the engine successfully received its call.
return
Future
<
void
>.
value
();
});
await
BrowserContextMenu
.
disableContextMenu
();
});
tearDown
(()
async
{
await
BrowserContextMenu
.
enableContextMenu
();
SystemChannels
.
contextMenu
.
setMockMethodCallHandler
(
null
);
});
testWidgets
(
'web can show toolbar when the browser context menu is disabled'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
MaterialApp
(
home:
EditableText
(
backgroundCursorColor:
Colors
.
grey
,
controller:
controller
,
focusNode:
focusNode
,
style:
textStyle
,
cursorColor:
cursorColor
,
selectionControls:
materialTextSelectionControls
,
),
),
);
final
EditableTextState
state
=
tester
.
state
<
EditableTextState
>(
find
.
byType
(
EditableText
));
// Can't show the toolbar when there's no focus.
expect
(
state
.
showToolbar
(),
false
);
await
tester
.
pumpAndSettle
();
expect
(
find
.
text
(
'Paste'
),
findsNothing
);
// Can show the toolbar when focused even though there's no text.
state
.
renderEditable
.
selectWordsInRange
(
from:
Offset
.
zero
,
cause:
SelectionChangedCause
.
tap
,
);
await
tester
.
pump
();
expect
(
state
.
showToolbar
(),
isTrue
);
await
tester
.
pumpAndSettle
();
expect
(
find
.
text
(
'Paste'
),
findsOneWidget
);
// Hide the menu again.
state
.
hideToolbar
();
await
tester
.
pump
();
expect
(
find
.
text
(
'Paste'
),
findsNothing
);
// Can show the menu with text and a selection.
controller
.
text
=
'blah'
;
await
tester
.
pump
();
expect
(
state
.
showToolbar
(),
isTrue
);
await
tester
.
pumpAndSettle
();
expect
(
find
.
text
(
'Paste'
),
findsOneWidget
);
},
skip:
!
kIsWeb
,
// [intended]
);
});
testWidgets
(
'can hide toolbar with DismissIntent'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
MaterialApp
(
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment