Merge mozilla-central to autoland

This commit is contained in:
Carsten "Tomcat" Book 2016-07-25 16:22:04 +02:00
commit a3e240204c
606 changed files with 9682 additions and 7189 deletions

View File

@ -113,6 +113,8 @@ devtools/server/**
!devtools/server/child.js
!devtools/server/css-logic.js
!devtools/server/main.js
!devtools/server/actors/inspector.js
!devtools/server/actors/highlighters/eye-dropper.js
!devtools/server/actors/webbrowser.js
!devtools/server/actors/styles.js
!devtools/server/actors/string.js

View File

@ -22,4 +22,4 @@
# changes to stick? As of bug 928195, this shouldn't be necessary! Please
# don't change CLOBBER for WebIDL changes any more.
Bug 1287946 - clobber due to generated SDK headers changing (bug 1182840)
Bug 1285541 - Clobber needed because this patch renames a file.

View File

@ -39,29 +39,33 @@ function tabEventsFor(window) {
return merge(channels);
}
// Filter DOMContentLoaded events from all the browser events.
var readyEvents = filter(events, e => e.type === "DOMContentLoaded");
// Map DOMContentLoaded events to it's target browser windows.
var futureWindows = map(readyEvents, e => e.target);
// Expand all browsers that will become interactive to supported tab events
// on these windows. Result will be a tab events from all tabs of all windows
// that will become interactive.
var eventsFromFuture = expand(futureWindows, tabEventsFor);
// Create our event channels. We do this in a separate function to
// minimize the chance of leaking intermediate objects on the global.
function makeEvents() {
// Filter DOMContentLoaded events from all the browser events.
var readyEvents = filter(events, e => e.type === "DOMContentLoaded");
// Map DOMContentLoaded events to it's target browser windows.
var futureWindows = map(readyEvents, e => e.target);
// Expand all browsers that will become interactive to supported tab events
// on these windows. Result will be a tab events from all tabs of all windows
// that will become interactive.
var eventsFromFuture = expand(futureWindows, tabEventsFor);
// Above covers only windows that will become interactive in a future, but some
// windows may already be interactive so we pick those and expand to supported
// tab events for them too.
var interactiveWindows = windows("navigator:browser", { includePrivate: true }).
filter(isInteractive);
var eventsFromInteractive = merge(interactiveWindows.map(tabEventsFor));
// Above covers only windows that will become interactive in a future, but some
// windows may already be interactive so we pick those and expand to supported
// tab events for them too.
var interactiveWindows = windows("navigator:browser", { includePrivate: true }).
filter(isInteractive);
var eventsFromInteractive = merge(interactiveWindows.map(tabEventsFor));
// Finally merge stream of tab events from future windows and current windows
// to cover all tab events on all windows that will open.
var allEvents = merge([eventsFromInteractive, eventsFromFuture]);
// Finally merge stream of tab events from future windows and current windows
// to cover all tab events on all windows that will open.
return merge([eventsFromInteractive, eventsFromFuture]);
}
// Map events to Fennec format if necessary
exports.events = map(allEvents, function (event) {
exports.events = map(makeEvents(), function (event) {
return !isFennec ? event : {
type: event.type,
target: event.target.ownerDocument.defaultView.BrowserApp

View File

@ -43,22 +43,26 @@ function eventsFor(window) {
return map(changes, toEventWithDefaultViewTarget);
}
// In addition to observing windows that are open we also observe windows
// that are already already opened in case they're in process of loading.
var opened = windows(null, { includePrivate: true });
var currentEvents = merge(opened.map(eventsFor));
// Create our event channels. We do this in a separate function to
// minimize the chance of leaking intermediate objects on the global.
function makeEvents() {
// In addition to observing windows that are open we also observe windows
// that are already already opened in case they're in process of loading.
var opened = windows(null, { includePrivate: true });
var currentEvents = merge(opened.map(eventsFor));
// Register system event listeners for top level window open / close.
function rename({type, target, data}) {
return { type: rename[type], target: target, data: data }
// Register system event listeners for top level window open / close.
function rename({type, target, data}) {
return { type: rename[type], target: target, data: data }
}
rename.domwindowopened = "open";
rename.domwindowclosed = "close";
var openEvents = map(observe("domwindowopened"), rename);
var closeEvents = map(observe("domwindowclosed"), rename);
var futureEvents = expand(openEvents, ({target}) => eventsFor(target));
return merge([currentEvents, futureEvents, openEvents, closeEvents]);
}
rename.domwindowopened = "open";
rename.domwindowclosed = "close";
var openEvents = map(observe("domwindowopened"), rename);
var closeEvents = map(observe("domwindowclosed"), rename);
var futureEvents = expand(openEvents, ({target}) => eventsFor(target));
var channel = merge([currentEvents, futureEvents,
openEvents, closeEvents]);
exports.events = channel;
exports.events = makeEvents();

View File

@ -4,4 +4,5 @@ support-files =
[test-leak-window-events.js]
[test-leak-event-dom-closed-window.js]
[test-leak-tab-events.js]
[test-leak-event-chrome.js]

View File

@ -0,0 +1,46 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
'use strict';
const { asyncWindowLeakTest } = require("./leak-utils");
const { Loader } = require('sdk/test/loader');
const openWindow = require("sdk/window/utils").open;
exports["test sdk/tab/events does not leak new window"] = function*(assert) {
yield asyncWindowLeakTest(assert, _ => {
return new Promise(resolve => {
let loader = Loader(module);
let { events } = loader.require('sdk/tab/events');
let w = openWindow();
w.addEventListener("load", function windowLoaded(evt) {
w.removeEventListener("load", windowLoaded);
w.addEventListener("DOMWindowClose", function windowClosed(evt) {
w.removeEventListener("DOMWindowClose", windowClosed);
resolve(loader);
});
w.close();
});
});
});
}
exports["test sdk/tab/events does not leak when attached to existing window"] = function*(assert) {
yield asyncWindowLeakTest(assert, _ => {
return new Promise(resolve => {
let loader = Loader(module);
let w = openWindow();
w.addEventListener("load", function windowLoaded(evt) {
w.removeEventListener("load", windowLoaded);
let { events } = loader.require('sdk/tab/events');
w.addEventListener("DOMWindowClose", function windowClosed(evt) {
w.removeEventListener("DOMWindowClose", windowClosed);
resolve(loader);
});
w.close();
});
});
});
}
require("sdk/test").run(exports);

View File

@ -44,4 +44,22 @@ exports["test window/events for leaks"] = function*(assert) {
});
};
exports["test window/events for leaks with existing window"] = function*(assert) {
yield asyncWindowLeakTest(assert, _ => {
return new Promise((resolve, reject) => {
let loader = Loader(module);
let w = open();
w.addEventListener("load", function windowLoaded(evt) {
w.removeEventListener("load", windowLoaded);
let { events } = loader.require("sdk/window/events");
w.addEventListener("DOMWindowClose", function windowClosed(evt) {
w.removeEventListener("DOMWindowClose", windowClosed);
resolve(loader);
});
w.close();
});
});
});
};
require("sdk/test").run(exports);

View File

@ -80,6 +80,9 @@ if CONFIG['MOZ_LINKER']:
if CONFIG['HAVE_CLOCK_MONOTONIC']:
OS_LIBS += CONFIG['REALTIME_LIBS']
if CONFIG['MOZ_GPSD']:
DEFINES['MOZ_GPSD'] = True
for icon in ('firefox', 'document', 'newwindow', 'newtab', 'pbmode'):
DEFINES[icon.upper() + '_ICO'] = '"%s/dist/branding/%s.ico"' % (
TOPOBJDIR, icon)

View File

@ -1240,6 +1240,16 @@ pref("geo.provider.use_corelocation", true);
pref("geo.provider.ms-windows-location", false);
#endif
#ifdef MOZ_WIDGET_GTK
#ifdef MOZ_GPSD
#ifdef RELEASE_BUILD
pref("geo.provider.use_gpsd", false);
#else
pref("geo.provider.use_gpsd", true);
#endif
#endif
#endif
// Necko IPC security checks only needed for app isolation for cookies/cache/etc:
// currently irrelevant for desktop e10s
pref("network.disable.ipc.security", true);
@ -1463,5 +1473,5 @@ pref("signon.schemeUpgrades", true);
pref("print.use_simplify_page", true);
// Space separated list of URLS that are allowed to send objects (instead of
// only strings) through webchannels.
// only strings) through webchannels. This list is duplicated in mobile/android/app/mobile.js
pref("webchannel.allowObject.urlWhitelist", "https://accounts.firefox.com https://content.cdn.mozilla.net https://hello.firefox.com https://input.mozilla.org https://support.mozilla.org https://install.mozilla.org");

View File

@ -407,10 +407,12 @@ var gFxAccounts = {
}
// "All devices" menu item
const separator = document.createElement("menuseparator");
fragment.appendChild(separator);
const allDevicesLabel = this.strings.GetStringFromName("sendTabToAllDevices.menuitem");
addTargetDevice("", allDevicesLabel);
if (clients.length > 1) {
const separator = document.createElement("menuseparator");
fragment.appendChild(separator);
const allDevicesLabel = this.strings.GetStringFromName("sendTabToAllDevices.menuitem");
addTargetDevice("", allDevicesLabel);
}
devicesPopup.appendChild(fragment);
},

View File

@ -8,6 +8,7 @@ var Cu = Components.utils;
var Cc = Components.classes;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/ContextualIdentityService.jsm");
Cu.import("resource://gre/modules/NotificationDB.jsm");
Cu.import("resource:///modules/RecentWindow.jsm");
@ -56,8 +57,6 @@ XPCOMUtils.defineLazyServiceGetter(this, "WindowsUIUtils",
"@mozilla.org/windows-ui-utils;1", "nsIWindowsUIUtils");
XPCOMUtils.defineLazyModuleGetter(this, "LightweightThemeManager",
"resource://gre/modules/LightweightThemeManager.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "ContextualIdentityService",
"resource://gre/modules/ContextualIdentityService.jsm");
XPCOMUtils.defineLazyServiceGetter(this, "gAboutNewTabService",
"@mozilla.org/browser/aboutnewtab-service;1",
"nsIAboutNewTabService");
@ -7761,21 +7760,6 @@ XPCOMUtils.defineLazyGetter(ResponsiveUI, "ResponsiveUIManager", function() {
return tmp.ResponsiveUIManager;
});
function openEyedropper() {
var eyedropper = new this.Eyedropper(this, { context: "menu",
copyOnSelect: true });
eyedropper.open();
}
Object.defineProperty(this, "Eyedropper", {
get: function() {
let devtools = Cu.import("resource://devtools/shared/Loader.jsm", {}).devtools;
return devtools.require("devtools/client/eyedropper/eyedropper").Eyedropper;
},
configurable: true,
enumerable: true
});
XPCOMUtils.defineLazyGetter(window, "gShowPageResizers", function () {
// Only show resizers on Windows 2000 and XP
return AppConstants.isPlatformAndVersionAtMost("win", "5.9");

View File

@ -4,6 +4,7 @@
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
Components.utils.import("resource://gre/modules/ContextualIdentityService.jsm");
Components.utils.import("resource://gre/modules/PrivateBrowsingUtils.jsm");
Components.utils.import("resource://gre/modules/InlineSpellChecker.jsm");
Components.utils.import("resource://gre/modules/LoginManagerContextMenu.jsm");
@ -11,8 +12,6 @@ Components.utils.import("resource://gre/modules/BrowserUtils.jsm");
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
Components.utils.import("resource://gre/modules/Services.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "ContextualIdentityService",
"resource://gre/modules/ContextualIdentityService.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "LoginHelper",
"resource://gre/modules/LoginHelper.jsm");

View File

@ -5,6 +5,7 @@
// Services = object with smart getters for common XPCOM services
Components.utils.import("resource://gre/modules/AppConstants.jsm");
Components.utils.import("resource://gre/modules/ContextualIdentityService.jsm");
Components.utils.import("resource://gre/modules/Services.jsm");
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
Components.utils.import("resource://gre/modules/PrivateBrowsingUtils.jsm");
@ -13,9 +14,6 @@ Components.utils.import("resource:///modules/RecentWindow.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "ShellService",
"resource:///modules/ShellService.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "ContextualIdentityService",
"resource://gre/modules/ContextualIdentityService.jsm");
XPCOMUtils.defineLazyServiceGetter(this, "aboutNewTabService",
"@mozilla.org/browser/aboutnewtab-service;1",
"nsIAboutNewTabService");
@ -447,8 +445,12 @@ function createUserContextMenu(event, addCommandAttribute = true, excludeUserCon
let menuitem = document.createElement("menuitem");
menuitem.setAttribute("usercontextid", identity.userContextId);
menuitem.setAttribute("label", bundle.getString(identity.label));
menuitem.setAttribute("accesskey", bundle.getString(identity.accessKey));
menuitem.setAttribute("label", ContextualIdentityService.getUserContextLabel(identity.userContextId));
if (identity.accessKey) {
menuitem.setAttribute("accesskey", bundle.getString(identity.accessKey));
}
menuitem.classList.add("menuitem-iconic");
if (addCommandAttribute) {

View File

@ -1148,7 +1148,7 @@ const CustomizableWidgets = [
ContextualIdentityService.getIdentities().forEach(identity => {
let bundle = doc.getElementById("bundle_browser");
let label = bundle.getString(identity.label);
let label = ContextualIdentityService.getUserContextLabel(identity.userContextId);
let item = doc.createElementNS(kNSXUL, "toolbarbutton");
item.setAttribute("label", label);

View File

@ -42,44 +42,46 @@ function getSender(context, target, sender) {
}
}
// WeakMap[ExtensionContext -> {tab, parentWindow}]
var pageDataMap = new WeakMap();
function getDocShellOwner(docShell) {
let browser = docShell.chromeEventHandler;
let xulWindow = browser.ownerGlobal;
let {gBrowser} = xulWindow;
if (gBrowser) {
let tab = gBrowser.getTabForBrowser(browser);
return {xulWindow, tab};
}
return {};
}
/* eslint-disable mozilla/balanced-listeners */
// This listener fires whenever an extension page opens in a tab
// (either initiated by the extension or the user). Its job is to fill
// in some tab-specific details and keep data around about the
// ExtensionContext.
extensions.on("page-load", (type, page, params, sender, delegate) => {
extensions.on("page-load", (type, context, params, sender, delegate) => {
if (params.type == "tab" || params.type == "popup") {
let browser = params.docShell.chromeEventHandler;
let {xulWindow, tab} = getDocShellOwner(params.docShell);
let parentWindow = browser.ownerGlobal;
page.windowId = WindowManager.getId(parentWindow);
let tab = parentWindow.gBrowser.getTabForBrowser(browser);
// FIXME: Handle tabs being moved between windows.
context.windowId = WindowManager.getId(xulWindow);
if (tab) {
sender.tabId = TabManager.getId(tab);
page.tabId = TabManager.getId(tab);
context.tabId = TabManager.getId(tab);
}
pageDataMap.set(page, {tab, parentWindow});
}
delegate.getSender = getSender;
});
extensions.on("page-unload", (type, page) => {
pageDataMap.delete(page);
});
extensions.on("page-shutdown", (type, page) => {
if (pageDataMap.has(page)) {
let {tab, parentWindow} = pageDataMap.get(page);
pageDataMap.delete(page);
extensions.on("page-shutdown", (type, context) => {
if (context.type == "tab") {
let {xulWindow, tab} = getDocShellOwner(context.docShell);
if (tab) {
parentWindow.gBrowser.removeTab(tab);
xulWindow.gBrowser.removeTab(tab);
}
}
});
@ -96,9 +98,9 @@ extensions.on("fill-browser-data", (type, browser, data, result) => {
/* eslint-enable mozilla/balanced-listeners */
global.currentWindow = function(context) {
let pageData = pageDataMap.get(context);
if (pageData) {
return pageData.parentWindow;
let {xulWindow} = getDocShellOwner(context.docShell);
if (xulWindow) {
return xulWindow;
}
return WindowManager.topWindow;
};

View File

@ -43,7 +43,10 @@ add_task(function* () {
});
});
browser.tabs.executeScript({file: "script.js"});
browser.tabs.executeScript({file: "script.js"}).catch(e => {
browser.test.fail(`Error: ${e} :: ${e.stack}`);
browser.test.notifyFail("contentscript_connect.pass");
});
},
files: {

View File

@ -257,3 +257,66 @@ add_task(function* test_options_no_manifest() {
yield extension.awaitFinish("options-no-manifest");
yield extension.unload();
});
add_task(function* test_inline_options_uninstall() {
let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "http://example.com/");
let extension = yield loadExtension({
manifest: {
"options_ui": {
"page": "options.html",
},
},
background: function() {
let _optionsPromise;
let awaitOptions = () => {
browser.test.assertFalse(_optionsPromise, "Should not be awaiting options already");
return new Promise(resolve => {
_optionsPromise = {resolve};
});
};
browser.runtime.onMessage.addListener((msg, sender) => {
if (msg == "options.html") {
if (_optionsPromise) {
_optionsPromise.resolve(sender.tab);
_optionsPromise = null;
} else {
browser.test.fail("Saw unexpected options page load");
}
}
});
let firstTab;
browser.tabs.query({currentWindow: true, active: true}).then(tabs => {
firstTab = tabs[0].id;
browser.test.log("Open options page. Expect fresh load.");
return Promise.all([
browser.runtime.openOptionsPage(),
awaitOptions(),
]);
}).then(([, tab]) => {
browser.test.assertEq("about:addons", tab.url, "Tab contains AddonManager");
browser.test.assertTrue(tab.active, "Tab is active");
browser.test.assertTrue(tab.id != firstTab, "Tab is a new tab");
browser.test.sendMessage("options-ui-open");
}).catch(error => {
browser.test.fail(`Error: ${error} :: ${error.stack}`);
});
},
});
yield extension.awaitMessage("options-ui-open");
yield extension.unload();
is(gBrowser.selectedBrowser.currentURI.spec, "about:addons",
"Add-on manager tab should still be open");
yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
yield BrowserTestUtils.removeTab(tab);
});

View File

@ -24,6 +24,14 @@
"unpack": true
},
{
"version": "cargo 0.13.0-nightly (664125b 2016-07-19)",
"size": 3123796,
"digest": "4b9d2bcb8488b6649ba6c748e19d33bfceb25c7566e882fc7e00322392e424a5a9c5878c11c61d57cdaecf67bcc110842c6eff95e49736e8f3c83d9ce1677122",
"algorithm": "sha512",
"filename": "cargo.tar.xz",
"unpack": true
},
{
"size": 167175,
"digest": "0b71a936edf5bd70cf274aaa5d7abc8f77fe8e7b5593a208f805cc9436fac646b9c4f0b43c2b10de63ff3da671497d35536077ecbc72dba7f8159a38b580f831",
"algorithm": "sha512",

View File

@ -24,6 +24,14 @@
"unpack": true
},
{
"version": "cargo 0.13.0-nightly (664125b 2016-07-19)",
"size": 3123796,
"digest": "4b9d2bcb8488b6649ba6c748e19d33bfceb25c7566e882fc7e00322392e424a5a9c5878c11c61d57cdaecf67bcc110842c6eff95e49736e8f3c83d9ce1677122",
"algorithm": "sha512",
"filename": "cargo.tar.xz",
"unpack": true
},
{
"size": 167175,
"digest": "0b71a936edf5bd70cf274aaa5d7abc8f77fe8e7b5593a208f805cc9436fac646b9c4f0b43c2b10de63ff3da671497d35536077ecbc72dba7f8159a38b580f831",
"algorithm": "sha512",

View File

@ -24,6 +24,14 @@
"filename": "MacOSX10.7.sdk.tar.bz2"
},
{
"version": "cargo 0.13.0-nightly (664125b 2016-07-19)",
"size": 2571167,
"digest": "b2616459fbf15c75b54628a6bfe8cf89c0841ea08431f5096e72be4fac4c685785dfc7a2f18a03a5f7bd377e78d3c108e5029b12616842cbbd0497ff7363fdaf",
"algorithm": "sha512",
"filename": "cargo.tar.bz2",
"unpack": true
},
{
"size": 167175,
"digest": "0b71a936edf5bd70cf274aaa5d7abc8f77fe8e7b5593a208f805cc9436fac646b9c4f0b43c2b10de63ff3da671497d35536077ecbc72dba7f8159a38b580f831",
"algorithm": "sha512",

View File

@ -15,6 +15,14 @@
"unpack": true
},
{
"version": "cargo 0.13.0-nightly (664125b 2016-07-19)",
"size": 2571167,
"digest": "b2616459fbf15c75b54628a6bfe8cf89c0841ea08431f5096e72be4fac4c685785dfc7a2f18a03a5f7bd377e78d3c108e5029b12616842cbbd0497ff7363fdaf",
"algorithm": "sha512",
"filename": "cargo.tar.bz2",
"unpack": true
},
{
"size": 167175,
"digest": "0b71a936edf5bd70cf274aaa5d7abc8f77fe8e7b5593a208f805cc9436fac646b9c4f0b43c2b10de63ff3da671497d35536077ecbc72dba7f8159a38b580f831",
"algorithm": "sha512",

View File

@ -14,6 +14,14 @@
"unpack": true
},
{
"version": "cargo 0.13.0-nightly (664125b 2016-07-19)",
"size": 2298848,
"digest": "d3d1f7b6d195248550f98eb8ce87aa314d36a8a667c110ff2058777fe5a97b7007a41dc1c8a4605c4230e9105972768918222352d5e0fdebbc49639671de38ca",
"algorithm": "sha512",
"filename": "cargo.tar.bz2",
"unpack": true
},
{
"size": 167175,
"digest": "0b71a936edf5bd70cf274aaa5d7abc8f77fe8e7b5593a208f805cc9436fac646b9c4f0b43c2b10de63ff3da671497d35536077ecbc72dba7f8159a38b580f831",
"algorithm": "sha512",

View File

@ -15,6 +15,14 @@
"unpack": true
},
{
"version": "cargo 0.13.0-nightly (664125b 2016-07-19)",
"size": 2561498,
"digest": "d300fd06b16efe49bdb1a238d516c8797d2de0edca7efadd55249401e1dd1d775fb84649630e273f95d9e8b956d87d1f75726c0a68294d25fafe078c3b2b9ba9",
"algorithm": "sha512",
"filename": "cargo.tar.bz2",
"unpack": true
},
{
"size": 167175,
"digest": "0b71a936edf5bd70cf274aaa5d7abc8f77fe8e7b5593a208f805cc9436fac646b9c4f0b43c2b10de63ff3da671497d35536077ecbc72dba7f8159a38b580f831",
"algorithm": "sha512",

View File

@ -1,3 +1,3 @@
This is the pdf.js project output, https://github.com/mozilla/pdf.js
Current extension version is: 1.5.337
Current extension version is: 1.5.345

View File

@ -277,7 +277,7 @@ var PdfJs = {
/**
* pdf.js is only enabled if it is both selected as the pdf viewer and if the
* global switch enabling it is true.
* @return {boolean} Wether or not it's enabled.
* @return {boolean} Whether or not it's enabled.
*/
get enabled() {
var disabled = getBoolPref(PREF_DISABLED, true);

View File

@ -334,7 +334,7 @@ ChromeActions.prototype = {
var result = this.localizedStrings[data];
return JSON.stringify(result || null);
} catch (e) {
log('Unable to retrive localized strings: ' + e);
log('Unable to retrieve localized strings: ' + e);
return 'null';
}
},

View File

@ -28,8 +28,8 @@ factory((root.pdfjsDistBuildPdf = {}));
// Use strict in our context only - users might not want it
'use strict';
var pdfjsVersion = '1.5.337';
var pdfjsBuild = '11381cd';
var pdfjsVersion = '1.5.345';
var pdfjsBuild = '10f9f11';
var pdfjsFilePath =
typeof document !== 'undefined' && document.currentScript ?
@ -1080,7 +1080,7 @@ function isSpace(ch) {
*
* @typedef {Object} PromiseCapability
* @property {Promise} promise - A promise object.
* @property {function} resolve - Fullfills the promise.
* @property {function} resolve - Fulfills the promise.
* @property {function} reject - Rejects the promise.
*/
@ -1103,8 +1103,8 @@ function createPromiseCapability() {
/**
* Polyfill for Promises:
* The following promise implementation tries to generally implement the
* Promise/A+ spec. Some notable differences from other promise libaries are:
* - There currently isn't a seperate deferred and promise object.
* Promise/A+ spec. Some notable differences from other promise libraries are:
* - There currently isn't a separate deferred and promise object.
* - Unhandled rejections eventually show an error if they aren't handled.
*
* Based off of the work in:
@ -3615,7 +3615,7 @@ var createMeshCanvas = (function createMeshCanvasClosure() {
// MAX_PATTERN_SIZE is used to avoid OOM situation.
var MAX_PATTERN_SIZE = 3000; // 10in @ 300dpi shall be enough
// We need to keep transparent border around our pattern for fill():
// createPattern with 'no-repeat' will bleed edges accross entire area.
// createPattern with 'no-repeat' will bleed edges across entire area.
var BORDER_SIZE = 2;
var offsetX = Math.floor(bounds[0]);
@ -5688,7 +5688,7 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
var currentCtx = this.ctx;
// TODO non-isolated groups - according to Rik at adobe non-isolated
// group results aren't usually that different and they even have tools
// that ignore this setting. Notes from Rik on implmenting:
// that ignore this setting. Notes from Rik on implementing:
// - When you encounter an transparency group, create a new canvas with
// the dimensions of the bbox
// - copy the content from the previous canvas to the new canvas

View File

@ -28,8 +28,8 @@ factory((root.pdfjsDistBuildPdfWorker = {}));
// Use strict in our context only - users might not want it
'use strict';
var pdfjsVersion = '1.5.337';
var pdfjsBuild = '11381cd';
var pdfjsVersion = '1.5.345';
var pdfjsBuild = '10f9f11';
var pdfjsFilePath =
typeof document !== 'undefined' && document.currentScript ?
@ -3101,7 +3101,7 @@ function isSpace(ch) {
*
* @typedef {Object} PromiseCapability
* @property {Promise} promise - A promise object.
* @property {function} resolve - Fullfills the promise.
* @property {function} resolve - Fulfills the promise.
* @property {function} reject - Rejects the promise.
*/
@ -3124,8 +3124,8 @@ function createPromiseCapability() {
/**
* Polyfill for Promises:
* The following promise implementation tries to generally implement the
* Promise/A+ spec. Some notable differences from other promise libaries are:
* - There currently isn't a seperate deferred and promise object.
* Promise/A+ spec. Some notable differences from other promise libraries are:
* - There currently isn't a separate deferred and promise object.
* - Unhandled rejections eventually show an error if they aren't handled.
*
* Based off of the work in:
@ -4298,7 +4298,7 @@ var CFFParser = (function CFFParserClosure() {
break;
default:
error('Unknow encoding format: ' + format + ' in CFF');
error('Unknown encoding format: ' + format + ' in CFF');
break;
}
var dataEnd = pos;
@ -4771,7 +4771,7 @@ var CFFCompiler = (function CFFCompilerClosure() {
var globalSubrIndex = this.compileIndex(cff.globalSubrIndex);
output.add(globalSubrIndex);
// Now start on the other entries that have no specfic order.
// Now start on the other entries that have no specific order.
if (cff.encoding && cff.topDict.hasName('Encoding')) {
if (cff.encoding.predefined) {
topDictTracker.setEntryLocation('Encoding', [cff.encoding.format],
@ -12533,13 +12533,13 @@ var JpxImage = (function JpxImageClosure() {
var subband = resolution.subbands[j];
var gainLog2 = SubbandsGainLog2[subband.type];
// calulate quantization coefficient (Section E.1.1.1)
// calculate quantization coefficient (Section E.1.1.1)
var delta = (reversible ? 1 :
Math.pow(2, precision + gainLog2 - epsilon) * (1 + mu / 2048));
var mb = (guardBits + epsilon - 1);
// In the first resolution level, copyCoefficients will fill the
// whole array with coefficients. In the succeding passes,
// whole array with coefficients. In the succeeding passes,
// copyCoefficients will consecutively fill in the values that belong
// to the interleaved positions of the HL, LH, and HH coefficients.
// The LL coefficients will then be interleaved in Transform.iterate().
@ -16378,7 +16378,7 @@ exports.getMetrics = getMetrics;
var Uint32ArrayView = sharedUtil.Uint32ArrayView;
var MurmurHash3_64 = (function MurmurHash3_64Closure (seed) {
// Workaround for missing math precison in JS.
// Workaround for missing math precision in JS.
var MASK_HIGH = 0xffff0000;
var MASK_LOW = 0xffff;
@ -24781,7 +24781,7 @@ var Lexer = (function LexerClosure() {
} else if (ch === 0x2D) { // '-'
// ignore minus signs in the middle of numbers to match
// Adobe's behavior
warn('Badly formated number');
warn('Badly formatted number');
} else if (ch === 0x45 || ch === 0x65) { // 'E', 'e'
// 'E' can be either a scientific notation or the beginning of a new
// operator
@ -25862,9 +25862,11 @@ exports.Type1Parser = Type1Parser;
var Util = sharedUtil.Util;
var assert = sharedUtil.assert;
var warn = sharedUtil.warn;
var error = sharedUtil.error;
var isInt = sharedUtil.isInt;
var isString = sharedUtil.isString;
var MissingDataException = sharedUtil.MissingDataException;
var isName = corePrimitives.isName;
var isCmd = corePrimitives.isCmd;
var isStream = corePrimitives.isStream;
@ -26712,41 +26714,49 @@ var CMapFactory = (function CMapFactoryClosure() {
var previous;
var embededUseCMap;
objLoop: while (true) {
var obj = lexer.getObj();
if (isEOF(obj)) {
break;
} else if (isName(obj)) {
if (obj.name === 'WMode') {
parseWMode(cMap, lexer);
} else if (obj.name === 'CMapName') {
parseCMapName(cMap, lexer);
try {
var obj = lexer.getObj();
if (isEOF(obj)) {
break;
} else if (isName(obj)) {
if (obj.name === 'WMode') {
parseWMode(cMap, lexer);
} else if (obj.name === 'CMapName') {
parseCMapName(cMap, lexer);
}
previous = obj;
} else if (isCmd(obj)) {
switch (obj.cmd) {
case 'endcmap':
break objLoop;
case 'usecmap':
if (isName(previous)) {
embededUseCMap = previous.name;
}
break;
case 'begincodespacerange':
parseCodespaceRange(cMap, lexer);
break;
case 'beginbfchar':
parseBfChar(cMap, lexer);
break;
case 'begincidchar':
parseCidChar(cMap, lexer);
break;
case 'beginbfrange':
parseBfRange(cMap, lexer);
break;
case 'begincidrange':
parseCidRange(cMap, lexer);
break;
}
}
previous = obj;
} else if (isCmd(obj)) {
switch (obj.cmd) {
case 'endcmap':
break objLoop;
case 'usecmap':
if (isName(previous)) {
embededUseCMap = previous.name;
}
break;
case 'begincodespacerange':
parseCodespaceRange(cMap, lexer);
break;
case 'beginbfchar':
parseBfChar(cMap, lexer);
break;
case 'begincidchar':
parseCidChar(cMap, lexer);
break;
case 'beginbfrange':
parseBfRange(cMap, lexer);
break;
case 'begincidrange':
parseCidRange(cMap, lexer);
break;
} catch (ex) {
if (ex instanceof MissingDataException) {
throw ex;
}
warn('Invalid cMap data: ' + ex);
continue;
}
}
@ -26757,9 +26767,8 @@ var CMapFactory = (function CMapFactoryClosure() {
}
if (useCMap) {
return extendCMap(cMap, builtInCMapParams, useCMap);
} else {
return Promise.resolve(cMap);
}
return Promise.resolve(cMap);
}
function extendCMap(cMap, builtInCMapParams, useCMap) {
@ -26821,8 +26830,6 @@ var CMapFactory = (function CMapFactoryClosure() {
parseCMap(cMap, lexer, builtInCMapParams, null).then(
function (parsedCMap) {
resolve(parsedCMap);
}).catch(function (e) {
reject(new Error({ message: 'Invalid CMap data', error: e }));
});
} else {
reject(new Error('Unable to get cMap at: ' + url));
@ -27290,6 +27297,7 @@ var ProblematicCharRanges = new Int32Array([
0x0600, 0x0780,
0x08A0, 0x10A0,
0x1780, 0x1800,
0x1C00, 0x1C50,
// General punctuation chars.
0x2000, 0x2010,
0x2011, 0x2012,
@ -29470,7 +29478,7 @@ var Font = (function FontClosure() {
// Naming tables
builder.addTable('name', createNameTable(fontName));
// PostScript informations
// PostScript information
builder.addTable('post', createPostTable(properties));
return builder.toArray();
@ -29847,7 +29855,7 @@ var Type1Font = (function Type1FontClosure() {
(pfbHeader[3] << 8) | pfbHeader[2];
}
// Get the data block containing glyphs and subrs informations
// Get the data block containing glyphs and subrs information
var headerBlock = getHeaderBlock(file, headerBlockLength);
headerBlockLength = headerBlock.length;
var headerBlockParser = new Type1Parser(headerBlock.stream, false,
@ -30714,7 +30722,7 @@ var PDFFunction = (function PDFFunctionClosure() {
// clip to domain
var v = clip(src[srcOffset], domain[0], domain[1]);
// calulate which bound the value is in
// calculate which bound the value is in
for (var i = 0, ii = bounds.length; i < ii; ++i) {
if (v < bounds[i]) {
break;
@ -33157,7 +33165,7 @@ var PDFImage = (function PDFImageClosure() {
i += 8;
}
// handle remaing bits
// handle remaining bits
if (i < loop2End) {
buf = buffer[bufferPos++];
mask = 128;
@ -33225,7 +33233,7 @@ var PDFImage = (function PDFImageClosure() {
width, height);
}
} else if (isArray(mask)) {
// Color key mask: if any of the compontents are outside the range
// Color key mask: if any of the components are outside the range
// then they should be painted.
alphaBuf = new Uint8Array(width * height);
var numComps = this.numComps;
@ -38058,7 +38066,7 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
// According to the spec if 'FontDescriptor' is declared, 'FirstChar',
// 'LastChar' and 'Widths' should exist too, but some PDF encoders seem
// to ignore this rule when a variant of a standart font is used.
// to ignore this rule when a variant of a standard font is used.
// TODO Fill the width array depending on which of the base font this is
// a variant.
var firstChar = (dict.get('FirstChar') || 0);

View File

@ -578,7 +578,7 @@ var PDFBug = (function PDFBugClosure() {
} else {
panel.textContent = tool.name + ' is disabled. To enable add ' +
' "' + tool.id + '" to the pdfBug parameter ' +
'and refresh (seperate multiple by commas).';
'and refresh (separate multiple by commas).';
}
buttons.push(panelButton);
}

View File

@ -5549,7 +5549,7 @@ var PDFPageView = (function PDFPageViewClosure() {
}, function(error) {
console.error(error);
// Tell the printEngine that rendering this canvas/page has failed.
// This will make the print proces stop.
// This will make the print process stop.
if ('abort' in obj) {
obj.abort();
} else {
@ -6346,22 +6346,34 @@ var PDFViewer = (function pdfViewer() {
return this._pages[index];
},
/**
* @returns {number}
*/
get currentPageNumber() {
return this._currentPageNumber;
},
/**
* @param {number} val - The page number.
*/
set currentPageNumber(val) {
if (!this.pdfDocument) {
this._currentPageNumber = val;
return;
}
this._setCurrentPageNumber(val);
// The intent can be to just reset a scroll position and/or scale.
this._resetCurrentPageView();
this._setCurrentPageNumber(val, /* resetCurrentPageView = */ true);
},
_setCurrentPageNumber: function pdfViewer_setCurrentPageNumber(val) {
/**
* @private
*/
_setCurrentPageNumber:
function pdfViewer_setCurrentPageNumber(val, resetCurrentPageView) {
if (this._currentPageNumber === val) {
if (resetCurrentPageView) {
this._resetCurrentPageView();
}
return;
}
var arg;
@ -6384,6 +6396,10 @@ var PDFViewer = (function pdfViewer() {
this._currentPageNumber = val;
this.eventBus.dispatch('pagechanging', arg);
this.eventBus.dispatch('pagechange', arg);
if (resetCurrentPageView) {
this._resetCurrentPageView();
}
},
/**
@ -6691,6 +6707,7 @@ var PDFViewer = (function pdfViewer() {
/**
* Refreshes page view: scrolls to the current page and updates the scale.
* @private
*/
_resetCurrentPageView: function () {
if (this.isInPresentationMode) {
@ -6715,8 +6732,7 @@ var PDFViewer = (function pdfViewer() {
}
if (this.isInPresentationMode || !dest) {
this._setCurrentPageNumber(pageNumber);
this._resetCurrentPageView();
this._setCurrentPageNumber(pageNumber, /* resetCurrentPageView */ true);
return;
}

View File

@ -46,6 +46,7 @@ PluginContent.prototype = {
global.addEventListener("pagehide", this, true);
global.addEventListener("pageshow", this, true);
global.addEventListener("unload", this);
global.addEventListener("HiddenPlugin", this, true);
global.addMessageListener("BrowserPlugins:ActivatePlugins", this);
global.addMessageListener("BrowserPlugins:NotificationShown", this);
@ -66,6 +67,7 @@ PluginContent.prototype = {
global.removeEventListener("pagehide", this, true);
global.removeEventListener("pageshow", this, true);
global.removeEventListener("unload", this);
global.removeEventListener("HiddenPlugin", this, true);
global.removeMessageListener("BrowserPlugins:ActivatePlugins", this);
global.removeMessageListener("BrowserPlugins:NotificationShown", this);
@ -194,6 +196,45 @@ PluginContent.prototype = {
};
},
_getPluginInfoForTag: function (pluginTag, tagMimetype) {
let pluginHost = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost);
let pluginName = gNavigatorBundle.GetStringFromName("pluginInfo.unknownPlugin");
let permissionString = null;
let blocklistState = null;
if (pluginTag) {
pluginName = BrowserUtils.makeNicePluginName(pluginTag.name);
permissionString = pluginHost.getPermissionStringForTag(pluginTag);
blocklistState = pluginTag.blocklistState;
// Convert this from nsIPluginTag so it can be serialized.
let properties = ["name", "description", "filename", "version", "enabledState", "niceName"];
let pluginTagCopy = {};
for (let prop of properties) {
pluginTagCopy[prop] = pluginTag[prop];
}
pluginTag = pluginTagCopy;
// Make state-softblocked == state-notblocked for our purposes,
// they have the same UI. STATE_OUTDATED should not exist for plugin
// items, but let's alias it anyway, just in case.
if (blocklistState == Ci.nsIBlocklistService.STATE_SOFTBLOCKED ||
blocklistState == Ci.nsIBlocklistService.STATE_OUTDATED) {
blocklistState = Ci.nsIBlocklistService.STATE_NOT_BLOCKED;
}
}
return { mimetype: tagMimetype,
pluginName: pluginName,
pluginTag: pluginTag,
permissionString: permissionString,
fallbackType: null,
blocklistState: blocklistState,
};
},
/**
* Update the visibility of the plugin overlay.
*/
@ -353,6 +394,14 @@ PluginContent.prototype = {
return;
}
if (eventType == "HiddenPlugin") {
let pluginTag = event.tag.QueryInterface(Ci.nsIPluginTag);
if (event.target.defaultView.top.document != this.content.document) {
return;
}
this._showClickToPlayNotification(pluginTag, true);
}
let plugin = event.target;
let doc = plugin.ownerDocument;
@ -713,7 +762,13 @@ PluginContent.prototype = {
let location = this.content.document.location.href;
for (let p of plugins) {
let pluginInfo = this._getPluginInfo(p);
let pluginInfo;
if (p instanceof Ci.nsIPluginTag) {
let mimeType = p.getMimeTypes() > 0 ? p.getMimeTypes()[0] : null;
pluginInfo = this._getPluginInfoForTag(p, mimeType);
} else {
pluginInfo = this._getPluginInfo(p);
}
if (pluginInfo.permissionString === null) {
Cu.reportError("No permission string for active plugin.");
continue;

View File

@ -4,6 +4,7 @@
# Assume this is compiled with --enable-rpath so we don't
# have to set LD_LIBRARY_PATH.
RUSTC="$topsrcdir/rustc/bin/rustc"
CARGO="$topsrcdir/cargo/bin/cargo"
# Enable rust in the build.
ac_add_options --enable-rust

View File

@ -498,6 +498,7 @@ glib-object.h
gmodule.h
gnome.h
gnu/libc-version.h
gps.h
grp.h
gssapi_generic.h
gssapi/gssapi_generic.h

View File

@ -98,8 +98,7 @@ Tools.inspector = {
inMenu: true,
commands: [
"devtools/client/responsivedesign/resize-commands",
"devtools/client/inspector/inspector-commands",
"devtools/client/eyedropper/commands.js"
"devtools/client/inspector/inspector-commands"
],
preventClosingOnKey: true,

View File

@ -1,57 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
const l10n = require("gcli/l10n");
const EventEmitter = require("devtools/shared/event-emitter");
const eventEmitter = new EventEmitter();
var { Eyedropper, EyedropperManager } = require("devtools/client/eyedropper/eyedropper");
/**
* 'eyedropper' command
*/
exports.items = [{
item: "command",
runAt: "client",
name: "eyedropper",
description: l10n.lookup("eyedropperDesc"),
manual: l10n.lookup("eyedropperManual"),
buttonId: "command-button-eyedropper",
buttonClass: "command-button command-button-invertable",
tooltipText: l10n.lookup("eyedropperTooltip"),
state: {
isChecked: function (target) {
if (!target.tab) {
return false;
}
let chromeWindow = target.tab.ownerDocument.defaultView;
let dropper = EyedropperManager.getInstance(chromeWindow);
if (dropper) {
return true;
}
return false;
},
onChange: function (target, changeHandler) {
eventEmitter.on("changed", changeHandler);
},
offChange: function (target, changeHandler) {
eventEmitter.off("changed", changeHandler);
},
},
exec: function (args, context) {
let chromeWindow = context.environment.chromeWindow;
let target = context.environment.target;
let dropper = EyedropperManager.createInstance(chromeWindow,
{ context: "command",
copyOnSelect: true });
dropper.open();
eventEmitter.emit("changed", { target: target });
dropper.once("destroy", () => {
eventEmitter.emit("changed", { target: target });
});
}
}];

View File

@ -1,3 +0,0 @@
* {
cursor: crosshair !important;
}

View File

@ -1,24 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
var { interfaces: Ci } = Components;
addMessageListener("Eyedropper:RequestContentScreenshot", sendContentScreenshot);
function sendContentScreenshot() {
let canvas = content.document.createElementNS("http://www.w3.org/1999/xhtml", "canvas");
let scale = content.getInterface(Ci.nsIDOMWindowUtils).fullZoom;
let width = content.innerWidth;
let height = content.innerHeight;
canvas.width = width * scale;
canvas.height = height * scale;
canvas.mozOpaque = true;
let ctx = canvas.getContext("2d");
ctx.scale(scale, scale);
ctx.drawWindow(content, content.scrollX, content.scrollY, width, height, "#fff");
sendAsyncMessage("Eyedropper:Screenshot", canvas.toDataURL());
}

View File

@ -1,839 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const {Cc, Ci} = require("chrome");
const {rgbToHsl, rgbToColorName} =
require("devtools/client/shared/css-color").colorUtils;
const Telemetry = require("devtools/client/shared/telemetry");
const EventEmitter = require("devtools/shared/event-emitter");
const promise = require("promise");
const defer = require("devtools/shared/defer");
const Services = require("Services");
loader.lazyGetter(this, "clipboardHelper", function () {
return Cc["@mozilla.org/widget/clipboardhelper;1"]
.getService(Ci.nsIClipboardHelper);
});
loader.lazyGetter(this, "ssService", function () {
return Cc["@mozilla.org/content/style-sheet-service;1"]
.getService(Ci.nsIStyleSheetService);
});
loader.lazyGetter(this, "ioService", function () {
return Cc["@mozilla.org/network/io-service;1"]
.getService(Ci.nsIIOService);
});
loader.lazyGetter(this, "DOMUtils", function () {
return Cc["@mozilla.org/inspector/dom-utils;1"].getService(Ci.inIDOMUtils);
});
loader.lazyGetter(this, "l10n", () => Services.strings
.createBundle("chrome://devtools/locale/eyedropper.properties"));
const EYEDROPPER_URL = "chrome://devtools/content/eyedropper/eyedropper.xul";
const CROSSHAIRS_URL = "chrome://devtools/content/eyedropper/crosshairs.css";
const NOCURSOR_URL = "chrome://devtools/content/eyedropper/nocursor.css";
const ZOOM_PREF = "devtools.eyedropper.zoom";
const FORMAT_PREF = "devtools.defaultColorUnit";
const CANVAS_WIDTH = 96;
const CANVAS_OFFSET = 3; // equals the border width of the canvas.
const CLOSE_DELAY = 750;
const HEX_BOX_WIDTH = CANVAS_WIDTH + CANVAS_OFFSET * 2;
const HSL_BOX_WIDTH = 158;
/**
* Manage instances of eyedroppers for windows. Registering here isn't
* necessary for creating an eyedropper, but can be used for testing.
*/
var EyedropperManager = {
_instances: new WeakMap(),
getInstance: function (chromeWindow) {
return this._instances.get(chromeWindow);
},
createInstance: function (chromeWindow, options) {
let dropper = this.getInstance(chromeWindow);
if (dropper) {
return dropper;
}
dropper = new Eyedropper(chromeWindow, options);
this._instances.set(chromeWindow, dropper);
dropper.on("destroy", () => {
this.deleteInstance(chromeWindow);
});
return dropper;
},
deleteInstance: function (chromeWindow) {
this._instances.delete(chromeWindow);
}
};
exports.EyedropperManager = EyedropperManager;
/**
* Eyedropper widget. Once opened, shows zoomed area above current pixel and
* displays the color value of the center pixel. Clicking on the window will
* close the widget and fire a 'select' event. If 'copyOnSelect' is true, the color
* will also be copied to the clipboard.
*
* let eyedropper = new Eyedropper(window);
* eyedropper.open();
*
* eyedropper.once("select", (ev, color) => {
* console.log(color); // "rgb(20, 50, 230)"
* })
*
* @param {DOMWindow} chromeWindow
* window to inspect
* @param {object} opts
* optional options object, with 'copyOnSelect', 'context'
*/
function Eyedropper(chromeWindow, opts = { copyOnSelect: true, context: "other" }) {
this.copyOnSelect = opts.copyOnSelect;
this._onFirstMouseMove = this._onFirstMouseMove.bind(this);
this._onMouseMove = this._onMouseMove.bind(this);
this._onMouseDown = this._onMouseDown.bind(this);
this._onKeyDown = this._onKeyDown.bind(this);
this._onFrameLoaded = this._onFrameLoaded.bind(this);
this._chromeWindow = chromeWindow;
this._chromeDocument = chromeWindow.document;
this._OS = Services.appinfo.OS;
this._dragging = true;
this.loaded = false;
this._mouseMoveCounter = 0;
this.format = Services.prefs.getCharPref(FORMAT_PREF); // color value format
this.zoom = Services.prefs.getIntPref(ZOOM_PREF); // zoom level - integer
this._zoomArea = {
x: 0, // the left coordinate of the center of the inspected region
y: 0, // the top coordinate of the center of the inspected region
width: CANVAS_WIDTH, // width of canvas to draw zoomed area onto
height: CANVAS_WIDTH // height of canvas
};
if (this._contentTab) {
let mm = this._contentTab.linkedBrowser.messageManager;
mm.loadFrameScript("resource://devtools/client/eyedropper/eyedropper-child.js", true);
}
// record if this was opened via the picker or standalone
var telemetry = new Telemetry();
if (opts.context == "command") {
telemetry.toolOpened("eyedropper");
}
else if (opts.context == "menu") {
telemetry.toolOpened("menueyedropper");
}
else if (opts.context == "picker") {
telemetry.toolOpened("pickereyedropper");
}
EventEmitter.decorate(this);
}
exports.Eyedropper = Eyedropper;
Eyedropper.prototype = {
/**
* Get the number of cells (blown-up pixels) per direction in the grid.
*/
get cellsWide() {
// Canvas will render whole "pixels" (cells) only, and an even
// number at that. Round up to the nearest even number of pixels.
let cellsWide = Math.ceil(this._zoomArea.width / this.zoom);
cellsWide += cellsWide % 2;
return cellsWide;
},
/**
* Get the size of each cell (blown-up pixel) in the grid.
*/
get cellSize() {
return this._zoomArea.width / this.cellsWide;
},
/**
* Get index of cell in the center of the grid.
*/
get centerCell() {
return Math.floor(this.cellsWide / 2);
},
/**
* Get color of center cell in the grid.
*/
get centerColor() {
let x, y;
x = y = (this.centerCell * this.cellSize) + (this.cellSize / 2);
let rgb = this._ctx.getImageData(x, y, 1, 1).data;
return rgb;
},
get _contentTab() {
return this._chromeWindow.gBrowser && this._chromeWindow.gBrowser.selectedTab;
},
/**
* Fetch a screenshot of the content.
*
* @return {promise}
* Promise that resolves with the screenshot as a dataURL
*/
getContentScreenshot: function () {
if (!this._contentTab) {
return promise.resolve(null);
}
let deferred = defer();
let mm = this._contentTab.linkedBrowser.messageManager;
function onScreenshot(message) {
mm.removeMessageListener("Eyedropper:Screenshot", onScreenshot);
deferred.resolve(message.data);
}
mm.addMessageListener("Eyedropper:Screenshot", onScreenshot);
mm.sendAsyncMessage("Eyedropper:RequestContentScreenshot");
return deferred.promise;
},
/**
* Start the eyedropper. Add listeners for a mouse move in the window to
* show the eyedropper.
*/
open: function () {
if (this.isOpen) {
// the eyedropper is aready open, don't create another panel.
return promise.resolve();
}
this.isOpen = true;
this._showCrosshairs();
// Get screenshot of content so we can inspect colors
return this.getContentScreenshot().then((dataURL) => {
// The data url may be null, e.g. if there is no content tab
if (dataURL) {
this._contentImage = new this._chromeWindow.Image();
this._contentImage.src = dataURL;
// Wait for screenshot to load
let imageLoaded = promise.defer();
this._contentImage.onload = imageLoaded.resolve
return imageLoaded.promise;
}
}).then(() => {
// Then start showing the eyedropper UI
this._chromeDocument.addEventListener("mousemove", this._onFirstMouseMove);
this.isStarted = true;
this.emit("started");
});
},
/**
* Called on the first mouse move over the window. Opens the eyedropper
* panel where the mouse is.
*/
_onFirstMouseMove: function (event) {
this._chromeDocument.removeEventListener("mousemove", this._onFirstMouseMove);
this._panel = this._buildPanel();
let popupSet = this._chromeDocument.querySelector("#mainPopupSet");
popupSet.appendChild(this._panel);
let { panelX, panelY } = this._getPanelCoordinates(event);
this._panel.openPopupAtScreen(panelX, panelY);
this._setCoordinates(event);
this._addListeners();
// hide cursor as we'll be showing the panel over the mouse instead.
this._hideCrosshairs();
this._hideCursor();
},
/**
* Whether the coordinates are over the content or chrome.
*
* @param {number} clientX
* x-coordinate of mouse relative to browser window.
* @param {number} clientY
* y-coordinate of mouse relative to browser window.
*/
_isInContent: function (clientX, clientY) {
let box = this._contentTab && this._contentTab.linkedBrowser.getBoundingClientRect();
if (box &&
clientX > box.left &&
clientX < box.right &&
clientY > box.top &&
clientY < box.bottom) {
return true;
}
return false;
},
/**
* Set the current coordinates to inspect from where a mousemove originated.
*
* @param {MouseEvent} event
* Event for the mouse move.
*/
_setCoordinates: function (event) {
let inContent = this._isInContent(event.clientX, event.clientY);
let win = this._chromeWindow;
// offset of mouse from browser window
let x = event.clientX;
let y = event.clientY;
if (inContent) {
// calculate the offset of the mouse from the content window
let box = this._contentTab.linkedBrowser.getBoundingClientRect();
x = x - box.left;
y = y - box.top;
this._zoomArea.contentWidth = box.width;
this._zoomArea.contentHeight = box.height;
}
this._zoomArea.inContent = inContent;
// don't let it inspect outside the browser window
x = Math.max(0, Math.min(x, win.outerWidth - 1));
y = Math.max(0, Math.min(y, win.outerHeight - 1));
this._zoomArea.x = x;
this._zoomArea.y = y;
},
/**
* Build and add a new eyedropper panel to the window.
*
* @return {Panel}
* The XUL panel holding the eyedropper UI.
*/
_buildPanel: function () {
let panel = this._chromeDocument.createElement("panel");
panel.setAttribute("noautofocus", true);
panel.setAttribute("noautohide", true);
panel.setAttribute("level", "floating");
panel.setAttribute("class", "devtools-eyedropper-panel");
let iframe = this._iframe = this._chromeDocument.createElement("iframe");
iframe.addEventListener("load", this._onFrameLoaded, true);
iframe.setAttribute("flex", "1");
iframe.setAttribute("transparent", "transparent");
iframe.setAttribute("allowTransparency", true);
iframe.setAttribute("class", "devtools-eyedropper-iframe");
iframe.setAttribute("src", EYEDROPPER_URL);
iframe.setAttribute("width", CANVAS_WIDTH);
iframe.setAttribute("height", CANVAS_WIDTH);
panel.appendChild(iframe);
return panel;
},
/**
* Event handler for the panel's iframe's load event. Emits
* a "load" event from this eyedropper object.
*/
_onFrameLoaded: function () {
this._iframe.removeEventListener("load", this._onFrameLoaded, true);
this._iframeDocument = this._iframe.contentDocument;
this._colorPreview = this._iframeDocument.querySelector("#color-preview");
this._colorValue = this._iframeDocument.querySelector("#color-value");
// value box will be too long for hex values and too short for hsl
let valueBox = this._iframeDocument.querySelector("#color-value-box");
if (this.format == "hex") {
valueBox.style.width = HEX_BOX_WIDTH + "px";
}
else if (this.format == "hsl") {
valueBox.style.width = HSL_BOX_WIDTH + "px";
}
this._canvas = this._iframeDocument.querySelector("#canvas");
this._ctx = this._canvas.getContext("2d");
// so we preserve the clear pixel boundaries
this._ctx.mozImageSmoothingEnabled = false;
this._drawWindow();
this._addPanelListeners();
this._iframe.focus();
this.loaded = true;
this.emit("load");
},
/**
* Add key listeners to the panel.
*/
_addPanelListeners: function () {
this._iframeDocument.addEventListener("keydown", this._onKeyDown);
let closeCmd = this._iframeDocument.getElementById("eyedropper-cmd-close");
closeCmd.addEventListener("command", this.destroy.bind(this), true);
let copyCmd = this._iframeDocument.getElementById("eyedropper-cmd-copy");
copyCmd.addEventListener("command", this.selectColor.bind(this), true);
},
/**
* Remove listeners from the panel.
*/
_removePanelListeners: function () {
this._iframeDocument.removeEventListener("keydown", this._onKeyDown);
},
/**
* Add mouse event listeners to the document we're inspecting.
*/
_addListeners: function () {
this._chromeDocument.addEventListener("mousemove", this._onMouseMove);
this._chromeDocument.addEventListener("mousedown", this._onMouseDown);
},
/**
* Remove mouse event listeners from the document we're inspecting.
*/
_removeListeners: function () {
this._chromeDocument.removeEventListener("mousemove", this._onFirstMouseMove);
this._chromeDocument.removeEventListener("mousemove", this._onMouseMove);
this._chromeDocument.removeEventListener("mousedown", this._onMouseDown);
},
/**
* Hide the cursor.
*/
_hideCursor: function () {
registerStyleSheet(NOCURSOR_URL);
},
/**
* Reset the cursor back to default.
*/
_resetCursor: function () {
unregisterStyleSheet(NOCURSOR_URL);
},
/**
* Show a crosshairs as the mouse cursor
*/
_showCrosshairs: function () {
registerStyleSheet(CROSSHAIRS_URL);
},
/**
* Reset cursor.
*/
_hideCrosshairs: function () {
unregisterStyleSheet(CROSSHAIRS_URL);
},
/**
* Event handler for a mouse move over the page we're inspecting.
* Preview the area under the cursor, and move panel to be under the cursor.
*
* @param {DOMEvent} event
* MouseEvent for the mouse moving
*/
_onMouseMove: function (event) {
if (!this._dragging || !this._panel || !this._canvas) {
return;
}
if (this._OS == "Linux" && ++this._mouseMoveCounter % 2 == 0) {
// skip every other mousemove to preserve performance.
return;
}
this._setCoordinates(event);
this._drawWindow();
let { panelX, panelY } = this._getPanelCoordinates(event);
this._movePanel(panelX, panelY);
},
/**
* Get coordinates of where the eyedropper panel should go based on
* the current coordinates of the mouse cursor.
*
* @param {MouseEvent} event
* object with properties 'screenX' and 'screenY'
*
* @return {object}
* object with properties 'panelX', 'panelY'
*/
_getPanelCoordinates: function ({screenX, screenY}) {
let win = this._chromeWindow;
let offset = CANVAS_WIDTH / 2 + CANVAS_OFFSET;
let panelX = screenX - offset;
let windowX = win.screenX + (win.outerWidth - win.innerWidth);
let maxX = win.screenX + win.outerWidth - offset - 1;
let panelY = screenY - offset;
let windowY = win.screenY + (win.outerHeight - win.innerHeight);
let maxY = win.screenY + win.outerHeight - offset - 1;
// don't let the panel move outside the browser window
panelX = Math.max(windowX - offset, Math.min(panelX, maxX));
panelY = Math.max(windowY - offset, Math.min(panelY, maxY));
return { panelX: panelX, panelY: panelY };
},
/**
* Move the eyedropper panel to the given coordinates.
*
* @param {number} screenX
* left coordinate on the screen
* @param {number} screenY
* top coordinate
*/
_movePanel: function (screenX, screenY) {
this._panelX = screenX;
this._panelY = screenY;
this._panel.moveTo(screenX, screenY);
},
/**
* Handler for the mouse down event on the inspected page. This means a
* click, so we'll select the color that's currently hovered.
*
* @param {Event} event
* DOM MouseEvent object
*/
_onMouseDown: function (event) {
event.preventDefault();
event.stopPropagation();
this.selectColor();
},
/**
* Select the current color that's being previewed. Fire a
* "select" event with the color as an rgb string.
*/
selectColor: function () {
if (this._isSelecting) {
return;
}
this._isSelecting = true;
this._dragging = false;
this.emit("select", this._colorValue.value);
if (this.copyOnSelect) {
this.copyColor(this.destroy.bind(this));
}
else {
this.destroy();
}
},
/**
* Copy the currently inspected color to the clipboard.
*
* @param {Function} callback
* Callback to be called when the color is in the clipboard.
*/
copyColor: function (callback) {
clearTimeout(this._copyTimeout);
let color = this._colorValue.value;
clipboardHelper.copyString(color);
this._colorValue.classList.add("highlight");
this._colorValue.value = "✓ " + l10n.GetStringFromName("colorValue.copied");
this._copyTimeout = setTimeout(() => {
this._colorValue.classList.remove("highlight");
this._colorValue.value = color;
if (callback) {
callback();
}
}, CLOSE_DELAY);
},
/**
* Handler for the keydown event on the panel. Either copy the color
* or move the panel in a direction depending on the key pressed.
*
* @param {Event} event
* DOM KeyboardEvent object
*/
_onKeyDown: function (event) {
if (event.metaKey && event.keyCode === event.DOM_VK_C) {
this.copyColor();
return;
}
let offsetX = 0;
let offsetY = 0;
let modifier = 1;
if (event.keyCode === event.DOM_VK_LEFT) {
offsetX = -1;
}
if (event.keyCode === event.DOM_VK_RIGHT) {
offsetX = 1;
}
if (event.keyCode === event.DOM_VK_UP) {
offsetY = -1;
}
if (event.keyCode === event.DOM_VK_DOWN) {
offsetY = 1;
}
if (event.shiftKey) {
modifier = 10;
}
offsetY *= modifier;
offsetX *= modifier;
if (offsetX !== 0 || offsetY !== 0) {
this._zoomArea.x += offsetX;
this._zoomArea.y += offsetY;
this._drawWindow();
this._movePanel(this._panelX + offsetX, this._panelY + offsetY);
event.preventDefault();
}
},
/**
* Draw the inspected area onto the canvas using the zoom level.
*/
_drawWindow: function () {
let { width, height, x, y, inContent,
contentWidth, contentHeight } = this._zoomArea;
let zoomedWidth = width / this.zoom;
let zoomedHeight = height / this.zoom;
let leftX = x - (zoomedWidth / 2);
let topY = y - (zoomedHeight / 2);
// draw the portion of the window we're inspecting
if (inContent) {
// draw from content source image "s" to destination rect "d"
let sx = leftX;
let sy = topY;
let sw = zoomedWidth;
let sh = zoomedHeight;
let dx = 0;
let dy = 0;
// we're at the content edge, so we have to crop the drawing
if (leftX < 0) {
sx = 0;
sw = zoomedWidth + leftX;
dx = -leftX;
}
else if (leftX + zoomedWidth > contentWidth) {
sw = contentWidth - leftX;
}
if (topY < 0) {
sy = 0;
sh = zoomedHeight + topY;
dy = -topY;
}
else if (topY + zoomedHeight > contentHeight) {
sh = contentHeight - topY;
}
let dw = sw;
let dh = sh;
// we don't want artifacts when we're inspecting the edges of content
if (leftX < 0 || topY < 0 ||
leftX + zoomedWidth > contentWidth ||
topY + zoomedHeight > contentHeight) {
this._ctx.fillStyle = "white";
this._ctx.fillRect(0, 0, width, height);
}
// draw from the screenshot to the eyedropper canvas
this._ctx.drawImage(this._contentImage, sx, sy, sw,
sh, dx, dy, dw, dh);
}
else {
// the mouse is over the chrome, so draw that instead of the content
this._ctx.drawWindow(this._chromeWindow, leftX, topY, zoomedWidth,
zoomedHeight, "white");
}
// now scale it
this._ctx.drawImage(this._canvas, 0, 0, zoomedWidth, zoomedHeight,
0, 0, width, height);
let rgb = this.centerColor;
this._colorPreview.style.backgroundColor = toColorString(rgb, "rgb");
this._colorValue.value = toColorString(rgb, this.format);
if (this.zoom > 2) {
// grid at 2x is too busy
this._drawGrid();
}
this._drawCrosshair();
},
/**
* Draw a grid on the canvas representing pixel boundaries.
*/
_drawGrid: function () {
let { width, height } = this._zoomArea;
this._ctx.lineWidth = 1;
this._ctx.strokeStyle = "rgba(143, 143, 143, 0.2)";
for (let i = 0; i < width; i += this.cellSize) {
this._ctx.beginPath();
this._ctx.moveTo(i - .5, 0);
this._ctx.lineTo(i - .5, height);
this._ctx.stroke();
this._ctx.beginPath();
this._ctx.moveTo(0, i - .5);
this._ctx.lineTo(width, i - .5);
this._ctx.stroke();
}
},
/**
* Draw a box on the canvas to highlight the center cell.
*/
_drawCrosshair: function () {
let x, y;
x = y = this.centerCell * this.cellSize;
this._ctx.lineWidth = 1;
this._ctx.lineJoin = "miter";
this._ctx.strokeStyle = "rgba(0, 0, 0, 1)";
this._ctx.strokeRect(x - 1.5, y - 1.5, this.cellSize + 2, this.cellSize + 2);
this._ctx.strokeStyle = "rgba(255, 255, 255, 1)";
this._ctx.strokeRect(x - 0.5, y - 0.5, this.cellSize, this.cellSize);
},
/**
* Destroy the eyedropper and clean up. Emits a "destroy" event.
*/
destroy: function () {
this._resetCursor();
this._hideCrosshairs();
if (this._panel) {
this._panel.hidePopup();
this._panel.remove();
this._panel = null;
}
this._removePanelListeners();
this._removeListeners();
this.isStarted = false;
this.isOpen = false;
this._isSelecting = false;
this.emit("destroy");
}
};
/**
* Add a user style sheet that applies to all documents.
*/
function registerStyleSheet(url) {
var uri = ioService.newURI(url, null, null);
if (!ssService.sheetRegistered(uri, ssService.AGENT_SHEET)) {
ssService.loadAndRegisterSheet(uri, ssService.AGENT_SHEET);
}
}
/**
* Remove a user style sheet.
*/
function unregisterStyleSheet(url) {
var uri = ioService.newURI(url, null, null);
if (ssService.sheetRegistered(uri, ssService.AGENT_SHEET)) {
ssService.unregisterSheet(uri, ssService.AGENT_SHEET);
}
}
/**
* Get a formatted CSS color string from a color value.
*
* @param {array} rgb
* Rgb values of a color to format
* @param {string} format
* Format of string. One of "hex", "rgb", "hsl", "name"
*
* @return {string}
* Formatted color value, e.g. "#FFF" or "hsl(20, 10%, 10%)"
*/
function toColorString(rgb, format) {
let [r, g, b] = rgb;
switch (format) {
case "hex":
return hexString(rgb);
case "rgb":
return "rgb(" + r + ", " + g + ", " + b + ")";
case "hsl":
let [h, s, l] = rgbToHsl(rgb);
return "hsl(" + h + ", " + s + "%, " + l + "%)";
case "name":
let str;
try {
str = rgbToColorName(r, g, b);
} catch (e) {
str = hexString(rgb);
}
return str;
default:
return hexString(rgb);
}
}
/**
* Produce a hex-formatted color string from rgb values.
*
* @param {array} rgb
* Rgb values of color to stringify
*
* @return {string}
* Hex formatted string for color, e.g. "#FFEE00"
*/
function hexString([r, g, b]) {
let val = (1 << 24) + (r << 16) + (g << 8) + (b << 0);
return "#" + val.toString(16).substr(-6).toUpperCase();
}

View File

@ -1,44 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<!DOCTYPE window []>
<?xml-stylesheet href="chrome://devtools/skin/common.css" type="text/css"?>
<?xml-stylesheet href="chrome://devtools/skin/eyedropper.css" type="text/css"?>
<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
no-theme="true">
<script type="application/javascript;version=1.8"
src="chrome://devtools/content/shared/theme-switching.js"/>
<commandset id="eyedropper-commandset">
<command id="eyedropper-cmd-close"
oncommand="void(0);"/>
<command id="eyedropper-cmd-copy"
oncommand="void(0);"/>
</commandset>
<keyset id="eyedropper-keyset">
<key id="eyedropper-key-escape"
keycode="VK_ESCAPE"
command="eyedropper-cmd-close"/>
<key id="eyedropper-key-enter"
keycode="VK_RETURN"
command="eyedropper-cmd-copy"/>
</keyset>
<box id="canvas-overflow">
<canvas id="canvas" xmlns="http://www.w3.org/1999/xhtml" width="96" height="96">
</canvas>
</box>
<hbox id="color-value-container">
<hbox id="color-value-box">
<box id="color-preview">
</box>
<label id="color-value" class="devtools-monospace">
</label>
</hbox>
</hbox>
</window>

View File

@ -1,13 +0,0 @@
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=python:
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
DevToolsModules(
'commands.js',
'eyedropper-child.js',
'eyedropper.js'
)
BROWSER_CHROME_MANIFESTS += ['test/browser.ini']

View File

@ -1,3 +0,0 @@
* {
cursor: none !important;
}

View File

@ -1,4 +0,0 @@
{
// Extend from the shared list of defined globals for mochitests.
"extends": "../../../.eslintrc.mochitests"
}

View File

@ -1,13 +0,0 @@
[DEFAULT]
tags = devtools
subsuite = clipboard
support-files =
color-block.html
head.js
!/devtools/client/commandline/test/helpers.js
!/devtools/client/framework/test/shared-head.js
[browser_eyedropper_basic.js]
skip-if = os == "win" && debug # bug 963492
[browser_eyedropper_cmd.js]
skip-if = true # bug 1278400

View File

@ -1,80 +0,0 @@
/* vim: set ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
const TESTCASE_URI = CHROME_URL_ROOT + "color-block.html";
const DIV_COLOR = "#0000FF";
/**
* Test basic eyedropper widget functionality:
* - Opening eyedropper and pressing ESC closes the eyedropper
* - Opening eyedropper and clicking copies the center color
*/
add_task(function* () {
yield addTab(TESTCASE_URI);
info("added tab");
yield testEscape();
info("testing selecting a color");
yield testSelect();
});
function* testEscape() {
let dropper = new Eyedropper(window);
yield inspectPage(dropper, false);
let destroyed = dropper.once("destroy");
pressESC();
yield destroyed;
ok(true, "escape closed the eyedropper");
}
function* testSelect() {
let dropper = new Eyedropper(window);
let selected = dropper.once("select");
let copied = waitForClipboard(() => {}, DIV_COLOR);
yield inspectPage(dropper);
let color = yield selected;
is(color, DIV_COLOR, "correct color selected");
// wait for DIV_COLOR to be copied to the clipboard
yield copied;
}
/* Helpers */
function* inspectPage(dropper, click = true) {
yield dropper.open();
info("dropper opened");
let target = document.documentElement;
let win = window;
// get location of the <div> in the content, offset from browser window
let box = gBrowser.selectedBrowser.getBoundingClientRect();
let x = box.left + 100;
let y = box.top + 100;
EventUtils.synthesizeMouse(target, x, y, { type: "mousemove" }, win);
yield dropperLoaded(dropper);
EventUtils.synthesizeMouse(target, x + 10, y + 10, { type: "mousemove" }, win);
if (click) {
EventUtils.synthesizeMouse(target, x + 10, y + 10, {}, win);
}
}
function pressESC() {
EventUtils.synthesizeKey("VK_ESCAPE", { });
}

View File

@ -1,61 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
// Tests that the eyedropper command works
const TESTCASE_URI = CHROME_URL_ROOT + "color-block.html";
const DIV_COLOR = "#0000FF";
function test() {
return Task.spawn(spawnTest).then(finish, helpers.handleError);
}
function* spawnTest() {
let options = yield helpers.openTab(TESTCASE_URI);
yield helpers.openToolbar(options);
yield helpers.audit(options, [
{
setup: "eyedropper",
check: {
input: "eyedropper"
},
exec: { output: "" }
},
]);
yield inspectAndWaitForCopy();
yield helpers.closeToolbar(options);
yield helpers.closeTab(options);
}
function inspectAndWaitForCopy() {
let copied = waitForClipboard(() => {}, DIV_COLOR);
let ready = inspectPage(); // resolves once eyedropper is destroyed
return Promise.all([copied, ready]);
}
function inspectPage() {
let target = document.documentElement;
let win = window;
// get location of the <div> in the content, offset from browser window
let box = gBrowser.selectedBrowser.getBoundingClientRect();
let x = box.left + 100;
let y = box.top + 100;
let dropper = EyedropperManager.getInstance(window);
return dropperStarted(dropper).then(() => {
EventUtils.synthesizeMouse(target, x, y, { type: "mousemove" }, win);
return dropperLoaded(dropper).then(() => {
EventUtils.synthesizeMouse(target, x + 10, y + 10, { type: "mousemove" }, win);
EventUtils.synthesizeMouse(target, x + 10, y + 10, {}, win);
return dropper.once("destroy");
});
});
}

View File

@ -1,22 +0,0 @@
<!doctype html>
<html>
<head>
<title>basic eyedropper test case</title>
<style type="text/css">
body {
background: #f99;
}
#test {
margin: 100px;
background-color: blue;
width: 20px;
height: 20px;
}
</style>
</head>
<body>
<div id="test">
</div>
</body>
</html>

View File

@ -1,28 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
// shared-head.js handles imports, constants, and utility functions
Services.scriptloader.loadSubScript("chrome://mochitests/content/browser/devtools/client/framework/test/shared-head.js", this);
Services.scriptloader.loadSubScript(TEST_DIR + "../../../commandline/test/helpers.js", this);
const { Eyedropper, EyedropperManager } = require("devtools/client/eyedropper/eyedropper");
function waitForClipboard(setup, expected) {
let deferred = defer();
SimpleTest.waitForClipboard(expected, setup, deferred.resolve, deferred.reject);
return deferred.promise;
}
function dropperStarted(dropper) {
if (dropper.isStarted) {
return promise.resolve();
}
return dropper.once("started");
}
function dropperLoaded(dropper) {
if (dropper.loaded) {
return promise.resolve();
}
return dropper.once("load");
}

View File

@ -93,7 +93,6 @@ const ToolboxButtons = exports.ToolboxButtons = [
{ id: "command-button-responsive" },
{ id: "command-button-paintflashing" },
{ id: "command-button-scratchpad" },
{ id: "command-button-eyedropper" },
{ id: "command-button-screenshot" },
{ id: "command-button-rulers" },
{ id: "command-button-measure" },

View File

@ -5,8 +5,9 @@
"use strict";
const l10n = require("gcli/l10n");
loader.lazyRequireGetter(this, "gDevTools",
"devtools/client/framework/devtools", true);
loader.lazyRequireGetter(this, "gDevTools", "devtools/client/framework/devtools", true);
const {EyeDropper, HighlighterEnvironment} = require("devtools/server/actors/highlighters");
const Telemetry = require("devtools/client/shared/telemetry");
exports.items = [{
item: "command",
@ -28,4 +29,43 @@ exports.items = [{
toolbox.getCurrentPanel().selection.setNode(args.selector, "gcli");
});
}
}, {
item: "command",
runAt: "client",
name: "eyedropper",
description: l10n.lookup("eyedropperDesc"),
manual: l10n.lookup("eyedropperManual"),
params: [{
// This hidden parameter is only set to true when the eyedropper browser menu item is
// used. It is useful to log a different telemetry event whether the tool was used
// from the menu, or from the gcli command line.
group: "hiddengroup",
params: [{
name: "frommenu",
type: "boolean",
hidden: true
}]
}],
exec: function (args, context) {
let telemetry = new Telemetry();
telemetry.toolOpened(args.frommenu ? "menueyedropper" : "eyedropper");
context.updateExec("eyedropper_server").catch(e => console.error(e));
}
}, {
item: "command",
runAt: "server",
name: "eyedropper_server",
hidden: true,
exec: function (args, {environment}) {
let env = new HighlighterEnvironment();
env.initFromWindow(environment.window);
let eyeDropper = new EyeDropper(env);
eyeDropper.show(environment.document.documentElement, {copyOnSelect: true});
eyeDropper.once("hidden", () => {
eyeDropper.destroy();
env.destroy();
});
}
}];

View File

@ -20,6 +20,7 @@ var {KeyShortcuts} = require("devtools/client/shared/key-shortcuts");
var {Task} = require("devtools/shared/task");
const {initCssProperties} = require("devtools/shared/fronts/css-properties");
const nodeConstants = require("devtools/shared/dom-node-constants");
const Telemetry = require("devtools/client/shared/telemetry");
const Menu = require("devtools/client/framework/menu");
const MenuItem = require("devtools/client/framework/menu-item");
@ -89,6 +90,8 @@ function InspectorPanel(iframeWindow, toolbox) {
this.panelWin = iframeWindow;
this.panelWin.inspector = this;
this.telemetry = new Telemetry();
this.nodeMenuTriggerInfo = null;
this._handleRejectionIfNotDestroyed = this._handleRejectionIfNotDestroyed.bind(this);
@ -102,13 +105,6 @@ function InspectorPanel(iframeWindow, toolbox) {
this.onPaneToggleButtonClicked = this.onPaneToggleButtonClicked.bind(this);
this._onMarkupFrameLoad = this._onMarkupFrameLoad.bind(this);
let doc = this.panelDoc;
// Handle 'Add Node' toolbar button.
this.addNode = this.addNode.bind(this);
this.addNodeButton = doc.getElementById("inspector-element-add-button");
this.addNodeButton.addEventListener("click", this.addNode);
this._target.on("will-navigate", this._onBeforeNavigate);
this._detectingActorFeatures = this._detectActorFeatures();
@ -255,6 +251,7 @@ InspectorPanel.prototype = {
this.setupSearchBox();
this.setupSidebar();
this.setupToolbar();
return deferred.promise;
},
@ -462,7 +459,6 @@ InspectorPanel.prototype = {
this.sidebar.toggleTab(true, "fontinspector");
}
this.setupSidebarToggle();
this.setupSidebarSize();
this.sidebar.show(defaultTab);
@ -513,10 +509,8 @@ InspectorPanel.prototype = {
});
},
/**
* Add the expand/collapse behavior for the sidebar panel.
*/
setupSidebarToggle: function () {
setupToolbar: function () {
// Setup the sidebar toggle button.
let SidebarToggle = this.React.createFactory(this.browserRequire(
"devtools/client/shared/components/sidebar-toggle"));
@ -529,6 +523,36 @@ InspectorPanel.prototype = {
let parentBox = this.panelDoc.getElementById("inspector-sidebar-toggle-box");
this._sidebarToggle = this.ReactDOM.render(sidebarToggle, parentBox);
// Setup the add-node button.
this.addNode = this.addNode.bind(this);
this.addNodeButton = this.panelDoc.getElementById("inspector-element-add-button");
this.addNodeButton.addEventListener("click", this.addNode);
// Setup the eye-dropper icon.
this.toolbox.target.actorHasMethod("inspector", "pickColorFromPage").then(value => {
if (!value) {
return;
}
this.onEyeDropperDone = this.onEyeDropperDone.bind(this);
this.onEyeDropperButtonClicked = this.onEyeDropperButtonClicked.bind(this);
this.eyeDropperButton = this.panelDoc.getElementById("inspector-eyedropper-toggle");
this.eyeDropperButton.style.display = "initial";
this.eyeDropperButton.addEventListener("click", this.onEyeDropperButtonClicked);
}, e => console.error(e));
},
teardownToolbar: function () {
if (this.addNodeButton) {
this.addNodeButton.removeEventListener("click", this.addNode);
this.addNodeButton = null;
}
if (this.eyeDropperButton) {
this.eyeDropperButton.removeEventListener("click", this.onEyeDropperButtonClicked);
this.eyeDropperButton = null;
}
},
/**
@ -768,7 +792,7 @@ InspectorPanel.prototype = {
let sidebarDestroyer = this.sidebar.destroy();
this.sidebar = null;
this.addNodeButton.removeEventListener("click", this.addNode);
this.teardownToolbar();
this.breadcrumbs.destroy();
this.selection.off("new-node-front", this.onNewSelection);
this.selection.off("before-new-node", this.onBeforeNewSelection);
@ -1251,6 +1275,52 @@ InspectorPanel.prototype = {
}, sidePaneContainer);
},
onEyeDropperButtonClicked: function () {
this.eyeDropperButton.hasAttribute("checked")
? this.hideEyeDropper()
: this.showEyeDropper();
},
startEyeDropperListeners: function () {
this.inspector.once("color-pick-canceled", this.onEyeDropperDone);
this.inspector.once("color-picked", this.onEyeDropperDone);
this.walker.once("new-root", this.onEyeDropperDone);
},
stopEyeDropperListeners: function () {
this.inspector.off("color-pick-canceled", this.onEyeDropperDone);
this.inspector.off("color-picked", this.onEyeDropperDone);
this.walker.off("new-root", this.onEyeDropperDone);
},
onEyeDropperDone: function () {
this.eyeDropperButton.removeAttribute("checked");
this.stopEyeDropperListeners();
},
/**
* Show the eyedropper on the page.
* @return {Promise} resolves when the eyedropper is visible.
*/
showEyeDropper: function () {
this.telemetry.toolOpened("toolbareyedropper");
this.eyeDropperButton.setAttribute("checked", "true");
this.startEyeDropperListeners();
return this.inspector.pickColorFromPage({copyOnSelect: true})
.catch(e => console.error(e));
},
/**
* Hide the eyedropper.
* @return {Promise} resolves when the eyedropper is hidden.
*/
hideEyeDropper: function () {
this.eyeDropperButton.removeAttribute("checked");
this.stopEyeDropperListeners();
return this.inspector.cancelPickColorFromPage()
.catch(e => console.error(e));
},
/**
* Create a new node as the last child of the current selection, expand the
* parent and select the new node.

View File

@ -45,8 +45,11 @@
timeout="50"
class="devtools-searchinput"
placeholder="&inspectorSearchHTML.label3;"/>
<html:button id="inspector-eyedropper-toggle"
title="&inspectorEyeDropper.label;"
class="devtools-button command-button-invertable" />
<div xmlns="http://www.w3.org/1999/xhtml"
id="inspector-sidebar-toggle-box" />
id="inspector-sidebar-toggle-box" />
</html:div>
<vbox flex="1" id="markup-box">
</vbox>
@ -88,83 +91,87 @@
</html:div>
<html:div id="ruleview-container" class="ruleview">
<html:div id="ruleview-container-focusable" tabindex="-1">
</html:div>
</html:div>
</html:div>
<html:div id="sidebar-panel-computedview" class="devtools-monospace theme-sidebar inspector-tabpanel">
<html:div class="devtools-toolbar">
<html:div class="devtools-searchbox">
<html:input id="computedview-searchbox"
class="devtools-filterinput devtools-rule-searchbox"
type="search"
placeholder="&filterStylesPlaceholder;"/>
<html:button id="computedview-searchinput-clear" class="devtools-searchinput-clear"></html:button>
</html:div>
<html:label id="browser-style-checkbox-label" for="browser-style-checkbox">
<html:input id="browser-style-checkbox"
type="checkbox"
class="includebrowserstyles"
label="&browserStylesLabel;"/>&browserStylesLabel;</html:label>
</html:div>
<html:div id="computedview-container">
<html:div id="layout-wrapper" class="theme-separator" tabindex="0">
<html:div id="layout-header">
<html:div id="layout-expander" class="expander theme-twisty expandable" open=""></html:div>
<html:span>&layoutViewTitle;</html:span>
<html:button class="devtools-button" id="layout-geometry-editor" title="&geometry.button.tooltip;"></html:button>
</html:div>
<html:div id="computedview-container-focusable" tabindex="-1">
<html:div id="layout-wrapper" tabindex="0">
<html:div id="layout-header">
<html:div id="layout-expander" class="expander theme-twisty expandable" open=""></html:div>
<html:span>&layoutViewTitle;</html:span>
<html:button class="devtools-button" id="layout-geometry-editor" title="&geometry.button.tooltip;"></html:button>
</html:div>
<html:div id="layout-container">
<html:div id="layout-main">
<html:span class="layout-legend" data-box="margin" title="&margin.tooltip;">&margin.tooltip;</html:span>
<html:div id="layout-margins" data-box="margin" title="&margin.tooltip;">
<html:span class="layout-legend" data-box="border" title="&border.tooltip;">&border.tooltip;</html:span>
<html:div id="layout-borders" data-box="border" title="&border.tooltip;">
<html:span class="layout-legend" data-box="padding" title="&padding.tooltip;">&padding.tooltip;</html:span>
<html:div id="layout-padding" data-box="padding" title="&padding.tooltip;">
<html:div id="layout-content" data-box="content" title="&content.tooltip;">
<html:div id="layout-container">
<html:div id="layout-main">
<html:span class="layout-legend" data-box="margin" title="&margin.tooltip;">&margin.tooltip;</html:span>
<html:div id="layout-margins" data-box="margin" title="&margin.tooltip;">
<html:span class="layout-legend" data-box="border" title="&border.tooltip;">&border.tooltip;</html:span>
<html:div id="layout-borders" data-box="border" title="&border.tooltip;">
<html:span class="layout-legend" data-box="padding" title="&padding.tooltip;">&padding.tooltip;</html:span>
<html:div id="layout-padding" data-box="padding" title="&padding.tooltip;">
<html:div id="layout-content" data-box="content" title="&content.tooltip;">
</html:div>
</html:div>
</html:div>
</html:div>
<html:p class="layout-margin layout-top"><html:span data-box="margin" class="layout-editable" title="margin-top"></html:span></html:p>
<html:p class="layout-margin layout-right"><html:span data-box="margin" class="layout-editable" title="margin-right"></html:span></html:p>
<html:p class="layout-margin layout-bottom"><html:span data-box="margin" class="layout-editable" title="margin-bottom"></html:span></html:p>
<html:p class="layout-margin layout-left"><html:span data-box="margin" class="layout-editable" title="margin-left"></html:span></html:p>
<html:p class="layout-border layout-top"><html:span data-box="border" class="layout-editable" title="border-top"></html:span></html:p>
<html:p class="layout-border layout-right"><html:span data-box="border" class="layout-editable" title="border-right"></html:span></html:p>
<html:p class="layout-border layout-bottom"><html:span data-box="border" class="layout-editable" title="border-bottom"></html:span></html:p>
<html:p class="layout-border layout-left"><html:span data-box="border" class="layout-editable" title="border-left"></html:span></html:p>
<html:p class="layout-padding layout-top"><html:span data-box="padding" class="layout-editable" title="padding-top"></html:span></html:p>
<html:p class="layout-padding layout-right"><html:span data-box="padding" class="layout-editable" title="padding-right"></html:span></html:p>
<html:p class="layout-padding layout-bottom"><html:span data-box="padding" class="layout-editable" title="padding-bottom"></html:span></html:p>
<html:p class="layout-padding layout-left"><html:span data-box="padding" class="layout-editable" title="padding-left"></html:span></html:p>
<html:p class="layout-size"><html:span data-box="content" title="&content.tooltip;"></html:span></html:p>
</html:div>
<html:p class="layout-margin layout-top"><html:span data-box="margin" class="layout-editable" title="margin-top"></html:span></html:p>
<html:p class="layout-margin layout-right"><html:span data-box="margin" class="layout-editable" title="margin-right"></html:span></html:p>
<html:p class="layout-margin layout-bottom"><html:span data-box="margin" class="layout-editable" title="margin-bottom"></html:span></html:p>
<html:p class="layout-margin layout-left"><html:span data-box="margin" class="layout-editable" title="margin-left"></html:span></html:p>
<html:div id="layout-info">
<html:span id="layout-element-size"></html:span>
<html:section id="layout-position-group">
<html:span id="layout-element-position"></html:span>
</html:section>
</html:div>
<html:p class="layout-border layout-top"><html:span data-box="border" class="layout-editable" title="border-top"></html:span></html:p>
<html:p class="layout-border layout-right"><html:span data-box="border" class="layout-editable" title="border-right"></html:span></html:p>
<html:p class="layout-border layout-bottom"><html:span data-box="border" class="layout-editable" title="border-bottom"></html:span></html:p>
<html:p class="layout-border layout-left"><html:span data-box="border" class="layout-editable" title="border-left"></html:span></html:p>
<html:p class="layout-padding layout-top"><html:span data-box="padding" class="layout-editable" title="padding-top"></html:span></html:p>
<html:p class="layout-padding layout-right"><html:span data-box="padding" class="layout-editable" title="padding-right"></html:span></html:p>
<html:p class="layout-padding layout-bottom"><html:span data-box="padding" class="layout-editable" title="padding-bottom"></html:span></html:p>
<html:p class="layout-padding layout-left"><html:span data-box="padding" class="layout-editable" title="padding-left"></html:span></html:p>
<html:p class="layout-size"><html:span data-box="content" title="&content.tooltip;"></html:span></html:p>
</html:div>
<html:div id="layout-info">
<html:span id="layout-element-size"></html:span>
<html:section id="layout-position-group">
<html:span id="layout-element-position"></html:span>
</html:section>
</html:div>
<html:div style="display: none">
<html:p id="layout-dummy"></html:p>
<html:div style="display: none">
<html:p id="layout-dummy"></html:p>
</html:div>
</html:div>
</html:div>
</html:div>
<html:div id="propertyContainer" class="theme-separator" tabindex="0">
</html:div>
<html:div id="computedview-toolbar" class="devtools-toolbar">
<html:div class="devtools-searchbox">
<html:input id="computedview-searchbox"
class="devtools-filterinput devtools-rule-searchbox"
type="search"
placeholder="&filterStylesPlaceholder;"/>
<html:button id="computedview-searchinput-clear" class="devtools-searchinput-clear"></html:button>
</html:div>
<html:label id="browser-style-checkbox-label" for="browser-style-checkbox">
<html:input id="browser-style-checkbox"
type="checkbox"
class="includebrowserstyles"
label="&browserStylesLabel;"/>&browserStylesLabel;</html:label>
</html:div>
<html:div id="computedview-no-results" hidden="">
&noPropertiesFound;
<html:div id="propertyContainer" class="theme-separator" tabindex="0">
</html:div>
<html:div id="computedview-no-results" hidden="">
&noPropertiesFound;
</html:div>
</html:div>
</html:div>
</html:div>

View File

@ -174,7 +174,7 @@ function CssRuleView(inspector, document, store, pageStyle) {
this._onTogglePseudoClass = this._onTogglePseudoClass.bind(this);
let doc = this.styleDocument;
this.element = doc.getElementById("ruleview-container");
this.element = doc.getElementById("ruleview-container-focusable");
this.addRuleButton = doc.getElementById("ruleview-add-rule-button");
this.searchField = doc.getElementById("ruleview-searchbox");
this.searchClearButton = doc.getElementById("ruleview-searchinput-clear");

View File

@ -76,7 +76,7 @@ add_task(function* () {
});
function* clickOnRuleviewScrollbar(view) {
let container = view.element;
let container = view.element.parentNode;
let onScroll = once(container, "scroll");
let rect = container.getBoundingClientRect();
// click 5 pixels before the bottom-right corner should hit the scrollbar

View File

@ -1,19 +1,10 @@
/* vim: set ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// So we can test collecting telemetry on the eyedropper
var oldCanRecord = Services.telemetry.canRecordExtended;
Services.telemetry.canRecordExtended = true;
registerCleanupFunction(function () {
Services.telemetry.canRecordExtended = oldCanRecord;
});
const EXPECTED_TELEMETRY = {
"DEVTOOLS_PICKER_EYEDROPPER_OPENED_COUNT": 2,
"DEVTOOLS_PICKER_EYEDROPPER_OPENED_PER_USER_FLAG": 1
};
// Test opening the eyedropper from the color picker. Pressing escape to close it, and
// clicking the page to select a color.
const TEST_URI = `
<style type="text/css">
@ -43,61 +34,61 @@ const ORIGINAL_COLOR = "rgb(255, 0, 153)";
// #ff5
const EXPECTED_COLOR = "rgb(255, 255, 85)";
// Test opening the eyedropper from the color picker. Pressing escape
// to close it, and clicking the page to select a color.
add_task(function* () {
// clear telemetry so we can get accurate counts
clearTelemetry();
info("Add the test tab, open the rule-view and select the test node");
yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
let {inspector, view} = yield openRuleView();
let {testActor, inspector, view} = yield openRuleView();
yield selectNode("#div2", inspector);
info("Get the background-color property from the rule-view");
let property = getRuleViewProperty(view, "#div2", "background-color");
let swatch = property.valueSpan.querySelector(".ruleview-colorswatch");
ok(swatch, "Color swatch is displayed for the bg-color property");
let dropper = yield openEyedropper(view, swatch);
info("Open the eyedropper from the colorpicker tooltip");
yield openEyedropper(view, swatch);
let tooltip = view.tooltips.colorPicker.tooltip;
ok(!tooltip.isVisible(),
"color picker tooltip is closed after opening eyedropper");
ok(!tooltip.isVisible(), "color picker tooltip is closed after opening eyedropper");
yield testESC(swatch, dropper);
info("Test that pressing escape dismisses the eyedropper");
yield testESC(swatch, inspector, testActor);
dropper = yield openEyedropper(view, swatch);
info("Open the eyedropper again");
yield openEyedropper(view, swatch);
ok(dropper, "dropper opened");
yield testSelect(view, swatch, dropper);
checkTelemetry();
info("Test that a color can be selected with the eyedropper");
yield testSelect(view, swatch, inspector, testActor);
});
function testESC(swatch, dropper) {
let deferred = defer();
dropper.once("destroy", () => {
let color = swatch.style.backgroundColor;
is(color, ORIGINAL_COLOR, "swatch didn't change after pressing ESC");
deferred.resolve();
function* testESC(swatch, inspector, testActor) {
info("Press escape");
let onCanceled = new Promise(resolve => {
inspector.inspector.once("color-pick-canceled", resolve);
});
yield testActor.synthesizeKey({key: "VK_ESCAPE", options: {}});
yield onCanceled;
inspectPage(dropper, false).then(pressESC);
return deferred.promise;
let color = swatch.style.backgroundColor;
is(color, ORIGINAL_COLOR, "swatch didn't change after pressing ESC");
}
function* testSelect(view, swatch, dropper) {
let onDestroyed = dropper.once("destroy");
// the change to the content is done async after rule view change
function* testSelect(view, swatch, inspector, testActor) {
info("Click at x:10px y:10px");
let onPicked = new Promise(resolve => {
inspector.inspector.once("color-picked", resolve);
});
// The change to the content is done async after rule view change
let onRuleViewChanged = view.once("ruleview-changed");
inspectPage(dropper);
yield testActor.synthesizeMouse({selector: "html", x: 10, y: 10,
options: {type: "mousemove"}});
yield testActor.synthesizeMouse({selector: "html", x: 10, y: 10,
options: {type: "mousedown"}});
yield testActor.synthesizeMouse({selector: "html", x: 10, y: 10,
options: {type: "mouseup"}});
yield onDestroyed;
yield onPicked;
yield onRuleViewChanged;
let color = swatch.style.backgroundColor;
@ -108,81 +99,18 @@ function* testSelect(view, swatch, dropper) {
"div's color set to body color after dropper");
}
function clearTelemetry() {
for (let histogramId in EXPECTED_TELEMETRY) {
let histogram = Services.telemetry.getHistogramById(histogramId);
histogram.clear();
}
}
function checkTelemetry() {
for (let histogramId in EXPECTED_TELEMETRY) {
let expected = EXPECTED_TELEMETRY[histogramId];
let histogram = Services.telemetry.getHistogramById(histogramId);
let snapshot = histogram.snapshot();
is(snapshot.sum, expected,
"eyedropper telemetry value correct for " + histogramId);
}
}
/* Helpers */
function openEyedropper(view, swatch) {
let deferred = defer();
function* openEyedropper(view, swatch) {
let tooltip = view.tooltips.colorPicker.tooltip;
tooltip.once("shown", () => {
let dropperButton = tooltip.doc.querySelector("#eyedropper-button");
tooltip.once("eyedropper-opened", (event, dropper) => {
deferred.resolve(dropper);
});
dropperButton.click();
});
info("Click on the swatch");
let onShown = tooltip.once("shown");
swatch.click();
return deferred.promise;
}
function inspectPage(dropper, click = true) {
let target = document.documentElement;
let win = window;
// get location of the content, offset from browser window
let box = gBrowser.selectedBrowser.getBoundingClientRect();
let x = box.left + 1;
let y = box.top + 1;
return dropperStarted(dropper).then(() => {
EventUtils.synthesizeMouse(target, x, y, { type: "mousemove" }, win);
return dropperLoaded(dropper).then(() => {
EventUtils.synthesizeMouse(target, x + 10, y + 10,
{ type: "mousemove" }, win);
if (click) {
EventUtils.synthesizeMouse(target, x + 10, y + 10, {}, win);
}
});
});
}
function dropperStarted(dropper) {
if (dropper.isStarted) {
return promise.resolve();
}
return dropper.once("started");
}
function dropperLoaded(dropper) {
if (dropper.loaded) {
return promise.resolve();
}
return dropper.once("load");
}
function pressESC() {
EventUtils.synthesizeKey("VK_ESCAPE", { });
yield onShown;
let dropperButton = tooltip.doc.querySelector("#eyedropper-button");
info("Click on the eyedropper icon");
let onOpened = tooltip.once("eyedropper-opened");
dropperButton.click();
yield onOpened;
}

View File

@ -293,7 +293,7 @@ TooltipsOverlay.prototype = {
if (this.isRuleView) {
// Color picker tooltip
this.colorPicker = new SwatchColorPickerTooltip(toolbox);
this.colorPicker = new SwatchColorPickerTooltip(toolbox, this.view.inspector);
// Cubic bezier tooltip
this.cubicBezier = new SwatchCubicBezierTooltip(toolbox);
// Filter editor tooltip

View File

@ -63,6 +63,10 @@ skip-if = os == "mac" # Full keyboard navigation on OSX only works if Full Keybo
[browser_inspector_highlighter-csstransform_01.js]
[browser_inspector_highlighter-csstransform_02.js]
[browser_inspector_highlighter-embed.js]
[browser_inspector_highlighter-eyedropper-clipboard.js]
subsuite = clipboard
[browser_inspector_highlighter-eyedropper-events.js]
[browser_inspector_highlighter-eyedropper-show-hide.js]
[browser_inspector_highlighter-geometry_01.js]
[browser_inspector_highlighter-geometry_02.js]
[browser_inspector_highlighter-geometry_03.js]
@ -134,6 +138,7 @@ skip-if = os == "mac" # Full keyboard navigation on OSX only works if Full Keybo
[browser_inspector_search_keyboard_trap.js]
[browser_inspector_search-reserved.js]
[browser_inspector_search-selection.js]
[browser_inspector_search-sidebar.js]
[browser_inspector_select-docshell.js]
[browser_inspector_select-last-selected.js]
[browser_inspector_search-navigation.js]

View File

@ -0,0 +1,65 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
// Test that the eyedropper can copy colors to the clipboard
const HIGHLIGHTER_TYPE = "EyeDropper";
const ID = "eye-dropper-";
const TEST_URI = "data:text/html;charset=utf-8,<style>html{background:red}</style>";
add_task(function* () {
let helper = yield openInspectorForURL(TEST_URI)
.then(getHighlighterHelperFor(HIGHLIGHTER_TYPE));
helper.prefix = ID;
let {show, synthesizeKey, finalize} = helper;
info("Show the eyedropper with the copyOnSelect option");
yield show("html", {copyOnSelect: true});
info("Make sure to wait until the eyedropper is done taking a screenshot of the page");
yield waitForElementAttributeSet("root", "drawn", helper);
yield waitForClipboard(() => {
info("Activate the eyedropper so the background color is copied");
let generateKey = synthesizeKey({key: "VK_RETURN", options: {}});
generateKey.next();
}, "#FF0000");
ok(true, "The clipboard contains the right value");
yield waitForElementAttributeRemoved("root", "drawn", helper);
yield waitForElementAttributeSet("root", "hidden", helper);
ok(true, "The eyedropper is now hidden");
finalize();
});
function* waitForElementAttributeSet(id, name, {getElementAttribute}) {
yield poll(function* () {
let value = yield getElementAttribute(id, name);
return !!value;
}, `Waiting for element ${id} to have attribute ${name} set`);
}
function* waitForElementAttributeRemoved(id, name, {getElementAttribute}) {
yield poll(function* () {
let value = yield getElementAttribute(id, name);
return !value;
}, `Waiting for element ${id} to have attribute ${name} removed`);
}
function* poll(check, desc) {
info(desc);
for (let i = 0; i < 10; i++) {
if (yield check()) {
return;
}
yield new Promise(resolve => setTimeout(resolve, 200));
}
throw new Error(`Timeout while: ${desc}`);
}

View File

@ -0,0 +1,71 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
// Test the eyedropper mouse and keyboard handling.
const HIGHLIGHTER_TYPE = "EyeDropper";
const ID = "eye-dropper-";
const MOVE_EVENTS_DATA = [
{type: "mouse", x: 200, y: 100, expected: {x: 200, y: 100}},
{type: "mouse", x: 100, y: 200, expected: {x: 100, y: 200}},
{type: "keyboard", key: "VK_LEFT", expected: {x: 99, y: 200}},
{type: "keyboard", key: "VK_LEFT", shift: true, expected: {x: 89, y: 200}},
{type: "keyboard", key: "VK_RIGHT", expected: {x: 90, y: 200}},
{type: "keyboard", key: "VK_RIGHT", shift: true, expected: {x: 100, y: 200}},
{type: "keyboard", key: "VK_DOWN", expected: {x: 100, y: 201}},
{type: "keyboard", key: "VK_DOWN", shift: true, expected: {x: 100, y: 211}},
{type: "keyboard", key: "VK_UP", expected: {x: 100, y: 210}},
{type: "keyboard", key: "VK_UP", shift: true, expected: {x: 100, y: 200}},
];
add_task(function* () {
let helper = yield openInspectorForURL("data:text/html;charset=utf-8,eye-dropper test")
.then(getHighlighterHelperFor(HIGHLIGHTER_TYPE));
helper.prefix = ID;
yield helper.show("html");
yield respondsToMoveEvents(helper);
yield respondsToReturnAndEscape(helper);
helper.finalize();
});
function* respondsToMoveEvents(helper) {
info("Checking that the eyedropper responds to events from the mouse and keyboard");
let {mouse, synthesizeKey} = helper;
for (let {type, x, y, key, shift, expected} of MOVE_EVENTS_DATA) {
info(`Simulating a ${type} event to move to ${expected.x} ${expected.y}`);
if (type === "mouse") {
yield mouse.move(x, y);
} else if (type === "keyboard") {
let options = shift ? {shiftKey: true} : {};
yield synthesizeKey({key, options});
}
yield checkPosition(expected, helper);
}
}
function* checkPosition({x, y}, {getElementAttribute}) {
let style = yield getElementAttribute("root", "style");
is(style, `top:${y}px;left:${x}px;`,
`The eyedropper is at the expected ${x} ${y} position`);
}
function* respondsToReturnAndEscape({synthesizeKey, isElementHidden, show}) {
info("Simulating return to select the color and hide the eyedropper");
yield synthesizeKey({key: "VK_RETURN", options: {}});
let hidden = yield isElementHidden("root");
ok(hidden, "The eyedropper has been hidden");
info("Showing the eyedropper again and simulating escape to hide it");
yield show("html");
yield synthesizeKey({key: "VK_ESCAPE", options: {}});
hidden = yield isElementHidden("root");
ok(hidden, "The eyedropper has been hidden again");
}

View File

@ -0,0 +1,42 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
// Test the basic structure of the eye-dropper highlighter.
const HIGHLIGHTER_TYPE = "EyeDropper";
const ID = "eye-dropper-";
add_task(function* () {
let helper = yield openInspectorForURL("data:text/html;charset=utf-8,eye-dropper test")
.then(getHighlighterHelperFor(HIGHLIGHTER_TYPE));
helper.prefix = ID;
yield isInitiallyHidden(helper);
yield canBeShownAndHidden(helper);
helper.finalize();
});
function* isInitiallyHidden({isElementHidden}) {
info("Checking that the eyedropper is hidden by default");
let hidden = yield isElementHidden("root");
ok(hidden, "The eyedropper is hidden by default");
}
function* canBeShownAndHidden({show, hide, isElementHidden, getElementAttribute}) {
info("Asking to show and hide the highlighter actually works");
yield show("html");
let hidden = yield isElementHidden("root");
ok(!hidden, "The eyedropper is now shown");
let style = yield getElementAttribute("root", "style");
is(style, "top:100px;left:100px;", "The eyedropper is correctly positioned");
yield hide();
hidden = yield isElementHidden("root");
ok(hidden, "The eyedropper is now hidden again");
}

View File

@ -0,0 +1,74 @@
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Test that depending where the user last clicked in the inspector, the right search
// field is focused when ctrl+F is pressed.
add_task(function* () {
let {inspector} = yield openInspectorForURL("data:text/html;charset=utf-8,Search!");
info("Check that by default, the inspector search field gets focused");
pressCtrlF();
isInInspectorSearchBox(inspector);
info("Click somewhere in the rule-view");
clickInRuleView(inspector);
info("Check that the rule-view search field gets focused");
pressCtrlF();
isInRuleViewSearchBox(inspector);
info("Click in the inspector again");
yield clickContainer("head", inspector);
info("Check that now we're back in the inspector, its search field gets focused");
pressCtrlF();
isInInspectorSearchBox(inspector);
info("Switch to the computed view, and click somewhere inside it");
selectComputedView(inspector);
clickInComputedView(inspector);
info("Check that the computed-view search field gets focused");
pressCtrlF();
isInComputedViewSearchBox(inspector);
info("Click in the inspector yet again");
yield clickContainer("body", inspector);
info("We're back in the inspector again, check the inspector search field focuses");
pressCtrlF();
isInInspectorSearchBox(inspector);
});
function pressCtrlF() {
EventUtils.synthesizeKey("f", {accelKey: true});
}
function clickInRuleView(inspector) {
let el = inspector.panelDoc.querySelector("#sidebar-panel-ruleview");
EventUtils.synthesizeMouseAtCenter(el, {}, inspector.panelDoc.defaultView);
}
function clickInComputedView(inspector) {
let el = inspector.panelDoc.querySelector("#sidebar-panel-computedview");
EventUtils.synthesizeMouseAtCenter(el, {}, inspector.panelDoc.defaultView);
}
function isInInspectorSearchBox(inspector) {
// Focus ends up in an anonymous child of the XUL textbox.
ok(inspector.panelDoc.activeElement.closest("#inspector-searchbox"),
"The inspector search field is focused when ctrl+F is pressed");
}
function isInRuleViewSearchBox(inspector) {
is(inspector.panelDoc.activeElement, inspector.ruleview.view.searchField,
"The rule-view search field is focused when ctrl+F is pressed");
}
function isInComputedViewSearchBox(inspector) {
is(inspector.panelDoc.activeElement, inspector.computedview.computedView.searchField,
"The computed-view search field is focused when ctrl+F is pressed");
}

View File

@ -422,6 +422,7 @@ const getHighlighterHelperFor = (type) => Task.async(
set prefix(value) {
prefix = value;
},
get highlightedNode() {
if (!highlightedNode) {
return null;
@ -435,9 +436,9 @@ const getHighlighterHelperFor = (type) => Task.async(
};
},
show: function* (selector = ":root") {
show: function* (selector = ":root", options) {
highlightedNode = yield getNodeFront(selector, inspector);
return yield highlighter.show(highlightedNode);
return yield highlighter.show(highlightedNode, options);
},
hide: function* () {
@ -464,6 +465,10 @@ const getHighlighterHelperFor = (type) => Task.async(
yield testActor.synthesizeMouse(options);
},
synthesizeKey: function* (options) {
yield testActor.synthesizeKey(options);
},
// This object will synthesize any "mouse" prefixed event to the
// `testActor`, using the name of method called as suffix for the
// event's name.

View File

@ -131,9 +131,6 @@ devtools.jar:
content/shared/widgets/mdn-docs.css (shared/widgets/mdn-docs.css)
content/shared/widgets/filter-widget.css (shared/widgets/filter-widget.css)
content/shared/widgets/spectrum.css (shared/widgets/spectrum.css)
content/eyedropper/eyedropper.xul (eyedropper/eyedropper.xul)
content/eyedropper/crosshairs.css (eyedropper/crosshairs.css)
content/eyedropper/nocursor.css (eyedropper/nocursor.css)
content/aboutdebugging/aboutdebugging.xhtml (aboutdebugging/aboutdebugging.xhtml)
content/aboutdebugging/aboutdebugging.css (aboutdebugging/aboutdebugging.css)
content/aboutdebugging/initializer.js (aboutdebugging/initializer.js)

View File

@ -15,3 +15,8 @@
DOM (as children of the currently selected element). -->
<!ENTITY inspectorAddNode.label "Create New Node">
<!ENTITY inspectorAddNode.accesskey "C">
<!-- LOCALIZATION NOTE (inspectorEyeDropper.label): A string displayed as the tooltip of
a button in the inspector which toggles the Eyedropper tool -->
<!ENTITY inspectorEyeDropper.label "Grab a color from the page">

View File

@ -37,7 +37,8 @@ const Services = require("Services");
const isMac = Services.appinfo.OS === "Darwin";
loader.lazyRequireGetter(this, "gDevToolsBrowser", "devtools/client/framework/devtools-browser", true);
loader.lazyRequireGetter(this, "Eyedropper", "devtools/client/eyedropper/eyedropper", true);
loader.lazyRequireGetter(this, "CommandUtils", "devtools/client/shared/developer-toolbar", true);
loader.lazyRequireGetter(this, "TargetFactory", "devtools/client/framework/target", true);
loader.lazyImporter(this, "BrowserToolboxProcess", "resource://devtools/client/framework/ToolboxProcess.jsm");
loader.lazyImporter(this, "ResponsiveUIManager", "resource://devtools/client/responsivedesign/responsivedesign.jsm");
@ -144,9 +145,13 @@ exports.menuitems = [
l10nKey: "eyedropper",
oncommand(event) {
let window = event.target.ownerDocument.defaultView;
let eyedropper = new Eyedropper(window, { context: "menu",
copyOnSelect: true });
eyedropper.open();
let target = TargetFactory.forTab(window.gBrowser.selectedTab);
CommandUtils.createRequisition(target, {
environment: CommandUtils.createEnvironment({target})
}).then(requisition => {
requisition.updateExec("eyedropper --frommenu");
}, e => console.error(e));
},
checkbox: true
},

View File

@ -13,7 +13,6 @@ DIRS += [
'commandline',
'debugger',
'dom',
'eyedropper',
'framework',
'inspector',
'jsonview',

View File

@ -7,6 +7,14 @@
overflow: hidden;
}
/**
* Collapsed details pane needs to be truly hidden to prevent both accessibility
* tools and keyboard from accessing its contents.
*/
#details-pane.pane-collapsed {
visibility: hidden;
}
#details-pane-toggle[disabled] {
display: none;
}
@ -36,7 +44,6 @@
@media (max-width: 700px) {
#toolbar-spacer,
#details-pane-toggle,
#details-pane.pane-collapsed,
.requests-menu-waterfall,
#requests-menu-network-summary-button > .toolbarbutton-text {
display: none;

View File

@ -18,7 +18,7 @@ const { CanvasGraphUtils } = require("devtools/client/shared/widgets/Graphs");
const promise = require("promise");
const EventEmitter = require("devtools/shared/event-emitter");
const { colorUtils } = require("devtools/client/shared/css-color");
const { colorUtils } = require("devtools/shared/css-color");
const { getColor } = require("devtools/client/shared/theme");
const ProfilerGlobal = require("devtools/client/performance/modules/global");
const { MarkersOverview } = require("devtools/client/performance/modules/widgets/markers-overview");

View File

@ -13,7 +13,7 @@ const { Cc, Ci, Cu, Cr } = require("chrome");
const { Heritage } = require("devtools/client/shared/widgets/view-helpers");
const { AbstractCanvasGraph } = require("devtools/client/shared/widgets/Graphs");
const { colorUtils } = require("devtools/client/shared/css-color");
const { colorUtils } = require("devtools/shared/css-color");
const { getColor } = require("devtools/client/shared/theme");
const ProfilerGlobal = require("devtools/client/performance/modules/global");
const { MarkerBlueprintUtils } = require("devtools/client/performance/modules/marker-blueprint-utils");

View File

@ -30,7 +30,7 @@ pref("devtools.toolbox.sidebar.width", 500);
pref("devtools.toolbox.host", "bottom");
pref("devtools.toolbox.previousHost", "side");
pref("devtools.toolbox.selectedTool", "webconsole");
pref("devtools.toolbox.toolbarSpec", '["splitconsole", "paintflashing toggle","scratchpad","resize toggle","eyedropper","screenshot --fullpage", "rulers", "measure"]');
pref("devtools.toolbox.toolbarSpec", '["splitconsole", "paintflashing toggle","scratchpad","resize toggle","screenshot --fullpage", "rulers", "measure"]');
pref("devtools.toolbox.sideEnabled", true);
pref("devtools.toolbox.zoomValue", "1");
pref("devtools.toolbox.splitconsoleEnabled", false);
@ -43,7 +43,6 @@ pref("devtools.command-button-splitconsole.enabled", true);
pref("devtools.command-button-paintflashing.enabled", false);
pref("devtools.command-button-scratchpad.enabled", false);
pref("devtools.command-button-responsive.enabled", true);
pref("devtools.command-button-eyedropper.enabled", false);
pref("devtools.command-button-screenshot.enabled", false);
pref("devtools.command-button-rulers.enabled", false);
pref("devtools.command-button-measure.enabled", false);

View File

@ -35,8 +35,9 @@ define(function (require, exports, module) {
},
getTitle: function (object, context) {
if (this.props.objectLink) {
return this.props.objectLink({
let objectLink = this.props.objectLink || span;
if (this.props.mode != "tiny") {
return objectLink({
object: object
}, object.class);
}
@ -117,11 +118,12 @@ define(function (require, exports, module) {
}
let objectLink = this.props.objectLink || span;
let title = this.getTitle(object);
return (
ObjectBox({
className: "array"},
this.getTitle(object),
title,
objectLink({
className: "arrayLeftBracket",
role: "presentation",

View File

@ -55,30 +55,24 @@ define(function (require, exports, module) {
return (
type == "boolean" ||
type == "number" ||
type == "string" ||
type == "object"
(type == "string" && value.length != 0)
);
};
// Object members with non-empty values are preferred since it gives the
// user a better overview of the object.
let props = this.getProps(object, max, isInterestingProp);
if (props.length <= max) {
// There are not enough props yet (or at least, not enough props to
// be able to know whether we should print "more…" or not).
// Let's display also empty members and functions.
props = props.concat(this.getProps(object, max, (t, value) => {
return !isInterestingProp(t, value);
}));
let ownProperties = object.preview ? object.preview.ownProperties : [];
let indexes = this.getPropIndexes(ownProperties, max, isInterestingProp);
if (indexes.length < max && indexes.length < object.ownPropertyLength) {
// There are not enough props yet. Then add uninteresting props to display them.
indexes = indexes.concat(
this.getPropIndexes(ownProperties, max - indexes.length, (t, value) => {
return !isInterestingProp(t, value);
})
);
}
// getProps() can return max+1 properties (it can't return more)
// to indicate that there is more props than allowed. Remove the last
// one and append 'more…' postfix in such case.
if (props.length > max) {
props.pop();
let props = this.getProps(ownProperties, indexes);
if (props.length < object.ownPropertyLength) {
// There are some undisplayed props. Then display "more...".
let objectLink = this.props.objectLink || span;
props.push(Caption({
@ -100,46 +94,73 @@ define(function (require, exports, module) {
return props;
},
getProps: function (object, max, filter) {
/**
* Get props ordered by index.
*
* @param {Object} ownProperties Props object.
* @param {Array} indexes Indexes of props.
* @return {Array} Props.
*/
getProps: function (ownProperties, indexes) {
let props = [];
max = max || 3;
if (!object) {
return props;
}
// Make indexes ordered by ascending.
indexes.sort(function (a, b) {
return a - b;
});
indexes.forEach((i) => {
let name = Object.keys(ownProperties)[i];
let value = ownProperties[name].value;
props.push(PropRep(Object.assign({}, this.props, {
key: name,
mode: "tiny",
name: name,
object: value,
equal: ": ",
delim: ", ",
})));
});
return props;
},
/**
* Get the indexes of props in the object.
*
* @param {Object} ownProperties Props object.
* @param {Number} max The maximum length of indexes array.
* @param {Function} filter Filter the props you want.
* @return {Array} Indexes of interesting props in the object.
*/
getPropIndexes: function (ownProperties, max, filter) {
let indexes = [];
try {
let ownProperties = object.preview ? object.preview.ownProperties : [];
let i = 0;
for (let name in ownProperties) {
if (props.length > max) {
return props;
if (indexes.length >= max) {
return indexes;
}
let prop = ownProperties[name];
let value = prop.value || {};
let value = prop.value;
// Type is specified in grip's "class" field and for primitive
// values use typeof.
let type = (value.class || typeof value);
type = type.toLowerCase();
// Show only interesting properties.
if (filter(type, value)) {
props.push(PropRep(Object.assign({}, this.props, {
key: name,
mode: "tiny",
name: name,
object: value,
equal: ": ",
delim: ", ",
})));
indexes.push(i);
}
i++;
}
} catch (err) {
console.error(err);
}
return props;
return indexes;
},
render: function () {

View File

@ -14,7 +14,6 @@ DevToolsModules(
'function.js',
'grip-array.js',
'grip.js',
'named-node-map.js',
'null.js',
'number.js',
'object-box.js',

View File

@ -1,180 +0,0 @@
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
// Make this available to both AMD and CJS environments
define(function (require, exports, module) {
// ReactJS
const React = require("devtools/client/shared/vendor/react");
// Reps
const { createFactories, isGrip } = require("./rep-utils");
const { ObjectBox } = createFactories(require("./object-box"));
const { Caption } = createFactories(require("./caption"));
// Shortcuts
const { span } = React.DOM;
/**
* Used to render a map of values provided as a grip.
*/
let NamedNodeMap = React.createClass({
displayName: "NamedNodeMap",
propTypes: {
object: React.PropTypes.object.isRequired,
mode: React.PropTypes.string,
provider: React.PropTypes.object,
},
getLength: function (object) {
return object.preview.length;
},
getTitle: function (object) {
if (this.props.objectLink && object.class) {
return this.props.objectLink({
object: object
}, object.class);
}
return object.class ? object.class : "";
},
getItems: function (array, max) {
let items = this.propIterator(array, max);
items = items.map(item => PropRep(item));
if (items.length > max + 1) {
items.pop();
let objectLink = this.props.objectLink || span;
items.push(Caption({
key: "more",
object: objectLink({
object: this.props.object
}, "more…")
}));
}
return items;
},
propIterator: function (grip, max) {
max = max || 3;
let props = [];
let provider = this.props.provider;
if (!provider) {
return props;
}
let ownProperties = grip.preview ? grip.preview.ownProperties : [];
for (let name in ownProperties) {
if (props.length > max) {
break;
}
let item = ownProperties[name];
let label = provider.getLabel(item);
let value = provider.getValue(item);
props.push(Object.assign({}, this.props, {
name: label,
object: value,
equal: ": ",
delim: ", ",
}));
}
return props;
},
render: function () {
let grip = this.props.object;
let mode = this.props.mode;
let items;
if (mode == "tiny") {
items = this.getLength(grip);
} else {
let max = (mode == "short") ? 3 : 100;
items = this.getItems(grip, max);
}
let objectLink = this.props.objectLink || span;
return (
ObjectBox({className: "NamedNodeMap"},
this.getTitle(grip),
objectLink({
className: "arrayLeftBracket",
role: "presentation",
object: grip
}, "["),
items,
objectLink({
className: "arrayRightBracket",
role: "presentation",
object: grip
}, "]")
)
);
},
});
/**
* Property for a grip object.
*/
let PropRep = React.createFactory(React.createClass({
displayName: "PropRep",
propTypes: {
equal: React.PropTypes.string,
delim: React.PropTypes.string,
},
render: function () {
const { Rep } = createFactories(require("./rep"));
return (
span({},
span({
className: "nodeName"},
"$prop.name"
),
span({
className: "objectEqual",
role: "presentation"},
this.props.equal
),
Rep(this.props),
span({
className: "objectComma",
role: "presentation"},
this.props.delim
)
)
);
}
}));
// Registration
function supportsObject(grip, type) {
if (!isGrip(grip)) {
return false;
}
return (type == "NamedNodeMap" && grip.preview);
}
// Exports from this module
exports.NamedNodeMap = {
rep: NamedNodeMap,
supportsObject: supportsObject
};
});

View File

@ -27,7 +27,6 @@ define(function (require, exports, module) {
const { Document } = require("./document");
const { Event } = require("./event");
const { Func } = require("./function");
const { NamedNodeMap } = require("./named-node-map");
const { RegExp } = require("./regexp");
const { StyleSheet } = require("./stylesheet");
const { TextNode } = require("./text-node");
@ -46,7 +45,6 @@ define(function (require, exports, module) {
Event,
DateTime,
TextNode,
NamedNodeMap,
Attribute,
Func,
ArrayRep,

View File

@ -32,6 +32,8 @@ window.onload = Task.async(function* () {
yield testMoreThanShortMaxProps();
yield testMoreThanLongMaxProps();
yield testRecursiveArray();
yield testNamedNodeMap();
} catch(e) {
ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e));
} finally {
@ -48,7 +50,7 @@ window.onload = Task.async(function* () {
is(renderedRep.type, GripArray.rep, `Rep correctly selects ${GripArray.rep.displayName}`);
// Test rendering
const defaultOutput = `[]`;
const defaultOutput = `Array[]`;
const modeTests = [
{
@ -76,7 +78,7 @@ window.onload = Task.async(function* () {
// Test array: `[1, "foo", {}]`;
const testName = "testMaxProps";
const defaultOutput = `[1, "foo", Object]`;
const defaultOutput = `Array[1, "foo", Object]`;
const modeTests = [
{
@ -104,7 +106,7 @@ window.onload = Task.async(function* () {
// Test array = `["test string"…] //4 items`
const testName = "testMoreThanShortMaxProps";
const defaultOutput = `[${Array(maxLength.short).fill("\"test string\"").join(", ")}, more…]`;
const defaultOutput = `Array[${Array(maxLength.short).fill("\"test string\"").join(", ")}, more…]`;
const modeTests = [
{
@ -121,7 +123,7 @@ window.onload = Task.async(function* () {
},
{
mode: "long",
expectedOutput: `[${Array(maxLength.short + 1).fill("\"test string\"").join(", ")}]`,
expectedOutput: `Array[${Array(maxLength.short + 1).fill("\"test string\"").join(", ")}]`,
}
];
@ -132,8 +134,8 @@ window.onload = Task.async(function* () {
// Test array = `["test string"…] //301 items`
const testName = "testMoreThanLongMaxProps";
const defaultShortOutput = `[${Array(maxLength.short).fill("\"test string\"").join(", ")}, more…]`;
const defaultLongOutput = `[${Array(maxLength.long).fill("\"test string\"").join(", ")}, more…]`;
const defaultShortOutput = `Array[${Array(maxLength.short).fill("\"test string\"").join(", ")}, more…]`;
const defaultLongOutput = `Array[${Array(maxLength.long).fill("\"test string\"").join(", ")}, more…]`;
const modeTests = [
{
@ -164,7 +166,7 @@ window.onload = Task.async(function* () {
// Test array = `let a = []; a = [a]`
const testName = "testRecursiveArray";
const defaultOutput = `[[1]]`;
const defaultOutput = `Array[[1]]`;
const modeTests = [
{
@ -188,6 +190,33 @@ window.onload = Task.async(function* () {
testRepRenderModes(modeTests, testName, componentUnderTest, getGripStub(testName));
}
function testNamedNodeMap() {
const testName = "testNamedNodeMap";
const defaultOutput = `NamedNodeMap[class="myclass", cellpadding="7", border="3"]`;
const modeTests = [
{
mode: undefined,
expectedOutput: defaultOutput,
},
{
mode: "tiny",
expectedOutput: `[3]`,
},
{
mode: "short",
expectedOutput: defaultOutput,
},
{
mode: "long",
expectedOutput: defaultOutput,
}
];
testRepRenderModes(modeTests, testName, componentUnderTest, getGripStub(testName));
}
function getGripStub(functionName) {
switch (functionName) {
case "testBasic":
@ -311,6 +340,68 @@ window.onload = Task.async(function* () {
]
}
};
case "testNamedNodeMap":
return {
"type": "object",
"class": "NamedNodeMap",
"actor": "server1.conn3.obj42",
"extensible": true,
"frozen": false,
"sealed": false,
"ownPropertyLength": 6,
"preview": {
"kind": "ArrayLike",
"length": 3,
"items": [
{
"type": "object",
"class": "Attr",
"actor": "server1.conn3.obj43",
"extensible": true,
"frozen": false,
"sealed": false,
"ownPropertyLength": 0,
"preview": {
"kind": "DOMNode",
"nodeType": 2,
"nodeName": "class",
"value": "myclass"
}
},
{
"type": "object",
"class": "Attr",
"actor": "server1.conn3.obj44",
"extensible": true,
"frozen": false,
"sealed": false,
"ownPropertyLength": 0,
"preview": {
"kind": "DOMNode",
"nodeType": 2,
"nodeName": "cellpadding",
"value": "7"
}
},
{
"type": "object",
"class": "Attr",
"actor": "server1.conn3.obj44",
"extensible": true,
"frozen": false,
"sealed": false,
"ownPropertyLength": 0,
"preview": {
"kind": "DOMNode",
"nodeType": 2,
"nodeName": "border",
"value": "3"
}
}
]
}
};
}
}
});

View File

@ -20,8 +20,6 @@ DevToolsModules(
'autocomplete-popup.js',
'browser-loader.js',
'css-angle.js',
'css-color-db.js',
'css-color.js',
'css-reload.js',
'Curl.jsm',
'demangle.js',

View File

@ -6,7 +6,7 @@
const {Cc, Ci} = require("chrome");
const {angleUtils} = require("devtools/client/shared/css-angle");
const {colorUtils} = require("devtools/client/shared/css-color");
const {colorUtils} = require("devtools/shared/css-color");
const {getCSSLexer} = require("devtools/shared/css-lexer");
const EventEmitter = require("devtools/shared/event-emitter");
const {

View File

@ -187,6 +187,10 @@ Telemetry.prototype = {
histogram: "DEVTOOLS_PICKER_EYEDROPPER_OPENED_COUNT",
userHistogram: "DEVTOOLS_PICKER_EYEDROPPER_OPENED_PER_USER_FLAG",
},
toolbareyedropper: {
histogram: "DEVTOOLS_TOOLBAR_EYEDROPPER_OPENED_COUNT",
userHistogram: "DEVTOOLS_TOOLBAR_EYEDROPPER_OPENED_PER_USER_FLAG",
},
developertoolbar: {
histogram: "DEVTOOLS_DEVELOPERTOOLBAR_OPENED_COUNT",
userHistogram: "DEVTOOLS_DEVELOPERTOOLBAR_OPENED_PER_USER_FLAG",

View File

@ -2,7 +2,7 @@
http://creativecommons.org/publicdomain/zero/1.0/ */
const TEST_URI = "data:text/html;charset=utf-8,browser_css_color.js";
var {colorUtils} = require("devtools/client/shared/css-color");
var {colorUtils} = require("devtools/shared/css-color");
var origColorUnit;
add_task(function* () {

View File

@ -202,6 +202,7 @@ var testMouseInteraction = Task.async(function* () {
"Which is the unique column");
// popup should be open now
// clicking on second column label
let onPopupHidden = once(table.menupopup, "popuphidden");
event = table.once(TableWidget.EVENTS.HEADER_CONTEXT_MENU);
node = table.menupopup.querySelector("[data-id='col2']");
info("selecting to hide the second column");
@ -209,6 +210,7 @@ var testMouseInteraction = Task.async(function* () {
"Column is not hidden before hiding it");
click(node);
id = yield event;
yield onPopupHidden;
is(id, "col2", "Correct column was triggered to be hidden");
is(table.tbody.children[2].getAttribute("hidden"), "true",
"Column is hidden after hiding it");
@ -225,6 +227,7 @@ var testMouseInteraction = Task.async(function* () {
"Only 1 menuitem is disabled");
// popup should be open now
// clicking on second column label
onPopupHidden = once(table.menupopup, "popuphidden");
event = table.once(TableWidget.EVENTS.HEADER_CONTEXT_MENU);
node = table.menupopup.querySelector("[data-id='col3']");
info("selecting to hide the second column");
@ -232,6 +235,7 @@ var testMouseInteraction = Task.async(function* () {
"Column is not hidden before hiding it");
click(node);
id = yield event;
yield onPopupHidden;
is(id, "col3", "Correct column was triggered to be hidden");
is(table.tbody.children[4].getAttribute("hidden"), "true",
"Column is hidden after hiding it");
@ -256,6 +260,7 @@ var testMouseInteraction = Task.async(function* () {
// showing back 2nd column
// popup should be open now
// clicking on second column label
onPopupHidden = once(table.menupopup, "popuphidden");
event = table.once(TableWidget.EVENTS.HEADER_CONTEXT_MENU);
node = table.menupopup.querySelector("[data-id='col2']");
info("selecting to hide the second column");
@ -263,6 +268,7 @@ var testMouseInteraction = Task.async(function* () {
"Column is hidden before unhiding it");
click(node);
id = yield event;
yield onPopupHidden;
is(id, "col2", "Correct column was triggered to be hidden");
ok(!table.tbody.children[2].hasAttribute("hidden"),
"Column is not hidden after unhiding it");
@ -277,6 +283,7 @@ var testMouseInteraction = Task.async(function* () {
// popup should be open now
// clicking on second column label
onPopupHidden = once(table.menupopup, "popuphidden");
event = table.once(TableWidget.EVENTS.HEADER_CONTEXT_MENU);
node = table.menupopup.querySelector("[data-id='col3']");
info("selecting to hide the second column");
@ -284,6 +291,7 @@ var testMouseInteraction = Task.async(function* () {
"Column is hidden before unhiding it");
click(node);
id = yield event;
yield onPopupHidden;
is(id, "col3", "Correct column was triggered to be hidden");
ok(!table.tbody.children[4].hasAttribute("hidden"),
"Column is not hidden after unhiding it");

View File

@ -1,61 +1,55 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
const TEST_URI = "data:text/html;charset=utf-8," +
"<p>browser_telemetry_button_eyedropper.js</p><div>test</div>";
var {EyedropperManager} = require("devtools/client/eyedropper/eyedropper");
add_task(function* () {
yield addTab(TEST_URI);
let Telemetry = loadTelemetryAndRecordLogs();
let target = TargetFactory.forTab(gBrowser.selectedTab);
let toolbox = yield gDevTools.showToolbox(target, "inspector");
info("inspector opened");
info("testing the eyedropper button");
yield testButton(toolbox, Telemetry);
stopRecordingTelemetryLogs(Telemetry);
yield gDevTools.closeToolbox(target);
gBrowser.removeCurrentTab();
});
function* testButton(toolbox, Telemetry) {
let button = toolbox.doc.querySelector("#command-button-eyedropper");
ok(button, "Captain, we have the eyedropper button");
let clicked = toolbox._requisition.commandOutputManager.onOutput.once();
info("clicking the button to open the eyedropper");
button.click();
yield clicked;
checkResults("_EYEDROPPER_", Telemetry);
}
function checkResults(histIdFocus, Telemetry) {
let result = Telemetry.prototype.telemetryInfo;
for (let [histId, value] of Iterator(result)) {
if (histId.startsWith("DEVTOOLS_INSPECTOR_") ||
!histId.includes(histIdFocus)) {
// Inspector stats are tested in
// browser_telemetry_toolboxtabs_{toolname}.js so we skip them here
// because we only open the inspector once for this test.
continue;
}
if (histId.endsWith("OPENED_PER_USER_FLAG")) {
ok(value.length === 1 && value[0] === true,
"Per user value " + histId + " has a single value of true");
} else if (histId.endsWith("OPENED_COUNT")) {
is(value.length, 1, histId + " has one entry");
let okay = value.every(element => element === true);
ok(okay, "All " + histId + " entries are === true");
}
}
}
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
const TEST_URI = "data:text/html;charset=utf-8," +
"<p>browser_telemetry_button_eyedropper.js</p><div>test</div>";
add_task(function* () {
yield addTab(TEST_URI);
let Telemetry = loadTelemetryAndRecordLogs();
let target = TargetFactory.forTab(gBrowser.selectedTab);
let toolbox = yield gDevTools.showToolbox(target, "inspector");
info("inspector opened");
info("testing the eyedropper button");
yield testButton(toolbox, Telemetry);
stopRecordingTelemetryLogs(Telemetry);
yield gDevTools.closeToolbox(target);
gBrowser.removeCurrentTab();
});
function* testButton(toolbox, Telemetry) {
info("Calling the eyedropper button's callback");
// We call the button callback directly because we don't need to test the UI here, we're
// only concerned about testing the telemetry probe.
yield toolbox.getPanel("inspector").showEyeDropper();
checkResults("_EYEDROPPER_", Telemetry);
}
function checkResults(histIdFocus, Telemetry) {
let result = Telemetry.prototype.telemetryInfo;
for (let [histId, value] of Iterator(result)) {
if (histId.startsWith("DEVTOOLS_INSPECTOR_") ||
!histId.includes(histIdFocus)) {
// Inspector stats are tested in
// browser_telemetry_toolboxtabs_{toolname}.js so we skip them here
// because we only open the inspector once for this test.
continue;
}
if (histId.endsWith("OPENED_PER_USER_FLAG")) {
ok(value.length === 1 && value[0] === true,
"Per user value " + histId + " has a single value of true");
} else if (histId.endsWith("OPENED_COUNT")) {
is(value.length, 1, histId + " has one entry");
let okay = value.every(element => element === true);
ok(okay, "All " + histId + " entries are === true");
}
}
}

View File

@ -10,7 +10,7 @@ var Ci = Components.interfaces;
var Cc = Components.classes;
var {require, loader} = Cu.import("resource://devtools/shared/Loader.jsm", {});
const {colorUtils} = require("devtools/client/shared/css-color");
const {colorUtils} = require("devtools/shared/css-color");
loader.lazyGetter(this, "DOMUtils", function () {
return Cc["@mozilla.org/inspector/dom-utils;1"].getService(Ci.inIDOMUtils);

View File

@ -13,8 +13,8 @@ var {require} = Cu.import("resource://devtools/shared/Loader.jsm", {});
const DOMUtils = Cc["@mozilla.org/inspector/dom-utils;1"].getService(Ci.inIDOMUtils);
const {colorUtils} = require("devtools/client/shared/css-color");
const {cssColors} = require("devtools/client/shared/css-color-db");
const {colorUtils} = require("devtools/shared/css-color");
const {cssColors} = require("devtools/shared/css-color-db");
function isValid(colorName) {
ok(colorUtils.isValidCSSColor(colorName),

View File

@ -12,11 +12,8 @@ const {CubicBezierWidget} =
const {CSSFilterEditorWidget} = require("devtools/client/shared/widgets/FilterWidget");
const {TooltipToggle} = require("devtools/client/shared/widgets/tooltip/TooltipToggle");
const EventEmitter = require("devtools/shared/event-emitter");
const {colorUtils} = require("devtools/client/shared/css-color");
const {colorUtils} = require("devtools/shared/css-color");
const Heritage = require("sdk/core/heritage");
const {Eyedropper} = require("devtools/client/eyedropper/eyedropper");
const {gDevTools} = require("devtools/client/framework/devtools");
const Services = require("Services");
const {XPCOMUtils} = require("resource://gre/modules/XPCOMUtils.jsm");
const {HTMLTooltip} = require("devtools/client/shared/widgets/HTMLTooltip");
const {KeyShortcuts} = require("devtools/client/shared/key-shortcuts");
@ -746,11 +743,15 @@ SwatchBasedEditorTooltip.prototype = {
*
* @param {Toolbox} toolbox
* The devtools toolbox, needed to get the devtools main window.
* @param {InspectorPanel} inspector
* The inspector panel, needed for the eyedropper.
*/
function SwatchColorPickerTooltip(toolbox) {
function SwatchColorPickerTooltip(toolbox, inspector) {
let stylesheet = "chrome://devtools/content/shared/widgets/spectrum.css";
SwatchBasedEditorTooltip.call(this, toolbox, stylesheet);
this.inspector = inspector;
// Creating a spectrum instance. this.spectrum will always be a promise that
// resolves to the spectrum instance
this.spectrum = this.setColorPickerContent([0, 0, 0, 1]);
@ -779,7 +780,7 @@ Heritage.extend(SwatchBasedEditorTooltip.prototype, {
eyedropper.className = "devtools-button";
container.appendChild(eyedropper);
this.tooltip.setContent(container, { width: 210, height: 216 });
this.tooltip.setContent(container, { width: 218, height: 224 });
let spectrum = new Spectrum(spectrumNode, color);
@ -810,8 +811,16 @@ Heritage.extend(SwatchBasedEditorTooltip.prototype, {
this.spectrum.updateUI();
}
let eyeButton = this.tooltip.doc.querySelector("#eyedropper-button");
eyeButton.addEventListener("click", this._openEyeDropper);
let {target} = this.inspector.toolbox;
target.actorHasMethod("inspector", "pickColorFromPage").then(value => {
let tooltipDoc = this.tooltip.doc;
let eyeButton = tooltipDoc.querySelector("#eyedropper-button");
if (value) {
eyeButton.addEventListener("click", this._openEyeDropper);
} else {
eyeButton.style.display = "none";
}
}, e => console.error(e));
},
_onSpectrumColorChange: function (event, rgba, cssColor) {
@ -834,39 +843,26 @@ Heritage.extend(SwatchBasedEditorTooltip.prototype, {
},
_openEyeDropper: function () {
let chromeWindow = this.tooltip.doc.defaultView.top;
let windowType = chromeWindow.document.documentElement
.getAttribute("windowtype");
let toolboxWindow;
if (windowType != gDevTools.chromeWindowType) {
// this means the toolbox is in a seperate window. We need to make
// sure we'll be inspecting the browser window instead
toolboxWindow = chromeWindow;
chromeWindow = Services.wm.getMostRecentWindow(gDevTools.chromeWindowType);
chromeWindow.focus();
}
let dropper = new Eyedropper(chromeWindow, { copyOnSelect: false,
context: "picker" });
let {inspector, toolbox, telemetry} = this.inspector;
telemetry.toolOpened("pickereyedropper");
inspector.pickColorFromPage({copyOnSelect: false}).catch(e => console.error(e));
dropper.once("select", (event, color) => {
if (toolboxWindow) {
toolboxWindow.focus();
}
inspector.once("color-picked", color => {
toolbox.win.focus();
this._selectColor(color);
});
dropper.once("destroy", () => {
inspector.once("color-pick-canceled", () => {
this.eyedropperOpen = false;
this.activeSwatch = null;
});
dropper.open();
this.eyedropperOpen = true;
// close the colorpicker tooltip so that only the eyedropper is open.
this.hide();
this.tooltip.emit("eyedropper-opened", dropper);
this.tooltip.emit("eyedropper-opened");
},
_colorToRgba: function (color) {
@ -883,6 +879,7 @@ Heritage.extend(SwatchBasedEditorTooltip.prototype, {
destroy: function () {
SwatchBasedEditorTooltip.prototype.destroy.call(this);
this.inspector = null;
this.currentSwatchColor = null;
this.spectrum.off("changed", this._onSpectrumColorChange);
this.spectrum.destroy();

View File

@ -7,11 +7,12 @@
.cubic-bezier-container {
display: flex;
width: 500px;
width: 510px;
height: 370px;
flex-direction: row-reverse;
overflow: hidden;
padding: 5px;
box-sizing: border-box;
}
.display-wrap {
@ -30,11 +31,6 @@
position: relative;
}
.theme-dark .coordinate-plane:before,
.theme-dark .coordinate-plane:after {
border-color: #eee;
}
.control-point {
position: absolute;
z-index: 1;
@ -51,30 +47,36 @@
}
.display-wrap {
background: repeating-linear-gradient(0deg, transparent, rgba(0, 0, 0, 0.05) 0, rgba(0, 0, 0, 0.05) 1px, transparent 1px, transparent 15px) no-repeat, repeating-linear-gradient(90deg, transparent, rgba(0, 0, 0, 0.05) 0, rgba(0, 0, 0, 0.05) 1px, transparent 1px, transparent 15px) no-repeat;
background:
repeating-linear-gradient(0deg,
transparent,
var(--bezier-grid-color) 0,
var(--bezier-grid-color) 1px,
transparent 1px,
transparent 15px) no-repeat,
repeating-linear-gradient(90deg,
transparent,
var(--bezier-grid-color) 0,
var(--bezier-grid-color) 1px,
transparent 1px,
transparent 15px) no-repeat;
background-size: 100% 100%, 100% 100%;
background-position: -2px 5px, -2px 5px;
-moz-user-select: none;
}
.theme-dark .display-wrap {
background: repeating-linear-gradient(0deg, transparent, rgba(0, 0, 0, 0.2) 0, rgba(0, 0, 0, 0.2) 1px, transparent 1px, transparent 15px) no-repeat, repeating-linear-gradient(90deg, transparent, rgba(0, 0, 0, 0.2) 0, rgba(0, 0, 0, 0.2) 1px, transparent 1px, transparent 15px) no-repeat;
background-size: 100% 100%, 100% 100%;
background-position: -2px 5px, -2px 5px;
-moz-user-select: none;
}
canvas.curve {
background: linear-gradient(-45deg, transparent 49.7%, rgba(0,0,0,.2) 49.7%, rgba(0,0,0,.2) 50.3%, transparent 50.3%) center no-repeat;
background:
linear-gradient(-45deg,
transparent 49.7%,
var(--bezier-diagonal-color) 49.7%,
var(--bezier-diagonal-color) 50.3%,
transparent 50.3%) center no-repeat;
background-size: 100% 100%;
background-position: 0 0;
}
.theme-dark canvas.curve {
background: linear-gradient(-45deg, transparent 49.7%, #eee 49.7%, #eee 50.3%, transparent 50.3%) center no-repeat;
}
/* Timing Function Preview Widget */
.timing-function-preview {
@ -184,16 +186,12 @@ canvas.curve {
.preset canvas {
display: block;
border: 1px solid #ccc;
border: 1px solid var(--theme-splitter-color);
border-radius: 3px;
background-color: var(--theme-body-background);
margin: 0 auto;
}
.theme-dark .preset canvas {
border-color: #444e58;
}
.preset p {
font-size: 80%;
margin: 2px auto 0px auto;
@ -211,8 +209,8 @@ canvas.curve {
border-color: var(--theme-selection-background);
}
.active-preset canvas, .active-preset:hover canvas,
.theme-dark .active-preset canvas, .theme-dark .preset:hover canvas {
.active-preset canvas,
.active-preset:hover canvas {
background-color: var(--theme-selection-background-semitransparent);
border-color: var(--theme-selection-background);
}

View File

@ -5,10 +5,12 @@
/* Main container: Displays the filters and presets in 2 columns */
#filter-container {
height: 100%;
width: 510px;
height: 200px;
display: flex;
position: relative;
padding: 5px;
box-sizing: border-box;
/* when opened in a xul:panel, a gray color is applied to text */
color: var(--theme-body-color);
}
@ -138,12 +140,6 @@ input {
width: 8em;
}
.theme-light .add,
.theme-light .remove-button,
.theme-light #toggle-presets {
filter: invert(1);
}
.preset {
display: flex;
margin-bottom: 10px;
@ -174,10 +170,6 @@ input {
color: var(--theme-selection-color);
}
.theme-light .preset:hover .remove-button {
filter: invert(0);
}
.preset .remove-button {
order: 2;
}
@ -238,6 +230,12 @@ input {
background: url(chrome://devtools/skin/images/pseudo-class.svg);
}
.add,
.remove-button,
#toggle-presets {
filter: var(--icon-filter);
}
.show-presets #toggle-presets {
filter: url(chrome://devtools/skin/images/filters.svg#checked-icon-state);
}

View File

@ -8,7 +8,7 @@
}
#eyedropper-button::before {
background-image: url("chrome://devtools/skin/images/command-eyedropper.svg");
background-image: url(chrome://devtools/skin/images/command-eyedropper.svg);
}
/* Mix-in classes */
@ -37,6 +37,10 @@
/* Elements */
#spectrum-tooltip {
padding: 4px;
}
.spectrum-container {
position: relative;
display: none;

View File

@ -190,7 +190,15 @@ function removeBreakpoints(ctx) {
meta.breakpoints = {};
}
cm.doc.iter((line) => { removeBreakpoint(ctx, line); });
cm.doc.iter((line) => {
// The hasBreakpoint is a slow operation: checks the line type, whether cm
// is initialized and creates several new objects. Inlining the line's
// wrapClass property check directly.
if (line.wrapClass == null || !line.wrapClass.includes("breakpoint")) {
return;
}
removeBreakpoint(ctx, line);
});
}
/**

View File

@ -11,7 +11,22 @@
height: 100%;
}
#sidebar-panel-computedview > .devtools-toolbar {
#computedview-container {
overflow: auto;
height: 100%;
}
/* This extra wrapper only serves as a way to get the content of the view focusable.
So that when the user reaches it either via keyboard or mouse, we know that the view
is focused and therefore can handle shortcuts.
However, for accessibility reasons, tabindex is set to -1 to avoid having to tab
through it, and the outline is hidden. */
#computedview-container-focusable {
height: 100%;
outline: none;
}
#computedview-toolbar {
display: flex;
}
@ -32,17 +47,11 @@
align-items: center;
}
#computedview-container {
overflow: auto;
}
#propertyContainer {
-moz-user-select: text;
overflow-y: auto;
overflow-x: hidden;
flex: auto;
border-top-width: 1px;
border-top-style: dotted;
}
.row-striped {

View File

@ -3,6 +3,14 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
:root {
--eyedropper-image: url(images/command-eyedropper.svg);
}
.theme-firebug {
--eyedropper-image: url(images/firebug/command-eyedropper.svg);
}
/* Use flex layout for the Inspector toolbar. For now, it's done
specifically for the Inspector toolbar since general rule applied
on .devtools-toolbar breaks breadcrubs and also toolbars in other
@ -78,6 +86,17 @@
font: message-box;
}
/* Eyedropper toolbar button */
#inspector-eyedropper-toggle {
/* hidden by default, until we can check that the required highlighter exists */
display: none;
}
#inspector-eyedropper-toggle::before {
background-image: var(--eyedropper-image);
}
/* Add element toolbar button */
#inspector-element-add-button::before {
background-image: url("chrome://devtools/skin/images/add.svg");

View File

@ -2,6 +2,12 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/ */
#layout-wrapper {
border-bottom-style: solid;
border-bottom-width: 1px;
border-color: var(--theme-splitter-color);
}
#layout-container {
/* The view will grow bigger as the window gets resized, until 400px */
max-width: 400px;

View File

@ -223,7 +223,6 @@
width: 16px;
height: 16px;
margin-inline-end: 4px;
cursor: pointer;
}
.security-state-insecure {
@ -269,7 +268,6 @@
margin: 0;
margin-inline-end: 3px;
-moz-user-select: none;
cursor: pointer;
}
.requests-menu-transferred {

View File

@ -82,6 +82,16 @@
height: 100%;
}
/* This extra wrapper only serves as a way to get the content of the view focusable.
So that when the user reaches it either via keyboard or mouse, we know that the view
is focused and therefore can handle shortcuts.
However, for accessibility reasons, tabindex is set to -1 to avoid having to tab
through it, and the outline is hidden. */
#ruleview-container-focusable {
height: 100%;
outline: none;
}
#ruleview-container.non-interactive {
pointer-events: none;
visibility: collapse;

View File

@ -17,7 +17,6 @@
--command-frames-image: url(images/command-frames.svg);
--command-splitconsole-image: url(images/command-console.svg);
--command-noautohide-image: url(images/command-noautohide.svg);
--command-eyedropper-image: url(images/command-eyedropper.svg);
--command-rulers-image: url(images/command-rulers.svg);
--command-measure-image: url(images/command-measure.svg);
}
@ -36,7 +35,6 @@
--command-frames-image: url(images/firebug/command-frames.svg);
--command-splitconsole-image: url(images/firebug/command-console.svg);
--command-noautohide-image: url(images/firebug/command-noautohide.svg);
--command-eyedropper-image: url(images/firebug/command-eyedropper.svg);
--command-rulers-image: url(images/firebug/command-rulers.svg);
--command-measure-image: url(images/firebug/command-measure.svg);
}
@ -382,4 +380,23 @@
.toolbox-panel[selected] {
visibility: visible;
}
}
/**
* When panels are collapsed or hidden, making sure that they are also
* inaccessible by keyboard. This is not the case by default because the are
* predominantly hidden using visibility: collapse; style or collapsed
* attribute.
*/
.toolbox-panel *,
#toolbox-panel-webconsole[collapsed] * {
-moz-user-focus: ignore;
}
/**
* Enrure that selected toolbox panel's contents are keyboard accessible as they
* are explicitly made not to be when hidden (default).
*/
.toolbox-panel[selected] * {
-moz-user-focus: normal;
}

View File

@ -3,6 +3,18 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/* Tooltip specific theme variables */
.theme-dark {
--bezier-diagonal-color: #eee;
--bezier-grid-color: rgba(0, 0, 0, 0.2);
}
.theme-light {
--bezier-diagonal-color: rgba(0, 0, 0, 0.2);
--bezier-grid-color: rgba(0, 0, 0, 0.05);
}
/* Tooltip widget (see devtools/client/shared/widgets/Tooltip.js) */
.devtools-tooltip .panel-arrowcontent {

View File

@ -400,3 +400,67 @@
stroke-dasharray: 5 3;
shape-rendering: crispEdges;
}
/* Eye dropper */
:-moz-native-anonymous .eye-dropper-root {
--magnifier-width: 96px;
--magnifier-height: 96px;
/* Width accounts for all color formats (hsl being the longest) */
--label-width: 160px;
--color: #e0e0e0;
position: absolute;
/* Tool start position. This should match the X/Y defines in JS */
top: 100px;
left: 100px;
/* Prevent interacting with the page when hovering and clicking */
pointer-events: auto;
/* Offset the UI so it is centered around the pointer */
transform: translate(
calc(var(--magnifier-width) / -2), calc(var(--magnifier-height) / -2));
filter: drop-shadow(0 0 1px rgba(0,0,0,.4));
/* We don't need the UI to be reversed in RTL locales, otherwise the # would appear
to the right of the hex code. Force LTR */
direction: ltr;
}
:-moz-native-anonymous .eye-dropper-canvas {
image-rendering: -moz-crisp-edges;
cursor: none;
width: var(--magnifier-width);
height: var(--magnifier-height);
border-radius: 50%;
box-shadow: 0 0 0 3px var(--color);
display: block;
}
:-moz-native-anonymous .eye-dropper-color-container {
background-color: var(--color);
border-radius: 2px;
width: var(--label-width);
transform: translateX(calc((var(--magnifier-width) - var(--label-width)) / 2));
position: relative;
}
:-moz-native-anonymous .eye-dropper-color-preview {
width: 16px;
height: 16px;
position: absolute;
offset-inline-start: 3px;
offset-block-start: 3px;
box-shadow: 0px 0px 0px black;
border: solid 1px #fff;
}
:-moz-native-anonymous .eye-dropper-color-value {
text-shadow: 1px 1px 1px #fff;
font: message-box;
font-size: 11px;
text-align: center;
padding: 4px 0;
}

View File

@ -679,3 +679,7 @@ exports.RulersHighlighter = RulersHighlighter;
const { MeasuringToolHighlighter } = require("./highlighters/measuring-tool");
register(MeasuringToolHighlighter);
exports.MeasuringToolHighlighter = MeasuringToolHighlighter;
const { EyeDropper } = require("./highlighters/eye-dropper");
register(EyeDropper);
exports.EyeDropper = EyeDropper;

View File

@ -0,0 +1,499 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
// Eye-dropper tool. This is implemented as a highlighter so it can be displayed in the
// content page.
// It basically displays a magnifier that tracks mouse moves and shows a magnified version
// of the page. On click, it samples the color at the pixel being hovered.
const {Ci, Cc} = require("chrome");
const {CanvasFrameAnonymousContentHelper, createNode} = require("./utils/markup");
const Services = require("Services");
const EventEmitter = require("devtools/shared/event-emitter");
const {rgbToHsl, rgbToColorName} = require("devtools/shared/css-color").colorUtils;
const {getCurrentZoom, getFrameOffsets} = require("devtools/shared/layout/utils");
loader.lazyGetter(this, "clipboardHelper",
() => Cc["@mozilla.org/widget/clipboardhelper;1"].getService(Ci.nsIClipboardHelper));
loader.lazyGetter(this, "l10n",
() => Services.strings.createBundle("chrome://devtools/locale/eyedropper.properties"));
const ZOOM_LEVEL_PREF = "devtools.eyedropper.zoom";
const FORMAT_PREF = "devtools.defaultColorUnit";
// Width of the canvas.
const MAGNIFIER_WIDTH = 96;
// Height of the canvas.
const MAGNIFIER_HEIGHT = 96;
// Start position, when the tool is first shown. This should match the top/left position
// defined in CSS.
const DEFAULT_START_POS_X = 100;
const DEFAULT_START_POS_Y = 100;
// How long to wait before closing after copy.
const CLOSE_DELAY = 750;
/**
* The EyeDropper is the class that draws the gradient line and
* color stops as an overlay on top of a linear-gradient background-image.
*/
function EyeDropper(highlighterEnv) {
EventEmitter.decorate(this);
this.highlighterEnv = highlighterEnv;
this.markup = new CanvasFrameAnonymousContentHelper(this.highlighterEnv,
this._buildMarkup.bind(this));
// Get a couple of settings from prefs.
this.format = Services.prefs.getCharPref(FORMAT_PREF);
this.eyeDropperZoomLevel = Services.prefs.getIntPref(ZOOM_LEVEL_PREF);
}
EyeDropper.prototype = {
typeName: "EyeDropper",
ID_CLASS_PREFIX: "eye-dropper-",
get win() {
return this.highlighterEnv.window;
},
_buildMarkup() {
// Highlighter main container.
let container = createNode(this.win, {
attributes: {"class": "highlighter-container"}
});
// Wrapper element.
let wrapper = createNode(this.win, {
parent: container,
attributes: {
"id": "root",
"class": "root",
"hidden": "true"
},
prefix: this.ID_CLASS_PREFIX
});
// The magnifier canvas element.
createNode(this.win, {
parent: wrapper,
nodeType: "canvas",
attributes: {
"id": "canvas",
"class": "canvas",
"width": MAGNIFIER_WIDTH,
"height": MAGNIFIER_HEIGHT
},
prefix: this.ID_CLASS_PREFIX
});
// The color label element.
let colorLabelContainer = createNode(this.win, {
parent: wrapper,
attributes: {"class": "color-container"},
prefix: this.ID_CLASS_PREFIX
});
createNode(this.win, {
nodeType: "div",
parent: colorLabelContainer,
attributes: {"id": "color-preview", "class": "color-preview"},
prefix: this.ID_CLASS_PREFIX
});
createNode(this.win, {
nodeType: "div",
parent: colorLabelContainer,
attributes: {"id": "color-value", "class": "color-value"},
prefix: this.ID_CLASS_PREFIX
});
return container;
},
destroy() {
this.hide();
this.markup.destroy();
},
getElement(id) {
return this.markup.getElement(this.ID_CLASS_PREFIX + id);
},
/**
* Show the eye-dropper highlighter.
* @param {DOMNode} node The node which document the highlighter should be inserted in.
* @param {Object} options The options object may contain the following properties:
* - {Boolean} copyOnSelect Whether selecting a color should copy it to the clipboard.
*/
show(node, options = {}) {
this.options = options;
// Get the page's current zoom level.
this.pageZoom = getCurrentZoom(this.win);
// Take a screenshot of the viewport. This needs to be done first otherwise the
// eyedropper UI will appear in the screenshot itself (since the UI is injected as
// native anonymous content in the page).
// Once the screenshot is ready, the magnified area will be drawn.
this.prepareImageCapture();
// Start listening for user events.
let {pageListenerTarget} = this.highlighterEnv;
pageListenerTarget.addEventListener("mousemove", this);
pageListenerTarget.addEventListener("click", this);
pageListenerTarget.addEventListener("keydown", this);
pageListenerTarget.addEventListener("DOMMouseScroll", this);
pageListenerTarget.addEventListener("FullZoomChange", this);
// Show the eye-dropper.
this.getElement("root").removeAttribute("hidden");
// Prepare the canvas context on which we're drawing the magnified page portion.
this.ctx = this.getElement("canvas").getCanvasContext();
this.ctx.mozImageSmoothingEnabled = false;
this.magnifiedArea = {width: MAGNIFIER_WIDTH, height: MAGNIFIER_HEIGHT,
x: DEFAULT_START_POS_X, y: DEFAULT_START_POS_Y};
this.moveTo(DEFAULT_START_POS_X, DEFAULT_START_POS_Y);
// Focus the content so the keyboard can be used.
this.win.document.documentElement.focus();
return true;
},
/**
* Hide the eye-dropper highlighter.
*/
hide() {
this.pageImage = null;
let {pageListenerTarget} = this.highlighterEnv;
pageListenerTarget.removeEventListener("mousemove", this);
pageListenerTarget.removeEventListener("click", this);
pageListenerTarget.removeEventListener("keydown", this);
pageListenerTarget.removeEventListener("DOMMouseScroll", this);
pageListenerTarget.removeEventListener("FullZoomChange", this);
this.getElement("root").setAttribute("hidden", "true");
this.getElement("root").removeAttribute("drawn");
},
prepareImageCapture() {
// Get the page as an image.
let imageData = getWindowAsImageData(this.win);
let image = new this.win.Image();
image.src = imageData;
// Wait for screenshot to load
image.onload = () => {
this.pageImage = image;
// We likely haven't drawn anything yet (no mousemove events yet), so start now.
this.draw();
// Set an attribute on the root element to be able to run tests after the first draw
// was done.
this.getElement("root").setAttribute("drawn", "true");
};
},
/**
* Get the number of cells (blown-up pixels) per direction in the grid.
*/
get cellsWide() {
// Canvas will render whole "pixels" (cells) only, and an even number at that. Round
// up to the nearest even number of pixels.
let cellsWide = Math.ceil(this.magnifiedArea.width / this.eyeDropperZoomLevel);
cellsWide += cellsWide % 2;
return cellsWide;
},
/**
* Get the size of each cell (blown-up pixel) in the grid.
*/
get cellSize() {
return this.magnifiedArea.width / this.cellsWide;
},
/**
* Get index of cell in the center of the grid.
*/
get centerCell() {
return Math.floor(this.cellsWide / 2);
},
/**
* Get color of center cell in the grid.
*/
get centerColor() {
let pos = (this.centerCell * this.cellSize) + (this.cellSize / 2);
let rgb = this.ctx.getImageData(pos, pos, 1, 1).data;
return rgb;
},
draw() {
// If the image of the page isn't ready yet, bail out, we'll draw later on mousemove.
if (!this.pageImage) {
return;
}
let {width, height, x, y} = this.magnifiedArea;
let zoomedWidth = width / this.eyeDropperZoomLevel;
let zoomedHeight = height / this.eyeDropperZoomLevel;
let sx = x - (zoomedWidth / 2);
let sy = y - (zoomedHeight / 2);
let sw = zoomedWidth;
let sh = zoomedHeight;
this.ctx.drawImage(this.pageImage, sx, sy, sw, sh, 0, 0, width, height);
// Draw the grid on top, but only at 3x or more, otherwise it's too busy.
if (this.eyeDropperZoomLevel > 2) {
this.drawGrid();
}
this.drawCrosshair();
// Update the color preview and value.
let rgb = this.centerColor;
this.getElement("color-preview").setAttribute("style",
`background-color:${toColorString(rgb, "rgb")};`);
this.getElement("color-value").setTextContent(toColorString(rgb, this.format));
},
/**
* Draw a grid on the canvas representing pixel boundaries.
*/
drawGrid() {
let {width, height} = this.magnifiedArea;
this.ctx.lineWidth = 1;
this.ctx.strokeStyle = "rgba(143, 143, 143, 0.2)";
for (let i = 0; i < width; i += this.cellSize) {
this.ctx.beginPath();
this.ctx.moveTo(i - .5, 0);
this.ctx.lineTo(i - .5, height);
this.ctx.stroke();
this.ctx.beginPath();
this.ctx.moveTo(0, i - .5);
this.ctx.lineTo(width, i - .5);
this.ctx.stroke();
}
},
/**
* Draw a box on the canvas to highlight the center cell.
*/
drawCrosshair() {
let pos = this.centerCell * this.cellSize;
this.ctx.lineWidth = 1;
this.ctx.lineJoin = "miter";
this.ctx.strokeStyle = "rgba(0, 0, 0, 1)";
this.ctx.strokeRect(pos - 1.5, pos - 1.5, this.cellSize + 2, this.cellSize + 2);
this.ctx.strokeStyle = "rgba(255, 255, 255, 1)";
this.ctx.strokeRect(pos - 0.5, pos - 0.5, this.cellSize, this.cellSize);
},
handleEvent(e) {
switch (e.type) {
case "mousemove":
// We might be getting an event from a child frame, so account for the offset.
let [xOffset, yOffset] = getFrameOffsets(this.win, e.target);
let x = xOffset + e.pageX - this.win.scrollX;
let y = yOffset + e.pageY - this.win.scrollY;
// Update the zoom area.
this.magnifiedArea.x = x * this.pageZoom;
this.magnifiedArea.y = y * this.pageZoom;
// Redraw the portion of the screenshot that is now under the mouse.
this.draw();
// And move the eye-dropper's UI so it follows the mouse.
this.moveTo(x, y);
break;
case "click":
this.selectColor();
break;
case "keydown":
this.handleKeyDown(e);
break;
case "DOMMouseScroll":
// Prevent scrolling. That's because we only took a screenshot of the viewport, so
// scrolling out of the viewport wouldn't draw the expected things. In the future
// we can take the screenshot again on scroll, but for now it doesn't seem
// important.
e.preventDefault();
break;
case "FullZoomChange":
this.hide();
this.show();
break;
}
},
moveTo(x, y) {
this.getElement("root").setAttribute("style", `top:${y}px;left:${x}px;`);
},
/**
* Select the current color that's being previewed. Depending on the current options,
* selecting might mean copying to the clipboard and closing the
*/
selectColor() {
let onColorSelected = Promise.resolve();
if (this.options.copyOnSelect) {
onColorSelected = this.copyColor();
}
this.emit("selected", toColorString(this.centerColor, this.format));
onColorSelected.then(() => this.hide(), e => console.error(e));
},
/**
* Handler for the keydown event. Either select the color or move the panel in a
* direction depending on the key pressed.
*/
handleKeyDown(e) {
if (e.keyCode === e.DOM_VK_RETURN) {
this.selectColor();
return;
}
if (e.keyCode === e.DOM_VK_ESCAPE) {
this.emit("canceled");
this.hide();
return;
}
let offsetX = 0;
let offsetY = 0;
let modifier = 1;
if (e.keyCode === e.DOM_VK_LEFT) {
offsetX = -1;
}
if (e.keyCode === e.DOM_VK_RIGHT) {
offsetX = 1;
}
if (e.keyCode === e.DOM_VK_UP) {
offsetY = -1;
}
if (e.keyCode === e.DOM_VK_DOWN) {
offsetY = 1;
}
if (e.shiftKey) {
modifier = 10;
}
offsetY *= modifier;
offsetX *= modifier;
if (offsetX !== 0 || offsetY !== 0) {
this.magnifiedArea.x += offsetX;
this.magnifiedArea.y += offsetY;
this.draw();
this.moveTo(this.magnifiedArea.x / this.pageZoom,
this.magnifiedArea.y / this.pageZoom);
}
// Prevent all keyboard interaction with the page, except if a modifier is used to let
// keyboard shortcuts through.
let hasModifier = e.metaKey || e.ctrlKey || e.altKey || e.shiftKey;
if (!hasModifier) {
e.preventDefault();
}
},
/**
* Copy the currently inspected color to the clipboard.
* @return {Promise} Resolves when the copy has been done (after a delay that is used to
* let users know that something was copied).
*/
copyColor() {
// Copy to the clipboard.
let color = toColorString(this.centerColor, this.format);
clipboardHelper.copyString(color);
// Provide some feedback.
this.getElement("color-value").setTextContent(
"✓ " + l10n.GetStringFromName("colorValue.copied"));
// Hide the tool after a delay.
clearTimeout(this._copyTimeout);
return new Promise(resolve => {
this._copyTimeout = setTimeout(resolve, CLOSE_DELAY);
});
}
};
exports.EyeDropper = EyeDropper;
/**
* Get a content window as image data-url.
* @param {Window} win
* @return {String} The data-url
*/
function getWindowAsImageData(win) {
let canvas = win.document.createElementNS("http://www.w3.org/1999/xhtml", "canvas");
let scale = getCurrentZoom(win);
let width = win.innerWidth;
let height = win.innerHeight;
canvas.width = width * scale;
canvas.height = height * scale;
canvas.mozOpaque = true;
let ctx = canvas.getContext("2d");
ctx.scale(scale, scale);
ctx.drawWindow(win, win.scrollX, win.scrollY, width, height, "#fff");
return canvas.toDataURL();
}
/**
* Get a formatted CSS color string from a color value.
* @param {array} rgb Rgb values of a color to format.
* @param {string} format Format of string. One of "hex", "rgb", "hsl", "name".
* @return {string} Formatted color value, e.g. "#FFF" or "hsl(20, 10%, 10%)".
*/
function toColorString(rgb, format) {
let [r, g, b] = rgb;
switch (format) {
case "hex":
return hexString(rgb);
case "rgb":
return "rgb(" + r + ", " + g + ", " + b + ")";
case "hsl":
let [h, s, l] = rgbToHsl(rgb);
return "hsl(" + h + ", " + s + "%, " + l + "%)";
case "name":
let str;
try {
str = rgbToColorName(r, g, b);
} catch (e) {
str = hexString(rgb);
}
return str;
default:
return hexString(rgb);
}
}
/**
* Produce a hex-formatted color string from rgb values.
* @param {array} rgb Rgb values of color to stringify.
* @return {string} Hex formatted string for color, e.g. "#FFEE00".
*/
function hexString([r, g, b]) {
let val = (1 << 24) + (r << 16) + (g << 8) + (b << 0);
return "#" + val.toString(16).substr(-6).toUpperCase();
}

View File

@ -12,6 +12,7 @@ DevToolsModules(
'auto-refresh.js',
'box-model.js',
'css-transform.js',
'eye-dropper.js',
'geometry-editor.js',
'measuring-tool.js',
'rect.js',

View File

@ -325,6 +325,10 @@ CanvasFrameAnonymousContentHelper.prototype = {
return typeof this.getAttributeForElement(id, name) === "string";
},
getCanvasContext: function (id, type = "2d") {
return this.content ? this.content.getCanvasContext(id, type) : null;
},
/**
* Add an event listener to one of the elements inserted in the canvasFrame
* native anonymous container.
@ -460,6 +464,7 @@ CanvasFrameAnonymousContentHelper.prototype = {
getAttribute: name => this.getAttributeForElement(id, name),
removeAttribute: name => this.removeAttributeForElement(id, name),
hasAttribute: name => this.hasAttributeForElement(id, name),
getCanvasContext: type => this.getCanvasContext(id, type),
addEventListener: (type, handler) => {
return this.addEventListenerForElement(id, type, handler);
},

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