• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Using Web Menus
2Menus are key components for user interaction. They help build a clear navigation system and display feature entries in a structured layout, enabling users to quickly find target content or perform operations. As an important hub for human-machine interaction, menus significantly improve the accessibility and user experience of **Web** components and are an indispensable part of application design. The following table lists the menu types of **Web** components. You can select a menu type for your application as required.
3|Menu Type|Target Element|Response Conditions|Customization Support|
4|----|----|----|----|
5|[Text Selection Menu](./web_menu.md#text-selection-menu)|Text|Long press|The menu items can be added or deleted while the menu style cannot be customized.|
6|[Context Menu](./web_menu.md#context-menu)|Hyperlink, image, and text|Long press and right-click|Customization can be implemented using menu components.|
7|[Custom Menu](./web_menu.md#custom-menu)|Image|Long press|Customization can be implemented using menu components.|
8## Text Selection Menu
9The text selection menu of the **Web** component is a context interaction component implemented by custom elements. It is dynamically displayed when a user selects text and provides operations such as copy, share, and mark. It has standard features and scalability and is the core feature of text operations on mobile devices. The text selection menu is displayed when a user long-presses the selected text or long-presses the single handle in the editing state. The menu items are horizontally arranged. The system provides the default menu implementation. You can use the [editMenuOptions](../reference/apis-arkweb/arkts-basic-components-web-attributes.md#editmenuoptions12) API to customize the text selection menu.
101. Use the **onCreateMenu** method to customize menu items. Operate **Array<[TextMenuItem](../reference/apis-arkui/arkui-ts/ts-basic-components-menuitem.md)>** to add or delete menu items. Define the menu item name, icon, and ID in [TextMenuItem](../reference/apis-arkui/arkui-ts/ts-basic-components-menuitem.md).
112. Use the **onMenuItemClick** method to process the menu item click event. When false is returned, the default system logic is executed.
123. Create an [EditMenuOptions](../reference/apis-arkui/arkui-ts/ts-text-common.md#editmenuoptions) object, which contains the **onCreateMenu** and **onMenuItemClick** methods, and bind the object to the **Web** component using the [editMenuOptions](../reference/apis-arkweb/arkts-basic-components-web-attributes.md#editmenuoptions12) method of the **Web** component.
13
14  ```ts
15  // xxx.ets
16  import { webview } from '@kit.ArkWeb';
17  @Entry
18  @Component
19  struct WebComponent {
20    controller: webview.WebviewController = new webview.WebviewController();
21
22    onCreateMenu(menuItems: Array<TextMenuItem>): Array<TextMenuItem> {
23      let items = menuItems.filter((menuItem) => {
24        // Filter the menu items as required.
25        return (
26          menuItem.id.equals(TextMenuItemId.CUT) ||
27          menuItem.id.equals(TextMenuItemId.COPY) ||
28          menuItem.id.equals((TextMenuItemId.PASTE))
29        )
30      });
31      let customItem1: TextMenuItem = {
32        content: 'customItem1',
33        id: TextMenuItemId.of('customItem1'),
34        icon: $r('app.media.startIcon')
35      };
36      let customItem2: TextMenuItem = {
37        content: $r('app.string.EntryAbility_label'),
38        id: TextMenuItemId.of('customItem2'),
39        icon: $r('app.media.startIcon')
40      };
41      items.push(customItem1);// Add an item to the end of the item list.
42      items.unshift(customItem2);// Add an item to the beginning of the item list.
43      items.push(customItem1);
44      items.push(customItem1);
45      items.push(customItem1);
46      items.push(customItem1);
47      items.push(customItem1);
48      return items;
49    }
50
51    onMenuItemClick(menuItem: TextMenuItem, textRange: TextRange): boolean {
52      if (menuItem.id.equals(TextMenuItemId.CUT)) {
53        // Custom behavior
54        console.log("Intercept ID: CUT")
55        return true; // Return true to not execute the system callback.
56      } else if (menuItem.id.equals(TextMenuItemId.COPY)) {
57        // Custom behavior
58        console.log("Not intercept ID: COPY")
59        return false; // Return false to execute the system callback.
60      } else if (menuItem.id.equals(TextMenuItemId.of('customItem1'))) {
61        // Custom behavior
62        console.log("Intercept ID: customItem1")
63        return true;// Custom menu item. If true is returned, the menu is not closed after being clicked. If false is returned, the menu is closed.
64      } else if (menuItem.id.equals((TextMenuItemId.of($r('app.string.EntryAbility_label'))))){
65        // Custom behavior
66        console.log("Intercept ID: app.string.customItem2")
67        return true;
68      }
69      return false;// Return the default value false.
70    }
71
72    @State EditMenuOptions: EditMenuOptions = { onCreateMenu: this.onCreateMenu, onMenuItemClick: this.onMenuItemClick }
73
74    build() {
75      Column() {
76        Web({ src: $rawfile("index.html"), controller: this.controller })
77          .editMenuOptions(this.EditMenuOptions)
78      }
79    }
80  }
81  ```
82
83  ```html
84  <!--index.html-->
85  <!DOCTYPE html>
86  <html>
87    <head>
88        <title>Test Web Page</title>
89    </head>
90    <body>
91      <h1>editMenuOptions Demo</h1>
92      <span>edit menu options</span>
93    </body>
94  </html>
95  ```
96
97## Context Menu
98A context menu is a shortcut menu triggered by a specific operation (such as right-clicking or holding down the rich text) to provide feature items related to the current operation object or UI element. Items in a context menu are arranged vertically. This menu is not provided by default. If it is not implemented in the application, the context menu is not displayed. To implement a context menu, the application needs to use the [Menu](../reference/apis-arkui/arkui-ts/ts-basic-components-menu.md#menu) component to create a menu child window and bind it to the **Web** component. When the menu is displayed, use the [onContextMenuShow](../reference/apis-arkweb/arkts-basic-components-web-events.md#oncontextmenushow9) API to obtain the context menu details, including the click location and HTML element information.
99
1001. The [Menu](../reference/apis-arkui/arkui-ts/ts-basic-components-menu.md#menu) component contains the behaviors and styles of all menu items.
1012. Use the **bindPopup** method to bind the **Menu** component to the **Web** component. When the context menu is displayed, the created **Menu** component is displayed.
1023. Obtain the context menu event information [onContextMenuShowEvent](../reference/apis-arkweb/arkts-basic-components-web-i.md#oncontextmenushowevent12) in the **onContextMenuShow** callback. The **param** parameter is of the [WebContextMenuParam](../reference/apis-arkweb/arkts-basic-components-web-WebContextMenuParam.md) type, which contains the HTML element information and location information corresponding to the click position. The **result** parameter is of the [WebContextMenuResult](../reference/apis-arkweb/arkts-basic-components-web-WebContextMenuResult.md) type, which provides common menu capabilities.
103
104```ts
105// xxx.ets
106import { webview } from '@kit.ArkWeb';
107import { pasteboard } from '@kit.BasicServicesKit';
108
109const TAG = 'ContextMenu';
110
111@Entry
112@Component
113struct WebComponent {
114  controller: webview.WebviewController = new webview.WebviewController();
115  private result: WebContextMenuResult | undefined = undefined;
116  @State linkUrl: string = '';
117  @State offsetX: number = 0;
118  @State offsetY: number = 0;
119  @State showMenu: boolean = false;
120  uiContext: UIContext = this.getUIContext();
121
122  @Builder
123  // Build and trigger a custom menu.
124  MenuBuilder() {
125    // A component that is used to present a vertical list of items to the user.
126    Menu() {
127      // A component that is used to represent an item in a menu.
128      MenuItem({
129        content: 'Copy Image',
130      })
131        .width(100)
132        .height(50)
133        .onClick(() => {
134          this.result?.copyImage();
135          this.showMenu = false;
136        })
137      MenuItem({
138        content: 'Cut',
139      })
140        .width(100)
141        .height(50)
142        .onClick(() => {
143          this.result?.cut();
144          this.showMenu = false;
145        })
146      MenuItem({
147        content: 'Copy',
148      })
149        .width(100)
150        .height(50)
151        .onClick(() => {
152          this.result?.copy();
153          this.showMenu = false;
154        })
155      MenuItem({
156        content: 'Paste',
157      })
158        .width(100)
159        .height(50)
160        .onClick(() => {
161          this.result?.paste();
162          this.showMenu = false;
163        })
164      MenuItem({
165        content: 'Copy Link',
166      })
167        .width(100)
168        .height(50)
169        .onClick(() => {
170          let pasteData = pasteboard.createData('text/plain', this.linkUrl);
171          pasteboard.getSystemPasteboard().setData(pasteData, (error) => {
172            if (error) {
173              return;
174            }
175          })
176          this.showMenu = false;
177        })
178      MenuItem({
179        content: 'Select All',
180      })
181        .width(100)
182        .height(50)
183        .onClick(() => {
184          this.result?.selectAll();
185          this.showMenu = false;
186        })
187    }
188    .width(150)
189    .height(300)
190  }
191
192  build() {
193    Column() {
194      Web({ src: $rawfile("index.html"), controller: this.controller })
195        // Trigger a custom dialog box.
196        .onContextMenuShow((event) => {
197          if (event) {
198            this.result = event.result
199            console.info("x coord = " + event.param.x());
200            console.info("link url = " + event.param.getLinkUrl());
201            this.linkUrl = event.param.getLinkUrl();
202          }
203          console.info(TAG, `x: ${this.offsetX}, y: ${this.offsetY}`);
204          this.showMenu = true;
205          this.offsetX = 0;
206          this.offsetY = Math.max(this.uiContext!.px2vp(event?.param.y() ?? 0) - 0, 0);
207          return true;
208        })
209        .bindPopup(this.showMenu,
210          {
211            builder: this.MenuBuilder(),
212            enableArrow: false,
213            placement: Placement.LeftTop,
214            offset: { x: this.offsetX, y: this.offsetY },
215            mask: false,
216            onStateChange: (e) => {
217              if (!e.isVisible) {
218                this.showMenu = false;
219                this.result!.closeContextMenu();
220              }
221            }
222          })
223    }
224  }
225}
226```
227```html
228<!-- index.html -->
229<!DOCTYPE html>
230<html lang="en">
231<body>
232  <h1>onContextMenuShow</h1>
233  <a href="http://www.example.com" style="font-size:27px">Hyperlink www.example.com</a>
234  <div><img src="example.png"></div>
235  <p>Right-click text to display the context menu</p>
236</body>
237</html>
238```
239
240## Custom Menu
241Custom menus enable you to adjust menu triggering timing and visual display, so that your application can dynamically match feature entries based on user operation scenarios. This simplifies UI adaptation in the development process and makes application interaction more intuitive. Custom menus allow an application to display a custom menu by calling [bindSelectionMenu](../reference/apis-arkweb/arkts-basic-components-web-attributes.md#bindselectionmenu13) based on the event type and element type. Currently, the long-press event of an image is supported.
2421. Create a [Menu](../reference/apis-arkui/arkui-ts/ts-basic-components-menu.md#menu) component as the menu pop-up window.
2432. Use the [bindSelectionMenu](../reference/apis-arkweb/arkts-basic-components-web-attributes.md#bindselectionmenu13) method of the **Web** component to bind the **MenuBuilder** pop-up window. Set [WebElementType](../reference/apis-arkweb/arkts-basic-components-web-e.md#webelementtype13) to **WebElementType.IMAGE** and [responseType](../reference/apis-arkweb/arkts-basic-components-web-e.md#webresponsetype13) to **WebResponseType.LONG_PRESS**, so that the menu is displayed when the image is pressed for a long time. Define **onAppear**, **onDisappear**, **preview**, and **menuType** in [options](../reference/apis-arkweb/arkts-basic-components-web-i.md#selectionmenuoptionsext13).
244```ts
245// xxx.ets
246import { webview } from '@kit.ArkWeb';
247
248interface PreviewBuilderParam {
249  previewImage: Resource | string | undefined;
250  width: number;
251  height: number;
252}
253
254@Builder function PreviewBuilderGlobal($$: PreviewBuilderParam) {
255  Column() {
256    Image($$.previewImage)
257      .objectFit(ImageFit.Fill)
258      .autoResize(true)
259  }.width($$.width).height($$.height)
260}
261
262@Entry
263@Component
264struct WebComponent {
265  controller: webview.WebviewController = new webview.WebviewController();
266
267  private result: WebContextMenuResult | undefined = undefined;
268  @State previewImage: Resource | string | undefined = undefined;
269  @State previewWidth: number = 0;
270  @State previewHeight: number = 0;
271  uiContext: UIContext = this.getUIContext();
272
273  @Builder
274  MenuBuilder() {
275    Menu() {
276      MenuItem({content:'Copy',})
277        .onClick(() => {
278          this.result?.copy();
279          this.result?.closeContextMenu();
280        })
281      MenuItem({content:'Select All',})
282        .onClick(() => {
283          this.result?.selectAll();
284          this.result?.closeContextMenu();
285        })
286    }
287  }
288  build() {
289    Column() {
290      Web({ src: $rawfile("index.html"), controller: this.controller })
291        .bindSelectionMenu(WebElementType.IMAGE, this.MenuBuilder, WebResponseType.LONG_PRESS,
292          {
293            onAppear: () => {},
294            onDisappear: () => {
295              this.result?.closeContextMenu();
296            },
297            preview: PreviewBuilderGlobal({
298              previewImage: this.previewImage,
299              width: this.previewWidth,
300              height: this.previewHeight
301            }),
302            menuType: MenuType.PREVIEW_MENU
303          })
304        .onContextMenuShow((event) => {
305            if (event) {
306              this.result = event.result;
307              if (event.param.getLinkUrl()) {
308                return false;
309              }
310              this.previewWidth = this.uiContext!.px2vp(event.param.getPreviewWidth());
311              this.previewHeight = this.uiContext!.px2vp(event.param.getPreviewHeight());
312              if (event.param.getSourceUrl().indexOf("resource://rawfile/") == 0) {
313                this.previewImage = $rawfile(event.param.getSourceUrl().substr(19));
314              } else {
315                this.previewImage = event.param.getSourceUrl();
316              }
317              return true;
318            }
319            return false;
320          })
321    }
322  }
323}
324```
325```html
326<!--index.html-->
327<!DOCTYPE html>
328<html>
329  <head>
330      <title>Test Web Page</title>
331  </head>
332  <body>
333    <h1>bindSelectionMenu Demo</h1>
334    <img src="./img.png" >
335  </body>
336</html>
337```
338
339
340Since API version 20, the hyperlink menu triggered by long-pressing can be bound. You can bind different custom menus to images and links.
341
342The following example uses **PreviewBuilder** to define the content of the pop-up menu corresponding to the hyperlink, loads the hyperlink content using the **Web** component, and displays the loading progress using [the Progress component](../ui/arkts-common-components-progress-indicator.md).
343
344```ts
345import { webview } from '@kit.ArkWeb';
346import { pasteboard } from '@kit.BasicServicesKit';
347
348interface PreviewBuilderParam {
349  width: number;
350  height: number;
351  url:Resource | string | undefined;
352}
353
354interface PreviewBuilderParamForImage {
355  previewImage: Resource | string | undefined;
356  width: number;
357  height: number;
358}
359
360
361@Builder function PreviewBuilderGlobalForImage($$: PreviewBuilderParamForImage) {
362  Column() {
363    Image($$.previewImage)
364      .objectFit(ImageFit.Fill)
365      .autoResize(true)
366  }.width($$.width).height($$.height)
367}
368
369@Entry
370@Component
371struct SelectionMenuLongPress {
372  controller: webview.WebviewController = new webview.WebviewController();
373  previewController: webview.WebviewController = new webview.WebviewController();
374  @Builder PreviewBuilder($$: PreviewBuilderParam){
375    Column() {
376      Stack(){
377        Text("") // Select whether to display the URL.
378          .padding(5)
379          .width('100%')
380          .textAlign(TextAlign.Start)
381          .backgroundColor(Color.White)
382          .copyOption(CopyOptions.LocalDevice)
383          .maxLines(1)
384          .textOverflow({overflow:TextOverflow.Ellipsis})
385        Progress({ value: this.progressValue, total: 100, type: ProgressType.Linear }) // Display the progress bar.
386          .style({ strokeWidth: 3, enableSmoothEffect: true })
387          .backgroundColor(Color.White)
388          .opacity(this.progressVisible?1:0)
389          .backgroundColor(Color.White)
390      }.alignContent(Alignment.Bottom)
391      Web({src:$$.url,controller: new webview.WebviewController()})
392        .javaScriptAccess(true)
393        .fileAccess(true)
394        .onlineImageAccess(true)
395        .imageAccess(true)
396        .domStorageAccess(true)
397        .onPageBegin(()=>{
398          this.progressValue = 0;
399          this.progressVisible = true;
400        })
401        .onProgressChange((event)=>{
402          this.progressValue = event.newProgress;
403        })
404        .onPageEnd(()=>{
405          this.progressVisible = false;
406        })
407        .hitTestBehavior(HitTestMode.None) // Disable the gesture response during web page preview.
408    }.width($$.width).height ($$.height) // Set the preview width and height.
409  }
410
411  private result: WebContextMenuResult | undefined = undefined;
412  @State previewImage: Resource | string | undefined = undefined;
413  @State previewWidth: number = 1;
414  @State previewHeight: number = 1;
415  @State previewWidthImage: number = 1;
416  @State previewHeightImage: number = 1;
417  @State linkURL:string = "";
418  @State progressValue:number = 0;
419  @State progressVisible:boolean = true;
420  uiContext: UIContext = this.getUIContext();
421
422  @Builder
423  LinkMenuBuilder() {
424    Menu() {
425      MenuItem({ content: 'Copy Link', })
426        .onClick(() => {
427          const pasteboardData = pasteboard.createData(pasteboard.MIMETYPE_TEXT_PLAIN, this.linkURL);
428          const systemPasteboard = pasteboard.getSystemPasteboard();
429          systemPasteboard.setData(pasteboardData);
430        })
431      MenuItem({content:'Open Link'})
432        .onClick(()=>{
433          this.controller.loadUrl(this.linkURL);
434        })
435    }
436  }
437  @Builder
438  ImageMenuBuilder() {
439    Menu() {
440      MenuItem({ content: 'Copy Image', })
441        .onClick(() => {
442          this.result?.copyImage();
443          this.result?.closeContextMenu();
444        })
445    }
446  }
447  build() {
448    Column() {
449      Web({ src: $rawfile("index.html"), controller: this.controller })
450        .javaScriptAccess(true)
451        .fileAccess(true)
452        .onlineImageAccess(true)
453        .imageAccess(true)
454        .domStorageAccess(true)
455        .bindSelectionMenu(WebElementType.LINK, this.LinkMenuBuilder, WebResponseType.LONG_PRESS,
456          {
457            onAppear: () => {},
458            onDisappear: () => {
459              this.result?.closeContextMenu();
460            },
461            preview: this.PreviewBuilder({
462              width: 500,
463              height: 400,
464              url:this.linkURL
465            }),
466            menuType: MenuType.PREVIEW_MENU,
467          })
468        .bindSelectionMenu(WebElementType.IMAGE, this.ImageMenuBuilder, WebResponseType.LONG_PRESS,
469          {
470            onAppear: () => {},
471            onDisappear: () => {
472              this.result?.closeContextMenu();
473            },
474            preview: PreviewBuilderGlobalForImage({
475              previewImage: this.previewImage,
476              width: this.previewWidthImage,
477              height: this.previewHeightImage,
478            }),
479            menuType: MenuType.PREVIEW_MENU,
480          })
481        .zoomAccess(true)
482        .onContextMenuShow((event) => {
483          if (event) {
484            this.result = event.result;
485            this.previewWidthImage = this.uiContext!.px2vp(event.param.getPreviewWidth());
486            this.previewHeightImage = this.uiContext!.px2vp(event.param.getPreviewHeight());
487            if (event.param.getSourceUrl().indexOf("resource://rawfile/") == 0) {
488              this.previewImage = $rawfile(event.param.getSourceUrl().substring(19));
489            } else {
490              this.previewImage = event.param.getSourceUrl();
491            }
492            this.linkURL = event.param.getLinkUrl()
493            return true;
494          }
495          return false;
496        })
497    }
498
499  }
500  // Swipe back
501  onBackPress(): boolean | void {
502    if (this.controller.accessStep(-1)) {
503      this.controller.backward();
504      return true;
505    } else {
506      return false;
507    }
508  }
509}
510```
511HTML example:
512```html
513<html lang="zh-CN"><head>
514    <meta charset="UTF-8">
515    <meta name="viewport" content="width=device-width, initial-scale=1.0">
516    <title>Information Page</title>
517</head>
518<body>
519    <div>
520        <section>
521            <a href="https://www.example1.com/">EMAPLE1</a>
522            <a href="https://www.example.com">EXAMPLE</a>
523        </section>
524    </div>
525    <footer>
526        <p>Note that all the above URLs are for reference only.</p>
527    </footer>
528</body></html>
529```
530
531
532## Saving Images by Using the Web Menu
5331. Create the **MenuBuilder** component as the menu pop-up window, use the [SaveButton](../reference/apis-arkui/arkui-ts/ts-security-components-savebutton.md) component to save images, and bind **MenuBuilder** to the **Web** component using **bindContextMenu**.
5342. Obtain the image URL in **onContextMenuShow** and save the image to the application sandbox by calling **copyLocalPicToDir** or **copyUrlPicToDir**.
5353. Save images in the application sandbox to the gallery by using **photoAccessHelper**.
536
537  ```ts
538import { webview } from '@kit.ArkWeb';
539import { common } from '@kit.AbilityKit';
540import { fileIo as fs, ReadOptions, WriteOptions } from '@kit.CoreFileKit';
541import { systemDateTime } from '@kit.BasicServicesKit';
542import { http } from '@kit.NetworkKit';
543import { photoAccessHelper } from '@kit.MediaLibraryKit';
544
545@Entry
546@Component
547struct WebComponent {
548  saveButtonOptions: SaveButtonOptions = {
549    icon: SaveIconStyle.FULL_FILLED,
550    text: SaveDescription.SAVE_IMAGE,
551    buttonType: ButtonType.Capsule
552  }
553  controller: webview.WebviewController = new webview.WebviewController();
554  private result: WebContextMenuResult | undefined = undefined;
555  @State showMenu: boolean = false;
556  @State imgUrl: string = '';
557  context = this.getUIContext().getHostContext() as common.UIAbilityContext;
558
559  copyLocalPicToDir(rawfilePath: string, newFileName: string): string {
560    let srcFileDes = this.context.resourceManager.getRawFdSync(rawfilePath);
561    let dstPath = this.context.filesDir + "/" +newFileName;
562    let dest: fs.File = fs.openSync(dstPath, fs.OpenMode.CREATE | fs.OpenMode.READ_WRITE);
563    let bufsize = 4096;
564    let buf = new ArrayBuffer(bufsize);
565    let off = 0, len = 0, readedLen = 0;
566    while (len = fs.readSync(srcFileDes.fd, buf, { offset: srcFileDes.offset + off, length: bufsize })) {
567      readedLen += len;
568      fs.writeSync(dest.fd, buf, { offset: off, length: len });
569      off = off + len;
570      if ((srcFileDes.length - readedLen) < bufsize) {
571        bufsize = srcFileDes.length - readedLen;
572      }
573    }
574    fs.close(dest.fd);
575    return dest.path;
576  }
577
578  async copyUrlPicToDir(picUrl: string, newFileName: string): Promise<string> {
579    let uri = '';
580    let httpRequest = http.createHttp();
581    let data: http.HttpResponse = await(httpRequest.request(picUrl) as Promise<http.HttpResponse>);
582    if (data?.responseCode == http.ResponseCode.OK) {
583      let dstPath = this.context.filesDir + "/" + newFileName;
584      let dest: fs.File = fs.openSync(dstPath, fs.OpenMode.CREATE | fs.OpenMode.READ_WRITE);
585      let writeLen: number = fs.writeSync(dest.fd, data.result as ArrayBuffer);
586      uri = dest.path;
587    }
588    return uri;
589  }
590
591  @Builder
592  MenuBuilder() {
593    Column() {
594      Row() {
595        SaveButton(this.saveButtonOptions)
596          .onClick(async (event, result: SaveButtonOnClickResult) => {
597            if (result == SaveButtonOnClickResult.SUCCESS) {
598              try {
599                let context = this.context;
600                let phAccessHelper = photoAccessHelper.getPhotoAccessHelper(context);
601                let uri = '';
602                if (this.imgUrl?.includes('rawfile')) {
603                  let rawFileName: string = this.imgUrl.substring(this.imgUrl.lastIndexOf('/') + 1);
604                  uri = this.copyLocalPicToDir(rawFileName, 'copyFile.png');
605                } else if (this.imgUrl?.includes('http') || this.imgUrl?.includes('https')) {
606                  uri = await this.copyUrlPicToDir(this.imgUrl, `onlinePic${systemDateTime.getTime()}.png`);
607                }
608                let assetChangeRequest: photoAccessHelper.MediaAssetChangeRequest = photoAccessHelper.MediaAssetChangeRequest.createImageAssetRequest(context, uri);
609                await phAccessHelper.applyChanges(assetChangeRequest);
610              }
611              catch (err) {
612                console.error(`create asset failed with error: ${err.code}}, ${err.message}}`);
613              }
614            } else {
615              console.error(`SaveButtonOnClickResult create asset failed`);
616            }
617            this.showMenu = false;
618          })
619      }
620      .margin({ top: 20, bottom: 20 })
621      .justifyContent(FlexAlign.Center)
622    }
623    .width('80')
624    .backgroundColor(Color.White)
625    .borderRadius(10)
626  }
627
628  build() {
629    Column() {
630      Web({src: $rawfile("index.html"), controller: this.controller})
631        .onContextMenuShow((event) => {
632          if (event) {
633            let hitValue = this.controller.getLastHitTest();
634            this.imgUrl = hitValue.extra;
635          }
636          this.showMenu = true;
637          return true;
638        })
639        .bindContextMenu(this.MenuBuilder, ResponseType.LongPress)
640        .fileAccess(true)
641        .javaScriptAccess(true)
642        .domStorageAccess(true)
643    }
644  }
645}
646  ```
647  ```html
648<!--index.html-->
649<!DOCTYPE html>
650<html>
651<head>
652    <title>SavePicture</title>
653</head>
654<body>
655<h1>SavePicture</h1>
656<br>
657<br>
658<br>
659<br>
660<br>
661<img src="./startIcon.png">
662</body>
663</html>
664  ```
665
666## FAQs
667### How do I disable the menu triggered by a long press?
668You can use the [editMenuOptions](../reference/apis-arkweb/arkts-basic-components-web-attributes.md#editmenuoptions12) API to filter out all default menus. Then, default menus are not displayed if there is no menu item.
669  ```ts
670// xxx.ets
671import { webview } from '@kit.ArkWeb';
672@Entry
673@Component
674struct WebComponent {
675  controller: webview.WebviewController = new webview.WebviewController();
676
677  onCreateMenu(menuItems: Array<TextMenuItem>): Array<TextMenuItem> {
678    let items = menuItems.filter((menuItem) => {
679      // Filter the menu items as required.
680      return false;
681    });
682    return items;
683  }
684
685  onMenuItemClick(menuItem: TextMenuItem, textRange: TextRange): boolean {
686    return false;// Return the default value false.
687  }
688
689  @State EditMenuOptions: EditMenuOptions = { onCreateMenu: this.onCreateMenu, onMenuItemClick: this.onMenuItemClick }
690
691  build() {
692    Column() {
693      Web({ src: $rawfile("index.html"), controller: this.controller })
694        .editMenuOptions(this.EditMenuOptions)
695    }
696  }
697}
698  ```
699  ```html
700<!--index.html-->
701<!DOCTYPE html>
702<html>
703  <head>
704      <title>Test Web Page</title>
705  </head>
706  <body>
707    <h1>editMenuOptions Demo</h1>
708    <span>edit menu options</span>
709  </body>
710</html>
711  ```
712![emptyEditMenuOption](./figures/emptyEditMenuOption.gif)
713
714### What should I do if the menu with a selection handle is not displayed when the selection area is displayed?
715Check whether the selection area is operated using the [selection API](https://www.w3.org/TR/selection-api/) of the JavaScript. If yes, the menu with a selection handle is not displayed.
716