author | Wes Kocher <wkocher@mozilla.com> |
Wed, 25 May 2016 15:36:21 -0700 | |
changeset 299019 | 8d0aadfe7da782d415363880008b4ca027686137 |
parent 299006 | 1012461fa7bb33adf728ec631020cb18153da6f0 (current diff) |
parent 299018 | df627da479f868c9704d7ac0b1c6aa76fb298150 (diff) |
child 299058 | da921523812f62a85d6d7963703af963f99ad9ed |
child 299087 | 9ef45b3ae61d40b772319a314205ddacfe00cff9 |
push id | 30287 |
push user | kwierso@gmail.com |
push date | Wed, 25 May 2016 22:36:26 +0000 |
treeherder | mozilla-central@8d0aadfe7da7 [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | merge |
milestone | 49.0a1 |
first release with | nightly linux32
8d0aadfe7da7
/
49.0a1
/
20160526030223
/
files
nightly linux64
8d0aadfe7da7
/
49.0a1
/
20160526030223
/
files
nightly mac
8d0aadfe7da7
/
49.0a1
/
20160526030223
/
files
nightly win32
8d0aadfe7da7
/
49.0a1
/
20160526030223
/
files
nightly win64
8d0aadfe7da7
/
49.0a1
/
20160526030223
/
files
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
releases | nightly linux32
49.0a1
/
20160526030223
/
pushlog to previous
nightly linux64
49.0a1
/
20160526030223
/
pushlog to previous
nightly mac
49.0a1
/
20160526030223
/
pushlog to previous
nightly win32
49.0a1
/
20160526030223
/
pushlog to previous
nightly win64
49.0a1
/
20160526030223
/
pushlog to previous
|
--- a/build/autoconf/android.m4 +++ b/build/autoconf/android.m4 @@ -202,50 +202,56 @@ AC_DEFUN([MOZ_ANDROID_AAR],[ define([root], $MOZ_BUILD_ROOT/dist/exploded-aar/$1-$2/) MOZ_ANDROID_AAR_COMPONENT(concat(local_aar_var, _LIB), concat(root, $1-$2-classes.jar), REQUIRED) MOZ_ANDROID_AAR_COMPONENT(concat(local_aar_var, _RES), concat(root, res), REQUIRED) MOZ_ANDROID_AAR_COMPONENT(concat(local_aar_var, _INTERNAL_LIB), concat(root, libs/$1-$2-internal_impl-$2.jar), $5) MOZ_ANDROID_AAR_COMPONENT(concat(local_aar_var, _ASSETS), concat(root, assets), $6) ]) +ANDROID_SUPPORT_LIBRARY_VERSION="23.0.1" +AC_SUBST(ANDROID_SUPPORT_LIBRARY_VERSION) + +ANDROID_GOOGLE_PLAY_SERVICES_VERSION="8.1.0" +AC_SUBST(ANDROID_GOOGLE_PLAY_SERVICES_VERSION) + AC_DEFUN([MOZ_ANDROID_GOOGLE_PLAY_SERVICES], [ if test -n "$MOZ_NATIVE_DEVICES" ; then AC_SUBST(MOZ_NATIVE_DEVICES) - MOZ_ANDROID_AAR(play-services-base, 8.1.0, google, com/google/android/gms) - MOZ_ANDROID_AAR(play-services-basement, 8.1.0, google, com/google/android/gms) - MOZ_ANDROID_AAR(play-services-cast, 8.1.0, google, com/google/android/gms) - MOZ_ANDROID_AAR(mediarouter-v7, 23.0.1, android, com/android/support, REQUIRED_INTERNAL_IMPL) + MOZ_ANDROID_AAR(play-services-base, $ANDROID_GOOGLE_PLAY_SERVICES_VERSION, google, com/google/android/gms) + MOZ_ANDROID_AAR(play-services-basement, $ANDROID_GOOGLE_PLAY_SERVICES_VERSION, google, com/google/android/gms) + MOZ_ANDROID_AAR(play-services-cast, $ANDROID_GOOGLE_PLAY_SERVICES_VERSION, google, com/google/android/gms) + MOZ_ANDROID_AAR(mediarouter-v7, $ANDROID_SUPPORT_LIBRARY_VERSION, android, com/android/support, REQUIRED_INTERNAL_IMPL) fi ]) AC_DEFUN([MOZ_ANDROID_GOOGLE_CLOUD_MESSAGING], [ if test -n "$MOZ_ANDROID_GCM" ; then - MOZ_ANDROID_AAR(play-services-base, 8.1.0, google, com/google/android/gms) - MOZ_ANDROID_AAR(play-services-basement, 8.1.0, google, com/google/android/gms) - MOZ_ANDROID_AAR(play-services-gcm, 8.1.0, google, com/google/android/gms) + MOZ_ANDROID_AAR(play-services-base, $ANDROID_GOOGLE_PLAY_SERVICES_VERSION, google, com/google/android/gms) + MOZ_ANDROID_AAR(play-services-basement, $ANDROID_GOOGLE_PLAY_SERVICES_VERSION, google, com/google/android/gms) + MOZ_ANDROID_AAR(play-services-gcm, $ANDROID_GOOGLE_PLAY_SERVICES_VERSION, google, com/google/android/gms) fi ]) AC_DEFUN([MOZ_ANDROID_INSTALL_TRACKING], [ if test -n "$MOZ_INSTALL_TRACKING"; then AC_SUBST(MOZ_INSTALL_TRACKING) - MOZ_ANDROID_AAR(play-services-ads, 8.1.0, google, com/google/android/gms) - MOZ_ANDROID_AAR(play-services-analytics, 8.1.0, google, com/google/android/gms) - MOZ_ANDROID_AAR(play-services-appindexing, 8.1.0, google, com/google/android/gms) - MOZ_ANDROID_AAR(play-services-basement, 8.1.0, google, com/google/android/gms) + MOZ_ANDROID_AAR(play-services-ads, $ANDROID_GOOGLE_PLAY_SERVICES_VERSION, google, com/google/android/gms) + MOZ_ANDROID_AAR(play-services-analytics, $ANDROID_GOOGLE_PLAY_SERVICES_VERSION, google, com/google/android/gms) + MOZ_ANDROID_AAR(play-services-appindexing, $ANDROID_GOOGLE_PLAY_SERVICES_VERSION, google, com/google/android/gms) + MOZ_ANDROID_AAR(play-services-basement, $ANDROID_GOOGLE_PLAY_SERVICES_VERSION, google, com/google/android/gms) fi ]) dnl Configure an Android SDK. dnl Arg 1: target SDK version, like 22. dnl Arg 2: build tools version, like 22.0.1. AC_DEFUN([MOZ_ANDROID_SDK], @@ -329,29 +335,31 @@ case "$target" in if test -z "$EMULATOR" -o "$EMULATOR" = ":"; then AC_MSG_ERROR([The program emulator was not found. Try |mach bootstrap|.]) fi ANDROID_TARGET_SDK="${android_target_sdk}" ANDROID_SDK="${android_sdk}" ANDROID_SDK_ROOT="${android_sdk_root}" ANDROID_TOOLS="${android_tools}" + ANDROID_BUILD_TOOLS_VERSION="$2" AC_DEFINE_UNQUOTED(ANDROID_TARGET_SDK,$ANDROID_TARGET_SDK) AC_SUBST(ANDROID_TARGET_SDK) AC_SUBST(ANDROID_SDK_ROOT) AC_SUBST(ANDROID_SDK) AC_SUBST(ANDROID_TOOLS) + AC_SUBST(ANDROID_BUILD_TOOLS_VERSION) - MOZ_ANDROID_AAR(appcompat-v7, 23.0.1, android, com/android/support) - MOZ_ANDROID_AAR(cardview-v7, 23.0.1, android, com/android/support) - MOZ_ANDROID_AAR(design, 23.0.1, android, com/android/support) - MOZ_ANDROID_AAR(recyclerview-v7, 23.0.1, android, com/android/support) - MOZ_ANDROID_AAR(support-v4, 23.0.1, android, com/android/support, REQUIRED_INTERNAL_IMPL) + MOZ_ANDROID_AAR(appcompat-v7, $ANDROID_SUPPORT_LIBRARY_VERSION, android, com/android/support) + MOZ_ANDROID_AAR(cardview-v7, $ANDROID_SUPPORT_LIBRARY_VERSION, android, com/android/support) + MOZ_ANDROID_AAR(design, $ANDROID_SUPPORT_LIBRARY_VERSION, android, com/android/support) + MOZ_ANDROID_AAR(recyclerview-v7, $ANDROID_SUPPORT_LIBRARY_VERSION, android, com/android/support) + MOZ_ANDROID_AAR(support-v4, $ANDROID_SUPPORT_LIBRARY_VERSION, android, com/android/support, REQUIRED_INTERNAL_IMPL) - ANDROID_SUPPORT_ANNOTATIONS_JAR="$ANDROID_SDK_ROOT/extras/android/m2repository/com/android/support/support-annotations/23.0.1/support-annotations-23.0.1.jar" + ANDROID_SUPPORT_ANNOTATIONS_JAR="$ANDROID_SDK_ROOT/extras/android/m2repository/com/android/support/support-annotations/$ANDROID_SUPPORT_LIBRARY_VERSION/support-annotations-$ANDROID_SUPPORT_LIBRARY_VERSION.jar" AC_MSG_CHECKING([for support-annotations JAR]) if ! test -e $ANDROID_SUPPORT_ANNOTATIONS_JAR ; then AC_MSG_ERROR([You must download the support-annotations lib. Run the Android SDK tool and install the Android Support Repository under Extras. See https://developer.android.com/tools/extras/support-library.html for more info. (looked for $ANDROID_SUPPORT_ANNOTATIONS_JAR)]) fi AC_MSG_RESULT([$ANDROID_SUPPORT_ANNOTATIONS_JAR]) AC_SUBST(ANDROID_SUPPORT_ANNOTATIONS_JAR) ANDROID_SUPPORT_ANNOTATIONS_JAR_LIB=$ANDROID_SUPPORT_ANNOTATIONS_JAR AC_SUBST(ANDROID_SUPPORT_ANNOTATIONS_JAR_LIB)
--- a/devtools/client/devtools-startup.manifest +++ b/devtools/client/devtools-startup.manifest @@ -1,2 +1,8 @@ component {9e9a9283-0ce9-4e4a-8f1c-ba129a032c32} devtools-startup.js -contract @mozilla.org/toolkit/console-clh;1 {9e9a9283-0ce9-4e4a-8f1c-ba129a032c32} +contract @mozilla.org/devtools/startup-clh;1 {9e9a9283-0ce9-4e4a-8f1c-ba129a032c32} +# We want this to override toolkit's --jsconsole handling, so it must have a +# a higher priority than the entry in jsconsole-clhandler.manifest. Higher +# priority means the "m-devtools" value below needs to be something that sorts +# before the one in jsconsole-clhandler.manifest. See details in +# nsICommandLineHandler.idl. +category command-line-handler m-devtools @mozilla.org/devtools/startup-clh;1
--- a/devtools/client/framework/test/browser_keybindings_02.js +++ b/devtools/client/framework/test/browser_keybindings_02.js @@ -5,16 +5,18 @@ "use strict"; // Test that the toolbox keybindings still work after the host is changed. const URL = "data:text/html;charset=utf8,test page"; var {Toolbox} = require("devtools/client/framework/toolbox"); +var strings = Services.strings.createBundle( + "chrome://devtools/locale/toolbox.properties"); add_task(function* () { info("Create a test tab and open the toolbox"); let tab = yield addTab(URL); let target = TargetFactory.forTab(tab); let toolbox = yield gDevTools.showToolbox(target, "webconsole"); let {SIDE, BOTTOM} = Toolbox.HostType; @@ -28,31 +30,31 @@ add_task(function* () { Services.prefs.clearUserPref("devtools.toolbox.zoomValue"); Services.prefs.setCharPref("devtools.toolbox.host", BOTTOM); yield toolbox.destroy(); gBrowser.removeCurrentTab(); }); function zoomWithKey(toolbox, key) { - if (!key) { + let shortcut = strings.GetStringFromName(key); + if (!shortcut) { info("Key was empty, skipping zoomWithKey"); return; } - info("Zooming with key: " + key); let currentZoom = toolbox.zoomValue; - EventUtils.synthesizeKey(key, {accelKey: true}, toolbox.win); + synthesizeKeyShortcut(shortcut); isnot(toolbox.zoomValue, currentZoom, "The zoom level was changed in the toolbox"); } function* checkKeyBindings(toolbox) { - zoomWithKey(toolbox, toolbox.doc.getElementById("toolbox-zoom-in-key").getAttribute("key")); - zoomWithKey(toolbox, toolbox.doc.getElementById("toolbox-zoom-in-key2").getAttribute("key")); - zoomWithKey(toolbox, toolbox.doc.getElementById("toolbox-zoom-in-key3").getAttribute("key")); + zoomWithKey(toolbox, "toolbox.zoomIn.key"); + zoomWithKey(toolbox, "toolbox.zoomIn2.key"); + zoomWithKey(toolbox, "toolbox.zoomIn3.key"); - zoomWithKey(toolbox, toolbox.doc.getElementById("toolbox-zoom-reset-key").getAttribute("key")); + zoomWithKey(toolbox, "toolbox.zoomReset.key"); - zoomWithKey(toolbox, toolbox.doc.getElementById("toolbox-zoom-out-key").getAttribute("key")); - zoomWithKey(toolbox, toolbox.doc.getElementById("toolbox-zoom-out-key2").getAttribute("key")); + zoomWithKey(toolbox, "toolbox.zoomOut.key"); + zoomWithKey(toolbox, "toolbox.zoomOut2.key"); - zoomWithKey(toolbox, toolbox.doc.getElementById("toolbox-zoom-reset-key2").getAttribute("key")); + zoomWithKey(toolbox, "toolbox.zoomReset2.key"); }
--- a/devtools/client/framework/test/browser_keybindings_03.js +++ b/devtools/client/framework/test/browser_keybindings_03.js @@ -6,42 +6,47 @@ "use strict"; // Test that the toolbox 'switch to previous host' feature works. // Pressing ctrl/cmd+shift+d should switch to the last used host. const URL = "data:text/html;charset=utf8,test page for toolbox switching"; var {Toolbox} = require("devtools/client/framework/toolbox"); +var strings = Services.strings.createBundle( + "chrome://devtools/locale/toolbox.properties"); add_task(function* () { info("Create a test tab and open the toolbox"); let tab = yield addTab(URL); let target = TargetFactory.forTab(tab); let toolbox = yield gDevTools.showToolbox(target, "webconsole"); - let keyElement = toolbox.doc.getElementById("toolbox-toggle-host-key"); + let shortcut = strings.GetStringFromName("toolbox.toggleHost.key"); let {SIDE, BOTTOM, WINDOW} = Toolbox.HostType; checkHostType(toolbox, BOTTOM, SIDE); info("Switching from bottom to side"); - synthesizeKeyElement(keyElement); - yield toolbox.once("host-changed"); + let onHostChanged = toolbox.once("host-changed"); + synthesizeKeyShortcut(shortcut, toolbox.win); + yield onHostChanged; checkHostType(toolbox, SIDE, BOTTOM); info("Switching from side to bottom"); - synthesizeKeyElement(keyElement); - yield toolbox.once("host-changed"); + onHostChanged = toolbox.once("host-changed"); + synthesizeKeyShortcut(shortcut, toolbox.win); + yield onHostChanged; checkHostType(toolbox, BOTTOM, SIDE); info("Switching to window"); yield toolbox.switchHost(WINDOW); checkHostType(toolbox, WINDOW, BOTTOM); info("Switching from window to bottom"); - synthesizeKeyElement(keyElement); - yield toolbox.once("host-changed"); + onHostChanged = toolbox.once("host-changed"); + synthesizeKeyShortcut(shortcut, toolbox.win); + yield onHostChanged; checkHostType(toolbox, BOTTOM, WINDOW); yield toolbox.destroy(); gBrowser.removeCurrentTab(); });
--- a/devtools/client/framework/test/browser_toolbox_options.js +++ b/devtools/client/framework/test/browser_toolbox_options.js @@ -5,16 +5,18 @@ /* import-globals-from shared-head.js */ "use strict"; // Tests that changing preferences in the options panel updates the prefs // and toggles appropriate things in the toolbox. var doc = null, toolbox = null, panelWin = null, modifiedPrefs = []; +var strings = Services.strings.createBundle( + "chrome://devtools/locale/toolbox.properties"); add_task(function* () { const URL = "data:text/html;charset=utf8,test for dynamically registering " + "and unregistering tools"; registerNewTool(); let tab = yield addTab(URL); let target = TargetFactory.forTab(tab); toolbox = yield gDevTools.showToolbox(target); @@ -53,28 +55,28 @@ function* testSelectTool() { ok(true, "Toolbox selected via selectTool method"); } function* testOptionsShortcut() { info("Selecting another tool, then reselecting options panel with keyboard."); yield toolbox.selectTool("webconsole"); is(toolbox.currentToolId, "webconsole", "webconsole is selected"); - synthesizeKeyFromKeyTag(doc.getElementById("toolbox-options-key")); + synthesizeKeyShortcut(strings.GetStringFromName("toolbox.options.key")); is(toolbox.currentToolId, "options", "Toolbox selected via shortcut key (1)"); - synthesizeKeyFromKeyTag(doc.getElementById("toolbox-options-key")); + synthesizeKeyShortcut(strings.GetStringFromName("toolbox.options.key")); is(toolbox.currentToolId, "webconsole", "webconsole is selected (1)"); yield toolbox.selectTool("webconsole"); is(toolbox.currentToolId, "webconsole", "webconsole is selected"); - synthesizeKeyFromKeyTag(doc.getElementById("toolbox-options-key2")); + synthesizeKeyShortcut(strings.GetStringFromName("toolbox.help.key")); is(toolbox.currentToolId, "options", "Toolbox selected via shortcut key (2)"); - synthesizeKeyFromKeyTag(doc.getElementById("toolbox-options-key")); + synthesizeKeyShortcut(strings.GetStringFromName("toolbox.options.key")); is(toolbox.currentToolId, "webconsole", "webconsole is reselected (2)"); - synthesizeKeyFromKeyTag(doc.getElementById("toolbox-options-key2")); + synthesizeKeyShortcut(strings.GetStringFromName("toolbox.help.key")); is(toolbox.currentToolId, "options", "Toolbox selected via shortcut key (2)"); } function* testOptions() { let tool = toolbox.getPanel("options"); panelWin = tool.panelWin; let prefNodes = tool.panelDoc.querySelectorAll( "input[type=checkbox][data-pref]");
--- a/devtools/client/framework/test/browser_toolbox_tabsswitch_shortcuts.js +++ b/devtools/client/framework/test/browser_toolbox_tabsswitch_shortcuts.js @@ -1,66 +1,66 @@ /* -*- 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/ */ requestLongerTimeout(2); var {Toolbox} = require("devtools/client/framework/toolbox"); +var strings = Services.strings.createBundle( + "chrome://devtools/locale/toolbox.properties"); add_task(function* () { let tab = yield addTab("about:blank"); let target = TargetFactory.forTab(tab); yield target.makeRemote(); let toolIDs = gDevTools.getToolDefinitionArray() .filter(def => def.isTargetSupported(target)) .map(def => def.id); let toolbox = yield gDevTools.showToolbox(target, toolIDs[0], Toolbox.HostType.BOTTOM); - let nextKey = toolbox.doc.getElementById("toolbox-next-tool-key") - .getAttribute("key"); - let prevKey = toolbox.doc.getElementById("toolbox-previous-tool-key") - .getAttribute("key"); + let nextShortcut = strings.GetStringFromName("toolbox.nextTool.key") + let prevShortcut = strings.GetStringFromName("toolbox.previousTool.key") // Iterate over all tools, starting from options to netmonitor, in normal // order. for (let i = 1; i < toolIDs.length; i++) { - yield testShortcuts(toolbox, i, nextKey, toolIDs); + yield testShortcuts(toolbox, i, nextShortcut, toolIDs); } // Iterate again, in the same order, starting from netmonitor (so next one is // 0: options). for (let i = 0; i < toolIDs.length; i++) { - yield testShortcuts(toolbox, i, nextKey, toolIDs); + yield testShortcuts(toolbox, i, nextShortcut, toolIDs); } // Iterate over all tools in reverse order, starting from netmonitor to // options. for (let i = toolIDs.length - 2; i >= 0; i--) { - yield testShortcuts(toolbox, i, prevKey, toolIDs); + yield testShortcuts(toolbox, i, prevShortcut, toolIDs); } // Iterate again, in reverse order again, starting from options (so next one // is length-1: netmonitor). for (let i = toolIDs.length - 1; i >= 0; i--) { - yield testShortcuts(toolbox, i, prevKey, toolIDs); + yield testShortcuts(toolbox, i, prevShortcut, toolIDs); } yield toolbox.destroy(); gBrowser.removeCurrentTab(); }); -function* testShortcuts(toolbox, index, key, toolIDs) { +function* testShortcuts(toolbox, index, shortcut, toolIDs) { info("Testing shortcut to switch to tool " + index + ":" + toolIDs[index] + - " using key " + key); + " using shortcut " + shortcut); let onToolSelected = toolbox.once("select"); - EventUtils.synthesizeKey(key, {accelKey: true}, toolbox.win); + synthesizeKeyShortcut(shortcut); let id = yield onToolSelected; info("toolbox-select event from " + id); is(toolIDs.indexOf(id), index, "Correct tool is selected on pressing the shortcut for " + id); }
--- a/devtools/client/framework/test/browser_toolbox_window_reload_target.js +++ b/devtools/client/framework/test/browser_toolbox_window_reload_target.js @@ -5,16 +5,18 @@ requestLongerTimeout(10); const TEST_URL = "data:text/html;charset=utf-8," + "<html><head><title>Test reload</title></head>" + "<body><h1>Testing reload from devtools</h1></body></html>"; var {Toolbox} = require("devtools/client/framework/toolbox"); +var strings = Services.strings.createBundle( + "chrome://devtools/locale/toolbox.properties"); var target, toolbox, description, reloadsSent, toolIDs; function test() { addTab(TEST_URL).then(() => { target = TargetFactory.forTab(gBrowser.selectedTab); target.makeRemote().then(() => { @@ -55,39 +57,38 @@ function startReloadTest(aToolbox) { }, toolIDs.length - 1 /* only test 1 tool in docked mode, to cut down test time */); } function testAllTheTools(docked, callback, toolNum = 0) { if (toolNum >= toolIDs.length) { return callback(); } toolbox.selectTool(toolIDs[toolNum]).then(() => { - testReload("toolbox-reload-key", docked, toolIDs[toolNum], () => { - testReload("toolbox-reload-key2", docked, toolIDs[toolNum], () => { - testReload("toolbox-force-reload-key", docked, toolIDs[toolNum], () => { - testReload("toolbox-force-reload-key2", docked, toolIDs[toolNum], () => { + testReload("toolbox.reload.key", docked, toolIDs[toolNum], () => { + testReload("toolbox.reload2.key", docked, toolIDs[toolNum], () => { + testReload("toolbox.forceReload.key", docked, toolIDs[toolNum], () => { + testReload("toolbox.forceReload2.key", docked, toolIDs[toolNum], () => { testAllTheTools(docked, callback, toolNum + 1); }); }); }); }); }); } -function testReload(key, docked, toolID, callback) { +function testReload(shortcut, docked, toolID, callback) { let complete = () => { gBrowser.selectedBrowser.messageManager.removeMessageListener("devtools:test:load", complete); return callback(); }; gBrowser.selectedBrowser.messageManager.addMessageListener("devtools:test:load", complete); - description = docked + " devtools with tool " + toolID + ", key #" + key; + description = docked + " devtools with tool " + toolID + ", shortcut #" + shortcut; info("Testing reload in " + description); - let el = toolbox.doc.getElementById(key); - synthesizeKeyElement(el); + synthesizeKeyShortcut(strings.GetStringFromName(shortcut), toolbox.win); reloadsSent++; } function finishUp() { toolbox.destroy().then(() => { gBrowser.removeCurrentTab(); target = toolbox = description = reloadsSent = toolIDs = null;
--- a/devtools/client/framework/test/browser_toolbox_zoom.js +++ b/devtools/client/framework/test/browser_toolbox_zoom.js @@ -3,16 +3,18 @@ /* Any copyright is dedicated to the Public Domain. * http://creativecommons.org/publicdomain/zero/1.0/ */ var modifiers = { accelKey: true }; var toolbox; +var strings = Services.strings.createBundle( + "chrome://devtools/locale/toolbox.properties"); function test() { addTab("about:blank").then(openToolbox); } function openToolbox() { let target = TargetFactory.forTab(gBrowser.selectedTab); @@ -20,37 +22,36 @@ function openToolbox() { toolbox = aToolbox; toolbox.selectTool("styleeditor").then(testZoom); }); } function testZoom() { info("testing zoom keys"); - testZoomLevel("in", 2, 1.2); - testZoomLevel("out", 3, 0.9); - testZoomLevel("reset", 1, 1); + testZoomLevel("In", 2, 1.2); + testZoomLevel("Out", 3, 0.9); + testZoomLevel("Reset", 1, 1); tidyUp(); } function testZoomLevel(type, times, expected) { - sendZoomKey("toolbox-zoom-" + type + "-key", times); + sendZoomKey("toolbox.zoom" + type + ".key", times); let zoom = getCurrentZoom(toolbox); is(zoom.toFixed(2), expected, "zoom level correct after zoom " + type); is(toolbox.zoomValue.toFixed(2), expected, "saved zoom level is correct after zoom " + type); } -function sendZoomKey(id, times) { - let key = toolbox.doc.getElementById(id).getAttribute("key"); +function sendZoomKey(shortcut, times) { for (let i = 0; i < times; i++) { - EventUtils.synthesizeKey(key, modifiers, toolbox.win); + synthesizeKeyShortcut(strings.GetStringFromName(shortcut)); } } function getCurrentZoom() { var contViewer = toolbox.frame.docShell.contentViewer; return contViewer.fullZoom; }
--- a/devtools/client/framework/test/shared-head.js +++ b/devtools/client/framework/test/shared-head.js @@ -163,30 +163,32 @@ function synthesizeKeyFromKeyTag(key) { EventUtils.synthesizeKey(name, modifiers); } /** * Simulate a key event from an electron key shortcut string: * https://github.com/electron/electron/blob/master/docs/api/accelerator.md * * @param {String} key + * @param {DOMWindow} target + * Optional window where to fire the key event */ -function synthesizeKeyShortcut(key) { +function synthesizeKeyShortcut(key, target) { // parseElectronKey requires any window, just to access `KeyboardEvent` let window = Services.appShell.hiddenDOMWindow; let shortcut = KeyShortcuts.parseElectronKey(window, key); info("Synthesizing key shortcut: " + key); EventUtils.synthesizeKey(shortcut.key || "", { keyCode: shortcut.keyCode, altKey: shortcut.alt, ctrlKey: shortcut.ctrl, metaKey: shortcut.meta, shiftKey: shortcut.shift - }); + }, target); } /** * Wait for eventName on target to be delivered a number of times. * * @param {Object} target * An observable object that either supports on/off or * addEventListener/removeEventListener
--- a/devtools/client/framework/toolbox.js +++ b/devtools/client/framework/toolbox.js @@ -63,16 +63,19 @@ loader.lazyRequireGetter(this, "DevTools loader.lazyRequireGetter(this, "showDoorhanger", "devtools/client/shared/doorhanger", true); loader.lazyRequireGetter(this, "createPerformanceFront", "devtools/server/actors/performance", true); loader.lazyRequireGetter(this, "system", "devtools/shared/system"); loader.lazyRequireGetter(this, "getPreferenceFront", "devtools/server/actors/preference", true); +loader.lazyRequireGetter(this, "KeyShortcuts", + "devtools/client/shared/key-shortcuts", true); + 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 @@ -412,29 +415,30 @@ Toolbox.prototype = { let noautohideMenu = this.doc.getElementById("command-button-noautohide"); noautohideMenu.addEventListener("command", this._toggleAutohide, true); this.textboxContextMenuPopup = this.doc.getElementById("toolbox-textbox-context-popup"); this.textboxContextMenuPopup.addEventListener("popupshowing", this._updateTextboxMenuItems, true); + var shortcuts = new KeyShortcuts({ + window: this.doc.defaultView + }); this._buildDockButtons(); - this._buildOptions(); + this._buildOptions(shortcuts); this._buildTabs(); this._applyCacheSettings(); this._applyServiceWorkersTestingSettings(); this._addKeysToWindow(); - this._addReloadKeys(); - this._addHostListeners(); + this._addReloadKeys(shortcuts); + this._addHostListeners(shortcuts); this._registerOverlays(); - if (this._hostOptions && this._hostOptions.zoom === false) { - this._disableZoomKeys(); - } else { - this._addZoomKeys(); + if (!this._hostOptions || this._hostOptions.zoom === true) { + this._addZoomKeys(shortcuts); this._loadInitialZoom(); } this._setToolbarKeyboardNavigation(); this.webconsolePanel = this.doc.querySelector("#toolbox-panel-webconsole"); this.webconsolePanel.height = Services.prefs.getIntPref(SPLITCONSOLE_HEIGHT_PREF); this.webconsolePanel.addEventListener("resize", this._saveSplitConsoleHeight); @@ -518,44 +522,45 @@ Toolbox.prototype = { this._applyCacheSettings(); break; case "devtools.serviceWorkers.testing.enabled": this._applyServiceWorkersTestingSettings(); break; } }, - _buildOptions: function () { - let selectOptions = () => { + _buildOptions: function (shortcuts) { + let selectOptions = (name, event) => { // Flip back to the last used panel if we are already // on the options panel. if (this.currentToolId === "options" && gDevTools.getToolDefinition(this.lastUsedToolId)) { this.selectTool(this.lastUsedToolId); } else { this.selectTool("options"); } + // Prevent the opening of bookmarks window on toolbox.options.key + event.preventDefault(); }; - let key = this.doc.getElementById("toolbox-options-key"); - key.addEventListener("command", selectOptions, true); - let key2 = this.doc.getElementById("toolbox-options-key2"); - key2.addEventListener("command", selectOptions, true); + shortcuts.on(toolboxStrings("toolbox.options.key"), selectOptions); + shortcuts.on(toolboxStrings("toolbox.help.key"), selectOptions); }, _splitConsoleOnKeypress: function (e) { if (e.keyCode === e.DOM_VK_ESCAPE) { this.toggleSplitConsole(); // If the debugger is paused, don't let the ESC key stop any pending // navigation. let jsdebugger = this.getPanel("jsdebugger"); if (jsdebugger && jsdebugger.panelWin.gThreadClient.state == "paused") { e.preventDefault(); } } }, + /** * Add a shortcut key that should work when a split console * has focus to the toolbox. * * @param {element} keyElement * They <key> XUL element describing the shortcut key * @param {string} whichTool * The tool the key belongs to. The corresponding command @@ -569,44 +574,41 @@ Toolbox.prototype = { // Only forward the command if the tool is active if (this.currentToolId === whichTool && this.isSplitConsoleFocused()) { keyElement.doCommand(); } }, true); this.doc.getElementById("toolbox-keyset").appendChild(cloned); }, - _addReloadKeys: function () { + _addReloadKeys: function (shortcuts) { [ - ["toolbox-reload-key", false], - ["toolbox-reload-key2", false], - ["toolbox-force-reload-key", true], - ["toolbox-force-reload-key2", true] + ["reload", false], + ["reload2", false], + ["forceReload", true], + ["forceReload2", true] ].forEach(([id, force]) => { - this.doc.getElementById(id).addEventListener("command", () => { - this.reloadTarget(force); - }, true); + let key = toolboxStrings("toolbox." + id + ".key"); + shortcuts.on(key, this.reloadTarget.bind(this, force)); }); }, - _addHostListeners: function () { - let nextKey = this.doc.getElementById("toolbox-next-tool-key"); - nextKey.addEventListener("command", this.selectNextTool.bind(this), true); - - let prevKey = this.doc.getElementById("toolbox-previous-tool-key"); - prevKey.addEventListener("command", this.selectPreviousTool.bind(this), true); + _addHostListeners: function (shortcuts) { + shortcuts.on(toolboxStrings("toolbox.nextTool.key"), + this.selectNextTool.bind(this)); + shortcuts.on(toolboxStrings("toolbox.previousTool.key"), + this.selectPreviousTool.bind(this)); + shortcuts.on(toolboxStrings("toolbox.minimize.key"), + this._toggleMinimizeMode.bind(this)); + shortcuts.on(toolboxStrings("toolbox.toggleHost.key"), + (name, event) => { + this.switchToPreviousHost(); + event.preventDefault(); + }); - let minimizeKey = this.doc.getElementById("toolbox-minimize-key"); - minimizeKey.addEventListener("command", this._toggleMinimizeMode, true); - - let toggleKey = this.doc.getElementById("toolbox-toggle-host-key"); - toggleKey.addEventListener("command", this.switchToPreviousHost.bind(this), true); - - // Split console uses keypress instead of command so the event can be - // cancelled with stopPropagation on the keypress, and not preventDefault. this.doc.addEventListener("keypress", this._splitConsoleOnKeypress, false); this.doc.addEventListener("focus", this._onFocus, true); }, _registerOverlays: function () { registerHarOverlay(this); }, @@ -648,88 +650,72 @@ Toolbox.prototype = { splitter.setAttribute("hidden", "true"); } } }, /** * Wire up the listeners for the zoom keys. */ - _addZoomKeys: function () { - let inKey = this.doc.getElementById("toolbox-zoom-in-key"); - inKey.addEventListener("command", this.zoomIn.bind(this), true); - - let inKey2 = this.doc.getElementById("toolbox-zoom-in-key2"); - inKey2.addEventListener("command", this.zoomIn.bind(this), true); - - let inKey3 = this.doc.getElementById("toolbox-zoom-in-key3"); - inKey3.addEventListener("command", this.zoomIn.bind(this), true); - - let outKey = this.doc.getElementById("toolbox-zoom-out-key"); - outKey.addEventListener("command", this.zoomOut.bind(this), true); - - let outKey2 = this.doc.getElementById("toolbox-zoom-out-key2"); - outKey2.addEventListener("command", this.zoomOut.bind(this), true); - - let resetKey = this.doc.getElementById("toolbox-zoom-reset-key"); - resetKey.addEventListener("command", this.zoomReset.bind(this), true); + _addZoomKeys: function (shortcuts) { + shortcuts.on(toolboxStrings("toolbox.zoomIn.key"), + this.zoomIn.bind(this)); + let zoomIn2 = toolboxStrings("toolbox.zoomIn2.key"); + if (zoomIn2) { + shortcuts.on(zoomIn2, this.zoomIn.bind(this)); + } + let zoomIn3 = toolboxStrings("toolbox.zoomIn2.key"); + if (zoomIn3) { + shortcuts.on(zoomIn3, this.zoomIn.bind(this)); + } - let resetKey2 = this.doc.getElementById("toolbox-zoom-reset-key2"); - resetKey2.addEventListener("command", this.zoomReset.bind(this), true); - }, - - _disableZoomKeys: function () { - let inKey = this.doc.getElementById("toolbox-zoom-in-key"); - inKey.setAttribute("disabled", "true"); - - let inKey2 = this.doc.getElementById("toolbox-zoom-in-key2"); - inKey2.setAttribute("disabled", "true"); + shortcuts.on(toolboxStrings("toolbox.zoomOut.key"), + this.zoomOut.bind(this)); + let zoomOut2 = toolboxStrings("toolbox.zoomOut2.key"); + if (zoomOut2) { + shortcuts.on(zoomOut2, this.zoomOut.bind(this)); + } - let inKey3 = this.doc.getElementById("toolbox-zoom-in-key3"); - inKey3.setAttribute("disabled", "true"); - - let outKey = this.doc.getElementById("toolbox-zoom-out-key"); - outKey.setAttribute("disabled", "true"); - - let outKey2 = this.doc.getElementById("toolbox-zoom-out-key2"); - outKey2.setAttribute("disabled", "true"); - - let resetKey = this.doc.getElementById("toolbox-zoom-reset-key"); - resetKey.setAttribute("disabled", "true"); - - let resetKey2 = this.doc.getElementById("toolbox-zoom-reset-key2"); - resetKey2.setAttribute("disabled", "true"); + shortcuts.on(toolboxStrings("toolbox.zoomReset.key"), + this.zoomReset.bind(this)); + let zoomReset2 = toolboxStrings("toolbox.zoomReset2.key"); + if (zoomReset2) { + shortcuts.on(zoomReset2, this.zoomReset.bind(this)); + } }, /** * Set zoom on toolbox to whatever the last setting was. */ _loadInitialZoom: function () { this.setZoom(this.zoomValue); }, /** * Increase zoom level of toolbox window - make things bigger. */ - zoomIn: function () { + zoomIn: function (name, event) { this.setZoom(this.zoomValue + 0.1); + event.preventDefault(); }, /** * Decrease zoom level of toolbox window - make things smaller. */ - zoomOut: function () { + zoomOut: function (name, event) { this.setZoom(this.zoomValue - 0.1); + event.preventDefault(); }, /** * Reset zoom level of the toolbox window. */ - zoomReset: function () { + zoomReset: function (name, event) { this.setZoom(1); + event.preventDefault(); }, /** * Set zoom level of the toolbox window. * * @param {number} zoomValue * Zoom level e.g. 1.2 */ @@ -895,20 +881,19 @@ Toolbox.prototype = { this.switchHost(position); }); dockBox.appendChild(button); } }, _getMinimizeButtonShortcutTooltip: function () { - let key = this.doc.getElementById("toolbox-minimize-key") - .getAttribute("key"); - return "(" + (osString == "Darwin" ? "Cmd+Shift+" : "Ctrl+Shift+") + - key.toUpperCase() + ")"; + let str = toolboxStrings("toolbox.minimize.key"); + let key = KeyShortcuts.parseElectronKey(this.win, str); + return "(" + KeyShortcuts.stringify(key) + ")"; }, _onBottomHostMinimized: function () { let btn = this.doc.querySelector("#toolbox-dock-bottom-minimize"); btn.className = "minimized"; btn.setAttribute("tooltiptext", toolboxStrings("toolboxDockButtons.bottom.maximize") + " " +
--- a/devtools/client/framework/toolbox.xul +++ b/devtools/client/framework/toolbox.xul @@ -24,85 +24,17 @@ src="chrome://global/content/viewSourceUtils.js"/> <script type="application/javascript" src="chrome://global/content/globalOverlay.js"/> <script type="application/javascript;version=1.8" src="chrome://devtools/content/framework/toolbox-init.js"/> <commandset id="editMenuCommands"/> <keyset id="editMenuKeys"/> - <keyset id="toolbox-keyset"> - <key id="toolbox-options-key" - key="&toolboxOptionsButton.key;" - oncommand="void(0);" - modifiers="shift, accel"/> - <key id="toolbox-options-key2" - keycode="&openHelp.commandkey;" - oncommand="void(0);"/> - <key id="toolbox-next-tool-key" - key="&toolboxNextTool.key;" - oncommand="void(0);" - modifiers="accel"/> - <key id="toolbox-previous-tool-key" - key="&toolboxPreviousTool.key;" - oncommand="void(0);" - modifiers="accel"/> - <key id="toolbox-zoom-in-key" - key="&fullZoomEnlargeCmd.commandkey;" - oncommand="void(0);" - modifiers="accel"/> - <key id="toolbox-zoom-in-key2" - key="&fullZoomEnlargeCmd.commandkey2;" - oncommand="void(0);" - modifiers="accel"/> - <key id="toolbox-zoom-in-key3" - key="&fullZoomEnlargeCmd.commandkey3;" - oncommand="void(0);" - modifiers="accel"/> - <key id="toolbox-zoom-out-key" - key="&fullZoomReduceCmd.commandkey;" - oncommand="void(0);" - modifiers="accel"/> - <key id="toolbox-zoom-out-key2" - key="&fullZoomReduceCmd.commandkey2;" - oncommand="void(0);" - modifiers="accel"/> - <key id="toolbox-zoom-reset-key" - key="&fullZoomResetCmd.commandkey;" - oncommand="void(0);" - modifiers="accel"/> - <key id="toolbox-zoom-reset-key2" - key="&fullZoomResetCmd.commandkey2;" - oncommand="void(0);" - modifiers="accel"/> - <key id="toolbox-reload-key" - key="&toolboxReload.key;" - oncommand="void(0);" - modifiers="accel"/> - <key id="toolbox-force-reload-key" - key="&toolboxReload.key;" - oncommand="void(0);" - modifiers="accel shift"/> - <key id="toolbox-reload-key2" - keycode="VK_F5" - oncommand="void(0);" - modifiers=""/> - <key id="toolbox-force-reload-key2" - keycode="VK_F5" - oncommand="void(0);" - modifiers="accel"/> - <key id="toolbox-minimize-key" - key="&toolboxToggleMinimize.key;" - oncommand="void(0);" - modifiers="shift, accel"/> - <key id="toolbox-toggle-host-key" - key="&toolboxToggle.key;" - oncommand="void(0);" - modifiers="accel shift"/> - </keyset> + <keyset id="toolbox-keyset"/> <popupset> <menupopup id="toolbox-textbox-context-popup"> <menuitem id="cMenu_undo"/> <menuseparator/> <menuitem id="cMenu_cut"/> <menuitem id="cMenu_copy"/> <menuitem id="cMenu_paste"/>
--- a/devtools/client/locales/en-US/toolbox.dtd +++ b/devtools/client/locales/en-US/toolbox.dtd @@ -6,41 +6,17 @@ <!-- LOCALIZATION NOTE : FILE Do not translate key --> <!ENTITY closeCmd.key "W"> <!ENTITY toggleToolbox.key "I"> <!ENTITY toggleToolboxF12.keycode "VK_F12"> <!ENTITY toggleToolboxF12.keytext "F12"> <!ENTITY toolboxCloseButton.tooltip "Close Developer Tools"> -<!ENTITY toolboxOptionsButton.key "O"> -<!ENTITY toolboxNextTool.key "]"> -<!ENTITY toolboxPreviousTool.key "["> -<!-- LOCALIZATION NOTE : -fullZoomEnlargeCmd.commandkey, fullZoomEnlargeCmd.commandkey2, -fullZoomEnlargeCmd.commandkey3, fullZoomReduceCmd.commandkey, -fullZoomReduceCmd.commandkey2, fullZoomResetCmd.commandkey, -and fullZoomResetCmd.commandkey2 should all match the corresponding -values from browser.dtd. --> -<!ENTITY fullZoomEnlargeCmd.commandkey "+"> -<!ENTITY fullZoomEnlargeCmd.commandkey2 "="> -<!ENTITY fullZoomEnlargeCmd.commandkey3 ""> - -<!ENTITY fullZoomReduceCmd.commandkey "-"> -<!ENTITY fullZoomReduceCmd.commandkey2 ""> - -<!ENTITY fullZoomResetCmd.commandkey "0"> -<!ENTITY fullZoomResetCmd.commandkey2 ""> - -<!ENTITY toolboxReload.key "r"> -<!-- This key is used with the accel+shift modifiers to minimize the toolbox --> -<!ENTITY toolboxToggleMinimize.key "U"> - -<!ENTITY toolboxToggle.key "d"> <!-- LOCALIZATION NOTE (toolboxFramesButton): This is the label for - the iframes menu list that appears only when the document has some. - It allows you to switch the context of the whole toolbox. --> <!ENTITY toolboxFramesTooltip "Select an iframe as the currently targeted document"> <!-- LOCALIZATION NOTE (toolboxNoAutoHideButton): This is the label for - the button to force the popups/panels to stay visible on blur. - This is only visible in the browser toolbox as it is meant for
--- a/devtools/client/locales/en-US/toolbox.properties +++ b/devtools/client/locales/en-US/toolbox.properties @@ -130,8 +130,55 @@ toolbox.viewCssSourceInStyleEditor.label # LOCALIZATION NOTE (toolbox.viewJsSourceInDebugger.label) # Used as a message in either tooltips or contextual menu items to open the # corresponding URL as a js file in the Debugger tool. # DEV NOTE: Mostly used wherever toolbox.viewSourceInDebugger is used. toolbox.viewJsSourceInDebugger.label=Open File in Debugger toolbox.resumeOrderWarning=Page did not resume after the debugger was attached. To fix this, please close and re-open the toolbox. + +# LOCALIZATION NOTE (toolbox.options.key) +# Key shortcut used to open the options panel +toolbox.options.key=CmdOrCtrl+Shift+O + +# LOCALIZATION NOTE (toolbox.help.key) +# Key shortcut used to open the options panel +toolbox.help.key=F1 + +# LOCALIZATION NOTE (toolbox.nextTool.key) +# Key shortcut used to select the next tool +toolbox.nextTool.key=CmdOrCtrl+] + +# LOCALIZATION NOTE (toolbox.previousTool.key) +# Key shortcut used to select the previous tool +toolbox.previousTool.key=CmdOrCtrl+[ + +# LOCALIZATION NOTE (toolbox.zoom*.key) +# Key shortcuts used to zomm in/out or reset the toolbox +# Should match fullZoom*Cmd.commandkey values from browser.dtd +toolbox.zoomIn.key=CmdOrCtrl+Plus +toolbox.zoomIn2.key=CmdOrCtrl+= +toolbox.zoomIn3.key= + +toolbox.zoomOut.key=CmdOrCtrl+- +toolbox.zoomOut2.key= + +toolbox.zoomReset.key=CmdOrCtrl+0 +toolbox.zoomReset2.key= + +# LOCALIZATION NOTE (toolbox.reload*.key) +# Key shortcuts used to reload the page +toolbox.reload.key=CmdOrCtrl+R +toolbox.reload2.key=F5 + +# LOCALIZATION NOTE (toolbox.forceReload*.key) +# Key shortcuts used to force reload of the page by bypassing caches +toolbox.forceReload.key=CmdOrCtrl+Shift+R +toolbox.forceReload2.key=CmdOrCtrl+F5 + +# LOCALIZATION NOTE (toolbox.minimize.key) +# Key shortcut used to minimize the toolbox +toolbox.minimize.key=CmdOrCtrl+Shift+U + +# LOCALIZATION NOTE (toolbox.toggleHost.key) +# Key shortcut used to move the toolbox in bottom or side of the browser window +toolbox.toggleHost.key=CmdOrCtrl+Shift+D
--- a/devtools/client/shared/key-shortcuts.js +++ b/devtools/client/shared/key-shortcuts.js @@ -29,17 +29,16 @@ const ElectronKeysMapping = { "F17": "DOM_VK_F17", "F18": "DOM_VK_F18", "F19": "DOM_VK_F19", "F20": "DOM_VK_F20", "F21": "DOM_VK_F21", "F22": "DOM_VK_F22", "F23": "DOM_VK_F23", "F24": "DOM_VK_F24", - "Plus": "DOM_VK_PLUS", "Space": "DOM_VK_SPACE", "Backspace": "DOM_VK_BACK_SPACE", "Delete": "DOM_VK_DELETE", "Insert": "DOM_VK_INSERT", "Return": "DOM_VK_RETURN", "Enter": "DOM_VK_RETURN", "Up": "DOM_VK_UP", "Down": "DOM_VK_DOWN", @@ -117,30 +116,62 @@ KeyShortcuts.parseElectronKey = function shortcut.ctrl = true; } else if (mod === "Shift") { shortcut.shift = true; } else { throw new Error("Unsupported modifier: " + mod); } } - if (typeof (key) === "string" && key.length === 1) { + // Plus is a special case. It's a character key and shouldn't be matched + // against a keycode as it is only accessible via Shift/Capslock + if (key === "Plus") { + key = "+"; + } + + if (typeof key === "string" && key.length === 1) { // Match any single character shortcut.key = key.toLowerCase(); } else if (key in ElectronKeysMapping) { // Maps the others manually to DOM API DOM_VK_* key = ElectronKeysMapping[key]; shortcut.keyCode = window.KeyboardEvent[key]; + // Used only to stringify the shortcut + shortcut.keyCodeString = key; } else { throw new Error("Unsupported key: " + key); } return shortcut; }; +KeyShortcuts.stringify = function (shortcut) { + let list = []; + if (shortcut.alt) { + list.push("Alt"); + } + if (shortcut.ctrl) { + list.push("Ctrl"); + } + if (shortcut.meta) { + list.push("Cmd"); + } + if (shortcut.shift) { + list.push("Shift"); + } + let key; + if (shortcut.key) { + key = shortcut.key.toUpperCase(); + } else { + key = shortcut.keyCodeString; + } + list.push(key); + return list.join("+"); +}; + KeyShortcuts.prototype = { destroy() { this.window.removeEventListener("keydown", this); this.keys.clear(); }, doesEventMatchShortcut(event, shortcut) { if (shortcut.meta != event.metaKey) { @@ -156,17 +187,22 @@ KeyShortcuts.prototype = { // expected key is a special character accessible via shift. if (shortcut.shift != event.shiftKey && event.key && event.key.match(/[a-zA-Z]/)) { return false; } if (shortcut.keyCode) { return event.keyCode == shortcut.keyCode; } - return event.key.toLowerCase() == shortcut.key; + // For character keys, we match if the final character is the expected one. + // But for digits we also accept indirect match to please azerty keyboard, + // which requires Shift to be pressed to get digits. + return event.key.toLowerCase() == shortcut.key || + (shortcut.key.match(/[0-9]/) && + event.keyCode == shortcut.key.charCodeAt(0)); }, handleEvent(event) { for (let [key, shortcut] of this.keys) { if (this.doesEventMatchShortcut(event, shortcut)) { this.eventEmitter.emit(key, event); } }
--- a/devtools/client/shared/test/browser.ini +++ b/devtools/client/shared/test/browser.ini @@ -112,16 +112,18 @@ skip-if = e10s # Bug 1221911, bug 122228 skip-if = e10s # Bug 1221911, bug 1222289, frequent e10s timeouts [browser_graphs-16.js] skip-if = e10s # Bug 1221911, bug 1222289, frequent e10s timeouts [browser_html_tooltip-01.js] [browser_html_tooltip-02.js] [browser_html_tooltip-03.js] [browser_html_tooltip-04.js] [browser_html_tooltip-05.js] +[browser_html_tooltip_arrow-01.js] +[browser_html_tooltip_arrow-02.js] [browser_inplace-editor-01.js] [browser_inplace-editor-02.js] [browser_inplace-editor_maxwidth.js] [browser_key_shortcuts.js] [browser_layoutHelpers.js] skip-if = e10s # Layouthelpers test should not run in a content page. [browser_layoutHelpers-getBoxQuads.js] skip-if = e10s # Layouthelpers test should not run in a content page.
--- a/devtools/client/shared/test/browser_html_tooltip-02.js +++ b/devtools/client/shared/test/browser_html_tooltip-02.js @@ -6,16 +6,17 @@ /** * Test the HTMLTooltip is closed when clicking outside of its container. */ const HTML_NS = "http://www.w3.org/1999/xhtml"; const TEST_URI = `data:text/xml;charset=UTF-8,<?xml version="1.0"?> <?xml-stylesheet href="chrome://global/skin/global.css"?> + <?xml-stylesheet href="chrome://devtools/skin/common.css"?> <window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" title="Tooltip test"> <vbox flex="1"> <hbox id="box1" flex="1">test1</hbox> <hbox id="box2" flex="1">test2</hbox> <hbox id="box3" flex="1">test3</hbox> <hbox id="box4" flex="1">test4</hbox> </vbox>
--- a/devtools/client/shared/test/browser_html_tooltip-03.js +++ b/devtools/client/shared/test/browser_html_tooltip-03.js @@ -6,16 +6,17 @@ /** * Test the HTMLTooltip autofocus configuration option. */ const HTML_NS = "http://www.w3.org/1999/xhtml"; const TEST_URI = `data:text/xml;charset=UTF-8,<?xml version="1.0"?> <?xml-stylesheet href="chrome://global/skin/global.css"?> + <?xml-stylesheet href="chrome://devtools/skin/common.css"?> <window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" title="Tooltip test"> <vbox flex="1"> <hbox id="box1" flex="1">test1</hbox> <hbox id="box2" flex="1">test2</hbox> <hbox id="box3" flex="1">test3</hbox> <hbox id="box4" flex="1"> <textbox id="box4-input"></textbox>
new file mode 100644 --- /dev/null +++ b/devtools/client/shared/test/browser_html_tooltip_arrow-01.js @@ -0,0 +1,95 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ +/* import-globals-from helper_html_tooltip.js */ + +"use strict"; + +/** + * Test the HTMLTooltip "arrow" type on small anchors. The arrow should remain + * aligned with the anchors as much as possible + */ + +const HTML_NS = "http://www.w3.org/1999/xhtml"; +const getAnchor = function (position) { + return `<html:div class="anchor" style="width:10px; + height: 10px; + position: absolute; + background: red; + ${position}"></html:div>`; +}; + +const TEST_URI = `data:text/xml;charset=UTF-8,<?xml version="1.0"?> + <?xml-stylesheet href="chrome://global/skin/global.css"?> + <?xml-stylesheet href="chrome://devtools/skin/common.css"?> + <?xml-stylesheet href="chrome://devtools/skin/light-theme.css"?> + + <window class="theme-light" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + xmlns:html="http://www.w3.org/1999/xhtml" + title="Tooltip test"> + <vbox flex="1" style="position: relative"> + ${getAnchor("top: 0; left: 0;")} + ${getAnchor("top: 0; left: 25px;")} + ${getAnchor("top: 0; left: 50px;")} + ${getAnchor("top: 0; left: 75px;")} + ${getAnchor("bottom: 0; left: 0;")} + ${getAnchor("bottom: 0; left: 25px;")} + ${getAnchor("bottom: 0; left: 50px;")} + ${getAnchor("bottom: 0; left: 75px;")} + ${getAnchor("bottom: 0; right: 0;")} + ${getAnchor("bottom: 0; right: 25px;")} + ${getAnchor("bottom: 0; right: 50px;")} + ${getAnchor("bottom: 0; right: 75px;")} + ${getAnchor("top: 0; right: 0;")} + ${getAnchor("top: 0; right: 25px;")} + ${getAnchor("top: 0; right: 50px;")} + ${getAnchor("top: 0; right: 75px;")} + </vbox> + </window>`; + +const {HTMLTooltip} = require("devtools/client/shared/widgets/HTMLTooltip"); +loadHelperScript("helper_html_tooltip.js"); + +add_task(function* () { + // Force the toolbox to be 200px high; + yield pushPref("devtools.toolbox.footer.height", 200); + + yield addTab("about:blank"); + let [,, doc] = yield createHost("bottom", TEST_URI); + + info("Create HTML tooltip"); + let tooltip = new HTMLTooltip({doc}, {type: "arrow"}); + let div = doc.createElementNS(HTML_NS, "div"); + div.style.height = "100%"; + yield tooltip.setContent(div, 200, 35); + + let {right: docRight} = doc.documentElement.getBoundingClientRect(); + + let elements = [...doc.querySelectorAll(".anchor")]; + for (let el of elements) { + info("Display the tooltip on an anchor."); + yield showTooltip(tooltip, el); + + let arrow = tooltip.arrow; + ok(arrow, "Tooltip has an arrow"); + + // Get the geometry of the anchor, the tooltip frame & arrow. + let arrowBounds = arrow.getBoxQuads({relativeTo: doc})[0].bounds; + let frameBounds = tooltip.frame.getBoxQuads({relativeTo: doc})[0].bounds; + let anchorBounds = el.getBoxQuads({relativeTo: doc})[0].bounds; + + let intersects = arrowBounds.left <= anchorBounds.right && + arrowBounds.right >= anchorBounds.left; + let isBlockedByViewport = arrowBounds.left == 0 || + arrowBounds.right == docRight; + ok(intersects || isBlockedByViewport, + "Tooltip arrow is aligned with the anchor, or stuck on viewport's edge."); + + let isInFrame = arrowBounds.left >= frameBounds.left && + arrowBounds.right <= frameBounds.right; + ok(isInFrame, + "The tooltip arrow remains inside the tooltip frame horizontally"); + + yield hideTooltip(tooltip); + } +});
new file mode 100644 --- /dev/null +++ b/devtools/client/shared/test/browser_html_tooltip_arrow-02.js @@ -0,0 +1,88 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ +/* import-globals-from helper_html_tooltip.js */ + +"use strict"; + +/** + * Test the HTMLTooltip "arrow" type on wide anchors. The arrow should remain + * aligned with the anchors as much as possible + */ + +const HTML_NS = "http://www.w3.org/1999/xhtml"; +const getAnchor = function (position) { + return `<html:div class="anchor" style="height: 5px; + position: absolute; + background: red; + ${position}"></html:div>`; +}; + +const TEST_URI = `data:text/xml;charset=UTF-8,<?xml version="1.0"?> + <?xml-stylesheet href="chrome://global/skin/global.css"?> + <?xml-stylesheet href="chrome://devtools/skin/common.css"?> + <?xml-stylesheet href="chrome://devtools/skin/light-theme.css"?> + + <window class="theme-light" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + xmlns:html="http://www.w3.org/1999/xhtml" + title="Tooltip test"> + <vbox flex="1" style="position: relative"> + ${getAnchor("top: 0; left: 0; width: 50px;")} + ${getAnchor("top: 10px; left: 0; width: 100px;")} + ${getAnchor("top: 20px; left: 0; width: 150px;")} + ${getAnchor("top: 30px; left: 0; width: 200px;")} + ${getAnchor("top: 40px; left: 0; width: 250px;")} + ${getAnchor("top: 50px; left: 100px; width: 250px;")} + ${getAnchor("top: 100px; width: 50px; right: 0;")} + ${getAnchor("top: 110px; width: 100px; right: 0;")} + ${getAnchor("top: 120px; width: 150px; right: 0;")} + ${getAnchor("top: 130px; width: 200px; right: 0;")} + ${getAnchor("top: 140px; width: 250px; right: 0;")} + </vbox> + </window>`; + +const {HTMLTooltip} = require("devtools/client/shared/widgets/HTMLTooltip"); +loadHelperScript("helper_html_tooltip.js"); + +add_task(function* () { + // Force the toolbox to be 200px high; + yield pushPref("devtools.toolbox.footer.height", 200); + + yield addTab("about:blank"); + let [,, doc] = yield createHost("bottom", TEST_URI); + + info("Create HTML tooltip"); + let tooltip = new HTMLTooltip({doc}, {type: "arrow"}); + let div = doc.createElementNS(HTML_NS, "div"); + div.style.height = "100%"; + yield tooltip.setContent(div, 200, 35); + + let {right: docRight} = doc.documentElement.getBoundingClientRect(); + + let elements = [...doc.querySelectorAll(".anchor")]; + for (let el of elements) { + info("Display the tooltip on an anchor."); + yield showTooltip(tooltip, el); + + let arrow = tooltip.arrow; + ok(arrow, "Tooltip has an arrow"); + + // Get the geometry of the anchor, the tooltip frame & arrow. + let arrowBounds = arrow.getBoxQuads({relativeTo: doc})[0].bounds; + let frameBounds = tooltip.frame.getBoxQuads({relativeTo: doc})[0].bounds; + let anchorBounds = el.getBoxQuads({relativeTo: doc})[0].bounds; + + let intersects = arrowBounds.left <= anchorBounds.right && + arrowBounds.right >= anchorBounds.left; + let isBlockedByViewport = arrowBounds.left == 0 || + arrowBounds.right == docRight; + ok(intersects || isBlockedByViewport, + "Tooltip arrow is aligned with the anchor, or stuck on viewport's edge."); + + let isInFrame = arrowBounds.left >= frameBounds.left && + arrowBounds.right <= frameBounds.right; + ok(isInFrame, + "The tooltip arrow remains inside the tooltip frame horizontally"); + yield hideTooltip(tooltip); + } +});
--- a/devtools/client/shared/test/browser_key_shortcuts.js +++ b/devtools/client/shared/test/browser_key_shortcuts.js @@ -4,17 +4,19 @@ var isOSX = Services.appinfo.OS === "Darwin"; add_task(function* () { let shortcuts = new KeyShortcuts({ window }); yield testSimple(shortcuts); yield testNonLetterCharacter(shortcuts); + yield testPlusCharacter(shortcuts); yield testMixup(shortcuts); + yield testLooseDigits(shortcuts); yield testExactModifiers(shortcuts); yield testLooseShiftModifier(shortcuts); yield testStrictLetterShiftModifier(shortcuts); yield testAltModifier(shortcuts); yield testCommandOrControlModifier(shortcuts); yield testCtrlModifier(shortcuts); shortcuts.destroy(); }); @@ -56,16 +58,30 @@ function testNonLetterCharacter(shortcut let onKey = once(shortcuts, "[", (key, event) => { is(event.key, "["); }); EventUtils.synthesizeKey("[", {}, window); yield onKey; } +// Plus is special. It's keycode is the one for "=". That's because it requires +// shift to be pressed and is behind "=" key. So it should be considered as a +// character key +function testPlusCharacter(shortcuts) { + info("Test 'Plus' key shortcuts"); + + let onKey = once(shortcuts, "Plus", (key, event) => { + is(event.key, "+"); + }); + + EventUtils.synthesizeKey("+", { keyCode: 61, shiftKey: true }, window); + yield onKey; +} + // Test they listeners are not mixed up between shortcuts function testMixup(shortcuts) { info("Test possible listener mixup"); let hitFirst = false, hitSecond = false; let onFirstKey = once(shortcuts, "0", (key, event) => { is(event.key, "0"); hitFirst = true; @@ -90,16 +106,50 @@ function testMixup(shortcuts) { ok(!hitSecond, "No mixup, second shortcut is still not notified (2/2)"); // Finally dispatch the second shortcut EventUtils.synthesizeKey("a", { altKey: true }, window); yield onSecondKey; ok(hitSecond, "Got the second shortcut notified once it is actually fired"); } +// On azerty keyboard, digits are only available by pressing Shift/Capslock, +// but we accept them even if we omit doing that. +function testLooseDigits(shortcuts) { + info("Test Loose digits"); + let onKey = once(shortcuts, "0", (key, event) => { + is(event.key, "Ã "); + ok(!event.altKey); + ok(!event.ctrlKey); + ok(!event.metaKey); + ok(!event.shiftKey); + }); + // Simulate a press on the "0" key, without shift pressed on a french + // keyboard + EventUtils.synthesizeKey( + "Ã ", + { keyCode: 48 }, + window); + yield onKey; + + onKey = once(shortcuts, "0", (key, event) => { + is(event.key, "0"); + ok(!event.altKey); + ok(!event.ctrlKey); + ok(!event.metaKey); + ok(event.shiftKey); + }); + // Simulate the same press with shift pressed + EventUtils.synthesizeKey( + "0", + { keyCode: 48, shiftKey: true }, + window); + yield onKey; +} + // Test that shortcuts is notified only when the modifiers match exactly function testExactModifiers(shortcuts) { info("Test exact modifiers match"); let hit = false; let onKey = once(shortcuts, "Alt+A", (key, event) => { is(event.key, "a"); ok(event.altKey);
--- a/devtools/client/shared/test/helper_html_tooltip.js +++ b/devtools/client/shared/test/helper_html_tooltip.js @@ -47,20 +47,20 @@ function* hideTooltip(tooltip) { /** * Forces the reflow of an HTMLTooltip document and waits for the next repaint. * * @param {HTMLTooltip} the tooltip to reflow * @return {Promise} a promise that will resolve after the reflow and repaint * have been executed. */ function waitForReflow(tooltip) { - let {document} = tooltip; + let {doc} = tooltip; return new Promise(resolve => { - document.documentElement.offsetWidth; - document.defaultView.requestAnimationFrame(resolve); + doc.documentElement.offsetWidth; + doc.defaultView.requestAnimationFrame(resolve); }); } /** * Test helper designed to check that a tooltip is displayed at the expected * position relative to an anchor, given a set of expectations. * * @param {HTMLTooltip} tooltip
--- a/devtools/client/shared/widgets/HTMLTooltip.js +++ b/devtools/client/shared/widgets/HTMLTooltip.js @@ -8,101 +8,153 @@ const EventEmitter = require("devtools/shared/event-emitter"); const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"; const XHTML_NS = "http://www.w3.org/1999/xhtml"; const IFRAME_URL = "chrome://devtools/content/shared/widgets/tooltip-frame.xhtml"; const IFRAME_CONTAINER_ID = "tooltip-iframe-container"; +const POSITION = { + TOP: "top", + BOTTOM: "bottom", +}; + +module.exports.POSITION = POSITION; + +const TYPE = { + NORMAL: "normal", + ARROW: "arrow", +}; + +module.exports.TYPE = TYPE; + +const ARROW_WIDTH = 32; + +// Default offset between the tooltip's left edge and the tooltip arrow. +const ARROW_OFFSET = 20; + +const EXTRA_HEIGHT = { + "normal": 0, + // The arrow is 16px tall, but merges on 3px with the panel border + "arrow": 13, +}; + +const EXTRA_BORDER = { + "normal": 0, + "arrow": 3, +}; + /** * The HTMLTooltip can display HTML content in a tooltip popup. * * @param {Toolbox} toolbox * The devtools toolbox, needed to get the devtools main window. * @param {Object} * - {String} type - * Display type of the tooltip. Possible values: "normal" + * Display type of the tooltip. Possible values: "normal", "arrow" * - {Boolean} autofocus * Defaults to true. Should the tooltip be focused when opening it. * - {Boolean} consumeOutsideClicks * Defaults to true. The tooltip is closed when clicking outside. * Should this event be stopped and consumed or not. */ function HTMLTooltip(toolbox, {type = "normal", autofocus = true, consumeOutsideClicks = true} = {}) { EventEmitter.decorate(this); - this.document = toolbox.doc; + this.doc = toolbox.doc; this.type = type; this.autofocus = autofocus; this.consumeOutsideClicks = consumeOutsideClicks; // Use the topmost window to listen for click events to close the tooltip - this.topWindow = this.document.defaultView.top; + this.topWindow = this.doc.defaultView.top; this._onClick = this._onClick.bind(this); this.container = this._createContainer(); // Promise that will resolve when the container can be filled with content. this.containerReady = new Promise(resolve => { if (this._isXUL()) { // In XUL context, load a placeholder document in the iframe container. let onLoad = () => { - this.container.removeEventListener("load", onLoad, true); + this.frame.removeEventListener("load", onLoad, true); resolve(); }; - this.container.addEventListener("load", onLoad, true); - this.container.setAttribute("src", IFRAME_URL); + this.frame.addEventListener("load", onLoad, true); + this.frame.setAttribute("src", IFRAME_URL); + this.doc.querySelector("window").appendChild(this.container); } else { // In non-XUL context the container is ready to use as is. + this.doc.body.appendChild(this.container); resolve(); } }); } module.exports.HTMLTooltip = HTMLTooltip; HTMLTooltip.prototype = { - position: { - TOP: "top", - BOTTOM: "bottom", + /** + * The tooltip frame is the child of the tooltip container that will only + * contain the tooltip content (and not the arrow or any other tooltip styling + * element). + * In XUL contexts, this is an iframe. In non XUL contexts this is a div, + * which also happens to be the tooltip.panel property. + */ + get frame() { + return this.container.querySelector(".tooltip-panel"); }, - get parent() { - if (this._isXUL()) { - // In XUL context, we are wrapping the HTML content in an iframe. - let win = this.container.contentWindow.wrappedJSObject; - return win.document.getElementById(IFRAME_CONTAINER_ID); + /** + * The tooltip panel is the parentNode of the tooltip content provided in + * setContent(). + */ + get panel() { + if (!this._isXUL()) { + return this.frame; } - return this.container; + // In XUL context, the content is wrapped in an iframe. + let win = this.frame.contentWindow.wrappedJSObject; + return win.document.getElementById(IFRAME_CONTAINER_ID); + }, + + /** + * The arrow element. Might be null depending on the tooltip type. + */ + get arrow() { + return this.container.querySelector(".tooltip-arrow"); }, /** * Set the tooltip content element. The preferred width/height should also be * specified here. * * @param {Element} content * The tooltip content, should be a HTML element. * @param {Number} width * Preferred width for the tooltip container * @param {Number} height * Preferred height for the tooltip container * @return {Promise} a promise that will resolve when the content has been * added in the tooltip container. */ setContent: function (content, width, height) { - this.preferredWidth = width; - this.preferredHeight = height; + let themeHeight = EXTRA_HEIGHT[this.type] + 2 * EXTRA_BORDER[this.type]; + let themeWidth = 2 * EXTRA_BORDER[this.type]; + + this.preferredWidth = width + themeWidth; + this.preferredHeight = height + themeHeight; return this.containerReady.then(() => { - this.parent.innerHTML = ""; - this.parent.appendChild(content); + this.panel.innerHTML = ""; + this.panel.appendChild(content); }); }, /** * Show the tooltip next to the provided anchor element. A preferred position * can be set. The event "shown" will be fired after the tooltip is displayed. * * @param {Element} anchor @@ -110,186 +162,217 @@ HTMLTooltip.prototype = { * @param {Object} * - {String} position: optional, possible values: top|bottom * If layout permits, the tooltip will be displayed on top/bottom * of the anchor. If ommitted, the tooltip will be displayed where * more space is available. */ show: function (anchor, {position} = {}) { this.containerReady.then(() => { - let {top, left, width, height} = this._findBestPosition(anchor, position); + let computedPosition = this._findBestPosition(anchor, position); + + let isTop = computedPosition.position === POSITION.TOP; + this.container.classList.toggle("tooltip-top", isTop); + this.container.classList.toggle("tooltip-bottom", !isTop); - if (this._isXUL()) { - this.container.setAttribute("width", width); - this.container.setAttribute("height", height); - } else { - this.container.style.width = width + "px"; - this.container.style.height = height + "px"; + this.container.style.width = computedPosition.width + "px"; + this.container.style.height = computedPosition.height + "px"; + this.container.style.top = computedPosition.top + "px"; + this.container.style.left = computedPosition.left + "px"; + + if (this.type === TYPE.ARROW) { + this.arrow.style.left = computedPosition.arrowLeft + "px"; } - this.container.style.top = top + "px"; - this.container.style.left = left + "px"; - this.container.style.display = "block"; + this.container.classList.add("tooltip-visible"); - if (this.autofocus) { - this.container.focus(); - } - - this.attachEventsTimer = this.document.defaultView.setTimeout(() => { + this.attachEventsTimer = this.doc.defaultView.setTimeout(() => { + if (this.autofocus) { + this.frame.focus(); + } this.topWindow.addEventListener("click", this._onClick, true); this.emit("shown"); }, 0); }); }, /** * Hide the current tooltip. The event "hidden" will be fired when the tooltip * is hidden. */ hide: function () { - this.document.defaultView.clearTimeout(this.attachEventsTimer); + this.doc.defaultView.clearTimeout(this.attachEventsTimer); if (this.isVisible()) { this.topWindow.removeEventListener("click", this._onClick, true); - this.container.style.display = "none"; + this.container.classList.remove("tooltip-visible"); this.emit("hidden"); } }, /** * Check if the tooltip is currently displayed. * @return {Boolean} true if the tooltip is visible */ isVisible: function () { - let win = this.document.defaultView; - return win.getComputedStyle(this.container).display != "none"; + return this.container.classList.contains("tooltip-visible"); }, /** * Destroy the tooltip instance. Hide the tooltip if displayed, remove the * tooltip container from the document. */ destroy: function () { this.hide(); this.container.remove(); }, _createContainer: function () { - let container; + let container = this.doc.createElementNS(XHTML_NS, "div"); + container.setAttribute("type", this.type); + container.classList.add("tooltip-container"); + + let html; if (this._isXUL()) { - container = this.document.createElementNS(XHTML_NS, "iframe"); - container.classList.add("devtools-tooltip-iframe"); - this.document.querySelector("window").appendChild(container); + html = '<iframe class="devtools-tooltip-iframe tooltip-panel"></iframe>'; } else { - container = this.document.createElementNS(XHTML_NS, "div"); - this.document.body.appendChild(container); + html = '<div class="tooltip-panel theme-body"></div>'; } - container.classList.add("theme-body"); - container.classList.add("devtools-htmltooltip-container"); - + if (this.type === TYPE.ARROW) { + html += '<div class="tooltip-arrow"></div>'; + } + container.innerHTML = html; return container; }, _onClick: function (e) { if (this._isInTooltipContainer(e.target)) { return; } this.hide(); if (this.consumeOutsideClicks) { e.preventDefault(); e.stopPropagation(); } }, _isInTooltipContainer: function (node) { - let contentWindow = this.parent.ownerDocument.defaultView; + let contentWindow = this.panel.ownerDocument.defaultView; let win = node.ownerDocument.defaultView; if (win === contentWindow) { // If node is in the same window as the tooltip, check if the tooltip // parent contains node. - return this.parent.contains(node); + return this.panel.contains(node); } // Otherwise check if the node window is in the tooltip window. while (win.parent && win.parent != win) { win = win.parent; if (win === contentWindow) { return true; } } return false; }, + /** + * Calculates the best possible position to display the tooltip near the + * provided anchor. An optional position can be provided, but will be + * respected only if it doesn't force the tooltip to be resized. + * + * If the tooltip has to be resized, the position will be wherever the most + * space is available. + * + */ _findBestPosition: function (anchor, position) { - let top, left; - let {TOP, BOTTOM} = this.position; + let {TOP, BOTTOM} = POSITION; - let {left: anchorLeft, top: anchorTop, height: anchorHeight} - = this._getRelativeRect(anchor, this.document); - - let {bottom: docBottom, right: docRight} = - this.document.documentElement.getBoundingClientRect(); + // Get anchor geometry + let { + left: anchorLeft, top: anchorTop, + height: anchorHeight, width: anchorWidth + } = this._getRelativeRect(anchor, this.doc); - let height = this.preferredHeight; - // Check if the popup can fit above the anchor. + // Get document geometry + let {bottom: docBottom, right: docRight} = + this.doc.documentElement.getBoundingClientRect(); + + // Calculate available space for the tooltip. let availableTop = anchorTop; - let fitsAbove = availableTop >= height; - // Check if the popup can fit below the anchor. - let availableBelow = docBottom - (anchorTop + anchorHeight); - let fitsBelow = availableBelow >= height; + let availableBottom = docBottom - (anchorTop + anchorHeight); - let isPositionSuitable = (fitsAbove && position === TOP) - || (fitsBelow && position === BOTTOM); - if (!isPositionSuitable) { - // If the preferred position does not fit the preferred height, - // pick the position offering the most height. - position = availableTop > availableBelow ? TOP : BOTTOM; + // Find POSITION + let keepPosition = false; + if (position === TOP) { + keepPosition = availableTop >= this.preferredHeight; + } else if (position === BOTTOM) { + keepPosition = availableBottom >= this.preferredHeight; + } + if (!keepPosition) { + position = availableTop > availableBottom ? TOP : BOTTOM; } - // Calculate height, capped by the maximum height available. - height = Math.min(height, Math.max(availableTop, availableBelow)); - top = position === TOP ? anchorTop - height : anchorTop + anchorHeight; + // Calculate HEIGHT. + let availableHeight = position === TOP ? availableTop : availableBottom; + let height = Math.min(this.preferredHeight, availableHeight); + height = Math.floor(height); + // Calculate TOP. + let top = position === TOP ? anchorTop - height : anchorTop + anchorHeight; + + // Calculate WIDTH. let availableWidth = docRight; let width = Math.min(this.preferredWidth, availableWidth); - // By default, align the tooltip's left edge with the anchor left edge. - if (anchorLeft + width <= docRight) { - left = anchorLeft; - } else { - // If the tooltip cannot fit, shift to the left just enough to fit. - left = docRight - width; + // Calculate LEFT. + // By default the tooltip is aligned with the anchor left edge. Unless this + // makes it overflow the viewport, in which case is shifts to the left. + let left = Math.min(anchorLeft, docRight - width); + + // Calculate ARROW LEFT (tooltip's LEFT might be updated) + let arrowLeft; + // Arrow style tooltips may need to be shifted to the left + if (this.type === TYPE.ARROW) { + let arrowCenter = left + ARROW_OFFSET + ARROW_WIDTH / 2; + let anchorCenter = anchorLeft + anchorWidth / 2; + // If the anchor is too narrow, align the arrow and the anchor center. + if (arrowCenter > anchorCenter) { + left = Math.max(0, left - (arrowCenter - anchorCenter)); + } + // Arrow's feft offset relative to the anchor. + arrowLeft = Math.min(ARROW_OFFSET, (anchorWidth - ARROW_WIDTH) / 2) | 0; + // Translate the coordinate to tooltip container + arrowLeft += anchorLeft - left; + // Make sure the arrow remains in the tooltip container. + arrowLeft = Math.min(arrowLeft, width - ARROW_WIDTH); + arrowLeft = Math.max(arrowLeft, 0); } - return {top, left, width, height}; + return {top, left, width, height, position, arrowLeft}; }, /** * Get the bounding client rectangle for a given node, relative to a custom * reference element (instead of the default for getBoundingClientRect which * is always the element's ownerDocument). */ _getRelativeRect: function (node, relativeTo) { // Width and Height can be taken from the rect. let {width, height} = node.getBoundingClientRect(); - // Find the smallest top/left coordinates from all quads. - let top = Infinity, left = Infinity; - let quads = node.getBoxQuads({relativeTo: relativeTo}); - for (let quad of quads) { - top = Math.min(top, quad.bounds.top); - left = Math.min(left, quad.bounds.left); - } + let quads = node.getBoxQuads({relativeTo}); + let top = quads[0].bounds.top; + let left = quads[0].bounds.left; // Compute right and bottom coordinates using the rest of the data. let right = left + width; let bottom = top + height; return {top, right, bottom, left, width, height}; }, _isXUL: function () { - return this.document.documentElement.namespaceURI === XUL_NS; + return this.doc.documentElement.namespaceURI === XUL_NS; }, };
--- a/devtools/client/shared/widgets/tooltip-frame.xhtml +++ b/devtools/client/shared/widgets/tooltip-frame.xhtml @@ -10,15 +10,16 @@ <script type="application/javascript;version=1.8" src="chrome://devtools/content/shared/theme-switching.js"/> <style> html, body, #tooltip-iframe-container { margin: 0; padding: 0; width: 100%; height: 100%; overflow: hidden; + color: var(--theme-body-color); } </style> </head> <body role="application" class="theme-body"> <div id="tooltip-iframe-container"></div> </body> </html>
--- a/devtools/client/themes/common.css +++ b/devtools/client/themes/common.css @@ -241,20 +241,98 @@ background-position: 0 0, 10px 10px; } .devtools-tooltip-iframe { border: none; background: transparent; } -.devtools-htmltooltip-container { +.tooltip-container { display: none; position: fixed; z-index: 9999; + display: none; + background: transparent; +} + +.tooltip-panel{ + background-color: var(--theme-tooltip-background); +} + +.tooltip-visible { + display: block; +} + +.tooltip-container[type="normal"] > .tooltip-panel { + height: 100%; + width: 100%; +} + +/* Tooltip : arrow style */ + +.tooltip-container[type="arrow"] { + filter: drop-shadow(0 3px 4px var(--theme-tooltip-shadow)); +} + +.tooltip-container[type="arrow"] > .tooltip-panel { + position: absolute; + box-sizing: border-box; + height: calc(100% - 13px); + width: 100%; + + border: 3px solid var(--theme-tooltip-border); + border-radius: 5px; +} + +.tooltip-top[type="arrow"] .tooltip-panel { + top: 0; +} + +.tooltip-bottom[type="arrow"] .tooltip-panel { + bottom: 0; +} + +.tooltip-arrow { + position: absolute; + height: 16px; + width: 32px; + overflow: hidden; +} + +.tooltip-top .tooltip-arrow { + bottom: 0; +} + +.tooltip-bottom .tooltip-arrow { + top: 0; +} + +.tooltip-arrow:before { + content: ""; + position: absolute; + width: 21px; + height: 21px; + margin-left: 4px; + background: linear-gradient(-45deg, + var(--theme-tooltip-background) 50%, transparent 50%); + border-color: var(--theme-tooltip-border); + border-style: solid; + border-width: 0px 3px 3px 0px; + border-radius: 3px; +} + +.tooltip-bottom .tooltip-arrow:before { + margin-top: 4px; + transform: rotate(225deg); +} + +.tooltip-top .tooltip-arrow:before { + margin-top: -12px; + transform: rotate(45deg); } /* links to source code, like displaying `myfile.js:45` */ .devtools-source-link { font-family: var(--monospace-font-family); color: var(--theme-highlight-blue); cursor: pointer;
--- a/devtools/client/themes/variables.css +++ b/devtools/client/themes/variables.css @@ -58,16 +58,21 @@ --theme-graphs-red: #e57180; --theme-graphs-grey: #cccccc; --theme-graphs-full-red: #f00; --theme-graphs-full-blue: #00f; /* Images */ --theme-pane-collapse-image: url(chrome://devtools/skin/images/pane-collapse.svg); --theme-pane-expand-image: url(chrome://devtools/skin/images/pane-expand.svg); + + /* Tooltips */ + --theme-tooltip-border: #d9e1e8; + --theme-tooltip-background: rgba(255, 255, 255, .9); + --theme-tooltip-shadow: rgba(155, 155, 155, 0.26); } :root.theme-dark { --theme-body-background: #393f4c; --theme-sidebar-background: #393f4c; --theme-contrast-background: #ffb35b; --theme-tab-toolbar-background: #272b35; @@ -109,16 +114,21 @@ --theme-graphs-red: #eb5368; --theme-graphs-grey: #757873; --theme-graphs-full-red: #f00; --theme-graphs-full-blue: #00f; /* Images */ --theme-pane-collapse-image: url(chrome://devtools/skin/images/pane-collapse.svg); --theme-pane-expand-image: url(chrome://devtools/skin/images/pane-expand.svg); + + /* Tooltips */ + --theme-tooltip-border: #434850; + --theme-tooltip-background: rgba(19, 28, 38, .9); + --theme-tooltip-shadow: rgba(25, 25, 25, 0.76); } :root.theme-firebug { --theme-body-background: #fcfcfc; --theme-sidebar-background: #fcfcfc; --theme-contrast-background: #e6b064; --theme-tab-toolbar-background: #ebeced;
--- a/devtools/server/actors/styleeditor.js +++ b/devtools/server/actors/styleeditor.js @@ -27,251 +27,16 @@ var TRANSITION_RULE = "\ transition-duration: " + TRANSITION_DURATION_MS + "ms !important; \ transition-delay: 0ms !important;\ transition-timing-function: ease-out !important;\ transition-property: all !important;\ }"; var LOAD_ERROR = "error-load"; -types.addActorType("old-stylesheet"); - -/** - * Creates a StyleEditorActor. StyleEditorActor provides remote access to the - * stylesheets of a document. - */ -var StyleEditorActor = exports.StyleEditorActor = protocol.ActorClass({ - typeName: "styleeditor", - - /** - * The window we work with, taken from the parent actor. - */ - get window() { - return this.parentActor.window; - }, - - /** - * The current content document of the window we work with. - */ - get document() { - return this.window.document; - }, - - events: { - "document-load" : { - type: "documentLoad", - styleSheets: Arg(0, "array:old-stylesheet") - } - }, - - form: function () - { - return { actor: this.actorID }; - }, - - initialize: function (conn, tabActor) { - protocol.Actor.prototype.initialize.call(this, null); - - this.parentActor = tabActor; - - // keep a map of sheets-to-actors so we don't create two actors for one sheet - this._sheets = new Map(); - }, - - /** - * Destroy the current StyleEditorActor instance. - */ - destroy: function () - { - this._sheets.clear(); - }, - - /** - * Called by client when target navigates to a new document. - * Adds load listeners to document. - */ - newDocument: method(function () { - // delete previous document's actors - this._clearStyleSheetActors(); - - // Note: listening for load won't be necessary once - // https://bugzilla.mozilla.org/show_bug.cgi?id=839103 is fixed - if (this.document.readyState == "complete") { - this._onDocumentLoaded(); - } - else { - this.window.addEventListener("load", this._onDocumentLoaded, false); - } - return {}; - }), - - /** - * Event handler for document loaded event. Add actor for each stylesheet - * and send an event notifying of the load - */ - _onDocumentLoaded: function (event) { - if (event) { - this.window.removeEventListener("load", this._onDocumentLoaded, false); - } - - let documents = [this.document]; - var forms = []; - for (let doc of documents) { - let sheetForms = this._addStyleSheets(doc.styleSheets); - forms = forms.concat(sheetForms); - // Recursively handle style sheets of the documents in iframes. - for (let iframe of doc.getElementsByTagName("iframe")) { - documents.push(iframe.contentDocument); - } - } - - events.emit(this, "document-load", forms); - }, - - /** - * Add all the stylesheets to the map and create an actor for each one - * if not already created. Send event that there are new stylesheets. - * - * @param {[DOMStyleSheet]} styleSheets - * Stylesheets to add - * @return {[object]} - * Array of actors for each StyleSheetActor created - */ - _addStyleSheets: function (styleSheets) - { - let sheets = []; - for (let i = 0; i < styleSheets.length; i++) { - let styleSheet = styleSheets[i]; - sheets.push(styleSheet); - - // Get all sheets, including imported ones - let imports = this._getImported(styleSheet); - sheets = sheets.concat(imports); - } - let actors = sheets.map(this._createStyleSheetActor.bind(this)); - - return actors; - }, - - /** - * Create a new actor for a style sheet, if it hasn't already been created. - * - * @param {DOMStyleSheet} styleSheet - * The style sheet to create an actor for. - * @return {StyleSheetActor} - * The actor for this style sheet - */ - _createStyleSheetActor: function (styleSheet) - { - if (this._sheets.has(styleSheet)) { - return this._sheets.get(styleSheet); - } - let actor = new OldStyleSheetActor(styleSheet, this); - - this.manage(actor); - this._sheets.set(styleSheet, actor); - - return actor; - }, - - /** - * Get all the stylesheets @imported from a stylesheet. - * - * @param {DOMStyleSheet} styleSheet - * Style sheet to search - * @return {array} - * All the imported stylesheets - */ - _getImported: function (styleSheet) { - let imported = []; - - for (let i = 0; i < styleSheet.cssRules.length; i++) { - let rule = styleSheet.cssRules[i]; - if (rule.type == Ci.nsIDOMCSSRule.IMPORT_RULE) { - // Associated styleSheet may be null if it has already been seen due to - // duplicate @imports for the same URL. - if (!rule.styleSheet) { - continue; - } - imported.push(rule.styleSheet); - - // recurse imports in this stylesheet as well - imported = imported.concat(this._getImported(rule.styleSheet)); - } - else if (rule.type != Ci.nsIDOMCSSRule.CHARSET_RULE) { - // @import rules must precede all others except @charset - break; - } - } - return imported; - }, - - /** - * Clear all the current stylesheet actors in map. - */ - _clearStyleSheetActors: function () { - for (let actor in this._sheets) { - this.unmanage(this._sheets[actor]); - } - this._sheets.clear(); - }, - - /** - * Create a new style sheet in the document with the given text. - * Return an actor for it. - * - * @param {object} request - * Debugging protocol request object, with 'text property' - * @return {object} - * Object with 'styelSheet' property for form on new actor. - */ - newStyleSheet: method(function (text) { - let parent = this.document.documentElement; - let style = this.document.createElementNS("http://www.w3.org/1999/xhtml", "style"); - style.setAttribute("type", "text/css"); - - if (text) { - style.appendChild(this.document.createTextNode(text)); - } - parent.appendChild(style); - - let actor = this._createStyleSheetActor(style.sheet); - return actor; - }, { - request: { text: Arg(0, "string") }, - response: { styleSheet: RetVal("old-stylesheet") } - }) -}); - -/** - * The corresponding Front object for the StyleEditorActor. - */ -var StyleEditorFront = protocol.FrontClass(StyleEditorActor, { - initialize: function (client, tabForm) { - protocol.Front.prototype.initialize.call(this, client); - this.actorID = tabForm.styleEditorActor; - this.manage(this); - }, - - getStyleSheets: function () { - let deferred = promise.defer(); - - events.once(this, "document-load", (styleSheets) => { - deferred.resolve(styleSheets); - }); - this.newDocument(); - - return deferred.promise; - }, - - addStyleSheet: function (text) { - return this.newStyleSheet(text); - } -}); - /** * A StyleSheetActor represents a stylesheet on the server. */ var OldStyleSheetActor = protocol.ActorClass({ typeName: "old-stylesheet", events: { "property-change" : { @@ -645,27 +410,492 @@ var OldStyleSheetFront = protocol.FrontC get styleSheetIndex() { return this._form.styleSheetIndex; }, get ruleCount() { return this._form.ruleCount; } }); +/** + * Creates a StyleEditorActor. StyleEditorActor provides remote access to the + * stylesheets of a document. + */ +var StyleEditorActor = exports.StyleEditorActor = protocol.ActorClass({ + typeName: "styleeditor", + + /** + * The window we work with, taken from the parent actor. + */ + get window() { + return this.parentActor.window; + }, + + /** + * The current content document of the window we work with. + */ + get document() { + return this.window.document; + }, + + events: { + "document-load" : { + type: "documentLoad", + styleSheets: Arg(0, "array:old-stylesheet") + } + }, + + form: function () + { + return { actor: this.actorID }; + }, + + initialize: function (conn, tabActor) { + protocol.Actor.prototype.initialize.call(this, null); + + this.parentActor = tabActor; + + // keep a map of sheets-to-actors so we don't create two actors for one sheet + this._sheets = new Map(); + }, + + /** + * Destroy the current StyleEditorActor instance. + */ + destroy: function () + { + this._sheets.clear(); + }, + + /** + * Called by client when target navigates to a new document. + * Adds load listeners to document. + */ + newDocument: method(function () { + // delete previous document's actors + this._clearStyleSheetActors(); + + // Note: listening for load won't be necessary once + // https://bugzilla.mozilla.org/show_bug.cgi?id=839103 is fixed + if (this.document.readyState == "complete") { + this._onDocumentLoaded(); + } + else { + this.window.addEventListener("load", this._onDocumentLoaded, false); + } + return {}; + }), + + /** + * Event handler for document loaded event. Add actor for each stylesheet + * and send an event notifying of the load + */ + _onDocumentLoaded: function (event) { + if (event) { + this.window.removeEventListener("load", this._onDocumentLoaded, false); + } + + let documents = [this.document]; + var forms = []; + for (let doc of documents) { + let sheetForms = this._addStyleSheets(doc.styleSheets); + forms = forms.concat(sheetForms); + // Recursively handle style sheets of the documents in iframes. + for (let iframe of doc.getElementsByTagName("iframe")) { + documents.push(iframe.contentDocument); + } + } + + events.emit(this, "document-load", forms); + }, + + /** + * Add all the stylesheets to the map and create an actor for each one + * if not already created. Send event that there are new stylesheets. + * + * @param {[DOMStyleSheet]} styleSheets + * Stylesheets to add + * @return {[object]} + * Array of actors for each StyleSheetActor created + */ + _addStyleSheets: function (styleSheets) + { + let sheets = []; + for (let i = 0; i < styleSheets.length; i++) { + let styleSheet = styleSheets[i]; + sheets.push(styleSheet); + + // Get all sheets, including imported ones + let imports = this._getImported(styleSheet); + sheets = sheets.concat(imports); + } + let actors = sheets.map(this._createStyleSheetActor.bind(this)); + + return actors; + }, + + /** + * Create a new actor for a style sheet, if it hasn't already been created. + * + * @param {DOMStyleSheet} styleSheet + * The style sheet to create an actor for. + * @return {StyleSheetActor} + * The actor for this style sheet + */ + _createStyleSheetActor: function (styleSheet) + { + if (this._sheets.has(styleSheet)) { + return this._sheets.get(styleSheet); + } + let actor = new OldStyleSheetActor(styleSheet, this); + + this.manage(actor); + this._sheets.set(styleSheet, actor); + + return actor; + }, + + /** + * Get all the stylesheets @imported from a stylesheet. + * + * @param {DOMStyleSheet} styleSheet + * Style sheet to search + * @return {array} + * All the imported stylesheets + */ + _getImported: function (styleSheet) { + let imported = []; + + for (let i = 0; i < styleSheet.cssRules.length; i++) { + let rule = styleSheet.cssRules[i]; + if (rule.type == Ci.nsIDOMCSSRule.IMPORT_RULE) { + // Associated styleSheet may be null if it has already been seen due to + // duplicate @imports for the same URL. + if (!rule.styleSheet) { + continue; + } + imported.push(rule.styleSheet); + + // recurse imports in this stylesheet as well + imported = imported.concat(this._getImported(rule.styleSheet)); + } + else if (rule.type != Ci.nsIDOMCSSRule.CHARSET_RULE) { + // @import rules must precede all others except @charset + break; + } + } + return imported; + }, + + /** + * Clear all the current stylesheet actors in map. + */ + _clearStyleSheetActors: function () { + for (let actor in this._sheets) { + this.unmanage(this._sheets[actor]); + } + this._sheets.clear(); + }, + + /** + * Create a new style sheet in the document with the given text. + * Return an actor for it. + * + * @param {object} request + * Debugging protocol request object, with 'text property' + * @return {object} + * Object with 'styelSheet' property for form on new actor. + */ + newStyleSheet: method(function (text) { + let parent = this.document.documentElement; + let style = this.document.createElementNS("http://www.w3.org/1999/xhtml", "style"); + style.setAttribute("type", "text/css"); + + if (text) { + style.appendChild(this.document.createTextNode(text)); + } + parent.appendChild(style); + + let actor = this._createStyleSheetActor(style.sheet); + return actor; + }, { + request: { text: Arg(0, "string") }, + response: { styleSheet: RetVal("old-stylesheet") } + }) +}); + +/** + * The corresponding Front object for the StyleEditorActor. + */ +var StyleEditorFront = protocol.FrontClass(StyleEditorActor, { + initialize: function (client, tabForm) { + protocol.Front.prototype.initialize.call(this, client); + this.actorID = tabForm.styleEditorActor; + this.manage(this); + }, + + getStyleSheets: function () { + let deferred = promise.defer(); + + events.once(this, "document-load", (styleSheets) => { + deferred.resolve(styleSheets); + }); + this.newDocument(); + + return deferred.promise; + }, + + addStyleSheet: function (text) { + return this.newStyleSheet(text); + } +}); + +/** + * Creates a StyleEditorActor. StyleEditorActor provides remote access to the + * stylesheets of a document. + */ +var StyleEditorActor = exports.StyleEditorActor = protocol.ActorClass({ + typeName: "styleeditor", + + /** + * The window we work with, taken from the parent actor. + */ + get window() { + return this.parentActor.window; + }, + + /** + * The current content document of the window we work with. + */ + get document() { + return this.window.document; + }, + + events: { + "document-load" : { + type: "documentLoad", + styleSheets: Arg(0, "array:old-stylesheet") + } + }, + + form: function () + { + return { actor: this.actorID }; + }, + + initialize: function (conn, tabActor) { + protocol.Actor.prototype.initialize.call(this, null); + + this.parentActor = tabActor; + + // keep a map of sheets-to-actors so we don't create two actors for one sheet + this._sheets = new Map(); + }, + + /** + * Destroy the current StyleEditorActor instance. + */ + destroy: function () + { + this._sheets.clear(); + }, + + /** + * Called by client when target navigates to a new document. + * Adds load listeners to document. + */ + newDocument: method(function () { + // delete previous document's actors + this._clearStyleSheetActors(); + + // Note: listening for load won't be necessary once + // https://bugzilla.mozilla.org/show_bug.cgi?id=839103 is fixed + if (this.document.readyState == "complete") { + this._onDocumentLoaded(); + } + else { + this.window.addEventListener("load", this._onDocumentLoaded, false); + } + return {}; + }), + + /** + * Event handler for document loaded event. Add actor for each stylesheet + * and send an event notifying of the load + */ + _onDocumentLoaded: function (event) { + if (event) { + this.window.removeEventListener("load", this._onDocumentLoaded, false); + } + + let documents = [this.document]; + var forms = []; + for (let doc of documents) { + let sheetForms = this._addStyleSheets(doc.styleSheets); + forms = forms.concat(sheetForms); + // Recursively handle style sheets of the documents in iframes. + for (let iframe of doc.getElementsByTagName("iframe")) { + documents.push(iframe.contentDocument); + } + } + + events.emit(this, "document-load", forms); + }, + + /** + * Add all the stylesheets to the map and create an actor for each one + * if not already created. Send event that there are new stylesheets. + * + * @param {[DOMStyleSheet]} styleSheets + * Stylesheets to add + * @return {[object]} + * Array of actors for each StyleSheetActor created + */ + _addStyleSheets: function (styleSheets) + { + let sheets = []; + for (let i = 0; i < styleSheets.length; i++) { + let styleSheet = styleSheets[i]; + sheets.push(styleSheet); + + // Get all sheets, including imported ones + let imports = this._getImported(styleSheet); + sheets = sheets.concat(imports); + } + let actors = sheets.map(this._createStyleSheetActor.bind(this)); + + return actors; + }, + + /** + * Create a new actor for a style sheet, if it hasn't already been created. + * + * @param {DOMStyleSheet} styleSheet + * The style sheet to create an actor for. + * @return {StyleSheetActor} + * The actor for this style sheet + */ + _createStyleSheetActor: function (styleSheet) + { + if (this._sheets.has(styleSheet)) { + return this._sheets.get(styleSheet); + } + let actor = new OldStyleSheetActor(styleSheet, this); + + this.manage(actor); + this._sheets.set(styleSheet, actor); + + return actor; + }, + + /** + * Get all the stylesheets @imported from a stylesheet. + * + * @param {DOMStyleSheet} styleSheet + * Style sheet to search + * @return {array} + * All the imported stylesheets + */ + _getImported: function (styleSheet) { + let imported = []; + + for (let i = 0; i < styleSheet.cssRules.length; i++) { + let rule = styleSheet.cssRules[i]; + if (rule.type == Ci.nsIDOMCSSRule.IMPORT_RULE) { + // Associated styleSheet may be null if it has already been seen due to + // duplicate @imports for the same URL. + if (!rule.styleSheet) { + continue; + } + imported.push(rule.styleSheet); + + // recurse imports in this stylesheet as well + imported = imported.concat(this._getImported(rule.styleSheet)); + } + else if (rule.type != Ci.nsIDOMCSSRule.CHARSET_RULE) { + // @import rules must precede all others except @charset + break; + } + } + return imported; + }, + + /** + * Clear all the current stylesheet actors in map. + */ + _clearStyleSheetActors: function () { + for (let actor in this._sheets) { + this.unmanage(this._sheets[actor]); + } + this._sheets.clear(); + }, + + /** + * Create a new style sheet in the document with the given text. + * Return an actor for it. + * + * @param {object} request + * Debugging protocol request object, with 'text property' + * @return {object} + * Object with 'styelSheet' property for form on new actor. + */ + newStyleSheet: method(function (text) { + let parent = this.document.documentElement; + let style = this.document.createElementNS("http://www.w3.org/1999/xhtml", "style"); + style.setAttribute("type", "text/css"); + + if (text) { + style.appendChild(this.document.createTextNode(text)); + } + parent.appendChild(style); + + let actor = this._createStyleSheetActor(style.sheet); + return actor; + }, { + request: { text: Arg(0, "string") }, + response: { styleSheet: RetVal("old-stylesheet") } + }) +}); + +/** + * The corresponding Front object for the StyleEditorActor. + */ +var StyleEditorFront = protocol.FrontClass(StyleEditorActor, { + initialize: function (client, tabForm) { + protocol.Front.prototype.initialize.call(this, client); + this.actorID = tabForm.styleEditorActor; + this.manage(this); + }, + + getStyleSheets: function () { + let deferred = promise.defer(); + + events.once(this, "document-load", (styleSheets) => { + deferred.resolve(styleSheets); + }); + this.newDocument(); + + return deferred.promise; + }, + + addStyleSheet: function (text) { + return this.newStyleSheet(text); + } +}); + XPCOMUtils.defineLazyGetter(this, "DOMUtils", function () { return Cc["@mozilla.org/inspector/dom-utils;1"].getService(Ci.inIDOMUtils); }); exports.StyleEditorActor = StyleEditorActor; exports.StyleEditorFront = StyleEditorFront; exports.OldStyleSheetActor = OldStyleSheetActor; exports.OldStyleSheetFront = OldStyleSheetFront; - /** * Normalize multiple relative paths towards the base paths on the right. */ function normalize(...aURLs) { let base = Services.io.newURI(aURLs.pop(), null, null); let url; while ((url = aURLs.pop())) { base = Services.io.newURI(url, null, base);
--- a/mobile/android/app/build.gradle +++ b/mobile/android/app/build.gradle @@ -1,17 +1,17 @@ buildDir "${topobjdir}/gradle/build/mobile/android/app" apply plugin: 'android-sdk-manager' // Must come before 'com.android.*'. apply plugin: 'com.android.application' apply plugin: 'checkstyle' android { compileSdkVersion 23 - buildToolsVersion "23.0.1" + buildToolsVersion mozconfig.substs.ANDROID_BUILD_TOOLS_VERSION defaultConfig { targetSdkVersion 23 minSdkVersion 15 applicationId mozconfig.substs.ANDROID_PACKAGE_NAME testApplicationId 'org.mozilla.roboexample.test' testInstrumentationRunner 'org.mozilla.gecko.FennecInstrumentationTestRunner' manifestPlaceholders = [ @@ -166,40 +166,40 @@ android { // we have tests that start test servers and the bound ports // collide. We'll fix this soon to have much faster test cycles. maxParallelForks 1 } } } dependencies { - compile 'com.android.support:support-v4:23.0.1' - compile 'com.android.support:appcompat-v7:23.0.1' - compile 'com.android.support:cardview-v7:23.0.1' - compile 'com.android.support:recyclerview-v7:23.0.1' - compile 'com.android.support:design:23.0.1' + compile "com.android.support:support-v4:${mozconfig.substs.ANDROID_SUPPORT_LIBRARY_VERSION}" + compile "com.android.support:appcompat-v7:${mozconfig.substs.ANDROID_SUPPORT_LIBRARY_VERSION}" + compile "com.android.support:cardview-v7:${mozconfig.substs.ANDROID_SUPPORT_LIBRARY_VERSION}" + compile "com.android.support:recyclerview-v7:${mozconfig.substs.ANDROID_SUPPORT_LIBRARY_VERSION}" + compile "com.android.support:design:${mozconfig.substs.ANDROID_SUPPORT_LIBRARY_VERSION}" if (mozconfig.substs.MOZ_NATIVE_DEVICES) { - compile 'com.android.support:mediarouter-v7:23.0.1' - compile 'com.google.android.gms:play-services-basement:8.1.0' - compile 'com.google.android.gms:play-services-base:8.1.0' - compile 'com.google.android.gms:play-services-cast:8.1.0' + compile "com.android.support:mediarouter-v7:${mozconfig.substs.ANDROID_SUPPORT_LIBRARY_VERSION}" + compile "com.google.android.gms:play-services-basement:${mozconfig.substs.ANDROID_GOOGLE_PLAY_SERVICES_VERSION}" + compile "com.google.android.gms:play-services-base:${mozconfig.substs.ANDROID_GOOGLE_PLAY_SERVICES_VERSION}" + compile "com.google.android.gms:play-services-cast:${mozconfig.substs.ANDROID_GOOGLE_PLAY_SERVICES_VERSION}" } if (mozconfig.substs.MOZ_INSTALL_TRACKING) { - compile 'com.google.android.gms:play-services-ads:8.1.0' - compile 'com.google.android.gms:play-services-analytics:8.1.0' - compile 'com.google.android.gms:play-services-appindexing:8.1.0' - compile 'com.google.android.gms:play-services-basement:8.1.0' + compile "com.google.android.gms:play-services-ads:${mozconfig.substs.ANDROID_GOOGLE_PLAY_SERVICES_VERSION}" + compile "com.google.android.gms:play-services-analytics:${mozconfig.substs.ANDROID_GOOGLE_PLAY_SERVICES_VERSION}" + compile "com.google.android.gms:play-services-appindexing:${mozconfig.substs.ANDROID_GOOGLE_PLAY_SERVICES_VERSION}" + compile "com.google.android.gms:play-services-basement:${mozconfig.substs.ANDROID_GOOGLE_PLAY_SERVICES_VERSION}" } if (mozconfig.substs.MOZ_ANDROID_GCM) { - compile 'com.google.android.gms:play-services-basement:8.1.0' - compile 'com.google.android.gms:play-services-base:8.1.0' - compile 'com.google.android.gms:play-services-gcm:8.1.0' + compile "com.google.android.gms:play-services-basement:${mozconfig.substs.ANDROID_GOOGLE_PLAY_SERVICES_VERSION}" + compile "com.google.android.gms:play-services-base:${mozconfig.substs.ANDROID_GOOGLE_PLAY_SERVICES_VERSION}" + compile "com.google.android.gms:play-services-gcm:${mozconfig.substs.ANDROID_GOOGLE_PLAY_SERVICES_VERSION}" } // Gradle based builds include LeakCanary. Gradle based tests include the no-op version. Mach // based builds only include the no-op version of this library. compile 'com.squareup.leakcanary:leakcanary-android:1.4-beta1' testCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.4-beta1' compile project(':thirdparty')
--- a/mobile/android/bouncer/build.gradle +++ b/mobile/android/bouncer/build.gradle @@ -1,15 +1,15 @@ buildDir "${topobjdir}/gradle/build/mobile/android/bouncer" apply plugin: 'com.android.application' android { compileSdkVersion 23 - buildToolsVersion "23.0.1" + buildToolsVersion mozconfig.substs.ANDROID_BUILD_TOOLS_VERSION defaultConfig { targetSdkVersion 23 minSdkVersion 15 applicationId mozconfig.substs.ANDROID_PACKAGE_NAME } compileOptions {
--- a/mobile/android/thirdparty/build.gradle +++ b/mobile/android/thirdparty/build.gradle @@ -1,15 +1,15 @@ buildDir "${topobjdir}/gradle/build/mobile/android/thirdparty" apply plugin: 'com.android.library' android { compileSdkVersion 23 - buildToolsVersion "23.0.1" + buildToolsVersion mozconfig.substs.ANDROID_BUILD_TOOLS_VERSION defaultConfig { targetSdkVersion 23 minSdkVersion 15 } compileOptions { sourceCompatibility JavaVersion.VERSION_1_7 @@ -33,17 +33,17 @@ android { // here is only the no-op library for mach-based builds. exclude 'com/squareup/leakcanary/**' } } } } dependencies { - compile 'com.android.support:support-v4:23.0.1' + compile "com.android.support:support-v4:${mozconfig.substs.ANDROID_SUPPORT_LIBRARY_VERSION}" } apply plugin: 'idea' idea { module { // This is cosmetic. See the excludes in the root project. if (!mozconfig.substs.MOZ_INSTALL_TRACKING) {
--- a/toolkit/components/console/jsconsole-clhandler.manifest +++ b/toolkit/components/console/jsconsole-clhandler.manifest @@ -1,3 +1,3 @@ component {2cd0c310-e127-44d0-88fc-4435c9ab4d4b} jsconsole-clhandler.js contract @mozilla.org/toolkit/console-clh;1 {2cd0c310-e127-44d0-88fc-4435c9ab4d4b} -category command-line-handler b-jsconsole @mozilla.org/toolkit/console-clh;1 +category command-line-handler t-jsconsole @mozilla.org/toolkit/console-clh;1