mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Merge mozilla-central to mozilla-inbound
This commit is contained in:
commit
6b185089c6
@ -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");
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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]
|
||||
|
@ -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);
|
||||
});
|
@ -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);
|
||||
},
|
||||
|
10
browser/components/extensions/ext-desktop-runtime.js
Normal file
10
browser/components/extensions/ext-desktop-runtime.js
Normal 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 });
|
||||
}
|
||||
});
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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]
|
||||
|
@ -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");
|
||||
});
|
||||
});
|
||||
},
|
||||
});
|
||||
|
@ -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();
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -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);
|
||||
});
|
@ -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");
|
||||
|
@ -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
|
||||
|
@ -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]
|
||||
|
@ -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);
|
||||
}
|
12
browser/extensions/pocket/bootstrap.js
vendored
12
browser/extensions/pocket/bootstrap.js
vendored
@ -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_"]) {
|
||||
|
828
devtools/client/framework/devtools-browser.js
Normal file
828
devtools/client/framework/devtools-browser.js
Normal 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");
|
||||
|
511
devtools/client/framework/devtools.js
Normal file
511
devtools/client/framework/devtools.js
Normal 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
@ -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',
|
||||
|
@ -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", {});
|
||||
|
@ -17,6 +17,9 @@ createEnum([
|
||||
// Add an additional viewport to display the document.
|
||||
"ADD_VIEWPORT",
|
||||
|
||||
// Rotate the viewport.
|
||||
"ROTATE_VIEWPORT",
|
||||
|
||||
], module.exports);
|
||||
|
||||
/**
|
||||
|
@ -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,
|
||||
};
|
||||
},
|
||||
|
||||
};
|
||||
|
@ -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)),
|
||||
});
|
||||
},
|
||||
|
||||
|
@ -6,6 +6,7 @@
|
||||
|
||||
DevToolsModules(
|
||||
'browser.js',
|
||||
'viewport-toolbar.js',
|
||||
'viewport.js',
|
||||
'viewports.js',
|
||||
)
|
||||
|
@ -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,
|
||||
})
|
||||
);
|
||||
},
|
||||
|
||||
});
|
@ -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,
|
||||
|
@ -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),
|
||||
});
|
||||
})
|
||||
);
|
||||
|
9
devtools/client/responsive.html/images/moz.build
Normal file
9
devtools/client/responsive.html/images/moz.build
Normal 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',
|
||||
)
|
@ -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 |
@ -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 {
|
||||
|
@ -7,6 +7,7 @@
|
||||
DIRS += [
|
||||
'actions',
|
||||
'components',
|
||||
'images',
|
||||
'reducers',
|
||||
]
|
||||
|
||||
|
@ -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,
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
};
|
||||
|
@ -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");
|
||||
});
|
@ -6,3 +6,4 @@ firefox-appdir = browser
|
||||
|
||||
[test_add_viewport.js]
|
||||
[test_change_location.js]
|
||||
[test_rotate_viewport.js]
|
||||
|
@ -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,
|
||||
|
||||
|
@ -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();
|
||||
},
|
||||
|
@ -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");
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
|
@ -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) {
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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"));
|
||||
}
|
||||
}
|
||||
}
|
@ -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();
|
||||
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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',
|
||||
|
@ -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");
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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");
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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.");
|
||||
|
@ -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");
|
||||
}
|
||||
|
@ -10,4 +10,8 @@ public class NonArrayJSONException extends UnexpectedJSONException {
|
||||
public NonArrayJSONException(String detailMessage) {
|
||||
super(detailMessage);
|
||||
}
|
||||
|
||||
public NonArrayJSONException(Throwable throwable) {
|
||||
super(throwable);
|
||||
}
|
||||
}
|
||||
|
@ -10,4 +10,8 @@ public class NonObjectJSONException extends UnexpectedJSONException {
|
||||
public NonObjectJSONException(String detailMessage) {
|
||||
super(detailMessage);
|
||||
}
|
||||
|
||||
public NonObjectJSONException(Throwable throwable) {
|
||||
super(throwable);
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
|
@ -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.");
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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);
|
||||
}});
|
||||
}
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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");
|
||||
|
||||
|
@ -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();
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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");
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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"));
|
||||
|
@ -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";
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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());
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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.
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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";
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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"));
|
||||
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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
Loading…
Reference in New Issue
Block a user