Java tutorial
// Copyright (C) 2009 Google Inc. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package com.google.caja.demos.playground.client.ui; import java.util.EnumMap; import java.util.HashMap; import java.util.Map; import com.google.caja.demos.playground.client.Playground; import com.google.caja.demos.playground.client.PlaygroundResource; import com.google.gwt.core.client.JavaScriptObject; import com.google.gwt.dom.client.Element; import com.google.gwt.event.dom.client.ClickEvent; import com.google.gwt.event.dom.client.ClickHandler; import com.google.gwt.event.dom.client.FocusEvent; import com.google.gwt.event.dom.client.FocusHandler; import com.google.gwt.event.logical.shared.SelectionEvent; import com.google.gwt.event.logical.shared.SelectionHandler; import com.google.gwt.event.logical.shared.ValueChangeEvent; import com.google.gwt.event.logical.shared.ValueChangeHandler; import com.google.gwt.user.client.ui.Anchor; import com.google.gwt.user.client.ui.HasHorizontalAlignment; import com.google.gwt.user.client.ui.Label; import com.google.gwt.user.client.ui.MultiWordSuggestOracle; import com.google.gwt.user.client.ui.RootLayoutPanel; import com.google.gwt.user.client.ui.SuggestBox; import com.google.gwt.user.client.ui.TreeItem; /** * GUI elements of the playground client * * @author Jasvir Nagra (jasvir@gmail.com) */ public class PlaygroundView { private final Playground controller; private final MultiWordSuggestOracle sourceExamples; private MultiWordSuggestOracle policyExamples; private final PlaygroundUI playgroundUI; private int idSeq = 0; private String genId() { return "CajaGadget" + (idSeq++) + "___"; } public void setVersion(String v) { playgroundUI.version.setText(v); } public void setPolicyUrl(String url) { playgroundUI.policyAddressField.setText(url); policyExamples.add(url); } public void setUrl(String url) { playgroundUI.addressField.setText(url); sourceExamples.add(url); } public void selectTab(Tabs tab) { playgroundUI.editorPanel.selectTab(tab.ordinal()); } private void initSourcePanel() { for (Example eg : Example.values()) { sourceExamples.add(eg.url); } playgroundUI.addressField.getTextBox().addFocusHandler(new FocusHandler() { public void onFocus(FocusEvent event) { playgroundUI.addressField.showSuggestionList(); } }); playgroundUI.addressField.setText("https://"); playgroundUI.goButton.addClickHandler(new ClickHandler() { public void onClick(ClickEvent event) { controller.loadSource(playgroundUI.addressField.getText()); } }); playgroundUI.cajoleButton.addClickHandler(new ClickHandler() { public void onClick(ClickEvent event) { playgroundUI.runtimeMessages.clear(); playgroundUI.renderPanel.setText(""); playgroundUI.renderTime.setText("Unknown"); controller.cajole(playgroundUI.addressField.getText(), playgroundUI.sourceText.getText(), playgroundUI.policyText.getText(), true /* debug */, genId()); } }); } private void initFeedbackPanel() { playgroundUI.feedbackPanel.setHorizontalAlignment(HasHorizontalAlignment.ALIGN_RIGHT); for (Menu menu : Menu.values()) { Anchor menuItem = new Anchor(); menuItem.setHTML(menu.description); menuItem.setHref(menu.url); menuItem.setWordWrap(false); menuItem.addStyleName("menuItems"); playgroundUI.feedbackPanel.add(menuItem); playgroundUI.feedbackPanel.setCellWidth(menuItem, "100%"); } } private void initPolicyPanel() { policyExamples = new MultiWordSuggestOracle(); playgroundUI.policyAddressField = new SuggestBox(policyExamples); playgroundUI.policyAddressField.getTextBox().addFocusHandler(new FocusHandler() { public void onFocus(FocusEvent event) { playgroundUI.policyAddressField.showSuggestionList(); } }); playgroundUI.policyAddressField.setText("http://"); playgroundUI.clearButton.addClickHandler(new ClickHandler() { public void onClick(ClickEvent event) { controller.clearPolicy(); } }); playgroundUI.loadButton.addClickHandler(new ClickHandler() { public void onClick(ClickEvent event) { controller.loadPolicy(playgroundUI.policyAddressField.getText()); } }); playgroundUI.defaultButton.addClickHandler(new ClickHandler() { public void onClick(ClickEvent event) { setPolicySource(defaultPolicy()); } }); setPolicySource(defaultPolicy()); } private static String defaultPolicy() { return PlaygroundResource.INSTANCE.defaultPolicy().getText(); } native static String encodeURIComponent(String uri) /*-{ return $wnd.encodeURIComponent(uri); }-*/; /** * Extracts the location map and original source from content cajoled in * debug mode. * The format is described at <a href= * "http://google-caja.googlecode.com/svn/trunk/doc/html/compiledModuleFormat/index.html" * ><tt>doc/html/compiledModuleFormat/index.html</tt></a>. */ private static native boolean srcLocMapAndOriginalSrc(String source, String[] out) /*-{ var str = "'(?:[^'\\\\]|\\\\.)*'"; var colon = "\\s*:\\s*"; var comma = "\\s*,\\s*"; var block = str + colon + "\\{(?:\\s*" + str + colon + str + comma + ")*?" + "\\s*'content'" + colon + "\\[\\s*" + str + "(?:" + comma + str + ")*\\s*\\]\\s*\\}"; // TODO(mikesamuel): extract this a better way once we're providing module // output in an easy to consume JSON format. var re = new RegExp( // sourceLocationMap in group 1 "'sourceLocationMap'" + colon + "\\{" + "(?:\\s*" + str + colon + str + comma + ")*?" // any number of pairs + "\\s*'content'" + colon + "\\[\\s*(" + str + "(?:" + comma + str + ")*)\\s*\\]\\s*\\}" + comma // originalSource in group 2 + "'originalSource'" + colon + "\\{\\s*(" + block + "(?:" + comma + block + ")*)\\s*\\}\\s*\\}\\s*\\)\\s*;?\\s*\\}\\s*<\\/script>\s*$"); var match = source.match(re); if (match) { out[0] = match[0]; out[1] = match[1]; return true; } else { return false; } }-*/; /* private Widget createSpeedtracerPanel() { FlowPanel hp = new FlowPanel(); hp.setSize("100%", "100%"); speedtracerManifestButton = new Button("Manifest URI", new ClickHandler() { PopupPanel panel; Label uriLbl; private String getManifestUri() { String[] locMapAndSrc = new String[2]; if (srcLocMapAndOriginalSrc(cajoledSource.getText(), locMapAndSrc)) { String json = "[[" + locMapAndSrc[0] + "],[" + locMapAndSrc[1] + "]]"; return "data:text/plain," + encodeURIComponent(json); } else { return null; } } public void onClick(ClickEvent event) { String dataUri = getManifestUri(); if (panel == null) { HorizontalPanel body = new HorizontalPanel(); body.add(uriLbl = new Label()); body.add(new Button("\u00d7", new ClickHandler() { public void onClick(ClickEvent ev) { panel.hide(); } })); panel = new PopupPanel(); panel.setWidget(body); panel.setTitle("Manifest URI"); } uriLbl.setText(dataUri); if (panel.isShowing()) { panel.hide(); } else { panel.show(); } } }); hp.add(speedtracerManifestButton); return hp; } */ private native void setupNativeSelectLineBridge() /*-{ var that = this; $wnd.selectLine = function (uri, start, sOffset, end, eOffset) { that.@com.google.caja.demos.playground.client.ui.PlaygroundView::selectTab(Lcom/google/caja/demos/playground/client/ui/PlaygroundView$Tabs;)( @com.google.caja.demos.playground.client.ui.PlaygroundView.Tabs::SOURCE); that.@com.google.caja.demos.playground.client.ui.PlaygroundView::highlightSource(Ljava/lang/String;IIII)(uri, start, sOffset, end, eOffset); } }-*/; private void initEditor() { setupNativeSelectLineBridge(); selectTab(Tabs.SOURCE); } private native void initPlusOne() /*-{ try { $wnd.gapi.plusone.render("plusone",{size: "medium"}); } catch (e) { // failure to initialize +1 button should not prevent load of page } }-*/; private void initUnsafe() { playgroundUI.unsafe.addValueChangeHandler(new ValueChangeHandler<Boolean>() { @Override public void onValueChange(ValueChangeEvent<Boolean> event) { setUnsafe(playgroundUI.unsafe.getValue()); } }); } private static TreeItem addExampleItem(Map<Example.Type, TreeItem> menu, Example eg) { if (!menu.containsKey(eg.type)) { TreeItem menuItem = new TreeItem(eg.type.description); menu.put(eg.type, menuItem); } TreeItem egItem = new TreeItem(eg.description); menu.get(eg.type).addItem(egItem); return egItem; } private void initExamples() { Map<Example.Type, TreeItem> menuMap = new EnumMap<Example.Type, TreeItem>(Example.Type.class); final Map<TreeItem, Example> entryMap = new HashMap<TreeItem, Example>(); playgroundUI.exampleTree.setTitle("Select an example"); for (Example eg : Example.values()) { TreeItem it = addExampleItem(menuMap, eg); entryMap.put(it, eg); } boolean first = true; for (TreeItem menuItem : menuMap.values()) { if (first) { first = false; menuItem.setState(true); } playgroundUI.exampleTree.addItem(menuItem); } playgroundUI.exampleTree.addSelectionHandler(new SelectionHandler<TreeItem>() { public void onSelection(SelectionEvent<TreeItem> event) { Example eg = entryMap.get(event.getSelectedItem()); // No associated example - e.g. when opening a subtree menu if (null == eg) { return; } controller.loadSource(eg.url); } }); } private native void initCaja(boolean debug) /*-{ var that = this; function success(detail) { } function failed(e) { that.@com.google.caja.demos.playground.client.ui.PlaygroundView::addRuntimeMessage(Ljava/lang/String;) (e); } $wnd.caja.initialize({ server: '.', debug: debug, // TODO(kpreid): Make sure we warn the user if the actual severity is // UNSAFE_SPEC_VIOLATION or worse, so that we don't silently appear to // have unknown security bugs. maxAcceptableSeverity: 'NEW_SYMPTOM' }, success, failed); }-*/; private native void setUnsafe(boolean unsafe) /*-{ $wnd.caja.disableSecurityForDebugger(unsafe); }-*/; public PlaygroundView(Playground controller) { this.controller = controller; this.sourceExamples = new MultiWordSuggestOracle(); this.policyExamples = new MultiWordSuggestOracle(); this.playgroundUI = new com.google.caja.demos.playground.client.ui.PlaygroundUI(sourceExamples, policyExamples); RootLayoutPanel.get().add(playgroundUI); initSourcePanel(); initPolicyPanel(); initFeedbackPanel(); initExamples(); initEditor(); initCaja(true); initPlusOne(); initUnsafe(); } public void setOriginalSource(String result) { if (result == null) { playgroundUI.sourceText.setText(""); } else { playgroundUI.sourceText.setText(result); } } public void setPolicySource(String result) { if (result == null) { playgroundUI.policyText.setText(""); } else { playgroundUI.policyText.setText(result); } } public void setLoading(boolean isLoading) { playgroundUI.loadingLabel.setVisible(isLoading); } private native String prettyPrint(String result, String lang) /*-{ return $wnd.prettyPrintOne($wnd.indentAndWrapCode(result), lang); }-*/; public void setRenderedResult(String baseUrl, final String policy, final String html, final String js, final String idClass) { if (html == null && js == null) { playgroundUI.renderResult.setText("There were cajoling errors"); return; } // Make the cajoled content visible so that the DOM will be laid out before // the script checks DOM geometry. selectTab(Tabs.RENDER); setRenderedResultNative(playgroundUI.renderPanel.getElement(), baseUrl, makeUriPolicy(), idClass, policy, html, js); } private native void setRenderedResultNative(Element element, String baseUrl, JavaScriptObject uriPolicy, String idClass, String policy, String html, String js) /*-{ var startTime = Date.now(); var that = this; $wnd.caja.load( element, uriPolicy, function(frame) { var api = that.@com.google.caja.demos.playground.client.ui.PlaygroundView::makeExtraImports(Lcom/google/gwt/core/client/JavaScriptObject;Lcom/google/gwt/core/client/JavaScriptObject;Ljava/lang/String;)($wnd.caja, frame, policy); frame = frame.api(api); frame = frame.code(baseUrl, "text/html", html); frame.run(function(r) { that.@com.google.caja.demos.playground.client.ui.PlaygroundView::setRenderedResult(Ljava/lang/String;)(r + ''); that.@com.google.caja.demos.playground.client.ui.PlaygroundView::setRenderTime(I)(Date.now() - startTime); }); }, { idClass: idClass, title: 'Playground Untrusted Content' }); }-*/; private void setRenderedResult(String result) { playgroundUI.renderResult.setText(result); } private void setRenderTime(int time) { playgroundUI.renderTime.setText(time + "ms"); } private native JavaScriptObject makeExtraImports(JavaScriptObject caja, JavaScriptObject guestFrame, String policy) /*-{ var that = this; var extraImports = {}; try { var tamings___ = $wnd.eval(policy); } catch (e) { that.@com.google.caja.demos.playground.client.ui.PlaygroundView::addRuntimeError(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;) (e, "evaluating policy"); } for (var i=0; i < tamings___.length; i++) { try { tamings___[i].call(undefined, caja, extraImports); } catch (e) { that.@com.google.caja.demos.playground.client.ui.PlaygroundView::addRuntimeError(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;) (e, "evaluating " + i + "th policy function"); } } extraImports.onerror = caja.tame(caja.markFunction( function (message, source, lineNum) { that.@com.google.caja.demos.playground.client.ui.PlaygroundView::addRuntimeError(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;) (message, source, lineNum); })); extraImports.alert = caja.tame( caja.markFunction( function(msg) { alert('Untrusted code says: ' + String(msg)); })); return extraImports; }-*/; private native JavaScriptObject makeUriPolicy() /*-{ return { mitigate: function (uri) { // Skip rewriting jquery and jqueryui when loaded // from the google cdn if (uri.getDomain() === "ajax.googleapis.com" && (uri.getPath().indexOf("/ajax/libs/jquery/") === 0 || uri.getPath().indexOf("/ajax/libs/jqueryui/") === 0)) { return uri; } return null; }, fetch: $wnd.caja.policy.net.ALL.fetch, rewrite: function (uri, uriEffect, loaderType, hints) { if (uriEffect === $wnd.html4.ueffects.NEW_DOCUMENT) { return uri; } if (uriEffect === $wnd.html4.ueffects.SAME_DOCUMENT && (loaderType === $wnd.html4.ltypes.SANDBOXED || loaderType === $wnd.html4.ltypes.DATA)) { if (hints && hints.XHR) { return uri; } return "http://www.gmodules.com/gadgets/proxy" + "?url=" + encodeURIComponent(uri.toString()) + "&container=caja"; } return null; } }; }-*/; public void addRuntimeError(String message, String source, String lineNum) { // Labels are texty, so no escaping needed Label i = new Label( "Uncaught script error: '" + message + "' in source: '" + source + "' at line: " + lineNum + "\n"); playgroundUI.runtimeMessages.add(i); } public void addRuntimeMessage(String message) { // Labels are texty, so no escaping needed Label i = new Label(message); playgroundUI.runtimeMessages.add(i); } /** @param uri unused but provided for consistency with native GWT caller. */ public void highlightSource(String uri, int start, int sOffset, int end, int eOffset) { playgroundUI.sourceText.setCursorPos(start); playgroundUI.sourceText.setSelectionRange(start, sOffset, end, eOffset); } public enum Tabs { SOURCE, POLICY, RENDER, COMPILE_WARNINGS, RUNTIME_WARNINGS, TAMING, MANIFEST; } }