Merge mozilla-central to mozilla-inbound

This commit is contained in:
Carsten "Tomcat" Book 2016-02-11 11:56:20 +01:00
commit 6b185089c6
120 changed files with 3156 additions and 2207 deletions

View File

@ -369,7 +369,7 @@ function showDefaultSnippets()
}
function fitToWidth() {
if (window.scrollMaxX != window.scrollMinX) {
if (document.documentElement.scrollWidth > window.innerWidth) {
document.body.setAttribute("narrow", "true");
} else if (document.body.hasAttribute("narrow")) {
document.body.removeAttribute("narrow");

View File

@ -31,6 +31,7 @@ nsContextMenu.prototype = {
return;
this.hasPageMenu = false;
this.isContentSelected = !this.selectionInfo.docSelectionIsCollapsed;
if (!aIsShift) {
if (this.isRemote) {
this.hasPageMenu =
@ -71,6 +72,8 @@ nsContextMenu.prototype = {
Ci.nsIPrefLocalizedString).data;
} catch (e) { }
// Reset after "on-build-contextmenu" notification in case selection was
// changed during the notification.
this.isContentSelected = !this.selectionInfo.docSelectionIsCollapsed;
this.onPlainTextLink = false;

View File

@ -2936,6 +2936,62 @@
</body>
</method>
<!-- Adopts a tab from another browser window, and inserts it at aIndex -->
<method name="adoptTab">
<parameter name="aTab"/>
<parameter name="aIndex"/>
<parameter name="aSelectTab"/>
<body>
<![CDATA[
// Swap the dropped tab with a new one we create and then close
// it in the other window (making it seem to have moved between
// windows).
let newTab = this.addTab("about:blank");
let newBrowser = this.getBrowserForTab(newTab);
let newURL = aTab.linkedBrowser.currentURI.spec;
// If we're an e10s browser window, an exception will be thrown
// if we attempt to drag a non-remote browser in, so we need to
// ensure that the remoteness of the newly created browser is
// appropriate for the URL of the tab being dragged in.
this.updateBrowserRemotenessByURL(newBrowser, newURL);
// Stop the about:blank load.
newBrowser.stop();
// Make sure it has a docshell.
newBrowser.docShell;
let numPinned = this._numPinnedTabs;
if (aIndex < numPinned || (aTab.pinned && aIndex == numPinned)) {
this.pinTab(newTab);
}
this.moveTabTo(newTab, aIndex);
// We need to select the tab before calling swapBrowsersAndCloseOther
// so that window.content in chrome windows points to the right tab
// when pagehide/show events are fired. This is no longer necessary
// for any exiting browser code, but it may be necessary for add-on
// compatibility.
if (aSelectTab) {
this.selectedTab = newTab;
}
aTab.parentNode._finishAnimateTabMove();
this.swapBrowsersAndCloseOther(newTab, aTab);
if (aSelectTab) {
// Call updateCurrentBrowser to make sure the URL bar is up to date
// for our new tab after we've done swapBrowsersAndCloseOther.
this.updateCurrentBrowser(true);
}
return newTab;
]]>
</body>
</method>
<method name="moveTabBackward">
<body>
<![CDATA[
@ -5791,42 +5847,8 @@
this.tabbrowser.moveTabTo(draggedTab, newIndex);
}
} else if (draggedTab) {
// swap the dropped tab with a new one we create and then close
// it in the other window (making it seem to have moved between
// windows)
let newIndex = this._getDropIndex(event, false);
let newTab = this.tabbrowser.addTab("about:blank");
let newBrowser = this.tabbrowser.getBrowserForTab(newTab);
let draggedBrowserURL = draggedTab.linkedBrowser.currentURI.spec;
// If we're an e10s browser window, an exception will be thrown
// if we attempt to drag a non-remote browser in, so we need to
// ensure that the remoteness of the newly created browser is
// appropriate for the URL of the tab being dragged in.
this.tabbrowser.updateBrowserRemotenessByURL(newBrowser,
draggedBrowserURL);
// Stop the about:blank load
newBrowser.stop();
// make sure it has a docshell
newBrowser.docShell;
let numPinned = this.tabbrowser._numPinnedTabs;
if (newIndex < numPinned || draggedTab.pinned && newIndex == numPinned)
this.tabbrowser.pinTab(newTab);
this.tabbrowser.moveTabTo(newTab, newIndex);
// We need to select the tab before calling swapBrowsersAndCloseOther
// so that window.content in chrome windows points to the right tab
// when pagehide/show events are fired.
this.tabbrowser.selectedTab = newTab;
draggedTab.parentNode._finishAnimateTabMove();
this.tabbrowser.swapBrowsersAndCloseOther(newTab, draggedTab);
// Call updateCurrentBrowser to make sure the URL bar is up to date
// for our new tab after we've done swapBrowsersAndCloseOther.
this.tabbrowser.updateCurrentBrowser(true);
this.tabbrowser.adoptTab(draggedTab, newIndex, true);
} else {
// Pass true to disallow dropping javascript: or data: urls
let url;

View File

@ -129,6 +129,7 @@ support-files =
skip-if = os == "linux" # Bug 924307
[browser_aboutHome.js]
skip-if = e10s # Bug 1093153 - no about:home support yet
[browser_aboutHome_wrapsCorrectly.js]
[browser_action_keyword.js]
[browser_action_keyword_override.js]
[browser_action_searchengine.js]

View File

@ -0,0 +1,28 @@
add_task(function* () {
let newWindow = yield BrowserTestUtils.openNewBrowserWindow();
let resizedPromise = BrowserTestUtils.waitForEvent(newWindow, "resize");
newWindow.resizeTo(300, 300);
yield resizedPromise;
yield BrowserTestUtils.openNewForegroundTab(newWindow.gBrowser, "about:home");
yield ContentTask.spawn(newWindow.gBrowser.selectedBrowser, {}, function* () {
is(content.document.body.getAttribute("narrow"), "true", "narrow mode");
});
resizedPromise = BrowserTestUtils.waitForContentEvent(newWindow.gBrowser.selectedBrowser, "resize");
yield ContentTask.spawn(newWindow.gBrowser.selectedBrowser, {}, function* () {
content.window.resizeTo(800, 800);
});
yield resizedPromise;
yield ContentTask.spawn(newWindow.gBrowser.selectedBrowser, {}, function* () {
is(content.document.body.hasAttribute("narrow"), false, "non-narrow mode");
});
yield BrowserTestUtils.closeWindow(newWindow);
});

View File

@ -194,7 +194,7 @@ const CustomizableWidgets = [
let items = doc.getElementById("PanelUI-historyItems");
// Clear previous history items.
while (items.firstChild) {
items.removeChild(items.firstChild);
items.firstChild.remove();
}
// Get all statically placed buttons to supply them with keyboard shortcuts.
@ -205,32 +205,28 @@ const CustomizableWidgets = [
PlacesUtils.history.QueryInterface(Ci.nsPIPlacesDatabase)
.asyncExecuteLegacyQueries([query], 1, options, {
handleResult: function (aResultSet) {
let onHistoryVisit = function (aUri, aEvent, aItem) {
doc.defaultView.openUILink(aUri, aEvent);
CustomizableUI.hidePanelForNode(aItem);
let onItemClick = function (aEvent) {
let item = aEvent.target;
win.openUILink(item.getAttribute("targetURI"), aEvent);
CustomizableUI.hidePanelForNode(item);
};
let fragment = doc.createDocumentFragment();
for (let row, i = 0; (row = aResultSet.getNextRow()); i++) {
try {
let uri = row.getResultByIndex(1);
let title = row.getResultByIndex(2);
let icon = row.getResultByIndex(6);
let row;
while ((row = aResultSet.getNextRow())) {
let uri = row.getResultByIndex(1);
let title = row.getResultByIndex(2);
let icon = row.getResultByIndex(6);
let item = doc.createElementNS(kNSXUL, "toolbarbutton");
item.setAttribute("label", title || uri);
item.setAttribute("targetURI", uri);
item.setAttribute("class", "subviewbutton");
item.addEventListener("click", function (aEvent) {
onHistoryVisit(uri, aEvent, item);
});
if (icon) {
let iconURL = "moz-anno:favicon:" + icon;
item.setAttribute("image", iconURL);
}
fragment.appendChild(item);
} catch (e) {
log.error("Error while showing history subview: " + e);
let item = doc.createElementNS(kNSXUL, "toolbarbutton");
item.setAttribute("label", title || uri);
item.setAttribute("targetURI", uri);
item.setAttribute("class", "subviewbutton");
item.addEventListener("click", onItemClick);
if (icon) {
let iconURL = "moz-anno:favicon:" + icon;
item.setAttribute("image", iconURL);
}
fragment.appendChild(item);
}
items.appendChild(fragment);
},

View File

@ -0,0 +1,10 @@
"use strict";
/* eslint-disable mozilla/balanced-listeners */
extensions.on("uninstall", (msg, extension) => {
if (extension.uninstallURL) {
let browser = Services.wm.getMostRecentWindow("navigator:browser").gBrowser;
browser.addTab(extension.uninstallURL, { relatedToCurrent: true });
}
});

View File

@ -613,41 +613,33 @@ extensions.registerSchemaAPI("tabs", null, (extension, context) => {
// If the window is not specified, use the window from the tab.
let window = destinationWindow || tab.ownerDocument.defaultView;
let windowId = WindowManager.getId(window);
let gBrowser = window.gBrowser;
let getInsertionPoint = () => {
let point = indexMap.get(window) || index;
// If the index is -1 it should go to the end of the tabs.
if (point == -1) {
point = gBrowser.tabs.length;
}
indexMap.set(window, point + 1);
return point;
};
let insertionPoint = indexMap.get(window) || index;
// If the index is -1 it should go to the end of the tabs.
if (insertionPoint == -1) {
insertionPoint = gBrowser.tabs.length;
}
if (WindowManager.getId(tab.ownerDocument.defaultView) !== windowId) {
// We can only move pinned tabs to a point within, or just after,
// the current set of pinned tabs. Unpinned tabs, likewise, can only
// be moved to a position after the current set of pinned tabs.
// Attempts to move a tab to an illegal position are ignored.
let numPinned = gBrowser._numPinnedTabs;
let ok = tab.pinned ? insertionPoint <= numPinned : insertionPoint >= numPinned;
if (!ok) {
continue;
}
indexMap.set(window, insertionPoint + 1);
if (tab.ownerDocument.defaultView !== window) {
// If the window we are moving the tab in is different, then move the tab
// to the new window.
let newTab = gBrowser.addTab("about:blank");
let newBrowser = gBrowser.getBrowserForTab(newTab);
gBrowser.updateBrowserRemotenessByURL(newBrowser, tab.linkedBrowser.currentURI.spec);
newBrowser.stop();
// This is necessary for getter side-effects.
void newBrowser.docShell;
if (tab.pinned) {
gBrowser.pinTab(newTab);
}
gBrowser.moveTabTo(newTab, getInsertionPoint());
tab.parentNode._finishAnimateTabMove();
gBrowser.swapBrowsersAndCloseOther(newTab, tab);
tab = newTab;
tab = gBrowser.adoptTab(tab, insertionPoint, false);
} else {
// If the window we are moving is the same, just move the tab.
gBrowser.moveTabTo(tab, getInsertionPoint());
gBrowser.moveTabTo(tab, insertionPoint);
}
tabsMoved.push(tab);
}

View File

@ -8,6 +8,7 @@ browser.jar:
content/browser/ext-contextMenus.js
content/browser/ext-browserAction.js
content/browser/ext-pageAction.js
content/browser/ext-desktop-runtime.js
content/browser/ext-tabs.js
content/browser/ext-windows.js
content/browser/ext-bookmarks.js

View File

@ -21,6 +21,7 @@ support-files =
[browser_ext_contextMenus.js]
[browser_ext_getViews.js]
[browser_ext_lastError.js]
[browser_ext_runtime_setUninstallURL.js]
[browser_ext_tabs_audio.js]
[browser_ext_tabs_captureVisibleTab.js]
[browser_ext_tabs_executeScript.js]

View File

@ -233,39 +233,43 @@ add_task(function* testInvalidIconSizes() {
browser.tabs.query({ active: true, currentWindow: true }, tabs => {
let tabId = tabs[0].id;
let promises = [];
for (let api of ["pageAction", "browserAction"]) {
// helper function to run setIcon and check if it fails
let assertSetIconThrows = function(detail, error, message) {
try {
detail.tabId = tabId;
browser[api].setIcon(detail);
browser.test.fail("Expected an error on invalid icon size.");
browser.test.notifyFail("setIcon with invalid icon size");
return;
} catch (e) {
browser.test.succeed("setIcon with invalid icon size");
}
detail.tabId = tabId;
promises.push(
browser[api].setIcon(detail).then(
() => {
browser.test.fail("Expected an error on invalid icon size.");
browser.test.notifyFail("setIcon with invalid icon size");
},
error => {
browser.test.succeed("setIcon with invalid icon size");
}));
};
let imageData = new ImageData(1, 1);
// test invalid icon size inputs
for (let type of ["path", "imageData"]) {
assertSetIconThrows({ [type]: { "abcdef": "test.png" } });
assertSetIconThrows({ [type]: { "48px": "test.png" } });
assertSetIconThrows({ [type]: { "20.5": "test.png" } });
assertSetIconThrows({ [type]: { "5.0": "test.png" } });
assertSetIconThrows({ [type]: { "-300": "test.png" } });
assertSetIconThrows({ [type]: {
"abc": "test.png",
"5": "test.png"
}});
let img = type == "imageData" ? imageData : "test.png";
assertSetIconThrows({ [type]: { "abcdef": img } });
assertSetIconThrows({ [type]: { "48px": img } });
assertSetIconThrows({ [type]: { "20.5": img } });
assertSetIconThrows({ [type]: { "5.0": img } });
assertSetIconThrows({ [type]: { "-300": img } });
assertSetIconThrows({ [type]: { "abc": img, "5": img }});
}
assertSetIconThrows({ imageData: { "abcdef": "test.png" }, path: {"5": "test.png"} });
assertSetIconThrows({ path: { "abcdef": "test.png" }, imageData: {"5": "test.png"} });
assertSetIconThrows({ imageData: { "abcdef": imageData }, path: {"5": "test.png"} });
assertSetIconThrows({ path: { "abcdef": "test.png" }, imageData: {"5": imageData} });
}
browser.test.notifyPass("setIcon with invalid icon size");
Promise.all(promises).then(() => {
browser.test.notifyPass("setIcon with invalid icon size");
});
});
}
});
@ -347,25 +351,24 @@ add_task(function* testSecureURLsDenied() {
let urls = ["chrome://browser/content/browser.xul",
"javascript:true"];
let promises = [];
for (let url of urls) {
for (let api of ["pageAction", "browserAction"]) {
try {
browser[api].setIcon({tabId, path: url});
browser.test.fail(`Load of '${url}' succeeded. Expected failure.`);
browser.test.notifyFail("setIcon security tests");
return;
} catch (e) {
// We can't actually inspect the error here, since the
// error object belongs to the privileged scope of the API,
// rather than to the extension scope that calls into it.
// Just assume it's the expected security error, for now.
browser.test.succeed(`Load of '${url}' failed. Expected failure.`);
}
promises.push(
browser[api].setIcon({tabId, path: url}).then(
() => {
browser.test.fail(`Load of '${url}' succeeded. Expected failure.`);
browser.test.notifyFail("setIcon security tests");
},
error => {
browser.test.succeed(`Load of '${url}' failed. Expected failure. ${error}`);
}));
}
}
browser.test.notifyPass("setIcon security tests");
Promise.all(promises).then(() => {
browser.test.notifyPass("setIcon security tests");
});
});
},
});

View File

@ -54,12 +54,13 @@ add_task(function* () {
{ title: "child2", parentId: parentToDel, onclick: genericOnClick });
browser.contextMenus.remove(parentToDel);
try {
browser.contextMenus.update(parent, { parentId: child2 });
browser.test.notifyFail();
} catch (e) {
browser.test.notifyPass();
}
browser.contextMenus.update(parent, { parentId: child2 }).then(
() => {
browser.test.notifyFail();
},
() => {
browser.test.notifyPass();
});
},
});

View File

@ -0,0 +1,114 @@
"use strict";
let { AddonManager } = Components.utils.import("resource://gre/modules/AddonManager.jsm", {});
let { Extension } = Components.utils.import("resource://gre/modules/Extension.jsm", {});
function install(url) {
return new Promise((resolve, reject) => {
AddonManager.getInstallForURL(url, (install) => {
install.addListener({
onInstallEnded: (i, addon) => resolve(addon),
onInstallFailed: () => reject(),
});
install.install();
}, "application/x-xpinstall");
});
}
function* makeAndInstallXPI(id, backgroundScript, loadedURL) {
let xpi = Extension.generateXPI(id, {
background: "(" + backgroundScript.toString() + ")()",
});
SimpleTest.registerCleanupFunction(function cleanupXPI() {
Services.obs.notifyObservers(xpi, "flush-cache-entry", null);
xpi.remove(false);
});
let loadPromise = BrowserTestUtils.waitForNewTab(gBrowser, loadedURL);
let fileURI = Services.io.newFileURI(xpi);
info(`installing ${fileURI.spec}`);
let addon = yield install(fileURI.spec);
info("installed");
// A WebExtension is started asynchronously, we have our test extension
// open a new tab to signal that the background script has executed.
let loadTab = yield loadPromise;
yield BrowserTestUtils.removeTab(loadTab);
return addon;
}
add_task(function* test_setuninstallurl_badargs() {
function backgroundScript() {
let promises = [
browser.runtime.setUninstallURL("this is not a url")
.then(() => {
browser.test.notifyFail("setUninstallURL should have failed with bad url");
})
.catch(error => {
browser.test.assertTrue(/Invalid URL/.test(error.message), "error message indicates malformed url");
}),
browser.runtime.setUninstallURL("file:///etc/passwd")
.then(() => {
browser.test.notifyFail("setUninstallURL should have failed with non-http[s] url");
})
.catch(error => {
browser.test.assertTrue(/must have the scheme http or https/.test(error.message), "error message indicates bad scheme");
}),
];
Promise.all(promises)
.then(() => browser.test.notifyPass("setUninstallURL bad params"));
}
let extension = ExtensionTestUtils.loadExtension({
background: "(" + backgroundScript.toString() + ")()",
});
yield extension.startup();
yield extension.awaitFinish();
yield extension.unload();
});
// Test the documented behavior of setUninstallURL() that passing an
// empty string is equivalent to not setting an uninstall URL
// (i.e., no new tab is opened upon uninstall)
add_task(function* test_setuninstall_empty_url() {
function backgroundScript() {
browser.runtime.setUninstallURL("")
.then(() => browser.tabs.create({ url: "http://example.com/addon_loaded" }));
}
let addon = yield makeAndInstallXPI("test_uinstallurl2@tests.mozilla.org",
backgroundScript,
"http://example.com/addon_loaded");
addon.uninstall(true);
info("uninstalled");
// no need to explicitly check for the absence of a new tab,
// BrowserTestUtils will eventually complain if one is opened.
});
add_task(function* test_setuninstallurl() {
function backgroundScript() {
browser.runtime.setUninstallURL("http://example.com/addon_uninstalled")
.then(() => browser.tabs.create({ url: "http://example.com/addon_loaded" }));
}
let addon = yield makeAndInstallXPI("test_uinstallurl@tests.mozilla.org",
backgroundScript,
"http://example.com/addon_loaded");
// look for a new tab with the uninstall url.
let uninstallPromise = BrowserTestUtils.waitForNewTab(gBrowser, "http://example.com/addon_uninstalled");
addon.uninstall(true);
info("uninstalled");
let uninstalledTab = yield uninstallPromise;
isnot(uninstalledTab, null, "opened tab with uninstall url");
yield BrowserTestUtils.removeTab(uninstalledTab);
});

View File

@ -551,6 +551,7 @@ BrowserGlue.prototype = {
ExtensionManagement.registerScript("chrome://browser/content/ext-browserAction.js");
ExtensionManagement.registerScript("chrome://browser/content/ext-pageAction.js");
ExtensionManagement.registerScript("chrome://browser/content/ext-contextMenus.js");
ExtensionManagement.registerScript("chrome://browser/content/ext-desktop-runtime.js");
ExtensionManagement.registerScript("chrome://browser/content/ext-tabs.js");
ExtensionManagement.registerScript("chrome://browser/content/ext-windows.js");
ExtensionManagement.registerScript("chrome://browser/content/ext-bookmarks.js");

View File

@ -30,9 +30,9 @@ var gMainPane = {
// In Windows 8 we launch the control panel since it's the only
// way to get all file type association prefs. So we don't know
// when the user will select the default. We refresh here periodically
// in case the default changes. On other Windows OS's defaults can also
// in case the default changes. On other Windows OS's defaults can also
// be set while the prefs are open.
window.setInterval(this.updateSetDefaultBrowser, 1000);
window.setInterval(this.updateSetDefaultBrowser.bind(this), 1000);
#endif
#endif
@ -695,8 +695,11 @@ var gMainPane = {
return;
}
let setDefaultPane = document.getElementById("setDefaultPane");
let selectedIndex = shellSvc.isDefaultBrowser(false, true) ? 1 : 0;
setDefaultPane.selectedIndex = selectedIndex;
let isDefault = shellSvc.isDefaultBrowser(false, true);
setDefaultPane.selectedIndex = isDefault ? 1 : 0;
let alwaysCheck = document.getElementById("alwaysCheckDefault");
alwaysCheck.disabled = alwaysCheck.disabled ||
isDefault && alwaysCheck.checked;
},
/**
@ -704,6 +707,9 @@ var gMainPane = {
*/
setDefaultBrowser: function()
{
let alwaysCheckPref = document.getElementById("browser.shell.checkDefaultBrowser");
alwaysCheckPref.value = true;
let shellSvc = getShellService();
if (!shellSvc)
return;
@ -713,8 +719,8 @@ var gMainPane = {
Cu.reportError(ex);
return;
}
let selectedIndex =
shellSvc.isDefaultBrowser(false, true) ? 1 : 0;
let selectedIndex = shellSvc.isDefaultBrowser(false, true) ? 1 : 0;
document.getElementById("setDefaultPane").selectedIndex = selectedIndex;
}
#endif

View File

@ -17,6 +17,7 @@ skip-if = os != "win" # This test tests the windows-specific app selection dialo
[browser_connection.js]
[browser_connection_bug388287.js]
[browser_cookies_exceptions.js]
[browser_defaultbrowser_alwayscheck.js]
[browser_healthreport.js]
skip-if = true || !healthreport # Bug 1185403 for the "true"
[browser_homepages_filter_aboutpreferences.js]

View File

@ -0,0 +1,103 @@
"use strict";
const CHECK_DEFAULT_INITIAL = Services.prefs.getBoolPref("browser.shell.checkDefaultBrowser");
add_task(function* clicking_make_default_checks_alwaysCheck_checkbox() {
yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:preferences");
yield test_with_mock_shellservice({isDefault: false}, function*() {
let setDefaultPane = content.document.getElementById("setDefaultPane");
is(setDefaultPane.selectedIndex, "0",
"The 'make default' pane should be visible when not default");
let alwaysCheck = content.document.getElementById("alwaysCheckDefault");
is(alwaysCheck.checked, false, "Always Check is unchecked by default");
is(Services.prefs.getBoolPref("browser.shell.checkDefaultBrowser"), false,
"alwaysCheck pref should be false by default in test runs");
let setDefaultButton = content.document.getElementById("setDefaultButton");
setDefaultButton.click();
content.window.gMainPane.updateSetDefaultBrowser();
yield ContentTaskUtils.waitForCondition(() => alwaysCheck.checked,
"'Always Check' checkbox should get checked after clicking the 'Set Default' button");
is(alwaysCheck.checked, true,
"Clicking 'Make Default' checks the 'Always Check' checkbox");
is(Services.prefs.getBoolPref("browser.shell.checkDefaultBrowser"), true,
"Checking the checkbox should set the pref to true");
is(alwaysCheck.disabled, true,
"'Always Check' checkbox is locked with default browser and alwaysCheck=true");
is(setDefaultPane.selectedIndex, "1",
"The 'make default' pane should not be visible when default");
is(Services.prefs.getBoolPref("browser.shell.checkDefaultBrowser"), true,
"checkDefaultBrowser pref is now enabled");
});
gBrowser.removeCurrentTab();
Services.prefs.clearUserPref("browser.shell.checkDefaultBrowser");
});
add_task(function* clicking_make_default_checks_alwaysCheck_checkbox() {
Services.prefs.lockPref("browser.shell.checkDefaultBrowser");
yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:preferences");
yield test_with_mock_shellservice({isDefault: false}, function*() {
let setDefaultPane = content.document.getElementById("setDefaultPane");
is(setDefaultPane.selectedIndex, "0",
"The 'make default' pane should be visible when not default");
let alwaysCheck = content.document.getElementById("alwaysCheckDefault");
is(alwaysCheck.disabled, true, "Always Check is disabled when locked");
is(alwaysCheck.checked, true,
"Always Check is checked because defaultPref is true and pref is locked");
is(Services.prefs.getBoolPref("browser.shell.checkDefaultBrowser"), true,
"alwaysCheck pref should ship with 'true' by default");
let setDefaultButton = content.document.getElementById("setDefaultButton");
setDefaultButton.click();
content.window.gMainPane.updateSetDefaultBrowser();
yield ContentTaskUtils.waitForCondition(() => setDefaultPane.selectedIndex == "1",
"Browser is now default");
is(alwaysCheck.checked, true,
"'Always Check' is still checked because it's locked");
is(alwaysCheck.disabled, true,
"'Always Check is disabled because it's locked");
is(Services.prefs.getBoolPref("browser.shell.checkDefaultBrowser"), true,
"The pref is locked and so doesn't get changed");
});
Services.prefs.unlockPref("browser.shell.checkDefaultBrowser");
gBrowser.removeCurrentTab();
});
registerCleanupFunction(function() {
Services.prefs.unlockPref("browser.shell.checkDefaultBrowser");
Services.prefs.setBoolPref("browser.shell.checkDefaultBrowser", CHECK_DEFAULT_INITIAL);
});
function* test_with_mock_shellservice(options, testFn) {
yield ContentTask.spawn(gBrowser.selectedBrowser, options, function*(options) {
let doc = content.document;
let win = doc.defaultView;
win.oldShellService = win.getShellService();
let mockShellService = {
_isDefault: false,
isDefaultBrowser() {
return this._isDefault;
},
setDefaultBrowser() {
this._isDefault = true;
},
};
win.getShellService = function() {
return mockShellService;
}
mockShellService._isDefault = options.isDefault;
win.gMainPane.updateSetDefaultBrowser();
});
yield ContentTask.spawn(gBrowser.selectedBrowser, null, testFn);
Services.prefs.setBoolPref("browser.shell.checkDefaultBrowser", CHECK_DEFAULT_INITIAL);
}

View File

@ -468,18 +468,6 @@ var PocketOverlay = {
this.updatePocketItemVisibility(win.document);
}
},
onWidgetReset: function(aNode, aContainer) {
// CUI was reset and doesn't respect default area for API widgets, place our
// widget back to the default area
// initially place the button after the bookmarks button if it is in the UI
let widgets = CustomizableUI.getWidgetIdsInArea(CustomizableUI.AREA_NAVBAR);
let bmbtn = widgets.indexOf("bookmarks-menu-button");
if (bmbtn > -1) {
CustomizableUI.addWidgetToArea("pocket-button", CustomizableUI.AREA_NAVBAR, bmbtn + 1);
} else {
CustomizableUI.addWidgetToArea("pocket-button", CustomizableUI.AREA_NAVBAR);
}
},
updatePocketItemVisibility: function(doc) {
let hidden = !CustomizableUI.getPlacementOfWidget("pocket-button");
for (let prefix of ["panelMenu_", "menu_", "BMB_"]) {

View File

@ -0,0 +1,828 @@
/* 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/. */
"use strict";
const {Cc, Ci, Cu} = require("chrome");
const Services = require("Services");
const promise = require("promise");
const {gDevTools} = require("./devtools");
// Load target and toolbox lazily as they need gDevTools to be fully initialized
loader.lazyRequireGetter(this, "TargetFactory", "devtools/client/framework/target", true);
loader.lazyRequireGetter(this, "Toolbox", "devtools/client/framework/toolbox", true);
loader.lazyRequireGetter(this, "DebuggerServer", "devtools/server/main", true);
loader.lazyRequireGetter(this, "DebuggerClient", "devtools/shared/client/main", true);
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "CustomizableUI",
"resource:///modules/CustomizableUI.jsm");
const bundle = Services.strings.createBundle("chrome://devtools/locale/toolbox.properties");
/**
* gDevToolsBrowser exposes functions to connect the gDevTools instance with a
* Firefox instance.
*/
var gDevToolsBrowser = exports.gDevToolsBrowser = {
/**
* A record of the windows whose menus we altered, so we can undo the changes
* as the window is closed
*/
_trackedBrowserWindows: new Set(),
_tabStats: {
peakOpen: 0,
peakPinned: 0,
histOpen: [],
histPinned: []
},
/**
* This function is for the benefit of Tools:DevToolbox in
* browser/base/content/browser-sets.inc and should not be used outside
* of there
*/
// used by browser-sets.inc, command
toggleToolboxCommand: function(gBrowser) {
let target = TargetFactory.forTab(gBrowser.selectedTab);
let toolbox = gDevTools.getToolbox(target);
// If a toolbox exists, using toggle from the Main window :
// - should close a docked toolbox
// - should focus a windowed toolbox
let isDocked = toolbox && toolbox.hostType != Toolbox.HostType.WINDOW;
isDocked ? toolbox.destroy() : gDevTools.showToolbox(target);
},
/**
* This function ensures the right commands are enabled in a window,
* depending on their relevant prefs. It gets run when a window is registered,
* or when any of the devtools prefs change.
*/
updateCommandAvailability: function(win) {
let doc = win.document;
function toggleCmd(id, isEnabled) {
let cmd = doc.getElementById(id);
if (isEnabled) {
cmd.removeAttribute("disabled");
cmd.removeAttribute("hidden");
} else {
cmd.setAttribute("disabled", "true");
cmd.setAttribute("hidden", "true");
}
};
// Enable developer toolbar?
let devToolbarEnabled = Services.prefs.getBoolPref("devtools.toolbar.enabled");
toggleCmd("Tools:DevToolbar", devToolbarEnabled);
let focusEl = doc.getElementById("Tools:DevToolbarFocus");
if (devToolbarEnabled) {
focusEl.removeAttribute("disabled");
} else {
focusEl.setAttribute("disabled", "true");
}
if (devToolbarEnabled && Services.prefs.getBoolPref("devtools.toolbar.visible")) {
win.DeveloperToolbar.show(false).catch(console.error);
}
// Enable WebIDE?
let webIDEEnabled = Services.prefs.getBoolPref("devtools.webide.enabled");
toggleCmd("Tools:WebIDE", webIDEEnabled);
let showWebIDEWidget = Services.prefs.getBoolPref("devtools.webide.widget.enabled");
if (webIDEEnabled && showWebIDEWidget) {
gDevToolsBrowser.installWebIDEWidget();
} else {
gDevToolsBrowser.uninstallWebIDEWidget();
}
// Enable Browser Toolbox?
let chromeEnabled = Services.prefs.getBoolPref("devtools.chrome.enabled");
let devtoolsRemoteEnabled = Services.prefs.getBoolPref("devtools.debugger.remote-enabled");
let remoteEnabled = chromeEnabled && devtoolsRemoteEnabled;
toggleCmd("Tools:BrowserToolbox", remoteEnabled);
toggleCmd("Tools:BrowserContentToolbox", remoteEnabled && win.gMultiProcessBrowser);
// Enable Error Console?
let consoleEnabled = Services.prefs.getBoolPref("devtools.errorconsole.enabled");
toggleCmd("Tools:ErrorConsole", consoleEnabled);
// Enable DevTools connection screen, if the preference allows this.
toggleCmd("Tools:DevToolsConnect", devtoolsRemoteEnabled);
},
observe: function(subject, topic, prefName) {
if (prefName.endsWith("enabled")) {
for (let win of this._trackedBrowserWindows) {
this.updateCommandAvailability(win);
}
}
},
_prefObserverRegistered: false,
ensurePrefObserver: function() {
if (!this._prefObserverRegistered) {
this._prefObserverRegistered = true;
Services.prefs.addObserver("devtools.", this, false);
}
},
/**
* This function is for the benefit of Tools:{toolId} commands,
* triggered from the WebDeveloper menu and keyboard shortcuts.
*
* selectToolCommand's behavior:
* - if the toolbox is closed,
* we open the toolbox and select the tool
* - if the toolbox is open, and the targeted tool is not selected,
* we select it
* - if the toolbox is open, and the targeted tool is selected,
* and the host is NOT a window, we close the toolbox
* - if the toolbox is open, and the targeted tool is selected,
* and the host is a window, we raise the toolbox window
*/
// Used when: - registering a new tool
// - new xul window, to add menu items
selectToolCommand: function(gBrowser, toolId) {
let target = TargetFactory.forTab(gBrowser.selectedTab);
let toolbox = gDevTools.getToolbox(target);
let toolDefinition = gDevTools.getToolDefinition(toolId);
if (toolbox &&
(toolbox.currentToolId == toolId ||
(toolId == "webconsole" && toolbox.splitConsole)))
{
toolbox.fireCustomKey(toolId);
if (toolDefinition.preventClosingOnKey || toolbox.hostType == Toolbox.HostType.WINDOW) {
toolbox.raise();
} else {
toolbox.destroy();
}
gDevTools.emit("select-tool-command", toolId);
} else {
gDevTools.showToolbox(target, toolId).then(() => {
let target = TargetFactory.forTab(gBrowser.selectedTab);
let toolbox = gDevTools.getToolbox(target);
toolbox.fireCustomKey(toolId);
gDevTools.emit("select-tool-command", toolId);
});
}
},
/**
* Open a tab to allow connects to a remote browser
*/
// Used by browser-sets.inc, command
openConnectScreen: function(gBrowser) {
gBrowser.selectedTab = gBrowser.addTab("chrome://devtools/content/framework/connect/connect.xhtml");
},
/**
* Open WebIDE
*/
// Used by browser-sets.inc, command
// itself, webide widget
openWebIDE: function() {
let win = Services.wm.getMostRecentWindow("devtools:webide");
if (win) {
win.focus();
} else {
Services.ww.openWindow(null, "chrome://webide/content/", "webide", "chrome,centerscreen,resizable", null);
}
},
_getContentProcessTarget: function () {
// Create a DebuggerServer in order to connect locally to it
if (!DebuggerServer.initialized) {
DebuggerServer.init();
DebuggerServer.addBrowserActors();
}
DebuggerServer.allowChromeProcess = true;
let transport = DebuggerServer.connectPipe();
let client = new DebuggerClient(transport);
let deferred = promise.defer();
client.connect().then(() => {
client.mainRoot.listProcesses(response => {
// Do nothing if there is only one process, the parent process.
let contentProcesses = response.processes.filter(p => (!p.parent));
if (contentProcesses.length < 1) {
let msg = bundle.GetStringFromName("toolbox.noContentProcess.message");
Services.prompt.alert(null, "", msg);
deferred.reject("No content processes available.");
return;
}
// Otherwise, arbitrary connect to the unique content process.
client.getProcess(contentProcesses[0].id)
.then(response => {
let options = {
form: response.form,
client: client,
chrome: true,
isTabActor: false
};
return TargetFactory.forRemoteTab(options);
})
.then(target => {
// Ensure closing the connection in order to cleanup
// the debugger client and also the server created in the
// content process
target.on("close", () => {
client.close();
});
deferred.resolve(target);
});
});
});
return deferred.promise;
},
// Used by browser-sets.inc, command
openContentProcessToolbox: function () {
this._getContentProcessTarget()
.then(target => {
// Display a new toolbox, in a new window, with debugger by default
return gDevTools.showToolbox(target, "jsdebugger",
Toolbox.HostType.WINDOW);
});
},
/**
* Install WebIDE widget
*/
// Used by itself
installWebIDEWidget: function() {
if (this.isWebIDEWidgetInstalled()) {
return;
}
let defaultArea;
if (Services.prefs.getBoolPref("devtools.webide.widget.inNavbarByDefault")) {
defaultArea = CustomizableUI.AREA_NAVBAR;
} else {
defaultArea = CustomizableUI.AREA_PANEL;
}
CustomizableUI.createWidget({
id: "webide-button",
shortcutId: "key_webide",
label: "devtools-webide-button2.label",
tooltiptext: "devtools-webide-button2.tooltiptext",
defaultArea: defaultArea,
onCommand: function(aEvent) {
gDevToolsBrowser.openWebIDE();
}
});
},
isWebIDEWidgetInstalled: function() {
let widgetWrapper = CustomizableUI.getWidget("webide-button");
return !!(widgetWrapper && widgetWrapper.provider == CustomizableUI.PROVIDER_API);
},
/**
* The deferred promise will be resolved by WebIDE's UI.init()
*/
isWebIDEInitialized: promise.defer(),
/**
* Uninstall WebIDE widget
*/
uninstallWebIDEWidget: function() {
if (this.isWebIDEWidgetInstalled()) {
CustomizableUI.removeWidgetFromArea("webide-button");
}
CustomizableUI.destroyWidget("webide-button");
},
/**
* Move WebIDE widget to the navbar
*/
// Used by webide.js
moveWebIDEWidgetInNavbar: function() {
CustomizableUI.addWidgetToArea("webide-button", CustomizableUI.AREA_NAVBAR);
},
/**
* Add this DevTools's presence to a browser window's document
*
* @param {XULDocument} doc
* The document to which menuitems and handlers are to be added
*/
// Used by browser.js
registerBrowserWindow: function DT_registerBrowserWindow(win) {
this.updateCommandAvailability(win);
this.ensurePrefObserver();
gDevToolsBrowser._trackedBrowserWindows.add(win);
gDevToolsBrowser._addAllToolsToMenu(win.document);
if (this._isFirebugInstalled()) {
let broadcaster = win.document.getElementById("devtoolsMenuBroadcaster_DevToolbox");
broadcaster.removeAttribute("key");
}
let tabContainer = win.gBrowser.tabContainer;
tabContainer.addEventListener("TabSelect", this, false);
tabContainer.addEventListener("TabOpen", this, false);
tabContainer.addEventListener("TabClose", this, false);
tabContainer.addEventListener("TabPinned", this, false);
tabContainer.addEventListener("TabUnpinned", this, false);
},
/**
* Add a <key> to <keyset id="devtoolsKeyset">.
* Appending a <key> element is not always enough. The <keyset> needs
* to be detached and reattached to make sure the <key> is taken into
* account (see bug 832984).
*
* @param {XULDocument} doc
* The document to which keys are to be added
* @param {XULElement} or {DocumentFragment} keys
* Keys to add
*/
attachKeybindingsToBrowser: function DT_attachKeybindingsToBrowser(doc, keys) {
let devtoolsKeyset = doc.getElementById("devtoolsKeyset");
if (!devtoolsKeyset) {
devtoolsKeyset = doc.createElement("keyset");
devtoolsKeyset.setAttribute("id", "devtoolsKeyset");
}
devtoolsKeyset.appendChild(keys);
let mainKeyset = doc.getElementById("mainKeyset");
mainKeyset.parentNode.insertBefore(devtoolsKeyset, mainKeyset);
},
/**
* Hook the JS debugger tool to the "Debug Script" button of the slow script
* dialog.
*/
setSlowScriptDebugHandler: function DT_setSlowScriptDebugHandler() {
let debugService = Cc["@mozilla.org/dom/slow-script-debug;1"]
.getService(Ci.nsISlowScriptDebug);
let tm = Cc["@mozilla.org/thread-manager;1"].getService(Ci.nsIThreadManager);
function slowScriptDebugHandler(aTab, aCallback) {
let target = TargetFactory.forTab(aTab);
gDevTools.showToolbox(target, "jsdebugger").then(toolbox => {
let threadClient = toolbox.getCurrentPanel().panelWin.gThreadClient;
// Break in place, which means resuming the debuggee thread and pausing
// right before the next step happens.
switch (threadClient.state) {
case "paused":
// When the debugger is already paused.
threadClient.resumeThenPause();
aCallback();
break;
case "attached":
// When the debugger is already open.
threadClient.interrupt(() => {
threadClient.resumeThenPause();
aCallback();
});
break;
case "resuming":
// The debugger is newly opened.
threadClient.addOneTimeListener("resumed", () => {
threadClient.interrupt(() => {
threadClient.resumeThenPause();
aCallback();
});
});
break;
default:
throw Error("invalid thread client state in slow script debug handler: " +
threadClient.state);
}
});
}
debugService.activationHandler = function(aWindow) {
let chromeWindow = aWindow.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIWebNavigation)
.QueryInterface(Ci.nsIDocShellTreeItem)
.rootTreeItem
.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindow)
.QueryInterface(Ci.nsIDOMChromeWindow);
let setupFinished = false;
slowScriptDebugHandler(chromeWindow.gBrowser.selectedTab,
() => { setupFinished = true; });
// Don't return from the interrupt handler until the debugger is brought
// up; no reason to continue executing the slow script.
let utils = aWindow.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils);
utils.enterModalState();
while (!setupFinished) {
tm.currentThread.processNextEvent(true);
}
utils.leaveModalState();
};
debugService.remoteActivationHandler = function(aBrowser, aCallback) {
let chromeWindow = aBrowser.ownerDocument.defaultView;
let tab = chromeWindow.gBrowser.getTabForBrowser(aBrowser);
chromeWindow.gBrowser.selected = tab;
function callback() {
aCallback.finishDebuggerStartup();
}
slowScriptDebugHandler(tab, callback);
};
},
/**
* Unset the slow script debug handler.
*/
unsetSlowScriptDebugHandler: function DT_unsetSlowScriptDebugHandler() {
let debugService = Cc["@mozilla.org/dom/slow-script-debug;1"]
.getService(Ci.nsISlowScriptDebug);
debugService.activationHandler = undefined;
},
/**
* Detect the presence of a Firebug.
*
* @return promise
*/
_isFirebugInstalled: function DT_isFirebugInstalled() {
let bootstrappedAddons = Services.prefs.getCharPref("extensions.bootstrappedAddons");
return bootstrappedAddons.indexOf("firebug@software.joehewitt.com") != -1;
},
/**
* Add the menuitem for a tool to all open browser windows.
*
* @param {object} toolDefinition
* properties of the tool to add
*/
_addToolToWindows: function DT_addToolToWindows(toolDefinition) {
// No menu item or global shortcut is required for options panel.
if (!toolDefinition.inMenu) {
return;
}
// Skip if the tool is disabled.
try {
if (toolDefinition.visibilityswitch &&
!Services.prefs.getBoolPref(toolDefinition.visibilityswitch)) {
return;
}
} catch(e) {}
// We need to insert the new tool in the right place, which means knowing
// the tool that comes before the tool that we're trying to add
let allDefs = gDevTools.getToolDefinitionArray();
let prevDef;
for (let def of allDefs) {
if (!def.inMenu) {
continue;
}
if (def === toolDefinition) {
break;
}
prevDef = def;
}
for (let win of gDevToolsBrowser._trackedBrowserWindows) {
let doc = win.document;
let elements = gDevToolsBrowser._createToolMenuElements(toolDefinition, doc);
doc.getElementById("mainCommandSet").appendChild(elements.cmd);
if (elements.key) {
this.attachKeybindingsToBrowser(doc, elements.key);
}
doc.getElementById("mainBroadcasterSet").appendChild(elements.bc);
let amp = doc.getElementById("appmenu_webDeveloper_popup");
if (amp) {
let ref;
if (prevDef != null) {
let menuitem = doc.getElementById("appmenuitem_" + prevDef.id);
ref = menuitem && menuitem.nextSibling ? menuitem.nextSibling : null;
} else {
ref = doc.getElementById("appmenu_devtools_separator");
}
if (ref) {
amp.insertBefore(elements.appmenuitem, ref);
}
}
let ref;
if (prevDef) {
let menuitem = doc.getElementById("menuitem_" + prevDef.id);
ref = menuitem && menuitem.nextSibling ? menuitem.nextSibling : null;
} else {
ref = doc.getElementById("menu_devtools_separator");
}
if (ref) {
ref.parentNode.insertBefore(elements.menuitem, ref);
}
}
if (toolDefinition.id === "jsdebugger") {
gDevToolsBrowser.setSlowScriptDebugHandler();
}
},
/**
* Add all tools to the developer tools menu of a window.
*
* @param {XULDocument} doc
* The document to which the tool items are to be added.
*/
_addAllToolsToMenu: function DT_addAllToolsToMenu(doc) {
let fragCommands = doc.createDocumentFragment();
let fragKeys = doc.createDocumentFragment();
let fragBroadcasters = doc.createDocumentFragment();
let fragAppMenuItems = doc.createDocumentFragment();
let fragMenuItems = doc.createDocumentFragment();
for (let toolDefinition of gDevTools.getToolDefinitionArray()) {
if (!toolDefinition.inMenu) {
continue;
}
let elements = gDevToolsBrowser._createToolMenuElements(toolDefinition, doc);
if (!elements) {
return;
}
fragCommands.appendChild(elements.cmd);
if (elements.key) {
fragKeys.appendChild(elements.key);
}
fragBroadcasters.appendChild(elements.bc);
fragAppMenuItems.appendChild(elements.appmenuitem);
fragMenuItems.appendChild(elements.menuitem);
}
let mcs = doc.getElementById("mainCommandSet");
mcs.appendChild(fragCommands);
this.attachKeybindingsToBrowser(doc, fragKeys);
let mbs = doc.getElementById("mainBroadcasterSet");
mbs.appendChild(fragBroadcasters);
let amps = doc.getElementById("appmenu_devtools_separator");
if (amps) {
amps.parentNode.insertBefore(fragAppMenuItems, amps);
}
let mps = doc.getElementById("menu_devtools_separator");
if (mps) {
mps.parentNode.insertBefore(fragMenuItems, mps);
}
},
/**
* Add a menu entry for a tool definition
*
* @param {string} toolDefinition
* Tool definition of the tool to add a menu entry.
* @param {XULDocument} doc
* The document to which the tool menu item is to be added.
*/
_createToolMenuElements: function DT_createToolMenuElements(toolDefinition, doc) {
let id = toolDefinition.id;
// Prevent multiple entries for the same tool.
if (doc.getElementById("Tools:" + id)) {
return;
}
let cmd = doc.createElement("command");
cmd.id = "Tools:" + id;
cmd.setAttribute("oncommand",
'gDevToolsBrowser.selectToolCommand(gBrowser, "' + id + '");');
let key = null;
if (toolDefinition.key) {
key = doc.createElement("key");
key.id = "key_" + id;
if (toolDefinition.key.startsWith("VK_")) {
key.setAttribute("keycode", toolDefinition.key);
} else {
key.setAttribute("key", toolDefinition.key);
}
key.setAttribute("command", cmd.id);
key.setAttribute("modifiers", toolDefinition.modifiers);
}
let bc = doc.createElement("broadcaster");
bc.id = "devtoolsMenuBroadcaster_" + id;
bc.setAttribute("label", toolDefinition.menuLabel || toolDefinition.label);
bc.setAttribute("command", cmd.id);
if (key) {
bc.setAttribute("key", "key_" + id);
}
let appmenuitem = doc.createElement("menuitem");
appmenuitem.id = "appmenuitem_" + id;
appmenuitem.setAttribute("observes", "devtoolsMenuBroadcaster_" + id);
let menuitem = doc.createElement("menuitem");
menuitem.id = "menuitem_" + id;
menuitem.setAttribute("observes", "devtoolsMenuBroadcaster_" + id);
if (toolDefinition.accesskey) {
menuitem.setAttribute("accesskey", toolDefinition.accesskey);
}
return {
cmd: cmd,
key: key,
bc: bc,
appmenuitem: appmenuitem,
menuitem: menuitem
};
},
hasToolboxOpened: function(win) {
let tab = win.gBrowser.selectedTab;
for (let [target, toolbox] of gDevTools._toolboxes) {
if (target.tab == tab) {
return true;
}
}
return false;
},
/**
* Update the "Toggle Tools" checkbox in the developer tools menu. This is
* called when a toolbox is created or destroyed.
*/
_updateMenuCheckbox: function DT_updateMenuCheckbox() {
for (let win of gDevToolsBrowser._trackedBrowserWindows) {
let hasToolbox = gDevToolsBrowser.hasToolboxOpened(win);
let broadcaster = win.document.getElementById("devtoolsMenuBroadcaster_DevToolbox");
if (hasToolbox) {
broadcaster.setAttribute("checked", "true");
} else {
broadcaster.removeAttribute("checked");
}
}
},
/**
* Remove the menuitem for a tool to all open browser windows.
*
* @param {string} toolId
* id of the tool to remove
*/
_removeToolFromWindows: function DT_removeToolFromWindows(toolId) {
for (let win of gDevToolsBrowser._trackedBrowserWindows) {
gDevToolsBrowser._removeToolFromMenu(toolId, win.document);
}
if (toolId === "jsdebugger") {
gDevToolsBrowser.unsetSlowScriptDebugHandler();
}
},
/**
* Remove a tool's menuitem from a window
*
* @param {string} toolId
* Id of the tool to add a menu entry for
* @param {XULDocument} doc
* The document to which the tool menu item is to be removed from
*/
_removeToolFromMenu: function DT_removeToolFromMenu(toolId, doc) {
let command = doc.getElementById("Tools:" + toolId);
if (command) {
command.parentNode.removeChild(command);
}
let key = doc.getElementById("key_" + toolId);
if (key) {
key.parentNode.removeChild(key);
}
let bc = doc.getElementById("devtoolsMenuBroadcaster_" + toolId);
if (bc) {
bc.parentNode.removeChild(bc);
}
let appmenuitem = doc.getElementById("appmenuitem_" + toolId);
if (appmenuitem) {
appmenuitem.parentNode.removeChild(appmenuitem);
}
let menuitem = doc.getElementById("menuitem_" + toolId);
if (menuitem) {
menuitem.parentNode.removeChild(menuitem);
}
},
/**
* Called on browser unload to remove menu entries, toolboxes and event
* listeners from the closed browser window.
*
* @param {XULWindow} win
* The window containing the menu entry
*/
forgetBrowserWindow: function DT_forgetBrowserWindow(win) {
gDevToolsBrowser._trackedBrowserWindows.delete(win);
// Destroy toolboxes for closed window
for (let [target, toolbox] of gDevTools._toolboxes) {
if (toolbox.frame && toolbox.frame.ownerDocument.defaultView == win) {
toolbox.destroy();
}
}
let tabContainer = win.gBrowser.tabContainer;
tabContainer.removeEventListener("TabSelect", this, false);
tabContainer.removeEventListener("TabOpen", this, false);
tabContainer.removeEventListener("TabClose", this, false);
tabContainer.removeEventListener("TabPinned", this, false);
tabContainer.removeEventListener("TabUnpinned", this, false);
},
handleEvent: function(event) {
switch (event.type) {
case "TabOpen":
case "TabClose":
case "TabPinned":
case "TabUnpinned":
let open = 0;
let pinned = 0;
for (let win of this._trackedBrowserWindows) {
let tabContainer = win.gBrowser.tabContainer;
let numPinnedTabs = win.gBrowser._numPinnedTabs || 0;
let numTabs = tabContainer.itemCount - numPinnedTabs;
open += numTabs;
pinned += numPinnedTabs;
}
this._tabStats.histOpen.push(open);
this._tabStats.histPinned.push(pinned);
this._tabStats.peakOpen = Math.max(open, this._tabStats.peakOpen);
this._tabStats.peakPinned = Math.max(pinned, this._tabStats.peakPinned);
break;
case "TabSelect":
gDevToolsBrowser._updateMenuCheckbox();
}
},
/**
* All browser windows have been closed, tidy up remaining objects.
*/
destroy: function() {
Services.prefs.removeObserver("devtools.", gDevToolsBrowser);
Services.obs.removeObserver(gDevToolsBrowser.destroy, "quit-application");
},
}
gDevTools.on("tool-registered", function(ev, toolId) {
let toolDefinition = gDevTools._tools.get(toolId);
gDevToolsBrowser._addToolToWindows(toolDefinition);
});
gDevTools.on("tool-unregistered", function(ev, toolId) {
if (typeof toolId != "string") {
toolId = toolId.id;
}
gDevToolsBrowser._removeToolFromWindows(toolId);
});
gDevTools.on("toolbox-ready", gDevToolsBrowser._updateMenuCheckbox);
gDevTools.on("toolbox-destroyed", gDevToolsBrowser._updateMenuCheckbox);
Services.obs.addObserver(gDevToolsBrowser.destroy, "quit-application", false);
// Load the browser devtools main module as the loader's main module.
// This is done precisely here as main.js ends up dispatching the
// tool-registered events we are listening in this module.
loader.main("devtools/client/main");

View File

@ -0,0 +1,511 @@
/* 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/. */
"use strict";
const Services = require("Services");
const promise = require("promise");
// Load gDevToolsBrowser toolbox lazily as they need gDevTools to be fully initialized
loader.lazyRequireGetter(this, "Toolbox", "devtools/client/framework/toolbox", true);
loader.lazyRequireGetter(this, "gDevToolsBrowser", "devtools/client/framework/devtools-browser", true);
const {defaultTools: DefaultTools, defaultThemes: DefaultThemes} =
require("devtools/client/definitions");
const EventEmitter = require("devtools/shared/event-emitter");
const Telemetry = require("devtools/client/shared/telemetry");
const {JsonView} = require("devtools/client/jsonview/main");
const TABS_OPEN_PEAK_HISTOGRAM = "DEVTOOLS_TABS_OPEN_PEAK_LINEAR";
const TABS_OPEN_AVG_HISTOGRAM = "DEVTOOLS_TABS_OPEN_AVERAGE_LINEAR";
const TABS_PINNED_PEAK_HISTOGRAM = "DEVTOOLS_TABS_PINNED_PEAK_LINEAR";
const TABS_PINNED_AVG_HISTOGRAM = "DEVTOOLS_TABS_PINNED_AVERAGE_LINEAR";
const FORBIDDEN_IDS = new Set(["toolbox", ""]);
const MAX_ORDINAL = 99;
/**
* DevTools is a class that represents a set of developer tools, it holds a
* set of tools and keeps track of open toolboxes in the browser.
*/
this.DevTools = function DevTools() {
this._tools = new Map(); // Map<toolId, tool>
this._themes = new Map(); // Map<themeId, theme>
this._toolboxes = new Map(); // Map<target, toolbox>
this._telemetry = new Telemetry();
// destroy() is an observer's handler so we need to preserve context.
this.destroy = this.destroy.bind(this);
this._teardown = this._teardown.bind(this);
// JSON Viewer for 'application/json' documents.
JsonView.initialize();
EventEmitter.decorate(this);
Services.obs.addObserver(this._teardown, "devtools-unloaded", false);
Services.obs.addObserver(this.destroy, "quit-application", false);
};
DevTools.prototype = {
/**
* Register a new developer tool.
*
* A definition is a light object that holds different information about a
* developer tool. This object is not supposed to have any operational code.
* See it as a "manifest".
* The only actual code lives in the build() function, which will be used to
* start an instance of this tool.
*
* Each toolDefinition has the following properties:
* - id: Unique identifier for this tool (string|required)
* - visibilityswitch: Property name to allow us to hide this tool from the
* DevTools Toolbox.
* A falsy value indicates that it cannot be hidden.
* - icon: URL pointing to a graphic which will be used as the src for an
* 16x16 img tag (string|required)
* - invertIconForLightTheme: The icon can automatically have an inversion
* filter applied (default is false). All builtin tools are true, but
* addons may omit this to prevent unwanted changes to the `icon`
* image. filter: invert(1) is applied to the image (boolean|optional)
* - url: URL pointing to a XUL/XHTML document containing the user interface
* (string|required)
* - label: Localized name for the tool to be displayed to the user
* (string|required)
* - hideInOptions: Boolean indicating whether or not this tool should be
shown in toolbox options or not. Defaults to false.
* (boolean)
* - build: Function that takes an iframe, which has been populated with the
* markup from |url|, and also the toolbox containing the panel.
* And returns an instance of ToolPanel (function|required)
*/
registerTool: function DT_registerTool(toolDefinition) {
let toolId = toolDefinition.id;
if (!toolId || FORBIDDEN_IDS.has(toolId)) {
throw new Error("Invalid definition.id");
}
// Make sure that additional tools will always be able to be hidden.
// When being called from main.js, defaultTools has not yet been exported.
// But, we can assume that in this case, it is a default tool.
if (DefaultTools && DefaultTools.indexOf(toolDefinition) == -1) {
toolDefinition.visibilityswitch = "devtools." + toolId + ".enabled";
}
this._tools.set(toolId, toolDefinition);
this.emit("tool-registered", toolId);
},
/**
* Removes all tools that match the given |toolId|
* Needed so that add-ons can remove themselves when they are deactivated
*
* @param {string|object} tool
* Definition or the id of the tool to unregister. Passing the
* tool id should be avoided as it is a temporary measure.
* @param {boolean} isQuitApplication
* true to indicate that the call is due to app quit, so we should not
* cause a cascade of costly events
*/
unregisterTool: function DT_unregisterTool(tool, isQuitApplication) {
let toolId = null;
if (typeof tool == "string") {
toolId = tool;
tool = this._tools.get(tool);
}
else {
toolId = tool.id;
}
this._tools.delete(toolId);
if (!isQuitApplication) {
this.emit("tool-unregistered", tool);
}
},
/**
* Sorting function used for sorting tools based on their ordinals.
*/
ordinalSort: function DT_ordinalSort(d1, d2) {
let o1 = (typeof d1.ordinal == "number") ? d1.ordinal : MAX_ORDINAL;
let o2 = (typeof d2.ordinal == "number") ? d2.ordinal : MAX_ORDINAL;
return o1 - o2;
},
getDefaultTools: function DT_getDefaultTools() {
return DefaultTools.sort(this.ordinalSort);
},
getAdditionalTools: function DT_getAdditionalTools() {
let tools = [];
for (let [key, value] of this._tools) {
if (DefaultTools.indexOf(value) == -1) {
tools.push(value);
}
}
return tools.sort(this.ordinalSort);
},
/**
* Get a tool definition if it exists and is enabled.
*
* @param {string} toolId
* The id of the tool to show
*
* @return {ToolDefinition|null} tool
* The ToolDefinition for the id or null.
*/
getToolDefinition: function DT_getToolDefinition(toolId) {
let tool = this._tools.get(toolId);
if (!tool) {
return null;
} else if (!tool.visibilityswitch) {
return tool;
}
let enabled;
try {
enabled = Services.prefs.getBoolPref(tool.visibilityswitch);
} catch (e) {
enabled = true;
}
return enabled ? tool : null;
},
/**
* Allow ToolBoxes to get at the list of tools that they should populate
* themselves with.
*
* @return {Map} tools
* A map of the the tool definitions registered in this instance
*/
getToolDefinitionMap: function DT_getToolDefinitionMap() {
let tools = new Map();
for (let [id, definition] of this._tools) {
if (this.getToolDefinition(id)) {
tools.set(id, definition);
}
}
return tools;
},
/**
* Tools have an inherent ordering that can't be represented in a Map so
* getToolDefinitionArray provides an alternative representation of the
* definitions sorted by ordinal value.
*
* @return {Array} tools
* A sorted array of the tool definitions registered in this instance
*/
getToolDefinitionArray: function DT_getToolDefinitionArray() {
let definitions = [];
for (let [id, definition] of this._tools) {
if (this.getToolDefinition(id)) {
definitions.push(definition);
}
}
return definitions.sort(this.ordinalSort);
},
/**
* Register a new theme for developer tools toolbox.
*
* A definition is a light object that holds various information about a
* theme.
*
* Each themeDefinition has the following properties:
* - id: Unique identifier for this theme (string|required)
* - label: Localized name for the theme to be displayed to the user
* (string|required)
* - stylesheets: Array of URLs pointing to a CSS document(s) containing
* the theme style rules (array|required)
* - classList: Array of class names identifying the theme within a document.
* These names are set to document element when applying
* the theme (array|required)
* - onApply: Function that is executed by the framework when the theme
* is applied. The function takes the current iframe window
* and the previous theme id as arguments (function)
* - onUnapply: Function that is executed by the framework when the theme
* is unapplied. The function takes the current iframe window
* and the new theme id as arguments (function)
*/
registerTheme: function DT_registerTheme(themeDefinition) {
let themeId = themeDefinition.id;
if (!themeId) {
throw new Error("Invalid theme id");
}
if (this._themes.get(themeId)) {
throw new Error("Theme with the same id is already registered");
}
this._themes.set(themeId, themeDefinition);
this.emit("theme-registered", themeId);
},
/**
* Removes an existing theme from the list of registered themes.
* Needed so that add-ons can remove themselves when they are deactivated
*
* @param {string|object} theme
* Definition or the id of the theme to unregister.
*/
unregisterTheme: function DT_unregisterTheme(theme) {
let themeId = null;
if (typeof theme == "string") {
themeId = theme;
theme = this._themes.get(theme);
}
else {
themeId = theme.id;
}
let currTheme = Services.prefs.getCharPref("devtools.theme");
// Note that we can't check if `theme` is an item
// of `DefaultThemes` as we end up reloading definitions
// module and end up with different theme objects
let isCoreTheme = DefaultThemes.some(t => t.id === themeId);
// Reset the theme if an extension theme that's currently applied
// is being removed.
// Ignore shutdown since addons get disabled during that time.
if (!Services.startup.shuttingDown &&
!isCoreTheme &&
theme.id == currTheme) {
Services.prefs.setCharPref("devtools.theme", "light");
let data = {
pref: "devtools.theme",
newValue: "light",
oldValue: currTheme
};
this.emit("pref-changed", data);
this.emit("theme-unregistered", theme);
}
this._themes.delete(themeId);
},
/**
* Get a theme definition if it exists.
*
* @param {string} themeId
* The id of the theme
*
* @return {ThemeDefinition|null} theme
* The ThemeDefinition for the id or null.
*/
getThemeDefinition: function DT_getThemeDefinition(themeId) {
let theme = this._themes.get(themeId);
if (!theme) {
return null;
}
return theme;
},
/**
* Get map of registered themes.
*
* @return {Map} themes
* A map of the the theme definitions registered in this instance
*/
getThemeDefinitionMap: function DT_getThemeDefinitionMap() {
let themes = new Map();
for (let [id, definition] of this._themes) {
if (this.getThemeDefinition(id)) {
themes.set(id, definition);
}
}
return themes;
},
/**
* Get registered themes definitions sorted by ordinal value.
*
* @return {Array} themes
* A sorted array of the theme definitions registered in this instance
*/
getThemeDefinitionArray: function DT_getThemeDefinitionArray() {
let definitions = [];
for (let [id, definition] of this._themes) {
if (this.getThemeDefinition(id)) {
definitions.push(definition);
}
}
return definitions.sort(this.ordinalSort);
},
/**
* Show a Toolbox for a target (either by creating a new one, or if a toolbox
* already exists for the target, by bring to the front the existing one)
* If |toolId| is specified then the displayed toolbox will have the
* specified tool selected.
* If |hostType| is specified then the toolbox will be displayed using the
* specified HostType.
*
* @param {Target} target
* The target the toolbox will debug
* @param {string} toolId
* The id of the tool to show
* @param {Toolbox.HostType} hostType
* The type of host (bottom, window, side)
* @param {object} hostOptions
* Options for host specifically
*
* @return {Toolbox} toolbox
* The toolbox that was opened
*/
showToolbox: function(target, toolId, hostType, hostOptions) {
let deferred = promise.defer();
let toolbox = this._toolboxes.get(target);
if (toolbox) {
let hostPromise = (hostType != null && toolbox.hostType != hostType) ?
toolbox.switchHost(hostType) :
promise.resolve(null);
if (toolId != null && toolbox.currentToolId != toolId) {
hostPromise = hostPromise.then(function() {
return toolbox.selectTool(toolId);
});
}
return hostPromise.then(function() {
toolbox.raise();
return toolbox;
});
}
else {
// No toolbox for target, create one
toolbox = new Toolbox(target, toolId, hostType, hostOptions);
this.emit("toolbox-created", toolbox);
this._toolboxes.set(target, toolbox);
toolbox.once("destroy", () => {
this.emit("toolbox-destroy", target);
});
toolbox.once("destroyed", () => {
this._toolboxes.delete(target);
this.emit("toolbox-destroyed", target);
});
// If toolId was passed in, it will already be selected before the
// open promise resolves.
toolbox.open().then(() => {
deferred.resolve(toolbox);
this.emit("toolbox-ready", toolbox);
});
}
return deferred.promise;
},
/**
* Return the toolbox for a given target.
*
* @param {object} target
* Target value e.g. the target that owns this toolbox
*
* @return {Toolbox} toolbox
* The toolbox that is debugging the given target
*/
getToolbox: function DT_getToolbox(target) {
return this._toolboxes.get(target);
},
/**
* Close the toolbox for a given target
*
* @return promise
* This promise will resolve to false if no toolbox was found
* associated to the target. true, if the toolbox was successfully
* closed.
*/
closeToolbox: function DT_closeToolbox(target) {
let toolbox = this._toolboxes.get(target);
if (toolbox == null) {
return promise.resolve(false);
}
return toolbox.destroy().then(() => true);
},
_pingTelemetry: function() {
let mean = function(arr) {
if (arr.length === 0) {
return 0;
}
let total = arr.reduce((a, b) => a + b);
return Math.ceil(total / arr.length);
};
let tabStats = gDevToolsBrowser._tabStats;
this._telemetry.log(TABS_OPEN_PEAK_HISTOGRAM, tabStats.peakOpen);
this._telemetry.log(TABS_OPEN_AVG_HISTOGRAM, mean(tabStats.histOpen));
this._telemetry.log(TABS_PINNED_PEAK_HISTOGRAM, tabStats.peakPinned);
this._telemetry.log(TABS_PINNED_AVG_HISTOGRAM, mean(tabStats.histPinned));
},
/**
* Called to tear down a tools provider.
*/
_teardown: function DT_teardown() {
for (let [target, toolbox] of this._toolboxes) {
toolbox.destroy();
}
},
/**
* All browser windows have been closed, tidy up remaining objects.
*/
destroy: function() {
Services.obs.removeObserver(this.destroy, "quit-application");
Services.obs.removeObserver(this._teardown, "devtools-unloaded");
for (let [key, tool] of this.getToolDefinitionMap()) {
this.unregisterTool(key, true);
}
JsonView.destroy();
this._pingTelemetry();
this._telemetry = null;
// Cleaning down the toolboxes: i.e.
// for (let [target, toolbox] of this._toolboxes) toolbox.destroy();
// Is taken care of by the gDevToolsBrowser.forgetBrowserWindow
},
/**
* Iterator that yields each of the toolboxes.
*/
*[Symbol.iterator]() {
for (let toolbox of this._toolboxes) {
yield toolbox;
}
}
};
exports.gDevTools = new DevTools();

File diff suppressed because it is too large Load Diff

View File

@ -11,6 +11,8 @@ TEST_HARNESS_FILES.xpcshell.devtools.client.framework.test += [
DevToolsModules(
'attach-thread.js',
'devtools-browser.js',
'devtools.js',
'gDevTools.jsm',
'selection.js',
'sidebar.js',

View File

@ -5,8 +5,12 @@
var { classes: Cc, interfaces: Ci, utils: Cu } = Components;
var { gDevTools } = Cu.import("resource://devtools/client/framework/gDevTools.jsm", {});
var { require } = Cu.import("resource://devtools/shared/Loader.jsm", {});
var { loader, require } = Cu.import("resource://devtools/shared/Loader.jsm", {});
// Require this module just to setup things like themes and tools
// devtools-browser is special as it loads main module
// To be cleaned up in bug 1247203.
require("devtools/client/framework/devtools-browser");
var { gDevTools } = require("devtools/client/framework/devtools");
var { TargetFactory } = require("devtools/client/framework/target");
var { Toolbox } = require("devtools/client/framework/toolbox");
var { Services } = Cu.import("resource://gre/modules/Services.jsm", {});

View File

@ -17,6 +17,9 @@ createEnum([
// Add an additional viewport to display the document.
"ADD_VIEWPORT",
// Rotate the viewport.
"ROTATE_VIEWPORT",
], module.exports);
/**

View File

@ -4,7 +4,7 @@
"use strict";
const { ADD_VIEWPORT } = require("./index");
const { ADD_VIEWPORT, ROTATE_VIEWPORT } = require("./index");
module.exports = {
@ -17,4 +17,14 @@ module.exports = {
};
},
/**
* Rotate the viewport.
*/
rotateViewport(id) {
return {
type: ROTATE_VIEWPORT,
id,
};
},
};

View File

@ -8,6 +8,7 @@ const { createClass, createFactory, PropTypes } =
require("devtools/client/shared/vendor/react");
const { connect } = require("devtools/client/shared/vendor/react-redux");
const { rotateViewport } = require("./actions/viewports");
const Types = require("./types");
const Viewports = createFactory(require("./components/viewports"));
@ -22,6 +23,7 @@ let App = createClass({
render() {
let {
dispatch,
location,
viewports,
} = this.props;
@ -31,6 +33,7 @@ let App = createClass({
return Viewports({
location,
viewports,
onRotateViewport: id => dispatch(rotateViewport(id)),
});
},

View File

@ -6,6 +6,7 @@
DevToolsModules(
'browser.js',
'viewport-toolbar.js',
'viewport.js',
'viewports.js',
)

View File

@ -0,0 +1,34 @@
/* 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/. */
"use strict";
const { DOM: dom, createClass, PropTypes } =
require("devtools/client/shared/vendor/react");
module.exports = createClass({
displayName: "ViewportToolbar",
propTypes: {
onRotateViewport: PropTypes.func.isRequired,
},
render() {
let {
onRotateViewport,
} = this.props;
return dom.div(
{
className: "viewport-toolbar",
},
dom.button({
className: "viewport-rotate-button viewport-toolbar-button",
onClick: onRotateViewport,
})
);
},
});

View File

@ -9,6 +9,7 @@ const { DOM: dom, createClass, createFactory, PropTypes } =
const Types = require("../types");
const Browser = createFactory(require("./browser"));
const ViewportToolbar = createFactory(require("./viewport-toolbar"));
module.exports = createClass({
@ -17,22 +18,22 @@ module.exports = createClass({
propTypes: {
location: Types.location.isRequired,
viewport: PropTypes.shape(Types.viewport).isRequired,
onRotateViewport: PropTypes.func.isRequired,
},
render() {
let {
location,
viewport,
onRotateViewport,
} = this.props;
// Additional elements will soon appear here around the Browser, like drag
// handles, etc.
return dom.div(
{
className: "viewport"
},
dom.div({
className: "viewport-header",
ViewportToolbar({
onRotateViewport,
}),
Browser({
location,

View File

@ -17,23 +17,26 @@ module.exports = createClass({
propTypes: {
location: Types.location.isRequired,
viewports: PropTypes.arrayOf(PropTypes.shape(Types.viewport)).isRequired,
onRotateViewport: PropTypes.func.isRequired,
},
render() {
let {
location,
viewports,
onRotateViewport,
} = this.props;
return dom.div(
{
id: "viewports",
},
viewports.map((viewport, index) => {
viewports.map(viewport => {
return Viewport({
key: index,
key: viewport.id,
location,
viewport,
onRotateViewport: () => onRotateViewport(viewport.id),
});
})
);

View File

@ -0,0 +1,9 @@
# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=python:
# 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/.
DevToolsModules(
'rotate-viewport.svg',
)

View File

@ -0,0 +1,6 @@
<!-- 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/. -->
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
<path d="M12.3 11.5l-2.2-8.2-.3-.3c-.1-.1-.2-.1-.4-.1L4 4.4c-.2.1-.4.4-.3.6l2.2 8.2c0 .1.1.2.2.3.1 0 .2.1.3.1h.1l5.4-1.5c.3 0 .5-.3.4-.6zM9.2 4.1l1.5 5.5-4.4 1.2-1.5-5.5 4.4-1.2zm-2.4 8.4l-.3-1.1 4.4-1.2.3 1.1-4.4 1.2zM3.7 13.7c-1.2 0-3.4-.6-3.7-2.8-.3-2.2 1.3-3.3 2.1-3.5.2-.1.4.1.5.3.1.2-.1.4-.3.5-.1 0-1.8.6-1.6 2.7.2 1.5 1.6 1.9 2.4 2l-.7-2.4c0-.2.2-.5.4-.5.2-.1.4 0 .5.2l.9 3c0 .1 0 .3-.1.4-.1.1-.2.1-.4.1zM12.3 3.1c1.2 0 3.4.6 3.7 2.8.3 2.2-1.3 3.3-2.1 3.5-.2.1-.4-.1-.5-.3-.1-.2.1-.4.3-.5.1 0 1.8-.6 1.6-2.7-.2-1.5-1.6-1.9-2.4-2l.7 2.4c.1.2-.1.4-.3.5-.2.1-.4-.1-.5-.3l-.9-3c0-.1 0-.3.1-.4h.3z"/>
</svg>

After

Width:  |  Height:  |  Size: 908 B

View File

@ -1,6 +1,18 @@
/* TODO: May break up into component local CSS. Pending future discussions by
* React component group on how to best handle CSS. */
/**
* CSS Variables specific to the responsive design mode
*/
.theme-light {
--viewport-box-shadow: 0 4px 4px 0 rgba(155, 155, 155, 0.26);
}
.theme-dark {
--viewport-box-shadow: 0 4px 4px 0 rgba(105, 105, 105, 0.26);
}
html, body {
margin: 0;
height: 100%;
@ -42,14 +54,46 @@ body {
/* Align all viewports to the top */
vertical-align: top;
border: 1px solid var(--theme-splitter-color);
box-shadow: 0 4px 4px 0 rgba(155, 155, 155, 0.26);
box-shadow: var(--viewport-box-shadow);
}
.viewport-header {
/**
* Viewport Toolbar
*/
.viewport-toolbar {
background-color: var(--theme-toolbar-background);
border-bottom: 1px solid var(--theme-splitter-color);
color: var(--theme-body-color-alt);
color: var(--theme-body-color);
display: flex;
flex-direction: row;
justify-content: flex-end;
height: 18px;
}
.viewport-toolbar-button {
border: none;
display: block;
margin: 1px 3px;
padding: 0;
width: 16px;
height: 16px;
opacity: 0.8;
background-color: var(--theme-body-color);
transition: background 0.25s ease;
}
.viewport-toolbar-button:hover {
opacity: 1;
}
.viewport-toolbar-button:active {
background-color: var(--theme-selection-background);
opacity: 1;
}
.viewport-rotate-button {
mask-image: url("./images/rotate-viewport.svg");
}
.browser {

View File

@ -7,6 +7,7 @@
DIRS += [
'actions',
'components',
'images',
'reducers',
]

View File

@ -4,10 +4,13 @@
"use strict";
const { ADD_VIEWPORT } = require("../actions/index");
const { ADD_VIEWPORT, ROTATE_VIEWPORT } = require("../actions/index");
let nextViewportId = 0;
const INITIAL_VIEWPORTS = [];
const INITIAL_VIEWPORT = {
id: nextViewportId++,
width: 320,
height: 480,
};
@ -19,7 +22,20 @@ let reducers = {
if (viewports.length === 1) {
return viewports;
}
return [...viewports, INITIAL_VIEWPORT];
return [...viewports, Object.assign({}, INITIAL_VIEWPORT)];
},
[ROTATE_VIEWPORT](viewports, { id }) {
return viewports.map(viewport => {
if (viewport.id !== id) {
return viewport;
}
return Object.assign({}, viewport, {
width: viewport.height,
height: viewport.width,
});
});
},
};

View File

@ -0,0 +1,25 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Test rotating the viewport.
const { addViewport, rotateViewport } =
require("devtools/client/responsive.html/actions/viewports");
add_task(function*() {
let store = Store();
const { getState, dispatch } = store;
dispatch(addViewport());
let viewport = getState().viewports[0];
equal(viewport.width, 320, "Default width of 320");
equal(viewport.height, 480, "Default height of 480");
dispatch(rotateViewport(0));
viewport = getState().viewports[0];
equal(viewport.width, 480, "Rotated width of 480");
equal(viewport.height, 320, "Rotated height of 320");
});

View File

@ -6,3 +6,4 @@ firefox-appdir = browser
[test_add_viewport.js]
[test_change_location.js]
[test_rotate_viewport.js]

View File

@ -14,6 +14,9 @@ const { PropTypes } = require("devtools/client/shared/vendor/react");
*/
exports.viewport = {
// The id of the viewport
id: PropTypes.number.isRequired,
// The width of the viewport
width: PropTypes.number,

View File

@ -44,13 +44,13 @@ var Simulators = {
// If the simulator had a reference to an addon, fix it.
if (options.addonID) {
let job = promise.defer();
let deferred = promise.defer();
AddonManager.getAddonByID(options.addonID, addon => {
simulator.addon = addon;
delete simulator.options.addonID;
job.resolve();
deferred.resolve();
});
jobs.push(job);
jobs.push(deferred.promise);
}
});
}
@ -232,7 +232,7 @@ var Simulators = {
},
emitUpdated() {
this.emit("updated");
this.emit("updated", { length: this._simulators.length });
this._simulators.sort(LocaleCompare);
this._save();
},

View File

@ -41,6 +41,21 @@
return deferred.promise;
}
function waitForUpdate(length) {
info(`Wait for update with length ${length}`);
let deferred = promise.defer();
let handler = (_, data) => {
if (data.length != length) {
return;
}
info(`Got update with length ${length}`);
Simulators.off("updated", handler);
deferred.resolve();
};
Simulators.on("updated", handler);
return deferred.promise;
}
Task.spawn(function* () {
let win = yield openWebIDE(false);
@ -83,7 +98,11 @@
sim10.install();
let updated = waitForUpdate(1);
yield addonStatus(sim10, "installed");
yield updated;
// Wait for next tick to ensure UI elements are updated
yield nextTick();
is(findAll(".runtime-panel-item-simulator").length, 1, "One simulator in runtime panel");
@ -93,7 +112,11 @@
sim20.install();
updated = waitForUpdate(2);
yield addonStatus(sim20, "installed");
yield updated;
// Wait for next tick to ensure UI elements are updated
yield nextTick();
is(findAll(".runtime-panel-item-simulator").length, 2, "Two simulators in runtime panel");
@ -113,6 +136,7 @@
ok(params.args.indexOf("-no-remote") > -1, "Simulator process arguments have --no-remote");
// Wait for next tick to ensure UI elements are updated
yield nextTick();
// Configure the fake 1.0 simulator.
@ -255,6 +279,7 @@
// Configure the fake 2.0 simulator.
simulatorList.querySelectorAll(".configure-button")[1].click();
// Wait for next tick to ensure UI elements are updated
yield nextTick();
// Test `name`.
@ -297,11 +322,12 @@
ok(params.args[sid + 1].includes(device.width + "x" + device.height), "Simulator screen resolution looks right");
// Test Simulator Menu.
is(doc.querySelector("#tv_simulator_menu").style.visibility, "hidden", "OpenTVDummyDirectory Button is not hidden\n");
is(doc.querySelector("#tv_simulator_menu").style.visibility, "hidden", "OpenTVDummyDirectory Button is not hidden");
// Restore default simulator options.
doc.querySelector("#reset").click();
// Wait for next tick to ensure UI elements are updated
yield nextTick();
for (let param in defaults.phone) {
@ -314,11 +340,16 @@
sim30tv.install();
updated = waitForUpdate(3);
yield addonStatus(sim30tv, "installed");
yield updated;
// Wait for next tick to ensure UI elements are updated
yield nextTick();
is(findAll(".runtime-panel-item-simulator").length, 3, "Three simulators in runtime panel");
simulatorList.querySelectorAll(".configure-button")[2].click();
// Wait for next tick to ensure UI elements are updated
yield nextTick();
for (let param in defaults.television) {
@ -333,6 +364,7 @@
Simulators._loadingPromise = null;
Simulators._simulators = [];
yield Simulators._load();
// Wait for next tick to ensure UI elements are updated
yield nextTick();
is(findAll(".runtime-panel-item-simulator").length, 3, "Three simulators saved and reloaded " + Simulators._simulators.map(s => s.name).join(','));
@ -354,9 +386,11 @@
// Remove 1.0 simulator.
simulatorList.querySelectorAll(".configure-button")[0].click();
// Wait for next tick to ensure UI elements are updated
yield nextTick();
doc.querySelector("#remove").click();
// Wait for next tick to ensure UI elements are updated
yield nextTick();
is(findAll(".runtime-panel-item-simulator").length, 0, "Last simulator was removed");

View File

@ -7,22 +7,24 @@ once a parent is removed from the pool, its children are removed as well.
The overall hierarchy of actors looks like this:
RootActor: First one, automatically instanciated when we start connecting.
| Mostly meant to instanciate new actors.
RootActor: First one, automatically instantiated when we start connecting.
| Mostly meant to instantiate new actors.
|
|--> Global-scoped actors:
| Actors exposing features related to the main process,
| that are not specific to any document/app/addon.
| that are not specific to any particular context (document, tab, app,
| add-on, or worker).
| A good example is the preference actor.
|
\--> "TabActor" (or alike):
| Actors meant to designate one document, tab, app, addon
| and track its lifetime.
| Actors meant to designate one context (document, tab, app,
| add-on, or worker) and track its lifetime. Generally, there is
| one of these for each thing you can point a toolbox at.
|
\--> Tab-scoped actors:
Actors exposing one particular feature set, this time,
specific to a given document/app/addon.
Like console, inspector actors.
specific to a given context (document, tab, app, add-on, or
worker). Examples include the console and inspector actors.
These actors may extend this hierarchy by having their
own children, like LongStringActor, WalkerActor, etc.
@ -52,6 +54,15 @@ and returns its `actorID`. That's the main role of RootActor.
| Returned by "connect" on RemoteBrowserActor (for tabs) or
| "getAppActor" on the Webapps actor (for apps).
|
|-- WorkerActor (worker.js)
| Targets a worker (applies to various kinds like web worker, service
| worker, etc.).
| Returned by "listWorkers" request to the root actor to get all workers.
| Returned by "listWorkers" request to a BrowserTabActor to get workers for
| a specific tab.
| Returned by "listWorkers" request to a ChildProcessActor to get workers
| for the chrome of the child process.
|
|-- ChromeActor (chrome.js)
| Targets all resources in the parent process of firefox
| (chrome documents, JSM, JS XPCOM, etc.).
@ -63,23 +74,23 @@ and returns its `actorID`. That's the main role of RootActor.
| matching the targeted process.
|
\-- BrowserAddonActor (addon.js)
Targets the javascript of addons.
Targets the javascript of add-ons.
Returned by "listAddons" request.
## "TabActor"
Those are the actors exposed by the root actors which are meant to track the
lifetime of a given context: tab, app, process or addon. It also allows
to fetch the tab-scoped actors connected to this context. Actors like console,
inspector, thread (for debugger), styleinspector, etc. Most of them inherit
from TabActor (defined in webbrowser.js) which is document centric.
It automatically tracks the lifetime of the targeted document, but it also
tracks its iframes and allows switching the context to one of its iframes.
For historical reasons, these actors also handle creating the ThreadActor,
used to manage breakpoints in the debugger. All the other tab-scoped actors are
created when we access the TabActor's grip. We return the tab-scoped actors
`actorID` in it. Actors inheriting from TabActor expose `attach`/`detach`
requests, that allows to start/stop the ThreadActor.
lifetime of a given context: tab, app, process, add-on, or worker. It also
allows to fetch the tab-scoped actors connected to this context. Actors like
console, inspector, thread (for debugger), styleinspector, etc. Most of them
inherit from TabActor (defined in webbrowser.js) which is document centric. It
automatically tracks the lifetime of the targeted document, but it also tracks
its iframes and allows switching the context to one of its iframes. For
historical reasons, these actors also handle creating the ThreadActor, used to
manage breakpoints in the debugger. All the other tab-scoped actors are created
when we access the TabActor's grip. We return the tab-scoped actors `actorID` in
it. Actors inheriting from TabActor expose `attach`/`detach` requests, that
allows to start/stop the ThreadActor.
The tab-scoped actors expect to find the following properties on the "TabActor":
- threadActor:
@ -108,11 +119,11 @@ attributes and events:
- chromeEventHandler:
The chrome event handler for the current context. Allows to listen to events
that can be missing/cancelled on this document itself.
See TabActor documentation for events definition.
See TabActor documentation for events definition.
## Tab-scoped actors
Each of these actors focuses on providing one particular feature set, specific to one context,
that can be a web page, an app, a top level firefox window, a process or an addon resource.
Each of these actors focuses on providing one particular feature set, specific
to one context, that can be a web page, an app, a top level firefox window, a
process, an add-on, or a worker.

View File

@ -254,6 +254,7 @@ this.DevToolsLoader = function DevToolsLoader() {
this.lazyImporter = XPCOMUtils.defineLazyModuleGetter.bind(XPCOMUtils);
this.lazyServiceGetter = XPCOMUtils.defineLazyServiceGetter.bind(XPCOMUtils);
this.lazyRequireGetter = this.lazyRequireGetter.bind(this);
this.main = this.main.bind(this);
};
DevToolsLoader.prototype = {
@ -390,7 +391,8 @@ DevToolsLoader.prototype = {
lazyImporter: this.lazyImporter,
lazyServiceGetter: this.lazyServiceGetter,
lazyRequireGetter: this.lazyRequireGetter,
id: this.id
id: this.id,
main: this.main
},
};
// Lazy define console in order to load Console.jsm only when it is used

View File

@ -936,6 +936,9 @@ pref("layout.accessiblecaret.enabled", true);
pref("layout.accessiblecaret.enabled", false);
#endif
// Android hides the selection bars at the two ends of the selection highlight.
pref("layout.accessiblecaret.bar.enabled", false);
// Android needs to show the caret when long tapping on an empty content.
pref("layout.accessiblecaret.caret_shown_when_long_tapping_on_empty_content", true);

View File

@ -557,7 +557,7 @@ public class BrowserApp extends GeckoApp
@Override
public void onCreate(Bundle savedInstanceState) {
if (!isSupportedSystem()) {
if (!HardwareUtils.isSupportedSystem()) {
// This build does not support the Android version of the device; Exit early.
super.onCreate(savedInstanceState);
return;
@ -1345,7 +1345,7 @@ public class BrowserApp extends GeckoApp
@Override
public void onDestroy() {
if (!isSupportedSystem()) {
if (!HardwareUtils.isSupportedSystem()) {
// This build does not support the Android version of the device; Exit early.
super.onDestroy();
return;

View File

@ -1162,7 +1162,7 @@ public abstract class GeckoApp
enableStrictMode();
}
if (!isSupportedSystem()) {
if (!HardwareUtils.isSupportedSystem()) {
// This build does not support the Android version of the device: Show an error and finish the app.
super.onCreate(savedInstanceState);
showSDKVersionError();
@ -2085,7 +2085,7 @@ public abstract class GeckoApp
@Override
public void onDestroy() {
if (!isSupportedSystem()) {
if (!HardwareUtils.isSupportedSystem()) {
// This build does not support the Android version of the device:
// We did not initialize anything, so skip cleaning up.
super.onDestroy();
@ -2193,32 +2193,6 @@ public abstract class GeckoApp
}
}
protected boolean isSupportedSystem() {
if (Build.VERSION.SDK_INT < Versions.MIN_SDK_VERSION ||
Build.VERSION.SDK_INT > Versions.MAX_SDK_VERSION) {
return false;
}
// See http://developer.android.com/ndk/guides/abis.html
boolean isSystemARM = Build.CPU_ABI != null && Build.CPU_ABI.startsWith("arm");
boolean isSystemX86 = Build.CPU_ABI != null && Build.CPU_ABI.startsWith("x86");
boolean isAppARM = AppConstants.ANDROID_CPU_ARCH.startsWith("arm");
boolean isAppX86 = AppConstants.ANDROID_CPU_ARCH.startsWith("x86");
// Only reject known incompatible ABIs. Better safe than sorry.
if ((isSystemX86 && isAppARM) || (isSystemARM && isAppX86)) {
return false;
}
if ((isSystemX86 && isAppX86) || (isSystemARM && isAppARM)) {
return true;
}
Log.w(LOGTAG, "Unknown app/system ABI combination: " + AppConstants.MOZ_APP_ABI + " / " + Build.CPU_ABI);
return true;
}
public void showSDKVersionError() {
final String message = getString(R.string.unsupported_sdk_version, Build.CPU_ABI, Build.VERSION.SDK_INT);
Toast.makeText(this, message, Toast.LENGTH_LONG).show();

View File

@ -5,21 +5,21 @@
package org.mozilla.gecko;
import android.annotation.TargetApi;
import android.content.Context;
import android.os.Build;
import android.util.Log;
import org.mozilla.gecko.AppConstants.Versions;
import org.mozilla.gecko.annotation.RobocopTarget;
import org.mozilla.gecko.annotation.WrapForJNI;
import org.mozilla.gecko.AppConstants.Versions;
import org.mozilla.gecko.restrictions.DefaultConfiguration;
import org.mozilla.gecko.restrictions.GuestProfileConfiguration;
import org.mozilla.gecko.restrictions.Restrictable;
import org.mozilla.gecko.restrictions.RestrictedProfileConfiguration;
import org.mozilla.gecko.restrictions.RestrictionCache;
import org.mozilla.gecko.restrictions.RestrictionConfiguration;
import android.annotation.TargetApi;
import android.content.Context;
import android.os.Build;
import android.os.UserManager;
import android.util.Log;
@RobocopTarget
public class Restrictions {
private static final String LOGTAG = "GeckoRestrictedProfiles";
@ -74,8 +74,7 @@ public class Restrictions {
}
// The user is on a restricted profile if, and only if, we injected application restrictions during account setup.
final UserManager mgr = (UserManager) context.getSystemService(Context.USER_SERVICE);
return !mgr.getApplicationRestrictions(context.getPackageName()).isEmpty();
return RestrictionCache.hasApplicationRestrictions(context);
}
public static void update(Context context) {

View File

@ -10,6 +10,7 @@ import org.mozilla.gecko.GeckoAppShell;
import org.mozilla.gecko.GeckoEvent;
import org.mozilla.gecko.dlc.catalog.DownloadContent;
import org.mozilla.gecko.dlc.catalog.DownloadContentCatalog;
import org.mozilla.gecko.util.HardwareUtils;
import android.app.IntentService;
import android.content.ComponentName;
@ -64,6 +65,12 @@ public class DownloadContentService extends IntentService {
return;
}
if (!HardwareUtils.isSupportedSystem()) {
// This service is running very early before checks in BrowserApp can prevent us from running.
Log.w(LOGTAG, "System is not supported. Stop.");
return;
}
if (intent == null) {
return;
}

View File

@ -66,9 +66,6 @@ public class RestrictedProfileConfiguration implements RestrictionConfiguration
}
private Context context;
private Bundle cachedAppRestrictions;
private Bundle cachedUserRestrictions;
private boolean isCacheInvalid = true;
public RestrictedProfileConfiguration(Context context) {
this.context = context.getApplicationContext();
@ -76,38 +73,17 @@ public class RestrictedProfileConfiguration implements RestrictionConfiguration
@Override
public synchronized boolean isAllowed(Restrictable restrictable) {
if (isCacheInvalid || !ThreadUtils.isOnUiThread()) {
readRestrictions();
isCacheInvalid = false;
}
// Special casing system/user restrictions
if (restrictable == Restrictable.INSTALL_APPS || restrictable == Restrictable.MODIFY_ACCOUNTS) {
return !cachedUserRestrictions.getBoolean(restrictable.name);
return RestrictionCache.getUserRestriction(context, restrictable.name);
}
if (!cachedAppRestrictions.containsKey(restrictable.name) && !configuration.containsKey(restrictable)) {
if (!RestrictionCache.hasApplicationRestriction(context, restrictable.name) && !configuration.containsKey(restrictable)) {
// Always allow features that are not in the configuration
return true;
}
return cachedAppRestrictions.getBoolean(restrictable.name, configuration.get(restrictable));
}
private void readRestrictions() {
final UserManager mgr = (UserManager) context.getSystemService(Context.USER_SERVICE);
StrictMode.ThreadPolicy policy = StrictMode.allowThreadDiskReads();
try {
Bundle appRestrictions = mgr.getApplicationRestrictions(context.getPackageName());
migrateRestrictionsIfNeeded(appRestrictions);
cachedAppRestrictions = appRestrictions;
cachedUserRestrictions = mgr.getUserRestrictions();
} finally {
StrictMode.setThreadPolicy(policy);
}
return RestrictionCache.getApplicationRestriction(context, restrictable.name, configuration.get(restrictable));
}
@Override
@ -135,7 +111,7 @@ public class RestrictedProfileConfiguration implements RestrictionConfiguration
@Override
public synchronized void update() {
isCacheInvalid = true;
RestrictionCache.invalidate();
}
public static List<Restrictable> getVisibleRestrictions() {
@ -150,25 +126,4 @@ public class RestrictedProfileConfiguration implements RestrictionConfiguration
return visibleList;
}
/**
* This method migrates the old set of DISALLOW_ restrictions to the new restrictable feature ones (Bug 1189336).
*/
public static void migrateRestrictionsIfNeeded(Bundle bundle) {
if (!bundle.containsKey(Restrictable.INSTALL_EXTENSION.name) && bundle.containsKey("no_install_extensions")) {
bundle.putBoolean(Restrictable.INSTALL_EXTENSION.name, !bundle.getBoolean("no_install_extensions"));
}
if (!bundle.containsKey(Restrictable.PRIVATE_BROWSING.name) && bundle.containsKey("no_private_browsing")) {
bundle.putBoolean(Restrictable.PRIVATE_BROWSING.name, !bundle.getBoolean("no_private_browsing"));
}
if (!bundle.containsKey(Restrictable.CLEAR_HISTORY.name) && bundle.containsKey("no_clear_history")) {
bundle.putBoolean(Restrictable.CLEAR_HISTORY.name, !bundle.getBoolean("no_clear_history"));
}
if (!bundle.containsKey(Restrictable.ADVANCED_SETTINGS.name) && bundle.containsKey("no_advanced_settings")) {
bundle.putBoolean(Restrictable.ADVANCED_SETTINGS.name, !bundle.getBoolean("no_advanced_settings"));
}
}
}

View File

@ -0,0 +1,99 @@
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
* 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/. */
package org.mozilla.gecko.restrictions;
import android.annotation.TargetApi;
import android.content.Context;
import android.os.Build;
import android.os.Bundle;
import android.os.StrictMode;
import android.os.UserManager;
import org.mozilla.gecko.util.ThreadUtils;
/**
* Cache for user and application restrictions.
*/
public class RestrictionCache {
private static Bundle cachedAppRestrictions;
private static Bundle cachedUserRestrictions;
private static boolean isCacheInvalid = true;
private RestrictionCache() {}
public static synchronized boolean getUserRestriction(Context context, String restriction) {
updateCacheIfNeeded(context);
return cachedUserRestrictions.getBoolean(restriction);
}
public static synchronized boolean hasApplicationRestriction(Context context, String restriction) {
updateCacheIfNeeded(context);
return cachedAppRestrictions.containsKey(restriction);
}
public static synchronized boolean getApplicationRestriction(Context context, String restriction, boolean defaultValue) {
updateCacheIfNeeded(context);
return cachedAppRestrictions.getBoolean(restriction, defaultValue);
}
public static synchronized boolean hasApplicationRestrictions(Context context) {
updateCacheIfNeeded(context);
return !cachedAppRestrictions.isEmpty();
}
public static synchronized void invalidate() {
isCacheInvalid = true;
}
private static void updateCacheIfNeeded(Context context) {
// If we are not on the UI thread then we can just go ahead and read the values (Bug 1189347).
// Otherwise we read from the cache to avoid blocking the UI thread. If the cache is invalid
// then we hazard the consequences and just do the read.
if (isCacheInvalid || !ThreadUtils.isOnUiThread()) {
readRestrictions(context);
isCacheInvalid = false;
}
}
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
private static void readRestrictions(Context context) {
final UserManager mgr = (UserManager) context.getSystemService(Context.USER_SERVICE);
// If we do not have anything in the cache yet then this read might happen on the UI thread (Bug 1189347).
final StrictMode.ThreadPolicy policy = StrictMode.allowThreadDiskReads();
try {
Bundle appRestrictions = mgr.getApplicationRestrictions(context.getPackageName());
migrateRestrictionsIfNeeded(appRestrictions);
cachedAppRestrictions = appRestrictions;
cachedUserRestrictions = mgr.getUserRestrictions(); // Always implies disk read
} finally {
StrictMode.setThreadPolicy(policy);
}
}
/**
* This method migrates the old set of DISALLOW_ restrictions to the new restrictable feature ones (Bug 1189336).
*/
/* package-private */ static void migrateRestrictionsIfNeeded(Bundle bundle) {
if (!bundle.containsKey(Restrictable.INSTALL_EXTENSION.name) && bundle.containsKey("no_install_extensions")) {
bundle.putBoolean(Restrictable.INSTALL_EXTENSION.name, !bundle.getBoolean("no_install_extensions"));
}
if (!bundle.containsKey(Restrictable.PRIVATE_BROWSING.name) && bundle.containsKey("no_private_browsing")) {
bundle.putBoolean(Restrictable.PRIVATE_BROWSING.name, !bundle.getBoolean("no_private_browsing"));
}
if (!bundle.containsKey(Restrictable.CLEAR_HISTORY.name) && bundle.containsKey("no_clear_history")) {
bundle.putBoolean(Restrictable.CLEAR_HISTORY.name, !bundle.getBoolean("no_clear_history"));
}
if (!bundle.containsKey(Restrictable.ADVANCED_SETTINGS.name) && bundle.containsKey("no_advanced_settings")) {
bundle.putBoolean(Restrictable.ADVANCED_SETTINGS.name, !bundle.getBoolean("no_advanced_settings"));
}
}
}

View File

@ -38,7 +38,7 @@ public class RestrictionProvider extends BroadcastReceiver {
@Override
public void run() {
final Bundle oldRestrictions = intent.getBundleExtra(Intent.EXTRA_RESTRICTIONS_BUNDLE);
RestrictedProfileConfiguration.migrateRestrictionsIfNeeded(oldRestrictions);
RestrictionCache.migrateRestrictionsIfNeeded(oldRestrictions);
final Bundle extras = new Bundle();

View File

@ -7,17 +7,18 @@ package org.mozilla.gecko.telemetry;
import android.content.Context;
import android.os.Build;
import java.io.IOException;
import java.util.Locale;
import com.keepsafe.switchboard.SwitchBoard;
import org.json.JSONArray;
import org.mozilla.gecko.AppConstants;
import org.mozilla.gecko.Locales;
import org.mozilla.gecko.sync.ExtendedJSONObject;
import org.mozilla.gecko.telemetry.TelemetryConstants.CorePing;
import org.mozilla.gecko.util.StringUtils;
import java.io.IOException;
import java.util.Locale;
/**
* A class with static methods to generate the various Java-created Telemetry pings to upload to the telemetry server.
*/
@ -87,7 +88,7 @@ public class TelemetryPingGenerator {
ping.put(CorePing.OS_VERSION, Integer.toString(Build.VERSION.SDK_INT)); // A String for cross-platform reasons.
ping.put(CorePing.SEQ, seq);
if (AppConstants.MOZ_SWITCHBOARD) {
ping.put(CorePing.EXPERIMENTS, getActiveExperiments(context));
ping.putArray(CorePing.EXPERIMENTS, SwitchBoard.getActiveExperiments(context));
}
// TODO (bug 1246816): Remove this "optional" parameter work-around when
// GeckoProfile.getAndPersistProfileCreationDateFromFilesystem is implemented. That method returns -1
@ -97,11 +98,4 @@ public class TelemetryPingGenerator {
}
return ping;
}
private static JSONArray getActiveExperiments(final Context context) {
if (!AppConstants.MOZ_SWITCHBOARD) {
throw new IllegalStateException("This method should not be called with switchboard disabled");
}
return new JSONArray(SwitchBoard.getActiveExperiments(context));
}
}

View File

@ -5,6 +5,7 @@
package org.mozilla.gecko.util;
import org.mozilla.gecko.AppConstants;
import org.mozilla.gecko.SysInfo;
import android.content.Context;
@ -97,4 +98,33 @@ public final class HardwareUtils {
return memSize < LOW_MEMORY_THRESHOLD_MB;
}
/**
* @return false if the current system is not supported (e.g. APK/system ABI mismatch).
*/
public static boolean isSupportedSystem() {
if (Build.VERSION.SDK_INT < AppConstants.Versions.MIN_SDK_VERSION ||
Build.VERSION.SDK_INT > AppConstants.Versions.MAX_SDK_VERSION) {
return false;
}
// See http://developer.android.com/ndk/guides/abis.html
boolean isSystemARM = Build.CPU_ABI != null && Build.CPU_ABI.startsWith("arm");
boolean isSystemX86 = Build.CPU_ABI != null && Build.CPU_ABI.startsWith("x86");
boolean isAppARM = AppConstants.ANDROID_CPU_ARCH.startsWith("arm");
boolean isAppX86 = AppConstants.ANDROID_CPU_ARCH.startsWith("x86");
// Only reject known incompatible ABIs. Better safe than sorry.
if ((isSystemX86 && isAppARM) || (isSystemARM && isAppX86)) {
return false;
}
if ((isSystemX86 && isAppX86) || (isSystemARM && isAppARM)) {
return true;
}
Log.w(LOGTAG, "Unknown app/system ABI combination: " + AppConstants.MOZ_APP_ABI + " / " + Build.CPU_ABI);
return true;
}
}

View File

@ -505,6 +505,7 @@ gbjar.sources += ['java/org/mozilla/gecko/' + x for x in [
'restrictions/GuestProfileConfiguration.java',
'restrictions/Restrictable.java',
'restrictions/RestrictedProfileConfiguration.java',
'restrictions/RestrictionCache.java',
'restrictions/RestrictionConfiguration.java',
'restrictions/RestrictionProvider.java',
'ScreenshotObserver.java',

View File

@ -1316,8 +1316,11 @@ var BrowserApp = {
let message;
let title = closedTabData.entries[closedTabData.index - 1].title;
let isPrivate = PrivateBrowsingUtils.isBrowserPrivate(aTab.browser);
if (title) {
if (isPrivate) {
message = Strings.browser.GetStringFromName("privateClosedMessage.message");
} else if (title) {
message = Strings.browser.formatStringFromName("undoCloseToast.message", [title], 1);
} else {
message = Strings.browser.GetStringFromName("undoCloseToast.messageDefault");

View File

@ -188,6 +188,11 @@ newtabpopup.switch=SWITCH
# when the user closes a tab. %S is the title of the tab that was closed.
undoCloseToast.message=Closed %S
# Private Tab closed message
# LOCALIZATION NOTE (privateClosedMessage.message): This message appears
# when the user closes a private tab.
privateClosedMessage.message=Closed Private Browsing
# LOCALIZATION NOTE (undoCloseToast.messageDefault): This message appears in a
# toast when the user closes a tab if there is no title to display.
undoCloseToast.messageDefault=Closed tab

View File

@ -4,21 +4,19 @@
package org.mozilla.gecko.browserid;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.security.GeneralSecurityException;
import java.util.ArrayList;
import java.util.Map;
import java.util.TreeMap;
import org.json.simple.JSONObject;
import org.json.simple.parser.ParseException;
import org.mozilla.apache.commons.codec.binary.Base64;
import org.mozilla.apache.commons.codec.binary.StringUtils;
import org.mozilla.gecko.sync.ExtendedJSONObject;
import org.mozilla.gecko.sync.NonObjectJSONException;
import org.mozilla.gecko.sync.Utils;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.security.GeneralSecurityException;
import java.util.ArrayList;
import java.util.TreeMap;
/**
* Encode and decode JSON Web Tokens.
* <p>
@ -35,14 +33,7 @@ public class JSONWebTokenUtils {
public static final String DEFAULT_ASSERTION_ISSUER = "127.0.0.1";
public static String encode(String payload, SigningPrivateKey privateKey) throws UnsupportedEncodingException, GeneralSecurityException {
return encode(payload, privateKey, null);
}
protected static String encode(String payload, SigningPrivateKey privateKey, Map<String, Object> headerFields) throws UnsupportedEncodingException, GeneralSecurityException {
ExtendedJSONObject header = new ExtendedJSONObject();
if (headerFields != null) {
header.putAll(headerFields);
}
final ExtendedJSONObject header = new ExtendedJSONObject();
header.put("alg", privateKey.getAlgorithm());
String encodedHeader = Base64.encodeBase64URLSafeString(header.toJSONString().getBytes("UTF-8"));
String encodedPayload = Base64.encodeBase64URLSafeString(payload.getBytes("UTF-8"));
@ -78,8 +69,7 @@ public class JSONWebTokenUtils {
*/
@SuppressWarnings("unchecked")
public static String getPayloadString(String payloadString, String audience, String issuer,
Long issuedAt, long expiresAt) throws NonObjectJSONException,
IOException, ParseException {
Long issuedAt, long expiresAt) throws NonObjectJSONException, IOException {
ExtendedJSONObject payload;
if (payloadString != null) {
payload = new ExtendedJSONObject(payloadString);
@ -98,7 +88,7 @@ public class JSONWebTokenUtils {
return JSONObject.toJSONString(new TreeMap<Object, Object>(payload.object));
}
protected static String getCertificatePayloadString(VerifyingPublicKey publicKeyToSign, String email) throws NonObjectJSONException, IOException, ParseException {
protected static String getCertificatePayloadString(VerifyingPublicKey publicKeyToSign, String email) throws NonObjectJSONException, IOException {
ExtendedJSONObject payload = new ExtendedJSONObject();
ExtendedJSONObject principal = new ExtendedJSONObject();
principal.put("email", email);
@ -108,7 +98,7 @@ public class JSONWebTokenUtils {
}
public static String createCertificate(VerifyingPublicKey publicKeyToSign, String email,
String issuer, long issuedAt, long expiresAt, SigningPrivateKey privateKey) throws NonObjectJSONException, IOException, ParseException, GeneralSecurityException {
String issuer, long issuedAt, long expiresAt, SigningPrivateKey privateKey) throws NonObjectJSONException, IOException, GeneralSecurityException {
String certificatePayloadString = getCertificatePayloadString(publicKeyToSign, email);
String payloadString = getPayloadString(certificatePayloadString, null, issuer, issuedAt, expiresAt);
return JSONWebTokenUtils.encode(payloadString, privateKey);
@ -135,11 +125,10 @@ public class JSONWebTokenUtils {
* @return assertion.
* @throws NonObjectJSONException
* @throws IOException
* @throws ParseException
* @throws GeneralSecurityException
*/
public static String createAssertion(SigningPrivateKey privateKeyToSignWith, String certificate, String audience,
String issuer, Long issuedAt, long expiresAt) throws NonObjectJSONException, IOException, ParseException, GeneralSecurityException {
String issuer, Long issuedAt, long expiresAt) throws NonObjectJSONException, IOException, GeneralSecurityException {
String emptyAssertionPayloadString = "{}";
String payloadString = getPayloadString(emptyAssertionPayloadString, audience, issuer, issuedAt, expiresAt);
String signature = JSONWebTokenUtils.encode(payloadString, privateKeyToSignWith);

View File

@ -174,7 +174,7 @@ public class AccountPickler {
ExtendedJSONObject json = null;
try {
json = ExtendedJSONObject.parseJSONObject(jsonString);
json = new ExtendedJSONObject(jsonString);
} catch (Exception e) {
Logger.warn(LOG_TAG, "Got exception reading pickle file '" + filename + "'; aborting.", e);
return null;

View File

@ -13,7 +13,6 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import org.json.simple.parser.ParseException;
import org.mozilla.gecko.background.fxa.FxAccountUtils;
import org.mozilla.gecko.browserid.BrowserIDKeyPair;
import org.mozilla.gecko.browserid.JSONWebTokenUtils;
@ -55,7 +54,7 @@ public class Married extends TokensAndKeysState {
delegate.handleTransition(new LogMessage("staying married"), this);
}
public String generateAssertion(String audience, String issuer) throws NonObjectJSONException, IOException, ParseException, GeneralSecurityException {
public String generateAssertion(String audience, String issuer) throws NonObjectJSONException, IOException, GeneralSecurityException {
// We generate assertions with no iat and an exp after 2050 to avoid
// invalid-timestamp errors from the token server.
final long expiresAt = JSONWebTokenUtils.DEFAULT_FUTURE_EXPIRES_AT_IN_MILLISECONDS;

View File

@ -12,7 +12,6 @@ import java.util.Map.Entry;
import java.util.Set;
import org.json.simple.JSONArray;
import org.json.simple.parser.ParseException;
import org.mozilla.apache.commons.codec.binary.Base64;
import org.mozilla.gecko.sync.crypto.CryptoException;
import org.mozilla.gecko.sync.crypto.KeyBundle;
@ -108,7 +107,7 @@ public class CollectionKeys {
* If non-null, the sync key bundle to decrypt <code>keys</code> with.
*/
public void setKeyPairsFromWBO(CryptoRecord keys, KeyBundle syncKeyBundle)
throws CryptoException, IOException, ParseException, NonObjectJSONException {
throws CryptoException, IOException, NonObjectJSONException {
if (keys == null) {
throw new IllegalArgumentException("cannot set key pairs from null record");
}

View File

@ -8,7 +8,6 @@ import java.io.IOException;
import java.io.UnsupportedEncodingException;
import org.json.simple.JSONObject;
import org.json.simple.parser.ParseException;
import org.mozilla.apache.commons.codec.binary.Base64;
import org.mozilla.gecko.sync.crypto.CryptoException;
import org.mozilla.gecko.sync.crypto.CryptoInfo;
@ -87,8 +86,9 @@ public class CryptoRecord extends Record {
this.payload = payload;
}
public CryptoRecord(String jsonString) throws IOException, ParseException, NonObjectJSONException {
this(ExtendedJSONObject.parseJSONObject(jsonString));
public CryptoRecord(String jsonString) throws IOException, NonObjectJSONException {
this(new ExtendedJSONObject(jsonString));
}
/**
@ -125,11 +125,10 @@ public class CryptoRecord extends Record {
* A CryptoRecord that encapsulates the provided record.
*
* @throws NonObjectJSONException
* @throws ParseException
* @throws IOException
*/
public static CryptoRecord fromJSONRecord(String jsonRecord)
throws ParseException, NonObjectJSONException, IOException, RecordParseException {
throws NonObjectJSONException, IOException, RecordParseException {
byte[] bytes = jsonRecord.getBytes("UTF-8");
ExtendedJSONObject object = ExtendedJSONObject.parseUTF8AsJSONObject(bytes);
@ -138,12 +137,12 @@ public class CryptoRecord extends Record {
// TODO: defensive programming.
public static CryptoRecord fromJSONRecord(ExtendedJSONObject jsonRecord)
throws IOException, ParseException, NonObjectJSONException, RecordParseException {
throws IOException, NonObjectJSONException, RecordParseException {
String id = (String) jsonRecord.get(KEY_ID);
String collection = (String) jsonRecord.get(KEY_COLLECTION);
String jsonEncodedPayload = (String) jsonRecord.get(KEY_PAYLOAD);
ExtendedJSONObject payload = ExtendedJSONObject.parseJSONObject(jsonEncodedPayload);
ExtendedJSONObject payload = new ExtendedJSONObject(jsonEncodedPayload);
CryptoRecord record = new CryptoRecord(payload);
record.guid = id;
@ -181,8 +180,7 @@ public class CryptoRecord extends Record {
this.keyBundle = bundle;
}
public CryptoRecord decrypt() throws CryptoException, IOException, ParseException,
NonObjectJSONException {
public CryptoRecord decrypt() throws CryptoException, IOException, NonObjectJSONException {
if (keyBundle == null) {
throw new NoKeyBundleException();
}

View File

@ -4,13 +4,6 @@
package org.mozilla.gecko.sync;
import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;
@ -18,6 +11,14 @@ import org.json.simple.parser.ParseException;
import org.mozilla.apache.commons.codec.binary.Base64;
import org.mozilla.gecko.sync.UnexpectedJSONException.BadRequiredFieldJSONException;
import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
/**
* Extend JSONObject to do little things, like, y'know, accessing members.
*
@ -105,13 +106,17 @@ public class ExtendedJSONObject {
* You should prefer the stream interface {@link #parseJSONArray(Reader)}.
*
* @param jsonString input.
* @throws ParseException
* @throws IOException
* @throws NonArrayJSONException if the object is valid JSON, but not an array.
* @throws NonArrayJSONException if the object is invalid JSON or not an array.
*/
public static JSONArray parseJSONArray(String jsonString)
throws IOException, ParseException, NonArrayJSONException {
Object o = parseRaw(jsonString);
throws IOException, NonArrayJSONException {
Object o = null;
try {
o = parseRaw(jsonString);
} catch (ParseException e) {
throw new NonArrayJSONException(e);
}
if (o == null) {
return null;
@ -124,45 +129,16 @@ public class ExtendedJSONObject {
throw new NonArrayJSONException("value must be a JSON array");
}
/**
* Helper method to get a JSON object from a stream.
*
* @param in input {@link Reader}.
* @throws ParseException
* @throws IOException
* @throws NonArrayJSONException if the object is valid JSON, but not an object.
*/
public static ExtendedJSONObject parseJSONObject(Reader in)
throws IOException, ParseException, NonObjectJSONException {
return new ExtendedJSONObject(in);
}
/**
* Helper method to get a JSON object from a string.
* <p>
* You should prefer the stream interface {@link #parseJSONObject(Reader)}.
*
* @param jsonString input.
* @throws ParseException
* @throws IOException
* @throws NonObjectJSONException if the object is valid JSON, but not an object.
*/
public static ExtendedJSONObject parseJSONObject(String jsonString)
throws IOException, ParseException, NonObjectJSONException {
return new ExtendedJSONObject(jsonString);
}
/**
* Helper method to get a JSON object from a UTF-8 byte array.
*
* @param in UTF-8 bytes.
* @throws ParseException
* @throws NonObjectJSONException if the object is valid JSON, but not an object.
* @throws NonObjectJSONException if the object is not valid JSON or not an object.
* @throws IOException
*/
public static ExtendedJSONObject parseUTF8AsJSONObject(byte[] in)
throws ParseException, NonObjectJSONException, IOException {
return parseJSONObject(new String(in, "UTF-8"));
throws NonObjectJSONException, IOException {
return new ExtendedJSONObject(new String(in, "UTF-8"));
}
public ExtendedJSONObject() {
@ -173,44 +149,19 @@ public class ExtendedJSONObject {
this.object = o;
}
public ExtendedJSONObject deepCopy() {
final ExtendedJSONObject out = new ExtendedJSONObject();
@SuppressWarnings("unchecked")
final Set<Map.Entry<String, Object>> entries = this.object.entrySet();
for (Map.Entry<String, Object> entry : entries) {
final String key = entry.getKey();
final Object value = entry.getValue();
if (value instanceof JSONArray) {
// Oh god.
try {
out.put(key, new JSONParser().parse(((JSONArray) value).toJSONString()));
} catch (ParseException e) {
// This should never occur, because we're round-tripping.
}
continue;
}
if (value instanceof JSONObject) {
out.put(key, new ExtendedJSONObject((JSONObject) value).deepCopy().object);
continue;
}
if (value instanceof ExtendedJSONObject) {
out.put(key, ((ExtendedJSONObject) value).deepCopy());
continue;
}
// Oh well.
out.put(key, value);
}
return out;
}
public ExtendedJSONObject(Reader in) throws IOException, ParseException, NonObjectJSONException {
public ExtendedJSONObject(Reader in) throws IOException, NonObjectJSONException {
if (in == null) {
this.object = new JSONObject();
return;
}
Object obj = parseRaw(in);
Object obj = null;
try {
obj = parseRaw(in);
} catch (ParseException e) {
throw new NonObjectJSONException(e);
}
if (obj instanceof JSONObject) {
this.object = ((JSONObject) obj);
} else {
@ -218,7 +169,7 @@ public class ExtendedJSONObject {
}
}
public ExtendedJSONObject(String jsonString) throws IOException, ParseException, NonObjectJSONException {
public ExtendedJSONObject(String jsonString) throws IOException, NonObjectJSONException {
this(jsonString == null ? null : new StringReader(jsonString));
}
@ -319,15 +270,42 @@ public class ExtendedJSONObject {
return this.object.toString();
}
public void put(String key, Object value) {
protected void putRaw(String key, Object value) {
@SuppressWarnings("unchecked")
Map<Object, Object> map = this.object;
map.put(key, value);
}
@SuppressWarnings({ "unchecked", "rawtypes" })
public void putAll(Map map) {
this.object.putAll(map);
public void put(String key, String value) {
this.putRaw(key, value);
}
public void put(String key, boolean value) {
this.putRaw(key, value);
}
public void put(String key, long value) {
this.putRaw(key, value);
}
public void put(String key, int value) {
this.putRaw(key, value);
}
public void put(String key, ExtendedJSONObject value) {
this.putRaw(key, value);
}
public void put(String key, JSONArray value) {
this.putRaw(key, value);
}
@SuppressWarnings("unchecked")
public void putArray(String key, List<String> value) {
// Frustratingly inefficient, but there you have it.
final JSONArray jsonArray = new JSONArray();
jsonArray.addAll(value);
this.putRaw(key, jsonArray);
}
/**

View File

@ -7,13 +7,12 @@ package org.mozilla.gecko.sync;
import android.content.Context;
import org.json.simple.JSONArray;
import org.json.simple.parser.ParseException;
import org.mozilla.gecko.background.common.log.Logger;
import org.mozilla.gecko.sync.crypto.CryptoException;
import org.mozilla.gecko.sync.crypto.KeyBundle;
import org.mozilla.gecko.sync.delegates.GlobalSessionCallback;
import org.mozilla.gecko.sync.delegates.ClientsDataDelegate;
import org.mozilla.gecko.sync.delegates.FreshStartDelegate;
import org.mozilla.gecko.sync.delegates.GlobalSessionCallback;
import org.mozilla.gecko.sync.delegates.JSONRecordFetchDelegate;
import org.mozilla.gecko.sync.delegates.KeyUploadDelegate;
import org.mozilla.gecko.sync.delegates.MetaGlobalDelegate;
@ -102,7 +101,7 @@ public class GlobalSession implements HttpResponseObserver {
GlobalSessionCallback callback,
Context context,
ClientsDataDelegate clientsDelegate)
throws SyncConfigurationException, IllegalArgumentException, IOException, ParseException, NonObjectJSONException {
throws SyncConfigurationException, IllegalArgumentException, IOException, NonObjectJSONException {
if (callback == null) {
throw new IllegalArgumentException("Must provide a callback to GlobalSession constructor.");

View File

@ -13,7 +13,6 @@ import java.util.Map;
import java.util.Set;
import org.json.simple.JSONArray;
import org.json.simple.parser.ParseException;
import org.mozilla.gecko.background.common.log.Logger;
import org.mozilla.gecko.sync.MetaGlobalException.MetaGlobalMalformedSyncIDException;
import org.mozilla.gecko.sync.MetaGlobalException.MetaGlobalMalformedVersionException;
@ -56,7 +55,7 @@ public class MetaGlobal implements SyncStorageRequestDelegate {
this.isUploading = false;
SyncStorageRecordRequest r = new SyncStorageRecordRequest(this.metaURL);
r.delegate = this;
r.deferGet();
r.get();
} catch (URISyntaxException e) {
this.callback.handleError(e);
}
@ -97,7 +96,7 @@ public class MetaGlobal implements SyncStorageRequestDelegate {
return record;
}
public void setFromRecord(CryptoRecord record) throws IllegalStateException, IOException, ParseException, NonObjectJSONException, NonArrayJSONException {
public void setFromRecord(CryptoRecord record) throws IllegalStateException, IOException, NonObjectJSONException, NonArrayJSONException {
if (record == null) {
throw new IllegalArgumentException("Cannot set meta/global from null record");
}

View File

@ -10,4 +10,8 @@ public class NonArrayJSONException extends UnexpectedJSONException {
public NonArrayJSONException(String detailMessage) {
super(detailMessage);
}
public NonArrayJSONException(Throwable throwable) {
super(throwable);
}
}

View File

@ -10,4 +10,8 @@ public class NonObjectJSONException extends UnexpectedJSONException {
public NonObjectJSONException(String detailMessage) {
super(detailMessage);
}
public NonObjectJSONException(Throwable throwable) {
super(throwable);
}
}

View File

@ -171,7 +171,7 @@ public class SyncConfiguration {
return null;
}
try {
final ExtendedJSONObject o = ExtendedJSONObject.parseJSONObject(json);
final ExtendedJSONObject o = new ExtendedJSONObject(json);
return new HashSet<String>(o.keySet());
} catch (Exception e) {
return null;
@ -212,7 +212,7 @@ public class SyncConfiguration {
return null;
}
try {
ExtendedJSONObject o = ExtendedJSONObject.parseJSONObject(json);
ExtendedJSONObject o = new ExtendedJSONObject(json);
Map<String, Boolean> map = new HashMap<String, Boolean>();
for (Entry<String, Object> e : o.entrySet()) {
String key = e.getKey();

View File

@ -4,14 +4,13 @@
package org.mozilla.gecko.sync;
import java.io.IOException;
import android.content.SharedPreferences.Editor;
import org.json.simple.parser.ParseException;
import org.mozilla.gecko.background.common.PrefsBranch;
import org.mozilla.gecko.background.common.log.Logger;
import org.mozilla.gecko.sync.repositories.RepositorySessionBundle;
import android.content.SharedPreferences.Editor;
import java.io.IOException;
public class SynchronizerConfiguration {
private static final String LOG_TAG = "SynczrConfiguration";
@ -20,7 +19,7 @@ public class SynchronizerConfiguration {
public RepositorySessionBundle remoteBundle;
public RepositorySessionBundle localBundle;
public SynchronizerConfiguration(PrefsBranch config) throws NonObjectJSONException, IOException, ParseException {
public SynchronizerConfiguration(PrefsBranch config) throws NonObjectJSONException, IOException {
this.load(config);
}
@ -31,7 +30,7 @@ public class SynchronizerConfiguration {
}
// This should get partly shuffled back into SyncConfiguration, I think.
public void load(PrefsBranch config) throws NonObjectJSONException, IOException, ParseException {
public void load(PrefsBranch config) throws NonObjectJSONException, IOException {
if (config == null) {
throw new IllegalArgumentException("config cannot be null.");
}

View File

@ -11,6 +11,10 @@ public class UnexpectedJSONException extends Exception {
super(detailMessage);
}
public UnexpectedJSONException(Throwable throwable) {
super(throwable);
}
public static class BadRequiredFieldJSONException extends UnexpectedJSONException {
private static final long serialVersionUID = -9207736984784497612L;

View File

@ -34,9 +34,6 @@ import org.mozilla.gecko.sync.setup.Constants;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.text.Spannable;
import android.text.Spanned;
import android.text.style.ClickableSpan;
public class Utils {
@ -425,14 +422,14 @@ public class Utils {
ArrayList<String> toSkip = null;
if (toSyncString != null) {
try {
toSync = new ArrayList<String>(ExtendedJSONObject.parseJSONObject(toSyncString).keySet());
toSync = new ArrayList<String>(new ExtendedJSONObject(toSyncString).keySet());
} catch (Exception e) {
Logger.warn(LOG_TAG, "Got exception parsing stages to sync: '" + toSyncString + "'.", e);
}
}
if (toSkipString != null) {
try {
toSkip = new ArrayList<String>(ExtendedJSONObject.parseJSONObject(toSkipString).keySet());
toSkip = new ArrayList<String>(new ExtendedJSONObject(toSkipString).keySet());
} catch (Exception e) {
Logger.warn(LOG_TAG, "Got exception parsing stages to skip: '" + toSkipString + "'.", e);
}

View File

@ -11,7 +11,6 @@ import java.io.InputStreamReader;
import java.io.Reader;
import java.util.Scanner;
import org.json.simple.parser.ParseException;
import org.mozilla.gecko.background.common.log.Logger;
import org.mozilla.gecko.sync.ExtendedJSONObject;
import org.mozilla.gecko.sync.NonObjectJSONException;
@ -89,14 +88,12 @@ public class MozResponse {
*
* @throws IllegalStateException
* @throws IOException
* @throws ParseException
* @throws NonObjectJSONException
*/
public ExtendedJSONObject jsonObjectBody() throws IllegalStateException, IOException,
ParseException, NonObjectJSONException {
public ExtendedJSONObject jsonObjectBody() throws IllegalStateException, IOException, NonObjectJSONException {
if (body != null) {
// Do it from the cached String.
return ExtendedJSONObject.parseJSONObject(body);
return new ExtendedJSONObject(body);
}
HttpEntity entity = this.response.getEntity();
@ -107,7 +104,7 @@ public class MozResponse {
InputStream content = entity.getContent();
try {
Reader in = new BufferedReader(new InputStreamReader(content, "UTF-8"));
return ExtendedJSONObject.parseJSONObject(in);
return new ExtendedJSONObject(in);
} finally {
content.close();
}

View File

@ -4,14 +4,13 @@
package org.mozilla.gecko.sync.net;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URISyntaxException;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import org.mozilla.gecko.sync.CryptoRecord;
import org.mozilla.gecko.sync.ThreadPool;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URISyntaxException;
/**
* Resource class that implements expected headers and processing for Sync.
@ -93,22 +92,4 @@ public class SyncStorageRecordRequest extends SyncStorageRequest {
public void put(CryptoRecord record) {
this.put(record.toJSONObject());
}
public void deferGet() {
final SyncStorageRecordRequest self = this;
ThreadPool.run(new Runnable() {
@Override
public void run() {
self.get();
}});
}
public void deferPut(final JSONObject body) {
final SyncStorageRecordRequest self = this;
ThreadPool.run(new Runnable() {
@Override
public void run() {
self.put(body);
}});
}
}

View File

@ -4,13 +4,12 @@
package org.mozilla.gecko.sync.repositories;
import java.io.IOException;
import org.json.simple.parser.ParseException;
import org.mozilla.gecko.background.common.log.Logger;
import org.mozilla.gecko.sync.ExtendedJSONObject;
import org.mozilla.gecko.sync.NonObjectJSONException;
import java.io.IOException;
public class RepositorySessionBundle {
public static final String LOG_TAG = RepositorySessionBundle.class.getSimpleName();
@ -18,8 +17,9 @@ public class RepositorySessionBundle {
protected final ExtendedJSONObject object;
public RepositorySessionBundle(String jsonString) throws IOException, ParseException, NonObjectJSONException {
object = ExtendedJSONObject.parseJSONObject(jsonString);
public RepositorySessionBundle(String jsonString) throws IOException, NonObjectJSONException {
object = new ExtendedJSONObject(jsonString);
}
public RepositorySessionBundle(long lastSyncTimestamp) {

View File

@ -4,10 +4,14 @@
package org.mozilla.gecko.sync.repositories.android;
import java.io.IOException;
import android.content.ContentProviderClient;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.net.Uri;
import android.os.RemoteException;
import org.json.simple.JSONArray;
import org.json.simple.parser.ParseException;
import org.mozilla.gecko.background.common.log.Logger;
import org.mozilla.gecko.db.BrowserContract;
import org.mozilla.gecko.sync.ExtendedJSONObject;
@ -16,12 +20,7 @@ import org.mozilla.gecko.sync.repositories.NullCursorException;
import org.mozilla.gecko.sync.repositories.domain.ClientRecord;
import org.mozilla.gecko.sync.repositories.domain.HistoryRecord;
import android.content.ContentProviderClient;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.net.Uri;
import android.os.RemoteException;
import java.io.IOException;
public class RepoUtils {
@ -139,9 +138,6 @@ public class RepoUtils {
} catch (IOException e) {
Logger.error(LOG_TAG, "JSON parsing error for " + colId, e);
return null;
} catch (ParseException e) {
Logger.error(LOG_TAG, "JSON parsing error for " + colId, e);
return null;
}
}

View File

@ -4,12 +4,8 @@
package org.mozilla.gecko.sync.stage;
import java.io.IOException;
import java.net.URISyntaxException;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import android.content.Context;
import org.json.simple.parser.ParseException;
import org.mozilla.gecko.background.common.log.Logger;
import org.mozilla.gecko.sync.EngineSettings;
import org.mozilla.gecko.sync.GlobalSession;
@ -43,7 +39,10 @@ import org.mozilla.gecko.sync.synchronizer.Synchronizer;
import org.mozilla.gecko.sync.synchronizer.SynchronizerDelegate;
import org.mozilla.gecko.sync.synchronizer.SynchronizerSession;
import android.content.Context;
import java.io.IOException;
import java.net.URISyntaxException;
import java.util.Map;
import java.util.concurrent.ExecutorService;
/**
* Fetch from a server collection into a local repository, encrypting
@ -121,7 +120,7 @@ public abstract class ServerSyncStage extends AbstractSessionManagingSyncStage i
}
}
protected EngineSettings getEngineSettings() throws NonObjectJSONException, IOException, ParseException {
protected EngineSettings getEngineSettings() throws NonObjectJSONException, IOException {
Integer version = getStorageVersion();
if (version == null) {
Logger.warn(LOG_TAG, "null storage version for " + this + "; using version 0.");
@ -167,7 +166,7 @@ public abstract class ServerSyncStage extends AbstractSessionManagingSyncStage i
return this.getCollection() + ".";
}
protected SynchronizerConfiguration getConfig() throws NonObjectJSONException, IOException, ParseException {
protected SynchronizerConfiguration getConfig() throws NonObjectJSONException, IOException {
return new SynchronizerConfiguration(session.config.getBranch(bundlePrefix()));
}
@ -175,7 +174,7 @@ public abstract class ServerSyncStage extends AbstractSessionManagingSyncStage i
synchronizerConfiguration.persist(session.config.getBranch(bundlePrefix()));
}
public Synchronizer getConfiguredSynchronizer(GlobalSession session) throws NoCollectionKeysSetException, URISyntaxException, NonObjectJSONException, IOException, ParseException {
public Synchronizer getConfiguredSynchronizer(GlobalSession session) throws NoCollectionKeysSetException, URISyntaxException, NonObjectJSONException, IOException {
Repository remote = wrappedServerRepo();
Synchronizer synchronizer = new ServerLocalSynchronizer();
@ -549,7 +548,7 @@ public abstract class ServerSyncStage extends AbstractSessionManagingSyncStage i
} catch (URISyntaxException e) {
session.abort(e, "Invalid URI syntax for server repository.");
return;
} catch (NonObjectJSONException | ParseException | IOException e) {
} catch (NonObjectJSONException | IOException e) {
session.abort(e, "Invalid persisted JSON for config.");
return;
}

View File

@ -171,7 +171,7 @@ public class TokenServerClient {
}
}
} catch (NonArrayJSONException e) {
Logger.warn(LOG_TAG, "Got non-JSON array '" + result.getString(JSON_KEY_ERRORS) + "'.", e);
Logger.warn(LOG_TAG, "Got non-JSON array '" + JSON_KEY_ERRORS + "'.", e);
}
}

View File

@ -3,24 +3,20 @@
package org.mozilla.gecko.background.db;
import java.io.IOException;
import java.util.ArrayList;
import android.database.Cursor;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import org.json.simple.parser.ParseException;
import org.mozilla.gecko.background.helpers.AndroidSyncTestCase;
import org.mozilla.gecko.background.sync.helpers.HistoryHelpers;
import org.mozilla.gecko.sync.ExtendedJSONObject;
import org.mozilla.gecko.sync.NonArrayJSONException;
import org.mozilla.gecko.sync.NonObjectJSONException;
import org.mozilla.gecko.sync.Utils;
import org.mozilla.gecko.sync.repositories.NullCursorException;
import org.mozilla.gecko.sync.repositories.android.AndroidBrowserHistoryDataExtender;
import org.mozilla.gecko.sync.repositories.android.RepoUtils;
import org.mozilla.gecko.sync.repositories.domain.HistoryRecord;
import android.database.Cursor;
import java.util.ArrayList;
public class TestAndroidBrowserHistoryDataExtender extends AndroidSyncTestCase {
@ -36,7 +32,7 @@ public class TestAndroidBrowserHistoryDataExtender extends AndroidSyncTestCase {
extender.close();
}
public void testStoreFetch() throws NullCursorException, NonObjectJSONException, IOException, ParseException {
public void testStoreFetch() throws Exception {
String guid = Utils.generateGuid();
extender.store(Utils.generateGuid(), null);
extender.store(guid, null);
@ -55,7 +51,7 @@ public class TestAndroidBrowserHistoryDataExtender extends AndroidSyncTestCase {
}
}
public void testVisitsForGUID() throws NonArrayJSONException, NonObjectJSONException, IOException, ParseException, NullCursorException {
public void testVisitsForGUID() throws Exception {
String guid = Utils.generateGuid();
JSONArray visits = new ExtendedJSONObject("{ \"visits\": [ { \"key\" : \"value\" } ] }").getArray("visits");

View File

@ -3,9 +3,8 @@
package org.mozilla.gecko.background.sync;
import java.io.IOException;
import android.content.SharedPreferences;
import org.json.simple.parser.ParseException;
import org.mozilla.gecko.background.helpers.AndroidSyncTestCase;
import org.mozilla.gecko.background.testhelpers.BaseMockServerSyncStage;
import org.mozilla.gecko.background.testhelpers.DefaultGlobalSessionCallback;
@ -16,11 +15,8 @@ import org.mozilla.gecko.background.testhelpers.WaitHelper;
import org.mozilla.gecko.sync.EngineSettings;
import org.mozilla.gecko.sync.GlobalSession;
import org.mozilla.gecko.sync.MetaGlobalException;
import org.mozilla.gecko.sync.NonObjectJSONException;
import org.mozilla.gecko.sync.SyncConfiguration;
import org.mozilla.gecko.sync.SyncConfigurationException;
import org.mozilla.gecko.sync.SynchronizerConfiguration;
import org.mozilla.gecko.sync.crypto.CryptoException;
import org.mozilla.gecko.sync.crypto.KeyBundle;
import org.mozilla.gecko.sync.delegates.GlobalSessionCallback;
import org.mozilla.gecko.sync.net.AuthHeaderProvider;
@ -29,8 +25,6 @@ import org.mozilla.gecko.sync.repositories.domain.Record;
import org.mozilla.gecko.sync.stage.NoSuchStageException;
import org.mozilla.gecko.sync.synchronizer.Synchronizer;
import android.content.SharedPreferences;
/**
* Test the on-device side effects of reset operations on a stage.
*
@ -156,8 +150,7 @@ public class TestResetting extends AndroidSyncTestCase {
}
}
private GlobalSession createDefaultGlobalSession(final GlobalSessionCallback callback) throws SyncConfigurationException, IllegalArgumentException, NonObjectJSONException, IOException, ParseException, CryptoException {
private GlobalSession createDefaultGlobalSession(final GlobalSessionCallback callback) throws Exception {
final KeyBundle keyBundle = new KeyBundle(TEST_USERNAME, TEST_SYNC_KEY);
final AuthHeaderProvider authHeaderProvider = new BasicAuthHeaderProvider(TEST_USERNAME, TEST_PASSWORD);
final SharedPreferences prefs = new MockSharedPreferences();

View File

@ -3,17 +3,14 @@
package org.mozilla.gecko.background.testhelpers;
import java.io.IOException;
import java.net.URISyntaxException;
import org.json.simple.parser.ParseException;
import org.mozilla.gecko.sync.NoCollectionKeysSetException;
import org.mozilla.gecko.sync.NonObjectJSONException;
import org.mozilla.gecko.sync.SynchronizerConfiguration;
import org.mozilla.gecko.sync.repositories.RecordFactory;
import org.mozilla.gecko.sync.repositories.Repository;
import org.mozilla.gecko.sync.stage.ServerSyncStage;
import java.net.URISyntaxException;
/**
* A stage that joins two Repositories with no wrapping.
*/
@ -66,8 +63,7 @@ public abstract class BaseMockServerSyncStage extends ServerSyncStage {
return getRemoteRepository();
}
public SynchronizerConfiguration leakConfig()
throws NonObjectJSONException, IOException, ParseException {
public SynchronizerConfiguration leakConfig() throws Exception {
return this.getConfig();
}
}

View File

@ -3,10 +3,6 @@
package org.mozilla.gecko.background.testhelpers;
import java.io.IOException;
import java.util.HashMap;
import org.json.simple.parser.ParseException;
import org.mozilla.gecko.sync.EngineSettings;
import org.mozilla.gecko.sync.NonObjectJSONException;
import org.mozilla.gecko.sync.SyncConfiguration;
@ -18,15 +14,18 @@ import org.mozilla.gecko.sync.stage.CompletedStage;
import org.mozilla.gecko.sync.stage.GlobalSyncStage;
import org.mozilla.gecko.sync.stage.GlobalSyncStage.Stage;
import java.io.IOException;
import java.util.HashMap;
public class MockGlobalSession extends MockPrefsGlobalSession {
public MockGlobalSession(String username, String password, KeyBundle keyBundle, GlobalSessionCallback callback) throws SyncConfigurationException, IllegalArgumentException, NonObjectJSONException, IOException, ParseException {
public MockGlobalSession(String username, String password, KeyBundle keyBundle, GlobalSessionCallback callback) throws SyncConfigurationException, IllegalArgumentException, NonObjectJSONException, IOException {
this(new SyncConfiguration(username, new BasicAuthHeaderProvider(username, password), new MockSharedPreferences(), keyBundle), callback);
}
public MockGlobalSession(SyncConfiguration config, GlobalSessionCallback callback)
throws SyncConfigurationException, IllegalArgumentException, IOException, ParseException, NonObjectJSONException {
throws SyncConfigurationException, IllegalArgumentException, IOException, NonObjectJSONException {
super(config, callback, null, null);
}

View File

@ -3,9 +3,9 @@
package org.mozilla.gecko.background.testhelpers;
import java.io.IOException;
import android.content.Context;
import android.content.SharedPreferences;
import org.json.simple.parser.ParseException;
import org.mozilla.gecko.sync.GlobalSession;
import org.mozilla.gecko.sync.NonObjectJSONException;
import org.mozilla.gecko.sync.SyncConfiguration;
@ -16,8 +16,7 @@ import org.mozilla.gecko.sync.delegates.GlobalSessionCallback;
import org.mozilla.gecko.sync.net.AuthHeaderProvider;
import org.mozilla.gecko.sync.net.BasicAuthHeaderProvider;
import android.content.Context;
import android.content.SharedPreferences;
import java.io.IOException;
/**
* GlobalSession touches the Android prefs system. Stub that out.
@ -30,7 +29,7 @@ public class MockPrefsGlobalSession extends GlobalSession {
SyncConfiguration config, GlobalSessionCallback callback, Context context,
ClientsDataDelegate clientsDelegate)
throws SyncConfigurationException, IllegalArgumentException, IOException,
ParseException, NonObjectJSONException {
NonObjectJSONException {
super(config, callback, context, clientsDelegate);
}
@ -39,7 +38,7 @@ public class MockPrefsGlobalSession extends GlobalSession {
KeyBundle syncKeyBundle, GlobalSessionCallback callback, Context context,
ClientsDataDelegate clientsDelegate)
throws SyncConfigurationException, IllegalArgumentException, IOException,
ParseException, NonObjectJSONException {
NonObjectJSONException {
return getSession(username, new BasicAuthHeaderProvider(username, password), null,
syncKeyBundle, callback, context, clientsDelegate);
}
@ -49,7 +48,7 @@ public class MockPrefsGlobalSession extends GlobalSession {
KeyBundle syncKeyBundle, GlobalSessionCallback callback, Context context,
ClientsDataDelegate clientsDelegate)
throws SyncConfigurationException, IllegalArgumentException, IOException,
ParseException, NonObjectJSONException {
NonObjectJSONException {
final SharedPreferences prefs = new MockSharedPreferences();
final SyncConfiguration config = new SyncConfiguration(username, authHeaderProvider, prefs);

View File

@ -6,7 +6,6 @@ package org.mozilla.android.sync.net.test;
import ch.boye.httpclientandroidlib.HttpStatus;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import org.json.simple.parser.ParseException;
import org.junit.After;
import org.junit.Test;
import org.junit.runner.RunWith;
@ -61,14 +60,14 @@ import static org.junit.Assert.fail;
public class TestClientsEngineStage extends MockSyncClientsEngineStage {
public final static String LOG_TAG = "TestClientsEngSta";
public TestClientsEngineStage() throws SyncConfigurationException, IllegalArgumentException, NonObjectJSONException, IOException, ParseException, CryptoException, URISyntaxException {
public TestClientsEngineStage() throws SyncConfigurationException, IllegalArgumentException, NonObjectJSONException, IOException, CryptoException, URISyntaxException {
super();
session = initializeSession();
}
// Static so we can set it during the constructor. This is so evil.
private static MockGlobalSessionCallback callback;
private static GlobalSession initializeSession() throws SyncConfigurationException, IllegalArgumentException, NonObjectJSONException, IOException, ParseException, CryptoException, URISyntaxException {
private static GlobalSession initializeSession() throws SyncConfigurationException, IllegalArgumentException, NonObjectJSONException, IOException, CryptoException, URISyntaxException {
callback = new MockGlobalSessionCallback();
SyncConfiguration config = new SyncConfiguration(USERNAME, new BasicAuthHeaderProvider(USERNAME, PASSWORD), new MockSharedPreferences());
config.syncKeyBundle = new KeyBundle(USERNAME, SYNC_KEY);
@ -178,7 +177,6 @@ public class TestClientsEngineStage extends MockSyncClientsEngineStage {
throws SyncConfigurationException,
IllegalArgumentException,
IOException,
ParseException,
NonObjectJSONException {
super(config, callback);
}

View File

@ -3,8 +3,6 @@
package org.mozilla.android.sync.net.test;
import ch.boye.httpclientandroidlib.Header;
import org.json.simple.parser.ParseException;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mozilla.gecko.background.testhelpers.TestRunner;
@ -16,6 +14,8 @@ import org.mozilla.gecko.sync.net.BasicAuthHeaderProvider;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import ch.boye.httpclientandroidlib.Header;
import static org.junit.Assert.assertEquals;
/**
@ -46,7 +46,7 @@ public class TestCredentialsEndToEnd {
}
@Test
public void testAuthHeaderFromPassword() throws NonObjectJSONException, IOException, ParseException {
public void testAuthHeaderFromPassword() throws NonObjectJSONException, IOException {
final ExtendedJSONObject parsed = new ExtendedJSONObject(DESKTOP_PASSWORD_JSON);
final String password = parsed.getString("password");

View File

@ -8,7 +8,6 @@ import ch.boye.httpclientandroidlib.ProtocolVersion;
import ch.boye.httpclientandroidlib.message.BasicHttpResponse;
import ch.boye.httpclientandroidlib.message.BasicStatusLine;
import junit.framework.AssertionFailedError;
import org.json.simple.parser.ParseException;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@ -72,7 +71,7 @@ public class TestGlobalSession {
}
@Test
public void testGetSyncStagesBy() throws SyncConfigurationException, IllegalArgumentException, NonObjectJSONException, IOException, ParseException, CryptoException, NoSuchStageException {
public void testGetSyncStagesBy() throws SyncConfigurationException, IllegalArgumentException, NonObjectJSONException, IOException, CryptoException, NoSuchStageException {
final MockGlobalSessionCallback callback = new MockGlobalSessionCallback();
GlobalSession s = MockPrefsGlobalSession.getSession(TEST_USERNAME, TEST_PASSWORD,
@ -240,7 +239,7 @@ public class TestGlobalSession {
});
}
public MockGlobalSessionCallback doTestSuccess(final boolean stageShouldBackoff, final boolean stageShouldAdvance) throws SyncConfigurationException, IllegalArgumentException, NonObjectJSONException, IOException, ParseException, CryptoException {
public MockGlobalSessionCallback doTestSuccess(final boolean stageShouldBackoff, final boolean stageShouldAdvance) throws SyncConfigurationException, IllegalArgumentException, NonObjectJSONException, IOException, CryptoException {
MockServer server = new MockServer() {
@Override
public void handle(Request request, Response response) {
@ -295,7 +294,7 @@ public class TestGlobalSession {
@Test
public void testOnSuccessBackoffAdvanced() throws SyncConfigurationException,
IllegalArgumentException, NonObjectJSONException, IOException,
ParseException, CryptoException {
CryptoException {
MockGlobalSessionCallback callback = doTestSuccess(true, true);
assertTrue(callback.calledError); // TODO: this should be calledAborted.
@ -306,7 +305,7 @@ public class TestGlobalSession {
@Test
public void testOnSuccessBackoffAborted() throws SyncConfigurationException,
IllegalArgumentException, NonObjectJSONException, IOException,
ParseException, CryptoException {
CryptoException {
MockGlobalSessionCallback callback = doTestSuccess(true, false);
assertTrue(callback.calledError); // TODO: this should be calledAborted.
@ -317,7 +316,7 @@ public class TestGlobalSession {
@Test
public void testOnSuccessNoBackoffAdvanced() throws SyncConfigurationException,
IllegalArgumentException, NonObjectJSONException, IOException,
ParseException, CryptoException {
CryptoException {
MockGlobalSessionCallback callback = doTestSuccess(false, true);
assertTrue(callback.calledSuccess);
@ -327,7 +326,7 @@ public class TestGlobalSession {
@Test
public void testOnSuccessNoBackoffAborted() throws SyncConfigurationException,
IllegalArgumentException, NonObjectJSONException, IOException,
ParseException, CryptoException {
CryptoException {
MockGlobalSessionCallback callback = doTestSuccess(false, false);
assertTrue(callback.calledError); // TODO: this should be calledAborted.
@ -396,7 +395,7 @@ public class TestGlobalSession {
ExtendedJSONObject origEnginesJSONObject = new ExtendedJSONObject();
for (String engineName : origEngines) {
EngineSettings mockEngineSettings = new EngineSettings(Utils.generateGuid(), Integer.valueOf(0));
origEnginesJSONObject.put(engineName, mockEngineSettings);
origEnginesJSONObject.put(engineName, mockEngineSettings.toJSONObject());
}
session.config.metaGlobal.setEngines(origEnginesJSONObject);

View File

@ -3,7 +3,6 @@
package org.mozilla.android.sync.net.test;
import org.json.simple.parser.ParseException;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@ -14,6 +13,7 @@ import org.mozilla.gecko.background.testhelpers.WaitHelper;
import org.mozilla.gecko.sync.CryptoRecord;
import org.mozilla.gecko.sync.ExtendedJSONObject;
import org.mozilla.gecko.sync.MetaGlobal;
import org.mozilla.gecko.sync.NonObjectJSONException;
import org.mozilla.gecko.sync.delegates.MetaGlobalDelegate;
import org.mozilla.gecko.sync.net.BaseResource;
import org.mozilla.gecko.sync.net.BasicAuthHeaderProvider;
@ -33,8 +33,6 @@ import static org.junit.Assert.assertTrue;
@RunWith(TestRunner.class)
public class TestMetaGlobal {
public static Object monitor = new Object();
private static final int TEST_PORT = HTTPServerTestHelper.getTestPort();
private static final String TEST_SERVER = "http://localhost:" + TEST_PORT;
private static final String TEST_SYNC_ID = "foobar";
@ -223,7 +221,7 @@ public class TestMetaGlobal {
assertTrue(delegate.errorCalled);
assertNotNull(delegate.errorException);
assertEquals(ParseException.class, delegate.errorException.getClass());
assertEquals(NonObjectJSONException.class, delegate.errorException.getClass());
}
@SuppressWarnings("static-method")
@ -319,7 +317,7 @@ public class TestMetaGlobal {
public void handle(Request request, Response response) {
if (request.getMethod().equals("PUT")) {
try {
ExtendedJSONObject body = ExtendedJSONObject.parseJSONObject(request.getContent());
ExtendedJSONObject body = new ExtendedJSONObject(request.getContent());
System.out.println(body.toJSONString());
assertTrue(body.containsKey("payload"));
assertFalse(body.containsKey("default"));

View File

@ -4,7 +4,6 @@
package org.mozilla.android.sync.test;
import org.json.simple.JSONArray;
import org.json.simple.parser.ParseException;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mozilla.apache.commons.codec.binary.Base64;
@ -71,7 +70,7 @@ public class TestCollectionKeys {
@Test
public void testSetKeysFromWBO() throws IOException, ParseException, NonObjectJSONException, CryptoException, NoCollectionKeysSetException {
public void testSetKeysFromWBO() throws IOException, NonObjectJSONException, CryptoException, NoCollectionKeysSetException {
String json = "{\"default\":[\"3fI6k1exImMgAKjilmMaAWxGqEIzFX/9K5EjEgH99vc=\",\"/AMaoCX4hzic28WY94XtokNi7N4T0nv+moS1y5wlbug=\"],\"collections\":{},\"collection\":\"crypto\",\"id\":\"keys\"}";
CryptoRecord rec = new CryptoRecord(json);
@ -87,7 +86,7 @@ public class TestCollectionKeys {
}
@Test
public void testCryptoRecordFromCollectionKeys() throws CryptoException, NoCollectionKeysSetException, IOException, ParseException, NonObjectJSONException {
public void testCryptoRecordFromCollectionKeys() throws CryptoException, NoCollectionKeysSetException, IOException, NonObjectJSONException {
CollectionKeys ck1 = CollectionKeys.generateCollectionKeys();
assertNotNull(ck1.defaultKeyBundle());
assertEquals(ck1.keyBundleForCollection("foobar"), ck1.defaultKeyBundle());
@ -103,7 +102,7 @@ public class TestCollectionKeys {
}
@Test
public void testCreateKeysBundle() throws CryptoException, NonObjectJSONException, IOException, ParseException, NoCollectionKeysSetException {
public void testCreateKeysBundle() throws CryptoException, NonObjectJSONException, IOException, NoCollectionKeysSetException {
String username = "b6evr62dptbxz7fvebek7btljyu322wp";
String friendlyBase32SyncKey = "basuxv2426eqj7frhvpcwkavdi";

View File

@ -3,7 +3,6 @@
package org.mozilla.android.sync.test;
import org.json.simple.parser.ParseException;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mozilla.gecko.background.testhelpers.TestRunner;
@ -49,14 +48,14 @@ public class TestCommandProcessor extends CommandProcessor {
}
@Test
public void testRegisterCommand() throws NonObjectJSONException, IOException, ParseException {
public void testRegisterCommand() throws NonObjectJSONException, IOException {
assertNull(commands.get(commandType));
this.registerCommand(commandType, new MockCommandRunner(1));
assertNotNull(commands.get(commandType));
}
@Test
public void testProcessRegisteredCommand() throws NonObjectJSONException, IOException, ParseException {
public void testProcessRegisteredCommand() throws NonObjectJSONException, IOException {
commandExecuted = false;
ExtendedJSONObject unparsedCommand = new ExtendedJSONObject(wellFormedCommand);
this.registerCommand(commandType, new MockCommandRunner(1));
@ -65,7 +64,7 @@ public class TestCommandProcessor extends CommandProcessor {
}
@Test
public void testProcessUnregisteredCommand() throws NonObjectJSONException, IOException, ParseException {
public void testProcessUnregisteredCommand() throws NonObjectJSONException, IOException {
commandExecuted = false;
ExtendedJSONObject unparsedCommand = new ExtendedJSONObject(wellFormedCommand);
this.processCommand(session, unparsedCommand);
@ -73,7 +72,7 @@ public class TestCommandProcessor extends CommandProcessor {
}
@Test
public void testProcessInvalidCommand() throws NonObjectJSONException, IOException, ParseException {
public void testProcessInvalidCommand() throws NonObjectJSONException, IOException {
ExtendedJSONObject unparsedCommand = new ExtendedJSONObject(commandWithNoType);
this.registerCommand(commandType, new MockCommandRunner(1));
this.processCommand(session, unparsedCommand);
@ -81,19 +80,19 @@ public class TestCommandProcessor extends CommandProcessor {
}
@Test
public void testParseCommandNoType() throws NonObjectJSONException, IOException, ParseException {
public void testParseCommandNoType() throws NonObjectJSONException, IOException {
ExtendedJSONObject unparsedCommand = new ExtendedJSONObject(commandWithNoType);
assertNull(CommandProcessor.parseCommand(unparsedCommand));
}
@Test
public void testParseCommandNoArgs() throws NonObjectJSONException, IOException, ParseException {
public void testParseCommandNoArgs() throws NonObjectJSONException, IOException {
ExtendedJSONObject unparsedCommand = new ExtendedJSONObject(commandWithNoArgs);
assertNull(CommandProcessor.parseCommand(unparsedCommand));
}
@Test
public void testParseWellFormedCommand() throws NonObjectJSONException, IOException, ParseException {
public void testParseWellFormedCommand() throws NonObjectJSONException, IOException {
ExtendedJSONObject unparsedCommand = new ExtendedJSONObject(wellFormedCommand);
Command parsedCommand = CommandProcessor.parseCommand(unparsedCommand);
assertNotNull(parsedCommand);
@ -102,7 +101,7 @@ public class TestCommandProcessor extends CommandProcessor {
}
@Test
public void testParseCommandNullArg() throws NonObjectJSONException, IOException, ParseException {
public void testParseCommandNullArg() throws NonObjectJSONException, IOException {
ExtendedJSONObject unparsedCommand = new ExtendedJSONObject(wellFormedCommandWithNullArgs);
Command parsedCommand = CommandProcessor.parseCommand(unparsedCommand);
assertNotNull(parsedCommand);

View File

@ -5,7 +5,6 @@ package org.mozilla.android.sync.test;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import org.json.simple.parser.ParseException;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mozilla.apache.commons.codec.binary.Base64;
@ -35,8 +34,14 @@ public class TestCryptoRecord {
String base64HmacKey = "MMntEfutgLTc8FlTLQFms8/xMPmCldqPlq/QQXEjx70=";
@Test
public void testBaseCryptoRecordEncrypt() throws IOException, ParseException, NonObjectJSONException, CryptoException {
ExtendedJSONObject clearPayload = ExtendedJSONObject.parseJSONObject("{\"id\":\"5qRsgXWRJZXr\",\"title\":\"Index of file:///Users/jason/Library/Application Support/Firefox/Profiles/ksgd7wpk.LocalSyncServer/weave/logs/\",\"histUri\":\"file:///Users/jason/Library/Application%20Support/Firefox/Profiles/ksgd7wpk.LocalSyncServer/weave/logs/\",\"visits\":[{\"type\":1,\"date\":1319149012372425}]}");
public void testBaseCryptoRecordEncrypt() throws IOException, NonObjectJSONException, CryptoException {
ExtendedJSONObject clearPayload = new ExtendedJSONObject("{\"id\":\"5qRsgXWRJZXr\"," +
"\"title\":\"Index of file:///Users/jason/Library/Application " +
"Support/Firefox/Profiles/ksgd7wpk.LocalSyncServer/weave/logs/\"," +
"\"histUri\":\"file:///Users/jason/Library/Application%20Support/Firefox/Profiles" +
"/ksgd7wpk.LocalSyncServer/weave/logs/\",\"visits\":[{\"type\":1," +
"\"date\":1319149012372425}]}");
CryptoRecord record = new CryptoRecord();
record.payload = clearPayload;
@ -256,7 +261,7 @@ public class TestCryptoRecord {
assertEquals(expectedJson.get("collections"), decrypted.payload.get("collections"));
// Check that the extracted keys were as expected.
JSONArray keys = ExtendedJSONObject.parseJSONObject(decrypted.payload.toJSONString()).getArray("default");
JSONArray keys = new ExtendedJSONObject(decrypted.payload.toJSONString()).getArray("default");
KeyBundle keyBundle = KeyBundle.fromBase64EncodedKeys((String)keys.get(0), (String)keys.get(1));
assertArrayEquals(Base64.decodeBase64(expectedBase64EncryptionKey.getBytes("UTF-8")), keyBundle.getEncryptionKey());

View File

@ -5,7 +5,6 @@ package org.mozilla.android.sync.test;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import org.json.simple.parser.ParseException;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mozilla.gecko.background.db.Tab;
@ -33,7 +32,7 @@ public class TestRecord {
@SuppressWarnings("static-method")
@Test
public void testQueryRecord() throws NonObjectJSONException, IOException, ParseException {
public void testQueryRecord() throws NonObjectJSONException, IOException {
final String expectedGUID = "Bl3n3gpKag3s";
final String testRecord =
"{\"id\":\"" + expectedGUID + "\"," +
@ -193,7 +192,7 @@ public class TestRecord {
" \"urlHistory\":[\"http://mxr.mozilla.org/mozilla-central/source/browser/base/content/syncSetup.js#72\"]," +
" \"icon\":\"http://mxr.mozilla.org/mxr.png\"," +
" \"lastUsed\":\"1306374531\"}";
Tab tab = TabsRecord.tabFromJSONObject(ExtendedJSONObject.parseJSONObject(json).object);
Tab tab = TabsRecord.tabFromJSONObject(new ExtendedJSONObject(json).object);
assertEquals("mozilla-central mozilla/browser/base/content/syncSetup.js", tab.title);
assertEquals("http://mxr.mozilla.org/mxr.png", tab.icon);
@ -204,7 +203,7 @@ public class TestRecord {
" \"urlHistory\":[\"http://example.com\"]," +
" \"icon\":\"\"," +
" \"lastUsed\":0}";
Tab zero = TabsRecord.tabFromJSONObject(ExtendedJSONObject.parseJSONObject(zeroJSON).object);
Tab zero = TabsRecord.tabFromJSONObject(new ExtendedJSONObject(zeroJSON).object);
assertEquals("a", zero.title);
assertEquals("", zero.icon);
@ -280,7 +279,7 @@ public class TestRecord {
super("abcdefghijkl", "bookmarks", 1234, false);
}
public void doTest() throws NonObjectJSONException, IOException, ParseException {
public void doTest() throws NonObjectJSONException, IOException {
this.initFromPayload(new ExtendedJSONObject(payload));
assertEquals("abcdefghijkl", this.guid); // Ignores payload.
assertEquals("livemark", this.type);
@ -297,7 +296,7 @@ public class TestRecord {
}
@Test
public void testUnusualBookmarkRecords() throws NonObjectJSONException, IOException, ParseException {
public void testUnusualBookmarkRecords() throws NonObjectJSONException, IOException {
PayloadBookmarkRecord record = new PayloadBookmarkRecord();
record.doTest();
}

View File

@ -4,7 +4,6 @@
package org.mozilla.android.sync.test;
import android.content.SharedPreferences;
import org.json.simple.parser.ParseException;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@ -62,7 +61,7 @@ public class TestResetCommands {
}
@Test
public void testHandleResetCommand() throws SyncConfigurationException, IllegalArgumentException, NonObjectJSONException, IOException, ParseException, CryptoException {
public void testHandleResetCommand() throws SyncConfigurationException, IllegalArgumentException, NonObjectJSONException, IOException, CryptoException {
// Create a global session.
// Set up stage mappings for a real stage name (because they're looked up by name
// in an enumeration) pointing to our fake stage.

View File

@ -3,7 +3,6 @@
package org.mozilla.gecko.background.testhelpers;
import org.json.simple.parser.ParseException;
import org.mozilla.gecko.sync.NoCollectionKeysSetException;
import org.mozilla.gecko.sync.NonObjectJSONException;
import org.mozilla.gecko.sync.SynchronizerConfiguration;
@ -67,7 +66,7 @@ public abstract class BaseMockServerSyncStage extends ServerSyncStage {
}
public SynchronizerConfiguration leakConfig()
throws NonObjectJSONException, IOException, ParseException {
throws NonObjectJSONException, IOException {
return this.getConfig();
}
}

View File

@ -3,7 +3,6 @@
package org.mozilla.gecko.background.testhelpers;
import org.json.simple.parser.ParseException;
import org.mozilla.gecko.sync.EngineSettings;
import org.mozilla.gecko.sync.NonObjectJSONException;
import org.mozilla.gecko.sync.SyncConfiguration;
@ -21,12 +20,12 @@ import java.util.HashMap;
public class MockGlobalSession extends MockPrefsGlobalSession {
public MockGlobalSession(String username, String password, KeyBundle keyBundle, GlobalSessionCallback callback) throws SyncConfigurationException, IllegalArgumentException, NonObjectJSONException, IOException, ParseException {
public MockGlobalSession(String username, String password, KeyBundle keyBundle, GlobalSessionCallback callback) throws SyncConfigurationException, IllegalArgumentException, NonObjectJSONException, IOException {
this(new SyncConfiguration(username, new BasicAuthHeaderProvider(username, password), new MockSharedPreferences(), keyBundle), callback);
}
public MockGlobalSession(SyncConfiguration config, GlobalSessionCallback callback)
throws SyncConfigurationException, IllegalArgumentException, IOException, ParseException, NonObjectJSONException {
throws SyncConfigurationException, IllegalArgumentException, IOException, NonObjectJSONException {
super(config, callback, null, null);
}

View File

@ -5,7 +5,7 @@ package org.mozilla.gecko.background.testhelpers;
import android.content.Context;
import android.content.SharedPreferences;
import org.json.simple.parser.ParseException;
import org.mozilla.gecko.sync.GlobalSession;
import org.mozilla.gecko.sync.NonObjectJSONException;
import org.mozilla.gecko.sync.SyncConfiguration;
@ -28,8 +28,7 @@ public class MockPrefsGlobalSession extends GlobalSession {
public MockPrefsGlobalSession(
SyncConfiguration config, GlobalSessionCallback callback, Context context,
ClientsDataDelegate clientsDelegate)
throws SyncConfigurationException, IllegalArgumentException, IOException,
ParseException, NonObjectJSONException {
throws SyncConfigurationException, IllegalArgumentException, IOException, NonObjectJSONException {
super(config, callback, context, clientsDelegate);
}
@ -37,8 +36,7 @@ public class MockPrefsGlobalSession extends GlobalSession {
String username, String password,
KeyBundle syncKeyBundle, GlobalSessionCallback callback, Context context,
ClientsDataDelegate clientsDelegate)
throws SyncConfigurationException, IllegalArgumentException, IOException,
ParseException, NonObjectJSONException {
throws SyncConfigurationException, IllegalArgumentException, IOException, NonObjectJSONException {
return getSession(username, new BasicAuthHeaderProvider(username, password), null,
syncKeyBundle, callback, context, clientsDelegate);
}
@ -47,8 +45,7 @@ public class MockPrefsGlobalSession extends GlobalSession {
String username, AuthHeaderProvider authHeaderProvider, String prefsPath,
KeyBundle syncKeyBundle, GlobalSessionCallback callback, Context context,
ClientsDataDelegate clientsDelegate)
throws SyncConfigurationException, IllegalArgumentException, IOException,
ParseException, NonObjectJSONException {
throws SyncConfigurationException, IllegalArgumentException, IOException, NonObjectJSONException {
final SharedPreferences prefs = new MockSharedPreferences();
final SyncConfiguration config = new SyncConfiguration(username, authHeaderProvider, prefs);

View File

@ -4,7 +4,6 @@
package org.mozilla.gecko.sync.middleware.test;
import junit.framework.AssertionFailedError;
import org.json.simple.parser.ParseException;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@ -161,7 +160,7 @@ public class TestCrypto5MiddlewareRepositorySession {
/**
* Verify that store is actually writing encrypted data to the underlying repository.
*/
public void testStoreEncrypts() throws NonObjectJSONException, CryptoException, IOException, ParseException {
public void testStoreEncrypts() throws NonObjectJSONException, CryptoException, IOException {
final BookmarkRecord record = new BookmarkRecord("nncdefghiaaa", "coll", System.currentTimeMillis(), false);
record.title = "unencrypted title";

View File

@ -87,7 +87,7 @@ public class TestEnsureCrypto5KeysStage {
session.config.setClusterURL(new URI(TEST_CLUSTER_URL));
// Set info collections to not have crypto.
final ExtendedJSONObject noCrypto = ExtendedJSONObject.parseJSONObject(TEST_JSON_NO_CRYPTO);
final ExtendedJSONObject noCrypto = new ExtendedJSONObject(TEST_JSON_NO_CRYPTO);
session.config.infoCollections = new InfoCollections(noCrypto);
calledResetStages = false;
stagesReset = null;
@ -113,7 +113,8 @@ public class TestEnsureCrypto5KeysStage {
@Test
public void testDownloadUsesPersisted() throws Exception {
session.config.infoCollections = new InfoCollections(ExtendedJSONObject.parseJSONObject(TEST_JSON_OLD_CRYPTO));
session.config.infoCollections = new InfoCollections(new ExtendedJSONObject
(TEST_JSON_OLD_CRYPTO));
session.config.persistedCryptoKeys().persistLastModified(System.currentTimeMillis());
assertNull(session.config.collectionKeys);
@ -136,7 +137,7 @@ public class TestEnsureCrypto5KeysStage {
@Test
public void testDownloadFetchesNew() throws Exception {
session.config.infoCollections = new InfoCollections(ExtendedJSONObject.parseJSONObject(TEST_JSON_NEW_CRYPTO));
session.config.infoCollections = new InfoCollections(new ExtendedJSONObject(TEST_JSON_NEW_CRYPTO));
session.config.persistedCryptoKeys().persistLastModified(System.currentTimeMillis());
assertNull(session.config.collectionKeys);
@ -172,7 +173,7 @@ public class TestEnsureCrypto5KeysStage {
public void testDownloadResetsOnDifferentDefaultKey() throws Exception {
String TEST_COLLECTION = "bookmarks";
session.config.infoCollections = new InfoCollections(ExtendedJSONObject.parseJSONObject(TEST_JSON_NEW_CRYPTO));
session.config.infoCollections = new InfoCollections(new ExtendedJSONObject(TEST_JSON_NEW_CRYPTO));
session.config.persistedCryptoKeys().persistLastModified(System.currentTimeMillis());
KeyBundle keyBundle = KeyBundle.withRandomKeys();
@ -212,7 +213,7 @@ public class TestEnsureCrypto5KeysStage {
public void testDownloadResetsEngineOnDifferentKey() throws Exception {
final String TEST_COLLECTION = "history";
session.config.infoCollections = new InfoCollections(ExtendedJSONObject.parseJSONObject(TEST_JSON_NEW_CRYPTO));
session.config.infoCollections = new InfoCollections(new ExtendedJSONObject(TEST_JSON_NEW_CRYPTO));
session.config.persistedCryptoKeys().persistLastModified(System.currentTimeMillis());
assertNull(session.config.collectionKeys);

View File

@ -4,7 +4,6 @@
package org.mozilla.gecko.sync.stage.test;
import org.json.simple.JSONArray;
import org.json.simple.parser.ParseException;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@ -93,7 +92,7 @@ public class TestFetchMetaGlobalStage {
calledResetAllStages = false;
// Set info collections to not have crypto.
infoCollections = new InfoCollections(ExtendedJSONObject.parseJSONObject(TEST_INFO_COLLECTIONS_JSON));
infoCollections = new InfoCollections(new ExtendedJSONObject(TEST_INFO_COLLECTIONS_JSON));
syncKeyBundle = new KeyBundle(TEST_USERNAME, TEST_SYNC_KEY);
callback = new MockGlobalSessionCallback();
@ -335,7 +334,7 @@ public class TestFetchMetaGlobalStage {
doSession(server);
assertEquals(true, callback.calledError);
assertEquals(ParseException.class, callback.calledErrorException.getClass());
assertEquals(NonObjectJSONException.class, callback.calledErrorException.getClass());
}
protected void doFreshStart(MockServer server) {
@ -350,7 +349,7 @@ public class TestFetchMetaGlobalStage {
}
@Test
public void testFreshStart() throws SyncConfigurationException, IllegalArgumentException, NonObjectJSONException, IOException, ParseException, CryptoException {
public void testFreshStart() throws SyncConfigurationException, IllegalArgumentException, NonObjectJSONException, IOException, CryptoException {
final AtomicBoolean mgUploaded = new AtomicBoolean(false);
final AtomicBoolean mgDownloaded = new AtomicBoolean(false);
final MetaGlobal uploadedMg = new MetaGlobal(null, null);
@ -360,7 +359,7 @@ public class TestFetchMetaGlobalStage {
public void handle(Request request, Response response) {
if (request.getMethod().equals("PUT")) {
try {
ExtendedJSONObject body = ExtendedJSONObject.parseJSONObject(request.getContent());
ExtendedJSONObject body = new ExtendedJSONObject(request.getContent());
assertTrue(body.containsKey("payload"));
assertFalse(body.containsKey("default"));

View File

@ -5,7 +5,6 @@ package org.mozilla.gecko.sync.test;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import org.json.simple.parser.ParseException;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mozilla.gecko.background.testhelpers.TestRunner;
@ -20,6 +19,7 @@ import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.assertNull;
@ -33,18 +33,7 @@ public class TestExtendedJSONObject {
public static String exampleIntegral = "{\"modified\":1233702554,}";
@Test
public void testDeepCopy() throws NonObjectJSONException, IOException, ParseException, NonArrayJSONException {
ExtendedJSONObject a = new ExtendedJSONObject(exampleJSON);
ExtendedJSONObject c = a.deepCopy();
assertTrue(a != c);
assertTrue(a.equals(c));
assertTrue(a.get("modified") == c.get("modified"));
assertTrue(a.getArray("success") != c.getArray("success"));
assertTrue(a.getArray("success").equals(c.getArray("success")));
}
@Test
public void testFractional() throws IOException, ParseException, NonObjectJSONException {
public void testFractional() throws IOException, NonObjectJSONException {
ExtendedJSONObject o = new ExtendedJSONObject(exampleJSON);
assertTrue(o.containsKey("modified"));
assertTrue(o.containsKey("success"));
@ -59,7 +48,7 @@ public class TestExtendedJSONObject {
}
@Test
public void testIntegral() throws IOException, ParseException, NonObjectJSONException {
public void testIntegral() throws IOException, NonObjectJSONException {
ExtendedJSONObject o = new ExtendedJSONObject(exampleIntegral);
assertTrue(o.containsKey("modified"));
assertFalse(o.containsKey("success"));
@ -73,10 +62,9 @@ public class TestExtendedJSONObject {
public void testSafeInteger() {
ExtendedJSONObject o = new ExtendedJSONObject();
o.put("integer", Integer.valueOf(5));
o.put("double", Double.valueOf(1.2));
o.put("string", "66");
o.put("object", new ExtendedJSONObject());
o.put("null", null);
o.put("null", (JSONArray) null);
assertEquals(Integer.valueOf(5), o.getIntegerSafely("integer"));
assertEquals(Integer.valueOf(66), o.getIntegerSafely("string"));
@ -98,7 +86,7 @@ public class TestExtendedJSONObject {
try {
ExtendedJSONObject.parseJSONArray("[0, ");
fail();
} catch (ParseException e) {
} catch (NonArrayJSONException e) {
// Do nothing.
}
@ -124,14 +112,14 @@ public class TestExtendedJSONObject {
try {
ExtendedJSONObject.parseUTF8AsJSONObject("{}".getBytes("UTF-16"));
fail();
} catch (ParseException e) {
} catch (NonObjectJSONException e) {
// Do nothing.
}
try {
ExtendedJSONObject.parseUTF8AsJSONObject("{".getBytes("UTF-8"));
fail();
} catch (ParseException e) {
} catch (NonObjectJSONException e) {
// Do nothing.
}
}
@ -157,8 +145,7 @@ public class TestExtendedJSONObject {
ExtendedJSONObject q = new ExtendedJSONObject(exampleJSON);
q.put("modified", 0);
assertNotSame(o, q);
q.put("modified", o.get("modified"));
assertEquals(o, q);
assertNotEquals(o, q);
}
@Test

View File

@ -49,7 +49,7 @@ public class TestInfoCollections {
InfoCounts infoCountsEmpty = new InfoCounts(new ExtendedJSONObject("{}"));
assertEquals(null, infoCountsEmpty.getCount("bookmarks"));
ExtendedJSONObject record = ExtendedJSONObject.parseJSONObject(TEST_COUNTS_JSON);
ExtendedJSONObject record = new ExtendedJSONObject(TEST_COUNTS_JSON);
InfoCounts infoCountsFull = new InfoCounts(record);
assertEquals(Integer.valueOf(766), infoCountsFull.getCount("bookmarks"));
assertEquals(null, infoCountsFull.getCount("notpresent"));
@ -59,7 +59,7 @@ public class TestInfoCollections {
@SuppressWarnings("static-method")
@Test
public void testSetCollectionsFromRecord() throws Exception {
ExtendedJSONObject record = ExtendedJSONObject.parseJSONObject(TEST_COLLECTIONS_JSON);
ExtendedJSONObject record = new ExtendedJSONObject(TEST_COLLECTIONS_JSON);
InfoCollections infoCollections = new InfoCollections(record);
assertEquals(Utils.decimalSecondsToMilliseconds(1.3319567131E9), infoCollections.getTimestamp("history").longValue());
@ -71,7 +71,7 @@ public class TestInfoCollections {
@SuppressWarnings("static-method")
@Test
public void testUpdateNeeded() throws Exception {
ExtendedJSONObject record = ExtendedJSONObject.parseJSONObject(TEST_COLLECTIONS_JSON);
ExtendedJSONObject record = new ExtendedJSONObject(TEST_COLLECTIONS_JSON);
InfoCollections infoCollections = new InfoCollections(record);
long none = -1;

View File

@ -190,13 +190,16 @@ this.HawkClient.prototype = {
* @param payloadObj
* An object that can be encodable as JSON as the payload of the
* request
* @param extraHeaders
* An object with header/value pairs to send with the request.
* @return Promise
* Returns a promise that resolves to the response of the API call,
* or is rejected with an error. If the server response can be parsed
* as JSON and contains an 'error' property, the promise will be
* rejected with this JSON-parsed response.
*/
request: function(path, method, credentials=null, payloadObj={}, retryOK=true) {
request: function(path, method, credentials=null, payloadObj={}, extraHeaders = {},
retryOK=true) {
method = method.toLowerCase();
let deferred = Promise.defer();
@ -237,7 +240,7 @@ this.HawkClient.prototype = {
// Clock offset is adjusted already in the top of this function.
log.debug("Received 401 for " + path + ": retrying");
return deferred.resolve(
self.request(path, method, credentials, payloadObj, false));
self.request(path, method, credentials, payloadObj, extraHeaders, false));
}
// If the server returned a json error message, use it in the rejection
@ -278,6 +281,7 @@ this.HawkClient.prototype = {
let extra = {
now: this.now(),
localtimeOffsetMsec: this.localtimeOffsetMsec,
headers: extraHeaders
};
let request = this.newHAWKAuthenticatedRESTRequest(uri, credentials, extra);

View File

@ -42,7 +42,9 @@ const Prefs = new Preferences("services.common.rest.");
* Valid properties are:
*
* now: <current time in milliseconds>,
* localtimeOffsetMsec: <local clock offset vs server>
* localtimeOffsetMsec: <local clock offset vs server>,
* headers: <An object with header/value pairs to be sent
* as headers on the request>
*
* extra.localtimeOffsetMsec is the value in milliseconds that must be added to
* the local clock to make it agree with the server's clock. For instance, if
@ -58,6 +60,7 @@ this.HAWKAuthenticatedRESTRequest =
this.now = extra.now || Date.now();
this.localtimeOffsetMsec = extra.localtimeOffsetMsec || 0;
this._log.trace("local time, offset: " + this.now + ", " + (this.localtimeOffsetMsec));
this.extraHeaders = extra.headers || {};
// Expose for testing
this._intl = getIntl();
@ -83,6 +86,10 @@ HAWKAuthenticatedRESTRequest.prototype = {
this._log.trace("hawk auth header: " + header.field);
}
for (let header in this.extraHeaders) {
this.setHeader(header, this.extraHeaders[header]);
}
this.setHeader("Content-Type", contentType);
this.setHeader("Accept-Language", this._intl.accept_languages);

View File

@ -98,6 +98,29 @@ add_task(function test_authenticated_patch_request() {
check_authenticated_request("PATCH");
});
add_task(function* test_extra_headers() {
let server = httpd_setup({"/foo": (request, response) => {
do_check_true(request.hasHeader("Authorization"));
do_check_true(request.hasHeader("myHeader"));
do_check_eq(request.getHeader("myHeader"), "fake");
response.setStatusLine(request.httpVersion, 200, "OK");
response.setHeader("Content-Type", "application/json");
response.bodyOutputStream.writeFrom(request.bodyInputStream, request.bodyInputStream.available());
}
});
let client = new HawkClient(server.baseURI);
let response = yield client.request("/foo", "POST", TEST_CREDS, {foo: "bar"},
{"myHeader": "fake"});
let result = JSON.parse(response.body);
do_check_eq("bar", result.foo);
yield deferredStop(server);
});
add_task(function* test_credentials_optional() {
let method = "GET";
let server = httpd_setup({

Some files were not shown because too many files have changed in this diff Show More