LadybirdWebView.mm 64 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753
  1. /*
  2. * Copyright (c) 2023-2024, Tim Flynn <trflynn89@serenityos.org>
  3. *
  4. * SPDX-License-Identifier: BSD-2-Clause
  5. */
  6. #include <AK/Optional.h>
  7. #include <AK/TemporaryChange.h>
  8. #include <LibGfx/ImageFormats/PNGWriter.h>
  9. #include <LibGfx/ShareableBitmap.h>
  10. #include <LibURL/URL.h>
  11. #include <LibWeb/HTML/SelectedFile.h>
  12. #include <LibWebView/Application.h>
  13. #include <LibWebView/SearchEngine.h>
  14. #include <LibWebView/SourceHighlighter.h>
  15. #include <LibWebView/URL.h>
  16. #include <UI/LadybirdWebViewBridge.h>
  17. #import <Application/Application.h>
  18. #import <Application/ApplicationDelegate.h>
  19. #import <UI/Event.h>
  20. #import <UI/LadybirdWebView.h>
  21. #import <UniformTypeIdentifiers/UniformTypeIdentifiers.h>
  22. #import <Utilities/Conversions.h>
  23. #if !__has_feature(objc_arc)
  24. # error "This project requires ARC"
  25. #endif
  26. static constexpr NSInteger CONTEXT_MENU_PLAY_PAUSE_TAG = 1;
  27. static constexpr NSInteger CONTEXT_MENU_MUTE_UNMUTE_TAG = 2;
  28. static constexpr NSInteger CONTEXT_MENU_CONTROLS_TAG = 3;
  29. static constexpr NSInteger CONTEXT_MENU_LOOP_TAG = 4;
  30. static constexpr NSInteger CONTEXT_MENU_SEARCH_SELECTED_TEXT_TAG = 5;
  31. static constexpr NSInteger CONTEXT_MENU_COPY_LINK_TAG = 6;
  32. // Calls to [NSCursor hide] and [NSCursor unhide] must be balanced. We use this struct to ensure
  33. // we only call [NSCursor hide] once and to ensure that we do call [NSCursor unhide].
  34. // https://developer.apple.com/documentation/appkit/nscursor#1651301
  35. struct HideCursor {
  36. HideCursor()
  37. {
  38. [NSCursor hide];
  39. }
  40. ~HideCursor()
  41. {
  42. [NSCursor unhide];
  43. }
  44. };
  45. @interface LadybirdWebView () <NSDraggingDestination>
  46. {
  47. OwnPtr<Ladybird::WebViewBridge> m_web_view_bridge;
  48. URL::URL m_context_menu_url;
  49. Gfx::ShareableBitmap m_context_menu_bitmap;
  50. Optional<String> m_context_menu_search_text;
  51. Optional<HideCursor> m_hidden_cursor;
  52. // We have to send key events for modifer keys, but AppKit does not generate key down/up events when only a modifier
  53. // key is pressed. Instead, we only receive an event that the modifier flags have changed, and we must determine for
  54. // ourselves whether the modifier key was pressed or released.
  55. NSEventModifierFlags m_modifier_flags;
  56. }
  57. @property (nonatomic, weak) id<LadybirdWebViewObserver> observer;
  58. @property (nonatomic, strong) NSMenu* page_context_menu;
  59. @property (nonatomic, strong) NSMenu* link_context_menu;
  60. @property (nonatomic, strong) NSMenu* image_context_menu;
  61. @property (nonatomic, strong) NSMenu* audio_context_menu;
  62. @property (nonatomic, strong) NSMenu* video_context_menu;
  63. @property (nonatomic, strong) NSMenu* select_dropdown;
  64. @property (nonatomic, strong) NSTextField* status_label;
  65. @property (nonatomic, strong) NSAlert* dialog;
  66. // NSEvent does not provide a way to mark whether it has been handled, nor can we attach user data to the event. So
  67. // when we dispatch the event for a second time after WebContent has had a chance to handle it, we must track that
  68. // event ourselves to prevent indefinitely repeating the event.
  69. @property (nonatomic, strong) NSEvent* event_being_redispatched;
  70. @end
  71. @implementation LadybirdWebView
  72. @synthesize page_context_menu = _page_context_menu;
  73. @synthesize link_context_menu = _link_context_menu;
  74. @synthesize image_context_menu = _image_context_menu;
  75. @synthesize audio_context_menu = _audio_context_menu;
  76. @synthesize video_context_menu = _video_context_menu;
  77. @synthesize status_label = _status_label;
  78. - (instancetype)init:(id<LadybirdWebViewObserver>)observer
  79. {
  80. if (self = [self initWebView:observer]) {
  81. m_web_view_bridge->initialize_client();
  82. }
  83. return self;
  84. }
  85. - (instancetype)initAsChild:(id<LadybirdWebViewObserver>)observer
  86. parent:(LadybirdWebView*)parent
  87. pageIndex:(u64)page_index
  88. {
  89. if (self = [self initWebView:observer]) {
  90. m_web_view_bridge->initialize_client_as_child(*parent->m_web_view_bridge, page_index);
  91. }
  92. return self;
  93. }
  94. - (instancetype)initWebView:(id<LadybirdWebViewObserver>)observer
  95. {
  96. if (self = [super init]) {
  97. self.observer = observer;
  98. auto* delegate = (ApplicationDelegate*)[NSApp delegate];
  99. auto* screens = [NSScreen screens];
  100. Vector<Web::DevicePixelRect> screen_rects;
  101. screen_rects.ensure_capacity([screens count]);
  102. for (id screen in screens) {
  103. auto screen_rect = Ladybird::ns_rect_to_gfx_rect([screen frame]).to_type<Web::DevicePixels>();
  104. screen_rects.unchecked_append(screen_rect);
  105. }
  106. // This returns device pixel ratio of the screen the window is opened in
  107. auto device_pixel_ratio = [[NSScreen mainScreen] backingScaleFactor];
  108. m_web_view_bridge = MUST(Ladybird::WebViewBridge::create(move(screen_rects), device_pixel_ratio, [delegate preferredColorScheme], [delegate preferredContrast], [delegate preferredMotion]));
  109. [self setWebViewCallbacks];
  110. auto* area = [[NSTrackingArea alloc] initWithRect:[self bounds]
  111. options:NSTrackingActiveInKeyWindow | NSTrackingInVisibleRect | NSTrackingMouseMoved
  112. owner:self
  113. userInfo:nil];
  114. [self addTrackingArea:area];
  115. [self registerForDraggedTypes:[NSArray arrayWithObjects:NSPasteboardTypeFileURL, nil]];
  116. m_modifier_flags = 0;
  117. }
  118. return self;
  119. }
  120. #pragma mark - Public methods
  121. - (void)loadURL:(URL::URL const&)url
  122. {
  123. m_web_view_bridge->load(url);
  124. }
  125. - (void)loadHTML:(StringView)html
  126. {
  127. m_web_view_bridge->load_html(html);
  128. }
  129. - (void)navigateBack
  130. {
  131. m_web_view_bridge->traverse_the_history_by_delta(-1);
  132. }
  133. - (void)navigateForward
  134. {
  135. m_web_view_bridge->traverse_the_history_by_delta(1);
  136. }
  137. - (void)reload
  138. {
  139. m_web_view_bridge->reload();
  140. }
  141. - (WebView::ViewImplementation&)view
  142. {
  143. return *m_web_view_bridge;
  144. }
  145. - (String const&)handle
  146. {
  147. return m_web_view_bridge->handle();
  148. }
  149. - (void)handleResize
  150. {
  151. [self updateViewportRect:Ladybird::WebViewBridge::ForResize::Yes];
  152. [self updateStatusLabelPosition];
  153. }
  154. - (void)handleDevicePixelRatioChange
  155. {
  156. m_web_view_bridge->set_device_pixel_ratio([[self window] backingScaleFactor]);
  157. [self updateViewportRect:Ladybird::WebViewBridge::ForResize::Yes];
  158. [self updateStatusLabelPosition];
  159. }
  160. - (void)handleScroll
  161. {
  162. [self updateViewportRect:Ladybird::WebViewBridge::ForResize::No];
  163. [self updateStatusLabelPosition];
  164. }
  165. - (void)handleVisibility:(BOOL)is_visible
  166. {
  167. m_web_view_bridge->set_system_visibility_state(is_visible);
  168. }
  169. - (void)findInPage:(NSString*)query
  170. caseSensitivity:(CaseSensitivity)case_sensitivity
  171. {
  172. m_web_view_bridge->find_in_page(Ladybird::ns_string_to_string(query), case_sensitivity);
  173. }
  174. - (void)findInPageNextMatch
  175. {
  176. m_web_view_bridge->find_in_page_next_match();
  177. }
  178. - (void)findInPagePreviousMatch
  179. {
  180. m_web_view_bridge->find_in_page_previous_match();
  181. }
  182. - (void)zoomIn
  183. {
  184. m_web_view_bridge->zoom_in();
  185. }
  186. - (void)zoomOut
  187. {
  188. m_web_view_bridge->zoom_out();
  189. }
  190. - (void)resetZoom
  191. {
  192. m_web_view_bridge->reset_zoom();
  193. }
  194. - (float)zoomLevel
  195. {
  196. return m_web_view_bridge->zoom_level();
  197. }
  198. - (void)setPreferredColorScheme:(Web::CSS::PreferredColorScheme)color_scheme
  199. {
  200. m_web_view_bridge->set_preferred_color_scheme(color_scheme);
  201. }
  202. - (void)setPreferredContrast:(Web::CSS::PreferredContrast)contrast
  203. {
  204. m_web_view_bridge->set_preferred_contrast(contrast);
  205. }
  206. - (void)setPreferredMotion:(Web::CSS::PreferredMotion)motion
  207. {
  208. m_web_view_bridge->set_preferred_motion(motion);
  209. }
  210. - (void)debugRequest:(ByteString const&)request argument:(ByteString const&)argument
  211. {
  212. m_web_view_bridge->debug_request(request, argument);
  213. }
  214. - (void)setEnableAutoplay:(BOOL)enabled
  215. {
  216. m_web_view_bridge->set_enable_autoplay(enabled);
  217. }
  218. - (void)viewSource
  219. {
  220. m_web_view_bridge->get_source();
  221. }
  222. #pragma mark - Private methods
  223. static void copy_data_to_clipboard(StringView data, NSPasteboardType pasteboard_type)
  224. {
  225. auto* ns_data = Ladybird::string_to_ns_data(data);
  226. auto* pasteBoard = [NSPasteboard generalPasteboard];
  227. [pasteBoard clearContents];
  228. [pasteBoard setData:ns_data forType:pasteboard_type];
  229. }
  230. - (void)updateViewportRect:(Ladybird::WebViewBridge::ForResize)for_resize
  231. {
  232. auto content_rect = [self frame];
  233. auto document_rect = [[self documentView] frame];
  234. auto device_pixel_ratio = m_web_view_bridge->device_pixel_ratio();
  235. auto position = [&](auto content_size, auto document_size, auto scroll) {
  236. return max(0, (document_size - content_size) * device_pixel_ratio * scroll);
  237. };
  238. auto horizontal_scroll = [[[self scrollView] horizontalScroller] floatValue];
  239. auto vertical_scroll = [[[self scrollView] verticalScroller] floatValue];
  240. auto ns_viewport_rect = NSMakeRect(
  241. position(content_rect.size.width, document_rect.size.width, horizontal_scroll),
  242. position(content_rect.size.height, document_rect.size.height, vertical_scroll),
  243. content_rect.size.width,
  244. content_rect.size.height);
  245. auto viewport_rect = Ladybird::ns_rect_to_gfx_rect(ns_viewport_rect);
  246. m_web_view_bridge->set_viewport_rect(viewport_rect, for_resize);
  247. }
  248. - (void)updateStatusLabelPosition
  249. {
  250. static constexpr CGFloat LABEL_INSET = 10;
  251. if (_status_label == nil || [[self status_label] isHidden]) {
  252. return;
  253. }
  254. auto visible_rect = [self visibleRect];
  255. auto status_label_rect = [self.status_label frame];
  256. auto position = NSMakePoint(LABEL_INSET, visible_rect.origin.y + visible_rect.size.height - status_label_rect.size.height - LABEL_INSET);
  257. [self.status_label setFrameOrigin:position];
  258. }
  259. - (void)setWebViewCallbacks
  260. {
  261. // We need to make sure that these callbacks don't cause reference cycles.
  262. // By default, capturing self will copy a strong reference to self in ARC.
  263. __weak LadybirdWebView* weak_self = self;
  264. m_web_view_bridge->on_did_layout = [weak_self](auto content_size) {
  265. LadybirdWebView* self = weak_self;
  266. if (self == nil) {
  267. return;
  268. }
  269. auto inverse_device_pixel_ratio = m_web_view_bridge->inverse_device_pixel_ratio();
  270. [[self documentView] setFrameSize:NSMakeSize(content_size.width() * inverse_device_pixel_ratio, content_size.height() * inverse_device_pixel_ratio)];
  271. };
  272. m_web_view_bridge->on_ready_to_paint = [weak_self]() {
  273. LadybirdWebView* self = weak_self;
  274. if (self == nil) {
  275. return;
  276. }
  277. [self setNeedsDisplay:YES];
  278. };
  279. m_web_view_bridge->on_new_web_view = [weak_self](auto activate_tab, auto, auto page_index) {
  280. LadybirdWebView* self = weak_self;
  281. if (self == nil) {
  282. return String {};
  283. }
  284. if (page_index.has_value()) {
  285. return [self.observer onCreateChildTab:{}
  286. activateTab:activate_tab
  287. pageIndex:*page_index];
  288. }
  289. return [self.observer onCreateNewTab:{} activateTab:activate_tab];
  290. };
  291. m_web_view_bridge->on_request_web_content = [weak_self]() {
  292. Application* application = NSApp;
  293. LadybirdWebView* self = weak_self;
  294. if (self == nil) {
  295. VERIFY_NOT_REACHED();
  296. }
  297. return [application launchWebContent:*(self->m_web_view_bridge)].release_value_but_fixme_should_propagate_errors();
  298. };
  299. m_web_view_bridge->on_request_worker_agent = []() {
  300. Application* application = NSApp;
  301. return [application launchWebWorker].release_value_but_fixme_should_propagate_errors();
  302. };
  303. m_web_view_bridge->on_activate_tab = [weak_self]() {
  304. LadybirdWebView* self = weak_self;
  305. if (self == nil) {
  306. return;
  307. }
  308. [[self window] orderFront:nil];
  309. };
  310. m_web_view_bridge->on_close = [weak_self]() {
  311. LadybirdWebView* self = weak_self;
  312. if (self == nil) {
  313. return;
  314. }
  315. [[self window] close];
  316. };
  317. m_web_view_bridge->on_load_start = [weak_self](auto const& url, bool is_redirect) {
  318. LadybirdWebView* self = weak_self;
  319. if (self == nil) {
  320. return;
  321. }
  322. [self.observer onLoadStart:url isRedirect:is_redirect];
  323. if (_status_label != nil) {
  324. [self.status_label setHidden:YES];
  325. }
  326. };
  327. m_web_view_bridge->on_load_finish = [weak_self](auto const& url) {
  328. LadybirdWebView* self = weak_self;
  329. if (self == nil) {
  330. return;
  331. }
  332. [self.observer onLoadFinish:url];
  333. };
  334. m_web_view_bridge->on_url_change = [weak_self](auto const& url) {
  335. LadybirdWebView* self = weak_self;
  336. if (self == nil) {
  337. return;
  338. }
  339. [self.observer onURLChange:url];
  340. };
  341. m_web_view_bridge->on_navigation_buttons_state_changed = [weak_self](auto back_enabled, auto forward_enabled) {
  342. LadybirdWebView* self = weak_self;
  343. if (self == nil) {
  344. return;
  345. }
  346. [self.observer onBackNavigationEnabled:back_enabled
  347. forwardNavigationEnabled:forward_enabled];
  348. };
  349. m_web_view_bridge->on_title_change = [weak_self](auto const& title) {
  350. LadybirdWebView* self = weak_self;
  351. if (self == nil) {
  352. return;
  353. }
  354. [self.observer onTitleChange:title];
  355. };
  356. m_web_view_bridge->on_favicon_change = [weak_self](auto const& bitmap) {
  357. LadybirdWebView* self = weak_self;
  358. if (self == nil) {
  359. return;
  360. }
  361. [self.observer onFaviconChange:bitmap];
  362. };
  363. m_web_view_bridge->on_finish_handling_key_event = [weak_self](auto const& key_event) {
  364. LadybirdWebView* self = weak_self;
  365. if (self == nil) {
  366. return;
  367. }
  368. NSEvent* event = Ladybird::key_event_to_ns_event(key_event);
  369. self.event_being_redispatched = event;
  370. [NSApp sendEvent:event];
  371. self.event_being_redispatched = nil;
  372. };
  373. m_web_view_bridge->on_finish_handling_drag_event = [weak_self](auto const& event) {
  374. LadybirdWebView* self = weak_self;
  375. if (self == nil) {
  376. return;
  377. }
  378. if (event.type != Web::DragEvent::Type::Drop) {
  379. return;
  380. }
  381. if (auto urls = Ladybird::drag_event_url_list(event); !urls.is_empty()) {
  382. [self.observer loadURL:urls[0]];
  383. for (size_t i = 1; i < urls.size(); ++i) {
  384. [self.observer onCreateNewTab:urls[i] activateTab:Web::HTML::ActivateTab::No];
  385. }
  386. }
  387. };
  388. m_web_view_bridge->on_cursor_change = [weak_self](auto cursor) {
  389. LadybirdWebView* self = weak_self;
  390. if (self == nil) {
  391. return;
  392. }
  393. if (cursor == Gfx::StandardCursor::Hidden) {
  394. if (!m_hidden_cursor.has_value()) {
  395. m_hidden_cursor.emplace();
  396. }
  397. return;
  398. }
  399. m_hidden_cursor.clear();
  400. switch (cursor) {
  401. case Gfx::StandardCursor::Arrow:
  402. [[NSCursor arrowCursor] set];
  403. break;
  404. case Gfx::StandardCursor::Crosshair:
  405. [[NSCursor crosshairCursor] set];
  406. break;
  407. case Gfx::StandardCursor::IBeam:
  408. [[NSCursor IBeamCursor] set];
  409. break;
  410. case Gfx::StandardCursor::ResizeHorizontal:
  411. [[NSCursor resizeLeftRightCursor] set];
  412. break;
  413. case Gfx::StandardCursor::ResizeVertical:
  414. [[NSCursor resizeUpDownCursor] set];
  415. break;
  416. case Gfx::StandardCursor::ResizeDiagonalTLBR:
  417. // FIXME: AppKit does not have a corresponding cursor, so we should make one.
  418. [[NSCursor arrowCursor] set];
  419. break;
  420. case Gfx::StandardCursor::ResizeDiagonalBLTR:
  421. // FIXME: AppKit does not have a corresponding cursor, so we should make one.
  422. [[NSCursor arrowCursor] set];
  423. break;
  424. case Gfx::StandardCursor::ResizeColumn:
  425. [[NSCursor resizeLeftRightCursor] set];
  426. break;
  427. case Gfx::StandardCursor::ResizeRow:
  428. [[NSCursor resizeUpDownCursor] set];
  429. break;
  430. case Gfx::StandardCursor::Hand:
  431. [[NSCursor pointingHandCursor] set];
  432. break;
  433. case Gfx::StandardCursor::Help:
  434. // FIXME: AppKit does not have a corresponding cursor, so we should make one.
  435. [[NSCursor arrowCursor] set];
  436. break;
  437. case Gfx::StandardCursor::Drag:
  438. [[NSCursor closedHandCursor] set];
  439. break;
  440. case Gfx::StandardCursor::DragCopy:
  441. [[NSCursor dragCopyCursor] set];
  442. break;
  443. case Gfx::StandardCursor::Move:
  444. // FIXME: AppKit does not have a corresponding cursor, so we should make one.
  445. [[NSCursor dragCopyCursor] set];
  446. break;
  447. case Gfx::StandardCursor::Wait:
  448. // FIXME: AppKit does not have a corresponding cursor, so we should make one.
  449. [[NSCursor arrowCursor] set];
  450. break;
  451. case Gfx::StandardCursor::Disallowed:
  452. // FIXME: AppKit does not have a corresponding cursor, so we should make one.
  453. [[NSCursor arrowCursor] set];
  454. break;
  455. case Gfx::StandardCursor::Eyedropper:
  456. // FIXME: AppKit does not have a corresponding cursor, so we should make one.
  457. [[NSCursor arrowCursor] set];
  458. break;
  459. case Gfx::StandardCursor::Zoom:
  460. // FIXME: AppKit does not have a corresponding cursor, so we should make one.
  461. [[NSCursor arrowCursor] set];
  462. break;
  463. default:
  464. break;
  465. }
  466. };
  467. m_web_view_bridge->on_zoom_level_changed = [weak_self]() {
  468. LadybirdWebView* self = weak_self;
  469. if (self == nil) {
  470. return;
  471. }
  472. [self updateViewportRect:Ladybird::WebViewBridge::ForResize::Yes];
  473. };
  474. m_web_view_bridge->on_request_tooltip_override = [weak_self](auto, auto const& tooltip) {
  475. LadybirdWebView* self = weak_self;
  476. if (self == nil) {
  477. return;
  478. }
  479. self.toolTip = Ladybird::string_to_ns_string(tooltip);
  480. };
  481. m_web_view_bridge->on_stop_tooltip_override = [weak_self]() {
  482. LadybirdWebView* self = weak_self;
  483. if (self == nil) {
  484. return;
  485. }
  486. self.toolTip = nil;
  487. };
  488. m_web_view_bridge->on_enter_tooltip_area = [weak_self](auto const& tooltip) {
  489. LadybirdWebView* self = weak_self;
  490. if (self == nil) {
  491. return;
  492. }
  493. self.toolTip = Ladybird::string_to_ns_string(tooltip);
  494. };
  495. m_web_view_bridge->on_leave_tooltip_area = [weak_self]() {
  496. LadybirdWebView* self = weak_self;
  497. if (self == nil) {
  498. return;
  499. }
  500. self.toolTip = nil;
  501. };
  502. m_web_view_bridge->on_link_hover = [weak_self](auto const& url) {
  503. LadybirdWebView* self = weak_self;
  504. if (self == nil) {
  505. return;
  506. }
  507. auto* url_string = Ladybird::string_to_ns_string(url.serialize());
  508. [self.status_label setStringValue:url_string];
  509. [self.status_label sizeToFit];
  510. [self.status_label setHidden:NO];
  511. [self updateStatusLabelPosition];
  512. };
  513. m_web_view_bridge->on_link_unhover = [weak_self]() {
  514. LadybirdWebView* self = weak_self;
  515. if (self == nil) {
  516. return;
  517. }
  518. [self.status_label setHidden:YES];
  519. };
  520. m_web_view_bridge->on_link_click = [weak_self](auto const& url, auto const& target, unsigned modifiers) {
  521. LadybirdWebView* self = weak_self;
  522. if (self == nil) {
  523. return;
  524. }
  525. if (modifiers == Web::UIEvents::KeyModifier::Mod_Super) {
  526. [self.observer onCreateNewTab:url activateTab:Web::HTML::ActivateTab::No];
  527. } else if (target == "_blank"sv) {
  528. [self.observer onCreateNewTab:url activateTab:Web::HTML::ActivateTab::Yes];
  529. } else {
  530. [self.observer loadURL:url];
  531. }
  532. };
  533. m_web_view_bridge->on_link_middle_click = [weak_self](auto url, auto, unsigned) {
  534. LadybirdWebView* self = weak_self;
  535. if (self == nil) {
  536. return;
  537. }
  538. [self.observer onCreateNewTab:url activateTab:Web::HTML::ActivateTab::No];
  539. };
  540. m_web_view_bridge->on_context_menu_request = [weak_self](auto position) {
  541. LadybirdWebView* self = weak_self;
  542. if (self == nil) {
  543. return;
  544. }
  545. auto* search_selected_text_menu_item = [self.page_context_menu itemWithTag:CONTEXT_MENU_SEARCH_SELECTED_TEXT_TAG];
  546. auto selected_text = self.observer
  547. ? m_web_view_bridge->selected_text_with_whitespace_collapsed()
  548. : OptionalNone {};
  549. TemporaryChange change_url { m_context_menu_search_text, move(selected_text) };
  550. if (m_context_menu_search_text.has_value()) {
  551. auto* delegate = (ApplicationDelegate*)[NSApp delegate];
  552. auto action_text = WebView::format_search_query_for_display([delegate searchEngine].query_url, *m_context_menu_search_text);
  553. [search_selected_text_menu_item setTitle:Ladybird::string_to_ns_string(action_text)];
  554. [search_selected_text_menu_item setHidden:NO];
  555. } else {
  556. [search_selected_text_menu_item setHidden:YES];
  557. }
  558. auto* event = Ladybird::create_context_menu_mouse_event(self, position);
  559. [NSMenu popUpContextMenu:self.page_context_menu withEvent:event forView:self];
  560. };
  561. m_web_view_bridge->on_link_context_menu_request = [weak_self](auto const& url, auto position) {
  562. LadybirdWebView* self = weak_self;
  563. if (self == nil) {
  564. return;
  565. }
  566. TemporaryChange change_url { m_context_menu_url, url };
  567. auto* copy_link_menu_item = [self.link_context_menu itemWithTag:CONTEXT_MENU_COPY_LINK_TAG];
  568. switch (WebView::url_type(url)) {
  569. case WebView::URLType::Email:
  570. [copy_link_menu_item setTitle:@"Copy Email Address"];
  571. break;
  572. case WebView::URLType::Telephone:
  573. [copy_link_menu_item setTitle:@"Copy Phone Number"];
  574. break;
  575. case WebView::URLType::Other:
  576. [copy_link_menu_item setTitle:@"Copy URL"];
  577. break;
  578. }
  579. auto* event = Ladybird::create_context_menu_mouse_event(self, position);
  580. [NSMenu popUpContextMenu:self.link_context_menu withEvent:event forView:self];
  581. };
  582. m_web_view_bridge->on_image_context_menu_request = [weak_self](auto const& url, auto position, auto const& bitmap) {
  583. LadybirdWebView* self = weak_self;
  584. if (self == nil) {
  585. return;
  586. }
  587. TemporaryChange change_url { m_context_menu_url, url };
  588. TemporaryChange change_bitmap { m_context_menu_bitmap, bitmap };
  589. auto* event = Ladybird::create_context_menu_mouse_event(self, position);
  590. [NSMenu popUpContextMenu:self.image_context_menu withEvent:event forView:self];
  591. };
  592. m_web_view_bridge->on_media_context_menu_request = [weak_self](auto position, auto const& menu) {
  593. LadybirdWebView* self = weak_self;
  594. if (self == nil) {
  595. return;
  596. }
  597. TemporaryChange change_url { m_context_menu_url, menu.media_url };
  598. auto* context_menu = menu.is_video ? self.video_context_menu : self.audio_context_menu;
  599. auto* play_pause_menu_item = [context_menu itemWithTag:CONTEXT_MENU_PLAY_PAUSE_TAG];
  600. auto* mute_unmute_menu_item = [context_menu itemWithTag:CONTEXT_MENU_MUTE_UNMUTE_TAG];
  601. auto* controls_menu_item = [context_menu itemWithTag:CONTEXT_MENU_CONTROLS_TAG];
  602. auto* loop_menu_item = [context_menu itemWithTag:CONTEXT_MENU_LOOP_TAG];
  603. if (menu.is_playing) {
  604. [play_pause_menu_item setTitle:@"Pause"];
  605. } else {
  606. [play_pause_menu_item setTitle:@"Play"];
  607. }
  608. if (menu.is_muted) {
  609. [mute_unmute_menu_item setTitle:@"Unmute"];
  610. } else {
  611. [mute_unmute_menu_item setTitle:@"Mute"];
  612. }
  613. auto controls_state = menu.has_user_agent_controls ? NSControlStateValueOn : NSControlStateValueOff;
  614. [controls_menu_item setState:controls_state];
  615. auto loop_state = menu.is_looping ? NSControlStateValueOn : NSControlStateValueOff;
  616. [loop_menu_item setState:loop_state];
  617. auto* event = Ladybird::create_context_menu_mouse_event(self, position);
  618. [NSMenu popUpContextMenu:context_menu withEvent:event forView:self];
  619. };
  620. m_web_view_bridge->on_request_alert = [weak_self](auto const& message) {
  621. LadybirdWebView* self = weak_self;
  622. if (self == nil) {
  623. return;
  624. }
  625. auto* ns_message = Ladybird::string_to_ns_string(message);
  626. self.dialog = [[NSAlert alloc] init];
  627. [self.dialog setMessageText:ns_message];
  628. [self.dialog beginSheetModalForWindow:[self window]
  629. completionHandler:^(NSModalResponse) {
  630. m_web_view_bridge->alert_closed();
  631. self.dialog = nil;
  632. }];
  633. };
  634. m_web_view_bridge->on_request_confirm = [weak_self](auto const& message) {
  635. LadybirdWebView* self = weak_self;
  636. if (self == nil) {
  637. return;
  638. }
  639. auto* ns_message = Ladybird::string_to_ns_string(message);
  640. self.dialog = [[NSAlert alloc] init];
  641. [[self.dialog addButtonWithTitle:@"OK"] setTag:NSModalResponseOK];
  642. [[self.dialog addButtonWithTitle:@"Cancel"] setTag:NSModalResponseCancel];
  643. [self.dialog setMessageText:ns_message];
  644. [self.dialog beginSheetModalForWindow:[self window]
  645. completionHandler:^(NSModalResponse response) {
  646. m_web_view_bridge->confirm_closed(response == NSModalResponseOK);
  647. self.dialog = nil;
  648. }];
  649. };
  650. m_web_view_bridge->on_request_prompt = [weak_self](auto const& message, auto const& default_) {
  651. LadybirdWebView* self = weak_self;
  652. if (self == nil) {
  653. return;
  654. }
  655. auto* ns_message = Ladybird::string_to_ns_string(message);
  656. auto* ns_default = Ladybird::string_to_ns_string(default_);
  657. auto* input = [[NSTextField alloc] initWithFrame:NSMakeRect(0, 0, 200, 24)];
  658. [input setStringValue:ns_default];
  659. self.dialog = [[NSAlert alloc] init];
  660. [[self.dialog addButtonWithTitle:@"OK"] setTag:NSModalResponseOK];
  661. [[self.dialog addButtonWithTitle:@"Cancel"] setTag:NSModalResponseCancel];
  662. [self.dialog setMessageText:ns_message];
  663. [self.dialog setAccessoryView:input];
  664. self.dialog.window.initialFirstResponder = input;
  665. [self.dialog beginSheetModalForWindow:[self window]
  666. completionHandler:^(NSModalResponse response) {
  667. Optional<String> text;
  668. if (response == NSModalResponseOK) {
  669. text = Ladybird::ns_string_to_string([input stringValue]);
  670. }
  671. m_web_view_bridge->prompt_closed(move(text));
  672. self.dialog = nil;
  673. }];
  674. };
  675. m_web_view_bridge->on_request_set_prompt_text = [weak_self](auto const& message) {
  676. LadybirdWebView* self = weak_self;
  677. if (self == nil) {
  678. return;
  679. }
  680. if (self.dialog == nil || [self.dialog accessoryView] == nil) {
  681. return;
  682. }
  683. auto* ns_message = Ladybird::string_to_ns_string(message);
  684. auto* input = (NSTextField*)[self.dialog accessoryView];
  685. [input setStringValue:ns_message];
  686. };
  687. m_web_view_bridge->on_request_accept_dialog = [weak_self]() {
  688. LadybirdWebView* self = weak_self;
  689. if (self == nil || self.dialog == nil) {
  690. return;
  691. }
  692. [[self window] endSheet:[[self dialog] window]
  693. returnCode:NSModalResponseOK];
  694. };
  695. m_web_view_bridge->on_request_dismiss_dialog = [weak_self]() {
  696. LadybirdWebView* self = weak_self;
  697. if (self == nil || self.dialog == nil) {
  698. return;
  699. }
  700. [[self window] endSheet:[[self dialog] window]
  701. returnCode:NSModalResponseCancel];
  702. };
  703. m_web_view_bridge->on_request_color_picker = [weak_self](Color current_color) {
  704. LadybirdWebView* self = weak_self;
  705. if (self == nil) {
  706. return;
  707. }
  708. auto* panel = [NSColorPanel sharedColorPanel];
  709. [panel setColor:Ladybird::gfx_color_to_ns_color(current_color)];
  710. [panel setShowsAlpha:NO];
  711. [panel setTarget:self];
  712. [panel setAction:@selector(colorPickerUpdate:)];
  713. NSNotificationCenter* notification_center = [NSNotificationCenter defaultCenter];
  714. [notification_center addObserver:self
  715. selector:@selector(colorPickerClosed:)
  716. name:NSWindowWillCloseNotification
  717. object:panel];
  718. [panel makeKeyAndOrderFront:nil];
  719. };
  720. m_web_view_bridge->on_request_file_picker = [weak_self](auto const& accepted_file_types, auto allow_multiple_files) {
  721. LadybirdWebView* self = weak_self;
  722. if (self == nil) {
  723. return;
  724. }
  725. auto* panel = [NSOpenPanel openPanel];
  726. [panel setCanChooseFiles:YES];
  727. [panel setCanChooseDirectories:NO];
  728. if (allow_multiple_files == Web::HTML::AllowMultipleFiles::Yes) {
  729. [panel setAllowsMultipleSelection:YES];
  730. [panel setMessage:@"Select files"];
  731. } else {
  732. [panel setAllowsMultipleSelection:NO];
  733. [panel setMessage:@"Select file"];
  734. }
  735. NSMutableArray<UTType*>* accepted_file_filters = [NSMutableArray array];
  736. for (auto const& filter : accepted_file_types.filters) {
  737. filter.visit(
  738. [&](Web::HTML::FileFilter::FileType type) {
  739. switch (type) {
  740. case Web::HTML::FileFilter::FileType::Audio:
  741. [accepted_file_filters addObject:UTTypeAudio];
  742. break;
  743. case Web::HTML::FileFilter::FileType::Image:
  744. [accepted_file_filters addObject:UTTypeImage];
  745. break;
  746. case Web::HTML::FileFilter::FileType::Video:
  747. [accepted_file_filters addObject:UTTypeVideo];
  748. break;
  749. }
  750. },
  751. [&](Web::HTML::FileFilter::MimeType const& filter) {
  752. auto* ns_mime_type = Ladybird::string_to_ns_string(filter.value);
  753. if (auto* ut_type = [UTType typeWithMIMEType:ns_mime_type]) {
  754. [accepted_file_filters addObject:ut_type];
  755. }
  756. },
  757. [&](Web::HTML::FileFilter::Extension const& filter) {
  758. auto* ns_extension = Ladybird::string_to_ns_string(filter.value);
  759. if (auto* ut_type = [UTType typeWithFilenameExtension:ns_extension]) {
  760. [accepted_file_filters addObject:ut_type];
  761. }
  762. });
  763. }
  764. // FIXME: Create an accessory view to allow selecting the active file filter.
  765. [panel setAllowedContentTypes:accepted_file_filters];
  766. [panel setAllowsOtherFileTypes:YES];
  767. [panel beginSheetModalForWindow:[self window]
  768. completionHandler:^(NSInteger result) {
  769. Vector<Web::HTML::SelectedFile> selected_files;
  770. auto create_selected_file = [&](NSString* ns_file_path) {
  771. auto file_path = Ladybird::ns_string_to_byte_string(ns_file_path);
  772. if (auto file = Web::HTML::SelectedFile::from_file_path(file_path); file.is_error())
  773. warnln("Unable to open file {}: {}", file_path, file.error());
  774. else
  775. selected_files.append(file.release_value());
  776. };
  777. if (result == NSModalResponseOK) {
  778. for (NSURL* url : [panel URLs]) {
  779. create_selected_file([url path]);
  780. }
  781. }
  782. m_web_view_bridge->file_picker_closed(move(selected_files));
  783. }];
  784. };
  785. self.select_dropdown = [[NSMenu alloc] initWithTitle:@"Select Dropdown"];
  786. [self.select_dropdown setDelegate:self];
  787. m_web_view_bridge->on_request_select_dropdown = [weak_self](Gfx::IntPoint content_position, i32 minimum_width, Vector<Web::HTML::SelectItem> items) {
  788. LadybirdWebView* self = weak_self;
  789. if (self == nil) {
  790. return;
  791. }
  792. [self.select_dropdown removeAllItems];
  793. self.select_dropdown.minimumWidth = minimum_width;
  794. auto add_menu_item = [self](Web::HTML::SelectItemOption const& item_option, bool in_option_group) {
  795. NSMenuItem* menuItem = [[NSMenuItem alloc]
  796. initWithTitle:Ladybird::string_to_ns_string(in_option_group ? MUST(String::formatted(" {}", item_option.label)) : item_option.label)
  797. action:item_option.disabled ? nil : @selector(selectDropdownAction:)
  798. keyEquivalent:@""];
  799. menuItem.representedObject = [NSNumber numberWithUnsignedInt:item_option.id];
  800. menuItem.state = item_option.selected ? NSControlStateValueOn : NSControlStateValueOff;
  801. [self.select_dropdown addItem:menuItem];
  802. };
  803. for (auto const& item : items) {
  804. if (item.has<Web::HTML::SelectItemOptionGroup>()) {
  805. auto const& item_option_group = item.get<Web::HTML::SelectItemOptionGroup>();
  806. NSMenuItem* subtitle = [[NSMenuItem alloc]
  807. initWithTitle:Ladybird::string_to_ns_string(item_option_group.label)
  808. action:nil
  809. keyEquivalent:@""];
  810. [self.select_dropdown addItem:subtitle];
  811. for (auto const& item_option : item_option_group.items)
  812. add_menu_item(item_option, true);
  813. }
  814. if (item.has<Web::HTML::SelectItemOption>())
  815. add_menu_item(item.get<Web::HTML::SelectItemOption>(), false);
  816. if (item.has<Web::HTML::SelectItemSeparator>())
  817. [self.select_dropdown addItem:[NSMenuItem separatorItem]];
  818. }
  819. auto device_pixel_ratio = m_web_view_bridge->device_pixel_ratio();
  820. auto* event = Ladybird::create_context_menu_mouse_event(self, Gfx::IntPoint { content_position.x() / device_pixel_ratio, content_position.y() / device_pixel_ratio });
  821. [NSMenu popUpContextMenu:self.select_dropdown withEvent:event forView:self];
  822. };
  823. m_web_view_bridge->on_restore_window = [weak_self]() {
  824. LadybirdWebView* self = weak_self;
  825. if (self == nil) {
  826. return;
  827. }
  828. [[self window] setIsMiniaturized:NO];
  829. [[self window] orderFront:nil];
  830. };
  831. m_web_view_bridge->on_reposition_window = [weak_self](auto const& position) {
  832. LadybirdWebView* self = weak_self;
  833. if (self == nil) {
  834. return Gfx::IntPoint {};
  835. }
  836. auto frame = [[self window] frame];
  837. frame.origin = Ladybird::gfx_point_to_ns_point(position);
  838. [[self window] setFrame:frame display:YES];
  839. return Ladybird::ns_point_to_gfx_point([[self window] frame].origin);
  840. };
  841. m_web_view_bridge->on_resize_window = [weak_self](auto const& size) {
  842. LadybirdWebView* self = weak_self;
  843. if (self == nil) {
  844. return Gfx::IntSize {};
  845. }
  846. auto frame = [[self window] frame];
  847. frame.size = Ladybird::gfx_size_to_ns_size(size);
  848. [[self window] setFrame:frame display:YES];
  849. return Ladybird::ns_size_to_gfx_size([[self window] frame].size);
  850. };
  851. m_web_view_bridge->on_maximize_window = [weak_self]() {
  852. LadybirdWebView* self = weak_self;
  853. if (self == nil) {
  854. return Gfx::IntRect {};
  855. }
  856. auto frame = [[NSScreen mainScreen] frame];
  857. [[self window] setFrame:frame display:YES];
  858. return Ladybird::ns_rect_to_gfx_rect([[self window] frame]);
  859. };
  860. m_web_view_bridge->on_minimize_window = [weak_self]() {
  861. LadybirdWebView* self = weak_self;
  862. if (self == nil) {
  863. return Gfx::IntRect {};
  864. }
  865. [[self window] setIsMiniaturized:YES];
  866. return Ladybird::ns_rect_to_gfx_rect([[self window] frame]);
  867. };
  868. m_web_view_bridge->on_fullscreen_window = [weak_self]() {
  869. LadybirdWebView* self = weak_self;
  870. if (self == nil) {
  871. return Gfx::IntRect {};
  872. }
  873. if (([[self window] styleMask] & NSWindowStyleMaskFullScreen) == 0) {
  874. [[self window] toggleFullScreen:nil];
  875. }
  876. return Ladybird::ns_rect_to_gfx_rect([[self window] frame]);
  877. };
  878. m_web_view_bridge->on_received_source = [weak_self](auto const& url, auto const& base_url, auto const& source) {
  879. LadybirdWebView* self = weak_self;
  880. if (self == nil) {
  881. return;
  882. }
  883. auto html = WebView::highlight_source(url, base_url, source, Syntax::Language::HTML, WebView::HighlightOutputMode::FullDocument);
  884. [self.observer onCreateNewTab:html
  885. url:url
  886. activateTab:Web::HTML::ActivateTab::Yes];
  887. };
  888. m_web_view_bridge->on_theme_color_change = [weak_self](auto color) {
  889. LadybirdWebView* self = weak_self;
  890. if (self == nil) {
  891. return;
  892. }
  893. self.backgroundColor = Ladybird::gfx_color_to_ns_color(color);
  894. };
  895. m_web_view_bridge->on_find_in_page = [weak_self](auto current_match_index, auto const& total_match_count) {
  896. LadybirdWebView* self = weak_self;
  897. if (self == nil) {
  898. return;
  899. }
  900. [self.observer onFindInPageResult:current_match_index + 1
  901. totalMatchCount:total_match_count];
  902. };
  903. m_web_view_bridge->on_insert_clipboard_entry = [](auto const& data, auto const&, auto const& mime_type) {
  904. NSPasteboardType pasteboard_type = nil;
  905. // https://w3c.github.io/clipboard-apis/#os-specific-well-known-format
  906. if (mime_type == "text/plain"sv)
  907. pasteboard_type = NSPasteboardTypeString;
  908. else if (mime_type == "text/html"sv)
  909. pasteboard_type = NSPasteboardTypeHTML;
  910. else if (mime_type == "text/png"sv)
  911. pasteboard_type = NSPasteboardTypePNG;
  912. if (pasteboard_type)
  913. copy_data_to_clipboard(data, pasteboard_type);
  914. };
  915. m_web_view_bridge->on_audio_play_state_changed = [weak_self](auto play_state) {
  916. LadybirdWebView* self = weak_self;
  917. if (self == nil) {
  918. return;
  919. }
  920. [self.observer onAudioPlayStateChange:play_state];
  921. };
  922. }
  923. - (void)selectDropdownAction:(NSMenuItem*)menuItem
  924. {
  925. NSNumber* data = [menuItem representedObject];
  926. m_web_view_bridge->select_dropdown_closed([data unsignedIntValue]);
  927. }
  928. - (void)menuDidClose:(NSMenu*)menu
  929. {
  930. if (!menu.highlightedItem)
  931. m_web_view_bridge->select_dropdown_closed({});
  932. }
  933. - (void)colorPickerUpdate:(NSColorPanel*)colorPanel
  934. {
  935. m_web_view_bridge->color_picker_update(Ladybird::ns_color_to_gfx_color(colorPanel.color), Web::HTML::ColorPickerUpdateState::Update);
  936. }
  937. - (void)colorPickerClosed:(NSNotification*)notification
  938. {
  939. m_web_view_bridge->color_picker_update(Ladybird::ns_color_to_gfx_color([NSColorPanel sharedColorPanel].color), Web::HTML::ColorPickerUpdateState::Closed);
  940. }
  941. - (NSScrollView*)scrollView
  942. {
  943. return (NSScrollView*)[self superview];
  944. }
  945. - (void)copy:(id)sender
  946. {
  947. copy_data_to_clipboard(m_web_view_bridge->selected_text(), NSPasteboardTypeString);
  948. }
  949. - (void)paste:(id)sender
  950. {
  951. auto* paste_board = [NSPasteboard generalPasteboard];
  952. if (auto* contents = [paste_board stringForType:NSPasteboardTypeString]) {
  953. m_web_view_bridge->paste(Ladybird::ns_string_to_string(contents));
  954. }
  955. }
  956. - (void)selectAll:(id)sender
  957. {
  958. m_web_view_bridge->select_all();
  959. }
  960. - (void)searchSelectedText:(id)sender
  961. {
  962. auto* delegate = (ApplicationDelegate*)[NSApp delegate];
  963. auto url = MUST(String::formatted([delegate searchEngine].query_url, URL::percent_encode(*m_context_menu_search_text)));
  964. [self.observer onCreateNewTab:url activateTab:Web::HTML::ActivateTab::Yes];
  965. }
  966. - (void)takeVisibleScreenshot:(id)sender
  967. {
  968. [self takeScreenshot:WebView::ViewImplementation::ScreenshotType::Visible];
  969. }
  970. - (void)takeFullScreenshot:(id)sender
  971. {
  972. [self takeScreenshot:WebView::ViewImplementation::ScreenshotType::Full];
  973. }
  974. - (void)takeScreenshot:(WebView::ViewImplementation::ScreenshotType)type
  975. {
  976. m_web_view_bridge->take_screenshot(type)
  977. ->when_resolved([self](auto const& path) {
  978. auto message = MUST(String::formatted("Screenshot saved to: {}", path));
  979. auto* dialog = [[NSAlert alloc] init];
  980. [dialog setMessageText:Ladybird::string_to_ns_string(message)];
  981. [[dialog addButtonWithTitle:@"OK"] setTag:NSModalResponseOK];
  982. [[dialog addButtonWithTitle:@"Open folder"] setTag:NSModalResponseContinue];
  983. __block auto* ns_path = Ladybird::string_to_ns_string(path.string());
  984. [dialog beginSheetModalForWindow:[self window]
  985. completionHandler:^(NSModalResponse response) {
  986. if (response == NSModalResponseContinue) {
  987. [[NSWorkspace sharedWorkspace] selectFile:ns_path inFileViewerRootedAtPath:@""];
  988. }
  989. }];
  990. })
  991. .when_rejected([self](auto const& error) {
  992. if (error.is_errno() && error.code() == ECANCELED)
  993. return;
  994. auto error_message = MUST(String::formatted("{}", error));
  995. auto* dialog = [[NSAlert alloc] init];
  996. [dialog setMessageText:Ladybird::string_to_ns_string(error_message)];
  997. [dialog beginSheetModalForWindow:[self window]
  998. completionHandler:nil];
  999. });
  1000. }
  1001. - (void)openLink:(id)sender
  1002. {
  1003. m_web_view_bridge->on_link_click(m_context_menu_url, {}, 0);
  1004. }
  1005. - (void)openLinkInNewTab:(id)sender
  1006. {
  1007. m_web_view_bridge->on_link_middle_click(m_context_menu_url, {}, 0);
  1008. }
  1009. - (void)copyLink:(id)sender
  1010. {
  1011. auto link = WebView::url_text_to_copy(m_context_menu_url);
  1012. copy_data_to_clipboard(link, NSPasteboardTypeString);
  1013. }
  1014. - (void)copyImage:(id)sender
  1015. {
  1016. auto* bitmap = m_context_menu_bitmap.bitmap();
  1017. if (bitmap == nullptr) {
  1018. return;
  1019. }
  1020. auto png = Gfx::PNGWriter::encode(*bitmap);
  1021. if (png.is_error()) {
  1022. return;
  1023. }
  1024. auto* data = [NSData dataWithBytes:png.value().data() length:png.value().size()];
  1025. auto* pasteBoard = [NSPasteboard generalPasteboard];
  1026. [pasteBoard clearContents];
  1027. [pasteBoard setData:data forType:NSPasteboardTypePNG];
  1028. }
  1029. - (void)toggleMediaPlayState:(id)sender
  1030. {
  1031. m_web_view_bridge->toggle_media_play_state();
  1032. }
  1033. - (void)toggleMediaMuteState:(id)sender
  1034. {
  1035. m_web_view_bridge->toggle_media_mute_state();
  1036. }
  1037. - (void)toggleMediaControlsState:(id)sender
  1038. {
  1039. m_web_view_bridge->toggle_media_controls_state();
  1040. }
  1041. - (void)toggleMediaLoopState:(id)sender
  1042. {
  1043. m_web_view_bridge->toggle_media_loop_state();
  1044. }
  1045. #pragma mark - Properties
  1046. - (NSMenu*)page_context_menu
  1047. {
  1048. if (!_page_context_menu) {
  1049. _page_context_menu = [[NSMenu alloc] initWithTitle:@"Page Context Menu"];
  1050. [_page_context_menu addItem:[[NSMenuItem alloc] initWithTitle:@"Go Back"
  1051. action:@selector(navigateBack:)
  1052. keyEquivalent:@""]];
  1053. [_page_context_menu addItem:[[NSMenuItem alloc] initWithTitle:@"Go Forward"
  1054. action:@selector(navigateForward:)
  1055. keyEquivalent:@""]];
  1056. [_page_context_menu addItem:[[NSMenuItem alloc] initWithTitle:@"Reload"
  1057. action:@selector(reload:)
  1058. keyEquivalent:@""]];
  1059. [_page_context_menu addItem:[NSMenuItem separatorItem]];
  1060. [_page_context_menu addItem:[[NSMenuItem alloc] initWithTitle:@"Copy"
  1061. action:@selector(copy:)
  1062. keyEquivalent:@""]];
  1063. [_page_context_menu addItem:[[NSMenuItem alloc] initWithTitle:@"Paste"
  1064. action:@selector(paste:)
  1065. keyEquivalent:@""]];
  1066. [_page_context_menu addItem:[[NSMenuItem alloc] initWithTitle:@"Select All"
  1067. action:@selector(selectAll:)
  1068. keyEquivalent:@""]];
  1069. [_page_context_menu addItem:[NSMenuItem separatorItem]];
  1070. auto* search_selected_text_menu_item = [[NSMenuItem alloc] initWithTitle:@"Search for <query>"
  1071. action:@selector(searchSelectedText:)
  1072. keyEquivalent:@""];
  1073. [search_selected_text_menu_item setTag:CONTEXT_MENU_SEARCH_SELECTED_TEXT_TAG];
  1074. [_page_context_menu addItem:search_selected_text_menu_item];
  1075. [_page_context_menu addItem:[NSMenuItem separatorItem]];
  1076. [_page_context_menu addItem:[[NSMenuItem alloc] initWithTitle:@"Take Visible Screenshot"
  1077. action:@selector(takeVisibleScreenshot:)
  1078. keyEquivalent:@""]];
  1079. [_page_context_menu addItem:[[NSMenuItem alloc] initWithTitle:@"Take Full Screenshot"
  1080. action:@selector(takeFullScreenshot:)
  1081. keyEquivalent:@""]];
  1082. [_page_context_menu addItem:[NSMenuItem separatorItem]];
  1083. [_page_context_menu addItem:[[NSMenuItem alloc] initWithTitle:@"View Source"
  1084. action:@selector(viewSource:)
  1085. keyEquivalent:@""]];
  1086. [_page_context_menu addItem:[[NSMenuItem alloc] initWithTitle:@"Inspect Element"
  1087. action:@selector(inspectElement:)
  1088. keyEquivalent:@""]];
  1089. }
  1090. return _page_context_menu;
  1091. }
  1092. - (NSMenu*)link_context_menu
  1093. {
  1094. if (!_link_context_menu) {
  1095. _link_context_menu = [[NSMenu alloc] initWithTitle:@"Link Context Menu"];
  1096. [_link_context_menu addItem:[[NSMenuItem alloc] initWithTitle:@"Open"
  1097. action:@selector(openLink:)
  1098. keyEquivalent:@""]];
  1099. [_link_context_menu addItem:[[NSMenuItem alloc] initWithTitle:@"Open in New Tab"
  1100. action:@selector(openLinkInNewTab:)
  1101. keyEquivalent:@""]];
  1102. [_link_context_menu addItem:[NSMenuItem separatorItem]];
  1103. auto* copy_link_menu_item = [[NSMenuItem alloc] initWithTitle:@"Copy URL"
  1104. action:@selector(copyLink:)
  1105. keyEquivalent:@""];
  1106. [copy_link_menu_item setTag:CONTEXT_MENU_COPY_LINK_TAG];
  1107. [_link_context_menu addItem:copy_link_menu_item];
  1108. [_link_context_menu addItem:[NSMenuItem separatorItem]];
  1109. [_link_context_menu addItem:[[NSMenuItem alloc] initWithTitle:@"Inspect Element"
  1110. action:@selector(inspectElement:)
  1111. keyEquivalent:@""]];
  1112. }
  1113. return _link_context_menu;
  1114. }
  1115. - (NSMenu*)image_context_menu
  1116. {
  1117. if (!_image_context_menu) {
  1118. _image_context_menu = [[NSMenu alloc] initWithTitle:@"Image Context Menu"];
  1119. [_image_context_menu addItem:[[NSMenuItem alloc] initWithTitle:@"Open Image"
  1120. action:@selector(openLink:)
  1121. keyEquivalent:@""]];
  1122. [_image_context_menu addItem:[[NSMenuItem alloc] initWithTitle:@"Open Image in New Tab"
  1123. action:@selector(openLinkInNewTab:)
  1124. keyEquivalent:@""]];
  1125. [_image_context_menu addItem:[NSMenuItem separatorItem]];
  1126. [_image_context_menu addItem:[[NSMenuItem alloc] initWithTitle:@"Copy Image"
  1127. action:@selector(copyImage:)
  1128. keyEquivalent:@""]];
  1129. [_image_context_menu addItem:[[NSMenuItem alloc] initWithTitle:@"Copy Image URL"
  1130. action:@selector(copyLink:)
  1131. keyEquivalent:@""]];
  1132. [_image_context_menu addItem:[NSMenuItem separatorItem]];
  1133. [_image_context_menu addItem:[[NSMenuItem alloc] initWithTitle:@"Inspect Element"
  1134. action:@selector(inspectElement:)
  1135. keyEquivalent:@""]];
  1136. }
  1137. return _image_context_menu;
  1138. }
  1139. - (NSMenu*)audio_context_menu
  1140. {
  1141. if (!_audio_context_menu) {
  1142. _audio_context_menu = [[NSMenu alloc] initWithTitle:@"Audio Context Menu"];
  1143. auto* play_pause_menu_item = [[NSMenuItem alloc] initWithTitle:@"Play"
  1144. action:@selector(toggleMediaPlayState:)
  1145. keyEquivalent:@""];
  1146. [play_pause_menu_item setTag:CONTEXT_MENU_PLAY_PAUSE_TAG];
  1147. auto* mute_unmute_menu_item = [[NSMenuItem alloc] initWithTitle:@"Mute"
  1148. action:@selector(toggleMediaMuteState:)
  1149. keyEquivalent:@""];
  1150. [mute_unmute_menu_item setTag:CONTEXT_MENU_MUTE_UNMUTE_TAG];
  1151. auto* controls_menu_item = [[NSMenuItem alloc] initWithTitle:@"Controls"
  1152. action:@selector(toggleMediaControlsState:)
  1153. keyEquivalent:@""];
  1154. [controls_menu_item setTag:CONTEXT_MENU_CONTROLS_TAG];
  1155. auto* loop_menu_item = [[NSMenuItem alloc] initWithTitle:@"Loop"
  1156. action:@selector(toggleMediaLoopState:)
  1157. keyEquivalent:@""];
  1158. [loop_menu_item setTag:CONTEXT_MENU_LOOP_TAG];
  1159. [_audio_context_menu addItem:play_pause_menu_item];
  1160. [_audio_context_menu addItem:mute_unmute_menu_item];
  1161. [_audio_context_menu addItem:controls_menu_item];
  1162. [_audio_context_menu addItem:loop_menu_item];
  1163. [_audio_context_menu addItem:[NSMenuItem separatorItem]];
  1164. [_audio_context_menu addItem:[[NSMenuItem alloc] initWithTitle:@"Open Audio"
  1165. action:@selector(openLink:)
  1166. keyEquivalent:@""]];
  1167. [_audio_context_menu addItem:[[NSMenuItem alloc] initWithTitle:@"Open Audio in New Tab"
  1168. action:@selector(openLinkInNewTab:)
  1169. keyEquivalent:@""]];
  1170. [_audio_context_menu addItem:[NSMenuItem separatorItem]];
  1171. [_audio_context_menu addItem:[[NSMenuItem alloc] initWithTitle:@"Copy Audio URL"
  1172. action:@selector(copyLink:)
  1173. keyEquivalent:@""]];
  1174. [_audio_context_menu addItem:[NSMenuItem separatorItem]];
  1175. [_audio_context_menu addItem:[[NSMenuItem alloc] initWithTitle:@"Inspect Element"
  1176. action:@selector(inspectElement:)
  1177. keyEquivalent:@""]];
  1178. }
  1179. return _audio_context_menu;
  1180. }
  1181. - (NSMenu*)video_context_menu
  1182. {
  1183. if (!_video_context_menu) {
  1184. _video_context_menu = [[NSMenu alloc] initWithTitle:@"Video Context Menu"];
  1185. auto* play_pause_menu_item = [[NSMenuItem alloc] initWithTitle:@"Play"
  1186. action:@selector(toggleMediaPlayState:)
  1187. keyEquivalent:@""];
  1188. [play_pause_menu_item setTag:CONTEXT_MENU_PLAY_PAUSE_TAG];
  1189. auto* mute_unmute_menu_item = [[NSMenuItem alloc] initWithTitle:@"Mute"
  1190. action:@selector(toggleMediaMuteState:)
  1191. keyEquivalent:@""];
  1192. [mute_unmute_menu_item setTag:CONTEXT_MENU_MUTE_UNMUTE_TAG];
  1193. auto* controls_menu_item = [[NSMenuItem alloc] initWithTitle:@"Controls"
  1194. action:@selector(toggleMediaControlsState:)
  1195. keyEquivalent:@""];
  1196. [controls_menu_item setTag:CONTEXT_MENU_CONTROLS_TAG];
  1197. auto* loop_menu_item = [[NSMenuItem alloc] initWithTitle:@"Loop"
  1198. action:@selector(toggleMediaLoopState:)
  1199. keyEquivalent:@""];
  1200. [loop_menu_item setTag:CONTEXT_MENU_LOOP_TAG];
  1201. [_video_context_menu addItem:play_pause_menu_item];
  1202. [_video_context_menu addItem:mute_unmute_menu_item];
  1203. [_video_context_menu addItem:controls_menu_item];
  1204. [_video_context_menu addItem:loop_menu_item];
  1205. [_video_context_menu addItem:[NSMenuItem separatorItem]];
  1206. [_video_context_menu addItem:[[NSMenuItem alloc] initWithTitle:@"Open Video"
  1207. action:@selector(openLink:)
  1208. keyEquivalent:@""]];
  1209. [_video_context_menu addItem:[[NSMenuItem alloc] initWithTitle:@"Open Video in New Tab"
  1210. action:@selector(openLinkInNewTab:)
  1211. keyEquivalent:@""]];
  1212. [_video_context_menu addItem:[NSMenuItem separatorItem]];
  1213. [_video_context_menu addItem:[[NSMenuItem alloc] initWithTitle:@"Copy Video URL"
  1214. action:@selector(copyLink:)
  1215. keyEquivalent:@""]];
  1216. [_video_context_menu addItem:[NSMenuItem separatorItem]];
  1217. [_video_context_menu addItem:[[NSMenuItem alloc] initWithTitle:@"Inspect Element"
  1218. action:@selector(inspectElement:)
  1219. keyEquivalent:@""]];
  1220. }
  1221. return _video_context_menu;
  1222. }
  1223. - (NSTextField*)status_label
  1224. {
  1225. if (!_status_label) {
  1226. _status_label = [NSTextField labelWithString:@""];
  1227. [_status_label setDrawsBackground:YES];
  1228. [_status_label setBordered:YES];
  1229. [_status_label setHidden:YES];
  1230. [self addSubview:_status_label];
  1231. }
  1232. return _status_label;
  1233. }
  1234. #pragma mark - NSView
  1235. - (void)drawRect:(NSRect)rect
  1236. {
  1237. auto paintable = m_web_view_bridge->paintable();
  1238. if (!paintable.has_value()) {
  1239. [super drawRect:rect];
  1240. return;
  1241. }
  1242. auto [bitmap, bitmap_size] = *paintable;
  1243. VERIFY(bitmap.format() == Gfx::BitmapFormat::BGRA8888);
  1244. static constexpr size_t BITS_PER_COMPONENT = 8;
  1245. static constexpr size_t BITS_PER_PIXEL = 32;
  1246. auto* context = [[NSGraphicsContext currentContext] CGContext];
  1247. CGContextSaveGState(context);
  1248. auto device_pixel_ratio = m_web_view_bridge->device_pixel_ratio();
  1249. auto inverse_device_pixel_ratio = m_web_view_bridge->inverse_device_pixel_ratio();
  1250. CGContextScaleCTM(context, inverse_device_pixel_ratio, inverse_device_pixel_ratio);
  1251. auto* provider = CGDataProviderCreateWithData(nil, bitmap.scanline_u8(0), bitmap.size_in_bytes(), nil);
  1252. auto image_rect = CGRectMake(rect.origin.x * device_pixel_ratio, rect.origin.y * device_pixel_ratio, bitmap_size.width(), bitmap_size.height());
  1253. static auto color_space = CGColorSpaceCreateWithName(kCGColorSpaceSRGB);
  1254. // Ideally, this would be NSBitmapImageRep, but the equivalent factory initWithBitmapDataPlanes: does
  1255. // not seem to actually respect endianness. We need NSBitmapFormatThirtyTwoBitLittleEndian, but the
  1256. // resulting image is always big endian. CGImageCreate actually does respect the endianness.
  1257. auto* bitmap_image = CGImageCreate(
  1258. bitmap_size.width(),
  1259. bitmap_size.height(),
  1260. BITS_PER_COMPONENT,
  1261. BITS_PER_PIXEL,
  1262. bitmap.pitch(),
  1263. color_space,
  1264. kCGBitmapByteOrder32Little | kCGImageAlphaFirst,
  1265. provider,
  1266. nil,
  1267. NO,
  1268. kCGRenderingIntentDefault);
  1269. auto* image = [[NSImage alloc] initWithCGImage:bitmap_image size:NSZeroSize];
  1270. [image drawInRect:image_rect];
  1271. CGContextRestoreGState(context);
  1272. CGDataProviderRelease(provider);
  1273. CGImageRelease(bitmap_image);
  1274. [super drawRect:rect];
  1275. }
  1276. - (void)viewDidMoveToWindow
  1277. {
  1278. [super viewDidMoveToWindow];
  1279. [self handleResize];
  1280. }
  1281. - (void)viewDidEndLiveResize
  1282. {
  1283. [super viewDidEndLiveResize];
  1284. [self handleResize];
  1285. }
  1286. - (void)viewDidChangeEffectiveAppearance
  1287. {
  1288. m_web_view_bridge->update_palette();
  1289. }
  1290. - (BOOL)isFlipped
  1291. {
  1292. // The origin of a NSScrollView is the lower-left corner, with the y-axis extending upwards. Instead,
  1293. // we want the origin to be the top-left corner, with the y-axis extending downward.
  1294. return YES;
  1295. }
  1296. - (void)mouseMoved:(NSEvent*)event
  1297. {
  1298. auto mouse_event = Ladybird::ns_event_to_mouse_event(Web::MouseEvent::Type::MouseMove, event, self, [self scrollView], Web::UIEvents::MouseButton::None);
  1299. m_web_view_bridge->enqueue_input_event(move(mouse_event));
  1300. }
  1301. - (void)scrollWheel:(NSEvent*)event
  1302. {
  1303. auto mouse_event = Ladybird::ns_event_to_mouse_event(Web::MouseEvent::Type::MouseWheel, event, self, [self scrollView], Web::UIEvents::MouseButton::Middle);
  1304. m_web_view_bridge->enqueue_input_event(move(mouse_event));
  1305. }
  1306. - (void)mouseDown:(NSEvent*)event
  1307. {
  1308. [[self window] makeFirstResponder:self];
  1309. auto mouse_event = Ladybird::ns_event_to_mouse_event(Web::MouseEvent::Type::MouseDown, event, self, [self scrollView], Web::UIEvents::MouseButton::Primary);
  1310. m_web_view_bridge->enqueue_input_event(move(mouse_event));
  1311. }
  1312. - (void)mouseUp:(NSEvent*)event
  1313. {
  1314. auto mouse_event = Ladybird::ns_event_to_mouse_event(Web::MouseEvent::Type::MouseUp, event, self, [self scrollView], Web::UIEvents::MouseButton::Primary);
  1315. m_web_view_bridge->enqueue_input_event(move(mouse_event));
  1316. }
  1317. - (void)mouseDragged:(NSEvent*)event
  1318. {
  1319. auto mouse_event = Ladybird::ns_event_to_mouse_event(Web::MouseEvent::Type::MouseMove, event, self, [self scrollView], Web::UIEvents::MouseButton::Primary);
  1320. m_web_view_bridge->enqueue_input_event(move(mouse_event));
  1321. }
  1322. - (void)rightMouseDown:(NSEvent*)event
  1323. {
  1324. [[self window] makeFirstResponder:self];
  1325. auto mouse_event = Ladybird::ns_event_to_mouse_event(Web::MouseEvent::Type::MouseDown, event, self, [self scrollView], Web::UIEvents::MouseButton::Secondary);
  1326. m_web_view_bridge->enqueue_input_event(move(mouse_event));
  1327. }
  1328. - (void)rightMouseUp:(NSEvent*)event
  1329. {
  1330. auto mouse_event = Ladybird::ns_event_to_mouse_event(Web::MouseEvent::Type::MouseUp, event, self, [self scrollView], Web::UIEvents::MouseButton::Secondary);
  1331. m_web_view_bridge->enqueue_input_event(move(mouse_event));
  1332. }
  1333. - (void)rightMouseDragged:(NSEvent*)event
  1334. {
  1335. auto mouse_event = Ladybird::ns_event_to_mouse_event(Web::MouseEvent::Type::MouseMove, event, self, [self scrollView], Web::UIEvents::MouseButton::Secondary);
  1336. m_web_view_bridge->enqueue_input_event(move(mouse_event));
  1337. }
  1338. - (void)otherMouseDown:(NSEvent*)event
  1339. {
  1340. if (event.buttonNumber != 2)
  1341. return;
  1342. [[self window] makeFirstResponder:self];
  1343. auto mouse_event = Ladybird::ns_event_to_mouse_event(Web::MouseEvent::Type::MouseDown, event, self, [self scrollView], Web::UIEvents::MouseButton::Middle);
  1344. m_web_view_bridge->enqueue_input_event(move(mouse_event));
  1345. }
  1346. - (void)otherMouseUp:(NSEvent*)event
  1347. {
  1348. if (event.buttonNumber != 2)
  1349. return;
  1350. auto mouse_event = Ladybird::ns_event_to_mouse_event(Web::MouseEvent::Type::MouseUp, event, self, [self scrollView], Web::UIEvents::MouseButton::Middle);
  1351. m_web_view_bridge->enqueue_input_event(move(mouse_event));
  1352. }
  1353. - (void)otherMouseDragged:(NSEvent*)event
  1354. {
  1355. if (event.buttonNumber != 2)
  1356. return;
  1357. auto mouse_event = Ladybird::ns_event_to_mouse_event(Web::MouseEvent::Type::MouseMove, event, self, [self scrollView], Web::UIEvents::MouseButton::Middle);
  1358. m_web_view_bridge->enqueue_input_event(move(mouse_event));
  1359. }
  1360. - (BOOL)performKeyEquivalent:(NSEvent*)event
  1361. {
  1362. if ([event window] != [self window]) {
  1363. return NO;
  1364. }
  1365. if ([[self window] firstResponder] != self) {
  1366. return NO;
  1367. }
  1368. if (self.event_being_redispatched == event) {
  1369. return NO;
  1370. }
  1371. [self keyDown:event];
  1372. return YES;
  1373. }
  1374. - (void)keyDown:(NSEvent*)event
  1375. {
  1376. if (self.event_being_redispatched == event) {
  1377. return;
  1378. }
  1379. auto key_event = Ladybird::ns_event_to_key_event(Web::KeyEvent::Type::KeyDown, event);
  1380. m_web_view_bridge->enqueue_input_event(move(key_event));
  1381. }
  1382. - (void)keyUp:(NSEvent*)event
  1383. {
  1384. if (self.event_being_redispatched == event) {
  1385. return;
  1386. }
  1387. auto key_event = Ladybird::ns_event_to_key_event(Web::KeyEvent::Type::KeyUp, event);
  1388. m_web_view_bridge->enqueue_input_event(move(key_event));
  1389. }
  1390. - (void)flagsChanged:(NSEvent*)event
  1391. {
  1392. if (self.event_being_redispatched == event) {
  1393. return;
  1394. }
  1395. auto enqueue_event_if_needed = [&](auto flag) {
  1396. auto is_flag_set = [&](auto flags) { return (flags & flag) != 0; };
  1397. Web::KeyEvent::Type type;
  1398. if (is_flag_set(event.modifierFlags) && !is_flag_set(m_modifier_flags)) {
  1399. type = Web::KeyEvent::Type::KeyDown;
  1400. } else if (!is_flag_set(event.modifierFlags) && is_flag_set(m_modifier_flags)) {
  1401. type = Web::KeyEvent::Type::KeyUp;
  1402. } else {
  1403. return;
  1404. }
  1405. auto key_event = Ladybird::ns_event_to_key_event(type, event);
  1406. m_web_view_bridge->enqueue_input_event(move(key_event));
  1407. };
  1408. enqueue_event_if_needed(NSEventModifierFlagShift);
  1409. enqueue_event_if_needed(NSEventModifierFlagControl);
  1410. enqueue_event_if_needed(NSEventModifierFlagOption);
  1411. enqueue_event_if_needed(NSEventModifierFlagCommand);
  1412. m_modifier_flags = event.modifierFlags;
  1413. }
  1414. #pragma mark - NSDraggingDestination
  1415. - (NSDragOperation)draggingEntered:(id<NSDraggingInfo>)event
  1416. {
  1417. auto drag_event = Ladybird::ns_event_to_drag_event(Web::DragEvent::Type::DragStart, event, self);
  1418. m_web_view_bridge->enqueue_input_event(move(drag_event));
  1419. return NSDragOperationCopy;
  1420. }
  1421. - (NSDragOperation)draggingUpdated:(id<NSDraggingInfo>)event
  1422. {
  1423. auto drag_event = Ladybird::ns_event_to_drag_event(Web::DragEvent::Type::DragMove, event, self);
  1424. m_web_view_bridge->enqueue_input_event(move(drag_event));
  1425. return NSDragOperationCopy;
  1426. }
  1427. - (void)draggingExited:(id<NSDraggingInfo>)event
  1428. {
  1429. auto drag_event = Ladybird::ns_event_to_drag_event(Web::DragEvent::Type::DragEnd, event, self);
  1430. m_web_view_bridge->enqueue_input_event(move(drag_event));
  1431. }
  1432. - (BOOL)performDragOperation:(id<NSDraggingInfo>)event
  1433. {
  1434. auto drag_event = Ladybird::ns_event_to_drag_event(Web::DragEvent::Type::Drop, event, self);
  1435. m_web_view_bridge->enqueue_input_event(move(drag_event));
  1436. return YES;
  1437. }
  1438. - (BOOL)wantsPeriodicDraggingUpdates
  1439. {
  1440. return NO;
  1441. }
  1442. @end