Merge m-c to graphics

MozReview-Commit-ID: Ejcj1CD87t3
This commit is contained in:
Kartikaya Gupta 2017-02-09 10:12:03 -05:00
commit 61bc491994
1076 changed files with 19851 additions and 31366 deletions

View File

@ -134,3 +134,6 @@ GPATH
# tup database
^\.tup
subinclude:servo/.hgignore

View File

@ -22,4 +22,4 @@
# changes to stick? As of bug 928195, this shouldn't be necessary! Please
# don't change CLOBBER for WebIDL changes any more.
Touch clobber because of bug 1336456
Touch clobber again because of bug 1336456

View File

@ -569,25 +569,12 @@ var Output = {
}
},
get androidBridge() {
delete this.androidBridge;
if (Utils.MozBuildApp === 'mobile/android') {
this.androidBridge = Services.androidBridge;
} else {
this.androidBridge = null;
}
return this.androidBridge;
},
Android: function Android(aDetails, aBrowser) {
const ANDROID_VIEW_TEXT_CHANGED = 0x10;
const ANDROID_VIEW_TEXT_SELECTION_CHANGED = 0x2000;
if (!this.androidBridge) {
return;
}
for (let androidEvent of aDetails) {
androidEvent.type = 'Accessibility:Event';
if (androidEvent.bounds) {
androidEvent.bounds = AccessFu.adjustContentBounds(
androidEvent.bounds, aBrowser);
@ -607,9 +594,8 @@ var Output = {
androidEvent.brailleOutput);
break;
}
let win = Utils.win;
let view = win && win.QueryInterface(Ci.nsIAndroidView);
view.dispatch('Accessibility:Event', androidEvent);
Utils.win.WindowEventDispatcher.sendRequest(androidEvent);
}
},
@ -805,9 +791,7 @@ var Input = {
if (Utils.MozBuildApp == 'mobile/android') {
// Return focus to native Android browser chrome.
let win = Utils.win;
let view = win && win.QueryInterface(Ci.nsIAndroidView);
view.dispatch('ToggleChrome:Focus');
Utils.win.WindowEventDispatcher.dispatch('ToggleChrome:Focus');
}
break;
case aEvent.DOM_VK_RETURN:

View File

@ -315,7 +315,7 @@ AndroidPresenter.prototype.actionInvoked =
// Checkable objects use TalkBack's text derived from the event state,
// so we don't populate the text here.
let text = '';
let text = null;
if (!state.contains(States.CHECKABLE)) {
text = Utils.localize(UtteranceGenerator.genForAction(aObject,
aActionName));

View File

@ -1,5 +1,4 @@
[DEFAULT]
skip-if = (e10s && os == 'win') # Bug 1269369: Document loaded event does not fire in Windows
support-files =
events.js
head.js
@ -16,36 +15,62 @@ support-files =
# Caching tests
[browser_caching_attributes.js]
skip-if = e10s && os == 'win' # Bug 1288839
[browser_caching_description.js]
skip-if = e10s && os == 'win' && os_version == '5.1'
[browser_caching_name.js]
skip-if = e10s && os == 'win' && os_version == '5.1'
[browser_caching_relations.js]
skip-if = e10s && os == 'win' # Bug 1288839
[browser_caching_states.js]
skip-if = e10s && os == 'win' && os_version == '5.1'
[browser_caching_value.js]
skip-if = e10s && os == 'win' && os_version == '5.1'
# Events tests
[browser_events_caretmove.js]
skip-if = e10s && os == 'win' && os_version == '5.1'
[browser_events_hide.js]
skip-if = e10s && os == 'win' && os_version == '5.1'
[browser_events_show.js]
skip-if = e10s && os == 'win' && os_version == '5.1'
[browser_events_statechange.js]
skip-if = e10s && os == 'win' && os_version == '5.1'
[browser_events_textchange.js]
skip-if = e10s && os == 'win' && os_version == '5.1'
# Tree update tests
[browser_treeupdate_ariadialog.js]
skip-if = e10s && os == 'win' && os_version == '5.1'
[browser_treeupdate_ariaowns.js]
skip-if = e10s && os == 'win' && os_version == '5.1'
[browser_treeupdate_canvas.js]
skip-if = e10s && os == 'win' && os_version == '5.1'
[browser_treeupdate_cssoverflow.js]
skip-if = e10s && os == 'win' && os_version == '5.1'
[browser_treeupdate_doc.js]
skip-if = e10s && os == 'win' # Bug 1288839
[browser_treeupdate_gencontent.js]
skip-if = e10s && os == 'win' && os_version == '5.1'
[browser_treeupdate_hidden.js]
skip-if = e10s && os == 'win' && os_version == '5.1'
[browser_treeupdate_imagemap.js]
skip-if = e10s # Bug 1318569
skip-if = e10s && os == 'win' && os_version == '5.1'
[browser_treeupdate_list.js]
skip-if = e10s && os == 'win' && os_version == '5.1'
[browser_treeupdate_list_editabledoc.js]
skip-if = e10s && os == 'win' && os_version == '5.1'
[browser_treeupdate_listener.js]
skip-if = e10s && os == 'win' && os_version == '5.1'
[browser_treeupdate_optgroup.js]
skip-if = e10s && os == 'win' && os_version == '5.1'
[browser_treeupdate_removal.js]
skip-if = e10s && os == 'win' && os_version == '5.1'
[browser_treeupdate_table.js]
skip-if = e10s && os == 'win' && os_version == '5.1'
[browser_treeupdate_textleaf.js]
skip-if = e10s && os == 'win' && os_version == '5.1'
[browser_treeupdate_visibility.js]
skip-if = e10s && os == 'win' && os_version == '5.1'
[browser_treeupdate_whitespace.js]
skip-if = true # Failing due to incorrect index of test container children on document load.

View File

@ -170,7 +170,22 @@ function* testContainer(browser) {
testAccessibleTree(acc, tree);
}
function* waitForImageMap(browser, accDoc) {
const id = 'imgmap';
const acc = findAccessibleChildByID(accDoc, id);
if (acc.firstChild) {
return;
}
const onReorder = waitForEvent(EVENT_REORDER, id);
// Wave over image map
yield BrowserTestUtils.synthesizeMouse(`#${id}`, 10, 10,
{ type: 'mousemove' }, browser);
yield onReorder;
}
addAccessibleTask('doc_treeupdate_imagemap.html', function*(browser, accDoc) {
yield waitForImageMap(browser, accDoc);
yield testImageMap(browser, accDoc);
yield testContainer(browser);
});

View File

@ -492,7 +492,6 @@
@RESPATH@/components/nsWebHandlerApp.js
@RESPATH@/components/satchel.manifest
@RESPATH@/components/nsFormAutoComplete.js
@RESPATH@/components/nsFormHistory.js
@RESPATH@/components/FormHistoryStartup.js
@RESPATH@/components/nsInputListAutoComplete.js
@RESPATH@/components/formautofill.manifest

View File

@ -2878,24 +2878,6 @@ var BrowserOnClick = {
}
},
handleEvent(event) {
if (!event.isTrusted || // Don't trust synthetic events
event.button == 2) {
return;
}
let originalTarget = event.originalTarget;
let ownerDoc = originalTarget.ownerDocument;
if (!ownerDoc) {
return;
}
if (gMultiProcessBrowser &&
ownerDoc.documentURI.toLowerCase() == "about:newtab") {
this.onE10sAboutNewTab(event, ownerDoc);
}
},
receiveMessage(msg) {
switch (msg.name) {
case "Browser:CertExceptionError":
@ -3082,28 +3064,6 @@ var BrowserOnClick = {
}
},
/**
* This functions prevents navigation from happening directly through the <a>
* link in about:newtab (which is loaded in the parent and therefore would load
* the next page also in the parent) and instructs the browser to open the url
* in the current tab which will make it update the remoteness of the tab.
*/
onE10sAboutNewTab(event, ownerDoc) {
let isTopFrame = (ownerDoc.defaultView.parent === ownerDoc.defaultView);
if (!isTopFrame) {
return;
}
let anchorTarget = event.originalTarget.parentNode;
if (anchorTarget instanceof HTMLAnchorElement &&
anchorTarget.classList.contains("newtab-link")) {
event.preventDefault();
let where = whereToOpenLink(event, false, false);
openLinkIn(anchorTarget.href, where, { charset: ownerDoc.characterSet, referrerURI: ownerDoc.documentURIObject });
}
},
ignoreWarningButton(reason) {
// Allow users to override and continue through to the site,
// but add a notify bar as a reminder, so that they don't lose
@ -4924,13 +4884,9 @@ var TabsProgressListener = {
}
}
// Attach a listener to watch for "click" events bubbling up from error
// pages and other similar pages (like about:newtab). This lets us fix bugs
// like 401575 which require error page UI to do privileged things, without
// letting error pages have any privilege themselves.
// We can't look for this during onLocationChange since at that point the
// document URI is not yet the about:-uri of the error page.
// We used to listen for clicks in the browser here, but when that
// became unnecessary, removing the code below caused focus issues.
// This code should be removed. Tracked in bug 1337794.
let isRemoteBrowser = aBrowser.isRemoteBrowser;
// We check isRemoteBrowser here to avoid requesting the doc CPOW
let doc = isRemoteBrowser ? null : aWebProgress.DOMWindow.document;
@ -4945,11 +4901,9 @@ var TabsProgressListener = {
// STATE_STOP may be received twice for documents, thus store an
// attribute to ensure handling it just once.
doc.documentElement.setAttribute("hasBrowserHandlers", "true");
aBrowser.addEventListener("click", BrowserOnClick, true);
aBrowser.addEventListener("pagehide", function onPageHide(event) {
if (event.target.defaultView.frameElement)
return;
aBrowser.removeEventListener("click", BrowserOnClick, true);
aBrowser.removeEventListener("pagehide", onPageHide, true);
if (event.target.documentElement)
event.target.documentElement.removeAttribute("hasBrowserHandlers");

View File

@ -81,6 +81,8 @@ support-files =
browser_webext_update2.xpi
browser_webext_update_icon1.xpi
browser_webext_update_icon2.xpi
browser_webext_update_perms1.xpi
browser_webext_update_perms2.xpi
browser_webext_update.json
!/image/test/mochitest/blue.png
!/toolkit/content/tests/browser/common/mockTransfer.js
@ -252,7 +254,8 @@ skip-if = os == "mac" # decoder doctor isn't implemented on osx
skip-if = true # browser_drag.js is disabled, as it needs to be updated for the new behavior from bug 320638.
[browser_extension_permissions.js]
[browser_extension_sideloading.js]
[browser_extension_update.js]
[browser_extension_update_background.js]
[browser_extension_update_interactive.js]
[browser_favicon_change.js]
[browser_favicon_change_not_in_document.js]
[browser_findbarClose.js]
@ -349,7 +352,7 @@ skip-if = os == 'linux' # Bug 1304272
[browser_tab_detach_restore.js]
[browser_tab_drag_drop_perwindow.js]
[browser_tab_dragdrop.js]
skip-if = buildapp == 'mulet' || (e10s && (debug || asan)) # Bug 1312436
skip-if = buildapp == 'mulet' || (e10s && (debug || os == 'linux')) # Bug 1312436
[browser_tab_dragdrop2.js]
[browser_tabbar_big_widgets.js]
skip-if = os == "linux" || os == "mac" # No tabs in titlebar on linux

View File

@ -27,6 +27,11 @@ function* test_bookmarks_popup({isNewBookmark, popupShowFn, popupEditFn,
});
}
info(`BookmarkingUI.status is ${BookmarkingUI.status}`);
yield BrowserTestUtils.waitForCondition(
() => BookmarkingUI.status != BookmarkingUI.STATUS_UPDATING,
"BookmarkingUI should not be updating");
is(bookmarkStar.hasAttribute("starred"), !isNewBookmark,
"Page should only be starred prior to popupshown if editing bookmark");
is(bookmarkPanel.state, "closed", "Panel should be 'closed' to start test");

View File

@ -71,25 +71,6 @@ class MockProvider {
}
}
function promiseViewLoaded(tab, viewid) {
let win = tab.linkedBrowser.contentWindow;
if (win.gViewController && !win.gViewController.isLoading &&
win.gViewController.currentViewId == viewid) {
return Promise.resolve();
}
return new Promise(resolve => {
function listener() {
if (win.gViewController.currentViewId != viewid) {
return;
}
win.document.removeEventListener("ViewChanged", listener);
resolve();
}
win.document.addEventListener("ViewChanged", listener);
});
}
function promisePopupNotificationShown(name) {
return new Promise(resolve => {
function popupshown() {
@ -169,6 +150,17 @@ add_task(function* () {
ExtensionsUI.emit("change");
});
// Navigate away from the starting page to force about:addons to load
// in a new tab during the tests below.
gBrowser.selectedBrowser.loadURI("about:robots");
yield BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
registerCleanupFunction(function*() {
// Return to about:blank when we're done
gBrowser.selectedBrowser.loadURI("about:blank");
yield BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
});
let changePromise = new Promise(resolve => {
ExtensionsUI.on("change", function listener() {
ExtensionsUI.off("change", listener);
@ -189,22 +181,20 @@ add_task(function* () {
is(addons.children.length, 2, "Have 2 menu entries for sideloaded extensions");
// Click the first sideloaded extension
let tabPromise = BrowserTestUtils.waitForNewTab(gBrowser, "about:addons");
let popupPromise = promisePopupNotificationShown("addon-webext-permissions");
addons.children[0].click();
// about:addons should load and go to the list of extensions
let tab = yield tabPromise;
is(tab.linkedBrowser.currentURI.spec, "about:addons", "Newly opened tab is at about:addons");
// When we get the permissions prompt, we should be at the extensions
// list in about:addons
let panel = yield popupPromise;
is(gBrowser.currentURI.spec, "about:addons", "Foreground tab is at about:addons");
const VIEW = "addons://list/extension";
yield promiseViewLoaded(tab, VIEW);
let win = tab.linkedBrowser.contentWindow;
let win = gBrowser.selectedBrowser.contentWindow;
ok(!win.gViewController.isLoading, "about:addons view is fully loaded");
is(win.gViewController.currentViewId, VIEW, "about:addons is at extensions list");
// Wait for the permission prompt and cancel it
let panel = yield popupPromise;
// Check the contents of the notification, then choose "Cancel"
let icon = panel.getAttribute("icon");
is(icon, ICON_URL, "Permissions notification has the addon icon");
@ -219,7 +209,7 @@ add_task(function* () {
is(addon1.userDisabled, true, "Addon 1 should still be disabled");
is(addon2.userDisabled, true, "Addon 2 should still be disabled");
yield BrowserTestUtils.removeTab(tab);
yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
// Should still have 1 entry in the hamburger menu
yield PanelUI.show();
@ -227,23 +217,21 @@ add_task(function* () {
addons = document.getElementById("PanelUI-footer-addons");
is(addons.children.length, 1, "Have 1 menu entry for sideloaded extensions");
// Click the second sideloaded extension
tabPromise = BrowserTestUtils.waitForNewTab(gBrowser, "about:addons");
// Click the second sideloaded extension and wait for the notification
popupPromise = promisePopupNotificationShown("addon-webext-permissions");
addons.children[0].click();
tab = yield tabPromise;
is(tab.linkedBrowser.currentURI.spec, "about:addons", "Newly opened tab is at about:addons");
panel = yield popupPromise;
isnot(menuButton.getAttribute("badge-status"), "addon-alert", "Should no longer have addon alert badge");
yield promiseViewLoaded(tab, VIEW);
win = tab.linkedBrowser.contentWindow;
// Again we should be at the extentions list in about:addons
is(gBrowser.currentURI.spec, "about:addons", "Foreground tab is at about:addons");
win = gBrowser.selectedBrowser.contentWindow;
ok(!win.gViewController.isLoading, "about:addons view is fully loaded");
is(win.gViewController.currentViewId, VIEW, "about:addons is at extensions list");
// Wait for the permission prompt and accept it this time
panel = yield popupPromise;
// Check the notification contents, this time accept the install
icon = panel.getAttribute("icon");
is(icon, DEFAULT_ICON_URL, "Permissions notification has the default icon");
disablePromise = promiseSetDisabled(mock2);
@ -256,5 +244,5 @@ add_task(function* () {
is(addon1.userDisabled, true, "Addon 1 should still be disabled");
is(addon2.userDisabled, false, "Addon 2 should now be enabled");
yield BrowserTestUtils.removeTab(tab);
yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
});

View File

@ -74,8 +74,8 @@ function promiseInstallEvent(addon, event) {
}
// Set some prefs that apply to all the tests in this file
add_task(function setup() {
return SpecialPowers.pushPrefEnv({set: [
add_task(function* setup() {
yield SpecialPowers.pushPrefEnv({set: [
// We don't have pre-pinned certificates for the local mochitest server
["extensions.install.requireBuiltInCerts", false],
["extensions.update.requireBuiltInCerts", false],
@ -83,6 +83,17 @@ add_task(function setup() {
// XXX remove this when prompts are enabled by default
["extensions.webextPermissionPrompts", true],
]});
// Navigate away from the initial page so that about:addons always
// opens in a new tab during tests
gBrowser.selectedBrowser.loadURI("about:robots");
yield BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
registerCleanupFunction(function*() {
// Return to about:blank when we're done
gBrowser.selectedBrowser.loadURI("about:blank");
yield BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
});
});
// Helper function to test background updates.
@ -123,7 +134,7 @@ function* backgroundUpdateTest(url, id, checkIconFn) {
// about:addons should load and go to the list of extensions
let tab = yield tabPromise;
is(tab.linkedBrowser.currentURI.spec, "about:addons");
is(tab.linkedBrowser.currentURI.spec, "about:addons", "Browser is at about:addons");
const VIEW = "addons://list/extension";
yield promiseViewLoaded(tab, VIEW);
@ -211,114 +222,45 @@ function checkNonDefaultIcon(icon) {
add_task(() => backgroundUpdateTest(`${URL_BASE}/browser_webext_update_icon1.xpi`,
ID_ICON, checkNonDefaultIcon));
// Helper function to test a specific scenario for interactive updates.
// `checkFn` is a callable that triggers a check for updates.
// `autoUpdate` specifies whether the test should be run with
// updates applied automatically or not.
function* interactiveUpdateTest(autoUpdate, checkFn) {
// Test that an update that adds new non-promptable permissions is just
// applied without showing a notification dialog.
add_task(function*() {
yield SpecialPowers.pushPrefEnv({set: [
["extensions.update.autoUpdateDefault", autoUpdate],
// Turn on background updates
["extensions.update.enabled", true],
// Point updates to the local mochitest server
["extensions.update.url", `${URL_BASE}/browser_webext_update.json`],
["extensions.update.background.url", `${URL_BASE}/browser_webext_update.json`],
]});
// Trigger an update check, manually applying the update if we're testing
// without auto-update.
function* triggerUpdate(win, addon) {
let manualUpdatePromise;
if (!autoUpdate) {
manualUpdatePromise = new Promise(resolve => {
let listener = {
onNewInstall() {
AddonManager.removeInstallListener(listener);
resolve();
},
};
AddonManager.addInstallListener(listener);
});
}
checkFn(win, addon);
if (manualUpdatePromise) {
yield manualUpdatePromise;
let item = win.document.getElementById("addon-list")
.children.find(_item => _item.value == ID);
EventUtils.synthesizeMouseAtCenter(item._updateBtn, {}, win);
}
}
// Install version 1.0 of the test extension
let addon = yield promiseInstallAddon(`${URL_BASE}/browser_webext_update1.xpi`);
let addon = yield promiseInstallAddon(`${URL_BASE}/browser_webext_update_perms1.xpi`);
ok(addon, "Addon was installed");
is(addon.version, "1.0", "Version 1 of the addon is installed");
// Open add-ons manager and navigate to extensions list
let loadPromise = new Promise(resolve => {
let listener = (subject, topic) => {
if (subject.location.href == "about:addons") {
Services.obs.removeObserver(listener, topic);
resolve(subject);
}
};
Services.obs.addObserver(listener, "EM-loaded", false);
});
let tab = gBrowser.addTab("about:addons");
gBrowser.selectedTab = tab;
let win = yield loadPromise;
let sawPopup = false;
PopupNotifications.panel.addEventListener("popupshown",
() => sawPopup = true,
{once: true});
const VIEW = "addons://list/extension";
let viewPromise = promiseViewLoaded(tab, VIEW);
win.loadView(VIEW);
yield viewPromise;
// Trigger an update check
let popupPromise = promisePopupNotificationShown("addon-webext-permissions");
yield triggerUpdate(win, addon);
let panel = yield popupPromise;
// Click the cancel button, wait to see the cancel event
let cancelPromise = promiseInstallEvent(addon, "onInstallCancelled");
panel.secondaryButton.click();
yield cancelPromise;
addon = yield AddonManager.getAddonByID(ID);
is(addon.version, "1.0", "Should still be running the old version");
// Trigger a new update check
popupPromise = promisePopupNotificationShown("addon-webext-permissions");
yield triggerUpdate(win, addon);
// This time, accept the upgrade
// Trigger an update check and wait for the update to be applied.
let updatePromise = promiseInstallEvent(addon, "onInstallEnded");
panel = yield popupPromise;
panel.button.click();
AddonManagerPrivate.backgroundUpdateCheck();
yield updatePromise;
addon = yield updatePromise;
is(addon.version, "2.0", "Should have upgraded");
// There should be no notifications about the update
is(getBadgeStatus(), "", "Should not have addon alert badge");
yield PanelUI.show();
let addons = document.getElementById("PanelUI-footer-addons");
is(addons.children.length, 0, "Have 0 updates in the PanelUI menu");
yield PanelUI.hide();
ok(!sawPopup, "Should not have seen permissions notification");
addon = yield AddonManager.getAddonByID("update_perms@tests.mozilla.org");
is(addon.version, "2.0", "Update should have applied");
yield BrowserTestUtils.removeTab(tab);
addon.uninstall();
yield SpecialPowers.popPrefEnv();
}
// Invoke the "Check for Updates" menu item
function checkAll(win) {
win.gViewController.doCommand("cmd_findAllUpdates");
}
// Test "Check for Updates" with both auto-update settings
add_task(() => interactiveUpdateTest(true, checkAll));
add_task(() => interactiveUpdateTest(false, checkAll));
// Invoke an invidual extension's "Find Updates" menu item
function checkOne(win, addon) {
win.gViewController.doCommand("cmd_findItemUpdates", addon);
}
// Test "Find Updates" with both auto-update settings
add_task(() => interactiveUpdateTest(true, checkOne));
add_task(() => interactiveUpdateTest(false, checkOne));
});

View File

@ -0,0 +1,192 @@
const {AddonManagerPrivate} = Cu.import("resource://gre/modules/AddonManager.jsm", {});
const URL_BASE = "https://example.com/browser/browser/base/content/test/general";
const ID = "update@tests.mozilla.org";
function promiseInstallAddon(url) {
return AddonManager.getInstallForURL(url, null, "application/x-xpinstall")
.then(install => {
ok(install, "Created install");
return new Promise(resolve => {
install.addListener({
onInstallEnded(_install, addon) {
resolve(addon);
},
});
install.install();
});
});
}
function promiseViewLoaded(tab, viewid) {
let win = tab.linkedBrowser.contentWindow;
if (win.gViewController && !win.gViewController.isLoading &&
win.gViewController.currentViewId == viewid) {
return Promise.resolve();
}
return new Promise(resolve => {
function listener() {
if (win.gViewController.currentViewId != viewid) {
return;
}
win.document.removeEventListener("ViewChanged", listener);
resolve();
}
win.document.addEventListener("ViewChanged", listener);
});
}
function promisePopupNotificationShown(name) {
return new Promise(resolve => {
function popupshown() {
let notification = PopupNotifications.getNotification(name);
if (!notification) { return; }
ok(notification, `${name} notification shown`);
ok(PopupNotifications.isPanelOpen, "notification panel open");
PopupNotifications.panel.removeEventListener("popupshown", popupshown);
resolve(PopupNotifications.panel.firstChild);
}
PopupNotifications.panel.addEventListener("popupshown", popupshown);
});
}
function promiseInstallEvent(addon, event) {
return new Promise(resolve => {
let listener = {};
listener[event] = (install, ...args) => {
if (install.addon.id == addon.id) {
AddonManager.removeInstallListener(listener);
resolve(...args);
}
};
AddonManager.addInstallListener(listener);
});
}
// Set some prefs that apply to all the tests in this file
add_task(function* setup() {
yield SpecialPowers.pushPrefEnv({set: [
// We don't have pre-pinned certificates for the local mochitest server
["extensions.install.requireBuiltInCerts", false],
["extensions.update.requireBuiltInCerts", false],
// XXX remove this when prompts are enabled by default
["extensions.webextPermissionPrompts", true],
]});
});
// Helper function to test a specific scenario for interactive updates.
// `checkFn` is a callable that triggers a check for updates.
// `autoUpdate` specifies whether the test should be run with
// updates applied automatically or not.
function* interactiveUpdateTest(autoUpdate, checkFn) {
yield SpecialPowers.pushPrefEnv({set: [
["extensions.update.autoUpdateDefault", autoUpdate],
// Point updates to the local mochitest server
["extensions.update.url", `${URL_BASE}/browser_webext_update.json`],
]});
// Trigger an update check, manually applying the update if we're testing
// without auto-update.
function* triggerUpdate(win, addon) {
let manualUpdatePromise;
if (!autoUpdate) {
manualUpdatePromise = new Promise(resolve => {
let listener = {
onNewInstall() {
AddonManager.removeInstallListener(listener);
resolve();
},
};
AddonManager.addInstallListener(listener);
});
}
checkFn(win, addon);
if (manualUpdatePromise) {
yield manualUpdatePromise;
let item = win.document.getElementById("addon-list")
.children.find(_item => _item.value == ID);
EventUtils.synthesizeMouseAtCenter(item._updateBtn, {}, win);
}
}
// Install version 1.0 of the test extension
let addon = yield promiseInstallAddon(`${URL_BASE}/browser_webext_update1.xpi`);
ok(addon, "Addon was installed");
is(addon.version, "1.0", "Version 1 of the addon is installed");
// Open add-ons manager and navigate to extensions list
let loadPromise = new Promise(resolve => {
let listener = (subject, topic) => {
if (subject.location.href == "about:addons") {
Services.obs.removeObserver(listener, topic);
resolve(subject);
}
};
Services.obs.addObserver(listener, "EM-loaded", false);
});
let tab = gBrowser.addTab("about:addons");
gBrowser.selectedTab = tab;
let win = yield loadPromise;
const VIEW = "addons://list/extension";
let viewPromise = promiseViewLoaded(tab, VIEW);
win.loadView(VIEW);
yield viewPromise;
// Trigger an update check
let popupPromise = promisePopupNotificationShown("addon-webext-permissions");
yield triggerUpdate(win, addon);
let panel = yield popupPromise;
// Click the cancel button, wait to see the cancel event
let cancelPromise = promiseInstallEvent(addon, "onInstallCancelled");
panel.secondaryButton.click();
yield cancelPromise;
addon = yield AddonManager.getAddonByID(ID);
is(addon.version, "1.0", "Should still be running the old version");
// Trigger a new update check
popupPromise = promisePopupNotificationShown("addon-webext-permissions");
yield triggerUpdate(win, addon);
// This time, accept the upgrade
let updatePromise = promiseInstallEvent(addon, "onInstallEnded");
panel = yield popupPromise;
panel.button.click();
addon = yield updatePromise;
is(addon.version, "2.0", "Should have upgraded");
yield BrowserTestUtils.removeTab(tab);
addon.uninstall();
yield SpecialPowers.popPrefEnv();
}
// Invoke the "Check for Updates" menu item
function checkAll(win) {
win.gViewController.doCommand("cmd_findAllUpdates");
}
// Test "Check for Updates" with both auto-update settings
add_task(() => interactiveUpdateTest(true, checkAll));
add_task(() => interactiveUpdateTest(false, checkAll));
// Invoke an invidual extension's "Find Updates" menu item
function checkOne(win, addon) {
win.gViewController.doCommand("cmd_findItemUpdates", addon);
}
// Test "Find Updates" with both auto-update settings
add_task(() => interactiveUpdateTest(true, checkOne));
add_task(() => interactiveUpdateTest(false, checkOne));

View File

@ -94,6 +94,18 @@ const PAGECONTENT_COLORS =
' <option value="Four" class="defaultColor defaultBackground">{"color": "-moz-ComboboxText", "backgroundColor": "transparent", "unstyled": "true"}</option>' +
' <option value="Five" class="defaultColor">{"color": "-moz-ComboboxText", "backgroundColor": "transparent", "unstyled": "true"}</option>' +
' <option value="Six" class="defaultBackground">{"color": "-moz-ComboboxText", "backgroundColor": "transparent", "unstyled": "true"}</option>' +
' <option value="Seven" selected="true">{"unstyled": "true"}</option>' +
"</select></body></html>";
const PAGECONTENT_COLORS_ON_SELECT =
"<html><head><style>" +
" #one { background-color: #7E3A3A; color: #fff }" +
"</style>" +
"<body><select id='one'>" +
' <option value="One">{"color": "rgb(255, 255, 255)", "backgroundColor": "transparent"}</option>' +
' <option value="Two">{"color": "rgb(255, 255, 255)", "backgroundColor": "transparent"}</option>' +
' <option value="Three">{"color": "rgb(255, 255, 255)", "backgroundColor": "transparent"}</option>' +
' <option value="Four" selected="true">{"end": "true"}</option>' +
"</select></body></html>";
function openSelectPopup(selectPopup, mode = "key", selector = "select", win = window) {
@ -150,6 +162,38 @@ function getClickEvents() {
});
}
function testOptionColors(index, item, menulist) {
let expected = JSON.parse(item.label);
for (let color of Object.keys(expected)) {
if (color.toLowerCase().includes("color") &&
!expected[color].startsWith("rgb")) {
// Need to convert system color to RGB color.
let textarea = document.createElementNS("http://www.w3.org/1999/xhtml", "textarea");
textarea.style.color = expected[color];
expected[color] = getComputedStyle(textarea).color;
}
}
// Press Down to move the selected item to the next item in the
// list and check the colors of this item when it's not selected.
EventUtils.synthesizeKey("KEY_ArrowDown", { code: "ArrowDown" });
if (expected.end) {
return;
}
if (expected.unstyled) {
ok(!item.hasAttribute("customoptionstyling"),
`Item ${index} should not have any custom option styling`);
} else {
is(getComputedStyle(item).color, expected.color,
"Item " + (index) + " has correct foreground color");
is(getComputedStyle(item).backgroundColor, expected.backgroundColor,
"Item " + (index) + " has correct background color");
}
}
function* doSelectTests(contentType, dtd) {
const pageUrl = "data:" + contentType + "," + escape(dtd + "\n" + PAGECONTENT);
let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, pageUrl);
@ -745,11 +789,13 @@ add_task(function* test_somehidden() {
yield BrowserTestUtils.removeTab(tab);
});
add_task(function* test_colors_applied_to_popup() {
// This test checks when a <select> element has styles applied to <option>s within it.
add_task(function* test_colors_applied_to_popup_items() {
const pageUrl = "data:text/html," + escape(PAGECONTENT_COLORS);
let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, pageUrl);
let selectPopup = document.getElementById("ContentSelectDropdown").menupopup;
let menulist = document.getElementById("ContentSelectDropdown");
let selectPopup = menulist.menupopup;
let popupShownPromise = BrowserTestUtils.waitForEvent(selectPopup, "popupshown");
yield BrowserTestUtils.synthesizeMouseAtCenter("#one", { type: "mousedown" }, gBrowser.selectedBrowser);
@ -757,38 +803,47 @@ add_task(function* test_colors_applied_to_popup() {
// The label contains a JSON string of the expected colors for
// `color` and `background-color`.
is(selectPopup.parentNode.itemCount, 6, "Correct number of items");
is(selectPopup.parentNode.itemCount, 7, "Correct number of items");
let child = selectPopup.firstChild;
let idx = 1;
ok(child.selected, "The first child should be selected");
ok(!child.selected, "The first child should not be selected");
while (child) {
let expected = JSON.parse(child.label);
for (let color of Object.keys(expected)) {
if (color.toLowerCase().includes("color") &&
!expected[color].startsWith("rgb")) {
// Need to convert system color to RGB color.
let textarea = document.createElementNS("http://www.w3.org/1999/xhtml", "textarea");
textarea.style.color = expected[color];
expected[color] = getComputedStyle(textarea).color;
}
}
// Press Down to move the selected item to the next item in the
// list and check the colors of this item when it's not selected.
EventUtils.synthesizeKey("KEY_ArrowDown", { code: "ArrowDown" });
if (expected.unstyled) {
ok(!child.hasAttribute("customoptionstyling"),
`Item ${idx} should not have any custom option styling`);
} else {
is(getComputedStyle(child).color, expected.color,
"Item " + (idx) + " has correct foreground color");
is(getComputedStyle(child).backgroundColor, expected.backgroundColor,
"Item " + (idx) + " has correct background color");
}
testOptionColors(idx, child, menulist);
idx++;
child = child.nextSibling;
}
yield hideSelectPopup(selectPopup, "escape");
yield BrowserTestUtils.removeTab(tab);
});
// This test checks when a <select> element has styles applied to itself.
add_task(function* test_colors_applied_to_popup() {
const pageUrl = "data:text/html," + escape(PAGECONTENT_COLORS_ON_SELECT);
let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, pageUrl);
let menulist = document.getElementById("ContentSelectDropdown");
let selectPopup = menulist.menupopup;
let popupShownPromise = BrowserTestUtils.waitForEvent(selectPopup, "popupshown");
yield BrowserTestUtils.synthesizeMouseAtCenter("#one", { type: "mousedown" }, gBrowser.selectedBrowser);
yield popupShownPromise;
// The label contains a JSON string of the expected colors for
// `color` and `background-color`.
is(selectPopup.parentNode.itemCount, 4, "Correct number of items");
let child = selectPopup.firstChild;
let idx = 1;
is(getComputedStyle(selectPopup).color, "rgb(255, 255, 255)",
"popup has expected foreground color");
is(getComputedStyle(selectPopup).backgroundColor, "rgb(126, 58, 58)",
"popup has expected background color");
ok(!child.selected, "The first child should not be selected");
while (child) {
testOptionColors(idx, child, menulist);
idx++;
child = child.nextSibling;
}

View File

@ -27,6 +27,20 @@
}
}
]
},
"update_perms@tests.mozilla.org": {
"updates": [
{
"version": "2.0",
"update_link": "https://example.com/browser/browser/base/content/test/general/browser_webext_update_perms2.xpi",
"applications": {
"gecko": {
"strict_min_version": "1",
"advisory_max_version": "55.0"
}
}
}
]
}
}
}

View File

@ -148,9 +148,11 @@ var tests = [
EventUtils.synthesizeMouseAtCenter(checkbox, {});
dismissNotification(popup);
},
onHidden(popup) {
*onHidden(popup) {
let icon = document.getElementById("default-notification-icon");
let shown = waitForNotificationPanel();
EventUtils.synthesizeMouseAtCenter(icon, {});
yield shown;
let notification = popup.childNodes[0];
let checkbox = notification.checkbox;
checkCheckbox(checkbox, "This is a checkbox", true);

View File

@ -101,8 +101,8 @@ function* runNextTest() {
});
onPopupEvent("popuphidden", function() {
info("[" + nextTest.id + "] popup hidden");
nextTest.onHidden(this);
goNext();
Task.spawn(() => nextTest.onHidden(this))
.then(() => goNext(), ex => Assert.ok(false, "onHidden failed: " + ex));
}, () => shownState);
info("[" + nextTest.id + "] added listeners; panel is open: " + PopupNotifications.isPanelOpen);
}

View File

@ -6,9 +6,7 @@ support-files =
head.js
[browser_referrer_middle_click.js]
skip-if = true # Bug 1315042
[browser_referrer_middle_click_in_container.js]
skip-if = true # Bug 1315042
[browser_referrer_open_link_in_private.js]
skip-if = os == 'linux' # Bug 1145199
[browser_referrer_open_link_in_tab.js]

View File

@ -5,9 +5,10 @@ function startMiddleClickTestCase(aTestNumber) {
info("browser_referrer_middle_click: " +
getReferrerTestDescription(aTestNumber));
someTabLoaded(gTestWindow).then(function(aNewTab) {
gTestWindow.gBrowser.selectedTab = aNewTab;
checkReferrerAndStartNextTest(aTestNumber, null, aNewTab,
startMiddleClickTestCase);
BrowserTestUtils.switchTab(gTestWindow.gBrowser, aNewTab).then(() => {
checkReferrerAndStartNextTest(aTestNumber, null, aNewTab,
startMiddleClickTestCase);
});
});
clickTheLink(gTestWindow, "testlink", {button: 1});

View File

@ -5,10 +5,11 @@ function startMiddleClickTestCase(aTestNumber) {
info("browser_referrer_middle_click: " +
getReferrerTestDescription(aTestNumber));
someTabLoaded(gTestWindow).then(function(aNewTab) {
gTestWindow.gBrowser.selectedTab = aNewTab;
checkReferrerAndStartNextTest(aTestNumber, null, aNewTab,
startMiddleClickTestCase,
{ userContextId: 3 });
BrowserTestUtils.switchTab(gTestWindow.gBrowser, aNewTab).then(() => {
checkReferrerAndStartNextTest(aTestNumber, null, aNewTab,
startMiddleClickTestCase,
{ userContextId: 3 });
});
});
clickTheLink(gTestWindow, "testlink", {button: 1});

View File

@ -142,6 +142,9 @@ function delayedStartupFinished(aWindow) {
function someTabLoaded(aWindow) {
return new Promise(function(resolve) {
aWindow.gBrowser.addEventListener("load", function onLoad(aEvent) {
if (aWindow.location === "about:blank") {
return;
}
let tab = aWindow.gBrowser._getTabForContentWindow(
aEvent.target.defaultView.top);
if (tab) {

View File

@ -541,28 +541,32 @@ Cookies.prototype = {
},
_readCookieFile(aFile, aCallback) {
let fileReader = new FileReader();
let onLoadEnd = () => {
fileReader.removeEventListener("loadend", onLoadEnd);
File.createFromNsIFile(aFile).then(aFile => {
let fileReader = new FileReader();
let onLoadEnd = () => {
fileReader.removeEventListener("loadend", onLoadEnd);
if (fileReader.readyState != fileReader.DONE) {
Cu.reportError("Could not read cookie contents: " + fileReader.error);
aCallback(false);
return;
}
if (fileReader.readyState != fileReader.DONE) {
Cu.reportError("Could not read cookie contents: " + fileReader.error);
aCallback(false);
return;
}
let success = true;
try {
this._parseCookieBuffer(fileReader.result);
} catch (ex) {
Components.utils.reportError("Unable to migrate cookie: " + ex);
success = false;
} finally {
aCallback(success);
}
};
fileReader.addEventListener("loadend", onLoadEnd);
fileReader.readAsText(File.createFromNsIFile(aFile));
let success = true;
try {
this._parseCookieBuffer(fileReader.result);
} catch (ex) {
Components.utils.reportError("Unable to migrate cookie: " + ex);
success = false;
} finally {
aCallback(success);
}
};
fileReader.addEventListener("loadend", onLoadEnd);
fileReader.readAsText(aFile);
}, () => {
aCallback(false);
});
},
/**

View File

@ -12,6 +12,7 @@ Services.scriptloader.loadSubScript("resource://testing-common/sinon-1.16.1.js")
const TEST_HOST = "example.com";
const TEST_ORIGIN = "http://" + TEST_HOST;
const TEST_BASE_URL = TEST_ORIGIN + "/browser/browser/components/preferences/in-content/tests/";
const REMOVE_DIALOG_URL = "chrome://browser/content/preferences/siteDataRemoveSelected.xul";
const { NetUtil } = Cu.import("resource://gre/modules/NetUtil.jsm", {});
const { SiteDataManager } = Cu.import("resource:///modules/SiteDataManager.jsm", {});
@ -187,6 +188,22 @@ function promiseCookiesCleared() {
});
}
function assertSitesListed(doc, origins) {
let frameDoc = doc.getElementById("dialogFrame").contentDocument;
let removeBtn = frameDoc.getElementById("removeSelected");
let removeAllBtn = frameDoc.getElementById("removeAll");
let sitesList = frameDoc.getElementById("sitesList");
let totalSitesNumber = sitesList.getElementsByTagName("richlistitem").length;
is(totalSitesNumber, origins.length, "Should list the right sites number");
origins.forEach(origin => {
let site = sitesList.querySelector(`richlistitem[data-origin="${origin}"]`);
let host = site.getAttribute("host");
ok(origin.includes(host), `Should list the site of ${origin}`);
});
is(removeBtn.disabled, false, "Should enable the removeSelected button");
is(removeAllBtn.disabled, false, "Should enable the removeAllBtn button");
}
registerCleanupFunction(function() {
delete window.sinon;
delete window.setImmediate;
@ -370,28 +387,18 @@ add_task(function* () {
searchBox.value = "xyz";
searchBox.doCommand();
assertSitesListed(mockOrigins.filter(o => o.includes("xyz")));
assertSitesListed(doc, mockOrigins.filter(o => o.includes("xyz")));
searchBox.value = "bar";
searchBox.doCommand();
assertSitesListed(mockOrigins.filter(o => o.includes("bar")));
assertSitesListed(doc, mockOrigins.filter(o => o.includes("bar")));
searchBox.value = "";
searchBox.doCommand();
assertSitesListed(mockOrigins);
assertSitesListed(doc, mockOrigins);
mockSiteDataManager.unregister();
yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
function assertSitesListed(origins) {
let sitesList = frameDoc.getElementById("sitesList");
let totalSitesNumber = sitesList.getElementsByTagName("richlistitem").length;
is(totalSitesNumber, origins.length, "Should list the right sites number");
origins.forEach(origin => {
let site = sitesList.querySelector(`richlistitem[data-origin="${origin}"]`);
ok(site instanceof XULElement, `Should list the site of ${origin}`);
});
}
});
// Test selecting and removing all sites one by one
@ -478,19 +485,23 @@ add_task(function* () {
function assertAllSitesListed() {
frameDoc = doc.getElementById("dialogFrame").contentDocument;
let removeBtn = frameDoc.getElementById("removeSelected");
let removeAllBtn = frameDoc.getElementById("removeAll");
let sitesList = frameDoc.getElementById("sitesList");
let sites = sitesList.getElementsByTagName("richlistitem");
is(sites.length, fakeOrigins.length, "Should list all sites");
is(removeBtn.disabled, false, "Should enable the removeSelected button");
is(removeAllBtn.disabled, false, "Should enable the removeAllBtn button");
}
function assertAllSitesNotListed() {
frameDoc = doc.getElementById("dialogFrame").contentDocument;
let removeBtn = frameDoc.getElementById("removeSelected");
let removeAllBtn = frameDoc.getElementById("removeAll");
let sitesList = frameDoc.getElementById("sitesList");
let sites = sitesList.getElementsByTagName("richlistitem");
is(sites.length, 0, "Should not list all sites");
is(removeBtn.disabled, true, "Should disable the removeSelected button");
is(removeAllBtn.disabled, true, "Should disable the removeAllBtn button");
}
});
@ -512,7 +523,6 @@ add_task(function* () {
yield updatePromise;
yield openSettingsDialog();
const removeDialogURL = "chrome://browser/content/preferences/siteDataRemoveSelected.xul";
let doc = gBrowser.selectedBrowser.contentDocument;
let frameDoc = null;
let saveBtn = null;
@ -521,44 +531,44 @@ add_task(function* () {
let settingsDialogClosePromise = null;
// Test the initial state
assertSitesListed(fakeOrigins);
assertSitesListed(doc, fakeOrigins);
// Test the "Cancel" button
settingsDialogClosePromise = promiseSettingsDialogClose();
frameDoc = doc.getElementById("dialogFrame").contentDocument;
cancelBtn = frameDoc.getElementById("cancel");
removeSelectedSite(fakeOrigins.slice(0, 4));
assertSitesListed(fakeOrigins.slice(4));
assertSitesListed(doc, fakeOrigins.slice(4));
cancelBtn.doCommand();
yield settingsDialogClosePromise;
yield openSettingsDialog();
assertSitesListed(fakeOrigins);
assertSitesListed(doc, fakeOrigins);
// Test the "Save Changes" button but canceling save
removeDialogOpenPromise = promiseWindowDialogOpen("cancel", removeDialogURL);
removeDialogOpenPromise = promiseWindowDialogOpen("cancel", REMOVE_DIALOG_URL);
settingsDialogClosePromise = promiseSettingsDialogClose();
frameDoc = doc.getElementById("dialogFrame").contentDocument;
saveBtn = frameDoc.getElementById("save");
removeSelectedSite(fakeOrigins.slice(0, 4));
assertSitesListed(fakeOrigins.slice(4));
assertSitesListed(doc, fakeOrigins.slice(4));
saveBtn.doCommand();
yield removeDialogOpenPromise;
yield settingsDialogClosePromise;
yield openSettingsDialog();
assertSitesListed(fakeOrigins);
assertSitesListed(doc, fakeOrigins);
// Test the "Save Changes" button and accepting save
removeDialogOpenPromise = promiseWindowDialogOpen("accept", removeDialogURL);
removeDialogOpenPromise = promiseWindowDialogOpen("accept", REMOVE_DIALOG_URL);
settingsDialogClosePromise = promiseSettingsDialogClose();
frameDoc = doc.getElementById("dialogFrame").contentDocument;
saveBtn = frameDoc.getElementById("save");
removeSelectedSite(fakeOrigins.slice(0, 4));
assertSitesListed(fakeOrigins.slice(4));
assertSitesListed(doc, fakeOrigins.slice(4));
saveBtn.doCommand();
yield removeDialogOpenPromise;
yield settingsDialogClosePromise;
yield openSettingsDialog();
assertSitesListed(fakeOrigins.slice(4));
assertSitesListed(doc, fakeOrigins.slice(4));
// Always clean up the fake origins
fakeOrigins.forEach(origin => removePersistentStoragePerm(origin));
@ -578,17 +588,48 @@ add_task(function* () {
}
});
}
function assertSitesListed(origins) {
frameDoc = doc.getElementById("dialogFrame").contentDocument;
let removeBtn = frameDoc.getElementById("removeSelected");
let sitesList = frameDoc.getElementById("sitesList");
let totalSitesNumber = sitesList.getElementsByTagName("richlistitem").length;
is(totalSitesNumber, origins.length, "Should list the right sites number");
origins.forEach(origin => {
let site = sitesList.querySelector(`richlistitem[data-origin="${origin}"]`);
ok(!!site, `Should list the site of ${origin}`);
});
is(removeBtn.disabled, false, "Should enable the removeSelected button");
}
});
add_task(function* () {
yield SpecialPowers.pushPrefEnv({set: [["browser.storageManager.enabled", true]]});
let fakeOrigins = [
"https://news.foo.com/",
"https://books.foo.com/",
"https://mails.bar.com/",
"https://account.bar.com/",
"https://videos.xyz.com/",
"https://shopping.xyz.com/"
];
fakeOrigins.forEach(origin => addPersistentStoragePerm(origin));
let updatePromise = promiseSitesUpdated();
yield openPreferencesViaOpenPreferencesAPI("advanced", "networkTab", { leaveOpen: true });
yield updatePromise;
yield openSettingsDialog();
// Search "foo" to only list foo.com sites
let doc = gBrowser.selectedBrowser.contentDocument;
let frameDoc = doc.getElementById("dialogFrame").contentDocument;
let searchBox = frameDoc.getElementById("searchBox");
searchBox.value = "foo";
searchBox.doCommand();
assertSitesListed(doc, fakeOrigins.slice(0, 2));
// Test only removing all visible sites listed
updatePromise = promiseSitesUpdated();
let acceptRemovePromise = promiseWindowDialogOpen("accept", REMOVE_DIALOG_URL);
let settingsDialogClosePromise = promiseSettingsDialogClose();
let removeAllBtn = frameDoc.getElementById("removeAll");
let saveBtn = frameDoc.getElementById("save");
removeAllBtn.doCommand();
saveBtn.doCommand();
yield acceptRemovePromise;
yield settingsDialogClosePromise;
yield updatePromise;
yield openSettingsDialog();
assertSitesListed(doc, fakeOrigins.slice(2));
// Always clean up the fake origins
fakeOrigins.forEach(origin => removePersistentStoragePerm(origin));
yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
});

View File

@ -40,23 +40,25 @@ let gSiteDataSettings = {
let sortCol = document.getElementById("hostCol");
this._sortSites(this._sites, sortCol);
this._buildSitesList(this._sites);
this._updateButtonsState();
Services.obs.notifyObservers(null, "sitedata-settings-init", null);
});
setEventListener("hostCol", "click", this.onClickTreeCol);
setEventListener("usageCol", "click", this.onClickTreeCol);
setEventListener("statusCol", "click", this.onClickTreeCol);
setEventListener("searchBox", "command", this.onCommandSearch);
setEventListener("cancel", "command", this.close);
setEventListener("save", "command", this.saveChanges);
setEventListener("removeSelected", "command", this.removeSelected);
setEventListener("searchBox", "command", this.onCommandSearch);
setEventListener("removeAll", "command", this.onClickRemoveAll);
setEventListener("removeSelected", "command", this.onClickRemoveSelected);
},
_updateButtonsState() {
let items = this._list.getElementsByTagName("richlistitem");
let removeBtn = document.getElementById("removeSelected");
removeBtn.disabled = !(items.length > 0);
let removeSelectedBtn = document.getElementById("removeSelected");
let removeAllBtn = document.getElementById("removeAll");
removeSelectedBtn.disabled = items.length == 0;
removeAllBtn.disabled = removeSelectedBtn.disabled;
},
/**
@ -136,30 +138,22 @@ let gSiteDataSettings = {
item.setAttribute("usage", prefStrBundle.getFormattedString("siteUsage", size));
this._list.appendChild(item);
}
this._updateButtonsState();
},
onClickTreeCol(e) {
this._sortSites(this._sites, e.target);
this._buildSitesList(this._sites);
},
onCommandSearch() {
this._buildSitesList(this._sites);
},
removeSelected() {
let selected = this._list.selectedItem;
if (selected) {
let origin = selected.getAttribute("data-origin");
_removeSiteItems(items) {
for (let i = items.length - 1; i >= 0; --i) {
let item = items[i];
let origin = item.getAttribute("data-origin");
for (let site of this._sites) {
if (site.uri.spec === origin) {
site.userAction = "remove";
break;
}
}
this._list.removeChild(selected);
this._updateButtonsState();
item.remove();
}
this._updateButtonsState();
},
saveChanges() {
@ -235,5 +229,28 @@ let gSiteDataSettings = {
close() {
window.close();
},
onClickTreeCol(e) {
this._sortSites(this._sites, e.target);
this._buildSitesList(this._sites);
},
onCommandSearch() {
this._buildSitesList(this._sites);
},
onClickRemoveSelected() {
let selected = this._list.selectedItem;
if (selected) {
this._removeSiteItems([selected]);
}
},
onClickRemoveAll() {
let siteItems = this._list.getElementsByTagName("richlistitem");
if (siteItems.length > 0) {
this._removeSiteItems(siteItems);
}
}
};

View File

@ -44,6 +44,7 @@
<hbox align="start">
<button id="removeSelected" label="&removeSelected.label;" accesskey="&removeSelected.accesskey;"/>
<button id="removeAll" label="&removeAll.label;" accesskey="&removeAll.accesskey;"/>
</hbox>
<vbox align="end">

View File

@ -1561,10 +1561,22 @@
if (rowCount == 1 && hasDummyItems) {
// When there's only one row, make the compact settings button
// hug the right edge of the panel. It may not due to the panel's
// width not being an integral factor of the button width. (See
// width not being an integral multiple of the button width. (See
// the "There will be an emtpy area" comment above.) Increase the
// width of the last dummy item by the remainder.
//
// There's one weird thing to guard against. When layout pixels
// aren't an integral multiple of device pixels, the calculated
// remainder can end up being ~1px too big, at least on Windows,
// which pushes the settings button to a new row. The remainder
// is integral, not a fraction, so that's not the problem. To
// work around that, unscale the remainder, floor it, scale it
// back, and then floor that.
let scale = window.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils)
.screenPixelsPerCSSPixel;
let remainder = panelWidth - (enginesPerRow * buttonWidth);
remainder = Math.floor(Math.floor(remainder * scale) / scale);
let width = remainder + buttonWidth;
let lastDummyItem = this.settingsButton.previousSibling;
lastDummyItem.setAttribute("width", width);

View File

@ -27,9 +27,12 @@ skip-if = os == "mac" # bug 967013
[browser_ddg.js]
[browser_ddg_behavior.js]
[browser_google.js]
skip-if = artifact # bug 1315953
[browser_google_codes.js]
skip-if = artifact # bug 1315953
[browser_google_nocodes.js]
[browser_google_behavior.js]
skip-if = artifact # bug 1315953
[browser_healthreport.js]
[browser_hiddenOneOffs_cleanup.js]
[browser_hiddenOneOffs_diacritics.js]

View File

@ -0,0 +1,3 @@
. "$topsrcdir/browser/config/mozconfigs/linux32/debug"
ac_add_options --enable-webrender

View File

@ -0,0 +1,3 @@
. "$topsrcdir/browser/config/mozconfigs/linux32/nightly"
ac_add_options --enable-webrender

View File

@ -0,0 +1,3 @@
. "$topsrcdir/browser/config/mozconfigs/win32/debug"
ac_add_options --enable-webrender

View File

@ -0,0 +1,3 @@
. "$topsrcdir/browser/config/mozconfigs/win32/nightly"
ac_add_options --enable-webrender

View File

@ -1,10 +0,0 @@
[
{
"version": "clang 3.8.0, libgcc 4.8.5",
"size": 140319580,
"digest": "34e219d7e8eaffa81710631c34d21355563d06335b3c00851e94c1f42f9098788fded8463dd0f67dd699f77b47a0245dd7aff754943a7a03fb5fd145a808254f",
"algorithm": "sha512",
"filename": "clang.tar.xz",
"unpack": true
}
]

View File

@ -1,19 +1,19 @@
[
{
"version": "clang 3.8.0, libgcc 4.8.5",
"size": 140319580,
"digest": "34e219d7e8eaffa81710631c34d21355563d06335b3c00851e94c1f42f9098788fded8463dd0f67dd699f77b47a0245dd7aff754943a7a03fb5fd145a808254f",
"version": "clang 3.9.0",
"size": 168062128,
"digest": "2a5458a25792fcade86a56ff0f4acdfa284d2b62966991a7c34a92c2e8c0b4a162ce00512d4467754e7f74598d64c56e91517e1606ed3fba011f7c10e8ad3288",
"algorithm": "sha512",
"filename": "clang.tar.xz",
"unpack": true
},
{
"size": 3008804,
"size": 6075028,
"visibility": "public",
"digest": "ba6937f14f3d8b26dcb2d39490dee6b0a8afb60f672f5debb71d7b62c1ec52103201b4b1a3d258f945567de531384b36ddb2ce4aa73dc63d72305b11c146847c",
"digest": "0b962ba55a5a2fbae44218683bdf6ea0dfe8165aba436173a065f7190976184586b9acf4c23478bc5b6d81a3e00f681bf16df0536c9c9718ad0570d064f69027",
"algorithm": "sha512",
"unpack": true,
"filename": "cctools.tar.gz"
"filename": "cctools.tar.xz"
},
{
"size": 35215976,
@ -41,19 +41,19 @@
"filename": "dmg.tar.xz"
},
{
"size": 188880,
"visibility": "public",
"digest": "1ffddd43efb03aed897ee42035d9d8d758a8d66ab6c867599ef755e1a586768fc22011ce03698af61454920b00fe8bed08c9a681e7bd324d7f8f78c026c83943",
"algorithm": "sha512",
"unpack": true,
"filename": "genisoimage.tar.xz"
},
{
"version": "rustc 1.14.0 (e8a012324 2016-12-16) repack",
"size": 152573516,
"digest": "eef2f10bf57005d11c34b9b49eb76d0f09d026295055039fea89952a3be51580abdab29366278ed4bfa393b33c5cee4d51d3af4221e9e7d7d833d0fc1347597c",
"algorithm": "sha512",
"filename": "rustc.tar.xz",
"unpack": true
},
{
"size": 281576,
"visibility": "public",
"digest": "71616564533d138fb12f08e761c2638d054814fdf9c9439638ec57b201e100445c364d73d8d7a4f0e3b784898d5fe6264e8242863fc5ac40163f1791468bbc46",
"algorithm": "sha512",
"filename": "hfsplus-tools.tar.xz",
"unpack": true
}
]

View File

@ -0,0 +1,89 @@
'use strict';
class PasswordPrompt {
/**
* @constructs PasswordPrompt
*/
constructor(viewport) {
this.overlayName = 'passwordOverlay';
this.container = document.getElementById('passwordOverlay');
this.overlaysParent = document.getElementById('overlayContainer');
this.label = document.getElementById('passwordText');
this.input = document.getElementById('password');
this.submitButton = document.getElementById('passwordSubmit');
this.cancelButton = document.getElementById('passwordCancel');
this.updateCallback = null;
this.reason = null;
this.active = false;
this._viewport = viewport;
// PDFium doesn't return the result of password check. We count the retries
// to determine whether to show "invalid password" prompt instead.
// PDFium allows at most 3 times of tries.
this._passwordTries = 0;
this._viewport.onPasswordRequest = this.open.bind(this);
// Attach the event listeners.
this.submitButton.addEventListener('click', this);
this.cancelButton.addEventListener('click', this);
this.input.addEventListener('keydown', this);
}
handleEvent(e) {
switch(e.type) {
case 'keydown':
if (e.target == this.input && e.keyCode === KeyEvent.DOM_VK_RETURN) {
this.verify();
e.stopPropagation();
} else if (e.currentTarget == window &&
e.keyCode === KeyEvent.DOM_VK_ESCAPE) {
this.close();
e.preventDefault();
e.stopImmediatePropagation();
}
break;
case 'click':
if (e.target == this.submitButton) {
this.verify();
} else if (e.target == this.cancelButton) {
this.close();
}
break;
}
}
open() {
this.container.classList.remove('hidden');
this.overlaysParent.classList.remove('hidden');
window.addEventListener('keydown', this);
this.active = true;
let promptKey = this._passwordTries ? 'password_invalid' : 'password_label';
this._passwordTries++;
this.input.type = 'password';
this.input.focus();
document.l10n.formatValue(promptKey).then(promptString => {
this.label.textContent = promptString;
});
}
close() {
this.container.classList.add('hidden');
this.overlaysParent.classList.add('hidden');
window.removeEventListener('keydown', this);
this.active = false;
this.input.value = '';
this.input.type = '';
}
verify() {
let password = this.input.value;
if (password && password.length > 0) {
this.close();
this._viewport.verifyPassword(password);
}
}
}

View File

@ -7,6 +7,7 @@
window.addEventListener('DOMContentLoaded', function() {
let viewport = new Viewport();
let toolbar = new Toolbar(viewport);
let passwordPrompt = new PasswordPrompt(viewport);
// Expose the custom viewport object to runtime
window.createCustomViewport = function(actionHandler) {

View File

@ -36,6 +36,7 @@ class Viewport {
this.onZoomChanged = null;
this.onDimensionChanged = null;
this.onPageChanged = null;
this.onPasswordRequest = null;
this._viewportController.addEventListener('scroll', this);
window.addEventListener('resize', this);
@ -123,14 +124,14 @@ class Viewport {
}
_getScrollbarWidth() {
var div = document.createElement('div');
let div = document.createElement('div');
div.style.visibility = 'hidden';
div.style.overflow = 'scroll';
div.style.width = '50px';
div.style.height = '50px';
div.style.position = 'absolute';
document.body.appendChild(div);
var result = div.offsetWidth - div.clientWidth;
let result = div.offsetWidth - div.clientWidth;
div.remove();
return result;
}
@ -468,6 +469,13 @@ class Viewport {
}
}
verifyPassword(password) {
this._doAction({
type: 'getPasswordComplete',
password: password
});
}
handleEvent(evt) {
switch(evt.type) {
case 'resize':
@ -591,6 +599,9 @@ class Viewport {
case 'fullscreenChange':
this._handleFullscreenChange(message.fullscreen);
break;
case 'getPassword':
this.onPasswordRequest && this.onPasswordRequest();
break;
}
}
}

View File

@ -18,6 +18,7 @@
<script src="js/polyfill.js"></script>
<script src="js/toolbar.js"></script>
<script src="js/viewport.js"></script>
<script src="js/password-prompt.js"></script>
<script src="js/viewer.js"></script>
</head>

View File

@ -7,6 +7,14 @@ const {utils: Cu} = Components;
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/Preferences.jsm");
Cu.import("resource://gre/modules/Log.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "LogManager",
"resource://shield-recipe-client/lib/LogManager.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "RecipeRunner",
"resource://shield-recipe-client/lib/RecipeRunner.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "CleanupManager",
"resource://shield-recipe-client/lib/CleanupManager.jsm");
const REASONS = {
APP_STARTUP: 1, // The application is starting up.
@ -16,7 +24,7 @@ const REASONS = {
ADDON_INSTALL: 5, // The add-on is being installed.
ADDON_UNINSTALL: 6, // The add-on is being uninstalled.
ADDON_UPGRADE: 7, // The add-on is being upgraded.
ADDON_DOWNGRADE: 8, //The add-on is being downgraded.
ADDON_DOWNGRADE: 8, // The add-on is being downgraded.
};
const PREF_BRANCH = "extensions.shield-recipe-client.";
@ -53,19 +61,15 @@ this.startup = function() {
}
// Setup logging and listen for changes to logging prefs
Cu.import("resource://shield-recipe-client/lib/LogManager.jsm");
LogManager.configure(Services.prefs.getIntPref(PREF_LOGGING_LEVEL));
Preferences.observe(PREF_LOGGING_LEVEL, LogManager.configure);
CleanupManager.addCleanupHandler(
() => Preferences.ignore(PREF_LOGGING_LEVEL, LogManager.configure));
Cu.import("resource://shield-recipe-client/lib/RecipeRunner.jsm");
RecipeRunner.init();
};
this.shutdown = function(data, reason) {
Preferences.ignore(PREF_LOGGING_LEVEL, LogManager.configure);
Cu.import("resource://shield-recipe-client/lib/CleanupManager.jsm");
CleanupManager.cleanup();
if (reason === REASONS.ADDON_DISABLE || reason === REASONS.ADDON_UNINSTALL) {

View File

@ -5,7 +5,6 @@
"use strict";
const {utils: Cu} = Components;
this.EXPORTED_SYMBOLS = ["CleanupManager"];
const cleanupHandlers = new Set();

View File

@ -6,6 +6,7 @@
const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/Preferences.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/TelemetryArchive.jsm");
Cu.import("resource://gre/modules/Task.jsm");
@ -74,14 +75,16 @@ this.EnvExpressions = {
eval(expr, extraContext = {}) {
// First clone the extra context
const context = Object.assign({}, extraContext);
const context = Object.assign({normandy: {}}, extraContext);
// jexl handles promises, so it is fine to include them in this data.
context.telemetry = EnvExpressions.getLatestTelemetry();
context.normandy = context.normandy || {};
context.normandy.userId = EnvExpressions.getUserId();
context.normandy = Object.assign(context.normandy, {
userId: EnvExpressions.getUserId(),
distribution: Preferences.get("distribution.id", "default"),
});
const onelineExpr = expr.replace(/[\t\n\r]/g, " ");
return jexl.eval(onelineExpr, context);
},
};

View File

@ -9,7 +9,7 @@ Cu.import("resource://gre/modules/Log.jsm");
this.EXPORTED_SYMBOLS = ["LogManager"];
const ROOT_LOGGER_NAME = "extensions.shield-recipe-client"
const ROOT_LOGGER_NAME = "extensions.shield-recipe-client";
let rootLogger = null;
this.LogManager = {

View File

@ -75,8 +75,12 @@ this.NormandyDriver = function(sandboxManager, extraContext = {}) {
isDefaultBrowser: ShellService.isDefaultBrowser() || null,
searchEngine: null,
syncSetup: Preferences.isSet("services.sync.username"),
syncDesktopDevices: Preferences.get("services.sync.clients.devices.desktop", 0),
syncMobileDevices: Preferences.get("services.sync.clients.devices.mobile", 0),
syncTotalDevices: Preferences.get("services.sync.numClients", 0),
plugins: {},
doNotTrack: Preferences.get("privacy.donottrackheader.enabled", false),
distribution: Preferences.get("distribution.id", "default"),
};
const searchEnginePromise = new Promise(resolve => {

View File

@ -82,4 +82,13 @@ add_task(function* () {
"[normandy.userId, normandy.injectedValue]",
{normandy: {injectedValue: "injected"}});
Assert.deepEqual(val, ["fake id", "injected"], "context is correctly merged");
// distribution id defaults to "default"
val = yield EnvExpressions.eval("normandy.distribution");
Assert.equal(val, "default", "distribution has a default value");
// distribution id is in the context
yield SpecialPowers.pushPrefEnv({set: [["distribution.id", "funnelcake"]]});
val = yield EnvExpressions.eval("normandy.distribution");
Assert.equal(val, "funnelcake", "distribution is read from preferences");
});

View File

@ -18,3 +18,32 @@ add_task(Utils.withDriver(Assert, function* userId(driver) {
// Test that userId is a UUID
ok(Utils.UUID_REGEX.test(driver.userId), "userId is a uuid");
}));
add_task(Utils.withDriver(Assert, function* syncDeviceCounts(driver) {
let client = yield driver.client();
is(client.syncMobileDevices, 0, "syncMobileDevices defaults to zero");
is(client.syncDesktopDevices, 0, "syncDesktopDevices defaults to zero");
is(client.syncTotalDevices, 0, "syncTotalDevices defaults to zero");
yield SpecialPowers.pushPrefEnv({
set: [
["services.sync.numClients", 9],
["services.sync.clients.devices.mobile", 5],
["services.sync.clients.devices.desktop", 4],
],
});
client = yield driver.client();
is(client.syncMobileDevices, 5, "syncMobileDevices is read when set");
is(client.syncDesktopDevices, 4, "syncDesktopDevices is read when set");
is(client.syncTotalDevices, 9, "syncTotalDevices is read when set");
}));
add_task(Utils.withDriver(Assert, function* distribution(driver) {
let client = yield driver.client();
is(client.distribution, "default", "distribution has a default value");
yield SpecialPowers.pushPrefEnv({set: [["distribution.id", "funnelcake"]]});
client = yield driver.client();
is(client.distribution, "funnelcake", "distribution is read from preferences");
}));

View File

@ -465,7 +465,6 @@
@RESPATH@/components/nsWebHandlerApp.js
@RESPATH@/components/satchel.manifest
@RESPATH@/components/nsFormAutoComplete.js
@RESPATH@/components/nsFormHistory.js
@RESPATH@/components/FormHistoryStartup.js
@RESPATH@/components/nsInputListAutoComplete.js
@RESPATH@/components/formautofill.manifest

View File

@ -108,6 +108,7 @@ Var PreviousInstallArch
Var ControlHeightPX
Var ControlRightPX
Var ControlTopAdjustment
Var OptionsItemWidthPX
; Uncomment the following to prevent pinging the metrics server when testing
; the stub installer
@ -905,6 +906,16 @@ Function createOptions
StrCpy $ExistingTopDir ""
StrCpy $ControlTopAdjustment 0
; Convert the options item width to pixels, so we can tell when a text string
; exceeds this width and needs multiple lines.
StrCpy $2 "${OPTIONS_ITEM_WIDTH_DU}" -1
IntOp $2 $2 - 14 ; subtract approximate width of a checkbox
System::Call "*(i r2,i,i,i) p .r3"
System::Call "user32::MapDialogRect(p $HWNDPARENT, p r3)"
System::Call "*$3(i .s,i,i,i)"
Pop $OptionsItemWidthPX
System::Free $3
nsDialogs::Create /NOUNLOAD 1018
Pop $Dialog
; Since the text color for controls is set in this Dialog the foreground and
@ -999,7 +1010,7 @@ Function createOptions
; In that case, we'll need to give the control two lines worth of height.
StrCpy $1 12 ; single line height
${GetTextExtent} $0 $FontNormal $R1 $R2
${If} $R1 > ${OPTIONS_ITEM_WIDTH_DU}
${If} $R1 > $OptionsItemWidthPX
; Add a second line to the control height.
IntOp $1 $1 + 12
; The rest of the controls will have to be lower to account for this label
@ -1021,14 +1032,14 @@ Function createOptions
; In that case, we'll need to give the control two lines worth of height.
StrCpy $1 12 ; single line height
${GetTextExtent} "$(SEND_PING)" $FontNormal $R1 $R2
${If} $R1 > ${OPTIONS_ITEM_WIDTH_DU}
${If} $R1 > $OptionsItemWidthPX
; Add a second line to the control height.
IntOp $1 $1 + 12
; The rest of the controls will have to be lower to account for this label
; needing two lines worth of height.
IntOp $ControlTopAdjustment $ControlTopAdjustment + 12
${EndIf}
${NSD_CreateCheckbox} ${OPTIONS_ITEM_EDGE_DU} "$0u" ${OPTIONS_SUBITEM_WIDTH_DU} \
${NSD_CreateCheckbox} ${OPTIONS_ITEM_EDGE_DU} "$0u" ${OPTIONS_ITEM_WIDTH_DU} \
"$1u" "$(SEND_PING)"
Pop $CheckboxSendPing
; The uxtheme must be disabled on checkboxes in order to override the system
@ -1058,8 +1069,19 @@ Function createOptions
ReadRegStr $0 HKLM "SYSTEM\CurrentControlSet\services\MozillaMaintenance" "ImagePath"
${If} ${Errors}
IntOp $0 132 + $ControlTopAdjustment
; In some locales, this string may be too long to fit on one line.
; In that case, we'll need to give the control two lines worth of height.
StrCpy $1 12 ; single line height
${GetTextExtent} "$(INSTALL_MAINT_SERVICE)" $FontNormal $R1 $R2
${If} $R1 > $OptionsItemWidthPX
; Add a second line to the control height.
IntOp $1 $1 + 12
; The rest of the controls will have to be lower to account for this label
; needing two lines worth of height.
IntOp $ControlTopAdjustment $ControlTopAdjustment + 12
${EndIf}
${NSD_CreateCheckbox} ${OPTIONS_ITEM_EDGE_DU} "$0u" ${OPTIONS_ITEM_WIDTH_DU} \
12u "$(INSTALL_MAINT_SERVICE)"
"$1u" "$(INSTALL_MAINT_SERVICE)"
Pop $CheckboxInstallMaintSvc
System::Call 'uxtheme::SetWindowTheme(i $CheckboxInstallMaintSvc, w " ", w " ")'
SetCtlColors $CheckboxInstallMaintSvc ${COMMON_TEXT_COLOR_NORMAL} ${COMMON_BKGRD_COLOR}

View File

@ -61,12 +61,12 @@ webextPerms.sideloadMenuItem=%1$S added to %2$S
# %S is replaced with the localized name of the extension being installed.
# Note, this string will be used as raw markup. Avoid characters like <, >, &
webextPerms.sideloadHeader=%S added
webextPerms.sideloadText=Another program on your computer installed an add-on that may affect your browser. Please review this add-ons permissions requests and choose to Enable or Disable.
webextPerms.sideloadText2=Another program on your computer installed an add-on that may affect your browser. Please review this add-ons permissions requests and choose to Enable or Cancel (to leave it disabled).
webextPerms.sideloadEnable.label=Enable
webextPerms.sideloadEnable.accessKey=E
webextPerms.sideloadDisable.label=Disable
webextPerms.sideloadDisable.accessKey=D
webextPerms.sideloadCancel.label=Cancel
webextPerms.sideloadCancel.accessKey=C
# LOCALIZATION NOTE (webextPerms.updateMenuItem)
# %S will be replaced with the localized name of the extension which

View File

@ -11,6 +11,8 @@
<!ENTITY search.accesskey "S">
<!ENTITY removeSelected.label "Remove Selected">
<!ENTITY removeSelected.accesskey "r">
<!ENTITY removeAll.label "Remove All">
<!ENTITY removeAll.accesskey "e">
<!ENTITY save.label "Save Changes">
<!ENTITY save.accesskey "a">
<!ENTITY cancel.label "Cancel">

View File

@ -24,6 +24,9 @@ XPCOMUtils.defineLazyPreferenceGetter(this, "WEBEXT_PERMISSION_PROMPTS",
const DEFAULT_EXTENSION_ICON = "chrome://mozapps/skin/extensions/extensionGeneric.svg";
const BROWSER_PROPERTIES = "chrome://browser/locale/browser.properties";
const BRAND_PROPERTIES = "chrome://browser/locale/brand.properties";
const HTML_NS = "http://www.w3.org/1999/xhtml";
this.ExtensionsUI = {
@ -65,22 +68,13 @@ this.ExtensionsUI = {
});
},
showAddonsManager(browser, info) {
let loadPromise = new Promise(resolve => {
let listener = (subject, topic) => {
if (subject.location.href == "about:addons") {
Services.obs.removeObserver(listener, topic);
resolve(subject);
}
};
Services.obs.addObserver(listener, "EM-loaded", false);
});
let tab = browser.addTab("about:addons");
browser.selectedTab = tab;
return loadPromise.then(win => {
win.loadView("addons://list/extension");
return this.showPermissionsPrompt(browser.selectedBrowser, info);
showAddonsManager(browser, strings, icon) {
let global = browser.selectedBrowser.ownerGlobal;
return global.BrowserOpenAddonsMgr("addons://list/extension").then(aomWin => {
let aomBrowser = aomWin.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDocShell)
.chromeEventHandler;
return this.showPermissionsPrompt(aomBrowser, strings, icon);
});
},
@ -89,31 +83,29 @@ this.ExtensionsUI = {
this.sideloaded.delete(addon);
this.emit("change");
let info = {
let strings = this._buildStrings({
addon,
permissions: addon.userPermissions,
icon: addon.iconURL,
type: "sideload",
};
this.showAddonsManager(browser, info).then(answer => {
});
this.showAddonsManager(browser, strings, addon.iconURL).then(answer => {
addon.userDisabled = !answer;
});
},
showUpdate(browser, info) {
info.icon = info.addon.iconURL;
info.type = "update";
this.showAddonsManager(browser, info).then(answer => {
if (answer) {
info.resolve();
} else {
info.reject();
}
// At the moment, this prompt will re-appear next time we do an update
// check. See bug 1332360 for proposal to avoid this.
this.updates.delete(info);
this.emit("change");
});
this.showAddonsManager(browser, info.strings, info.addon.iconURL)
.then(answer => {
if (answer) {
info.resolve();
} else {
info.reject();
}
// At the moment, this prompt will re-appear next time we do an update
// check. See bug 1332360 for proposal to avoid this.
this.updates.delete(info);
this.emit("change");
});
},
observe(subject, topic, data) {
@ -141,10 +133,27 @@ this.ExtensionsUI = {
reply(true);
} else {
info.permissions = perms;
this.showPermissionsPrompt(target, info).then(reply);
let strings = this._buildStrings(info);
this.showPermissionsPrompt(target, strings, info.icon).then(reply);
}
} else if (topic == "webextension-update-permissions") {
this.updates.add(subject.wrappedJSObject);
let info = subject.wrappedJSObject;
let strings = this._buildStrings(info);
// If we don't prompt for any new permissions, just apply it
if (strings.msgs.length == 0) {
info.resolve();
return;
}
let update = {
strings,
addon: info.addon,
resolve: info.resolve,
reject: info.reject,
};
this.updates.add(update);
this.emit("change");
} else if (topic == "webextension-install-notify") {
let {target, addon, callback} = subject.wrappedJSObject;
@ -164,56 +173,54 @@ this.ExtensionsUI = {
.replace(/>/g, "&gt;");
},
showPermissionsPrompt(target, info) {
let perms = info.permissions;
if (!perms) {
return Promise.resolve();
}
// Create a set of formatted strings for a permission prompt
_buildStrings(info) {
let result = {};
let win = target.ownerGlobal;
let bundle = Services.strings.createBundle(BROWSER_PROPERTIES);
let name = info.addon.name;
if (name.length > 50) {
name = name.slice(0, 49) + "…";
}
name = this._sanitizeName(name);
let addonName = `<span class="addon-webext-name">${name}</span>`;
let bundle = win.gNavigatorBundle;
let header = bundle.getFormattedString("webextPerms.header", [addonName]);
let text = "";
let listIntro = bundle.getString("webextPerms.listIntro");
result.header = bundle.formatStringFromName("webextPerms.header", [addonName], 1);
result.text = "";
result.listIntro = bundle.GetStringFromName("webextPerms.listIntro");
let acceptText = bundle.getString("webextPerms.add.label");
let acceptKey = bundle.getString("webextPerms.add.accessKey");
let cancelText = bundle.getString("webextPerms.cancel.label");
let cancelKey = bundle.getString("webextPerms.cancel.accessKey");
result.acceptText = bundle.GetStringFromName("webextPerms.add.label");
result.acceptKey = bundle.GetStringFromName("webextPerms.add.accessKey");
result.cancelText = bundle.GetStringFromName("webextPerms.cancel.label");
result.cancelKey = bundle.GetStringFromName("webextPerms.cancel.accessKey");
if (info.type == "sideload") {
header = bundle.getFormattedString("webextPerms.sideloadHeader", [addonName]);
text = bundle.getString("webextPerms.sideloadText");
acceptText = bundle.getString("webextPerms.sideloadEnable.label");
acceptKey = bundle.getString("webextPerms.sideloadEnable.accessKey");
cancelText = bundle.getString("webextPerms.sideloadDisable.label");
cancelKey = bundle.getString("webextPerms.sideloadDisable.accessKey");
result.header = bundle.formatStringFromName("webextPerms.sideloadHeader", [addonName], 1);
result.text = bundle.GetStringFromName("webextPerms.sideloadText2");
result.acceptText = bundle.GetStringFromName("webextPerms.sideloadEnable.label");
result.acceptKey = bundle.GetStringFromName("webextPerms.sideloadEnable.accessKey");
result.cancelText = bundle.GetStringFromName("webextPerms.sideloadCancel.label");
result.cancelKey = bundle.GetStringFromName("webextPerms.sideloadCancel.accessKey");
} else if (info.type == "update") {
header = "";
text = bundle.getFormattedString("webextPerms.updateText", [addonName]);
acceptText = bundle.getString("webextPerms.updateAccept.label");
acceptKey = bundle.getString("webextPerms.updateAccept.accessKey");
result.header = "";
result.text = bundle.formatStringFromName("webextPerms.updateText", [addonName], 1);
result.acceptText = bundle.GetStringFromName("webextPerms.updateAccept.label");
result.acceptKey = bundle.GetStringFromName("webextPerms.updateAccept.accessKey");
}
let msgs = [];
let perms = info.permissions || {hosts: [], permissions: []};
result.msgs = [];
for (let permission of perms.permissions) {
let key = `webextPerms.description.${permission}`;
if (permission == "nativeMessaging") {
let brandBundle = win.document.getElementById("bundle_brand");
let appName = brandBundle.getString("brandShortName");
msgs.push(bundle.getFormattedString(key, [appName]));
let brandBundle = Services.strings.createBundle(BRAND_PROPERTIES);
let appName = brandBundle.GetStringFromName("brandShortName");
result.msgs.push(bundle.formatStringFromName(key, [appName], 1));
} else {
try {
msgs.push(bundle.getString(key));
result.msgs.push(bundle.GetStringFromName(key));
} catch (err) {
// We deliberately do not include all permissions in the prompt.
// So if we don't find one then just skip it.
@ -241,14 +248,14 @@ this.ExtensionsUI = {
}
if (allUrls) {
msgs.push(bundle.getString("webextPerms.hostDescription.allUrls"));
result.msgs.push(bundle.GetStringFromName("webextPerms.hostDescription.allUrls"));
} else {
// Formats a list of host permissions. If we have 4 or fewer, display
// them all, otherwise display the first 3 followed by an item that
// says "...plus N others"
function format(list, itemKey, moreKey) {
function formatItems(items) {
msgs.push(...items.map(item => bundle.getFormattedString(itemKey, [item])));
result.msgs.push(...items.map(item => bundle.formatStringFromName(itemKey, [item], 1)));
}
if (list.length < 5) {
formatItems(list);
@ -256,8 +263,8 @@ this.ExtensionsUI = {
formatItems(list.slice(0, 3));
let remaining = list.length - 3;
msgs.push(PluralForm.get(remaining, bundle.getString(moreKey))
.replace("#1", remaining));
result.msgs.push(PluralForm.get(remaining, bundle.GetStringFromName(moreKey))
.replace("#1", remaining));
}
}
@ -267,56 +274,64 @@ this.ExtensionsUI = {
"webextPerms.hostDescription.tooManySites");
}
return result;
},
showPermissionsPrompt(browser, strings, icon) {
function eventCallback(topic) {
if (topic == "showing") {
let doc = this.browser.ownerDocument;
doc.getElementById("addon-webext-perm-header").innerHTML = strings.header;
let textEl = doc.getElementById("addon-webext-perm-text");
textEl.innerHTML = strings.text;
textEl.hidden = !strings.text;
let listIntroEl = doc.getElementById("addon-webext-perm-intro");
listIntroEl.value = strings.listIntro;
listIntroEl.hidden = (strings.msgs.length == 0);
let list = doc.getElementById("addon-webext-perm-list");
while (list.firstChild) {
list.firstChild.remove();
}
for (let msg of strings.msgs) {
let item = doc.createElementNS(HTML_NS, "li");
item.textContent = msg;
list.appendChild(item);
}
} else if (topic == "swapping") {
return true;
}
return false;
}
let popupOptions = {
hideClose: true,
popupIconURL: info.icon || DEFAULT_EXTENSION_ICON,
popupIconURL: icon || DEFAULT_EXTENSION_ICON,
persistent: true,
eventCallback(topic) {
if (topic == "showing") {
let doc = this.browser.ownerDocument;
doc.getElementById("addon-webext-perm-header").innerHTML = header;
let textEl = doc.getElementById("addon-webext-perm-text");
textEl.innerHTML = text;
textEl.hidden = !text;
let listIntroEl = doc.getElementById("addon-webext-perm-intro");
listIntroEl.value = listIntro;
listIntroEl.hidden = (msgs.length == 0);
let list = doc.getElementById("addon-webext-perm-list");
while (list.firstChild) {
list.firstChild.remove();
}
for (let msg of msgs) {
let item = doc.createElementNS(HTML_NS, "li");
item.textContent = msg;
list.appendChild(item);
}
} else if (topic == "swapping") {
return true;
}
return false;
},
eventCallback,
};
let win = browser.ownerGlobal;
return new Promise(resolve => {
win.PopupNotifications.show(target, "addon-webext-permissions", "",
let action = {
label: strings.acceptText,
accessKey: strings.acceptKey,
callback: () => resolve(true),
};
let secondaryActions = [
{
label: strings.cancelText,
accessKey: strings.cancelKey,
callback: () => resolve(false),
},
];
win.PopupNotifications.show(browser, "addon-webext-permissions", "",
"addons-notification-icon",
{
label: acceptText,
accessKey: acceptKey,
callback: () => resolve(true),
},
[
{
label: cancelText,
accessKey: cancelKey,
callback: () => resolve(false),
},
], popupOptions);
action, secondaryActions, popupOptions);
});
},

View File

@ -112,6 +112,20 @@
}
}
/* Override tab close icon (to disable inversion) for better contrast with
light theme on Windows 7 Classic theme. */
@media not all and (min-resolution: 1.1dppx) {
#TabsToolbar[brighttext] .tab-close-button:-moz-lwtheme-darktext:not([selected="true"]) {
list-style-image: url("chrome://global/skin/icons/close.png");
}
}
@media (min-resolution: 1.1dppx) {
#TabsToolbar[brighttext] .tab-close-button:-moz-lwtheme-darktext:not([selected="true"]) {
list-style-image: url("chrome://global/skin/icons/close@2x.png");
}
}
@media (-moz-os-version: windows-win7),
(-moz-os-version: windows-win8) {
:root {

View File

@ -15,6 +15,8 @@
"cxx": "/home/worker/workspace/build/src/gcc/bin/g++",
"as": "/home/worker/workspace/build/src/gcc/bin/gcc",
"patches": [
"llvm-debug-frame.patch"
"llvm-debug-frame.patch",
"r277806.patch",
"r285657.patch"
]
}

View File

@ -22,17 +22,18 @@ CROSS_CCTOOLS_PATH=$topsrcdir/cctools
# `browser/config/tooltool-manifests/macosx64/cross-releng.manifest`.
CROSS_SYSROOT=$topsrcdir/MacOSX10.7.sdk
CROSS_PRIVATE_FRAMEWORKS=$CROSS_SYSROOT/System/Library/PrivateFrameworks
FLAGS="-target x86_64-apple-darwin10 -mlinker-version=136 -B $CROSS_CCTOOLS_PATH/bin -isysroot $CROSS_SYSROOT"
FLAGS="-target x86_64-apple-darwin11 -B $CROSS_CCTOOLS_PATH/bin -isysroot $CROSS_SYSROOT"
export CC="$topsrcdir/clang/bin/clang $FLAGS"
export CXX="$topsrcdir/clang/bin/clang++ $FLAGS"
export CPP="$topsrcdir/clang/bin/clang $FLAGS -E"
export LLVMCONFIG=$topsrcdir/clang/bin/llvm-config
export LDFLAGS="-Wl,-syslibroot,$CROSS_SYSROOT -Wl,-dead_strip"
export TOOLCHAIN_PREFIX=$CROSS_CCTOOLS_PATH/bin/x86_64-apple-darwin10-
export TOOLCHAIN_PREFIX=$CROSS_CCTOOLS_PATH/bin/x86_64-apple-darwin11-
export DSYMUTIL=$topsrcdir/clang/bin/llvm-dsymutil
export GENISOIMAGE=$topsrcdir/genisoimage/genisoimage
export MKFSHFS=$topsrcdir/hfsplus-tools/newfs_hfs
export DMG_TOOL=$topsrcdir/dmg/dmg
export HFS_TOOL=$topsrcdir/dmg/hfsplus
export HOST_CC="$topsrcdir/clang/bin/clang"
export HOST_CXX="$topsrcdir/clang/bin/clang++"

View File

@ -763,6 +763,8 @@ def compiler(language, host_or_target, c_compiler=None, other_compiler=None,
# Set CC_TYPE/CC_VERSION/HOST_CC_TYPE/HOST_CC_VERSION to allow
# old-configure to do some of its still existing checks.
if language == 'C':
set_config(
'%s_TYPE' % var, delayed_getattr(valid_compiler, 'type'))
add_old_configure_assignment(
'%s_TYPE' % var, delayed_getattr(valid_compiler, 'type'))
add_old_configure_assignment(

View File

@ -0,0 +1,53 @@
-----BEGIN PGP PUBLIC KEY BLOCK-----
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
Lt44/DBv052TOb47tw==
=ERlK
-----END PGP PUBLIC KEY BLOCK-----

View File

@ -0,0 +1,76 @@
-----BEGIN PGP PUBLIC KEY BLOCK-----
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
9ig=
=2zZQ
-----END PGP PUBLIC KEY BLOCK-----

View File

@ -0,0 +1,33 @@
-----BEGIN PGP PUBLIC KEY BLOCK-----
mQGiBECGYZsRBAC9VE8N8vHAG87MTC2wbtnmbSD8Wc2xYCaLofZAH+vXyio3Dnwx
jQLlj7IgwRWNAVq13uL0wn0WAsGop5Cs7nA/JD4MEBBNSdnvq1bMYitch2PTtAU+
h6HaI9JXBDUh4AKZz2rllKgbigMHlgIugxnKTAMJIhS63lCTHWEDlnycJwCgqSX9
hDs9eBC5coearGDhc0BDvTsD/A05YkZkQBgsYD6cjWFwNLJIcaHORKlLLZ9gRJO5
LVcKaCEgYSWAM7dadJeqIFi9RkXdv+cWozxTgrGlY4T7/PakIBB7wWj2Zl72mW5a
NHT2vAemB8IFV1saiFXZM+qDhCHbV4yKSmNOQHY1VnSCUrgINiM0qlTz08yjUazK
fm2BBACDF3ZfUQNeHC9zwfsgCzKnqOm7FSlwOyI0f+j83B5PH2+KuzuyEqYoxGp+
2d1zTxvbOeBBaX8T1M4n5d9ixiFMhgbTzuyit3nn6cp5j2L0IAS9pw0kaWpPMhpQ
zydNgnaBxHs1Y+cP4iM/4FWFCvfjUdR7xULdEzkgGxevu8pNEbQgSmFrdWIgSmVs
aW5layA8amFrdWJAcmVkaGF0LmNvbT6IZAQTEQIAJAIbAwYLCQgHAwIDFQIDAxYC
AQIeAQIXgAUCSe3VIgUJEs109wAKCRCjKMOiw8RcBqANAJ0VlFMTtevlkEM+ym4k
yE3YOrGZ+wCeP7lZGc2jVLHJfrOKxXsTM5YPWhqIZAQTEQIAJAIbAwYLCQgHAwID
FQIDAxYCAQIeAQIXgAUCTI3tMgUJHtOOlwAKCRCjKMOiw8RcBjySAJ9ApMXF3+gW
Ir0zpMxvWb53/oxsHgCaAl6V5JS9GJUnrPiHKdR+sMFPkd6IZAQTEQIAJAUCQIZh
mwIbAwUJCWYBgAYLCQgHAwIDFQIDAxYCAQIeAQIXgAAKCRCjKMOiw8RcBrC+AJ9d
mQcWoZHFGoinHck309KD0m2FegCeMBjr/M6Ec1myCYMUhtpl5DI7zY25Ag0EQIZh
ohAIALrI1X59CM30/Ufg+O9FFRRyM8GefACfItrIvp6jx+0ZMY+/ZbYnlMzI7Gz4
xNXc+83Zsz7zE5xogNcq9LILdhB7Ta1ZRkRttM8AdfyakRQTjzCPtxSPgSao/Dcu
CL09BZdaeeqMAxLmw9DnY3xmZqQtCau8PlgIiClq2db9Wy0bpQ+DDfQV4MlX6eoI
33TG9Moy59QQUG5reQ2JNkQZRebPxJAPiAgHoF/Q+XO1pLeCccIN7SApe7yVd/4A
sS3Y9lZj2JvEvutLojsRGL0E/CAwH8cJqPAt65qbOgQzCILhcc9aYZ234g9n7Kpx
Ck1h2QMtXfsmaA7GsrXo1Ddfra8ABA0H/0sa4SCQhWQ14tOFkN15xzuaqGOxUD+O
uAsgRdKaFdIhZnj0MRmvOfBSP7hONw7fE0m9DVq9NDPqFcMeyCuBNIMpGIuN6CAK
/G0K2UgzoCxMXUEYGncFfVnOoNURV9u2lGq7ZMNJmuzt0BhxXtUYRlH3WRPqPyGv
s/OrIqvgN+Kf9+i0kQSObWz6CeYnBKzCc++MPkVhYj8KR5Y6n3zPZpnOfmO3c0rY
C+KiNoMwchlZmiOh7zgcTybv4zuOU7bppEidreIq2/o4nBNTao/5uzYdDX9FBpDT
hhU9ErdO8Vd7Vf2I1/WQdt6dHUXPLfkwI8+ODE/4R/Oz8opFC5L22kSITwQYEQIA
DwIbDAUCTI3tTQUJHtOOqwAKCRCjKMOiw8RcBrBvAKCTFx5FOuuxM2VoQka8iBGj
f1vcugCdHV/JIhOwETTqOQEbkw3y9ng2+4U=
=K9Jj
-----END PGP PUBLIC KEY BLOCK-----

View File

@ -0,0 +1,35 @@
-----BEGIN PGP PUBLIC KEY BLOCK-----
mQFNBFDrIWMBCgCyyYoTAD/aL6Yl90eSJ1xuFpODTcwyRZsNSUZKSmKwnqXo9LgS
2B00yVZ2nO2OrSmWPiYikTciitv04bAqFaggSstx6hlni6n3h2PL0jXpf9EI6qOO
oKwi2IVtbBnJAhWpfRcAce6WEqvnav6KjuBM3lr8/5GzDV8tm6+X/G/paTnBqTB9
pBxrH7smB+iRjDt/6ykWkbYLd6uBKzIkAp4HqAZb/aZMvxI28PeWGjZJQYq2nVPf
LroM6Ub/sNlXpv/bmHJusFQjUL368njhZD1+aVLCUfBCCDzvZc3EYt3wBkbmuCiA
xOb9ramHgiVkNENtzXR+sbQHtKRQv/jllY1qxROM2/rWmL+HohdxL5E0VPple2bg
U/zqX0Hg2byb8FbpzPJO5PnBD+1PME3Uirsly4N7XT80OvhXlYe4t+9X0QARAQAB
tCROaWVscyBNw7ZsbGVyIDxuaXNzZUBseXNhdG9yLmxpdS5zZT6JAX4EEwECACgF
AlDrIWMCGwMFCRLMAwAGCwkIBwMCBhUIAgkKCwQWAgMBAh4BAheAAAoJEPNZn/go
xnKYqm0J/A4b6TE5qPWiWj0kriUBSmpys3qUz93gR6Ft7w2f478KJuzbSadvyn0u
PcnP26AGTOQq75RhtgCJgdYbvRocTjlMh9jOX584Hx8hi/QSrpCSYMnj6dQKbu0Y
QIFjZx8gPeYvzG8t34FCNEzZ09RQZqy/ukRyN99LkwEuP4FWq486b7dpgv7GC+SH
lZcMco6VW8FLOT7KMalH06cmdhFPrFSYAIHDu3CsYhC8knIQV99Xzno/KeSkEwkq
tYDOdz0x4HWdOwHrl2S2X6Ex1q3QRXcq84EYQwHz2WEGaPR7Vd76P5J1wiHN6rwO
4exfgsRyTvc6NDQPTFqmoCzwuPviYk6JNnHr9E5TkLT7lAnESEhMLyyIG/7Uwpgu
5C71IMaTpOpf8DEU9NU/zuxgHoMaKBZaeYKs0S26s1zwGOlQX0T9uQFNBFDrIWMB
CgDKlONI+5Bqcu69+72fmLZPizzEUsIRA2Y0w2RE7+uJ5Es9/YTp5PnWANpPT7GS
8JJnc6NJJeh6GkMkGGwq5Op7CDsjW9pQZ0vAW90XjnyniDa9W0W+m5+X/LPOzh+n
is9Zcf17P91tprLCLi+TOOb35xt396pZ+S+PwuV0dLiIYdVYV3e6LNCV0LjhEqp5
3TRwTrLTNPQVnt0DPYTh/Kn1x6d5zOS0MK4QybKN1WJU6nYIQRXyWKkixjbs++jc
gV/juck96Ve0blvn6DfqfpG8YzbmqRCufLo683LtlBUZ0c+znrD1nouqX2Eb/Cyl
G8Q8ZUHXimCJ+g6RfH9kOmtVH/208u/nDofVL/Q0dvAXfU5MX49c7XYy7B2rTlk+
4nuNeaHM0aU2Y14+SQy+sR6zydu7eGLdqjzV0CX/ekgrjQARAQABiQFlBBgBAgAP
BQJQ6yFjAhsMBQkSzAMAAAoJEPNZn/goxnKYGUcJ/j+L0/uzfwCR1aTBZ6FBT9Od
NyatVjmz20ahskF3BySmkT1R06K08YOGJ//LPajj0eKqU8WKgxMc7pWi5SG+yMFn
2db5HnJDGiSmSjCXW/BzsSt1786LtO0m0ehatj9kl6JrxQNXazOkRJ2ww13P6/91
RBaV6R08BmFTrUco2P6w+djCF4NlnkOLa7fM6QtNZM+yB+EzaPjSBFjZG52BVWZk
cXEVN0cEjPuznuQOmx8Dny7lQikp49NumrbamaxZEilx2Bi9gSbovNaKBuncKi9X
boiEiNbAarGxP40Qvlk2AuXWvq+fiBnU1e1nU2oV7/7nAWH7kj/Vr/JxcBeOpsND
GkW7Yrd3mkJCrhG+jMs1V2qNb9Uhr5ZLOA40sIz2PHfDrR+gc8THm2p5OvCWEAeu
kYJ22XTUIt6XoPO0ERYD
=MH4q
-----END PGP PUBLIC KEY BLOCK-----

View File

@ -0,0 +1,57 @@
-----BEGIN PGP PUBLIC KEY BLOCK-----
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
8GOBvibrC/azKvoBKLY1/stpKCrecZdRFkk=
=SDN9
-----END PGP PUBLIC KEY BLOCK-----

View File

@ -0,0 +1,29 @@
-----BEGIN PGP PUBLIC KEY BLOCK-----
mQGiBEj3S14RBACUE+e2hRWwM6AFWaNKsLgDg6ebDNCI6z/Pk38t6JUeM+D5MAvq
fnL45bF/3CUrZRK+/qLg5iwRWehKh08VQ7GqDxMerZkPmfvirVxLwpc5ngCOGJwv
ba13xdaxfTLMkHxaQyGWiUqHIwdzFoNBgjq9XTY0GGqHwVA1Hb+xTAL8PwCg+wru
41p/aOq9cfPN1U1BjulWCSMD/2YP23pI19o9Hr26ltyJcd/xkSRiCUk84efIw5JH
7QlxoMoW/SdJQAGi2pZN9o4I/fPDB3Gna9M9rZfacKda857dwkALPK8xTsfHiZzH
40g+eYUvl9nloNidneSFcxbLO/euURcCJ6Ri6nb4QWWLVH1XF6wQxEGyn7ojYMOn
ihlkA/9KATtSt+T0zgWskqckgV1ZQg9Ysqwp3GAvezJuyUTXlB02ApVFEsRTQtLZ
2WPvo/gzfih/EdNLrq7UeeB4dr/nlpANn4IyBRN6EmBHaJ4MMKN77B7bB0GaBfGJ
rgNNp7W0xG0FLjaMoQzP62qqAtZHNlW1qCmkyJGUpAa6tJw9CbQlVHJpc3RhbiBH
aW5nb2xkIDxnaW5nb2xkQGFkYWNvcmUuY29tPohgBBMRAgAgBQJI90teAhsDBgsJ
CAcDAgQVAggDBBYCAwECHgECF4AACgkQwxJtO0rlXpMVcwCgoQ91OLI+m2bsu2SS
d5MsRQH3FWsAnRFG2YGk5o5zuoLzdZd6KlL9xJ+uuQINBEj3S2MQCACnDo5dHujc
u7QHRPnxNwiKhMP6eIZaEm9tavab3UxsRufMyVC8nQ8+EmCOwfBrqstfRVoQnoDI
s5UY1XAM3mBFXYqfY9wR6NISUlzK/HPyFhGE7t3lVjOkiqbWOftDt6GgRETeqYsW
XkDV/dL4+P3eSaOSP6KMZwdjgXPOciN59KIiii9NK4icxP0lJHDk5WJFwfucEyUt
Sz7uwuUFcajHZmMxxHAnWT3uJ+ZasSijduZevsHhKTTXaZRideqf+ur1/TcUaZDQ
O3wist1qc03NkL+oGu6HYPx9ZV40p/axdTaUXMcBjtAZIzvy984HF9EsFQnvbiXt
R8zg6SYXLRmbAAMGB/9JMKWsCuxUzXmU1jyJvMXdRBZ4YQYkKFYWrEXwjYlBEGx6
01PkR//4QJVR4zFjy4zVnaUrOxtR+65Eedf+9fNZzSNeI24TGaqyVM0OYYQtp9cH
kRDu3wif1k2NW3BnrmTjVefdAWVH6zKT9lP9m6RPHCwVGyORhVQtB3+ZXOehNJwL
9NBU4MUpGKpoQCuODdgZ8iQXbo+plg0eCxcpNaYzSnq9DMAU+2qnP6d3x4DeWzlL
wvJ2K2Mw89gvCImy/JDe05EXqKowR6aiIPvw5ou9xSHmjT6rcaIBiROCe+1hh4XC
djCdb4kOskWCEXfFKcHax4N5fI9vmk3P5068BELMiEkEGBECAAkFAkj3S2MCGwwA
CgkQwxJtO0rlXpNxdwCgjr4sQRf2cyDkCSWe4AElbI74BREAoPdet3XvE6ZcZJGl
UIZySRkdpk/A
=dGh0
-----END PGP PUBLIC KEY BLOCK-----

View File

@ -1,5 +1,9 @@
#!/bin/bash
set -e
set -x
gcc_version=4.9.4
binutils_version=2.25.1
this_path=$(readlink -f $(dirname $0))
@ -15,38 +19,102 @@ if test -z $TMPDIR; then
TMPDIR=/tmp/
fi
wget -c -P $TMPDIR ftp://ftp.gnu.org/gnu/binutils/binutils-$binutils_version.tar.bz2 || exit 1
mkdir gpg
GPG="gpg --homedir $root_dir/gpg"
# GPG key used to sign GCC
$GPG --import $this_path/13975A70E63C361C73AE69EF6EEB81F8981C74C7.key
# GPG key used to sign binutils
$GPG --import $this_path/EAF1C276A747E9ED86210CBAC3126D3B4AE55E93.key
# GPG key used to sign GMP
$GPG --import $this_path/343C2FF0FBEE5EC2EDBEF399F3599FF828C67298.key
# GPG key used to sign MPFR
$GPG --import $this_path/07F3DBBECC1A39605078094D980C197698C3739D.key
# GPG key used to sign MPC
$GPG --import $this_path/AD17A21EF8AED8F1CC02DBD9F7D5C9BF765C61E3.key
> $root_dir/downloads
download() {
wget -c -P $TMPDIR $1/$2
(cd $TMPDIR; sha256sum $2) >> $root_dir/downloads
}
download_and_check() {
download $1 ${2%.*}
wget -c -P $TMPDIR $1/$2
$GPG --verify $TMPDIR/$2 $TMPDIR/${2%.*}
}
download_and_check ftp://ftp.gnu.org/gnu/binutils binutils-$binutils_version.tar.bz2.sig
tar xjf $TMPDIR/binutils-$binutils_version.tar.bz2
mkdir binutils-objdir
cd binutils-objdir
# gold is disabled because we don't use it on automation, and also we ran into
# some issues with it using this script in build-clang.py.
../binutils-$binutils_version/configure --prefix /tools/gcc/ --disable-gold --enable-plugins --disable-nls || exit 1
make $make_flags || exit 1
make install $make_flags DESTDIR=$root_dir || exit 1
../binutils-$binutils_version/configure --prefix /tools/gcc/ --disable-gold --enable-plugins --disable-nls
make $make_flags
make install $make_flags DESTDIR=$root_dir
cd ..
case "$gcc_version" in
*-*)
wget -c -P $TMPDIR ftp://gcc.gnu.org/pub/gcc/snapshots/$gcc_version/gcc-$gcc_version.tar.bz2 || exit 1
download ftp://gcc.gnu.org/pub/gcc/snapshots/$gcc_version/gcc-$gcc_version.tar.bz2
;;
*)
wget -c -P $TMPDIR ftp://ftp.gnu.org/gnu/gcc/gcc-$gcc_version/gcc-$gcc_version.tar.bz2 || exit 1
download_and_check ftp://ftp.gnu.org/gnu/gcc/gcc-$gcc_version gcc-$gcc_version.tar.bz2.sig
;;
esac
tar xjf $TMPDIR/gcc-$gcc_version.tar.bz2
cd gcc-$gcc_version
./contrib/download_prerequisites
(
# Divert commands that download_prerequisites use
ln() { :; }
tar() { :; }
sed() { :; }
wget() {
echo $1
}
patch -p1 < "${this_path}/PR64905.patch" || exit 1
. ./contrib/download_prerequisites
) | while read url; do
file=$(basename $url)
case "$file" in
gmp-*.tar.*)
# If download_prerequisites wants 4.3.2, use 5.1.3 instead.
file=${file/4.3.2/5.1.3}
download_and_check https://gmplib.org/download/gmp $file.sig
;;
mpfr-*.tar.*)
# If download_prerequisites wants 2.4.2, use 3.1.5 instead.
file=${file/2.4.2/3.1.5}
download_and_check http://www.mpfr.org/${file%.tar.*} $file.asc
;;
mpc-*.tar.*)
# If download_prerequisites wants 0.8.1, use 0.8.2 instead.
file=${file/0.8.1/0.8.2}
download_and_check http://www.multiprecision.org/mpc/download $file.asc
;;
*)
download $(dirname $url) $file
;;
esac
tar xaf $TMPDIR/$file
ln -sf ${file%.tar.*} ${file%-*}
done
# Check all the downloads we did are in the checksums list, and that the
# checksums match.
diff -u <(sort -k 2 $root_dir/downloads) $this_path/checksums
patch -p1 < "${this_path}/PR64905.patch"
cd ..
mkdir gcc-objdir
cd gcc-objdir
../gcc-$gcc_version/configure --prefix=/tools/gcc --enable-languages=c,c++ --disable-nls --disable-gnu-unique-object --enable-__cxa_atexit --with-arch-32=pentiumpro || exit 1
make $make_flags || exit 1
make $make_flags install DESTDIR=$root_dir || exit 1
../gcc-$gcc_version/configure --prefix=/tools/gcc --enable-languages=c,c++ --disable-nls --disable-gnu-unique-object --enable-__cxa_atexit --with-arch-32=pentiumpro
make $make_flags
make $make_flags install DESTDIR=$root_dir
cd $root_dir/tools
tar caf $root_dir/gcc.tar.xz gcc/

View File

@ -0,0 +1,7 @@
b5b14added7d78a8d1ca70b5cb75fef57ce2197264f4f5835326b0df22ac9f22 binutils-2.25.1.tar.bz2
02500a4edd14875f94fe84cbeda4290425cb0c1c2474c6f75d75a303d64b4196 cloog-0.18.1.tar.gz
6c11d292cd01b294f9f84c9a59c230d80e9e4a47e5c6355f046bb36d4f358092 gcc-4.9.4.tar.bz2
752079520b4690531171d0f4532e40f08600215feefede70b24fabdc6f1ab160 gmp-5.1.3.tar.bz2
f4b3dbee9712850006e44f0db2103441ab3d13b406f77996d1df19ee89d11fb4 isl-0.12.2.tar.bz2
ae79f8d41d8a86456b68607e9ca398d00f8b7342d1d83bcf4428178ac45380c7 mpc-0.8.2.tar.gz
ca498c1c7a74dd37a576f353312d1e68d490978de4395fa28f1cbd46a364e658 mpfr-3.1.5.tar.bz2

View File

@ -0,0 +1,46 @@
#!/bin/bash
set -x
hfplus_version=540.1.linux3
md5sum=0435afc389b919027b69616ad1b05709
filename=diskdev_cmds-${hfplus_version}.tar.gz
make_flags="-j$(getconf _NPROCESSORS_ONLN)"
root_dir="$1"
if [ -z "$root_dir" -o ! -d "$root_dir" ]; then
root_dir=$(mktemp -d)
fi
cd $root_dir
if test -z $TMPDIR; then
TMPDIR=/tmp/
fi
# Install clang first
yum install -y clang
# Set an md5 check file to validate input
echo "${md5sum} *${TMPDIR}/${filename}" > $TMPDIR/hfsplus.MD5
# Most-upstream is https://opensource.apple.com/source/diskdev_cmds/
# Download the source of the specified version of binutils
wget -c -P $TMPDIR http://pkgs.fedoraproject.org/repo/pkgs/hfsplus-tools/${filename}/${md5sum}/${filename} || exit 1
md5sum -c $TMPDIR/hfsplus.MD5 || exit 1
mkdir hfsplus-source
tar xzf $TMPDIR/${filename} -C hfsplus-source --strip-components=1
# Build
cd hfsplus-source
make $make_flags || exit 1
cd ..
mkdir hfsplus-tools
cp hfsplus-source/newfs_hfs.tproj/newfs_hfs hfsplus-tools/newfs_hfs
## XXX fsck_hfs is unused, but is small and built from the package.
cp hfsplus-source/fsck_hfs.tproj/fsck_hfs hfsplus-tools/fsck_hfs
# Make a package of the built utils
cd $root_dir
tar caf $root_dir/hfsplus-tools.tar.xz hfsplus-tools

View File

@ -85,6 +85,9 @@ else:
elif CONFIG['FFI_TARGET'] == 'X86_64':
ffi_srcs = ('ffi64.c', 'unix64.S', 'ffi.c', 'sysv.S')
elif CONFIG['FFI_TARGET'] == 'X86_WIN32':
# MinGW Build for 32 bit
if CONFIG['CC_TYPE'] == 'gcc':
DEFINES['SYMBOL_UNDERSCORE'] = True
ffi_srcs = ('ffi.c', 'win32.S')
elif CONFIG['FFI_TARGET'] == 'X86_WIN64':
ffi_srcs = ('ffi.c', 'win64.S')

View File

@ -958,6 +958,7 @@ CARGO_BUILD = env $(rustflags_override) \
MOZ_DIST=$(ABS_DIST) \
LIBCLANG_PATH=$(MOZ_LIBCLANG_PATH) \
CLANG_PATH=$(MOZ_CLANG_PATH) \
PKG_CONFIG_ALLOW_CROSS=1 \
$(CARGO) build $(cargo_build_flags)
ifdef RUST_LIBRARY_FILE

View File

@ -5620,7 +5620,7 @@ var Debugger =
"unicode-bidi": {
inherited: false,
supports: 0,
values: ["-moz-isolate", "-moz-isolate-override", "-moz-plaintext", "bidi-override", "embed", "inherit", "initial", "normal", "unset", ],
values: ["isolate", "isolate-override", "plaintext", "bidi-override", "embed", "inherit", "initial", "normal", "unset", ],
},
"-moz-user-focus": {
inherited: true,

View File

@ -79,12 +79,20 @@ var submit = Task.async(function* () {
var onConnectionReady = Task.async(function* ([aType, aTraits]) {
clearTimeout(gConnectionTimeout);
let response = yield gClient.listAddons();
let addons = [];
try {
let response = yield gClient.listAddons();
if (!response.error && response.addons.length > 0) {
addons = response.addons;
}
} catch(e) {
// listAddons throws if the runtime doesn't support addons
}
let parent = document.getElementById("addonActors");
if (!response.error && response.addons.length > 0) {
if (addons.length > 0) {
// Add one entry for each add-on.
for (let addon of response.addons) {
for (let addon of addons) {
if (!addon.debuggable) {
continue;
}
@ -97,7 +105,7 @@ var onConnectionReady = Task.async(function* ([aType, aTraits]) {
parent.remove();
}
response = yield gClient.listTabs();
let response = yield gClient.listTabs();
parent = document.getElementById("tabActors");

View File

@ -34,8 +34,6 @@ const { BrowserLoader } =
const {LocalizationHelper} = require("devtools/shared/l10n");
const L10N = new LocalizationHelper("devtools/client/locales/toolbox.properties");
loader.lazyRequireGetter(this, "CommandUtils",
"devtools/client/shared/developer-toolbar", true);
loader.lazyRequireGetter(this, "getHighlighterUtils",
"devtools/client/framework/toolbox-highlighter-utils", true);
loader.lazyRequireGetter(this, "Selection",
@ -1204,15 +1202,6 @@ Toolbox.prototype = {
}
},
/**
* Get the toolbar spec for toolbox
*/
getToolbarSpec: function () {
let spec = CommandUtils.getCommandbarSpec("devtools.toolbox.toolbarSpec");
return spec;
},
/**
* Return all toolbox buttons (command buttons, plus any others that were
* added manually).

View File

@ -28,6 +28,7 @@ const App = createClass({
boxModel: PropTypes.shape(Types.boxModel).isRequired,
grids: PropTypes.arrayOf(PropTypes.shape(Types.grid)).isRequired,
highlighterSettings: PropTypes.shape(Types.highlighterSettings).isRequired,
showBoxModelProperties: PropTypes.bool.isRequired,
onShowBoxModelEditor: PropTypes.func.isRequired,
onHideBoxModelHighlighter: PropTypes.func.isRequired,
onShowBoxModelHighlighter: PropTypes.func.isRequired,

View File

@ -9,6 +9,7 @@ const { addons, createClass, createFactory, DOM: dom, PropTypes } =
const BoxModelInfo = createFactory(require("./BoxModelInfo"));
const BoxModelMain = createFactory(require("./BoxModelMain"));
const BoxModelProperties = createFactory(require("./BoxModelProperties"));
const Types = require("../types");
@ -18,6 +19,7 @@ module.exports = createClass({
propTypes: {
boxModel: PropTypes.shape(Types.boxModel).isRequired,
showBoxModelProperties: PropTypes.bool.isRequired,
onHideBoxModelHighlighter: PropTypes.func.isRequired,
onShowBoxModelEditor: PropTypes.func.isRequired,
onShowBoxModelHighlighter: PropTypes.func.isRequired,
@ -28,6 +30,7 @@ module.exports = createClass({
render() {
let {
boxModel,
showBoxModelProperties,
onHideBoxModelHighlighter,
onShowBoxModelEditor,
onShowBoxModelHighlighter,
@ -45,7 +48,13 @@ module.exports = createClass({
}),
BoxModelInfo({
boxModel,
})
}),
showBoxModelProperties ?
BoxModelProperties({
boxModel,
})
:
null,
);
},

View File

@ -0,0 +1,89 @@
/* 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 { addons, createClass, createFactory, DOM: dom, PropTypes } =
require("devtools/client/shared/vendor/react");
const { LocalizationHelper } = require("devtools/shared/l10n");
const ComputedProperty = createFactory(require("./ComputedProperty"));
const Types = require("../types");
const BOXMODEL_STRINGS_URI = "devtools/client/locales/boxmodel.properties";
const BOXMODEL_L10N = new LocalizationHelper(BOXMODEL_STRINGS_URI);
module.exports = createClass({
displayName: "BoxModelProperties",
propTypes: {
boxModel: PropTypes.shape(Types.boxModel).isRequired,
},
mixins: [ addons.PureRenderMixin ],
getInitialState() {
return {
isOpen: true,
};
},
onToggleExpander() {
this.setState({
isOpen: !this.state.isOpen,
});
},
render() {
let { boxModel } = this.props;
let { layout } = boxModel;
let layoutInfo = ["box-sizing", "display", "float",
"line-height", "position", "z-index"];
const properties = layoutInfo.map(info => ComputedProperty({
name: info,
key: info,
value: layout[info],
}));
return dom.div(
{
className: "boxmodel-properties",
},
dom.div(
{
className: "boxmodel-properties-header",
onDoubleClick: this.onToggleExpander,
},
dom.div(
{
className: "boxmodel-properties-expander theme-twisty",
open: this.state.isOpen,
onClick: this.onToggleExpander,
},
),
dom.span(
{
className: "boxmodel-properties-label",
title: BOXMODEL_L10N.getStr("boxmodel.propertiesLabel"),
},
BOXMODEL_L10N.getStr("boxmodel.propertiesLabel"),
),
),
dom.div(
{
className: "boxmodel-properties-wrapper",
hidden: !this.state.isOpen,
tabIndex: 0,
},
properties,
),
);
},
});

View File

@ -0,0 +1,67 @@
/* 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 { addons, createClass, DOM: dom, PropTypes } =
require("devtools/client/shared/vendor/react");
module.exports = createClass({
displayName: "ComputedProperty",
propTypes: {
name: PropTypes.string.isRequired,
value: PropTypes.string,
},
mixins: [ addons.PureRenderMixin ],
onFocus() {
this.container.focus();
},
render() {
const { name, value } = this.props;
return dom.div(
{
className: "property-view",
tabIndex: "0",
ref: container => {
this.container = container;
},
},
dom.div(
{
className: "property-name-container",
},
dom.div(
{
className: "property-name theme-fg-color5",
tabIndex: "",
title: name,
onClick: this.onFocus,
},
name
)
),
dom.div(
{
className: "property-value-container",
},
dom.div(
{
className: "property-value theme-fg-color1",
dir: "ltr",
tabIndex: "",
onClick: this.onFocus,
},
value
)
)
);
},
});

View File

@ -12,6 +12,8 @@ DevToolsModules(
'BoxModelEditable.js',
'BoxModelInfo.js',
'BoxModelMain.js',
'BoxModelProperties.js',
'ComputedProperty.js',
'Grid.js',
'GridDisplaySettings.js',
'GridList.js',

View File

@ -77,6 +77,12 @@ LayoutView.prototype = {
this.loadHighlighterSettings();
let app = App({
/**
* Shows the box model properties under the box model if true, otherwise, hidden by
* default.
*/
showBoxModelProperties: true,
/**
* Hides the box-model highlighter on the currently selected element.
*/

View File

@ -35,3 +35,7 @@ boxmodel.content=content
# tooltip that appears when hovering over the button that allows users to edit the
# position of an element in the page.
boxmodel.geometryButton.tooltip=Edit position
# LOCALIZATION NOTE: (boxmodel.propertiesLabel) This label is displayed as the header
# for showing and collapsing the properties underneath the box model in the layout view
boxmodel.propertiesLabel=Box Model Properties

View File

@ -82,3 +82,55 @@ responsive.devicePixelRatio=Device Pixel Ratio
# The argument (%1$S) is the selected device (e.g. iPhone 6) that set
# automatically the DPR value.
responsive.autoDPR=DPR automatically set by %1$S
# LOCALIZATION NOTE (responsive.customDeviceName): Default value in a form to
# add a custom device based on an arbitrary size (no association to an existing
# device).
responsive.customDeviceName=Custom Device
# LOCALIZATION NOTE (responsive.customDeviceNameFromBase): Default value in a
# form to add a custom device based on the properties of another. %1$S is the
# name of the device we're staring from, such as "Apple iPhone 6".
responsive.customDeviceNameFromBase=%1$S (Custom)
# LOCALIZATION NOTE (responsive.addDevice): Button text that reveals a form to
# be used for adding custom devices.
responsive.addDevice=Add Device
# LOCALIZATION NOTE (responsive.deviceAdderName): Label of form field for the
# name of a new device. The available width is very low, so you might see
# overlapping text if the length is much longer than 5 or so characters.
responsive.deviceAdderName=Name
# LOCALIZATION NOTE (responsive.deviceAdderSize): Label of form field for the
# size of a new device. The available width is very low, so you might see
# overlapping text if the length is much longer than 5 or so characters.
responsive.deviceAdderSize=Size
# LOCALIZATION NOTE (responsive.deviceAdderPixelRatio): Label of form field for
# the devicePixelRatio of a new device. The available width is very low, so you
# might see overlapping text if the length is much longer than 5 or so
# characters.
responsive.deviceAdderPixelRatio=DPR
# LOCALIZATION NOTE (responsive.deviceAdderUserAgent): Label of form field for
# the user agent of a new device. The available width is very low, so you might
# see overlapping text if the length is much longer than 5 or so characters.
responsive.deviceAdderUserAgent=UA
# LOCALIZATION NOTE (responsive.deviceAdderTouch): Label of form field for the
# touch input support of a new device. The available width is very low, so you
# might see overlapping text if the length is much longer than 5 or so
# characters.
responsive.deviceAdderTouch=Touch
# LOCALIZATION NOTE (responsive.deviceAdderSave): Button text that submits a
# form to add a new device.
responsive.deviceAdderSave=Save
# LOCALIZATION NOTE (responsive.deviceDetails): Tooltip that appears when
# hovering on a device in the device modal. %1$S is the width of the device.
# %2$S is the height of the device. %3$S is the devicePixelRatio value of the
# device. %4$S is the user agent of the device. %5$S is a boolean value
# noting whether touch input is supported.
responsive.deviceDetails=Size: %1$S x %2$S\nDPR: %3$S\nUA: %4$S\nTouch: %5$S

View File

@ -63,14 +63,25 @@ function ParamsPanel({
name ? Object.assign(acc, { [name]: value }) : acc
, {});
}
// Form Data section
if (formDataSections && formDataSections.length > 0) {
let sections = formDataSections.filter((str) => /\S/.test(str)).join("&");
object[PARAMS_FORM_DATA] =
parseQueryString(sections)
.reduce((acc, { name, value }) =>
name ? Object.assign(acc, { [name]: value }) : acc
, {});
.reduce((map, obj) => {
let value = map[obj.name];
// Deal with duplicate key case (ex: multiple selection)
if (value) {
if (typeof value !== "object") {
map[obj.name] = [value];
}
map[obj.name].push(obj.value);
} else {
map[obj.name] = obj.value;
}
return map;
}, {});
}
// Request payload section

View File

@ -10,11 +10,13 @@ const {
LOAD_DEVICE_LIST_START,
LOAD_DEVICE_LIST_ERROR,
LOAD_DEVICE_LIST_END,
REMOVE_DEVICE,
UPDATE_DEVICE_DISPLAYED,
UPDATE_DEVICE_MODAL_OPEN,
UPDATE_DEVICE_MODAL,
} = require("./index");
const { removeDeviceAssociation } = require("./viewports");
const { getDevices } = require("devtools/client/shared/devices");
const { addDevice, getDevices, removeDevice } = require("devtools/client/shared/devices");
const Services = require("Services");
const DISPLAYED_DEVICES_PREF = "devtools.responsive.html.displayedDeviceList";
@ -71,6 +73,18 @@ module.exports = {
updatePreferredDevices: updatePreferredDevices,
addCustomDevice(device) {
return function* (dispatch) {
// Add custom device to device storage
yield addDevice(device, "custom");
dispatch({
type: ADD_DEVICE,
device,
deviceType: "custom",
});
};
},
addDevice(device, deviceType) {
return {
type: ADD_DEVICE,
@ -86,6 +100,26 @@ module.exports = {
};
},
removeCustomDevice(device) {
return function* (dispatch, getState) {
// Check if the custom device is currently associated with any viewports
let { viewports } = getState();
for (let viewport of viewports) {
if (viewport.device == device.name) {
dispatch(removeDeviceAssociation(viewport.id));
}
}
// Remove custom device from device storage
yield removeDevice(device, "custom");
dispatch({
type: REMOVE_DEVICE,
device,
deviceType: "custom",
});
};
},
updateDeviceDisplayed(device, deviceType, displayed) {
return {
type: UPDATE_DEVICE_DISPLAYED,
@ -96,8 +130,8 @@ module.exports = {
},
loadDevices() {
return function* (dispatch, getState) {
yield dispatch({ type: LOAD_DEVICE_LIST_START });
return function* (dispatch) {
dispatch({ type: LOAD_DEVICE_LIST_START });
let preferredDevices = loadPreferredDevices();
let devices;
@ -124,14 +158,21 @@ module.exports = {
dispatch(module.exports.addDevice(newDevice, type));
}
}
// Add an empty "custom" type if it doesn't exist in device storage
if (!devices.TYPES.find(type => type == "custom")) {
dispatch(module.exports.addDeviceType("custom"));
}
dispatch({ type: LOAD_DEVICE_LIST_END });
};
},
updateDeviceModalOpen(isOpen) {
updateDeviceModal(isOpen, modalOpenedFromViewport = null) {
return {
type: UPDATE_DEVICE_MODAL_OPEN,
type: UPDATE_DEVICE_MODAL,
isOpen,
modalOpenedFromViewport,
};
},

View File

@ -53,9 +53,12 @@ createEnum([
// Indicates that the device list has been loaded successfully
"LOAD_DEVICE_LIST_END",
// Remove the viewport's device assocation.
// Remove a device.
"REMOVE_DEVICE",
// Remove the viewport's device assocation.
"REMOVE_DEVICE_ASSOCIATION",
// Resize the viewport.
"RESIZE_VIEWPORT",
@ -71,7 +74,7 @@ createEnum([
// Update the device display state in the device selector.
"UPDATE_DEVICE_DISPLAYED",
// Update the device modal open state.
"UPDATE_DEVICE_MODAL_OPEN",
// Update the device modal state.
"UPDATE_DEVICE_MODAL",
], module.exports);

View File

@ -8,7 +8,7 @@ const {
ADD_VIEWPORT,
CHANGE_DEVICE,
CHANGE_PIXEL_RATIO,
REMOVE_DEVICE,
REMOVE_DEVICE_ASSOCIATION,
RESIZE_VIEWPORT,
ROTATE_VIEWPORT
} = require("./index");
@ -27,11 +27,12 @@ module.exports = {
/**
* Change the viewport device.
*/
changeDevice(id, device) {
changeDevice(id, device, deviceType) {
return {
type: CHANGE_DEVICE,
id,
device,
deviceType,
};
},
@ -49,9 +50,9 @@ module.exports = {
/**
* Remove the viewport's device assocation.
*/
removeDevice(id) {
removeDeviceAssociation(id) {
return {
type: REMOVE_DEVICE,
type: REMOVE_DEVICE_ASSOCIATION,
id,
};
},

View File

@ -11,8 +11,10 @@ const { createClass, createFactory, PropTypes, DOM: dom } =
const { connect } = require("devtools/client/shared/vendor/react-redux");
const {
addCustomDevice,
removeCustomDevice,
updateDeviceDisplayed,
updateDeviceModalOpen,
updateDeviceModal,
updatePreferredDevices,
} = require("./actions/devices");
const { changeNetworkThrottling } = require("./actions/network-throttling");
@ -21,7 +23,7 @@ const { changeTouchSimulation } = require("./actions/touch-simulation");
const {
changeDevice,
changePixelRatio,
removeDevice,
removeDeviceAssociation,
resizeViewport,
rotateViewport,
} = require("./actions/viewports");
@ -44,16 +46,20 @@ let App = createClass({
viewports: PropTypes.arrayOf(PropTypes.shape(Types.viewport)).isRequired,
},
onAddCustomDevice(device) {
this.props.dispatch(addCustomDevice(device));
},
onBrowserMounted() {
window.postMessage({ type: "browser-mounted" }, "*");
},
onChangeDevice(id, device) {
onChangeDevice(id, device, deviceType) {
window.postMessage({
type: "change-device",
device,
}, "*");
this.props.dispatch(changeDevice(id, device.name));
this.props.dispatch(changeDevice(id, device.name, deviceType));
this.props.dispatch(changeTouchSimulation(device.touch));
this.props.dispatch(changePixelRatio(id, device.pixelRatio));
},
@ -99,12 +105,16 @@ let App = createClass({
window.postMessage({ type: "exit" }, "*");
},
onRemoveDevice(id) {
onRemoveCustomDevice(device) {
this.props.dispatch(removeCustomDevice(device));
},
onRemoveDeviceAssociation(id) {
// TODO: Bug 1332754: Move messaging and logic into the action creator.
window.postMessage({
type: "remove-device",
type: "remove-device-association",
}, "*");
this.props.dispatch(removeDevice(id));
this.props.dispatch(removeDeviceAssociation(id));
this.props.dispatch(changeTouchSimulation(false));
this.props.dispatch(changePixelRatio(id, 0));
},
@ -125,8 +135,8 @@ let App = createClass({
this.props.dispatch(updateDeviceDisplayed(device, deviceType, displayed));
},
onUpdateDeviceModalOpen(isOpen) {
this.props.dispatch(updateDeviceModalOpen(isOpen));
onUpdateDeviceModal(isOpen, modalOpenedFromViewport) {
this.props.dispatch(updateDeviceModal(isOpen, modalOpenedFromViewport));
},
render() {
@ -141,6 +151,7 @@ let App = createClass({
} = this.props;
let {
onAddCustomDevice,
onBrowserMounted,
onChangeDevice,
onChangeNetworkThrottling,
@ -149,12 +160,13 @@ let App = createClass({
onContentResize,
onDeviceListUpdate,
onExit,
onRemoveDevice,
onRemoveCustomDevice,
onRemoveDeviceAssociation,
onResizeViewport,
onRotateViewport,
onScreenshot,
onUpdateDeviceDisplayed,
onUpdateDeviceModalOpen,
onUpdateDeviceModal,
} = this;
let selectedDevice = "";
@ -165,6 +177,11 @@ let App = createClass({
selectedPixelRatio = viewports[0].pixelRatio;
}
let deviceAdderViewportTemplate = {};
if (devices.modalOpenedFromViewport !== null) {
deviceAdderViewportTemplate = viewports[devices.modalOpenedFromViewport];
}
return dom.div(
{
id: "app",
@ -191,16 +208,19 @@ let App = createClass({
onBrowserMounted,
onChangeDevice,
onContentResize,
onRemoveDevice,
onRemoveDeviceAssociation,
onRotateViewport,
onResizeViewport,
onUpdateDeviceModalOpen,
onUpdateDeviceModal,
}),
DeviceModal({
deviceAdderViewportTemplate,
devices,
onAddCustomDevice,
onDeviceListUpdate,
onRemoveCustomDevice,
onUpdateDeviceDisplayed,
onUpdateDeviceModalOpen,
onUpdateDeviceModal,
})
);
},

View File

@ -0,0 +1,252 @@
/* 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/. */
/* eslint-env browser */
"use strict";
const { DOM: dom, createClass, createFactory, PropTypes, addons } =
require("devtools/client/shared/vendor/react");
const { getFormatStr, getStr } = require("../utils/l10n");
const Types = require("../types");
const ViewportDimension = createFactory(require("./viewport-dimension"));
module.exports = createClass({
displayName: "DeviceAdder",
propTypes: {
devices: PropTypes.shape(Types.devices).isRequired,
viewportTemplate: PropTypes.shape(Types.viewport).isRequired,
onAddCustomDevice: PropTypes.func.isRequired,
},
mixins: [ addons.PureRenderMixin ],
getInitialState() {
return {};
},
componentWillReceiveProps(nextProps) {
let {
width,
height,
} = nextProps.viewportTemplate;
this.setState({
width,
height,
});
},
onChangeSize(width, height) {
this.setState({
width,
height,
});
},
onDeviceAdderShow() {
this.setState({
deviceAdderDisplayed: true,
});
},
onDeviceAdderSave() {
let {
devices,
onAddCustomDevice,
} = this.props;
if (!this.pixelRatioInput.checkValidity()) {
return;
}
if (devices.custom.find(device => device.name == this.nameInput.value)) {
this.nameInput.setCustomValidity("Device name already in use");
return;
}
this.setState({
deviceAdderDisplayed: false,
});
onAddCustomDevice({
name: this.nameInput.value,
width: this.state.width,
height: this.state.height,
pixelRatio: parseFloat(this.pixelRatioInput.value),
userAgent: this.userAgentInput.value,
touch: this.touchInput.checked,
});
},
render() {
let {
devices,
viewportTemplate,
} = this.props;
let {
deviceAdderDisplayed,
height,
width,
} = this.state;
if (!deviceAdderDisplayed) {
return dom.div(
{
id: "device-adder"
},
dom.button(
{
id: "device-adder-show",
onClick: this.onDeviceAdderShow,
},
getStr("responsive.addDevice")
)
);
}
// If a device is currently selected, fold its attributes into a single object for use
// as the starting values of the form. If no device is selected, use the values for
// the current window.
let deviceName;
let normalizedViewport = Object.assign({}, viewportTemplate);
if (viewportTemplate.device) {
let device = devices[viewportTemplate.deviceType].find(d => {
return d.name == viewportTemplate.device;
});
deviceName = getFormatStr("responsive.customDeviceNameFromBase", device.name);
Object.assign(normalizedViewport, {
pixelRatio: device.pixelRatio,
userAgent: device.userAgent,
touch: device.touch,
});
} else {
deviceName = getStr("responsive.customDeviceName");
Object.assign(normalizedViewport, {
pixelRatio: window.devicePixelRatio,
userAgent: navigator.userAgent,
touch: false,
});
}
return dom.div(
{
id: "device-adder"
},
dom.div(
{
id: "device-adder-content",
},
dom.div(
{
id: "device-adder-column-1",
},
dom.label(
{
id: "device-adder-name",
},
dom.span(
{
className: "device-adder-label",
},
getStr("responsive.deviceAdderName")
),
dom.input({
defaultValue: deviceName,
ref: input => {
this.nameInput = input;
},
})
),
dom.label(
{
id: "device-adder-size",
},
dom.span(
{
className: "device-adder-label"
},
getStr("responsive.deviceAdderSize")
),
ViewportDimension({
viewport: {
width,
height,
},
onChangeSize: this.onChangeSize,
onRemoveDeviceAssociation: () => {},
})
),
dom.label(
{
id: "device-adder-pixel-ratio",
},
dom.span(
{
className: "device-adder-label"
},
getStr("responsive.deviceAdderPixelRatio")
),
dom.input({
type: "number",
step: "any",
defaultValue: normalizedViewport.pixelRatio,
ref: input => {
this.pixelRatioInput = input;
},
})
)
),
dom.div(
{
id: "device-adder-column-2",
},
dom.label(
{
id: "device-adder-user-agent",
},
dom.span(
{
className: "device-adder-label"
},
getStr("responsive.deviceAdderUserAgent")
),
dom.input({
defaultValue: normalizedViewport.userAgent,
ref: input => {
this.userAgentInput = input;
},
})
),
dom.label(
{
id: "device-adder-touch",
},
dom.span(
{
className: "device-adder-label"
},
getStr("responsive.deviceAdderTouch")
),
dom.input({
defaultChecked: normalizedViewport.touch,
type: "checkbox",
ref: input => {
this.touchInput = input;
},
})
)
),
),
dom.button(
{
id: "device-adder-save",
onClick: this.onDeviceAdderSave,
},
getStr("responsive.deviceAdderSave")
)
);
},
});

View File

@ -6,19 +6,24 @@
"use strict";
const { DOM: dom, createClass, PropTypes, addons } =
const { DOM: dom, createClass, createFactory, PropTypes, addons } =
require("devtools/client/shared/vendor/react");
const { getStr } = require("../utils/l10n");
const { getStr, getFormatStr } = require("../utils/l10n");
const Types = require("../types");
const DeviceAdder = createFactory(require("./device-adder"));
module.exports = createClass({
displayName: "DeviceModal",
propTypes: {
deviceAdderViewportTemplate: PropTypes.shape(Types.viewport).isRequired,
devices: PropTypes.shape(Types.devices).isRequired,
onAddCustomDevice: PropTypes.func.isRequired,
onDeviceListUpdate: PropTypes.func.isRequired,
onRemoveCustomDevice: PropTypes.func.isRequired,
onUpdateDeviceDisplayed: PropTypes.func.isRequired,
onUpdateDeviceModalOpen: PropTypes.func.isRequired,
onUpdateDeviceModal: PropTypes.func.isRequired,
},
mixins: [ addons.PureRenderMixin ],
@ -63,7 +68,7 @@ module.exports = createClass({
devices,
onDeviceListUpdate,
onUpdateDeviceDisplayed,
onUpdateDeviceModalOpen,
onUpdateDeviceModal,
} = this.props;
let preferredDevices = {
@ -88,7 +93,7 @@ module.exports = createClass({
}
onDeviceListUpdate(preferredDevices);
onUpdateDeviceModalOpen(false);
onUpdateDeviceModal(false);
},
onKeyDown(event) {
@ -98,16 +103,19 @@ module.exports = createClass({
// Escape keycode
if (event.keyCode === 27) {
let {
onUpdateDeviceModalOpen
onUpdateDeviceModal
} = this.props;
onUpdateDeviceModalOpen(false);
onUpdateDeviceModal(false);
}
},
render() {
let {
deviceAdderViewportTemplate,
devices,
onUpdateDeviceModalOpen,
onAddCustomDevice,
onRemoveCustomDevice,
onUpdateDeviceModal,
} = this.props;
const sortedDevices = {};
@ -128,7 +136,7 @@ module.exports = createClass({
dom.button({
id: "device-close-button",
className: "toolbar-button devtools-button",
onClick: () => onUpdateDeviceModalOpen(false),
onClick: () => onUpdateDeviceModal(false),
}),
dom.div(
{
@ -147,10 +155,24 @@ module.exports = createClass({
type
),
sortedDevices[type].map(device => {
let details = getFormatStr(
"responsive.deviceDetails", device.width, device.height,
device.pixelRatio, device.userAgent, device.touch
);
let removeDeviceButton;
if (type == "custom") {
removeDeviceButton = dom.button({
className: "device-remove-button toolbar-button devtools-button",
onClick: () => onRemoveCustomDevice(device),
});
}
return dom.label(
{
className: "device-label",
key: device.name,
title: details,
},
dom.input({
className: "device-input-checkbox",
@ -159,12 +181,23 @@ module.exports = createClass({
checked: this.state[device.name],
onChange: this.onDeviceCheckboxChange,
}),
device.name
dom.span(
{
className: "device-name",
},
device.name
),
removeDeviceButton
);
})
);
})
),
DeviceAdder({
devices,
viewportTemplate: deviceAdderViewportTemplate,
onAddCustomDevice,
}),
dom.button(
{
id: "device-submit-button",
@ -176,7 +209,7 @@ module.exports = createClass({
dom.div(
{
className: "modal-overlay",
onClick: () => onUpdateDeviceModalOpen(false),
onClick: () => onUpdateDeviceModal(false),
}
)
);

View File

@ -17,9 +17,10 @@ module.exports = createClass({
propTypes: {
devices: PropTypes.shape(Types.devices).isRequired,
selectedDevice: PropTypes.string.isRequired,
viewportId: PropTypes.number.isRequired,
onChangeDevice: PropTypes.func.isRequired,
onResizeViewport: PropTypes.func.isRequired,
onUpdateDeviceModalOpen: PropTypes.func.isRequired,
onUpdateDeviceModal: PropTypes.func.isRequired,
},
mixins: [ addons.PureRenderMixin ],
@ -27,20 +28,21 @@ module.exports = createClass({
onSelectChange({ target }) {
let {
devices,
viewportId,
onChangeDevice,
onResizeViewport,
onUpdateDeviceModalOpen,
onUpdateDeviceModal,
} = this.props;
if (target.value === OPEN_DEVICE_MODAL_VALUE) {
onUpdateDeviceModalOpen(true);
onUpdateDeviceModal(true, viewportId);
return;
}
for (let type of devices.types) {
for (let device of devices[type]) {
if (device.name === target.value) {
onResizeViewport(device.width, device.height);
onChangeDevice(device);
onChangeDevice(device, type);
return;
}
}

View File

@ -6,6 +6,7 @@
DevToolsModules(
'browser.js',
'device-adder.js',
'device-modal.js',
'device-selector.js',
'dpr-selector.js',

View File

@ -30,10 +30,10 @@ module.exports = createClass({
onBrowserMounted: PropTypes.func.isRequired,
onChangeDevice: PropTypes.func.isRequired,
onContentResize: PropTypes.func.isRequired,
onRemoveDevice: PropTypes.func.isRequired,
onRemoveDeviceAssociation: PropTypes.func.isRequired,
onResizeViewport: PropTypes.func.isRequired,
onRotateViewport: PropTypes.func.isRequired,
onUpdateDeviceModalOpen: PropTypes.func.isRequired,
onUpdateDeviceModal: PropTypes.func.isRequired,
},
getInitialState() {
@ -114,7 +114,7 @@ module.exports = createClass({
// the properties of the device on resize. However, at the moment, there is no
// way to edit dPR when a device is selected, and there is no UI at all for editing
// UA, so it's important to keep doing this for now.
this.props.onRemoveDevice();
this.props.onRemoveDeviceAssociation();
}
this.setState({
@ -135,7 +135,7 @@ module.exports = createClass({
onContentResize,
onResizeViewport,
onRotateViewport,
onUpdateDeviceModalOpen,
onUpdateDeviceModal,
} = this.props;
let resizeHandleClass = "viewport-resize-handle";
@ -154,11 +154,11 @@ module.exports = createClass({
},
ViewportToolbar({
devices,
selectedDevice: viewport.device,
viewport,
onChangeDevice,
onResizeViewport,
onRotateViewport,
onUpdateDeviceModalOpen,
onUpdateDeviceModal,
}),
dom.div(
{

View File

@ -15,8 +15,8 @@ module.exports = createClass({
propTypes: {
viewport: PropTypes.shape(Types.viewport).isRequired,
onRemoveDevice: PropTypes.func.isRequired,
onResizeViewport: PropTypes.func.isRequired,
onChangeSize: PropTypes.func.isRequired,
onRemoveDeviceAssociation: PropTypes.func.isRequired,
},
getInitialState() {
@ -116,10 +116,10 @@ module.exports = createClass({
// Change the device selector back to an unselected device
// TODO: Bug 1332754: Logic like this probably belongs in the action creator.
if (this.props.viewport.device) {
this.props.onRemoveDevice();
this.props.onRemoveDeviceAssociation();
}
this.props.onResizeViewport(parseInt(this.state.width, 10),
parseInt(this.state.height, 10));
this.props.onChangeSize(parseInt(this.state.width, 10),
parseInt(this.state.height, 10));
},
render() {

View File

@ -16,11 +16,11 @@ module.exports = createClass({
propTypes: {
devices: PropTypes.shape(Types.devices).isRequired,
selectedDevice: PropTypes.string.isRequired,
viewport: PropTypes.shape(Types.viewport).isRequired,
onChangeDevice: PropTypes.func.isRequired,
onResizeViewport: PropTypes.func.isRequired,
onRotateViewport: PropTypes.func.isRequired,
onUpdateDeviceModalOpen: PropTypes.func.isRequired,
onUpdateDeviceModal: PropTypes.func.isRequired,
},
mixins: [ addons.PureRenderMixin ],
@ -28,11 +28,11 @@ module.exports = createClass({
render() {
let {
devices,
selectedDevice,
viewport,
onChangeDevice,
onResizeViewport,
onRotateViewport,
onUpdateDeviceModalOpen,
onUpdateDeviceModal,
} = this.props;
return dom.div(
@ -41,10 +41,11 @@ module.exports = createClass({
},
DeviceSelector({
devices,
selectedDevice,
selectedDevice: viewport.device,
viewportId: viewport.id,
onChangeDevice,
onResizeViewport,
onUpdateDeviceModalOpen,
onUpdateDeviceModal,
}),
dom.button({
className: "viewport-rotate-button toolbar-button devtools-button",

View File

@ -24,28 +24,28 @@ module.exports = createClass({
onBrowserMounted: PropTypes.func.isRequired,
onChangeDevice: PropTypes.func.isRequired,
onContentResize: PropTypes.func.isRequired,
onRemoveDevice: PropTypes.func.isRequired,
onRemoveDeviceAssociation: PropTypes.func.isRequired,
onResizeViewport: PropTypes.func.isRequired,
onRotateViewport: PropTypes.func.isRequired,
onUpdateDeviceModalOpen: PropTypes.func.isRequired,
onUpdateDeviceModal: PropTypes.func.isRequired,
},
onChangeDevice(device) {
onChangeDevice(device, deviceType) {
let {
viewport,
onChangeDevice,
} = this.props;
onChangeDevice(viewport.id, device);
onChangeDevice(viewport.id, device, deviceType);
},
onRemoveDevice() {
onRemoveDeviceAssociation() {
let {
viewport,
onRemoveDevice,
onRemoveDeviceAssociation,
} = this.props;
onRemoveDevice(viewport.id);
onRemoveDeviceAssociation(viewport.id);
},
onResizeViewport(width, height) {
@ -75,12 +75,12 @@ module.exports = createClass({
viewport,
onBrowserMounted,
onContentResize,
onUpdateDeviceModalOpen,
onUpdateDeviceModal,
} = this.props;
let {
onChangeDevice,
onRemoveDevice,
onRemoveDeviceAssociation,
onRotateViewport,
onResizeViewport,
} = this;
@ -91,8 +91,8 @@ module.exports = createClass({
},
ViewportDimension({
viewport,
onRemoveDevice,
onResizeViewport,
onChangeSize: onResizeViewport,
onRemoveDeviceAssociation,
}),
ResizableViewport({
devices,
@ -103,10 +103,10 @@ module.exports = createClass({
onBrowserMounted,
onChangeDevice,
onContentResize,
onRemoveDevice,
onRemoveDeviceAssociation,
onResizeViewport,
onRotateViewport,
onUpdateDeviceModalOpen,
onUpdateDeviceModal,
})
);
},

View File

@ -22,10 +22,10 @@ module.exports = createClass({
onBrowserMounted: PropTypes.func.isRequired,
onChangeDevice: PropTypes.func.isRequired,
onContentResize: PropTypes.func.isRequired,
onRemoveDevice: PropTypes.func.isRequired,
onRemoveDeviceAssociation: PropTypes.func.isRequired,
onResizeViewport: PropTypes.func.isRequired,
onRotateViewport: PropTypes.func.isRequired,
onUpdateDeviceModalOpen: PropTypes.func.isRequired,
onUpdateDeviceModal: PropTypes.func.isRequired,
},
render() {
@ -37,10 +37,10 @@ module.exports = createClass({
onBrowserMounted,
onChangeDevice,
onContentResize,
onRemoveDevice,
onRemoveDeviceAssociation,
onResizeViewport,
onRotateViewport,
onUpdateDeviceModalOpen,
onUpdateDeviceModal,
} = this.props;
return dom.div(
@ -58,10 +58,10 @@ module.exports = createClass({
onBrowserMounted,
onChangeDevice,
onContentResize,
onRemoveDevice,
onRemoveDeviceAssociation,
onResizeViewport,
onRotateViewport,
onUpdateDeviceModalOpen,
onUpdateDeviceModal,
});
})
);

View File

@ -38,7 +38,8 @@
}
#root,
html, body {
html,
body {
height: 100%;
margin: 0;
}
@ -366,6 +367,7 @@ select > option.divider {
.viewport-dimension-editable.editing,
.viewport-dimension-input.editing {
color: var(--viewport-active-color);
outline: none;
}
.viewport-dimension-editable.editing {
@ -424,7 +426,7 @@ select > option.divider {
left: 0;
right: 0;
width: 642px;
height: 612px;
height: 650px;
z-index: 1;
}
@ -457,9 +459,9 @@ select > option.divider {
flex-direction: column;
flex-wrap: wrap;
overflow: auto;
height: 550px;
height: 515px;
width: 600px;
margin: 20px;
margin: 20px 20px 0;
}
#device-close-button,
@ -494,12 +496,29 @@ select > option.divider {
padding-bottom: 3px;
display: flex;
align-items: center;
/* Largest size without horizontal scrollbars */
max-width: 181px;
}
.device-input-checkbox {
margin-right: 5px;
}
.device-name {
flex: 1;
}
.device-remove-button,
.device-remove-button::before {
width: 12px;
height: 12px;
}
.device-remove-button::before {
background-image: url("./images/close.svg");
margin: -6px 0 0 -6px;
}
#device-submit-button {
background-color: var(--theme-tab-toolbar-background);
border-width: 1px 0 0 0;
@ -509,6 +528,8 @@ select > option.divider {
color: var(--theme-body-color);
width: 100%;
height: 20px;
position: absolute;
bottom: 0;
}
#device-submit-button:hover {
@ -519,3 +540,74 @@ select > option.divider {
background-color: var(--submit-button-active-background-color);
color: var(--submit-button-active-color);
}
/**
* Device Adder
*/
#device-adder {
display: flex;
flex-direction: column;
margin: 0 20px;
}
#device-adder-content {
display: flex;
}
#device-adder-column-1 {
flex: 1;
margin-right: 10px;
}
#device-adder-column-2 {
flex: 2;
}
#device-adder button {
background-color: var(--theme-tab-toolbar-background);
border: 1px solid var(--theme-splitter-color);
border-radius: 2px;
color: var(--theme-body-color);
margin: 0 auto;
}
#device-adder label {
display: flex;
margin-bottom: 5px;
align-items: center;
}
#device-adder label > input,
#device-adder label > .viewport-dimension {
flex: 1;
margin: 0;
}
#device-adder input {
background: transparent;
border: 1px solid transparent;
text-align: center;
color: var(--theme-body-color-inactive);
transition: all 0.25s ease;
}
#device-adder input:focus {
color: var(--viewport-active-color);
}
#device-adder label > input:focus,
#device-adder label > .viewport-dimension:focus {
border-bottom: 1px solid var(--theme-selection-background);
outline: none;
}
.device-adder-label {
display: inline-block;
margin-right: 5px;
min-width: 35px;
}
#device-adder #device-adder-save {
margin-top: 5px;
}

View File

@ -467,8 +467,8 @@ ResponsiveUI.prototype = {
case "exit":
this.onExit();
break;
case "remove-device":
this.onRemoveDevice(event);
case "remove-device-association":
this.onRemoveDeviceAssociation(event);
break;
}
},
@ -512,7 +512,7 @@ ResponsiveUI.prototype = {
ResponsiveUIManager.closeIfNeeded(browserWindow, tab);
},
onRemoveDevice: Task.async(function* (event) {
onRemoveDeviceAssociation: Task.async(function* (event) {
yield this.updateUserAgent();
yield this.updateDPPX();
yield this.updateTouchSimulation();

View File

@ -10,8 +10,9 @@ const {
LOAD_DEVICE_LIST_START,
LOAD_DEVICE_LIST_ERROR,
LOAD_DEVICE_LIST_END,
REMOVE_DEVICE,
UPDATE_DEVICE_DISPLAYED,
UPDATE_DEVICE_MODAL_OPEN,
UPDATE_DEVICE_MODAL,
} = require("../actions/index");
const Types = require("../types");
@ -19,6 +20,7 @@ const Types = require("../types");
const INITIAL_DEVICES = {
types: [],
isModalOpen: false,
modalOpenedFromViewport: null,
listState: Types.deviceListState.INITIALIZED,
};
@ -69,9 +71,23 @@ let reducers = {
});
},
[UPDATE_DEVICE_MODAL_OPEN](devices, { isOpen }) {
[REMOVE_DEVICE](devices, { device, deviceType }) {
let index = devices[deviceType].indexOf(device);
if (index < 0) {
return devices;
}
let list = [...devices[deviceType]];
list.splice(index, 1);
return Object.assign({}, devices, {
[deviceType]: list
});
},
[UPDATE_DEVICE_MODAL](devices, { isOpen, modalOpenedFromViewport }) {
return Object.assign({}, devices, {
isModalOpen: isOpen,
modalOpenedFromViewport,
});
},

View File

@ -8,7 +8,7 @@ const {
ADD_VIEWPORT,
CHANGE_DEVICE,
CHANGE_PIXEL_RATIO,
REMOVE_DEVICE,
REMOVE_DEVICE_ASSOCIATION,
RESIZE_VIEWPORT,
ROTATE_VIEWPORT,
} = require("../actions/index");
@ -19,6 +19,7 @@ const INITIAL_VIEWPORTS = [];
const INITIAL_VIEWPORT = {
id: nextViewportId++,
device: "",
deviceType: "",
width: 320,
height: 480,
pixelRatio: {
@ -36,7 +37,7 @@ let reducers = {
return [...viewports, Object.assign({}, INITIAL_VIEWPORT)];
},
[CHANGE_DEVICE](viewports, { id, device }) {
[CHANGE_DEVICE](viewports, { id, device, deviceType }) {
return viewports.map(viewport => {
if (viewport.id !== id) {
return viewport;
@ -44,6 +45,7 @@ let reducers = {
return Object.assign({}, viewport, {
device,
deviceType,
});
});
},
@ -62,7 +64,7 @@ let reducers = {
});
},
[REMOVE_DEVICE](viewports, { id }) {
[REMOVE_DEVICE_ASSOCIATION](viewports, { id }) {
return viewports.map(viewport => {
if (viewport.id !== id) {
return viewport;
@ -70,6 +72,7 @@ let reducers = {
return Object.assign({}, viewport, {
device: "",
deviceType: "",
});
});
},

View File

@ -17,6 +17,7 @@ support-files =
!/devtools/client/shared/test/test-actor-registry.js
[browser_device_change.js]
[browser_device_custom.js]
[browser_device_modal_error.js]
[browser_device_modal_exit.js]
[browser_device_modal_submit.js]

View File

@ -0,0 +1,152 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Test adding and removing custom devices via the modal.
const device = {
name: "Test Device",
width: 400,
height: 570,
pixelRatio: 1.5,
userAgent: "Mozilla/5.0 (Mobile; rv:39.0) Gecko/39.0 Firefox/39.0",
touch: true,
firefoxOS: false,
os: "android",
};
const TEST_URL = "data:text/html;charset=utf-8,";
const Types = require("devtools/client/responsive.html/types");
addRDMTask(TEST_URL, function* ({ ui }) {
let { toolWindow } = ui;
let { store, document } = toolWindow;
let React = toolWindow.require("devtools/client/shared/vendor/react");
let { Simulate } = React.addons.TestUtils;
// Wait until the viewport has been added and the device list has been loaded
yield waitUntilState(store, state => state.viewports.length == 1
&& state.devices.listState == Types.deviceListState.LOADED);
let deviceSelector = document.querySelector(".viewport-device-selector");
let submitButton = document.querySelector("#device-submit-button");
openDeviceModal(ui);
info("Reveal device adder form, check that defaults match the viewport");
let adderShow = document.querySelector("#device-adder-show");
Simulate.click(adderShow);
testDeviceAdder(ui, {
name: "Custom Device",
width: 320,
height: 480,
pixelRatio: window.devicePixelRatio,
userAgent: navigator.userAgent,
touch: false,
});
info("Fill out device adder form and save");
setDeviceAdder(ui, device);
let adderSave = document.querySelector("#device-adder-save");
let saved = waitUntilState(store, state => state.devices.custom.length == 1);
Simulate.click(adderSave);
yield saved;
info("Enable device in modal");
let deviceCb = [...document.querySelectorAll(".device-input-checkbox")].find(cb => {
return cb.value == device.name;
});
ok(deviceCb, "Custom device checkbox added to modal");
deviceCb.click();
Simulate.click(submitButton);
info("Look for custom device in device selector");
let selectorOption = [...deviceSelector.options].find(opt => opt.value == device.name);
ok(selectorOption, "Custom device option added to device selector");
});
addRDMTask(TEST_URL, function* ({ ui }) {
let { toolWindow } = ui;
let { store, document } = toolWindow;
let React = toolWindow.require("devtools/client/shared/vendor/react");
let { Simulate } = React.addons.TestUtils;
// Wait until the viewport has been added and the device list has been loaded
yield waitUntilState(store, state => state.viewports.length == 1
&& state.devices.listState == Types.deviceListState.LOADED);
let deviceSelector = document.querySelector(".viewport-device-selector");
let submitButton = document.querySelector("#device-submit-button");
info("Select existing device from the selector");
yield selectDevice(ui, "Test Device");
openDeviceModal(ui);
info("Reveal device adder form, check that defaults are based on selected device");
let adderShow = document.querySelector("#device-adder-show");
Simulate.click(adderShow);
testDeviceAdder(ui, Object.assign({}, device, {
name: "Test Device (Custom)",
}));
info("Remove previously added custom device");
let deviceRemoveButton = document.querySelector(".device-remove-button");
let removed = waitUntilState(store, state => state.devices.custom.length == 0);
Simulate.click(deviceRemoveButton);
yield removed;
Simulate.click(submitButton);
info("Ensure custom device was removed from device selector");
yield waitUntilState(store, state => state.viewports[0].device == "");
is(deviceSelector.value, "", "Device selector reset to no device");
let selectorOption = [...deviceSelector.options].find(opt => opt.value == device.name);
ok(!selectorOption, "Custom device option removed from device selector");
});
function testDeviceAdder(ui, expected) {
let { document } = ui.toolWindow;
let nameInput = document.querySelector("#device-adder-name input");
let [ widthInput, heightInput ] = document.querySelectorAll("#device-adder-size input");
let pixelRatioInput = document.querySelector("#device-adder-pixel-ratio input");
let userAgentInput = document.querySelector("#device-adder-user-agent input");
let touchInput = document.querySelector("#device-adder-touch input");
is(nameInput.value, expected.name, "Device name matches");
is(parseInt(widthInput.value, 10), expected.width, "Width matches");
is(parseInt(heightInput.value, 10), expected.height, "Height matches");
is(parseFloat(pixelRatioInput.value), expected.pixelRatio,
"devicePixelRatio matches");
is(userAgentInput.value, expected.userAgent, "User agent matches");
is(touchInput.checked, expected.touch, "Touch matches");
}
function setDeviceAdder(ui, value) {
let { toolWindow } = ui;
let { document } = ui.toolWindow;
let React = toolWindow.require("devtools/client/shared/vendor/react");
let { Simulate } = React.addons.TestUtils;
let nameInput = document.querySelector("#device-adder-name input");
let [ widthInput, heightInput ] = document.querySelectorAll("#device-adder-size input");
let pixelRatioInput = document.querySelector("#device-adder-pixel-ratio input");
let userAgentInput = document.querySelector("#device-adder-user-agent input");
let touchInput = document.querySelector("#device-adder-touch input");
nameInput.value = value.name;
Simulate.change(nameInput);
widthInput.value = value.width;
Simulate.change(widthInput);
Simulate.blur(widthInput);
heightInput.value = value.height;
Simulate.change(heightInput);
Simulate.blur(heightInput);
pixelRatioInput.value = value.pixelRatio;
Simulate.change(pixelRatioInput);
userAgentInput.value = value.userAgent;
Simulate.change(userAgentInput);
touchInput.checked = value.touch;
Simulate.change(touchInput);
}

View File

@ -60,6 +60,7 @@ registerCleanupFunction(() => {
Services.prefs.clearUserPref("devtools.responsive.html.enabled");
Services.prefs.clearUserPref("devtools.responsive.html.displayedDeviceList");
asyncStorage.removeItem("devtools.devices.url_cache");
asyncStorage.removeItem("devtools.devices.local");
});
// This depends on the "devtools.responsive.html.enabled" pref
@ -242,30 +243,20 @@ function openDeviceModal({ toolWindow }) {
}
function changeSelectValue({ toolWindow }, selector, value) {
let { document } = toolWindow;
let React = toolWindow.require("devtools/client/shared/vendor/react");
let { Simulate } = React.addons.TestUtils;
info(`Selecting ${value} in ${selector}.`);
return new Promise(resolve => {
let select = toolWindow.document.querySelector(selector);
isnot(select, null, `selector "${selector}" should match an existing element.`);
let select = document.querySelector(selector);
isnot(select, null, `selector "${selector}" should match an existing element.`);
let option = [...select.options].find(o => o.value === String(value));
isnot(option, undefined, `value "${value}" should match an existing option.`);
let option = [...select.options].find(o => o.value === String(value));
isnot(option, undefined, `value "${value}" should match an existing option.`);
let event = new toolWindow.UIEvent("change", {
view: toolWindow,
bubbles: true,
cancelable: true
});
select.addEventListener("change", () => {
is(select.value, value,
`Select's option with value "${value}" should be selected.`);
resolve();
}, { once: true });
select.value = value;
select.dispatchEvent(event);
});
select.value = value;
Simulate.change(select);
}
const selectDevice = (ui, value) => Promise.all([

View File

@ -34,7 +34,7 @@ add_task(function* () {
let viewport = getState().viewports[0];
equal(viewport.device, "", "Default device is unselected");
dispatch(changeDevice(0, "Firefox OS Flame"));
dispatch(changeDevice(0, "Firefox OS Flame", "phones"));
viewport = getState().viewports[0];
equal(viewport.device, "Firefox OS Flame",

View File

@ -89,6 +89,9 @@ exports.devices = {
// Whether or not the device modal is open
isModalOpen: PropTypes.bool,
// Viewport id that triggered the modal to open
modalOpenedFromViewport: PropTypes.number,
// Device list state, possible values are exported above in an enum
listState: PropTypes.oneOf(Object.keys(exports.deviceListState)),
@ -140,6 +143,9 @@ exports.viewport = {
// The currently selected device applied to the viewport
device: PropTypes.string,
// The currently selected device type applied to the viewport
deviceType: PropTypes.string,
// The width of the viewport
width: PropTypes.number,

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