Merge m-c to inbound.

This commit is contained in:
Ryan VanderMeulen 2013-08-06 14:24:39 -04:00
commit f2638ccd28
19 changed files with 1888 additions and 340 deletions

View File

@ -255,7 +255,6 @@ pref("layers.async-pan-zoom.enabled", true);
// Web Notifications
pref("notification.feature.enabled", true);
pref("dom.webnotifications.enabled", false);
// IndexedDB
pref("indexedDB.feature.enabled", true);
@ -660,13 +659,13 @@ pref("accessibility.accessfu.utterance", 1);
pref("accessibility.accessfu.skip_empty_images", true);
// Enable hit-target fluffing
pref("ui.touch.radius.enabled", false);
pref("ui.touch.radius.enabled", true);
pref("ui.touch.radius.leftmm", 3);
pref("ui.touch.radius.topmm", 5);
pref("ui.touch.radius.rightmm", 3);
pref("ui.touch.radius.bottommm", 2);
pref("ui.mouse.radius.enabled", false);
pref("ui.mouse.radius.enabled", true);
pref("ui.mouse.radius.leftmm", 3);
pref("ui.mouse.radius.topmm", 5);
pref("ui.mouse.radius.rightmm", 3);

View File

@ -1,4 +1,4 @@
{
"revision": "bfebfb81d21bd6a4fb30f11044e6531e8bfaea3a",
"revision": "ca81894022ba9094e24f6f1d233ef0ebb1b59bba",
"repo_path": "/integration/gaia-central"
}

File diff suppressed because it is too large Load Diff

View File

@ -11,7 +11,8 @@ import os
def get_build_entries(root_path):
""" Iterates through the root_path, creating a list for each file and
directory. Excludes any file paths ending with channel-prefs.js.
directory. Excludes any path starting with extensions or distribution
and paths ending with channel-prefs.js.
"""
rel_file_path_set = set()
rel_dir_path_set = set()
@ -20,14 +21,18 @@ def get_build_entries(root_path):
parent_dir_rel_path = root[len(root_path)+1:]
rel_path_file = os.path.join(parent_dir_rel_path, file_name)
rel_path_file = rel_path_file.replace("\\", "/")
if not (rel_path_file.endswith("channel-prefs.js")):
if not (rel_path_file.startswith("distribution/") or
rel_path_file.startswith("extensions/") or
rel_path_file.endswith("channel-prefs.js")):
rel_file_path_set.add(rel_path_file)
for dir_name in dirs:
parent_dir_rel_path = root[len(root_path)+1:]
rel_path_dir = os.path.join(parent_dir_rel_path, dir_name)
rel_path_dir = rel_path_dir.replace("\\", "/")+"/"
rel_dir_path_set.add(rel_path_dir)
if not (rel_path_dir.startswith("distribution/") or
rel_path_dir.startswith("extensions/")):
rel_dir_path_set.add(rel_path_dir)
rel_file_path_list = list(rel_file_path_set)
rel_file_path_list.sort(reverse=True)

View File

