Merge mozilla-central to mozilla-inbound

--HG--
rename : toolkit/components/prompts/test/test_bug625187.html => toolkit/components/prompts/test/test_subresources_prompts.html
This commit is contained in:
Carsten "Tomcat" Book 2016-06-06 12:02:34 +02:00
commit 06a7fd0491
199 changed files with 16417 additions and 12756 deletions

View File

@ -110,6 +110,7 @@ devtools/client/webide/**
devtools/server/**
!devtools/server/actors/webbrowser.js
!devtools/server/actors/styles.js
!devtools/server/actors/string.js
devtools/shared/*.js
!devtools/shared/css-lexer.js
!devtools/shared/defer.js

View File

@ -1,5 +1,5 @@
<?xml version="1.0"?>
<blocklist xmlns="http://www.mozilla.org/2006/addons-blocklist" lastupdate="1464091834000">
<blocklist xmlns="http://www.mozilla.org/2006/addons-blocklist" lastupdate="1464967719000">
<emItems>
<emItem blockID="i58" id="webmaster@buzzzzvideos.info">
<versionRange minVersion="0" maxVersion="*">
@ -363,6 +363,15 @@
</versionRange>
<prefs>
</prefs>
</emItem>
<emItem blockID="i1227" id="{A34CAF42-A3E3-11E5-945F-18C31D5D46B0}">
<versionRange minVersion="0" maxVersion="*" severity="1">
</versionRange>
<prefs>
<pref>security.csp.enable</pref>
<pref>security.fileuri.strict_origin_policy</pref>
<pref>security.mixed_content.block_active_content</pref>
</prefs>
</emItem>
<emItem blockID="i1129" id="youtubeunblocker__web@unblocker.yt">
<versionRange minVersion="0" maxVersion="*" severity="3">
@ -484,6 +493,12 @@
</versionRange>
<prefs>
</prefs>
</emItem>
<emItem blockID="i1229" id="/^.*@unblocker\.yt$/">
<versionRange minVersion="0" maxVersion="*" severity="3">
</versionRange>
<prefs>
</prefs>
</emItem>
<emItem blockID="i404" id="{a9bb9fa0-4122-4c75-bd9a-bc27db3f9155}">
<versionRange minVersion="0" maxVersion="*" severity="1">
@ -978,7 +993,9 @@
</prefs>
</emItem>
<emItem blockID="i1222" id="tmbepff@trendmicro.com">
<versionRange minVersion="0" maxVersion="9.2.0.1024" severity="1">
<versionRange minVersion="0" maxVersion="9.1.0.1035" severity="1">
</versionRange>
<versionRange minVersion="9.2" maxVersion="9.2.0.1023" severity="1">
</versionRange>
<prefs>
</prefs>
@ -1652,6 +1669,12 @@
</versionRange>
<prefs>
</prefs>
</emItem>
<emItem blockID="i1228" id="unblocker30__web@unblocker.yt">
<versionRange minVersion="0" maxVersion="*" severity="3">
</versionRange>
<prefs>
</prefs>
</emItem>
<emItem blockID="i692" id="/^(j003-lqgrmgpcekslhg|SupraSavings|j003-dkqonnnthqjnkq|j003-kaggrpmirxjpzh)@jetpack$/">
<versionRange minVersion="0" maxVersion="*" severity="1">
@ -3478,6 +3501,18 @@
<match name="filename" exp="npqtplugin\.dll" /> <versionRange minVersion="0" maxVersion="*" severity="0" vulnerabilitystatus="2"></versionRange>
<infoURL>https://support.apple.com/en-us/HT205771</infoURL>
</pluginItem>
<pluginItem os="Linux" blockID="p1224">
<match name="filename" exp="libflashplayer\.so" /> <versionRange minVersion="11.2.202.577" maxVersion="11.2.202.616" severity="0" vulnerabilitystatus="1"></versionRange>
<infoURL>https://get.adobe.com/flashplayer/</infoURL>
</pluginItem>
<pluginItem blockID="p1225">
<match name="filename" exp="(NPSWF32.*\.dll)|(NPSWF64.*\.dll)|(Flash\ Player\.plugin)" /> <versionRange minVersion="18.0.0.333" maxVersion="18.0.0.343" severity="0" vulnerabilitystatus="1"></versionRange>
<infoURL>https://get.adobe.com/flashplayer/</infoURL>
</pluginItem>
<pluginItem blockID="p1226">
<match name="filename" exp="(NPSWF32.*\.dll)|(NPSWF64.*\.dll)|(Flash\ Player\.plugin)" /> <versionRange minVersion="21.0.0.197" maxVersion="21.0.0.226" severity="0" vulnerabilitystatus="1"></versionRange>
<infoURL>https://get.adobe.com/flashplayer/</infoURL>
</pluginItem>
</pluginItems>
<gfxItems>

View File

@ -4,8 +4,6 @@
var gFxAccounts = {
PREF_SYNC_START_DOORHANGER: "services.sync.ui.showSyncStartDoorhanger",
DOORHANGER_ACTIVATE_DELAY_MS: 5000,
SYNC_MIGRATION_NOTIFICATION_TITLE: "fxa-migration",
_initialized: false,
@ -23,13 +21,11 @@ var gFxAccounts = {
delete this.topics;
return this.topics = [
"weave:service:ready",
"weave:service:sync:start",
"weave:service:login:error",
"weave:service:setup-complete",
"weave:ui:login:error",
"fxa-migration:state-changed",
this.FxAccountsCommon.ONLOGIN_NOTIFICATION,
this.FxAccountsCommon.ONVERIFIED_NOTIFICATION,
this.FxAccountsCommon.ONLOGOUT_NOTIFICATION,
this.FxAccountsCommon.ON_PROFILE_CHANGE_NOTIFICATION,
];
@ -82,11 +78,6 @@ var gFxAccounts = {
return Weave.Status.login == Weave.LOGIN_FAILED_LOGIN_REJECTED;
},
get isActiveWindow() {
let fm = Services.focus;
return fm.activeWindow == window;
},
init: function () {
// Bail out if we're already initialized and for pop-up windows.
if (this._initialized || !window.toolbar.visible) {
@ -97,7 +88,6 @@ var gFxAccounts = {
Services.obs.addObserver(this, topic, false);
}
addEventListener("activate", this);
gNavToolbox.addEventListener("customizationstarting", this);
gNavToolbox.addEventListener("customizationending", this);
@ -121,12 +111,6 @@ var gFxAccounts = {
observe: function (subject, topic, data) {
switch (topic) {
case this.FxAccountsCommon.ONVERIFIED_NOTIFICATION:
Services.prefs.setBoolPref(this.PREF_SYNC_START_DOORHANGER, true);
break;
case "weave:service:sync:start":
this.onSyncStart();
break;
case "fxa-migration:state-changed":
this.onMigrationStateChanged(data, subject);
break;
@ -139,23 +123,6 @@ var gFxAccounts = {
}
},
onSyncStart: function () {
if (!this.isActiveWindow) {
return;
}
let showDoorhanger = false;
try {
showDoorhanger = Services.prefs.getBoolPref(this.PREF_SYNC_START_DOORHANGER);
} catch (e) { /* The pref might not exist. */ }
if (showDoorhanger) {
Services.prefs.clearUserPref(this.PREF_SYNC_START_DOORHANGER);
this.showSyncStartedDoorhanger();
}
},
onMigrationStateChanged: function () {
// Since we nuked most of the migration code, this notification will fire
// once after legacy Sync has been disconnected (and should never fire
@ -199,33 +166,8 @@ var gFxAccounts = {
},
handleEvent: function (event) {
if (event.type == "activate") {
// Our window might have been in the background while we received the
// sync:start notification. If still needed, show the doorhanger after
// a short delay. Without this delay the doorhanger would not show up
// or with a too small delay show up while we're still animating the
// window.
setTimeout(() => this.onSyncStart(), this.DOORHANGER_ACTIVATE_DELAY_MS);
} else {
this._inCustomizationMode = event.type == "customizationstarting";
this.updateAppMenuItem();
}
},
showDoorhanger: function (id) {
let panel = document.getElementById(id);
let anchor = document.getElementById("PanelUI-menu-button");
let iconAnchor =
document.getAnonymousElementByAttribute(anchor, "class",
"toolbarbutton-icon");
panel.hidden = false;
panel.openPopup(iconAnchor || anchor, "bottomcenter topright");
},
showSyncStartedDoorhanger: function () {
this.showDoorhanger("sync-start-panel");
this._inCustomizationMode = event.type == "customizationstarting";
this.updateAppMenuItem();
},
updateUI: function () {

View File

@ -3228,6 +3228,7 @@ function getPEMString(cert)
var PrintPreviewListener = {
_printPreviewTab: null,
_tabBeforePrintPreview: null,
_simplifyPageTab: null,
getPrintPreviewBrowser: function () {
if (!this._printPreviewTab) {
@ -3241,10 +3242,19 @@ var PrintPreviewListener = {
}
return gBrowser.getBrowserForTab(this._printPreviewTab);
},
createSimplifiedBrowser: function () {
this._simplifyPageTab = gBrowser.loadOneTab("about:blank",
{ inBackground: true });
return this.getSimplifiedSourceBrowser();
},
getSourceBrowser: function () {
return this._tabBeforePrintPreview ?
this._tabBeforePrintPreview.linkedBrowser : gBrowser.selectedBrowser;
},
getSimplifiedSourceBrowser: function () {
return this._simplifyPageTab ?
gBrowser.getBrowserForTab(this._simplifyPageTab) : null;
},
getNavToolbox: function () {
return gNavToolbox;
},
@ -3257,6 +3267,10 @@ var PrintPreviewListener = {
this._tabBeforePrintPreview = null;
gInPrintPreviewMode = false;
this._toggleAffectedChrome();
if (this._simplifyPageTab) {
gBrowser.removeTab(this._simplifyPageTab);
this._simplifyPageTab = null;
}
gBrowser.removeTab(this._printPreviewTab);
this._printPreviewTab = null;
},
@ -7162,13 +7176,8 @@ var gIdentityHandler = {
let uri = gBrowser.currentURI;
for (let permission of SitePermissions.listPermissions()) {
let state = SitePermissions.get(uri, permission);
if (state == SitePermissions.UNKNOWN)
continue;
let item = this._createPermissionItem(permission, state);
for (let permission of SitePermissions.getPermissionsByURI(uri)) {
let item = this._createPermissionItem(permission);
this._permissionList.appendChild(item);
}
},
@ -7180,26 +7189,26 @@ var gIdentityHandler = {
SitePermissions.set(gBrowser.currentURI, aPermission, aState);
},
_createPermissionItem: function (aPermission, aState) {
_createPermissionItem: function (aPermission) {
let menulist = document.createElement("menulist");
let menupopup = document.createElement("menupopup");
for (let state of SitePermissions.getAvailableStates(aPermission)) {
for (let state of aPermission.availableStates) {
let menuitem = document.createElement("menuitem");
menuitem.setAttribute("value", state);
menuitem.setAttribute("label", SitePermissions.getStateLabel(aPermission, state));
menuitem.setAttribute("value", state.id);
menuitem.setAttribute("label", state.label);
menupopup.appendChild(menuitem);
}
menulist.appendChild(menupopup);
menulist.setAttribute("value", aState);
menulist.setAttribute("value", aPermission.state);
menulist.setAttribute("oncommand", "gIdentityHandler.setPermission('" +
aPermission + "', this.value)");
menulist.setAttribute("id", "identity-popup-permission:" + aPermission);
aPermission.id + "', this.value)");
menulist.setAttribute("id", "identity-popup-permission:" + aPermission.id);
let label = document.createElement("label");
label.setAttribute("flex", "1");
label.setAttribute("class", "identity-popup-permission-label");
label.setAttribute("control", menulist.getAttribute("id"));
label.textContent = SitePermissions.getPermissionLabel(aPermission);
label.textContent = aPermission.label;
let container = document.createElement("hbox");
container.setAttribute("align", "center");

View File

@ -421,21 +421,6 @@
</hbox>
</panel>
<!-- Sync Panel -->
<panel id="sync-start-panel" class="sync-panel" type="arrow" hidden="true"
noautofocus="true" onclick="this.hidePopup();"
flip="slide">
<hbox class="sync-panel-outer">
<image class="sync-panel-icon"/>
<vbox class="sync-panel-inner">
<description id="sync-start-panel-title"
value="&syncStartPanel2.heading;"/>
<description id="sync-start-panel-subtitle"
value="&syncStartPanel2.subTitle;"/>
</vbox>
</hbox>
</panel>
<!-- Bookmarks and history tooltip -->
<tooltip id="bhTooltip"/>

View File

@ -42,7 +42,6 @@ function* test_bookmarks_popup({isNewBookmark, popupShowFn, popupEditFn,
yield PlacesUtils.bookmarks.fetch({url: "about:home"}, bm => bookmarks.push(bm));
is(bookmarks.length, 1, "Only one bookmark should exist");
is(bookmarkStar.getAttribute("starred"), "true", "Page is starred");
is(bookmarkPanel.state, "open", "Check that panel state is 'open'");
is(bookmarkPanelTitle.value,
isNewBookmark ?
gNavigatorBundle.getString("editBookmarkPanel.pageBookmarkedTitle") :

View File

@ -230,6 +230,36 @@ let tabListener = {
this.emit("tab-removed", {tab, tabId, windowId, isWindowClosing});
},
tabReadyInitialized: false,
tabReadyPromises: new WeakMap(),
initTabReady() {
if (!this.tabReadyInitialized) {
AllWindowEvents.addListener("progress", this);
this.tabReadyInitialized = true;
}
},
onLocationChange(browser, webProgress, request, locationURI, flags) {
if (webProgress.isTopLevel) {
let gBrowser = browser.ownerDocument.defaultView.gBrowser;
let tab = gBrowser.getTabForBrowser(browser);
let deferred = this.tabReadyPromises.get(tab);
if (deferred) {
deferred.resolve(tab);
this.tabReadyPromises.delete(tab);
}
}
},
awaitTabReady(tab) {
return new Promise((resolve, reject) => {
this.tabReadyPromises.set(tab, {resolve, reject});
});
},
};
extensions.registerSchemaAPI("tabs", null, (extension, context) => {
@ -453,39 +483,6 @@ extensions.registerSchemaAPI("tabs", null, (extension, context) => {
create: function(createProperties) {
return new Promise((resolve, reject) => {
function createInWindow(window) {
let url;
if (createProperties.url !== null) {
url = context.uri.resolve(createProperties.url);
if (!context.checkLoadURL(url, {dontReportErrors: true})) {
reject({message: `URL not allowed: ${url}`});
return;
}
}
let tab = window.gBrowser.addTab(url || window.BROWSER_NEW_TAB_URL);
let active = true;
if (createProperties.active !== null) {
active = createProperties.active;
}
if (active) {
window.gBrowser.selectedTab = tab;
}
if (createProperties.index !== null) {
window.gBrowser.moveTabTo(tab, createProperties.index);
}
if (createProperties.pinned) {
window.gBrowser.pinTab(tab);
}
resolve(TabManager.convert(extension, tab));
}
let window = createProperties.windowId !== null ?
WindowManager.getWindow(createProperties.windowId, context) :
WindowManager.topWindow;
@ -495,12 +492,55 @@ extensions.registerSchemaAPI("tabs", null, (extension, context) => {
return;
}
Services.obs.removeObserver(obs, "browser-delayed-startup-finished");
createInWindow(window);
resolve(window);
};
Services.obs.addObserver(obs, "browser-delayed-startup-finished", false);
} else {
createInWindow(window);
resolve(window);
}
}).then(window => {
let url;
if (createProperties.url !== null) {
url = context.uri.resolve(createProperties.url);
if (!context.checkLoadURL(url, {dontReportErrors: true})) {
return Promise.reject({message: `Illegal URL: ${url}`});
}
}
tabListener.initTabReady();
let tab = window.gBrowser.addTab(url || window.BROWSER_NEW_TAB_URL);
let active = true;
if (createProperties.active !== null) {
active = createProperties.active;
}
if (active) {
window.gBrowser.selectedTab = tab;
}
if (createProperties.index !== null) {
window.gBrowser.moveTabTo(tab, createProperties.index);
}
if (createProperties.pinned) {
window.gBrowser.pinTab(tab);
}
if (!createProperties.url || createProperties.url.startsWith("about:")) {
// We can't wait for a location change event for about:newtab,
// since it may be pre-rendered, in which case its initial
// location change event has already fired.
return tab;
}
// Wait for the first location change event, so that operations
// like `executeScript` are dispatched to the inner window that
// contains the URL we're attempting to load.
return tabListener.awaitTabReady(tab);
}).then(tab => {
return TabManager.convert(extension, tab);
});
},
@ -530,7 +570,7 @@ extensions.registerSchemaAPI("tabs", null, (extension, context) => {
let url = context.uri.resolve(updateProperties.url);
if (!context.checkLoadURL(url, {dontReportErrors: true})) {
return Promise.reject({message: `URL not allowed: ${url}`});
return Promise.reject({message: `Illegal URL: ${url}`});
}
tab.linkedBrowser.loadURI(url);

View File

@ -558,7 +558,7 @@ global.TabManager = {
},
handleWindowOpen(window) {
if (window.arguments[0] instanceof window.XULElement) {
if (window.arguments && window.arguments[0] instanceof window.XULElement) {
// If the first window argument is a XUL element, it means the
// window is about to adopt a tab from another window to replace its
// initial tab.

View File

@ -11,7 +11,7 @@
"choices": [
{
"type": "string",
"pattern": "^\\s*(Alt|Ctrl|Command|MacCtr)\\s*\\+\\s*(Shift\\s*\\+\\s*)?([A-Z0-9]|Comma|Period|Home|End|PageUp|PageDown|Space|Insert|Delete|Up|Down|Left|Right)\\s*$"
"pattern": "^\\s*(Alt|Ctrl|Command|MacCtrl)\\s*\\+\\s*(Shift\\s*\\+\\s*)?([A-Z0-9]|Comma|Period|Home|End|PageUp|PageDown|Space|Insert|Delete|Up|Down|Left|Right)\\s*$"
},
{
"type": "string",

View File

@ -205,8 +205,6 @@ add_task(function* testTabSwitchContext() {
});
});
};
let tabLoadPromise;
return [
expect => {
browser.test.log("Initial state. No icon visible.");
@ -225,16 +223,13 @@ add_task(function* testTabSwitchContext() {
expect => {
browser.test.log("Create a new tab. No icon visible.");
browser.tabs.create({active: true, url: "about:blank?0"}, tab => {
tabLoadPromise = promiseTabLoad({url: "about:blank?0", id: tab.id});
tabs.push(tab.id);
expect(null);
});
},
expect => {
browser.test.log("Await tab load. No icon visible.");
tabLoadPromise.then(() => {
expect(null);
});
expect(null);
},
expect => {
browser.test.log("Change properties. Expect new properties.");

View File

@ -101,12 +101,11 @@ add_task(function* () {
browser.test.log(`Testing tabs.create(${JSON.stringify(test.create)}), expecting ${JSON.stringify(test.result)}`);
let tabId;
let updatedPromise = new Promise(resolve => {
let onUpdated = (changedTabId, changed) => {
if (changedTabId === tabId && changed.url) {
if (changed.url) {
browser.tabs.onUpdated.removeListener(onUpdated);
resolve(changed.url);
resolve({tabId: changedTabId, url: changed.url});
}
};
browser.tabs.onUpdated.addListener(onUpdated);
@ -120,6 +119,7 @@ add_task(function* () {
browser.tabs.onCreated.addListener(onCreated);
});
let tabId;
Promise.all([
browser.tabs.create(test.create),
createdPromise,
@ -136,8 +136,9 @@ add_task(function* () {
}
return updatedPromise;
}).then(url => {
browser.test.assertEq(expected.url, url, `Expected value for tab.url`);
}).then(updated => {
browser.test.assertEq(tabId, updated.tabId, `Expected value for tab.id`);
browser.test.assertEq(expected.url, updated.url, `Expected value for tab.url`);
return browser.tabs.remove(tabId);
}).then(() => {

View File

@ -13,7 +13,7 @@ function* testTabsCreateInvalidURL(tabsCreateURL) {
browser.test.onMessage.addListener((msg, tabsCreateURL) => {
browser.tabs.create({url: tabsCreateURL}, (tab) => {
browser.test.assertEq(undefined, tab, "on error tab should be undefined");
browser.test.assertTrue(/URL not allowed/.test(browser.runtime.lastError.message),
browser.test.assertTrue(/Illegal URL/.test(browser.runtime.lastError.message),
"runtime.lastError should report the expected error message");
// Remove the opened tab is any.

View File

@ -12,20 +12,7 @@ add_task(function* testDetectLanguage() {
const BASE_PATH = "browser/browser/components/extensions/test/browser";
function loadTab(url) {
let tabId;
let awaitUpdated = new Promise(resolve => {
browser.tabs.onUpdated.addListener(function onUpdated(changedTabId, changed, tab) {
if (changedTabId === tabId && changed.url) {
browser.tabs.onUpdated.removeListener(onUpdated);
resolve(tab);
}
});
});
return browser.tabs.create({url}).then(tab => {
tabId = tab.id;
return awaitUpdated;
});
return browser.tabs.create({url});
}
loadTab(`http://example.co.jp/${BASE_PATH}/file_language_ja.html`).then(tab => {

View File

@ -118,6 +118,14 @@ add_task(function* testExecuteScript() {
browser.test.assertEq("http://mochi.test:8888/", result, "Result for frameId[1] is correct");
}),
browser.tabs.create({url: "http://example.com/"}).then(tab => {
return browser.tabs.executeScript(tab.id, {code: "location.href"}).then(result => {
browser.test.assertEq("http://example.com/", result, "Script executed correctly in new tab");
return browser.tabs.remove(tab.id);
});
}),
new Promise(resolve => {
browser.runtime.onMessage.addListener(message => {
browser.test.assertEq("script ran", message, "Expected runtime message");
@ -135,7 +143,7 @@ add_task(function* testExecuteScript() {
let extension = ExtensionTestUtils.loadExtension({
manifest: {
"permissions": ["http://mochi.test/", "webNavigation"],
"permissions": ["http://mochi.test/", "http://example.com/", "webNavigation"],
},
background,

View File

@ -37,7 +37,7 @@ function* testTabsUpdateURL(existentTabURL, tabsUpdateURL, isErrorExpected) {
if (!isErrorExpected) {
browser.test.fails(`tabs.update with URL ${tabsUpdateURL} should not be rejected`);
} else {
browser.test.assertTrue(/^URL not allowed/.test(error.message),
browser.test.assertTrue(/^Illegal URL/.test(error.message),
"tabs.update should be rejected with the expected error message");
}
};

View File

@ -2,9 +2,13 @@
/* vim: set sts=2 sw=2 et tw=80: */
"use strict";
SimpleTest.requestCompleteLog();
add_task(function* testWindowsEvents() {
function background() {
browser.windows.onCreated.addListener(function listener(window) {
browser.test.log(`onCreated: windowId=${window.id}`);
browser.test.assertTrue(Number.isInteger(window.id),
"Window object's id is an integer");
browser.test.assertEq("normal", window.type,
@ -14,22 +18,28 @@ add_task(function* testWindowsEvents() {
let lastWindowId;
browser.windows.onFocusChanged.addListener(function listener(windowId) {
browser.test.log(`onFocusChange: windowId=${windowId} lastWindowId=${lastWindowId}`);
browser.test.assertTrue(lastWindowId !== windowId,
"onFocusChanged fired once for the given window");
lastWindowId = windowId;
browser.test.assertTrue(Number.isInteger(windowId),
"windowId is an integer");
browser.windows.getLastFocused().then(window => {
browser.test.assertEq(windowId, window.id,
"Last focused window has the correct id");
browser.test.sendMessage(`window-focus-changed-${windowId}`);
browser.test.sendMessage(`window-focus-changed`, window.id);
});
});
browser.windows.onRemoved.addListener(function listener(windowId) {
browser.test.log(`onRemoved: windowId=${windowId}`);
browser.test.assertTrue(Number.isInteger(windowId),
"windowId is an integer");
browser.test.sendMessage(`window-removed-${windowId}`);
browser.test.sendMessage(`window-removed`, windowId);
browser.test.notifyPass("windows.events");
});
@ -44,27 +54,48 @@ add_task(function* testWindowsEvents() {
yield extension.awaitMessage("ready");
let {WindowManager} = Cu.import("resource://gre/modules/Extension.jsm", {});
let currentWindow = window;
let currentWindowId = WindowManager.getId(currentWindow);
info(`Current window ID: ${currentWindowId}`);
info(`Create browser window 1`);
let win1 = yield BrowserTestUtils.openNewBrowserWindow();
let win1Id = yield extension.awaitMessage("window-created");
yield extension.awaitMessage(`window-focus-changed-${win1Id}`);
info(`Window 1 ID: ${win1Id}`);
let winId = yield extension.awaitMessage(`window-focus-changed`);
is(winId, win1Id, "Got focus change event for the correct window ID.");
info(`Create browser window 2`);
let win2 = yield BrowserTestUtils.openNewBrowserWindow();
let win2Id = yield extension.awaitMessage("window-created");
yield extension.awaitMessage(`window-focus-changed-${win2Id}`);
info(`Window 2 ID: ${win2Id}`);
winId = yield extension.awaitMessage(`window-focus-changed`);
is(winId, win2Id, "Got focus change event for the correct window ID.");
info(`Focus browser window 1`);
yield focusWindow(win1);
yield extension.awaitMessage(`window-focus-changed-${win1Id}`);
winId = yield extension.awaitMessage(`window-focus-changed`);
is(winId, win1Id, "Got focus change event for the correct window ID.");
info(`Close browser window 2`);
yield BrowserTestUtils.closeWindow(win2);
yield extension.awaitMessage(`window-removed-${win2Id}`);
winId = yield extension.awaitMessage(`window-removed`);
is(winId, win2Id, "Got removed event for the correct window ID.");
info(`Close browser window 1`);
yield BrowserTestUtils.closeWindow(win1);
yield extension.awaitMessage(`window-removed-${win1Id}`);
yield extension.awaitMessage(`window-focus-changed-${currentWindowId}`);
winId = yield extension.awaitMessage(`window-removed`);
is(winId, win1Id, "Got removed event for the correct window ID.");
winId = yield extension.awaitMessage(`window-focus-changed`);
is(winId, currentWindowId, "Got focus change event for the correct window ID.");
yield extension.awaitFinish("windows.events");
yield extension.unload();
});

View File

@ -309,6 +309,9 @@ BrowserGlue.prototype = {
case "weave:service:ready":
this._setSyncAutoconnectDelay();
break;
case "fxaccounts:onverified":
this._showSyncStartedDoorhanger();
break;
case "weave:engine:clients:display-uri":
this._onDisplaySyncURI(subject);
break;
@ -526,6 +529,7 @@ BrowserGlue.prototype = {
os.addObserver(this, "browser-lastwindow-close-granted", false);
}
os.addObserver(this, "weave:service:ready", false);
os.addObserver(this, "fxaccounts:onverified", false);
os.addObserver(this, "weave:engine:clients:display-uri", false);
os.addObserver(this, "session-save", false);
os.addObserver(this, "places-init-complete", false);
@ -591,6 +595,7 @@ BrowserGlue.prototype = {
os.removeObserver(this, "browser-lastwindow-close-granted");
}
os.removeObserver(this, "weave:service:ready");
os.removeObserver(this, "fxaccounts:onverified");
os.removeObserver(this, "weave:engine:clients:display-uri");
os.removeObserver(this, "session-save");
if (this._bookmarksBackupIdleTime) {
@ -1895,6 +1900,19 @@ BrowserGlue.prototype = {
notification.persistence = -1; // Until user closes it
},
_showSyncStartedDoorhanger: function () {
let bundle = Services.strings.createBundle("chrome://browser/locale/accounts.properties");
let title = bundle.GetStringFromName("syncStartNotification.title");
let body = bundle.GetStringFromName("syncStartNotification.body");
let clickCallback = (subject, topic, data) => {
if (topic != "alertclickcallback")
return;
this._openPreferences("sync");
}
AlertsService.showAlertNotification(null, title, body, true, null, clickCallback);
},
_migrateUI: function BG__migrateUI() {
const UI_VERSION = 38;
const BROWSER_DOCURL = "chrome://browser/content/browser.xul";

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.276
Current extension version is: 1.5.281

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.276';
var pdfjsBuild = '41f978c';
var pdfjsVersion = '1.5.281';
var pdfjsBuild = '5a5bb99';
var pdfjsFilePath =
typeof document !== 'undefined' && document.currentScript ?

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.276';
var pdfjsBuild = '41f978c';
var pdfjsVersion = '1.5.281';
var pdfjsBuild = '5a5bb99';
var pdfjsFilePath =
typeof document !== 'undefined' && document.currentScript ?
@ -37210,7 +37210,7 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
case OPS.setFont:
flushTextContentItem();
textState.fontSize = args[1];
next(handleSetFont(args[0].name));
next(handleSetFont(args[0].name, null));
return;
case OPS.setTextRise:
flushTextContentItem();
@ -37416,21 +37416,17 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
var dictName = args[0];
var extGState = resources.get('ExtGState');
if (!isDict(extGState) || !extGState.has(dictName.name)) {
if (!isDict(extGState) || !isName(dictName)) {
break;
}
var gsStateMap = extGState.get(dictName.name);
var gsStateFont = null;
for (var key in gsStateMap) {
if (key === 'Font') {
assert(!gsStateFont);
gsStateFont = gsStateMap[key];
}
var gState = extGState.get(dictName.name);
if (!isDict(gState)) {
break;
}
if (gsStateFont) {
textState.fontSize = gsStateFont[1];
next(handleSetFont(gsStateFont[0]));
var gStateFont = gState.get('Font');
if (gStateFont) {
textState.fontSize = gStateFont[1];
next(handleSetFont(null, gStateFont[0]));
return;
}
break;

View File

@ -2560,6 +2560,7 @@ exports.binarySearchFirstItem = binarySearchFirstItem;
var event = document.createEvent('CustomEvent');
event.initCustomEvent('find' + e.type, true, true, {
query: e.query,
phraseSearch: e.phraseSearch,
caseSensitive: e.caseSensitive,
highlightAll: e.highlightAll,
findPrevious: e.findPrevious
@ -3010,6 +3011,7 @@ var PDFFindController = (function PDFFindControllerClosure() {
this.active = false; // If active, find results will be highlighted.
this.pageContents = []; // Stores the text for each page.
this.pageMatches = [];
this.pageMatchesLength = null;
this.matchCount = 0;
this.selected = { // Currently selected match.
pageIdx: -1,
@ -3036,10 +3038,114 @@ var PDFFindController = (function PDFFindControllerClosure() {
});
},
// Helper for multiple search - fills matchesWithLength array
// and takes into account cases when one search term
// include another search term (for example, "tamed tame" or "this is").
// Looking for intersecting terms in the 'matches' and
// leave elements with a longer match-length.
_prepareMatches: function PDFFindController_prepareMatches(
matchesWithLength, matches, matchesLength) {
function isSubTerm(matchesWithLength, currentIndex) {
var currentElem, prevElem, nextElem;
currentElem = matchesWithLength[currentIndex];
nextElem = matchesWithLength[currentIndex + 1];
// checking for cases like "TAMEd TAME"
if (currentIndex < matchesWithLength.length - 1 &&
currentElem.match === nextElem.match) {
currentElem.skipped = true;
return true;
}
// checking for cases like "thIS IS"
for (var i = currentIndex - 1; i >= 0; i--) {
prevElem = matchesWithLength[i];
if (prevElem.skipped) {
continue;
}
if (prevElem.match + prevElem.matchLength < currentElem.match) {
break;
}
if (prevElem.match + prevElem.matchLength >=
currentElem.match + currentElem.matchLength) {
currentElem.skipped = true;
return true;
}
}
return false;
}
var i, len;
// Sorting array of objects { match: <match>, matchLength: <matchLength> }
// in increasing index first and then the lengths.
matchesWithLength.sort(function(a, b) {
return a.match === b.match ?
a.matchLength - b.matchLength : a.match - b.match;
});
for (i = 0, len = matchesWithLength.length; i < len; i++) {
if (isSubTerm(matchesWithLength, i)) {
continue;
}
matches.push(matchesWithLength[i].match);
matchesLength.push(matchesWithLength[i].matchLength);
}
},
calcFindPhraseMatch: function PDFFindController_calcFindPhraseMatch(
query, pageIndex, pageContent) {
var matches = [];
var queryLen = query.length;
var matchIdx = -queryLen;
while (true) {
matchIdx = pageContent.indexOf(query, matchIdx + queryLen);
if (matchIdx === -1) {
break;
}
matches.push(matchIdx);
}
this.pageMatches[pageIndex] = matches;
},
calcFindWordMatch: function PDFFindController_calcFindWordMatch(
query, pageIndex, pageContent) {
var matchesWithLength = [];
// Divide the query into pieces and search for text on each piece.
var queryArray = query.match(/\S+/g);
var subquery, subqueryLen, matchIdx;
for (var i = 0, len = queryArray.length; i < len; i++) {
subquery = queryArray[i];
subqueryLen = subquery.length;
matchIdx = -subqueryLen;
while (true) {
matchIdx = pageContent.indexOf(subquery, matchIdx + subqueryLen);
if (matchIdx === -1) {
break;
}
// Other searches do not, so we store the length.
matchesWithLength.push({
match: matchIdx,
matchLength: subqueryLen,
skipped: false
});
}
}
// Prepare arrays for store the matches.
if (!this.pageMatchesLength) {
this.pageMatchesLength = [];
}
this.pageMatchesLength[pageIndex] = [];
this.pageMatches[pageIndex] = [];
// Sort matchesWithLength, clean up intersecting terms
// and put the result into the two arrays.
this._prepareMatches(matchesWithLength, this.pageMatches[pageIndex],
this.pageMatchesLength[pageIndex]);
},
calcFindMatch: function PDFFindController_calcFindMatch(pageIndex) {
var pageContent = this.normalize(this.pageContents[pageIndex]);
var query = this.normalize(this.state.query);
var caseSensitive = this.state.caseSensitive;
var phraseSearch = this.state.phraseSearch;
var queryLen = query.length;
if (queryLen === 0) {
@ -3052,16 +3158,12 @@ var PDFFindController = (function PDFFindControllerClosure() {
query = query.toLowerCase();
}
var matches = [];
var matchIdx = -queryLen;
while (true) {
matchIdx = pageContent.indexOf(query, matchIdx + queryLen);
if (matchIdx === -1) {
break;
}
matches.push(matchIdx);
if (phraseSearch) {
this.calcFindPhraseMatch(query, pageIndex, pageContent);
} else {
this.calcFindWordMatch(query, pageIndex, pageContent);
}
this.pageMatches[pageIndex] = matches;
this.updatePage(pageIndex);
if (this.resumePageIdx === pageIndex) {
this.resumePageIdx = null;
@ -3069,8 +3171,8 @@ var PDFFindController = (function PDFFindControllerClosure() {
}
// Update the matches count
if (matches.length > 0) {
this.matchCount += matches.length;
if (this.pageMatches[pageIndex].length > 0) {
this.matchCount += this.pageMatches[pageIndex].length;
this.updateUIResultsCount();
}
},
@ -3165,6 +3267,7 @@ var PDFFindController = (function PDFFindControllerClosure() {
this.resumePageIdx = null;
this.pageMatches = [];
this.matchCount = 0;
this.pageMatchesLength = null;
var self = this;
for (var i = 0; i < numPages; i++) {
@ -3980,6 +4083,7 @@ var PDFFindBar = (function PDFFindBarClosure() {
type: type,
query: this.findField.value,
caseSensitive: this.caseSensitive.checked,
phraseSearch: true,
highlightAll: this.highlightAll.checked,
findPrevious: findPrev
});
@ -4651,6 +4755,13 @@ var PDFLinkService = (function () {
setHash: function PDFLinkService_setHash(hash) {
if (hash.indexOf('=') >= 0) {
var params = parseQueryString(hash);
if ('search' in params) {
this.eventBus.dispatch('findfromurlhash', {
source: this,
query: params['search'].replace(/"/g, ''),
phraseSearch: (params['phrase'] === 'true')
});
}
// borrowing syntax from "Parameters for Opening PDF Files"
if ('nameddest' in params) {
if (this.pdfHistory) {
@ -5690,7 +5801,8 @@ var TextLayerBuilder = (function TextLayerBuilderClosure() {
this.divContentDone = true;
},
convertMatches: function TextLayerBuilder_convertMatches(matches) {
convertMatches: function TextLayerBuilder_convertMatches(matches,
matchesLength) {
var i = 0;
var iIndex = 0;
var bidiTexts = this.textContent.items;
@ -5698,7 +5810,9 @@ var TextLayerBuilder = (function TextLayerBuilderClosure() {
var queryLen = (this.findController === null ?
0 : this.findController.state.query.length);
var ret = [];
if (!matches) {
return ret;
}
for (var m = 0, len = matches.length; m < len; m++) {
// Calculate the start position.
var matchIdx = matches[m];
@ -5721,7 +5835,11 @@ var TextLayerBuilder = (function TextLayerBuilderClosure() {
};
// Calculate the end position.
matchIdx += queryLen;
if (matchesLength) { // multiterm search
matchIdx += matchesLength[m];
} else { // phrase search
matchIdx += queryLen;
}
// Somewhat the same array as above, but use > instead of >= to get
// the end position right.
@ -5863,8 +5981,14 @@ var TextLayerBuilder = (function TextLayerBuilderClosure() {
// Convert the matches on the page controller into the match format
// used for the textLayer.
this.matches = this.convertMatches(this.findController === null ?
[] : (this.findController.pageMatches[this.pageIdx] || []));
var pageMatches, pageMatchesLength;
if (this.findController !== null) {
pageMatches = this.findController.pageMatches[this.pageIdx] || null;
pageMatchesLength = (this.findController.pageMatchesLength) ?
this.findController.pageMatchesLength[this.pageIdx] || null : null;
}
this.matches = this.convertMatches(pageMatches, pageMatchesLength);
this.renderMatches(this.matches);
},
@ -7947,6 +8071,7 @@ var PDFViewerApplication = {
eventBus.on('rotateccw', webViewerRotateCcw);
eventBus.on('documentproperties', webViewerDocumentProperties);
eventBus.on('find', webViewerFind);
eventBus.on('findfromurlhash', webViewerFindFromUrlHash);
}
};
@ -8436,12 +8561,23 @@ function webViewerDocumentProperties() {
function webViewerFind(e) {
PDFViewerApplication.findController.executeCommand('find' + e.type, {
query: e.query,
phraseSearch: e.phraseSearch,
caseSensitive: e.caseSensitive,
highlightAll: e.highlightAll,
findPrevious: e.findPrevious
});
}
function webViewerFindFromUrlHash(e) {
PDFViewerApplication.findController.executeCommand('find', {
query: e.query,
phraseSearch: e.phraseSearch,
caseSensitive: false,
highlightAll: true,
findPrevious: false
});
}
function webViewerScaleChanging(e) {
var appConfig = PDFViewerApplication.appConfig;
appConfig.toolbar.zoomOut.disabled = (e.scale === MIN_SCALE);
@ -8585,6 +8721,7 @@ window.addEventListener('keydown', function keydown(evt) {
if (findState) {
PDFViewerApplication.findController.executeCommand('findagain', {
query: findState.query,
phraseSearch: findState.phraseSearch,
caseSensitive: findState.caseSensitive,
highlightAll: findState.highlightAll,
findPrevious: cmd === 5 || cmd === 12
@ -8958,6 +9095,7 @@ Preferences._readFromStorage = function (prefObj) {
source: window,
type: evt.type.substring('find'.length),
query: evt.detail.query,
phraseSearch: true,
caseSensitive: !!evt.detail.caseSensitive,
highlightAll: !!evt.detail.highlightAll,
findPrevious: !!evt.detail.findPrevious

View File

@ -23,3 +23,8 @@ verificationSentTitle = Verification Sent
verificationSentBody = A verification link has been sent to %S.
verificationNotSentTitle = Unable to Send Verification
verificationNotSentBody = We are unable to send a verification mail at this time, please try again later.
# LOCALIZATION NOTE (syncStartNotification.title, syncStartNotification.body)
# These strings are used in a notification shown after Sync is connected.
syncStartNotification.title = Sync enabled
syncStartNotification.body = Firefox will begin syncing momentarily.

View File

@ -110,8 +110,6 @@ These should match what Safari and other Apple applications use on OS X Lion. --
<!ENTITY fxaSignedIn.tooltip "Open &syncBrand.shortName.label; preferences">
<!ENTITY fxaSignInError.label "Reconnect to &syncBrand.shortName.label;">
<!ENTITY fxaUnverified.label "Verify Your Account">
<!ENTITY syncStartPanel2.heading "&syncBrand.shortName.label; enabled">
<!ENTITY syncStartPanel2.subTitle "&brandShortName; will begin syncing momentarily.">
<!ENTITY fullScreenMinimize.tooltip "Minimize">

View File

@ -16,6 +16,45 @@ this.SitePermissions = {
BLOCK: Services.perms.DENY_ACTION,
SESSION: Components.interfaces.nsICookiePermission.ACCESS_SESSION,
/* Returns a list of objects representing all permissions that are currently
* set for the given URI. Each object contains the following keys:
* - id: the permissionID of the permission
* - label: the localized label
* - state: a constant representing the current permission state
* (e.g. SitePermissions.ALLOW)
* - availableStates: an array of all available states for that permission,
* represented as objects with the keys:
* - id: the state constant
* - label: the translated label of that state
*/
getPermissionsByURI: function (aURI) {
if (!this.isSupportedURI(aURI)) {
return [];
}
let permissions = [];
for (let permission of this.listPermissions()) {
let state = this.get(aURI, permission);
if (state === this.UNKNOWN) {
continue;
}
let availableStates = this.getAvailableStates(permission).map( state => {
return { id: state, label: this.getStateLabel(permission, state) };
});
let label = this.getPermissionLabel(permission);
permissions.push({
id: permission,
label: label,
state: state,
availableStates: availableStates,
});
}
return permissions;
},
/* Returns a boolean indicating whether there are any granted
* (meaning allowed or session-allowed) permissions for the given URI.
* Will return false for invalid URIs (such as file:// URLs).

View File

@ -14,6 +14,11 @@ add_task(function* testPermissionsListing() {
});
add_task(function* testHasGrantedPermissions() {
// check that it returns false on an invalid URI
// like a file URI, which doesn't support site permissions
let wrongURI = Services.io.newURI("file:///example.js", null, null)
Assert.equal(SitePermissions.hasGrantedPermissions(wrongURI), false);
let uri = Services.io.newURI("https://example.com", null, null)
Assert.equal(SitePermissions.hasGrantedPermissions(uri), false);
@ -49,3 +54,65 @@ add_task(function* testHasGrantedPermissions() {
SitePermissions.remove(uri, "pointerLock");
});
add_task(function* testGetPermissionsByURI() {
// check that it returns an empty array on an invalid URI
// like a file URI, which doesn't support site permissions
let wrongURI = Services.io.newURI("file:///example.js", null, null)
Assert.deepEqual(SitePermissions.getPermissionsByURI(wrongURI), []);
let uri = Services.io.newURI("https://example.com", null, null)
SitePermissions.set(uri, "camera", SitePermissions.ALLOW);
SitePermissions.set(uri, "cookie", SitePermissions.SESSION);
SitePermissions.set(uri, "popup", SitePermissions.BLOCK);
let permissions = SitePermissions.getPermissionsByURI(uri);
let camera = permissions.find(({id}) => id === "camera");
Assert.deepEqual(camera, {
id: "camera",
label: "Use the Camera",
state: SitePermissions.ALLOW,
availableStates: [
{ id: SitePermissions.UNKNOWN, label: "Always Ask" },
{ id: SitePermissions.ALLOW, label: "Allow" },
{ id: SitePermissions.BLOCK, label: "Block" },
]
});
// check that removed permissions (State.UNKNOWN) are skipped
SitePermissions.remove(uri, "camera");
permissions = SitePermissions.getPermissionsByURI(uri);
camera = permissions.find(({id}) => id === "camera");
Assert.equal(camera, undefined);
// check that different available state values are represented
let cookie = permissions.find(({id}) => id === "cookie");
Assert.deepEqual(cookie, {
id: "cookie",
label: "Set Cookies",
state: SitePermissions.SESSION,
availableStates: [
{ id: SitePermissions.ALLOW, label: "Allow" },
{ id: SitePermissions.SESSION, label: "Allow for Session" },
{ id: SitePermissions.BLOCK, label: "Block" },
]
});
let popup = permissions.find(({id}) => id === "popup");
Assert.deepEqual(popup, {
id: "popup",
label: "Open Pop-up Windows",
state: SitePermissions.BLOCK,
availableStates: [
{ id: SitePermissions.ALLOW, label: "Allow" },
{ id: SitePermissions.BLOCK, label: "Block" },
]
});
SitePermissions.remove(uri, "cookie");
SitePermissions.remove(uri, "popup");
});

View File

@ -1747,33 +1747,6 @@ toolbarbutton.chevron > .toolbarbutton-icon {
margin-top: 10px;
}
/* Sync Panel */
.sync-panel-icon {
height:32px;
width: 32px;
background: url("chrome://browser/content/abouthome/sync.png") top left no-repeat;
}
.sync-panel-inner {
width: 0;
padding-left: 10px;
}
.sync-panel-button-box {
margin-top: 1em;
}
#sync-start-panel-title {
font-size: 120%;
font-weight: bold;
margin-bottom: 5px;
}
#sync-start-panel-subtitle {
margin-bottom: 0;
}
/* Status panel */
.statuspanel-label {

View File

@ -3260,55 +3260,6 @@ menulist.translate-infobar-element > .menulist-dropmarker {
margin-top: .5em;
}
/* Sync Panels */
.sync-panel-icon {
height:32px;
width: 32px;
background: url("chrome://browser/content/abouthome/sync.png") top left no-repeat;
}
@media (min-resolution: 2dppx) {
.sync-panel-icon {
background: url("chrome://browser/content/abouthome/sync@2x.png") top left no-repeat;
background-size: 32px 32px;
}
}
.sync-panel-inner {
width: 0;
padding-left: 10px;
}
.sync-panel-button-box {
margin-top: 1em;
}
.sync-panel-button {
@hudButton@
margin: 0;
min-width: 72px;
min-height: 22px;
}
.sync-panel-button:hover:active {
@hudButtonPressed@
}
.sync-panel-button:-moz-focusring {
@hudButtonFocused@
}
#sync-start-panel-title {
font-size: 120%;
font-weight: bold;
margin-bottom: 5px;
}
#sync-start-panel-subtitle {
margin-bottom: 0;
}
/* Status panel */
.statuspanel-label {

View File

@ -2425,33 +2425,6 @@ notification[value="translation"] {
margin-top: .5em;
}
/* Sync Panel */
.sync-panel-icon {
height:32px;
width: 32px;
background: url("chrome://browser/content/abouthome/sync.png") top left no-repeat;
}
.sync-panel-inner {
width: 0;
padding-left: 10px;
}
.sync-panel-button-box {
margin-top: 1em;
}
#sync-start-panel-title {
font-size: 120%;
font-weight: bold;
margin-bottom: 5px;
}
#sync-start-panel-subtitle {
margin-bottom: 0;
}
/* Status panel */
.statuspanel-label {

View File

@ -12,8 +12,8 @@ const { require } = Cu.import("resource://devtools/shared/Loader.jsm", {});
const promise = require("promise");
const Services = require("Services");
const EventEmitter = require("devtools/shared/event-emitter");
const { CallWatcherFront } = require("devtools/server/actors/call-watcher");
const { CanvasFront } = require("devtools/server/actors/canvas");
const { CallWatcherFront } = require("devtools/shared/fronts/call-watcher");
const { CanvasFront } = require("devtools/shared/fronts/canvas");
const DevToolsUtils = require("devtools/shared/DevToolsUtils");
const { LocalizationHelper } = require("devtools/client/shared/l10n");
const { Heritage, WidgetMethods, setNamedTimeout, clearNamedTimeout,

View File

@ -8,7 +8,7 @@
const { Cc, Ci, Cu, Cr } = require("chrome");
const promise = require("promise");
const EventEmitter = require("devtools/shared/event-emitter");
const { CanvasFront } = require("devtools/server/actors/canvas");
const { CanvasFront } = require("devtools/shared/fronts/canvas");
const DevToolsUtils = require("devtools/shared/DevToolsUtils");
function CanvasDebuggerPanel(iframeWindow, toolbox) {

View File

@ -12,8 +12,8 @@ var promise = require("promise");
var { gDevTools } = require("devtools/client/framework/devtools");
var { DebuggerClient } = require("devtools/shared/client/main");
var { DebuggerServer } = require("devtools/server/main");
var { CallWatcherFront } = require("devtools/server/actors/call-watcher");
var { CanvasFront } = require("devtools/server/actors/canvas");
var { CallWatcherFront } = require("devtools/shared/fronts/call-watcher");
var { CanvasFront } = require("devtools/shared/fronts/canvas");
var { setTimeout } = require("sdk/timers");
var DevToolsUtils = require("devtools/shared/DevToolsUtils");
var { TargetFactory } = require("devtools/client/framework/target");

View File

@ -6,7 +6,8 @@
"use strict";
const { Cu, Ci } = require("chrome");
const { Cu } = require("chrome");
const nodeConstants = require("devtools/shared/dom-node-constants.js")
const { getRootBindingParent } = require("devtools/shared/layout/utils");
var EventEmitter = require("devtools/shared/event-emitter");
@ -162,9 +163,9 @@ Selection.prototype = {
setNodeFront: function (value, reason = "unknown") {
this.reason = reason;
// If a singleTextChild text node is being set, then set it's parent instead.
// If an inlineTextChild text node is being set, then set it's parent instead.
let parentNode = value && value.parentNode();
if (value && parentNode && parentNode.singleTextChild === value) {
if (value && parentNode && parentNode.inlineTextChild === value) {
value = parentNode;
}
@ -262,8 +263,8 @@ Selection.prototype = {
// Node type
isElementNode: function () {
return this.isNode() && this.nodeFront.nodeType == Ci.nsIDOMNode.ELEMENT_NODE;
isElementNode: function() {
return this.isNode() && this.nodeFront.nodeType == nodeConstants.ELEMENT_NODE;
},
isPseudoElementNode: function () {
@ -274,36 +275,36 @@ Selection.prototype = {
return this.isNode() && this.nodeFront.isAnonymous;
},
isAttributeNode: function () {
return this.isNode() && this.nodeFront.nodeType == Ci.nsIDOMNode.ATTRIBUTE_NODE;
isAttributeNode: function() {
return this.isNode() && this.nodeFront.nodeType == nodeConstants.ATTRIBUTE_NODE;
},
isTextNode: function () {
return this.isNode() && this.nodeFront.nodeType == Ci.nsIDOMNode.TEXT_NODE;
isTextNode: function() {
return this.isNode() && this.nodeFront.nodeType == nodeConstants.TEXT_NODE;
},
isCDATANode: function () {
return this.isNode() && this.nodeFront.nodeType == Ci.nsIDOMNode.CDATA_SECTION_NODE;
isCDATANode: function() {
return this.isNode() && this.nodeFront.nodeType == nodeConstants.CDATA_SECTION_NODE;
},
isEntityRefNode: function () {
return this.isNode() && this.nodeFront.nodeType == Ci.nsIDOMNode.ENTITY_REFERENCE_NODE;
isEntityRefNode: function() {
return this.isNode() && this.nodeFront.nodeType == nodeConstants.ENTITY_REFERENCE_NODE;
},
isEntityNode: function () {
return this.isNode() && this.nodeFront.nodeType == Ci.nsIDOMNode.ENTITY_NODE;
isEntityNode: function() {
return this.isNode() && this.nodeFront.nodeType == nodeConstants.ENTITY_NODE;
},
isProcessingInstructionNode: function () {
return this.isNode() && this.nodeFront.nodeType == Ci.nsIDOMNode.PROCESSING_INSTRUCTION_NODE;
isProcessingInstructionNode: function() {
return this.isNode() && this.nodeFront.nodeType == nodeConstants.PROCESSING_INSTRUCTION_NODE;
},
isCommentNode: function () {
return this.isNode() && this.nodeFront.nodeType == Ci.nsIDOMNode.PROCESSING_INSTRUCTION_NODE;
isCommentNode: function() {
return this.isNode() && this.nodeFront.nodeType == nodeConstants.PROCESSING_INSTRUCTION_NODE;
},
isDocumentNode: function () {
return this.isNode() && this.nodeFront.nodeType == Ci.nsIDOMNode.DOCUMENT_NODE;
isDocumentNode: function() {
return this.isNode() && this.nodeFront.nodeType == nodeConstants.DOCUMENT_NODE;
},
/**
@ -324,15 +325,15 @@ Selection.prototype = {
this.nodeFront.nodeName === "HEAD";
},
isDocumentTypeNode: function () {
return this.isNode() && this.nodeFront.nodeType == Ci.nsIDOMNode.DOCUMENT_TYPE_NODE;
isDocumentTypeNode: function() {
return this.isNode() && this.nodeFront.nodeType == nodeConstants.DOCUMENT_TYPE_NODE;
},
isDocumentFragmentNode: function () {
return this.isNode() && this.nodeFront.nodeType == Ci.nsIDOMNode.DOCUMENT_FRAGMENT_NODE;
isDocumentFragmentNode: function() {
return this.isNode() && this.nodeFront.nodeType == nodeConstants.DOCUMENT_FRAGMENT_NODE;
},
isNotationNode: function () {
return this.isNode() && this.nodeFront.nodeType == Ci.nsIDOMNode.NOTATION_NODE;
isNotationNode: function() {
return this.isNode() && this.nodeFront.nodeType == nodeConstants.NOTATION_NODE;
},
};

View File

@ -7,7 +7,7 @@
// `actorHasMethod` and `getTrait`.
var { WebAudioFront } =
require("devtools/server/actors/webaudio");
require("devtools/shared/fronts/webaudio");
function* testTarget(client, target) {
yield target.makeRemote();

View File

@ -30,13 +30,17 @@ add_task(function* () {
is(getTitle(), `Developer Tools - Page title - ${URL}`,
"Devtools title correct after switching to detached window host");
// Wait for tick to avoid unexpected 'popuphidden' event, which
// blocks the frame popup menu opened below. See also bug 1276873
yield waitForTick();
// Open frame menu and wait till it's available on the screen.
let btn = toolbox.doc.getElementById("command-button-frames");
let menu = toolbox.showFramesMenu({target: btn});
yield once(menu, "open");
// Verify that the frame list menu is populated
let frames = menu.menuitems;
let frames = menu.items;
is(frames.length, 2, "We have both frames in the list");
let topFrameBtn = frames.filter(b => b.label == URL)[0];

View File

@ -67,7 +67,7 @@ loader.lazyRequireGetter(this, "createPerformanceFront",
loader.lazyRequireGetter(this, "system",
"devtools/shared/system");
loader.lazyRequireGetter(this, "getPreferenceFront",
"devtools/server/actors/preference", true);
"devtools/shared/fronts/preference", true);
loader.lazyRequireGetter(this, "KeyShortcuts",
"devtools/client/shared/key-shortcuts", true);
loader.lazyRequireGetter(this, "ZoomKeys",

View File

@ -24,6 +24,8 @@ loader.lazyRequireGetter(this, "overlays",
"devtools/client/inspector/shared/style-inspector-overlays");
loader.lazyRequireGetter(this, "StyleInspectorMenu",
"devtools/client/inspector/shared/style-inspector-menu");
loader.lazyRequireGetter(this, "KeyShortcuts",
"devtools/client/shared/key-shortcuts", true);
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
@ -155,12 +157,10 @@ function CssComputedView(inspector, document, pageStyle) {
// Create bound methods.
this.focusWindow = this.focusWindow.bind(this);
this._onKeypress = this._onKeypress.bind(this);
this._onContextMenu = this._onContextMenu.bind(this);
this._onClick = this._onClick.bind(this);
this._onCopy = this._onCopy.bind(this);
this._onFilterStyles = this._onFilterStyles.bind(this);
this._onFilterKeyPress = this._onFilterKeyPress.bind(this);
this._onClearSearch = this._onClearSearch.bind(this);
this._onIncludeBrowserStyles = this._onIncludeBrowserStyles.bind(this);
this._onFilterTextboxContextMenu =
@ -174,13 +174,15 @@ function CssComputedView(inspector, document, pageStyle) {
this.includeBrowserStylesCheckbox =
doc.getElementById("browser-style-checkbox");
this.styleDocument.addEventListener("keypress", this._onKeypress);
this.shortcuts = new KeyShortcuts({ window: this.styleWindow });
this._onShortcut = this._onShortcut.bind(this);
this.shortcuts.on("CmdOrCtrl+F", this._onShortcut);
this.shortcuts.on("Escape", this._onShortcut);
this.styleDocument.addEventListener("mousedown", this.focusWindow);
this.element.addEventListener("click", this._onClick);
this.element.addEventListener("copy", this._onCopy);
this.element.addEventListener("contextmenu", this._onContextMenu);
this.searchField.addEventListener("input", this._onFilterStyles);
this.searchField.addEventListener("keypress", this._onFilterKeyPress);
this.searchField.addEventListener("contextmenu",
this._onFilterTextboxContextMenu);
this.searchClearButton.addEventListener("click", this._onClearSearch);
@ -502,17 +504,19 @@ CssComputedView.prototype = {
},
/**
* Handle the keypress event in the computed view.
* Handle the shortcut events in the computed view.
*/
_onKeypress: function (event) {
_onShortcut: function (name, event) {
if (!event.target.closest("#sidebar-panel-computedview")) {
return;
}
let isOSX = Services.appinfo.OS === "Darwin";
if (((isOSX && event.metaKey && !event.ctrlKey && !event.altKey) ||
(!isOSX && event.ctrlKey && !event.metaKey && !event.altKey)) &&
event.key === "f") {
// Handle the search box's keypress event. If the escape key is pressed,
// clear the search box field.
if (name === "Escape" && event.target === this.searchField &&
this._onClearSearch()) {
event.preventDefault();
event.stopPropagation();
} else if (name === "CmdOrCtrl+F") {
this.searchField.focus();
event.preventDefault();
}
@ -553,18 +557,6 @@ CssComputedView.prototype = {
}, filterTimeout);
},
/**
* Handle the search box's keypress event. If the escape key is pressed,
* clear the search box field.
*/
_onFilterKeyPress: function (event) {
if (event.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_ESCAPE &&
this._onClearSearch()) {
event.preventDefault();
event.stopPropagation();
}
},
/**
* Context menu handler for filter style search box.
*/
@ -776,7 +768,6 @@ CssComputedView.prototype = {
this.element.removeEventListener("copy", this._onCopy);
this.element.removeEventListener("contextmenu", this._onContextMenu);
this.searchField.removeEventListener("input", this._onFilterStyles);
this.searchField.removeEventListener("keypress", this._onFilterKeyPress);
this.searchField.removeEventListener("contextmenu",
this._onFilterTextboxContextMenu);
this.searchClearButton.removeEventListener("click", this._onClearSearch);
@ -957,18 +948,15 @@ PropertyView.prototype = {
// Make it keyboard navigable
this.element.setAttribute("tabindex", "0");
this.onKeyDown = (event) => {
let keyEvent = Ci.nsIDOMKeyEvent;
if (event.keyCode === keyEvent.DOM_VK_F1) {
this.mdnLinkClick();
event.preventDefault();
}
if (event.keyCode === keyEvent.DOM_VK_RETURN ||
event.keyCode === keyEvent.DOM_VK_SPACE) {
this.onMatchedToggle(event);
}
};
this.element.addEventListener("keydown", this.onKeyDown, false);
this.shortcuts = new KeyShortcuts({
window: this.tree.styleWindow,
target: this.element
});
this.shortcuts.on("F1", (name, event) => {
this.mdnLinkClick(event);
});
this.shortcuts.on("Return", (name, event) => this.onMatchedToggle(event));
this.shortcuts.on("Space", (name, event) => this.onMatchedToggle(event));
let nameContainer = doc.createElementNS(HTML_NS, "div");
nameContainer.className = "property-name-container";
@ -1116,7 +1104,11 @@ PropertyView.prototype = {
textContent: selector.source
});
link.addEventListener("click", selector.openStyleEditor, false);
link.addEventListener("keydown", selector.maybeOpenStyleEditor, false);
let shortcuts = new KeyShortcuts({
window: this.tree.styleWindow,
target: link
});
shortcuts.on("Return", () => selector.openStyleEditor());
let status = createChild(p, "span", {
dir: "ltr",
@ -1191,6 +1183,7 @@ PropertyView.prototype = {
browserWin.openUILinkIn(this.link, "tab");
}
event.preventDefault();
event.stopPropagation();
},
/**
@ -1198,7 +1191,7 @@ PropertyView.prototype = {
*/
destroy: function () {
this.element.removeEventListener("dblclick", this.onMatchedToggle, false);
this.element.removeEventListener("keydown", this.onKeyDown, false);
this.shortcuts.destroy();
this.element = null;
this.matchedExpander.removeEventListener("click", this.onMatchedToggle,
@ -1226,7 +1219,6 @@ function SelectorView(tree, selectorInfo) {
this._cacheStatusNames();
this.openStyleEditor = this.openStyleEditor.bind(this);
this.maybeOpenStyleEditor = this.maybeOpenStyleEditor.bind(this);
this.ready = this.updateSourceLink();
}
@ -1368,16 +1360,6 @@ SelectorView.prototype = {
return promise.resolve(oldSource);
},
/**
* Open the style editor if the RETURN key was pressed.
*/
maybeOpenStyleEditor: function (event) {
let keyEvent = Ci.nsIDOMKeyEvent;
if (event.keyCode === keyEvent.DOM_VK_RETURN) {
this.openStyleEditor();
}
},
/**
* When a css link is clicked this method is called in order to either:
* 1. Open the link in view source (for chrome stylesheets).

View File

@ -18,6 +18,7 @@ const {executeSoon} = require("devtools/shared/DevToolsUtils");
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");
loader.lazyRequireGetter(this, "CSS", "CSS");
@ -1254,15 +1255,15 @@ InspectorPanel.prototype = {
let node = this.selection.nodeFront;
switch (node.nodeType) {
case Ci.nsIDOMNode.ELEMENT_NODE :
case nodeConstants.ELEMENT_NODE :
this._copyLongString(this.walker.outerHTML(node));
break;
case Ci.nsIDOMNode.COMMENT_NODE :
case nodeConstants.COMMENT_NODE :
this._getLongString(node.getNodeValue()).then(comment => {
clipboardHelper.copyString("<!--" + comment + "-->");
});
break;
case Ci.nsIDOMNode.DOCUMENT_TYPE_NODE :
case nodeConstants.DOCUMENT_TYPE_NODE :
clipboardHelper.copyString(node.doctypeString);
break;
}

View File

@ -44,13 +44,12 @@ const EventEmitter = require("devtools/shared/event-emitter");
const Heritage = require("sdk/core/heritage");
const {parseAttribute} =
require("devtools/client/shared/node-attribute-parser");
const ELLIPSIS = Services.prefs.getComplexValue("intl.ellipsis",
Ci.nsIPrefLocalizedString).data;
const {Task} = require("devtools/shared/task");
const {scrollIntoViewIfNeeded} = require("devtools/shared/layout/utils");
const {PrefObserver} = require("devtools/client/styleeditor/utils");
const {KeyShortcuts} = require("devtools/client/shared/key-shortcuts");
const {template} = require("devtools/shared/gcli/templater");
const nodeConstants = require("devtools/shared/dom-node-constants");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
@ -219,7 +218,7 @@ MarkupView.prototype = {
let container = target.container;
if (this._hoveredNode !== container.node) {
if (container.node.nodeType !== Ci.nsIDOMNode.TEXT_NODE) {
if (container.node.nodeType !== nodeConstants.TEXT_NODE) {
this._showBoxModel(container.node);
} else {
this._hideBoxModel();
@ -843,7 +842,7 @@ MarkupView.prototype = {
*/
deleteNode: function (node, moveBackward) {
if (node.isDocumentElement ||
node.nodeType == Ci.nsIDOMNode.DOCUMENT_TYPE_NODE ||
node.nodeType == nodeConstants.DOCUMENT_TYPE_NODE ||
node.isAnonymous) {
return;
}
@ -870,6 +869,8 @@ MarkupView.prototype = {
}
});
}, () => {
let isValidSibling = nextSibling && !nextSibling.isPseudoElement;
nextSibling = isValidSibling ? nextSibling : null;
this.walker.insertBefore(node, parent, nextSibling);
});
}).then(null, console.error);
@ -928,10 +929,10 @@ MarkupView.prototype = {
container = new RootContainer(this, node);
this._elt.appendChild(container.elt);
this._rootNode = node;
} else if (nodeType == Ci.nsIDOMNode.ELEMENT_NODE && !isPseudoElement) {
} else if (nodeType == nodeConstants.ELEMENT_NODE && !isPseudoElement) {
container = new MarkupElementContainer(this, node, this._inspector);
} else if (nodeType == Ci.nsIDOMNode.COMMENT_NODE ||
nodeType == Ci.nsIDOMNode.TEXT_NODE) {
} else if (nodeType == nodeConstants.COMMENT_NODE ||
nodeType == nodeConstants.TEXT_NODE) {
container = new MarkupTextContainer(this, node, this._inspector);
} else {
container = new MarkupReadOnlyContainer(this, node, this._inspector);
@ -989,6 +990,10 @@ MarkupView.prototype = {
// accessibility where necessary.
this._updateChildren(container, {flash: true}).then(() =>
container.updateLevel());
} else if (type === "inlineTextChild") {
container.childrenDirty = true;
this._updateChildren(container, {flash: true});
container.update();
}
}
@ -1549,19 +1554,19 @@ MarkupView.prototype = {
return promise.resolve(container);
}
if (container.singleTextChild
&& container.singleTextChild != container.node.singleTextChild) {
if (container.inlineTextChild
&& container.inlineTextChild != container.node.inlineTextChild) {
// This container was doing double duty as a container for a single
// text child, back that out.
this._containers.delete(container.singleTextChild);
container.clearSingleTextChild();
this._containers.delete(container.inlineTextChild);
container.clearInlineTextChild();
if (container.hasChildren && container.selected) {
container.setExpanded(true);
}
}
if (container.node.singleTextChild) {
if (container.node.inlineTextChild) {
container.setExpanded(false);
// this container will do double duty as the container for the single
// text child.
@ -1569,9 +1574,9 @@ MarkupView.prototype = {
container.children.removeChild(container.children.firstChild);
}
container.setSingleTextChild(container.node.singleTextChild);
container.setInlineTextChild(container.node.inlineTextChild);
this._containers.set(container.node.singleTextChild, container);
this._containers.set(container.node.inlineTextChild, container);
container.childrenDirty = false;
return promise.resolve(container);
}
@ -1824,7 +1829,7 @@ MarkupView.prototype = {
nextSibling = null;
}
if (parent.nodeType !== Ci.nsIDOMNode.ELEMENT_NODE) {
if (parent.nodeType !== nodeConstants.ELEMENT_NODE) {
return null;
}
@ -2008,7 +2013,7 @@ MarkupContainer.prototype = {
* True if the current node can be expanded.
*/
get canExpand() {
return this._hasChildren && !this.node.singleTextChild;
return this._hasChildren && !this.node.inlineTextChild;
},
/**
@ -2568,9 +2573,9 @@ function MarkupTextContainer(markupView, node) {
MarkupContainer.prototype.initialize.call(this, markupView, node,
"textcontainer");
if (node.nodeType == Ci.nsIDOMNode.TEXT_NODE) {
if (node.nodeType == nodeConstants.TEXT_NODE) {
this.editor = new TextEditor(this, node, "text");
} else if (node.nodeType == Ci.nsIDOMNode.COMMENT_NODE) {
} else if (node.nodeType == nodeConstants.COMMENT_NODE) {
this.editor = new TextEditor(this, node, "comment");
} else {
throw new Error("Invalid node for MarkupTextContainer");
@ -2595,7 +2600,7 @@ function MarkupElementContainer(markupView, node) {
MarkupContainer.prototype.initialize.call(this, markupView, node,
"elementcontainer");
if (node.nodeType === Ci.nsIDOMNode.ELEMENT_NODE) {
if (node.nodeType === nodeConstants.ELEMENT_NODE) {
this.editor = new ElementEditor(this, node);
} else {
throw new Error("Invalid node for MarkupElementContainer");
@ -2717,13 +2722,13 @@ MarkupElementContainer.prototype = Heritage.extend(MarkupContainer.prototype, {
});
},
setSingleTextChild: function (singleTextChild) {
this.singleTextChild = singleTextChild;
setInlineTextChild: function (inlineTextChild) {
this.inlineTextChild = inlineTextChild;
this.editor.updateTextEditor();
},
clearSingleTextChild: function () {
this.singleTextChild = undefined;
clearInlineTextChild: function () {
this.inlineTextChild = undefined;
this.editor.updateTextEditor();
},
@ -2818,7 +2823,7 @@ function GenericEditor(container, node) {
if (node.isPseudoElement) {
this.tag.classList.add("theme-fg-color5");
this.tag.textContent = node.isBeforePseudoElement ? "::before" : "::after";
} else if (node.nodeType == Ci.nsIDOMNode.DOCUMENT_TYPE_NODE) {
} else if (node.nodeType == nodeConstants.DOCUMENT_TYPE_NODE) {
this.elt.classList.add("comment");
this.tag.textContent = node.doctypeString;
} else {
@ -2905,25 +2910,14 @@ TextEditor.prototype = {
},
update: function () {
if (!this.selected || !this.node.incompleteValue) {
let text = this.node.shortValue;
if (this.node.incompleteValue) {
text += ELLIPSIS;
}
this.value.textContent = text;
} else {
let longstr = null;
this.node.getNodeValue().then(ret => {
longstr = ret;
return longstr.string();
}).then(str => {
longstr.release().then(null, console.error);
if (this.selected) {
this.value.textContent = str;
this.markup.emit("text-expand");
}
}).then(null, console.error);
}
let longstr = null;
this.node.getNodeValue().then(ret => {
longstr = ret;
return longstr.string();
}).then(str => {
longstr.release().then(null, console.error);
this.value.textContent = str;
}).then(null, console.error);
},
destroy: function () {},
@ -3114,7 +3108,7 @@ ElementEditor.prototype = {
* Update the inline text editor in case of a single text child node.
*/
updateTextEditor: function () {
let node = this.node.singleTextChild;
let node = this.node.inlineTextChild;
if (this.textEditor && this.textEditor.node != node) {
this.elt.removeChild(this.textEditor.elt);

View File

@ -132,6 +132,7 @@ skip-if = e10s # Bug 1036409 - The last selected node isn't reselected
[browser_markup_tag_edit_11.js]
[browser_markup_tag_edit_12.js]
[browser_markup_tag_edit_13-other.js]
[browser_markup_textcontent_display.js]
[browser_markup_textcontent_edit_01.js]
[browser_markup_textcontent_edit_02.js]
[browser_markup_toggle_01.js]

View File

@ -89,7 +89,7 @@ const TEST_DATA = [
},
check: function* (inspector) {
let container = yield getContainerForSelector("#node1", inspector);
ok(container.singleTextChild, "Has single text child.");
ok(container.inlineTextChild, "Has single text child.");
}
},
{
@ -99,9 +99,9 @@ const TEST_DATA = [
},
check: function* (inspector) {
let container = yield getContainerForSelector("#node1", inspector);
ok(container.singleTextChild, "Has single text child.");
ok(!container.canExpand, "Can't expand container with singleTextChild.");
ok(!container.singleTextChild.canExpand, "Can't expand singleTextChild.");
ok(container.inlineTextChild, "Has single text child.");
ok(!container.canExpand, "Can't expand container with inlineTextChild.");
ok(!container.inlineTextChild.canExpand, "Can't expand inlineTextChild.");
is(container.editor.elt.querySelector(".text").textContent.trim(),
"newtext", "Single text child editor updated.");
}
@ -117,7 +117,7 @@ const TEST_DATA = [
},
check: function* (inspector) {
let container = yield getContainerForSelector("#node1", inspector);
ok(!container.singleTextChild, "Does not have single text child.");
ok(!container.inlineTextChild, "Does not have single text child.");
ok(container.canExpand, "Can expand container with child nodes.");
ok(container.editor.elt.querySelector(".text") == null,
"Single text child editor removed.");
@ -130,9 +130,9 @@ const TEST_DATA = [
},
check: function* (inspector) {
let container = yield getContainerForSelector("#node1", inspector);
ok(container.singleTextChild, "Has single text child.");
ok(!container.canExpand, "Can't expand container with singleTextChild.");
ok(!container.singleTextChild.canExpand, "Can't expand singleTextChild.");
ok(container.inlineTextChild, "Has single text child.");
ok(!container.canExpand, "Can't expand container with inlineTextChild.");
ok(!container.inlineTextChild.canExpand, "Can't expand inlineTextChild.");
ok(container.editor.elt.querySelector(".text").textContent.trim(),
"newtext", "Single text child editor updated.");
},
@ -144,7 +144,7 @@ const TEST_DATA = [
},
check: function* (inspector) {
let container = yield getContainerForSelector("#node1", inspector);
ok(!container.singleTextChild, "Does not have single text child.");
ok(!container.inlineTextChild, "Does not have single text child.");
ok(!container.canExpand, "Can't expand empty container.");
ok(container.editor.elt.querySelector(".text") == null,
"Single text child editor removed.");
@ -157,9 +157,9 @@ const TEST_DATA = [
},
check: function* (inspector) {
let container = yield getContainerForSelector("#node1", inspector);
ok(container.singleTextChild, "Has single text child.");
ok(!container.canExpand, "Can't expand container with singleTextChild.");
ok(!container.singleTextChild.canExpand, "Can't expand singleTextChild.");
ok(container.inlineTextChild, "Has single text child.");
ok(!container.canExpand, "Can't expand container with inlineTextChild.");
ok(!container.inlineTextChild.canExpand, "Can't expand inlineTextChild.");
ok(container.editor.elt.querySelector(".text").textContent.trim(),
"newtext", "Single text child editor updated.");
},

View File

@ -8,10 +8,20 @@
// Also checks that after deletion the correct element is highlighted.
// The next sibling is preferred, but the parent is a fallback.
const HTML = `<div id="parent">
const HTML = `<style type="text/css">
#pseudo::before { content: 'before'; }
#pseudo::after { content: 'after'; }
</style>
<div id="parent">
<div id="first"></div>
<div id="second"></div>
<div id="third"></div>
</div>
<div id="only-child">
<div id="fourth"></div>
</div>
<div id="pseudo">
<div id="fifth"></div>
</div>`;
const TEST_URL = "data:text/html;charset=utf-8," + encodeURIComponent(HTML);
@ -20,8 +30,8 @@ const TEST_URL = "data:text/html;charset=utf-8," + encodeURIComponent(HTML);
// - key: the key to press to delete the node (delete or back_space)
// - focusedSelector: the css selector of the node we expect to be selected as
// a result of the deletion
// - setup: an optional function that will be run before selecting and deleting
// the node
// - pseudo: (optional) if the focused node is actually supposed to be a pseudo element
// of the specified selector.
// Note that after each test case, undo is called.
const TEST_DATA = [{
selector: "#first",
@ -35,6 +45,15 @@ const TEST_DATA = [{
selector: "#third",
key: "delete",
focusedSelector: "#second"
}, {
selector: "#fourth",
key: "delete",
focusedSelector: "#only-child"
}, {
selector: "#fifth",
key: "delete",
focusedSelector: "#pseudo",
pseudo: "after"
}, {
selector: "#first",
key: "back_space",
@ -48,38 +67,25 @@ const TEST_DATA = [{
key: "back_space",
focusedSelector: "#second"
}, {
setup: function* (inspector, testActor) {
// Removing the siblings of #first in order to test with an only child.
let mutated = inspector.once("markupmutation");
yield testActor.eval(`
for (let node of content.document.querySelectorAll("#second, #third")) {
node.remove();
}
`);
yield mutated;
},
selector: "#first",
key: "delete",
focusedSelector: "#parent"
}, {
selector: "#first",
selector: "#fourth",
key: "back_space",
focusedSelector: "#parent"
focusedSelector: "#only-child"
}, {
selector: "#fifth",
key: "back_space",
focusedSelector: "#pseudo",
pseudo: "before"
}];
add_task(function* () {
let {inspector, testActor} = yield openInspectorForURL(TEST_URL);
let {inspector} = yield openInspectorForURL(TEST_URL);
for (let {setup, selector, key, focusedSelector} of TEST_DATA) {
if (setup) {
yield setup(inspector, testActor);
}
yield checkDeleteAndSelection(inspector, key, selector, focusedSelector);
for (let data of TEST_DATA) {
yield checkDeleteAndSelection(inspector, data);
}
});
function* checkDeleteAndSelection(inspector, key, selector, focusedSelector) {
function* checkDeleteAndSelection(inspector, {key, selector, focusedSelector, pseudo}) {
info("Test deleting node " + selector + " with " + key + ", " +
"expecting " + focusedSelector + " to be focused");
@ -93,6 +99,14 @@ function* checkDeleteAndSelection(inspector, key, selector, focusedSelector) {
yield Promise.all([mutated, inspector.once("inspector-updated")]);
let nodeFront = yield getNodeFront(focusedSelector, inspector);
if (pseudo) {
// Update the selector for logging in case of failure.
focusedSelector = focusedSelector + "::" + pseudo;
// Retrieve the :before or :after pseudo element of the nodeFront.
let {nodes} = yield inspector.walker.children(nodeFront);
nodeFront = pseudo === "before" ? nodes[0] : nodes[nodes.length - 1];
}
is(inspector.selection.nodeFront, nodeFront,
focusedSelector + " is selected after deletion");

View File

@ -0,0 +1,89 @@
/* vim: set ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Test the rendering of text nodes in the markup view.
const LONG_VALUE = "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do " +
"eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam.";
const SCHEMA = "data:text/html;charset=UTF-8,";
const TEST_URL = `${SCHEMA}<!DOCTYPE html>
<html>
<body>
<div id="shorttext">Short text</div>
<div id="longtext">${LONG_VALUE}</div>
<div id="shortcomment"><!--Short comment--></div>
<div id="longcomment"><!--${LONG_VALUE}--></div>
<div id="shorttext-and-node">Short text<span>Other element</span></div>
<div id="longtext-and-node">${LONG_VALUE}<span>Other element</span></div>
</body>
</html>`;
const TEST_DATA = [{
desc: "Test node containing a short text, short text nodes can be inlined.",
selector: "#shorttext",
inline: true,
value: "Short text",
}, {
desc: "Test node containing a long text, long text nodes are not inlined.",
selector: "#longtext",
inline: false,
value: LONG_VALUE,
}, {
desc: "Test node containing a short comment, comments are not inlined.",
selector: "#shortcomment",
inline: false,
value: "Short comment",
}, {
desc: "Test node containing a long comment, comments are not inlined.",
selector: "#longcomment",
inline: false,
value: LONG_VALUE,
}, {
desc: "Test node containing a short text and a span.",
selector: "#shorttext-and-node",
inline: false,
value: "Short text",
}, {
desc: "Test node containing a long text and a span.",
selector: "#longtext-and-node",
inline: false,
value: LONG_VALUE,
}];
add_task(function* () {
let {inspector, testActor} = yield openInspectorForURL(TEST_URL);
for (let data of TEST_DATA) {
yield checkNode(inspector, testActor, data);
}
});
function* checkNode(inspector, testActor, {desc, selector, inline, value}) {
info(desc);
let container = yield getContainerForSelector(selector, inspector);
let nodeValue = yield getFirstChildNodeValue(selector, testActor);
is(nodeValue, value, "The test node's text content is correct");
is(!!container.inlineTextChild, inline, "Container inlineTextChild is as expected");
is(!container.canExpand, inline, "Container canExpand property is as expected");
let textContainer;
if (inline) {
textContainer = container.elt.querySelector("pre");
ok(!!textContainer, "Text container is already rendered for inline text elements");
} else {
textContainer = container.elt.querySelector("pre");
ok(!textContainer, "Text container is not rendered for collapsed text nodes");
yield inspector.markup.expandNode(container.node);
yield waitForMultipleChildrenUpdates(inspector);
textContainer = container.elt.querySelector("pre");
ok(!!textContainer, "Text container is rendered after expanding the container");
}
is(textContainer.textContent, value, "The complete text node is rendered.");
}

View File

@ -7,6 +7,7 @@
// Test editing a node's text content
const TEST_URL = URL_ROOT + "doc_markup_edit.html";
const {DEFAULT_VALUE_SUMMARY_LENGTH} = require("devtools/server/actors/inspector");
add_task(function* () {
let {inspector, testActor} = yield openInspectorForURL(TEST_URL);
@ -26,55 +27,38 @@ add_task(function* () {
newValue: "LOREM IPSUM DOLOR SIT AMET, CONSECTETUR ADIPISCING ELIT. " +
"DONEC POSUERE PLACERAT MAGNA ET IMPERDIET.",
oldValue: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. " +
"Donec posuere placerat magna et imperdiet.",
shortValue: true
"Donec posuere placerat magna et imperdiet."
});
yield editContainer(inspector, testActor, {
selector: "#node17",
newValue: "New value",
oldValue: "LOREM IPSUM DOLOR SIT AMET, CONSECTETUR ADIPISCING ELIT. " +
"DONEC POSUERE PLACERAT MAGNA ET IMPERDIET."
});
yield editContainer(inspector, testActor, {
selector: "#node17",
newValue: "LOREM IPSUM DOLOR SIT AMET, CONSECTETUR ADIPISCING ELIT. " +
"DONEC POSUERE PLACERAT MAGNA ET IMPERDIET.",
shortValue: true
oldValue: "New value"
});
});
function* getNodeValue(selector, testActor) {
let nodeValue = yield testActor.eval(`
content.document.querySelector("${selector}").firstChild.nodeValue;
`);
return nodeValue;
}
function* editContainer(inspector, testActor,
{selector, newValue, oldValue, shortValue}) {
let nodeValue = yield getNodeValue(selector, testActor);
{selector, newValue, oldValue}) {
let nodeValue = yield getFirstChildNodeValue(selector, testActor);
is(nodeValue, oldValue, "The test node's text content is correct");
info("Changing the text content");
let onMutated = inspector.once("markupmutation");
let container = yield focusNode(selector, inspector);
let isOldValueInline = oldValue.length <= DEFAULT_VALUE_SUMMARY_LENGTH;
is(!!container.inlineTextChild, isOldValueInline, "inlineTextChild is as expected");
is(!container.canExpand, isOldValueInline, "canExpand property is as expected");
let field = container.elt.querySelector("pre");
if (shortValue) {
is(oldValue.indexOf(
field.textContent.substring(0, field.textContent.length - 1)),
0,
"The shortened value starts with the full value " + field.textContent);
ok(oldValue.length > field.textContent.length,
"The shortened value is short");
} else {
is(field.textContent, oldValue,
"The text node has the correct original value");
}
inspector.markup.markNodeAsSelected(container.node);
if (shortValue) {
info("Waiting for the text to be updated");
yield inspector.markup.once("text-expand");
}
is(field.textContent, oldValue,
"The text node has the correct original value after selecting");
setEditableFieldValue(field, newValue, inspector);
@ -82,9 +66,18 @@ function* editContainer(inspector, testActor,
info("Listening to the markupmutation event");
yield onMutated;
nodeValue = yield getNodeValue(selector, testActor);
nodeValue = yield getFirstChildNodeValue(selector, testActor);
is(nodeValue, newValue, "The test node's text content has changed");
let isNewValueInline = newValue.length <= DEFAULT_VALUE_SUMMARY_LENGTH;
is(!!container.inlineTextChild, isNewValueInline, "inlineTextChild is as expected");
is(!container.canExpand, isNewValueInline, "canExpand property is as expected");
if (isOldValueInline != isNewValueInline) {
is(container.expanded, !isNewValueInline,
"Container was automatically expanded/collapsed");
}
info("Selecting the <body> to reset the selection");
let bodyContainer = yield getContainerForSelector("body", inspector);
inspector.markup.markNodeAsSelected(bodyContainer.node);

View File

@ -17,7 +17,7 @@ add_task(function* () {
yield inspector.markup.expandAll();
yield waitForMultipleChildrenUpdates(inspector);
let nodeValue = yield getNodeValue(SELECTOR, testActor);
let nodeValue = yield getFirstChildNodeValue(SELECTOR, testActor);
let expectedValue = "line6";
is(nodeValue, expectedValue, "The test node's text content is correct");
@ -84,17 +84,10 @@ add_task(function* () {
yield sendKey("VK_RETURN", {}, editor, inspector.panelWin);
yield onMutated;
nodeValue = yield getNodeValue(SELECTOR, testActor);
nodeValue = yield getFirstChildNodeValue(SELECTOR, testActor);
is(nodeValue, expectedValue, "The test node's text content is correct");
});
function* getNodeValue(selector, testActor) {
let nodeValue = yield testActor.eval(`
content.document.querySelector("${selector}").firstChild.nodeValue;
`);
return nodeValue;
}
/**
* Check that the editor selection is at the expected positions.
*/

View File

@ -12,7 +12,7 @@ Services.scriptloader.loadSubScript(
var {getInplaceEditorForSpan: inplaceEditor} = require("devtools/client/shared/inplace-editor");
var clipboard = require("sdk/clipboard");
var {ActorRegistryFront} = require("devtools/server/actors/actor-registry");
var {ActorRegistryFront} = require("devtools/shared/fronts/actor-registry");
// If a test times out we want to see the complete log and not just the last few
// lines.
@ -89,6 +89,20 @@ var getContainerForSelector = Task.async(function* (selector, inspector) {
return container;
});
/**
* Retrieve the nodeValue for the firstChild of a provided selector on the content page.
*
* @param {String} selector
* @param {TestActorFront} testActor The current TestActorFront instance.
* @return {String} the nodeValue of the first
*/
function* getFirstChildNodeValue(selector, testActor) {
let nodeValue = yield testActor.eval(`
content.document.querySelector("${selector}").firstChild.nodeValue;
`);
return nodeValue;
}
/**
* Using the markupview's _waitForChildren function, wait for all queued
* children updates to be handled.

View File

@ -22,10 +22,6 @@ XPCOMUtils.defineLazyGetter(this, "osString", function () {
return Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime).OS;
});
XPCOMUtils.defineLazyGetter(this, "domUtils", function () {
return Cc["@mozilla.org/inspector/dom-utils;1"].getService(Ci.inIDOMUtils);
});
/**
* Rule is responsible for the following:
* Manages a single style declaration or rule.
@ -449,7 +445,7 @@ Rule.prototype = {
// Starting with FF49, StyleRuleActors provide parsed declarations.
let props = this.style.declarations;
if (!props) {
if (!props.length) {
props = parseDeclarations(this.cssProperties.isKnown,
this.style.authoredText, true);
}
@ -466,7 +462,7 @@ Rule.prototype = {
// However, we must keep all properties in order for rule
// rewriting to work properly. So, compute the "invisible"
// property here.
let invisible = this.inherited && !domUtils.isInheritedProperty(name);
let invisible = this.inherited && !this.cssProperties.isInherited(name);
let value = store.userProperties.getProperty(this.style, name,
prop.value);
let textProp = new TextProperty(this, name, value, prop.priority,

View File

@ -33,6 +33,8 @@ loader.lazyRequireGetter(this, "EventEmitter",
"devtools/shared/event-emitter");
loader.lazyRequireGetter(this, "StyleInspectorMenu",
"devtools/client/inspector/shared/style-inspector-menu");
loader.lazyRequireGetter(this, "KeyShortcuts",
"devtools/client/shared/key-shortcuts", true);
XPCOMUtils.defineLazyGetter(this, "clipboardHelper", function () {
return Cc["@mozilla.org/widget/clipboardhelper;1"]
@ -161,13 +163,10 @@ function CssRuleView(inspector, document, store, pageStyle) {
this._outputParser = new OutputParser(document);
this._onKeydown = this._onKeydown.bind(this);
this._onKeypress = this._onKeypress.bind(this);
this._onAddRule = this._onAddRule.bind(this);
this._onContextMenu = this._onContextMenu.bind(this);
this._onCopy = this._onCopy.bind(this);
this._onFilterStyles = this._onFilterStyles.bind(this);
this._onFilterKeyPress = this._onFilterKeyPress.bind(this);
this._onClearSearch = this._onClearSearch.bind(this);
this._onFilterTextboxContextMenu =
this._onFilterTextboxContextMenu.bind(this);
@ -187,13 +186,16 @@ function CssRuleView(inspector, document, store, pageStyle) {
this.searchClearButton.hidden = true;
this.styleDocument.addEventListener("keydown", this._onKeydown);
this.styleDocument.addEventListener("keypress", this._onKeypress);
this.shortcuts = new KeyShortcuts({ window: this.styleWindow });
this._onShortcut = this._onShortcut.bind(this);
this.shortcuts.on("Escape", this._onShortcut);
this.shortcuts.on("Return", this._onShortcut);
this.shortcuts.on("Space", this._onShortcut);
this.shortcuts.on("CmdOrCtrl+F", this._onShortcut);
this.element.addEventListener("copy", this._onCopy);
this.element.addEventListener("contextmenu", this._onContextMenu);
this.addRuleButton.addEventListener("click", this._onAddRule);
this.searchField.addEventListener("input", this._onFilterStyles);
this.searchField.addEventListener("keypress", this._onFilterKeyPress);
this.searchField.addEventListener("contextmenu",
this._onFilterTextboxContextMenu);
this.searchClearButton.addEventListener("click", this._onClearSearch);
@ -703,18 +705,6 @@ CssRuleView.prototype = {
}, filterTimeout);
},
/**
* Handle the search box's keypress event. If the escape key is pressed,
* clear the search box field.
*/
_onFilterKeyPress: function (event) {
if (event.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_ESCAPE &&
this._onClearSearch()) {
event.preventDefault();
event.stopPropagation();
}
},
/**
* Context menu handler for filter style search box.
*/
@ -766,13 +756,11 @@ CssRuleView.prototype = {
this.highlighters.destroy();
// Remove bound listeners
this.styleDocument.removeEventListener("keydown", this._onKeydown);
this.styleDocument.removeEventListener("keypress", this._onKeypress);
this.shortcuts.destroy();
this.element.removeEventListener("copy", this._onCopy);
this.element.removeEventListener("contextmenu", this._onContextMenu);
this.addRuleButton.removeEventListener("click", this._onAddRule);
this.searchField.removeEventListener("input", this._onFilterStyles);
this.searchField.removeEventListener("keypress", this._onFilterKeyPress);
this.searchField.removeEventListener("contextmenu",
this._onFilterTextboxContextMenu);
this.searchClearButton.removeEventListener("click", this._onClearSearch);
@ -1489,33 +1477,30 @@ CssRuleView.prototype = {
this.inspector.togglePseudoClass(target.value);
},
/**
* Handle the keydown event in the rule view.
*/
_onKeydown: function (event) {
if (this.element.classList.contains("non-interactive") &&
(event.code === "Enter" || event.code === " ")) {
event.preventDefault();
}
},
/**
* Handle the keypress event in the rule view.
*/
_onKeypress: function (event) {
_onShortcut: function (name, event) {
if (!event.target.closest("#sidebar-panel-ruleview")) {
return;
}
let isOSX = Services.appinfo.OS === "Darwin";
if (((isOSX && event.metaKey && !event.ctrlKey && !event.altKey) ||
(!isOSX && event.ctrlKey && !event.metaKey && !event.altKey)) &&
event.key === "f") {
if (name === "CmdOrCtrl+F") {
this.searchField.focus();
event.preventDefault();
} else if ((name === "Return" || name === "Space") &&
this.element.classList.contains("non-interactive")) {
event.preventDefault();
} else if (name === "Escape" &&
event.target === this.searchField &&
this._onClearSearch()) {
// Handle the search box's keypress event. If the escape key is pressed,
// clear the search box field.
event.preventDefault();
event.stopPropagation();
}
}
};
/**

View File

@ -127,6 +127,10 @@ skip-if = os == "mac" # Bug 1245996 : click on scrollbar not working on OSX
[browser_rules_edit-selector_07.js]
[browser_rules_edit-selector_08.js]
[browser_rules_edit-selector_09.js]
[browser_rules_edit-value-after-name_01.js]
[browser_rules_edit-value-after-name_02.js]
[browser_rules_edit-value-after-name_03.js]
[browser_rules_edit-value-after-name_04.js]
[browser_rules_editable-field-focus_01.js]
[browser_rules_editable-field-focus_02.js]
[browser_rules_eyedropper.js]

View File

@ -43,7 +43,8 @@ function* testColorChangeIsntRevertedWhenOtherTooltipIsShown(ruleView) {
});
let spectrum = yield picker.spectrum;
let onModifications = ruleView.once("ruleview-changed");
let onModifications = waitForNEvents(ruleView, "ruleview-changed", 2);
let onHidden = picker.tooltip.once("hidden");
EventUtils.sendKey("RETURN", spectrum.element.ownerDocument.defaultView);
yield onHidden;
@ -53,8 +54,7 @@ function* testColorChangeIsntRevertedWhenOtherTooltipIsShown(ruleView) {
let value = getRuleViewProperty(ruleView, "body", "background").valueSpan;
let url = value.querySelector(".theme-link");
onShown = ruleView.tooltips.previewTooltip.once("shown");
let anchor = yield isHoverTooltipTarget(ruleView.tooltips.previewTooltip,
url);
let anchor = yield isHoverTooltipTarget(ruleView.tooltips.previewTooltip, url);
ruleView.tooltips.previewTooltip.show(anchor);
yield onShown;

View File

@ -0,0 +1,107 @@
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Tests that clicking on swatch-preceeded value while editing the property name
// will result in editing the property value. Also tests that the value span is updated
// only if the property name has changed. See also Bug 1248274.
const TEST_URI = `
<style type="text/css">
#testid {
color: red;
}
</style>
<div id="testid">Styled Node</div>
`;
add_task(function* () {
yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
let {inspector, view} = yield openRuleView();
yield selectNode("#testid", inspector);
let ruleEditor = getRuleViewRuleEditor(view, 1);
let propEditor = ruleEditor.rule.textProps[0].editor;
yield testColorValueSpanClickWithoutNameChange(propEditor, view);
yield testColorValueSpanClickAfterNameChange(propEditor, view);
});
function* testColorValueSpanClickWithoutNameChange(propEditor, view) {
info("Test click on color span while focusing property name editor");
let colorSpan = propEditor.valueSpan.querySelector(".ruleview-color");
info("Focus the color name span");
yield focusEditableField(view, propEditor.nameSpan);
let editor = inplaceEditor(propEditor.doc.activeElement);
// We add a click event to make sure the color span won't be cleared
// on nameSpan blur (which would lead to the click event not being triggered)
let onColorSpanClick = once(colorSpan, "click");
// The property-value-updated is emitted when the valueSpan markup is being
// re-populated, which should not be the case when not modifying the property name
let onPropertyValueUpdated = function () {
ok(false, "The \"property-value-updated\" should not be emitted");
};
view.on("property-value-updated", onPropertyValueUpdated);
info("blur propEditor.nameSpan by clicking on the color span");
EventUtils.synthesizeMouse(colorSpan, 1, 1, {}, propEditor.doc.defaultView);
info("wait for the click event on the color span");
yield onColorSpanClick;
ok(true, "Expected click event was emitted");
editor = inplaceEditor(propEditor.doc.activeElement);
is(inplaceEditor(propEditor.valueSpan), editor,
"The property value editor got focused");
// We remove this listener in order to not cause unwanted conflict in the next test
view.off("property-value-updated", onPropertyValueUpdated);
info("blur valueSpan editor to trigger ruleview-changed event and prevent " +
"having pending request");
let onRuleViewChanged = view.once("ruleview-changed");
editor.input.blur();
yield onRuleViewChanged;
}
function* testColorValueSpanClickAfterNameChange(propEditor, view) {
info("Test click on color span after property name change");
let colorSpan = propEditor.valueSpan.querySelector(".ruleview-color");
info("Focus the color name span");
yield focusEditableField(view, propEditor.nameSpan);
let editor = inplaceEditor(propEditor.doc.activeElement);
info("Modify the property to border-color to trigger the " +
"property-value-updated event");
editor.input.value = "border-color";
let onRuleViewChanged = view.once("ruleview-changed");
let onPropertyValueUpdate = view.once("property-value-updated");
info("blur propEditor.nameSpan by clicking on the color span");
EventUtils.synthesizeMouse(colorSpan, 1, 1, {}, propEditor.doc.defaultView);
info("wait for ruleview-changed event to be triggered to prevent pending requests");
yield onRuleViewChanged;
info("wait for the property value to be updated");
yield onPropertyValueUpdate;
ok(true, "Expected \"property-value-updated\" event was emitted");
editor = inplaceEditor(propEditor.doc.activeElement);
is(inplaceEditor(propEditor.valueSpan), editor,
"The property value editor got focused");
info("blur valueSpan editor to trigger ruleview-changed event and prevent " +
"having pending request");
onRuleViewChanged = view.once("ruleview-changed");
editor.input.blur();
yield onRuleViewChanged;
}

View File

@ -0,0 +1,65 @@
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Tests that hitting shift + click on color swatch while editing the property
// name will only change the color unit and not lead to edit the property value.
// See also Bug 1248274.
const TEST_URI = `
<style type="text/css">
#testid {
color: red;
background: linear-gradient(
90deg,
rgb(183,222,237),
rgb(33,180,226),
rgb(31,170,217),
rgba(200,170,140,0.5));
}
</style>
<div id="testid">Styled Node</div>
`;
add_task(function* () {
yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
let {inspector, view} = yield openRuleView();
info("Test shift + click on color swatch while editing property name");
yield selectNode("#testid", inspector);
let ruleEditor = getRuleViewRuleEditor(view, 1);
let propEditor = ruleEditor.rule.textProps[1].editor;
let swatchSpan = propEditor.valueSpan.querySelectorAll(".ruleview-colorswatch")[2];
info("Focus the background name span");
yield focusEditableField(view, propEditor.nameSpan);
let editor = inplaceEditor(propEditor.doc.activeElement);
info("Modify the property to background-image to trigger the " +
"property-value-updated event");
editor.input.value = "background-image";
let onPropertyValueUpdate = view.once("property-value-updated");
let onSwatchUnitChange = swatchSpan.once("unit-change");
let onRuleViewChanged = view.once("ruleview-changed");
info("blur propEditor.nameSpan by clicking on the color swatch");
EventUtils.synthesizeMouseAtCenter(swatchSpan, {shiftKey: true},
propEditor.doc.defaultView);
info("wait for ruleview-changed event to be triggered to prevent pending requests");
yield onRuleViewChanged;
info("wait for the color unit to change");
yield onSwatchUnitChange;
ok(true, "the color unit was changed");
info("wait for the property value to be updated");
yield onPropertyValueUpdate;
ok(!inplaceEditor(propEditor.valueSpan), "The inplace editor wasn't shown " +
"as a result of the color swatch shift + click");
});

View File

@ -0,0 +1,69 @@
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Tests that clicking on color swatch while editing the property name
// will show the color tooltip with the correct value. See also Bug 1248274.
const TEST_URI = `
<style type="text/css">
#testid {
color: red;
background: linear-gradient(
90deg,
rgb(183,222,237),
rgb(33,180,226),
rgb(31,170,217),
rgba(200,170,140,0.5));
}
</style>
<div id="testid">Styled Node</div>
`;
add_task(function* () {
yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
let {inspector, view} = yield openRuleView();
info("Test click on color swatch while editing property name");
yield selectNode("#testid", inspector);
let ruleEditor = getRuleViewRuleEditor(view, 1);
let propEditor = ruleEditor.rule.textProps[1].editor;
let swatchSpan = propEditor.valueSpan.querySelectorAll(
".ruleview-colorswatch")[3];
let colorPicker = view.tooltips.colorPicker;
info("Focus the background name span");
yield focusEditableField(view, propEditor.nameSpan);
let editor = inplaceEditor(propEditor.doc.activeElement);
info("Modify the background property to background-image to trigger the " +
"property-value-updated event");
editor.input.value = "background-image";
let onRuleViewChanged = view.once("ruleview-changed");
let onPropertyValueUpdate = view.once("property-value-updated");
let onShown = colorPicker.tooltip.once("shown");
info("blur propEditor.nameSpan by clicking on the color swatch");
EventUtils.synthesizeMouseAtCenter(swatchSpan, {},
propEditor.doc.defaultView);
info("wait for ruleview-changed event to be triggered to prevent pending requests");
yield onRuleViewChanged;
info("wait for the property value to be updated");
yield onPropertyValueUpdate;
info("wait for the color picker to be shown");
yield onShown;
ok(true, "The color picker was shown on click of the color swatch");
ok(!inplaceEditor(propEditor.valueSpan),
"The inplace editor wasn't shown as a result of the color swatch click");
let spectrum = yield colorPicker.spectrum;
is(spectrum.rgb, "200,170,140,0.5", "The correct color picker was shown");
});

View File

@ -0,0 +1,62 @@
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Tests that clicking on a property's value URL while editing the property name
// will open the link in a new tab. See also Bug 1248274.
const TEST_URI = `
<style type="text/css">
#testid {
background: url("chrome://global/skin/icons/warning-64.png"), linear-gradient(white, #F06 400px);
}
</style>
<div id="testid">Styled Node</div>
`;
add_task(function* () {
yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
let {inspector, view} = yield openRuleView();
info("Test click on background-image url while editing property name");
yield selectNode("#testid", inspector);
let ruleEditor = getRuleViewRuleEditor(view, 1);
let propEditor = ruleEditor.rule.textProps[0].editor;
let anchor = propEditor.valueSpan.querySelector(".ruleview-propertyvalue .theme-link");
info("Focus the background name span");
yield focusEditableField(view, propEditor.nameSpan);
let editor = inplaceEditor(propEditor.doc.activeElement);
info("Modify the property to background to trigger the " +
"property-value-updated event");
editor.input.value = "background-image";
let onRuleViewChanged = view.once("ruleview-changed");
let onPropertyValueUpdate = view.once("property-value-updated");
let onTabOpened = waitForTab();
info("blur propEditor.nameSpan by clicking on the link");
// The url can be wrapped across multiple lines, and so we click the lower left corner
// of the anchor to make sure to target the link.
let rect = anchor.getBoundingClientRect();
EventUtils.synthesizeMouse(anchor, 2, rect.height - 2, {}, propEditor.doc.defaultView);
info("wait for ruleview-changed event to be triggered to prevent pending requests");
yield onRuleViewChanged;
info("wait for the property value to be updated");
yield onPropertyValueUpdate;
info("wait for the image to be open in a new tab");
let tab = yield onTabOpened;
ok(true, "A new tab opened");
is(tab.linkedBrowser.currentURI.spec, anchor.href,
"The URL for the new tab is correct");
gBrowser.removeTab(tab);
});

View File

@ -23,6 +23,24 @@ const {
const HTML_NS = "http://www.w3.org/1999/xhtml";
const SHARED_SWATCH_CLASS = "ruleview-swatch";
const COLOR_SWATCH_CLASS = "ruleview-colorswatch";
const BEZIER_SWATCH_CLASS = "ruleview-bezierswatch";
const FILTER_SWATCH_CLASS = "ruleview-filterswatch";
const ANGLE_SWATCH_CLASS = "ruleview-angleswatch";
/*
* An actionable element is an element which on click triggers a specific action
* (e.g. shows a color tooltip, opens a link, ).
*/
const ACTIONABLE_ELEMENTS_SELECTORS = [
`.${COLOR_SWATCH_CLASS}`,
`.${BEZIER_SWATCH_CLASS}`,
`.${FILTER_SWATCH_CLASS}`,
`.${ANGLE_SWATCH_CLASS}`,
"a"
];
/**
* TextPropertyEditor is responsible for the following:
* Owns a TextProperty object.
@ -44,6 +62,8 @@ function TextPropertyEditor(ruleEditor, property) {
this.prop.editor = this;
this.browserWindow = this.doc.defaultView.top;
this._populatedComputed = false;
this._hasPendingClick = false;
this._clickedElementOptions = null;
const toolbox = this.ruleView.inspector.toolbox;
this.cssProperties = getCssProperties(toolbox);
@ -58,6 +78,7 @@ function TextPropertyEditor(ruleEditor, property) {
this._onSwatchRevert = this._onSwatchRevert.bind(this);
this._onValidate = throttle(this._previewValue, 10, this);
this.update = this.update.bind(this);
this.updatePropertyState = this.updatePropertyState.bind(this);
this._create();
this.update();
@ -195,7 +216,7 @@ TextPropertyEditor.prototype = {
start: this._onStartEditing,
element: this.nameSpan,
done: this._onNameDone,
destroy: this.update,
destroy: this.updatePropertyState,
advanceChars: ":",
contentType: InplaceEditor.CONTENT_TYPES.CSS_PROPERTY,
popup: this.popup
@ -215,6 +236,37 @@ TextPropertyEditor.prototype = {
}
}, false);
// The mousedown event could trigger a blur event on nameContainer, which
// will trigger a call to the update function. The update function clears
// valueSpan's markup. Thus the regular click event does not bubble up, and
// listener's callbacks are not called.
// So we need to remember where the user clicks in order to re-trigger the click
// after the valueSpan's markup is re-populated. We only need to track this for
// valueSpan's child elements, because direct click on valueSpan will always
// trigger a click event.
this.valueSpan.addEventListener("mousedown", (event) => {
let clickedEl = event.target;
if (clickedEl === this.valueSpan) {
return;
}
this._hasPendingClick = true;
let matchedSelector = ACTIONABLE_ELEMENTS_SELECTORS.find(
(selector) => clickedEl.matches(selector));
if (matchedSelector) {
let similarElements = [...this.valueSpan.querySelectorAll(matchedSelector)];
this._clickedElementOptions = {
selector: matchedSelector,
index: similarElements.indexOf(clickedEl)
};
}
}, false);
this.valueSpan.addEventListener("mouseup", (event) => {
this._clickedElementOptions = null;
this._hasPendingClick = false;
}, false);
this.valueSpan.addEventListener("click", (event) => {
let target = event.target;
@ -263,27 +315,7 @@ TextPropertyEditor.prototype = {
return;
}
if (this.prop.enabled) {
this.enable.style.removeProperty("visibility");
this.enable.setAttribute("checked", "");
} else {
this.enable.style.visibility = "visible";
this.enable.removeAttribute("checked");
}
this.warning.hidden = this.editing || this.isValid();
this.filterProperty.hidden = this.editing ||
!this.isValid() ||
!this.prop.overridden ||
this.ruleEditor.rule.isUnmatched;
if (!this.editing &&
(this.prop.overridden || !this.prop.enabled ||
!this.prop.isKnownProperty())) {
this.element.classList.add("ruleview-overridden");
} else {
this.element.classList.remove("ruleview-overridden");
}
this.updatePropertyState();
let name = this.prop.name;
this.nameSpan.textContent = name;
@ -305,21 +337,15 @@ TextPropertyEditor.prototype = {
this.element.removeAttribute("dirty");
}
const sharedSwatchClass = "ruleview-swatch ";
const colorSwatchClass = "ruleview-colorswatch";
const bezierSwatchClass = "ruleview-bezierswatch";
const filterSwatchClass = "ruleview-filterswatch";
const angleSwatchClass = "ruleview-angleswatch";
let outputParser = this.ruleView._outputParser;
let parserOptions = {
colorSwatchClass: sharedSwatchClass + colorSwatchClass,
colorSwatchClass: SHARED_SWATCH_CLASS + " " + COLOR_SWATCH_CLASS,
colorClass: "ruleview-color",
bezierSwatchClass: sharedSwatchClass + bezierSwatchClass,
bezierSwatchClass: SHARED_SWATCH_CLASS + " " + BEZIER_SWATCH_CLASS,
bezierClass: "ruleview-bezier",
filterSwatchClass: sharedSwatchClass + filterSwatchClass,
filterSwatchClass: SHARED_SWATCH_CLASS + " " + FILTER_SWATCH_CLASS,
filterClass: "ruleview-filter",
angleSwatchClass: sharedSwatchClass + angleSwatchClass,
angleSwatchClass: SHARED_SWATCH_CLASS + " " + ANGLE_SWATCH_CLASS,
angleClass: "ruleview-angle",
defaultColorType: !propDirty,
urlClass: "theme-link",
@ -329,9 +355,11 @@ TextPropertyEditor.prototype = {
this.valueSpan.innerHTML = "";
this.valueSpan.appendChild(frag);
this.ruleView.emit("property-value-updated", this.valueSpan);
// Attach the color picker tooltip to the color swatches
this._colorSwatchSpans =
this.valueSpan.querySelectorAll("." + colorSwatchClass);
this.valueSpan.querySelectorAll("." + COLOR_SWATCH_CLASS);
if (this.ruleEditor.isEditable) {
for (let span of this._colorSwatchSpans) {
// Adding this swatch to the list of swatches our colorpicker
@ -350,7 +378,7 @@ TextPropertyEditor.prototype = {
// Attach the cubic-bezier tooltip to the bezier swatches
this._bezierSwatchSpans =
this.valueSpan.querySelectorAll("." + bezierSwatchClass);
this.valueSpan.querySelectorAll("." + BEZIER_SWATCH_CLASS);
if (this.ruleEditor.isEditable) {
for (let span of this._bezierSwatchSpans) {
// Adding this swatch to the list of swatches our colorpicker
@ -367,7 +395,7 @@ TextPropertyEditor.prototype = {
}
// Attach the filter editor tooltip to the filter swatch
let span = this.valueSpan.querySelector("." + filterSwatchClass);
let span = this.valueSpan.querySelector("." + FILTER_SWATCH_CLASS);
if (this.ruleEditor.isEditable) {
if (span) {
parserOptions.filterSwatch = true;
@ -384,7 +412,7 @@ TextPropertyEditor.prototype = {
}
this.angleSwatchSpans =
this.valueSpan.querySelectorAll("." + angleSwatchClass);
this.valueSpan.querySelectorAll("." + ANGLE_SWATCH_CLASS);
if (this.ruleEditor.isEditable) {
for (let angleSpan of this.angleSwatchSpans) {
angleSpan.on("unit-change", this._onSwatchCommit);
@ -393,6 +421,26 @@ TextPropertyEditor.prototype = {
}
}
// Now that we have updated the property's value, we might have a pending
// click on the value container. If we do, we have to trigger a click event
// on the right element.
if (this._hasPendingClick) {
this._hasPendingClick = false;
let elToClick;
if (this._clickedElementOptions !== null) {
let {selector, index} = this._clickedElementOptions;
elToClick = this.valueSpan.querySelectorAll(selector)[index];
this._clickedElementOptions = null;
}
if (!elToClick) {
elToClick = this.valueSpan;
}
elToClick.click();
}
// Populate the computed styles.
this._updateComputed();
@ -405,6 +453,34 @@ TextPropertyEditor.prototype = {
this.enable.style.visibility = "hidden";
},
/**
* Update the visibility of the enable checkbox, the warning indicator and
* the filter property, as well as the overriden state of the property.
*/
updatePropertyState: function () {
if (this.prop.enabled) {
this.enable.style.removeProperty("visibility");
this.enable.setAttribute("checked", "");
} else {
this.enable.style.visibility = "visible";
this.enable.removeAttribute("checked");
}
this.warning.hidden = this.editing || this.isValid();
this.filterProperty.hidden = this.editing ||
!this.isValid() ||
!this.prop.overridden ||
this.ruleEditor.rule.isUnmatched;
if (!this.editing &&
(this.prop.overridden || !this.prop.enabled ||
!this.prop.isKnownProperty())) {
this.element.classList.add("ruleview-overridden");
} else {
this.element.classList.remove("ruleview-overridden");
}
},
/**
* Update the indicator for computed styles. The computed styles themselves
* are populated on demand, when they become visible.

View File

@ -12,8 +12,14 @@
// - in-content highlighters that appear when hovering over property values
// - etc.
const {getTheme} = require("devtools/client/shared/theme");
const {HTMLTooltip} = require("devtools/client/shared/widgets/HTMLTooltip");
const {
getImageDimensions,
setImageTooltip,
setBrokenImageTooltip,
} = require("devtools/client/shared/widgets/tooltip/ImageTooltipHelper");
const {
Tooltip,
SwatchColorPickerTooltip,
SwatchCubicBezierTooltip,
CssDocsTooltip,
@ -273,7 +279,9 @@ TooltipsOverlay.prototype = {
let panelDoc = this.view.inspector.panelDoc;
// Image, fonts, ... preview tooltip
this.previewTooltip = new Tooltip(panelDoc);
this.previewTooltip = new HTMLTooltip(this.view.inspector.toolbox, {
type: "arrow"
});
this.previewTooltip.startTogglingOnHover(this.view.element,
this._onPreviewTooltipTargetHover.bind(this));
@ -395,23 +403,85 @@ TooltipsOverlay.prototype = {
let inspector = this.view.inspector;
if (type === TOOLTIP_IMAGE_TYPE) {
let dim = Services.prefs.getIntPref(PREF_IMAGE_TOOLTIP_SIZE);
// nodeInfo contains an absolute uri
let uri = nodeInfo.value.url;
yield this.previewTooltip.setRelativeImageContent(uri,
inspector.inspector, dim);
try {
yield this._setImagePreviewTooltip(nodeInfo.value.url);
} catch (e) {
yield setBrokenImageTooltip(this.previewTooltip, this.view.inspector.panelDoc);
}
return true;
}
if (type === TOOLTIP_FONTFAMILY_TYPE) {
yield this.previewTooltip.setFontFamilyContent(nodeInfo.value.value,
inspector.selection.nodeFront);
let font = nodeInfo.value.value;
let nodeFront = inspector.selection.nodeFront;
yield this._setFontPreviewTooltip(font, nodeFront);
return true;
}
return false;
}),
/**
* Set the content of the preview tooltip to display an image preview. The image URL can
* be relative, a call will be made to the debuggee to retrieve the image content as an
* imageData URI.
*
* @param {String} imageUrl
* The image url value (may be relative or absolute).
* @return {Promise} A promise that resolves when the preview tooltip content is ready
*/
_setImagePreviewTooltip: Task.async(function* (imageUrl) {
let doc = this.view.inspector.panelDoc;
let maxDim = Services.prefs.getIntPref(PREF_IMAGE_TOOLTIP_SIZE);
let naturalWidth, naturalHeight;
if (imageUrl.startsWith("data:")) {
// If the imageUrl already is a data-url, save ourselves a round-trip
let size = yield getImageDimensions(doc, imageUrl);
naturalWidth = size.naturalWidth;
naturalHeight = size.naturalHeight;
} else {
let inspectorFront = this.view.inspector.inspector;
let {data, size} = yield inspectorFront.getImageDataFromURL(imageUrl, maxDim);
imageUrl = yield data.string();
naturalWidth = size.naturalWidth;
naturalHeight = size.naturalHeight;
}
yield setImageTooltip(this.previewTooltip, doc, imageUrl,
{maxDim, naturalWidth, naturalHeight});
}),
/**
* Set the content of the preview tooltip to display a font family preview.
*
* @param {String} font
* The font family value.
* @param {object} nodeFront
* The NodeActor that will used to retrieve the dataURL for the font
* family tooltip contents.
* @return {Promise} A promise that resolves when the preview tooltip content is ready
*/
_setFontPreviewTooltip: Task.async(function* (font, nodeFront) {
if (!font || !nodeFront || typeof nodeFront.getFontFamilyDataURL !== "function") {
throw new Error("Unable to create font preview tooltip content.");
}
font = font.replace(/"/g, "'");
font = font.replace("!important", "");
font = font.trim();
let fillStyle = getTheme() === "light" ? "black" : "white";
let {data, size: maxDim} = yield nodeFront.getFontFamilyDataURL(font, fillStyle);
let imageUrl = yield data.string();
let doc = this.view.inspector.panelDoc;
let {naturalWidth, naturalHeight} = yield getImageDimensions(doc, imageUrl);
yield setImageTooltip(this.previewTooltip, doc, imageUrl,
{hideDimensionLabel: true, maxDim, naturalWidth, naturalHeight});
}),
_onNewSelection: function () {
if (this.previewTooltip) {
this.previewTooltip.hide();

View File

@ -65,7 +65,7 @@ function* testBodyRuleView(view) {
yield assertHoverTooltipOn(view.tooltips.previewTooltip, uriSpan);
let images = panel.getElementsByTagName("image");
let images = panel.getElementsByTagName("img");
is(images.length, 1, "Tooltip contains an image");
ok(images[0].getAttribute("src")
.indexOf("iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHe") !== -1,
@ -81,7 +81,7 @@ function* testDivRuleView(view) {
yield assertHoverTooltipOn(view.tooltips.previewTooltip, uriSpan);
let images = panel.getElementsByTagName("image");
let images = panel.getElementsByTagName("img");
is(images.length, 1, "Tooltip contains an image");
ok(images[0].getAttribute("src").startsWith("data:"),
"Tooltip contains a data-uri image as expected");
@ -117,7 +117,7 @@ function* testComputedView(view) {
yield assertHoverTooltipOn(view.tooltips.previewTooltip, uriSpan);
let images = panel.getElementsByTagName("image");
let images = panel.getElementsByTagName("img");
is(images.length, 1, "Tooltip contains an image");
ok(images[0].getAttribute("src").startsWith("data:"),

View File

@ -7,6 +7,7 @@
// Test that if a tooltip is visible when a new selection is made, it closes
const TEST_URI = "<div class='one'>el 1</div><div class='two'>el 2</div>";
const XHTML_NS = "http://www.w3.org/1999/xhtml";
add_task(function* () {
yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
@ -25,7 +26,9 @@ function* testRuleView(ruleView, inspector) {
info("Showing the tooltip");
let tooltip = ruleView.tooltips.previewTooltip;
tooltip.setTextContent({messages: ["rule-view tooltip"]});
let tooltipContent = ruleView.styleDocument.createElementNS(XHTML_NS, "div");
yield tooltip.setContent(tooltipContent, 100, 30);
// Stop listening for mouse movements because it's not needed for this test,
// and causes intermittent failures on Linux. When this test runs in the suite
// sometimes a mouseleave event is dispatched at the start, which causes the
@ -48,7 +51,8 @@ function* testComputedView(computedView, inspector) {
info("Showing the tooltip");
let tooltip = computedView.tooltips.previewTooltip;
tooltip.setTextContent({messages: ["computed-view tooltip"]});
let tooltipContent = computedView.styleDocument.createElementNS(XHTML_NS, "div");
yield tooltip.setContent(tooltipContent, 100, 30);
// Stop listening for mouse movements because it's not needed for this test,
// and causes intermittent failures on Linux. When this test runs in the suite
// sometimes a mouseleave event is dispatched at the start, which causes the

View File

@ -51,7 +51,7 @@ function* testRuleView(ruleView, nodeFront) {
// And verify that the tooltip gets shown on this property
yield assertHoverTooltipOn(tooltip, valueSpan);
let images = panel.getElementsByTagName("image");
let images = panel.getElementsByTagName("img");
is(images.length, 1, "Tooltip contains an image");
ok(images[0].getAttribute("src").startsWith("data:"),
"Tooltip contains a data-uri image as expected");
@ -70,7 +70,7 @@ function* testComputedView(computedView, nodeFront) {
yield assertHoverTooltipOn(tooltip, valueSpan);
let images = panel.getElementsByTagName("image");
let images = panel.getElementsByTagName("img");
is(images.length, 1, "Tooltip contains an image");
ok(images[0].getAttribute("src").startsWith("data:"),
"Tooltip contains a data-uri image as expected");
@ -97,7 +97,7 @@ function* testExpandedComputedViewProperty(computedView, nodeFront) {
yield assertHoverTooltipOn(tooltip, valueSpan);
let images = panel.getElementsByTagName("image");
let images = panel.getElementsByTagName("img");
is(images.length, 1, "Tooltip contains an image");
ok(images[0].getAttribute("src").startsWith("data:"),
"Tooltip contains a data-uri image as expected");

View File

@ -45,7 +45,7 @@ function* testComputedViewUrls(inspector) {
*/
function* performChecks(view, propertyValue) {
function checkTooltip(panel, imageSrc) {
let images = panel.getElementsByTagName("image");
let images = panel.getElementsByTagName("img");
is(images.length, 1, "Tooltip contains an image");
is(images[0].getAttribute("src"), imageSrc, "The image URL is correct");
}

View File

@ -47,7 +47,7 @@ function* testRuleView(ruleView, nodeFront) {
// And verify that the tooltip gets shown on this property
yield assertHoverTooltipOn(tooltip, valueSpan);
let images = panel.getElementsByTagName("image");
let images = panel.getElementsByTagName("img");
is(images.length, 1, "Tooltip contains an image");
ok(images[0].getAttribute("src")
.startsWith("data:"), "Tooltip contains a data-uri image as expected");

View File

@ -39,12 +39,12 @@ function* testImageDimension(ruleView) {
info("Showing the tooltip");
let onShown = tooltip.once("shown");
tooltip.show();
tooltip.show(uriSpan);
yield onShown;
// Let's not test for a specific size, but instead let's make sure it's at
// least as big as the image
let imageRect = panel.querySelector("image").getBoundingClientRect();
let imageRect = panel.querySelector("img").getBoundingClientRect();
let panelRect = panel.getBoundingClientRect();
ok(panelRect.width >= imageRect.width,

View File

@ -50,7 +50,7 @@ function* switchToFrameContext(frameIndex, toolbox, inspector) {
info("Select the iframe in the frame list.");
let newRoot = inspector.once("new-root");
menu.menuitems[frameIndex].click();
menu.items[frameIndex].click();
yield newRoot;
yield inspector.once("inspector-updated");

View File

@ -33,7 +33,7 @@ add_task(function* () {
yield once(menu, "open");
// Verify that the menu is popuplated.
let frames = menu.menuitems.slice();
let frames = menu.items.slice();
is(frames.length, 2, "We have both frames in the menu");
frames.sort(function (a, b) {

View File

@ -83,6 +83,12 @@ module.exports = createClass({
modalClass += " hidden";
}
const sortedDevices = {};
for (let type of devices.types) {
sortedDevices[type] = Object.assign([], devices[type])
.sort((a, b) => a.name.localeCompare(b.name));
}
return dom.div(
{
className: modalClass,
@ -108,7 +114,7 @@ module.exports = createClass({
},
type
),
devices[type].map(device => {
sortedDevices[type].map(device => {
return dom.label(
{
className: "device-label",

View File

@ -8,7 +8,7 @@
const { Cc, Ci, Cu, Cr } = require("chrome");
const promise = require("promise");
const EventEmitter = require("devtools/shared/event-emitter");
const { WebGLFront } = require("devtools/server/actors/webgl");
const { WebGLFront } = require("devtools/shared/fronts/webgl");
const DevToolsUtils = require("devtools/shared/DevToolsUtils");
function ShaderEditorPanel(iframeWindow, toolbox) {

View File

@ -12,7 +12,7 @@ var promise = require("promise");
var { gDevTools } = require("devtools/client/framework/devtools");
var { DebuggerClient } = require("devtools/shared/client/main");
var { DebuggerServer } = require("devtools/server/main");
var { WebGLFront } = require("devtools/server/actors/webgl");
var { WebGLFront } = require("devtools/shared/fronts/webgl");
var DevToolsUtils = require("devtools/shared/DevToolsUtils");
var { TargetFactory } = require("devtools/client/framework/target");
var { Toolbox } = require("devtools/client/framework/toolbox");

View File

@ -1,422 +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";
/**
* This list is generated from the output of the CssPropertiesActor. If a server
* does not support the actor, this is loaded as a backup. This list does not
* guarantee that the server actually supports these CSS properties.
*/
exports.propertiesList = [
"align-content",
"align-items",
"align-self",
"animation-delay",
"animation-direction",
"animation-duration",
"animation-fill-mode",
"animation-iteration-count",
"animation-name",
"animation-play-state",
"animation-timing-function",
"-moz-appearance",
"backface-visibility",
"background-attachment",
"background-blend-mode",
"background-clip",
"background-color",
"background-image",
"background-origin",
"background-position-x",
"background-position-y",
"background-repeat",
"background-size",
"-moz-binding",
"block-size",
"border-block-end-color",
"border-block-end-style",
"border-block-end-width",
"border-block-start-color",
"border-block-start-style",
"border-block-start-width",
"border-bottom-color",
"-moz-border-bottom-colors",
"border-bottom-left-radius",
"border-bottom-right-radius",
"border-bottom-style",
"border-bottom-width",
"border-collapse",
"border-image-outset",
"border-image-repeat",
"border-image-slice",
"border-image-source",
"border-image-width",
"border-inline-end-color",
"border-inline-end-style",
"border-inline-end-width",
"border-inline-start-color",
"border-inline-start-style",
"border-inline-start-width",
"border-left-color",
"-moz-border-left-colors",
"border-left-style",
"border-left-width",
"border-right-color",
"-moz-border-right-colors",
"border-right-style",
"border-right-width",
"border-spacing",
"border-top-color",
"-moz-border-top-colors",
"border-top-left-radius",
"border-top-right-radius",
"border-top-style",
"border-top-width",
"bottom",
"-moz-box-align",
"box-decoration-break",
"-moz-box-direction",
"-moz-box-flex",
"-moz-box-ordinal-group",
"-moz-box-orient",
"-moz-box-pack",
"box-shadow",
"box-sizing",
"caption-side",
"clear",
"clip",
"clip-path",
"clip-rule",
"color",
"color-adjust",
"color-interpolation",
"color-interpolation-filters",
"-moz-column-count",
"-moz-column-fill",
"-moz-column-gap",
"-moz-column-rule-color",
"-moz-column-rule-style",
"-moz-column-rule-width",
"-moz-column-width",
"content",
"-moz-control-character-visibility",
"counter-increment",
"counter-reset",
"cursor",
"direction",
"display",
"dominant-baseline",
"empty-cells",
"fill",
"fill-opacity",
"fill-rule",
"filter",
"flex-basis",
"flex-direction",
"flex-grow",
"flex-shrink",
"flex-wrap",
"float",
"-moz-float-edge",
"flood-color",
"flood-opacity",
"font-family",
"font-feature-settings",
"font-kerning",
"font-language-override",
"font-size",
"font-size-adjust",
"font-stretch",
"font-style",
"font-synthesis",
"font-variant-alternates",
"font-variant-caps",
"font-variant-east-asian",
"font-variant-ligatures",
"font-variant-numeric",
"font-variant-position",
"font-weight",
"-moz-force-broken-image-icon",
"grid-auto-columns",
"grid-auto-flow",
"grid-auto-rows",
"grid-column-end",
"grid-column-gap",
"grid-column-start",
"grid-row-end",
"grid-row-gap",
"grid-row-start",
"grid-template-areas",
"grid-template-columns",
"grid-template-rows",
"height",
"hyphens",
"image-orientation",
"-moz-image-region",
"image-rendering",
"ime-mode",
"inline-size",
"isolation",
"justify-content",
"justify-items",
"justify-self",
"left",
"letter-spacing",
"lighting-color",
"line-height",
"list-style-image",
"list-style-position",
"list-style-type",
"margin-block-end",
"margin-block-start",
"margin-bottom",
"margin-inline-end",
"margin-inline-start",
"margin-left",
"margin-right",
"margin-top",
"marker-end",
"marker-mid",
"marker-offset",
"marker-start",
"mask",
"mask-type",
"max-block-size",
"max-height",
"max-inline-size",
"max-width",
"min-block-size",
"min-height",
"min-inline-size",
"min-width",
"mix-blend-mode",
"object-fit",
"object-position",
"offset-block-end",
"offset-block-start",
"offset-inline-end",
"offset-inline-start",
"opacity",
"order",
"-moz-orient",
"-moz-osx-font-smoothing",
"outline-color",
"outline-offset",
"-moz-outline-radius-bottomleft",
"-moz-outline-radius-bottomright",
"-moz-outline-radius-topleft",
"-moz-outline-radius-topright",
"outline-style",
"outline-width",
"overflow-x",
"overflow-y",
"padding-block-end",
"padding-block-start",
"padding-bottom",
"padding-inline-end",
"padding-inline-start",
"padding-left",
"padding-right",
"padding-top",
"page-break-after",
"page-break-before",
"page-break-inside",
"paint-order",
"perspective",
"perspective-origin",
"pointer-events",
"position",
"quotes",
"resize",
"right",
"ruby-align",
"ruby-position",
"scroll-behavior",
"scroll-snap-coordinate",
"scroll-snap-destination",
"scroll-snap-points-x",
"scroll-snap-points-y",
"scroll-snap-type-x",
"scroll-snap-type-y",
"shape-rendering",
"-moz-stack-sizing",
"stop-color",
"stop-opacity",
"stroke",
"stroke-dasharray",
"stroke-dashoffset",
"stroke-linecap",
"stroke-linejoin",
"stroke-miterlimit",
"stroke-opacity",
"stroke-width",
"-moz-tab-size",
"table-layout",
"text-align",
"-moz-text-align-last",
"text-anchor",
"text-combine-upright",
"text-decoration-color",
"text-decoration-line",
"text-decoration-style",
"text-emphasis-color",
"text-emphasis-position",
"text-emphasis-style",
"-webkit-text-fill-color",
"text-indent",
"text-orientation",
"text-overflow",
"text-rendering",
"text-shadow",
"-moz-text-size-adjust",
"-webkit-text-stroke-color",
"-webkit-text-stroke-width",
"text-transform",
"top",
"transform",
"transform-box",
"transform-origin",
"transform-style",
"transition-delay",
"transition-duration",
"transition-property",
"transition-timing-function",
"unicode-bidi",
"-moz-user-focus",
"-moz-user-input",
"-moz-user-modify",
"-moz-user-select",
"vector-effect",
"vertical-align",
"visibility",
"white-space",
"width",
"will-change",
"-moz-window-dragging",
"word-break",
"word-spacing",
"word-wrap",
"writing-mode",
"z-index",
"all",
"animation",
"background",
"background-position",
"border",
"border-block-end",
"border-block-start",
"border-bottom",
"border-color",
"border-image",
"border-inline-end",
"border-inline-start",
"border-left",
"border-radius",
"border-right",
"border-style",
"border-top",
"border-width",
"-moz-column-rule",
"-moz-columns",
"flex",
"flex-flow",
"font",
"font-variant",
"grid",
"grid-area",
"grid-column",
"grid-gap",
"grid-row",
"grid-template",
"list-style",
"margin",
"marker",
"outline",
"-moz-outline-radius",
"overflow",
"padding",
"scroll-snap-type",
"text-decoration",
"text-emphasis",
"-webkit-text-stroke",
"-moz-transform",
"transition",
"-moz-transform-origin",
"-moz-perspective-origin",
"-moz-perspective",
"-moz-transform-style",
"-moz-backface-visibility",
"-moz-border-image",
"-moz-transition",
"-moz-transition-delay",
"-moz-transition-duration",
"-moz-transition-property",
"-moz-transition-timing-function",
"-moz-animation",
"-moz-animation-delay",
"-moz-animation-direction",
"-moz-animation-duration",
"-moz-animation-fill-mode",
"-moz-animation-iteration-count",
"-moz-animation-name",
"-moz-animation-play-state",
"-moz-animation-timing-function",
"-moz-box-sizing",
"-moz-font-feature-settings",
"-moz-font-language-override",
"-moz-padding-end",
"-moz-padding-start",
"-moz-margin-end",
"-moz-margin-start",
"-moz-border-end",
"-moz-border-end-color",
"-moz-border-end-style",
"-moz-border-end-width",
"-moz-border-start",
"-moz-border-start-color",
"-moz-border-start-style",
"-moz-border-start-width",
"-moz-hyphens",
"-webkit-animation",
"-webkit-animation-delay",
"-webkit-animation-direction",
"-webkit-animation-duration",
"-webkit-animation-fill-mode",
"-webkit-animation-iteration-count",
"-webkit-animation-name",
"-webkit-animation-play-state",
"-webkit-animation-timing-function",
"-webkit-filter",
"-webkit-text-size-adjust",
"-webkit-transform",
"-webkit-transform-origin",
"-webkit-transform-style",
"-webkit-backface-visibility",
"-webkit-perspective",
"-webkit-perspective-origin",
"-webkit-transition",
"-webkit-transition-delay",
"-webkit-transition-duration",
"-webkit-transition-property",
"-webkit-transition-timing-function",
"-webkit-border-radius",
"-webkit-border-top-left-radius",
"-webkit-border-top-right-radius",
"-webkit-border-bottom-left-radius",
"-webkit-border-bottom-right-radius",
"-webkit-background-clip",
"-webkit-background-origin",
"-webkit-background-size",
"-webkit-border-image",
"-webkit-box-shadow",
"-webkit-box-sizing",
"-webkit-box-flex",
"-webkit-box-ordinal-group",
"-webkit-box-orient",
"-webkit-box-direction",
"-webkit-box-align",
"-webkit-box-pack",
"-webkit-user-select"
];

View File

@ -12,7 +12,6 @@ const Telemetry = require("devtools/client/shared/telemetry");
const NS_XHTML = "http://www.w3.org/1999/xhtml";
const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
const Node = Ci.nsIDOMNode;
loader.lazyImporter(this, "PluralForm", "resource://gre/modules/PluralForm.jsm");
loader.lazyImporter(this, "EventEmitter", "resource://devtools/shared/event-emitter.js");
@ -30,6 +29,7 @@ loader.lazyRequireGetter(this, "util", "gcli/util/util");
loader.lazyRequireGetter(this, "ConsoleServiceListener", "devtools/shared/webconsole/utils", true);
loader.lazyRequireGetter(this, "gDevTools", "devtools/client/framework/devtools", true);
loader.lazyRequireGetter(this, "gDevToolsBrowser", "devtools/client/framework/devtools-browser", true);
loader.lazyRequireGetter(this, "nodeConstants", "devtools/shared/dom-node-constants", true);
/**
* A collection of utilities to help working with commands
@ -392,7 +392,7 @@ DeveloperToolbar.prototype.focusToggle = function () {
// inside the xul input element
let active = this._chromeWindow.document.activeElement;
let position = this._input.compareDocumentPosition(active);
if (position & Node.DOCUMENT_POSITION_CONTAINED_BY) {
if (position & nodeConstants.DOCUMENT_POSITION_CONTAINED_BY) {
this.hide();
}
else {

View File

@ -68,12 +68,16 @@ const ElectronKeysMapping = {
*
* @param DOMWindow window
* The window object of the document to listen events from.
* @param DOMElement target
* Optional DOM Element on which we should listen events from.
* If omitted, we listen for all events fired on `window`.
*/
function KeyShortcuts({ window }) {
function KeyShortcuts({ window, target }) {
this.window = window;
this.target = target || window;
this.keys = new Map();
this.eventEmitter = new EventEmitter();
this.window.addEventListener("keydown", this);
this.target.addEventListener("keydown", this);
}
/*
@ -169,7 +173,7 @@ KeyShortcuts.stringify = function (shortcut) {
KeyShortcuts.prototype = {
destroy() {
this.window.removeEventListener("keydown", this);
this.target.removeEventListener("keydown", this);
this.keys.clear();
},

View File

@ -22,7 +22,6 @@ DevToolsModules(
'css-angle.js',
'css-color-db.js',
'css-color.js',
'css-properties-db.js',
'css-reload.js',
'Curl.jsm',
'demangle.js',

View File

@ -454,13 +454,13 @@ OutputParser.prototype = {
},
_onColorSwatchMouseDown: function (event) {
// Prevent text selection in the case of shift-click or double-click.
event.preventDefault();
if (!event.shiftKey) {
return;
}
// Prevent click event to be fired to not show the tooltip
event.stopPropagation();
let swatch = event.target;
let color = this.colorSwatches.get(swatch);
let val = color.nextColorUnit();
@ -470,13 +470,12 @@ OutputParser.prototype = {
},
_onAngleSwatchMouseDown: function (event) {
// Prevent text selection in the case of shift-click or double-click.
event.preventDefault();
if (!event.shiftKey) {
return;
}
event.stopPropagation();
let swatch = event.target;
let angle = this.angleSwatches.get(swatch);
let val = angle.nextAngleUnit();

View File

@ -17,7 +17,9 @@ const TEST_URI = `data:text/xml;charset=UTF-8,<?xml version="1.0"?>
<vbox flex="1">
<hbox id="box1" flex="1">test1</hbox>
<hbox id="box2" flex="1">test2</hbox>
<hbox id="box3" flex="1">test3</hbox>
<hbox id="box3" flex="1">
<textbox id="box3-input"></textbox>
</hbox>
<hbox id="box4" flex="1">
<textbox id="box4-input"></textbox>
</hbox>
@ -31,51 +33,70 @@ add_task(function* () {
yield addTab("about:blank");
let [,, doc] = yield createHost("bottom", TEST_URI);
yield testTooltipWithAutoFocus(doc);
yield testTooltipWithoutAutoFocus(doc);
yield testNoAutoFocus(doc);
yield testAutoFocus(doc);
yield testAutoFocusPreservesFocusChange(doc);
});
function* testTooltipWithAutoFocus(doc) {
info("Test a tooltip with autofocus takes focus when displayed");
let textbox = doc.querySelector("textbox");
info("Focus a XUL textbox");
let onInputFocus = once(textbox, "focus");
EventUtils.synthesizeMouseAtCenter(textbox, {}, doc.defaultView);
yield onInputFocus;
function* testNoAutoFocus(doc) {
yield focusNode(doc, "#box4-input");
is(getFocusedDocument(doc), doc, "Focus is in the XUL document");
let tooltip = new HTMLTooltip({doc}, {autofocus: true});
let tooltipNode = getTooltipContent(doc);
yield tooltip.setContent(tooltipNode, 150, 50);
yield showTooltip(tooltip, doc.getElementById("box1"));
is(getFocusedDocument(doc), tooltipNode.ownerDocument,
"Focus is in the tooltip document");
yield hideTooltip(tooltip);
}
function* testTooltipWithoutAutoFocus(doc) {
info("Test a tooltip can be closed by clicking outside");
let textbox = doc.querySelector("textbox");
info("Focus a XUL textbox");
let onInputFocus = once(textbox, "focus");
EventUtils.synthesizeMouseAtCenter(textbox, {}, doc.defaultView);
yield onInputFocus;
is(getFocusedDocument(doc), doc, "Focus is in the XUL document");
let tooltip = new HTMLTooltip({doc}, {autofocus: false});
let tooltipNode = getTooltipContent(doc);
yield tooltip.setContent(tooltipNode, 150, 50);
info("Test a tooltip without autofocus will not take focus");
let tooltip = yield createTooltip(doc, false);
yield showTooltip(tooltip, doc.getElementById("box1"));
is(getFocusedDocument(doc), doc, "Focus is still in the XUL document");
ok(doc.activeElement.closest("#box4-input"), "Focus is in the #box4-input");
yield hideTooltip(tooltip);
yield blurNode(doc, "#box4-input");
}
function* testAutoFocus(doc) {
yield focusNode(doc, "#box4-input");
is(getFocusedDocument(doc), doc, "Focus is in the XUL document");
info("Test autofocus tooltip takes focus when displayed, " +
"and restores the focus when hidden");
let tooltip = yield createTooltip(doc, true);
yield showTooltip(tooltip, doc.getElementById("box1"));
is(getFocusedDocument(doc), tooltip.panel.ownerDocument,
"Focus is in the tooltip document");
yield hideTooltip(tooltip);
is(getFocusedDocument(doc), doc, "Focus is back in the XUL document");
ok(doc.activeElement.closest("#box4-input"), "Focus is in the #box4-input");
info("Blur the textbox before moving to the next test to reset the state.");
yield blurNode(doc, "#box4-input");
}
function* testAutoFocusPreservesFocusChange(doc) {
yield focusNode(doc, "#box4-input");
is(getFocusedDocument(doc), doc, "Focus is in the XUL document");
info("Test autofocus tooltip takes focus when displayed, " +
"but does not try to restore the active element if it is not focused when hidden");
let tooltip = yield createTooltip(doc, true);
yield showTooltip(tooltip, doc.getElementById("box1"));
is(getFocusedDocument(doc), tooltip.panel.ownerDocument,
"Focus is in the tooltip document");
info("Move the focus to #box3-input while the tooltip is displayed");
yield focusNode(doc, "#box3-input");
is(getFocusedDocument(doc), doc, "Focus is back in the XUL document");
ok(doc.activeElement.closest("#box3-input"), "Focus is in the #box3-input");
yield hideTooltip(tooltip);
is(getFocusedDocument(doc), doc, "Focus is still in the XUL document");
ok(doc.activeElement.closest("#box3-input"), "Focus is still in the #box3-input");
info("Blur the textbox before moving to the next test to reset the state.");
yield blurNode(doc, "#box3-input");
}
function getFocusedDocument(doc) {
@ -86,9 +107,41 @@ function getFocusedDocument(doc) {
return activeElement.ownerDocument;
}
function getTooltipContent(doc) {
/**
* Fpcus the node corresponding to the provided selector in the provided document. Returns
* a promise that will resolve when receiving the focus event on the node.
*/
function focusNode(doc, selector) {
let node = doc.querySelector(selector);
let onFocus = once(node, "focus");
node.focus();
return onFocus;
}
/**
* Blur the node corresponding to the provided selector in the provided document. Returns
* a promise that will resolve when receiving the blur event on the node.
*/
function blurNode(doc, selector) {
let node = doc.querySelector(selector);
let onBlur = once(node, "blur");
node.blur();
return onBlur;
}
/**
* Create an HTMLTooltip instance with the provided autofocus setting.
*
* @param {Document} doc
* Document in which the tooltip should be created
* @param {Boolean} autofocus
* @return {Promise} promise that will resolve the HTMLTooltip instance created when the
* tooltip content will be ready.
*/
function* createTooltip(doc, autofocus) {
let tooltip = new HTMLTooltip({doc}, {autofocus});
let div = doc.createElementNS(HTML_NS, "div");
div.style.height = "50px";
div.style.boxSizing = "border-box";
return div;
yield tooltip.setContent(div, 150, 50);
return tooltip;
}

View File

@ -19,6 +19,8 @@ add_task(function* () {
yield testCommandOrControlModifier(shortcuts);
yield testCtrlModifier(shortcuts);
shortcuts.destroy();
yield testTarget();
});
// Test helper to listen to the next key press for a given key,
@ -336,3 +338,27 @@ function testCtrlModifier(shortcuts) {
yield onKey;
yield onKeyAlias;
}
function testTarget() {
info("Test KeyShortcuts with target argument");
let target = document.createElementNS("http://www.w3.org/1999/xhtml",
"input");
document.documentElement.appendChild(target);
target.focus();
let shortcuts = new KeyShortcuts({
window,
target
});
let onKey = once(shortcuts, "0", (key, event) => {
is(event.key, "0");
is(event.target, target);
});
EventUtils.synthesizeKey("0", {}, window);
yield onKey;
target.remove();
shortcuts.destroy();
}

View File

@ -25,7 +25,7 @@
let deferred = promise.defer();
client.listTabs(deferred.resolve);
let response = yield deferred.promise;
let { ActorRegistryFront } = require("devtools/server/actors/actor-registry");
let { ActorRegistryFront } = require("devtools/shared/fronts/actor-registry");
let registryFront = ActorRegistryFront(client, response);
// Then ask to register our test-actor to retrieve its front

View File

@ -54,13 +54,13 @@ const EXTRA_BORDER = {
* - {String} type
* Display type of the tooltip. Possible values: "normal", "arrow"
* - {Boolean} autofocus
* Defaults to true. Should the tooltip be focused when opening it.
* Defaults to false. Should the tooltip be focused when opening it.
* - {Boolean} consumeOutsideClicks
* Defaults to true. The tooltip is closed when clicking outside.
* Should this event be stopped and consumed or not.
*/
function HTMLTooltip(toolbox,
{type = "normal", autofocus = true, consumeOutsideClicks = true} = {}) {
{type = "normal", autofocus = false, consumeOutsideClicks = true} = {}) {
EventEmitter.decorate(this);
this.doc = toolbox.doc;
@ -191,6 +191,7 @@ HTMLTooltip.prototype = {
this.container.classList.add("tooltip-visible");
this.attachEventsTimer = this.doc.defaultView.setTimeout(() => {
this._focusedElement = this.doc.activeElement;
if (this.autofocus) {
this.frame.focus();
}
@ -211,6 +212,11 @@ HTMLTooltip.prototype = {
this.topWindow.removeEventListener("click", this._onClick, true);
this.container.classList.remove("tooltip-visible");
this.emit("hidden");
if (this.container.contains(this.doc.activeElement) && this._focusedElement) {
this._focusedElement.focus();
this._focusedElement = null;
}
}
},

View File

@ -18,7 +18,6 @@ const Heritage = require("sdk/core/heritage");
const {Eyedropper} = require("devtools/client/eyedropper/eyedropper");
const Editor = require("devtools/client/sourceeditor/editor");
const Services = require("Services");
const {Task} = require("devtools/shared/task");
loader.lazyRequireGetter(this, "beautify", "devtools/shared/jsbeautify/beautify");
loader.lazyRequireGetter(this, "setNamedTimeout", "devtools/client/shared/widgets/view-helpers", true);
@ -520,31 +519,6 @@ Tooltip.prototype = {
this.panel.setAttribute("clamped-dimensions", "");
},
/**
* Uses the provided inspectorFront's getImageDataFromURL method to resolve
* the relative URL on the server-side, in the page context, and then sets the
* tooltip content with the resulting image just like |setImageContent| does.
* @return a promise that resolves when the image is shown in the tooltip or
* resolves when the broken image tooltip content is ready, but never rejects.
*/
setRelativeImageContent: Task.async(function* (imageUrl, inspectorFront,
maxDim) {
if (imageUrl.startsWith("data:")) {
// If the imageUrl already is a data-url, save ourselves a round-trip
this.setImageContent(imageUrl, {maxDim: maxDim});
} else if (inspectorFront) {
try {
let {data, size} = yield inspectorFront.getImageDataFromURL(imageUrl,
maxDim);
size.maxDim = maxDim;
let str = yield data.string();
this.setImageContent(str, size);
} catch (e) {
this.setBrokenImageContent();
}
}
}),
/**
* Fill the tooltip with a message explaining the the image is missing
*/
@ -560,7 +534,7 @@ Tooltip.prototype = {
* Fill the tooltip with an image and add the image dimension at the bottom.
*
* Only use this for absolute URLs that can be queried from the devtools
* client-side. For relative URLs, use |setRelativeImageContent|.
* client-side.
*
* @param {string} imageUrl
* The url to load the image from
@ -768,38 +742,6 @@ Tooltip.prototype = {
}
},
/**
* Set the content of the tooltip to display a font family preview.
* This is based on Lea Verou's Dablet.
* See https://github.com/LeaVerou/dabblet
* for more info.
* @param {String} font The font family value.
* @param {object} nodeFront
* The NodeActor that will used to retrieve the dataURL for the font
* family tooltip contents.
* @return A promise that resolves when the font tooltip content is ready, or
* rejects if no font is provided
*/
setFontFamilyContent: Task.async(function* (font, nodeFront) {
if (!font || !nodeFront) {
throw new Error("Missing font");
}
if (typeof nodeFront.getFontFamilyDataURL === "function") {
font = font.replace(/"/g, "'");
font = font.replace("!important", "");
font = font.trim();
let fillStyle =
(Services.prefs.getCharPref("devtools.theme") === "light") ?
"black" : "white";
let {data, size} = yield nodeFront.getFontFamilyDataURL(font, fillStyle);
let str = yield data.string();
this.setImageContent(str, { hideDimensionLabel: true, maxDim: size });
}
}),
/**
* Set the content of this tooltip to the MDN docs widget.
*

View File

@ -27,6 +27,7 @@ const promise = require("promise");
const { Heritage, ViewHelpers, setNamedTimeout } =
require("devtools/client/shared/widgets/view-helpers");
const { Task } = require("devtools/shared/task");
const nodeConstants = require("devtools/shared/dom-node-constants");
XPCOMUtils.defineLazyModuleGetter(this, "PluralForm",
"resource://gre/modules/PluralForm.jsm");
@ -3758,7 +3759,7 @@ VariablesView.stringifiers.byObjectKind = {
let {preview} = aGrip;
switch (preview.nodeType) {
case Ci.nsIDOMNode.DOCUMENT_NODE: {
case nodeConstants.DOCUMENT_NODE: {
let result = aGrip.class;
if (preview.location) {
result += ` \u2192 ${getSourceNames(preview.location)[concise ? "short" : "long"]}`;
@ -3767,22 +3768,22 @@ VariablesView.stringifiers.byObjectKind = {
return result;
}
case Ci.nsIDOMNode.ATTRIBUTE_NODE: {
case nodeConstants.ATTRIBUTE_NODE: {
let value = VariablesView.getString(preview.value, { noStringQuotes: true });
return preview.nodeName + '="' + escapeHTML(value) + '"';
}
case Ci.nsIDOMNode.TEXT_NODE:
case nodeConstants.TEXT_NODE:
return preview.nodeName + " " +
VariablesView.getString(preview.textContent);
case Ci.nsIDOMNode.COMMENT_NODE: {
case nodeConstants.COMMENT_NODE: {
let comment = VariablesView.getString(preview.textContent,
{ noStringQuotes: true });
return "<!--" + comment + "-->";
}
case Ci.nsIDOMNode.DOCUMENT_FRAGMENT_NODE: {
case nodeConstants.DOCUMENT_FRAGMENT_NODE: {
if (concise || !preview.childNodes) {
return aGrip.class + "[" + preview.childNodesLength + "]";
}
@ -3797,7 +3798,7 @@ VariablesView.stringifiers.byObjectKind = {
return aGrip.class + " [" + nodes.join(", ") + "]";
}
case Ci.nsIDOMNode.ELEMENT_NODE: {
case nodeConstants.ELEMENT_NODE: {
let attrs = preview.attributes;
if (!concise) {
let n = 0, result = "<" + preview.nodeName;

View File

@ -5,7 +5,7 @@ code, and optionally help with indentation.
# Upgrade
Currently used version is 5.14.2. To upgrade, download a new version of
Currently used version is 5.15.2. To upgrade, download a new version of
CodeMirror from the project's page [1] and replace all JavaScript and
CSS files inside the codemirror directory [2].

View File

@ -109,7 +109,7 @@
var ranges = cm.listSelections();
var opening = pos % 2 == 0;
var type, next;
var type;
for (var i = 0; i < ranges.length; i++) {
var range = ranges[i], cur = range.head, curType;
var next = cm.getRange(cur, Pos(cur.line, cur.ch + 1));

View File

@ -13,7 +13,7 @@
CodeMirror.registerHelper("fold", "brace", function(cm, start) {
var line = start.line, lineText = cm.getLine(line);
var startCh, tokenType;
var tokenType;
function findOpening(openCh) {
for (var at = start.ch, pass = 0;;) {
@ -72,15 +72,15 @@ CodeMirror.registerHelper("fold", "import", function(cm, start) {
}
}
var start = start.line, has = hasImport(start), prev;
if (!has || hasImport(start - 1) || ((prev = hasImport(start - 2)) && prev.end.line == start - 1))
var startLine = start.line, has = hasImport(startLine), prev;
if (!has || hasImport(startLine - 1) || ((prev = hasImport(startLine - 2)) && prev.end.line == startLine - 1))
return null;
for (var end = has.end;;) {
var next = hasImport(end.line + 1);
if (next == null) break;
end = next.end;
}
return {from: cm.clipPos(CodeMirror.Pos(start, has.startCh + 1)), to: end};
return {from: cm.clipPos(CodeMirror.Pos(startLine, has.startCh + 1)), to: end};
});
CodeMirror.registerHelper("fold", "include", function(cm, start) {
@ -91,14 +91,14 @@ CodeMirror.registerHelper("fold", "include", function(cm, start) {
if (start.type == "meta" && start.string.slice(0, 8) == "#include") return start.start + 8;
}
var start = start.line, has = hasInclude(start);
if (has == null || hasInclude(start - 1) != null) return null;
for (var end = start;;) {
var startLine = start.line, has = hasInclude(startLine);
if (has == null || hasInclude(startLine - 1) != null) return null;
for (var end = startLine;;) {
var next = hasInclude(end + 1);
if (next == null) break;
++end;
}
return {from: CodeMirror.Pos(start, has + 1),
return {from: CodeMirror.Pos(startLine, has + 1),
to: cm.clipPos(CodeMirror.Pos(end))};
});

View File

@ -140,9 +140,9 @@
var openTag = toNextTag(iter), end;
if (!openTag || iter.line != start.line || !(end = toTagEnd(iter))) return;
if (!openTag[1] && end != "selfClose") {
var start = Pos(iter.line, iter.ch);
var close = findMatchingClose(iter, openTag[2]);
return close && {from: start, to: close.from};
var startPos = Pos(iter.line, iter.ch);
var endPos = findMatchingClose(iter, openTag[2]);
return endPos && {from: startPos, to: endPos.from};
}
}
});

View File

@ -124,6 +124,7 @@
}
cm.setSelections(newSelection);
});
cm.execCommand("indentAuto");
}
cmds[map[ctrl + "Enter"] = "insertLineAfter"] = function(cm) { return insertLine(cm, false); };
@ -419,27 +420,6 @@
map[cK + ctrl + "Backspace"] = "delLineLeft";
cmds[map["Backspace"] = "smartBackspace"] = function(cm) {
if (cm.somethingSelected()) return CodeMirror.Pass;
var cursor = cm.getCursor();
var toStartOfLine = cm.getRange({line: cursor.line, ch: 0}, cursor);
var column = CodeMirror.countColumn(toStartOfLine, null, cm.getOption("tabSize"));
var indentUnit = cm.getOption("indentUnit");
if (toStartOfLine && !/\S/.test(toStartOfLine) && column % indentUnit == 0) {
var prevIndent = new Pos(cursor.line,
CodeMirror.findColumn(toStartOfLine, column - indentUnit, indentUnit));
// If no smart delete is happening (due to tab sizing) just do a regular delete
if (prevIndent.ch == cursor.ch) return CodeMirror.Pass;
return cm.replaceRange("", prevIndent, cursor, "+delete");
} else {
return CodeMirror.Pass;
}
};
cmds[map[cK + ctrl + "K"] = "delLineRight"] = function(cm) {
cm.operation(function() {
var ranges = cm.listSelections();

View File

@ -3782,17 +3782,10 @@
}
}
function makePrompt(prefix, desc) {
var raw = '';
if (prefix) {
raw += '<span style="font-family: monospace">' + prefix + '</span>';
}
raw += '<input type="text"/> ' +
'<span style="color: #888">';
if (desc) {
raw += '<span style="color: #888">';
raw += desc;
raw += '</span>';
}
var raw = '<span style="font-family: monospace; white-space: pre">' +
(prefix || "") + '<input type="text"></span>';
if (desc)
raw += ' <span style="color: #888">' + desc + '</span>';
return raw;
}
var searchPromptDesc = '(Javascript regexp)';

View File

@ -52,7 +52,7 @@
}
.cm-fat-cursor .CodeMirror-cursor {
width: auto;
border: 0;
border: 0 !important;
background: #7e7;
}
.cm-fat-cursor div.CodeMirror-cursors {

View File

@ -1096,9 +1096,9 @@
if (!cm.state.focused) { cm.display.input.focus(); onFocus(cm); }
}
// This will be set to an array of strings when copying, so that,
// when pasting, we know what kind of selections the copied text
// was made out of.
// This will be set to a {lineWise: bool, text: [string]} object, so
// that, when pasting, we know what kind of selections the copied
// text was made out of.
var lastCopied = null;
function applyTextInput(cm, inserted, deleted, sel, origin) {
@ -1107,14 +1107,14 @@
if (!sel) sel = doc.sel;
var paste = cm.state.pasteIncoming || origin == "paste";
var textLines = doc.splitLines(inserted), multiPaste = null;
var textLines = doc.splitLines(inserted), multiPaste = null
// When pasing N lines into N selections, insert one line per selection
if (paste && sel.ranges.length > 1) {
if (lastCopied && lastCopied.join("\n") == inserted) {
if (sel.ranges.length % lastCopied.length == 0) {
if (lastCopied && lastCopied.text.join("\n") == inserted) {
if (sel.ranges.length % lastCopied.text.length == 0) {
multiPaste = [];
for (var i = 0; i < lastCopied.length; i++)
multiPaste.push(doc.splitLines(lastCopied[i]));
for (var i = 0; i < lastCopied.text.length; i++)
multiPaste.push(doc.splitLines(lastCopied.text[i]));
}
} else if (textLines.length == sel.ranges.length) {
multiPaste = map(textLines, function(l) { return [l]; });
@ -1130,6 +1130,8 @@
from = Pos(from.line, from.ch - deleted);
else if (cm.state.overwrite && !paste) // Handle overwrite
to = Pos(to.line, Math.min(getLine(doc, to.line).text.length, to.ch + lst(textLines).length));
else if (lastCopied && lastCopied.lineWise && lastCopied.text.join("\n") == inserted)
from = to = Pos(from.line, 0)
}
var updateInput = cm.curOp.updateInput;
var changeEvent = {from: from, to: to, text: multiPaste ? multiPaste[i % multiPaste.length] : textLines,
@ -1262,18 +1264,18 @@
function prepareCopyCut(e) {
if (signalDOMEvent(cm, e)) return
if (cm.somethingSelected()) {
lastCopied = cm.getSelections();
lastCopied = {lineWise: false, text: cm.getSelections()};
if (input.inaccurateSelection) {
input.prevInput = "";
input.inaccurateSelection = false;
te.value = lastCopied.join("\n");
te.value = lastCopied.text.join("\n");
selectInput(te);
}
} else if (!cm.options.lineWiseCopyCut) {
return;
} else {
var ranges = copyableRanges(cm);
lastCopied = ranges.text;
lastCopied = {lineWise: true, text: ranges.text};
if (e.type == "cut") {
cm.setSelections(ranges.ranges, null, sel_dontScroll);
} else {
@ -1621,13 +1623,13 @@
function onCopyCut(e) {
if (signalDOMEvent(cm, e)) return
if (cm.somethingSelected()) {
lastCopied = cm.getSelections();
lastCopied = {lineWise: false, text: cm.getSelections()};
if (e.type == "cut") cm.replaceSelection("", null, "cut");
} else if (!cm.options.lineWiseCopyCut) {
return;
} else {
var ranges = copyableRanges(cm);
lastCopied = ranges.text;
lastCopied = {lineWise: true, text: ranges.text};
if (e.type == "cut") {
cm.operation(function() {
cm.setSelections(ranges.ranges, 0, sel_dontScroll);
@ -1639,12 +1641,12 @@
if (e.clipboardData && !ios) {
e.preventDefault();
e.clipboardData.clearData();
e.clipboardData.setData("text/plain", lastCopied.join("\n"));
e.clipboardData.setData("text/plain", lastCopied.text.join("\n"));
} else {
// Old-fashioned briefly-focus-a-textarea hack
var kludge = hiddenTextarea(), te = kludge.firstChild;
cm.display.lineSpace.insertBefore(kludge, cm.display.lineSpace.firstChild);
te.value = lastCopied.join("\n");
te.value = lastCopied.text.join("\n");
var hadFocus = document.activeElement;
selectInput(te);
setTimeout(function() {
@ -1663,9 +1665,9 @@
return result;
},
showSelection: function(info) {
showSelection: function(info, takeFocus) {
if (!info || !this.cm.display.view.length) return;
if (info.focus) this.showPrimarySelection();
if (info.focus || takeFocus) this.showPrimarySelection();
this.showMultipleSelections(info);
},
@ -3101,7 +3103,7 @@
}
if (op.updatedDisplay || op.selectionChanged)
op.preparedSelection = display.input.prepareSelection();
op.preparedSelection = display.input.prepareSelection(op.focus);
}
function endOperation_W2(op) {
@ -3114,8 +3116,9 @@
cm.display.maxLineChanged = false;
}
var takeFocus = op.focus && op.focus == activeElt() && (!document.hasFocus || document.hasFocus())
if (op.preparedSelection)
cm.display.input.showSelection(op.preparedSelection);
cm.display.input.showSelection(op.preparedSelection, takeFocus);
if (op.updatedDisplay || op.startHeight != cm.doc.height)
updateScrollbars(cm, op.barMeasure);
if (op.updatedDisplay)
@ -3125,8 +3128,7 @@
if (cm.state.focused && op.updateInput)
cm.display.input.reset(op.typing);
if (op.focus && op.focus == activeElt() && (!document.hasFocus || document.hasFocus()))
ensureFocus(op.cm);
if (takeFocus) ensureFocus(op.cm);
}
function endOperation_finish(op) {
@ -5392,7 +5394,7 @@
for (var i = newBreaks.length - 1; i >= 0; i--)
replaceRange(cm.doc, val, newBreaks[i], Pos(newBreaks[i].line, newBreaks[i].ch + val.length))
});
option("specialChars", /[\t\u0000-\u0019\u00ad\u200b-\u200f\u2028\u2029\ufeff]/g, function(cm, val, old) {
option("specialChars", /[\u0000-\u001f\u007f\u00ad\u200b-\u200f\u2028\u2029\ufeff]/g, function(cm, val, old) {
cm.state.specialChars = new RegExp(val.source + (val.test("\t") ? "" : "|\t"), "g");
if (old != CodeMirror.Init) cm.refresh();
});
@ -5721,7 +5723,7 @@
for (var i = 0; i < ranges.length; i++) {
var pos = ranges[i].from();
var col = countColumn(cm.getLine(pos.line), pos.ch, tabSize);
spaces.push(new Array(tabSize - col % tabSize + 1).join(" "));
spaces.push(spaceStr(tabSize - col % tabSize));
}
cm.replaceSelections(spaces);
},
@ -5764,6 +5766,7 @@
ensureCursorVisible(cm);
});
},
openLine: function(cm) {cm.replaceSelection("\n", "start")},
toggleOverwrite: function(cm) {cm.toggleOverwrite();}
};
@ -5798,7 +5801,8 @@
"Ctrl-F": "goCharRight", "Ctrl-B": "goCharLeft", "Ctrl-P": "goLineUp", "Ctrl-N": "goLineDown",
"Alt-F": "goWordRight", "Alt-B": "goWordLeft", "Ctrl-A": "goLineStart", "Ctrl-E": "goLineEnd",
"Ctrl-V": "goPageDown", "Shift-Ctrl-V": "goPageUp", "Ctrl-D": "delCharAfter", "Ctrl-H": "delCharBefore",
"Alt-D": "delWordAfter", "Alt-Backspace": "delWordBefore", "Ctrl-K": "killLine", "Ctrl-T": "transposeChars"
"Alt-D": "delWordAfter", "Alt-Backspace": "delWordBefore", "Ctrl-K": "killLine", "Ctrl-T": "transposeChars",
"Ctrl-O": "openLine"
};
keyMap.macDefault = {
"Cmd-A": "selectAll", "Cmd-D": "deleteLine", "Cmd-Z": "undo", "Shift-Cmd-Z": "redo", "Cmd-Y": "redo",
@ -6560,8 +6564,8 @@
var fromCmp = cmp(found.from, from) || extraLeft(sp.marker) - extraLeft(marker);
var toCmp = cmp(found.to, to) || extraRight(sp.marker) - extraRight(marker);
if (fromCmp >= 0 && toCmp <= 0 || fromCmp <= 0 && toCmp >= 0) continue;
if (fromCmp <= 0 && (cmp(found.to, from) > 0 || (sp.marker.inclusiveRight && marker.inclusiveLeft)) ||
fromCmp >= 0 && (cmp(found.from, to) < 0 || (sp.marker.inclusiveLeft && marker.inclusiveRight)))
if (fromCmp <= 0 && (sp.marker.inclusiveRight && marker.inclusiveLeft ? cmp(found.to, from) >= 0 : cmp(found.to, from) > 0) ||
fromCmp >= 0 && (sp.marker.inclusiveRight && marker.inclusiveLeft ? cmp(found.from, to) <= 0 : cmp(found.from, to) < 0))
return true;
}
}
@ -6963,8 +6967,11 @@
}
// See issue #2901
if (webkit && /\bcm-tab\b/.test(builder.content.lastChild.className))
builder.content.className = "cm-tab-wrap-hack";
if (webkit) {
var last = builder.content.lastChild
if (/\bcm-tab\b/.test(last.className) || (last.querySelector && last.querySelector(".cm-tab")))
builder.content.className = "cm-tab-wrap-hack";
}
signal(cm, "renderLine", cm, lineView.line, builder.pre);
if (builder.pre.className)
@ -7316,13 +7323,16 @@
if (at <= sz) {
child.insertInner(at, lines, height);
if (child.lines && child.lines.length > 50) {
while (child.lines.length > 50) {
var spilled = child.lines.splice(child.lines.length - 25, 25);
var newleaf = new LeafChunk(spilled);
child.height -= newleaf.height;
this.children.splice(i + 1, 0, newleaf);
newleaf.parent = this;
// To avoid memory thrashing when child.lines is huge (e.g. first view of a large file), it's never spliced.
// Instead, small slices are taken. They're taken in order because sequential memory accesses are fastest.
var remaining = child.lines.length % 25 + 25
for (var pos = remaining; pos < child.lines.length;) {
var leaf = new LeafChunk(child.lines.slice(pos, pos += 25));
child.height -= leaf.height;
this.children.splice(++i, 0, leaf);
leaf.parent = this;
}
child.lines = child.lines.slice(0, remaining);
this.maybeSpill();
}
break;
@ -7342,7 +7352,7 @@
copy.parent = me;
me.children = [copy, sibling];
me = copy;
} else {
} else {
me.size -= sibling.size;
me.height -= sibling.height;
var myIndex = indexOf(me.parent.children, me);
@ -8892,7 +8902,7 @@
// THE END
CodeMirror.version = "5.14.2";
CodeMirror.version = "5.15.2";
return CodeMirror;
});

View File

@ -11,21 +11,19 @@
})(function(CodeMirror) {
"use strict";
function Context(indented, column, type, align, prev) {
function Context(indented, column, type, info, align, prev) {
this.indented = indented;
this.column = column;
this.type = type;
this.info = info;
this.align = align;
this.prev = prev;
}
function isStatement(type) {
return type == "statement" || type == "switchstatement" || type == "namespace";
}
function pushContext(state, col, type) {
function pushContext(state, col, type, info) {
var indent = state.indented;
if (state.context && isStatement(state.context.type) && !isStatement(type))
if (state.context && state.context.type != "statement" && type != "statement")
indent = state.context.indented;
return state.context = new Context(indent, col, type, null, state.context);
return state.context = new Context(indent, col, type, info, null, state.context);
}
function popContext(state) {
var t = state.context.type;
@ -34,15 +32,16 @@ function popContext(state) {
return state.context = state.context.prev;
}
function typeBefore(stream, state) {
function typeBefore(stream, state, pos) {
if (state.prevToken == "variable" || state.prevToken == "variable-3") return true;
if (/\S(?:[^- ]>|[*\]])\s*$|\*$/.test(stream.string.slice(0, stream.start))) return true;
if (/\S(?:[^- ]>|[*\]])\s*$|\*$/.test(stream.string.slice(0, pos))) return true;
if (state.typeAtEndOfLine && stream.column() == stream.indentation()) return true;
}
function isTopScope(context) {
for (;;) {
if (!context || context.type == "top") return true;
if (context.type == "}" && context.prev.type != "namespace") return false;
if (context.type == "}" && context.prev.info != "namespace") return false;
context = context.prev;
}
}
@ -147,13 +146,18 @@ CodeMirror.defineMode("clike", function(config, parserConfig) {
return "comment";
}
function maybeEOL(stream, state) {
if (parserConfig.typeFirstDefinitions && stream.eol() && isTopScope(state.context))
state.typeAtEndOfLine = typeBefore(stream, state, stream.pos)
}
// Interface
return {
startState: function(basecolumn) {
return {
tokenize: null,
context: new Context((basecolumn || 0) - indentUnit, 0, "top", false),
context: new Context((basecolumn || 0) - indentUnit, 0, "top", null, false),
indented: 0,
startOfLine: true,
prevToken: null
@ -167,36 +171,31 @@ CodeMirror.defineMode("clike", function(config, parserConfig) {
state.indented = stream.indentation();
state.startOfLine = true;
}
if (stream.eatSpace()) return null;
if (stream.eatSpace()) { maybeEOL(stream, state); return null; }
curPunc = isDefKeyword = null;
var style = (state.tokenize || tokenBase)(stream, state);
if (style == "comment" || style == "meta") return style;
if (ctx.align == null) ctx.align = true;
if (endStatement.test(curPunc)) while (isStatement(state.context.type)) popContext(state);
if (endStatement.test(curPunc)) while (state.context.type == "statement") popContext(state);
else if (curPunc == "{") pushContext(state, stream.column(), "}");
else if (curPunc == "[") pushContext(state, stream.column(), "]");
else if (curPunc == "(") pushContext(state, stream.column(), ")");
else if (curPunc == "}") {
while (isStatement(ctx.type)) ctx = popContext(state);
while (ctx.type == "statement") ctx = popContext(state);
if (ctx.type == "}") ctx = popContext(state);
while (isStatement(ctx.type)) ctx = popContext(state);
while (ctx.type == "statement") ctx = popContext(state);
}
else if (curPunc == ctx.type) popContext(state);
else if (indentStatements &&
(((ctx.type == "}" || ctx.type == "top") && curPunc != ";") ||
(isStatement(ctx.type) && curPunc == "newstatement"))) {
var type = "statement";
if (curPunc == "newstatement" && indentSwitch && stream.current() == "switch")
type = "switchstatement";
else if (style == "keyword" && stream.current() == "namespace")
type = "namespace";
pushContext(state, stream.column(), type);
(ctx.type == "statement" && curPunc == "newstatement"))) {
pushContext(state, stream.column(), "statement", stream.current());
}
if (style == "variable" &&
((state.prevToken == "def" ||
(parserConfig.typeFirstDefinitions && typeBefore(stream, state) &&
(parserConfig.typeFirstDefinitions && typeBefore(stream, state, stream.start) &&
isTopScope(state.context) && stream.match(/^\s*\(/, false)))))
style = "def";
@ -209,24 +208,28 @@ CodeMirror.defineMode("clike", function(config, parserConfig) {
state.startOfLine = false;
state.prevToken = isDefKeyword ? "def" : style || curPunc;
maybeEOL(stream, state);
return style;
},
indent: function(state, textAfter) {
if (state.tokenize != tokenBase && state.tokenize != null) return CodeMirror.Pass;
if (state.tokenize != tokenBase && state.tokenize != null || state.typeAtEndOfLine) return CodeMirror.Pass;
var ctx = state.context, firstChar = textAfter && textAfter.charAt(0);
if (isStatement(ctx.type) && firstChar == "}") ctx = ctx.prev;
if (ctx.type == "statement" && firstChar == "}") ctx = ctx.prev;
if (parserConfig.dontIndentStatements)
while (ctx.type == "statement" && parserConfig.dontIndentStatements.test(ctx.info))
ctx = ctx.prev
if (hooks.indent) {
var hook = hooks.indent(state, ctx, textAfter);
if (typeof hook == "number") return hook
}
var closing = firstChar == ctx.type;
var switchBlock = ctx.prev && ctx.prev.type == "switchstatement";
var switchBlock = ctx.prev && ctx.prev.info == "switch";
if (parserConfig.allmanIndentation && /[{(]/.test(firstChar)) {
while (ctx.type != "top" && ctx.type != "}") ctx = ctx.prev
return ctx.indented
}
if (isStatement(ctx.type))
if (ctx.type == "statement")
return ctx.indented + (firstChar == "{" ? 0 : statementIndentUnit);
if (ctx.align && (!dontAlignCalls || ctx.type != ")"))
return ctx.column + (closing ? 0 : 1);
@ -386,6 +389,7 @@ CodeMirror.defineMode("clike", function(config, parserConfig) {
defKeywords: words("class namespace struct enum union"),
typeFirstDefinitions: true,
atoms: words("true false null"),
dontIndentStatements: /^template$/,
hooks: {
"#": cppHook,
"*": pointerHook,
@ -429,6 +433,7 @@ CodeMirror.defineMode("clike", function(config, parserConfig) {
typeFirstDefinitions: true,
atoms: words("true false null"),
endStatement: /^[;:]$/,
number: /^(?:0x[a-f\d_]+|0b[01_]+|(?:[\d_]+\.?\d*|\.\d+)(?:e[-+]?[\d_]+)?)(u|ll?|l|f)?/i,
hooks: {
"@": function(stream) {
stream.eatWhile(/[\w\$_]/);
@ -531,7 +536,7 @@ CodeMirror.defineMode("clike", function(config, parserConfig) {
"=": function(stream, state) {
var cx = state.context
if (cx.type == "}" && cx.align && stream.eat(">")) {
state.context = new Context(cx.indented, cx.column, cx.type, null, cx.prev)
state.context = new Context(cx.indented, cx.column, cx.type, cx.info, null, cx.prev)
return "operator"
} else {
return false

View File

@ -484,9 +484,9 @@ CodeMirror.defineMode("css", function(config, parserConfig) {
"font-variant-alternates", "font-variant-caps", "font-variant-east-asian",
"font-variant-ligatures", "font-variant-numeric", "font-variant-position",
"font-weight", "grid", "grid-area", "grid-auto-columns", "grid-auto-flow",
"grid-auto-position", "grid-auto-rows", "grid-column", "grid-column-end",
"grid-column-start", "grid-row", "grid-row-end", "grid-row-start",
"grid-template", "grid-template-areas", "grid-template-columns",
"grid-auto-rows", "grid-column", "grid-column-end", "grid-column-gap",
"grid-column-start", "grid-gap", "grid-row", "grid-row-end", "grid-row-gap",
"grid-row-start", "grid-template", "grid-template-areas", "grid-template-columns",
"grid-template-rows", "hanging-punctuation", "height", "hyphens",
"icon", "image-orientation", "image-rendering", "image-resolution",
"inline-box-align", "justify-content", "left", "letter-spacing",
@ -601,7 +601,7 @@ CodeMirror.defineMode("css", function(config, parserConfig) {
"compact", "condensed", "contain", "content",
"content-box", "context-menu", "continuous", "copy", "counter", "counters", "cover", "crop",
"cross", "crosshair", "currentcolor", "cursive", "cyclic", "darken", "dashed", "decimal",
"decimal-leading-zero", "default", "default-button", "destination-atop",
"decimal-leading-zero", "default", "default-button", "dense", "destination-atop",
"destination-in", "destination-out", "destination-over", "devanagari", "difference",
"disc", "discard", "disclosure-closed", "disclosure-open", "document",
"dot-dash", "dot-dot-dash",
@ -615,13 +615,13 @@ CodeMirror.defineMode("css", function(config, parserConfig) {
"ethiopic-halehame-ti-er", "ethiopic-halehame-ti-et", "ethiopic-halehame-tig",
"ethiopic-numeric", "ew-resize", "exclusion", "expanded", "extends", "extra-condensed",
"extra-expanded", "fantasy", "fast", "fill", "fixed", "flat", "flex", "flex-end", "flex-start", "footnotes",
"forwards", "from", "geometricPrecision", "georgian", "graytext", "groove",
"forwards", "from", "geometricPrecision", "georgian", "graytext", "grid", "groove",
"gujarati", "gurmukhi", "hand", "hangul", "hangul-consonant", "hard-light", "hebrew",
"help", "hidden", "hide", "higher", "highlight", "highlighttext",
"hiragana", "hiragana-iroha", "horizontal", "hsl", "hsla", "hue", "icon", "ignore",
"inactiveborder", "inactivecaption", "inactivecaptiontext", "infinite",
"infobackground", "infotext", "inherit", "initial", "inline", "inline-axis",
"inline-block", "inline-flex", "inline-table", "inset", "inside", "intrinsic", "invert",
"inline-block", "inline-flex", "inline-grid", "inline-table", "inset", "inside", "intrinsic", "invert",
"italic", "japanese-formal", "japanese-informal", "justify", "kannada",
"katakana", "katakana-iroha", "keep-all", "khmer",
"korean-hangul-formal", "korean-hanja-formal", "korean-hanja-informal",

View File

@ -115,7 +115,7 @@
return {
startState: function () {
var state = htmlMode.startState();
var state = CodeMirror.startState(htmlMode);
return {token: html, inTag: null, localMode: null, localState: null, htmlState: state};
},

View File

@ -42,7 +42,8 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
"in": operator, "typeof": operator, "instanceof": operator,
"true": atom, "false": atom, "null": atom, "undefined": atom, "NaN": atom, "Infinity": atom,
"this": kw("this"), "class": kw("class"), "super": kw("atom"),
"yield": C, "export": kw("export"), "import": kw("import"), "extends": C
"yield": C, "export": kw("export"), "import": kw("import"), "extends": C,
"await": C, "async": kw("async")
};
// Extend the 'normal' keywords with the TypeScript language extensions
@ -366,6 +367,7 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
if (type == "export") return cont(pushlex("stat"), afterExport, poplex);
if (type == "import") return cont(pushlex("stat"), afterImport, poplex);
if (type == "module") return cont(pushlex("form"), pattern, pushlex("}"), expect("{"), block, poplex, poplex)
if (type == "async") return cont(statement)
return pass(pushlex("stat"), expression, expect(";"), poplex);
}
function expression(type) {
@ -488,17 +490,17 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
if (type == "(") return pass(functiondef);
}
function commasep(what, end) {
function proceed(type) {
function proceed(type, value) {
if (type == ",") {
var lex = cx.state.lexical;
if (lex.info == "call") lex.pos = (lex.pos || 0) + 1;
return cont(what, proceed);
}
if (type == end) return cont();
if (type == end || value == end) return cont();
return cont(expect(end));
}
return function(type) {
if (type == end) return cont();
return function(type, value) {
if (type == end || value == end) return cont();
return pass(what, proceed);
};
}
@ -512,13 +514,17 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
return pass(statement, block);
}
function maybetype(type) {
if (isTS && type == ":") return cont(typedef);
if (isTS && type == ":") return cont(typeexpr);
}
function maybedefault(_, value) {
if (value == "=") return cont(expressionNoComma);
}
function typedef(type) {
if (type == "variable") {cx.marked = "variable-3"; return cont();}
function typeexpr(type) {
if (type == "variable") {cx.marked = "variable-3"; return cont(afterType);}
}
function afterType(type, value) {
if (value == "<") return cont(commasep(typeexpr, ">"), afterType)
if (type == "[") return cont(expect("]"), afterType)
}
function vardef() {
return pass(pattern, maybetype, maybeAssign, vardefCont);
@ -573,7 +579,7 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
function functiondef(type, value) {
if (value == "*") {cx.marked = "keyword"; return cont(functiondef);}
if (type == "variable") {register(value); return cont(functiondef);}
if (type == "(") return cont(pushcontext, pushlex(")"), commasep(funarg, ")"), poplex, statement, popcontext);
if (type == "(") return cont(pushcontext, pushlex(")"), commasep(funarg, ")"), poplex, maybetype, statement, popcontext);
}
function funarg(type) {
if (type == "spread") return cont(funarg);

View File

@ -7,7 +7,7 @@
const { Cc, Ci, Cu, Cr } = require("chrome");
const EventEmitter = require("devtools/shared/event-emitter");
const { WebAudioFront } = require("devtools/server/actors/webaudio");
const { WebAudioFront } = require("devtools/shared/fronts/webaudio");
var Promise = require("promise");
function WebAudioEditorPanel(iframeWindow, toolbox) {

View File

@ -32,7 +32,7 @@ add_task(function* () {
let menu = toolbox.showFramesMenu({target: btn});
yield once(menu, "open");
let frames = menu.menuitems;
let frames = menu.items;
is(frames.length, 2, "We have both frames in the list");
// Select the iframe

View File

@ -14,7 +14,7 @@ var { generateUUID } = Cc["@mozilla.org/uuid-generator;1"].getService(Ci.nsIUUID
var Promise = require("promise");
var Services = require("Services");
var { WebAudioFront } = require("devtools/server/actors/webaudio");
var { WebAudioFront } = require("devtools/shared/fronts/webaudio");
var DevToolsUtils = require("devtools/shared/DevToolsUtils");
var audioNodes = require("devtools/server/actors/utils/audionodes.json");
var mm = null;

View File

@ -28,6 +28,7 @@ const WebConsoleUtils = require("devtools/shared/webconsole/utils").Utils;
const { getSourceNames } = require("devtools/client/shared/source-utils");
const {Task} = require("devtools/shared/task");
const l10n = new WebConsoleUtils.L10n(STRINGS_URI);
const nodeConstants = require("devtools/shared/dom-node-constants");
const MAX_STRING_GRIP_LENGTH = 36;
const ELLIPSIS = Services.prefs.getComplexValue("intl.ellipsis", Ci.nsIPrefLocalizedString).data;
@ -1506,7 +1507,7 @@ Messages.ConsoleGeneric.prototype = extend(Messages.Extended.prototype, {
let result = elem;
if (style) {
if (elem.nodeType == Ci.nsIDOMNode.ELEMENT_NODE) {
if (elem.nodeType == nodeConstants.ELEMENT_NODE) {
elem.style = style;
} else {
let span = this.document.createElementNS(XHTML_NS, "span");
@ -3054,12 +3055,12 @@ Widgets.ObjectRenderers.add({
}
switch (preview.nodeType) {
case Ci.nsIDOMNode.DOCUMENT_NODE:
case Ci.nsIDOMNode.ATTRIBUTE_NODE:
case Ci.nsIDOMNode.TEXT_NODE:
case Ci.nsIDOMNode.COMMENT_NODE:
case Ci.nsIDOMNode.DOCUMENT_FRAGMENT_NODE:
case Ci.nsIDOMNode.ELEMENT_NODE:
case nodeConstants.DOCUMENT_NODE:
case nodeConstants.ATTRIBUTE_NODE:
case nodeConstants.TEXT_NODE:
case nodeConstants.COMMENT_NODE:
case nodeConstants.DOCUMENT_FRAGMENT_NODE:
case nodeConstants.ELEMENT_NODE:
return true;
default:
return false;
@ -3069,26 +3070,26 @@ Widgets.ObjectRenderers.add({
render: function ()
{
switch (this.objectActor.preview.nodeType) {
case Ci.nsIDOMNode.DOCUMENT_NODE:
case nodeConstants.DOCUMENT_NODE:
this._renderDocumentNode();
break;
case Ci.nsIDOMNode.ATTRIBUTE_NODE: {
case nodeConstants.ATTRIBUTE_NODE: {
let {preview} = this.objectActor;
this.element = this.el("span.attributeNode.kind-" + preview.kind);
let attr = this._renderAttributeNode(preview.nodeName, preview.value, true);
this.element.appendChild(attr);
break;
}
case Ci.nsIDOMNode.TEXT_NODE:
case nodeConstants.TEXT_NODE:
this._renderTextNode();
break;
case Ci.nsIDOMNode.COMMENT_NODE:
case nodeConstants.COMMENT_NODE:
this._renderCommentNode();
break;
case Ci.nsIDOMNode.DOCUMENT_FRAGMENT_NODE:
case nodeConstants.DOCUMENT_FRAGMENT_NODE:
this._renderDocumentFragmentNode();
break;
case Ci.nsIDOMNode.ELEMENT_NODE:
case nodeConstants.ELEMENT_NODE:
this._renderElementNode();
break;
default:
@ -3234,7 +3235,7 @@ Widgets.ObjectRenderers.add({
* inspector, or rejects if it wasn't (either if no toolbox could be found to
* access the inspector, or if the node isn't present in the inspector, i.e.
* if the node is in a DocumentFragment or not part of the tree, or not of
* type Ci.nsIDOMNode.ELEMENT_NODE).
* type nodeConstants.ELEMENT_NODE).
*/
linkToInspector: Task.async(function* ()
{
@ -3243,7 +3244,7 @@ Widgets.ObjectRenderers.add({
}
// Checking the node type
if (this.objectActor.preview.nodeType !== Ci.nsIDOMNode.ELEMENT_NODE) {
if (this.objectActor.preview.nodeType !== nodeConstants.ELEMENT_NODE) {
throw new Error("The object cannot be linked to the inspector as it " +
"isn't an element node");
}

View File

@ -16,7 +16,7 @@ var listener = {
observe: function (subject) {
if (subject instanceof Ci.nsIScriptError &&
subject.category === "XPConnect JavaScript" &&
subject.sourceName.contains("webconsole")) {
subject.sourceName.includes("webconsole")) {
good = false;
}
}

View File

@ -16,8 +16,8 @@ const {AppValidator} = require("devtools/client/webide/modules/app-validator");
const {ConnectionManager, Connection} = require("devtools/shared/client/connection-manager");
const {AppActorFront} = require("devtools/shared/apps/app-actor-front");
const {getDeviceFront} = require("devtools/server/actors/device");
const {getPreferenceFront} = require("devtools/server/actors/preference");
const {getSettingsFront} = require("devtools/server/actors/settings");
const {getPreferenceFront} = require("devtools/shared/fronts/preference");
const {getSettingsFront} = require("devtools/shared/fronts/settings");
const {Task} = require("devtools/shared/task");
const {RuntimeScanners, RuntimeTypes} = require("devtools/client/webide/modules/runtimes");
const {NetUtil} = Cu.import("resource://gre/modules/NetUtil.jsm", {});

View File

@ -11,50 +11,34 @@ const { Cu, CC, components } = require("chrome");
const Services = require("Services");
const { DebuggerServer } = require("devtools/server/main");
const { registerActor, unregisterActor } = require("devtools/server/actors/utils/actor-registry-utils");
loader.lazyImporter(this, "NetUtil", "resource://gre/modules/NetUtil.jsm");
const { actorActorSpec, actorRegistrySpec } = require("devtools/shared/specs/actor-registry");
/**
* The ActorActor gives you a handle to an actor you've dynamically
* registered and allows you to unregister it.
*/
const ActorActor = protocol.ActorClass({
typeName: "actorActor",
const ActorActor = protocol.ActorClassWithSpec(actorActorSpec, {
initialize: function (conn, options) {
protocol.Actor.prototype.initialize.call(this, conn);
this.options = options;
},
unregister: method(function () {
unregister: function () {
unregisterActor(this.options);
}, {
request: {},
response: {}
})
});
const ActorActorFront = protocol.FrontClass(ActorActor, {
initialize: function (client, form) {
protocol.Front.prototype.initialize.call(this, client, form);
}
});
exports.ActorActorFront = ActorActorFront;
/*
* The ActorRegistryActor allows clients to define new actors on the
* server. This is particularly useful for addons.
*/
const ActorRegistryActor = protocol.ActorClass({
typeName: "actorRegistry",
const ActorRegistryActor = protocol.ActorClassWithSpec(actorRegistrySpec, {
initialize: function (conn) {
protocol.Actor.prototype.initialize.call(this, conn);
},
registerActor: method(function (sourceText, fileName, options) {
registerActor: function (sourceText, fileName, options) {
return registerActor(sourceText, fileName, options).then(() => {
let { constructor, type } = options;
@ -64,63 +48,7 @@ const ActorRegistryActor = protocol.ActorClass({
global: type.global
});
});
}, {
request: {
sourceText: Arg(0, "string"),
filename: Arg(1, "string"),
options: Arg(2, "json")
},
response: {
actorActor: RetVal("actorActor")
}
})
}
});
exports.ActorRegistryActor = ActorRegistryActor;
function request(uri) {
return new Promise((resolve, reject) => {
try {
uri = Services.io.newURI(uri, null, null);
} catch (e) {
reject(e);
}
NetUtil.asyncFetch({
uri,
loadUsingSystemPrincipal: true,
}, (stream, status, req) => {
if (!components.isSuccessCode(status)) {
reject(new Error("Request failed with status code = "
+ status
+ " after NetUtil.asyncFetch for url = "
+ uri));
return;
}
let source = NetUtil.readInputStreamToString(stream, stream.available());
stream.close();
resolve(source);
});
});
}
const ActorRegistryFront = protocol.FrontClass(ActorRegistryActor, {
initialize: function (client, form) {
protocol.Front.prototype.initialize.call(this, client,
{ actor: form.actorRegistryActor });
this.manage(this);
},
registerActor: custom(function (uri, options) {
return request(uri, options)
.then(sourceText => {
return this._registerActor(sourceText, uri, options);
});
}, {
impl: "_registerActor"
})
});
exports.ActorRegistryFront = ActorRegistryFront;

View File

@ -11,31 +11,14 @@ const {serializeStack, parseStack} = require("toolkit/loader");
const {on, once, off, emit} = events;
const {method, Arg, Option, RetVal} = protocol;
/**
* Type describing a single function call in a stack trace.
*/
protocol.types.addDictType("call-stack-item", {
name: "string",
file: "string",
line: "number"
});
/**
* Type describing an overview of a function call.
*/
protocol.types.addDictType("call-details", {
type: "number",
name: "string",
stack: "array:call-stack-item"
});
const { functionCallSpec, callWatcherSpec } = require("devtools/shared/specs/call-watcher");
const { CallWatcherFront } = require("devtools/shared/fronts/call-watcher");
/**
* This actor contains information about a function call, like the function
* type, name, stack, arguments, returned value etc.
*/
var FunctionCallActor = protocol.ActorClass({
typeName: "function-call",
var FunctionCallActor = protocol.ActorClassWithSpec(functionCallSpec, {
/**
* Creates the function call actor.
*
@ -135,7 +118,7 @@ var FunctionCallActor = protocol.ActorClass({
* Gets more information about this function call, which is not necessarily
* available on the Front instance.
*/
getDetails: method(function () {
getDetails: function () {
let { type, name, stack, timestamp } = this.details;
// Since not all calls on the stack have corresponding owner files (e.g.
@ -157,9 +140,7 @@ var FunctionCallActor = protocol.ActorClass({
stack: stack,
timestamp: timestamp
};
}, {
response: { info: RetVal("call-details") }
}),
},
/**
* Serializes the arguments so that they can be easily be transferred
@ -242,36 +223,10 @@ var FunctionCallActor = protocol.ActorClass({
}
});
/**
* The corresponding Front object for the FunctionCallActor.
*/
var FunctionCallFront = protocol.FrontClass(FunctionCallActor, {
initialize: function (client, form) {
protocol.Front.prototype.initialize.call(this, client, form);
},
/**
* Adds some generic information directly to this instance,
* to avoid extra roundtrips.
*/
form: function (form) {
this.actorID = form.actor;
this.type = form.type;
this.name = form.name;
this.file = form.file;
this.line = form.line;
this.timestamp = form.timestamp;
this.callerPreview = form.callerPreview;
this.argsPreview = form.argsPreview;
this.resultPreview = form.resultPreview;
}
});
/**
* This actor observes function calls on certain objects or globals.
*/
var CallWatcherActor = exports.CallWatcherActor = protocol.ActorClass({
typeName: "call-watcher",
var CallWatcherActor = exports.CallWatcherActor = protocol.ActorClassWithSpec(callWatcherSpec, {
initialize: function (conn, tabActor) {
protocol.Actor.prototype.initialize.call(this, conn);
this.tabActor = tabActor;
@ -284,16 +239,6 @@ var CallWatcherActor = exports.CallWatcherActor = protocol.ActorClass({
this.finalize();
},
events: {
/**
* Events emitted when the `onCall` function isn't provided.
*/
"call": {
type: "call",
function: Arg(0, "function-call")
}
},
/**
* Lightweight listener invoked whenever an instrumented function is called
* while recording. We're doing this to avoid the event emitter overhead,
@ -306,7 +251,7 @@ var CallWatcherActor = exports.CallWatcherActor = protocol.ActorClass({
* created, in order to instrument the specified objects and become
* aware of everything the content does with them.
*/
setup: method(function ({ tracedGlobals, tracedFunctions, startRecording, performReload, holdWeak, storeCalls }) {
setup: function ({ tracedGlobals, tracedFunctions, startRecording, performReload, holdWeak, storeCalls }) {
if (this._initialized) {
return;
}
@ -328,24 +273,14 @@ var CallWatcherActor = exports.CallWatcherActor = protocol.ActorClass({
if (performReload) {
this.tabActor.window.location.reload();
}
}, {
request: {
tracedGlobals: Option(0, "nullable:array:string"),
tracedFunctions: Option(0, "nullable:array:string"),
startRecording: Option(0, "boolean"),
performReload: Option(0, "boolean"),
holdWeak: Option(0, "boolean"),
storeCalls: Option(0, "boolean")
},
oneway: true
}),
},
/**
* Stops listening for document global changes and puts this actor
* to hibernation. This method is called automatically just before the
* actor is destroyed.
*/
finalize: method(function () {
finalize: function () {
if (!this._initialized) {
return;
}
@ -357,50 +292,44 @@ var CallWatcherActor = exports.CallWatcherActor = protocol.ActorClass({
this._tracedGlobals = null;
this._tracedFunctions = null;
}, {
oneway: true
}),
},
/**
* Returns whether the instrumented function calls are currently recorded.
*/
isRecording: method(function () {
isRecording: function () {
return this._recording;
}, {
response: RetVal("boolean")
}),
},
/**
* Initialize the timestamp epoch used to offset function call timestamps.
*/
initTimestampEpoch: method(function () {
initTimestampEpoch: function () {
this._timestampEpoch = this.tabActor.window.performance.now();
}),
},
/**
* Starts recording function calls.
*/
resumeRecording: method(function () {
resumeRecording: function () {
this._recording = true;
}),
},
/**
* Stops recording function calls.
*/
pauseRecording: method(function () {
pauseRecording: function () {
this._recording = false;
return this._functionCalls;
}, {
response: { calls: RetVal("array:function-call") }
}),
},
/**
* Erases all the recorded function calls.
* Calling `resumeRecording` or `pauseRecording` does not erase history.
*/
eraseRecording: method(function () {
eraseRecording: function () {
this._functionCalls = [];
}),
},
/**
* Invoked whenever the current tab actor's document global is created.
@ -604,196 +533,6 @@ var CallWatcherActor = exports.CallWatcherActor = protocol.ActorClass({
}
});
/**
* The corresponding Front object for the CallWatcherActor.
*/
var CallWatcherFront = exports.CallWatcherFront = protocol.FrontClass(CallWatcherActor, {
initialize: function (client, { callWatcherActor }) {
protocol.Front.prototype.initialize.call(this, client, { actor: callWatcherActor });
this.manage(this);
}
});
/**
* Constants.
*/
CallWatcherFront.METHOD_FUNCTION = 0;
CallWatcherFront.GETTER_FUNCTION = 1;
CallWatcherFront.SETTER_FUNCTION = 2;
CallWatcherFront.KNOWN_METHODS = {};
CallWatcherFront.KNOWN_METHODS["CanvasRenderingContext2D"] = {
asyncDrawXULElement: {
enums: new Set([6]),
},
drawWindow: {
enums: new Set([6])
},
};
CallWatcherFront.KNOWN_METHODS["WebGLRenderingContext"] = {
activeTexture: {
enums: new Set([0]),
},
bindBuffer: {
enums: new Set([0]),
},
bindFramebuffer: {
enums: new Set([0]),
},
bindRenderbuffer: {
enums: new Set([0]),
},
bindTexture: {
enums: new Set([0]),
},
blendEquation: {
enums: new Set([0]),
},
blendEquationSeparate: {
enums: new Set([0, 1]),
},
blendFunc: {
enums: new Set([0, 1]),
},
blendFuncSeparate: {
enums: new Set([0, 1, 2, 3]),
},
bufferData: {
enums: new Set([0, 1, 2]),
},
bufferSubData: {
enums: new Set([0, 1]),
},
checkFramebufferStatus: {
enums: new Set([0]),
},
clear: {
enums: new Set([0]),
},
compressedTexImage2D: {
enums: new Set([0, 2]),
},
compressedTexSubImage2D: {
enums: new Set([0, 6]),
},
copyTexImage2D: {
enums: new Set([0, 2]),
},
copyTexSubImage2D: {
enums: new Set([0]),
},
createShader: {
enums: new Set([0]),
},
cullFace: {
enums: new Set([0]),
},
depthFunc: {
enums: new Set([0]),
},
disable: {
enums: new Set([0]),
},
drawArrays: {
enums: new Set([0]),
},
drawElements: {
enums: new Set([0, 2]),
},
enable: {
enums: new Set([0]),
},
framebufferRenderbuffer: {
enums: new Set([0, 1, 2]),
},
framebufferTexture2D: {
enums: new Set([0, 1, 2]),
},
frontFace: {
enums: new Set([0]),
},
generateMipmap: {
enums: new Set([0]),
},
getBufferParameter: {
enums: new Set([0, 1]),
},
getParameter: {
enums: new Set([0]),
},
getFramebufferAttachmentParameter: {
enums: new Set([0, 1, 2]),
},
getProgramParameter: {
enums: new Set([1]),
},
getRenderbufferParameter: {
enums: new Set([0, 1]),
},
getShaderParameter: {
enums: new Set([1]),
},
getShaderPrecisionFormat: {
enums: new Set([0, 1]),
},
getTexParameter: {
enums: new Set([0, 1]),
},
getVertexAttrib: {
enums: new Set([1]),
},
getVertexAttribOffset: {
enums: new Set([1]),
},
hint: {
enums: new Set([0, 1]),
},
isEnabled: {
enums: new Set([0]),
},
pixelStorei: {
enums: new Set([0]),
},
readPixels: {
enums: new Set([4, 5]),
},
renderbufferStorage: {
enums: new Set([0, 1]),
},
stencilFunc: {
enums: new Set([0]),
},
stencilFuncSeparate: {
enums: new Set([0, 1]),
},
stencilMaskSeparate: {
enums: new Set([0]),
},
stencilOp: {
enums: new Set([0, 1, 2]),
},
stencilOpSeparate: {
enums: new Set([0, 1, 2, 3]),
},
texImage2D: {
enums: args => args.length > 6 ? new Set([0, 2, 6, 7]) : new Set([0, 2, 3, 4]),
},
texParameterf: {
enums: new Set([0, 1]),
},
texParameteri: {
enums: new Set([0, 1, 2]),
},
texSubImage2D: {
enums: args => args.length === 9 ? new Set([0, 6, 7]) : new Set([0, 4, 5]),
},
vertexAttribPointer: {
enums: new Set([2])
},
};
/**
* A lookup table for cross-referencing flags or properties with their name
* assuming they look LIKE_THIS most of the time.

View File

@ -7,101 +7,30 @@ const {Cc, Ci, Cu, Cr} = require("chrome");
const events = require("sdk/event/core");
const promise = require("promise");
const protocol = require("devtools/shared/protocol");
const {CallWatcherActor, CallWatcherFront} = require("devtools/server/actors/call-watcher");
const {CallWatcherActor} = require("devtools/server/actors/call-watcher");
const {CallWatcherFront} = require("devtools/shared/fronts/call-watcher");
const DevToolsUtils = require("devtools/shared/DevToolsUtils");
const {WebGLPrimitiveCounter} = require("devtools/server/primitive");
const {
frameSnapshotSpec,
canvasSpec,
CANVAS_CONTEXTS,
ANIMATION_GENERATORS,
LOOP_GENERATORS,
DRAW_CALLS,
INTERESTING_CALLS,
} = require("devtools/shared/specs/canvas");
const {CanvasFront} = require("devtools/shared/fronts/canvas");
const {on, once, off, emit} = events;
const {method, custom, Arg, Option, RetVal} = protocol;
const CANVAS_CONTEXTS = [
"CanvasRenderingContext2D",
"WebGLRenderingContext"
];
const ANIMATION_GENERATORS = [
"requestAnimationFrame"
];
const LOOP_GENERATORS = [
"setTimeout"
];
const DRAW_CALLS = [
// 2D canvas
"fill",
"stroke",
"clearRect",
"fillRect",
"strokeRect",
"fillText",
"strokeText",
"drawImage",
// WebGL
"clear",
"drawArrays",
"drawElements",
"finish",
"flush"
];
const INTERESTING_CALLS = [
// 2D canvas
"save",
"restore",
// WebGL
"useProgram"
];
/**
* Type representing an ArrayBufferView, serialized fast(er).
*
* Don't create a new array buffer view from the parsed array on the frontend.
* Consumers may copy the data into an existing buffer, or create a new one if
* necesasry. For example, this avoids the need for a redundant copy when
* populating ImageData objects, at the expense of transferring char views
* of a pixel buffer over the protocol instead of a packed int view.
*
* XXX: It would be nice if on local connections (only), we could just *give*
* the buffer directly to the front, instead of going through all this
* serialization redundancy.
*/
protocol.types.addType("array-buffer-view", {
write: (v) => "[" + Array.join(v, ",") + "]",
read: (v) => JSON.parse(v)
});
/**
* Type describing a thumbnail or screenshot in a recorded animation frame.
*/
protocol.types.addDictType("snapshot-image", {
index: "number",
width: "number",
height: "number",
scaling: "number",
flipped: "boolean",
pixels: "array-buffer-view"
});
/**
* Type describing an overview of a recorded animation frame.
*/
protocol.types.addDictType("snapshot-overview", {
calls: "array:function-call",
thumbnails: "array:snapshot-image",
screenshot: "snapshot-image"
});
/**
* This actor represents a recorded animation frame snapshot, along with
* all the corresponding canvas' context methods invoked in that frame,
* thumbnails for each draw call and a screenshot of the end result.
*/
var FrameSnapshotActor = protocol.ActorClass({
typeName: "frame-snapshot",
var FrameSnapshotActor = protocol.ActorClassWithSpec(frameSnapshotSpec, {
/**
* Creates the frame snapshot call actor.
*
@ -125,7 +54,7 @@ var FrameSnapshotActor = protocol.ActorClass({
/**
* Gets as much data about this snapshot without computing anything costly.
*/
getOverview: method(function () {
getOverview: function () {
return {
calls: this._functionCalls,
thumbnails: this._functionCalls.map(e => e._thumbnail).filter(e => !!e),
@ -137,15 +66,13 @@ var FrameSnapshotActor = protocol.ActorClass({
lines: this._primitive.lines
}
};
}, {
response: { overview: RetVal("snapshot-overview") }
}),
},
/**
* Gets a screenshot of the canvas's contents after the specified
* function was called.
*/
generateScreenshotFor: method(function (functionCall) {
generateScreenshotFor: function (functionCall) {
let caller = functionCall.details.caller;
let global = functionCall.details.global;
@ -185,54 +112,7 @@ var FrameSnapshotActor = protocol.ActorClass({
screenshot.scaling = replayContextScaling;
screenshot.index = lastDrawCallIndex;
return screenshot;
}, {
request: { call: Arg(0, "function-call") },
response: { screenshot: RetVal("snapshot-image") }
})
});
/**
* The corresponding Front object for the FrameSnapshotActor.
*/
var FrameSnapshotFront = protocol.FrontClass(FrameSnapshotActor, {
initialize: function (client, form) {
protocol.Front.prototype.initialize.call(this, client, form);
this._animationFrameEndScreenshot = null;
this._cachedScreenshots = new WeakMap();
},
/**
* This implementation caches the animation frame end screenshot to optimize
* frontend requests to `generateScreenshotFor`.
*/
getOverview: custom(function () {
return this._getOverview().then(data => {
this._animationFrameEndScreenshot = data.screenshot;
return data;
});
}, {
impl: "_getOverview"
}),
/**
* This implementation saves a roundtrip to the backend if the screenshot
* was already generated and retrieved once.
*/
generateScreenshotFor: custom(function (functionCall) {
if (CanvasFront.ANIMATION_GENERATORS.has(functionCall.name) ||
CanvasFront.LOOP_GENERATORS.has(functionCall.name)) {
return promise.resolve(this._animationFrameEndScreenshot);
}
let cachedScreenshot = this._cachedScreenshots.get(functionCall);
if (cachedScreenshot) {
return cachedScreenshot;
}
let screenshot = this._generateScreenshotFor(functionCall);
this._cachedScreenshots.set(functionCall, screenshot);
return screenshot;
}, {
impl: "_generateScreenshotFor"
})
}
});
/**
@ -240,12 +120,11 @@ var FrameSnapshotFront = protocol.FrontClass(FrameSnapshotActor, {
* of a 2D or WebGL context, to provide information regarding all the calls
* made when drawing frame inside an animation loop.
*/
var CanvasActor = exports.CanvasActor = protocol.ActorClass({
var CanvasActor = exports.CanvasActor = protocol.ActorClassWithSpec(canvasSpec, {
// Reset for each recording, boolean indicating whether or not
// any draw calls were called for a recording.
_animationContainsDrawCall: false,
typeName: "canvas",
initialize: function (conn, tabActor) {
protocol.Actor.prototype.initialize.call(this, conn);
this.tabActor = tabActor;
@ -261,7 +140,7 @@ var CanvasActor = exports.CanvasActor = protocol.ActorClass({
/**
* Starts listening for function calls.
*/
setup: method(function ({ reload }) {
setup: function ({ reload }) {
if (this._initialized) {
return;
}
@ -275,15 +154,12 @@ var CanvasActor = exports.CanvasActor = protocol.ActorClass({
performReload: reload,
storeCalls: true
});
}, {
request: { reload: Option(0, "boolean") },
oneway: true
}),
},
/**
* Stops listening for function calls.
*/
finalize: method(function () {
finalize: function () {
if (!this._initialized) {
return;
}
@ -291,35 +167,29 @@ var CanvasActor = exports.CanvasActor = protocol.ActorClass({
this._callWatcher.finalize();
this._callWatcher = null;
}, {
oneway: true
}),
},
/**
* Returns whether this actor has been set up.
*/
isInitialized: method(function () {
isInitialized: function () {
return !!this._initialized;
}, {
response: { initialized: RetVal("boolean") }
}),
},
/**
* Returns whether or not the CanvasActor is recording an animation.
* Used in tests.
*/
isRecording: method(function () {
isRecording: function () {
return !!this._callWatcher.isRecording();
}, {
response: { recording: RetVal("boolean") }
}),
},
/**
* Records a snapshot of all the calls made during the next animation frame.
* The animation should be implemented via the de-facto requestAnimationFrame
* utility, or inside recursive `setTimeout`s. `setInterval` at this time are not supported.
*/
recordAnimationFrame: method(function () {
recordAnimationFrame: function () {
if (this._callWatcher.isRecording()) {
return this._currentAnimationFrameSnapshot.promise;
}
@ -332,14 +202,12 @@ var CanvasActor = exports.CanvasActor = protocol.ActorClass({
let deferred = this._currentAnimationFrameSnapshot = promise.defer();
return deferred.promise;
}, {
response: { snapshot: RetVal("nullable:frame-snapshot") }
}),
},
/**
* Cease attempts to record an animation frame.
*/
stopRecordingAnimationFrame: method(function () {
stopRecordingAnimationFrame: function () {
if (!this._callWatcher.isRecording()) {
return;
}
@ -348,9 +216,7 @@ var CanvasActor = exports.CanvasActor = protocol.ActorClass({
this._callWatcher.eraseRecording();
this._currentAnimationFrameSnapshot.resolve(null);
this._currentAnimationFrameSnapshot = null;
}, {
oneway: true
}),
},
/**
* Invoked whenever an instrumented function is called, be it on a
@ -841,33 +707,6 @@ var ContextUtils = {
}
};
/**
* The corresponding Front object for the CanvasActor.
*/
var CanvasFront = exports.CanvasFront = protocol.FrontClass(CanvasActor, {
initialize: function (client, { canvasActor }) {
protocol.Front.prototype.initialize.call(this, client, { actor: canvasActor });
this.manage(this);
}
});
/**
* Constants.
*/
CanvasFront.CANVAS_CONTEXTS = new Set(CANVAS_CONTEXTS);
CanvasFront.ANIMATION_GENERATORS = new Set(ANIMATION_GENERATORS);
CanvasFront.LOOP_GENERATORS = new Set(LOOP_GENERATORS);
CanvasFront.DRAW_CALLS = new Set(DRAW_CALLS);
CanvasFront.INTERESTING_CALLS = new Set(INTERESTING_CALLS);
CanvasFront.THUMBNAIL_SIZE = 50; // px
CanvasFront.WEBGL_SCREENSHOT_MAX_HEIGHT = 256; // px
CanvasFront.INVALID_SNAPSHOT_IMAGE = {
index: -1,
width: 0,
height: 0,
pixels: []
};
/**
* Goes through all the arguments and creates a one-level shallow copy
* of all arrays and array buffers.

View File

@ -13,6 +13,7 @@ loader.lazyGetter(this, "DOMUtils", () => {
const protocol = require("devtools/shared/protocol");
const { ActorClassWithSpec, Actor } = protocol;
const { cssPropertiesSpec } = require("devtools/shared/specs/css-properties");
const clientCssDatabase = require("devtools/shared/css-properties-db")
var CssPropertiesActor = exports.CssPropertiesActor = ActorClassWithSpec(cssPropertiesSpec, {
typeName: "cssProperties",
@ -27,8 +28,22 @@ var CssPropertiesActor = exports.CssPropertiesActor = ActorClassWithSpec(cssProp
},
getCSSDatabase: function() {
const propertiesList = DOMUtils.getCSSPropertyNames(DOMUtils.INCLUDE_ALIASES);
return { propertiesList };
const db = {};
const properties = DOMUtils.getCSSPropertyNames(DOMUtils.INCLUDE_ALIASES);
properties.forEach(name => {
// In order to maintain any backwards compatible changes when debugging
// older clients, take the definition from the static database in the
// devtools client, and fill it in with the most recent property
// definition from the server.
const clientDefinition = clientCssDatabase[name] || {};
const serverDefinition = {
isInherited: DOMUtils.isInheritedProperty(name)
};
db[name] = Object.assign(clientDefinition, serverDefinition);
});
return db;
}
});

View File

@ -9,6 +9,8 @@ const { method, Arg } = protocol;
const Services = require("Services");
const { Task } = require("devtools/shared/task");
const { heapSnapshotFileSpec } = require("devtools/shared/specs/heap-snapshot-file");
loader.lazyRequireGetter(this, "DevToolsUtils",
"devtools/shared/DevToolsUtils");
loader.lazyRequireGetter(this, "OS", "resource://gre/modules/osfile.jsm", true);
@ -21,9 +23,7 @@ loader.lazyRequireGetter(this, "HeapSnapshotFileUtils",
* because child processes are sandboxed and do not have access to the file
* system.
*/
exports.HeapSnapshotFileActor = protocol.ActorClass({
typeName: "heapSnapshotFile",
exports.HeapSnapshotFileActor = protocol.ActorClassWithSpec(heapSnapshotFileSpec, {
initialize: function (conn, parent) {
if (Services.appInfo &&
(Services.appInfo.processType !==
@ -42,7 +42,7 @@ exports.HeapSnapshotFileActor = protocol.ActorClass({
/**
* @see MemoryFront.prototype.transferHeapSnapshot
*/
transferHeapSnapshot: method(Task.async(function* (snapshotId) {
transferHeapSnapshot: Task.async(function* (snapshotId) {
const snapshotFilePath =
HeapSnapshotFileUtils.getHeapSnapshotTempFilePath(snapshotId);
if (!snapshotFilePath) {
@ -65,10 +65,6 @@ exports.HeapSnapshotFileActor = protocol.ActorClass({
} finally {
stream.close();
}
}), {
request: {
snapshotId: Arg(0, "string")
}
}),
});

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