/** Returns the first line to be generated to the output, or null if no special first line. */ public static final String outFirstLine(Execution exec, Page page) { if (exec.getAttribute(FIRST_LINE_GENED) == null && !exec.isAsyncUpdate(null)) { exec.setAttribute(FIRST_LINE_GENED, Boolean.TRUE); return trimAndLF(((PageCtrl) page).getFirstLine()); } return ""; }
/** * Generates and returns the ZK specific HTML tags such as stylesheet and JavaScript. * * <p>For each desktop, we have to generate a set of HTML tags to load ZK Client engine, style * sheets and so on. For ZUL pages, it is generated automatically by page.dsp. However, for ZHTML * pages, we have to generate these tags with special component such as org.zkoss.zhtml.Head, such * that the result HTML page is legal. * * @param exec the execution (never null) * @return the string holding the HTML tags, or null if already generated. * @param wapp the Web application. If null, exec.getDesktop().getWebApp() is used. So you have to * specify it if the execution is not associated with desktop (a fake execution, such as * JSP/DSP). * @param deviceType the device type, such as ajax. If null, exec.getDesktop().getDeviceType() is * used. So you have to specify it if the execution is not associated with desktop (a fake * execution). * @see #outHeaderZkTags */ public static String outZkTags(Execution exec, WebApp wapp, String deviceType) { if (exec.getAttribute(ATTR_ZK_TAGS_GENERATED) != null) return null; exec.setAttribute(ATTR_ZK_TAGS_GENERATED, Boolean.TRUE); final StringBuffer sb = new StringBuffer(512) .append('\n') .append(outLangStyleSheets(exec, wapp, deviceType)) .append(outLangJavaScripts(exec, wapp, deviceType)); final Desktop desktop = exec.getDesktop(); if (desktop != null && exec.getAttribute(ATTR_DESKTOP_JS_GENED) == null) { sb.append("<script class=\"z-runonce\" type=\"text/javascript\">\nzkdt('") .append(desktop.getId()) .append("','") .append(getContextURI(exec)) .append("','") .append(desktop.getUpdateURI(null)) .append("','") .append(desktop.getRequestPath()) .append("');") .append(outSpecialJS(desktop)) .append("\n</script>\n"); } return sb.toString(); }
public Set<? extends Component> getAvailableAtClient() { if (!isCropper()) return null; final Tree tree = getTree(); final Component parent = getParent(); final Execution exe = Executions.getCurrent(); final String attrnm = VISIBLE_ITEM + tree.getUuid(); Map<Treeitem, Boolean> map = cast((Map) exe.getAttribute(attrnm)); if (map == null) { // Test very simple case first since getVisibleItems costly if (parent instanceof Treeitem) { for (Treeitem ti = (Treeitem) parent; ; ) { if (!ti.isOpen()) return Collections.emptySet(); Component gp = ti.getParent().getParent(); if (!(gp instanceof Treeitem)) break; ti = (Treeitem) gp; } } map = tree.getVisibleItems(); Executions.getCurrent().setAttribute(attrnm, map); } return map.keySet(); // yes, we return all visible items, not just direct children // in other words, we consider the whole tree as a single scope // See also bug 2814504 }
/** Returns the doc type, or null if not available. It is null or <!DOCTYPE ...>. */ public static final String outDocType(Execution exec, Page page) { if (exec.getAttribute(DOCTYPE_GENED) == null && !exec.isAsyncUpdate(null)) { exec.setAttribute(DOCTYPE_GENED, Boolean.TRUE); final String docType = ((PageCtrl) page).getDocType(); return trimAndLF(docType != null ? docType : page.getDesktop().getDevice().getDocType()); } return ""; }
private void subscribe(EventListener<T> listener, EventListener<T> callback, boolean async) { if (async && _nAsync++ == 0) { final Execution exec = Executions.getCurrent(); if (exec == null) throw new IllegalStateException("Execution required"); ((DesktopCtrl) exec.getDesktop()).enableServerPush(true, this); // B65-ZK-1840 make sure the flag is true after enabling (otherwise disabling will fail) _serverPushEnabled = true; } _listenerInfos.add(new ListenerInfo<T>(listener, callback, async)); }
/** * Returns the content of the specified condition that will be placed inside the header element of * the specified page, or null if it was generated before. For HTML, the header element is the * HEAD element. * * <p>Notice that this method ignores the following invocations against the same page in the same * execution. In other words, it is safe to invoke this method multiple times. * * @param before whether to return the headers that shall be shown before ZK's CSS/JS headers. If * true, only the headers that shall be shown before (such as meta) are returned. If true, * only the headers that shall be shown after (such as link) are returned. */ public static final String outHeaders(Execution exec, Page page, boolean before) { if (page == null) return ""; String attr = "zkHeaderGened" + page.getUuid(); if (before) attr += "Bf"; if (exec.getAttribute(attr) != null) return null; exec.setAttribute(attr, Boolean.TRUE); // generated only once return before ? ((PageCtrl) page).getBeforeHeadTags() : ((PageCtrl) page).getAfterHeadTags(); }
/** * Generates the unavailable message in HTML tags, if any. * * @param exec the execution (never null) */ public static String outUnavailable(Execution exec) { if (exec.getAttribute(ATTR_UNAVAILABLE_GENED) == null && !exec.isAsyncUpdate(null)) { exec.setAttribute(ATTR_UNAVAILABLE_GENED, Boolean.TRUE); final Device device = exec.getDesktop().getDevice(); String s = device.getUnavailableMessage(); return s != null ? "<noscript>\n" + s + "\n</noscript>" : ""; } return ""; // nothing to generate }
private static Map<Component, ShadowInfo> getShadowInfos(boolean autoCreate) { Execution exec = Executions.getCurrent(); if (exec == null) return null; Map<Component, ShadowInfo> result = cast((Map) exec.getAttribute(COMPONENT_INFO)); if (result == null && autoCreate) { result = new HashMap<Component, ShadowInfo>(); exec.setAttribute(COMPONENT_INFO, result); } return result; }
private boolean isEventThreadEnabled(boolean attachedRequired) { Desktop desktop = getDesktop(); if (desktop == null) { if (attachedRequired) throw new SuspendNotAllowedException("Not attached, " + this); final Execution exec = Executions.getCurrent(); if (exec == null || (desktop = exec.getDesktop()) == null) return true; // assume enabled (safer) } return desktop.getWebApp().getConfiguration().isEventThreadEnabled(); }
/** * Returns HTML tags to include all style sheets that are defined in all languages of the * specified device (never null). * * <p>In addition to style sheets defined in lang.xml and lang-addon.xml, it also include: * * <ol> * <li>The style sheet specified in the theme-uri parameter. * </ol> * * <p>FUTURE CONSIDERATION: we might generate the inclusion on demand instead of all at once. * * @param exec the execution (never null) * @param wapp the Web application. If null, exec.getDesktop().getWebApp() is used. So you have to * specify it if the execution is not associated with desktop (a fake execution, such as * JSP/DSP). * @param deviceType the device type, such as ajax. If null, exec.getDesktop().getDeviceType() is * used. So you have to specify it if the execution is not associated with desktop (a fake * execution). */ public static final String outLangStyleSheets(Execution exec, WebApp wapp, String deviceType) { if (exec.isAsyncUpdate(null) || exec.getAttribute(ATTR_LANG_CSS_GENED) != null) return ""; // nothing to generate exec.setAttribute(ATTR_LANG_CSS_GENED, Boolean.TRUE); final StringBuffer sb = new StringBuffer(512); for (StyleSheet ss : getStyleSheets(exec, wapp, deviceType)) append(sb, ss, exec, null); if (sb.length() > 0) sb.append('\n'); return sb.toString(); }
public BindingAnnotationInfoChecker getAnnotationInfoChecker() { Execution exec = Executions.getCurrent(); if (exec == null) return null; BindingAnnotationInfoChecker checker = (BindingAnnotationInfoChecker) exec.getAttribute(CHECKER_KEY); if (checker == null) { checker = createDefaultAnnotationInfoChecker(); exec.setAttribute(CHECKER_KEY, checker); } return checker; }
public BindingExecutionInfoCollector getExecutionInfoCollector() { Execution exec = Executions.getCurrent(); if (exec == null) return null; BindingExecutionInfoCollector collector = (BindingExecutionInfoCollector) exec.getAttribute(COLLECTOR_KEY); if (collector == null) { collector = createBindingExecutionInfoCollector(); exec.setAttribute(COLLECTOR_KEY, collector); } return collector; }
/** * Outputs the HTML tags of the given component to the given writer. * * @param path the request path. If null, the servlet path is assumed. * @param out the output (never null). * @param richlet the richlet to run. If you have only one component to show and no need process * it under an execution, you could use {@link #render(javax.servlet.ServletContext, * javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, * org.zkoss.zk.ui.Component, String, java.io.Writer)} instead. * @since 5.0.5 */ public static final void render( ServletContext ctx, HttpServletRequest request, HttpServletResponse response, Richlet richlet, String path, Writer out) throws ServletException, IOException { if (path == null) path = Https.getThisServletPath(request); WebManager webman = WebManager.getWebManagerIfAny(ctx); if (webman == null) { final String ATTR = "org.zkoss.zkplus.embed.updateURI"; String updateURI = Library.getProperty(ATTR); if (updateURI == null) updateURI = "/zkau"; else updateURI = Utils.checkUpdateURI(updateURI, ATTR); webman = new WebManager(ctx, updateURI); } final Session sess = WebManager.getSession(ctx, request); final WebApp wapp = sess.getWebApp(); final WebAppCtrl wappc = (WebAppCtrl) wapp; final Object old = I18Ns.setup(sess, request, response, wapp.getConfiguration().getResponseCharset()); Execution exec = null; try { final Desktop desktop = webman.getDesktop(sess, request, response, path, true); if (desktop == null) // forward or redirect return; final RequestInfo ri = new RequestInfoImpl(wapp, sess, desktop, request, PageDefinitions.getLocator(wapp, path)); sess.setAttribute(Attributes.GAE_FIX, new Integer(0)); ((SessionCtrl) sess).notifyClientRequest(true); final UiFactory uf = wappc.getUiFactory(); final Page page = WebManager.newPage(uf, ri, richlet, response, path); exec = new ExecutionImpl(ctx, request, response, desktop, page); exec.setAttribute(Attributes.PAGE_REDRAW_CONTROL, "page"); exec.setAttribute(Attributes.PAGE_RENDERER, new PageRenderer(exec)); wappc.getUiEngine().execNewPage(exec, richlet, page, out); // no need to set device type here, since UiEngine will do it later } finally { I18Ns.cleanup(request, old); if (exec != null) { exec.removeAttribute(Attributes.PAGE_REDRAW_CONTROL); exec.removeAttribute(Attributes.PAGE_RENDERER); } } }
// @Override public void render(Page page, Writer out) throws IOException { final Desktop desktop = _exec.getDesktop(); out.write("<script type=\"text/javascript\">zkpb('"); out.write(page.getUuid()); out.write("','"); out.write(desktop.getId()); out.write("','"); out.write(getContextURI()); out.write("','"); out.write(desktop.getUpdateURI(null)); out.write("','"); out.write(desktop.getRequestPath()); out.write('\''); String style = page.getStyle(); if (style != null && style.length() > 0) { out.write(",{style:'"); out.write(style); out.write("'}"); } out.write(");zkpe();</script>\n"); for (Component root = page.getFirstRoot(); root != null; root = root.getNextSibling()) { // HtmlPageRenders.outStandalone(_exec, root, out); } }
private String getContextURI() { if (_exec != null) { String s = _exec.encodeURL("/"); int j = s.lastIndexOf('/'); // might have jsessionid=... return j >= 0 ? s.substring(0, j) + s.substring(j + 1) : s; } return ""; }
@Override public Collection getThemeURIs(Execution exec, List uris) { for (Iterator it = uris.iterator(); it.hasNext(); ) { if (it.next().toString().startsWith("~./eccTheme")) it.remove(); // remove the ecc theme } HttpServletRequest req = (HttpServletRequest) exec.getNativeRequest(); Object uri = getMyThemeURI(req); if (uri != null) uris.add(uri); return uris; }
/** * Returns a list of {@link StyleSheet} that shall be generated to the client for the specified * execution. * * @param exec the execution (never null) * @param wapp the Web application. If null, exec.getDesktop().getWebApp() is used. So you have to * specify it if the execution is not associated with desktop (a fake execution, such as * JSP/DSP). * @param deviceType the device type, such as ajax. If null, exec.getDesktop().getDeviceType() is * used. So you have to specify it if the execution is not associated with desktop (a fake * execution). */ public static final List<StyleSheet> getStyleSheets( Execution exec, WebApp wapp, String deviceType) { if (wapp == null) wapp = exec.getDesktop().getWebApp(); if (deviceType == null) deviceType = exec.getDesktop().getDeviceType(); final Configuration config = wapp.getConfiguration(); final Set<String> disabled = config.getDisabledThemeURIs(); final List<StyleSheet> sses = new LinkedList<StyleSheet>(); // a list of StyleSheet for (LanguageDefinition langdef : LanguageDefinition.getByDeviceType(deviceType)) { for (StyleSheet ss : langdef.getStyleSheets()) { if (!disabled.contains(ss.getHref())) sses.add(ss); } } // Process configuration final ThemeProvider themeProvider = config.getThemeProvider(); if (themeProvider != null) { final List<Object> orgss = new LinkedList<Object>(); for (StyleSheet ss : sses) { final String href = ss.getHref(); if (href != null && href.length() > 0) orgss.add(ss.getMedia() != null ? ss : href); // we don't support getContent } final String[] hrefs = config.getThemeURIs(); for (int j = 0; j < hrefs.length; ++j) orgss.add(hrefs[j]); sses.clear(); final Collection<?> res = themeProvider.getThemeURIs(exec, orgss); if (res != null) { for (Object re : res) { sses.add( re instanceof StyleSheet ? (StyleSheet) re : new StyleSheet((String) re, "text/css")); } } } else { final String[] hrefs = config.getThemeURIs(); for (int j = 0; j < hrefs.length; ++j) sses.add(new StyleSheet(hrefs[j], "text/css")); } return sses; }
private static void append(StringBuffer sb, StyleSheet ss, Execution exec, Page page) { String href = ss.getHref(); String media = ss.getMedia(); if (href != null) { try { if (exec != null) href = (String) exec.evaluate(page, href, String.class); if (href != null && href.length() > 0) { sb.append("\n<link rel=\"stylesheet\" type=\"") .append(ss.getType()) .append("\" href=\"") .append(ServletFns.encodeURL(href)); if (media != null) sb.append("\" media=\"").append(media); sb.append("\"/>"); } } catch (javax.servlet.ServletException ex) { throw new UiException(ex); } } else { sb.append("\n<style"); if (ss.getType() != null) sb.append(" type=\"").append(ss.getType()).append('"'); sb.append(">\n").append(ss.getContent()).append("\n</style>"); } }
private static final void setRenderContext(Execution exec, RenderContext rc) { exec.setAttribute(ATTR_RENDER_CONTEXT, rc); }
/** * Returns the render context, or null if not available. It is used to render the content that * will appear before the content generated by {@link ContentRenderer}, such as crawlable content. * * @param exec the execution. If null, {@link Executions#getCurrent} is assumed. */ public static final RenderContext getRenderContext(Execution exec) { if (exec == null) exec = Executions.getCurrent(); return exec != null ? (RenderContext) exec.getAttribute(ATTR_RENDER_CONTEXT) : null; }
/** * Returns whether a component can directly generate HTML tags to the output. This flag is used by * components that can generate the content directly, such as {@link * org.zkoss.zk.ui.HtmlNativeComponent} * * @see #setDirectContent */ public static boolean isDirectContent(Execution exec) { if (exec == null) exec = Executions.getCurrent(); return exec != null && exec.getAttribute(ATTR_DIRECT_CONTENT) != null; }
/** * Sets whether a component can directly generate HTML tags to the output. * * @see #isDirectContent */ public static boolean setDirectContent(Execution exec, boolean direct) { return (direct ? exec.setAttribute(ATTR_DIRECT_CONTENT, Boolean.TRUE) : exec.removeAttribute(ATTR_DIRECT_CONTENT)) != null; }
/** * Returns if the ZK specific HTML tags are generated. * * @since 5.0.3 */ public static boolean isZkTagsGenerated(Execution exec) { return exec.getAttribute(ATTR_ZK_TAGS_GENERATED) != null; }
/** * Returns the HTML content representing a page. * * @param au whether it is caused by asynchronous update * @param exec the execution (never null) */ public static final void outPageContent(Execution exec, Page page, Writer out, boolean au) throws IOException { final Desktop desktop = page.getDesktop(); final PageCtrl pageCtrl = (PageCtrl) page; final Component owner = pageCtrl.getOwner(); boolean contained = owner == null && exec.isIncluded(); // a standalone page (i.e., no owner), and being included by // non-ZK page (e.g., JSP). // // Revisit Bug 2001707: OK to use exec.isIncluded() since // we use PageRenderer now (rather than Servlet's include) // TODO: test again // prepare style String style = page.getStyle(); if (style == null || style.length() == 0) { style = null; String wd = null, hgh = null; if (owner instanceof HtmlBasedComponent) { final HtmlBasedComponent hbc = (HtmlBasedComponent) owner; wd = hbc.getWidth(); // null if not set hgh = hbc.getHeight(); // null if not set } if (wd != null || hgh != null || contained) { final StringBuffer sb = new StringBuffer(32); HTMLs.appendStyle(sb, "width", wd != null ? wd : "100%"); HTMLs.appendStyle(sb, "height", hgh != null ? hgh : contained ? null : "100%"); style = sb.toString(); } } RenderContext rc = null, old = null; final boolean aupg = exec.isAsyncUpdate(page); // AU this page final boolean includedAndPart = owner != null && !aupg; // this page is included and rendered with its owner final boolean divRequired = !au || includedAndPart; final boolean standalone = !au && owner == null; if (standalone) { rc = new RenderContext( out, new StringWriter(), desktop.getWebApp().getConfiguration().isCrawlable(), false); setRenderContext(exec, rc); } else if (owner != null) { old = getRenderContext(exec); // store final boolean crawlable = old != null && old.temp != null && desktop.getWebApp().getConfiguration().isCrawlable(); setRenderContext(exec, crawlable ? new RenderContext(old.temp, null, true, true) : null); } // generate div first if (divRequired) { outDivTemplateBegin(out, page.getUuid()); } if (standalone) { // switch out // don't call outDivTemplateEnd yet since rc.temp will be generated before it out = new StringWriter(); } else if (divRequired) { outDivTemplateEnd(page, out); // close it now since no rc.temp } if (includedAndPart) { out = new StringWriter(); } else if (divRequired) { // generate JS second out.write("\n<script class=\"z-runonce\" type=\"text/javascript\">\n"); } exec.setAttribute(ATTR_DESKTOP_JS_GENED, Boolean.TRUE); final int order = ComponentRedraws.beforeRedraw(false); final String extra; try { if (order < 0) { if (aupg) out.write('['); else { out.write(outSpecialJS(desktop)); out.write(divRequired ? "zkmx(" : "zkx("); } } else if (order > 0) // not first child out.write(','); out.write("\n[0,'"); // 0: page out.write(page.getUuid()); out.write("',{"); final StringBuffer props = new StringBuffer(128); final String pgid = page.getId(); if (pgid.length() > 0) appendProp(props, "id", pgid); if (owner != null) { appendProp(props, "ow", owner.getUuid()); } else { appendProp(props, "dt", desktop.getId()); appendProp(props, "cu", getContextURI(exec)); appendProp(props, "uu", desktop.getUpdateURI(null)); appendProp(props, "ru", desktop.getRequestPath()); } final String pageWgtCls = pageCtrl.getWidgetClass(); if (pageWgtCls != null) appendProp(props, "wc", pageWgtCls); if (style != null) appendProp(props, "style", style); if (!isClientROD(page)) appendProp(props, "z$rod", Boolean.FALSE); if (contained) appendProp(props, "ct", Boolean.TRUE); out.write(props.toString()); out.write("},["); for (Component root = page.getFirstRoot(); root != null; root = root.getNextSibling()) ((ComponentCtrl) root).redraw(out); out.write("]]"); } finally { extra = ComponentRedraws.afterRedraw(); } if (order < 0) { outEndJavaScriptFunc(exec, out, extra, aupg); } if (standalone) { setRenderContext(exec, null); StringBuffer sw = ((StringWriter) out).getBuffer(); out = rc.temp; if (divRequired) outDivTemplateEnd(page, out); // close tag after temp, but before perm (so perm won't be destroyed) Files.write(out, ((StringWriter) rc.perm).getBuffer()); // perm // B65-ZK-1836 Files.write( out, new StringBuffer(sw.toString().replaceAll("</(?i)(?=script>)", "<\\\\/"))); // js } else if (owner != null) { // restore setRenderContext(exec, old); } if (includedAndPart) { ((Includer) owner).setRenderingResult(((StringWriter) out).toString()); } else if (divRequired) { out.write("\n</script>\n"); } }
/** * Returns HTML tags to include all JavaScript files and codes that are required when loading a * ZUML page (never null). * * <p>FUTURE CONSIDERATION: we might generate the inclusion on demand instead of all at once. * * @param exec the execution (never null) * @param wapp the Web application. If null, exec.getDesktop().getWebApp() is used. So you have to * specify it if the execution is not associated with desktop (a fake execution, such as * JSP/DSP). * @param deviceType the device type, such as ajax. If null, exec.getDesktop().getDeviceType() is * used. So you have to specify it if the execution is not associated with desktop (a fake * execution). */ public static final String outLangJavaScripts(Execution exec, WebApp wapp, String deviceType) { if (exec.isAsyncUpdate(null) || exec.getAttribute(ATTR_LANG_JS_GENED) != null) return ""; // nothing to generate exec.setAttribute(ATTR_LANG_JS_GENED, Boolean.TRUE); final Desktop desktop = exec.getDesktop(); if (wapp == null) wapp = desktop.getWebApp(); if (deviceType == null) deviceType = desktop != null ? desktop.getDeviceType() : "ajax"; final StringBuffer sb = new StringBuffer(1536); final Set<JavaScript> jses = new LinkedHashSet<JavaScript>(32); for (LanguageDefinition langdef : LanguageDefinition.getByDeviceType(deviceType)) jses.addAll(langdef.getJavaScripts()); for (JavaScript js : jses) append(sb, js); sb.append("\n<!-- ZK ").append(wapp.getVersion()); if (WebApps.getFeature("ee")) sb.append(" EE"); else if (WebApps.getFeature("pe")) sb.append(" PE"); sb.append(' ').append(wapp.getBuild()); Object o = wapp.getAttribute("org.zkoss.zk.ui.notice"); if (o != null) sb.append(o); sb.append(" -->\n"); int tmout = 0; final Boolean autoTimeout = getAutomaticTimeout(desktop); if (autoTimeout != null ? autoTimeout.booleanValue() : wapp.getConfiguration().isAutomaticTimeout(deviceType)) { if (desktop != null) { tmout = desktop.getSession().getMaxInactiveInterval(); } else { Object req = exec.getNativeRequest(); if (req instanceof HttpServletRequest) { final HttpSession hsess = ((HttpServletRequest) req).getSession(false); if (hsess != null) { final Session sess = SessionsCtrl.getSession(wapp, hsess); if (sess != null) { tmout = sess.getMaxInactiveInterval(); } else { // try configuration first since HttpSession's timeout is set // when ZK Session is created (so it is not set yet) // Note: no need to setMaxInactiveInternval here since it will // be set later or not useful at the end tmout = wapp.getConfiguration().getSessionMaxInactiveInterval(); if (tmout <= 0) // system default tmout = hsess.getMaxInactiveInterval(); } } else tmout = wapp.getConfiguration().getSessionMaxInactiveInterval(); } } if (tmout > 0) { // unit: seconds int extra = tmout / 8; tmout += extra > 60 ? 60 : extra < 5 ? 5 : extra; // Add extra seconds to ensure it is really timeout } } final boolean keepDesktop = exec.getAttribute(Attributes.NO_CACHE) == null && !"page".equals(ExecutionsCtrl.getPageRedrawControl(exec)), groupingAllowed = isGroupingAllowed(desktop); final String progressboxPos = org.zkoss.lang.Library.getProperty("org.zkoss.zul.progressbox.position", ""); if (tmout > 0 || keepDesktop || progressboxPos.length() > 0 || !groupingAllowed) { sb.append("<script class=\"z-runonce\" type=\"text/javascript\">\nzkopt({"); if (keepDesktop) sb.append("kd:1,"); if (!groupingAllowed) sb.append("gd:1,"); if (tmout > 0) sb.append("to:").append(tmout).append(','); if (progressboxPos.length() > 0) sb.append("ppos:'").append(progressboxPos).append('\''); if (sb.charAt(sb.length() - 1) == ',') sb.setLength(sb.length() - 1); sb.append("});\n</script>"); } final Device device = Devices.getDevice(deviceType); String s = device.getEmbedded(); if (s != null) sb.append(s).append('\n'); return sb.toString(); }