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.

Page.java 48KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443
  1. /*
  2. * Copyright 2000-2018 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.server;
  17. import java.io.Serializable;
  18. import java.lang.reflect.Method;
  19. import java.net.URI;
  20. import java.net.URISyntaxException;
  21. import java.util.ArrayList;
  22. import java.util.Collection;
  23. import java.util.EventObject;
  24. import java.util.LinkedHashSet;
  25. import java.util.LinkedList;
  26. import java.util.List;
  27. import com.vaadin.annotations.HtmlImport;
  28. import com.vaadin.annotations.StyleSheet;
  29. import com.vaadin.event.EventRouter;
  30. import com.vaadin.event.SerializableEventListener;
  31. import com.vaadin.shared.Registration;
  32. import com.vaadin.shared.ui.BorderStyle;
  33. import com.vaadin.shared.ui.ui.PageClientRpc;
  34. import com.vaadin.shared.ui.ui.PageState;
  35. import com.vaadin.shared.ui.ui.UIConstants;
  36. import com.vaadin.shared.ui.ui.UIState;
  37. import com.vaadin.shared.util.SharedUtil;
  38. import com.vaadin.ui.Dependency;
  39. import com.vaadin.ui.JavaScript;
  40. import com.vaadin.ui.LegacyWindow;
  41. import com.vaadin.ui.Link;
  42. import com.vaadin.ui.Notification;
  43. import com.vaadin.ui.UI;
  44. import com.vaadin.util.ReflectTools;
  45. public class Page implements Serializable {
  46. /**
  47. * Listener that gets notified when the size of the browser window
  48. * containing the uI has changed.
  49. *
  50. * @see #addBrowserWindowResizeListener(BrowserWindowResizeListener)
  51. */
  52. @FunctionalInterface
  53. public interface BrowserWindowResizeListener extends SerializableEventListener {
  54. /**
  55. * Invoked when the browser window containing a UI has been resized.
  56. *
  57. * @param event
  58. * a browser window resize event
  59. */
  60. public void browserWindowResized(BrowserWindowResizeEvent event);
  61. }
  62. /**
  63. * Event that is fired when a browser window containing a uI is resized.
  64. */
  65. public static class BrowserWindowResizeEvent extends EventObject {
  66. private final int width;
  67. private final int height;
  68. /**
  69. * Creates a new event.
  70. *
  71. * @param source
  72. * the uI for which the browser window has been resized
  73. * @param width
  74. * the new width of the browser window
  75. * @param height
  76. * the new height of the browser window
  77. */
  78. public BrowserWindowResizeEvent(Page source, int width, int height) {
  79. super(source);
  80. this.width = width;
  81. this.height = height;
  82. }
  83. @Override
  84. public Page getSource() {
  85. return (Page) super.getSource();
  86. }
  87. /**
  88. * Gets the new browser window height.
  89. *
  90. * @return an integer with the new pixel height of the browser window
  91. */
  92. public int getHeight() {
  93. return height;
  94. }
  95. /**
  96. * Gets the new browser window width.
  97. *
  98. * @return an integer with the new pixel width of the browser window
  99. */
  100. public int getWidth() {
  101. return width;
  102. }
  103. }
  104. /**
  105. * Private class for storing properties related to opening resources.
  106. */
  107. private class OpenResource implements Serializable {
  108. /**
  109. * The resource to open
  110. */
  111. private final Resource resource;
  112. /**
  113. * The name of the target window
  114. */
  115. private final String name;
  116. /**
  117. * The width of the target window
  118. */
  119. private final int width;
  120. /**
  121. * The height of the target window
  122. */
  123. private final int height;
  124. /**
  125. * The border style of the target window
  126. */
  127. private final BorderStyle border;
  128. private final boolean tryToOpenAsPopup;
  129. /**
  130. * Creates a new open resource.
  131. *
  132. * @param url
  133. * The URL to open
  134. * @param name
  135. * The name of the target window
  136. * @param width
  137. * The width of the target window
  138. * @param height
  139. * The height of the target window
  140. * @param border
  141. * The border style of the target window
  142. * @param tryToOpenAsPopup
  143. * Should try to open as a pop-up
  144. */
  145. private OpenResource(String url, String name, int width, int height,
  146. BorderStyle border, boolean tryToOpenAsPopup) {
  147. this(new ExternalResource(url), name, width, height, border,
  148. tryToOpenAsPopup);
  149. }
  150. /**
  151. * Creates a new open resource.
  152. *
  153. * @param resource
  154. * The resource to open
  155. * @param name
  156. * The name of the target window
  157. * @param width
  158. * The width of the target window
  159. * @param height
  160. * The height of the target window
  161. * @param border
  162. * The border style of the target window
  163. * @param tryToOpenAsPopup
  164. * Should try to open as a pop-up
  165. */
  166. private OpenResource(Resource resource, String name, int width,
  167. int height, BorderStyle border, boolean tryToOpenAsPopup) {
  168. this.resource = resource;
  169. this.name = name;
  170. this.width = width;
  171. this.height = height;
  172. this.border = border;
  173. this.tryToOpenAsPopup = tryToOpenAsPopup;
  174. }
  175. /**
  176. * Paints the open request. Should be painted inside the window.
  177. *
  178. * @param target
  179. * the paint target
  180. * @throws PaintException
  181. * if the paint operation fails
  182. */
  183. private void paintContent(PaintTarget target) throws PaintException {
  184. target.startTag("open");
  185. target.addAttribute("src", resource);
  186. if (name != null && !name.isEmpty()) {
  187. target.addAttribute("name", name);
  188. }
  189. if (!tryToOpenAsPopup) {
  190. target.addAttribute("popup", tryToOpenAsPopup);
  191. }
  192. if (width >= 0) {
  193. target.addAttribute("width", width);
  194. }
  195. if (height >= 0) {
  196. target.addAttribute("height", height);
  197. }
  198. switch (border) {
  199. case MINIMAL:
  200. target.addAttribute("border", "minimal");
  201. break;
  202. case NONE:
  203. target.addAttribute("border", "none");
  204. break;
  205. }
  206. target.endTag("open");
  207. }
  208. }
  209. private static final Method BROWSER_RESIZE_METHOD = ReflectTools.findMethod(
  210. BrowserWindowResizeListener.class, "browserWindowResized",
  211. BrowserWindowResizeEvent.class);
  212. /**
  213. * @deprecated As of 7.0, use {@link BorderStyle#NONE} instead.
  214. */
  215. @Deprecated
  216. public static final BorderStyle BORDER_NONE = BorderStyle.NONE;
  217. /**
  218. * @deprecated As of 7.0, use {@link BorderStyle#MINIMAL} instead.
  219. */
  220. @Deprecated
  221. public static final BorderStyle BORDER_MINIMAL = BorderStyle.MINIMAL;
  222. /**
  223. * @deprecated As of 7.0, use {@link BorderStyle#DEFAULT} instead.
  224. */
  225. @Deprecated
  226. public static final BorderStyle BORDER_DEFAULT = BorderStyle.DEFAULT;
  227. /**
  228. * Listener that that gets notified when the URI fragment of the page
  229. * changes.
  230. *
  231. * @see Page#addUriFragmentChangedListener(UriFragmentChangedListener)
  232. * @deprecated Use {@link PopStateListener} instead
  233. */
  234. @Deprecated
  235. @FunctionalInterface
  236. public interface UriFragmentChangedListener extends SerializableEventListener {
  237. /**
  238. * Event handler method invoked when the URI fragment of the page
  239. * changes. Please note that the initial URI fragment has already been
  240. * set when a new UI is initialized, so there will not be any initial
  241. * event for listeners added during {@link UI#init(VaadinRequest)}.
  242. *
  243. * @see Page#addUriFragmentChangedListener(UriFragmentChangedListener)
  244. *
  245. * @param event
  246. * the URI fragment changed event
  247. */
  248. public void uriFragmentChanged(UriFragmentChangedEvent event);
  249. }
  250. private static final Method URI_FRAGMENT_CHANGED_METHOD = ReflectTools
  251. .findMethod(Page.UriFragmentChangedListener.class,
  252. "uriFragmentChanged", UriFragmentChangedEvent.class);
  253. /**
  254. * Listener that that gets notified when the URI of the page changes due to
  255. * back/forward functionality of the browser.
  256. *
  257. * @see Page#addPopStateListener(PopStateListener)
  258. * @since 8.0
  259. */
  260. @FunctionalInterface
  261. public interface PopStateListener extends SerializableEventListener {
  262. /**
  263. * Event handler method invoked when the URI fragment of the page
  264. * changes. Please note that the initial URI fragment has already been
  265. * set when a new UI is initialized, so there will not be any initial
  266. * event for listeners added during {@link UI#init(VaadinRequest)}.
  267. *
  268. * @see Page#addUriFragmentChangedListener(UriFragmentChangedListener)
  269. *
  270. * @param event
  271. * the URI fragment changed event
  272. */
  273. public void uriChanged(PopStateEvent event);
  274. }
  275. private static final Method URI_CHANGED_METHOD = ReflectTools.findMethod(
  276. Page.PopStateListener.class, "uriChanged", PopStateEvent.class);
  277. /**
  278. * Resources to be opened automatically on next repaint. The list is
  279. * automatically cleared when it has been sent to the client.
  280. */
  281. private final LinkedList<OpenResource> openList = new LinkedList<>();
  282. /**
  283. * Event fired when the URI fragment of a <code>Page</code> changes.
  284. *
  285. * @see Page#addUriFragmentChangedListener(UriFragmentChangedListener)
  286. */
  287. public static class UriFragmentChangedEvent extends EventObject {
  288. /**
  289. * The new URI fragment
  290. */
  291. private final String uriFragment;
  292. /**
  293. * Creates a new instance of UriFragmentReader change event.
  294. *
  295. * @param source
  296. * the Source of the event.
  297. * @param uriFragment
  298. * the new uriFragment
  299. */
  300. public UriFragmentChangedEvent(Page source, String uriFragment) {
  301. super(source);
  302. this.uriFragment = uriFragment;
  303. }
  304. /**
  305. * Gets the page in which the fragment has changed.
  306. *
  307. * @return the page in which the fragment has changed
  308. */
  309. public Page getPage() {
  310. return (Page) getSource();
  311. }
  312. /**
  313. * Get the new URI fragment.
  314. *
  315. * @return the new fragment
  316. */
  317. public String getUriFragment() {
  318. return uriFragment;
  319. }
  320. }
  321. /**
  322. * Event fired when the URI of a <code>Page</code> changes (aka HTML 5
  323. * popstate event) on the client side due to browsers back/forward
  324. * functionality.
  325. *
  326. * @see Page#addPopStateListener(PopStateListener)
  327. * @since 8.0
  328. */
  329. public static class PopStateEvent extends EventObject {
  330. /**
  331. * The new URI as String
  332. */
  333. private final String uri;
  334. /**
  335. * Creates a new instance of PopstateEvent.
  336. *
  337. * @param source
  338. * the Source of the event.
  339. * @param uri
  340. * the new uri
  341. */
  342. public PopStateEvent(Page source, String uri) {
  343. super(source);
  344. this.uri = uri;
  345. }
  346. /**
  347. * Gets the page in which the uri has changed.
  348. *
  349. * @return the page in which the uri has changed
  350. */
  351. public Page getPage() {
  352. return (Page) getSource();
  353. }
  354. /**
  355. * Get the new URI.
  356. *
  357. * @return the new uri
  358. */
  359. public String getUri() {
  360. return uri;
  361. }
  362. }
  363. @FunctionalInterface
  364. private static interface InjectedStyle extends Serializable {
  365. public void paint(int id, PaintTarget target) throws PaintException;
  366. }
  367. private static class InjectedStyleString implements InjectedStyle {
  368. private final String css;
  369. public InjectedStyleString(String css) {
  370. this.css = css;
  371. }
  372. @Override
  373. public void paint(int id, PaintTarget target) throws PaintException {
  374. target.startTag("css-string");
  375. target.addAttribute("id", id);
  376. target.addText(css);
  377. target.endTag("css-string");
  378. }
  379. }
  380. private static class InjectedStyleResource implements InjectedStyle {
  381. private final Resource resource;
  382. public InjectedStyleResource(Resource resource) {
  383. this.resource = resource;
  384. }
  385. @Override
  386. public void paint(int id, PaintTarget target) throws PaintException {
  387. target.startTag("css-resource");
  388. target.addAttribute("id", id);
  389. target.addAttribute("url", resource);
  390. target.endTag("css-resource");
  391. }
  392. @Override
  393. public boolean equals(Object obj) {
  394. if (obj == this) {
  395. return true;
  396. } else if (obj instanceof InjectedStyleResource) {
  397. InjectedStyleResource that = (InjectedStyleResource) obj;
  398. return resource.equals(that.resource);
  399. } else {
  400. return false;
  401. }
  402. }
  403. @Override
  404. public int hashCode() {
  405. return resource.hashCode();
  406. }
  407. }
  408. /**
  409. * Contains dynamically injected styles injected in the HTML document at
  410. * runtime.
  411. *
  412. * @since 7.1
  413. */
  414. public static class Styles implements Serializable {
  415. private LinkedHashSet<InjectedStyle> injectedStyles = new LinkedHashSet<>();
  416. private LinkedHashSet<InjectedStyle> pendingInjections = new LinkedHashSet<>();
  417. private final UI ui;
  418. private Styles(UI ui) {
  419. this.ui = ui;
  420. }
  421. /**
  422. * Injects a raw CSS string into the page.
  423. *
  424. * @param css
  425. * The CSS to inject
  426. */
  427. public void add(String css) {
  428. if (css == null) {
  429. throw new IllegalArgumentException(
  430. "Cannot inject null CSS string");
  431. }
  432. pendingInjections.add(new InjectedStyleString(css));
  433. ui.markAsDirty();
  434. }
  435. /**
  436. * Injects a CSS resource into the page.
  437. *
  438. * @param resource
  439. * The resource to inject.
  440. */
  441. public void add(Resource resource) {
  442. if (resource == null) {
  443. throw new IllegalArgumentException(
  444. "Cannot inject null resource");
  445. }
  446. InjectedStyleResource injection = new InjectedStyleResource(
  447. resource);
  448. if (!injectedStyles.contains(injection)
  449. && pendingInjections.add(injection)) {
  450. ui.markAsDirty();
  451. }
  452. }
  453. private void paint(PaintTarget target) throws PaintException {
  454. // If full repaint repaint all injections
  455. if (target.isFullRepaint()) {
  456. injectedStyles.addAll(pendingInjections);
  457. pendingInjections = injectedStyles;
  458. injectedStyles = new LinkedHashSet<>();
  459. }
  460. if (!pendingInjections.isEmpty()) {
  461. target.startTag("css-injections");
  462. for (InjectedStyle pending : pendingInjections) {
  463. int id = injectedStyles.size();
  464. pending.paint(id, target);
  465. injectedStyles.add(pending);
  466. }
  467. pendingInjections.clear();
  468. target.endTag("css-injections");
  469. }
  470. }
  471. }
  472. private EventRouter eventRouter;
  473. private final UI uI;
  474. private int browserWindowWidth = -1;
  475. private int browserWindowHeight = -1;
  476. private JavaScript javaScript;
  477. private Styles styles;
  478. /**
  479. * The current browser location.
  480. */
  481. private URI location;
  482. private final PageState state;
  483. private String windowName;
  484. private String newPushState;
  485. private String newReplaceState;
  486. private List<Dependency> pendingDependencies;
  487. public Page(UI uI, PageState state) {
  488. this.uI = uI;
  489. this.state = state;
  490. }
  491. private Registration addListener(Class<?> eventType, SerializableEventListener listener,
  492. Method method) {
  493. if (!hasEventRouter()) {
  494. eventRouter = new EventRouter();
  495. }
  496. return eventRouter.addListener(eventType, listener, method);
  497. }
  498. private void removeListener(Class<?> eventType, SerializableEventListener listener,
  499. Method method) {
  500. if (hasEventRouter()) {
  501. eventRouter.removeListener(eventType, listener, method);
  502. }
  503. }
  504. /**
  505. * Adds a listener that gets notified every time the URI fragment of this
  506. * page is changed. Please note that the initial URI fragment has already
  507. * been set when a new UI is initialized, so there will not be any initial
  508. * event for listeners added during {@link UI#init(VaadinRequest)}.
  509. *
  510. * @see #getUriFragment()
  511. * @see #setUriFragment(String)
  512. * @see Registration
  513. *
  514. * @param listener
  515. * the URI fragment listener to add
  516. * @return a registration object for removing the listener
  517. * @deprecated Use {@link Page#addPopStateListener(PopStateListener)}
  518. * instead
  519. * @since 8.0
  520. */
  521. @Deprecated
  522. public Registration addUriFragmentChangedListener(
  523. Page.UriFragmentChangedListener listener) {
  524. return addListener(UriFragmentChangedEvent.class, listener,
  525. URI_FRAGMENT_CHANGED_METHOD);
  526. }
  527. /**
  528. * Adds a listener that gets notified every time the URI of this page is
  529. * changed due to back/forward functionality of the browser.
  530. * <p>
  531. * Note that one only gets notified when the back/forward button affects
  532. * history changes with-in same UI, created by
  533. * {@link Page#pushState(String)} or {@link Page#replaceState(String)}
  534. * functions.
  535. *
  536. * @see #getLocation()
  537. * @see Registration
  538. *
  539. * @param listener
  540. * the Popstate listener to add
  541. * @return a registration object for removing the listener
  542. * @since 8.0
  543. */
  544. public Registration addPopStateListener(Page.PopStateListener listener) {
  545. return addListener(PopStateEvent.class, listener, URI_CHANGED_METHOD);
  546. }
  547. /**
  548. * Removes a URI fragment listener that was previously added to this page.
  549. *
  550. * @param listener
  551. * the URI fragment listener to remove
  552. *
  553. * @see Page#addUriFragmentChangedListener(UriFragmentChangedListener)
  554. *
  555. * @deprecated As of 8.0, replaced by {@link Registration#remove()} in the
  556. * registration object returned from
  557. * {@link #addUriFragmentChangedListener(UriFragmentChangedListener)}.
  558. */
  559. @Deprecated
  560. public void removeUriFragmentChangedListener(
  561. Page.UriFragmentChangedListener listener) {
  562. removeListener(UriFragmentChangedEvent.class, listener,
  563. URI_FRAGMENT_CHANGED_METHOD);
  564. }
  565. /**
  566. * Sets the fragment part in the current location URI. Optionally fires a
  567. * {@link UriFragmentChangedEvent}.
  568. * <p>
  569. * The fragment is the optional last component of a URI, prefixed with a
  570. * hash sign ("#").
  571. * <p>
  572. * Passing an empty string as <code>newFragment</code> sets an empty
  573. * fragment (a trailing "#" in the URI.) Passing <code>null</code> if there
  574. * is already a non-null fragment will leave a trailing # in the URI since
  575. * removing it would cause the browser to reload the page. This is not fully
  576. * consistent with the semantics of {@link java.net.URI}.
  577. *
  578. * @param newUriFragment
  579. * The new fragment.
  580. * @param fireEvents
  581. * true to fire event
  582. *
  583. * @see #getUriFragment()
  584. * @see #setLocation(URI)
  585. * @see UriFragmentChangedEvent
  586. * @see Page.UriFragmentChangedListener
  587. *
  588. */
  589. public void setUriFragment(String newUriFragment, boolean fireEvents) {
  590. String oldUriFragment = location.getFragment();
  591. if (newUriFragment == null && getUriFragment() != null) {
  592. // Can't completely remove the fragment once it has been set, will
  593. // instead set it to the empty string
  594. newUriFragment = "";
  595. }
  596. if (newUriFragment == oldUriFragment || (newUriFragment != null
  597. && newUriFragment.equals(oldUriFragment))) {
  598. return;
  599. }
  600. try {
  601. location = new URI(location.getScheme(),
  602. location.getSchemeSpecificPart(), newUriFragment);
  603. pushState(location);
  604. } catch (URISyntaxException e) {
  605. // This should not actually happen as the fragment syntax is not
  606. // constrained
  607. throw new RuntimeException(e);
  608. }
  609. if (fireEvents) {
  610. fireEvent(new UriFragmentChangedEvent(this, newUriFragment));
  611. }
  612. }
  613. private void fireEvent(EventObject event) {
  614. if (hasEventRouter()) {
  615. eventRouter.fireEvent(event);
  616. }
  617. }
  618. /**
  619. * Sets URI fragment. This method fires a {@link UriFragmentChangedEvent}
  620. *
  621. * @param newUriFragment
  622. * id of the new fragment
  623. * @see UriFragmentChangedEvent
  624. * @see Page.UriFragmentChangedListener
  625. */
  626. public void setUriFragment(String newUriFragment) {
  627. setUriFragment(newUriFragment, true);
  628. }
  629. /**
  630. * Gets the currently set URI fragment.
  631. * <p>
  632. * Returns <code>null</code> if there is no fragment and an empty string if
  633. * there is an empty fragment.
  634. * <p>
  635. * To listen to changes in fragment, hook a
  636. * {@link Page.UriFragmentChangedListener}.
  637. *
  638. * @return the current fragment in browser location URI.
  639. *
  640. * @see #getLocation()
  641. * @see #setUriFragment(String)
  642. * @see #addUriFragmentChangedListener(UriFragmentChangedListener)
  643. */
  644. public String getUriFragment() {
  645. return location.getFragment();
  646. }
  647. public void init(VaadinRequest request) {
  648. // NOTE: UI.refresh makes assumptions about the semantics of this
  649. // method.
  650. // It should be kept in sync if this method is changed.
  651. // Extract special parameter sent by vaadinBootstrap.js
  652. String location = request.getParameter("v-loc");
  653. String clientWidth = request.getParameter("v-cw");
  654. String clientHeight = request.getParameter("v-ch");
  655. windowName = request.getParameter("v-wn");
  656. if (location != null) {
  657. try {
  658. this.location = new URI(location);
  659. } catch (URISyntaxException e) {
  660. throw new RuntimeException(
  661. "Invalid location URI received from client", e);
  662. }
  663. }
  664. if (clientWidth != null && clientHeight != null) {
  665. try {
  666. browserWindowWidth = Integer.parseInt(clientWidth);
  667. browserWindowHeight = Integer.parseInt(clientHeight);
  668. } catch (NumberFormatException e) {
  669. throw new RuntimeException(
  670. "Invalid window size received from client", e);
  671. }
  672. }
  673. }
  674. public WebBrowser getWebBrowser() {
  675. return uI.getSession().getBrowser();
  676. }
  677. /**
  678. * Gets the window.name value of the browser window of this page.
  679. *
  680. * @since 7.2
  681. *
  682. * @return the window name, <code>null</code> if the name is not known
  683. */
  684. public String getWindowName() {
  685. return windowName;
  686. }
  687. /**
  688. * For internal use only. Updates the internal state with the given values.
  689. * Does not resize the Page or browser window.
  690. *
  691. * @deprecated As of 7.2, use
  692. * {@link #updateBrowserWindowSize(int, int, boolean)} instead.
  693. *
  694. * @param width
  695. * the new browser window width
  696. * @param height
  697. * the new browse window height
  698. */
  699. @Deprecated
  700. public void updateBrowserWindowSize(int width, int height) {
  701. updateBrowserWindowSize(width, height, true);
  702. }
  703. /**
  704. * For internal use only. Updates the internal state with the given values.
  705. * Does not resize the Page or browser window.
  706. *
  707. * @since 7.2
  708. *
  709. * @param width
  710. * the new browser window width
  711. * @param height
  712. * the new browser window height
  713. * @param fireEvents
  714. * whether to fire {@link BrowserWindowResizeEvent} if the size
  715. * changes
  716. */
  717. public void updateBrowserWindowSize(int width, int height,
  718. boolean fireEvents) {
  719. boolean sizeChanged = false;
  720. if (width != browserWindowWidth) {
  721. browserWindowWidth = width;
  722. sizeChanged = true;
  723. }
  724. if (height != browserWindowHeight) {
  725. browserWindowHeight = height;
  726. sizeChanged = true;
  727. }
  728. if (fireEvents && sizeChanged) {
  729. fireEvent(new BrowserWindowResizeEvent(this, browserWindowWidth,
  730. browserWindowHeight));
  731. }
  732. }
  733. /**
  734. * Adds a new {@link BrowserWindowResizeListener} to this UI. The listener
  735. * will be notified whenever the browser window within which this UI resides
  736. * is resized.
  737. * <p>
  738. * In most cases, the UI should be in lazy resize mode when using browser
  739. * window resize listeners. Otherwise, a large number of events can be
  740. * received while a resize is being performed. Use
  741. * {@link UI#setResizeLazy(boolean)}.
  742. * </p>
  743. *
  744. * @param resizeListener
  745. * the listener to add
  746. * @return a registration object for removing the listener
  747. *
  748. * @see BrowserWindowResizeListener#browserWindowResized(BrowserWindowResizeEvent)
  749. * @see UI#setResizeLazy(boolean)
  750. * @see Registration
  751. * @since 8.0
  752. */
  753. public Registration addBrowserWindowResizeListener(
  754. BrowserWindowResizeListener resizeListener) {
  755. Registration registration = addListener(BrowserWindowResizeEvent.class,
  756. resizeListener, BROWSER_RESIZE_METHOD);
  757. getState(true).hasResizeListeners = true;
  758. return () -> {
  759. registration.remove();
  760. getState(true).hasResizeListeners = hasEventRouter()
  761. && eventRouter.hasListeners(BrowserWindowResizeEvent.class);
  762. };
  763. }
  764. /**
  765. * Removes a {@link BrowserWindowResizeListener} from this UI. The listener
  766. * will no longer be notified when the browser window is resized.
  767. *
  768. * @param resizeListener
  769. * the listener to remove
  770. *
  771. * @deprecated As of 8.0, replaced by {@link Registration#remove()} in the
  772. * registration object returned from
  773. * {@link #addBrowserWindowResizeListener(BrowserWindowResizeListener)}
  774. * .
  775. */
  776. @Deprecated
  777. public void removeBrowserWindowResizeListener(
  778. BrowserWindowResizeListener resizeListener) {
  779. removeListener(BrowserWindowResizeEvent.class, resizeListener,
  780. BROWSER_RESIZE_METHOD);
  781. getState(true).hasResizeListeners = hasEventRouter()
  782. && eventRouter.hasListeners(BrowserWindowResizeEvent.class);
  783. }
  784. /**
  785. * Gets the last known height of the browser window in which this UI
  786. * resides.
  787. *
  788. * @return the browser window height in pixels
  789. */
  790. public int getBrowserWindowHeight() {
  791. return browserWindowHeight;
  792. }
  793. /**
  794. * Gets the last known width of the browser window in which this uI resides.
  795. *
  796. * @return the browser window width in pixels
  797. */
  798. public int getBrowserWindowWidth() {
  799. return browserWindowWidth;
  800. }
  801. public JavaScript getJavaScript() {
  802. if (javaScript == null) {
  803. // Create and attach on first use
  804. javaScript = new JavaScript();
  805. javaScript.extend(uI);
  806. }
  807. return javaScript;
  808. }
  809. /**
  810. * Returns that stylesheet associated with this Page. The stylesheet
  811. * contains additional styles injected at runtime into the HTML document.
  812. *
  813. * @since 7.1
  814. */
  815. public Styles getStyles() {
  816. if (styles == null) {
  817. styles = new Styles(uI);
  818. }
  819. return styles;
  820. }
  821. public void paintContent(PaintTarget target) throws PaintException {
  822. if (!openList.isEmpty()) {
  823. for (OpenResource anOpenList : openList) {
  824. (anOpenList).paintContent(target);
  825. }
  826. openList.clear();
  827. }
  828. if (newPushState != null) {
  829. target.addAttribute(UIConstants.ATTRIBUTE_PUSH_STATE, newPushState);
  830. newPushState = null;
  831. }
  832. if (newReplaceState != null) {
  833. target.addAttribute(UIConstants.ATTRIBUTE_REPLACE_STATE,
  834. newReplaceState);
  835. newReplaceState = null;
  836. }
  837. if (styles != null) {
  838. styles.paint(target);
  839. }
  840. }
  841. /**
  842. * Navigates this page to the given URI. The contents of this page in the
  843. * browser is replaced with whatever is returned for the given URI.
  844. * <p>
  845. * This method should not be used to start downloads, as the client side
  846. * will assume the browser will navigate away when opening the URI. Use one
  847. * of the {@code Page.open} methods or {@code FileDownloader} instead.
  848. *
  849. * @see #open(String, String)
  850. * @see FileDownloader
  851. *
  852. * @param uri
  853. * the URI to show
  854. */
  855. public void setLocation(String uri) {
  856. openList.add(
  857. new OpenResource(uri, "_self", -1, -1, BORDER_DEFAULT, false));
  858. uI.markAsDirty();
  859. }
  860. /**
  861. * Navigates this page to the given URI. The contents of this page in the
  862. * browser is replaced with whatever is returned for the given URI.
  863. * <p>
  864. * This method should not be used to start downloads, as the client side
  865. * will assume the browser will navigate away when opening the URI. Use one
  866. * of the {@code Page.open} methods or {@code FileDownloader} instead.
  867. *
  868. * @see #open(String, String)
  869. * @see FileDownloader
  870. *
  871. * @param uri
  872. * the URI to show
  873. */
  874. public void setLocation(URI uri) {
  875. setLocation(uri.toString());
  876. }
  877. /**
  878. * Returns the location URI of this page, as reported by the browser. Note
  879. * that this may not be consistent with the server URI the application is
  880. * deployed in due to potential proxies, redirections and similar.
  881. *
  882. * @return The browser location URI.
  883. * @throws IllegalStateException
  884. * if the
  885. * {@link DeploymentConfiguration#isSendUrlsAsParameters()} is
  886. * set to {@code false}
  887. */
  888. public URI getLocation() throws IllegalStateException {
  889. if (location == null) {
  890. if (uI.getSession() != null && !uI.getSession().getConfiguration()
  891. .isSendUrlsAsParameters()) {
  892. throw new IllegalStateException("Location is not available as the "
  893. + Constants.SERVLET_PARAMETER_SENDURLSASPARAMETERS
  894. + " parameter is configured as false");
  895. } else if (VaadinSession.getCurrent() == null) {
  896. throw new IllegalStateException("Location is not available as the "
  897. + Constants.SERVLET_PARAMETER_SENDURLSASPARAMETERS
  898. + " parameter state cannot be determined");
  899. } else if (!VaadinSession.getCurrent().getConfiguration()
  900. .isSendUrlsAsParameters()) {
  901. throw new IllegalStateException("Location is not available as the "
  902. + Constants.SERVLET_PARAMETER_SENDURLSASPARAMETERS
  903. + " parameter is configured as false");
  904. }
  905. }
  906. return location;
  907. }
  908. /**
  909. * Updates the browsers URI without causing actual page change. This method
  910. * is useful if you wish implement "deep linking" to your application.
  911. * Calling the method also adds a new entry to clients browser history and
  912. * you can further use {@link PopStateListener} to track the usage of
  913. * back/forward feature in browser.
  914. * <p>
  915. * Note, the current implementation supports setting only one new uri in one
  916. * user interaction.
  917. *
  918. * @param uri
  919. * to be used for pushState operation. The URI is resolved over
  920. * the current location. If the given URI is absolute, it must be
  921. * of same origin as the current URI or the browser will not
  922. * accept the new value.
  923. * @since 8.0
  924. */
  925. public void pushState(String uri) {
  926. newPushState = uri;
  927. uI.markAsDirty();
  928. location = location.resolve(uri);
  929. }
  930. /**
  931. * Updates the browsers URI without causing actual page change. This method
  932. * is useful if you wish implement "deep linking" to your application.
  933. * Calling the method also adds a new entry to clients browser history and
  934. * you can further use {@link PopStateListener} to track the usage of
  935. * back/forward feature in browser.
  936. * <p>
  937. * Note, the current implementation supports setting only one new uri in one
  938. * user interaction.
  939. *
  940. * @param uri
  941. * the URI to be used for pushState operation. The URI is
  942. * resolved over the current location. If the given URI is
  943. * absolute, it must be of same origin as the current URI or the
  944. * browser will not accept the new value.
  945. * @since 8.0
  946. */
  947. public void pushState(URI uri) {
  948. pushState(uri.toString());
  949. }
  950. /**
  951. * Updates the browsers URI without causing actual page change in the same
  952. * way as {@link #pushState(String)}, but does not add new entry to browsers
  953. * history.
  954. *
  955. * @param uri
  956. * the URI to be used for replaceState operation. The URI is
  957. * resolved over the current location. If the given URI is
  958. * absolute, it must be of same origin as the current URI or the
  959. * browser will not accept the new value.
  960. * @since 8.0
  961. */
  962. public void replaceState(String uri) {
  963. newReplaceState = uri;
  964. uI.markAsDirty();
  965. location = location.resolve(uri);
  966. }
  967. /**
  968. * Updates the browsers URI without causing actual page change in the same
  969. * way as {@link #pushState(URI)}, but does not add new entry to browsers
  970. * history.
  971. *
  972. * @param uri
  973. * the URI to be used for replaceState operation. The URI is
  974. * resolved over the current location. If the given URI is
  975. * absolute, it must be of same origin as the current URI or the
  976. * browser will not accept the new value.
  977. * @since 8.0
  978. */
  979. public void replaceState(URI uri) {
  980. replaceState(uri.toString());
  981. }
  982. /**
  983. * For internal use only. Used to update the server-side location when the
  984. * client-side location changes.
  985. *
  986. * @deprecated As of 7.2, use {@link #updateLocation(String, boolean)}
  987. * instead.
  988. *
  989. * @param location
  990. * the new location URI
  991. */
  992. @Deprecated
  993. public void updateLocation(String location) {
  994. updateLocation(location, true, false);
  995. }
  996. /**
  997. * For internal use only. Used to update the server-side location when the
  998. * client-side location changes.
  999. *
  1000. * @since 8.0
  1001. *
  1002. * @param location
  1003. * the new location URI
  1004. * @param fireEvents
  1005. * whether to fire {@link UriFragmentChangedEvent} if the URI
  1006. * fragment changes
  1007. * @param firePopstate
  1008. * whether to fire {@link PopStateEvent}
  1009. */
  1010. public void updateLocation(String location, boolean fireEvents,
  1011. boolean firePopstate) {
  1012. try {
  1013. String oldUriFragment = this.location.getFragment();
  1014. this.location = new URI(location);
  1015. String newUriFragment = this.location.getFragment();
  1016. if (fireEvents
  1017. && !SharedUtil.equals(oldUriFragment, newUriFragment)) {
  1018. fireEvent(new UriFragmentChangedEvent(this, newUriFragment));
  1019. }
  1020. if (firePopstate) {
  1021. fireEvent(new PopStateEvent(this, location));
  1022. }
  1023. } catch (URISyntaxException e) {
  1024. throw new RuntimeException(e);
  1025. }
  1026. }
  1027. /**
  1028. * Opens the given url in a window with the given name. Equivalent to
  1029. * {@link #open(String, String, boolean) open} (url, windowName, true) .
  1030. * <p>
  1031. * The supplied {@code windowName} is used as the target name in a
  1032. * window.open call in the client. This means that special values such as
  1033. * "_blank", "_self", "_top", "_parent" have special meaning. An empty or
  1034. * <code>null</code> window name is also a special case.
  1035. * </p>
  1036. * <p>
  1037. * "", null and "_self" as {@code windowName} all causes the URL to be
  1038. * opened in the current window, replacing any old contents. For
  1039. * downloadable content you should avoid "_self" as "_self" causes the
  1040. * client to skip rendering of any other changes as it considers them
  1041. * irrelevant (the page will be replaced by the response from the URL). This
  1042. * can speed up the opening of a URL, but it might also put the client side
  1043. * into an inconsistent state if the window content is not completely
  1044. * replaced e.g., if the URL is downloaded instead of displayed in the
  1045. * browser.
  1046. * </p>
  1047. * <p>
  1048. * "_blank" as {@code windowName} causes the URL to always be opened in a
  1049. * new window or tab (depends on the browser and browser settings).
  1050. * </p>
  1051. * <p>
  1052. * "_top" and "_parent" as {@code windowName} works as specified by the HTML
  1053. * standard.
  1054. * </p>
  1055. * <p>
  1056. * Any other {@code windowName} will open the URL in a window with that
  1057. * name, either by opening a new window/tab in the browser or by replacing
  1058. * the contents of an existing window with that name.
  1059. * </p>
  1060. * <p>
  1061. * Please note that opening a popup window in this way may be blocked by the
  1062. * browser's popup-blocker because the new browser window is opened when
  1063. * processing a response from the server. To avoid this, you should instead
  1064. * use {@link Link} for opening the window because browsers are more
  1065. * forgiving when the window is opened directly from a client-side click
  1066. * event.
  1067. * </p>
  1068. *
  1069. * @param url
  1070. * the URL to open.
  1071. * @param windowName
  1072. * the name of the window.
  1073. */
  1074. public void open(String url, String windowName) {
  1075. open(url, windowName, true);
  1076. }
  1077. /**
  1078. * Opens the given url in a window with the given name. Equivalent to
  1079. * {@link #open(String, String, boolean) open} (url, windowName, true) .
  1080. * <p>
  1081. * The supplied {@code windowName} is used as the target name in a
  1082. * window.open call in the client. This means that special values such as
  1083. * "_blank", "_self", "_top", "_parent" have special meaning. An empty or
  1084. * <code>null</code> window name is also a special case.
  1085. * </p>
  1086. * <p>
  1087. * "", null and "_self" as {@code windowName} all causes the URL to be
  1088. * opened in the current window, replacing any old contents. For
  1089. * downloadable content you should avoid "_self" as "_self" causes the
  1090. * client to skip rendering of any other changes as it considers them
  1091. * irrelevant (the page will be replaced by the response from the URL). This
  1092. * can speed up the opening of a URL, but it might also put the client side
  1093. * into an inconsistent state if the window content is not completely
  1094. * replaced e.g., if the URL is downloaded instead of displayed in the
  1095. * browser.
  1096. * </p>
  1097. * <p>
  1098. * "_blank" as {@code windowName} causes the URL to always be opened in a
  1099. * new window or tab (depends on the browser and browser settings).
  1100. * </p>
  1101. * <p>
  1102. * "_top" and "_parent" as {@code windowName} works as specified by the HTML
  1103. * standard.
  1104. * </p>
  1105. * <p>
  1106. * Any other {@code windowName} will open the URL in a window with that
  1107. * name, either by opening a new window/tab in the browser or by replacing
  1108. * the contents of an existing window with that name.
  1109. * </p>
  1110. * <p>
  1111. * Please note that opening a popup window in this way may be blocked by the
  1112. * browser's popup-blocker because the new browser window is opened when
  1113. * processing a response from the server. To avoid this, you should instead
  1114. * use {@link Link} for opening the window because browsers are more
  1115. * forgiving when the window is opened directly from a client-side click
  1116. * event.
  1117. * </p>
  1118. *
  1119. * @param url
  1120. * the URL to open.
  1121. * @param windowName
  1122. * the name of the window.
  1123. * @param tryToOpenAsPopup
  1124. * Whether to try to force the resource to be opened in a new
  1125. * window
  1126. */
  1127. public void open(String url, String windowName, boolean tryToOpenAsPopup) {
  1128. openList.add(new OpenResource(url, windowName, -1, -1, BORDER_DEFAULT,
  1129. tryToOpenAsPopup));
  1130. uI.markAsDirty();
  1131. }
  1132. /**
  1133. * Opens the given URL in a window with the given size, border and name. For
  1134. * more information on the meaning of {@code windowName}, see
  1135. * {@link #open(String, String)}.
  1136. * <p>
  1137. * Please note that opening a popup window in this way may be blocked by the
  1138. * browser's popup-blocker because the new browser window is opened when
  1139. * processing a response from the server. To avoid this, you should instead
  1140. * use {@link Link} for opening the window because browsers are more
  1141. * forgiving when the window is opened directly from a client-side click
  1142. * event.
  1143. * </p>
  1144. *
  1145. * @param url
  1146. * the URL to open.
  1147. * @param windowName
  1148. * the name of the window.
  1149. * @param width
  1150. * the width of the window in pixels
  1151. * @param height
  1152. * the height of the window in pixels
  1153. * @param border
  1154. * the border style of the window.
  1155. */
  1156. public void open(String url, String windowName, int width, int height,
  1157. BorderStyle border) {
  1158. openList.add(
  1159. new OpenResource(url, windowName, width, height, border, true));
  1160. uI.markAsDirty();
  1161. }
  1162. /**
  1163. * @deprecated As of 7.0, only retained to maintain compatibility with
  1164. * LegacyWindow.open methods. See documentation for
  1165. * {@link LegacyWindow#open(Resource, String, int, int, BorderStyle)}
  1166. * for discussion about replacing API.
  1167. */
  1168. @Deprecated
  1169. public void open(Resource resource, String windowName, int width,
  1170. int height, BorderStyle border) {
  1171. openList.add(new OpenResource(resource, windowName, width, height,
  1172. border, true));
  1173. uI.markAsDirty();
  1174. }
  1175. /**
  1176. * @deprecated As of 7.0, only retained to maintain compatibility with
  1177. * LegacyWindow.open methods. See documentation for
  1178. * {@link LegacyWindow#open(Resource, String, boolean)} for
  1179. * discussion about replacing API.
  1180. */
  1181. @Deprecated
  1182. public void open(Resource resource, String windowName,
  1183. boolean tryToOpenAsPopup) {
  1184. openList.add(new OpenResource(resource, windowName, -1, -1,
  1185. BORDER_DEFAULT, tryToOpenAsPopup));
  1186. uI.markAsDirty();
  1187. }
  1188. /**
  1189. * Shows a notification message.
  1190. *
  1191. * @see Notification
  1192. *
  1193. * @param notification
  1194. * The notification message to show
  1195. *
  1196. * @deprecated As of 7.0, use Notification.show(Page) instead.
  1197. */
  1198. @Deprecated
  1199. public void showNotification(Notification notification) {
  1200. notification.show(this);
  1201. }
  1202. /**
  1203. * Gets the Page to which the current uI belongs. This is automatically
  1204. * defined when processing requests to the server. In other cases, (e.g.
  1205. * from background threads), the current uI is not automatically defined.
  1206. *
  1207. * @see UI#getCurrent()
  1208. *
  1209. * @return the current page instance if available, otherwise
  1210. * <code>null</code>
  1211. */
  1212. public static Page getCurrent() {
  1213. UI currentUI = UI.getCurrent();
  1214. if (currentUI == null) {
  1215. return null;
  1216. }
  1217. return currentUI.getPage();
  1218. }
  1219. /**
  1220. * Sets the page title. The page title is displayed by the browser e.g. as
  1221. * the title of the browser window or as the title of the tab.
  1222. * <p>
  1223. * If the title is set to null, it will not left as-is. Set to empty string
  1224. * to clear the title.
  1225. *
  1226. * @param title
  1227. * the page title to set
  1228. */
  1229. public void setTitle(String title) {
  1230. getState(true).title = title;
  1231. }
  1232. /**
  1233. * Reloads the page in the browser.
  1234. */
  1235. public void reload() {
  1236. uI.getRpcProxy(PageClientRpc.class).reload();
  1237. }
  1238. /**
  1239. * Returns the page state.
  1240. * <p>
  1241. * The page state is transmitted to UIConnector together with
  1242. * {@link UIState} rather than as an individual entity.
  1243. * </p>
  1244. * <p>
  1245. * The state should be considered an internal detail of Page. Classes
  1246. * outside of Page should not access it directly but only through public
  1247. * APIs provided by Page.
  1248. * </p>
  1249. *
  1250. * @since 7.1
  1251. * @param markAsDirty
  1252. * true to mark the state as dirty
  1253. * @return PageState object that can be read in any case and modified if
  1254. * markAsDirty is true
  1255. */
  1256. protected PageState getState(boolean markAsDirty) {
  1257. if (markAsDirty) {
  1258. uI.markAsDirty();
  1259. }
  1260. return state;
  1261. }
  1262. private boolean hasEventRouter() {
  1263. return eventRouter != null;
  1264. }
  1265. /**
  1266. * Add a dependency that should be added to the current page.
  1267. * <p>
  1268. * These dependencies are always added before the dependencies included by
  1269. * using the annotations {@link HtmlImport}, {@link JavaScript} and
  1270. * {@link StyleSheet} during the same request.
  1271. * <p>
  1272. * Please note that these dependencies are always sent to the client side
  1273. * and not filtered out by any {@link DependencyFilter}.
  1274. *
  1275. * @param dependency
  1276. * the dependency to add
  1277. * @since 8.1
  1278. */
  1279. public void addDependency(Dependency dependency) {
  1280. if (pendingDependencies == null) {
  1281. pendingDependencies = new ArrayList<>();
  1282. }
  1283. pendingDependencies.add(dependency);
  1284. }
  1285. /**
  1286. * Returns all pending dependencies.
  1287. * <p>
  1288. * For internal use only, calling this method will clear the pending
  1289. * dependencies.
  1290. *
  1291. * @return the pending dependencies to the current page
  1292. * @since 8.1
  1293. */
  1294. public Collection<Dependency> getPendingDependencies() {
  1295. List<Dependency> copy = new ArrayList<>();
  1296. if (pendingDependencies != null) {
  1297. copy.addAll(pendingDependencies);
  1298. }
  1299. pendingDependencies = null;
  1300. return copy;
  1301. }
  1302. /**
  1303. * Returns the {@link UI} of this {@link Page}.
  1304. *
  1305. * @return the {@link UI} of this {@link Page}.
  1306. *
  1307. * @since 8.2
  1308. */
  1309. public UI getUI() {
  1310. return uI;
  1311. }
  1312. }