Bug 584767 - webapps frontend [r=mfinkle, r=wjohnston, r=fabrice]

This commit is contained in:
Fabrice Desré 2011-06-10 17:02:00 -04:00
parent 55e87ce52f
commit b84d535fd4
15 changed files with 351 additions and 32 deletions

View File

@ -69,6 +69,7 @@ XPCOMUtils.defineLazyGetter(this, "CommonUI", function() {
[
["FullScreenVideo"],
["WebappsUI"],
["BadgeHandlers"],
["ContextHelper"],
["SelectionHelper"],

View File

@ -1038,6 +1038,21 @@ var BrowserUI = {
return this._domWindowClose(browser);
break;
case "DOMLinkAdded":
// checks for an icon to use for a web app
// priority is : icon < apple-touch-icon
let rel = json.rel.toLowerCase().split(" ");
if ((rel.indexOf("icon") != -1) && !browser.appIcon) {
// We should also use the sizes attribute if available
// see http://www.whatwg.org/specs/web-apps/current-work/multipage/links.html#rel-icon
browser.appIcon = json.href;
}
else if (rel.indexOf("apple-touch-icon") != -1) {
// XXX should we support apple-touch-icon-precomposed ?
// see http://developer.apple.com/safari/library/documentation/appleapplications/reference/safariwebcontent/configuringwebapplications/configuringwebapplications.html
browser.appIcon = json.href;
}
// Handle favicon changes
if (Browser.selectedBrowser == browser)
this._updateIcon(Browser.selectedBrowser.mIconURL);
break;
@ -1238,7 +1253,8 @@ var BrowserUI = {
this.activePanel = RemoteTabsList;
break;
case "cmd_quit":
GlobalOverlay.goQuitApplication();
// Only close one window
this._closeOrQuit();
break;
case "cmd_close":
this._closeOrQuit();

View File

@ -378,13 +378,14 @@ var Browser = {
messageManager.addMessageListener("Browser:CertException", this);
messageManager.addMessageListener("Browser:BlockedSite", this);
// broadcast a UIReady message so add-ons know we are finished with startup
// Broadcast a UIReady message so add-ons know we are finished with startup
let event = document.createEvent("Events");
event.initEvent("UIReady", true, false);
window.dispatchEvent(event);
// if we have an opener this was not the first window opened and will not
// If we have an opener this was not the first window opened and will not
// receive an initial resize event. instead we fire the resize handler manually
// Bug 610834
if (window.opener)
resizeHandler({ target: window });
},
@ -941,7 +942,7 @@ var Browser = {
function visibility(aSidebarRect, aVisibleRect) {
let width = aSidebarRect.width;
aSidebarRect.restrictTo(aVisibleRect);
return (aSidebarRect.width ? aSidebarRect.width / width : 0);
return (width ? aSidebarRect.width / width : 0);
}
if (!dx) dx = 0;
@ -1492,6 +1493,7 @@ Browser.WebProgress.prototype = {
tab.hostChanged = true;
tab.browser.lastLocation = location;
tab.browser.userTypedValue = "";
tab.browser.appIcon = null;
#ifdef MOZ_CRASH_REPORTER
if (CrashReporter.enabled)
@ -1576,6 +1578,8 @@ Browser.WebProgress.prototype = {
};
const OPEN_APPTAB = 100; // Hack until we get a real API
function nsBrowserAccess() { }
nsBrowserAccess.prototype = {
@ -1618,13 +1622,31 @@ nsBrowserAccess.prototype = {
tab.closeOnExit = true;
browser = tab.browser;
BrowserUI.hidePanel();
} else if (aWhere == OPEN_APPTAB) {
Browser.tabs.forEach(function(aTab) {
if ("appURI" in aTab.browser && aTab.browser.appURI.spec == aURI.spec) {
Browser.selectedTab = aTab;
browser = aTab.browser;
}
});
if (!browser) {
// Make a new tab to hold the app
let tab = Browser.addTab("about:blank", true, null, { getAttention: true });
browser = tab.browser;
browser.appURI = aURI;
} else {
// Just use the existing browser, but return null to keep the system from trying to load the URI again
browser = null;
}
BrowserUI.hidePanel();
} else { // OPEN_CURRENTWINDOW and illegal values
browser = Browser.selectedBrowser;
}
try {
let referrer;
if (aURI) {
if (aURI && browser) {
if (aOpener) {
location = aOpener.location;
referrer = Services.io.newURI(location, null, null);

View File

@ -68,10 +68,9 @@
title="&brandShortName;"
#ifdef MOZ_PLATFORM_MAEMO
sizemode="fullscreen"
#else
#endif
width="480"
height="800"
#endif
onkeypress="onDebugKeyPress(event);"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
xmlns:html="http://www.w3.org/1999/xhtml">
@ -349,6 +348,7 @@
onclick="PageActions.clearPagePermissions(event);"/>
<pageaction id="pageaction-search" title="&pageactions.search.addNew;"/>
<pageaction id="pageaction-charset" title="&pageactions.charEncoding;" onclick="CharsetMenu.show();"/>
<pageaction id="pageaction-webapps-install" title="&pageactions.webapps.install;" onclick="WebappsUI.show();"/>
</hbox>
</arrowbox>

View File

@ -174,6 +174,7 @@ var PageActions = {
#endif
this.register("pageaction-share", this.updateShare, this);
this.register("pageaction-search", BrowserSearch.updatePageSearchEngines, BrowserSearch);
this.register("pageaction-webapps-install", WebappsUI.updateWebappsInstall, WebappsUI);
},
handleEvent: function handleEvent(aEvent) {
@ -1658,3 +1659,128 @@ var CharsetMenu = {
}
};
var WebappsUI = {
_dialog: null,
_manifest: null,
checkBox: function(aEvent) {
let elem = aEvent.originalTarget;
let perm = elem.getAttribute("perm");
if (this._manifest.capabilities && this._manifest.capabilities.indexOf(perm) != -1) {
if (elem.checked) {
elem.classList.remove("webapps-noperm");
elem.classList.add("webapps-perm");
} else {
elem.classList.remove("webapps-perm");
elem.classList.add("webapps-noperm");
}
}
},
show: function show(aManifest) {
if (!aManifest) {
// Try every way to get an icon
let browser = Browser.selectedBrowser;
let icon = browser.appIcon;
if (!icon)
icon = browser.mIconURL;
if (!icon)
icon = gFaviconService.getFaviconImageForPage(browser.currentURI).spec;
// Create a simple manifest
aManifest = {
uri: browser.currentURI.spec,
name: browser.contentTitle,
icon: icon
};
}
this._manifest = aManifest;
this._dialog = importDialog(window, "chrome://browser/content/webapps.xul", null);
if (aManifest.name)
document.getElementById("webapps-title").value = aManifest.name;
if (aManifest.icon)
document.getElementById("webapps-icon").src = aManifest.icon;
let uri = Services.io.newURI(aManifest.uri, null, null);
let perms = [["offline", "offline-app"], ["geoloc", "geo"], ["notifications", "desktop-notifications"]];
perms.forEach(function(tuple) {
let elem = document.getElementById("webapps-" + tuple[0] + "-checkbox");
let currentPerm = Services.perms.testExactPermission(uri, tuple[1]);
if ((aManifest.capabilities && (aManifest.capabilities.indexOf(tuple[1]) != -1)) || (currentPerm == Ci.nsIPermissionManager.ALLOW_ACTION))
elem.checked = true;
else
elem.checked = (currentPerm == Ci.nsIPermissionManager.ALLOW_ACTION);
elem.classList.remove("webapps-noperm");
elem.classList.add("webapps-perm");
});
BrowserUI.pushPopup(this, this._dialog);
// Force a modal dialog
this._dialog.waitForClose();
},
hide: function hide() {
this._dialog.close();
this._dialog = null;
BrowserUI.popPopup(this);
},
_updatePermission: function updatePermission(aId, aPerm) {
try {
let uri = Services.io.newURI(this._manifest.uri, null, null);
Services.perms.add(uri, aPerm, document.getElementById(aId).checked ? Ci.nsIPermissionManager.ALLOW_ACTION : Ci.nsIPermissionManager.DENY_ACTION);
} catch(e) {
Cu.reportError(e);
}
},
launch: function launch() {
let title = document.getElementById("webapps-title").value;
if (!title)
return;
this._updatePermission("webapps-offline-checkbox", "offline-app");
this._updatePermission("webapps-geoloc-checkbox", "geo");
this._updatePermission("webapps-notifications-checkbox", "desktop-notification");
this.hide();
this.install(this._manifest.uri, title, this._manifest.icon);
},
updateWebappsInstall: function updateWebappsInstall(aNode) {
return !document.getElementById("main-window").hasAttribute("webapp");
},
install: function(aURI, aTitle, aIcon) {
const kIconSize = 64;
let canvas = document.createElementNS("http://www.w3.org/1999/xhtml", "canvas");
canvas.setAttribute("style", "display: none");
let self = this;
let image = new Image();
image.onload = function() {
canvas.width = canvas.height = kIconSize; // clears the canvas
let ctx = canvas.getContext("2d");
ctx.drawImage(image, 0, 0, kIconSize, kIconSize);
let data = canvas.toDataURL("image/png", "");
canvas = null;
try {
let webapp = Cc["@mozilla.org/webapps/support;1"].getService(Ci.nsIWebappsSupport);
webapp.installApplication(aTitle, aURI, aIcon, data);
} catch(e) {
Cu.reportError(e);
}
}
image.onerror = function() {
// can't load the icon (bad URI) : fallback to the default one from chrome
self.install(aURI, aTitle, "chrome://browser/skin/images/favicon-default-30.png");
}
image.src = aIcon;
}
};

View File

@ -0,0 +1,94 @@
<?xml version="1.0"?>
<!-- ***** BEGIN LICENSE BLOCK *****
- Version: MPL 1.1/GPL 2.0/LGPL 2.1
-
- The contents of this file are subject to the Mozilla Public License Version
- 1.1 (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.mozilla.org/MPL/
-
- Software distributed under the License is distributed on an "AS IS" basis,
- WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
- for the specific language governing rights and limitations under the
- License.
-
- The Original Code is Mozilla Mobile Browser.
-
- The Initial Developer of the Original Code is
- Mozilla Corporation.
- Portions created by the Initial Developer are Copyright (C) 2008
- the Initial Developer. All Rights Reserved.
-
- Contributor(s):
- Fabrice Desré <fabrice@mozilla.com>
-
- Alternatively, the contents of this file may be used under the terms of
- either the GNU General Public License Version 2 or later (the "GPL"), or
- the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
- in which case the provisions of the GPL or the LGPL are applicable instead
- of those above. If you wish to allow use of your version of this file only
- under the terms of either the GPL or the LGPL, and not to allow others to
- use your version of this file under the terms of the MPL, indicate your
- decision by deleting the provisions above and replace them with the notice
- and other provisions required by the LGPL or the GPL. If you do not delete
- the provisions above, a recipient may use your version of this file under
- the terms of any one of the MPL, the GPL or the LGPL.
-
- ***** END LICENSE BLOCK ***** -->
<!DOCTYPE dialog [
<!ENTITY % promptDTD SYSTEM "chrome://browser/locale/prompt.dtd">
%promptDTD;
<!ENTITY % webappsDTD SYSTEM "chrome://browser/locale/webapps.dtd">
%webappsDTD;
]>
<dialog id="webapp-dialog" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<keyset>
<key keycode="VK_ESCAPE" command="cmd_cancel"/>
<key keycode="VK_RETURN" command="cmd_ok"/>
</keyset>
<commandset>
<command id="cmd_cancel" oncommand="WebappsUI.hide();"/>
<command id="cmd_ok" oncommand="WebappsUI.launch();"/>
</commandset>
<vbox class="prompt-title" id="webapps-title-box">
<hbox align="center">
<image id="webapps-icon"/>
<vbox flex="1">
<textbox id="webapps-title" placeholder="&webapps.title.placeholder;" flex="1"/>
</vbox>
</hbox>
</vbox>
<separator class="prompt-line"/>
<scrollbox class="prompt-message prompt-header" id="webapps-perm-box" orient="vertical" oncommand="WebappsUI.checkBox(event)" flex="1">
<label crop="center" flex="1" value="&webapps.permissions;"/>
<button id="webapps-geoloc-checkbox" perm="geo" type="checkbox" class="button-checkbox webapps-perm" flex="1">
<image class="button-image-icon"/>
<vbox flex="1">
<description class="prompt-checkbox-label" flex="1">&webapps.perm.geolocation;</description>
<description class="prompt-checkbox-label webapps-perm-requested-hint" id="webapps-geoloc-app">&webapps.perm.requested;</description>
</vbox>
</button>
<button id="webapps-offline-checkbox" perm="offline-app" type="checkbox" class="button-checkbox webapps-perm" flex="1">
<image class="button-image-icon"/>
<vbox flex="1">
<description class="prompt-checkbox-label" flex="1">&webapps.perm.offline;</description>
<description class="prompt-checkbox-label webapps-perm-requested-hint" id="webapps-offline-app">&webapps.perm.requested;</description>
</vbox>
</button>
<button id="webapps-notifications-checkbox" perm="desktop-notifications" type="checkbox" class="button-checkbox webapps-perm" flex="1">
<image class="button-image-icon"/>
<vbox flex="1">
<description class="prompt-checkbox-label" flex="1">&webapps.perm.notifications;</description>
<description class="prompt-checkbox-label webapps-perm-requested-hint" id="webapps-notifications-app">&webapps.perm.requested;</description>
</vbox>
</button>
</scrollbox>
<hbox pack="center" class="prompt-buttons">
<button class="prompt-button" command="cmd_ok" label="&ok.label;"/>
<separator/>
<button class="prompt-button" command="cmd_cancel" label="&cancel.label;"/>
</hbox>
</dialog>

View File

@ -64,6 +64,7 @@ chrome.jar:
content/prompt/select.xul (content/prompt/select.xul)
content/prompt/prompt.js (content/prompt/prompt.js)
content/share.xul (content/share.xul)
content/webapps.xul (content/webapps.xul)
content/AnimatedZoom.js (content/AnimatedZoom.js)
#ifdef MOZ_SERVICES_SYNC
content/sync.js (content/sync.js)

View File

@ -44,12 +44,12 @@ Cu.import("resource://gre/modules/Services.jsm");
function openWindow(aParent, aURL, aTarget, aFeatures, aArgs) {
let argString = null;
if (aArgs) {
if (aArgs && !(aArgs instanceof Ci.nsISupportsArray)) {
argString = Cc["@mozilla.org/supports-string;1"].createInstance(Ci.nsISupportsString);
argString.data = aArgs;
}
return Services.ww.openWindow(aParent, aURL, aTarget, aFeatures, argString);
return Services.ww.openWindow(aParent, aURL, aTarget, aFeatures, argString || aArgs);
}
function resolveURIInternal(aCmdLine, aArgument) {
@ -61,16 +61,14 @@ function resolveURIInternal(aCmdLine, aArgument) {
try {
if (uri.file.exists())
return uri;
}
catch (e) {
} catch (e) {
Cu.reportError(e);
}
try {
let urifixup = Cc["@mozilla.org/docshell/urifixup;1"].getService(Ci.nsIURIFixup);
uri = urifixup.createFixupURI(aArgument, 0);
}
catch (e) {
} catch (e) {
Cu.reportError(e);
}
@ -155,8 +153,7 @@ BrowserCLH.prototype = {
// Stop the normal commandline processing from continuing
aCmdLine.preventDefault = true;
}
}
catch (e) {
} catch (e) {
Cu.reportError(e);
}
return;
@ -165,6 +162,12 @@ BrowserCLH.prototype = {
// Check and remove the alert flag here, but we'll handle it a bit later - see below
let alertFlag = aCmdLine.handleFlagWithParam("alert", false);
// Check and remove the webapp param
let appFlag = aCmdLine.handleFlagWithParam("webapp", false);
let appURI;
if (appFlag)
appURI = resolveURIInternal(aCmdLine, appFlag);
// Keep an array of possible URL arguments
let uris = [];
@ -187,16 +190,17 @@ BrowserCLH.prototype = {
}
// Open the main browser window, if we don't already have one
let win;
let localePickerWin;
let browserWin;
try {
win = Services.wm.getMostRecentWindow("navigator:browser");
localePickerWin = Services.wm.getMostRecentWindow("navigator:localepicker");
if (localePickerWin) {
localePickerWin.focus();
let localeWin = Services.wm.getMostRecentWindow("navigator:localepicker");
if (localeWin) {
localeWin.focus();
aCmdLine.preventDefault = true;
return;
} else if (!win) {
}
browserWin = Services.wm.getMostRecentWindow("navigator:browser");
if (!browserWin) {
// Default to the saved homepage
let defaultURL = getHomePage();
@ -208,30 +212,35 @@ BrowserCLH.prototype = {
// Show the locale selector if we have a new profile
if (needHomepageOverride() == "new profile" && Services.prefs.getBoolPref("browser.firstrun.show.localepicker")) {
win = openWindow(null, "chrome://browser/content/localePicker.xul", "_blank", "chrome,dialog=no,all", defaultURL);
browserWin = openWindow(null, "chrome://browser/content/localePicker.xul", "_blank", "chrome,dialog=no,all", defaultURL);
aCmdLine.preventDefault = true;
return;
}
win = openWindow(null, "chrome://browser/content/browser.xul", "_blank", "chrome,dialog=no,all", defaultURL);
browserWin = openWindow(null, "chrome://browser/content/browser.xul", "_blank", "chrome,dialog=no,all", defaultURL);
}
win.focus();
browserWin.focus();
// Stop the normal commandline processing from continuing. We just opened the main browser window
aCmdLine.preventDefault = true;
} catch (e) { }
} catch (e) {
Cu.reportError(e);
}
// Assumption: All remaining command line arguments have been sent remotely (browser is already running)
// Action: Open any URLs we find into an existing browser window
// First, get a browserDOMWindow object
while (!win.browserDOMWindow)
while (!browserWin.browserDOMWindow)
Services.tm.currentThread.processNextEvent(true);
// Open any URIs into new tabs
for (let i = 0; i < uris.length; i++)
win.browserDOMWindow.openURI(uris[i], null, Ci.nsIBrowserDOMWindow.OPEN_NEWTAB, Ci.nsIBrowserDOMWindow.OPEN_EXTERNAL);
browserWin.browserDOMWindow.openURI(uris[i], null, Ci.nsIBrowserDOMWindow.OPEN_NEWTAB, Ci.nsIBrowserDOMWindow.OPEN_EXTERNAL);
if (appURI)
browserWin.browserDOMWindow.openURI(appURI, null, browserWin.OPEN_APPTAB, Ci.nsIBrowserDOMWindow.OPEN_NEW);
// Handle the notification, if called from it
if (alertFlag) {
@ -243,9 +252,9 @@ BrowserCLH.prototype = {
var updateTimerCallback = updateService.QueryInterface(Ci.nsITimerCallback);
updateTimerCallback.notify(null);
} else if (alertFlag.length >= 9 && alertFlag.substr(0, 9) == "download:") {
showPanelWhenReady(win, "downloads-container");
showPanelWhenReady(browserWin, "downloads-container");
} else if (alertFlag == "addons") {
showPanelWhenReady(win, "addons-container");
showPanelWhenReady(browserWin, "addons-container");
}
}
},

View File

@ -275,6 +275,7 @@
@BINPATH@/components/xuldoc.xpt
@BINPATH@/components/xultmpl.xpt
@BINPATH@/components/zipwriter.xpt
@BINPATH@/components/webapps.xpt
; JavaScript components
@BINPATH@/components/ConsoleAPI.manifest
@ -330,6 +331,7 @@
@BINPATH@/components/amContentHandler.js
@BINPATH@/components/amWebInstallListener.js
@BINPATH@/components/nsBlocklistService.js
#ifdef MOZ_UPDATER
@BINPATH@/components/nsUpdateService.manifest
@BINPATH@/components/nsUpdateService.js
@ -610,6 +612,7 @@ bin/components/@DLL_PREFIX@nkgnomevfs@DLL_SUFFIX@
#ifdef MOZ_UPDATER
@BINPATH@/components/UpdatePrompt.js
#endif
@BINPATH@/components/WebappsSupport.js
@BINPATH@/components/XPIDialogService.js
@BINPATH@/components/browsercomps.xpt
@BINPATH@/extensions/feedback@mobile.mozilla.org.xpi

View File

@ -111,5 +111,6 @@
<!ENTITY pageactions.findInPage "Find In Page">
<!ENTITY pageactions.search.addNew "Add Search Engine">
<!ENTITY pageactions.charEncoding "Character Encoding">
<!ENTITY pageactions.webapps.install "Install as App">
<!ENTITY appMenu.siteOptions "Site Options">

View File

@ -0,0 +1,6 @@
<!ENTITY webapps.title.placeholder "Enter a title">
<!ENTITY webapps.permissions "Allow access:">
<!ENTITY webapps.perm.geolocation "Location-aware browsing">
<!ENTITY webapps.perm.offline "Offline data storage">
<!ENTITY webapps.perm.notifications "Desktop notifications">
<!ENTITY webapps.perm.requested "requested">

View File

@ -16,6 +16,7 @@
locale/@AB_CD@/browser/sync.dtd (%chrome/sync.dtd)
locale/@AB_CD@/browser/sync.properties (%chrome/sync.properties)
locale/@AB_CD@/browser/prompt.dtd (%chrome/prompt.dtd)
locale/@AB_CD@/browser/webapps.dtd (%chrome/webapps.dtd)
locale/@AB_CD@/browser/feedback.dtd (%chrome/feedback.dtd)
locale/@AB_CD@/browser/phishing.dtd (%chrome/phishing.dtd)
locale/@AB_CD@/browser/bookmarks.json (bookmarks.json)

View File

@ -1329,6 +1329,25 @@ setting {
z-index: 500;
}
/* openwebapps capabilities ------------------------------------------------------------ */
.webapps-noperm description.webapps-perm-requested-hint {
display: block;
}
.webapps-perm description.webapps-perm-requested-hint {
display: none;
}
#webapps-icon {
width: 48px;
height: 48px;
margin: @margin_normal@;
}
#webapps-title {
-moz-margin-end: @margin_normal@;
}
/* Android menu ------------------------------------------------------------ */
#appmenu {
background: rgba(255,255,255,0.95);

View File

@ -1311,6 +1311,25 @@ setting {
z-index: 500;
}
/* openwebapps capabilities ------------------------------------------------------------ */
.webapps-noperm description.webapps-perm-requested-hint {
display: block;
}
.webapps-perm description.webapps-perm-requested-hint {
display: none;
}
#webapps-icon {
width: 32px;
height: 32px;
margin: @margin_normal@;
}
#webapps-title {
-moz-margin-end: @margin_normal@;
}
/* Android menu ------------------------------------------------------------ */
#appmenu {
background: @color_background_default@;

View File

@ -287,12 +287,13 @@ toolbarbutton[open="true"] {
list-style-image: url("chrome://browser/skin/images/check-selected-hdpi.png");
}
.button-checkbox > .button-box,
.button-checkbox:hover:active > .button-box,
.button-checkbox[checked="true"] > .button-box {
padding-top: @padding_tiny@;
padding-bottom: @padding_xsmall@;
-moz-padding-start: @margin_small@;
-moz-padding-end: @margin_small@;
-moz-padding-start: @padding_small@;
-moz-padding-end: @padding_small@;
}
/* radio buttons ----------------------------------------------------------- */