--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -3780,21 +3780,16 @@
* are hidden). This checks to make sure all conditions are
* satisfied, and then records the tab switch as finished.
*/
maybeFinishTabSwitch: function () {
if (this.switchInProgress && this.requestedTab &&
this.getTabState(this.requestedTab) == this.STATE_LOADED) {
// After this point the tab has switched from the content thread's point of view.
// The changes will be visible after the next refresh driver tick + composite.
- let event = new CustomEvent("TabSwitched", {
- bubbles: true,
- cancelable: true
- });
- this.tabbrowser.dispatchEvent(event);
let time = TelemetryStopwatch.timeElapsed("FX_TAB_SWITCH_TOTAL_E10S_MS", window);
if (time != -1) {
TelemetryStopwatch.finish("FX_TAB_SWITCH_TOTAL_E10S_MS", window);
this.log("DEBUG: tab switch time = " + time);
this.addMarker("AsyncTabSwitch:Finish");
}
this.switchInProgress = false;
}
--- a/browser/components/customizableui/CustomizableWidgets.jsm
+++ b/browser/components/customizableui/CustomizableWidgets.jsm
@@ -499,54 +499,51 @@ const CustomizableWidgets = [
});
return item;
},
}, {
id: "privatebrowsing-button",
shortcutId: "key_privatebrowsing",
defaultArea: CustomizableUI.AREA_PANEL,
onCommand: function(e) {
- let win = e.target && e.target.ownerGlobal;
- if (win && typeof win.OpenBrowserWindow == "function") {
+ let win = e.target.ownerGlobal;
+ if (typeof win.OpenBrowserWindow == "function") {
win.OpenBrowserWindow({private: true});
}
}
}, {
id: "save-page-button",
shortcutId: "key_savePage",
tooltiptext: "save-page-button.tooltiptext3",
defaultArea: CustomizableUI.AREA_PANEL,
onCommand: function(aEvent) {
- let win = aEvent.target &&
- aEvent.target.ownerGlobal;
- if (win && typeof win.saveBrowser == "function") {
+ let win = aEvent.target.ownerGlobal;
+ if (typeof win.saveBrowser == "function") {
win.saveBrowser(win.gBrowser.selectedBrowser);
}
}
}, {
id: "find-button",
shortcutId: "key_find",
tooltiptext: "find-button.tooltiptext3",
defaultArea: CustomizableUI.AREA_PANEL,
onCommand: function(aEvent) {
- let win = aEvent.target &&
- aEvent.target.ownerGlobal;
- if (win && win.gFindBar) {
+ let win = aEvent.target.ownerGlobal;
+ if (win.gFindBar) {
win.gFindBar.onFindCommand();
}
}
}, {
id: "open-file-button",
shortcutId: "openFileKb",
tooltiptext: "open-file-button.tooltiptext3",
defaultArea: CustomizableUI.AREA_PANEL,
onCommand: function(aEvent) {
- let win = aEvent.target
- && aEvent.target.ownerGlobal;
- if (win && typeof win.BrowserOpenFileWindow == "function") {
+ let win = aEvent.target.ownerGlobal;
+ if (typeof win.BrowserOpenFileWindow == "function") {
win.BrowserOpenFileWindow();
}
}
}, {
id: "sidebar-button",
type: "view",
viewId: "PanelUI-sidebar",
tooltiptext: "sidebar-button.tooltiptext2",
@@ -612,19 +609,18 @@ const CustomizableWidgets = [
return node;
}
}, {
id: "add-ons-button",
shortcutId: "key_openAddons",
tooltiptext: "add-ons-button.tooltiptext3",
defaultArea: CustomizableUI.AREA_PANEL,
onCommand: function(aEvent) {
- let win = aEvent.target &&
- aEvent.target.ownerGlobal;
- if (win && typeof win.BrowserOpenAddonsMgr == "function") {
+ let win = aEvent.target.ownerGlobal;
+ if (typeof win.BrowserOpenAddonsMgr == "function") {
win.BrowserOpenAddonsMgr();
}
}
}, {
id: "zoom-controls",
type: "custom",
tooltiptext: "zoom-controls.tooltiptext2",
defaultArea: CustomizableUI.AREA_PANEL,
@@ -1149,19 +1145,18 @@ if (Services.prefs.getBoolPref("privacy.
}
});
}
let preferencesButton = {
id: "preferences-button",
defaultArea: CustomizableUI.AREA_PANEL,
onCommand: function(aEvent) {
- let win = aEvent.target &&
- aEvent.target.ownerGlobal;
- if (win && typeof win.openPreferences == "function") {
+ let win = aEvent.target.ownerGlobal;
+ if (typeof win.openPreferences == "function") {
win.openPreferences();
}
}
};
if (AppConstants.platform == "win") {
preferencesButton.label = "preferences-button.labelWin";
preferencesButton.tooltiptext = "preferences-button.tooltipWin2";
} else if (AppConstants.platform == "macosx") {
--- a/browser/components/extensions/ext-commands.js
+++ b/browser/components/extensions/ext-commands.js
@@ -72,20 +72,22 @@ CommandList.prototype = {
loadCommandsFromManifest(manifest) {
let commands = new Map();
// For Windows, chrome.runtime expects 'win' while chrome.commands
// expects 'windows'. We can special case this for now.
let os = PlatformInfo.os == "win" ? "windows" : PlatformInfo.os;
for (let name of Object.keys(manifest.commands)) {
let command = manifest.commands[name];
let shortcut = command.suggested_key[os] || command.suggested_key.default;
- commands.set(name, {
- description: command.description,
- shortcut: shortcut.replace(/\s+/g, ""),
- });
+ if (shortcut) {
+ commands.set(name, {
+ description: command.description,
+ shortcut: shortcut.replace(/\s+/g, ""),
+ });
+ }
}
return commands;
},
/**
* Registers the commands to a document.
* @param {ChromeWindow} window The XUL window to insert the Keyset.
*/
@@ -119,17 +121,17 @@ CommandList.prototype = {
// and it is currently ignored when set to the empty string.
keyElement.setAttribute("oncommand", "//");
/* eslint-disable mozilla/balanced-listeners */
// We remove all references to the key elements when the extension is shutdown,
// therefore the listeners for these elements will be garbage collected.
keyElement.addEventListener("command", (event) => {
if (name == "_execute_page_action") {
- let win = event.target.ownerGlobal;
+ let win = event.target.ownerDocument.defaultView;
pageActionFor(this.extension).triggerAction(win);
} else {
this.emit("command", name);
}
});
/* eslint-enable mozilla/balanced-listeners */
return keyElement;
--- a/browser/components/extensions/test/browser/browser_ext_commands_onCommand.js
+++ b/browser/components/extensions/test/browser/browser_ext_commands_onCommand.js
@@ -1,98 +1,254 @@
/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set sts=2 sw=2 et tw=80: */
"use strict";
+Cu.import("resource://gre/modules/AppConstants.jsm");
+
add_task(function* test_user_defined_commands() {
+ const testCommands = [
+ // Ctrl Shortcuts
+ {
+ name: "toggle-ctrl-a",
+ shortcut: "Ctrl+A",
+ key: "A",
+ modifiers: {
+ accelKey: true,
+ },
+ },
+ {
+ name: "toggle-ctrl-up",
+ shortcut: "Ctrl+Up",
+ key: "VK_UP",
+ modifiers: {
+ accelKey: true,
+ },
+ },
+ // Alt Shortcuts
+ {
+ name: "toggle-alt-1",
+ shortcut: "Alt+1",
+ key: "1",
+ modifiers: {
+ altKey: true,
+ },
+ },
+ {
+ name: "toggle-alt-a",
+ shortcut: "Alt+A",
+ key: "A",
+ modifiers: {
+ altKey: true,
+ },
+ },
+ {
+ name: "toggle-alt-down",
+ shortcut: "Alt+Down",
+ key: "VK_DOWN",
+ modifiers: {
+ altKey: true,
+ },
+ },
+ // Mac Shortcuts
+ {
+ name: "toggle-command-shift-page-up",
+ shortcutMac: "Command+Shift+PageUp",
+ key: "VK_PAGE_UP",
+ modifiers: {
+ accelKey: true,
+ shiftKey: true,
+ },
+ },
+ {
+ name: "toggle-mac-control-b",
+ shortcut: "Ctrl+B",
+ shortcutMac: "MacCtrl+B",
+ key: "B",
+ modifiers: {
+ ctrlKey: true,
+ },
+ },
+ // Ctrl+Shift Shortcuts
+ {
+ name: "toggle-ctrl-shift-1",
+ shortcut: "Ctrl+Shift+1",
+ key: "1",
+ modifiers: {
+ accelKey: true,
+ shiftKey: true,
+ },
+ },
+ {
+ name: "toggle-ctrl-shift-i",
+ shortcut: "Ctrl+Shift+I",
+ key: "I",
+ modifiers: {
+ accelKey: true,
+ shiftKey: true,
+ },
+ },
+ {
+ name: "toggle-ctrl-shift-left",
+ shortcut: "Ctrl+Shift+Left",
+ key: "VK_LEFT",
+ modifiers: {
+ accelKey: true,
+ shiftKey: true,
+ },
+ },
+ // Alt+Shift Shortcuts
+ {
+ name: "toggle-alt-shift-1",
+ shortcut: "Alt+Shift+1",
+ key: "1",
+ modifiers: {
+ altKey: true,
+ shiftKey: true,
+ },
+ },
+ {
+ name: "toggle-alt-shift-a",
+ shortcut: "Alt+Shift+A",
+ key: "A",
+ modifiers: {
+ altKey: true,
+ shiftKey: true,
+ },
+ },
+ {
+ name: "toggle-alt-shift-right",
+ shortcut: "Alt+Shift+Right",
+ key: "VK_RIGHT",
+ modifiers: {
+ altKey: true,
+ shiftKey: true,
+ },
+ },
+ // Misc Shortcuts
+ {
+ name: "unrecognized-property-name",
+ shortcut: "Alt+Shift+3",
+ key: "3",
+ modifiers: {
+ altKey: true,
+ shiftKey: true,
+ },
+ unrecognized_property: "with-a-random-value",
+ },
+ {
+ name: "spaces-in-shortcut-name",
+ shortcut: " Alt + Shift + 2 ",
+ key: "2",
+ modifiers: {
+ altKey: true,
+ shiftKey: true,
+ },
+ },
+ ];
+
// Create a window before the extension is loaded.
let win1 = yield BrowserTestUtils.openNewBrowserWindow();
yield BrowserTestUtils.loadURI(win1.gBrowser.selectedBrowser, "about:robots");
yield BrowserTestUtils.browserLoaded(win1.gBrowser.selectedBrowser);
+ let commands = {};
+ let isMac = AppConstants.platform == "macosx";
+ let totalMacOnlyCommands = 0;
+
+ for (let testCommand of testCommands) {
+ let command = {
+ suggested_key: {},
+ };
+
+ if (testCommand.shortcut) {
+ command.suggested_key.default = testCommand.shortcut;
+ }
+
+ if (testCommand.shortcutMac) {
+ command.suggested_key.mac = testCommand.shortcutMac;
+ }
+
+ if (testCommand.shortcutMac && !testCommand.shortcut) {
+ totalMacOnlyCommands++;
+ }
+
+ if (testCommand.unrecognized_property) {
+ command.unrecognized_property = testCommand.unrecognized_property;
+ }
+
+ commands[testCommand.name] = command;
+ }
+
let extension = ExtensionTestUtils.loadExtension({
manifest: {
- "commands": {
- "toggle-feature-using-alt-shift-3": {
- "suggested_key": {
- "default": "Alt+Shift+3",
- },
- },
- "toggle-feature-using-alt-shift-comma": {
- "suggested_key": {
- "default": "Alt+Shift+Comma",
- },
- "unrecognized_property": "with-a-random-value",
- },
- "toggle-feature-with-whitespace-in-suggested-key": {
- "suggested_key": {
- "default": " Alt + Shift + 2 ",
- },
- },
- },
+ "commands": commands,
},
background: function() {
browser.commands.onCommand.addListener(commandName => {
browser.test.sendMessage("oncommand", commandName);
});
browser.test.sendMessage("ready");
},
});
-
SimpleTest.waitForExplicitFinish();
let waitForConsole = new Promise(resolve => {
SimpleTest.monitorConsole(resolve, [{
message: /Reading manifest: Error processing commands.*.unrecognized_property: An unexpected property was found/,
}]);
});
yield extension.startup();
yield extension.awaitMessage("ready");
+ function* runTest() {
+ for (let testCommand of testCommands) {
+ if (testCommand.shortcutMac && !isMac) {
+ continue;
+ }
+ EventUtils.synthesizeKey(testCommand.key, testCommand.modifiers);
+ let message = yield extension.awaitMessage("oncommand");
+ is(message, testCommand.name, "Expected onCommand listener to fire with the correct command name");
+ }
+ }
+
// Create another window after the extension is loaded.
let win2 = yield BrowserTestUtils.openNewBrowserWindow();
yield BrowserTestUtils.loadURI(win2.gBrowser.selectedBrowser, "about:config");
yield BrowserTestUtils.browserLoaded(win2.gBrowser.selectedBrowser);
+ let totalTestCommands = Object.keys(testCommands).length;
+ let expectedCommandsRegistered = isMac ? totalTestCommands : totalTestCommands - totalMacOnlyCommands;
+
// Confirm the keysets have been added to both windows.
let keysetID = `ext-keyset-id-${makeWidgetId(extension.id)}`;
let keyset = win1.document.getElementById(keysetID);
ok(keyset != null, "Expected keyset to exist");
- is(keyset.childNodes.length, 3, "Expected keyset to have 3 children");
+ is(keyset.childNodes.length, expectedCommandsRegistered, "Expected keyset to have the correct number of children");
keyset = win2.document.getElementById(keysetID);
ok(keyset != null, "Expected keyset to exist");
- is(keyset.childNodes.length, 3, "Expected keyset to have 3 children");
+ is(keyset.childNodes.length, expectedCommandsRegistered, "Expected keyset to have the correct number of children");
// Confirm that the commands are registered to both windows.
yield focusWindow(win1);
- EventUtils.synthesizeKey("3", {altKey: true, shiftKey: true});
- let message = yield extension.awaitMessage("oncommand");
- is(message, "toggle-feature-using-alt-shift-3", "Expected onCommand listener to fire with correct message");
+ yield runTest();
yield focusWindow(win2);
- EventUtils.synthesizeKey("VK_COMMA", {altKey: true, shiftKey: true});
- message = yield extension.awaitMessage("oncommand");
- is(message, "toggle-feature-using-alt-shift-comma", "Expected onCommand listener to fire with correct message");
-
- EventUtils.synthesizeKey("2", {altKey: true, shiftKey: true});
- message = yield extension.awaitMessage("oncommand");
- is(message, "toggle-feature-with-whitespace-in-suggested-key", "Expected onCommand listener to fire with correct message");
+ yield runTest();
yield extension.unload();
// Confirm that the keysets have been removed from both windows after the extension is unloaded.
keyset = win1.document.getElementById(keysetID);
is(keyset, null, "Expected keyset to be removed from the window");
keyset = win2.document.getElementById(keysetID);
is(keyset, null, "Expected keyset to be removed from the window");
yield BrowserTestUtils.closeWindow(win1);
yield BrowserTestUtils.closeWindow(win2);
SimpleTest.endMonitorConsole();
yield waitForConsole;
});
-
-
--- a/browser/components/nsBrowserGlue.js
+++ b/browser/components/nsBrowserGlue.js
@@ -120,24 +120,23 @@ XPCOMUtils.defineLazyModuleGetter(this,
XPCOMUtils.defineLazyModuleGetter(this, "SimpleServiceDiscovery",
"resource://gre/modules/SimpleServiceDiscovery.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "ContentSearch",
"resource:///modules/ContentSearch.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "TabCrashHandler",
"resource:///modules/ContentCrashHandlers.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PluralForm",
+ "resource://gre/modules/PluralForm.jsm");
if (AppConstants.MOZ_CRASHREPORTER) {
XPCOMUtils.defineLazyModuleGetter(this, "PluginCrashReporter",
"resource:///modules/ContentCrashHandlers.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "CrashSubmit",
"resource://gre/modules/CrashSubmit.jsm");
- XPCOMUtils.defineLazyModuleGetter(this, "PluralForm",
- "resource://gre/modules/PluralForm.jsm");
-
}
XPCOMUtils.defineLazyGetter(this, "gBrandBundle", function() {
return Services.strings.createBundle('chrome://branding/locale/brand.properties');
});
XPCOMUtils.defineLazyGetter(this, "gBrowserBundle", function() {
return Services.strings.createBundle('chrome://browser/locale/browser.properties');
@@ -2477,18 +2476,22 @@ BrowserGlue.prototype = {
if (URIs.length == 1) {
title = bundle.GetStringFromName("tabArrivingNotification.title");
const pageTitle = URIs[0].title || firstTab.linkedBrowser.contentTitle
|| URIs[0].uri;
body = bundle.formatStringFromName("tabArrivingNotification.body", [pageTitle, deviceName], 2);
} else {
title = bundle.GetStringFromName("tabsArrivingNotification.title");
const tabArrivingBody = URIs.every(URI => URI.clientId == URIs[0].clientId) ?
- "tabsArrivingNotification.body" : "tabsArrivingNotificationMultiple.body";
- body = bundle.formatStringFromName(tabArrivingBody, [URIs.length, deviceName], 2);
+ "unnamedTabsArrivingNotification.body" :
+ "unnamedTabsArrivingNotificationMultiple.body";
+ body = bundle.GetStringFromName(tabArrivingBody);
+ body = PluralForm.get(URIs.length, body);
+ body = body.replace("#1", URIs.length);
+ body = body.replace("#2", deviceName);
}
const clickCallback = (subject, topic, data) => {
if (topic == "alertclickcallback") {
win.gBrowser.selectedTab = firstTab;
}
}
AlertsService.showAlertNotification(null, title, body, true, null, clickCallback);
--- a/browser/components/sessionstore/content/content-sessionStore.js
+++ b/browser/components/sessionstore/content/content-sessionStore.js
@@ -365,21 +365,21 @@ var SessionHistoryListener = {
*/
var ScrollPositionListener = {
init: function () {
addEventListener("scroll", this);
gFrameTree.addObserver(this);
},
handleEvent: function (event) {
- let frame = event.target && event.target.defaultView;
+ let frame = event.target.defaultView;
// Don't collect scroll data for frames created at or after the load event
// as SessionStore can't restore scroll data for those.
- if (frame && gFrameTree.contains(frame)) {
+ if (gFrameTree.contains(frame)) {
MessageQueue.push("scroll", () => this.collect());
}
},
onFrameTreeCollected: function () {
MessageQueue.push("scroll", () => this.collect());
},
@@ -412,22 +412,21 @@ var ScrollPositionListener = {
var FormDataListener = {
init: function () {
addEventListener("input", this, true);
addEventListener("change", this, true);
gFrameTree.addObserver(this);
},
handleEvent: function (event) {
- let frame = event.target &&
- event.target.ownerGlobal;
+ let frame = event.target.ownerGlobal;
// Don't collect form data for frames created at or after the load event
// as SessionStore can't restore form data for those.
- if (frame && gFrameTree.contains(frame)) {
+ if (gFrameTree.contains(frame)) {
MessageQueue.push("formdata", () => this.collect());
}
},
onFrameTreeReset: function () {
MessageQueue.push("formdata", () => null);
},
--- a/browser/locales/en-US/chrome/browser/accounts.properties
+++ b/browser/locales/en-US/chrome/browser/accounts.properties
@@ -42,14 +42,18 @@ sendTabToAllDevices.menuitem = All Devic
# LOCALIZATION NOTE (tabArrivingNotification.title, tabArrivingNotification.body,
# tabsArrivingNotification.title, tabsArrivingNotification.body)
# These strings are used in a notification shown when we're opening tab(s) another device sent us to display.
tabArrivingNotification.title = Tab received
# LOCALIZATION NOTE (tabArrivingNotification.body) %1 is the title of the tab and %2 is the device name.
tabArrivingNotification.body = “%1$S” has arrived from %2$S.
tabsArrivingNotification.title = Multiple tabs received
-# LOCALIZATION NOTE (tabsArrivingNotification.body) %1 is the number of tabs received and %2 is the device name.
-tabsArrivingNotification.body = %1$S tabs have arrived from %2$S.
-# LOCALIZATION NOTE (tabsArrivingNotificationMultiple.body)
-# This string is used in a notification shown when we're opening tab(s) that several devices sent us to display.
-# %S is the number of tabs received
-tabsArrivingNotificationMultiple.body = %S tabs have arrived from your connected devices.
+# LOCALIZATION NOTE (unnamedTabsArrivingNotification.body):
+# Semi-colon list of plural forms.
+# See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
+# #1 is the number of tabs received and #2 is the device name.
+unnamedTabsArrivingNotification.body = #1 tab has arrived from #2.;#1 tabs have arrived from #2.
+# LOCALIZATION NOTE (unnamedTabsArrivingNotificationMultiple.body):
+# Semi-colon list of plural forms.
+# See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
+# #1 is the number of tabs received.
+unnamedTabsArrivingNotificationMultiple.body = #1 tab has arrived from your connected devices.;#1 tabs have arrived from your connected devices.
--- a/browser/themes/shared/addons/addon-install-anchor.svg
+++ b/browser/themes/shared/addons/addon-install-anchor.svg
@@ -1,19 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
width="16" height="16" viewBox="0 0 16 16">
<defs>
- <style>
- use:not(:target) {
- display: none;
- }
- .style-icon-notification {
- fill: #999;
- }
- </style>
<path id="shape-notifications-addons" d="M10,15c0.5,0,1-0.4,1-1v-3c0,0,0-0.8,0.8-0.8c0.6,0,0.6,0.8,1.8,0.8c0.6,0,1.5-0.2,1.5-2c0-1.8-0.9-2-1.5-2 c-1.1,0-1.1,0.7-1.8,0.7C11,7.7,11,7,11,7V6c0-0.6-0.5-1-1-1H8c0,0-0.8,0-0.8-0.8C7.2,3.6,8,3.6,8,2.5C8,1.9,7.8,1,6,1 C4.2,1,4,1.9,4,2.5c0,1.1,0.8,1.1,0.8,1.8C4.8,5,4,5,4,5H2C1.5,5,1,5.4,1,6l0,1.5c0,0-0.1,1,1.1,1c0.8,0,0.9-1,1.9-1 C4.5,7.4,5,8,5,9c0,1-0.5,1.6-1,1.6c-1,0-1.1-1.1-1.9-1.1C0.9,9.5,1,10.8,1,10.8V14c0,0.6,0.5,1,1,1l2.6,0c0,0,1.1,0,1.1-1 c0-0.8-1-0.1-1-1.1c0-0.5,0.7-1.2,1.8-1.2s1.8,0.7,1.8,1.2c0,1-1.1,0.3-1.1,1.1c0,1,1.2,1,1.2,1H10z"/>
</defs>
- <use id="default" xlink:href="#shape-notifications-addons" class="style-icon-notification"/>
+ <use id="default" xlink:href="#shape-notifications-addons" />
</svg>
--- a/browser/themes/shared/jar.inc.mn
+++ b/browser/themes/shared/jar.inc.mn
@@ -61,17 +61,17 @@
skin/classic/browser/heartbeat-star-lit.svg (../shared/heartbeat-star-lit.svg)
skin/classic/browser/heartbeat-star-off.svg (../shared/heartbeat-star-off.svg)
skin/classic/browser/identity-icon.svg (../shared/identity-block/identity-icon.svg)
skin/classic/browser/identity-not-secure.svg (../shared/identity-block/identity-not-secure.svg)
skin/classic/browser/identity-secure.svg (../shared/identity-block/identity-secure.svg)
skin/classic/browser/identity-mixed-passive-loaded.svg (../shared/identity-block/identity-mixed-passive-loaded.svg)
skin/classic/browser/identity-mixed-active-loaded.svg (../shared/identity-block/identity-mixed-active-loaded.svg)
skin/classic/browser/info.svg (../shared/info.svg)
- skin/classic/browser/permissions.svg (../shared/permissions.svg)
+ skin/classic/browser/notification-icons.svg (../shared/notification-icons.svg)
skin/classic/browser/tracking-protection-16.svg (../shared/identity-block/tracking-protection-16.svg)
skin/classic/browser/tracking-protection-disabled-16.svg (../shared/identity-block/tracking-protection-disabled-16.svg)
skin/classic/browser/newtab/close.png (../shared/newtab/close.png)
skin/classic/browser/newtab/controls.svg (../shared/newtab/controls.svg)
skin/classic/browser/newtab/whimsycorn.png (../shared/newtab/whimsycorn.png)
skin/classic/browser/preferences/in-content/favicon.ico (../shared/incontentprefs/favicon.ico)
skin/classic/browser/preferences/in-content/icons.svg (../shared/incontentprefs/icons.svg)
skin/classic/browser/preferences/in-content/search.css (../shared/incontentprefs/search.css)
@@ -122,22 +122,16 @@
skin/classic/browser/session-restore.svg (../shared/incontent-icons/session-restore.svg)
skin/classic/browser/tab-crashed.svg (../shared/incontent-icons/tab-crashed.svg)
skin/classic/browser/favicon-search-16.svg (../shared/favicon-search-16.svg)
skin/classic/browser/icon-search-64.svg (../shared/incontent-icons/icon-search-64.svg)
skin/classic/browser/welcome-back.svg (../shared/incontent-icons/welcome-back.svg)
skin/classic/browser/reader-tour.png (../shared/reader/reader-tour.png)
skin/classic/browser/reader-tour@2x.png (../shared/reader/reader-tour@2x.png)
skin/classic/browser/readerMode.svg (../shared/reader/readerMode.svg)
- skin/classic/browser/notification-pluginNormal.png (../shared/plugins/notification-pluginNormal.png)
- skin/classic/browser/notification-pluginNormal@2x.png (../shared/plugins/notification-pluginNormal@2x.png)
- skin/classic/browser/notification-pluginAlert.png (../shared/plugins/notification-pluginAlert.png)
- skin/classic/browser/notification-pluginAlert@2x.png (../shared/plugins/notification-pluginAlert@2x.png)
- skin/classic/browser/notification-pluginBlocked.png (../shared/plugins/notification-pluginBlocked.png)
- skin/classic/browser/notification-pluginBlocked@2x.png (../shared/plugins/notification-pluginBlocked@2x.png)
skin/classic/browser/webRTC-camera-white-16.png (../shared/webrtc/camera-white-16.png)
skin/classic/browser/webRTC-microphone-white-16.png (../shared/webrtc/microphone-white-16.png)
skin/classic/browser/webRTC-screen-white-16.png (../shared/webrtc/screen-white-16.png)
skin/classic/browser/panic-panel/header.png (../shared/panic-panel/header.png)
skin/classic/browser/panic-panel/header@2x.png (../shared/panic-panel/header@2x.png)
skin/classic/browser/panic-panel/header-small.png (../shared/panic-panel/header-small.png)
skin/classic/browser/panic-panel/header-small@2x.png (../shared/panic-panel/header-small@2x.png)
skin/classic/browser/panic-panel/icons.png (../shared/panic-panel/icons.png)
--- a/browser/themes/shared/notification-icons.inc.css
+++ b/browser/themes/shared/notification-icons.inc.css
@@ -36,27 +36,33 @@
}
.popup-notification-icon {
width: 64px;
height: 64px;
margin-inline-end: 10px;
}
+#notification-popup-box > .notification-anchor-icon:hover {
+ fill: #606060;
+}
+
/* INDIVIDUAL NOTIFICATIONS */
/* For the moment we apply the color filter only on the icons listed here.
The first two selectors are used by socialchat.xml (bug 1275558). */
.webRTC-sharingDevices-notification-icon,
.webRTC-sharingMicrophone-notification-icon,
.camera-icon,
.geo-icon,
.indexedDB-icon,
+.install-icon,
.login-icon,
.microphone-icon,
+.plugin-icon,
.pointerLock-icon,
.popup-icon,
.screen-icon,
.desktop-notification-icon,
.popup-notification-icon[popupid="geolocation"],
.popup-notification-icon[popupid="indexedDB-permissions-prompt"],
.popup-notification-icon[popupid="password"],
.popup-notification-icon[popupid="pointerLock"],
@@ -76,124 +82,124 @@
.webRTC-sharingDevices-notification-icon,
.webRTC-sharingMicrophone-notification-icon,
.in-use {
fill: #fea01b;
}
.popup-notification-icon[popupid="web-notifications"],
.desktop-notification-icon {
- list-style-image: url(chrome://browser/skin/permissions.svg#desktop-notification);
+ list-style-image: url(chrome://browser/skin/notification-icons.svg#desktop-notification);
}
.desktop-notification-icon.blocked {
- list-style-image: url(chrome://browser/skin/permissions.svg#desktop-notification-blocked);
+ list-style-image: url(chrome://browser/skin/notification-icons.svg#desktop-notification-blocked);
}
.geo-icon {
%ifdef XP_MACOSX
- list-style-image: url(chrome://browser/skin/permissions.svg#geo-osx);
+ list-style-image: url(chrome://browser/skin/notification-icons.svg#geo-osx);
%elif defined(MOZ_WIDGET_GTK)
- list-style-image: url(chrome://browser/skin/permissions.svg#geo-linux);
+ list-style-image: url(chrome://browser/skin/notification-icons.svg#geo-linux);
%else
- list-style-image: url(chrome://browser/skin/permissions.svg#geo-windows);
+ list-style-image: url(chrome://browser/skin/notification-icons.svg#geo-windows);
%endif
}
.geo-icon.blocked {
%ifdef XP_MACOSX
- list-style-image: url(chrome://browser/skin/permissions.svg#geo-osx-blocked);
+ list-style-image: url(chrome://browser/skin/notification-icons.svg#geo-osx-blocked);
%elif defined(MOZ_WIDGET_GTK)
- list-style-image: url(chrome://browser/skin/permissions.svg#geo-linux-blocked);
+ list-style-image: url(chrome://browser/skin/notification-icons.svg#geo-linux-blocked);
%else
- list-style-image: url(chrome://browser/skin/permissions.svg#geo-windows-blocked);
+ list-style-image: url(chrome://browser/skin/notification-icons.svg#geo-windows-blocked);
%endif
}
.popup-notification-icon[popupid="geolocation"] {
%ifdef XP_MACOSX
- list-style-image: url(chrome://browser/skin/permissions.svg#geo-osx);
+ list-style-image: url(chrome://browser/skin/notification-icons.svg#geo-osx);
%elif defined(MOZ_WIDGET_GTK)
- list-style-image: url(chrome://browser/skin/permissions.svg#geo-linux-detailed);
+ list-style-image: url(chrome://browser/skin/notification-icons.svg#geo-linux-detailed);
%else
- list-style-image: url(chrome://browser/skin/permissions.svg#geo-windows-detailed);
+ list-style-image: url(chrome://browser/skin/notification-icons.svg#geo-windows-detailed);
%endif
}
.popup-notification-icon[popupid="indexedDB-permissions-prompt"],
.indexedDB-icon {
- list-style-image: url(chrome://browser/skin/permissions.svg#indexedDB);
+ list-style-image: url(chrome://browser/skin/notification-icons.svg#indexedDB);
}
.indexedDB-icon.blocked {
- list-style-image: url(chrome://browser/skin/permissions.svg#indexedDB-blocked);
+ list-style-image: url(chrome://browser/skin/notification-icons.svg#indexedDB-blocked);
}
.login-icon {
- list-style-image: url(chrome://browser/skin/permissions.svg#login);
+ list-style-image: url(chrome://browser/skin/notification-icons.svg#login);
}
.popup-notification-icon[popupid="password"] {
- list-style-image: url(chrome://browser/skin/permissions.svg#login-detailed);
+ list-style-image: url(chrome://browser/skin/notification-icons.svg#login-detailed);
}
#login-fill-notification-icon {
/* Temporary solution until the capture and fill doorhangers are unified. */
transform: scaleX(-1);
}
/* The first selector is used by socialchat.xml (bug 1275558). */
.webRTC-sharingDevices-notification-icon,
.camera-icon,
.popup-notification-icon[popupid="webRTC-shareDevices"],
.popup-notification-icon[popupid="webRTC-sharingDevices"] {
- list-style-image: url(chrome://browser/skin/permissions.svg#camera);
+ list-style-image: url(chrome://browser/skin/notification-icons.svg#camera);
}
.camera-icon.blocked {
- list-style-image: url(chrome://browser/skin/permissions.svg#camera-blocked);
+ list-style-image: url(chrome://browser/skin/notification-icons.svg#camera-blocked);
}
/* The first selector is used by socialchat.xml (bug 1275558). */
.webRTC-sharingMicrophone-notification-icon,
.microphone-icon {
- list-style-image: url(chrome://browser/skin/permissions.svg#microphone);
+ list-style-image: url(chrome://browser/skin/notification-icons.svg#microphone);
}
.microphone-icon.blocked {
- list-style-image: url(chrome://browser/skin/permissions.svg#microphone-blocked);
+ list-style-image: url(chrome://browser/skin/notification-icons.svg#microphone-blocked);
}
.popup-notification-icon[popupid="webRTC-shareMicrophone"],
.popup-notification-icon[popupid="webRTC-sharingMicrophone"] {
- list-style-image: url(chrome://browser/skin/permissions.svg#microphone-detailed);
+ list-style-image: url(chrome://browser/skin/notification-icons.svg#microphone-detailed);
}
.popup-notification-icon[popupid="webRTC-shareScreen"],
.popup-notification-icon[popupid="webRTC-sharingScreen"],
.screen-icon {
- list-style-image: url(chrome://browser/skin/permissions.svg#screen);
+ list-style-image: url(chrome://browser/skin/notification-icons.svg#screen);
}
.screen-icon.blocked {
- list-style-image: url(chrome://browser/skin/permissions.svg#screen-blocked);
+ list-style-image: url(chrome://browser/skin/notification-icons.svg#screen-blocked);
}
.popup-notification-icon[popupid="pointerLock"],
.pointerLock-icon {
- list-style-image: url(chrome://browser/skin/permissions.svg#pointerLock);
+ list-style-image: url(chrome://browser/skin/notification-icons.svg#pointerLock);
}
.pointerLock-icon.blocked {
- list-style-image: url(chrome://browser/skin/permissions.svg#pointerLock-blocked);
+ list-style-image: url(chrome://browser/skin/notification-icons.svg#pointerLock-blocked);
}
/* This icon has a block sign in it, so we don't need a blocked version. */
.popup-icon {
- list-style-image: url("chrome://browser/skin/permissions.svg#popup");
+ list-style-image: url("chrome://browser/skin/notification-icons.svg#popup");
}
/* EME */
.popup-notification-icon[popupid="drmContentPlaying"],
.drm-icon {
list-style-image: url("chrome://browser/skin/drm-icon.svg#chains");
}
@@ -258,51 +264,24 @@
.popup-notification-icon[popupid*="offline-app-requested"],
.popup-notification-icon[popupid="offline-app-usage"] {
list-style-image: url(chrome://global/skin/icons/question-64.png);
}
/* PLUGINS */
.plugin-icon {
- list-style-image: url(chrome://browser/skin/notification-pluginNormal.png);
-}
-
-.plugin-icon.plugin-hidden {
- list-style-image: url(chrome://browser/skin/notification-pluginAlert.png);
+ list-style-image: url(chrome://browser/skin/notification-icons.svg#plugin);
}
.plugin-icon.plugin-blocked {
- list-style-image: url(chrome://browser/skin/notification-pluginBlocked.png);
-}
-
-.plugin-icon {
- -moz-image-region: rect(0, 16px, 16px, 0);
+ list-style-image: url(chrome://browser/skin/notification-icons.svg#plugin-blocked);
+ fill: #d92215;
}
-%ifdef XP_MACOSX
-@media (min-resolution: 1.1dppx) {
- .plugin-icon {
- list-style-image: url(chrome://browser/skin/notification-pluginNormal@2x.png);
- }
-
- .plugin-icon.plugin-hidden {
- list-style-image: url(chrome://browser/skin/notification-pluginAlert@2x.png);
- }
-
- .plugin-icon.plugin-blocked {
- list-style-image: url(chrome://browser/skin/notification-pluginBlocked@2x.png);
- }
-
- .plugin-icon {
- -moz-image-region: rect(0, 32px, 32px, 0);
- }
-}
-%endif
-
#notification-popup-box[hidden] {
/* Override display:none to make the pluginBlockedNotification animation work
when showing the notification repeatedly. */
display: -moz-box;
visibility: collapse;
}
#plugins-notification-icon.plugin-blocked[showing] {
rename from browser/themes/shared/permissions.svg
rename to browser/themes/shared/notification-icons.svg
--- a/browser/themes/shared/permissions.svg
+++ b/browser/themes/shared/notification-icons.svg
@@ -28,16 +28,17 @@
<path id="geo-osx-icon" d="m 0,16 16,0 0,16 12,-28 z" />
<path id="geo-windows-icon" d="m 2,14 0,4 2,0 a 12,12 0 0 0 10,10 l 0,2 4,0 0,-2 a 12,12 0 0 0 10,-10 l 2,0 0,-4 -2,0 a 12,12 0 0 0 -10,-10 l 0,-2 -4,0 0,2 a 12,12 0 0 0 -10,10 z m 4,1.9 a 10,10 0 1 1 0,0.2 z m 4,0 a 6,6 0 1 1 0,0.2 z" />
<path id="geo-windows-detailed-icon" d="m 2,14.5 0,3 2,0.5 a 12,12 0 0 0 10,10 l 0.5,2 3,0 0.5,-2 a 12,12 0 0 0 10,-10 l 2,-0.5 0,-3 -2,-0.5 a 12,12 0 0 0 -10,-10 l -0.5,-2 -3,0 -0.5,2 a 12,12 0 0 0 -10,10 z m 4,1.4 a 10,10 0 1 1 0,0.2 z m 3,0 a 7,7 0 1 1 0,0.2 z" />
<path id="indexedDB-icon" d="m 2,24 a 4,4 0 0 0 4,4 l 2,0 0,-4 -2,0 0,-16 20,0 0,16 -2,0 0,4 2,0 a 4,4 0 0 0 4,-4 l 0,-16 a 4,4 0 0 0 -4,-4 l -20,0 a 4,4 0 0 0 -4,4 z m 8,-2 6,7 6,-7 -4,0 0,-8 -4,0 0,8 z" />
<path id="login-icon" d="m 2,26 0,4 6,0 0,-2 2,0 0,-2 1,0 0,-1 2,0 0,-3 2,0 2.5,-2.5 1.5,1.5 3,-3 a 8,8 0 1 0 -8,-8 l -3,3 2,2 z m 20,-18.1 a 2,2 0 1 1 0,0.2 z" />
<path id="login-detailed-icon" d="m 1,27 0,3.5 a 0.5,0.5 0 0 0 0.5,0.5 l 5,0 a 0.5,0.5 0 0 0 0.5,-0.5 l 0,-1.5 1.5,0 a 0.5,0.5 0 0 0 0.5,-0.5 l 0,-1.5 1,0 a 0.5,0.5 0 0 0 0.5,-0.5 l 0,-1 1,0 a 0.5,0.5 0 0 0 0.5,-0.5 l 0,-2 2,0 2.5,-2.5 q 0.5,-0.5 1,0 l 1,1 c 0.5,0.5 1,0.5 1.5,-0.5 l 1,-2 a 9,9 0 1 0 -8,-8 l -2,1 c -1,0.5 -1,1 -0.5,1.5 l 1.5,1.5 q 0.5,0.5 0,1 z m 21,-19.1 a 2,2 0 1 1 0,0.2 z" />
<path id="microphone-icon" d="m 8,14 0,4 a 8,8 0 0 0 6,7.7 l 0,2.3 -2,0 a 2,2 0 0 0 -2,2 l 12,0 a 2,2 0 0 0 -2,-2 l -2,0 0,-2.3 a 8,8 0 0 0 6,-7.7 l 0,-4 -2,0 0,4 a 6,6 0 0 1 -12,0 l 0,-4 z m 4,4 a 4,4 0 0 0 8,0 l 0,-12 a 4,4 0 0 0 -8,0 z" />
<path id="microphone-detailed-icon" d="m 8,18 a 8,8 0 0 0 6,7.7 l 0,2.3 -1,0 a 3,2 0 0 0 -3,2 l 12,0 a 3,2 0 0 0 -3,-2 l -1,0 0,-2.3 a 8,8 0 0 0 6,-7.7 l 0,-4 a 1,1 0 0 0 -2,0 l 0,4 a 6,6 0 0 1 -12,0 l 0,-4 a 1,1 0 0 0 -2,0 z m 4,0 a 4,4 0 0 0 8,0 l 0,-12 a 4,4 0 0 0 -8,0 z" />
+ <path id="plugin-icon" d="m 2,26 a 2,2 0 0 0 2,2 l 24,0 a 2,2 0 0 0 2,-2 l 0,-16 a 2,2 0 0 0 -2,-2 l -24,0 a 2,2 0 0 0 -2,2 z m 2,-20 10,0 0,-2 a 2,2 0 0 0 -2,-2 l -6,0 a 2,2 0 0 0 -2,2 z m 14,0 10,0 0,-2 a 2,2 0 0 0 -2,-2 l -6,0 a 2,2 0 0 0 -2,2 z" />
<path id="pointerLock-icon" d="m 8,24 6,-5 5,10 4,-2 -5,-10 7,-1 -17,-14 z" />
<path id="popup-icon" d="m 2,24 a 4,4 0 0 0 4,4 l 8,0 a 10,10 0 0 1 -2,-4 l -4,0 a 2,2 0 0 1 -2,-2 l 0,-12 18,0 0,2 a 10,10 0 0 1 4,2 l 0,-8 a 4,4 0 0 0 -4,-4 l -18,0 a 4,4 0 0 0 -4,4 z m 12,-2.1 a 8,8 0 1 1 0,0.2 m 10.7,-4.3 a 5,5 0 0 0 -6.9,6.9 z m -5.4,8.4 a 5,5 0 0 0 6.9,-6.9 z" />
<path id="screen-icon" d="m 2,18 a 2,2 0 0 0 2,2 l 2,0 0,-6 a 4,4 0 0 1 4,-4 l 14,0 0,-6 a 2,2 0 0 0 -2,-2 l -18,0 a 2,2 0 0 0 -2,2 z m 6,10 a 2,2 0 0 0 2,2 l 18,0 a 2,2 0 0 0 2,-2 l 0,-14 a 2,2 0 0 0 -2,-2 l -18,0 a 2,2 0 0 0 -2,2 z" />
<clipPath id="clip">
<path d="m 0,0 0,31 31,-31 z m 6,32 26,0 0,-26 z"/>
</clipPath>
</defs>
@@ -56,16 +57,18 @@
<use id="geo-windows-detailed" xlink:href="#geo-windows-detailed-icon" />
<use id="indexedDB" xlink:href="#indexedDB-icon" />
<use id="indexedDB-blocked" class="blocked" xlink:href="#indexedDB-icon" />
<use id="login" xlink:href="#login-icon" />
<use id="login-detailed" xlink:href="#login-detailed-icon" />
<use id="microphone" xlink:href="#microphone-icon" />
<use id="microphone-blocked" class="blocked" xlink:href="#microphone-icon" />
<use id="microphone-detailed" xlink:href="#microphone-detailed-icon" />
+ <use id="plugin" xlink:href="#plugin-icon" />
+ <use id="plugin-blocked" class="blocked" xlink:href="#plugin-icon" />
<use id="pointerLock" xlink:href="#pointerLock-icon" />
<use id="pointerLock-blocked" class="blocked" xlink:href="#pointerLock-icon" />
<use id="popup" xlink:href="#popup-icon" />
<use id="screen" xlink:href="#screen-icon" />
<use id="screen-blocked" class="blocked" xlink:href="#screen-icon" />
<path id="strikeout" d="m 2,28 2,2 26,-26 -2,-2 z"/>
</svg>
deleted file mode 100644
index 7492fdd8670488b9b510d913424dcc46f5e1ab57..0000000000000000000000000000000000000000
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100644
index f3359969b6f30fb090f424961024a72435e01b49..0000000000000000000000000000000000000000
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100644
index e2e9489004eb7cd0661d6ebf8d3a7be4869fddca..0000000000000000000000000000000000000000
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100644
index 5126be01f08bfd06884278f6527e0e43f9029534..0000000000000000000000000000000000000000
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100644
index 979e92b7f51d98aebc1ce4b6255954e6ee8a12dd..0000000000000000000000000000000000000000
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100644
index c081bbb475f9b60070fcae3817967da05f1b533b..0000000000000000000000000000000000000000
GIT binary patch
literal 0
Hc$@<O00001
--- a/browser/themes/shared/tabs.inc.css
+++ b/browser/themes/shared/tabs.inc.css
@@ -109,25 +109,25 @@
.tab-sharing-icon-overlay {
/* 16px of the icon + 6px of margin-inline-end of .tab-icon-image */
margin-inline-start: -22px;
position: relative;
}
.tab-sharing-icon-overlay[sharing="camera"] {
- list-style-image: url("chrome://browser/skin/permissions.svg#camera");
+ list-style-image: url("chrome://browser/skin/notification-icons.svg#camera");
}
.tab-sharing-icon-overlay[sharing="microphone"] {
- list-style-image: url("chrome://browser/skin/permissions.svg#microphone");
+ list-style-image: url("chrome://browser/skin/notification-icons.svg#microphone");
}
.tab-sharing-icon-overlay[sharing="screen"] {
- list-style-image: url("chrome://browser/skin/permissions.svg#screen");
+ list-style-image: url("chrome://browser/skin/notification-icons.svg#screen");
}
.tab-sharing-icon-overlay[sharing] {
filter: url("chrome://browser/skin/filters.svg#fill");
fill: rgb(224, 41, 29);
}
.tab-icon-overlay {
--- a/browser/themes/windows/browser.css
+++ b/browser/themes/windows/browser.css
@@ -1018,76 +1018,144 @@ toolbar[brighttext] .toolbarbutton-1 > .
* padding (2 * 2px) + border (2 * 1px), but as a minimum because otherwise
* increase in text sizes would break things...
*/
min-height: 24px;
}
/* ::::: fullscreen window controls ::::: */
-#window-controls {
- margin-inline-start: 4px;
-}
-
#minimize-button,
#restore-button,
#close-button {
- list-style-image: url("chrome://global/skin/icons/windowControls.png");
- padding: 0;
+ -moz-appearance: none;
+ border: none;
+ margin: 0 !important;
+ padding: 6px 12px;
}
#minimize-button {
- -moz-image-region: rect(0, 16px, 16px, 0);
+ list-style-image: url(chrome://browser/skin/caption-buttons.svg#minimize);
}
-#minimize-button:hover {
- -moz-image-region: rect(16px, 16px, 32px, 0);
+
+#restore-button {
+ list-style-image: url(chrome://browser/skin/caption-buttons.svg#restore);
}
-#minimize-button:hover:active {
- -moz-image-region: rect(32px, 16px, 48px, 0);
+
+#minimize-button:hover,
+#restore-button:hover {
+ background-color: hsla(0, 0%, 0%, .12);
}
-#restore-button {
- -moz-image-region: rect(0, 32px, 16px, 16px);
-}
-#restore-button:hover {
- -moz-image-region: rect(16px, 32px, 32px, 16px);
-}
+
+#minimize-button:hover:active,
#restore-button:hover:active {
- -moz-image-region: rect(32px, 32px, 48px, 16px);
+ background-color: hsla(0, 0%, 0%, .22);
}
+
#close-button {
- -moz-image-region: rect(0, 48px, 16px, 32px);
- -moz-appearance: none;
- border-style: none;
- margin: 2px;
+ list-style-image: url(chrome://browser/skin/caption-buttons.svg#close);
+}
+
+#close-button:hover {
+ background-color: hsl(355, 86%, 49%);
+ list-style-image: url(chrome://browser/skin/caption-buttons.svg#close-white);
+}
+
+#close-button:hover:active {
+ background-color: hsl(355, 82%, 69%);
+}
+
+toolbar[brighttext] #minimize-button {
+ list-style-image: url(chrome://browser/skin/caption-buttons.svg#minimize-white);
+}
+
+toolbar[brighttext] #restore-button {
+ list-style-image: url(chrome://browser/skin/caption-buttons.svg#restore-white);
+}
+
+toolbar[brighttext] #close-button {
+ list-style-image: url(chrome://browser/skin/caption-buttons.svg#close-white);
}
-#close-button:hover {
- -moz-image-region: rect(16px, 48px, 32px, 32px);
+
+@media (-moz-os-version: windows-xp),
+ (-moz-os-version: windows-vista),
+ (-moz-os-version: windows-win7) {
+ #window-controls {
+ margin-inline-start: 4px;
+ }
+
+ #minimize-button,
+ #restore-button,
+ #close-button {
+ list-style-image: url("chrome://global/skin/icons/windowControls.png");
+ padding: 0;
+ }
+
+ #minimize-button {
+ -moz-image-region: rect(0, 16px, 16px, 0);
+ }
+
+ #minimize-button:hover {
+ -moz-image-region: rect(16px, 16px, 32px, 0);
+ }
+
+ #minimize-button:hover:active {
+ -moz-image-region: rect(32px, 16px, 48px, 0);
+ }
+
+ #restore-button {
+ -moz-image-region: rect(0, 32px, 16px, 16px);
+ }
+
+ #restore-button:hover {
+ -moz-image-region: rect(16px, 32px, 32px, 16px);
+ }
+
+ #restore-button:hover:active {
+ -moz-image-region: rect(32px, 32px, 48px, 16px);
+ }
+
+ #close-button {
+ -moz-image-region: rect(0, 48px, 16px, 32px);
+ -moz-appearance: none;
+ border-style: none;
+ margin: 2px;
+ }
+
+ #close-button:hover {
+ -moz-image-region: rect(16px, 48px, 32px, 32px);
+ }
+
+ #close-button:hover:active {
+ -moz-image-region: rect(32px, 48px, 48px, 32px);
+ }
}
-#close-button:hover:active {
- -moz-image-region: rect(32px, 48px, 48px, 32px);
-}
-
-@media not all and (-moz-os-version: windows-xp) {
+
+@media (-moz-os-version: windows-vista),
+ (-moz-os-version: windows-win7) {
#window-controls {
-moz-box-align: start;
}
#minimize-button,
#restore-button,
#close-button {
-moz-appearance: none;
border-style: none;
margin: 0;
}
+
#close-button {
-moz-image-region: rect(0, 49px, 16px, 32px);
}
+
#close-button:hover {
-moz-image-region: rect(16px, 49px, 32px, 32px);
}
+
#close-button:hover:active {
-moz-image-region: rect(32px, 49px, 48px, 32px);
}
#minimize-button:-moz-locale-dir(rtl),
#restore-button:-moz-locale-dir(rtl),
#close-button:-moz-locale-dir(rtl) {
transform: scaleX(-1);
--- a/build/pgo/server-locations.txt
+++ b/build/pgo/server-locations.txt
@@ -176,16 +176,19 @@ http://itisatracker.org:80
http://trackertest.org:80
https://malware.example.com:443
https://unwanted.example.com:443
https://tracking.example.com:443
https://not-tracking.example.com:443
https://tracking.example.org:443
+# Bug 1281083
+http://bug1281083.example.com:80
+
# Bug 483437, 484111
https://www.bank1.com:443 privileged,cert=escapeattack1
#
# CONNECT for redirproxy results in a 302 redirect to
# test1.example.com
#
https://redirproxy.example.com:443 privileged,redir=test1.example.com
--- a/devtools/client/definitions.js
+++ b/devtools/client/definitions.js
@@ -1,18 +1,16 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
-const {Cc, Ci} = require("chrome");
const Services = require("Services");
-
-loader.lazyGetter(this, "osString", () => Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime).OS);
+const osString = Services.appinfo.OS;
// Panels
loader.lazyGetter(this, "OptionsPanel", () => require("devtools/client/framework/toolbox-options").OptionsPanel);
loader.lazyGetter(this, "InspectorPanel", () => require("devtools/client/inspector/inspector-panel").InspectorPanel);
loader.lazyGetter(this, "WebConsolePanel", () => require("devtools/client/webconsole/panel").WebConsolePanel);
loader.lazyGetter(this, "DebuggerPanel", () => require("devtools/client/debugger/panel").DebuggerPanel);
loader.lazyGetter(this, "StyleEditorPanel", () => require("devtools/client/styleeditor/styleeditor-panel").StyleEditorPanel);
loader.lazyGetter(this, "ShaderEditorPanel", () => require("devtools/client/shadereditor/panel").ShaderEditorPanel);
--- a/devtools/client/eyedropper/eyedropper.js
+++ b/devtools/client/eyedropper/eyedropper.js
@@ -27,20 +27,16 @@ loader.lazyGetter(this, "ioService", fun
return Cc["@mozilla.org/network/io-service;1"]
.getService(Ci.nsIIOService);
});
loader.lazyGetter(this, "DOMUtils", function () {
return Cc["@mozilla.org/inspector/dom-utils;1"].getService(Ci.inIDOMUtils);
});
-loader.lazyGetter(this, "XULRuntime", function () {
- return Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime);
-});
-
loader.lazyGetter(this, "l10n", () => Services.strings
.createBundle("chrome://devtools/locale/eyedropper.properties"));
const EYEDROPPER_URL = "chrome://devtools/content/eyedropper/eyedropper.xul";
const CROSSHAIRS_URL = "chrome://devtools/content/eyedropper/crosshairs.css";
const NOCURSOR_URL = "chrome://devtools/content/eyedropper/nocursor.css";
const ZOOM_PREF = "devtools.eyedropper.zoom";
@@ -112,17 +108,17 @@ function Eyedropper(chromeWindow, opts =
this._onMouseMove = this._onMouseMove.bind(this);
this._onMouseDown = this._onMouseDown.bind(this);
this._onKeyDown = this._onKeyDown.bind(this);
this._onFrameLoaded = this._onFrameLoaded.bind(this);
this._chromeWindow = chromeWindow;
this._chromeDocument = chromeWindow.document;
- this._OS = XULRuntime.OS;
+ this._OS = Services.appinfo.OS;
this._dragging = true;
this.loaded = false;
this._mouseMoveCounter = 0;
this.format = Services.prefs.getCharPref(FORMAT_PREF); // color value format
this.zoom = Services.prefs.getIntPref(ZOOM_PREF); // zoom level - integer
--- a/devtools/client/framework/test/shared-head.js
+++ b/devtools/client/framework/test/shared-head.js
@@ -1,12 +1,13 @@
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
+/* eslint no-unused-vars: [2, {"vars": "local"}] */
"use strict";
// This shared-head.js file is used for multiple mochitest test directories in
// devtools.
// It contains various common helper functions.
const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr, Constructor: CC}
@@ -469,8 +470,22 @@ function waitForContextMenu(popup, butto
* @return {Promise} resolves when the preferences have been updated
*/
function pushPref(preferenceName, value) {
return new Promise(resolve => {
let options = {"set": [[preferenceName, value]]};
SpecialPowers.pushPrefEnv(options, resolve);
});
}
+
+/**
+ * Lookup the provided dotted path ("prop1.subprop2.myProp") in the provided object.
+ *
+ * @param {Object} obj
+ * Object to expand.
+ * @param {String} path
+ * Dotted path to use to expand the object.
+ * @return {?} anything that is found at the provided path in the object.
+ */
+function lookupPath(obj, path) {
+ let segments = path.split(".");
+ return segments.reduce((prev, current) => prev[current], obj);
+}
--- a/devtools/client/framework/toolbox.js
+++ b/devtools/client/framework/toolbox.js
@@ -65,19 +65,16 @@ loader.lazyRequireGetter(this, "system",
"devtools/shared/system");
loader.lazyRequireGetter(this, "getPreferenceFront",
"devtools/shared/fronts/preference", true);
loader.lazyRequireGetter(this, "KeyShortcuts",
"devtools/client/shared/key-shortcuts", true);
loader.lazyRequireGetter(this, "ZoomKeys",
"devtools/client/shared/zoom-keys");
-loader.lazyGetter(this, "osString", () => {
- return Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime).OS;
-});
loader.lazyGetter(this, "registerHarOverlay", () => {
return require("devtools/client/netmonitor/har/toolbox-overlay").register;
});
// White-list buttons that can be toggled to prevent adding prefs for
// addons that have manually inserted toolbarbuttons into DOM.
// (By default, supported target is only local tab)
const ToolboxButtons = exports.ToolboxButtons = [
@@ -490,17 +487,18 @@ Toolbox.prototype = {
get ReactDOM() {
return this.browserRequire("devtools/client/shared/vendor/react-dom");
},
_pingTelemetry: function () {
this._telemetry.toolOpened("toolbox");
this._telemetry.logOncePerBrowserVersion(OS_HISTOGRAM, system.getOSCPU());
- this._telemetry.logOncePerBrowserVersion(OS_IS_64_BITS, system.is64Bit ? 1 : 0);
+ this._telemetry.logOncePerBrowserVersion(OS_IS_64_BITS,
+ Services.appinfo.is64Bit ? 1 : 0);
this._telemetry.logOncePerBrowserVersion(SCREENSIZE_HISTOGRAM, system.getScreenDimensions());
},
/**
* Because our panels are lazy loaded this is a good place to watch for
* "pref-changed" events.
* @param {String} event
* The event type, "pref-changed".
--- a/devtools/client/inspector/computed/computed.js
+++ b/devtools/client/inspector/computed/computed.js
@@ -24,16 +24,18 @@ const {XPCOMUtils} = require("resource:/
const {getCssProperties} = require("devtools/shared/fronts/css-properties");
loader.lazyRequireGetter(this, "overlays",
"devtools/client/inspector/shared/style-inspector-overlays");
loader.lazyRequireGetter(this, "StyleInspectorMenu",
"devtools/client/inspector/shared/style-inspector-menu");
loader.lazyRequireGetter(this, "KeyShortcuts",
"devtools/client/shared/key-shortcuts", true);
+loader.lazyRequireGetter(this, "LayoutView",
+ "devtools/client/inspector/layout/layout", true);
XPCOMUtils.defineLazyModuleGetter(this, "PluralForm",
"resource://gre/modules/PluralForm.jsm");
XPCOMUtils.defineLazyGetter(CssComputedView, "_strings", function () {
return Services.strings.createBundle(
"chrome://devtools-shared/locale/styleinspector.properties");
});
@@ -164,17 +166,16 @@ function CssComputedView(inspector, docu
this._onCopy = this._onCopy.bind(this);
this._onFilterStyles = this._onFilterStyles.bind(this);
this._onClearSearch = this._onClearSearch.bind(this);
this._onIncludeBrowserStyles = this._onIncludeBrowserStyles.bind(this);
this._onFilterTextboxContextMenu =
this._onFilterTextboxContextMenu.bind(this);
let doc = this.styleDocument;
- this.root = doc.getElementById("root");
this.element = doc.getElementById("propertyContainer");
this.searchField = doc.getElementById("computedview-searchbox");
this.searchClearButton = doc.getElementById("computedview-searchinput-clear");
this.includeBrowserStylesCheckbox =
doc.getElementById("browser-style-checkbox");
this.shortcuts = new KeyShortcuts({ window: this.styleWindow });
this._onShortcut = this._onShortcut.bind(this);
@@ -772,17 +773,16 @@ CssComputedView.prototype = {
this.searchField.removeEventListener("input", this._onFilterStyles);
this.searchField.removeEventListener("contextmenu",
this._onFilterTextboxContextMenu);
this.searchClearButton.removeEventListener("click", this._onClearSearch);
this.includeBrowserStylesCheckbox.removeEventListener("input",
this._onIncludeBrowserStyles);
// Nodes used in templating
- this.root = null;
this.element = null;
this.panel = null;
this.searchField = null;
this.searchClearButton = null;
this.includeBrowserStylesCheckbox = null;
// Property views
for (let propView of this.propertyViews) {
@@ -1402,85 +1402,86 @@ SelectorView.prototype = {
});
}
};
function ComputedViewTool(inspector, window) {
this.inspector = inspector;
this.document = window.document;
- this.view = new CssComputedView(this.inspector, this.document,
+ this.computedView = new CssComputedView(this.inspector, this.document,
this.inspector.pageStyle);
+ this.layoutView = new LayoutView(this.inspector, this.document);
this.onSelected = this.onSelected.bind(this);
this.refresh = this.refresh.bind(this);
this.onPanelSelected = this.onPanelSelected.bind(this);
this.onMutations = this.onMutations.bind(this);
this.onResized = this.onResized.bind(this);
this.inspector.selection.on("detached", this.onSelected);
this.inspector.selection.on("new-node-front", this.onSelected);
this.inspector.selection.on("pseudoclass", this.refresh);
this.inspector.sidebar.on("computedview-selected", this.onPanelSelected);
this.inspector.pageStyle.on("stylesheet-updated", this.refresh);
this.inspector.walker.on("mutations", this.onMutations);
this.inspector.walker.on("resize", this.onResized);
- this.view.selectElement(null);
+ this.computedView.selectElement(null);
this.onSelected();
}
ComputedViewTool.prototype = {
isSidebarActive: function () {
- if (!this.view) {
+ if (!this.computedView) {
return false;
}
return this.inspector.sidebar.getCurrentTabID() == "computedview";
},
onSelected: function (event) {
// Ignore the event if the view has been destroyed, or if it's inactive.
// But only if the current selection isn't null. If it's been set to null,
// let the update go through as this is needed to empty the view on
// navigation.
- if (!this.view) {
+ if (!this.computedView) {
return;
}
let isInactive = !this.isSidebarActive() &&
this.inspector.selection.nodeFront;
if (isInactive) {
return;
}
- this.view.setPageStyle(this.inspector.pageStyle);
+ this.computedView.setPageStyle(this.inspector.pageStyle);
if (!this.inspector.selection.isConnected() ||
!this.inspector.selection.isElementNode()) {
- this.view.selectElement(null);
+ this.computedView.selectElement(null);
return;
}
if (!event || event == "new-node-front") {
let done = this.inspector.updating("computed-view");
- this.view.selectElement(this.inspector.selection.nodeFront).then(() => {
+ this.computedView.selectElement(this.inspector.selection.nodeFront).then(() => {
done();
});
}
},
refresh: function () {
if (this.isSidebarActive()) {
- this.view.refreshPanel();
+ this.computedView.refreshPanel();
}
},
onPanelSelected: function () {
- if (this.inspector.selection.nodeFront === this.view._viewedElement) {
+ if (this.inspector.selection.nodeFront === this.computedView._viewedElement) {
this.refresh();
} else {
this.onSelected();
}
},
/**
* When markup mutations occur, if an attribute of the selected node changes,
@@ -1511,17 +1512,18 @@ ComputedViewTool.prototype = {
this.inspector.selection.off("pseudoclass", this.refresh);
this.inspector.selection.off("new-node-front", this.onSelected);
this.inspector.selection.off("detached", this.onSelected);
this.inspector.sidebar.off("computedview-selected", this.onPanelSelected);
if (this.inspector.pageStyle) {
this.inspector.pageStyle.off("stylesheet-updated", this.refresh);
}
- this.view.destroy();
+ this.computedView.destroy();
+ this.layoutView.destroy();
- this.view = this.document = this.inspector = null;
+ this.computedView = this.layoutView = this.document = this.inspector = null;
}
};
exports.CssComputedView = CssComputedView;
exports.ComputedViewTool = ComputedViewTool;
exports.PropertyView = PropertyView;
--- a/devtools/client/inspector/computed/test/browser_computed_cycle_color.js
+++ b/devtools/client/inspector/computed/test/browser_computed_cycle_color.js
@@ -54,16 +54,17 @@ function* checkColorCycling(container, v
for (let test of tests) {
yield checkSwatchShiftClick(container, win, test.value, test.comment);
}
}
function* checkSwatchShiftClick(container, win, expectedValue, comment) {
let swatch = container.querySelector(".computedview-colorswatch");
let valueNode = container.querySelector(".computedview-color");
+ swatch.scrollIntoView();
let onUnitChange = swatch.once("unit-change");
EventUtils.synthesizeMouseAtCenter(swatch, {
type: "mousedown",
shiftKey: true
}, win);
yield onUnitChange;
is(valueNode.textContent, expectedValue, comment);
--- a/devtools/client/inspector/computed/test/browser_computed_keybindings_01.js
+++ b/devtools/client/inspector/computed/test/browser_computed_keybindings_01.js
@@ -20,16 +20,17 @@ add_task(function* () {
let {inspector, view} = yield openComputedView();
yield selectNode(".matches", inspector);
let propView = getFirstVisiblePropertyView(view);
let rulesTable = propView.matchedSelectorsContainer;
let matchedExpander = propView.element;
info("Focusing the property");
+ matchedExpander.scrollIntoView();
let onMatchedExpanderFocus = once(matchedExpander, "focus", true);
EventUtils.synthesizeMouseAtCenter(matchedExpander, {}, view.styleWindow);
yield onMatchedExpanderFocus;
yield checkToggleKeyBinding(view.styleWindow, "VK_SPACE", rulesTable,
inspector);
yield checkToggleKeyBinding(view.styleWindow, "VK_RETURN", rulesTable,
inspector);
--- a/devtools/client/inspector/computed/test/browser_computed_matched-selectors-toggle.js
+++ b/devtools/client/inspector/computed/test/browser_computed_matched-selectors-toggle.js
@@ -25,17 +25,17 @@ add_task(function* () {
yield testExpandOnDblClick(view, inspector);
yield testCollapseOnDblClick(view, inspector);
});
function* testExpandOnTwistyClick({styleDocument, styleWindow}, inspector) {
info("Testing that a property expands on twisty click");
info("Getting twisty element");
- let twisty = styleDocument.querySelector(".expandable");
+ let twisty = styleDocument.querySelector("#propertyContainer .expandable");
ok(twisty, "Twisty found");
let onExpand = inspector.once("computed-view-property-expanded");
info("Clicking on the twisty element");
twisty.click();
yield onExpand;
@@ -44,17 +44,17 @@ function* testExpandOnTwistyClick({style
ok(div.childNodes.length > 0,
"Matched selectors are expanded on twisty click");
}
function* testCollapseOnTwistyClick({styleDocument, styleWindow}, inspector) {
info("Testing that a property collapses on twisty click");
info("Getting twisty element");
- let twisty = styleDocument.querySelector(".expandable");
+ let twisty = styleDocument.querySelector("#propertyContainer .expandable");
ok(twisty, "Twisty found");
let onCollapse = inspector.once("computed-view-property-collapsed");
info("Clicking on the twisty element");
twisty.click();
yield onCollapse;
@@ -66,16 +66,18 @@ function* testCollapseOnTwistyClick({sty
function* testExpandOnDblClick({styleDocument, styleWindow}, inspector) {
info("Testing that a property expands on container dbl-click");
info("Getting computed property container");
let container = styleDocument.querySelector(".property-view");
ok(container, "Container found");
+ container.scrollIntoView();
+
let onExpand = inspector.once("computed-view-property-expanded");
info("Dbl-clicking on the container");
EventUtils.synthesizeMouseAtCenter(container, {clickCount: 2}, styleWindow);
yield onExpand;
// Expanded means the matchedselectors div is not empty
let div = styleDocument.querySelector(".property-content .matchedselectors");
--- a/devtools/client/inspector/computed/test/browser_computed_select-and-copy-styles.js
+++ b/devtools/client/inspector/computed/test/browser_computed_select-and-copy-styles.js
@@ -1,19 +1,17 @@
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Tests that properties can be selected and copied from the computed view.
-XPCOMUtils.defineLazyGetter(this, "osString", function () {
- return Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime).OS;
-});
+const osString = Services.appinfo.OS;
const TEST_URI = `
<style type="text/css">
span {
font-variant-caps: small-caps;
color: #000000;
}
.nomatches {
--- a/devtools/client/inspector/computed/test/head.js
+++ b/devtools/client/inspector/computed/test/head.js
@@ -127,17 +127,17 @@ function getComputedViewPropertyValue(vi
* The instance of the computed view panel
* @param {Number} index
* The index of the property to be expanded
* @return a promise that resolves when the property has been expanded, or
* rejects if the property was not found
*/
function expandComputedViewPropertyByIndex(view, index) {
info("Expanding property " + index + " in the computed view");
- let expandos = view.styleDocument.querySelectorAll(".expandable");
+ let expandos = view.styleDocument.querySelectorAll("#propertyContainer .expandable");
if (!expandos.length || !expandos[index]) {
return promise.reject();
}
let onExpand = view.inspector.once("computed-view-property-expanded");
expandos[index].click();
return onExpand;
}
--- a/devtools/client/inspector/inspector-panel.js
+++ b/devtools/client/inspector/inspector-panel.js
@@ -26,17 +26,16 @@ const MenuItem = require("devtools/clien
loader.lazyRequireGetter(this, "CSS", "CSS");
loader.lazyRequireGetter(this, "CommandUtils", "devtools/client/shared/developer-toolbar", true);
loader.lazyRequireGetter(this, "ComputedViewTool", "devtools/client/inspector/computed/computed", true);
loader.lazyRequireGetter(this, "FontInspector", "devtools/client/inspector/fonts/fonts", true);
loader.lazyRequireGetter(this, "HTMLBreadcrumbs", "devtools/client/inspector/breadcrumbs", true);
loader.lazyRequireGetter(this, "InspectorSearch", "devtools/client/inspector/inspector-search", true);
-loader.lazyRequireGetter(this, "LayoutView", "devtools/client/inspector/layout/layout", true);
loader.lazyRequireGetter(this, "MarkupView", "devtools/client/inspector/markup/markup", true);
loader.lazyRequireGetter(this, "RuleViewTool", "devtools/client/inspector/rules/rules", true);
loader.lazyRequireGetter(this, "ToolSidebar", "devtools/client/inspector/toolsidebar", true);
loader.lazyRequireGetter(this, "ViewHelpers", "devtools/client/shared/widgets/view-helpers", true);
loader.lazyGetter(this, "strings", () => {
return Services.strings.createBundle("chrome://devtools/locale/inspector.properties");
});
@@ -418,30 +417,24 @@ InspectorPanel.prototype = {
strings.GetStringFromName("inspector.sidebar.ruleViewTitle"),
defaultTab == "ruleview");
this.sidebar.addExistingTab(
"computedview",
strings.GetStringFromName("inspector.sidebar.computedViewTitle"),
defaultTab == "computedview");
- this.sidebar.addExistingTab(
- "layoutview",
- strings.GetStringFromName("inspector.sidebar.layoutViewTitle"),
- defaultTab == "layoutview");
-
this._setDefaultSidebar = (event, toolId) => {
Services.prefs.setCharPref("devtools.inspector.activeSidebar", toolId);
};
this.sidebar.on("select", this._setDefaultSidebar);
this.ruleview = new RuleViewTool(this, this.panelWin);
this.computedview = new ComputedViewTool(this, this.panelWin);
- this.layoutview = new LayoutView(this, this.panelWin);
if (this.target.form.animationsActor) {
this.sidebar.addFrameTab(
"animationinspector",
strings.GetStringFromName("inspector.sidebar.animationInspectorTitle"),
"chrome://devtools/content/animationinspector/animation-inspector.xhtml",
defaultTab == "animationinspector");
}
@@ -747,20 +740,16 @@ InspectorPanel.prototype = {
if (this.computedview) {
this.computedview.destroy();
}
if (this.fontInspector) {
this.fontInspector.destroy();
}
- if (this.layoutview) {
- this.layoutview.destroy();
- }
-
let cssPropertiesDestroyer = this._cssPropertiesLoaded.then(({front}) => {
if (front) {
front.destroy();
}
});
this.sidebar.off("select", this._setDefaultSidebar);
let sidebarDestroyer = this.sidebar.destroy();
--- a/devtools/client/inspector/inspector.xul
+++ b/devtools/client/inspector/inspector.xul
@@ -102,69 +102,74 @@
</html:div>
<html:label id="browser-style-checkbox-label" for="browser-style-checkbox">
<html:input id="browser-style-checkbox"
type="checkbox"
class="includebrowserstyles"
label="&browserStylesLabel;"/>&browserStylesLabel;</html:label>
</html:div>
- <html:div id="propertyContainer">
- </html:div>
-
- <html:div id="computedview-no-results" hidden="">
- &noPropertiesFound;
- </html:div>
- </html:div>
+ <html:div id="computedview-container">
+ <html:div id="layout-wrapper" class="theme-separator" tabindex="0">
+ <html:div id="layout-header">
+ <html:div id="layout-expander" class="expander theme-twisty expandable" open=""></html:div>
+ <html:span>&layoutViewTitle;</html:span>
+ <html:button class="devtools-button" id="layout-geometry-editor" title="&geometry.button.tooltip;"></html:button>
+ </html:div>
- <html:div id="sidebar-panel-layoutview" class="devtools-monospace theme-sidebar inspector-tabpanel">
- <html:div id="layout-wrapper">
- <html:div id="layout-container">
- <html:p id="layout-header">
- <html:span id="layout-element-size"></html:span>
- <html:section id="layout-position-group">
- <html:button class="devtools-button" id="layout-geometry-editor" title="&geometry.button.tooltip;"></html:button>
- <html:span id="layout-element-position"></html:span>
- </html:section>
- </html:p>
-
- <html:div id="layout-main">
- <html:span class="layout-legend" data-box="margin" title="&margin.tooltip;">&margin.tooltip;</html:span>
- <html:div id="layout-margins" data-box="margin" title="&margin.tooltip;">
- <html:span class="layout-legend" data-box="border" title="&border.tooltip;">&border.tooltip;</html:span>
- <html:div id="layout-borders" data-box="border" title="&border.tooltip;">
- <html:span class="layout-legend" data-box="padding" title="&padding.tooltip;">&padding.tooltip;</html:span>
- <html:div id="layout-padding" data-box="padding" title="&padding.tooltip;">
- <html:div id="layout-content" data-box="content" title="&content.tooltip;">
+ <html:div id="layout-container">
+ <html:div id="layout-main">
+ <html:span class="layout-legend" data-box="margin" title="&margin.tooltip;">&margin.tooltip;</html:span>
+ <html:div id="layout-margins" data-box="margin" title="&margin.tooltip;">
+ <html:span class="layout-legend" data-box="border" title="&border.tooltip;">&border.tooltip;</html:span>
+ <html:div id="layout-borders" data-box="border" title="&border.tooltip;">
+ <html:span class="layout-legend" data-box="padding" title="&padding.tooltip;">&padding.tooltip;</html:span>
+ <html:div id="layout-padding" data-box="padding" title="&padding.tooltip;">
+ <html:div id="layout-content" data-box="content" title="&content.tooltip;">
+ </html:div>
</html:div>
</html:div>
</html:div>
+
+ <html:p class="layout-margin layout-top"><html:span data-box="margin" class="layout-editable" title="margin-top"></html:span></html:p>
+ <html:p class="layout-margin layout-right"><html:span data-box="margin" class="layout-editable" title="margin-right"></html:span></html:p>
+ <html:p class="layout-margin layout-bottom"><html:span data-box="margin" class="layout-editable" title="margin-bottom"></html:span></html:p>
+ <html:p class="layout-margin layout-left"><html:span data-box="margin" class="layout-editable" title="margin-left"></html:span></html:p>
+
+ <html:p class="layout-border layout-top"><html:span data-box="border" class="layout-editable" title="border-top"></html:span></html:p>
+ <html:p class="layout-border layout-right"><html:span data-box="border" class="layout-editable" title="border-right"></html:span></html:p>
+ <html:p class="layout-border layout-bottom"><html:span data-box="border" class="layout-editable" title="border-bottom"></html:span></html:p>
+ <html:p class="layout-border layout-left"><html:span data-box="border" class="layout-editable" title="border-left"></html:span></html:p>
+
+ <html:p class="layout-padding layout-top"><html:span data-box="padding" class="layout-editable" title="padding-top"></html:span></html:p>
+ <html:p class="layout-padding layout-right"><html:span data-box="padding" class="layout-editable" title="padding-right"></html:span></html:p>
+ <html:p class="layout-padding layout-bottom"><html:span data-box="padding" class="layout-editable" title="padding-bottom"></html:span></html:p>
+ <html:p class="layout-padding layout-left"><html:span data-box="padding" class="layout-editable" title="padding-left"></html:span></html:p>
+
+ <html:p class="layout-size"><html:span data-box="content" title="&content.tooltip;"></html:span></html:p>
</html:div>
- <html:p class="layout-border layout-top"><html:span data-box="border" class="layout-editable" title="border-top"></html:span></html:p>
- <html:p class="layout-border layout-right"><html:span data-box="border" class="layout-editable" title="border-right"></html:span></html:p>
- <html:p class="layout-border layout-bottom"><html:span data-box="border" class="layout-editable" title="border-bottom"></html:span></html:p>
- <html:p class="layout-border layout-left"><html:span data-box="border" class="layout-editable" title="border-left"></html:span></html:p>
-
- <html:p class="layout-margin layout-top"><html:span data-box="margin" class="layout-editable" title="margin-top"></html:span></html:p>
- <html:p class="layout-margin layout-right"><html:span data-box="margin" class="layout-editable" title="margin-right"></html:span></html:p>
- <html:p class="layout-margin layout-bottom"><html:span data-box="margin" class="layout-editable" title="margin-bottom"></html:span></html:p>
- <html:p class="layout-margin layout-left"><html:span data-box="margin" class="layout-editable" title="margin-left"></html:span></html:p>
+ <html:div id="layout-info">
+ <html:span id="layout-element-size"></html:span>
+ <html:section id="layout-position-group">
+ <html:span id="layout-element-position"></html:span>
+ </html:section>
+ </html:div>
- <html:p class="layout-padding layout-top"><html:span data-box="padding" class="layout-editable" title="padding-top"></html:span></html:p>
- <html:p class="layout-padding layout-right"><html:span data-box="padding" class="layout-editable" title="padding-right"></html:span></html:p>
- <html:p class="layout-padding layout-bottom"><html:span data-box="padding" class="layout-editable" title="padding-bottom"></html:span></html:p>
- <html:p class="layout-padding layout-left"><html:span data-box="padding" class="layout-editable" title="padding-left"></html:span></html:p>
+ <html:div style="display: none">
+ <html:p id="layout-dummy"></html:p>
+ </html:div>
+ </html:div>
+ </html:div>
- <html:p class="layout-size"><html:span data-box="content" title="&content.tooltip;"></html:span></html:p>
- </html:div>
+ <html:div id="propertyContainer" class="theme-separator" tabindex="0">
+ </html:div>
- <html:div style="display: none">
- <html:p id="layout-dummy"></html:p>
- </html:div>
+ <html:div id="computedview-no-results" hidden="">
+ &noPropertiesFound;
</html:div>
</html:div>
</html:div>
<html:div id="sidebar-panel-fontinspector" class="devtools-monospace theme-sidebar inspector-tabpanel">
<html:div class="devtools-toolbar">
<html:div class="devtools-searchbox">
<html:input id="font-preview-text-input"
--- a/devtools/client/inspector/layout/layout.js
+++ b/devtools/client/inspector/layout/layout.js
@@ -63,17 +63,17 @@ EditingSession.prototype = {
* no style rules affect the property.
*
* @param property The name of the property as a string
*/
getProperty: function (property) {
// Create a hidden element for getPropertyFromRule to use
let div = this._doc.createElement("div");
div.setAttribute("style", "display: none");
- this._doc.getElementById("sidebar-panel-layoutview").appendChild(div);
+ this._doc.getElementById("sidebar-panel-computedview").appendChild(div);
this._element = this._doc.createElement("p");
div.appendChild(this._element);
// As the rules are in order of priority we can just iterate until we find
// the first that defines a value for the property and return that.
for (let rule of this._rules) {
let value = this.getPropertyFromRule(rule, property);
if (value !== "") {
@@ -178,43 +178,52 @@ EditingSession.prototype = {
this._doc = null;
this._rules = null;
this._modifications.clear();
}
};
/**
* The layout-view panel
- * @param {InspectorPanel} inspector An instance of the inspector-panel
- * currently loaded in the toolbox
- * @param {Window} win The window containing the panel
+ * @param {InspectorPanel} inspector
+ * An instance of the inspector-panel currently loaded in the toolbox
+ * @param {Document} document
+ * The document that will contain the layout view.
*/
-function LayoutView(inspector, win) {
+function LayoutView(inspector, document) {
this.inspector = inspector;
- this.doc = win.document;
+ this.doc = document;
+ this.wrapper = this.doc.getElementById("layout-wrapper");
+ this.container = this.doc.getElementById("layout-container");
+ this.expander = this.doc.getElementById("layout-expander");
this.sizeLabel = this.doc.querySelector(".layout-size > span");
this.sizeHeadingLabel = this.doc.getElementById("layout-element-size");
this._geometryEditorHighlighter = null;
this.init();
}
LayoutView.prototype = {
init: function () {
this.update = this.update.bind(this);
this.onNewSelection = this.onNewSelection.bind(this);
this.inspector.selection.on("new-node-front", this.onNewSelection);
this.onNewNode = this.onNewNode.bind(this);
- this.inspector.sidebar.on("layoutview-selected", this.onNewNode);
+ this.inspector.sidebar.on("computedview-selected", this.onNewNode);
this.onSidebarSelect = this.onSidebarSelect.bind(this);
this.inspector.sidebar.on("select", this.onSidebarSelect);
+ this.onToggleExpander = this.onToggleExpander.bind(this);
+ this.expander.addEventListener("click", this.onToggleExpander);
+ let header = this.doc.getElementById("layout-header");
+ header.addEventListener("dblclick", this.onToggleExpander);
+
this.onPickerStarted = this.onPickerStarted.bind(this);
this.onMarkupViewLeave = this.onMarkupViewLeave.bind(this);
this.onMarkupViewNodeHover = this.onMarkupViewNodeHover.bind(this);
this.onWillNavigate = this.onWillNavigate.bind(this);
this.initBoxModelHighlighter();
// Store for the different dimensions of the node.
@@ -308,17 +317,16 @@ LayoutView.prototype = {
// Mark document as RTL or LTR:
let chromeReg = Cc["@mozilla.org/chrome/chrome-registry;1"]
.getService(Ci.nsIXULChromeRegistry);
let dir = chromeReg.isLocaleRTL("global");
let container = this.doc.getElementById("layout-container");
container.setAttribute("dir", dir ? "rtl" : "ltr");
let nodeGeometry = this.doc.getElementById("layout-geometry-editor");
-
this.onGeometryButtonClick = this.onGeometryButtonClick.bind(this);
nodeGeometry.addEventListener("click", this.onGeometryButtonClick);
},
initBoxModelHighlighter: function () {
let highlightElts = this.doc.querySelectorAll("#layout-container *[title]");
this.onHighlightMouseOver = this.onHighlightMouseOver.bind(this);
this.onHighlightMouseOut = this.onHighlightMouseOut.bind(this);
@@ -410,17 +418,17 @@ LayoutView.prototype = {
},
/**
* Is the layoutview visible in the sidebar.
* @return {Boolean}
*/
isViewVisible: function () {
return this.inspector &&
- this.inspector.sidebar.getCurrentTabID() == "layoutview";
+ this.inspector.sidebar.getCurrentTabID() == "computedview";
},
/**
* Is the layoutview visible in the sidebar and is the current node valid to
* be displayed in the view.
* @return {Boolean}
*/
isViewVisibleAndNodeValid: function () {
@@ -435,55 +443,62 @@ LayoutView.prototype = {
destroy: function () {
let highlightElts = this.doc.querySelectorAll("#layout-container *[title]");
for (let element of highlightElts) {
element.removeEventListener("mouseover", this.onHighlightMouseOver, true);
element.removeEventListener("mouseout", this.onHighlightMouseOut, true);
}
+ this.expander.removeEventListener("click", this.onToggleExpander);
+ let header = this.doc.getElementById("layout-header");
+ header.removeEventListener("dblclick", this.onToggleExpander);
+
let nodeGeometry = this.doc.getElementById("layout-geometry-editor");
nodeGeometry.removeEventListener("click", this.onGeometryButtonClick);
this.inspector.off("picker-started", this.onPickerStarted);
// Inspector Panel will destroy `markup` object on "will-navigate" event,
// therefore we have to check if it's still available in case LayoutView
// is destroyed immediately after.
if (this.inspector.markup) {
this.inspector.markup.off("leave", this.onMarkupViewLeave);
this.inspector.markup.off("node-hover", this.onMarkupViewNodeHover);
}
- this.inspector.sidebar.off("layoutview-selected", this.onNewNode);
+ this.inspector.sidebar.off("computedview-selected", this.onNewNode);
this.inspector.selection.off("new-node-front", this.onNewSelection);
this.inspector.sidebar.off("select", this.onSidebarSelect);
this.inspector._target.off("will-navigate", this.onWillNavigate);
- this.sizeHeadingLabel = null;
- this.sizeLabel = null;
this.inspector = null;
this.doc = null;
+ this.wrapper = null;
+ this.container = null;
+ this.expander = null;
+ this.sizeLabel = null;
+ this.sizeHeadingLabel = null;
if (this.reflowFront) {
this.untrackReflows();
this.reflowFront.destroy();
this.reflowFront = null;
}
},
onSidebarSelect: function (e, sidebar) {
- this.setActive(sidebar === "layoutview");
+ this.setActive(sidebar === "computedview");
},
/**
* Selection 'new-node-front' event handler.
*/
onNewSelection: function () {
- let done = this.inspector.updating("layoutview");
+ let done = this.inspector.updating("computed-view");
this.onNewNode()
.then(() => this.hideGeometryEditor())
.then(done, (err) => {
console.error(err);
done();
}).catch(console.error);
},
@@ -521,16 +536,28 @@ LayoutView.prototype = {
this.showGeometryEditor();
}
},
onPickerStarted: function () {
this.hideGeometryEditor();
},
+ onToggleExpander: function () {
+ let isOpen = this.expander.hasAttribute("open");
+
+ if (isOpen) {
+ this.container.hidden = true;
+ this.expander.removeAttribute("open");
+ } else {
+ this.container.hidden = false;
+ this.expander.setAttribute("open", "");
+ }
+ },
+
onMarkupViewLeave: function () {
this.showGeometryEditor(true);
},
onMarkupViewNodeHover: function () {
this.hideGeometryEditor(false);
},
@@ -545,34 +572,33 @@ LayoutView.prototype = {
* @param {Boolean} isActive
*/
setActive: function (isActive) {
if (isActive === this.isActive) {
return;
}
this.isActive = isActive;
- let panel = this.doc.getElementById("sidebar-panel-layoutview");
- panel.classList.toggle("inactive", !isActive);
-
if (isActive) {
this.trackReflows();
} else {
this.untrackReflows();
}
},
/**
* Compute the dimensions of the node and update the values in
- * the layoutview/view.xhtml document.
+ * the inspector.xul document.
* @return a promise that will be resolved when complete.
*/
update: function () {
let lastRequest = Task.spawn((function* () {
if (!this.isViewVisibleAndNodeValid()) {
+ this.wrapper.hidden = true;
+ this.inspector.emit("layoutview-updated");
return null;
}
let node = this.inspector.selection.nodeFront;
let layout = yield this.inspector.pageStyle.getLayout(node, {
autoMargins: this.isActive
});
let styleEntries = yield this.inspector.pageStyle.getApplied(node, {});
@@ -588,22 +614,16 @@ LayoutView.prototype = {
let width = layout.width;
let height = layout.height;
let newLabel = SHARED_L10N.getFormatStr("dimensions", width, height);
if (this.sizeHeadingLabel.textContent != newLabel) {
this.sizeHeadingLabel.textContent = newLabel;
}
- // If the view isn't active, no need to do anything more.
- if (!this.isActive) {
- this.inspector.emit("layoutview-updated");
- return null;
- }
-
for (let i in this.map) {
let property = this.map[i].property;
if (!(property in layout)) {
// Depending on the actor version, some properties
// might be missing.
continue;
}
let parsedValue = parseFloat(layout[property]);
@@ -651,18 +671,20 @@ LayoutView.prototype = {
let newValue = width + "\u00D7" + height;
if (this.sizeLabel.textContent != newValue) {
this.sizeLabel.textContent = newValue;
}
this.elementRules = styleEntries.map(e => e.rule);
+ this.wrapper.hidden = false;
+
this.inspector.emit("layoutview-updated");
- return undefined;
+ return null;
}).bind(this)).catch(console.error);
this._lastRequest = lastRequest;
return this._lastRequest;
},
/**
* Update the text in the tooltip shown when hovering over a value to provide
--- a/devtools/client/inspector/layout/test/head.js
+++ b/devtools/client/inspector/layout/test/head.js
@@ -50,33 +50,33 @@ function selectAndHighlightNode(nodeOrSe
/**
* Open the toolbox, with the inspector tool visible, and the layout-view
* sidebar tab selected.
* @return a promise that resolves when the inspector is ready and the layout
* view is visible and ready
*/
function openLayoutView() {
- return openInspectorSidebarTab("layoutview").then(data => {
+ return openInspectorSidebarTab("computedview").then(data => {
// The actual highligher show/hide methods are mocked in layoutview tests.
// The highlighter is tested in devtools/inspector/test.
function mockHighlighter({highlighter}) {
highlighter.showBoxModel = function () {
return promise.resolve();
};
highlighter.hideBoxModel = function () {
return promise.resolve();
};
}
mockHighlighter(data.toolbox);
return {
toolbox: data.toolbox,
inspector: data.inspector,
- view: data.inspector.layoutview,
+ view: data.inspector.computedview.layoutView,
testActor: data.testActor
};
});
}
/**
* Wait for the layoutview-updated event.
* @return a promise
--- a/devtools/client/inspector/markup/markup.js
+++ b/devtools/client/inspector/markup/markup.js
@@ -48,16 +48,17 @@ const Heritage = require("sdk/core/herit
const {parseAttribute} =
require("devtools/client/shared/node-attribute-parser");
const {Task} = require("devtools/shared/task");
const {scrollIntoViewIfNeeded} = require("devtools/shared/layout/utils");
const {PrefObserver} = require("devtools/client/styleeditor/utils");
const {KeyShortcuts} = require("devtools/client/shared/key-shortcuts");
const {template} = require("devtools/shared/gcli/templater");
const nodeConstants = require("devtools/shared/dom-node-constants");
+const nodeFilterConstants = require("devtools/shared/dom-node-filter-constants");
const {XPCOMUtils} = require("resource://gre/modules/XPCOMUtils.jsm");
loader.lazyRequireGetter(this, "CSS", "CSS");
loader.lazyGetter(this, "AutocompletePopup", () => {
return require("devtools/client/shared/autocomplete-popup").AutocompletePopup;
});
XPCOMUtils.defineLazyModuleGetter(this, "PluralForm",
@@ -627,24 +628,24 @@ MarkupView.prototype = {
/**
* Create a TreeWalker to find the next/previous
* node for selection.
*/
_selectionWalker: function (start) {
let walker = this.doc.createTreeWalker(
start || this._elt,
- Ci.nsIDOMNodeFilter.SHOW_ELEMENT,
+ nodeFilterConstants.SHOW_ELEMENT,
function (element) {
if (element.container &&
element.container.elt === element &&
element.container.visible) {
- return Ci.nsIDOMNodeFilter.FILTER_ACCEPT;
+ return nodeFilterConstants.FILTER_ACCEPT;
}
- return Ci.nsIDOMNodeFilter.FILTER_SKIP;
+ return nodeFilterConstants.FILTER_SKIP;
}
);
walker.currentNode = this._selectedContainer.elt;
return walker;
},
_onCopy: function (evt) {
// Ignore copy events from editors
@@ -3188,16 +3189,17 @@ ElementEditor.prototype = {
attr.remove();
}
},
_createAttribute: function (attribute, before = null) {
// Create the template editor, which will save some variables here.
let data = {
attrName: attribute.name,
+ tabindex: this.container.canFocus ? "0" : "-1",
};
this.template("attribute", data);
let {attr, inner, name, val} = data;
// Double quotes need to be handled specially to prevent DOMParser failing.
// name="v"a"l"u"e" when editing -> name='v"a"l"u"e"'
// name="v'a"l'u"e" when editing -> name="v'a"l'u"e"
let editValueDisplayed = attribute.value || "";
--- a/devtools/client/inspector/markup/markup.xhtml
+++ b/devtools/client/inspector/markup/markup.xhtml
@@ -71,17 +71,17 @@
--></span>
<span id="template-attribute"
save="${attr}"
data-attr="${attrName}"
data-value="${attrValue}"
class="attreditor"
style="display:none"> <!--
- --><span class="editable" save="${inner}" tabindex="-1"><!--
+ --><span class="editable" save="${inner}" tabindex="${tabindex}"><!--
--><span save="${name}" class="attr-name theme-fg-color2"></span><!--
-->="<!--
--><span save="${val}" class="attr-value theme-fg-color6"></span><!--
-->"<!--
--></span><!--
--></span>
<span id="template-text" save="${elt}" class="editor text"><!--
--- a/devtools/client/inspector/markup/test/browser.ini
+++ b/devtools/client/inspector/markup/test/browser.ini
@@ -29,16 +29,17 @@ support-files =
doc_markup_toggle.html
doc_markup_tooltip.png
doc_markup_void_elements.html
doc_markup_void_elements.xhtml
doc_markup_xul.xul
head.js
helper_attributes_test_runner.js
helper_events_test_runner.js
+ helper_markup_accessibility_navigation.js
helper_outerhtml_test_runner.js
helper_style_attr_test_runner.js
lib_jquery_1.0.js
lib_jquery_1.1.js
lib_jquery_1.2_min.js
lib_jquery_1.3_min.js
lib_jquery_1.4_min.js
lib_jquery_1.6_min.js
@@ -50,16 +51,18 @@ support-files =
!/devtools/client/framework/test/shared-head.js
!/devtools/client/shared/test/test-actor.js
!/devtools/client/shared/test/test-actor-registry.js
[browser_markup_accessibility_focus_blur.js]
skip-if = os == "mac" # Full keyboard navigation on OSX only works if Full Keyboard Access setting is set to All Control in System Keyboard Preferences
[browser_markup_accessibility_navigation.js]
skip-if = os == "mac" # Full keyboard navigation on OSX only works if Full Keyboard Access setting is set to All Control in System Keyboard Preferences
+[browser_markup_accessibility_navigation_after_edit.js]
+skip-if = os == "mac" # Full keyboard navigation on OSX only works if Full Keyboard Access setting is set to All Control in System Keyboard Preferences
[browser_markup_accessibility_semantics.js]
[browser_markup_anonymous_01.js]
[browser_markup_anonymous_02.js]
skip-if = e10s # scratchpad.xul is not loading in e10s window
[browser_markup_anonymous_03.js]
[browser_markup_anonymous_04.js]
[browser_markup_copy_image_data.js]
subsuite = clipboard
@@ -85,16 +88,17 @@ subsuite = clipboard
[browser_markup_events_jquery_1.3.js]
[browser_markup_events_jquery_1.4.js]
[browser_markup_events_jquery_1.6.js]
[browser_markup_events_jquery_1.7.js]
[browser_markup_events_jquery_1.11.1.js]
[browser_markup_events_jquery_2.1.1.js]
[browser_markup_events-overflow.js]
skip-if = true # Bug 1177550
+[browser_markup_events-windowed-host.js]
[browser_markup_links_01.js]
[browser_markup_links_02.js]
[browser_markup_links_03.js]
[browser_markup_links_04.js]
subsuite = clipboard
[browser_markup_links_05.js]
[browser_markup_links_06.js]
[browser_markup_links_07.js]
--- a/devtools/client/inspector/markup/test/browser_markup_accessibility_navigation.js
+++ b/devtools/client/inspector/markup/test/browser_markup_accessibility_navigation.js
@@ -1,17 +1,18 @@
/* 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/. */
+/* import-globals-from helper_markup_accessibility_navigation.js */
"use strict";
-/* global getContainerForSelector, openInspectorForURL */
+// Test keyboard navigation accessibility of inspector's markup view.
-// Test keyboard navigation accessibility of inspector's markup view.
+loadHelperScript("helper_markup_accessibility_navigation.js");
/**
* Test data has the format of:
* {
* desc {String} description for better logging
* key {String} key event's key
* options {?Object} optional event data such as shiftKey, etc
* focused {String} path to expected focused element relative to
@@ -232,70 +233,45 @@ const TESTS = [
focused: "docBody",
activedescendant: "body.tagLine",
key: "VK_UP",
options: { },
waitFor: "inspector-updated"
},
];
+let containerID = 0;
let elms = {};
-let containerID = 0;
add_task(function* () {
let { inspector } = yield openInspectorForURL(`data:text/html;charset=utf-8,
<h1 id="some-id" class="some-class">foo<span>Child span<span></h1>`);
- let markup = inspector.markup;
- let doc = markup.doc;
- let win = doc.defaultView;
// Record containers that are created after inspector is initialized to be
// useful in testing.
inspector.on("container-created", memorizeContainer);
registerCleanupFunction(() => {
inspector.off("container-created", memorizeContainer);
});
- elms.docBody = doc.body;
- elms.root = markup.getContainer(markup._rootNode);
+ elms.docBody = inspector.markup.doc.body;
+ elms.root = inspector.markup.getContainer(inspector.markup._rootNode);
elms.header = yield getContainerForSelector("h1", inspector);
elms.body = yield getContainerForSelector("body", inspector);
// Initial focus is on root element and active descendant should be set on
// body tag line.
- testNavigationState(doc, elms.docBody, elms.body.tagLine);
+ testNavigationState(inspector, elms, elms.docBody, elms.body.tagLine);
// Focus on the tree element.
elms.root.elt.focus();
- for (let {desc, waitFor, focused, activedescendant, key, options} of TESTS) {
- info(desc);
- let updated;
- if (waitFor) {
- updated = waitFor === "inspector-updated" ?
- inspector.once(waitFor) : markup.once(waitFor);
- } else {
- updated = Promise.resolve();
- }
+ for (let testData of TESTS) {
+ yield runAccessibilityNavigationTest(inspector, elms, testData);
+ }
- EventUtils.synthesizeKey(key, options, win);
- yield updated;
- testNavigationState(doc, getElm(focused), getElm(activedescendant));
- }
+ elms = null;
});
// Record all containers that are created dynamically into elms object.
function memorizeContainer(event, container) {
elms[`container-${containerID++}`] = container;
}
-
-// Parse and lookup an element from elms object based on dotted path.
-function getElm(path) {
- let segments = path.split(".");
- return segments.reduce((prev, current) => prev[current], elms);
-}
-
-function testNavigationState(doc, focused, activedescendant) {
- let id = activedescendant.getAttribute("id");
- is(doc.activeElement, focused, `Keyboard focus should be set to ${focused}`);
- is(elms.root.elt.getAttribute("aria-activedescendant"), id,
- `Active descendant should be set to ${id}`);
-}
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/markup/test/browser_markup_accessibility_navigation_after_edit.js
@@ -0,0 +1,126 @@
+/* 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/. */
+/* import-globals-from helper_markup_accessibility_navigation.js */
+
+"use strict";
+
+// Test keyboard navigation accessibility is preserved after editing attributes.
+
+loadHelperScript("helper_markup_accessibility_navigation.js");
+
+const TEST_URI = '<div id="some-id" class="some-class"></div>';
+
+/**
+ * Test data has the format of:
+ * {
+ * desc {String} description for better logging
+ * key {String} key event's key
+ * options {?Object} optional event data such as shiftKey, etc
+ * focused {String} path to expected focused element relative to
+ * its container
+ * activedescendant {String} path to expected aria-activedescendant element
+ * relative to its container
+ * waitFor {String} optional event to wait for if keyboard actions
+ * result in asynchronous updates
+ * }
+ */
+const TESTS = [
+ {
+ desc: "Select header container",
+ focused: "root.elt",
+ activedescendant: "div.tagLine",
+ key: "VK_DOWN",
+ options: { },
+ waitFor: "inspector-updated"
+ },
+ {
+ desc: "Focus on header tag",
+ focused: "div.focusableElms.0",
+ activedescendant: "div.tagLine",
+ key: "VK_RETURN",
+ options: { }
+ },
+ {
+ desc: "Activate header tag editor",
+ focused: "div.editor.tag.inplaceEditor.input",
+ activedescendant: "div.tagLine",
+ key: "VK_RETURN",
+ options: { }
+ },
+ {
+ desc: "Activate header id attribute editor",
+ focused: "div.editor.attrList.children.0.children.1.inplaceEditor.input",
+ activedescendant: "div.tagLine",
+ key: "VK_TAB",
+ options: { }
+ },
+ {
+ desc: "Deselect text in header id attribute editor",
+ focused: "div.editor.attrList.children.0.children.1.inplaceEditor.input",
+ activedescendant: "div.tagLine",
+ key: "VK_TAB",
+ options: { }
+ },
+ {
+ desc: "Move the cursor to the left",
+ focused: "div.editor.attrList.children.0.children.1.inplaceEditor.input",
+ activedescendant: "div.tagLine",
+ key: "VK_LEFT",
+ options: { }
+ },
+ {
+ desc: "Modify the attribute",
+ focused: "div.editor.attrList.children.0.children.1.inplaceEditor.input",
+ activedescendant: "div.tagLine",
+ key: "A",
+ options: { }
+ },
+ {
+ desc: "Commit the attribute change",
+ focused: "div.focusableElms.1",
+ activedescendant: "div.tagLine",
+ key: "VK_RETURN",
+ options: { },
+ waitFor: "inspector-updated"
+ },
+ {
+ desc: "Tab and focus on header class attribute",
+ focused: "div.focusableElms.2",
+ activedescendant: "div.tagLine",
+ key: "VK_TAB",
+ options: { }
+ },
+ {
+ desc: "Tab and focus on header new attribute node",
+ focused: "div.focusableElms.3",
+ activedescendant: "div.tagLine",
+ key: "VK_TAB",
+ options: { }
+ },
+];
+
+let elms = {};
+
+add_task(function* () {
+ let url = `data:text/html;charset=utf-8,${TEST_URI}`;
+ let { inspector } = yield openInspectorForURL(url);
+
+ elms.docBody = inspector.markup.doc.body;
+ elms.root = inspector.markup.getContainer(inspector.markup._rootNode);
+ elms.div = yield getContainerForSelector("div", inspector);
+ elms.body = yield getContainerForSelector("body", inspector);
+
+ // Initial focus is on root element and active descendant should be set on
+ // body tag line.
+ testNavigationState(inspector, elms, elms.docBody, elms.body.tagLine);
+
+ // Focus on the tree element.
+ elms.root.elt.focus();
+
+ for (let testData of TESTS) {
+ yield runAccessibilityNavigationTest(inspector, elms, testData);
+ }
+
+ elms = null;
+});
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/markup/test/browser_markup_events-windowed-host.js
@@ -0,0 +1,61 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+/*
+ * Test that the event details tooltip can be hidden by clicking outside of the tooltip
+ * after switching hosts.
+ */
+
+const TEST_URL = URL_ROOT + "doc_markup_events-overflow.html";
+
+registerCleanupFunction(() => {
+ // Restore the default Toolbox host position after the test.
+ Services.prefs.clearUserPref("devtools.toolbox.host");
+});
+
+add_task(function* () {
+ let { inspector, toolbox } = yield openInspectorForURL(TEST_URL);
+ yield runTests(inspector);
+
+ yield toolbox.switchHost("window");
+ yield runTests(inspector);
+
+ yield toolbox.switchHost("bottom");
+ yield runTests(inspector);
+
+ yield toolbox.destroy();
+});
+
+function* runTests(inspector) {
+ let markupContainer = yield getContainerForSelector("#events", inspector);
+ let evHolder = markupContainer.elt.querySelector(".markupview-events");
+ let tooltip = inspector.markup.eventDetailsTooltip;
+
+ info("Clicking to open event tooltip.");
+
+ let onInspectorUpdated = inspector.once("inspector-updated");
+ let onTooltipShown = tooltip.once("shown");
+ EventUtils.synthesizeMouseAtCenter(evHolder, {}, inspector.markup.doc.defaultView);
+
+ yield onTooltipShown;
+ // New node is selected when clicking on the events bubble, wait for inspector-updated.
+ yield onInspectorUpdated;
+
+ ok(tooltip.isVisible(), "EventTooltip visible.");
+
+ onInspectorUpdated = inspector.once("inspector-updated");
+ let onTooltipHidden = tooltip.once("hidden");
+
+ info("Click on another tag to hide the event tooltip");
+ let h1 = yield getContainerForSelector("h1", inspector);
+ let tag = h1.elt.querySelector(".tag");
+ EventUtils.synthesizeMouseAtCenter(tag, {}, inspector.markup.doc.defaultView);
+
+ yield onTooltipHidden;
+ // New node is selected, wait for inspector-updated.
+ yield onInspectorUpdated;
+
+ ok(!tooltip.isVisible(), "EventTooltip hidden.");
+}
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/markup/test/helper_markup_accessibility_navigation.js
@@ -0,0 +1,70 @@
+/* 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 no-unused-vars: [2, {"vars": "local"}] */
+/* import-globals-from head.js */
+"use strict";
+
+/**
+ * Execute a keyboard event and check that the state is as expected (focused element, aria
+ * attribute etc...).
+ *
+ * @param {InspectorPanel} inspector
+ * Current instance of the inspector being tested.
+ * @param {Object} elms
+ * Map of elements that will be used to retrieve live references to children
+ * elements
+ * @param {Element} focused
+ * Element expected to be focused
+ * @param {Element} activedescendant
+ * Element expected to be the aria activedescendant of the root node
+ */
+function testNavigationState(inspector, elms, focused, activedescendant) {
+ let doc = inspector.markup.doc;
+ let id = activedescendant.getAttribute("id");
+ is(doc.activeElement, focused, `Keyboard focus should be set to ${focused}`);
+ is(elms.root.elt.getAttribute("aria-activedescendant"), id,
+ `Active descendant should be set to ${id}`);
+}
+
+/**
+ * Execute a keyboard event and check that the state is as expected (focused element, aria
+ * attribute etc...).
+ *
+ * @param {InspectorPanel} inspector
+ * Current instance of the inspector being tested.
+ * @param {Object} elms
+ * MarkupContainers/Elements that will be used to retrieve references to other
+ * elements based on objects' paths.
+ * @param {Object} testData
+ * - {String} desc: description for better logging.
+ * - {String} key: keyboard event's key.
+ * - {Object} options, optional: event data such as shiftKey, etc.
+ * - {String} focused: path to expected focused element in elms map.
+ * - {String} activedescendant: path to expected aria-activedescendant element in
+ * elms map.
+ * - {String} waitFor, optional: markupview event to wait for if keyboard actions
+ * result in async updates. Also accepts the inspector event "inspector-updated".
+ */
+function* runAccessibilityNavigationTest(inspector, elms,
+ {desc, key, options, focused, activedescendant, waitFor}) {
+ info(desc);
+
+ let markup = inspector.markup;
+ let doc = markup.doc;
+ let win = doc.defaultView;
+
+ let updated;
+ if (waitFor) {
+ updated = waitFor === "inspector-updated" ?
+ inspector.once(waitFor) : markup.once(waitFor);
+ } else {
+ updated = Promise.resolve();
+ }
+ EventUtils.synthesizeKey(key, options, win);
+ yield updated;
+
+ let focusedElement = lookupPath(elms, focused);
+ let activeDescendantElement = lookupPath(elms, activedescendant);
+ testNavigationState(inspector, elms, focusedElement, activeDescendantElement);
+}
--- a/devtools/client/inspector/rules/models/rule.js
+++ b/devtools/client/inspector/rules/models/rule.js
@@ -1,29 +1,25 @@
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set ts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
-const {Cc, Ci} = require("chrome");
+const {Ci} = require("chrome");
const promise = require("promise");
const CssLogic = require("devtools/shared/inspector/css-logic");
const {ELEMENT_STYLE} = require("devtools/shared/specs/styles");
const {TextProperty} =
require("devtools/client/inspector/rules/models/text-property");
const {promiseWarn} = require("devtools/client/inspector/shared/utils");
const {parseDeclarations} = require("devtools/shared/css-parsing-utils");
-const {XPCOMUtils} = require("resource://gre/modules/XPCOMUtils.jsm");
-
-XPCOMUtils.defineLazyGetter(this, "osString", function () {
- return Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime).OS;
-});
+const Services = require("Services");
/**
* Rule is responsible for the following:
* Manages a single style declaration or rule.
* Applies changes to the properties in a rule.
* Maintains a list of TextProperty objects.
*
* @param {ElementStyle} elementStyle
@@ -655,17 +651,17 @@ Rule.prototype = {
},
/**
* Return a string representation of the rule.
*/
stringifyRule: function () {
let selectorText = this.selectorText;
let cssText = "";
- let terminator = osString === "WINNT" ? "\r\n" : "\n";
+ let terminator = Services.appinfo.OS === "WINNT" ? "\r\n" : "\n";
for (let textProp of this.textProps) {
if (!textProp.invisible) {
cssText += "\t" + textProp.stringifyProperty() + terminator;
}
}
return selectorText + " {" + terminator + cssText + "}";
--- a/devtools/client/inspector/rules/test/browser_rules_copy_styles.js
+++ b/devtools/client/inspector/rules/test/browser_rules_copy_styles.js
@@ -4,19 +4,17 @@
"use strict";
/**
* Tests the behaviour of the copy styles context menu items in the rule
* view.
*/
-XPCOMUtils.defineLazyGetter(this, "osString", function () {
- return Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime).OS;
-});
+const osString = Services.appinfo.OS;
const TEST_URI = URL_ROOT + "doc_copystyles.html";
add_task(function* () {
yield addTab(TEST_URI);
let { inspector, view } = yield openRuleView();
yield selectNode("#testid", inspector);
--- a/devtools/client/inspector/rules/test/browser_rules_select-and-copy-styles.js
+++ b/devtools/client/inspector/rules/test/browser_rules_select-and-copy-styles.js
@@ -1,19 +1,17 @@
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Tests that properties can be selected and copied from the rule view
-XPCOMUtils.defineLazyGetter(this, "osString", function () {
- return Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime).OS;
-});
+const osString = Services.appinfo.OS;
const TEST_URI = `
<style type="text/css">
html {
color: #000000;
}
span {
font-variant: small-caps; color: #000000;
--- a/devtools/client/inspector/shared/test/browser_styleinspector_context-menu-copy-color_01.js
+++ b/devtools/client/inspector/shared/test/browser_styleinspector_context-menu-copy-color_01.js
@@ -19,17 +19,17 @@ add_task(function* () {
yield testView("ruleview", inspector);
yield testView("computedview", inspector);
});
function* testView(viewId, inspector) {
info("Testing " + viewId);
yield inspector.sidebar.select(viewId);
- let view = inspector[viewId].view;
+ let view = inspector[viewId].view || inspector[viewId].computedView;
yield selectNode("div", inspector);
testIsColorValueNode(view);
testIsColorPopupOnAllNodes(view);
yield clearCurrentNodeSelection(inspector);
}
/**
--- a/devtools/client/inspector/shared/test/browser_styleinspector_refresh_when_active.js
+++ b/devtools/client/inspector/shared/test/browser_styleinspector_refresh_when_active.js
@@ -15,17 +15,17 @@ add_task(function* () {
yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
let {inspector, view} = yield openRuleView();
yield selectNode("#one", inspector);
is(getRuleViewPropertyValue(view, "element", "color"), "red",
"The rule-view shows the properties for test node one");
- let cView = inspector.computedview.view;
+ let cView = inspector.computedview.computedView;
let prop = getComputedViewProperty(cView, "color");
ok(!prop, "The computed-view doesn't show the properties for test node one");
info("Switching to the computed-view");
let onComputedViewReady = inspector.once("computed-view-refreshed");
selectComputedView(inspector);
yield onComputedViewReady;
--- a/devtools/client/inspector/test/head.js
+++ b/devtools/client/inspector/test/head.js
@@ -292,17 +292,17 @@ function openRuleView() {
* view is visible and ready
*/
function openComputedView() {
return openInspectorSidebarTab("computedview").then(data => {
return {
toolbox: data.toolbox,
inspector: data.inspector,
testActor: data.testActor,
- view: data.inspector.computedview.view
+ view: data.inspector.computedview.computedView
};
});
}
/**
* Select the rule view sidebar tab on an already opened inspector panel.
*
* @param {InspectorPanel} inspector
@@ -318,17 +318,17 @@ function selectRuleView(inspector) {
* Select the computed view sidebar tab on an already opened inspector panel.
*
* @param {InspectorPanel} inspector
* The opened inspector panel
* @return {CssComputedView} the computed view
*/
function selectComputedView(inspector) {
inspector.sidebar.select("computedview");
- return inspector.computedview.view;
+ return inspector.computedview.computedView;
}
/**
* Get the NodeFront for a node that matches a given css selector, via the
* protocol.
* @param {String|NodeFront} selector
* @param {InspectorPanel} inspector The instance of InspectorPanel currently
* loaded in the toolbox
--- a/devtools/client/locales/en-US/layoutview.dtd
+++ b/devtools/client/locales/en-US/layoutview.dtd
@@ -11,16 +11,17 @@
- You want to make that choice consistent across the developer tools.
- A good criteria is the language in which you'd find the best
- documentation on web development on the web. -->
<!-- LOCALIZATION NOTE (*.tooltip): These tooltips are not regular tooltips.
- The text appears on the bottom right corner of the layout view when
- the corresponding box is hovered. -->
+<!ENTITY layoutViewTitle "Box Model">
<!ENTITY margin.tooltip "margin">
<!ENTITY border.tooltip "border">
<!ENTITY padding.tooltip "padding">
<!ENTITY content.tooltip "content">
<!-- LOCALIZATION NOTE: This label is displayed as a tooltip that appears when
- hovering over the button that allows users to edit the position of an
- element in the page. -->
--- a/devtools/client/netmonitor/har/har-builder.js
+++ b/devtools/client/netmonitor/har/har-builder.js
@@ -1,23 +1,20 @@
/* 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 { Ci, Cc } = require("chrome");
const { defer, all } = require("promise");
const { LocalizationHelper } = require("devtools/client/shared/l10n");
+const Services = require("Services");
+const appInfo = Services.appinfo;
loader.lazyRequireGetter(this, "NetworkHelper", "devtools/shared/webconsole/network-helper");
-loader.lazyGetter(this, "appInfo", () => {
- return Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULAppInfo);
-});
-
loader.lazyGetter(this, "L10N", () => {
return new LocalizationHelper("chrome://devtools/locale/har.properties");
});
const HAR_VERSION = "1.1";
/**
* This object is responsible for building HAR file. See HAR spec:
--- a/devtools/client/performance/test/browser.ini
+++ b/devtools/client/performance/test/browser.ini
@@ -97,16 +97,17 @@ support-files =
[browser_perf-telemetry-02.js]
[browser_perf-telemetry-03.js]
[browser_perf-telemetry-04.js]
# [browser_perf-theme-toggle.js] TODO bug 1256350
[browser_perf-tree-abstract-01.js]
[browser_perf-tree-abstract-02.js]
[browser_perf-tree-abstract-03.js]
[browser_perf-tree-abstract-04.js]
+[browser_perf-tree-abstract-05.js]
[browser_perf-tree-view-01.js]
[browser_perf-tree-view-02.js]
[browser_perf-tree-view-03.js]
[browser_perf-tree-view-04.js]
[browser_perf-tree-view-05.js]
[browser_perf-tree-view-06.js]
[browser_perf-tree-view-07.js]
[browser_perf-tree-view-08.js]
new file mode 100644
--- /dev/null
+++ b/devtools/client/performance/test/browser_perf-tree-abstract-05.js
@@ -0,0 +1,104 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+/**
+ * Tests if the abstract tree base class for the profiler's tree view
+ * supports PageUp/PageDown/Home/End keys.
+ */
+
+const { appendAndWaitForPaint } = require("devtools/client/performance/test/helpers/dom-utils");
+const { synthesizeCustomTreeClass } = require("devtools/client/performance/test/helpers/synth-utils");
+const { once } = require("devtools/client/performance/test/helpers/event-utils");
+
+add_task(function* () {
+ let { MyCustomTreeItem } = synthesizeCustomTreeClass();
+
+ let container = document.createElement("vbox");
+ container.style.height = '100%';
+ container.style.overflow = 'scroll';
+ yield appendAndWaitForPaint(gBrowser.selectedBrowser.parentNode, container);
+
+ let myDataSrc = {
+ label: "root",
+ children: []
+ };
+
+ for (let i = 0; i < 1000; i++) {
+ myDataSrc.children.push({
+ label: "child-" + i,
+ children: []
+ });
+ }
+
+ let treeRoot = new MyCustomTreeItem(myDataSrc, { parent: null });
+ treeRoot.attachTo(container);
+ treeRoot.focus();
+ treeRoot.expand();
+
+ is(document.commandDispatcher.focusedElement, treeRoot.target,
+ "The root node is focused.");
+
+ // Test HOME and END
+
+ key("VK_END");
+ is(document.commandDispatcher.focusedElement,
+ treeRoot.getChild(myDataSrc.children.length - 1).target,
+ "The last node is focused.");
+
+ key("VK_HOME");
+ is(document.commandDispatcher.focusedElement, treeRoot.target,
+ "The first (root) node is focused.");
+
+ // Test PageUp and PageDown
+
+ let nodesPerPageSize = treeRoot._getNodesPerPageSize();
+
+ key("VK_PAGE_DOWN");
+ is(document.commandDispatcher.focusedElement,
+ treeRoot.getChild(nodesPerPageSize - 1).target,
+ "The first node in the second page is focused.");
+
+ key("VK_PAGE_DOWN");
+ is(document.commandDispatcher.focusedElement,
+ treeRoot.getChild(nodesPerPageSize * 2 - 1).target,
+ "The first node in the third page is focused.");
+
+ key("VK_PAGE_UP");
+ is(document.commandDispatcher.focusedElement,
+ treeRoot.getChild(nodesPerPageSize - 1).target,
+ "The first node in the second page is focused.");
+
+ key("VK_PAGE_UP");
+ is(document.commandDispatcher.focusedElement, treeRoot.target,
+ "The first (root) node is focused.");
+
+ // Test PageUp in the middle of the first page
+
+ let middleIndex = Math.floor(nodesPerPageSize / 2);
+
+ treeRoot.getChild(middleIndex).target.focus();
+ is(document.commandDispatcher.focusedElement,
+ treeRoot.getChild(middleIndex).target,
+ "The middle node in the first page is focused.");
+
+ key("VK_PAGE_UP");
+ is(document.commandDispatcher.focusedElement, treeRoot.target,
+ "The first (root) node is focused.");
+
+ // Test PageDown in the middle of the last page
+
+ middleIndex = Math.ceil(myDataSrc.children.length - middleIndex);
+
+ treeRoot.getChild(middleIndex).target.focus();
+ is(document.commandDispatcher.focusedElement,
+ treeRoot.getChild(middleIndex).target,
+ "The middle node in the last page is focused.");
+
+ key("VK_PAGE_DOWN");
+ is(document.commandDispatcher.focusedElement,
+ treeRoot.getChild(myDataSrc.children.length - 1).target,
+ "The last node is focused.");
+
+ container.remove();
+});
--- a/devtools/client/responsivedesign/test/head.js
+++ b/devtools/client/responsivedesign/test/head.js
@@ -142,17 +142,17 @@ var openInspectorSideBar = Task.async(fu
let {toolbox, inspector} = yield openInspector();
info("Selecting the " + id + " sidebar");
inspector.sidebar.select(id);
return {
toolbox: toolbox,
inspector: inspector,
- view: inspector[id].view
+ view: inspector[id].view || inspector[id].computedView
};
});
/**
* Checks whether the inspector's sidebar corresponding to the given id already
* exists
* @param {InspectorPanel}
* @param {String}
--- a/devtools/client/shared/DOMHelpers.jsm
+++ b/devtools/client/shared/DOMHelpers.jsm
@@ -2,16 +2,18 @@
* 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 Ci = Components.interfaces;
const Cu = Components.utils;
const { Services } = Cu.import("resource://gre/modules/Services.jsm", {});
+const { require } = Cu.import("resource://devtools/shared/Loader.jsm", {});
+const nodeFilterConstants = require("devtools/shared/dom-node-filter-constants");
this.EXPORTED_SYMBOLS = ["DOMHelpers"];
/**
* DOMHelpers
* Makes DOM traversal easier. Goes through iframes.
*
* @constructor
@@ -98,17 +100,17 @@ DOMHelpers.prototype = {
return child;
}
return null; // we have no children worth showing.
},
getFirstChild: function Helpers_getFirstChild(node)
{
- let SHOW_ALL = Components.interfaces.nsIDOMNodeFilter.SHOW_ALL;
+ let SHOW_ALL = nodeFilterConstants.SHOW_ALL;
this.treeWalker = node.ownerDocument.createTreeWalker(node,
SHOW_ALL, null);
return this.treeWalker.firstChild();
},
getNextSibling: function Helpers_getNextSibling(node)
{
let next = this.treeWalker.nextSibling();
--- a/devtools/client/shared/components/reps/array.js
+++ b/devtools/client/shared/components/reps/array.js
@@ -55,17 +55,17 @@ define(function (require, exports, modul
items.push(ItemRep({
object: exc,
delim: delim,
key: i
}));
}
}
- if (array.length > max + 1) {
+ if (array.length > max) {
items.pop();
let objectLink = this.props.objectLink || DOM.span;
items.push(Caption({
key: "more",
object: objectLink({
object: this.props.object
}, "more…")
--- a/devtools/client/shared/components/reps/grip-array.js
+++ b/devtools/client/shared/components/reps/grip-array.js
@@ -82,17 +82,17 @@ define(function (require, exports, modul
items.push(GripArrayItem(Object.assign({}, this.props, {
object: exc,
delim: delim,
key: i}
)));
}
}
- if (array.length > max + 1) {
+ if (array.length > max) {
items.pop();
let objectLink = this.props.objectLink || span;
items.push(Caption({
key: "more",
object: objectLink({
object: this.props.object
}, "more…")
}));
--- a/devtools/client/shared/components/test/mochitest/test_reps_array.html
+++ b/devtools/client/shared/components/test/mochitest/test_reps_array.html
@@ -14,23 +14,28 @@ Test ArrayRep rep
<pre id="test">
<script src="head.js" type="application/javascript;version=1.8"></script>
<script type="application/javascript;version=1.8">
window.onload = Task.async(function* () {
let { Rep } = browserRequire("devtools/client/shared/components/reps/rep");
let { ArrayRep } = browserRequire("devtools/client/shared/components/reps/array");
let componentUnderTest = ArrayRep;
+ const maxLength = {
+ short: 3,
+ long: 300
+ };
try {
yield testBasic();
// Test property iterator
yield testMaxProps();
- yield testMoreThanMaxProps();
+ yield testMoreThanShortMaxProps();
+ yield testMoreThanLongMaxProps();
yield testRecursiveArray();
// Test that properties are rendered as expected by ItemRep
yield testNested();
} catch(e) {
ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e));
} finally {
SimpleTest.finish();
@@ -88,36 +93,63 @@ window.onload = Task.async(function* ()
mode: "long",
expectedOutput: defaultOutput,
}
];
testRepRenderModes(modeTests, "testMaxProps", componentUnderTest, stub);
}
- function testMoreThanMaxProps() {
- const stub = Array(302).fill("foo");
- const defaultOutput = `["foo", "foo", "foo", more…]`;
+ function testMoreThanShortMaxProps() {
+ const stub = Array(maxLength.short + 1).fill("foo");
+ const defaultShortOutput = `[${Array(maxLength.short).fill("\"foo\"").join(", ")}, more…]`;
const modeTests = [
{
mode: undefined,
- expectedOutput: defaultOutput,
+ expectedOutput: defaultShortOutput,
},
{
mode: "tiny",
- expectedOutput: `[302]`,
+ expectedOutput: `[${maxLength.short + 1}]`,
},
{
mode: "short",
- expectedOutput: defaultOutput,
+ expectedOutput: defaultShortOutput,
},
{
mode: "long",
- expectedOutput: `[${Array(300).fill("\"foo\"").join(", ")}, more…]`,
+ expectedOutput: `[${Array(maxLength.short + 1).fill("\"foo\"").join(", ")}]`,
+ }
+ ];
+
+ testRepRenderModes(modeTests, "testMoreThanMaxProps", componentUnderTest, stub);
+ }
+
+ function testMoreThanLongMaxProps() {
+ const stub = Array(maxLength.long + 1).fill("foo");
+ const defaultShortOutput = `[${Array(maxLength.short).fill("\"foo\"").join(", ")}, more…]`;
+ const defaultLongOutput = `[${Array(maxLength.long).fill("\"foo\"").join(", ")}, more…]`;
+
+ const modeTests = [
+ {
+ mode: undefined,
+ expectedOutput: defaultShortOutput,
+ },
+ {
+ mode: "tiny",
+ expectedOutput: `[${maxLength.long + 1}]`,
+ },
+ {
+ mode: "short",
+ expectedOutput: defaultShortOutput,
+ },
+ {
+ mode: "long",
+ expectedOutput: defaultLongOutput,
}
];
testRepRenderModes(modeTests, "testMoreThanMaxProps", componentUnderTest, stub);
}
function testRecursiveArray() {
let stub = [1];
--- a/devtools/client/shared/components/test/mochitest/test_reps_grip-array.html
+++ b/devtools/client/shared/components/test/mochitest/test_reps_grip-array.html
@@ -14,23 +14,28 @@ Test GripArray rep
<pre id="test">
<script src="head.js" type="application/javascript;version=1.8"></script>
<script type="application/javascript;version=1.8">
window.onload = Task.async(function* () {
let { Rep } = browserRequire("devtools/client/shared/components/reps/rep");
let { GripArray } = browserRequire("devtools/client/shared/components/reps/grip-array");
let componentUnderTest = GripArray;
+ const maxLength = {
+ short: 3,
+ long: 300
+ };
try {
yield testBasic();
// Test property iterator
yield testMaxProps();
- yield testMoreThanMaxProps();
+ yield testMoreThanShortMaxProps();
+ yield testMoreThanLongMaxProps();
yield testRecursiveArray();
} catch(e) {
ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e));
} finally {
SimpleTest.finish();
}
function testBasic() {
@@ -90,38 +95,67 @@ window.onload = Task.async(function* ()
mode: "long",
expectedOutput: defaultOutput,
}
];
testRepRenderModes(modeTests, testName, componentUnderTest, getGripStub(testName));
}
- function testMoreThanMaxProps() {
- // Test array = `["test string"…] //301 items`
- const testName = "testMoreThanMaxProps";
+ function testMoreThanShortMaxProps() {
+ // Test array = `["test string"…] //4 items`
+ const testName = "testMoreThanShortMaxProps";
- const defaultOutput = `[${Array(3).fill("\"test string\"").join(", ")}, more…]`;
+ const defaultOutput = `[${Array(maxLength.short).fill("\"test string\"").join(", ")}, more…]`;
const modeTests = [
{
mode: undefined,
expectedOutput: defaultOutput,
},
{
mode: "tiny",
- expectedOutput: `[302]`,
+ expectedOutput: `[${maxLength.short + 1}]`,
},
{
mode: "short",
expectedOutput: defaultOutput,
},
{
mode: "long",
- expectedOutput: `[${Array(300).fill("\"test string\"").join(", ")}, more…]`,
+ expectedOutput: `[${Array(maxLength.short + 1).fill("\"test string\"").join(", ")}]`,
+ }
+ ];
+
+ testRepRenderModes(modeTests, testName, componentUnderTest, getGripStub(testName));
+ }
+
+ function testMoreThanLongMaxProps() {
+ // Test array = `["test string"…] //301 items`
+ const testName = "testMoreThanLongMaxProps";
+
+ const defaultShortOutput = `[${Array(maxLength.short).fill("\"test string\"").join(", ")}, more…]`;
+ const defaultLongOutput = `[${Array(maxLength.long).fill("\"test string\"").join(", ")}, more…]`;
+
+ const modeTests = [
+ {
+ mode: undefined,
+ expectedOutput: defaultShortOutput,
+ },
+ {
+ mode: "tiny",
+ expectedOutput: `[${maxLength.long + 1}]`,
+ },
+ {
+ mode: "short",
+ expectedOutput: defaultShortOutput,
+ },
+ {
+ mode: "long",
+ expectedOutput: defaultLongOutput
}
];
testRepRenderModes(modeTests, testName, componentUnderTest, getGripStub(testName));
}
function testRecursiveArray() {
// @TODO This is not how this feature should actually work
@@ -195,39 +229,63 @@ window.onload = Task.async(function* ()
"frozen": false,
"sealed": false,
"ownPropertyLength": 0
}
]
}
};
- case "testMoreThanMaxProps":
- let grip = {
+ case "testMoreThanShortMaxProps":
+ let shortArrayGrip = {
"type": "object",
"class": "Array",
"actor": "server1.conn1.obj35",
"extensible": true,
"frozen": false,
"sealed": false,
"ownPropertyLength": 4,
"preview": {
"kind": "ArrayLike",
- "length": 302,
+ "length": maxLength.short + 1,
"items": []
}
};
- // Generate 101 properties, which is more that the maximum
- // limit in case of the 'long' mode.
- for (let i = 0; i < 302; i++) {
- grip.preview.items.push("test string");
+ // Generate array grip with length 4, which is more that the maximum
+ // limit in case of the 'short' mode.
+ for (let i = 0; i < maxLength.short + 1; i++) {
+ shortArrayGrip.preview.items.push("test string");
}
- return grip;
+ return shortArrayGrip;
+
+ case "testMoreThanLongMaxProps":
+ let longArrayGrip = {
+ "type": "object",
+ "class": "Array",
+ "actor": "server1.conn1.obj35",
+ "extensible": true,
+ "frozen": false,
+ "sealed": false,
+ "ownPropertyLength": 4,
+ "preview": {
+ "kind": "ArrayLike",
+ "length": maxLength.long + 1,
+ "items": []
+ }
+ };
+
+ // Generate array grip with length 301, which is more that the maximum
+ // limit in case of the 'long' mode.
+ for (let i = 0; i < maxLength.long + 1; i++) {
+ longArrayGrip.preview.items.push("test string");
+ }
+
+ return longArrayGrip;
case "testRecursiveArray":
return {
"type": "object",
"class": "Array",
"actor": "server1.conn3.obj42",
"extensible": true,
"frozen": false,
--- a/devtools/client/shared/developer-toolbar.js
+++ b/devtools/client/shared/developer-toolbar.js
@@ -218,25 +218,20 @@ exports.CommandUtils = CommandUtils;
/**
* Due to a number of panel bugs we need a way to check if we are running on
* Linux. See the comments for TooltipPanel and OutputPanel for further details.
*
* When bug 780102 is fixed all isLinux checks can be removed and we can revert
* to using panels.
*/
loader.lazyGetter(this, "isLinux", function () {
- return OS == "Linux";
+ return Services.appinfo.OS == "Linux";
});
loader.lazyGetter(this, "isMac", function () {
- return OS == "Darwin";
-});
-
-loader.lazyGetter(this, "OS", function () {
- let os = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime).OS;
- return os;
+ return Services.appinfo.OS == "Darwin";
});
/**
* A component to manage the global developer toolbar, which contains a GCLI
* and buttons for various developer tools.
* @param aChromeWindow The browser window to which this toolbar is attached
*/
function DeveloperToolbar(aChromeWindow)
@@ -1005,17 +1000,17 @@ OutputPanel.prototype._resize = function
// Set max panel width to match any content with a max of the width of the
// browser window.
let maxWidth = this._panel.ownerDocument.documentElement.clientWidth;
// Adjust max width according to OS.
// We'd like to put this in CSS but we can't:
// body { width: calc(min(-5px, max-content)); }
// #_panel { max-width: -5px; }
- switch (OS) {
+ switch (Services.appinfo.OS) {
case "Linux":
maxWidth -= 5;
break;
case "Darwin":
maxWidth -= 25;
break;
case "WINNT":
maxWidth -= 5;
--- a/devtools/client/shared/shim/Services.js
+++ b/devtools/client/shared/shim/Services.js
@@ -460,16 +460,52 @@ PrefBranch.prototype = {
const Services = {
/**
* An implementation of nsIPrefService that is based on local
* storage. Only the subset of nsIPrefService that is actually used
* by devtools is implemented here.
*/
prefs: new PrefBranch(null, "", ""),
+
+ /**
+ * An implementation of Services.appinfo that holds just the
+ * properties needed by devtools.
+ */
+ appinfo: {
+ get OS() {
+ const os = window.navigator.userAgent;
+ if (os) {
+ if (os.includes("Linux")) {
+ return "Linux";
+ } else if (os.includes("Windows")) {
+ return "WINNT";
+ } else if (os.includes("Mac")) {
+ return "Darwin";
+ }
+ }
+ return "Unknown";
+ },
+
+ // It's fine for this to be an approximation.
+ get name() {
+ return window.navigator.userAgent;
+ },
+
+ // It's fine for this to be an approximation.
+ get version() {
+ return window.navigator.appVersion;
+ },
+
+ // This is only used by telemetry, which is disabled for the
+ // content case. So, being totally wrong is ok.
+ get is64Bit() {
+ return true;
+ },
+ },
};
/**
* Create a new preference. This is used during startup (see
* devtools/client/preferences/devtools.js) to install the
* default preferences.
*
* @param {String} name the name of the preference
--- a/devtools/client/shared/shim/test/mochitest.ini
+++ b/devtools/client/shared/shim/test/mochitest.ini
@@ -1,5 +1,6 @@
[DEFAULT]
support-files =
prefs-wrapper.js
+[test_service_appinfo.html]
[test_service_prefs.html]
new file mode 100644
--- /dev/null
+++ b/devtools/client/shared/shim/test/test_service_appinfo.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1265802
+-->
+<head>
+ <title>Test for Bug 1265802 - replace Services.appinfo</title>
+ <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css">
+
+ <script type="application/javascript;version=1.8">
+ "use strict";
+ var exports = {};
+ </script>
+
+ <script type="application/javascript;version=1.8"
+ src="resource://devtools/client/shared/shim/Services.js"></script>
+</head>
+<body>
+<script type="application/javascript;version=1.8">
+"use strict";
+
+is(Services.appinfo.OS, SpecialPowers.Services.appinfo.OS,
+ "check that Services.appinfo.OS shim matches platform");
+</script>
+</body>
--- a/devtools/client/shared/telemetry.js
+++ b/devtools/client/shared/telemetry.js
@@ -51,19 +51,17 @@ this.Telemetry = function () {
this.logOncePerBrowserVersion = this.logOncePerBrowserVersion.bind(this);
this.destroy = this.destroy.bind(this);
this._timers = new Map();
};
module.exports = Telemetry;
-var {Cc, Ci, Cu} = require("chrome");
var Services = require("Services");
-var {XPCOMUtils} = Cu.import("resource://gre/modules/XPCOMUtils.jsm", {});
Telemetry.prototype = {
_histograms: {
toolbox: {
histogram: "DEVTOOLS_TOOLBOX_OPENED_COUNT",
userHistogram: "DEVTOOLS_TOOLBOX_OPENED_PER_USER_FLAG",
timerHistogram: "DEVTOOLS_TOOLBOX_TIME_ACTIVE_SECONDS"
},
@@ -92,21 +90,16 @@ Telemetry.prototype = {
userHistogram: "DEVTOOLS_RULEVIEW_OPENED_PER_USER_FLAG",
timerHistogram: "DEVTOOLS_RULEVIEW_TIME_ACTIVE_SECONDS"
},
computedview: {
histogram: "DEVTOOLS_COMPUTEDVIEW_OPENED_COUNT",
userHistogram: "DEVTOOLS_COMPUTEDVIEW_OPENED_PER_USER_FLAG",
timerHistogram: "DEVTOOLS_COMPUTEDVIEW_TIME_ACTIVE_SECONDS"
},
- layoutview: {
- histogram: "DEVTOOLS_LAYOUTVIEW_OPENED_COUNT",
- userHistogram: "DEVTOOLS_LAYOUTVIEW_OPENED_PER_USER_FLAG",
- timerHistogram: "DEVTOOLS_LAYOUTVIEW_TIME_ACTIVE_SECONDS"
- },
fontinspector: {
histogram: "DEVTOOLS_FONTINSPECTOR_OPENED_COUNT",
userHistogram: "DEVTOOLS_FONTINSPECTOR_OPENED_PER_USER_FLAG",
timerHistogram: "DEVTOOLS_FONTINSPECTOR_TIME_ACTIVE_SECONDS"
},
animationinspector: {
histogram: "DEVTOOLS_ANIMATIONINSPECTOR_OPENED_COUNT",
userHistogram: "DEVTOOLS_ANIMATIONINSPECTOR_OPENED_PER_USER_FLAG",
@@ -357,17 +350,17 @@ Telemetry.prototype = {
/**
* Log info about usage once per browser version. This allows us to discover
* how many individual users are using our tools for each browser version.
*
* @param {String} perUserHistogram
* Histogram in which the data is to be stored.
*/
logOncePerBrowserVersion: function (perUserHistogram, value) {
- let currentVersion = appInfo.version;
+ let currentVersion = Services.appinfo.version;
let latest = Services.prefs.getCharPref(TOOLS_OPENED_PREF);
let latestObj = JSON.parse(latest);
let lastVersionHistogramUpdated = latestObj[perUserHistogram];
if (typeof lastVersionHistogramUpdated == "undefined" ||
lastVersionHistogramUpdated !== currentVersion) {
latestObj[perUserHistogram] = currentVersion;
@@ -378,12 +371,8 @@ Telemetry.prototype = {
},
destroy: function () {
for (let histogramId of this._timers.keys()) {
this.stopTimer(histogramId);
}
}
};
-
-XPCOMUtils.defineLazyGetter(this, "appInfo", function () {
- return Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULAppInfo);
-});
--- a/devtools/client/shared/test/browser_telemetry_sidebar.js
+++ b/devtools/client/shared/test/browser_telemetry_sidebar.js
@@ -23,17 +23,17 @@ add_task(function* () {
gBrowser.removeCurrentTab();
});
function* testSidebar(toolbox) {
info("Testing sidebar");
let inspector = toolbox.getCurrentPanel();
let sidebarTools = ["ruleview", "computedview", "fontinspector",
- "layoutview", "animationinspector"];
+ "animationinspector"];
// Concatenate the array with itself so that we can open each tool twice.
sidebarTools.push.apply(sidebarTools, sidebarTools);
return new Promise(resolve => {
// See TOOL_DELAY for why we need setTimeout here
setTimeout(function selectSidebarTab() {
let tool = sidebarTools.pop();
--- a/devtools/client/shared/widgets/AbstractTreeItem.jsm
+++ b/devtools/client/shared/widgets/AbstractTreeItem.jsm
@@ -473,17 +473,58 @@ AbstractTreeItem.prototype = {
* @param number delta
* The offset from this item to the target item.
* @return nsIDOMNode
* The element displaying the target item at the specified offset.
*/
_getSiblingAtDelta: function (delta) {
let childNodes = this._containerNode.childNodes;
let indexOfSelf = Array.indexOf(childNodes, this._targetNode);
- return childNodes[indexOfSelf + delta];
+ if (indexOfSelf + delta >= 0) {
+ return childNodes[indexOfSelf + delta];
+ }
+ return undefined;
+ },
+
+ _getNodesPerPageSize: function() {
+ let childNodes = this._containerNode.childNodes;
+ let nodeHeight = this._getHeight(childNodes[childNodes.length - 1]);
+ let containerHeight = this.bounds.height;
+ return Math.ceil(containerHeight / nodeHeight);
+ },
+
+ _getHeight: function(elem) {
+ let win = this.document.defaultView;
+ let utils = win.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindowUtils);
+ return utils.getBoundsWithoutFlushing(elem).height;
+ },
+
+ /**
+ * Focuses the first item in this tree.
+ */
+ _focusFirstNode: function () {
+ let childNodes = this._containerNode.childNodes;
+ // The root node of the tree may be hidden in practice, so uses for-loop
+ // here to find the next visible node.
+ for (let i = 0; i < childNodes.length; i++) {
+ // The height will be 0 if an element is invisible.
+ if (this._getHeight(childNodes[i])) {
+ childNodes[i].focus();
+ return;
+ }
+ }
+ },
+
+ /**
+ * Focuses the last item in this tree.
+ */
+ _focusLastNode: function () {
+ let childNodes = this._containerNode.childNodes;
+ childNodes[childNodes.length - 1].focus();
},
/**
* Focuses the next item in this tree.
*/
_focusNextNode: function () {
let nextElement = this._getSiblingAtDelta(1);
if (nextElement) nextElement.focus(); // nsIDOMNode
@@ -565,16 +606,46 @@ AbstractTreeItem.prototype = {
case e.DOM_VK_RIGHT:
if (!this._expanded) {
this.expand();
} else {
this._focusNextNode();
}
return;
+
+ case e.DOM_VK_PAGE_UP:
+ let pageUpElement =
+ this._getSiblingAtDelta(-this._getNodesPerPageSize());
+ // There's a chance that the root node is hidden. In this case, its
+ // height will be 0.
+ if (pageUpElement && this._getHeight(pageUpElement)) {
+ pageUpElement.focus();
+ } else {
+ this._focusFirstNode();
+ }
+ return;
+
+ case e.DOM_VK_PAGE_DOWN:
+ let pageDownElement =
+ this._getSiblingAtDelta(this._getNodesPerPageSize());
+ if (pageDownElement) {
+ pageDownElement.focus();
+ } else {
+ this._focusLastNode();
+ }
+ return;
+
+ case e.DOM_VK_HOME:
+ this._focusFirstNode();
+ return;
+
+ case e.DOM_VK_END:
+ this._focusLastNode();
+ return;
}
},
/**
* Handler for the "focus" event on the element displaying this tree item.
*/
_onFocus: function (e) {
this._rootItem.emit("focus", this);
--- a/devtools/client/shared/widgets/FlameGraph.js
+++ b/devtools/client/shared/widgets/FlameGraph.js
@@ -922,17 +922,19 @@ FlameGraph.prototype = {
},
/**
* Listener for the "keydown" event on the graph's container.
*/
_onKeyDown: function (e) {
ViewHelpers.preventScrolling(e);
- if (!this._keysPressed[e.keyCode]) {
+ const hasModifier = e.ctrlKey || e.shiftKey || e.altKey || e.metaKey;
+
+ if (!hasModifier && !this._keysPressed[e.keyCode]) {
this._keysPressed[e.keyCode] = true;
this._userInputStack++;
this._shouldRedraw = true;
}
},
/**
* Listener for the "keyup" event on the graph's container.
--- a/devtools/client/shared/widgets/HTMLTooltip.js
+++ b/devtools/client/shared/widgets/HTMLTooltip.js
@@ -217,20 +217,21 @@ function HTMLTooltip(toolbox, {
EventEmitter.decorate(this);
this.doc = toolbox.doc;
this.type = type;
this.autofocus = autofocus;
this.consumeOutsideClicks = consumeOutsideClicks;
this.useXulWrapper = this._isXUL() && useXulWrapper;
- this._position = null;
+ // The top window is used to attach click event listeners to close the tooltip if the
+ // user clicks on the content page.
+ this.topWindow = this._getTopWindow();
- // Use the topmost window to listen for click events to close the tooltip
- this.topWindow = this.doc.defaultView.top;
+ this._position = null;
this._onClick = this._onClick.bind(this);
this._toggle = new TooltipToggle(this);
this.startTogglingOnHover = this._toggle.start.bind(this._toggle);
this.stopTogglingOnHover = this._toggle.stop.bind(this._toggle);
this.container = this._createContainer();
@@ -377,16 +378,18 @@ HTMLTooltip.prototype = {
this.container.classList.add("tooltip-visible");
// Keep a pointer on the focused element to refocus it when hiding the tooltip.
this._focusedElement = this.doc.activeElement;
this.doc.defaultView.clearTimeout(this.attachEventsTimer);
this.attachEventsTimer = this.doc.defaultView.setTimeout(() => {
this._maybeFocusTooltip();
+ // Updated the top window reference each time in case the host changes.
+ this.topWindow = this._getTopWindow();
this.topWindow.addEventListener("click", this._onClick, true);
this.emit("shown");
}, 0);
}),
/**
* Calculate the rect of the viewport that limits the tooltip dimensions. When using a
* XUL panel wrapper, the viewport will be able to use the whole screen (excluding space
@@ -545,16 +548,20 @@ HTMLTooltip.prototype = {
// http://stackoverflow.com/questions/1599660/which-html-elements-can-receive-focus .
let focusableSelector = "a, button, iframe, input, select, textarea";
let focusableElement = this.panel.querySelector(focusableSelector);
if (this.autofocus && focusableElement) {
focusableElement.focus();
}
},
+ _getTopWindow: function () {
+ return this.doc.defaultView.top;
+ },
+
/**
* Check if the tooltip's owner document is a XUL document.
*/
_isXUL: function () {
return this.doc.documentElement.namespaceURI === XUL_NS;
},
_createXulPanelWrapper: function () {
--- a/devtools/client/themes/computed.css
+++ b/devtools/client/themes/computed.css
@@ -27,21 +27,27 @@
margin-right: 5px;
/* Vertically center the 'Browser styles' checkbox in the
Computed panel with its label. */
display: flex;
align-items: center;
}
+#computedview-container {
+ overflow: auto;
+}
+
#propertyContainer {
-moz-user-select: text;
overflow-y: auto;
overflow-x: hidden;
flex: auto;
+ border-top-width: 1px;
+ border-top-style: dotted;
}
.row-striped {
background: var(--theme-body-background);
}
.property-view-hidden,
.property-content-hidden {
@@ -54,17 +60,19 @@
flex-wrap: wrap;
}
.property-name-container {
width: 202px;
}
.property-value-container {
- width: 168px;
+ display: flex;
+ flex: 1 1 168px;
+ overflow: hidden;
}
.property-name-container > *,
.property-value-container > * {
display: inline-block;
vertical-align: middle;
}
--- a/devtools/client/themes/firebug-theme.css
+++ b/devtools/client/themes/firebug-theme.css
@@ -15,18 +15,20 @@
/* Remove filters on firebug specific images */
.theme-firebug .devtools-tabbar .devtools-button::before,
.theme-firebug .devtools-option-toolbarbutton > image,
.theme-firebug .command-button-invertable::before,
.theme-firebug #sources-toolbar image,
.theme-firebug [id$="pane-toggle"] > image,
.theme-firebug [id$="pane-toggle"]::before,
-.theme-firebug #global-toolbar .devtools-button::before,
+.theme-firebug .sidebar-toggle::before,
.theme-firebug #element-picker::before,
+.theme-firebug #rewind-timeline::before,
+.theme-firebug #pause-resume-timeline::before,
.theme-firebug #debugger-controls .toolbarbutton-icon,
.theme-firebug #filter-button .toolbarbutton-icon {
filter: none !important;
}
/* CodeMirror Color Syntax */
.theme-firebug .cm-keyword {color: BlueViolet; font-weight: bold;}
--- a/devtools/client/themes/layout.css
+++ b/devtools/client/themes/layout.css
@@ -1,72 +1,40 @@
/* 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/ */
-#sidebar-panel-layoutview {
- display: block;
- overflow: auto;
- height: 100%;
-}
-
-#layout-wrapper {
- /* The sidebar-panel is not focusable, this wrapper will catch click events in
- all the empty area around the layout-container */
- height: 100%;
-}
-
#layout-container {
/* The view will grow bigger as the window gets resized, until 400px */
max-width: 400px;
margin: 0px auto;
padding: 0;
- /* "Contain" the absolutely positioned #layout-main element */
- position: relative;
-}
-
-/* Header: contains the position and size of the element */
-
-#layout-header {
- box-sizing: border-box;
- width: 100%;
- padding: 4px 14px;
- display: -moz-box;
- vertical-align: top;
-}
-
-#layout-header:dir(rtl) {
- -moz-box-direction: reverse;
}
-#layout-header > span {
- display: -moz-box;
-}
+/* Header */
-#layout-element-size {
- -moz-box-flex: 1;
+#layout-header,
+#layout-info {
+ display: flex;
+ align-items: center;
+ padding: 4px 17px;
}
-#layout-element-size:dir(rtl) {
- -moz-box-pack: end;
+#layout-geometry-editor {
+ visibility: hidden;
}
-@media (max-height: 250px) {
- #layout-header {
- padding-top: 0;
- padding-bottom: 0;
- margin-top: 10px;
- margin-bottom: 8px;
- }
+#layout-geometry-editor::before {
+ background: url(images/geometry-editor.svg) no-repeat center center / 16px 16px;
}
/* Main: contains the box-model regions */
#layout-main {
- position: absolute;
+ position: relative;
box-sizing: border-box;
/* The regions are semi-transparent, so the white background is partly
visible */
background-color: white;
color: var(--theme-selection-color);
/* Make sure there is some space between the window's edges and the regions */
margin: 0 14px 10px 14px;
width: calc(100% - 2 * 14px);
@@ -311,17 +279,17 @@
left: 16px;
}
.layout-border.layout-right {
right: 17px;
}
}
-/* Legend, displayed inside regions */
+/* Legend: displayed inside regions */
.layout-legend {
position: absolute;
margin: 5px 6px;
z-index: 1;
}
.layout-legend[data-box="margin"] {
@@ -351,28 +319,18 @@
}
/* Make sure the content size doesn't appear as editable like the other sizes */
.layout-size > span {
cursor: default;
}
-/* Hide all values when the view is inactive */
+/* Layout info: contains the position and size of the element */
-#layout-container.inactive > #layout-header > #layout-element-position,
-#layout-container.inactive > #layout-header > #layout-element-size,
-#layout-container.inactive > #layout-main > p {
- visibility: hidden;
+#layout-element-size {
+ flex: 1;
}
#layout-position-group {
display: flex;
align-items: center;
}
-
-#layout-geometry-editor {
- visibility: hidden;
-}
-
-#layout-geometry-editor::before {
- background: url(images/geometry-editor.svg) no-repeat center center / 16px 16px;
-}
--- a/devtools/client/themes/rules.css
+++ b/devtools/client/themes/rules.css
@@ -40,17 +40,16 @@
#ruleview-toolbar-container {
display: flex;
flex-direction: column;
height: auto;
}
#ruleview-toolbar {
display: flex;
- height: 23px;
}
#ruleview-toolbar > .devtools-searchbox:first-child {
padding-inline-start: 0px;
}
#ruleview-command-toolbar {
display: flex;
@@ -251,21 +250,19 @@
.theme-firebug .ruleview-overridden .ruleview-propertyname,
.theme-firebug .ruleview-overridden .ruleview-propertyvalue {
text-decoration: line-through;
}
.theme-firebug .ruleview-enableproperty:not([checked]) ~ .ruleview-namecontainer,
.theme-firebug .ruleview-enableproperty:not([checked]) ~ .ruleview-namecontainer *,
.theme-firebug .ruleview-enableproperty:not([checked]) ~ .ruleview-propertyvaluecontainer,
-.theme-firebug .ruleview-enableproperty:not([checked]) ~ .ruleview-propertyvaluecontainer * {
- color: #CCCCCC;
-}
-
-.theme-firebug .ruleview-enableproperty:not([checked]) ~ .ruleview-computedlist * {
+.theme-firebug .ruleview-enableproperty:not([checked]) ~ .ruleview-propertyvaluecontainer *,
+.theme-firebug .ruleview-overridden > * > .ruleview-computed:not(.ruleview-overridden),
+.theme-firebug .ruleview-overridden > * > .ruleview-computed:not(.ruleview-overridden) * {
color: #CCCCCC;
}
.ruleview-rule + .ruleview-rule {
border-top-width: 1px;
border-top-style: dotted;
}
--- a/devtools/client/themes/toolbars.css
+++ b/devtools/client/themes/toolbars.css
@@ -385,16 +385,17 @@
.devtools-filterinput .textbox-input::-moz-placeholder {
font-style: normal;
}
/* Searchbox is a div container element for a search input element */
.devtools-searchbox {
display: flex;
flex: 1;
+ height: 23px;
position: relative;
padding: 0 3px;
}
/* The spacing is accomplished with a padding on the searchbox */
.devtools-searchbox > .devtools-textinput,
.devtools-searchbox > .devtools-searchinput,
.devtools-searchbox > .devtools-filterinput {
--- a/devtools/server/actors/inspector.js
+++ b/devtools/server/actors/inspector.js
@@ -71,16 +71,17 @@ const {
isAnonymous,
isNativeAnonymous,
isXBLAnonymous,
isShadowAnonymous,
getFrameElement
} = require("devtools/shared/layout/utils");
const {getLayoutChangesObserver, releaseLayoutChangesObserver} =
require("devtools/server/actors/layout");
+const nodeFilterConstants = require("devtools/shared/dom-node-filter-constants");
loader.lazyRequireGetter(this, "CSS", "CSS");
const {EventParsers} = require("devtools/shared/event-parsers");
const {nodeSpec, nodeListSpec, walkerSpec, inspectorSpec} = require("devtools/shared/specs/inspector");
const FONT_FAMILY_PREVIEW_TEXT = "The quick brown fox jumps over the lazy dog";
const FONT_FAMILY_PREVIEW_TEXT_SIZE = 20;
@@ -2763,23 +2764,23 @@ function isNodeDead(node) {
}
/**
* Wrapper for inDeepTreeWalker. Adds filtering to the traversal methods.
* See inDeepTreeWalker for more information about the methods.
*
* @param {DOMNode} node
* @param {Window} rootWin
- * @param {Int} whatToShow See Ci.nsIDOMNodeFilter / inIDeepTreeWalker for
+ * @param {Int} whatToShow See nodeFilterConstants / inIDeepTreeWalker for
* options.
* @param {Function} filter A custom filter function Taking in a DOMNode
* and returning an Int. See WalkerActor.nodeFilter for an example.
*/
function DocumentWalker(node, rootWin,
- whatToShow = Ci.nsIDOMNodeFilter.SHOW_ALL,
+ whatToShow = nodeFilterConstants.SHOW_ALL,
filter = standardTreeWalkerFilter) {
if (!rootWin.location) {
throw new Error("Got an invalid root window in DocumentWalker");
}
this.walker = Cc["@mozilla.org/inspector/deep-tree-walker;1"]
.createInstance(Ci.inIDeepTreeWalker);
this.walker.showAnonymousContent = true;
@@ -2788,17 +2789,17 @@ function DocumentWalker(node, rootWin,
this.walker.init(rootWin.document, whatToShow);
this.filter = filter;
// Make sure that the walker knows about the initial node (which could
// be skipped due to a filter). Note that simply calling parentNode()
// causes currentNode to be updated.
this.walker.currentNode = node;
while (node &&
- this.filter(node) === Ci.nsIDOMNodeFilter.FILTER_SKIP) {
+ this.filter(node) === nodeFilterConstants.FILTER_SKIP) {
node = this.walker.parentNode();
}
}
DocumentWalker.prototype = {
get node() {
return this.walker.node;
},
@@ -2819,64 +2820,64 @@ DocumentWalker.prototype = {
nextNode: function () {
let node = this.walker.currentNode;
if (!node) {
return null;
}
let nextNode = this.walker.nextNode();
while (nextNode &&
- this.filter(nextNode) === Ci.nsIDOMNodeFilter.FILTER_SKIP) {
+ this.filter(nextNode) === nodeFilterConstants.FILTER_SKIP) {
nextNode = this.walker.nextNode();
}
return nextNode;
},
firstChild: function () {
let node = this.walker.currentNode;
if (!node) {
return null;
}
let firstChild = this.walker.firstChild();
while (firstChild &&
- this.filter(firstChild) === Ci.nsIDOMNodeFilter.FILTER_SKIP) {
+ this.filter(firstChild) === nodeFilterConstants.FILTER_SKIP) {
firstChild = this.walker.nextSibling();
}
return firstChild;
},
lastChild: function () {
let node = this.walker.currentNode;
if (!node) {
return null;
}
let lastChild = this.walker.lastChild();
while (lastChild &&
- this.filter(lastChild) === Ci.nsIDOMNodeFilter.FILTER_SKIP) {
+ this.filter(lastChild) === nodeFilterConstants.FILTER_SKIP) {
lastChild = this.walker.previousSibling();
}
return lastChild;
},
previousSibling: function () {
let node = this.walker.previousSibling();
- while (node && this.filter(node) === Ci.nsIDOMNodeFilter.FILTER_SKIP) {
+ while (node && this.filter(node) === nodeFilterConstants.FILTER_SKIP) {
node = this.walker.previousSibling();
}
return node;
},
nextSibling: function () {
let node = this.walker.nextSibling();
- while (node && this.filter(node) === Ci.nsIDOMNodeFilter.FILTER_SKIP) {
+ while (node && this.filter(node) === nodeFilterConstants.FILTER_SKIP) {
node = this.walker.nextSibling();
}
return node;
}
};
function isInXULDocument(el) {
let doc = nodeDocument(el);
@@ -2890,49 +2891,49 @@ function isInXULDocument(el) {
* content with the exception of ::before and ::after and anonymous content
* in XUL document (needed to show all elements in the browser toolbox).
*/
function standardTreeWalkerFilter(node) {
// ::before and ::after are native anonymous content, but we always
// want to show them
if (node.nodeName === "_moz_generated_content_before" ||
node.nodeName === "_moz_generated_content_after") {
- return Ci.nsIDOMNodeFilter.FILTER_ACCEPT;
+ return nodeFilterConstants.FILTER_ACCEPT;
}
// Ignore empty whitespace text nodes.
if (node.nodeType == Ci.nsIDOMNode.TEXT_NODE &&
!/[^\s]/.exec(node.nodeValue)) {
- return Ci.nsIDOMNodeFilter.FILTER_SKIP;
+ return nodeFilterConstants.FILTER_SKIP;
}
// Ignore all native and XBL anonymous content inside a non-XUL document
if (!isInXULDocument(node) && (isXBLAnonymous(node) ||
isNativeAnonymous(node))) {
// Note: this will skip inspecting the contents of feedSubscribeLine since
// that's XUL content injected in an HTML document, but we need to because
// this also skips many other elements that need to be skipped - like form
// controls, scrollbars, video controls, etc (see bug 1187482).
- return Ci.nsIDOMNodeFilter.FILTER_SKIP;
+ return nodeFilterConstants.FILTER_SKIP;
}
- return Ci.nsIDOMNodeFilter.FILTER_ACCEPT;
+ return nodeFilterConstants.FILTER_ACCEPT;
}
/**
* This DeepTreeWalker filter is like standardTreeWalkerFilter except that
* it also includes all anonymous content (like internal form controls).
*/
function allAnonymousContentTreeWalkerFilter(node) {
// Ignore empty whitespace text nodes.
if (node.nodeType == Ci.nsIDOMNode.TEXT_NODE &&
!/[^\s]/.exec(node.nodeValue)) {
- return Ci.nsIDOMNodeFilter.FILTER_SKIP;
+ return nodeFilterConstants.FILTER_SKIP;
}
- return Ci.nsIDOMNodeFilter.FILTER_ACCEPT;
+ return nodeFilterConstants.FILTER_ACCEPT;
}
/**
* Returns a promise that is settled once the given HTMLImageElement has
* finished loading.
*
* @param {HTMLImageElement} image - The image element.
* @param {Number} timeout - Maximum amount of time the image is allowed to load
--- a/devtools/server/tests/mochitest/test_inspector-anonymous.html
+++ b/devtools/server/tests/mochitest/test_inspector-anonymous.html
@@ -7,21 +7,22 @@ https://bugzilla.mozilla.org/show_bug.cg
<meta charset="utf-8">
<title>Test for Bug 777674</title>
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
<script type="application/javascript;version=1.8" src="inspector-helpers.js"></script>
<script type="application/javascript;version=1.8">
window.onload = function() {
- const Ci = Components.interfaces;
const {InspectorFront} =
require("devtools/shared/fronts/inspector");
const {_documentWalker} =
require("devtools/server/actors/inspector");
+ const nodeFilterConstants =
+ require("devtools/shared/dom-node-filter-constants");
const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
SpecialPowers.pushPrefEnv({"set": [
["dom.webcomponents.enabled", true]
]});
SimpleTest.waitForExplicitFinish();
let gWalker = null;
@@ -71,19 +72,19 @@ window.onload = function() {
addAsyncTest(function* testNativeAnonymousStartingNode() {
info ("Tests attaching an element that a walker can't see.");
let serverConnection = gWalker.conn._transport._serverConnection;
let serverWalker = serverConnection.getActor(gWalker.actorID);
let docwalker = new _documentWalker(
gInspectee.querySelector("select"),
gInspectee.defaultView,
- Ci.nsIDOMNodeFilter.SHOW_ALL,
+ nodeFilterConstants.SHOW_ALL,
() => {
- return Ci.nsIDOMNodeFilter.FILTER_ACCEPT
+ return nodeFilterConstants.FILTER_ACCEPT
}
);
let scrollbar = docwalker.lastChild();
is (scrollbar.tagName, "scrollbar", "An anonymous child has been fetched");
let node = yield serverWalker.attachElement(scrollbar);
ok (node, "A response has arrived");
new file mode 100644
--- /dev/null
+++ b/devtools/shared/dom-node-filter-constants.js
@@ -0,0 +1,21 @@
+"use strict";
+
+module.exports = {
+ FILTER_ACCEPT: 1,
+ FILTER_REJECT: 2,
+ FILTER_SKIP: 3,
+
+ SHOW_ALL: 0xFFFFFFFF,
+ SHOW_ELEMENT: 0x00000001,
+ SHOW_ATTRIBUTE: 0x00000002,
+ SHOW_TEXT: 0x00000004,
+ SHOW_CDATA_SECTION: 0x00000008,
+ SHOW_ENTITY_REFERENCE: 0x00000010,
+ SHOW_ENTITY: 0x00000020,
+ SHOW_PROCESSING_INSTRUCTION: 0x00000040,
+ SHOW_COMMENT: 0x00000080,
+ SHOW_DOCUMENT: 0x00000100,
+ SHOW_DOCUMENT_TYPE: 0x00000200,
+ SHOW_DOCUMENT_FRAGMENT: 0x00000400,
+ SHOW_NOTATION: 0x00000800
+};
--- a/devtools/shared/inspector/css-logic.js
+++ b/devtools/shared/inspector/css-logic.js
@@ -35,17 +35,16 @@
* CssLogic uses the standard DOM API, and the Gecko inIDOMUtils API to access
* styling information in the page, and present this to the user in a way that
* helps them understand:
* - why their expectations may not have been fulfilled
* - how browsers process CSS
* @constructor
*/
-const { Cc, Ci } = require("chrome");
const Services = require("Services");
// This should be ok because none of the functions that use this should be used
// on the worker thread, where Cu is not available.
loader.lazyRequireGetter(this, "CSS", "CSS");
loader.lazyRequireGetter(this, "CSSLexer", "devtools/shared/css-lexer");
@@ -139,17 +138,17 @@ const TAB_CHARS = "\t";
* Prettify minified CSS text.
* This prettifies CSS code where there is no indentation in usual places while
* keeping original indentation as-is elsewhere.
* @param string text The CSS source to prettify.
* @return string Prettified CSS source
*/
function prettifyCSS(text, ruleCount) {
if (prettifyCSS.LINE_SEPARATOR == null) {
- let os = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime).OS;
+ let os = Services.appinfo.OS;
prettifyCSS.LINE_SEPARATOR = (os === "WINNT" ? "\r\n" : "\n");
}
// remove initial and terminating HTML comments and surrounding whitespace
text = text.replace(/(?:^\s*<!--[\r\n]*)|(?:\s*-->\s*$)/g, "");
let originalText = text;
text = text.trim();
--- a/devtools/shared/layout/utils.js
+++ b/devtools/shared/layout/utils.js
@@ -1,16 +1,17 @@
/* 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 { Ci, Cc } = require("chrome");
const { memoize } = require("sdk/lang/functional");
+const nodeFilterConstants = require("devtools/shared/dom-node-filter-constants");
loader.lazyRequireGetter(this, "setIgnoreLayoutChanges", "devtools/server/actors/layout", true);
exports.setIgnoreLayoutChanges = (...args) =>
this.setIgnoreLayoutChanges(...args);
/**
* Returns the `DOMWindowUtils` for the window given.
*
@@ -372,17 +373,17 @@ function safelyGetContentWindow(frame) {
if (frame.contentWindow) {
return frame.contentWindow;
}
let walker = Cc["@mozilla.org/inspector/deep-tree-walker;1"]
.createInstance(Ci.inIDeepTreeWalker);
walker.showSubDocuments = true;
walker.showDocumentsAsNodes = true;
- walker.init(frame, Ci.nsIDOMNodeFilter.SHOW_ALL);
+ walker.init(frame, nodeFilterConstants.SHOW_ALL);
walker.currentNode = frame;
let document = walker.nextNode();
if (!document || !document.defaultView) {
throw new Error("Couldn't get the content window inside frame " + frame);
}
return document.defaultView;
--- a/devtools/shared/moz.build
+++ b/devtools/shared/moz.build
@@ -44,16 +44,17 @@ DevToolsModules(
'content-observer.js',
'css-lexer.js',
'css-parsing-utils.js',
'css-properties-db.js',
'defer.js',
'deprecated-sync-thenables.js',
'DevToolsUtils.js',
'dom-node-constants.js',
+ 'dom-node-filter-constants.js',
'event-emitter.js',
'event-parsers.js',
'indentation.js',
'Loader.jsm',
'Parser.jsm',
'path.js',
'protocol.js',
'system.js',
--- a/devtools/shared/system.js
+++ b/devtools/shared/system.js
@@ -326,15 +326,14 @@ function getSetting(name) {
handleError: (error) => deferred.reject(error),
});
} else {
deferred.reject(new Error("No settings service"));
}
return deferred.promise;
}
-exports.is64Bit = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULAppInfo).is64Bit;
exports.getSystemInfo = Task.async(getSystemInfo);
exports.getAppIniString = getAppIniString;
exports.getSetting = getSetting;
exports.getScreenDimensions = getScreenDimensions;
exports.getOSCPU = getOSCPU;
exports.constants = AppConstants;
--- a/services/sync/modules/engines/clients.js
+++ b/services/sync/modules/engines/clients.js
@@ -71,32 +71,40 @@ ClientEngine.prototype = {
get lastRecordUpload() {
return Svc.Prefs.get(this.name + ".lastRecordUpload", 0);
},
set lastRecordUpload(value) {
Svc.Prefs.set(this.name + ".lastRecordUpload", Math.floor(value));
},
get remoteClients() {
- return Object.values(this._store._remoteClients);
+ // return all non-stale clients for external consumption.
+ return Object.values(this._store._remoteClients).filter(v => !v.stale);
+ },
+
+ remoteClientExists(id) {
+ let client = this._store._remoteClients[id];
+ return !!(client && !client.stale);
},
// Aggregate some stats on the composition of clients on this account
get stats() {
let stats = {
hasMobile: this.localType == DEVICE_TYPE_MOBILE,
names: [this.localName],
numClients: 1,
};
for (let id in this._store._remoteClients) {
- let {name, type} = this._store._remoteClients[id];
- stats.hasMobile = stats.hasMobile || type == DEVICE_TYPE_MOBILE;
- stats.names.push(name);
- stats.numClients++;
+ let {name, type, stale} = this._store._remoteClients[id];
+ if (!stale) {
+ stats.hasMobile = stats.hasMobile || type == DEVICE_TYPE_MOBILE;
+ stats.names.push(name);
+ stats.numClients++;
+ }
}
return stats;
},
/**
* Obtain information about device types.
*
@@ -104,16 +112,19 @@ ClientEngine.prototype = {
*/
get deviceTypes() {
let counts = new Map();
counts.set(this.localType, 1);
for (let id in this._store._remoteClients) {
let record = this._store._remoteClients[id];
+ if (record.stale) {
+ continue; // pretend "stale" records don't exist.
+ }
let type = record.type;
if (!counts.has(type)) {
counts.set(type, 0);
}
counts.set(type, counts.get(type) + 1);
}
@@ -152,20 +163,16 @@ ClientEngine.prototype = {
get localType() {
return Utils.getDeviceType();
},
set localType(value) {
Svc.Prefs.set("client.type", value);
},
- remoteClientExists(id) {
- return !!this._store._remoteClients[id];
- },
-
getClientName(id) {
if (id == this.localID) {
return this.localName;
}
let client = this._store._remoteClients[id];
return client ? client.name : "";
},
@@ -197,30 +204,32 @@ ClientEngine.prototype = {
// tabs collection, since showing their list of tabs is confusing.
for (let id in this._store._remoteClients) {
if (!this._incomingClients[id]) {
this._log.info(`Removing local state for deleted client ${id}`);
this._removeRemoteClient(id);
}
}
// Bug 1264498: Mobile clients don't remove themselves from the clients
- // collection when the user disconnects Sync, so we filter out clients
+ // collection when the user disconnects Sync, so we mark as stale clients
// with the same name that haven't synced in over a week.
+ // (Note we can't simply delete them, or we re-apply them next sync - see
+ // bug 1287687)
delete this._incomingClients[this.localID];
let names = new Set([this.localName]);
for (let id in this._incomingClients) {
let record = this._store._remoteClients[id];
if (!names.has(record.name)) {
names.add(record.name);
continue;
}
let remoteAge = AsyncResource.serverTime - this._incomingClients[id];
if (remoteAge > STALE_CLIENT_REMOTE_AGE) {
this._log.info(`Hiding stale client ${id} with age ${remoteAge}`);
- this._removeRemoteClient(id);
+ record.stale = true;
}
}
} finally {
this._incomingClients = null;
}
},
_uploadOutgoing() {
@@ -340,16 +349,19 @@ ClientEngine.prototype = {
*/
_sendCommandToClient: function sendCommandToClient(command, args, clientId) {
this._log.trace("Sending " + command + " to " + clientId);
let client = this._store._remoteClients[clientId];
if (!client) {
throw new Error("Unknown remote client ID: '" + clientId + "'.");
}
+ if (client.stale) {
+ throw new Error("Stale remote client ID: '" + clientId + "'.");
+ }
let action = {
command: command,
args: args,
};
if (!client.commands) {
client.commands = [action];
@@ -449,18 +461,20 @@ ClientEngine.prototype = {
this._log.error("Expected " + commandData.args + " args for '" +
command + "', but got " + args);
return;
}
if (clientId) {
this._sendCommandToClient(command, args, clientId);
} else {
- for (let id in this._store._remoteClients) {
- this._sendCommandToClient(command, args, id);
+ for (let [id, record] in Iterator(this._store._remoteClients)) {
+ if (!record.stale) {
+ this._sendCommandToClient(command, args, id);
+ }
}
}
},
/**
* Send a URI to another client for display.
*
* A side effect is the score is increased dramatically to incur an
@@ -604,16 +618,22 @@ ClientStore.prototype = {
record.appPackage = Services.appinfo.ID;
record.application = this.engine.brandName // "Nightly"
// We can't compute these yet.
// record.device = ""; // Bug 1100723
// record.formfactor = ""; // Bug 1100722
} else {
record.cleartext = this._remoteClients[id];
+ if (record.cleartext.stale) {
+ // It's almost certainly a logic error for us to upload a record we
+ // consider stale, so make log noise, but still remove the flag.
+ this._log.error(`Preparing to upload record ${id} that we consider stale`);
+ delete record.cleartext.stale;
+ }
}
return record;
},
itemExists(id) {
return id in this.getAllIDs();
},
--- a/services/sync/tests/unit/test_clients_engine.js
+++ b/services/sync/tests/unit/test_clients_engine.js
@@ -563,19 +563,51 @@ add_test(function test_filter_duplicate_
_("First sync");
strictEqual(engine.lastRecordUpload, 0);
engine._sync();
ok(engine.lastRecordUpload > 0);
deepEqual(user.collection("clients").keys().sort(),
[recentID, dupeID, oldID, engine.localID].sort(),
"Our record should be uploaded on first sync");
+
deepEqual(Object.keys(store.getAllIDs()).sort(),
- [recentID, oldID, engine.localID].sort(),
- "Fresh clients should be downloaded on first sync");
+ [recentID, dupeID, oldID, engine.localID].sort(),
+ "Duplicate ID should remain in getAllIDs");
+ ok(engine._store.itemExists(dupeID), "Dupe ID should be considered as existing for Sync methods.");
+ ok(!engine.remoteClientExists(dupeID), "Dupe ID should not be considered as existing for external methods.");
+
+ // dupe desktop should not appear in .deviceTypes.
+ equal(engine.deviceTypes.get("desktop"), 2);
+ equal(engine.deviceTypes.get("mobile"), 1);
+
+ // dupe desktop should not appear in stats
+ deepEqual(engine.stats, {
+ hasMobile: 1,
+ names: [engine.localName, "My Phone", "My old desktop"],
+ numClients: 3,
+ });
+
+ ok(engine.remoteClientExists(oldID), "non-dupe ID should exist.");
+ ok(!engine.remoteClientExists(dupeID), "dupe ID should not exist");
+ equal(engine.remoteClients.length, 2, "dupe should not be in remoteClients");
+
+ // Check that a subsequent Sync doesn't report anything as being processed.
+ let counts;
+ Svc.Obs.add("weave:engine:sync:applied", function observe(subject, data) {
+ Svc.Obs.remove("weave:engine:sync:applied", observe);
+ counts = subject;
+ });
+
+ engine._sync();
+ equal(counts.applied, 0); // We didn't report applying any records.
+ equal(counts.reconciled, 4); // We reported reconcilliation for all records
+ equal(counts.succeeded, 0);
+ equal(counts.failed, 0);
+ equal(counts.newFailed, 0);
_("Broadcast logout to all clients");
engine.sendCommand("logout", []);
engine._sync();
let collection = server.getCollection("foo", "clients");
let recentPayload = JSON.parse(JSON.parse(collection.payload(recentID)).ciphertext);
deepEqual(recentPayload.commands, [{ command: "logout", args: [] }],
@@ -600,16 +632,32 @@ add_test(function test_filter_duplicate_
}), now - 10));
_("Second sync.");
engine._sync();
deepEqual(Object.keys(store.getAllIDs()).sort(),
[recentID, oldID, dupeID, engine.localID].sort(),
"Stale client synced, so it should no longer be marked as a dupe");
+
+ ok(engine.remoteClientExists(dupeID), "Dupe ID should appear as it synced.");
+
+ // Recently synced dupe desktop should appear in .deviceTypes.
+ equal(engine.deviceTypes.get("desktop"), 3);
+
+ // Recently synced dupe desktop should now appear in stats
+ deepEqual(engine.stats, {
+ hasMobile: 1,
+ names: [engine.localName, "My Phone", engine.localName, "My old desktop"],
+ numClients: 4,
+ });
+
+ ok(engine.remoteClientExists(dupeID), "recently synced dupe ID should now exist");
+ equal(engine.remoteClients.length, 3, "recently synced dupe should now be in remoteClients");
+
} finally {
Svc.Prefs.resetBranch("");
Service.recordManager.clearCache();
try {
server.deleteCollections("foo");
} finally {
server.stop(run_next_test);
--- a/testing/web-platform/meta/html/semantics/forms/textfieldselection/selection.html.ini
+++ b/testing/web-platform/meta/html/semantics/forms/textfieldselection/selection.html.ini
@@ -1,14 +1,17 @@
[selection.html]
type: testharness
[test SelectionStart offset for input]
expected: FAIL
+ bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1287655
[test SelectionStart offset for textarea]
expected: FAIL
+ bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1287655
[test SelectionEnd offset for input]
expected: FAIL
+ bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1287655
[test SelectionEnd offset for textarea]
expected: FAIL
-
+ bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1287655
--- a/toolkit/components/telemetry/Histograms.json
+++ b/toolkit/components/telemetry/Histograms.json
@@ -6316,24 +6316,16 @@
"DEVTOOLS_COMPUTEDVIEW_OPENED_COUNT": {
"alert_emails": ["dev-developer-tools@lists.mozilla.org"],
"expires_in_version": "never",
"kind": "count",
"bug_numbers": [1247985],
"description": "Number of times the DevTools Computed View has been opened.",
"releaseChannelCollection": "opt-out"
},
- "DEVTOOLS_LAYOUTVIEW_OPENED_COUNT": {
- "alert_emails": ["dev-developer-tools@lists.mozilla.org"],
- "expires_in_version": "never",
- "kind": "count",
- "bug_numbers": [1247985],
- "description": "Number of times the DevTools Layout View has been opened.",
- "releaseChannelCollection": "opt-out"
- },
"DEVTOOLS_FONTINSPECTOR_OPENED_COUNT": {
"alert_emails": ["dev-developer-tools@lists.mozilla.org"],
"expires_in_version": "never",
"kind": "count",
"bug_numbers": [1247985],
"description": "Number of times the DevTools Font Inspector has been opened.",
"releaseChannelCollection": "opt-out"
},
@@ -6597,21 +6589,16 @@
"kind": "flag",
"description": "Number of users that have opened the DevTools Rule View."
},
"DEVTOOLS_COMPUTEDVIEW_OPENED_PER_USER_FLAG": {
"expires_in_version": "never",
"kind": "flag",
"description": "Number of users that have opened the DevTools Computed View."
},
- "DEVTOOLS_LAYOUTVIEW_OPENED_PER_USER_FLAG": {
- "expires_in_version": "never",
- "kind": "flag",
- "description": "Number of users that have opened the DevTools Layout View."
- },
"DEVTOOLS_FONTINSPECTOR_OPENED_PER_USER_FLAG": {
"expires_in_version": "never",
"kind": "flag",
"description": "Number of users that have opened the DevTools Font Inspector."
},
"DEVTOOLS_ANIMATIONINSPECTOR_OPENED_PER_USER_FLAG": {
"expires_in_version": "never",
"kind": "flag",
@@ -6808,23 +6795,16 @@
},
"DEVTOOLS_COMPUTEDVIEW_TIME_ACTIVE_SECONDS": {
"expires_in_version": "never",
"kind": "exponential",
"high": 10000000,
"n_buckets": 100,
"description": "How long has the computed view been active (seconds)"
},
- "DEVTOOLS_LAYOUTVIEW_TIME_ACTIVE_SECONDS": {
- "expires_in_version": "never",
- "kind": "exponential",
- "high": 10000000,
- "n_buckets": 100,
- "description": "How long has the layout view been active (seconds)"
- },
"DEVTOOLS_FONTINSPECTOR_TIME_ACTIVE_SECONDS": {
"expires_in_version": "never",
"kind": "exponential",
"high": 10000000,
"n_buckets": 100,
"description": "How long has the font inspector been active (seconds)"
},
"DEVTOOLS_ANIMATIONINSPECTOR_TIME_ACTIVE_SECONDS": {
--- a/toolkit/components/url-classifier/SafeBrowsing.jsm
+++ b/toolkit/components/url-classifier/SafeBrowsing.jsm
@@ -41,35 +41,38 @@ function getLists(prefName) {
if (!pref) {
return [];
}
return pref.split(",")
.filter(function(value) { return value.indexOf("test-") == -1; })
.map(function(value) { return value.trim(); });
}
-// These may be a comma-separated lists of tables.
-const phishingLists = getLists("urlclassifier.phishTable");
-const malwareLists = getLists("urlclassifier.malwareTable");
-const downloadBlockLists = getLists("urlclassifier.downloadBlockTable");
-const downloadAllowLists = getLists("urlclassifier.downloadAllowTable");
-const trackingProtectionLists = getLists("urlclassifier.trackingTable");
-const trackingProtectionWhitelists = getLists("urlclassifier.trackingWhitelistTable");
-const blockedLists = getLists("urlclassifier.blockedTable");
+const tablePreferences = [
+ "urlclassifier.phishTable",
+ "urlclassifier.malwareTable",
+ "urlclassifier.downloadBlockTable",
+ "urlclassifier.downloadAllowTable",
+ "urlclassifier.trackingTable",
+ "urlclassifier.trackingWhitelistTable",
+ "urlclassifier.blockedTable"
+];
this.SafeBrowsing = {
init: function() {
if (this.initialized) {
log("Already initialized");
return;
}
Services.prefs.addObserver("browser.safebrowsing", this.readPrefs.bind(this), false);
Services.prefs.addObserver("privacy.trackingprotection", this.readPrefs.bind(this), false);
+ Services.prefs.addObserver("urlclassifier", this.readPrefs.bind(this), false);
+
this.readPrefs();
this.addMozEntries();
this.controlUpdateChecking();
this.initialized = true;
log("init() finished");
},
@@ -86,46 +89,54 @@ this.SafeBrowsing = {
log("Check browser.safebrowsing.provider.[google/mozilla].lists");
return;
}
listManager.registerTable(listname, providerName, provider.updateURL, provider.gethashURL);
},
registerTables: function() {
- for (let i = 0; i < phishingLists.length; ++i) {
- this.registerTableWithURLs(phishingLists[i]);
+ for (let i = 0; i < this.phishingLists.length; ++i) {
+ this.registerTableWithURLs(this.phishingLists[i]);
}
- for (let i = 0; i < malwareLists.length; ++i) {
- this.registerTableWithURLs(malwareLists[i]);
+ for (let i = 0; i < this.malwareLists.length; ++i) {
+ this.registerTableWithURLs(this.malwareLists[i]);
}
- for (let i = 0; i < downloadBlockLists.length; ++i) {
- this.registerTableWithURLs(downloadBlockLists[i]);
+ for (let i = 0; i < this.downloadBlockLists.length; ++i) {
+ this.registerTableWithURLs(this.downloadBlockLists[i]);
}
- for (let i = 0; i < downloadAllowLists.length; ++i) {
- this.registerTableWithURLs(downloadAllowLists[i]);
+ for (let i = 0; i < this.downloadAllowLists.length; ++i) {
+ this.registerTableWithURLs(this.downloadAllowLists[i]);
}
- for (let i = 0; i < trackingProtectionLists.length; ++i) {
- this.registerTableWithURLs(trackingProtectionLists[i]);
+ for (let i = 0; i < this.trackingProtectionLists.length; ++i) {
+ this.registerTableWithURLs(this.trackingProtectionLists[i]);
}
- for (let i = 0; i < trackingProtectionWhitelists.length; ++i) {
- this.registerTableWithURLs(trackingProtectionWhitelists[i]);
+ for (let i = 0; i < this.trackingProtectionWhitelists.length; ++i) {
+ this.registerTableWithURLs(this.trackingProtectionWhitelists[i]);
}
- for (let i = 0; i < blockedLists.length; ++i) {
- this.registerTableWithURLs(blockedLists[i]);
+ for (let i = 0; i < this.blockedLists.length; ++i) {
+ this.registerTableWithURLs(this.blockedLists[i]);
}
},
initialized: false,
phishingEnabled: false,
malwareEnabled: false,
trackingEnabled: false,
blockedEnabled: false,
+ phishingLists: [],
+ malwareLists: [],
+ downloadBlockLists: [],
+ downloadAllowLists: [],
+ trackingProtectionLists: [],
+ trackingProtectionWhitelists: [],
+ blockedLists: [],
+
updateURL: null,
gethashURL: null,
reportURL: null,
getReportURL: function(kind, URI) {
let pref;
switch (kind) {
@@ -161,16 +172,25 @@ this.SafeBrowsing = {
readPrefs: function() {
log("reading prefs");
this.debug = Services.prefs.getBoolPref("browser.safebrowsing.debug");
this.phishingEnabled = Services.prefs.getBoolPref("browser.safebrowsing.phishing.enabled");
this.malwareEnabled = Services.prefs.getBoolPref("browser.safebrowsing.malware.enabled");
this.trackingEnabled = Services.prefs.getBoolPref("privacy.trackingprotection.enabled") || Services.prefs.getBoolPref("privacy.trackingprotection.pbmode.enabled");
this.blockedEnabled = Services.prefs.getBoolPref("browser.safebrowsing.blockedURIs.enabled");
+
+ [this.phishingLists,
+ this.malwareLists,
+ this.downloadBlockLists,
+ this.downloadAllowLists,
+ this.trackingProtectionLists,
+ this.trackingProtectionWhitelists,
+ this.blockedLists] = tablePreferences.map(getLists);
+
this.updateProviderURLs();
this.registerTables();
// XXX The listManager backend gets confused if this is called before the
// lists are registered. So only call it here when a pref changes, and not
// when doing initialization. I expect to refactor this later, so pardon the hack.
if (this.initialized) {
this.controlUpdateChecking();
@@ -242,63 +262,63 @@ this.SafeBrowsing = {
controlUpdateChecking: function() {
log("phishingEnabled:", this.phishingEnabled, "malwareEnabled:",
this.malwareEnabled, "trackingEnabled:", this.trackingEnabled,
"blockedEnabled:", this.blockedEnabled);
let listManager = Cc["@mozilla.org/url-classifier/listmanager;1"].
getService(Ci.nsIUrlListManager);
- for (let i = 0; i < phishingLists.length; ++i) {
+ for (let i = 0; i < this.phishingLists.length; ++i) {
if (this.phishingEnabled) {
- listManager.enableUpdate(phishingLists[i]);
+ listManager.enableUpdate(this.phishingLists[i]);
} else {
- listManager.disableUpdate(phishingLists[i]);
+ listManager.disableUpdate(this.phishingLists[i]);
}
}
- for (let i = 0; i < malwareLists.length; ++i) {
+ for (let i = 0; i < this.malwareLists.length; ++i) {
if (this.malwareEnabled) {
- listManager.enableUpdate(malwareLists[i]);
+ listManager.enableUpdate(this.malwareLists[i]);
} else {
- listManager.disableUpdate(malwareLists[i]);
+ listManager.disableUpdate(this.malwareLists[i]);
}
}
- for (let i = 0; i < downloadBlockLists.length; ++i) {
+ for (let i = 0; i < this.downloadBlockLists.length; ++i) {
if (this.malwareEnabled) {
- listManager.enableUpdate(downloadBlockLists[i]);
+ listManager.enableUpdate(this.downloadBlockLists[i]);
} else {
- listManager.disableUpdate(downloadBlockLists[i]);
+ listManager.disableUpdate(this.downloadBlockLists[i]);
}
}
- for (let i = 0; i < downloadAllowLists.length; ++i) {
+ for (let i = 0; i < this.downloadAllowLists.length; ++i) {
if (this.malwareEnabled) {
- listManager.enableUpdate(downloadAllowLists[i]);
+ listManager.enableUpdate(this.downloadAllowLists[i]);
} else {
- listManager.disableUpdate(downloadAllowLists[i]);
+ listManager.disableUpdate(this.downloadAllowLists[i]);
}
}
- for (let i = 0; i < trackingProtectionLists.length; ++i) {
+ for (let i = 0; i < this.trackingProtectionLists.length; ++i) {
if (this.trackingEnabled) {
- listManager.enableUpdate(trackingProtectionLists[i]);
+ listManager.enableUpdate(this.trackingProtectionLists[i]);
} else {
- listManager.disableUpdate(trackingProtectionLists[i]);
+ listManager.disableUpdate(this.trackingProtectionLists[i]);
}
}
- for (let i = 0; i < trackingProtectionWhitelists.length; ++i) {
+ for (let i = 0; i < this.trackingProtectionWhitelists.length; ++i) {
if (this.trackingEnabled) {
- listManager.enableUpdate(trackingProtectionWhitelists[i]);
+ listManager.enableUpdate(this.trackingProtectionWhitelists[i]);
} else {
- listManager.disableUpdate(trackingProtectionWhitelists[i]);
+ listManager.disableUpdate(this.trackingProtectionWhitelists[i]);
}
}
- for (let i = 0; i < blockedLists.length; ++i) {
+ for (let i = 0; i < this.blockedLists.length; ++i) {
if (this.blockedEnabled) {
- listManager.enableUpdate(blockedLists[i]);
+ listManager.enableUpdate(this.blockedLists[i]);
} else {
- listManager.disableUpdate(blockedLists[i]);
+ listManager.disableUpdate(this.blockedLists[i]);
}
}
listManager.maybeToggleUpdateChecking();
},
addMozEntries: function() {
// Add test entries to the DB.
new file mode 100644
--- /dev/null
+++ b/toolkit/components/url-classifier/tests/mochitest/bug_1281083.html
@@ -0,0 +1,35 @@
+<html>
+<head>
+<title></title>
+
+<script type="text/javascript">
+
+var scriptItem = "untouched";
+
+function checkLoads() {
+ // Make sure the javascript did not load.
+ window.parent.is(scriptItem, "untouched", "Should not load bad javascript");
+
+ // Call parent.loadTestFrame again to test classification metadata in HTTP
+ // cache entries.
+ if (window.parent.firstLoad) {
+ window.parent.info("Reloading from cache...");
+ window.parent.firstLoad = false;
+ window.parent.loadTestFrame();
+ return;
+ }
+
+ // End (parent) test.
+ window.parent.SimpleTest.finish();
+}
+
+</script>
+
+<!-- Try loading from a malware javascript URI -->
+<script type="text/javascript" src="http://bug1281083.example.com/tests/toolkit/components/url-classifier/tests/mochitest/evil.js"></script>
+
+</head>
+
+<body onload="checkLoads()">
+</body>
+</html>
--- a/toolkit/components/url-classifier/tests/mochitest/chrome.ini
+++ b/toolkit/components/url-classifier/tests/mochitest/chrome.ini
@@ -1,20 +1,22 @@
[DEFAULT]
skip-if = buildapp == 'b2g' || os == 'android'
support-files =
allowlistAnnotatedFrame.html
classifiedAnnotatedFrame.html
classifiedAnnotatedPBFrame.html
+ bug_1281083.html
[test_lookup_system_principal.html]
[test_classified_annotations.html]
tags = trackingprotection
skip-if = os == 'linux' && asan # Bug 1202548
[test_allowlisted_annotations.html]
tags = trackingprotection
[test_privatebrowsing_trackingprotection.html]
tags = trackingprotection
[test_trackingprotection_bug1157081.html]
tags = trackingprotection
[test_trackingprotection_whitelist.html]
tags = trackingprotection
[test_donottrack.html]
+[test_classifier_changetablepref.html]
--- a/toolkit/components/url-classifier/tests/mochitest/mochitest.ini
+++ b/toolkit/components/url-classifier/tests/mochitest/mochitest.ini
@@ -17,14 +17,15 @@ support-files =
unwantedWorker.js
vp9.webm
whitelistFrame.html
workerFrame.html
ping.sjs
basic.vtt
dnt.html
dnt.sjs
+ update.sjs
[test_classifier.html]
skip-if = (os == 'linux' && debug) #Bug 1199778
[test_classifier_worker.html]
[test_classify_ping.html]
[test_classify_track.html]
new file mode 100644
--- /dev/null
+++ b/toolkit/components/url-classifier/tests/mochitest/test_classifier_changetablepref.html
@@ -0,0 +1,149 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Bug 1281083 - Changing the urlclassifier.*Table prefs doesn't take effect before the next browser restart.</title>
+ <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="classifierHelper.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+</head>
+
+<body>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+
+<script class="testbody" type="text/javascript">
+
+const testTable = "moz-track-digest256";
+const UPDATE_URL = "http://mochi.test:8888/tests/toolkit/components/url-classifier/tests/mochitest/update.sjs";
+
+var Cc = SpecialPowers.Cc;
+var Ci = SpecialPowers.Ci;
+
+var prefService = Cc["@mozilla.org/preferences-service;1"]
+ .getService(Ci.nsIPrefService);
+
+var timer = Cc["@mozilla.org/timer;1"]
+ .createInstance(Ci.nsITimer);
+
+// If default preference contain the table we want to test,
+// We should change test table to a different one.
+var trackingTables = SpecialPowers.getCharPref("urlclassifier.trackingTable").split(",");
+ok(!trackingTables.includes(testTable), "test table should not be in the preference");
+
+var listmanager = Cc["@mozilla.org/url-classifier/listmanager;1"].
+ getService(Ci.nsIUrlListManager);
+
+is(listmanager.getGethashUrl(testTable), "",
+ "gethash url for test table should be empty before setting to preference");
+
+function loadTestFrame() {
+ // gethash url of test table "moz-track-digest256" should be updated
+ // after setting preference.
+ var url = listmanager.getGethashUrl(testTable);
+ var expected = SpecialPowers.getCharPref("browser.safebrowsing.provider.mozilla.gethashURL");
+
+ is(url, expected, testTable + " matches its gethash url");
+
+ // Trigger update
+ listmanager.disableUpdate(testTable);
+ listmanager.enableUpdate(testTable);
+ listmanager.maybeToggleUpdateChecking();
+
+ // We wait until "nextupdattime" was set as a signal that update is complete.
+ waitForUpdateSuccess(function() {
+ document.getElementById("testFrame").src = "bug_1281083.html";
+ });
+}
+
+function waitForUpdateSuccess(callback) {
+ let nextupdatetime =
+ SpecialPowers.getCharPref("browser.safebrowsing.provider.mozilla.nextupdatetime");
+
+ if (nextupdatetime !== "1") {
+ callback();
+ return;
+ }
+
+ timer.initWithCallback(function() {
+ waitForUpdateSuccess(callback);
+ }, 10, Components.interfaces.nsITimer.TYPE_ONE_SHOT);
+}
+
+function addCompletionToServer(list, url) {
+ return new Promise(function(resolve, reject) {
+ var listParam = "list=" + list;
+ var fullhashParam = "fullhash=" + hash(url);
+
+ var xhr = new XMLHttpRequest;
+ xhr.open("PUT", UPDATE_URL + "?" +
+ listParam + "&" +
+ fullhashParam, true);
+ xhr.setRequestHeader("Content-Type", "text/plain");
+ xhr.onreadystatechange = function() {
+ if (this.readyState == this.DONE) {
+ resolve();
+ }
+ };
+ xhr.send();
+ });
+}
+
+function hash(str) {
+ function bytesFromString(str) {
+ var converter =
+ SpecialPowers.Cc["@mozilla.org/intl/scriptableunicodeconverter"]
+ .createInstance(SpecialPowers.Ci.nsIScriptableUnicodeConverter);
+ converter.charset = "UTF-8";
+ return converter.convertToByteArray(str);
+ }
+
+ var hasher = SpecialPowers.Cc["@mozilla.org/security/hash;1"]
+ .createInstance(SpecialPowers.Ci.nsICryptoHash);
+
+ var data = bytesFromString(str);
+ hasher.init(hasher.SHA256);
+ hasher.update(data, data.length);
+
+ return hasher.finish(true);
+}
+
+function runTest() {
+ /**
+ * In this test we try to modify only urlclassifier.*Table preference to see if
+ * url specified in the table will be blocked after update.
+ */
+ var pushPrefPromise = SpecialPowers.pushPrefEnv(
+ {"set" : [["urlclassifier.trackingTable", testTable]]});
+
+ // To make sure url is not blocked by an already blocked url.
+ // Here we use non-tracking.example.com as a tracked url.
+ // Since this table is only used in this bug, so it won't affect other testcases.
+ var addCompletePromise =
+ addCompletionToServer(testTable, "bug1281083.example.com/");
+
+ Promise.all([pushPrefPromise, addCompletePromise])
+ .then(() => {
+ loadTestFrame();
+ });
+}
+
+// Set nextupdatetime to 1 to trigger an update
+SpecialPowers.pushPrefEnv(
+ {"set" : [["privacy.trackingprotection.enabled", true],
+ ["channelclassifier.allowlist_example", true],
+ ["browser.safebrowsing.provider.mozilla.nextupdatetime", "1"],
+ ["browser.safebrowsing.provider.mozilla.lists", testTable],
+ ["browser.safebrowsing.provider.mozilla.updateURL", UPDATE_URL]]},
+ runTest);
+
+// Expected finish() call is in "bug_1281083.html".
+SimpleTest.waitForExplicitFinish();
+
+</script>
+</pre>
+<iframe id="testFrame" width="100%" height="100%" onload=""></iframe>
+</body>
+</html>
+
new file mode 100644
--- /dev/null
+++ b/toolkit/components/url-classifier/tests/mochitest/update.sjs
@@ -0,0 +1,114 @@
+const CC = Components.Constructor;
+const BinaryInputStream = CC("@mozilla.org/binaryinputstream;1",
+ "nsIBinaryInputStream",
+ "setInputStream");
+
+function handleRequest(request, response)
+{
+ var query = {};
+ request.queryString.split('&').forEach(function (val) {
+ var idx = val.indexOf('=');
+ query[val.slice(0, idx)] = unescape(val.slice(idx + 1));
+ });
+
+ // Store fullhash in the server side.
+ if ("list" in query && "fullhash" in query) {
+ // In the server side we will store:
+ // 1. All the full hashes for a given list
+ // 2. All the lists we have right now
+ // data is separate by '\n'
+ let list = query["list"];
+ let hashes = getState(list);
+
+ let hash = base64ToString(query["fullhash"]);
+ hashes += hash + "\n";
+ setState(list, hashes);
+
+ let lists = getState("lists");
+ if (lists.indexOf(list) == -1) {
+ lists += list + "\n";
+ setState("lists", lists);
+ }
+
+ return;
+ }
+
+ var body = new BinaryInputStream(request.bodyInputStream);
+ var avail;
+ var bytes = [];
+
+ while ((avail = body.available()) > 0) {
+ Array.prototype.push.apply(bytes, body.readByteArray(avail));
+ }
+
+ var responseBody = parseV2Request(bytes);
+
+ response.setHeader("Content-Type", "text/plain", false);
+ response.write(responseBody);
+}
+
+function parseV2Request(bytes) {
+ var table = String.fromCharCode.apply(this, bytes).slice(0,-2);
+
+ var ret = "";
+ getState("lists").split("\n").forEach(function(list) {
+ if (list == table) {
+ var completions = getState(list).split("\n");
+ ret += "n:1000\n"
+ ret += "i:" + list + "\n";
+ ret += "a:1:32:" + 32*(completions.length - 1) + "\n";
+
+ for (var completion of completions) {
+ ret += completion;
+ }
+ }
+ });
+
+ return ret;
+}
+
+/* Convert Base64 data to a string */
+const toBinaryTable = [
+ -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,
+ -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,
+ -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,62, -1,-1,-1,63,
+ 52,53,54,55, 56,57,58,59, 60,61,-1,-1, -1, 0,-1,-1,
+ -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10, 11,12,13,14,
+ 15,16,17,18, 19,20,21,22, 23,24,25,-1, -1,-1,-1,-1,
+ -1,26,27,28, 29,30,31,32, 33,34,35,36, 37,38,39,40,
+ 41,42,43,44, 45,46,47,48, 49,50,51,-1, -1,-1,-1,-1
+];
+const base64Pad = '=';
+
+function base64ToString(data) {
+ var result = '';
+ var leftbits = 0; // number of bits decoded, but yet to be appended
+ var leftdata = 0; // bits decoded, but yet to be appended
+
+ // Convert one by one.
+ for (var i = 0; i < data.length; i++) {
+ var c = toBinaryTable[data.charCodeAt(i) & 0x7f];
+ var padding = (data[i] == base64Pad);
+ // Skip illegal characters and whitespace
+ if (c == -1) continue;
+
+ // Collect data into leftdata, update bitcount
+ leftdata = (leftdata << 6) | c;
+ leftbits += 6;
+
+ // If we have 8 or more bits, append 8 bits to the result
+ if (leftbits >= 8) {
+ leftbits -= 8;
+ // Append if not padding.
+ if (!padding)
+ result += String.fromCharCode((leftdata >> leftbits) & 0xff);
+ leftdata &= (1 << leftbits) - 1;
+ }
+ }
+
+ // If there are any bits left, the base64 string was corrupted
+ if (leftbits)
+ throw Components.Exception('Corrupted base64 string');
+
+ return result;
+}
--- a/toolkit/content/widgets/popup.xml
+++ b/toolkit/content/widgets/popup.xml
@@ -311,37 +311,45 @@
<handler event="popuphiding"><![CDATA[
try {
this._currentFocus = document.commandDispatcher.focusedElement;
} catch (e) {
this._currentFocus = document.activeElement;
}
]]></handler>
<handler event="popuphidden"><![CDATA[
+ function doFocus() {
+ // Focus was set on an element inside this panel,
+ // so we need to move it back to where it was previously
+ try {
+ let fm = Components.classes["@mozilla.org/focus-manager;1"]
+ .getService(Components.interfaces.nsIFocusManager);
+ fm.setFocus(prevFocus, fm.FLAG_NOSCROLL);
+ } catch(e) {
+ prevFocus.focus();
+ }
+ }
var currentFocus = this._currentFocus;
var prevFocus = this._prevFocus ? this._prevFocus.get() : null;
this._currentFocus = null;
this._prevFocus = null;
- if (prevFocus && currentFocus && this.getAttribute("norestorefocus") != "true") {
+ if (prevFocus && this.getAttribute("norestorefocus") != "true") {
// Try to restore focus
try {
if (document.commandDispatcher.focusedWindow != window)
return; // Focus has already been set to a window outside of this panel
} catch(ex) {}
+
+ if (!currentFocus) {
+ doFocus();
+ return;
+ }
while (currentFocus) {
if (currentFocus == this) {
- // Focus was set on an element inside this panel,
- // so we need to move it back to where it was previously
- try {
- let fm = Components.classes["@mozilla.org/focus-manager;1"]
- .getService(Components.interfaces.nsIFocusManager);
- fm.setFocus(prevFocus, fm.FLAG_NOSCROLL);
- } catch(e) {
- prevFocus.focus();
- }
+ doFocus();
return;
}
currentFocus = currentFocus.parentNode;
}
}
]]></handler>
</handlers>
</binding>