Merge mozilla-central to mozilla-inbound

This commit is contained in:
Carsten "Tomcat" Book 2014-03-25 13:40:56 +01:00
commit ecd3864bc0
52 changed files with 573 additions and 477 deletions

View File

@ -19,7 +19,7 @@
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="3e7409120827303329b00a8c6f2f26ccc5a4f59b"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="80af23f8c74d9d2e9388d8ed3c204040b5c528ec"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="266bca6e60dad43e395f38b66edabe8bdc882334"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="d11f524d00cacf5ba0dfbf25e4aa2158b1c3a036"/>

View File

@ -17,7 +17,7 @@
</project>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="3e7409120827303329b00a8c6f2f26ccc5a4f59b"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="80af23f8c74d9d2e9388d8ed3c204040b5c528ec"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="266bca6e60dad43e395f38b66edabe8bdc882334"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="cb16958e41105d7c551d9941f522db97b8312538"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="485846b2a40d8ac7d6c1c5f8af6d15b0c10af19d"/>

View File

@ -15,7 +15,7 @@
<project name="platform_build" path="build" remote="b2g" revision="a9e08b91e9cd1f0930f16cfc49ec72f63575d5fe">
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="gaia" path="gaia" remote="mozillaorg" revision="3e7409120827303329b00a8c6f2f26ccc5a4f59b"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="80af23f8c74d9d2e9388d8ed3c204040b5c528ec"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="266bca6e60dad43e395f38b66edabe8bdc882334"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="cb16958e41105d7c551d9941f522db97b8312538"/>

View File

@ -19,7 +19,7 @@
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="3e7409120827303329b00a8c6f2f26ccc5a4f59b"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="80af23f8c74d9d2e9388d8ed3c204040b5c528ec"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="266bca6e60dad43e395f38b66edabe8bdc882334"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="d11f524d00cacf5ba0dfbf25e4aa2158b1c3a036"/>

View File

@ -4,6 +4,6 @@
"branch": "",
"revision": ""
},
"revision": "e7950c4d17aa917001d257fc9b0288c0c0ea3c21",
"revision": "215bb3fd100d59db61cb9a710d814380e5f054b1",
"repo_path": "/integration/gaia-central"
}

View File

@ -17,7 +17,7 @@
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="3e7409120827303329b00a8c6f2f26ccc5a4f59b"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="80af23f8c74d9d2e9388d8ed3c204040b5c528ec"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="266bca6e60dad43e395f38b66edabe8bdc882334"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="1f6a1fe07f81c5bc5e1d079c9b60f7f78ca2bf4f"/>

View File

@ -15,7 +15,7 @@
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="3e7409120827303329b00a8c6f2f26ccc5a4f59b"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="80af23f8c74d9d2e9388d8ed3c204040b5c528ec"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="266bca6e60dad43e395f38b66edabe8bdc882334"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="1f6a1fe07f81c5bc5e1d079c9b60f7f78ca2bf4f"/>

View File

@ -19,7 +19,7 @@
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="3e7409120827303329b00a8c6f2f26ccc5a4f59b"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="80af23f8c74d9d2e9388d8ed3c204040b5c528ec"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="266bca6e60dad43e395f38b66edabe8bdc882334"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="1f6a1fe07f81c5bc5e1d079c9b60f7f78ca2bf4f"/>

View File

@ -17,7 +17,7 @@
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="3e7409120827303329b00a8c6f2f26ccc5a4f59b"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="80af23f8c74d9d2e9388d8ed3c204040b5c528ec"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="266bca6e60dad43e395f38b66edabe8bdc882334"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="1f6a1fe07f81c5bc5e1d079c9b60f7f78ca2bf4f"/>

View File

@ -17,7 +17,7 @@
</project>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="3e7409120827303329b00a8c6f2f26ccc5a4f59b"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="80af23f8c74d9d2e9388d8ed3c204040b5c528ec"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="266bca6e60dad43e395f38b66edabe8bdc882334"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="cb16958e41105d7c551d9941f522db97b8312538"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="485846b2a40d8ac7d6c1c5f8af6d15b0c10af19d"/>

View File

@ -17,7 +17,7 @@
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="3e7409120827303329b00a8c6f2f26ccc5a4f59b"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="80af23f8c74d9d2e9388d8ed3c204040b5c528ec"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="266bca6e60dad43e395f38b66edabe8bdc882334"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="1f6a1fe07f81c5bc5e1d079c9b60f7f78ca2bf4f"/>

View File

@ -140,16 +140,19 @@ let RemoteTabViewer = {
list.removeItemAt(i);
}
let seenURLs = new Set();
let localURLs = engine.getOpenURLs();
for (let [guid, client] in Iterator(engine.getAllClients())) {
// Create the client node, but don't add it in-case we don't show any tabs
let appendClient = true;
let seenURLs = {};
client.tabs.forEach(function({title, urlHistory, icon}) {
let url = urlHistory[0];
if (engine.locallyOpenTabMatchesURL(url) || url in seenURLs)
if (!url || localURLs.has(url) || seenURLs.has(url)) {
return;
seenURLs[url] = null;
}
seenURLs.add(url);
if (appendClient) {
let attrs = {

View File

@ -161,6 +161,11 @@ function test() {
},
},
},
{
type: "text/html",
method: "GET",
template: "https://www.google.com/",
},
],
},
};

View File

@ -24,6 +24,7 @@ function test() {
yield changeManifestValueBad("name", "the worst app");
yield addNewManifestProperty("developer", "foo", "bar");
yield addNewManifestPropertyBad("developer", "blob", "bob");
yield removeManifestProperty("developer", "foo");
gManifestWindow = null;
gManifestEditor = null;
@ -162,3 +163,26 @@ function addNewManifestPropertyBad(parent, key, value) {
"Manifest contains key, but it should not");
});
}
function removeManifestProperty(parent, key) {
info("*** Remove property test ***");
return Task.spawn(function() {
let parentElem = gManifestWindow.document
.querySelector("[id ^= '" + parent + "']");
ok(parentElem, "Found parent element");
let keyExists = key in gManifestEditor.manifest[parent];
ok(keyExists,
"The manifest contains the key under the expected parent");
let newElem = gManifestWindow.document.querySelector("[id ^= '" + key + "']");
let removePropertyButton = newElem.querySelector(".variables-view-delete");
ok(removePropertyButton, "The remove property button was found");
removePropertyButton.click();
yield waitForUpdate();
ok(!(key in gManifestEditor.manifest[parent]), "Property was successfully removed");
});
}

View File

@ -273,6 +273,8 @@ let DebuggerView = {
DebuggerController.Breakpoints.destroy().then(() => {
window.emit(EVENTS.EDITOR_UNLOADED, this.editor);
this.editor.destroy();
this.editor = null;
aCallback();
});
},

View File

