LadybirdWebView.mm 47 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244
  1. /*
  2. * Copyright (c) 2023, 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 <AK/URL.h>
  9. #include <LibGfx/ImageFormats/PNGWriter.h>
  10. #include <LibGfx/ShareableBitmap.h>
  11. #include <LibWebView/SearchEngine.h>
  12. #include <LibWebView/SourceHighlighter.h>
  13. #include <LibWebView/URL.h>
  14. #include <UI/LadybirdWebViewBridge.h>
  15. #import <Application/ApplicationDelegate.h>
  16. #import <UI/Event.h>
  17. #import <UI/LadybirdWebView.h>
  18. #import <Utilities/Conversions.h>
  19. #if !__has_feature(objc_arc)
  20. # error "This project requires ARC"
  21. #endif
  22. static constexpr NSInteger CONTEXT_MENU_PLAY_PAUSE_TAG = 1;
  23. static constexpr NSInteger CONTEXT_MENU_MUTE_UNMUTE_TAG = 2;
  24. static constexpr NSInteger CONTEXT_MENU_CONTROLS_TAG = 3;
  25. static constexpr NSInteger CONTEXT_MENU_LOOP_TAG = 4;
  26. static constexpr NSInteger CONTEXT_MENU_SEARCH_SELECTED_TEXT_TAG = 5;
  27. static constexpr NSInteger CONTEXT_MENU_COPY_LINK_TAG = 6;
  28. // Calls to [NSCursor hide] and [NSCursor unhide] must be balanced. We use this struct to ensure
  29. // we only call [NSCursor hide] once and to ensure that we do call [NSCursor unhide].
  30. // https://developer.apple.com/documentation/appkit/nscursor#1651301
  31. struct HideCursor {
  32. HideCursor()
  33. {
  34. [NSCursor hide];
  35. }
  36. ~HideCursor()
  37. {
  38. [NSCursor unhide];
  39. }
  40. };
  41. @interface LadybirdWebView ()
  42. {
  43. OwnPtr<Ladybird::WebViewBridge> m_web_view_bridge;
  44. URL m_context_menu_url;
  45. Gfx::ShareableBitmap m_context_menu_bitmap;
  46. Optional<String> m_context_menu_search_text;
  47. Optional<HideCursor> m_hidden_cursor;
  48. }
  49. @property (nonatomic, weak) id<LadybirdWebViewObserver> observer;
  50. @property (nonatomic, strong) NSMenu* page_context_menu;
  51. @property (nonatomic, strong) NSMenu* link_context_menu;
  52. @property (nonatomic, strong) NSMenu* image_context_menu;
  53. @property (nonatomic, strong) NSMenu* audio_context_menu;
  54. @property (nonatomic, strong) NSMenu* video_context_menu;
  55. @property (nonatomic, strong) NSMenu* select_dropdown;
  56. @property (nonatomic, strong) NSTextField* status_label;
  57. @property (nonatomic, strong) NSAlert* dialog;
  58. @end
  59. @implementation LadybirdWebView
  60. @synthesize page_context_menu = _page_context_menu;
  61. @synthesize link_context_menu = _link_context_menu;
  62. @synthesize image_context_menu = _image_context_menu;
  63. @synthesize audio_context_menu = _audio_context_menu;
  64. @synthesize video_context_menu = _video_context_menu;
  65. @synthesize status_label = _status_label;
  66. - (instancetype)init:(id<LadybirdWebViewObserver>)observer
  67. {
  68. if (self = [super init]) {
  69. self.observer = observer;
  70. auto* delegate = (ApplicationDelegate*)[NSApp delegate];
  71. auto* screens = [NSScreen screens];
  72. Vector<Web::DevicePixelRect> screen_rects;
  73. screen_rects.ensure_capacity([screens count]);
  74. for (id screen in screens) {
  75. auto screen_rect = Ladybird::ns_rect_to_gfx_rect([screen frame]).to_type<Web::DevicePixels>();
  76. screen_rects.unchecked_append(screen_rect);
  77. }
  78. // This returns device pixel ratio of the screen the window is opened in
  79. auto device_pixel_ratio = [[NSScreen mainScreen] backingScaleFactor];
  80. m_web_view_bridge = MUST(Ladybird::WebViewBridge::create(move(screen_rects), device_pixel_ratio, [delegate webContentOptions], [delegate webdriverContentIPCPath], [delegate preferredColorScheme]));
  81. [self setWebViewCallbacks];
  82. auto* area = [[NSTrackingArea alloc] initWithRect:[self bounds]
  83. options:NSTrackingActiveInKeyWindow | NSTrackingInVisibleRect | NSTrackingMouseMoved
  84. owner:self
  85. userInfo:nil];
  86. [self addTrackingArea:area];
  87. }
  88. return self;
  89. }
  90. #pragma mark - Public methods
  91. - (void)loadURL:(URL const&)url
  92. {
  93. m_web_view_bridge->load(url);
  94. }
  95. - (void)loadHTML:(StringView)html
  96. {
  97. m_web_view_bridge->load_html(html);
  98. }
  99. - (WebView::ViewImplementation&)view
  100. {
  101. return *m_web_view_bridge;
  102. }
  103. - (String const&)handle
  104. {
  105. return m_web_view_bridge->handle();
  106. }
  107. - (void)handleResize
  108. {
  109. [self updateViewportRect:Ladybird::WebViewBridge::ForResize::Yes];
  110. [self updateStatusLabelPosition];
  111. }
  112. - (void)handleDevicePixelRatioChange
  113. {
  114. m_web_view_bridge->set_device_pixel_ratio([[self window] backingScaleFactor]);
  115. [self updateViewportRect:Ladybird::WebViewBridge::ForResize::Yes];
  116. [self updateStatusLabelPosition];
  117. }
  118. - (void)handleScroll
  119. {
  120. [self updateViewportRect:Ladybird::WebViewBridge::ForResize::No];
  121. [self updateStatusLabelPosition];
  122. }
  123. - (void)handleVisibility:(BOOL)is_visible
  124. {
  125. m_web_view_bridge->set_system_visibility_state(is_visible);
  126. }
  127. - (void)zoomIn
  128. {
  129. m_web_view_bridge->zoom_in();
  130. }
  131. - (void)zoomOut
  132. {
  133. m_web_view_bridge->zoom_out();
  134. }
  135. - (void)resetZoom
  136. {
  137. m_web_view_bridge->reset_zoom();
  138. }
  139. - (float)zoomLevel
  140. {
  141. return m_web_view_bridge->zoom_level();
  142. }
  143. - (void)setPreferredColorScheme:(Web::CSS::PreferredColorScheme)color_scheme
  144. {
  145. m_web_view_bridge->set_preferred_color_scheme(color_scheme);
  146. }
  147. - (void)debugRequest:(DeprecatedString const&)request argument:(DeprecatedString const&)argument
  148. {
  149. m_web_view_bridge->debug_request(request, argument);
  150. }
  151. - (void)viewSource
  152. {
  153. m_web_view_bridge->get_source();
  154. }
  155. #pragma mark - Private methods
  156. static void copy_data_to_clipboard(StringView data, NSPasteboardType pasteboard_type)
  157. {
  158. auto* ns_data = Ladybird::string_to_ns_data(data);
  159. auto* pasteBoard = [NSPasteboard generalPasteboard];
  160. [pasteBoard clearContents];
  161. [pasteBoard setData:ns_data forType:pasteboard_type];
  162. }
  163. - (void)updateViewportRect:(Ladybird::WebViewBridge::ForResize)for_resize
  164. {
  165. auto content_rect = [self frame];
  166. auto document_rect = [[self documentView] frame];
  167. auto device_pixel_ratio = m_web_view_bridge->device_pixel_ratio();
  168. auto position = [&](auto content_size, auto document_size, auto scroll) {
  169. return max(0, (document_size - content_size) * device_pixel_ratio * scroll);
  170. };
  171. auto horizontal_scroll = [[[self scrollView] horizontalScroller] floatValue];
  172. auto vertical_scroll = [[[self scrollView] verticalScroller] floatValue];
  173. auto ns_viewport_rect = NSMakeRect(
  174. position(content_rect.size.width, document_rect.size.width, horizontal_scroll),
  175. position(content_rect.size.height, document_rect.size.height, vertical_scroll),
  176. content_rect.size.width,
  177. content_rect.size.height);
  178. auto viewport_rect = Ladybird::ns_rect_to_gfx_rect(ns_viewport_rect);
  179. m_web_view_bridge->set_viewport_rect(viewport_rect, for_resize);
  180. }
  181. - (void)updateStatusLabelPosition
  182. {
  183. static constexpr CGFloat LABEL_INSET = 10;
  184. if (_status_label == nil || [[self status_label] isHidden]) {
  185. return;
  186. }
  187. auto visible_rect = [self visibleRect];
  188. auto status_label_rect = [self.status_label frame];
  189. auto position = NSMakePoint(LABEL_INSET, visible_rect.origin.y + visible_rect.size.height - status_label_rect.size.height - LABEL_INSET);
  190. [self.status_label setFrameOrigin:position];
  191. }
  192. - (void)setWebViewCallbacks
  193. {
  194. m_web_view_bridge->on_did_layout = [self](auto content_size) {
  195. auto inverse_device_pixel_ratio = m_web_view_bridge->inverse_device_pixel_ratio();
  196. [[self documentView] setFrameSize:NSMakeSize(content_size.width() * inverse_device_pixel_ratio, content_size.height() * inverse_device_pixel_ratio)];
  197. };
  198. m_web_view_bridge->on_ready_to_paint = [self]() {
  199. [self setNeedsDisplay:YES];
  200. };
  201. m_web_view_bridge->on_new_tab = [self](auto activate_tab) {
  202. return [self.observer onCreateNewTab:"about:blank"sv activateTab:activate_tab];
  203. };
  204. m_web_view_bridge->on_activate_tab = [self]() {
  205. [[self window] orderFront:nil];
  206. };
  207. m_web_view_bridge->on_close = [self]() {
  208. [[self window] close];
  209. };
  210. m_web_view_bridge->on_load_start = [self](auto const& url, bool is_redirect) {
  211. [self.observer onLoadStart:url isRedirect:is_redirect];
  212. if (_status_label != nil) {
  213. [self.status_label setHidden:YES];
  214. }
  215. };
  216. m_web_view_bridge->on_load_finish = [self](auto const& url) {
  217. [self.observer onLoadFinish:url];
  218. };
  219. m_web_view_bridge->on_title_change = [self](auto const& title) {
  220. [self.observer onTitleChange:title];
  221. };
  222. m_web_view_bridge->on_favicon_change = [self](auto const& bitmap) {
  223. [self.observer onFaviconChange:bitmap];
  224. };
  225. m_web_view_bridge->on_scroll = [self](auto position) {
  226. auto content_rect = [self frame];
  227. auto document_rect = [[self documentView] frame];
  228. auto ns_position = Ladybird::gfx_point_to_ns_point(position);
  229. ns_position.x = max(ns_position.x, document_rect.origin.x);
  230. ns_position.x = min(ns_position.x, document_rect.size.width - content_rect.size.width);
  231. ns_position.y = max(ns_position.y, document_rect.origin.y);
  232. ns_position.y = min(ns_position.y, document_rect.size.height - content_rect.size.height);
  233. [self scrollToPoint:ns_position];
  234. [[self scrollView] reflectScrolledClipView:self];
  235. [self updateViewportRect:Ladybird::WebViewBridge::ForResize::No];
  236. };
  237. m_web_view_bridge->on_cursor_change = [self](auto cursor) {
  238. if (cursor == Gfx::StandardCursor::Hidden) {
  239. if (!m_hidden_cursor.has_value()) {
  240. m_hidden_cursor.emplace();
  241. }
  242. return;
  243. }
  244. m_hidden_cursor.clear();
  245. switch (cursor) {
  246. case Gfx::StandardCursor::Arrow:
  247. [[NSCursor arrowCursor] set];
  248. break;
  249. case Gfx::StandardCursor::Crosshair:
  250. [[NSCursor crosshairCursor] set];
  251. break;
  252. case Gfx::StandardCursor::IBeam:
  253. [[NSCursor IBeamCursor] set];
  254. break;
  255. case Gfx::StandardCursor::ResizeHorizontal:
  256. [[NSCursor resizeLeftRightCursor] set];
  257. break;
  258. case Gfx::StandardCursor::ResizeVertical:
  259. [[NSCursor resizeUpDownCursor] set];
  260. break;
  261. case Gfx::StandardCursor::ResizeDiagonalTLBR:
  262. // FIXME: AppKit does not have a corresponding cursor, so we should make one.
  263. [[NSCursor arrowCursor] set];
  264. break;
  265. case Gfx::StandardCursor::ResizeDiagonalBLTR:
  266. // FIXME: AppKit does not have a corresponding cursor, so we should make one.
  267. [[NSCursor arrowCursor] set];
  268. break;
  269. case Gfx::StandardCursor::ResizeColumn:
  270. [[NSCursor resizeLeftRightCursor] set];
  271. break;
  272. case Gfx::StandardCursor::ResizeRow:
  273. [[NSCursor resizeUpDownCursor] set];
  274. break;
  275. case Gfx::StandardCursor::Hand:
  276. [[NSCursor pointingHandCursor] set];
  277. break;
  278. case Gfx::StandardCursor::Help:
  279. // FIXME: AppKit does not have a corresponding cursor, so we should make one.
  280. [[NSCursor arrowCursor] set];
  281. break;
  282. case Gfx::StandardCursor::Drag:
  283. [[NSCursor closedHandCursor] set];
  284. break;
  285. case Gfx::StandardCursor::DragCopy:
  286. [[NSCursor dragCopyCursor] set];
  287. break;
  288. case Gfx::StandardCursor::Move:
  289. // FIXME: AppKit does not have a corresponding cursor, so we should make one.
  290. [[NSCursor dragCopyCursor] set];
  291. break;
  292. case Gfx::StandardCursor::Wait:
  293. // FIXME: AppKit does not have a corresponding cursor, so we should make one.
  294. [[NSCursor arrowCursor] set];
  295. break;
  296. case Gfx::StandardCursor::Disallowed:
  297. // FIXME: AppKit does not have a corresponding cursor, so we should make one.
  298. [[NSCursor arrowCursor] set];
  299. break;
  300. case Gfx::StandardCursor::Eyedropper:
  301. // FIXME: AppKit does not have a corresponding cursor, so we should make one.
  302. [[NSCursor arrowCursor] set];
  303. break;
  304. case Gfx::StandardCursor::Zoom:
  305. // FIXME: AppKit does not have a corresponding cursor, so we should make one.
  306. [[NSCursor arrowCursor] set];
  307. break;
  308. default:
  309. break;
  310. }
  311. };
  312. m_web_view_bridge->on_zoom_level_changed = [self]() {
  313. [self updateViewportRect:Ladybird::WebViewBridge::ForResize::Yes];
  314. };
  315. m_web_view_bridge->on_navigate_back = [self]() {
  316. [self.observer onNavigateBack];
  317. };
  318. m_web_view_bridge->on_navigate_forward = [self]() {
  319. [self.observer onNavigateForward];
  320. };
  321. m_web_view_bridge->on_refresh = [self]() {
  322. [self.observer onReload];
  323. };
  324. m_web_view_bridge->on_enter_tooltip_area = [self](auto, auto const& tooltip) {
  325. self.toolTip = Ladybird::string_to_ns_string(tooltip);
  326. };
  327. m_web_view_bridge->on_leave_tooltip_area = [self]() {
  328. self.toolTip = nil;
  329. };
  330. m_web_view_bridge->on_link_hover = [self](auto const& url) {
  331. auto* url_string = Ladybird::string_to_ns_string(url.serialize());
  332. [self.status_label setStringValue:url_string];
  333. [self.status_label sizeToFit];
  334. [self.status_label setHidden:NO];
  335. [self updateStatusLabelPosition];
  336. };
  337. m_web_view_bridge->on_link_unhover = [self]() {
  338. [self.status_label setHidden:YES];
  339. };
  340. m_web_view_bridge->on_link_click = [self](auto const& url, auto const& target, unsigned modifiers) {
  341. if (modifiers == Mod_Super) {
  342. [self.observer onCreateNewTab:url activateTab:Web::HTML::ActivateTab::No];
  343. } else if (target == "_blank"sv) {
  344. [self.observer onCreateNewTab:url activateTab:Web::HTML::ActivateTab::Yes];
  345. } else {
  346. [self.observer loadURL:url];
  347. }
  348. };
  349. m_web_view_bridge->on_link_middle_click = [self](auto url, auto, unsigned) {
  350. [self.observer onCreateNewTab:url activateTab:Web::HTML::ActivateTab::No];
  351. };
  352. m_web_view_bridge->on_context_menu_request = [self](auto position) {
  353. auto* search_selected_text_menu_item = [self.page_context_menu itemWithTag:CONTEXT_MENU_SEARCH_SELECTED_TEXT_TAG];
  354. auto selected_text = self.observer
  355. ? m_web_view_bridge->selected_text_with_whitespace_collapsed()
  356. : OptionalNone {};
  357. TemporaryChange change_url { m_context_menu_search_text, move(selected_text) };
  358. if (m_context_menu_search_text.has_value()) {
  359. auto* delegate = (ApplicationDelegate*)[NSApp delegate];
  360. auto action_text = WebView::format_search_query_for_display([delegate searchEngine].query_url, *m_context_menu_search_text);
  361. [search_selected_text_menu_item setTitle:Ladybird::string_to_ns_string(action_text)];
  362. [search_selected_text_menu_item setHidden:NO];
  363. } else {
  364. [search_selected_text_menu_item setHidden:YES];
  365. }
  366. auto* event = Ladybird::create_context_menu_mouse_event(self, position);
  367. [NSMenu popUpContextMenu:self.page_context_menu withEvent:event forView:self];
  368. };
  369. m_web_view_bridge->on_link_context_menu_request = [self](auto const& url, auto position) {
  370. TemporaryChange change_url { m_context_menu_url, url };
  371. auto* copy_link_menu_item = [self.link_context_menu itemWithTag:CONTEXT_MENU_COPY_LINK_TAG];
  372. switch (WebView::url_type(url)) {
  373. case WebView::URLType::Email:
  374. [copy_link_menu_item setTitle:@"Copy Email Address"];
  375. break;
  376. case WebView::URLType::Telephone:
  377. [copy_link_menu_item setTitle:@"Copy Phone Number"];
  378. break;
  379. case WebView::URLType::Other:
  380. [copy_link_menu_item setTitle:@"Copy URL"];
  381. break;
  382. }
  383. auto* event = Ladybird::create_context_menu_mouse_event(self, position);
  384. [NSMenu popUpContextMenu:self.link_context_menu withEvent:event forView:self];
  385. };
  386. m_web_view_bridge->on_image_context_menu_request = [self](auto const& url, auto position, auto const& bitmap) {
  387. TemporaryChange change_url { m_context_menu_url, url };
  388. TemporaryChange change_bitmap { m_context_menu_bitmap, bitmap };
  389. auto* event = Ladybird::create_context_menu_mouse_event(self, position);
  390. [NSMenu popUpContextMenu:self.image_context_menu withEvent:event forView:self];
  391. };
  392. m_web_view_bridge->on_media_context_menu_request = [self](auto position, auto const& menu) {
  393. TemporaryChange change_url { m_context_menu_url, menu.media_url };
  394. auto* context_menu = menu.is_video ? self.video_context_menu : self.audio_context_menu;
  395. auto* play_pause_menu_item = [context_menu itemWithTag:CONTEXT_MENU_PLAY_PAUSE_TAG];
  396. auto* mute_unmute_menu_item = [context_menu itemWithTag:CONTEXT_MENU_MUTE_UNMUTE_TAG];
  397. auto* controls_menu_item = [context_menu itemWithTag:CONTEXT_MENU_CONTROLS_TAG];
  398. auto* loop_menu_item = [context_menu itemWithTag:CONTEXT_MENU_LOOP_TAG];
  399. if (menu.is_playing) {
  400. [play_pause_menu_item setTitle:@"Pause"];
  401. } else {
  402. [play_pause_menu_item setTitle:@"Play"];
  403. }
  404. if (menu.is_muted) {
  405. [mute_unmute_menu_item setTitle:@"Unmute"];
  406. } else {
  407. [mute_unmute_menu_item setTitle:@"Mute"];
  408. }
  409. auto controls_state = menu.has_user_agent_controls ? NSControlStateValueOn : NSControlStateValueOff;
  410. [controls_menu_item setState:controls_state];
  411. auto loop_state = menu.is_looping ? NSControlStateValueOn : NSControlStateValueOff;
  412. [loop_menu_item setState:loop_state];
  413. auto* event = Ladybird::create_context_menu_mouse_event(self, position);
  414. [NSMenu popUpContextMenu:context_menu withEvent:event forView:self];
  415. };
  416. m_web_view_bridge->on_request_alert = [self](auto const& message) {
  417. auto* ns_message = Ladybird::string_to_ns_string(message);
  418. self.dialog = [[NSAlert alloc] init];
  419. [self.dialog setMessageText:ns_message];
  420. [self.dialog beginSheetModalForWindow:[self window]
  421. completionHandler:^(NSModalResponse) {
  422. m_web_view_bridge->alert_closed();
  423. self.dialog = nil;
  424. }];
  425. };
  426. m_web_view_bridge->on_request_confirm = [self](auto const& message) {
  427. auto* ns_message = Ladybird::string_to_ns_string(message);
  428. self.dialog = [[NSAlert alloc] init];
  429. [[self.dialog addButtonWithTitle:@"OK"] setTag:NSModalResponseOK];
  430. [[self.dialog addButtonWithTitle:@"Cancel"] setTag:NSModalResponseCancel];
  431. [self.dialog setMessageText:ns_message];
  432. [self.dialog beginSheetModalForWindow:[self window]
  433. completionHandler:^(NSModalResponse response) {
  434. m_web_view_bridge->confirm_closed(response == NSModalResponseOK);
  435. self.dialog = nil;
  436. }];
  437. };
  438. m_web_view_bridge->on_request_prompt = [self](auto const& message, auto const& default_) {
  439. auto* ns_message = Ladybird::string_to_ns_string(message);
  440. auto* ns_default = Ladybird::string_to_ns_string(default_);
  441. auto* input = [[NSTextField alloc] initWithFrame:NSMakeRect(0, 0, 200, 24)];
  442. [input setStringValue:ns_default];
  443. self.dialog = [[NSAlert alloc] init];
  444. [[self.dialog addButtonWithTitle:@"OK"] setTag:NSModalResponseOK];
  445. [[self.dialog addButtonWithTitle:@"Cancel"] setTag:NSModalResponseCancel];
  446. [self.dialog setMessageText:ns_message];
  447. [self.dialog setAccessoryView:input];
  448. self.dialog.window.initialFirstResponder = input;
  449. [self.dialog beginSheetModalForWindow:[self window]
  450. completionHandler:^(NSModalResponse response) {
  451. Optional<String> text;
  452. if (response == NSModalResponseOK) {
  453. text = Ladybird::ns_string_to_string([input stringValue]);
  454. }
  455. m_web_view_bridge->prompt_closed(move(text));
  456. self.dialog = nil;
  457. }];
  458. };
  459. m_web_view_bridge->on_request_set_prompt_text = [self](auto const& message) {
  460. if (self.dialog == nil || [self.dialog accessoryView] == nil) {
  461. return;
  462. }
  463. auto* ns_message = Ladybird::string_to_ns_string(message);
  464. auto* input = (NSTextField*)[self.dialog accessoryView];
  465. [input setStringValue:ns_message];
  466. };
  467. m_web_view_bridge->on_request_accept_dialog = [self]() {
  468. if (self.dialog == nil) {
  469. return;
  470. }
  471. [[self window] endSheet:[[self dialog] window]
  472. returnCode:NSModalResponseOK];
  473. };
  474. m_web_view_bridge->on_request_dismiss_dialog = [self]() {
  475. if (self.dialog == nil) {
  476. return;
  477. }
  478. [[self window] endSheet:[[self dialog] window]
  479. returnCode:NSModalResponseCancel];
  480. };
  481. m_web_view_bridge->on_request_color_picker = [self](Color current_color) {
  482. auto* panel = [NSColorPanel sharedColorPanel];
  483. [panel setColor:Ladybird::gfx_color_to_ns_color(current_color)];
  484. [panel setShowsAlpha:NO];
  485. NSNotificationCenter* notification_center = [NSNotificationCenter defaultCenter];
  486. [notification_center addObserver:self
  487. selector:@selector(colorPickerClosed:)
  488. name:NSWindowWillCloseNotification
  489. object:panel];
  490. [panel makeKeyAndOrderFront:nil];
  491. };
  492. self.select_dropdown = [[NSMenu alloc] initWithTitle:@"Select Dropdown"];
  493. [self.select_dropdown setDelegate:self];
  494. m_web_view_bridge->on_request_select_dropdown = [self](Gfx::IntPoint content_position, i32 minimum_width, Vector<Web::HTML::SelectItem> items) {
  495. [self.select_dropdown removeAllItems];
  496. self.select_dropdown.minimumWidth = minimum_width;
  497. for (auto const& item : items) {
  498. [self selectDropdownAdd:self.select_dropdown
  499. item:item];
  500. }
  501. auto* event = Ladybird::create_context_menu_mouse_event(self, content_position);
  502. [NSMenu popUpContextMenu:self.select_dropdown withEvent:event forView:self];
  503. };
  504. m_web_view_bridge->on_get_all_cookies = [](auto const& url) {
  505. auto* delegate = (ApplicationDelegate*)[NSApp delegate];
  506. return [delegate cookieJar].get_all_cookies(url);
  507. };
  508. m_web_view_bridge->on_get_named_cookie = [](auto const& url, auto const& name) {
  509. auto* delegate = (ApplicationDelegate*)[NSApp delegate];
  510. return [delegate cookieJar].get_named_cookie(url, name);
  511. };
  512. m_web_view_bridge->on_get_cookie = [](auto const& url, auto source) -> DeprecatedString {
  513. auto* delegate = (ApplicationDelegate*)[NSApp delegate];
  514. return [delegate cookieJar].get_cookie(url, source);
  515. };
  516. m_web_view_bridge->on_set_cookie = [](auto const& url, auto const& cookie, auto source) {
  517. auto* delegate = (ApplicationDelegate*)[NSApp delegate];
  518. [delegate cookieJar].set_cookie(url, cookie, source);
  519. };
  520. m_web_view_bridge->on_update_cookie = [](auto const& cookie) {
  521. auto* delegate = (ApplicationDelegate*)[NSApp delegate];
  522. [delegate cookieJar].update_cookie(cookie);
  523. };
  524. m_web_view_bridge->on_restore_window = [self]() {
  525. [[self window] setIsMiniaturized:NO];
  526. [[self window] orderFront:nil];
  527. };
  528. m_web_view_bridge->on_reposition_window = [self](auto const& position) {
  529. auto frame = [[self window] frame];
  530. frame.origin = Ladybird::gfx_point_to_ns_point(position);
  531. [[self window] setFrame:frame display:YES];
  532. return Ladybird::ns_point_to_gfx_point([[self window] frame].origin);
  533. };
  534. m_web_view_bridge->on_resize_window = [self](auto const& size) {
  535. auto frame = [[self window] frame];
  536. frame.size = Ladybird::gfx_size_to_ns_size(size);
  537. [[self window] setFrame:frame display:YES];
  538. return Ladybird::ns_size_to_gfx_size([[self window] frame].size);
  539. };
  540. m_web_view_bridge->on_maximize_window = [self]() {
  541. auto frame = [[NSScreen mainScreen] frame];
  542. [[self window] setFrame:frame display:YES];
  543. return Ladybird::ns_rect_to_gfx_rect([[self window] frame]);
  544. };
  545. m_web_view_bridge->on_minimize_window = [self]() {
  546. [[self window] setIsMiniaturized:YES];
  547. return Ladybird::ns_rect_to_gfx_rect([[self window] frame]);
  548. };
  549. m_web_view_bridge->on_fullscreen_window = [self]() {
  550. if (([[self window] styleMask] & NSWindowStyleMaskFullScreen) == 0) {
  551. [[self window] toggleFullScreen:nil];
  552. }
  553. return Ladybird::ns_rect_to_gfx_rect([[self window] frame]);
  554. };
  555. m_web_view_bridge->on_received_source = [self](auto const& url, auto const& source) {
  556. auto html = WebView::highlight_source(url, source);
  557. [self.observer onCreateNewTab:html
  558. url:url
  559. activateTab:Web::HTML::ActivateTab::Yes];
  560. };
  561. m_web_view_bridge->on_theme_color_change = [self](auto color) {
  562. self.backgroundColor = Ladybird::gfx_color_to_ns_color(color);
  563. };
  564. m_web_view_bridge->on_insert_clipboard_entry = [](auto const& data, auto const&, auto const& mime_type) {
  565. NSPasteboardType pasteboard_type = nil;
  566. // https://w3c.github.io/clipboard-apis/#os-specific-well-known-format
  567. if (mime_type == "text/plain"sv)
  568. pasteboard_type = NSPasteboardTypeString;
  569. else if (mime_type == "text/html"sv)
  570. pasteboard_type = NSPasteboardTypeHTML;
  571. else if (mime_type == "text/png"sv)
  572. pasteboard_type = NSPasteboardTypePNG;
  573. if (pasteboard_type)
  574. copy_data_to_clipboard(data, pasteboard_type);
  575. };
  576. }
  577. - (void)selectDropdownAdd:(NSMenu*)menu item:(Web::HTML::SelectItem const&)item
  578. {
  579. if (item.type == Web::HTML::SelectItem::Type::OptionGroup) {
  580. NSMenuItem* subtitle = [[NSMenuItem alloc]
  581. initWithTitle:Ladybird::string_to_ns_string(item.label.value_or(""_string))
  582. action:nil
  583. keyEquivalent:@""];
  584. subtitle.enabled = false;
  585. [menu addItem:subtitle];
  586. for (auto const& item : *item.items) {
  587. [self selectDropdownAdd:menu
  588. item:item];
  589. }
  590. }
  591. if (item.type == Web::HTML::SelectItem::Type::Option) {
  592. NSMenuItem* menuItem = [[NSMenuItem alloc]
  593. initWithTitle:Ladybird::string_to_ns_string(item.label.value_or(""_string))
  594. action:@selector(selectDropdownAction:)
  595. keyEquivalent:@""];
  596. [menuItem setRepresentedObject:Ladybird::string_to_ns_string(item.value.value_or(""_string))];
  597. [menuItem setEnabled:YES];
  598. [menuItem setState:item.selected ? NSControlStateValueOn : NSControlStateValueOff];
  599. [menu addItem:menuItem];
  600. }
  601. if (item.type == Web::HTML::SelectItem::Type::Separator) {
  602. [menu addItem:[NSMenuItem separatorItem]];
  603. }
  604. }
  605. - (void)selectDropdownAction:(NSMenuItem*)menuItem
  606. {
  607. auto value = Ladybird::ns_string_to_string([menuItem representedObject]);
  608. m_web_view_bridge->select_dropdown_closed(value);
  609. }
  610. - (void)menuDidClose:(NSMenu*)menu
  611. {
  612. if (!menu.highlightedItem)
  613. m_web_view_bridge->select_dropdown_closed({});
  614. }
  615. - (void)colorPickerClosed:(NSNotification*)notification
  616. {
  617. m_web_view_bridge->color_picker_closed(Ladybird::ns_color_to_gfx_color([[NSColorPanel sharedColorPanel] color]));
  618. }
  619. - (NSScrollView*)scrollView
  620. {
  621. return (NSScrollView*)[self superview];
  622. }
  623. - (void)copy:(id)sender
  624. {
  625. copy_data_to_clipboard(m_web_view_bridge->selected_text(), NSPasteboardTypeString);
  626. }
  627. - (void)selectAll:(id)sender
  628. {
  629. m_web_view_bridge->select_all();
  630. }
  631. - (void)searchSelectedText:(id)sender
  632. {
  633. auto* delegate = (ApplicationDelegate*)[NSApp delegate];
  634. auto url = MUST(String::formatted([delegate searchEngine].query_url, URL::percent_encode(*m_context_menu_search_text)));
  635. [self.observer onCreateNewTab:url activateTab:Web::HTML::ActivateTab::Yes];
  636. }
  637. - (void)takeVisibleScreenshot:(id)sender
  638. {
  639. auto result = m_web_view_bridge->take_screenshot(WebView::ViewImplementation::ScreenshotType::Visible);
  640. (void)result; // FIXME: Display an error if this failed.
  641. }
  642. - (void)takeFullScreenshot:(id)sender
  643. {
  644. auto result = m_web_view_bridge->take_screenshot(WebView::ViewImplementation::ScreenshotType::Full);
  645. (void)result; // FIXME: Display an error if this failed.
  646. }
  647. - (void)openLink:(id)sender
  648. {
  649. m_web_view_bridge->on_link_click(m_context_menu_url, {}, 0);
  650. }
  651. - (void)openLinkInNewTab:(id)sender
  652. {
  653. m_web_view_bridge->on_link_middle_click(m_context_menu_url, {}, 0);
  654. }
  655. - (void)copyLink:(id)sender
  656. {
  657. auto link = WebView::url_text_to_copy(m_context_menu_url);
  658. copy_data_to_clipboard(link, NSPasteboardTypeString);
  659. }
  660. - (void)copyImage:(id)sender
  661. {
  662. auto* bitmap = m_context_menu_bitmap.bitmap();
  663. if (bitmap == nullptr) {
  664. return;
  665. }
  666. auto png = Gfx::PNGWriter::encode(*bitmap);
  667. if (png.is_error()) {
  668. return;
  669. }
  670. auto* data = [NSData dataWithBytes:png.value().data() length:png.value().size()];
  671. auto* pasteBoard = [NSPasteboard generalPasteboard];
  672. [pasteBoard clearContents];
  673. [pasteBoard setData:data forType:NSPasteboardTypePNG];
  674. }
  675. - (void)toggleMediaPlayState:(id)sender
  676. {
  677. m_web_view_bridge->toggle_media_play_state();
  678. }
  679. - (void)toggleMediaMuteState:(id)sender
  680. {
  681. m_web_view_bridge->toggle_media_mute_state();
  682. }
  683. - (void)toggleMediaControlsState:(id)sender
  684. {
  685. m_web_view_bridge->toggle_media_controls_state();
  686. }
  687. - (void)toggleMediaLoopState:(id)sender
  688. {
  689. m_web_view_bridge->toggle_media_loop_state();
  690. }
  691. #pragma mark - Properties
  692. - (NSMenu*)page_context_menu
  693. {
  694. if (!_page_context_menu) {
  695. _page_context_menu = [[NSMenu alloc] initWithTitle:@"Page Context Menu"];
  696. [_page_context_menu addItem:[[NSMenuItem alloc] initWithTitle:@"Go Back"
  697. action:@selector(navigateBack:)
  698. keyEquivalent:@""]];
  699. [_page_context_menu addItem:[[NSMenuItem alloc] initWithTitle:@"Go Forward"
  700. action:@selector(navigateForward:)
  701. keyEquivalent:@""]];
  702. [_page_context_menu addItem:[[NSMenuItem alloc] initWithTitle:@"Reload"
  703. action:@selector(reload:)
  704. keyEquivalent:@""]];
  705. [_page_context_menu addItem:[NSMenuItem separatorItem]];
  706. [_page_context_menu addItem:[[NSMenuItem alloc] initWithTitle:@"Copy"
  707. action:@selector(copy:)
  708. keyEquivalent:@""]];
  709. [_page_context_menu addItem:[[NSMenuItem alloc] initWithTitle:@"Select All"
  710. action:@selector(selectAll:)
  711. keyEquivalent:@""]];
  712. [_page_context_menu addItem:[NSMenuItem separatorItem]];
  713. auto* search_selected_text_menu_item = [[NSMenuItem alloc] initWithTitle:@"Search for <query>"
  714. action:@selector(searchSelectedText:)
  715. keyEquivalent:@""];
  716. [search_selected_text_menu_item setTag:CONTEXT_MENU_SEARCH_SELECTED_TEXT_TAG];
  717. [_page_context_menu addItem:search_selected_text_menu_item];
  718. [_page_context_menu addItem:[NSMenuItem separatorItem]];
  719. [_page_context_menu addItem:[[NSMenuItem alloc] initWithTitle:@"Take Visible Screenshot"
  720. action:@selector(takeVisibleScreenshot:)
  721. keyEquivalent:@""]];
  722. [_page_context_menu addItem:[[NSMenuItem alloc] initWithTitle:@"Take Full Screenshot"
  723. action:@selector(takeFullScreenshot:)
  724. keyEquivalent:@""]];
  725. [_page_context_menu addItem:[NSMenuItem separatorItem]];
  726. [_page_context_menu addItem:[[NSMenuItem alloc] initWithTitle:@"View Source"
  727. action:@selector(viewSource:)
  728. keyEquivalent:@""]];
  729. [_page_context_menu addItem:[[NSMenuItem alloc] initWithTitle:@"Inspect Element"
  730. action:@selector(inspectElement:)
  731. keyEquivalent:@""]];
  732. }
  733. return _page_context_menu;
  734. }
  735. - (NSMenu*)link_context_menu
  736. {
  737. if (!_link_context_menu) {
  738. _link_context_menu = [[NSMenu alloc] initWithTitle:@"Link Context Menu"];
  739. [_link_context_menu addItem:[[NSMenuItem alloc] initWithTitle:@"Open"
  740. action:@selector(openLink:)
  741. keyEquivalent:@""]];
  742. [_link_context_menu addItem:[[NSMenuItem alloc] initWithTitle:@"Open in New Tab"
  743. action:@selector(openLinkInNewTab:)
  744. keyEquivalent:@""]];
  745. [_link_context_menu addItem:[NSMenuItem separatorItem]];
  746. auto* copy_link_menu_item = [[NSMenuItem alloc] initWithTitle:@"Copy URL"
  747. action:@selector(copyLink:)
  748. keyEquivalent:@""];
  749. [copy_link_menu_item setTag:CONTEXT_MENU_COPY_LINK_TAG];
  750. [_link_context_menu addItem:copy_link_menu_item];
  751. [_link_context_menu addItem:[NSMenuItem separatorItem]];
  752. [_link_context_menu addItem:[[NSMenuItem alloc] initWithTitle:@"Inspect Element"
  753. action:@selector(inspectElement:)
  754. keyEquivalent:@""]];
  755. }
  756. return _link_context_menu;
  757. }
  758. - (NSMenu*)image_context_menu
  759. {
  760. if (!_image_context_menu) {
  761. _image_context_menu = [[NSMenu alloc] initWithTitle:@"Image Context Menu"];
  762. [_image_context_menu addItem:[[NSMenuItem alloc] initWithTitle:@"Open Image"
  763. action:@selector(openLink:)
  764. keyEquivalent:@""]];
  765. [_image_context_menu addItem:[[NSMenuItem alloc] initWithTitle:@"Open Image in New Tab"
  766. action:@selector(openLinkInNewTab:)
  767. keyEquivalent:@""]];
  768. [_image_context_menu addItem:[NSMenuItem separatorItem]];
  769. [_image_context_menu addItem:[[NSMenuItem alloc] initWithTitle:@"Copy Image"
  770. action:@selector(copyImage:)
  771. keyEquivalent:@""]];
  772. [_image_context_menu addItem:[[NSMenuItem alloc] initWithTitle:@"Copy Image URL"
  773. action:@selector(copyLink:)
  774. keyEquivalent:@""]];
  775. [_image_context_menu addItem:[NSMenuItem separatorItem]];
  776. [_image_context_menu addItem:[[NSMenuItem alloc] initWithTitle:@"Inspect Element"
  777. action:@selector(inspectElement:)
  778. keyEquivalent:@""]];
  779. }
  780. return _image_context_menu;
  781. }
  782. - (NSMenu*)audio_context_menu
  783. {
  784. if (!_audio_context_menu) {
  785. _audio_context_menu = [[NSMenu alloc] initWithTitle:@"Audio Context Menu"];
  786. auto* play_pause_menu_item = [[NSMenuItem alloc] initWithTitle:@"Play"
  787. action:@selector(toggleMediaPlayState:)
  788. keyEquivalent:@""];
  789. [play_pause_menu_item setTag:CONTEXT_MENU_PLAY_PAUSE_TAG];
  790. auto* mute_unmute_menu_item = [[NSMenuItem alloc] initWithTitle:@"Mute"
  791. action:@selector(toggleMediaMuteState:)
  792. keyEquivalent:@""];
  793. [mute_unmute_menu_item setTag:CONTEXT_MENU_MUTE_UNMUTE_TAG];
  794. auto* controls_menu_item = [[NSMenuItem alloc] initWithTitle:@"Controls"
  795. action:@selector(toggleMediaControlsState:)
  796. keyEquivalent:@""];
  797. [controls_menu_item setTag:CONTEXT_MENU_CONTROLS_TAG];
  798. auto* loop_menu_item = [[NSMenuItem alloc] initWithTitle:@"Loop"
  799. action:@selector(toggleMediaLoopState:)
  800. keyEquivalent:@""];
  801. [loop_menu_item setTag:CONTEXT_MENU_LOOP_TAG];
  802. [_audio_context_menu addItem:play_pause_menu_item];
  803. [_audio_context_menu addItem:mute_unmute_menu_item];
  804. [_audio_context_menu addItem:controls_menu_item];
  805. [_audio_context_menu addItem:loop_menu_item];
  806. [_audio_context_menu addItem:[NSMenuItem separatorItem]];
  807. [_audio_context_menu addItem:[[NSMenuItem alloc] initWithTitle:@"Open Audio"
  808. action:@selector(openLink:)
  809. keyEquivalent:@""]];
  810. [_audio_context_menu addItem:[[NSMenuItem alloc] initWithTitle:@"Open Audio in New Tab"
  811. action:@selector(openLinkInNewTab:)
  812. keyEquivalent:@""]];
  813. [_audio_context_menu addItem:[NSMenuItem separatorItem]];
  814. [_audio_context_menu addItem:[[NSMenuItem alloc] initWithTitle:@"Copy Audio URL"
  815. action:@selector(copyLink:)
  816. keyEquivalent:@""]];
  817. [_audio_context_menu addItem:[NSMenuItem separatorItem]];
  818. [_audio_context_menu addItem:[[NSMenuItem alloc] initWithTitle:@"Inspect Element"
  819. action:@selector(inspectElement:)
  820. keyEquivalent:@""]];
  821. }
  822. return _audio_context_menu;
  823. }
  824. - (NSMenu*)video_context_menu
  825. {
  826. if (!_video_context_menu) {
  827. _video_context_menu = [[NSMenu alloc] initWithTitle:@"Video Context Menu"];
  828. auto* play_pause_menu_item = [[NSMenuItem alloc] initWithTitle:@"Play"
  829. action:@selector(toggleMediaPlayState:)
  830. keyEquivalent:@""];
  831. [play_pause_menu_item setTag:CONTEXT_MENU_PLAY_PAUSE_TAG];
  832. auto* mute_unmute_menu_item = [[NSMenuItem alloc] initWithTitle:@"Mute"
  833. action:@selector(toggleMediaMuteState:)
  834. keyEquivalent:@""];
  835. [mute_unmute_menu_item setTag:CONTEXT_MENU_MUTE_UNMUTE_TAG];
  836. auto* controls_menu_item = [[NSMenuItem alloc] initWithTitle:@"Controls"
  837. action:@selector(toggleMediaControlsState:)
  838. keyEquivalent:@""];
  839. [controls_menu_item setTag:CONTEXT_MENU_CONTROLS_TAG];
  840. auto* loop_menu_item = [[NSMenuItem alloc] initWithTitle:@"Loop"
  841. action:@selector(toggleMediaLoopState:)
  842. keyEquivalent:@""];
  843. [loop_menu_item setTag:CONTEXT_MENU_LOOP_TAG];
  844. [_video_context_menu addItem:play_pause_menu_item];
  845. [_video_context_menu addItem:mute_unmute_menu_item];
  846. [_video_context_menu addItem:controls_menu_item];
  847. [_video_context_menu addItem:loop_menu_item];
  848. [_video_context_menu addItem:[NSMenuItem separatorItem]];
  849. [_video_context_menu addItem:[[NSMenuItem alloc] initWithTitle:@"Open Video"
  850. action:@selector(openLink:)
  851. keyEquivalent:@""]];
  852. [_video_context_menu addItem:[[NSMenuItem alloc] initWithTitle:@"Open Video in New Tab"
  853. action:@selector(openLinkInNewTab:)
  854. keyEquivalent:@""]];
  855. [_video_context_menu addItem:[NSMenuItem separatorItem]];
  856. [_video_context_menu addItem:[[NSMenuItem alloc] initWithTitle:@"Copy Video URL"
  857. action:@selector(copyLink:)
  858. keyEquivalent:@""]];
  859. [_video_context_menu addItem:[NSMenuItem separatorItem]];
  860. [_video_context_menu addItem:[[NSMenuItem alloc] initWithTitle:@"Inspect Element"
  861. action:@selector(inspectElement:)
  862. keyEquivalent:@""]];
  863. }
  864. return _video_context_menu;
  865. }
  866. - (NSTextField*)status_label
  867. {
  868. if (!_status_label) {
  869. _status_label = [NSTextField labelWithString:@""];
  870. [_status_label setDrawsBackground:YES];
  871. [_status_label setBordered:YES];
  872. [_status_label setHidden:YES];
  873. [self addSubview:_status_label];
  874. }
  875. return _status_label;
  876. }
  877. #pragma mark - NSView
  878. - (void)drawRect:(NSRect)rect
  879. {
  880. auto paintable = m_web_view_bridge->paintable();
  881. if (!paintable.has_value()) {
  882. [super drawRect:rect];
  883. return;
  884. }
  885. auto [bitmap, bitmap_size] = *paintable;
  886. VERIFY(bitmap.format() == Gfx::BitmapFormat::BGRA8888);
  887. static constexpr size_t BITS_PER_COMPONENT = 8;
  888. static constexpr size_t BITS_PER_PIXEL = 32;
  889. static constexpr size_t COMPONENTS_PER_PIXEL = 4;
  890. auto* context = [[NSGraphicsContext currentContext] CGContext];
  891. CGContextSaveGState(context);
  892. auto device_pixel_ratio = m_web_view_bridge->device_pixel_ratio();
  893. auto inverse_device_pixel_ratio = m_web_view_bridge->inverse_device_pixel_ratio();
  894. CGContextScaleCTM(context, inverse_device_pixel_ratio, inverse_device_pixel_ratio);
  895. auto* provider = CGDataProviderCreateWithData(nil, bitmap.scanline_u8(0), bitmap.size_in_bytes(), nil);
  896. auto image_rect = CGRectMake(rect.origin.x * device_pixel_ratio, rect.origin.y * device_pixel_ratio, bitmap_size.width(), bitmap_size.height());
  897. // Ideally, this would be NSBitmapImageRep, but the equivalent factory initWithBitmapDataPlanes: does
  898. // not seem to actually respect endianness. We need NSBitmapFormatThirtyTwoBitLittleEndian, but the
  899. // resulting image is always big endian. CGImageCreate actually does respect the endianness.
  900. auto* bitmap_image = CGImageCreate(
  901. bitmap_size.width(),
  902. bitmap_size.height(),
  903. BITS_PER_COMPONENT,
  904. BITS_PER_PIXEL,
  905. COMPONENTS_PER_PIXEL * bitmap.width(),
  906. CGColorSpaceCreateDeviceRGB(),
  907. kCGBitmapByteOrder32Little | kCGImageAlphaFirst,
  908. provider,
  909. nil,
  910. NO,
  911. kCGRenderingIntentDefault);
  912. auto* image = [[NSImage alloc] initWithCGImage:bitmap_image size:NSZeroSize];
  913. [image drawInRect:image_rect];
  914. CGContextRestoreGState(context);
  915. CGDataProviderRelease(provider);
  916. CGImageRelease(bitmap_image);
  917. [super drawRect:rect];
  918. }
  919. - (void)viewDidMoveToWindow
  920. {
  921. [super viewDidMoveToWindow];
  922. [self handleResize];
  923. }
  924. - (void)viewDidEndLiveResize
  925. {
  926. [super viewDidEndLiveResize];
  927. [self handleResize];
  928. }
  929. - (void)viewDidChangeEffectiveAppearance
  930. {
  931. m_web_view_bridge->update_palette();
  932. }
  933. - (BOOL)isFlipped
  934. {
  935. // The origin of a NSScrollView is the lower-left corner, with the y-axis extending upwards. Instead,
  936. // we want the origin to be the top-left corner, with the y-axis extending downward.
  937. return YES;
  938. }
  939. - (void)mouseMoved:(NSEvent*)event
  940. {
  941. auto [position, screen_position, button, modifiers] = Ladybird::ns_event_to_mouse_event(event, self, GUI::MouseButton::None);
  942. m_web_view_bridge->mouse_move_event(position, screen_position, button, modifiers);
  943. }
  944. - (void)scrollWheel:(NSEvent*)event
  945. {
  946. auto [position, screen_position, button, modifiers] = Ladybird::ns_event_to_mouse_event(event, self, GUI::MouseButton::Middle);
  947. CGFloat delta_x = -[event scrollingDeltaX];
  948. CGFloat delta_y = -[event scrollingDeltaY];
  949. if (![event hasPreciseScrollingDeltas]) {
  950. delta_x *= [self scrollView].horizontalLineScroll;
  951. delta_y *= [self scrollView].verticalLineScroll;
  952. }
  953. m_web_view_bridge->mouse_wheel_event(position, screen_position, button, modifiers, delta_x, delta_y);
  954. }
  955. - (void)mouseDown:(NSEvent*)event
  956. {
  957. [[self window] makeFirstResponder:self];
  958. auto [position, screen_position, button, modifiers] = Ladybird::ns_event_to_mouse_event(event, self, GUI::MouseButton::Primary);
  959. if (event.clickCount % 2 == 0) {
  960. m_web_view_bridge->mouse_double_click_event(position, screen_position, button, modifiers);
  961. } else {
  962. m_web_view_bridge->mouse_down_event(position, screen_position, button, modifiers);
  963. }
  964. }
  965. - (void)mouseUp:(NSEvent*)event
  966. {
  967. auto [position, screen_position, button, modifiers] = Ladybird::ns_event_to_mouse_event(event, self, GUI::MouseButton::Primary);
  968. m_web_view_bridge->mouse_up_event(position, screen_position, button, modifiers);
  969. }
  970. - (void)mouseDragged:(NSEvent*)event
  971. {
  972. auto [position, screen_position, button, modifiers] = Ladybird::ns_event_to_mouse_event(event, self, GUI::MouseButton::Primary);
  973. m_web_view_bridge->mouse_move_event(position, screen_position, button, modifiers);
  974. }
  975. - (void)rightMouseDown:(NSEvent*)event
  976. {
  977. [[self window] makeFirstResponder:self];
  978. auto [position, screen_position, button, modifiers] = Ladybird::ns_event_to_mouse_event(event, self, GUI::MouseButton::Secondary);
  979. if (event.clickCount % 2 == 0) {
  980. m_web_view_bridge->mouse_double_click_event(position, screen_position, button, modifiers);
  981. } else {
  982. m_web_view_bridge->mouse_down_event(position, screen_position, button, modifiers);
  983. }
  984. }
  985. - (void)rightMouseUp:(NSEvent*)event
  986. {
  987. auto [position, screen_position, button, modifiers] = Ladybird::ns_event_to_mouse_event(event, self, GUI::MouseButton::Secondary);
  988. m_web_view_bridge->mouse_up_event(position, screen_position, button, modifiers);
  989. }
  990. - (void)rightMouseDragged:(NSEvent*)event
  991. {
  992. auto [position, screen_position, button, modifiers] = Ladybird::ns_event_to_mouse_event(event, self, GUI::MouseButton::Secondary);
  993. m_web_view_bridge->mouse_move_event(position, screen_position, button, modifiers);
  994. }
  995. - (void)keyDown:(NSEvent*)event
  996. {
  997. auto [key_code, modifiers, code_point] = Ladybird::ns_event_to_key_event(event);
  998. m_web_view_bridge->key_down_event(key_code, modifiers, code_point);
  999. }
  1000. - (void)keyUp:(NSEvent*)event
  1001. {
  1002. auto [key_code, modifiers, code_point] = Ladybird::ns_event_to_key_event(event);
  1003. m_web_view_bridge->key_up_event(key_code, modifiers, code_point);
  1004. }
  1005. @end