Merge "Wait for UI plugins to load"

This commit is contained in:
Shawn Pearce
2013-12-11 07:38:08 +00:00
committed by Gerrit Code Review
16 changed files with 428 additions and 164 deletions

View File

@@ -22,6 +22,7 @@ import com.google.gerrit.client.account.AccountCapabilities;
import com.google.gerrit.client.account.AccountInfo;
import com.google.gerrit.client.admin.ProjectScreen;
import com.google.gerrit.client.api.ApiGlue;
import com.google.gerrit.client.api.PluginLoader;
import com.google.gerrit.client.changes.ChangeConstants;
import com.google.gerrit.client.changes.ChangeListScreen;
import com.google.gerrit.client.config.ConfigServerApi;
@@ -49,12 +50,8 @@ import com.google.gerrit.reviewdb.client.AccountGeneralPreferences;
import com.google.gerrit.reviewdb.client.AuthType;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gwt.aria.client.Roles;
import com.google.gwt.core.client.Callback;
import com.google.gwt.core.client.CodeDownloadException;
import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.JavaScriptObject;
import com.google.gwt.core.client.ScriptInjector;
import com.google.gwt.dom.client.AnchorElement;
import com.google.gwt.dom.client.Document;
import com.google.gwt.event.dom.client.ClickEvent;
@@ -84,12 +81,9 @@ import com.google.gwt.user.client.ui.RootPanel;
import com.google.gwtexpui.clippy.client.CopyableLabel;
import com.google.gwtexpui.user.client.UserAgent;
import com.google.gwtexpui.user.client.ViewSite;
import com.google.gwtjsonrpc.client.CallbackHandle;
import com.google.gwtjsonrpc.client.JsonDefTarget;
import com.google.gwtjsonrpc.client.JsonUtil;
import com.google.gwtjsonrpc.client.XsrfManager;
import com.google.gwtjsonrpc.client.impl.ResultDeserializer;
import com.google.gwtjsonrpc.common.AsyncCallback;
import com.google.gwtorm.client.KeyUtil;
import java.util.HashMap;
@@ -571,7 +565,7 @@ public class Gerrit implements EntryPoint {
}
saveDefaultTheme();
loadPlugins(hpd, token);
PluginLoader.load(hpd.plugins, token);
}
private void saveDefaultTheme() {
@@ -580,53 +574,6 @@ public class Gerrit implements EntryPoint {
Document.get().getElementById("gerrit_footer"));
}
private void loadPlugins(HostPageData hpd, final String token) {
if (hpd.plugins != null && !hpd.plugins.isEmpty()) {
for (final String url : hpd.plugins) {
ScriptInjector.fromUrl(url)
.setWindow(ScriptInjector.TOP_WINDOW)
.setCallback(new Callback<Void, Exception>() {
@Override
public void onSuccess(Void result) {
}
@Override
public void onFailure(Exception reason) {
ErrorDialog d;
if (reason instanceof CodeDownloadException) {
d = new ErrorDialog(M.cannotDownloadPlugin(url));
} else {
d = new ErrorDialog(M.pluginFailed(url));
}
d.center();
}
}).inject();
}
}
CallbackHandle<Void> cb = new CallbackHandle<Void>(
new ResultDeserializer<Void>() {
@Override
public Void fromResult(JavaScriptObject responseObject) {
return null;
}
},
new AsyncCallback<Void>() {
@Override
public void onFailure(Throwable caught) {
}
@Override
public void onSuccess(Void result) {
display(token);
}
});
cb.install();
ScriptInjector.fromString(cb.getFunctionName() + "();")
.setWindow(ScriptInjector.TOP_WINDOW)
.inject();
}
public static void refreshMenuBar() {
menuLeft.clear();
menuRight.clear();

View File

@@ -20,6 +20,7 @@ public interface GerritConstants extends Constants {
String menuSignIn();
String menuRegister();
String reportBug();
String loadingPlugins();
String signInDialogTitle();
String signInDialogClose();

View File

@@ -1,6 +1,7 @@
menuSignIn = Sign In
menuRegister = Register
reportBug = Report Bug
loadingPlugins = Loading plugins ...
signInDialogTitle = Code Review - Sign In
signInDialogClose = Close

View File

@@ -116,6 +116,7 @@ public interface GerritCss extends CssResource {
String errorDialogGlass();
String errorDialogText();
String errorDialogTitle();
String loadingPluginsDialog();
String fileColumnHeader();
String fileCommentBorder();
String fileLine();

View File

@@ -13,7 +13,7 @@ branchCreationNotAllowedUnderRefnamePrefix = Branch creation is not allowed unde
branchAlreadyExists = A branch with the name {0} already exists.
branchCreationConflict = Cannot create branch {0} since it conflicts with branch {1}.
pluginFailed = Plugin JavaScript {0} failed to load
cannotDownloadPlugin = Cannot download JavaScript plugin from: {0}.
pluginFailed = Plugin "{0}" failed to load
cannotDownloadPlugin = Cannot load plugin from {0}
parentUpdateFailed = Setting parent project failed: {0}

View File

@@ -15,6 +15,8 @@
package com.google.gerrit.client.api;
import com.google.gerrit.client.Gerrit;
import com.google.gerrit.client.rpc.NativeString;
import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.JavaScriptObject;
import com.google.gwt.core.client.JsArray;
import com.google.gwt.user.client.History;
@@ -24,29 +26,36 @@ public class ApiGlue {
private static String pluginName;
public static void init() {
init0();
init0(GWT.getHostPageBaseURL(), NativeString.TYPE);
ActionContext.init();
Plugin.init();
addHistoryHook();
}
private static native void init0() /*-{
var serverUrl = @com.google.gwt.core.client.GWT::getHostPageBaseURL()();
var Plugin = function (name){this.name = name};
var Gerrit = {
private static native void init0(String serverUrl, JavaScriptObject JsonString) /*-{
$wnd.Gerrit = {
JsonString: JsonString,
events: {},
plugins: {},
change_actions: {},
revision_actions: {},
project_actions: {},
getPluginName: @com.google.gerrit.client.api.ApiGlue::getPluginName(),
install: function (f) {
var p = new Plugin(this.getPluginName());
@com.google.gerrit.client.api.ApiGlue::install(Lcom/google/gwt/core/client/JavaScriptObject;Lcom/google/gerrit/client/api/JsUiPlugin;)(f,p);
var p = this._getPluginByUrl(@com.google.gerrit.client.api.PluginName::getCallerUrl()());
@com.google.gerrit.client.api.ApiGlue::install(Lcom/google/gwt/core/client/JavaScriptObject;Lcom/google/gerrit/client/api/Plugin;)(f,p);
},
installGwt: function(u){return this._getPluginByUrl(u)},
_getPluginByUrl: function(u) {
return u.indexOf(serverUrl) == 0
? this.plugins[u.substring(serverUrl.length)]
: this.plugins[u]
},
go: @com.google.gerrit.client.api.ApiGlue::go(Ljava/lang/String;),
refresh: @com.google.gerrit.client.api.ApiGlue::refresh(),
events: {},
change_actions: {},
revision_actions: {},
project_actions: {},
on: function (e,f){(this.events[e] || (this.events[e]=[])).push(f)},
onAction: function (t,n,c){this._onAction(this.getPluginName(),t,n,c)},
_onAction: function (p,t,n,c) {
@@ -81,34 +90,7 @@ public class ApiGlue {
}
},
'delete': function(u,b){@com.google.gerrit.client.api.ActionContext::delete(Lcom/google/gerrit/client/rpc/RestApi;Lcom/google/gwt/core/client/JavaScriptObject;)(this._api(u),b)},
JsonString: @com.google.gerrit.client.rpc.NativeString::TYPE,
};
Plugin.prototype = {
getPluginName: function(){return this.name},
go: @com.google.gerrit.client.api.ApiGlue::go(Ljava/lang/String;),
refresh: Gerrit.refresh,
onAction: function(t,n,c) {Gerrit._onAction(this.name,t,n,c)},
url: function (d) {
var u = serverUrl + 'plugins/' + this.name + '/';
if (d && d.length > 0) u += d.charAt(0)=='/' ? d.substring(1) : d;
return u;
},
_api: function(d) {
var u = 'plugins/' + this.name + '/';
if (d && d.length > 0) u += d.charAt(0)=='/' ? d.substring(1) : d;
return @com.google.gerrit.client.rpc.RestApi::new(Ljava/lang/String;)(u);
},
get: function(u,b){@com.google.gerrit.client.api.ActionContext::get(Lcom/google/gerrit/client/rpc/RestApi;Lcom/google/gwt/core/client/JavaScriptObject;)(this._api(u),b)},
post: function(u,i,b){@com.google.gerrit.client.api.ActionContext::post(Lcom/google/gerrit/client/rpc/RestApi;Lcom/google/gwt/core/client/JavaScriptObject;Lcom/google/gwt/core/client/JavaScriptObject;)(this._api(u),i,b)},
put: function(u,i,b){@com.google.gerrit.client.api.ActionContext::put(Lcom/google/gerrit/client/rpc/RestApi;Lcom/google/gwt/core/client/JavaScriptObject;Lcom/google/gwt/core/client/JavaScriptObject;)(this._api(u),i,b)},
'delete': function(u,b){@com.google.gerrit.client.api.ActionContext::delete(Lcom/google/gerrit/client/rpc/RestApi;Lcom/google/gwt/core/client/JavaScriptObject;)(this._api(u),b)},
};
$wnd.Gerrit = Gerrit;
}-*/;
/** Install deprecated {@code gerrit_addHistoryHook()} function. */
@@ -119,17 +101,25 @@ public class ApiGlue {
};
}-*/;
private static void install(JavaScriptObject cb, JsUiPlugin p) {
private static void install(JavaScriptObject cb, Plugin p) throws Exception {
try {
pluginName = p.name();
invoke(cb, p);
p._initialized();
} catch (Exception e) {
p.failure(e);
throw e;
} finally {
pluginName = null;
PluginLoader.loaded();
}
}
private static final String getPluginName() {
return pluginName != null ? pluginName : PluginName.get();
if (pluginName != null) {
return pluginName;
}
return PluginName.fromUrl(PluginName.getCallerUrl());
}
private static final void go(String urlOrToken) {

View File

@@ -1,24 +0,0 @@
// Copyright (C) 2013 The Android Open Source Project
//
// 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.gerrit.client.api;
import com.google.gwt.core.client.JavaScriptObject;
class JsUiPlugin extends JavaScriptObject {
final native String name() /*-{ return this.name }-*/;
protected JsUiPlugin() {
}
}

View File

@@ -0,0 +1,77 @@
// Copyright (C) 2013 The Android Open Source Project
//
// 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.gerrit.client.api;
import com.google.gwt.core.client.JavaScriptObject;
final class Plugin extends JavaScriptObject {
private static final JavaScriptObject TYPE = createType();
static Plugin create(String url) {
int s = "plugins/".length();
int e = url.indexOf('/', s);
String name = url.substring(s, e);
return create(TYPE, url, name);
}
final native String url() /*-{ return this._scriptUrl }-*/;
final native String name() /*-{ return this.name }-*/;
final native boolean loaded() /*-{ return this._success || this._failure != null }-*/;
final native Exception failure() /*-{ return this._failure }-*/;
final native void failure(Exception e) /*-{ this._failure = e }-*/;
final native boolean success() /*-{ return this._success || false }-*/;
final native void _initialized() /*-{ this._success = true }-*/;
private static native Plugin create(JavaScriptObject T, String u, String n)
/*-{ return new T(u,n) }-*/;
private static native JavaScriptObject createType() /*-{
function Plugin(u, n) {
this._scriptUrl = u;
this.name = n;
}
return Plugin;
}-*/;
static native void init() /*-{
var G = $wnd.Gerrit;
@com.google.gerrit.client.api.Plugin::TYPE.prototype = {
getPluginName: function(){return this.name},
go: @com.google.gerrit.client.api.ApiGlue::go(Ljava/lang/String;),
refresh: @com.google.gerrit.client.api.ApiGlue::refresh(),
on: G.on,
onAction: function(t,n,c){G._onAction(this.name,t,n,c)},
url: function (u){return G.url(this._url(u))},
get: function(u,b){@com.google.gerrit.client.api.ActionContext::get(Lcom/google/gerrit/client/rpc/RestApi;Lcom/google/gwt/core/client/JavaScriptObject;)(this._api(u),b)},
post: function(u,i,b){@com.google.gerrit.client.api.ActionContext::post(Lcom/google/gerrit/client/rpc/RestApi;Lcom/google/gwt/core/client/JavaScriptObject;Lcom/google/gwt/core/client/JavaScriptObject;)(this._api(u),i,b)},
put: function(u,i,b){@com.google.gerrit.client.api.ActionContext::put(Lcom/google/gerrit/client/rpc/RestApi;Lcom/google/gwt/core/client/JavaScriptObject;Lcom/google/gwt/core/client/JavaScriptObject;)(this._api(u),i,b)},
'delete': function(u,b){@com.google.gerrit.client.api.ActionContext::delete(Lcom/google/gerrit/client/rpc/RestApi;Lcom/google/gwt/core/client/JavaScriptObject;)(this._api(u),b)},
_loadedGwt: function(){@com.google.gerrit.client.api.PluginLoader::loaded()()},
_api: function(u){return @com.google.gerrit.client.rpc.RestApi::new(Ljava/lang/String;)(this._url(u))},
_url: function (d) {
var u = 'plugins/' + this.name + '/';
if (d && d.length > 0)
return u + (d.charAt(0)=='/' ? d.substring(1) : d);
return u;
},
};
}-*/;
protected Plugin() {
}
}

View File

@@ -0,0 +1,185 @@
// Copyright (C) 2013 The Android Open Source Project
//
// 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.gerrit.client.api;
import com.google.gerrit.client.ErrorDialog;
import com.google.gerrit.client.Gerrit;
import com.google.gerrit.client.rpc.NativeMap;
import com.google.gerrit.client.rpc.Natives;
import com.google.gwt.core.client.Callback;
import com.google.gwt.core.client.CodeDownloadException;
import com.google.gwt.core.client.ScriptInjector;
import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.DialogBox;
import com.google.gwtexpui.progress.client.ProgressBar;
import java.util.List;
/** Loads JavaScript plugins with a progress meter visible. */
public class PluginLoader extends DialogBox {
private static final int MAX_LOAD_TIME_MILLIS = 5000;
private static PluginLoader self;
public static void load(List<String> plugins, final String token) {
if (plugins == null || plugins.isEmpty()) {
Gerrit.display(token);
} else {
self = new PluginLoader(token);
self.load(plugins);
self.startTimers();
self.center();
}
}
static void loaded() {
self.loadedOne();
}
private final String token;
private ProgressBar progress;
private Timer show;
private Timer update;
private Timer timeout;
private boolean visible;
private PluginLoader(String tokenToDisplay) {
super(/* auto hide */false, /* modal */true);
token = tokenToDisplay;
progress = new ProgressBar(Gerrit.C.loadingPlugins());
setStyleName(Gerrit.RESOURCES.css().errorDialog());
addStyleName(Gerrit.RESOURCES.css().loadingPluginsDialog());
}
private void load(List<String> pluginUrls) {
for (String url : pluginUrls) {
Plugin plugin = Plugin.create(url);
plugins().put(url, plugin);
ScriptInjector.fromUrl(url)
.setWindow(ScriptInjector.TOP_WINDOW)
.setCallback(new LoadCallback(plugin))
.inject();
}
}
private void startTimers() {
show = new Timer() {
@Override
public void run() {
setText(Window.getTitle());
setWidget(progress);
setGlassEnabled(true);
getGlassElement().addClassName(Gerrit.RESOURCES.css().errorDialogGlass());
hide(true);
center();
visible = true;
}
};
show.schedule(500);
update = new Timer() {
private int cycle;
@Override
public void run() {
progress.setValue(100 * ++cycle * 250 / MAX_LOAD_TIME_MILLIS);
}
};
update.scheduleRepeating(250);
timeout = new Timer() {
@Override
public void run() {
finish();
}
};
timeout.schedule(MAX_LOAD_TIME_MILLIS);
}
private void loadedOne() {
boolean done = true;
for (Plugin plugin : Natives.asList(plugins().values())) {
done &= plugin.loaded();
}
if (done) {
finish();
}
}
private void finish() {
show.cancel();
update.cancel();
timeout.cancel();
self = null;
if (!hadFailures()) {
if (visible) {
progress.setValue(100);
new Timer() {
@Override
public void run() {
hide(true);
}
}.schedule(250);
} else {
hide(true);
}
}
Gerrit.display(token);
}
private boolean hadFailures() {
boolean failed = false;
for (Plugin plugin : Natives.asList(plugins().values())) {
if (!plugin.success()) {
failed = true;
Exception e = plugin.failure();
String msg;
if (e != null && e instanceof CodeDownloadException) {
msg = Gerrit.M.cannotDownloadPlugin(plugin.url());
} else {
msg = Gerrit.M.pluginFailed(plugin.name());
}
hide(true);
new ErrorDialog(msg).center();
}
}
return failed;
}
private static native NativeMap<Plugin> plugins()
/*-{ return $wnd.Gerrit.plugins }-*/;
private class LoadCallback implements Callback<Void, Exception> {
private final Plugin plugin;
LoadCallback(Plugin plugin) {
this.plugin = plugin;
}
@Override
public void onSuccess(Void result) {
}
@Override
public void onFailure(Exception reason) {
plugin.failure(reason);
loadedOne();
}
}
}

View File

@@ -31,14 +31,29 @@ import com.google.gwt.core.client.impl.StackTraceCreator;
class PluginName {
private static final String UNKNOWN = "<unknown>";
static String get() {
return GWT.<PluginName> create(PluginName.class).guessName();
private static String baseUrl() {
return GWT.getHostPageBaseURL() + "plugins/";
}
String guessName() {
static String getCallerUrl() {
return GWT.<PluginName> create(PluginName.class).findCallerUrl();
}
static String fromUrl(String url) {
String baseUrl = baseUrl();
if (url != null && url.startsWith(baseUrl)) {
int s = url.indexOf('/', baseUrl.length());
if (s > 0) {
return url.substring(baseUrl.length(), s);
}
}
return UNKNOWN;
}
String findCallerUrl() {
JavaScriptException err = makeException();
if (hasStack(err)) {
return PluginNameMoz.guessName(err);
return PluginNameMoz.getUrl(err);
}
String baseUrl = baseUrl();
@@ -46,19 +61,12 @@ class PluginName {
for (int i = trace.length - 1; i >= 0; i--) {
String u = trace[i].getFileName();
if (u != null && u.startsWith(baseUrl)) {
int s = u.indexOf('/', baseUrl.length());
if (s > 0) {
return u.substring(baseUrl.length(), s);
}
return u;
}
}
return UNKNOWN;
}
private static String baseUrl() {
return GWT.getHostPageBaseURL() + "plugins/";
}
private static StackTraceElement[] getTrace(JavaScriptException err) {
StackTraceCreator.fillInStackTrace(err);
return err.getStackTrace();
@@ -72,21 +80,22 @@ class PluginName {
/** Extracts URL from the stack frame. */
static class PluginNameMoz extends PluginName {
String guessName() {
return guessName(makeException());
String findCallerUrl() {
return getUrl(makeException());
}
static String guessName(JavaScriptException e) {
private static String getUrl(JavaScriptException e) {
String baseUrl = baseUrl();
JsArrayString stack = getStack(e);
for (int i = stack.length() - 1; i >= 0; i--) {
String frame = stack.get(i);
int at = frame.indexOf(baseUrl);
if (at >= 0) {
int s = frame.indexOf('/', at + baseUrl.length());
if (s > 0) {
return frame.substring(at + baseUrl.length(), s);
int end = frame.indexOf(':', at + baseUrl.length());
if (end < 0) {
end = frame.length();
}
return frame.substring(at, end);
}
}
return UNKNOWN;

View File

@@ -431,6 +431,11 @@ a:hover {
font-size: 15px;
font-family: verdana;
}
.loadingPluginsDialog {
background: #fff;
color: #000;
width: auto;
}
/** Screen **/