You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

WindowConnector.java 20KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555
  1. /*
  2. * Copyright 2000-2021 Vaadin Ltd.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  5. * use this file except in compliance with the License. You may obtain a copy of
  6. * the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  12. * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  13. * License for the specific language governing permissions and limitations under
  14. * the License.
  15. */
  16. package com.vaadin.client.ui.window;
  17. import java.util.logging.Logger;
  18. import com.google.gwt.core.client.Scheduler;
  19. import com.google.gwt.dom.client.Element;
  20. import com.google.gwt.dom.client.NativeEvent;
  21. import com.google.gwt.dom.client.Node;
  22. import com.google.gwt.dom.client.NodeList;
  23. import com.google.gwt.dom.client.Style;
  24. import com.google.gwt.dom.client.Style.Position;
  25. import com.google.gwt.dom.client.Style.Unit;
  26. import com.google.gwt.event.dom.client.ClickEvent;
  27. import com.google.gwt.event.dom.client.ClickHandler;
  28. import com.google.gwt.event.dom.client.DoubleClickEvent;
  29. import com.google.gwt.event.dom.client.DoubleClickHandler;
  30. import com.google.gwt.event.dom.client.KeyCodes;
  31. import com.google.gwt.event.dom.client.KeyUpEvent;
  32. import com.google.gwt.event.dom.client.KeyUpHandler;
  33. import com.google.gwt.user.client.DOM;
  34. import com.google.gwt.user.client.Window;
  35. import com.vaadin.client.ApplicationConnection;
  36. import com.vaadin.client.ComponentConnector;
  37. import com.vaadin.client.ConnectorHierarchyChangeEvent;
  38. import com.vaadin.client.LayoutManager;
  39. import com.vaadin.client.Paintable;
  40. import com.vaadin.client.UIDL;
  41. import com.vaadin.client.communication.StateChangeEvent;
  42. import com.vaadin.client.ui.AbstractSingleComponentContainerConnector;
  43. import com.vaadin.client.ui.ClickEventHandler;
  44. import com.vaadin.client.ui.PostLayoutListener;
  45. import com.vaadin.client.ui.ShortcutActionHandler;
  46. import com.vaadin.client.ui.SimpleManagedLayout;
  47. import com.vaadin.client.ui.VWindow;
  48. import com.vaadin.client.ui.layout.MayScrollChildren;
  49. import com.vaadin.shared.MouseEventDetails;
  50. import com.vaadin.shared.ui.Connect;
  51. import com.vaadin.shared.ui.window.WindowMode;
  52. import com.vaadin.shared.ui.window.WindowServerRpc;
  53. import com.vaadin.shared.ui.window.WindowState;
  54. /**
  55. * A connector class for the Window component.
  56. *
  57. * @author Vaadin Ltd
  58. */
  59. @SuppressWarnings("deprecation")
  60. @Connect(value = com.vaadin.ui.Window.class)
  61. public class WindowConnector extends AbstractSingleComponentContainerConnector
  62. implements Paintable, SimpleManagedLayout, PostLayoutListener,
  63. MayScrollChildren, WindowMoveHandler {
  64. private Node windowClone;
  65. private ClickEventHandler clickEventHandler = new ClickEventHandler(this) {
  66. @Override
  67. protected void fireClick(NativeEvent event,
  68. MouseEventDetails mouseDetails) {
  69. getRpcProxy(WindowServerRpc.class).click(mouseDetails);
  70. }
  71. };
  72. abstract class WindowEventHandler
  73. implements ClickHandler, DoubleClickHandler, KeyUpHandler {
  74. }
  75. private WindowEventHandler maximizeRestoreClickHandler = new WindowEventHandler() {
  76. @Override
  77. public void onClick(ClickEvent event) {
  78. final Element target = event.getNativeEvent().getEventTarget()
  79. .cast();
  80. if (target == getWidget().maximizeRestoreBox) {
  81. // Click on maximize/restore box
  82. onMaximizeRestore();
  83. }
  84. }
  85. @Override
  86. public void onDoubleClick(DoubleClickEvent event) {
  87. final Element target = event.getNativeEvent().getEventTarget()
  88. .cast();
  89. if (getWidget().header.isOrHasChild(target)) {
  90. // Double click on header
  91. onMaximizeRestore();
  92. }
  93. }
  94. @Override
  95. public void onKeyUp(KeyUpEvent event) {
  96. final int keyCode = event.getNativeKeyCode();
  97. final Element target = event.getNativeEvent().getEventTarget()
  98. .cast();
  99. // key ENTER or SPACE on maximize/restore box
  100. if (target == getWidget().maximizeRestoreBox
  101. && isKeyEnterOrSpace(keyCode)) {
  102. onMaximizeRestore();
  103. }
  104. }
  105. };
  106. @Override
  107. public boolean delegateCaptionHandling() {
  108. return false;
  109. }
  110. @Override
  111. protected void init() {
  112. super.init();
  113. VWindow window = getWidget();
  114. window.id = getConnectorId();
  115. window.client = getConnection();
  116. window.connector = this;
  117. getLayoutManager().registerDependency(this,
  118. window.contentPanel.getElement());
  119. getLayoutManager().registerDependency(this, window.header);
  120. getLayoutManager().registerDependency(this, window.footer);
  121. window.addHandler(maximizeRestoreClickHandler, ClickEvent.getType());
  122. window.addHandler(maximizeRestoreClickHandler,
  123. DoubleClickEvent.getType());
  124. window.addHandler(maximizeRestoreClickHandler, KeyUpEvent.getType());
  125. window.setOwner(getConnection().getUIConnector().getWidget());
  126. window.addMoveHandler(this);
  127. }
  128. @Override
  129. public void onUnregister() {
  130. LayoutManager lm = getLayoutManager();
  131. VWindow window = getWidget();
  132. lm.unregisterDependency(this, window.contentPanel.getElement());
  133. lm.unregisterDependency(this, window.header);
  134. lm.unregisterDependency(this, window.footer);
  135. }
  136. @Override
  137. public void updateFromUIDL(UIDL uidl, ApplicationConnection client) {
  138. VWindow window = getWidget();
  139. String connectorId = getConnectorId();
  140. // Workaround needed for Testing Tools (GWT generates window DOM
  141. // slightly different in different browsers).
  142. window.closeBox.setId(connectorId + "_window_close");
  143. window.maximizeRestoreBox
  144. .setId(connectorId + "_window_maximizerestore");
  145. window.visibilityChangesDisabled = true;
  146. if (!isRealUpdate(uidl)) {
  147. return;
  148. }
  149. window.visibilityChangesDisabled = false;
  150. // we may have actions
  151. for (int i = 0; i < uidl.getChildCount(); i++) {
  152. UIDL childUidl = uidl.getChildUIDL(i);
  153. if (childUidl.getTag().equals("actions")) {
  154. if (window.shortcutHandler == null) {
  155. window.shortcutHandler = new ShortcutActionHandler(
  156. connectorId, client);
  157. }
  158. window.shortcutHandler.updateActionMap(childUidl);
  159. }
  160. }
  161. if (uidl.hasAttribute("bringToFront")) {
  162. /*
  163. * Focus as a side-effect. Will be overridden by
  164. * ApplicationConnection if another component was focused by the
  165. * server side.
  166. */
  167. window.contentPanel.focus();
  168. window.bringToFrontSequence = uidl.getIntAttribute("bringToFront");
  169. VWindow.deferOrdering();
  170. }
  171. }
  172. @Override
  173. public void updateCaption(ComponentConnector component) {
  174. // NOP, window has own caption, layout caption not rendered
  175. }
  176. @Override
  177. public VWindow getWidget() {
  178. return (VWindow) super.getWidget();
  179. }
  180. @Override
  181. public void onConnectorHierarchyChange(
  182. ConnectorHierarchyChangeEvent event) {
  183. // We always have 1 child, unless the child is hidden
  184. getWidget().contentPanel.setWidget(getContentWidget());
  185. if (getParent() == null && windowClone != null) {
  186. // If the window is removed from the UI, add the copy of the
  187. // contents to the window (in case of an 'out-animation')
  188. getWidget().getElement().removeAllChildren();
  189. getWidget().getElement().appendChild(windowClone);
  190. // Clean reference
  191. windowClone = null;
  192. }
  193. }
  194. @Override
  195. public void layout() {
  196. LayoutManager lm = getLayoutManager();
  197. VWindow window = getWidget();
  198. // ensure window is not larger than browser window
  199. if (window.getOffsetWidth() > Window.getClientWidth()) {
  200. window.setWidth(Window.getClientWidth() + "px");
  201. lm.setNeedsMeasure(getContent());
  202. }
  203. if (window.getOffsetHeight() > Window.getClientHeight()) {
  204. window.setHeight(Window.getClientHeight() + "px");
  205. lm.setNeedsMeasure(getContent());
  206. }
  207. ComponentConnector content = getContent();
  208. boolean hasContent = content != null;
  209. Element contentElement = window.contentPanel.getElement();
  210. Style contentStyle = window.contents.getStyle();
  211. int headerHeight = lm.getOuterHeight(window.header);
  212. contentStyle.setPaddingTop(headerHeight, Unit.PX);
  213. contentStyle.setMarginTop(-headerHeight, Unit.PX);
  214. int footerHeight = lm.getOuterHeight(window.footer);
  215. contentStyle.setPaddingBottom(footerHeight, Unit.PX);
  216. contentStyle.setMarginBottom(-footerHeight, Unit.PX);
  217. int minWidth = lm.getOuterWidth(window.header)
  218. - lm.getInnerWidth(window.header);
  219. int minHeight = footerHeight + headerHeight;
  220. getWidget().getElement().getStyle().setPropertyPx("minWidth", minWidth);
  221. getWidget().getElement().getStyle().setPropertyPx("minHeight",
  222. minHeight);
  223. /*
  224. * Must set absolute position if the child has relative height and
  225. * there's a chance of horizontal scrolling as some browsers will
  226. * otherwise not take the scrollbar into account when calculating the
  227. * height.
  228. */
  229. if (hasContent) {
  230. Element layoutElement = content.getWidget().getElement();
  231. Style childStyle = layoutElement.getStyle();
  232. if (content.isRelativeHeight()) {
  233. childStyle.setPosition(Position.ABSOLUTE);
  234. Style wrapperStyle = contentElement.getStyle();
  235. if (window.getElement().getStyle().getWidth().isEmpty()
  236. && !content.isRelativeWidth()) {
  237. /*
  238. * Need to lock width to make undefined width work even with
  239. * absolute positioning
  240. */
  241. int contentWidth = lm.getOuterWidth(layoutElement);
  242. wrapperStyle.setWidth(contentWidth, Unit.PX);
  243. } else {
  244. wrapperStyle.clearWidth();
  245. }
  246. } else {
  247. childStyle.clearPosition();
  248. }
  249. }
  250. }
  251. @Override
  252. public void postLayout() {
  253. VWindow window = getWidget();
  254. if (!window.isAttached()) {
  255. Logger.getLogger(WindowConnector.class.getName())
  256. .warning("Called postLayout to detached Window.");
  257. return;
  258. }
  259. if (window.centered && getState().windowMode != WindowMode.MAXIMIZED) {
  260. window.center();
  261. }
  262. window.positionOrSizeUpdated();
  263. if (getParent() != null) {
  264. // Take a copy of the contents, since the server will detach all
  265. // children of this window when it's closed, and the window will be
  266. // emptied during the following hierarchy update (we need to keep
  267. // the contents visible for the duration of a possible
  268. // 'out-animation')
  269. // Fix for #14645 and #14785 - as soon as we clone audio and video
  270. // tags, they start fetching data, and playing immediately in
  271. // background, in case autoplay attribute is present. Therefore we
  272. // have to replace them with stubs in the clone. And we can't just
  273. // erase them, because there are corresponding player widgets to
  274. // animate
  275. windowClone = cloneNodeFilteringMedia(
  276. getWidget().getElement().getFirstChild());
  277. }
  278. }
  279. private Node cloneNodeFilteringMedia(Node node) {
  280. if (node instanceof Element) {
  281. Element old = (Element) node;
  282. if ("audio".equalsIgnoreCase(old.getTagName())
  283. || "video".equalsIgnoreCase(old.getTagName())) {
  284. if (!old.hasAttribute("controls")
  285. && "audio".equalsIgnoreCase(old.getTagName())) {
  286. // nothing to animate, so we won't add this to
  287. // the clone
  288. return null;
  289. }
  290. Element newEl = DOM.createElement(old.getTagName());
  291. if (old.hasAttribute("controls")) {
  292. newEl.setAttribute("controls",
  293. old.getAttribute("controls"));
  294. }
  295. if (old.hasAttribute("style")) {
  296. newEl.setAttribute("style", old.getAttribute("style"));
  297. }
  298. if (old.hasAttribute("class")) {
  299. newEl.setAttribute("class", old.getAttribute("class"));
  300. }
  301. return newEl;
  302. }
  303. if ("iframe".equalsIgnoreCase(old.getTagName())
  304. && old.hasAttribute("src")
  305. && old.getAttribute("src").contains("APP/connector")) {
  306. // an iframe reloads when reattached to the DOM, but
  307. // unfortunately, when newEl is reattached, server-side
  308. // resource (e.g. generated PDF) is already gone, so this will
  309. // end up with 404, which might be undesirable.
  310. // Instead of the resource that is not available anymore,
  311. // let's just use empty iframe.
  312. // See
  313. // https://github.com/vaadin/framework/issues/11369
  314. // for details of the issue this works around
  315. Element newEl = old.cloneNode(false).cast();
  316. newEl.setAttribute("src", "about:blank");
  317. return newEl;
  318. }
  319. }
  320. Node res = node.cloneNode(false);
  321. if (node.hasChildNodes()) {
  322. NodeList<Node> nl = node.getChildNodes();
  323. for (int i = 0; i < nl.getLength(); i++) {
  324. Node clone = cloneNodeFilteringMedia(nl.getItem(i));
  325. if (clone != null) {
  326. res.appendChild(clone);
  327. }
  328. }
  329. }
  330. return res;
  331. }
  332. @Override
  333. public WindowState getState() {
  334. return (WindowState) super.getState();
  335. }
  336. @Override
  337. public void onStateChanged(StateChangeEvent stateChangeEvent) {
  338. super.onStateChanged(stateChangeEvent);
  339. VWindow window = getWidget();
  340. WindowState state = getState();
  341. if (state.modal != window.vaadinModality) {
  342. window.setVaadinModality(!window.vaadinModality);
  343. }
  344. boolean resizeable = state.resizable
  345. && state.windowMode == WindowMode.NORMAL;
  346. window.setResizable(resizeable);
  347. window.resizeLazy = state.resizeLazy;
  348. window.setDraggable(
  349. state.draggable && state.windowMode == WindowMode.NORMAL);
  350. window.updateMaximizeRestoreClassName(state.resizable,
  351. state.windowMode);
  352. // Caption must be set before required header size is measured. If
  353. // the caption attribute is missing the caption should be cleared.
  354. String iconURL = null;
  355. if (getIconUri() != null) {
  356. iconURL = getIconUri();
  357. }
  358. window.setAssistivePrefix(state.assistivePrefix);
  359. window.setAssistivePostfix(state.assistivePostfix);
  360. window.setCaption(state.caption, iconURL, getState().captionAsHtml);
  361. window.setWaiAriaRole(getState().role);
  362. window.setAssistiveDescription(state.contentDescription);
  363. window.setTabStopEnabled(getState().assistiveTabStop);
  364. window.setTabStopTopAssistiveText(getState().assistiveTabStopTopText);
  365. window.setTabStopBottomAssistiveText(
  366. getState().assistiveTabStopBottomText);
  367. clickEventHandler.handleEventHandlerRegistration();
  368. window.setClosable(state.closable);
  369. // initialize position from state
  370. updateWindowPosition();
  371. // setting scrollposition must happen after children is rendered
  372. window.contentPanel.setScrollPosition(state.scrollTop);
  373. window.contentPanel.setHorizontalScrollPosition(state.scrollLeft);
  374. // Center this window on screen if requested
  375. // This had to be here because we might not know the content size before
  376. // everything is painted into the window
  377. // centered if this is unset on move/resize
  378. window.centered = state.centered;
  379. // Ensure centering before setting visible (#16486)
  380. if (window.centered && getState().windowMode != WindowMode.MAXIMIZED) {
  381. Scheduler.get().scheduleFinally(() -> {
  382. // the window may have got removed again before this is
  383. // triggered, and centering would re-display it
  384. if (getWidget().isShowing()) {
  385. getWidget().center();
  386. }
  387. });
  388. }
  389. window.setVisible(true);
  390. }
  391. // Need to override default because of window mode
  392. @Override
  393. protected void updateComponentSize() {
  394. if (getState().windowMode == WindowMode.NORMAL) {
  395. super.updateComponentSize();
  396. } else if (getState().windowMode == WindowMode.MAXIMIZED) {
  397. super.updateComponentSize("100%", "100%");
  398. }
  399. }
  400. /**
  401. * Initializes or updates position from state.
  402. */
  403. protected void updateWindowPosition() {
  404. VWindow window = getWidget();
  405. WindowState state = getState();
  406. if (state.windowMode == WindowMode.NORMAL) {
  407. // if centered, position handled in postLayout()
  408. if (!state.centered
  409. && (state.positionX >= 0 || state.positionY >= 0)) {
  410. // If both positions are negative, then
  411. // setWindowOrderAndPosition has already taken care of
  412. // positioning the window so it stacks with other windows
  413. window.setPopupPosition(state.positionX, state.positionY);
  414. }
  415. } else if (state.windowMode == WindowMode.MAXIMIZED) {
  416. window.setPopupPositionNoUpdate(0, 0);
  417. }
  418. }
  419. /**
  420. * Updates the window state to match the current mode.
  421. */
  422. protected void updateWindowMode() {
  423. VWindow window = getWidget();
  424. WindowState state = getState();
  425. // update draggable on widget
  426. window.setDraggable(
  427. state.draggable && state.windowMode == WindowMode.NORMAL);
  428. // update resizable on widget
  429. window.setResizable(
  430. state.resizable && state.windowMode == WindowMode.NORMAL);
  431. updateComponentSize();
  432. updateWindowPosition();
  433. window.updateMaximizeRestoreClassName(state.resizable,
  434. state.windowMode);
  435. window.updateContentsSize();
  436. }
  437. /**
  438. * Maximizes or restores the window depending on the current mode.
  439. */
  440. protected void onMaximizeRestore() {
  441. WindowState state = getState();
  442. if (state.resizable) {
  443. if (state.windowMode == WindowMode.MAXIMIZED) {
  444. state.windowMode = WindowMode.NORMAL;
  445. } else {
  446. state.windowMode = WindowMode.MAXIMIZED;
  447. }
  448. updateWindowMode();
  449. VWindow window = getWidget();
  450. window.bringToFront();
  451. getRpcProxy(WindowServerRpc.class)
  452. .windowModeChanged(state.windowMode);
  453. }
  454. }
  455. /**
  456. * Gives the WindowConnector an order number. As a side effect, moves the
  457. * window according to its order number so the windows are stacked. This
  458. * method should be called for each window in the order they should appear.
  459. */
  460. public void setWindowOrderAndPosition() {
  461. getWidget().setWindowOrderAndPosition();
  462. }
  463. @Override
  464. public boolean hasTooltip() {
  465. /*
  466. * Tooltip event handler always needed on the window widget to make sure
  467. * tooltips are properly hidden. (#11448)
  468. */
  469. return true;
  470. }
  471. @Override
  472. public void onWindowMove(WindowMoveEvent event) {
  473. getRpcProxy(WindowServerRpc.class).windowMoved(event.getNewX(),
  474. event.getNewY());
  475. }
  476. private boolean isKeyEnterOrSpace(int keyCode) {
  477. return keyCode == KeyCodes.KEY_ENTER || keyCode == KeyCodes.KEY_SPACE;
  478. }
  479. }