/* * ShinyFrameHelper.java * * Copyright (C) 2009-14 by RStudio, Inc. * * Unless you have received this program directly from RStudio pursuant * to the terms of a commercial license agreement with RStudio, then * this program is licensed to you under the terms of version 3 of the * GNU Affero General Public License. This program is distributed WITHOUT * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT, * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details. * */ package org.rstudio.studio.client.shiny; import org.rstudio.core.client.dom.WindowEx; import org.rstudio.core.client.widget.Operation; import com.google.gwt.core.client.JavaScriptObject; import com.google.gwt.core.client.Scheduler; import com.google.gwt.core.client.Scheduler.RepeatingCommand; public class ShinyFrameHelper { private static class ShinyFrameEvent extends JavaScriptObject { protected ShinyFrameEvent() {} public final native String getEvent() /*-{ return this.data.event; }-*/; public final native int getIntData() /*-{ return this.data.data; }-*/; public final native String getStringData() /*-{ return this.data.data; }-*/; public final native String getOrigin() /*-{ return this.origin; }-*/; public final native WindowEx getSource() /*-{ return this.source; }-*/; } private static class ShinyFrameMethod extends JavaScriptObject { protected ShinyFrameMethod() {} public final native static ShinyFrameMethod create(String method, int arg) /*-{ return { method: method, arg: arg }; }-*/; public final native static ShinyFrameMethod create(String method, String arg) /*-{ return { method: method, arg: arg }; }-*/; public final native String getMethod() /*-{ return this.method; }-*/; } public void initialize(String url, Operation onComplete) { // remember the URL and begin waiting for the window object to arrive url_ = url; window_ = null; origin_ = null; onInitComplete_ = onComplete; } public ShinyFrameHelper() { initializeEvents(); } public int getScrollPosition() { return scrollPosition_; } public String getUrl() { return url_; } public void setScrollPosition(int pos) { sendMethod(ShinyFrameMethod.create(METHOD_SET_SCROLL, pos)); } public void setHash(String hash) { sendMethod(ShinyFrameMethod.create(METHOD_SET_HASH, hash)); } // Private methods --------------------------------------------------------- private native void initializeEvents() /*-{ var thiz = this; $wnd.addEventListener( "message", $entry(function(e) { if (typeof e.data != 'object') return; if (e.data.type !== 'ShinyFrameEvent') return; thiz.@org.rstudio.studio.client.shiny.ShinyFrameHelper::onMessage(Lcom/google/gwt/core/client/JavaScriptObject;)(e); }), true); }-*/; private void onMessage(JavaScriptObject data) { // reject messages that don't match the expected origin ShinyFrameEvent event = data.cast(); if (!url_.startsWith(event.getOrigin())) return; String eventName = event.getEvent(); if (eventName.equals(EVENT_SCROLL_CHANGE)) { scrollPosition_ = event.getIntData(); } else if (eventName.equals(EVENT_HASH_CHANGE)) { url_ = event.getStringData(); } else if (eventName.equals(EVENT_READY)) { window_ = event.getSource(); origin_ = event.getOrigin(); if (onInitComplete_ != null) { onInitComplete_.execute(); onInitComplete_ = null; } } } private void sendMethod(final ShinyFrameMethod method) { // if we don't have a valid window object yet, try again until we do // (every 100ms, up to 5s) if (window_ == null) { windowRetries_ = 0; Scheduler.get().scheduleFixedPeriod(new RepeatingCommand() { @Override public boolean execute() { if (window_ == null && windowRetries_ < MAX_WINDOW_RETRIES) { windowRetries_++; return true; } if (window_ != null) { window_.postMessage(method, origin_); } return false; } }, WINDOW_RETRY_DELAY); } else { window_.postMessage(method, origin_); } } private WindowEx window_ = null; private int scrollPosition_ = 0; private String url_ = ""; private String origin_ = ""; private Operation onInitComplete_; // how many times and how long we're willing to wait for a window object to // appear to communicate with private int windowRetries_ = 0; private final static int MAX_WINDOW_RETRIES = 50; private final static int WINDOW_RETRY_DELAY = 100; // event names (must match those in rsiframe.js) private final static String EVENT_SCROLL_CHANGE = "doc_scroll_change"; private final static String EVENT_HASH_CHANGE = "doc_hash_change"; private final static String EVENT_READY = "doc_ready"; private final static String METHOD_SET_SCROLL = "rs_set_scroll_pos"; private final static String METHOD_SET_HASH = "rs_set_hash"; }