@ -275,6 +275,11 @@ this.PermissionsTable = { geolocation: {
privileged: DENY_ACTION,
certified: ALLOW_ACTION
},
"wappush": {
app: DENY_ACTION,
privileged: DENY_ACTION,
certified: ALLOW_ACTION
},
};
/**

View File

@ -1920,17 +1920,70 @@ this.DOMApplicationRegistry = {
// content side. This let the webpage the opportunity to set event handlers
// on the app before we start firing progress events.
queuedDownload: {},
queuedPackageDownload: {},
onInstallSuccessAck: function onInstallSuccessAck(aManifestURL) {
let download = this.queuedDownload[aManifestURL];
if (!download) {
let cacheDownload = this.queuedDownload[aManifestURL];
if (cacheDownload) {
this.startOfflineCacheDownload(cacheDownload.manifest,
cacheDownload.app,
cacheDownload.profileDir,
cacheDownload.offlineCacheObserver);
delete this.queuedDownload[aManifestURL];
return;
}
this.startOfflineCacheDownload(download.manifest,
download.app,
download.profileDir,
download.offlineCacheObserver);
delete this.queuedDownload[aManifestURL];
let packageDownload = this.queuedPackageDownload[aManifestURL];
if (packageDownload) {
let manifest = packageDownload.manifest;
let appObject = packageDownload.app;
let installSuccessCallback = packageDownload.callback;
delete this.queuedPackageDownload[aManifestURL];
this.downloadPackage(manifest, appObject, false, (function(aId, aManifest) {
// Move the zip out of TmpD.
let app = DOMApplicationRegistry.webapps[aId];
let zipFile = FileUtils.getFile("TmpD", ["webapps", aId, "application.zip"], true);
let dir = this._getAppDir(aId);
zipFile.moveTo(dir, "application.zip");
let tmpDir = FileUtils.getDir("TmpD", ["webapps", aId], true, true);
try {
tmpDir.remove(true);
} catch(e) { }
// Save the manifest
let manFile = dir.clone();
manFile.append("manifest.webapp");
this._writeFile(manFile, JSON.stringify(aManifest), function() { });
// Set state and fire events.
app.installState = "installed";
app.downloading = false;
app.downloadAvailable = false;
this._saveApps((function() {
this.updateAppHandlers(null, aManifest, appObject);
this.broadcastMessage("Webapps:AddApp", { id: aId, app: appObject });
if (supportUseCurrentProfile()) {
// Update the permissions for this app.
PermissionsInstaller.installPermissions({ manifest: aManifest,
origin: appObject.origin,
manifestURL: appObject.manifestURL },
true);
}
debug("About to fire Webapps:PackageEvent 'installed'");
this.broadcastMessage("Webapps:PackageEvent",
{ type: "installed",
manifestURL: appObject.manifestURL,
app: app,
manifest: aManifest });
if (installSuccessCallback) {
installSuccessCallback(aManifest);
}
}).bind(this));
}).bind(this));
}
},
confirmInstall: function(aData, aFromSync, aProfileDir,
@ -2039,11 +2092,13 @@ this.DOMApplicationRegistry = {
aData.app[aProp] = appObject[aProp];
});
this.queuedDownload[app.manifestURL] = {
manifest: manifest,
app: appObject,
profileDir: aProfileDir,
offlineCacheObserver: aOfflineCacheObserver
if (manifest.appcache_path) {
this.queuedDownload[app.manifestURL] = {
manifest: manifest,
app: appObject,
profileDir: aProfileDir,
offlineCacheObserver: aOfflineCacheObserver
}
}
// We notify about the successful installation via mgmt.oninstall and the
@ -2068,47 +2123,12 @@ this.DOMApplicationRegistry = {
// origin for install apps is meaningless here, since it's app:// and this
// can't be used to resolve package paths.
manifest = new ManifestHelper(jsonManifest, app.manifestURL);
this.downloadPackage(manifest, appObject, false, (function(aId, aManifest) {
// Success! Move the zip out of TmpD.
let app = DOMApplicationRegistry.webapps[aId];
let zipFile = FileUtils.getFile("TmpD", ["webapps", aId, "application.zip"], true);
let dir = this._getAppDir(id);
zipFile.moveTo(dir, "application.zip");
let tmpDir = FileUtils.getDir("TmpD", ["webapps", aId], true, true);
try {
tmpDir.remove(true);
} catch(e) { }
// Save the manifest
let manFile = dir.clone();
manFile.append("manifest.webapp");
this._writeFile(manFile, JSON.stringify(aManifest), function() { });
// Set state and fire events.
app.installState = "installed";
app.downloading = false;
app.downloadAvailable = false;
this._saveApps((function() {
this.updateAppHandlers(null, aManifest, appObject);
this.broadcastMessage("Webapps:AddApp", { id: aId, app: appObject });
if (supportUseCurrentProfile()) {
// Update the permissions for this app.
PermissionsInstaller.installPermissions({ manifest: aManifest,
origin: appObject.origin,
manifestURL: appObject.manifestURL },
true);
}
debug("About to fire Webapps:PackageEvent 'installed'");
this.broadcastMessage("Webapps:PackageEvent",
{ type: "installed",
manifestURL: appObject.manifestURL,
app: app,
manifest: aManifest });
if (aInstallSuccessCallback) {
aInstallSuccessCallback(aManifest);
}
}).bind(this));
}).bind(this));
this.queuedPackageDownload[app.manifestURL] = {
manifest: manifest,
app: appObject,
callback: aInstallSuccessCallback
}
}
},

View File

@ -18,16 +18,11 @@ MOCHITEST_FILES = \
file_cached_app.template.appcache \
file_hosted_app.template.webapp \
test_app_update.html \
$(NULL)
ifdef MOZ_B2G
MOCHITEST_FILES += \
file_packaged_app.sjs \
file_packaged_app.template.webapp \
file_packaged_app.template.html \
test_packaged_app_install.html \
$(NULL)
endif
MOCHITEST_CHROME_FILES = \
test_apps_service.xul \

View File

@ -1493,6 +1493,15 @@ Navigator::DoNewResolve(JSContext* aCx, JS::Handle<JSObject*> aObject,
return true;
}
if (name.EqualsLiteral("mozSettings")) {
bool hasPermission = CheckPermission("settings-read") ||
CheckPermission("settings-write");
if (!hasPermission) {
aValue.setNull();
return true;
}
}
domObject = construct(aCx, naviObj);
if (!domObject) {
return Throw<true>(aCx, NS_ERROR_FAILURE);

View File

@ -97,7 +97,7 @@ this.SystemMessagePermissionsTable = {
"mobileconnection": []
},
"wappush-received": {
"sms": []
"wappush": []
},
};

View File

@ -1304,14 +1304,17 @@ this.PushService = {
_wsOnStop: function(context, statusCode) {
debug("wsOnStop()");
this._shutdownWS();
if (statusCode != Cr.NS_OK &&
!(statusCode == Cr.NS_BASE_STREAM_CLOSED && this._willBeWokenUpByUDP)) {
debug("Socket error " + statusCode);
this._reconnectAfterBackoff();
}
// Bug 896919. We always shutdown the WebSocket, even if we need to
// reconnect. This works because _reconnectAfterBackoff() is "async"
// (there is a minimum delay of the pref retryBaseInterval, which by default
// is 5000ms), so that function will open the WebSocket again.
this._shutdownWS();
},
_wsOnMessageAvailable: function(context, message) {

View File

@ -16,6 +16,7 @@ MOCHITEST_FILES = \
test_settings_events.html \
test_settings_onsettingchange.html \
test_settings_blobs.html \
test_settings_navigator_object.html \
$(NULL)
MOCHITEST_CHROME_FILES = \

View File

@ -0,0 +1,43 @@
<!DOCTYPE html>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=898512
-->
<head>
<title>Test for Bug 898512 Settings API</title>
<script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=898512">Mozilla Bug 898512</a>
<p id="display"></p>
<div id="content" style="display: none">
<iframe></iframe>
</div>
<pre id="test">
<script class="testbody" type="text/javascript;version=1.7">
SimpleTest.waitForExplicitFinish();
function testPref() {
SpecialPowers.pushPrefEnv({
set: [["dom.mozSettings.enabled", false]]
}, function() {
ise(navigator.mozSettings, undefined, "navigator.mozSettings is undefined");
SimpleTest.finish();
});
}
SpecialPowers.pushPermissions([
{type: "settings-read", allow: 0, context: document},
{type: "settings-write", allow: 0, context: document}
], function() {
ise(frames[0].navigator.mozSettings, null, "navigator.mozSettings is null when the page doesn't have permissions");
testPref();
});
</script>
</pre>
</body>
</html>

View File

@ -1530,10 +1530,11 @@ RILContentHelper.prototype = {
this._deliverEvent("_iccListeners", "notifyStkSessionEnd", null);
break;
case "RIL:IccOpenChannel":
this.handleIccOpenChannel(msg.json);
this.handleSimpleRequest(msg.json.requestId, msg.json.errorMsg,
msg.json.channel);
break;
case "RIL:IccCloseChannel":
this.handleIccCloseChannel(msg.json);
this.handleSimpleRequest(msg.json.requestId, msg.json.errorMsg, null);
break;
case "RIL:IccExchangeAPDU":
this.handleIccExchangeAPDU(msg.json);
@ -1542,7 +1543,7 @@ RILContentHelper.prototype = {
this.handleReadIccContacts(msg.json);
break;
case "RIL:UpdateIccContact":
this.handleUpdateIccContact(msg.json);
this.handleSimpleRequest(msg.json.requestId, msg.json.errorMsg, null);
break;
case "RIL:DataError": {
let data = msg.json.data;
@ -1555,19 +1556,20 @@ RILContentHelper.prototype = {
this.handleGetCallForwardingOption(msg.json);
break;
case "RIL:SetCallForwardingOption":
this.handleSetCallForwardingOption(msg.json);
this.handleSimpleRequest(msg.json.requestId, msg.json.errorMsg, null);
break;
case "RIL:GetCallBarringOption":
this.handleGetCallBarringOption(msg.json);
break;
case "RIL:SetCallBarringOption":
this.handleSetCallBarringOption(msg.json);
this.handleSimpleRequest(msg.json.requestId, msg.json.errorMsg, null);
break;
case "RIL:GetCallWaitingOption":
this.handleGetCallWaitingOption(msg.json);
this.handleSimpleRequest(msg.json.requestId, msg.json.errorMsg,
msg.json.enabled);
break;
case "RIL:SetCallWaitingOption":
this.handleSetCallWaitingOption(msg.json);
this.handleSimpleRequest(msg.json.requestId, msg.json.errorMsg, null);
break;
case "RIL:CfStateChanged": {
let data = msg.json.data;
@ -1582,7 +1584,7 @@ RILContentHelper.prototype = {
this.handleGetCallingLineIdRestriction(msg.json);
break;
case "RIL:SetCallingLineIdRestriction":
this.handleSetCallingLineIdRestriction(msg.json);
this.handleSimpleRequest(msg.json.requestId, msg.json.errorMsg, null);
break;
case "RIL:CellBroadcastReceived": {
let message = new CellBroadcastMessage(msg.json.data);
@ -1592,10 +1594,11 @@ RILContentHelper.prototype = {
break;
}
case "RIL:SetRoamingPreference":
this.handleSetRoamingPreference(msg.json);
this.handleSimpleRequest(msg.json.requestId, msg.json.errorMsg, null);
break;
case "RIL:GetRoamingPreference":
this.handleGetRoamingPreference(msg.json);
this.handleSimpleRequest(msg.json.requestId, msg.json.errorMsg,
msg.json.mode);
break;
}
},
@ -1629,19 +1632,19 @@ RILContentHelper.prototype = {
callback.enumerateCallStateComplete();
},
handleSimpleRequest: function handleSimpleRequest(requestId, errorMsg, result) {
if (errorMsg) {
this.fireRequestError(requestId, errorMsg);
} else {
this.fireRequestSuccess(requestId, result);
}
},
handleGetAvailableNetworks: function handleGetAvailableNetworks(message) {
debug("handleGetAvailableNetworks: " + JSON.stringify(message));
let requestId = message.requestId;
let request = this.takeRequest(requestId);
if (!request) {
debug("no DOMRequest found with request ID: " + requestId);
return;
}
if (message.errorMsg) {
debug("Received error from getAvailableNetworks: " + message.errorMsg);
Services.DOMRequest.fireError(request, message.errorMsg);
this.fireRequestError(message.requestId, message.errorMsg);
return;
}
@ -1653,7 +1656,7 @@ RILContentHelper.prototype = {
networks[i] = info;
}
Services.DOMRequest.fireSuccess(request, networks);
this.fireRequestSuccess(message.requestId, networks);
},
handleSelectNetwork: function handleSelectNetwork(message, mode) {
@ -1667,22 +1670,6 @@ RILContentHelper.prototype = {
}
},
handleIccOpenChannel: function handleIccOpenChannel(message) {
if (message.errorMsg) {
this.fireRequestError(message.requestId, message.errorMsg);
} else {
this.fireRequestSuccess(message.requestId, message.channel);
}
},
handleIccCloseChannel: function handleIccCloseChannel(message) {
if (message.errorMsg) {
this.fireRequestError(message.requestId, message.errorMsg);
} else {
this.fireRequestSuccess(message.requestId, null);
}
},
handleIccExchangeAPDU: function handleIccExchangeAPDU(message) {
if (message.errorMsg) {
this.fireRequestError(message.requestId, message.errorMsg);
@ -1723,14 +1710,6 @@ RILContentHelper.prototype = {
ObjectWrapper.wrap(result, window));
},
handleUpdateIccContact: function handleUpdateIccContact(message) {
if (message.errorMsg) {
this.fireRequestError(message.requestId, message.errorMsg);
} else {
this.fireRequestSuccess(message.requestId, null);
}
},
handleVoicemailNotification: function handleVoicemailNotification(message) {
let changed = false;
if (!this.voicemailStatus) {
@ -1777,33 +1756,13 @@ RILContentHelper.prototype = {
},
handleGetCallForwardingOption: function handleGetCallForwardingOption(message) {
let requestId = message.requestId;
let request = this.takeRequest(requestId);
if (!request) {
return;
}
if (!message.success) {
Services.DOMRequest.fireError(request, message.errorMsg);
if (message.errorMsg) {
this.fireRequestError(message.requestId, message.errorMsg);
return;
}
this._cfRulesToMobileCfInfo(message.rules);
Services.DOMRequest.fireSuccess(request, message.rules);
},
handleSetCallForwardingOption: function handleSetCallForwardingOption(message) {
let requestId = message.requestId;
let request = this.takeRequest(requestId);
if (!request) {
return;
}
if (!message.success) {
Services.DOMRequest.fireError(request, message.errorMsg);
return;
}
Services.DOMRequest.fireSuccess(request, null);
this.fireRequestSuccess(message.requestId, message.rules);
},
handleGetCallBarringOption: function handleGetCallBarringOption(message) {
@ -1815,72 +1774,15 @@ RILContentHelper.prototype = {
}
},
handleSetCallBarringOption: function handleSetCallBarringOption(message) {
if (!message.success) {
this.fireRequestError(message.requestId, message.errorMsg);
} else {
this.fireRequestSuccess(message.requestId, null);
}
},
handleGetCallWaitingOption: function handleGetCallWaitingOption(message) {
let requestId = message.requestId;
let request = this.takeRequest(requestId);
if (!request) {
return;
}
if (!message.success) {
Services.DOMRequest.fireError(request, message.errorMsg);
return;
}
Services.DOMRequest.fireSuccess(request, message.enabled);
},
handleSetCallWaitingOption: function handleSetCallWaitingOption(message) {
let requestId = message.requestId;
let request = this.takeRequest(requestId);
if (!request) {
return;
}
if (!message.success) {
Services.DOMRequest.fireError(request, message.errorMsg);
return;
}
Services.DOMRequest.fireSuccess(request, null);
},
handleGetCallingLineIdRestriction:
function handleGetCallingLineIdRestriction(message) {
let requestId = message.requestId;
let request = this.takeRequest(requestId);
if (!request) {
return;
}
if (!message.success) {
Services.DOMRequest.fireError(request, message.errorMsg);
if (message.errorMsg) {
this.fireRequestError(message.requestId, message.errorMsg);
return;
}
let status = new DOMCLIRStatus(message);
Services.DOMRequest.fireSuccess(request, status);
},
handleSetCallingLineIdRestriction:
function handleSetCallingLineIdRestriction(message) {
let requestId = message.requestId;
let request = this.takeRequest(requestId);
if (!request) {
return;
}
if (!message.success) {
Services.DOMRequest.fireError(request, message.errorMsg);
return;
}
Services.DOMRequest.fireSuccess(request, null);
this.fireRequestSuccess(message.requestId, status);
},
handleSendCancelMMI: function handleSendCancelMMI(message) {
@ -1932,22 +1834,6 @@ RILContentHelper.prototype = {
return gUUIDGenerator.generateUUID().toString();
},
handleSetRoamingPreference: function handleSetRoamingPreference(message) {
if (message.errorMsg) {
this.fireRequestError(message.requestId, message.errorMsg);
} else {
this.fireRequestSuccess(message.requestId, null);
}
},
handleGetRoamingPreference: function handleGetRoamingPreference(message) {
if (message.errorMsg) {
this.fireRequestError(message.requestId, message.errorMsg);
} else {
this.fireRequestSuccess(message.requestId, message.mode);
}
},
_deliverEvent: function _deliverEvent(listenerType, name, args) {
let thisListeners = this[listenerType];
if (!thisListeners) {

View File

@ -167,6 +167,9 @@ IsElementClickable(nsIFrame* aFrame, nsIAtom* stopAt = nullptr)
nsGkAtoms::button, eIgnoreCase)) {
return true;
}
if (content->IsEditable()) {
return true;
}
nsCOMPtr<nsIURI> linkURI;
if (content->IsLink(getter_AddRefs(linkURI))) {
return true;

View File

@ -43,6 +43,8 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=780847
<div class="target" id="t7" onmousedown="x=1" hidden></div>
<div class="target" id="t7_over" hidden></div>
<div id="t8" contenteditable="true" class="target" hidden></div>
</div>
<pre id="test">
<script type="application/javascript">
@ -191,6 +193,15 @@ function runTest() {
setShowing("t7", false);
setShowing("t7_over", false);
// Check that contenteditable elements are considered clickable for fluffing.
setShowing("t8", true);
var rect = document.getElementById("t8").getBoundingClientRect();
testMouseClick("t8", rect.left + 1, rect.top + 1, "t8", "content editable enabled for mouse input");
testMouseClick("t8", rect.left + 1, rect.top + 1, "t8", "content editable enabled for touch input", {
inputSource: SpecialPowers.Ci.nsIDOMMouseEvent.MOZ_SOURCE_TOUCH
});
setShowing("t8", false);
// Not yet tested:
// -- visited link weight
// -- "Closest" using Euclidean distance

View File

@ -0,0 +1,28 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
from marionette_test import MarionetteTestCase
from errors import NoSuchElementException
class TestImplicitWaits(MarionetteTestCase):
def testShouldImplicitlyWaitForASingleElement(self):
test_html = self.marionette.absolute_url("test_dynamic.html")
self.marionette.navigate(test_html)
add = self.marionette.find_element("id", "adder")
self.marionette.set_search_timeout("3000")
add.click()
# All is well if this doesnt throw
self.marionette.find_element("id", "box0")
def testShouldStillFailToFindAnElementWhenImplicitWaitsAreEnabled(self):
test_html = self.marionette.absolute_url("test_dynamic.html")
self.marionette.navigate(test_html)
self.marionette.set_search_timeout("3000")
try:
self.marionette.find_element("id", "box0")
self.fail("Should have thrown a a NoSuchElementException")
except NoSuchElementException:
pass
except Exception:
self.fail("Should have thrown a NoSuchElementException")

View File

@ -83,3 +83,5 @@ b2g = false
b2g = false
[test_window_type.py]
b2g = false
[test_implicit_waits.py]

View File

@ -0,0 +1,38 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<title></title>
<script type="text/javascript">
var next = 0;
function addMore() {
var box = document.createElement('DIV');
box.id = 'box' + next++;
box.className = 'redbox';
box.style.width = '150px';
box.style.height = '150px';
box.style.backgroundColor = 'red';
box.style.border = '1px solid black';
box.style.margin = '5px';
window.setTimeout(function() {
document.body.appendChild(box);
}, 1000);
}
function reveal() {
var elem = document.getElementById('revealed');
window.setTimeout(function() {
elem.style.display = '';
}, 1000);
}
</script>
</head>
<body>
<input id="adder" type="button" value="Add a box!" onclick="addMore()"/>
<input id="reveal" type="button" value="Reveal a new input" onclick="reveal();" />
<input id="revealed" style="display:none;" />
</body>
</html>

View File

@ -229,8 +229,8 @@ WinNativeApp.prototype = {
install: function() {
try {
this._copyPrebuiltFiles();
this._createConfigFiles();
this._createShortcutFiles();
this._createConfigFiles();
this._writeSystemKeys();
} catch (ex) {
this._removeInstallation(false);
@ -279,6 +279,29 @@ WinNativeApp.prototype = {
this.uninstallSubkeyStr = this.uniqueName;
// ${UninstallDir}/shortcuts_log.ini
this.shortcutLogsINI = this.uninstallDir.clone();
this.shortcutLogsINI.append("shortcuts_log.ini");
if (this.shortcutLogsINI.exists()) {
// If it's a reinstallation (or an update) get the shortcut names
// from the shortcut_log.ini file
let factory = Cc["@mozilla.org/xpcom/ini-processor-factory;1"]
.getService(Ci.nsIINIParserFactory);
let parser = factory.createINIParser(this.shortcutLogsINI);
this.shortcutName = parser.getString("STARTMENU", "Shortcut0");
} else {
let desktop = Services.dirsvc.get("Desk", Ci.nsIFile);
let startMenu = Services.dirsvc.get("Progs", Ci.nsIFile);
// Check in both directories to see if a shortcut with the same name
// already exists.
this.shortcutName = getAvailableFileName([ startMenu, desktop ],
this.appNameAsFilename,
".lnk");
}
// Remove previously installed app (for update purposes)
this._removeInstallation(true);
@ -306,11 +329,11 @@ WinNativeApp.prototype = {
uninstallKey.close();
}
let desktopShortcut = Services.dirsvc.get("Desk", Ci.nsILocalFile);
desktopShortcut.append(this.appNameAsFilename + ".lnk");
let desktopShortcut = Services.dirsvc.get("Desk", Ci.nsIFile);
desktopShortcut.append(this.shortcutName);
let startMenuShortcut = Services.dirsvc.get("Progs", Ci.nsILocalFile);
startMenuShortcut.append(this.appNameAsFilename + ".lnk");
let startMenuShortcut = Services.dirsvc.get("Progs", Ci.nsIFile);
startMenuShortcut.append(this.shortcutName);
let filesToRemove = [desktopShortcut, startMenuShortcut];
@ -382,13 +405,9 @@ WinNativeApp.prototype = {
writer.setString("WebappRT", "InstallDir", this.runtimeFolder.path);
writer.writeFile(null, Ci.nsIINIParserWriter.WRITE_UTF16);
// ${UninstallDir}/shortcuts_log.ini
let shortcutLogsINI = this.uninstallDir.clone().QueryInterface(Ci.nsILocalFile);
shortcutLogsINI.append("shortcuts_log.ini");
writer = factory.createINIParser(shortcutLogsINI).QueryInterface(Ci.nsIINIParserWriter);
writer.setString("STARTMENU", "Shortcut0", this.appNameAsFilename + ".lnk");
writer.setString("DESKTOP", "Shortcut0", this.appNameAsFilename + ".lnk");
writer = factory.createINIParser(this.shortcutLogsINI).QueryInterface(Ci.nsIINIParserWriter);
writer.setString("STARTMENU", "Shortcut0", this.shortcutName);
writer.setString("DESKTOP", "Shortcut0", this.shortcutName);
writer.setString("TASKBAR", "Migrated", "true");
writer.writeFile(null, Ci.nsIINIParserWriter.WRITE_UTF16);
@ -448,7 +467,7 @@ WinNativeApp.prototype = {
*/
_createShortcutFiles: function() {
let shortcut = this.installDir.clone().QueryInterface(Ci.nsILocalFileWin);
shortcut.append(this.appNameAsFilename + ".lnk");
shortcut.append(this.shortcutName);
let target = this.installDir.clone();
target.append(this.webapprt.leafName);
@ -462,8 +481,8 @@ WinNativeApp.prototype = {
let desktop = Services.dirsvc.get("Desk", Ci.nsILocalFile);
let startMenu = Services.dirsvc.get("Progs", Ci.nsILocalFile);
shortcut.copyTo(desktop, this.appNameAsFilename + ".lnk");
shortcut.copyTo(startMenu, this.appNameAsFilename + ".lnk");
shortcut.copyTo(desktop, this.shortcutName);
shortcut.copyTo(startMenu, this.shortcutName);
shortcut.followLinks = false;
shortcut.remove(false);
@ -660,13 +679,13 @@ MacNativeApp.prototype = {
_moveToApplicationsFolder: function() {
let appDir = Services.dirsvc.get("LocApp", Ci.nsILocalFile);
let destination = getAvailableFile(appDir,
this.appNameAsFilename,
".app");
if (!destination) {
let destinationName = getAvailableFileName([appDir],
this.appNameAsFilename,
".app");
if (!destinationName) {
return false;
}
this.installDir.moveTo(destination.parent, destination.leafName);
this.installDir.moveTo(appDir, destinationName);
},
/**
@ -967,38 +986,61 @@ function stripStringForFilename(aPossiblyBadFilenameString) {
/**
* Finds a unique name available in a folder (i.e., non-existent file)
*
* @param aFolder nsIFile that represents the directory where we want to write
* @param aFolderSet a set of nsIFile objects that represents the set of
* directories where we want to write
* @param aName string with the filename (minus the extension) desired
* @param aExtension string with the file extension, including the dot
* @return nsILocalFile or null if folder is unwritable or unique name
* @return file name or null if folder is unwritable or unique name
* was not available
*/
function getAvailableFile(aFolder, aName, aExtension) {
let folder = aFolder.QueryInterface(Ci.nsILocalFile);
folder.followLinks = false;
if (!folder.isDirectory() || !folder.isWritable()) {
return null;
}
function getAvailableFileName(aFolderSet, aName, aExtension) {
let fileSet = [];
let name = aName + aExtension;
let isUnique = true;
let file = folder.clone();
file.append(aName + aExtension);
if (!file.exists()) {
return file;
}
for (let i = 2; i < 10; i++) {
file.leafName = aName + " (" + i + ")" + aExtension;
if (!file.exists()) {
return file;
// Check if the plain name is a unique name in all the directories.
for (let folder of aFolderSet) {
folder.followLinks = false;
if (!folder.isDirectory() || !folder.isWritable()) {
return null;
}
let file = folder.clone();
file.append(name);
// Avoid exists() call if we already know this file name is not unique in
// one of the directories.
if (isUnique && file.exists()) {
isUnique = false;
}
fileSet.push(file);
}
for (let i = 10; i < 100; i++) {
file.leafName = aName + "-" + i + aExtension;
if (!file.exists()) {
return file;
if (isUnique) {
return name;
}
function checkUnique(aName) {
for (let file of fileSet) {
file.leafName = aName;
if (file.exists()) {
return false;
}
}
return true;
}
// If we're here, the plain name wasn't enough. Let's try modifying the name
// by adding "(" + num + ")".
for (let i = 2; i < 100; i++) {
name = aName + " (" + i + ")" + aExtension;
if (checkUnique(name)) {
return name;
}
}