@ -5,6 +5,9 @@
function test()
{
// Test is slow on Linux EC2 instances - Bug 962931
requestLongerTimeout(2);
let {devtools} = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
let {Task} = Cu.import("resource://gre/modules/Task.jsm", {});
let Toolbox = devtools.Toolbox;

View File

@ -39,6 +39,11 @@ XPCOMUtils.defineLazyGetter(this, "CertUtils",
return mod;
});
#ifdef MOZ_CRASHREPORTER
XPCOMUtils.defineLazyServiceGetter(this, "gCrashReporter",
"@mozilla.org/xre/app-info;1",
"nsICrashReporter");
#endif
const FILE_CACHE = "experiments.json";
const OBSERVER_TOPIC = "experiments-changed";
@ -673,11 +678,6 @@ Experiments.Experiments.prototype = {
return this._pendingTasks.loadFromCache;
}
if (this._pendingTasks.updateManifest) {
// We're already updating the manifest, no need to load the cached version.
return this._pendingTasks.updateManifest;
}
let path = this._cacheFilePath;
this._pendingTasks.loadFromCache = Task.spawn(function () {
try {
@ -888,6 +888,7 @@ Experiments.Experiments.prototype = {
yield activeExperiment.stop();
yield activeExperiment.start();
} catch (e) {
gLogger.error(e);
// On failure try the next experiment.
activeExperiment = null;
}
@ -921,6 +922,7 @@ Experiments.Experiments.prototype = {
try {
yield experiment.start();
activeChanged = true;
activeExperiment = experiment;
break;
} catch (e) {
// On failure try the next experiment.
@ -933,6 +935,12 @@ Experiments.Experiments.prototype = {
Services.obs.notifyObservers(null, OBSERVER_TOPIC, null);
}
#ifdef MOZ_CRASHREPORTER
if (activeExperiment) {
gCrashReporter.annotateCrashReport("ActiveExperiment", activeExperiment.id);
}
#endif
throw new Task.Result(activeChanged);
}.bind(this));
},

View File

@ -9,7 +9,7 @@ EXTRA_COMPONENTS += [
JS_MODULES_PATH = 'modules/experiments'
EXTRA_JS_MODULES += [
EXTRA_PP_JS_MODULES += [
'Experiments.jsm',
]

View File

@ -35,7 +35,7 @@ passwordsCount.label = #1 password;#1 passwords
# LOCALIZATION NOTE (addonsCount.label): Semicolon-separated list of plural forms.
# See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
# #1 is the number of add-ons, see the link above for forms
addonsCount.label = #1 addon;#1 addons
addonsCount.label = #1 add-on;#1 add-ons
save.recoverykey.title = Save Recovery Key
save.recoverykey.defaultfilename = Firefox Recovery Key.html

View File

@ -29,5 +29,5 @@
<MozParam name="channel" condition="purpose" purpose="homepage" value="np"/>
<MozParam name="source" condition="purpose" purpose="homepage" value="hp"/>
</Url>
<SearchForm>https://www.google.com/</SearchForm>
<Url type="text/html" method="GET" template="https://www.google.com/" rel="searchform"/>
</SearchPlugin>

View File

@ -68,6 +68,7 @@ RemoteTabsView.prototype = Util.extend(Object.create(View.prototype), {
let tabsEngine = Weave.Service.engineManager.get("tabs");
let list = this._set;
let seenURLs = new Set();
let localURLs = tabsEngine.getOpenURLs();
// Clear grid, We don't know what has happened to tabs since last sync
// Also can result in duplicate tabs(bug 864614)
@ -76,7 +77,7 @@ RemoteTabsView.prototype = Util.extend(Object.create(View.prototype), {
for (let [guid, client] in Iterator(tabsEngine.getAllClients())) {
client.tabs.forEach(function({title, urlHistory, icon}) {
let url = urlHistory[0];
if (tabsEngine.locallyOpenTabMatchesURL(url) || seenURLs.has(url)) {
if (!url || getOpenURLs.has(url) || seenURLs.has(url)) {
return;
}
seenURLs.add(url);

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

View File

@ -178,6 +178,10 @@
z-index: 1;
}
#main-window[tabsintitlebar]:not([inFullscreen]) .tab-close-button:not(:-moz-any(:hover,:-moz-lwtheme,[selected="true"])) {
-moz-image-region: rect(0, 64px, 16px, 48px);
}
#main-window[tabsintitlebar][sizemode="normal"] #titlebar-content:-moz-lwtheme {
/* Render a window top border: */
background-image: linear-gradient(to bottom,
@ -888,6 +892,7 @@ toolbarbutton[sdk-button="true"][cui-areatype="toolbar"] > .toolbarbutton-icon {
box-shadow: none !important;
}
#back-button:not([disabled="true"]):hover:active > .toolbarbutton-icon,
#back-button[open="true"] > .toolbarbutton-icon {
background-color: hsla(210,4%,10%,.12) !important;
box-shadow: 0 1px 0 0 hsla(210,80%,20%,.1) inset !important;

View File

@ -1164,36 +1164,37 @@ nsDOMCameraControl::OnError(CameraControlListener::CameraErrorContext aContext,
NS_LossyConvertUTF16toASCII(aError).get());
MOZ_ASSERT(NS_IsMainThread());
nsRefPtr<CameraErrorCallback>* errorCb;
nsRefPtr<CameraErrorCallback> errorCb;
switch (aContext) {
case CameraControlListener::kInStartCamera:
mGetCameraOnSuccessCb = nullptr;
errorCb = &mGetCameraOnErrorCb;
errorCb = mGetCameraOnErrorCb.forget();
break;
case CameraControlListener::kInStopCamera:
mReleaseOnSuccessCb = nullptr;
errorCb = &mReleaseOnErrorCb;
errorCb = mReleaseOnErrorCb.forget();
break;
case CameraControlListener::kInSetConfiguration:
mSetConfigurationOnSuccessCb = nullptr;
errorCb = &mSetConfigurationOnErrorCb;
errorCb = mSetConfigurationOnErrorCb.forget();
break;
case CameraControlListener::kInAutoFocus:
mAutoFocusOnSuccessCb = nullptr;
errorCb = &mAutoFocusOnErrorCb;
errorCb = mAutoFocusOnErrorCb.forget();
break;
case CameraControlListener::kInTakePicture:
mTakePictureOnSuccessCb = nullptr;
errorCb = &mTakePictureOnErrorCb;
errorCb = mTakePictureOnErrorCb.forget();
break;
case CameraControlListener::kInStartRecording:
mStartRecordingOnSuccessCb = nullptr;
errorCb = &mStartRecordingOnErrorCb;
errorCb = mStartRecordingOnErrorCb.forget();
break;
case CameraControlListener::kInStopRecording:
@ -1231,17 +1232,13 @@ nsDOMCameraControl::OnError(CameraControlListener::CameraErrorContext aContext,
return;
}
MOZ_ASSERT(errorCb);
if (!*errorCb) {
if (!errorCb) {
DOM_CAMERA_LOGW("DOM No error handler for error '%s' in context=%d\n",
NS_LossyConvertUTF16toASCII(aError).get(), aContext);
return;
}
// kung-fu death grip
nsRefPtr<CameraErrorCallback> cb = (*errorCb).forget();
ErrorResult ignored;
cb->Call(aError, ignored);
errorCb->Call(aError, ignored);
}

View File

@ -245,17 +245,7 @@ MozInputMethodManager.prototype = {
classID: Components.ID("{7e9d7280-ef86-11e2-b778-0800200c9a66}"),
QueryInterface: XPCOMUtils.generateQI([
Ci.nsIInputMethodManager
]),
classInfo: XPCOMUtils.generateCI({
"classID": Components.ID("{7e9d7280-ef86-11e2-b778-0800200c9a66}"),
"contractID": "@mozilla.org/b2g-imm;1",
"interfaces": [Ci.nsIInputMethodManager],
"flags": Ci.nsIClassInfo.DOM_OBJECT,
"classDescription": "B2G Input Method Manager"
}),
QueryInterface: XPCOMUtils.generateQI([]),
showAll: function() {
if (!WindowMap.isActive(this._window)) {
@ -301,19 +291,10 @@ MozInputMethod.prototype = {
classID: Components.ID("{4607330d-e7d2-40a4-9eb8-43967eae0142}"),
QueryInterface: XPCOMUtils.generateQI([
Ci.nsIInputMethod,
Ci.nsIDOMGlobalPropertyInitializer,
Ci.nsIObserver
]),
classInfo: XPCOMUtils.generateCI({
"classID": Components.ID("{4607330d-e7d2-40a4-9eb8-43967eae0142}"),
"contractID": "@mozilla.org/b2g-inputmethod;1",
"interfaces": [Ci.nsIInputMethod],
"flags": Ci.nsIClassInfo.DOM_OBJECT,
"classDescription": "B2G Input Method"
}),
init: function mozInputMethodInit(win) {
this._window = win;
this._mgmt = new MozInputMethodManager(win);
@ -473,19 +454,10 @@ MozInputContext.prototype = {
classID: Components.ID("{1e38633d-d08b-4867-9944-afa5c648adb6}"),
QueryInterface: XPCOMUtils.generateQI([
Ci.nsIB2GInputContext,
Ci.nsIObserver,
Ci.nsISupportsWeakReference
]),
classInfo: XPCOMUtils.generateCI({
"classID": Components.ID("{1e38633d-d08b-4867-9944-afa5c648adb6}"),
"contractID": "@mozilla.org/b2g-inputcontext;1",
"interfaces": [Ci.nsIB2GInputContext],
"flags": Ci.nsIClassInfo.DOM_OBJECT,
"classDescription": "B2G Input Context"
}),
init: function ic_init(win) {
this._window = win;
this._utils = win.QueryInterface(Ci.nsIInterfaceRequestor)

View File

@ -45,6 +45,7 @@ import org.mozilla.gecko.home.SearchEngine;
import org.mozilla.gecko.menu.GeckoMenu;
import org.mozilla.gecko.preferences.GeckoPreferences;
import org.mozilla.gecko.prompts.Prompt;
import org.mozilla.gecko.prompts.PromptListItem;
import org.mozilla.gecko.sync.setup.SyncAccounts;
import org.mozilla.gecko.toolbar.AutocompleteHandler;
import org.mozilla.gecko.toolbar.BrowserToolbar;
@ -225,15 +226,10 @@ abstract public class BrowserApp extends GeckoApp
final TabsPanel.Panel panel = tab.isPrivate()
? TabsPanel.Panel.PRIVATE_TABS
: TabsPanel.Panel.NORMAL_TABS;
// Delay calling showTabs so that it does not modify the mTabsChangedListeners
// array while we are still iterating through the array.
ThreadUtils.postToUiThread(new Runnable() {
@Override
public void run() {
if (areTabsShown() && mTabsPanel.getCurrentPanel() != panel)
showTabs(panel);
}
});
if (areTabsShown() && mTabsPanel.getCurrentPanel() != panel) {
showTabs(panel);
}
}
break;
case START:
@ -641,6 +637,42 @@ abstract public class BrowserApp extends GeckoApp
registerEventListener("Prompt:ShowTop");
}
private void showBookmarkDialog() {
final Tab tab = Tabs.getInstance().getSelectedTab();
final Prompt ps = new Prompt(this, new Prompt.PromptCallback() {
@Override
public void onPromptFinished(String result) {
int itemId = -1;
try {
itemId = new JSONObject(result).getInt("button");
} catch(JSONException ex) {
Log.e(LOGTAG, "Exception reading bookmark prompt result", ex);
}
if (tab == null)
return;
if (itemId == 0) {
new EditBookmarkDialog(BrowserApp.this).show(tab.getURL());
} else if (itemId == 1) {
String url = tab.getURL();
String title = tab.getDisplayTitle();
Bitmap favicon = tab.getFavicon();
if (url != null && title != null) {
GeckoAppShell.createShortcut(title, url, url, favicon, "");
}
}
}
});
final PromptListItem[] items = new PromptListItem[2];
Resources res = getResources();
items[0] = new PromptListItem(res.getString(R.string.contextmenu_edit_bookmark));
items[1] = new PromptListItem(res.getString(R.string.contextmenu_add_to_launcher));
ps.show("", "", items, ListView.CHOICE_MODE_NONE);
}
private void setDynamicToolbarEnabled(boolean enabled) {
ThreadUtils.assertOnUiThread();
@ -705,7 +737,16 @@ abstract public class BrowserApp extends GeckoApp
}
if (itemId == R.id.subscribe) {
subscribeToFeeds(Tabs.getInstance().getSelectedTab());
Tab tab = Tabs.getInstance().getSelectedTab();
if (tab != null && tab.hasFeeds()) {
JSONObject args = new JSONObject();
try {
args.put("tabId", tab.getId());
} catch (JSONException e) {
Log.e(LOGTAG, "error building json arguments");
}
GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Feeds:Subscribe", args.toString()));
}
return true;
}
@ -735,6 +776,27 @@ abstract public class BrowserApp extends GeckoApp
return true;
}
if (itemId == R.id.add_to_launcher) {
Tab tab = Tabs.getInstance().getSelectedTab();
if (tab == null) {
return true;
}
final String url = tab.getURL();
final String title = tab.getDisplayTitle();
if (url == null || title == null) {
return true;
}
final OnFaviconLoadedListener listener = new GeckoAppShell.CreateShortcutFaviconLoadedListener(url, title);
Favicons.getSizedFavicon(url,
tab.getFaviconURL(),
Integer.MAX_VALUE,
LoadFaviconTask.FLAG_PERSIST,
listener);
return true;
}
return false;
}
@ -2089,9 +2151,6 @@ abstract public class BrowserApp extends GeckoApp
MenuItem desktopMode = aMenu.findItem(R.id.desktop_mode);
MenuItem enterGuestMode = aMenu.findItem(R.id.new_guest_session);
MenuItem exitGuestMode = aMenu.findItem(R.id.exit_guest_session);
MenuItem subscribe = aMenu.findItem(R.id.save_subscribe);
MenuItem addToReadingList = aMenu.findItem(R.id.reading_list_add);
MenuItem save = aMenu.findItem(R.id.save);
// Only show the "Quit" menu item on pre-ICS or television devices.
// In ICS+, it's easy to kill an app through the task switcher.
@ -2116,15 +2175,11 @@ abstract public class BrowserApp extends GeckoApp
return true;
}
save.setVisible(!GeckoProfile.get(this).inGuestMode());
if (tab.isBookmark() || tab.isReadingListItem()) {
save.setIcon(R.drawable.ic_menu_bookmark_remove);
} else {
save.setIcon(R.drawable.ic_menu_bookmark_add);
}
bookmark.setEnabled(!AboutPages.isAboutReader(tab.getURL()));
bookmark.setVisible(!GeckoProfile.get(this).inGuestMode());
bookmark.setCheckable(true);
bookmark.setChecked(tab.isBookmark());
bookmark.setIcon(tab.isBookmark() ? R.drawable.ic_menu_bookmark_remove : R.drawable.ic_menu_bookmark_add);
back.setEnabled(tab.canDoBack());
forward.setEnabled(tab.canDoForward());
@ -2214,22 +2269,18 @@ abstract public class BrowserApp extends GeckoApp
else
enterGuestMode.setVisible(true);
addToReadingList.setChecked(tab.isReadingListItem());
addToReadingList.setEnabled(tab.getReaderEnabled());
subscribe.setEnabled(tab.hasFeeds());
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
final Tab tab = Tabs.getInstance().getSelectedTab();
Tab tab = null;
Intent intent = null;
final int itemId = item.getItemId();
if (itemId == R.id.bookmark) {
tab = Tabs.getInstance().getSelectedTab();
if (tab != null) {
if (item.isChecked()) {
tab.removeBookmark();
@ -2239,12 +2290,12 @@ abstract public class BrowserApp extends GeckoApp
tab.addBookmark();
getButtonToast().show(false,
getResources().getString(R.string.bookmark_added),
getResources().getString(R.string.contextmenu_edit_bookmark),
getResources().getString(R.string.bookmark_options),
null,
new ButtonToast.ToastListener() {
@Override
public void onButtonClicked() {
new EditBookmarkDialog(BrowserApp.this).show(tab.getURL());
showBookmarkDialog();
}
@Override
@ -2262,18 +2313,21 @@ abstract public class BrowserApp extends GeckoApp
}
if (itemId == R.id.reload) {
tab = Tabs.getInstance().getSelectedTab();
if (tab != null)
tab.doReload();
return true;
}
if (itemId == R.id.back) {
tab = Tabs.getInstance().getSelectedTab();
if (tab != null)
tab.doBack();
return true;
}
if (itemId == R.id.forward) {
tab = Tabs.getInstance().getSelectedTab();
if (tab != null)
tab.doForward();
return true;
@ -2316,12 +2370,13 @@ abstract public class BrowserApp extends GeckoApp
}
if (itemId == R.id.desktop_mode) {
if (tab == null)
Tab selectedTab = Tabs.getInstance().getSelectedTab();
if (selectedTab == null)
return true;
JSONObject args = new JSONObject();
try {
args.put("desktopMode", !item.isChecked());
args.put("tabId", tab.getId());
args.put("tabId", selectedTab.getId());
} catch (JSONException e) {
Log.e(LOGTAG, "error building json arguments");
}
@ -2356,25 +2411,6 @@ abstract public class BrowserApp extends GeckoApp
return true;
}
if (itemId == R.id.launcher_add) {
addToLauncher(tab.getURL(), tab.getTitle(), tab.getFaviconURL());
return true;
}
if (itemId == R.id.reading_list_add) {
if (item.isChecked()) {
ReaderModeUtils.removeFromReadingList(tab.getURL());
} else {
ReaderModeUtils.addToReadingList(tab);
}
return true;
}
if (itemId == R.id.subscribe || itemId == R.id.save_subscribe) {
subscribeToFeeds(tab);
return true;
}
return super.onOptionsItemSelected(item);
}
@ -2419,33 +2455,6 @@ abstract public class BrowserApp extends GeckoApp
ps.show(res.getString(titleString), res.getString(msgString), null, ListView.CHOICE_MODE_NONE);
}
public void subscribeToFeeds(Tab tab) {
if (!tab.hasFeeds()) {
return;
}
JSONObject args = new JSONObject();
try {
args.put("tabId", tab.getId());
} catch (JSONException e) {
Log.e(LOGTAG, "JSON error", e);
}
GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Feeds:Subscribe", args.toString()));
}
private void addToLauncher(String url, String title, String faviconUrl) {
if (url == null || title == null) {
return;
}
final OnFaviconLoadedListener listener = new GeckoAppShell.CreateShortcutFaviconLoadedListener(url, title);
Favicons.getSizedFavicon(url,
faviconUrl,
Integer.MAX_VALUE,
LoadFaviconTask.FLAG_PERSIST,
listener);
}
/**
* This will detect if the key pressed is back. If so, will show the history.
*/

View File

@ -244,14 +244,9 @@ class FilePickerResultHandler implements ActivityResultHandler {
}
});
// We're already on the UIThread, but we have to post this back to the uithread to avoid
// modifying the listener array while its being iterated through.
ThreadUtils.postToUiThread(new Runnable() {
@Override
public void run() {
Tabs.unregisterOnTabsChangedListener(FileLoaderCallbacks.this);
}
});
// Tabs' listener array is safe to modify during use: its
// iteration pattern is based on snapshots.
Tabs.unregisterOnTabsChangedListener(this);
}
}
}

View File

@ -20,15 +20,16 @@ import java.net.Proxy;
import java.net.URL;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Queue;
import java.util.StringTokenizer;
import java.util.TreeMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import org.mozilla.gecko.favicons.OnFaviconLoadedListener;
import org.mozilla.gecko.favicons.decoders.FaviconDecoder;
@ -114,48 +115,28 @@ import android.widget.Toast;
public class GeckoAppShell
{
private static final String LOGTAG = "GeckoAppShell";
private static final boolean LOGGING = false;
// static members only
// We have static members only.
private GeckoAppShell() { }
static private LinkedList<GeckoEvent> gPendingEvents =
new LinkedList<GeckoEvent>();
private static boolean restartScheduled = false;
private static GeckoEditableListener editableListener = null;
static private boolean gRestartScheduled = false;
private static final Queue<GeckoEvent> PENDING_EVENTS = new ConcurrentLinkedQueue<GeckoEvent>();
private static final Map<String, String> ALERT_COOKIES = new ConcurrentHashMap<String, String>();
static private GeckoEditableListener mEditableListener = null;
private static volatile boolean locationHighAccuracyEnabled;
static private final HashMap<String, String>
mAlertCookies = new HashMap<String, String>();
// Accessed by NotificationHelper. This should be encapsulated.
/* package */ static NotificationClient notificationClient;
// See also HardwareUtils.LOW_MEMORY_THRESHOLD_MB.
static private final int HIGH_MEMORY_DEVICE_THRESHOLD_MB = 768;
/* Keep in sync with constants found here:
http://mxr.mozilla.org/mozilla-central/source/uriloader/base/nsIWebProgressListener.idl
*/
static public final int WPL_STATE_START = 0x00000001;
static public final int WPL_STATE_STOP = 0x00000010;
static public final int WPL_STATE_IS_DOCUMENT = 0x00020000;
static public final int WPL_STATE_IS_NETWORK = 0x00040000;
/* Keep in sync with constants found here:
http://mxr.mozilla.org/mozilla-central/source/netwerk/base/public/nsINetworkLinkService.idl
*/
static public final int LINK_TYPE_UNKNOWN = 0;
static public final int LINK_TYPE_ETHERNET = 1;
static public final int LINK_TYPE_USB = 2;
static public final int LINK_TYPE_WIFI = 3;
static public final int LINK_TYPE_WIMAX = 4;
static public final int LINK_TYPE_2G = 5;
static public final int LINK_TYPE_3G = 6;
static public final int LINK_TYPE_4G = 7;
private static final int HIGH_MEMORY_DEVICE_THRESHOLD_MB = 768;
public static final String SHORTCUT_TYPE_WEBAPP = "webapp";
public static final String SHORTCUT_TYPE_BOOKMARK = "bookmark";
static private final boolean LOGGING = false;
static private int sDensityDpi = 0;
static private int sScreenDepth = 0;
@ -182,9 +163,26 @@ public class GeckoAppShell
private static Sensor gProximitySensor = null;
private static Sensor gLightSensor = null;
private static volatile boolean mLocationHighAccuracy;
/*
* Keep in sync with constants found here:
* http://mxr.mozilla.org/mozilla-central/source/uriloader/base/nsIWebProgressListener.idl
*/
static public final int WPL_STATE_START = 0x00000001;
static public final int WPL_STATE_STOP = 0x00000010;
static public final int WPL_STATE_IS_DOCUMENT = 0x00020000;
static public final int WPL_STATE_IS_NETWORK = 0x00040000;
static NotificationClient sNotificationClient;
/* Keep in sync with constants found here:
http://mxr.mozilla.org/mozilla-central/source/netwerk/base/public/nsINetworkLinkService.idl
*/
static public final int LINK_TYPE_UNKNOWN = 0;
static public final int LINK_TYPE_ETHERNET = 1;
static public final int LINK_TYPE_USB = 2;
static public final int LINK_TYPE_WIFI = 3;
static public final int LINK_TYPE_WIMAX = 4;
static public final int LINK_TYPE_2G = 5;
static public final int LINK_TYPE_3G = 6;
static public final int LINK_TYPE_4G = 7;
/* The Android-side API: API methods that Android calls */
@ -354,27 +352,48 @@ public class GeckoAppShell
private static void geckoLoaded() {
GeckoEditable editable = new GeckoEditable();
// install the gecko => editable listener
mEditableListener = editable;
editableListener = editable;
}
static void sendPendingEventsToGecko() {
try {
while (!gPendingEvents.isEmpty()) {
GeckoEvent e = gPendingEvents.removeFirst();
while (!PENDING_EVENTS.isEmpty()) {
final GeckoEvent e = PENDING_EVENTS.poll();
notifyGeckoOfEvent(e);
}
} catch (NoSuchElementException e) {}
}
/**
* If the Gecko thread is running, immediately dispatches the event to
* Gecko.
*
* If the Gecko thread is not running, queues the event. If the queue is
* full, throws {@link IllegalStateException}.
*
* Queued events will be dispatched in order of arrival when the Gecko
* thread becomes live.
*
* This method can be called from any thread.
*
* @param e
* the event to dispatch. Cannot be null.
*/
@RobocopTarget
public static void sendEventToGecko(GeckoEvent e) {
if (e == null) {
throw new IllegalArgumentException("e cannot be null.");
}
if (GeckoThread.checkLaunchState(GeckoThread.LaunchState.GeckoRunning)) {
notifyGeckoOfEvent(e);
// Gecko will copy the event data into a normal C++ object. We can recycle the evet now.
// Gecko will copy the event data into a normal C++ object. We can recycle the event now.
e.recycle();
} else {
gPendingEvents.addLast(e);
return;
}
// Throws if unable to add the event due to capacity restrictions.
PENDING_EVENTS.add(e);
}
// Tell the Gecko event loop that an event is available.
@ -427,16 +446,16 @@ public class GeckoAppShell
@WrapElementForJNI(generateStatic = true)
public static void notifyIME(int type) {
if (mEditableListener != null) {
mEditableListener.notifyIME(type);
if (editableListener != null) {
editableListener.notifyIME(type);
}
}
@WrapElementForJNI(generateStatic = true)
public static void notifyIMEContext(int state, String typeHint,
String modeHint, String actionHint) {
if (mEditableListener != null) {
mEditableListener.notifyIMEContext(state, typeHint,
if (editableListener != null) {
editableListener.notifyIMEContext(state, typeHint,
modeHint, actionHint);
}
}
@ -444,9 +463,9 @@ public class GeckoAppShell
@WrapElementForJNI(generateStatic = true)
public static void notifyIMEChange(String text, int start, int end, int newEnd) {
if (newEnd < 0) { // Selection change
mEditableListener.onSelectionChange(start, end);
editableListener.onSelectionChange(start, end);
} else { // Text change
mEditableListener.onTextChange(text, start, end, newEnd);
editableListener.onTextChange(text, start, end, newEnd);
}
}
@ -544,7 +563,7 @@ public class GeckoAppShell
criteria.setSpeedRequired(false);
criteria.setBearingRequired(false);
criteria.setAltitudeRequired(false);
if (mLocationHighAccuracy) {
if (locationHighAccuracyEnabled) {
criteria.setAccuracy(Criteria.ACCURACY_FINE);
criteria.setCostAllowed(true);
criteria.setPowerRequirement(Criteria.POWER_HIGH);
@ -582,7 +601,7 @@ public class GeckoAppShell
@WrapElementForJNI
public static void enableLocationHighAccuracy(final boolean enable) {
mLocationHighAccuracy = enable;
locationHighAccuracyEnabled = enable;
}
@WrapElementForJNI
@ -700,7 +719,7 @@ public class GeckoAppShell
// The launch state can only be Launched or GeckoRunning at this point
GeckoThread.setLaunchState(GeckoThread.LaunchState.GeckoExiting);
if (getGeckoInterface() != null) {
if (gRestartScheduled) {
if (restartScheduled) {
getGeckoInterface().doRestart();
} else {
getGeckoInterface().getActivity().finish();
@ -718,7 +737,7 @@ public class GeckoAppShell
@WrapElementForJNI
static void scheduleRestart() {
gRestartScheduled = true;
restartScheduled = true;
}
public static Intent getWebappIntent(String aURI, String aOrigin, String aTitle, Bitmap aIcon) {
@ -1316,9 +1335,12 @@ public class GeckoAppShell
return intent;
}
/**
* Only called from GeckoApp.
*/
public static void setNotificationClient(NotificationClient client) {
if (sNotificationClient == null) {
sNotificationClient = client;
if (notificationClient == null) {
notificationClient = client;
} else {
Log.d(LOGTAG, "Notification client already set");
}
@ -1345,30 +1367,30 @@ public class GeckoAppShell
PendingIntent contentIntent = PendingIntent.getActivity(
getContext(), 0, notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT);
mAlertCookies.put(aAlertName, aAlertCookie);
ALERT_COOKIES.put(aAlertName, aAlertCookie);
callObserver(aAlertName, "alertshow", aAlertCookie);
sNotificationClient.add(notificationID, aImageUrl, aAlertTitle, aAlertText, contentIntent);
notificationClient.add(notificationID, aImageUrl, aAlertTitle, aAlertText, contentIntent);
}
@WrapElementForJNI
public static void alertsProgressListener_OnProgress(String aAlertName, long aProgress, long aProgressMax, String aAlertText) {
int notificationID = aAlertName.hashCode();
sNotificationClient.update(notificationID, aProgress, aProgressMax, aAlertText);
notificationClient.update(notificationID, aProgress, aProgressMax, aAlertText);
}
@WrapElementForJNI
public static void closeNotification(String aAlertName) {
String alertCookie = mAlertCookies.get(aAlertName);
String alertCookie = ALERT_COOKIES.get(aAlertName);
if (alertCookie != null) {
callObserver(aAlertName, "alertfinished", alertCookie);
mAlertCookies.remove(aAlertName);
ALERT_COOKIES.remove(aAlertName);
}
removeObserver(aAlertName);
int notificationID = aAlertName.hashCode();
sNotificationClient.remove(notificationID);
notificationClient.remove(notificationID);
}
public static void handleNotification(String aAction, String aAlertName, String aAlertCookie) {
@ -1377,7 +1399,7 @@ public class GeckoAppShell
if (GeckoApp.ACTION_ALERT_CALLBACK.equals(aAction)) {
callObserver(aAlertName, "alertclickcallback", aAlertCookie);
if (sNotificationClient.isOngoing(notificationID)) {
if (notificationClient.isOngoing(notificationID)) {
// When clicked, keep the notification if it displays progress
return;
}

View File

@ -288,7 +288,7 @@ public final class NotificationHelper implements GeckoEventListener {
PendingIntent deletePendingIntent = buildNotificationPendingIntent(message, CLEARED_EVENT);
builder.setDeleteIntent(deletePendingIntent);
GeckoAppShell.sNotificationClient.add(id.hashCode(), builder.build());
GeckoAppShell.notificationClient.add(id.hashCode(), builder.build());
boolean persistent = message.optBoolean(PERSISTENT_ATTR);
// We add only not persistent notifications to the list since we want to purge only
@ -325,7 +325,7 @@ public final class NotificationHelper implements GeckoEventListener {
}
private void closeNotification(String id) {
GeckoAppShell.sNotificationClient.remove(id.hashCode());
GeckoAppShell.notificationClient.remove(id.hashCode());
sendNotificationWasClosed(id);
}

View File

@ -4,12 +4,9 @@
package org.mozilla.gecko;
import org.json.JSONException;
import org.json.JSONObject;
import org.mozilla.gecko.util.StringUtils;
import android.net.Uri;
import android.util.Log;
public class ReaderModeUtils {
private static final String LOGTAG = "ReaderModeUtils";
@ -48,30 +45,4 @@ public class ReaderModeUtils {
return aboutReaderUrl;
}
public static void addToReadingList(Tab tab) {
if (!tab.getReaderEnabled()) {
return;
}
JSONObject json = new JSONObject();
try {
json.put("tabID", String.valueOf(tab.getId()));
} catch (JSONException e) {
Log.e(LOGTAG, "JSON error - failing to add to reading list", e);
return;
}
GeckoEvent e = GeckoEvent.createBroadcastEvent("Reader:Add", json.toString());
GeckoAppShell.sendEventToGecko(e);
}
public static void removeFromReadingList(String url) {
if (url == null) {
return;
}
GeckoEvent e = GeckoEvent.createBroadcastEvent("Reader:Remove", url);
GeckoAppShell.sendEventToGecko(e);
}
}

View File

@ -457,6 +457,22 @@ public class Tab {
});
}
public void addToReadingList() {
if (!mReaderEnabled)
return;
JSONObject json = new JSONObject();
try {
json.put("tabID", String.valueOf(getId()));
} catch (JSONException e) {
Log.e(LOGTAG, "JSON error - failing to add to reading list", e);
return;
}
GeckoEvent e = GeckoEvent.createBroadcastEvent("Reader:Add", json.toString());
GeckoAppShell.sendEventToGecko(e);
}
public void toggleReaderMode() {
if (AboutPages.isAboutReader(mUrl)) {
Tabs.getInstance().loadUrl(ReaderModeUtils.getUrlFromAboutReader(mUrl));

View File

@ -5,8 +5,6 @@
package org.mozilla.gecko;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
@ -528,15 +526,14 @@ public class Tabs implements GeckoEventListener {
public void onTabChanged(Tab tab, TabEvents msg, Object data);
}
private static List<OnTabsChangedListener> mTabsChangedListeners =
Collections.synchronizedList(new ArrayList<OnTabsChangedListener>());
private static final List<OnTabsChangedListener> TABS_CHANGED_LISTENERS = new CopyOnWriteArrayList<OnTabsChangedListener>();
public static void registerOnTabsChangedListener(OnTabsChangedListener listener) {
mTabsChangedListeners.add(listener);
TABS_CHANGED_LISTENERS.add(listener);
}
public static void unregisterOnTabsChangedListener(OnTabsChangedListener listener) {
mTabsChangedListeners.remove(listener);
TABS_CHANGED_LISTENERS.remove(listener);
}
public enum TabEvents {
@ -578,15 +575,13 @@ public class Tabs implements GeckoEventListener {
public void run() {
onTabChanged(tab, msg, data);
synchronized (mTabsChangedListeners) {
if (mTabsChangedListeners.isEmpty()) {
return;
}
if (TABS_CHANGED_LISTENERS.isEmpty()) {
return;
}
Iterator<OnTabsChangedListener> items = mTabsChangedListeners.iterator();
while (items.hasNext()) {
items.next().onTabChanged(tab, msg, data);
}
Iterator<OnTabsChangedListener> items = TABS_CHANGED_LISTENERS.iterator();
while (items.hasNext()) {
items.next().onTabChanged(tab, msg, data);
}
}
});

View File

@ -93,9 +93,15 @@ public class FaviconDecoder {
result.length = length;
result.isICO = false;
Bitmap decodedImage = BitmapUtils.decodeByteArray(buffer, offset, length);
if (decodedImage == null) {
// What we got wasn't decodable after all. Probably corrupted image, or we got a muffled OOM.
return null;
}
// We assume here that decodeByteArray doesn't hold on to the entire supplied
// buffer -- worst case, each of our buffers will be twice the necessary size.
result.bitmapsDecoded = new SingleBitmapIterator(BitmapUtils.decodeByteArray(buffer, offset, length));
result.bitmapsDecoded = new SingleBitmapIterator(decodedImage);
result.faviconBytes = buffer;
return result;

View File

@ -102,14 +102,9 @@ public class TwoLinePageRow extends LinearLayout
@Override
protected void onDetachedFromWindow() {
// Delay removing the listener to avoid modifying mTabsChangedListeners
// while notifyListeners is iterating through the array.
ThreadUtils.postToUiThread(new Runnable() {
@Override
public void run() {
Tabs.unregisterOnTabsChangedListener(TwoLinePageRow.this);
}
});
// Tabs' listener array is safe to modify during use: its
// iteration pattern is based on snapshots.
Tabs.unregisterOnTabsChangedListener(this);
}
@Override

View File

@ -54,7 +54,6 @@
<!ENTITY back "Back">
<!ENTITY stop "Stop">
<!ENTITY site_security "Site Security">
<!ENTITY save "Save">
<!ENTITY close_tab "Close Tab">
<!ENTITY one_tab "1 tab">
@ -290,7 +289,6 @@ size. -->
<!ENTITY site_settings_clear "Clear">
<!ENTITY site_settings_no_settings "There are no settings to clear.">
<!ENTITY reading_list_add "Add to Reading List">
<!ENTITY reading_list_added "Page added to your Reading List">
<!ENTITY reading_list_removed "Page removed from your Reading List">
<!ENTITY reading_list_failed "Failed to add page to your Reading List">

View File

@ -20,30 +20,10 @@
android:title="@string/forward"
android:visible="false"/>
<item android:id="@+id/save"
<item android:id="@+id/bookmark"
android:icon="@drawable/ic_menu_bookmark_add"
android:title="@string/save"
android:showAsAction="ifRoom">
<menu>
<item android:id="@+id/bookmark"
android:title="@string/bookmark"
android:checkable="true" />
<item android:id="@+id/reading_list_add"
android:title="@string/reading_list_add"
android:checkable="true" />
<item android:id="@+id/launcher_add"
android:title="@string/contextmenu_add_to_launcher" />
<item android:id="@+id/save_subscribe"
android:title="@string/contextmenu_subscribe" />
</menu>
</item>
android:title="@string/bookmark"
android:showAsAction="ifRoom"/>
<item android:id="@+id/share"
android:icon="@drawable/ic_menu_share"

View File

@ -20,30 +20,10 @@
android:title="@string/reload"
android:showAsAction="always"/>
<item android:id="@+id/save"
<item android:id="@+id/bookmark"
android:icon="@drawable/ic_menu_bookmark_add"
android:title="@string/save"
android:showAsAction="ifRoom">
<menu>
<item android:id="@+id/bookmark"
android:title="@string/bookmark"
android:checkable="true" />
<item android:id="@+id/reading_list_add"
android:title="@string/reading_list_add"
android:checkable="true" />
<item android:id="@+id/launcher_add"
android:title="@string/contextmenu_add_to_launcher" />
<item android:id="@+id/save_subscribe"
android:title="@string/contextmenu_subscribe" />
</menu>
</item>
android:title="@string/bookmark"
android:showAsAction="ifRoom"/>
<item android:id="@+id/share"
android:icon="@drawable/ic_menu_share"

View File

@ -20,30 +20,10 @@
android:title="@string/forward"
android:visible="false"/>
<item android:id="@+id/save"
<item android:id="@+id/bookmark"
android:icon="@drawable/ic_menu_bookmark_add"
android:title="@string/save"
android:showAsAction="always">
<menu>
<item android:id="@+id/bookmark"
android:title="@string/bookmark"
android:checkable="true" />
<item android:id="@+id/reading_list_add"
android:title="@string/reading_list_add"
android:checkable="true" />
<item android:id="@+id/launcher_add"
android:title="@string/contextmenu_add_to_launcher" />
<item android:id="@+id/save_subscribe"
android:title="@string/contextmenu_subscribe" />
</menu>
</item>
android:title="@string/bookmark"
android:showAsAction="always"/>
<item android:id="@+id/share"
android:icon="@drawable/ic_menu_share"

View File

@ -18,29 +18,9 @@
android:icon="@drawable/ic_menu_forward"
android:title="@string/forward"/>
<item android:id="@+id/save"
<item android:id="@+id/bookmark"
android:icon="@drawable/ic_menu_bookmark_add"
android:title="@string/save">
<menu>
<item android:id="@+id/bookmark"
android:title="@string/bookmark"
android:checkable="true" />
<item android:id="@+id/reading_list_add"
android:title="@string/reading_list_add"
android:checkable="true" />
<item android:id="@+id/launcher_add"
android:title="@string/contextmenu_add_to_launcher" />
<item android:id="@+id/save_subscribe"
android:title="@string/contextmenu_subscribe" />
</menu>
</item>
android:title="@string/bookmark"/>
<item android:id="@+id/new_tab"
android:icon="@drawable/ic_menu_new_tab"

View File

@ -233,14 +233,12 @@
<string name="tabs_normal">&tabs_normal;</string>
<string name="tabs_private">&tabs_private;</string>
<string name="tabs_synced">&tabs_synced;</string>
<string name="save">&save;</string>
<string name="site_settings_title">&site_settings_title3;</string>
<string name="site_settings_cancel">&site_settings_cancel;</string>
<string name="site_settings_clear">&site_settings_clear;</string>
<string name="site_settings_no_settings">&site_settings_no_settings;</string>
<string name="reading_list_add">&reading_list_add;</string>
<string name="reading_list_added">&reading_list_added;</string>
<string name="reading_list_removed">&reading_list_removed;</string>
<string name="reading_list_failed">&reading_list_failed;</string>

View File

@ -91,9 +91,9 @@ skip-if = android_version == "10" || processor == "x86"
[testSessionOOMSave]
# disabled on x86 and 2.3; bug 945395
skip-if = android_version == "10" || processor == "x86"
[testSessionOOMRestore]
#[testSessionOOMRestore] # see bug 946957
# disabled on Android 2.3; bug 979600
skip-if = android_version == "10"
#skip-if = android_version == "10"
[testSettingsMenuItems]
# disabled on Android 2.3; bug 979552
skip-if = android_version == "10"

View File

@ -17,7 +17,6 @@ import org.mozilla.gecko.GeckoApplication;
import org.mozilla.gecko.GeckoProfile;
import org.mozilla.gecko.LightweightTheme;
import org.mozilla.gecko.R;
import org.mozilla.gecko.ReaderModeUtils;
import org.mozilla.gecko.Tab;
import org.mozilla.gecko.Tabs;
import org.mozilla.gecko.animation.PropertyAnimator;
@ -1352,7 +1351,10 @@ public class BrowserToolbar extends GeckoRelativeLayout
tab.toggleReaderMode();
}
} else if (event.equals("Reader:LongClick")) {
ReaderModeUtils.addToReadingList(Tabs.getInstance().getSelectedTab());
Tab tab = Tabs.getInstance().getSelectedTab();
if (tab != null) {
tab.addToReadingList();
}
}
}

View File

@ -73,19 +73,15 @@ TabEngine.prototype = {
this.service.resource(url).delete();
},
/* The intent is not to show tabs in the menu if they're already
* open locally. There are a couple ways to interpret this: for
* instance, we could do it by removing a tab from the list when
* you open it -- but then if you close it, you can't get back to
* it. So the way I'm doing it here is to not show a tab in the menu
* if you have a tab open to the same URL, even though this means
* that as soon as you navigate anywhere, the original tab will
* reappear in the menu.
/**
* Return a Set of open URLs.
*/
locallyOpenTabMatchesURL: function TabEngine_localTabMatches(url) {
return this._store.getAllTabs().some(function (tab) {
return tab.urlHistory[0] == url;
});
getOpenURLs: function () {
let urls = new Set();
for (let entry of this._store.getAllTabs()) {
urls.add(entry.urlHistory[0]);
}
return urls;
}
};
@ -100,39 +96,60 @@ TabStore.prototype = {
return id == this.engine.service.clientsEngine.localID;
},
getAllTabs: function getAllTabs(filter) {
getWindowEnumerator: function () {
return Services.wm.getEnumerator("navigator:browser");
},
shouldSkipWindow: function (win) {
return win.closed ||
PrivateBrowsingUtils.isWindowPrivate(win);
},
getTabState: function (tab) {
return JSON.parse(Svc.Session.getTabState(tab));
},
getAllTabs: function (filter) {
let filteredUrls = new RegExp(Svc.Prefs.get("engine.tabs.filteredUrls"), "i");
let allTabs = [];
let currentState = JSON.parse(Svc.Session.getBrowserState());
currentState.windows.forEach(function (window) {
if (window.isPrivate) {
return;
let winEnum = this.getWindowEnumerator();
while (winEnum.hasMoreElements()) {
let win = winEnum.getNext();
if (this.shouldSkipWindow(win)) {
continue;
}
window.tabs.forEach(function (tab) {
dump("WIN IS " + JSON.stringify(win) + "\n");
for (let tab of win.gBrowser.tabs) {
tabState = this.getTabState(tab);
// Make sure there are history entries to look at.
if (!tab.entries.length)
return;
if (!tabState || !tabState.entries.length) {
continue;
}
// Until we store full or partial history, just grab the current entry.
// index is 1 based, so make sure we adjust.
let entry = tab.entries[tab.index - 1];
let entry = tabState.entries[tabState.index - 1];
// Filter out some urls if necessary. SessionStore can return empty
// tabs in some cases - easiest thing is to just ignore them for now.
if (!entry.url || filter && filteredUrls.test(entry.url))
return;
if (!entry.url || filter && filteredUrls.test(entry.url)) {
continue;
}
// I think it's also possible that attributes[.image] might not be set
// so handle that as well.
allTabs.push({
title: entry.title || "",
urlHistory: [entry.url],
icon: tab.attributes && tab.attributes.image || "",
lastUsed: Math.floor((tab.lastAccessed || 0) / 1000)
icon: tabState.attributes && tabState.attributes.image || "",
lastUsed: Math.floor((tabState.lastAccessed || 0) / 1000)
});
});
});
}
}
return allTabs;
},

View File

@ -2,6 +2,7 @@
http://creativecommons.org/publicdomain/zero/1.0/ */
Cu.import("resource://services-common/async.js");
Cu.import("resource://testing-common/services-common/utils.js");
let provider = {
getFile: function(prop, persistent) {
@ -124,3 +125,70 @@ function generateNewKeys(collectionKeys, collections=null) {
collectionKeys.setContents(wbo.cleartext, modified);
}
// Helpers for testing open tabs.
// These reflect part of the internal structure of TabEngine,
// and stub part of Service.wm.
function mockShouldSkipWindow (win) {
return win.closed ||
win.mockIsPrivate;
}
function mockGetTabState (tab) {
return tab;
}
function mockGetWindowEnumerator(url, numWindows, numTabs) {
let elements = [];
for (let w = 0; w < numWindows; ++w) {
let tabs = [];
let win = {
closed: false,
mockIsPrivate: false,
gBrowser: {
tabs: tabs,
},
};
elements.push(win);
for (let t = 0; t < numTabs; ++t) {
tabs.push(TestingUtils.deepCopy({
index: 1,
entries: [{
url: ((typeof url == "string") ? url : url()),
title: "title"
}],
attributes: {
image: "image"
},
lastAccessed: 1499
}));
}
}
// Always include a closed window and a private window.
elements.push({
closed: true,
mockIsPrivate: false,
gBrowser: {
tabs: [],
},
});
elements.push({
closed: false,
mockIsPrivate: true,
gBrowser: {
tabs: [],
},
});
return {
hasMoreElements: function () {
return elements.length;
},
getNext: function () {
return elements.shift();
},
};
}

View File

@ -5,44 +5,32 @@ Cu.import("resource://services-sync/engines/tabs.js");
Cu.import("resource://services-sync/service.js");
Cu.import("resource://services-sync/util.js");
function fakeSessionSvc() {
let tabs = [];
for(let i = 0; i < arguments.length; i++) {
tabs.push({
index: 1,
entries: [{
url: arguments[i],
title: "title"
}],
attributes: {
image: "image"
}
});
}
let obj = {windows: [{tabs: tabs}]};
// delete the getter, or the previously created fake Session
delete Svc.Session;
Svc.Session = {
getBrowserState: function() JSON.stringify(obj)
};
function getMocks() {
let engine = new TabEngine(Service);
let store = engine._store;
store.getTabState = mockGetTabState;
store.shouldSkipWindow = mockShouldSkipWindow;
return [engine, store];
}
function run_test() {
_("Test getOpenURLs.");
let [engine, store] = getMocks();
_("test locallyOpenTabMatchesURL");
let engine = new TabEngine(Service);
// 3 tabs
fakeSessionSvc("http://bar.com", "http://foo.com", "http://foobar.com");
let urls = ["http://bar.com", "http://foo.com", "http://foobar.com"];
function threeURLs() {
return urls.pop();
}
store.getWindowEnumerator = mockGetWindowEnumerator.bind(this, threeURLs, 1, 3);
let matches;
_(" test matching works (true)");
matches = engine.locallyOpenTabMatchesURL("http://foo.com");
let openurlsset = engine.getOpenURLs();
matches = openurlsset.has("http://foo.com");
do_check_true(matches);
_(" test matching works (false)");
matches = engine.locallyOpenTabMatchesURL("http://barfoo.com");
matches = openurlsset.has("http://barfoo.com");
do_check_false(matches);
}

View File

@ -6,6 +6,14 @@ Cu.import("resource://services-sync/service.js");
Cu.import("resource://services-sync/util.js");
Cu.import("resource://testing-common/services-common/utils.js");
function getMockStore() {
let engine = new TabEngine(Service);
let store = engine._store;
store.getTabState = mockGetTabState;
store.shouldSkipWindow = mockShouldSkipWindow;
return store;
}
function test_create() {
let store = new TabEngine(Service)._store;
@ -40,43 +48,15 @@ function test_create() {
Svc.Prefs.reset("notifyTabState");
}
function fakeSessionSvc(url, numtabs) {
// first delete the getter, or the previously
// created fake Session
delete Svc.Session;
Svc.Session = {
getBrowserState: function() {
let obj = {
windows: [{
tabs: [{
index: 1,
entries: [{
url: url,
title: "title"
}],
attributes: {
image: "image"
},
lastAccessed: 1499
}]
}]
};
if (numtabs) {
let tabs = obj.windows[0].tabs;
for (let i = 0; i < numtabs-1; i++)
tabs.push(TestingUtils.deepCopy(tabs[0]));
}
return JSON.stringify(obj);
}
};
};
function test_getAllTabs() {
let store = new TabEngine(Service)._store, tabs;
let store = getMockStore();
let tabs;
_("get all tabs");
fakeSessionSvc("http://foo.com");
store.getWindowEnumerator = mockGetWindowEnumerator.bind(this, "http://foo.com", 1, 1);
_("Get all tabs.");
tabs = store.getAllTabs();
_("Tabs: " + JSON.stringify(tabs));
do_check_eq(tabs.length, 1);
do_check_eq(tabs[0].title, "title");
do_check_eq(tabs[0].urlHistory.length, 1);
@ -84,31 +64,32 @@ function test_getAllTabs() {
do_check_eq(tabs[0].icon, "image");
do_check_eq(tabs[0].lastUsed, 1);
_("get all tabs, and check that filtering works");
// we don't bother testing every URL type here, the
// filteredUrls regex really should have it own tests
fakeSessionSvc("about:foo");
_("Get all tabs, and check that filtering works.");
store.getWindowEnumerator = mockGetWindowEnumerator.bind(this, "about:foo", 1, 1);
tabs = store.getAllTabs(true);
_("Filtered: " + JSON.stringify(tabs));
do_check_eq(tabs.length, 0);
}
function test_createRecord() {
let store = new TabEngine(Service)._store, record;
let store = getMockStore();
let record;
store.getTabState = mockGetTabState;
store.shouldSkipWindow = mockShouldSkipWindow;
store.getWindowEnumerator = mockGetWindowEnumerator.bind(this, "http://foo.com", 1, 1);
// get some values before testing
fakeSessionSvc("http://foo.com");
let tabs = store.getAllTabs();
let tabsize = JSON.stringify(tabs[0]).length;
let numtabs = Math.ceil(20000./77.);
_("create a record");
fakeSessionSvc("http://foo.com");
store.getWindowEnumerator = mockGetWindowEnumerator.bind(this, "http://foo.com", 1, 1);
record = store.createRecord("fake-guid");
do_check_true(record instanceof TabSetRecord);
do_check_eq(record.tabs.length, 1);
_("create a big record");
fakeSessionSvc("http://foo.com", numtabs);
store.getWindowEnumerator = mockGetWindowEnumerator.bind(this, "http://foo.com", 1, numtabs);
record = store.createRecord("fake-guid");
do_check_true(record instanceof TabSetRecord);
do_check_eq(record.tabs.length, 256);

View File

@ -1163,7 +1163,7 @@ Engine.prototype = {
_type: null,
// The name of the charset used to submit the search terms.
_queryCharset: null,
// A URL string pointing to the engine's search form.
// The engine's raw SearchForm value (URL string pointing to a search form).
__searchForm: null,
get _searchForm() {
return this.__searchForm;
@ -1364,10 +1364,11 @@ Engine.prototype = {
* if no matching URL is found.
*
* @param aType string to match the EngineURL's type attribute
* @param aRel [optional] only return URLs that with this rel value
*/
_getURLOfType: function SRCH_ENG__getURLOfType(aType) {
_getURLOfType: function SRCH_ENG__getURLOfType(aType, aRel) {
for (var i = 0; i < this._urls.length; ++i) {
if (this._urls[i].type == aType)
if (this._urls[i].type == aType && (!aRel || this._urls[i]._hasRelation(aRel)))
return this._urls[i];
}
@ -1535,11 +1536,11 @@ Engine.prototype = {
if (engineToUpdate._isInAppDir) {
let oldUpdateURL = engineToUpdate._updateURL;
let newUpdateURL = aEngine._updateURL;
let oldSelfURL = engineToUpdate._getURLOfType(URLTYPE_OPENSEARCH);
if (oldSelfURL && oldSelfURL._hasRelation("self")) {
let oldSelfURL = engineToUpdate._getURLOfType(URLTYPE_OPENSEARCH, "self");
if (oldSelfURL) {
oldUpdateURL = oldSelfURL.template;
let newSelfURL = aEngine._getURLOfType(URLTYPE_OPENSEARCH);
if (!newSelfURL || !newSelfURL._hasRelation("self")) {
let newSelfURL = aEngine._getURLOfType(URLTYPE_OPENSEARCH, "self");
if (!newSelfURL) {
LOG("_onLoad: updateURL missing in updated engine for " +
aEngine.name + " aborted");
onError();
@ -1758,7 +1759,7 @@ Engine.prototype = {
"Can't call _initFromMetaData on a readonly engine!",
Cr.NS_ERROR_FAILURE);
this._urls.push(new EngineURL("text/html", aMethod, aTemplate));
this._urls.push(new EngineURL(URLTYPE_SEARCH_HTML, aMethod, aTemplate));
this._name = aName;
this.alias = aAlias;
@ -2240,11 +2241,11 @@ Engine.prototype = {
} else if (name != "")
template += "&" + name + "=" + value;
}
url = new EngineURL("text/html", method, template);
url = new EngineURL(URLTYPE_SEARCH_HTML, method, template);
} else if (method == "POST") {
// Create the URL object and just add the parameters directly
url = new EngineURL("text/html", method, template);
url = new EngineURL(URLTYPE_SEARCH_HTML, method, template);
for (var i = 0; i < inputs.length; i++) {
var name = inputs[i][0];
var value = inputs[i][1];
@ -2623,9 +2624,8 @@ Engine.prototype = {
get _hasUpdates() {
// Whether or not the engine has an update URL
let selfURL = this._getURLOfType(URLTYPE_OPENSEARCH);
return !!(this._updateURL || this._iconUpdateURL || (selfURL &&
selfURL._hasRelation("self")));
let selfURL = this._getURLOfType(URLTYPE_OPENSEARCH, "self");
return !!(this._updateURL || this._iconUpdateURL || selfURL);
},
get name() {
@ -2637,8 +2637,19 @@ Engine.prototype = {
},
get searchForm() {
// First look for a <Url rel="searchform">
var searchFormURL = this._getURLOfType(URLTYPE_SEARCH_HTML, "searchform");
if (searchFormURL) {
let submission = searchFormURL.getSubmission("", this);
// If the rel=searchform URL is not type="get" (i.e. has postData),
// ignore it, since we can only return a URL.
if (!submission.postData)
return submission.uri.spec;
}
if (!this._searchForm) {
// No searchForm specified in the engine definition file, use the prePath
// No SearchForm specified in the engine definition file, use the prePath
// (e.g. https://foo.com for https://foo.com/search.php?q=bar).
var htmlUrl = this._getURLOfType(URLTYPE_SEARCH_HTML);
ENSURE_WARN(htmlUrl, "Engine has no HTML URL!", Cr.NS_ERROR_UNEXPECTED);

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
<ShortName>engine-rel-searchform-post.xml</ShortName>
<Url type="text/html" method="POST" template="http://engine-rel-searchform-post.xml/POST" rel="searchform"/>
<SearchForm>http://engine-rel-searchform-post.xml/?search</SearchForm>
</SearchPlugin>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
<ShortName>engine-rel-searchform.xml</ShortName>
<Url type="text/html" method="GET" template="http://engine-rel-searchform.xml/?search" rel="searchform"/>
</SearchPlugin>

View File

@ -0,0 +1,66 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
/*
* Tests that <Url rel="searchform"/> is properly recognized as a searchForm.
*/
"use strict";
const Ci = Components.interfaces;
Components.utils.import("resource://testing-common/httpd.js");
let engines = [
"engine-rel-searchform.xml",
"engine-rel-searchform-post.xml",
];
function search_observer(aSubject, aTopic, aData) {
let engine = aSubject.QueryInterface(Ci.nsISearchEngine);
do_print("Observer: " + aData + " for " + engine.name);
if (aData != "engine-added")
return;
let idx = engines.indexOf(engine.name);
if (idx < 0)
// The engine is some other engine unrelated to the test, so ignore it.
return;
// The final searchForm of the engine should be a URL whose domain is the
// <ShortName> in the engine's XML and that has a ?search parameter. The
// point of the ?search parameter is to avoid accidentally matching the value
// returned as a last resort by Engine's searchForm getter, which is simply
// the prePath of the engine's first HTML <Url>.
do_check_eq(engine.searchForm, "http://" + engine.name + "/?search");
engines.splice(idx, 1);
if (!engines.length)
do_test_finished();
}
function run_test() {
removeMetadata();
updateAppInfo();
let httpServer = new HttpServer();
httpServer.start(-1);
httpServer.registerDirectory("/", do_get_cwd());
do_register_cleanup(function cleanup() {
httpServer.stop(function() {});
Services.obs.removeObserver(search_observer, "browser-search-engine-modified");
});
do_test_pending();
Services.obs.addObserver(search_observer, "browser-search-engine-modified", false);
for (let basename of engines) {
Services.search.addEngine("http://localhost:" +
httpServer.identity.primaryPort +
"/data/" + basename,
Ci.nsISearchEngine.DATA_XML,
null, false);
}
}

View File

@ -7,6 +7,8 @@ support-files =
data/engine.src
data/engine.xml
data/engine2.xml
data/engine-rel-searchform.xml
data/engine-rel-searchform-post.xml
data/engineImages.xml
data/ico-size-16x16-png.ico
data/invalid-engine.xml
@ -37,3 +39,4 @@ support-files =
[test_sync.js]
[test_sync_fallback.js]
[test_sync_delay_fallback.js]
[test_rel_searchform.js]

View File

@ -2936,8 +2936,12 @@ ObjectActor.prototype = {
let previewers = DebuggerServer.ObjectActorPreviewers[this.obj.class] ||
DebuggerServer.ObjectActorPreviewers.Object;
for (let fn of previewers) {
if (fn(this, g, raw)) {
break;
try {
if (fn(this, g, raw)) {
break;
}
} catch (e) {
DevToolsUtils.reportException("ObjectActor.prototype.grip previewer function", e);
}
}
}