зеркало из https://github.com/mozilla/gecko-dev.git
Merge fx-team to m-c
This commit is contained in:
Коммит
618b2c258b
|
@ -38,7 +38,7 @@ let developerHUD = {
|
|||
_targets: new Map(),
|
||||
_frames: new Map(),
|
||||
_client: null,
|
||||
_webappsActor: null,
|
||||
_conn: null,
|
||||
_watchers: [],
|
||||
_logging: true,
|
||||
|
||||
|
@ -61,32 +61,33 @@ let developerHUD = {
|
|||
RemoteDebugger.start();
|
||||
}
|
||||
|
||||
this._client = new DebuggerClient(DebuggerServer.connectPipe());
|
||||
this._client.connect((type, traits) => {
|
||||
// We instantiate a local debugger connection so that watchers can use our
|
||||
// DebuggerClient to send requests to tab actors (e.g. the consoleActor).
|
||||
// Note the special usage of the private _serverConnection, which we need
|
||||
// to call connectToChild and set up child process actors on a frame we
|
||||
// intend to track. These actors will use the connection to communicate with
|
||||
// our DebuggerServer in the parent process.
|
||||
let transport = DebuggerServer.connectPipe();
|
||||
this._conn = transport._serverConnection;
|
||||
this._client = new DebuggerClient(transport);
|
||||
|
||||
// FIXME(Bug 962577) see below.
|
||||
this._client.listTabs((res) => {
|
||||
this._webappsActor = res.webappsActor;
|
||||
for (let w of this._watchers) {
|
||||
if (w.init) {
|
||||
w.init(this._client);
|
||||
}
|
||||
}
|
||||
|
||||
for (let w of this._watchers) {
|
||||
if (w.init) {
|
||||
w.init(this._client);
|
||||
}
|
||||
}
|
||||
Services.obs.addObserver(this, 'remote-browser-shown', false);
|
||||
Services.obs.addObserver(this, 'inprocess-browser-shown', false);
|
||||
Services.obs.addObserver(this, 'message-manager-disconnect', false);
|
||||
|
||||
Services.obs.addObserver(this, 'remote-browser-shown', false);
|
||||
Services.obs.addObserver(this, 'inprocess-browser-shown', false);
|
||||
Services.obs.addObserver(this, 'message-manager-disconnect', false);
|
||||
let systemapp = document.querySelector('#systemapp');
|
||||
this.trackFrame(systemapp);
|
||||
|
||||
let systemapp = document.querySelector('#systemapp');
|
||||
this.trackFrame(systemapp);
|
||||
|
||||
let frames = systemapp.contentWindow.document.querySelectorAll('iframe[mozapp]');
|
||||
for (let frame of frames) {
|
||||
this.trackFrame(frame);
|
||||
}
|
||||
});
|
||||
});
|
||||
let frames = systemapp.contentWindow.document.querySelectorAll('iframe[mozapp]');
|
||||
for (let frame of frames) {
|
||||
this.trackFrame(frame);
|
||||
}
|
||||
|
||||
SettingsListener.observe('hud.logging', this._logging, enabled => {
|
||||
this._logging = enabled;
|
||||
|
@ -117,17 +118,12 @@ let developerHUD = {
|
|||
if (this._targets.has(frame))
|
||||
return;
|
||||
|
||||
// FIXME(Bug 962577) Factor getAppActor out of webappsActor.
|
||||
this._client.request({
|
||||
to: this._webappsActor,
|
||||
type: 'getAppActor',
|
||||
manifestURL: frame.appManifestURL
|
||||
}, (res) => {
|
||||
if (res.error) {
|
||||
return;
|
||||
}
|
||||
let mm = frame.QueryInterface(Ci.nsIFrameLoaderOwner)
|
||||
.frameLoader
|
||||
.messageManager;
|
||||
|
||||
let target = new Target(frame, res.actor);
|
||||
DebuggerServer.connectToChild(this._conn, mm).then(actor => {
|
||||
let target = new Target(frame, actor);
|
||||
this._targets.set(frame, target);
|
||||
|
||||
for (let w of this._watchers) {
|
||||
|
|
|
@ -1205,6 +1205,9 @@ pref("devtools.styleeditor.autocompletion-enabled", true);
|
|||
// Enable the Shader Editor.
|
||||
pref("devtools.shadereditor.enabled", false);
|
||||
|
||||
// Enable the Web Audio Editor
|
||||
pref("devtools.webaudioeditor.enabled", false);
|
||||
|
||||
// Enable tools for Chrome development.
|
||||
pref("devtools.chrome.enabled", false);
|
||||
|
||||
|
|
|
@ -4300,12 +4300,11 @@ function onViewToolbarsPopupShowing(aEvent, aInsertPoint) {
|
|||
|
||||
function onViewToolbarCommand(aEvent) {
|
||||
var toolbarId = aEvent.originalTarget.getAttribute("toolbarId");
|
||||
var toolbar = document.getElementById(toolbarId);
|
||||
var isVisible = aEvent.originalTarget.getAttribute("checked") == "true";
|
||||
setToolbarVisibility(toolbar, isVisible);
|
||||
CustomizableUI.setToolbarVisibility(toolbarId, isVisible);
|
||||
}
|
||||
|
||||
function setToolbarVisibility(toolbar, isVisible) {
|
||||
function setToolbarVisibility(toolbar, isVisible, persist=true) {
|
||||
let hidingAttribute;
|
||||
if (toolbar.getAttribute("type") == "menubar") {
|
||||
hidingAttribute = "autohide";
|
||||
|
@ -4317,7 +4316,10 @@ function setToolbarVisibility(toolbar, isVisible) {
|
|||
}
|
||||
|
||||
toolbar.setAttribute(hidingAttribute, !isVisible);
|
||||
document.persist(toolbar.id, hidingAttribute);
|
||||
if (persist) {
|
||||
document.persist(toolbar.id, hidingAttribute);
|
||||
}
|
||||
|
||||
let eventParams = {
|
||||
detail: {
|
||||
visible: isVisible
|
||||
|
|
|
@ -57,7 +57,7 @@ let gDrop = {
|
|||
this._cancelDelayedArrange();
|
||||
|
||||
// Update the grid and move all sites to their new places.
|
||||
gUpdater.updateGrid(gDrag.draggedSite);
|
||||
gUpdater.updateGrid();
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -145,6 +145,6 @@ let gDrop = {
|
|||
if (aCell)
|
||||
sites = gDropPreview.rearrange(aCell);
|
||||
|
||||
gTransformation.rearrangeSites(sites, gDrag.draggedSite, {unfreeze: !aCell});
|
||||
gTransformation.rearrangeSites(sites, {unfreeze: !aCell});
|
||||
}
|
||||
};
|
||||
|
|
|
@ -12,7 +12,7 @@ Cu.import("resource://gre/modules/Services.jsm");
|
|||
Cu.import("resource://gre/modules/PageThumbs.jsm");
|
||||
Cu.import("resource://gre/modules/BackgroundPageThumbs.jsm");
|
||||
Cu.import("resource://gre/modules/NewTabUtils.jsm");
|
||||
Cu.import("resource://gre/modules/Promise.jsm");
|
||||
Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "Rect",
|
||||
"resource://gre/modules/Geometry.jsm");
|
||||
|
|
|
@ -60,7 +60,7 @@ Site.prototype = {
|
|||
},
|
||||
|
||||
/**
|
||||
* Unpins the site.
|
||||
* Unpins the site and calls the given callback when done.
|
||||
*/
|
||||
unpin: function Site_unpin() {
|
||||
if (this.isPinned()) {
|
||||
|
@ -79,7 +79,8 @@ Site.prototype = {
|
|||
},
|
||||
|
||||
/**
|
||||
* Blocks the site (removes it from the grid).
|
||||
* Blocks the site (removes it from the grid) and calls the given callback
|
||||
* when done.
|
||||
*/
|
||||
block: function Site_block() {
|
||||
if (!gBlockedLinks.isBlocked(this._link)) {
|
||||
|
|
|
@ -38,24 +38,46 @@ let gTransformation = {
|
|||
return new Rect(left + scrollX, top + scrollY, width, height);
|
||||
},
|
||||
|
||||
/**
|
||||
* Fades a given node from zero to full opacity.
|
||||
* @param aNode The node to fade.
|
||||
* @param aCallback The callback to call when finished.
|
||||
*/
|
||||
fadeNodeIn: function Transformation_fadeNodeIn(aNode, aCallback) {
|
||||
this._setNodeOpacity(aNode, 1, function () {
|
||||
// Clear the style property.
|
||||
aNode.style.opacity = "";
|
||||
|
||||
if (aCallback)
|
||||
aCallback();
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Fades a given node from full to zero opacity.
|
||||
* @param aNode The node to fade.
|
||||
* @param aCallback The callback to call when finished.
|
||||
*/
|
||||
fadeNodeOut: function Transformation_fadeNodeOut(aNode, aCallback) {
|
||||
this._setNodeOpacity(aNode, 0, aCallback);
|
||||
},
|
||||
|
||||
/**
|
||||
* Fades a given site from zero to full opacity.
|
||||
* @param aSite The site to fade.
|
||||
* @param aCallback The callback to call when finished.
|
||||
*/
|
||||
showSite: function (aSite) {
|
||||
let node = aSite.node;
|
||||
return this._setNodeOpacity(node, 1).then(() => {
|
||||
// Clear the style property.
|
||||
node.style.opacity = "";
|
||||
});
|
||||
showSite: function Transformation_showSite(aSite, aCallback) {
|
||||
this.fadeNodeIn(aSite.node, aCallback);
|
||||
},
|
||||
|
||||
/**
|
||||
* Fades a given site from full to zero opacity.
|
||||
* @param aSite The site to fade.
|
||||
* @param aCallback The callback to call when finished.
|
||||
*/
|
||||
hideSite: function (aSite) {
|
||||
return this._setNodeOpacity(aSite.node, 0);
|
||||
hideSite: function Transformation_hideSite(aSite, aCallback) {
|
||||
this.fadeNodeOut(aSite.node, aCallback);
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -107,11 +129,22 @@ let gTransformation = {
|
|||
* @param aTarget The slide target.
|
||||
* @param aOptions Set of options (see below).
|
||||
* unfreeze - unfreeze the site after sliding
|
||||
* callback - the callback to call when finished
|
||||
*/
|
||||
slideSiteTo: function (aSite, aTarget, aOptions) {
|
||||
slideSiteTo: function Transformation_slideSiteTo(aSite, aTarget, aOptions) {
|
||||
let currentPosition = this.getNodePosition(aSite.node);
|
||||
let targetPosition = this.getNodePosition(aTarget.node)
|
||||
let promise;
|
||||
let callback = aOptions && aOptions.callback;
|
||||
|
||||
let self = this;
|
||||
|
||||
function finish() {
|
||||
if (aOptions && aOptions.unfreeze)
|
||||
self.unfreezeSitePosition(aSite);
|
||||
|
||||
if (callback)
|
||||
callback();
|
||||
}
|
||||
|
||||
// We need to take the width of a cell's border into account.
|
||||
targetPosition.left += this._cellBorderWidths.left;
|
||||
|
@ -120,70 +153,68 @@ let gTransformation = {
|
|||
// Nothing to do here if the positions already match.
|
||||
if (currentPosition.left == targetPosition.left &&
|
||||
currentPosition.top == targetPosition.top) {
|
||||
promise = Promise.resolve();
|
||||
finish();
|
||||
} else {
|
||||
this.setSitePosition(aSite, targetPosition);
|
||||
promise = this._whenTransitionEnded(aSite.node, ["left", "top"]);
|
||||
this._whenTransitionEnded(aSite.node, ["left", "top"], finish);
|
||||
}
|
||||
|
||||
if (aOptions && aOptions.unfreeze) {
|
||||
promise = promise.then(() => this.unfreezeSitePosition(aSite));
|
||||
}
|
||||
|
||||
return promise;
|
||||
},
|
||||
|
||||
/**
|
||||
* Rearranges a given array of sites and moves them to their new positions or
|
||||
* fades in/out new/removed sites.
|
||||
* @param aSites An array of sites to rearrange.
|
||||
* @param aDraggedSite The currently dragged site, may be null.
|
||||
* @param aOptions Set of options (see below).
|
||||
* unfreeze - unfreeze the site after rearranging
|
||||
* callback - the callback to call when finished
|
||||
*/
|
||||
rearrangeSites: function (aSites, aDraggedSite, aOptions) {
|
||||
let self = this;
|
||||
rearrangeSites: function Transformation_rearrangeSites(aSites, aOptions) {
|
||||
let batch = [];
|
||||
let cells = gGrid.cells;
|
||||
let callback = aOptions && aOptions.callback;
|
||||
let unfreeze = aOptions && aOptions.unfreeze;
|
||||
|
||||
function* promises() {
|
||||
let index = 0;
|
||||
aSites.forEach(function (aSite, aIndex) {
|
||||
// Do not re-arrange empty cells or the dragged site.
|
||||
if (!aSite || aSite == gDrag.draggedSite)
|
||||
return;
|
||||
|
||||
for (let site of aSites) {
|
||||
if (site && site !== aDraggedSite) {
|
||||
if (!cells[index]) {
|
||||
// The site disappeared from the grid, hide it.
|
||||
yield self.hideSite(site);
|
||||
} else if (self._getNodeOpacity(site.node) != 1) {
|
||||
// The site disappeared before but is now back, show it.
|
||||
yield self.showSite(site);
|
||||
} else {
|
||||
// The site's position has changed, move it around.
|
||||
yield self._moveSite(site, index, {unfreeze: unfreeze});
|
||||
}
|
||||
}
|
||||
index++;
|
||||
}
|
||||
}
|
||||
let deferred = Promise.defer();
|
||||
batch.push(deferred.promise);
|
||||
let cb = function () deferred.resolve();
|
||||
|
||||
return Promise.all([p for (p of promises())]);
|
||||
if (!cells[aIndex])
|
||||
// The site disappeared from the grid, hide it.
|
||||
this.hideSite(aSite, cb);
|
||||
else if (this._getNodeOpacity(aSite.node) != 1)
|
||||
// The site disappeared before but is now back, show it.
|
||||
this.showSite(aSite, cb);
|
||||
else
|
||||
// The site's position has changed, move it around.
|
||||
this._moveSite(aSite, aIndex, {unfreeze: unfreeze, callback: cb});
|
||||
}, this);
|
||||
|
||||
let wait = Promise.promised(function () callback && callback());
|
||||
wait.apply(null, batch);
|
||||
},
|
||||
|
||||
/**
|
||||
* Listens for the 'transitionend' event on a given node.
|
||||
* Listens for the 'transitionend' event on a given node and calls the given
|
||||
* callback.
|
||||
* @param aNode The node that is transitioned.
|
||||
* @param aProperties The properties we'll wait to be transitioned.
|
||||
* @param aCallback The callback to call when finished.
|
||||
*/
|
||||
_whenTransitionEnded: function (aNode, aProperties) {
|
||||
let deferred = Promise.defer();
|
||||
_whenTransitionEnded:
|
||||
function Transformation_whenTransitionEnded(aNode, aProperties, aCallback) {
|
||||
|
||||
let props = new Set(aProperties);
|
||||
aNode.addEventListener("transitionend", function onEnd(e) {
|
||||
if (props.has(e.propertyName)) {
|
||||
aNode.removeEventListener("transitionend", onEnd);
|
||||
deferred.resolve();
|
||||
aCallback();
|
||||
}
|
||||
});
|
||||
return deferred.promise;
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -200,14 +231,21 @@ let gTransformation = {
|
|||
* Sets a given node's opacity.
|
||||
* @param aNode The node to set the opacity value for.
|
||||
* @param aOpacity The opacity value to set.
|
||||
* @param aCallback The callback to call when finished.
|
||||
*/
|
||||
_setNodeOpacity: function (aNode, aOpacity) {
|
||||
if (this._getNodeOpacity(aNode) == aOpacity) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
_setNodeOpacity:
|
||||
function Transformation_setNodeOpacity(aNode, aOpacity, aCallback) {
|
||||
|
||||
aNode.style.opacity = aOpacity;
|
||||
return this._whenTransitionEnded(aNode, ["opacity"]);
|
||||
if (this._getNodeOpacity(aNode) == aOpacity) {
|
||||
if (aCallback)
|
||||
aCallback();
|
||||
} else {
|
||||
if (aCallback) {
|
||||
this._whenTransitionEnded(aNode, ["opacity"], aCallback);
|
||||
}
|
||||
|
||||
aNode.style.opacity = aOpacity;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -216,9 +254,9 @@ let gTransformation = {
|
|||
* @param aIndex The target cell's index.
|
||||
* @param aOptions Options that are directly passed to slideSiteTo().
|
||||
*/
|
||||
_moveSite: function (aSite, aIndex, aOptions) {
|
||||
_moveSite: function Transformation_moveSite(aSite, aIndex, aOptions) {
|
||||
this.freezeSitePosition(aSite);
|
||||
return this.slideSiteTo(aSite, gGrid.cells[aIndex], aOptions);
|
||||
this.slideSiteTo(aSite, gGrid.cells[aIndex], aOptions);
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
|
@ -12,30 +12,32 @@ let gUpdater = {
|
|||
/**
|
||||
* Updates the current grid according to its pinned and blocked sites.
|
||||
* This removes old, moves existing and creates new sites to fill gaps.
|
||||
* @param aCallback The callback to call when finished.
|
||||
*/
|
||||
updateGrid: function Updater_updateGrid(draggedSite = null) {
|
||||
updateGrid: function Updater_updateGrid(aCallback) {
|
||||
let links = gLinks.getLinks().slice(0, gGrid.cells.length);
|
||||
|
||||
// Find all sites that remain in the grid.
|
||||
let sites = this._findRemainingSites(links);
|
||||
|
||||
let self = this;
|
||||
|
||||
// Remove sites that are no longer in the grid.
|
||||
this._removeLegacySites(sites).then(() => {
|
||||
this._removeLegacySites(sites, function () {
|
||||
// Freeze all site positions so that we can move their DOM nodes around
|
||||
// without any visual impact.
|
||||
this._freezeSitePositions(sites);
|
||||
self._freezeSitePositions(sites);
|
||||
|
||||
// Move the sites' DOM nodes to their new position in the DOM. This will
|
||||
// have no visual effect as all the sites have been frozen and will
|
||||
// remain in their current position.
|
||||
this._moveSiteNodes(sites);
|
||||
self._moveSiteNodes(sites);
|
||||
|
||||
// Now it's time to animate the sites actually moving to their new
|
||||
// positions.
|
||||
let opts = {unfreeze: true};
|
||||
gTransformation.rearrangeSites(sites, draggedSite, opts).then(() => {
|
||||
self._rearrangeSites(sites, function () {
|
||||
// Try to fill empty cells and finish.
|
||||
this._fillEmptyCells(links);
|
||||
self._fillEmptyCells(links, aCallback);
|
||||
|
||||
// Update other pages that might be open to keep them synced.
|
||||
gAllPages.update(gPage);
|
||||
|
@ -107,52 +109,78 @@ let gUpdater = {
|
|||
}, this);
|
||||
},
|
||||
|
||||
/**
|
||||
* Rearranges the given sites and slides them to their new positions.
|
||||
* @param aSites The array of sites to re-arrange.
|
||||
* @param aCallback The callback to call when finished.
|
||||
*/
|
||||
_rearrangeSites: function Updater_rearrangeSites(aSites, aCallback) {
|
||||
let options = {callback: aCallback, unfreeze: true};
|
||||
gTransformation.rearrangeSites(aSites, options);
|
||||
},
|
||||
|
||||
/**
|
||||
* Removes all sites from the grid that are not in the given links array or
|
||||
* exceed the grid.
|
||||
* @param aSites The array of sites remaining in the grid.
|
||||
* @param aCallback The callback to call when finished.
|
||||
*/
|
||||
_removeLegacySites: function (aSites) {
|
||||
let remainingSites = new Set(aSites);
|
||||
_removeLegacySites: function Updater_removeLegacySites(aSites, aCallback) {
|
||||
let batch = [];
|
||||
|
||||
function* promises() {
|
||||
for (let site of gGrid.sites) {
|
||||
// The site must be valid and not in the current grid.
|
||||
if (site && !remainingSites.has(site)) {
|
||||
// Hide the site and remove it from the DOM.
|
||||
let remove = site.node.remove.bind(site.node);
|
||||
yield gTransformation.hideSite(site).then(remove);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Delete sites that were removed from the grid.
|
||||
gGrid.sites.forEach(function (aSite) {
|
||||
// The site must be valid and not in the current grid.
|
||||
if (!aSite || aSites.indexOf(aSite) != -1)
|
||||
return;
|
||||
|
||||
return Promise.all([p for (p of promises())]);
|
||||
let deferred = Promise.defer();
|
||||
batch.push(deferred.promise);
|
||||
|
||||
// Fade out the to-be-removed site.
|
||||
gTransformation.hideSite(aSite, function () {
|
||||
let node = aSite.node;
|
||||
|
||||
// Remove the site from the DOM.
|
||||
node.parentNode.removeChild(node);
|
||||
deferred.resolve();
|
||||
});
|
||||
});
|
||||
|
||||
let wait = Promise.promised(aCallback);
|
||||
wait.apply(null, batch);
|
||||
},
|
||||
|
||||
/**
|
||||
* Tries to fill empty cells with new links if available.
|
||||
* @param aLinks The array of links.
|
||||
* @param aCallback The callback to call when finished.
|
||||
*/
|
||||
_fillEmptyCells: function (aLinks) {
|
||||
_fillEmptyCells: function Updater_fillEmptyCells(aLinks, aCallback) {
|
||||
let {cells, sites} = gGrid;
|
||||
let index = 0;
|
||||
let batch = [];
|
||||
|
||||
// Find empty cells and fill them.
|
||||
for (let site of sites) {
|
||||
if (!site && aLinks[index]) {
|
||||
// Create the new site and fade it in.
|
||||
site = gGrid.createSite(aLinks[index], cells[index]);
|
||||
sites.forEach(function (aSite, aIndex) {
|
||||
if (aSite || !aLinks[aIndex])
|
||||
return;
|
||||
|
||||
// Set the site's initial opacity to zero.
|
||||
site.node.style.opacity = 0;
|
||||
let deferred = Promise.defer();
|
||||
batch.push(deferred.promise);
|
||||
|
||||
// Flush all style changes for the dynamically inserted site to make
|
||||
// the fade-in transition work.
|
||||
window.getComputedStyle(site.node).opacity;
|
||||
gTransformation.showSite(site);
|
||||
}
|
||||
// Create the new site and fade it in.
|
||||
let site = gGrid.createSite(aLinks[aIndex], cells[aIndex]);
|
||||
|
||||
index++;
|
||||
}
|
||||
// Set the site's initial opacity to zero.
|
||||
site.node.style.opacity = 0;
|
||||
|
||||
// Flush all style changes for the dynamically inserted site to make
|
||||
// the fade-in transition work.
|
||||
window.getComputedStyle(site.node).opacity;
|
||||
gTransformation.showSite(site, function () deferred.resolve());
|
||||
});
|
||||
|
||||
let wait = Promise.promised(aCallback);
|
||||
wait.apply(null, batch);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -2,8 +2,8 @@
|
|||
* 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/. */
|
||||
|
||||
pref("startup.homepage_override_url","");
|
||||
pref("startup.homepage_welcome_url","");
|
||||
pref("startup.homepage_override_url", "http://www.mozilla.org/projects/firefox/%VERSION%/whatsnew/?oldversion=%OLD_VERSION%");
|
||||
pref("startup.homepage_welcome_url", "http://www.mozilla.org/projects/firefox/%VERSION%/firstrun/");
|
||||
// The time interval between checks for a new version (in seconds)
|
||||
pref("app.update.interval", 7200); // 2 hours
|
||||
// The time interval between the downloading of mar file chunks in the
|
||||
|
|
|
@ -2146,15 +2146,15 @@ let CustomizableUIInternal = {
|
|||
},
|
||||
|
||||
_resetExtraToolbars: function(aFilter = null) {
|
||||
let firstWindow = true; // Only need to persist once
|
||||
let firstWindow = true; // Only need to unregister and persist once
|
||||
for (let [win, ] of gBuildWindows) {
|
||||
let toolbox = win.gNavToolbox;
|
||||
for (let child of toolbox.children) {
|
||||
let matchesFilter = !aFilter || aFilter == child.id;
|
||||
if (child.hasAttribute("customindex") && matchesFilter) {
|
||||
let toolbarId = "toolbar" + child.getAttribute("customindex");
|
||||
toolbox.toolbarset.removeAttribute(toolbarId);
|
||||
if (firstWindow) {
|
||||
let toolbarId = "toolbar" + child.getAttribute("customindex");
|
||||
toolbox.toolbarset.removeAttribute(toolbarId);
|
||||
win.document.persist(toolbox.toolbarset.id, toolbarId);
|
||||
// We have to unregister it properly to ensure we don't kill
|
||||
// XUL widgets which might be in here
|
||||
|
@ -2170,6 +2170,7 @@ let CustomizableUIInternal = {
|
|||
_rebuildRegisteredAreas: function() {
|
||||
for (let [areaId, areaNodes] of gBuildAreas) {
|
||||
let placements = gPlacements.get(areaId);
|
||||
let isFirstChangedToolbar = true;
|
||||
for (let areaNode of areaNodes) {
|
||||
this.buildArea(areaId, placements, areaNode);
|
||||
|
||||
|
@ -2178,9 +2179,10 @@ let CustomizableUIInternal = {
|
|||
let defaultCollapsed = area.get("defaultCollapsed");
|
||||
let win = areaNode.ownerDocument.defaultView;
|
||||
if (defaultCollapsed !== null) {
|
||||
win.setToolbarVisibility(areaNode, !defaultCollapsed);
|
||||
win.setToolbarVisibility(areaNode, !defaultCollapsed, isFirstChangedToolbar);
|
||||
}
|
||||
}
|
||||
isFirstChangedToolbar = false;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -2377,7 +2379,25 @@ let CustomizableUIInternal = {
|
|||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
},
|
||||
|
||||
setToolbarVisibility: function(aToolbarId, aIsVisible) {
|
||||
let area = gAreas.get(aToolbarId);
|
||||
if (area.get("type") != CustomizableUI.TYPE_TOOLBAR) {
|
||||
return;
|
||||
}
|
||||
let areaNodes = gBuildAreas.get(aToolbarId);
|
||||
if (!areaNodes) {
|
||||
return;
|
||||
}
|
||||
// We only persist the attribute the first time.
|
||||
let isFirstChangedToolbar = true;
|
||||
for (let areaNode of areaNodes) {
|
||||
let window = areaNode.ownerDocument.defaultView;
|
||||
window.setToolbarVisibility(areaNode, aIsVisible, isFirstChangedToolbar);
|
||||
isFirstChangedToolbar = false;
|
||||
}
|
||||
},
|
||||
};
|
||||
Object.freeze(CustomizableUIInternal);
|
||||
|
||||
|
@ -3095,6 +3115,16 @@ this.CustomizableUI = {
|
|||
get inDefaultState() {
|
||||
return CustomizableUIInternal.inDefaultState;
|
||||
},
|
||||
|
||||
/**
|
||||
* Set a toolbar's visibility state in all windows.
|
||||
* @param aToolbarId the toolbar whose visibility should be adjusted
|
||||
* @param aIsVisible whether the toolbar should be visible
|
||||
*/
|
||||
setToolbarVisibility: function(aToolbarId, aIsVisible) {
|
||||
CustomizableUIInternal.setToolbarVisibility(aToolbarId, aIsVisible);
|
||||
},
|
||||
|
||||
/**
|
||||
* Get a localized property off a (widget?) object.
|
||||
*
|
||||
|
|
|
@ -787,6 +787,14 @@ CustomizeMode.prototype = {
|
|||
wrapper.setAttribute("flex", aNode.getAttribute("flex"));
|
||||
}
|
||||
|
||||
if (aPlace == "panel") {
|
||||
if (aNode.classList.contains(CustomizableUI.WIDE_PANEL_CLASS)) {
|
||||
wrapper.setAttribute("haswideitem", "true");
|
||||
} else if (wrapper.hasAttribute("haswideitem")) {
|
||||
wrapper.removeAttribute("haswideitem");
|
||||
}
|
||||
}
|
||||
|
||||
let removable = aPlace == "palette" || CustomizableUI.isWidgetRemovable(aNode);
|
||||
wrapper.setAttribute("removable", removable);
|
||||
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
[DEFAULT]
|
||||
support-files =
|
||||
head.js
|
||||
support/test_967000_charEncoding_page.html
|
||||
support/feeds_test_page.html
|
||||
support/test-feed.xml
|
||||
|
||||
[browser_873501_handle_specials.js]
|
||||
[browser_876926_customize_mode_wrapping.js]
|
||||
|
@ -66,6 +69,10 @@ skip-if = os == "linux"
|
|||
[browser_952963_areaType_getter_no_area.js]
|
||||
[browser_956602_remove_special_widget.js]
|
||||
[browser_963639_customizing_attribute_non_customizable_toolbar.js]
|
||||
[browser_967000_button_charEncoding.js]
|
||||
[browser_967000_button_feeds.js]
|
||||
[browser_967000_button_sync.js]
|
||||
[browser_967000_button_tabView.js]
|
||||
[browser_968447_bookmarks_toolbar_items_in_panel.js]
|
||||
[browser_968565_insert_before_hidden_items.js]
|
||||
[browser_969427_recreate_destroyed_widget_after_reset.js]
|
||||
|
@ -81,5 +88,6 @@ skip-if = os == "linux"
|
|||
[browser_978084_dragEnd_after_move.js]
|
||||
[browser_980155_add_overflow_toolbar.js]
|
||||
[browser_981418-widget-onbeforecreated-handler.js]
|
||||
[browser_985815_propagate_setToolbarVisibility.js]
|
||||
[browser_981305_separator_insertion.js]
|
||||
[browser_panel_toggle.js]
|
||||
|
|
|
@ -0,0 +1,66 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
const TEST_PAGE = "http://mochi.test:8888/browser/browser/components/customizableui/test/support/test_967000_charEncoding_page.html";
|
||||
|
||||
let newTab;
|
||||
let initialLocation = gBrowser.currentURI.spec;
|
||||
|
||||
add_task(function() {
|
||||
info("Check Character Encoding button functionality");
|
||||
|
||||
// add the Character Encoding button to the panel
|
||||
CustomizableUI.addWidgetToArea("characterencoding-button",
|
||||
CustomizableUI.AREA_PANEL);
|
||||
|
||||
// check the button's functionality
|
||||
yield PanelUI.show();
|
||||
|
||||
let charEncodingButton = document.getElementById("characterencoding-button");
|
||||
ok(charEncodingButton, "The Character Encoding button was added to the Panel Menu");
|
||||
is(charEncodingButton.getAttribute("disabled"), "true",
|
||||
"The Character encoding button is initially disabled");
|
||||
|
||||
let panelHidePromise = promisePanelHidden(window);
|
||||
PanelUI.hide();
|
||||
yield panelHidePromise;
|
||||
|
||||
newTab = gBrowser.selectedTab;
|
||||
yield promiseTabLoadEvent(newTab, TEST_PAGE)
|
||||
|
||||
yield PanelUI.show();
|
||||
ok(!charEncodingButton.hasAttribute("disabled"), "The Character encoding button gets enabled");
|
||||
charEncodingButton.click();
|
||||
|
||||
let characterEncodingView = document.getElementById("PanelUI-characterEncodingView");
|
||||
ok(characterEncodingView.hasAttribute("current"), "The Character encoding panel is displayed");
|
||||
|
||||
let pinnedEncodings = document.getElementById("PanelUI-characterEncodingView-pinned");
|
||||
let charsetsList = document.getElementById("PanelUI-characterEncodingView-charsets");
|
||||
ok(pinnedEncodings, "Pinned charsets are available");
|
||||
ok(charsetsList, "Charsets list is available");
|
||||
|
||||
let checkedButtons = characterEncodingView.querySelectorAll("toolbarbutton[checked='true']");
|
||||
is(checkedButtons.length, 2, "There should be 2 checked items (1 charset, 1 detector).");
|
||||
is(checkedButtons[0].getAttribute("label"), "Unicode", "The unicode encoding is correctly selected");
|
||||
is(characterEncodingView.querySelectorAll("#PanelUI-characterEncodingView-autodetect toolbarbutton[checked='true']").length,
|
||||
1,
|
||||
"There should be 1 checked detector.");
|
||||
|
||||
panelHidePromise = promisePanelHidden(window);
|
||||
PanelUI.hide();
|
||||
yield panelHidePromise;
|
||||
});
|
||||
|
||||
add_task(function asyncCleanup() {
|
||||
// reset the panel to the default state
|
||||
yield resetCustomization();
|
||||
ok(CustomizableUI.inDefaultState, "The UI is in default state again.");
|
||||
|
||||
// restore the initial location
|
||||
gBrowser.addTab(initialLocation);
|
||||
gBrowser.removeTab(newTab);
|
||||
});
|
|
@ -0,0 +1,60 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
const TEST_PAGE = "http://mochi.test:8888/browser/browser/components/customizableui/test/support/feeds_test_page.html";
|
||||
const TEST_FEED = "http://mochi.test:8888/browser/browser/components/customizableui/test/support/test-feed.xml"
|
||||
|
||||
let newTab = null;
|
||||
let initialLocation = gBrowser.currentURI.spec;
|
||||
|
||||
add_task(function() {
|
||||
info("Check Subscribe button functionality");
|
||||
|
||||
// add the Subscribe button to the panel
|
||||
CustomizableUI.addWidgetToArea("feed-button",
|
||||
CustomizableUI.AREA_PANEL);
|
||||
|
||||
// check the button's functionality
|
||||
yield PanelUI.show();
|
||||
|
||||
let feedButton = document.getElementById("feed-button");
|
||||
ok(feedButton, "The Subscribe button was added to the Panel Menu");
|
||||
is(feedButton.getAttribute("disabled"), "true", "The Subscribe button is initially disabled");
|
||||
|
||||
let panelHidePromise = promisePanelHidden(window);
|
||||
PanelUI.hide();
|
||||
yield panelHidePromise;
|
||||
|
||||
newTab = gBrowser.selectedTab;
|
||||
yield promiseTabLoadEvent(newTab, TEST_PAGE);
|
||||
|
||||
yield PanelUI.show();
|
||||
|
||||
yield waitForCondition(function() !feedButton.hasAttribute("disabled"));
|
||||
ok(!feedButton.hasAttribute("disabled"), "The Subscribe button gets enabled");
|
||||
|
||||
feedButton.click();
|
||||
yield promiseTabLoadEvent(newTab, TEST_FEED);
|
||||
|
||||
is(gBrowser.currentURI.spec, TEST_FEED, "Subscribe page opened");
|
||||
ok(!isPanelUIOpen(), "Panel is closed");
|
||||
|
||||
if(isPanelUIOpen()) {
|
||||
panelHidePromise = promisePanelHidden(window);
|
||||
PanelUI.hide();
|
||||
yield panelHidePromise;
|
||||
}
|
||||
});
|
||||
|
||||
add_task(function asyncCleanup() {
|
||||
// reset the panel UI to the default state
|
||||
yield resetCustomization();
|
||||
ok(CustomizableUI.inDefaultState, "The UI is in default state again.");
|
||||
|
||||
// restore the initial location
|
||||
gBrowser.addTab(initialLocation);
|
||||
gBrowser.removeTab(newTab);
|
||||
});
|
|
@ -0,0 +1,44 @@
|
|||
/* 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";
|
||||
|
||||
let initialLocation = gBrowser.currentURI.spec;
|
||||
let newTab = null;
|
||||
|
||||
add_task(function() {
|
||||
info("Check Sync button functionality");
|
||||
|
||||
// add the Sync button to the panel
|
||||
CustomizableUI.addWidgetToArea("sync-button", CustomizableUI.AREA_PANEL);
|
||||
|
||||
// check the button's functionality
|
||||
yield PanelUI.show();
|
||||
|
||||
let syncButton = document.getElementById("sync-button");
|
||||
ok(syncButton, "The Sync button was added to the Panel Menu");
|
||||
syncButton.click();
|
||||
|
||||
newTab = gBrowser.selectedTab;
|
||||
yield promiseTabLoadEvent(newTab, "about:accounts");
|
||||
|
||||
is(gBrowser.currentURI.spec, "about:accounts", "Firefox Sync page opened");
|
||||
ok(!isPanelUIOpen(), "The panel closed");
|
||||
|
||||
if(isPanelUIOpen()) {
|
||||
let panelHidePromise = promisePanelHidden(window);
|
||||
PanelUI.hide();
|
||||
yield panelHidePromise;
|
||||
}
|
||||
});
|
||||
|
||||
add_task(function asyncCleanup() {
|
||||
// reset the panel UI to the default state
|
||||
yield resetCustomization();
|
||||
ok(CustomizableUI.inDefaultState, "The panel UI is in default state again.");
|
||||
|
||||
// restore the tabs
|
||||
gBrowser.addTab(initialLocation);
|
||||
gBrowser.removeTab(newTab);
|
||||
});
|
|
@ -0,0 +1,55 @@
|
|||
/* 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";
|
||||
|
||||
add_task(function() {
|
||||
info("Check Tab Groups button functionality");
|
||||
let deferred = Promise.defer();
|
||||
let timeout = null;
|
||||
|
||||
// add the Tab View button to the panel
|
||||
CustomizableUI.addWidgetToArea("tabview-button", CustomizableUI.AREA_PANEL);
|
||||
|
||||
window.addEventListener("tabviewhidden", function tabViewHidden() {
|
||||
clearTimeout(timeout);
|
||||
window.removeEventListener("tabviewhidden", tabViewHidden, false);
|
||||
ok(true, "Tab View is closed");
|
||||
deferred.resolve();
|
||||
}, false);
|
||||
|
||||
window.addEventListener("tabviewshown", function tabViewShown() {
|
||||
window.removeEventListener("tabviewshown", tabViewShown, false);
|
||||
ok(true, "Tab Groups are loaded");
|
||||
// close tabs view
|
||||
window.TabView.hide();
|
||||
timeout = setTimeout(() => {
|
||||
window.removeEventListener("tabviewhidden", tabViewHidden, false);
|
||||
deferred.reject("Tabs view wasn't hidden within 20000ms");
|
||||
}, 20000);
|
||||
}, false);
|
||||
|
||||
// check the button's functionality
|
||||
yield PanelUI.show();
|
||||
|
||||
let tabViewButton = document.getElementById("tabview-button");
|
||||
ok(tabViewButton, "Tab Groups button was added to the Panel Menu");
|
||||
tabViewButton.click();
|
||||
|
||||
yield deferred.promise;
|
||||
|
||||
ok(!isPanelUIOpen(), "The panel is closed");
|
||||
|
||||
if(isPanelUIOpen()) {
|
||||
let panelHidePromise = promisePanelHidden(window);
|
||||
PanelUI.hide();
|
||||
yield panelHidePromise;
|
||||
}
|
||||
});
|
||||
|
||||
add_task(function asyncCleanup() {
|
||||
// reset the panel to the default state
|
||||
yield resetCustomization();
|
||||
ok(CustomizableUI.inDefaultState, "UI is in default state again.");
|
||||
});
|
|
@ -30,6 +30,9 @@ add_task(function customizeToolbarAndKeepIt() {
|
|||
ok(toolbarElement.hasChildNodes(), "Toolbar should now have a button.");
|
||||
assertAreaPlacements(toolbarDOMID, ["open-file-button"]);
|
||||
|
||||
gNavToolbox.toolbarset.setAttribute("toolbar1", toolbarID + ":open-file-button");
|
||||
document.persist(gNavToolbox.toolbarset.id, "toolbar1");
|
||||
|
||||
yield startCustomizing();
|
||||
// First, exit customize mode without doing anything, and verify the toolbar doesn't get removed.
|
||||
yield endCustomizing();
|
||||
|
@ -41,6 +44,12 @@ add_task(function customizeToolbarAndKeepIt() {
|
|||
ok(toolbarElement.hasChildNodes(), "Toolbar should still have items in it.");
|
||||
assertAreaPlacements(toolbarDOMID, ["open-file-button"]);
|
||||
|
||||
let newWindow = yield openAndLoadWindow({}, true);
|
||||
is(newWindow.gNavToolbox.toolbarset.getAttribute("toolbar1"),
|
||||
gNavToolbox.toolbarset.getAttribute("toolbar1"),
|
||||
"Attribute should be the same in new window");
|
||||
yield promiseWindowClosed(newWindow);
|
||||
|
||||
// Then customize again, and this time empty out the toolbar and verify it *does* get removed.
|
||||
yield startCustomizing();
|
||||
let openFileButton = document.getElementById("open-file-button");
|
||||
|
@ -55,6 +64,12 @@ add_task(function customizeToolbarAndKeepIt() {
|
|||
ok(!CustomizableUI.inDefaultState, "Shouldn't be in default state because there's a non-collapsed toolbar again.");
|
||||
yield endCustomizing();
|
||||
ok(CustomizableUI.inDefaultState, "Should be in default state because the toolbar should have been removed.");
|
||||
|
||||
newWindow = yield openAndLoadWindow({}, true);
|
||||
ok(!newWindow.gNavToolbox.toolbarset.hasAttribute("toolbar1"),
|
||||
"Attribute should be gone in new window");
|
||||
yield promiseWindowClosed(newWindow);
|
||||
|
||||
ok(!toolbarElement.parentNode, "Toolbar should no longer be in the DOM.");
|
||||
cuiAreaType = CustomizableUI.getAreaType(toolbarDOMID);
|
||||
is(cuiAreaType, null, "CustomizableUI should have forgotten all about the area");
|
||||
|
@ -86,8 +101,22 @@ add_task(function resetShouldDealWithCustomToolbars() {
|
|||
ok(toolbarElement.hasChildNodes(), "Toolbar should now have a button.");
|
||||
assertAreaPlacements(toolbarDOMID, ["sync-button"]);
|
||||
|
||||
gNavToolbox.toolbarset.setAttribute("toolbar2", toolbarID + ":sync-button");
|
||||
document.persist(gNavToolbox.toolbarset.id, "toolbar2");
|
||||
|
||||
let newWindow = yield openAndLoadWindow({}, true);
|
||||
is(newWindow.gNavToolbox.toolbarset.getAttribute("toolbar2"),
|
||||
gNavToolbox.toolbarset.getAttribute("toolbar2"),
|
||||
"Attribute should be the same in new window");
|
||||
yield promiseWindowClosed(newWindow);
|
||||
|
||||
CustomizableUI.reset();
|
||||
|
||||
newWindow = yield openAndLoadWindow({}, true);
|
||||
ok(!newWindow.gNavToolbox.toolbarset.hasAttribute("toolbar2"),
|
||||
"Attribute should be gone in new window");
|
||||
yield promiseWindowClosed(newWindow);
|
||||
|
||||
ok(CustomizableUI.inDefaultState, "Should be in default state after reset.");
|
||||
let syncButton = document.getElementById("sync-button");
|
||||
ok(!syncButton, "Sync button shouldn't be in the document anymore.");
|
||||
|
@ -98,3 +127,10 @@ add_task(function resetShouldDealWithCustomToolbars() {
|
|||
is(cuiAreaType, null, "CustomizableUI should have forgotten all about the area");
|
||||
});
|
||||
|
||||
|
||||
add_task(function() {
|
||||
let newWin = yield openAndLoadWindow({}, true);
|
||||
ok(!newWin.gNavToolbox.toolbarset.hasAttribute("toolbar1"), "New window shouldn't have attribute toolbar1");
|
||||
ok(!newWin.gNavToolbox.toolbarset.hasAttribute("toolbar2"), "New window shouldn't have attribute toolbar2");
|
||||
yield promiseWindowClosed(newWin);
|
||||
});
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
/* 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";
|
||||
|
||||
add_task(function() {
|
||||
ok(CustomizableUI.inDefaultState, "Should start in default state.");
|
||||
this.otherWin = yield openAndLoadWindow({private: true}, true);
|
||||
yield startCustomizing(this.otherWin);
|
||||
let resetButton = this.otherWin.document.getElementById("customization-reset-button");
|
||||
ok(resetButton.disabled, "Reset button should be disabled");
|
||||
|
||||
if (typeof CustomizableUI.setToolbarVisibility == "function") {
|
||||
CustomizableUI.setToolbarVisibility("PersonalToolbar", true);
|
||||
} else {
|
||||
setToolbarVisibility(document.getElementById("PersonalToolbar"), true);
|
||||
}
|
||||
|
||||
let otherPersonalToolbar = this.otherWin.document.getElementById("PersonalToolbar");
|
||||
let personalToolbar = document.getElementById("PersonalToolbar");
|
||||
ok(!otherPersonalToolbar.collapsed, "Toolbar should be uncollapsed in private window");
|
||||
ok(!personalToolbar.collapsed, "Toolbar should be uncollapsed in normal window");
|
||||
ok(!resetButton.disabled, "Reset button should be enabled");
|
||||
|
||||
yield this.otherWin.gCustomizeMode.reset();
|
||||
|
||||
ok(otherPersonalToolbar.collapsed, "Toolbar should be collapsed in private window");
|
||||
ok(personalToolbar.collapsed, "Toolbar should be collapsed in normal window");
|
||||
ok(resetButton.disabled, "Reset button should be disabled");
|
||||
|
||||
yield endCustomizing(this.otherWin);
|
||||
|
||||
yield promiseWindowClosed(this.otherWin);
|
||||
});
|
||||
|
||||
|
||||
add_task(function asyncCleanup() {
|
||||
if (this.otherWin && !this.otherWin.closed) {
|
||||
yield promiseWindowClosed(this.otherWin);
|
||||
}
|
||||
if (!CustomizableUI.inDefaultState) {
|
||||
CustomizableUI.reset();
|
||||
}
|
||||
});
|
|
@ -0,0 +1,10 @@
|
|||
<html>
|
||||
<head>
|
||||
<title>Feeds test page</title>
|
||||
<link rel="alternate" type="application/rss+xml" href="test-feed.xml" title="Test feed">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
This is a test page for feeds
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,23 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<feed xmlns="http://www.w3.org/2005/Atom">
|
||||
|
||||
<title>Example Feed</title>
|
||||
<link href="http://example.org/"/>
|
||||
<updated>2010-08-22T18:30:02Z</updated>
|
||||
|
||||
<author>
|
||||
<name>John Doe</name>
|
||||
</author>
|
||||
<id>urn:uuid:e2df8375-99be-4848-b05e-b9d407555267</id>
|
||||
|
||||
<entry>
|
||||
|
||||
<title>Item</title>
|
||||
<link href="http://example.org/first"/>
|
||||
<id>urn:uuid:9e0f4bed-33d3-4a9d-97ab-ecaa31b3f14a</id>
|
||||
<updated>2010-08-22T18:30:02Z</updated>
|
||||
|
||||
<summary>Some text.</summary>
|
||||
</entry>
|
||||
|
||||
</feed>
|
|
@ -0,0 +1,11 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Test page</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
This is a test page
|
||||
</body>
|
||||
</html>
|
|
@ -1075,6 +1075,19 @@ BrowserGlue.prototype = {
|
|||
importBookmarks = true;
|
||||
} catch(ex) {}
|
||||
|
||||
// Support legacy bookmarks.html format for apps that depend on that format.
|
||||
let autoExportHTML = false;
|
||||
try {
|
||||
autoExportHTML = Services.prefs.getBoolPref("browser.bookmarks.autoExportHTML");
|
||||
} catch (ex) {} // Do not export.
|
||||
if (autoExportHTML) {
|
||||
// Sqlite.jsm and Places shutdown happen at profile-before-change, thus,
|
||||
// to be on the safe side, this should run earlier.
|
||||
AsyncShutdown.profileChangeTeardown.addBlocker(
|
||||
"Places: export bookmarks.html",
|
||||
() => BookmarkHTMLUtils.exportToFile(BookmarkHTMLUtils.defaultPath));
|
||||
}
|
||||
|
||||
Task.spawn(function() {
|
||||
// Check if Safe Mode or the user has required to restore bookmarks from
|
||||
// default profile's bookmarks.html
|
||||
|
@ -1132,10 +1145,6 @@ BrowserGlue.prototype = {
|
|||
// An import operation is about to run.
|
||||
// Don't try to recreate smart bookmarks if autoExportHTML is true or
|
||||
// smart bookmarks are disabled.
|
||||
let autoExportHTML = false;
|
||||
try {
|
||||
autoExportHTML = Services.prefs.getBoolPref("browser.bookmarks.autoExportHTML");
|
||||
} catch(ex) {}
|
||||
let smartBookmarksVersion = 0;
|
||||
try {
|
||||
smartBookmarksVersion = Services.prefs.getIntPref("browser.places.smartBookmarksVersion");
|
||||
|
@ -1238,19 +1247,6 @@ BrowserGlue.prototype = {
|
|||
this._idleService.removeIdleObserver(this, this._bookmarksBackupIdleTime);
|
||||
delete this._bookmarksBackupIdleTime;
|
||||
}
|
||||
|
||||
// Support legacy bookmarks.html format for apps that depend on that format.
|
||||
try {
|
||||
if (Services.prefs.getBoolPref("browser.bookmarks.autoExportHTML")) {
|
||||
// places-shutdown happens at profile-change-teardown, so here we
|
||||
// can safely add a profile-before-change blocker.
|
||||
AsyncShutdown.profileBeforeChange.addBlocker(
|
||||
"Places: bookmarks.html",
|
||||
() => BookmarkHTMLUtils.exportToFile(BookmarkHTMLUtils.defaultPath)
|
||||
.then(null, Cu.reportError)
|
||||
);
|
||||
}
|
||||
} catch (ex) {} // Do not export.
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim:set ts=2 sw=2 sts=2 et: */
|
||||
/* 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/. */
|
||||
|
||||
/**
|
||||
* Tests that nsBrowserGlue correctly exports bookmarks.html at shutdown if
|
||||
* browser.bookmarks.autoExportHTML is set to true.
|
||||
*/
|
||||
|
||||
function run_test() {
|
||||
run_next_test();
|
||||
}
|
||||
|
||||
add_task(function* () {
|
||||
remove_bookmarks_html();
|
||||
|
||||
Services.prefs.setBoolPref("browser.bookmarks.autoExportHTML", true);
|
||||
do_register_cleanup(() => Services.prefs.clearUserPref("browser.bookmarks.autoExportHTML"));
|
||||
|
||||
// Initialize nsBrowserGlue before Places.
|
||||
Cc["@mozilla.org/browser/browserglue;1"].getService(Ci.nsIBrowserGlue);
|
||||
|
||||
// Initialize Places through the History Service.
|
||||
Cc["@mozilla.org/browser/nav-history-service;1"]
|
||||
.getService(Ci.nsINavHistoryService);
|
||||
|
||||
Services.obs.addObserver(function observer() {
|
||||
Services.obs.removeObserver(observer, "profile-before-change");
|
||||
check_bookmarks_html();
|
||||
}, "profile-before-change", false);
|
||||
});
|
|
@ -9,6 +9,7 @@ support-files =
|
|||
distribution.ini
|
||||
|
||||
[test_421483.js]
|
||||
[test_browserGlue_bookmarkshtml.js]
|
||||
[test_browserGlue_corrupt.js]
|
||||
[test_browserGlue_corrupt_nobackup.js]
|
||||
[test_browserGlue_corrupt_nobackup_default.js]
|
||||
|
|
|
@ -2781,7 +2781,11 @@ let SessionStoreInternal = {
|
|||
|
||||
// only modify those aspects which aren't correct yet
|
||||
if (aWidth && aHeight && (aWidth != win_("width") || aHeight != win_("height"))) {
|
||||
aWindow.resizeTo(aWidth, aHeight);
|
||||
// Don't resize the window if it's currently maximized and we would
|
||||
// maximize it again shortly after.
|
||||
if (aSizeMode != "maximized" || win_("sizemode") != "maximized") {
|
||||
aWindow.resizeTo(aWidth, aHeight);
|
||||
}
|
||||
}
|
||||
if (!isNaN(aLeft) && !isNaN(aTop) && (aLeft != win_("screenX") || aTop != win_("screenY"))) {
|
||||
aWindow.moveTo(aLeft, aTop);
|
||||
|
|
|
@ -1,33 +1,35 @@
|
|||
[DEFAULT]
|
||||
support-files =
|
||||
browser_inspector_markup_edit.html
|
||||
browser_inspector_markup_mutation.html
|
||||
browser_inspector_markup_mutation_flashing.html
|
||||
browser_inspector_markup_navigation.html
|
||||
browser_inspector_markup_subset.html
|
||||
browser_inspector_markup_765105_tooltip.png
|
||||
browser_inspector_markup_950732.html
|
||||
browser_inspector_markup_962647_search.html
|
||||
doc_markup_edit.html
|
||||
doc_markup_flashing.html
|
||||
doc_markup_mutation.html
|
||||
doc_markup_navigation.html
|
||||
doc_markup_pagesize_01.html
|
||||
doc_markup_pagesize_02.html
|
||||
doc_markup_search.html
|
||||
doc_markup_tooltip.png
|
||||
head.js
|
||||
|
||||
[browser_bug896181_css_mixed_completion_new_attribute.js]
|
||||
# Bug 916763 - too many intermittent failures
|
||||
skip-if = true
|
||||
[browser_inspector_markup_edit.js]
|
||||
[browser_inspector_markup_edit_2.js]
|
||||
[browser_inspector_markup_edit_3.js]
|
||||
[browser_inspector_markup_edit_4.js]
|
||||
[browser_inspector_markup_add_attributes.js]
|
||||
[browser_inspector_markup_edit_outerhtml.js]
|
||||
skip-if = os == 'linux' && debug # bug 970240
|
||||
[browser_inspector_markup_edit_outerhtml2.js]
|
||||
[browser_inspector_markup_mutation.js]
|
||||
[browser_inspector_markup_mutation_flashing.js]
|
||||
[browser_inspector_markup_navigation.js]
|
||||
[browser_inspector_markup_subset.js]
|
||||
[browser_inspector_markup_765105_tooltip.js]
|
||||
[browser_inspector_markup_950732.js]
|
||||
[browser_inspector_markup_964014_copy_image_data.js]
|
||||
[browser_inspector_markup_968316_highlit_node_on_hover_then_select.js]
|
||||
[browser_inspector_markup_968316_highlight_node_after_mouseleave_mousemove.js]
|
||||
[browser_inspector_markup_962647_search.js]
|
||||
[browser_markupview_copy_image_data.js]
|
||||
[browser_markupview_css_completion_style_attribute.js]
|
||||
[browser_markupview_highlight_hover_01.js]
|
||||
[browser_markupview_highlight_hover_02.js]
|
||||
[browser_markupview_html_edit_01.js]
|
||||
[browser_markupview_html_edit_02.js]
|
||||
[browser_markupview_html_edit_03.js]
|
||||
[browser_markupview_image_tooltip.js]
|
||||
[browser_markupview_mutation_01.js]
|
||||
[browser_markupview_mutation_02.js]
|
||||
[browser_markupview_navigation.js]
|
||||
[browser_markupview_pagesize_01.js]
|
||||
[browser_markupview_pagesize_02.js]
|
||||
[browser_markupview_search_01.js]
|
||||
[browser_markupview_tag_edit_01.js]
|
||||
[browser_markupview_tag_edit_02.js]
|
||||
[browser_markupview_tag_edit_03.js]
|
||||
[browser_markupview_tag_edit_04.js]
|
||||
[browser_markupview_tag_edit_05.js]
|
||||
[browser_markupview_tag_edit_06.js]
|
||||
[browser_markupview_tag_edit_07.js]
|
||||
[browser_markupview_tag_edit_08.js]
|
||||
[browser_markupview_textcontent_edit_01.js]
|
||||
|
|
|
@ -1,176 +0,0 @@
|
|||
/* vim: set ts=2 et sw=2 tw=80: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
// Test CSS state is correctly determined and the corresponding suggestions are
|
||||
// displayed. i.e. CSS property suggestions are shown when cursor is like:
|
||||
// ```style="di|"``` where | is the cursor; And CSS value suggestion is
|
||||
// displayed when the cursor is like: ```style="display:n|"``` properly. No
|
||||
// suggestions should ever appear when the attribute is not a style attribute.
|
||||
// The correctness and cycling of the suggestions is covered in the ruleview
|
||||
// tests.
|
||||
function test() {
|
||||
let inspector;
|
||||
let {
|
||||
getInplaceEditorForSpan: inplaceEditor
|
||||
} = devtools.require("devtools/shared/inplace-editor");
|
||||
|
||||
waitForExplicitFinish();
|
||||
|
||||
// Will hold the doc we're viewing
|
||||
let doc;
|
||||
|
||||
// Holds the MarkupTool object we're testing.
|
||||
let markup;
|
||||
let editor;
|
||||
let state;
|
||||
// format :
|
||||
// [
|
||||
// what key to press,
|
||||
// expected input box value after keypress,
|
||||
// expected input.selectionStart,
|
||||
// expected input.selectionEnd,
|
||||
// is popup expected to be open ?
|
||||
// ]
|
||||
let testData = [
|
||||
['s', 's', 1, 1, false],
|
||||
['t', 'st', 2, 2, false],
|
||||
['y', 'sty', 3, 3, false],
|
||||
['l', 'styl', 4, 4, false],
|
||||
['e', 'style', 5, 5, false],
|
||||
['=', 'style=', 6, 6, false],
|
||||
['"', 'style="', 7, 7, false],
|
||||
['d', 'style="direction', 8, 16, true],
|
||||
['VK_DOWN', 'style="display', 8, 14, true],
|
||||
['VK_TAB', 'style="display', 14, 14, true],
|
||||
['VK_TAB', 'style="dominant-baseline', 24, 24, true],
|
||||
['VK_TAB', 'style="direction', 16, 16, true],
|
||||
['click_1', 'style="display', 14, 14, false],
|
||||
[':', 'style="display:', 15, 15, false],
|
||||
['n', 'style="display:none', 16, 19, false],
|
||||
['VK_BACK_SPACE', 'style="display:n', 16, 16, false],
|
||||
['VK_BACK_SPACE', 'style="display:', 15, 15, false],
|
||||
[' ', 'style="display: ', 16, 16, false],
|
||||
[' ', 'style="display: ', 17, 17, false],
|
||||
['i', 'style="display: inherit', 18, 24, true],
|
||||
['VK_RIGHT', 'style="display: inherit', 24, 24, false],
|
||||
[';', 'style="display: inherit;', 25, 25, false],
|
||||
[' ', 'style="display: inherit; ', 26, 26, false],
|
||||
[' ', 'style="display: inherit; ', 27, 27, false],
|
||||
['VK_LEFT', 'style="display: inherit; ', 26, 26, false],
|
||||
['c', 'style="display: inherit; caption-side ', 27, 38, true],
|
||||
['o', 'style="display: inherit; color ', 28, 31, true],
|
||||
['VK_RIGHT', 'style="display: inherit; color ', 31, 31, false],
|
||||
[' ', 'style="display: inherit; color ', 32, 32, false],
|
||||
['c', 'style="display: inherit; color c ', 33, 33, false],
|
||||
['VK_BACK_SPACE', 'style="display: inherit; color ', 32, 32, false],
|
||||
[':', 'style="display: inherit; color : ', 33, 33, false],
|
||||
['c', 'style="display: inherit; color :cadetblue ', 34, 42, true],
|
||||
['VK_DOWN', 'style="display: inherit; color :chartreuse ', 34, 43, true],
|
||||
['VK_RIGHT', 'style="display: inherit; color :chartreuse ', 43, 43, false],
|
||||
[' ', 'style="display: inherit; color :chartreuse ', 44, 44, false],
|
||||
['!', 'style="display: inherit; color :chartreuse !important; ', 45, 55, false],
|
||||
['VK_RIGHT', 'style="display: inherit; color :chartreuse !important; ', 55, 55, false],
|
||||
['VK_RETURN', 'style="display: inherit; color :chartreuse !important;"', -1, -1, false]
|
||||
];
|
||||
function startTests() {
|
||||
markup = inspector.markup;
|
||||
markup.expandAll().then(() => {
|
||||
let node = getContainerForRawNode(markup, doc.querySelector("#node14")).editor;
|
||||
let attr = node.newAttr;
|
||||
attr.focus();
|
||||
EventUtils.sendKey("return", inspector.panelWin);
|
||||
editor = inplaceEditor(attr);
|
||||
checkStateAndMoveOn(0);
|
||||
});
|
||||
}
|
||||
|
||||
function checkStateAndMoveOn(index) {
|
||||
if (index == testData.length) {
|
||||
finishUp();
|
||||
return;
|
||||
}
|
||||
|
||||
let [key] = testData[index];
|
||||
state = index;
|
||||
|
||||
info("pressing key " + key + " to get result: [" + testData[index].slice(1) +
|
||||
"] for state " + state);
|
||||
if (/(down|left|right|back_space|return)/ig.test(key)) {
|
||||
info("added event listener for down|left|right|back_space|return keys");
|
||||
editor.input.addEventListener("keypress", function onKeypress() {
|
||||
if (editor.input) {
|
||||
editor.input.removeEventListener("keypress", onKeypress);
|
||||
}
|
||||
info("inside event listener");
|
||||
checkState();
|
||||
})
|
||||
}
|
||||
else if (/click_[0-9]/.test(key)) {
|
||||
editor.once("after-suggest", checkState);
|
||||
let index = +key.split("_")[1];
|
||||
editor.popup._list.childNodes[index].click();
|
||||
editor.input.blur();
|
||||
return;
|
||||
}
|
||||
else {
|
||||
editor.once("after-suggest", checkState);
|
||||
}
|
||||
EventUtils.synthesizeKey(key, {}, inspector.panelWin);
|
||||
}
|
||||
|
||||
function checkState() {
|
||||
executeSoon(() => {
|
||||
info("After keypress for state " + state);
|
||||
let [key, completion, selStart, selEnd, popupOpen] = testData[state];
|
||||
if (selEnd != -1) {
|
||||
is(editor.input.value, completion,
|
||||
"Correct value is autocompleted for state " + state);
|
||||
is(editor.input.selectionStart, selStart,
|
||||
"Selection is starting at the right location for state " + state);
|
||||
is(editor.input.selectionEnd, selEnd,
|
||||
"Selection is ending at the right location for state " + state);
|
||||
if (popupOpen) {
|
||||
ok(editor.popup.isOpen, "Popup is open for state " + state);
|
||||
}
|
||||
else {
|
||||
ok(editor.popup._panel.state != "open" &&
|
||||
editor.popup._panel.state != "showing",
|
||||
"Popup is closed for state " + state);
|
||||
}
|
||||
}
|
||||
else {
|
||||
let editor = getContainerForRawNode(markup, doc.querySelector("#node14")).editor;
|
||||
let attr = editor.attrs["style"].querySelector(".editable");
|
||||
is(attr.textContent, completion,
|
||||
"Correct value is persisted after pressing Enter for state " + state);
|
||||
}
|
||||
checkStateAndMoveOn(state + 1);
|
||||
});
|
||||
}
|
||||
|
||||
// Create the helper tab for parsing...
|
||||
gBrowser.selectedTab = gBrowser.addTab();
|
||||
gBrowser.selectedBrowser.addEventListener("load", function onload() {
|
||||
gBrowser.selectedBrowser.removeEventListener("load", onload, true);
|
||||
doc = content.document;
|
||||
waitForFocus(setupTest, content);
|
||||
}, true);
|
||||
content.location = "http://mochi.test:8888/browser/browser/devtools/markupview/test/browser_inspector_markup_edit.html";
|
||||
|
||||
function setupTest() {
|
||||
var target = TargetFactory.forTab(gBrowser.selectedTab);
|
||||
gDevTools.showToolbox(target, "inspector").then(function(toolbox) {
|
||||
inspector = toolbox.getCurrentPanel();
|
||||
startTests();
|
||||
});
|
||||
}
|
||||
|
||||
function finishUp() {
|
||||
while (markup.undo.canUndo()) {
|
||||
markup.undo.undo();
|
||||
}
|
||||
doc = inspector = null;
|
||||
gBrowser.removeCurrentTab();
|
||||
finish();
|
||||
}
|
||||
}
|
|
@ -1,105 +0,0 @@
|
|||
/* Any copyright", " is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Tests that the markup view loads only as many nodes as specified
|
||||
* by the devtools.markup.pagesize preference.
|
||||
*/
|
||||
|
||||
let {devtools} = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
|
||||
let promise = devtools.require("sdk/core/promise");
|
||||
let {Task} = Cu.import("resource://gre/modules/Task.jsm", {});
|
||||
|
||||
// Make sure nodes are hidden when there are more than 5 in a row
|
||||
registerCleanupFunction(function() {
|
||||
Services.prefs.clearUserPref("devtools.markup.pagesize");
|
||||
});
|
||||
Services.prefs.setIntPref("devtools.markup.pagesize", 5);
|
||||
|
||||
function test() {
|
||||
waitForExplicitFinish();
|
||||
|
||||
let doc;
|
||||
let inspector;
|
||||
let markup;
|
||||
|
||||
gBrowser.selectedTab = gBrowser.addTab();
|
||||
gBrowser.selectedBrowser.addEventListener("load", function onload() {
|
||||
gBrowser.selectedBrowser.removeEventListener("load", onload, true);
|
||||
doc = content.document;
|
||||
waitForFocus(runTests, content);
|
||||
}, true);
|
||||
content.location = "http://mochi.test:8888/browser/browser/devtools/markupview/test/browser_inspector_markup_950732.html";
|
||||
|
||||
function runTests() {
|
||||
Task.spawn(function() {
|
||||
yield openMarkupView();
|
||||
yield selectUL();
|
||||
yield reloadPage();
|
||||
yield showAllNodes();
|
||||
|
||||
assertAllNodesAreVisible();
|
||||
finishUp();
|
||||
}).then(null, Cu.reportError);
|
||||
}
|
||||
|
||||
function openMarkupView() {
|
||||
let deferred = promise.defer();
|
||||
|
||||
var target = TargetFactory.forTab(gBrowser.selectedTab);
|
||||
let toolbox = gDevTools.showToolbox(target, "inspector").then(function(toolbox) {
|
||||
inspector = toolbox.getCurrentPanel();
|
||||
markup = inspector.markup;
|
||||
inspector.once("inspector-updated", deferred.resolve);
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
function selectUL() {
|
||||
let deferred = promise.defer();
|
||||
|
||||
let container = getContainerForRawNode(markup, doc.querySelector("ul"));
|
||||
let win = container.elt.ownerDocument.defaultView;
|
||||
|
||||
EventUtils.sendMouseEvent({type: "mousedown"}, container.elt, win);
|
||||
inspector.once("inspector-updated", deferred.resolve);
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
function reloadPage() {
|
||||
let deferred = promise.defer();
|
||||
|
||||
inspector.once("new-root", () => {
|
||||
doc = content.document;
|
||||
markup = inspector.markup;
|
||||
markup._waitForChildren().then(deferred.resolve);
|
||||
});
|
||||
content.location.reload();
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
function showAllNodes() {
|
||||
let container = getContainerForRawNode(markup, doc.querySelector("ul"));
|
||||
let button = container.elt.querySelector("button");
|
||||
let win = button.ownerDocument.defaultView;
|
||||
|
||||
EventUtils.sendMouseEvent({type: "click"}, button, win);
|
||||
return markup._waitForChildren();
|
||||
}
|
||||
|
||||
function assertAllNodesAreVisible() {
|
||||
let ul = doc.querySelector("ul");
|
||||
let container = getContainerForRawNode(markup, ul);
|
||||
ok(!container.elt.querySelector("button"), "All nodes button isn't here");
|
||||
is(container.children.childNodes.length, ul.children.length);
|
||||
}
|
||||
|
||||
function finishUp() {
|
||||
doc = inspector = markup = null;
|
||||
gBrowser.removeCurrentTab();
|
||||
finish();
|
||||
}
|
||||
}
|
|
@ -1,50 +0,0 @@
|
|||
/* vim: set ts=2 et sw=2 tw=80: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
// Test that searching for nodes using the selector-search input expands and
|
||||
// selects the right nodes in the markup-view, even when those nodes are deeply
|
||||
// nested (and therefore not attached yet when the markup-view is initialized).
|
||||
|
||||
const TEST_URL = "http://mochi.test:8888/browser/browser/devtools/markupview/test/browser_inspector_markup_962647_search.html";
|
||||
|
||||
function test() {
|
||||
waitForExplicitFinish();
|
||||
|
||||
let p = content.document.querySelector("p");
|
||||
Task.spawn(function() {
|
||||
info("loading the test page");
|
||||
yield addTab(TEST_URL);
|
||||
|
||||
info("opening the inspector");
|
||||
let {inspector, toolbox} = yield openInspector();
|
||||
|
||||
ok(!getContainerForRawNode(inspector.markup, getNode("em")),
|
||||
"The <em> tag isn't present yet in the markup-view");
|
||||
|
||||
// Searching for the innermost element first makes sure that the inspector
|
||||
// back-end is able to attach the resulting node to the tree it knows at the
|
||||
// moment. When the inspector is started, the <body> is the default selected
|
||||
// node, and only the parents up to the ROOT are known, and its direct children
|
||||
info("searching for the innermost child: <em>");
|
||||
let updated = inspector.once("inspector-updated");
|
||||
searchUsingSelectorSearch("em", inspector);
|
||||
yield updated;
|
||||
|
||||
ok(getContainerForRawNode(inspector.markup, getNode("em")),
|
||||
"The <em> tag is now imported in the markup-view");
|
||||
is(inspector.selection.node, getNode("em"),
|
||||
"The <em> tag is the currently selected node");
|
||||
|
||||
info("searching for other nodes too");
|
||||
for (let node of ["span", "li", "ul"]) {
|
||||
let updated = inspector.once("inspector-updated");
|
||||
searchUsingSelectorSearch(node, inspector);
|
||||
yield updated;
|
||||
is(inspector.selection.node, getNode(node),
|
||||
"The <" + node + "> tag is the currently selected node");
|
||||
}
|
||||
|
||||
gBrowser.removeCurrentTab();
|
||||
}).then(null, ok.bind(null, false)).then(finish);
|
||||
}
|
|
@ -1,47 +0,0 @@
|
|||
/* vim: set ts=2 et sw=2 tw=80: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
// Test that when after an element is selected and highlighted on hover, if the
|
||||
// mouse leaves the markup-view and comes back again on the same element, that
|
||||
// the highlighter is shown again on the node
|
||||
|
||||
function test() {
|
||||
waitForExplicitFinish();
|
||||
|
||||
gBrowser.selectedTab = gBrowser.addTab();
|
||||
gBrowser.selectedBrowser.addEventListener("load", function onload(evt) {
|
||||
gBrowser.selectedBrowser.removeEventListener("load", onload, true);
|
||||
waitForFocus(startTests, content);
|
||||
}, true);
|
||||
|
||||
content.location = "data:text/html;charset=utf-8,<p>Select me!</p>";
|
||||
}
|
||||
|
||||
function startTests(aInspector, aToolbox) {
|
||||
let p = content.document.querySelector("p");
|
||||
Task.spawn(function() {
|
||||
info("opening the inspector tool");
|
||||
let {inspector, toolbox} = yield openInspector();
|
||||
|
||||
info("hover over the <p> line in the markup-view so that it's the currently hovered node");
|
||||
yield hoverContainer(p, inspector);
|
||||
|
||||
info("select the <p> markup-container line by clicking");
|
||||
yield clickContainer(p, inspector);
|
||||
ok(isHighlighterVisible(), "the highlighter is shown");
|
||||
|
||||
info("mouse-leave the markup-view");
|
||||
yield mouseLeaveMarkupView(inspector);
|
||||
ok(!isHighlighterVisible(), "the highlighter is hidden after mouseleave");
|
||||
|
||||
info("hover over the <p> line again, which is still selected");
|
||||
yield hoverContainer(p, inspector);
|
||||
ok(isHighlighterVisible(), "the highlighter is visible again");
|
||||
}).then(null, ok.bind(null, false)).then(endTests);
|
||||
}
|
||||
|
||||
function endTests() {
|
||||
gBrowser.removeCurrentTab();
|
||||
finish();
|
||||
}
|
|
@ -1,54 +0,0 @@
|
|||
/* vim: set ts=2 et sw=2 tw=80: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
// Test that when first hovering over a node and immediately after selecting it
|
||||
// by clicking on it leaves the highlighter visible for as long as the mouse is
|
||||
// over the node
|
||||
|
||||
function test() {
|
||||
waitForExplicitFinish();
|
||||
|
||||
gBrowser.selectedTab = gBrowser.addTab();
|
||||
gBrowser.selectedBrowser.addEventListener("load", function onload(evt) {
|
||||
gBrowser.selectedBrowser.removeEventListener("load", onload, true);
|
||||
waitForFocus(startTests, content);
|
||||
}, true);
|
||||
|
||||
content.location = "data:text/html;charset=utf-8,<p>It's going to be legen....</p>";
|
||||
}
|
||||
|
||||
function startTests(aInspector, aToolbox) {
|
||||
let p = content.document.querySelector("p");
|
||||
Task.spawn(function() {
|
||||
info("opening the inspector tool");
|
||||
let {inspector, toolbox} = yield openInspector();
|
||||
|
||||
info("hovering over the <p> line in the markup-view");
|
||||
yield hoverContainer(p, inspector);
|
||||
ok(isHighlighterVisible(), "the highlighter is still visible");
|
||||
|
||||
info("selecting the <p> line by clicking in the markup-view");
|
||||
yield clickContainer(p, inspector);
|
||||
|
||||
p.textContent = "wait for it ....";
|
||||
info("wait and see if the highlighter stays visible even after the node was selected");
|
||||
yield waitForTheBrieflyShowBoxModelTimeout();
|
||||
|
||||
p.textContent = "dary!!!!";
|
||||
ok(isHighlighterVisible(), "the highlighter is still visible");
|
||||
}).then(null, ok.bind(null, false)).then(endTests);
|
||||
}
|
||||
|
||||
function endTests() {
|
||||
gBrowser.removeCurrentTab();
|
||||
finish();
|
||||
}
|
||||
|
||||
function waitForTheBrieflyShowBoxModelTimeout() {
|
||||
let deferred = promise.defer();
|
||||
// Note that the current timeout is 1 sec and is neither configurable nor
|
||||
// exported anywhere we can access, so hard-coding the timeout
|
||||
content.setTimeout(deferred.resolve, 1500);
|
||||
return deferred.promise;
|
||||
}
|
|
@ -1,171 +0,0 @@
|
|||
/* Any copyright", " is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Tests that adding various types of attributes to nodes in the markup-view
|
||||
* works as expected. Also checks that the changes are properly undoable and
|
||||
* redoable. For each step in the test, we:
|
||||
* - Create a new DIV
|
||||
* - Make the change, check that the change was made as we expect
|
||||
* - Undo the change, check that the node is back in its original state
|
||||
* - Redo the change, check that the node change was made again correctly.
|
||||
*/
|
||||
|
||||
waitForExplicitFinish();
|
||||
requestLongerTimeout(2);
|
||||
|
||||
let TEST_URL = "data:text/html,<div>markup-view attributes addition test</div>";
|
||||
let TEST_DATA = [{
|
||||
desc: "Add an attribute value without closing \"",
|
||||
enteredText: 'style="display: block;',
|
||||
expectedAttributes: {
|
||||
style: "display: block;"
|
||||
}
|
||||
}, {
|
||||
desc: "Add an attribute value without closing '",
|
||||
enteredText: "style='display: inline;",
|
||||
expectedAttributes: {
|
||||
style: "display: inline;"
|
||||
}
|
||||
}, {
|
||||
desc: "Add an attribute wrapped with with double quotes double quote in it",
|
||||
enteredText: 'style="display: "inline',
|
||||
expectedAttributes: {
|
||||
style: "display: ",
|
||||
inline: ""
|
||||
}
|
||||
}, {
|
||||
desc: "Add an attribute wrapped with single quotes with single quote in it",
|
||||
enteredText: "style='display: 'inline",
|
||||
expectedAttributes: {
|
||||
style: "display: ",
|
||||
inline: ""
|
||||
}
|
||||
}, {
|
||||
desc: "Add an attribute with no value",
|
||||
enteredText: "disabled",
|
||||
expectedAttributes: {
|
||||
disabled: ""
|
||||
}
|
||||
}, {
|
||||
desc: "Add multiple attributes with no value",
|
||||
enteredText: "disabled autofocus",
|
||||
expectedAttributes: {
|
||||
disabled: "",
|
||||
autofocus: ""
|
||||
}
|
||||
}, {
|
||||
desc: "Add multiple attributes with no value, and some with value",
|
||||
enteredText: "disabled name='name' data-test='test' autofocus",
|
||||
expectedAttributes: {
|
||||
disabled: "",
|
||||
autofocus: "",
|
||||
name: "name",
|
||||
'data-test': "test"
|
||||
}
|
||||
}, {
|
||||
desc: "Add attribute with xmlns",
|
||||
enteredText: "xmlns:edi='http://ecommerce.example.org/schema'",
|
||||
expectedAttributes: {
|
||||
'xmlns:edi': "http://ecommerce.example.org/schema"
|
||||
}
|
||||
}, {
|
||||
desc: "Mixed single and double quotes",
|
||||
enteredText: "name=\"hi\" maxlength='not a number'",
|
||||
expectedAttributes: {
|
||||
maxlength: "not a number",
|
||||
name: "hi"
|
||||
}
|
||||
}, {
|
||||
desc: "Invalid attribute name",
|
||||
enteredText: "x='y' <why-would-you-do-this>=\"???\"",
|
||||
expectedAttributes: {
|
||||
x: "y"
|
||||
}
|
||||
}, {
|
||||
desc: "Double quote wrapped in single quotes",
|
||||
enteredText: "x='h\"i'",
|
||||
expectedAttributes: {
|
||||
x: "h\"i"
|
||||
}
|
||||
}, {
|
||||
desc: "Single quote wrapped in double quotes",
|
||||
enteredText: "x=\"h'i\"",
|
||||
expectedAttributes: {
|
||||
x: "h'i"
|
||||
}
|
||||
}, {
|
||||
desc: "No quote wrapping",
|
||||
enteredText: "a=b x=y data-test=Some spaced data",
|
||||
expectedAttributes: {
|
||||
a: "b",
|
||||
x: "y",
|
||||
"data-test": "Some",
|
||||
spaced: "",
|
||||
data: ""
|
||||
}
|
||||
}, {
|
||||
desc: "Duplicate Attributes",
|
||||
enteredText: "a=b a='c' a=\"d\"",
|
||||
expectedAttributes: {
|
||||
a: "b"
|
||||
}
|
||||
}, {
|
||||
desc: "Inline styles",
|
||||
enteredText: "style=\"font-family: 'Lucida Grande', sans-serif; font-size: 75%;\"",
|
||||
expectedAttributes: {
|
||||
style: "font-family: 'Lucida Grande', sans-serif; font-size: 75%;"
|
||||
}
|
||||
}, {
|
||||
desc: "Object attribute names",
|
||||
enteredText: "toString=\"true\" hasOwnProperty=\"false\"",
|
||||
expectedAttributes: {
|
||||
toString: "true",
|
||||
hasOwnProperty: "false"
|
||||
}
|
||||
}, {
|
||||
desc: "Add event handlers",
|
||||
enteredText: "onclick=\"javascript: throw new Error('wont fire');\" onload=\"alert('here');\"",
|
||||
expectedAttributes: {
|
||||
onclick: "javascript: throw new Error('wont fire');",
|
||||
onload: "alert('here');"
|
||||
}
|
||||
}];
|
||||
|
||||
function test() {
|
||||
Task.spawn(function() {
|
||||
info("Opening the inspector on the test URL");
|
||||
let args = yield addTab(TEST_URL).then(openInspector);
|
||||
let inspector = args.inspector;
|
||||
let markup = inspector.markup;
|
||||
|
||||
info("Selecting the test node");
|
||||
let div = getNode("div");
|
||||
yield selectNode(div, inspector);
|
||||
let editor = getContainerForRawNode(markup, div).editor;
|
||||
|
||||
for (let test of TEST_DATA) {
|
||||
info("Starting test: " + test.desc);
|
||||
|
||||
info("Enter the new attribute(s) test: " + test.enteredText);
|
||||
let nodeMutated = inspector.once("markupmutation");
|
||||
setEditableFieldValue(editor.newAttr, test.enteredText, inspector);
|
||||
yield nodeMutated;
|
||||
|
||||
info("Assert that the attribute(s) has/have been applied correctly");
|
||||
assertAttributes(div, test.expectedAttributes);
|
||||
|
||||
info("Undo the change");
|
||||
yield undoChange(inspector);
|
||||
|
||||
info("Assert that the attribute(s) has/have been removed correctly");
|
||||
assertAttributes(div, {});
|
||||
}
|
||||
|
||||
yield inspector.once("inspector-updated");
|
||||
|
||||
gBrowser.removeCurrentTab();
|
||||
}).then(null, ok.bind(null, false)).then(finish);
|
||||
}
|
|
@ -1,441 +0,0 @@
|
|||
/* Any copyright", " is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Tests that various editors work as expected. Also checks
|
||||
* that the various changes are properly undoable and redoable.
|
||||
* For each step in the test, we:
|
||||
* - Run the setup for that test (if any)
|
||||
* - Check that the node we're editing is as we expect
|
||||
* - Make the change, check that the change was made as we expect
|
||||
* - Undo the change, check that the node is back in its original state
|
||||
* - Redo the change, check that the node change was made again correctly.
|
||||
*
|
||||
* This test mostly tries to verify that the editor makes changes to the
|
||||
* underlying DOM, not that the UI updates - UI updates are based on
|
||||
* underlying DOM changes, and the mutation tests should cover those cases.
|
||||
*/
|
||||
|
||||
waitForExplicitFinish();
|
||||
|
||||
let doc, inspector, markup;
|
||||
|
||||
let TEST_URL = "http://mochi.test:8888/browser/browser/devtools/markupview/test/browser_inspector_markup_edit.html";
|
||||
let LONG_ATTRIBUTE = "ABCDEFGHIJKLMNOPQRSTUVWXYZ-ABCDEFGHIJKLMNOPQRSTUVWXYZ-ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ-ABCDEFGHIJKLMNOPQRSTUVWXYZ-ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
||||
let LONG_ATTRIBUTE_COLLAPSED = "ABCDEFGHIJKLMNOPQRSTUVWXYZ-ABCDEFGHIJKLMNOPQRSTUVWXYZ-ABCDEF\u2026UVWXYZ-ABCDEFGHIJKLMNOPQRSTUVWXYZ-ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
||||
let DATA_URL_INLINE_STYLE='color: red; background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAABlBMVEUAAAD///+l2Z/dAAAAM0lEQVR4nGP4/5/h/1+G/58ZDrAz3D/McH8yw83NDDeNGe4Ug9C9zwz3gVLMDA/A6P9/AFGGFyjOXZtQAAAAAElFTkSuQmCC");';
|
||||
let DATA_URL_INLINE_STYLE_COLLAPSED='color: red; background: url("data:image/png;base64,iVBORw0KG\u2026NDDeNGe4Ug9C9zwz3gVLMDA/A6P9/AFGGFyjOXZtQAAAAAElFTkSuQmCC");';
|
||||
let DATA_URL_ATTRIBUTE = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAABlBMVEUAAAD///+l2Z/dAAAAM0lEQVR4nGP4/5/h/1+G/58ZDrAz3D/McH8yw83NDDeNGe4Ug9C9zwz3gVLMDA/A6P9/AFGGFyjOXZtQAAAAAElFTkSuQmCC";
|
||||
let DATA_URL_ATTRIBUTE_COLLAPSED = "data:image/png;base64,iVBORw0K\u20269/AFGGFyjOXZtQAAAAAElFTkSuQmCC";
|
||||
|
||||
let TEST_DATA = [
|
||||
{
|
||||
desc: "Change an attribute",
|
||||
before: function() {
|
||||
assertAttributes(doc.querySelector("#node1"), {
|
||||
id: "node1",
|
||||
class: "node1"
|
||||
});
|
||||
},
|
||||
execute: function(after) {
|
||||
inspector.once("markupmutation", after);
|
||||
let editor = getContainerForRawNode(markup, doc.querySelector("#node1")).editor;
|
||||
let attr = editor.attrs["class"].querySelector(".editable");
|
||||
setEditableFieldValue(attr, 'class="changednode1"', inspector);
|
||||
},
|
||||
after: function() {
|
||||
assertAttributes(doc.querySelector("#node1"), {
|
||||
id: "node1",
|
||||
class: "changednode1"
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
desc: 'Try changing an attribute to a quote (") - this should result ' +
|
||||
'in it being set to an empty string',
|
||||
before: function() {
|
||||
assertAttributes(doc.querySelector("#node22"), {
|
||||
id: "node22",
|
||||
class: "unchanged"
|
||||
});
|
||||
},
|
||||
execute: function(after) {
|
||||
let editor = getContainerForRawNode(markup, doc.querySelector("#node22")).editor;
|
||||
let attr = editor.attrs["class"].querySelector(".editable");
|
||||
setEditableFieldValue(attr, 'class="""', inspector);
|
||||
inspector.once("markupmutation", after);
|
||||
},
|
||||
after: function() {
|
||||
assertAttributes(doc.querySelector("#node22"), {
|
||||
id: "node22",
|
||||
class: ""
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
desc: "Remove an attribute",
|
||||
before: function() {
|
||||
assertAttributes(doc.querySelector("#node4"), {
|
||||
id: "node4",
|
||||
class: "node4"
|
||||
});
|
||||
},
|
||||
execute: function(after) {
|
||||
inspector.once("markupmutation", after);
|
||||
let editor = getContainerForRawNode(markup, doc.querySelector("#node4")).editor;
|
||||
let attr = editor.attrs["class"].querySelector(".editable");
|
||||
setEditableFieldValue(attr, '', inspector);
|
||||
},
|
||||
after: function() {
|
||||
assertAttributes(doc.querySelector("#node4"), {
|
||||
id: "node4",
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
desc: "Add an attribute by clicking the empty space after a node",
|
||||
before: function() {
|
||||
assertAttributes(doc.querySelector("#node14"), {
|
||||
id: "node14",
|
||||
});
|
||||
},
|
||||
execute: function(after) {
|
||||
inspector.once("markupmutation", after);
|
||||
let editor = getContainerForRawNode(markup, doc.querySelector("#node14")).editor;
|
||||
let attr = editor.newAttr;
|
||||
setEditableFieldValue(attr, 'class="newclass" style="color:green"', inspector);
|
||||
},
|
||||
after: function() {
|
||||
assertAttributes(doc.querySelector("#node14"), {
|
||||
id: "node14",
|
||||
class: "newclass",
|
||||
style: "color:green"
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
desc: 'Try add an attribute containing a quote (") attribute by ' +
|
||||
'clicking the empty space after a node - this should result ' +
|
||||
'in it being set to an empty string',
|
||||
before: function() {
|
||||
assertAttributes(doc.querySelector("#node23"), {
|
||||
id: "node23",
|
||||
});
|
||||
},
|
||||
execute: function(after) {
|
||||
let editor = getContainerForRawNode(markup, doc.querySelector("#node23")).editor;
|
||||
let attr = editor.newAttr;
|
||||
setEditableFieldValue(attr, 'class="newclass" style="""', inspector);
|
||||
inspector.once("markupmutation", after);
|
||||
},
|
||||
after: function() {
|
||||
assertAttributes(doc.querySelector("#node23"), {
|
||||
id: "node23",
|
||||
class: "newclass",
|
||||
style: ""
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
desc: "Try add attributes by adding to an existing attribute's entry",
|
||||
before: function() {
|
||||
assertAttributes(doc.querySelector("#node24"), {
|
||||
id: "node24",
|
||||
});
|
||||
},
|
||||
execute: function(after) {
|
||||
let editor = getContainerForRawNode(markup, doc.querySelector("#node24")).editor;
|
||||
let attr = editor.attrs["id"].querySelector(".editable");
|
||||
setEditableFieldValue(attr, attr.textContent + ' class="""', inspector);
|
||||
inspector.once("markupmutation", after);
|
||||
},
|
||||
after: function() {
|
||||
assertAttributes(doc.querySelector("#node24"), {
|
||||
id: "node24",
|
||||
class: ""
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
desc: "Try to add long attribute to make sure it is collapsed in attribute editor.",
|
||||
before: function() {
|
||||
assertAttributes(doc.querySelector("#node24"), {
|
||||
id: "node24",
|
||||
class: ""
|
||||
});
|
||||
},
|
||||
execute: function(after) {
|
||||
let editor = getContainerForRawNode(markup, doc.querySelector("#node24")).editor;
|
||||
let attr = editor.newAttr;
|
||||
setEditableFieldValue(attr, 'data-long="'+LONG_ATTRIBUTE+'"', inspector);
|
||||
inspector.once("markupmutation", after);
|
||||
},
|
||||
after: function() {
|
||||
|
||||
let editor = getContainerForRawNode(markup, doc.querySelector("#node24")).editor;
|
||||
let visibleAttrText = editor.attrs["data-long"].querySelector(".attr-value").textContent;
|
||||
is (visibleAttrText, LONG_ATTRIBUTE_COLLAPSED)
|
||||
|
||||
assertAttributes(doc.querySelector("#node24"), {
|
||||
id: "node24",
|
||||
class: "",
|
||||
'data-long':LONG_ATTRIBUTE
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
desc: "Try to modify the collapsed long attribute, making sure it expands.",
|
||||
before: function() {
|
||||
assertAttributes(doc.querySelector("#node24"), {
|
||||
id: "node24",
|
||||
class: "",
|
||||
'data-long': LONG_ATTRIBUTE
|
||||
});
|
||||
},
|
||||
execute: function(after) {
|
||||
let editor = getContainerForRawNode(markup, doc.querySelector("#node24")).editor;
|
||||
let attr = editor.attrs["data-long"].querySelector(".editable");
|
||||
|
||||
// Check to make sure it has expanded after focus
|
||||
attr.focus();
|
||||
EventUtils.sendKey("return", inspector.panelWin);
|
||||
let input = inplaceEditor(attr).input;
|
||||
is (input.value, 'data-long="'+LONG_ATTRIBUTE+'"');
|
||||
EventUtils.sendKey("escape", inspector.panelWin);
|
||||
|
||||
setEditableFieldValue(attr, input.value + ' data-short="ABC"', inspector);
|
||||
inspector.once("markupmutation", after);
|
||||
},
|
||||
after: function() {
|
||||
|
||||
let editor = getContainerForRawNode(markup, doc.querySelector("#node24")).editor;
|
||||
let visibleAttrText = editor.attrs["data-long"].querySelector(".attr-value").textContent;
|
||||
is (visibleAttrText, LONG_ATTRIBUTE_COLLAPSED)
|
||||
|
||||
assertAttributes(doc.querySelector("#node24"), {
|
||||
id: "node24",
|
||||
class: "",
|
||||
'data-long': LONG_ATTRIBUTE,
|
||||
"data-short": "ABC"
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
desc: "Try to add long data URL to make sure it is collapsed in attribute editor.",
|
||||
before: function() {
|
||||
assertAttributes(doc.querySelector("#node-data-url"), {
|
||||
id: "node-data-url"
|
||||
});
|
||||
},
|
||||
execute: function(after) {
|
||||
let editor = getContainerForRawNode(markup, doc.querySelector("#node-data-url")).editor;
|
||||
let attr = editor.newAttr;
|
||||
setEditableFieldValue(attr, 'src="'+DATA_URL_ATTRIBUTE+'"', inspector);
|
||||
inspector.once("markupmutation", after);
|
||||
},
|
||||
after: function() {
|
||||
|
||||
let editor = getContainerForRawNode(markup, doc.querySelector("#node-data-url")).editor;
|
||||
let visibleAttrText = editor.attrs["src"].querySelector(".attr-value").textContent;
|
||||
is (visibleAttrText, DATA_URL_ATTRIBUTE_COLLAPSED);
|
||||
|
||||
let node = doc.querySelector("#node-data-url");
|
||||
is (node.width, 16, "Image width has been set after data url src.");
|
||||
is (node.height, 16, "Image height has been set after data url src.");
|
||||
|
||||
assertAttributes(node, {
|
||||
id: "node-data-url",
|
||||
"src": DATA_URL_ATTRIBUTE
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
desc: "Try to add long data URL to make sure it is collapsed in attribute editor.",
|
||||
before: function() {
|
||||
assertAttributes(doc.querySelector("#node-data-url-style"), {
|
||||
id: "node-data-url-style"
|
||||
});
|
||||
},
|
||||
execute: function(after) {
|
||||
let editor = getContainerForRawNode(markup, doc.querySelector("#node-data-url-style")).editor;
|
||||
let attr = editor.newAttr;
|
||||
setEditableFieldValue(attr, "style='"+DATA_URL_INLINE_STYLE+"'", inspector);
|
||||
inspector.once("markupmutation", after);
|
||||
},
|
||||
after: function() {
|
||||
|
||||
let editor = getContainerForRawNode(markup, doc.querySelector("#node-data-url-style")).editor;
|
||||
let visibleAttrText = editor.attrs["style"].querySelector(".attr-value").textContent;
|
||||
is (visibleAttrText, DATA_URL_INLINE_STYLE_COLLAPSED)
|
||||
|
||||
assertAttributes(doc.querySelector("#node-data-url-style"), {
|
||||
id: "node-data-url-style",
|
||||
'style': DATA_URL_INLINE_STYLE
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
desc: "Edit text",
|
||||
before: function() {
|
||||
let node = doc.querySelector('.node6').firstChild;
|
||||
is(node.nodeValue, "line6", "Text should be unchanged");
|
||||
},
|
||||
execute: function(after) {
|
||||
inspector.once("markupmutation", after);
|
||||
let node = doc.querySelector('.node6').firstChild;
|
||||
let editor = getContainerForRawNode(markup, node).editor;
|
||||
let field = editor.elt.querySelector("pre");
|
||||
setEditableFieldValue(field, "New text", inspector);
|
||||
},
|
||||
after: function() {
|
||||
let node = doc.querySelector('.node6').firstChild;
|
||||
is(node.nodeValue, "New text", "Text should be changed.");
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Add an attribute value containing < > ü \" & '",
|
||||
before: function() {
|
||||
assertAttributes(doc.querySelector("#node25"), {
|
||||
id: "node25",
|
||||
});
|
||||
},
|
||||
execute: function(after) {
|
||||
inspector.once("markupmutation", after);
|
||||
let editor = getContainerForRawNode(markup, doc.querySelector("#node25")).editor;
|
||||
let attr = editor.newAttr;
|
||||
setEditableFieldValue(attr, 'src="somefile.html?param1=<a>¶m2=ü¶m3=\'"\'"', inspector);
|
||||
},
|
||||
after: function() {
|
||||
assertAttributes(doc.querySelector("#node25"), {
|
||||
id: "node25",
|
||||
src: "somefile.html?param1=<a>¶m2=\xfc¶m3='\"'"
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
desc: "Modify inline style containing \"",
|
||||
before: function() {
|
||||
assertAttributes(doc.querySelector("#node26"), {
|
||||
id: "node26",
|
||||
style: 'background-image: url("moz-page-thumb://thumbnail?url=http%3A%2F%2Fwww.mozilla.org%2F");'
|
||||
});
|
||||
},
|
||||
execute: function(after) {
|
||||
inspector.once("markupmutation", after);
|
||||
let editor = getContainerForRawNode(markup, doc.querySelector("#node26")).editor;
|
||||
let attr = editor.attrs["style"].querySelector(".editable");
|
||||
|
||||
|
||||
attr.focus();
|
||||
EventUtils.sendKey("return", inspector.panelWin);
|
||||
|
||||
let input = inplaceEditor(attr).input;
|
||||
let value = input.value;
|
||||
|
||||
is (value,
|
||||
"style='background-image: url(\"moz-page-thumb://thumbnail?url=http%3A%2F%2Fwww.mozilla.org%2F\");'",
|
||||
"Value contains actual double quotes"
|
||||
);
|
||||
|
||||
value = value.replace(/mozilla\.org/, "mozilla.com");
|
||||
input.value = value;
|
||||
|
||||
EventUtils.sendKey("return", inspector.panelWin);
|
||||
},
|
||||
after: function() {
|
||||
assertAttributes(doc.querySelector("#node26"), {
|
||||
id: "node26",
|
||||
style: 'background-image: url("moz-page-thumb://thumbnail?url=http%3A%2F%2Fwww.mozilla.com%2F");'
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
desc: "Modify inline style containing \" and \'",
|
||||
before: function() {
|
||||
assertAttributes(doc.querySelector("#node27"), {
|
||||
id: "node27",
|
||||
class: 'Double " and single \''
|
||||
});
|
||||
},
|
||||
execute: function(after) {
|
||||
inspector.once("markupmutation", after);
|
||||
let editor = getContainerForRawNode(markup, doc.querySelector("#node27")).editor;
|
||||
let attr = editor.attrs["class"].querySelector(".editable");
|
||||
|
||||
attr.focus();
|
||||
EventUtils.sendKey("return", inspector.panelWin);
|
||||
|
||||
let input = inplaceEditor(attr).input;
|
||||
let value = input.value;
|
||||
|
||||
is (value, "class=\"Double " and single '\"", "Value contains "");
|
||||
|
||||
value = value.replace(/Double/, """).replace(/single/, "'");
|
||||
input.value = value;
|
||||
|
||||
EventUtils.sendKey("return", inspector.panelWin);
|
||||
},
|
||||
after: function() {
|
||||
assertAttributes(doc.querySelector("#node27"), {
|
||||
id: "node27",
|
||||
class: '" " and \' \''
|
||||
});
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
function test() {
|
||||
addTab(TEST_URL).then(openInspector).then(args => {
|
||||
inspector = args.inspector;
|
||||
doc = content.document;
|
||||
markup = inspector.markup;
|
||||
|
||||
markup.expandAll().then(() => {
|
||||
// Iterate through the items in TEST_DATA
|
||||
let cursor = 0;
|
||||
|
||||
function nextEditTest() {
|
||||
executeSoon(function() {
|
||||
if (cursor >= TEST_DATA.length) {
|
||||
return finishUp();
|
||||
}
|
||||
|
||||
let step = TEST_DATA[cursor++];
|
||||
info("Start test for: " + step.desc);
|
||||
if (step.setup) {
|
||||
step.setup();
|
||||
}
|
||||
step.before();
|
||||
step.execute(function() {
|
||||
step.after();
|
||||
|
||||
undoChange(inspector).then(() => {
|
||||
step.before();
|
||||
|
||||
redoChange(inspector).then(() => {
|
||||
step.after();
|
||||
info("End test for: " + step.desc);
|
||||
nextEditTest();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
nextEditTest();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function finishUp() {
|
||||
while (markup.undo.canUndo()) {
|
||||
markup.undo.undo();
|
||||
}
|
||||
inspector.once("inspector-updated", () => {
|
||||
doc = inspector = markup = null;
|
||||
gBrowser.removeCurrentTab();
|
||||
finish();
|
||||
});
|
||||
}
|
|
@ -1,51 +0,0 @@
|
|||
/* Any copyright", " is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
// Tests that an existing attribute can be modified
|
||||
|
||||
waitForExplicitFinish();
|
||||
|
||||
const TEST_URL = "data:text/html,<div id='test-div'>Test modifying my ID attribute</div>";
|
||||
|
||||
function test() {
|
||||
Task.spawn(function() {
|
||||
info("Opening the inspector on the test page");
|
||||
let {toolbox, inspector} = yield addTab(TEST_URL).then(openInspector);
|
||||
|
||||
info("Selecting the test node");
|
||||
let node = content.document.getElementById("test-div");
|
||||
yield selectNode(node, inspector);
|
||||
|
||||
info("Verify attributes, only ID should be there for now");
|
||||
assertAttributes(node, {
|
||||
id: "test-div"
|
||||
});
|
||||
|
||||
info("Focus the ID attribute and change its content");
|
||||
let editor = getContainerForRawNode(inspector.markup, node).editor;
|
||||
let attr = editor.attrs["id"].querySelector(".editable");
|
||||
let mutated = inspector.once("markupmutation");
|
||||
setEditableFieldValue(attr,
|
||||
attr.textContent + ' class="newclass" style="color:green"', inspector);
|
||||
yield mutated;
|
||||
|
||||
info("Verify attributes, should have ID, class and style");
|
||||
assertAttributes(node, {
|
||||
id: "test-div",
|
||||
class: "newclass",
|
||||
style: "color:green"
|
||||
});
|
||||
|
||||
info("Trying to undo the change");
|
||||
yield undoChange(inspector);
|
||||
assertAttributes(node, {
|
||||
id: "test-div"
|
||||
});
|
||||
|
||||
yield inspector.once("inspector-updated");
|
||||
|
||||
gBrowser.removeCurrentTab();
|
||||
}).then(null, ok.bind(null, false)).then(finish);
|
||||
}
|
|
@ -1,45 +0,0 @@
|
|||
/* Any copyright", " is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
// Tests that a node's tagname can be edited in the markup-view
|
||||
|
||||
waitForExplicitFinish();
|
||||
|
||||
const TEST_URL = "data:text/html,<div id='retag-me'><div id='retag-me-2'></div></div>";
|
||||
|
||||
function test() {
|
||||
Task.spawn(function() {
|
||||
info("Opening the inspector on the test page");
|
||||
let {toolbox, inspector} = yield addTab(TEST_URL).then(openInspector);
|
||||
|
||||
yield inspector.markup.expandAll();
|
||||
|
||||
info("Selecting the test node");
|
||||
let node = content.document.getElementById("retag-me");
|
||||
let child = content.document.querySelector("#retag-me-2");
|
||||
yield selectNode(node, inspector);
|
||||
|
||||
let container = getContainerForRawNode(inspector.markup, node);
|
||||
is(node.tagName, "DIV", "We've got #retag-me element, it's a DIV");
|
||||
ok(container.expanded, "It is expanded");
|
||||
is(child.parentNode, node, "Child #retag-me-2 is inside #retag-me");
|
||||
|
||||
info("Changing the tagname");
|
||||
let mutated = inspector.once("markupmutation");
|
||||
let tagEditor = container.editor.tag;
|
||||
setEditableFieldValue(tagEditor, "p", inspector);
|
||||
yield mutated;
|
||||
|
||||
info("Checking that the tagname change was done");
|
||||
let node = content.document.getElementById("retag-me");
|
||||
let container = getContainerForRawNode(inspector.markup, node);
|
||||
is(node.tagName, "P", "We've got #retag-me, it should now be a P");
|
||||
ok(container.expanded, "It is still expanded");
|
||||
ok(container.selected, "It is still selected");
|
||||
is(child.parentNode, node, "Child #retag-me-2 is still inside #retag-me");
|
||||
|
||||
gBrowser.removeCurrentTab();
|
||||
}).then(null, ok.bind(null, false)).then(finish);
|
||||
}
|
|
@ -1,36 +0,0 @@
|
|||
/* Any copyright", " is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
// Tests that a node can be deleted from the markup-view with the delete key
|
||||
|
||||
waitForExplicitFinish();
|
||||
|
||||
const TEST_URL = "data:text/html,<div id='delete-me'></div>";
|
||||
|
||||
function test() {
|
||||
Task.spawn(function() {
|
||||
info("Opening the inspector on the test page");
|
||||
let {toolbox, inspector} = yield addTab(TEST_URL).then(openInspector);
|
||||
|
||||
info("Selecting the test node by clicking on it to make sure it receives focus");
|
||||
let node = content.document.getElementById("delete-me");
|
||||
yield clickContainer(node, inspector);
|
||||
|
||||
info("Deleting the element with the keyboard");
|
||||
let mutated = inspector.once("markupmutation");
|
||||
EventUtils.sendKey("delete", inspector.panelWin);
|
||||
yield mutated;
|
||||
|
||||
info("Checking that it's gone, baby gone!");
|
||||
ok(!content.document.getElementById("delete-me"), "The test node does not exist");
|
||||
|
||||
yield undoChange(inspector);
|
||||
ok(content.document.getElementById("delete-me"), "The test node is back!");
|
||||
|
||||
yield inspector.once("inspector-updated");
|
||||
|
||||
gBrowser.removeCurrentTab();
|
||||
}).then(null, ok.bind(null, false)).then(finish);
|
||||
}
|
|
@ -1,232 +0,0 @@
|
|||
/* Any copyright", " is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
|
||||
function test() {
|
||||
let inspector;
|
||||
let doc;
|
||||
|
||||
waitForExplicitFinish();
|
||||
|
||||
gBrowser.selectedTab = gBrowser.addTab();
|
||||
gBrowser.selectedBrowser.addEventListener("load", function onload() {
|
||||
gBrowser.selectedBrowser.removeEventListener("load", onload, true);
|
||||
doc = content.document;
|
||||
waitForFocus(setupTest, content);
|
||||
}, true);
|
||||
|
||||
let outerHTMLs = [
|
||||
{
|
||||
selector: "#one",
|
||||
oldHTML: '<div id="one">First <em>Div</em></div>',
|
||||
newHTML: '<div id="one">First Div</div>',
|
||||
validate: function(pageNode, selectedNode) {
|
||||
is (pageNode.textContent, "First Div", "New div has expected text content");
|
||||
ok (!doc.querySelector("#one em"), "No em remaining")
|
||||
}
|
||||
},
|
||||
{
|
||||
selector: "#removedChildren",
|
||||
oldHTML: '<div id="removedChildren">removedChild <i>Italic <b>Bold <u>Underline</u></b></i> Normal</div>',
|
||||
newHTML: '<div id="removedChildren">removedChild</div>'
|
||||
},
|
||||
{
|
||||
selector: "#addedChildren",
|
||||
oldHTML: '<div id="addedChildren">addedChildren</div>',
|
||||
newHTML: '<div id="addedChildren">addedChildren <i>Italic <b>Bold <u>Underline</u></b></i> Normal</div>'
|
||||
},
|
||||
{
|
||||
selector: "#addedAttribute",
|
||||
oldHTML: '<div id="addedAttribute">addedAttribute</div>',
|
||||
newHTML: '<div id="addedAttribute" class="important" disabled checked>addedAttribute</div>',
|
||||
validate: function(pageNode, selectedNode) {
|
||||
is (pageNode, selectedNode, "Original element is selected");
|
||||
is (pageNode.outerHTML, '<div id="addedAttribute" class="important" disabled="" checked="">addedAttribute</div>',
|
||||
"Attributes have been added");
|
||||
}
|
||||
},
|
||||
{
|
||||
selector: "#changedTag",
|
||||
oldHTML: '<div id="changedTag">changedTag</div>',
|
||||
newHTML: '<p id="changedTag" class="important">changedTag</p>'
|
||||
},
|
||||
{
|
||||
selector: "#badMarkup1",
|
||||
oldHTML: '<div id="badMarkup1">badMarkup1</div>',
|
||||
newHTML: '<div id="badMarkup1">badMarkup1</div> hanging</div>',
|
||||
validate: function(pageNode, selectedNode) {
|
||||
is (pageNode, selectedNode, "Original element is selected");
|
||||
|
||||
let textNode = pageNode.nextSibling;
|
||||
|
||||
is (textNode.nodeName, "#text", "Sibling is a text element");
|
||||
is (textNode.data, " hanging", "New text node has expected text content");
|
||||
}
|
||||
},
|
||||
{
|
||||
selector: "#badMarkup2",
|
||||
oldHTML: '<div id="badMarkup2">badMarkup2</div>',
|
||||
newHTML: '<div id="badMarkup2">badMarkup2</div> hanging<div></div></div></div></body>',
|
||||
validate: function(pageNode, selectedNode) {
|
||||
is (pageNode, selectedNode, "Original element is selected");
|
||||
|
||||
let textNode = pageNode.nextSibling;
|
||||
|
||||
is (textNode.nodeName, "#text", "Sibling is a text element");
|
||||
is (textNode.data, " hanging", "New text node has expected text content");
|
||||
}
|
||||
},
|
||||
{
|
||||
selector: "#badMarkup3",
|
||||
oldHTML: '<div id="badMarkup3">badMarkup3</div>',
|
||||
newHTML: '<div id="badMarkup3">badMarkup3 <em>Emphasized <strong> and strong</div>',
|
||||
validate: function(pageNode, selectedNode) {
|
||||
is (pageNode, selectedNode, "Original element is selected");
|
||||
|
||||
let em = doc.querySelector("#badMarkup3 em");
|
||||
let strong = doc.querySelector("#badMarkup3 strong");
|
||||
|
||||
is (em.textContent, "Emphasized and strong", "<em> was auto created");
|
||||
is (strong.textContent, " and strong", "<strong> was auto created");
|
||||
}
|
||||
},
|
||||
{
|
||||
selector: "#badMarkup4",
|
||||
oldHTML: '<div id="badMarkup4">badMarkup4</div>',
|
||||
newHTML: '<div id="badMarkup4">badMarkup4</p>',
|
||||
validate: function(pageNode, selectedNode) {
|
||||
is (pageNode, selectedNode, "Original element is selected");
|
||||
|
||||
let div = doc.querySelector("#badMarkup4");
|
||||
let p = doc.querySelector("#badMarkup4 p");
|
||||
|
||||
is (div.textContent, "badMarkup4", "textContent is correct");
|
||||
is (div.tagName, "DIV", "did not change to <p> tag");
|
||||
is (p.textContent, "", "The <p> tag has no children");
|
||||
is (p.tagName, "P", "Created an empty <p> tag");
|
||||
}
|
||||
},
|
||||
{
|
||||
selector: "#badMarkup5",
|
||||
oldHTML: '<p id="badMarkup5">badMarkup5</p>',
|
||||
newHTML: '<p id="badMarkup5">badMarkup5 <div>with a nested div</div></p>',
|
||||
validate: function(pageNode, selectedNode) {
|
||||
is (pageNode, selectedNode, "Original element is selected");
|
||||
|
||||
let p = doc.querySelector("#badMarkup5");
|
||||
let nodiv = doc.querySelector("#badMarkup5 div");
|
||||
let div = doc.querySelector("#badMarkup5 ~ div");
|
||||
|
||||
ok (!nodiv, "The invalid markup got created as a sibling");
|
||||
is (p.textContent, "badMarkup5 ", "The <p> tag does not take in the <div> content");
|
||||
is (p.tagName, "P", "Did not change to a <div> tag");
|
||||
is (div.textContent, "with a nested div", "textContent is correct");
|
||||
is (div.tagName, "DIV", "Did not change to <p> tag");
|
||||
}
|
||||
},
|
||||
{
|
||||
selector: "#siblings",
|
||||
oldHTML: '<div id="siblings">siblings</div>',
|
||||
newHTML: '<div id="siblings-before-sibling">before sibling</div>' +
|
||||
'<div id="siblings">siblings (updated)</div>' +
|
||||
'<div id="siblings-after-sibling">after sibling</div>',
|
||||
validate: function(pageNode, selectedNode) {
|
||||
let beforeSiblingNode = doc.querySelector("#siblings-before-sibling");
|
||||
let afterSiblingNode = doc.querySelector("#siblings-after-sibling");
|
||||
|
||||
is (beforeSiblingNode, selectedNode, "Sibling has been selected");
|
||||
is (pageNode.textContent, "siblings (updated)", "New div has expected text content");
|
||||
is (beforeSiblingNode.textContent, "before sibling", "Sibling has been inserted");
|
||||
is (afterSiblingNode.textContent, "after sibling", "Sibling has been inserted");
|
||||
}
|
||||
}
|
||||
];
|
||||
content.location = "data:text/html," +
|
||||
"<!DOCTYPE html>" +
|
||||
"<head><meta charset='utf-8' /></head>" +
|
||||
"<body>" +
|
||||
[outer.oldHTML for (outer of outerHTMLs) ].join("\n") +
|
||||
"</body>" +
|
||||
"</html>";
|
||||
|
||||
function setupTest() {
|
||||
var target = TargetFactory.forTab(gBrowser.selectedTab);
|
||||
gDevTools.showToolbox(target, "inspector").then(function(toolbox) {
|
||||
inspector = toolbox.getCurrentPanel();
|
||||
inspector.once("inspector-updated", startTests);
|
||||
});
|
||||
}
|
||||
|
||||
function startTests() {
|
||||
inspector.markup._frame.focus();
|
||||
nextStep(0);
|
||||
}
|
||||
|
||||
function nextStep(cursor) {
|
||||
if (cursor >= outerHTMLs.length) {
|
||||
finishUp();
|
||||
return;
|
||||
}
|
||||
|
||||
let currentTestData = outerHTMLs[cursor];
|
||||
let selector = currentTestData.selector;
|
||||
let oldHTML = currentTestData.oldHTML;
|
||||
let newHTML = currentTestData.newHTML;
|
||||
let rawNode = doc.querySelector(selector);
|
||||
|
||||
inspector.selection.once("new-node", () => {
|
||||
|
||||
let oldNodeFront = inspector.selection.nodeFront;
|
||||
|
||||
// markupmutation fires once the outerHTML is set, with a target
|
||||
// as the parent node and a type of "childList".
|
||||
inspector.once("markupmutation", (e, aMutations) => {
|
||||
|
||||
// Check to make the sure the correct mutation has fired, and that the
|
||||
// parent is selected (this will be reset to the child once the mutation is complete.
|
||||
let node = inspector.selection.node;
|
||||
let nodeFront = inspector.selection.nodeFront;
|
||||
let mutation = aMutations[0];
|
||||
let isFromOuterHTML = mutation.removed.some((n) => {
|
||||
return n === oldNodeFront;
|
||||
});
|
||||
|
||||
ok (isFromOuterHTML, "The node is in the 'removed' list of the mutation");
|
||||
is (mutation.type, "childList", "Mutation is a childList after updating outerHTML");
|
||||
is (mutation.target, nodeFront, "Parent node is selected immediately after setting outerHTML");
|
||||
|
||||
// Wait for node to be reselected after outerHTML has been set
|
||||
inspector.selection.once("new-node", () => {
|
||||
|
||||
// Typically selectedNode will === pageNode, but if a new element has been injected in front
|
||||
// of it, this will not be the case. If this happens.
|
||||
let selectedNode = inspector.selection.node;
|
||||
let nodeFront = inspector.selection.nodeFront;
|
||||
let pageNode = doc.querySelector(selector);
|
||||
|
||||
if (currentTestData.validate) {
|
||||
currentTestData.validate(pageNode, selectedNode);
|
||||
} else {
|
||||
is (pageNode, selectedNode, "Original node (grabbed by selector) is selected");
|
||||
is (pageNode.outerHTML, newHTML, "Outer HTML has been updated");
|
||||
}
|
||||
|
||||
nextStep(cursor + 1);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
is (inspector.selection.node, rawNode, "Selection is on the correct node");
|
||||
inspector.markup.updateNodeOuterHTML(inspector.selection.nodeFront, newHTML, oldHTML);
|
||||
});
|
||||
|
||||
inspector.selection.setNode(rawNode);
|
||||
}
|
||||
|
||||
|
||||
function finishUp() {
|
||||
doc = inspector = null;
|
||||
gBrowser.removeCurrentTab();
|
||||
finish();
|
||||
}
|
||||
}
|
|
@ -1,170 +0,0 @@
|
|||
|
||||
function test() {
|
||||
let inspector;
|
||||
let doc;
|
||||
let selector = "#keyboard";
|
||||
let oldHTML = '<div id="keyboard"></div>';
|
||||
let newHTML = '<div id="keyboard">Edited</div>';
|
||||
|
||||
waitForExplicitFinish();
|
||||
|
||||
gBrowser.selectedTab = gBrowser.addTab();
|
||||
gBrowser.selectedBrowser.addEventListener("load", function onload() {
|
||||
gBrowser.selectedBrowser.removeEventListener("load", onload, true);
|
||||
doc = content.document;
|
||||
waitForFocus(setupTest, content);
|
||||
}, true);
|
||||
|
||||
content.location = "data:text/html," +
|
||||
"<!DOCTYPE html>" +
|
||||
"<head><meta charset='utf-8' /></head>" +
|
||||
"<body>" +
|
||||
"<div id=\"keyboard\"></div>" +
|
||||
"</body>" +
|
||||
"</html>";
|
||||
|
||||
function setupTest() {
|
||||
var target = TargetFactory.forTab(gBrowser.selectedTab);
|
||||
gDevTools.showToolbox(target, "inspector").then(function(toolbox) {
|
||||
inspector = toolbox.getCurrentPanel();
|
||||
inspector.once("inspector-updated", startTests);
|
||||
});
|
||||
}
|
||||
|
||||
function startTests() {
|
||||
inspector.markup._frame.focus();
|
||||
testEscapeCancels();
|
||||
}
|
||||
|
||||
function testEscapeCancels() {
|
||||
info("Checking to make sure that pressing escape cancels edits");
|
||||
let rawNode = doc.querySelector(selector);
|
||||
|
||||
inspector.selection.once("new-node", () => {
|
||||
|
||||
inspector.markup.htmlEditor.on("popupshown", function onPopupShown() {
|
||||
inspector.markup.htmlEditor.off("popupshown", onPopupShown);
|
||||
|
||||
ok (inspector.markup.htmlEditor._visible, "HTML Editor is visible");
|
||||
is (rawNode.outerHTML, oldHTML, "The node is starting with old HTML.");
|
||||
|
||||
inspector.markup.htmlEditor.on("popuphidden", function onPopupHidden() {
|
||||
inspector.markup.htmlEditor.off("popuphidden", onPopupHidden);
|
||||
ok (!inspector.markup.htmlEditor._visible, "HTML Editor is not visible");
|
||||
|
||||
let rawNode = doc.querySelector(selector);
|
||||
is (rawNode.outerHTML, oldHTML, "Escape cancels edits");
|
||||
testF2Commits();
|
||||
});
|
||||
|
||||
inspector.markup.htmlEditor.editor.setText(newHTML);
|
||||
|
||||
EventUtils.sendKey("ESCAPE", inspector.markup.htmlEditor.doc.defaultView);
|
||||
});
|
||||
|
||||
EventUtils.sendKey("F2", inspector.markup._frame.contentWindow);
|
||||
});
|
||||
|
||||
inspector.selection.setNode(rawNode);
|
||||
}
|
||||
|
||||
function testF2Commits() {
|
||||
info("Checking to make sure that pressing F2 commits edits");
|
||||
let rawNode = doc.querySelector(selector);
|
||||
|
||||
inspector.markup.htmlEditor.on("popupshown", function onPopupShown() {
|
||||
inspector.markup.htmlEditor.off("popupshown", onPopupShown);
|
||||
|
||||
ok (inspector.markup.htmlEditor._visible, "HTML Editor is visible");
|
||||
is (rawNode.outerHTML, oldHTML, "The node is starting with old HTML.");
|
||||
|
||||
inspector.once("markupmutation", (e, aMutations) => {
|
||||
ok (!inspector.markup.htmlEditor._visible, "HTML Editor is not visible");
|
||||
|
||||
let rawNode = doc.querySelector(selector);
|
||||
is (rawNode.outerHTML, newHTML, "F2 commits edits - the node has new HTML.");
|
||||
testBody();
|
||||
});
|
||||
|
||||
inspector.markup.htmlEditor.editor.setText(newHTML);
|
||||
EventUtils.sendKey("F2", inspector.markup._frame.contentWindow);
|
||||
});
|
||||
|
||||
inspector.markup._frame.contentDocument.documentElement.focus();
|
||||
EventUtils.sendKey("F2", inspector.markup._frame.contentWindow);
|
||||
}
|
||||
|
||||
function testBody() {
|
||||
info("Checking to make sure that editing the <body> element works like other nodes");
|
||||
let body = doc.querySelector("body");
|
||||
let bodyHTML = '<body id="updated"><p></p></body>';
|
||||
let bodyFront = inspector.markup.walker.frontForRawNode(body);
|
||||
inspector.once("markupmutation", (e, aMutations) => {
|
||||
is (doc.querySelector("body").outerHTML, bodyHTML, "<body> HTML has been updated");
|
||||
is (doc.querySelectorAll("head").length, 1, "no extra <head>s have been added");
|
||||
testHead();
|
||||
});
|
||||
inspector.markup.updateNodeOuterHTML(bodyFront, bodyHTML, body.outerHTML);
|
||||
}
|
||||
|
||||
function testHead() {
|
||||
info("Checking to make sure that editing the <head> element works like other nodes");
|
||||
let head = doc.querySelector("head");
|
||||
let headHTML = '<head id="updated"><title>New Title</title><script>window.foo="bar";</script></head>';
|
||||
let headFront = inspector.markup.walker.frontForRawNode(head);
|
||||
inspector.once("markupmutation", (e, aMutations) => {
|
||||
is (doc.title, "New Title", "New title has been added");
|
||||
is (doc.defaultView.foo, undefined, "Script has not been executed");
|
||||
is (doc.querySelector("head").outerHTML, headHTML, "<head> HTML has been updated");
|
||||
is (doc.querySelectorAll("body").length, 1, "no extra <body>s have been added");
|
||||
testDocumentElement();
|
||||
});
|
||||
inspector.markup.updateNodeOuterHTML(headFront, headHTML, head.outerHTML);
|
||||
}
|
||||
|
||||
function testDocumentElement() {
|
||||
info("Checking to make sure that editing the <html> element works like other nodes");
|
||||
let docElement = doc.documentElement;
|
||||
let docElementHTML = '<html id="updated" foo="bar"><head><title>Updated from document element</title><script>window.foo="bar";</script></head><body><p>Hello</p></body></html>';
|
||||
let docElementFront = inspector.markup.walker.frontForRawNode(docElement);
|
||||
inspector.once("markupmutation", (e, aMutations) => {
|
||||
is (doc.title, "Updated from document element", "New title has been added");
|
||||
is (doc.defaultView.foo, undefined, "Script has not been executed");
|
||||
is (doc.documentElement.id, "updated", "<html> ID has been updated");
|
||||
is (doc.documentElement.className, "", "<html> class has been updated");
|
||||
is (doc.documentElement.getAttribute("foo"), "bar", "<html> attribute has been updated");
|
||||
is (doc.documentElement.outerHTML, docElementHTML, "<html> HTML has been updated");
|
||||
is (doc.querySelectorAll("head").length, 1, "no extra <head>s have been added");
|
||||
is (doc.querySelectorAll("body").length, 1, "no extra <body>s have been added");
|
||||
is (doc.body.textContent, "Hello", "document.body.textContent has been updated");
|
||||
testDocumentElement2();
|
||||
});
|
||||
inspector.markup.updateNodeOuterHTML(docElementFront, docElementHTML, docElement.outerHTML);
|
||||
}
|
||||
|
||||
function testDocumentElement2() {
|
||||
info("Checking to make sure (again) that editing the <html> element works like other nodes");
|
||||
let docElement = doc.documentElement;
|
||||
let docElementHTML = '<html class="updated" id="somethingelse"><head><title>Updated again from document element</title><script>window.foo="bar";</script></head><body><p>Hello again</p></body></html>';
|
||||
let docElementFront = inspector.markup.walker.frontForRawNode(docElement);
|
||||
inspector.once("markupmutation", (e, aMutations) => {
|
||||
is (doc.title, "Updated again from document element", "New title has been added");
|
||||
is (doc.defaultView.foo, undefined, "Script has not been executed");
|
||||
is (doc.documentElement.id, "somethingelse", "<html> ID has been updated");
|
||||
is (doc.documentElement.className, "updated", "<html> class has been updated");
|
||||
is (doc.documentElement.getAttribute("foo"), null, "<html> attribute has been removed");
|
||||
is (doc.documentElement.outerHTML, docElementHTML, "<html> HTML has been updated");
|
||||
is (doc.querySelectorAll("head").length, 1, "no extra <head>s have been added");
|
||||
is (doc.querySelectorAll("body").length, 1, "no extra <body>s have been added");
|
||||
is (doc.body.textContent, "Hello again", "document.body.textContent has been updated");
|
||||
finishUp();
|
||||
});
|
||||
inspector.markup.updateNodeOuterHTML(docElementFront, docElementHTML, docElement.outerHTML);
|
||||
}
|
||||
|
||||
function finishUp() {
|
||||
doc = inspector = null;
|
||||
gBrowser.removeCurrentTab();
|
||||
finish();
|
||||
}
|
||||
}
|
|
@ -1,190 +0,0 @@
|
|||
/* Any copyright", " is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Tests that various mutations to the dom update the markup tool correctly.
|
||||
* The test for comparing the markup tool to the real dom is a bit weird:
|
||||
* - Select the text in the markup tool
|
||||
* - Parse that as innerHTML in a document we've created for the purpose.
|
||||
* - Remove extraneous whitespace in that tree
|
||||
* - Compare it to the real dom with isEqualNode.
|
||||
*/
|
||||
|
||||
function fail(err) {
|
||||
ok(false, err)
|
||||
}
|
||||
|
||||
function test() {
|
||||
waitForExplicitFinish();
|
||||
|
||||
// Will hold the doc we're viewing
|
||||
let contentTab;
|
||||
let doc;
|
||||
|
||||
// Holds the MarkupTool object we're testing.
|
||||
let markup;
|
||||
|
||||
// Holds the document we use to help re-parse the markup tool's output.
|
||||
let parseTab;
|
||||
let parseDoc;
|
||||
|
||||
let inspector;
|
||||
|
||||
// Strip whitespace from a node and its children.
|
||||
function stripWhitespace(node)
|
||||
{
|
||||
node.normalize();
|
||||
let iter = node.ownerDocument.createNodeIterator(node, NodeFilter.SHOW_TEXT + NodeFilter.SHOW_COMMENT,
|
||||
null);
|
||||
|
||||
while ((node = iter.nextNode())) {
|
||||
node.nodeValue = node.nodeValue.replace(/\s+/g, '');
|
||||
if (node.nodeType == Node.TEXT_NODE &&
|
||||
!/[^\s]/.exec(node.nodeValue)) {
|
||||
node.parentNode.removeChild(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Verify that the markup in the tool is the same as the markup in the document.
|
||||
function checkMarkup()
|
||||
{
|
||||
return markup.expandAll().then(checkMarkup2);
|
||||
}
|
||||
|
||||
function checkMarkup2()
|
||||
{
|
||||
let contentNode = doc.querySelector("body");
|
||||
let panelNode = getContainerForRawNode(markup, contentNode).elt;
|
||||
let parseNode = parseDoc.querySelector("body");
|
||||
|
||||
// Grab the text from the markup panel...
|
||||
let sel = panelNode.ownerDocument.defaultView.getSelection();
|
||||
sel.selectAllChildren(panelNode);
|
||||
|
||||
// Parse it
|
||||
parseNode.outerHTML = sel;
|
||||
parseNode = parseDoc.querySelector("body");
|
||||
|
||||
// Pull whitespace out of text and comment nodes, there will
|
||||
// be minor unimportant differences.
|
||||
stripWhitespace(parseNode);
|
||||
|
||||
ok(contentNode.isEqualNode(parseNode), "Markup panel should match document.");
|
||||
}
|
||||
|
||||
// All the mutation types we want to test.
|
||||
let mutations = [
|
||||
// Add an attribute
|
||||
function() {
|
||||
let node1 = doc.querySelector("#node1");
|
||||
node1.setAttribute("newattr", "newattrval");
|
||||
},
|
||||
function() {
|
||||
let node1 = doc.querySelector("#node1");
|
||||
node1.removeAttribute("newattr");
|
||||
},
|
||||
function() {
|
||||
let node1 = doc.querySelector("#node1");
|
||||
node1.textContent = "newtext";
|
||||
},
|
||||
function() {
|
||||
let node2 = doc.querySelector("#node2");
|
||||
node2.innerHTML = "<div><span>foo</span></div>";
|
||||
},
|
||||
|
||||
function() {
|
||||
let node4 = doc.querySelector("#node4");
|
||||
while (node4.firstChild) {
|
||||
node4.removeChild(node4.firstChild);
|
||||
}
|
||||
},
|
||||
function() {
|
||||
// Move a child to a new parent.
|
||||
let node17 = doc.querySelector("#node17");
|
||||
let node1 = doc.querySelector("#node2");
|
||||
node1.appendChild(node17);
|
||||
},
|
||||
|
||||
function() {
|
||||
// Swap a parent and child element, putting them in the same tree.
|
||||
// body
|
||||
// node1
|
||||
// node18
|
||||
// node19
|
||||
// node20
|
||||
// node21
|
||||
// will become:
|
||||
// body
|
||||
// node1
|
||||
// node20
|
||||
// node21
|
||||
// node18
|
||||
// node19
|
||||
let node18 = doc.querySelector("#node18");
|
||||
let node20 = doc.querySelector("#node20");
|
||||
|
||||
let node1 = doc.querySelector("#node1");
|
||||
|
||||
node1.appendChild(node20);
|
||||
node20.appendChild(node18);
|
||||
},
|
||||
];
|
||||
|
||||
// Create the helper tab for parsing...
|
||||
parseTab = gBrowser.selectedTab = gBrowser.addTab();
|
||||
gBrowser.selectedBrowser.addEventListener("load", function onload() {
|
||||
gBrowser.selectedBrowser.removeEventListener("load", onload, true);
|
||||
parseDoc = content.document;
|
||||
|
||||
// Then create the actual dom we're inspecting...
|
||||
contentTab = gBrowser.selectedTab = gBrowser.addTab();
|
||||
gBrowser.selectedBrowser.addEventListener("load", function onload2() {
|
||||
gBrowser.selectedBrowser.removeEventListener("load", onload2, true);
|
||||
doc = content.document;
|
||||
// Strip whitespace from the doc for easier comparison.
|
||||
stripWhitespace(doc.documentElement);
|
||||
waitForFocus(setupTest, content);
|
||||
}, true);
|
||||
content.location = "http://mochi.test:8888/browser/browser/devtools/markupview/test/browser_inspector_markup_mutation.html";
|
||||
}, true);
|
||||
|
||||
content.location = "data:text/html,<html></html>";
|
||||
|
||||
function setupTest() {
|
||||
var target = TargetFactory.forTab(gBrowser.selectedTab);
|
||||
gDevTools.showToolbox(target, "inspector").then(function(toolbox) {
|
||||
inspector = toolbox.getCurrentPanel();
|
||||
startTests();
|
||||
});
|
||||
}
|
||||
|
||||
function startTests() {
|
||||
markup = inspector.markup;
|
||||
checkMarkup().then(() => {
|
||||
nextStep(0);
|
||||
}).then(null, fail);
|
||||
}
|
||||
|
||||
function nextStep(cursor) {
|
||||
if (cursor >= mutations.length) {
|
||||
finishUp();
|
||||
return;
|
||||
}
|
||||
mutations[cursor]();
|
||||
inspector.once("markupmutation", function() {
|
||||
executeSoon(function() {
|
||||
checkMarkup().then(() => {
|
||||
nextStep(cursor + 1);
|
||||
}).then(null, fail);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function finishUp() {
|
||||
doc = inspector = null;
|
||||
gBrowser.removeTab(contentTab);
|
||||
gBrowser.removeTab(parseTab);
|
||||
finish();
|
||||
}
|
||||
}
|
|
@ -1,130 +0,0 @@
|
|||
/* Any copyright", " is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
function test() {
|
||||
waitForExplicitFinish();
|
||||
|
||||
// Will hold the doc we're viewing
|
||||
let contentTab;
|
||||
let doc;
|
||||
let listElement;
|
||||
|
||||
// Holds the MarkupTool object we're testing.
|
||||
let markup;
|
||||
let inspector;
|
||||
|
||||
// Then create the actual dom we're inspecting...
|
||||
contentTab = gBrowser.selectedTab = gBrowser.addTab();
|
||||
gBrowser.selectedBrowser.addEventListener("load", function onload() {
|
||||
gBrowser.selectedBrowser.removeEventListener("load", onload, true);
|
||||
doc = content.document;
|
||||
waitForFocus(setupTest, content);
|
||||
}, true);
|
||||
content.location = "http://mochi.test:8888/browser/browser/devtools/markupview/test/browser_inspector_markup_mutation_flashing.html";
|
||||
|
||||
function setupTest() {
|
||||
var target = TargetFactory.forTab(gBrowser.selectedTab);
|
||||
gDevTools.showToolbox(target, "inspector").then(function(toolbox) {
|
||||
inspector = toolbox.getCurrentPanel();
|
||||
startTests();
|
||||
});
|
||||
}
|
||||
|
||||
function startTests() {
|
||||
markup = inspector.markup;
|
||||
|
||||
// Get the content UL element
|
||||
listElement = doc.querySelector(".list");
|
||||
|
||||
// Making sure children are expanded
|
||||
inspector.selection.setNode(listElement.lastElementChild);
|
||||
inspector.once("inspector-updated", () => {
|
||||
// testData contains a list of mutations to test
|
||||
// Each array item is an object with:
|
||||
// - mutate: a function that should make changes to the content DOM
|
||||
// - assert: a function that should test flashing background
|
||||
let testData = [{
|
||||
// Adding a new node should flash the new node
|
||||
mutate: () => {
|
||||
let newLi = doc.createElement("LI");
|
||||
newLi.textContent = "new list item";
|
||||
listElement.appendChild(newLi);
|
||||
},
|
||||
assert: () => {
|
||||
assertNodeFlashing(listElement.lastElementChild);
|
||||
}
|
||||
}, {
|
||||
// Removing a node should flash its parent
|
||||
mutate: () => {
|
||||
listElement.removeChild(listElement.lastElementChild);
|
||||
},
|
||||
assert: () => {
|
||||
assertNodeFlashing(listElement);
|
||||
}
|
||||
}, {
|
||||
// Re-appending an existing node should only flash this node
|
||||
mutate: () => {
|
||||
listElement.appendChild(listElement.firstElementChild);
|
||||
},
|
||||
assert: () => {
|
||||
assertNodeFlashing(listElement.lastElementChild);
|
||||
}
|
||||
}, {
|
||||
// Adding an attribute should flash the node
|
||||
mutate: () => {
|
||||
listElement.setAttribute("name-" + Date.now(), "value-" + Date.now());
|
||||
},
|
||||
assert: () => {
|
||||
assertNodeFlashing(listElement);
|
||||
}
|
||||
}, {
|
||||
// Editing an attribute should flash the node
|
||||
mutate: () => {
|
||||
listElement.setAttribute("class", "list value-" + Date.now());
|
||||
},
|
||||
assert: () => {
|
||||
assertNodeFlashing(listElement);
|
||||
}
|
||||
}, {
|
||||
// Removing an attribute should flash the node
|
||||
mutate: () => {
|
||||
listElement.removeAttribute("class");
|
||||
},
|
||||
assert: () => {
|
||||
assertNodeFlashing(listElement);
|
||||
}
|
||||
}];
|
||||
testMutation(testData, 0);
|
||||
});
|
||||
}
|
||||
|
||||
function testMutation(testData, cursor) {
|
||||
if (cursor < testData.length) {
|
||||
let {mutate, assert} = testData[cursor];
|
||||
mutate();
|
||||
inspector.once("markupmutation", () => {
|
||||
assert();
|
||||
testMutation(testData, cursor + 1);
|
||||
});
|
||||
} else {
|
||||
endTests();
|
||||
}
|
||||
}
|
||||
|
||||
function endTests() {
|
||||
gBrowser.removeTab(contentTab);
|
||||
doc = inspector = contentTab = markup = listElement = null;
|
||||
finish();
|
||||
}
|
||||
|
||||
function assertNodeFlashing(rawNode) {
|
||||
let container = getContainerForRawNode(markup, rawNode);
|
||||
|
||||
if(!container) {
|
||||
ok(false, "Node not found");
|
||||
} else {
|
||||
ok(container.tagState.classList.contains("theme-bg-contrast"),
|
||||
"Node is flashing");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,150 +0,0 @@
|
|||
/* Any copyright", " is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
|
||||
function test() {
|
||||
let inspector;
|
||||
|
||||
waitForExplicitFinish();
|
||||
|
||||
let doc;
|
||||
|
||||
let keySequences = [
|
||||
["pageup", "*doctype*"],
|
||||
["down", "html"],
|
||||
["down", "head"],
|
||||
["down", "body"],
|
||||
["down", "node0"],
|
||||
["right", "node0"],
|
||||
["down", "node1"],
|
||||
["down", "node2"],
|
||||
["down", "node3"],
|
||||
["down", "*comment*"],
|
||||
["down", "node4"],
|
||||
["right", "node4"],
|
||||
["down", "*text*"],
|
||||
["down", "node5"],
|
||||
["down", "node6"],
|
||||
["down", "*comment*"],
|
||||
["down" , "node7"],
|
||||
["right", "node7"],
|
||||
["down", "*text*"],
|
||||
["down", "node8"],
|
||||
["left", "node7"],
|
||||
["left", "node7"],
|
||||
["right", "node7"],
|
||||
["right", "*text*"],
|
||||
["down", "node8"],
|
||||
["right", "node8"],
|
||||
["left", "node8"],
|
||||
["down", "node9"],
|
||||
["down", "node10"],
|
||||
["down", "node11"],
|
||||
["down", "node12"],
|
||||
["right", "node12"],
|
||||
["down", "*text*"],
|
||||
["down", "node13"],
|
||||
["down", "node14"],
|
||||
["down", "node15"],
|
||||
["down", "node15"],
|
||||
["down", "node15"],
|
||||
["up", "node14"],
|
||||
["up", "node13"],
|
||||
["up", "*text*"],
|
||||
["up", "node12"],
|
||||
["left", "node12"],
|
||||
["down", "node14"],
|
||||
["home", "*doctype*"],
|
||||
["pagedown", "*text*"],
|
||||
["down", "node5"],
|
||||
["down", "node6"],
|
||||
["down", "*comment*"],
|
||||
["down", "node7"],
|
||||
["left", "node7"],
|
||||
["down", "node9"],
|
||||
["down", "node10"],
|
||||
["pageup", "node2"],
|
||||
["pageup", "*doctype*"],
|
||||
["down", "html"],
|
||||
["left", "html"],
|
||||
["down", "html"]
|
||||
];
|
||||
|
||||
gBrowser.selectedTab = gBrowser.addTab();
|
||||
gBrowser.selectedBrowser.addEventListener("load", function onload() {
|
||||
gBrowser.selectedBrowser.removeEventListener("load", onload, true);
|
||||
doc = content.document;
|
||||
waitForFocus(setupTest, content);
|
||||
}, true);
|
||||
|
||||
content.location = "http://mochi.test:8888/browser/browser/devtools/markupview/test/browser_inspector_markup_navigation.html";
|
||||
|
||||
function setupTest() {
|
||||
var target = TargetFactory.forTab(gBrowser.selectedTab);
|
||||
gDevTools.showToolbox(target, "inspector").then(function(toolbox) {
|
||||
inspector = toolbox.getCurrentPanel();
|
||||
inspector.once("inspector-updated", startNavigation);
|
||||
});
|
||||
}
|
||||
|
||||
function startNavigation() {
|
||||
nextStep(0);
|
||||
}
|
||||
|
||||
function nextStep(cursor) {
|
||||
if (cursor >= keySequences.length) {
|
||||
finishUp();
|
||||
return;
|
||||
}
|
||||
|
||||
let key = keySequences[cursor][0];
|
||||
let className = keySequences[cursor][1];
|
||||
inspector.markup._frame.focus();
|
||||
|
||||
switch(key) {
|
||||
case "right":
|
||||
EventUtils.synthesizeKey("VK_RIGHT", {});
|
||||
break;
|
||||
case "down":
|
||||
EventUtils.synthesizeKey("VK_DOWN", {});
|
||||
break;
|
||||
case "left":
|
||||
EventUtils.synthesizeKey("VK_LEFT", {});
|
||||
break;
|
||||
case "up":
|
||||
EventUtils.synthesizeKey("VK_UP", {});
|
||||
break;
|
||||
case "pageup":
|
||||
EventUtils.synthesizeKey("VK_PAGE_UP", {});
|
||||
break;
|
||||
case "pagedown":
|
||||
EventUtils.synthesizeKey("VK_PAGE_DOWN", {});
|
||||
break;
|
||||
case "home":
|
||||
EventUtils.synthesizeKey("VK_HOME", {});
|
||||
break;
|
||||
}
|
||||
|
||||
inspector.markup._waitForChildren().then(() => executeSoon(() => {
|
||||
let node = inspector.selection.node;
|
||||
|
||||
if (className == "*comment*") {
|
||||
is(node.nodeType, Node.COMMENT_NODE, "[" + cursor + "] should be a comment after moving " + key);
|
||||
} else if (className == "*text*") {
|
||||
is(node.nodeType, Node.TEXT_NODE, "[" + cursor + "] should be text after moving " + key);
|
||||
} else if (className == "*doctype*") {
|
||||
is(node.nodeType, Node.DOCUMENT_TYPE_NODE, "[" + cursor + "] should be doctype after moving " + key);
|
||||
} else {
|
||||
is(node.className, className, "[" + cursor + "] right node selected: " + className + " after moving " + key);
|
||||
}
|
||||
|
||||
nextStep(cursor + 1);
|
||||
}));
|
||||
}
|
||||
|
||||
function finishUp() {
|
||||
doc = inspector = null;
|
||||
gBrowser.removeCurrentTab();
|
||||
finish();
|
||||
}
|
||||
}
|
|
@ -1,150 +0,0 @@
|
|||
/* Any copyright", " is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Tests that the markup view loads only as many nodes as specified
|
||||
* by the devtools.markup.pagesize preference.
|
||||
*/
|
||||
|
||||
registerCleanupFunction(function() {
|
||||
Services.prefs.clearUserPref("devtools.markup.pagesize");
|
||||
});
|
||||
Services.prefs.setIntPref("devtools.markup.pagesize", 5);
|
||||
|
||||
|
||||
function test() {
|
||||
waitForExplicitFinish();
|
||||
|
||||
// Will hold the doc we're viewing
|
||||
let doc;
|
||||
|
||||
let inspector;
|
||||
|
||||
// Holds the MarkupTool object we're testing.
|
||||
let markup;
|
||||
|
||||
function assertChildren(expected)
|
||||
{
|
||||
let container = getContainerForRawNode(markup, doc.querySelector("body"));
|
||||
let found = [];
|
||||
for (let child of container.children.children) {
|
||||
if (child.classList.contains("more-nodes")) {
|
||||
found += "*more*";
|
||||
} else {
|
||||
found += child.container.node.getAttribute("id");
|
||||
}
|
||||
}
|
||||
is(found, expected, "Got the expected children.");
|
||||
}
|
||||
|
||||
function forceReload()
|
||||
{
|
||||
let container = getContainerForRawNode(markup, doc.querySelector("body"));
|
||||
container.childrenDirty = true;
|
||||
}
|
||||
|
||||
let selections = [
|
||||
{
|
||||
desc: "Select the last item",
|
||||
selector: "#z",
|
||||
before: function() {},
|
||||
after: function() {
|
||||
assertChildren("*more*vwxyz");
|
||||
}
|
||||
},
|
||||
{
|
||||
desc: "Select the first item",
|
||||
selector: "#a",
|
||||
before: function() {
|
||||
},
|
||||
after: function() {
|
||||
assertChildren("abcde*more*");
|
||||
}
|
||||
},
|
||||
{
|
||||
desc: "Select the last item",
|
||||
selector: "#z",
|
||||
before: function() {},
|
||||
after: function() {
|
||||
assertChildren("*more*vwxyz");
|
||||
}
|
||||
},
|
||||
{
|
||||
desc: "Select an already-visible item",
|
||||
selector: "#v",
|
||||
before: function() {},
|
||||
after: function() {
|
||||
// Because "v" was already visible, we shouldn't have loaded
|
||||
// a different page.
|
||||
assertChildren("*more*vwxyz");
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Verify childrenDirty reloads the page",
|
||||
selector: "#w",
|
||||
before: function() {
|
||||
forceReload();
|
||||
},
|
||||
after: function() {
|
||||
// But now that we don't already have a loaded page, selecting
|
||||
// w should center around w.
|
||||
assertChildren("*more*uvwxy*more*");
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
// Create the helper tab for parsing...
|
||||
gBrowser.selectedTab = gBrowser.addTab();
|
||||
gBrowser.selectedBrowser.addEventListener("load", function onload() {
|
||||
gBrowser.selectedBrowser.removeEventListener("load", onload, true);
|
||||
doc = content.document;
|
||||
waitForFocus(setupTest, content);
|
||||
}, true);
|
||||
content.location = "http://mochi.test:8888/browser/browser/devtools/markupview/test/browser_inspector_markup_subset.html";
|
||||
|
||||
function setupTest() {
|
||||
var target = TargetFactory.forTab(gBrowser.selectedTab);
|
||||
let toolbox = gDevTools.showToolbox(target, "inspector").then(function(toolbox) {
|
||||
inspector = toolbox.getCurrentPanel();
|
||||
markup = inspector.markup;
|
||||
inspector.once("inspector-updated", runNextSelection);
|
||||
});
|
||||
}
|
||||
|
||||
function runNextSelection() {
|
||||
let selection = selections.shift();
|
||||
if (!selection) {
|
||||
clickMore();
|
||||
return;
|
||||
}
|
||||
|
||||
info(selection.desc);
|
||||
selection.before();
|
||||
inspector.once("inspector-updated", function() {
|
||||
selection.after();
|
||||
runNextSelection();
|
||||
});
|
||||
inspector.selection.setNode(doc.querySelector(selection.selector));
|
||||
}
|
||||
|
||||
function clickMore() {
|
||||
info("Check that clicking more loads the whole thing.");
|
||||
// Make sure that clicking the "more" button loads all the nodes.
|
||||
let container = getContainerForRawNode(markup, doc.querySelector("body"));
|
||||
let button = container.elt.querySelector("button");
|
||||
let win = button.ownerDocument.defaultView;
|
||||
|
||||
EventUtils.sendMouseEvent({type: "click"}, button, win);
|
||||
|
||||
markup._waitForChildren().then(() => {
|
||||
assertChildren("abcdefghijklmnopqrstuvwxyz");
|
||||
finishUp();
|
||||
});
|
||||
}
|
||||
|
||||
function finishUp() {
|
||||
doc = inspector = markup = null;
|
||||
gBrowser.removeCurrentTab();
|
||||
finish();
|
||||
}
|
||||
}
|
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
|
@ -0,0 +1,142 @@
|
|||
/* vim: set ts=2 et sw=2 tw=80: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
// Test CSS state is correctly determined and the corresponding suggestions are
|
||||
// displayed. i.e. CSS property suggestions are shown when cursor is like:
|
||||
// ```style="di|"``` where | is the cursor; And CSS value suggestion is
|
||||
// displayed when the cursor is like: ```style="display:n|"``` properly. No
|
||||
// suggestions should ever appear when the attribute is not a style attribute.
|
||||
// The correctness and cycling of the suggestions is covered in the ruleview
|
||||
// tests.
|
||||
|
||||
const TEST_URL = TEST_URL_ROOT + "doc_markup_edit.html";
|
||||
// test data format :
|
||||
// [
|
||||
// what key to press,
|
||||
// expected input box value after keypress,
|
||||
// expected input.selectionStart,
|
||||
// expected input.selectionEnd,
|
||||
// is popup expected to be open ?
|
||||
// ]
|
||||
const TEST_DATA = [
|
||||
['s', 's', 1, 1, false],
|
||||
['t', 'st', 2, 2, false],
|
||||
['y', 'sty', 3, 3, false],
|
||||
['l', 'styl', 4, 4, false],
|
||||
['e', 'style', 5, 5, false],
|
||||
['=', 'style=', 6, 6, false],
|
||||
['"', 'style="', 7, 7, false],
|
||||
['d', 'style="direction', 8, 16, true],
|
||||
['VK_DOWN', 'style="display', 8, 14, true],
|
||||
['VK_TAB', 'style="display', 14, 14, true],
|
||||
['VK_TAB', 'style="dominant-baseline', 24, 24, true],
|
||||
['VK_TAB', 'style="direction', 16, 16, true],
|
||||
['click_1', 'style="display', 14, 14, false],
|
||||
[':', 'style="display:-moz-box', 15, 23, true],
|
||||
['n', 'style="display:none', 16, 19, false],
|
||||
['VK_BACK_SPACE', 'style="display:n', 16, 16, false],
|
||||
['VK_BACK_SPACE', 'style="display:', 15, 15, false],
|
||||
[' ', 'style="display: -moz-box', 16, 24, true],
|
||||
[' ', 'style="display: -moz-box', 17, 25, true],
|
||||
['i', 'style="display: inherit', 18, 24, true],
|
||||
['VK_RIGHT', 'style="display: inherit', 24, 24, false],
|
||||
[';', 'style="display: inherit;', 25, 25, false],
|
||||
[' ', 'style="display: inherit; ', 26, 26, false],
|
||||
[' ', 'style="display: inherit; ', 27, 27, false],
|
||||
['VK_LEFT', 'style="display: inherit; ', 26, 26, false],
|
||||
['c', 'style="display: inherit; caption-side ', 27, 38, true],
|
||||
['o', 'style="display: inherit; color ', 28, 31, true],
|
||||
['VK_RIGHT', 'style="display: inherit; color ', 31, 31, false],
|
||||
[' ', 'style="display: inherit; color ', 32, 32, false],
|
||||
['c', 'style="display: inherit; color c ', 33, 33, false],
|
||||
['VK_BACK_SPACE', 'style="display: inherit; color ', 32, 32, false],
|
||||
[':', 'style="display: inherit; color :aliceblue ', 33, 42, true],
|
||||
['c', 'style="display: inherit; color :cadetblue ', 34, 42, true],
|
||||
['VK_DOWN', 'style="display: inherit; color :chartreuse ', 34, 43, true],
|
||||
['VK_RIGHT', 'style="display: inherit; color :chartreuse ', 43, 43, false],
|
||||
[' ', 'style="display: inherit; color :chartreuse !important; ', 44, 55, true],
|
||||
['!', 'style="display: inherit; color :chartreuse !important; ', 45, 55, false],
|
||||
['VK_RIGHT', 'style="display: inherit; color :chartreuse !important; ', 55, 55, false],
|
||||
['VK_RETURN', 'style="display: inherit; color :chartreuse !important;"', -1, -1, false]
|
||||
];
|
||||
|
||||
let test = asyncTest(function*() {
|
||||
info("Opening the inspector on the test URL");
|
||||
let {inspector} = yield addTab(TEST_URL).then(openInspector);
|
||||
|
||||
yield inspector.markup.expandAll();
|
||||
|
||||
let node = getContainerForRawNode("#node14", inspector).editor;
|
||||
let attr = node.newAttr;
|
||||
attr.focus();
|
||||
EventUtils.sendKey("return", inspector.panelWin);
|
||||
let editor = inplaceEditor(attr);
|
||||
|
||||
for (let i = 0; i < TEST_DATA.length; i ++) {
|
||||
yield enterData(i, editor, inspector);
|
||||
checkData(i, editor, inspector);
|
||||
}
|
||||
|
||||
while (inspector.markup.undo.canUndo()) {
|
||||
yield undoChange(inspector);
|
||||
}
|
||||
|
||||
yield inspector.once("inspector-updated");
|
||||
});
|
||||
|
||||
function enterData(index, editor, inspector) {
|
||||
let [key] = TEST_DATA[index];
|
||||
info("Entering test data " + index + ": " + key + ", expecting: [" + TEST_DATA[index].slice(1) + "]");
|
||||
|
||||
let def = promise.defer();
|
||||
|
||||
if (/click_[0-9]/.test(key)) {
|
||||
let nb = +key.split("_")[1];
|
||||
info("Clicking on item " + nb + " in the list");
|
||||
editor.once("after-suggest", () => {
|
||||
executeSoon(def.resolve);
|
||||
});
|
||||
editor.popup._list.childNodes[nb].click();
|
||||
editor.input.blur();
|
||||
return def.promise;
|
||||
}
|
||||
|
||||
if (/(down|left|right|back_space|return)/ig.test(key)) {
|
||||
info("Adding event listener for down|left|right|back_space|return keys");
|
||||
editor.input.addEventListener("keypress", function onKeypress() {
|
||||
if (editor.input) {
|
||||
editor.input.removeEventListener("keypress", onKeypress);
|
||||
}
|
||||
executeSoon(def.resolve);
|
||||
});
|
||||
} else {
|
||||
editor.once("after-suggest", () => {
|
||||
executeSoon(def.resolve);
|
||||
});
|
||||
}
|
||||
EventUtils.synthesizeKey(key, {}, inspector.panelWin);
|
||||
|
||||
return def.promise;
|
||||
}
|
||||
|
||||
function checkData(index, editor, inspector) {
|
||||
let [key, completion, selStart, selEnd, popupOpen] = TEST_DATA[index];
|
||||
info("Test data " + index + " entered. Checking state.");
|
||||
|
||||
if (selEnd != -1) {
|
||||
is(editor.input.value, completion, "Completed value is correct");
|
||||
is(editor.input.selectionStart, selStart, "Selection start position is correct");
|
||||
is(editor.input.selectionEnd, selEnd, "Selection end position is correct");
|
||||
if (popupOpen) {
|
||||
ok(editor.popup.isOpen, "Popup is open");
|
||||
} else {
|
||||
ok(editor.popup._panel.state != "open" && editor.popup._panel.state != "showing",
|
||||
"Popup is closed");
|
||||
}
|
||||
} else {
|
||||
let editor = getContainerForRawNode("#node14", inspector).editor;
|
||||
let attr = editor.attrs["style"].querySelector(".editable");
|
||||
is(attr.textContent, completion, "Correct value is persisted after pressing Enter");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
/* vim: set ts=2 et sw=2 tw=80: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
// Test that when first hovering over a node and immediately after selecting it
|
||||
// by clicking on it leaves the highlighter visible for as long as the mouse is
|
||||
// over the node
|
||||
|
||||
let test = asyncTest(function*() {
|
||||
let {inspector} = yield addTab("data:text/html,<p>It's going to be legen....</p>").then(openInspector);
|
||||
let p = getNode("p");
|
||||
|
||||
info("hovering over the <p> line in the markup-view");
|
||||
yield hoverContainer(p, inspector);
|
||||
ok(isHighlighterVisible(), "the highlighter is still visible");
|
||||
|
||||
info("selecting the <p> line by clicking in the markup-view");
|
||||
yield clickContainer(p, inspector);
|
||||
|
||||
p.textContent = "wait for it ....";
|
||||
info("wait and see if the highlighter stays visible even after the node was selected");
|
||||
yield waitForTheBrieflyShowBoxModelTimeout();
|
||||
|
||||
let updated = inspector.once("inspector-updated");
|
||||
p.textContent = "dary!!!!";
|
||||
ok(isHighlighterVisible(), "the highlighter is still visible");
|
||||
yield updated;
|
||||
});
|
||||
|
||||
function waitForTheBrieflyShowBoxModelTimeout() {
|
||||
let deferred = promise.defer();
|
||||
// Note that the current timeout is 1 sec and is neither configurable nor
|
||||
// exported anywhere we can access, so hard-coding the timeout
|
||||
content.setTimeout(deferred.resolve, 1500);
|
||||
return deferred.promise;
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
/* vim: set ts=2 et sw=2 tw=80: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
// Test that when after an element is selected and highlighted on hover, if the
|
||||
// mouse leaves the markup-view and comes back again on the same element, that
|
||||
// the highlighter is shown again on the node
|
||||
|
||||
let test = asyncTest(function*() {
|
||||
let {inspector} = yield addTab("data:text/html,<p>Select me!</p>").then(openInspector);
|
||||
|
||||
info("hover over the <p> line in the markup-view so that it's the currently hovered node");
|
||||
yield hoverContainer("p", inspector);
|
||||
|
||||
info("select the <p> markup-container line by clicking");
|
||||
yield clickContainer("p", inspector);
|
||||
ok(isHighlighterVisible(), "the highlighter is shown");
|
||||
|
||||
info("mouse-leave the markup-view");
|
||||
yield mouseLeaveMarkupView(inspector);
|
||||
ok(!isHighlighterVisible(), "the highlighter is hidden after mouseleave");
|
||||
|
||||
info("hover over the <p> line again, which is still selected");
|
||||
yield hoverContainer("p", inspector);
|
||||
ok(isHighlighterVisible(), "the highlighter is visible again");
|
||||
});
|
|
@ -0,0 +1,72 @@
|
|||
/* vim: set ts=2 et sw=2 tw=80: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
// Test outerHTML edition via the markup-view
|
||||
|
||||
const TEST_DATA = [
|
||||
{
|
||||
selector: "#one",
|
||||
oldHTML: '<div id="one">First <em>Div</em></div>',
|
||||
newHTML: '<div id="one">First Div</div>',
|
||||
validate: function(pageNode, selectedNode) {
|
||||
is(pageNode.textContent, "First Div", "New div has expected text content");
|
||||
ok(!getNode("#one em"), "No em remaining")
|
||||
}
|
||||
},
|
||||
{
|
||||
selector: "#removedChildren",
|
||||
oldHTML: '<div id="removedChildren">removedChild <i>Italic <b>Bold <u>Underline</u></b></i> Normal</div>',
|
||||
newHTML: '<div id="removedChildren">removedChild</div>'
|
||||
},
|
||||
{
|
||||
selector: "#addedChildren",
|
||||
oldHTML: '<div id="addedChildren">addedChildren</div>',
|
||||
newHTML: '<div id="addedChildren">addedChildren <i>Italic <b>Bold <u>Underline</u></b></i> Normal</div>'
|
||||
},
|
||||
{
|
||||
selector: "#addedAttribute",
|
||||
oldHTML: '<div id="addedAttribute">addedAttribute</div>',
|
||||
newHTML: '<div id="addedAttribute" class="important" disabled checked>addedAttribute</div>',
|
||||
validate: function(pageNode, selectedNode) {
|
||||
is(pageNode, selectedNode, "Original element is selected");
|
||||
is(pageNode.outerHTML, '<div id="addedAttribute" class="important" disabled="" checked="">addedAttribute</div>',
|
||||
"Attributes have been added");
|
||||
}
|
||||
},
|
||||
{
|
||||
selector: "#changedTag",
|
||||
oldHTML: '<div id="changedTag">changedTag</div>',
|
||||
newHTML: '<p id="changedTag" class="important">changedTag</p>'
|
||||
},
|
||||
{
|
||||
selector: "#siblings",
|
||||
oldHTML: '<div id="siblings">siblings</div>',
|
||||
newHTML: '<div id="siblings-before-sibling">before sibling</div>' +
|
||||
'<div id="siblings">siblings (updated)</div>' +
|
||||
'<div id="siblings-after-sibling">after sibling</div>',
|
||||
validate: function(pageNode, selectedNode) {
|
||||
let beforeSiblingNode = getNode("#siblings-before-sibling");
|
||||
let afterSiblingNode = getNode("#siblings-after-sibling");
|
||||
|
||||
is(beforeSiblingNode, selectedNode, "Sibling has been selected");
|
||||
is(pageNode.textContent, "siblings (updated)", "New div has expected text content");
|
||||
is(beforeSiblingNode.textContent, "before sibling", "Sibling has been inserted");
|
||||
is(afterSiblingNode.textContent, "after sibling", "Sibling has been inserted");
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
const TEST_URL = "data:text/html," +
|
||||
"<!DOCTYPE html>" +
|
||||
"<head><meta charset='utf-8' /></head>" +
|
||||
"<body>" +
|
||||
[outer.oldHTML for (outer of TEST_DATA)].join("\n") +
|
||||
"</body>" +
|
||||
"</html>";
|
||||
|
||||
let test = asyncTest(function*() {
|
||||
let {inspector} = yield addTab(TEST_URL).then(openInspector);
|
||||
inspector.markup._frame.focus();
|
||||
yield runEditOuterHTMLTests(TEST_DATA, inspector);
|
||||
});
|
|
@ -0,0 +1,96 @@
|
|||
/* vim: set ts=2 et sw=2 tw=80: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
// Test outerHTML edition via the markup-view
|
||||
|
||||
const TEST_DATA = [
|
||||
{
|
||||
selector: "#badMarkup1",
|
||||
oldHTML: '<div id="badMarkup1">badMarkup1</div>',
|
||||
newHTML: '<div id="badMarkup1">badMarkup1</div> hanging</div>',
|
||||
validate: function(pageNode, selectedNode) {
|
||||
is(pageNode, selectedNode, "Original element is selected");
|
||||
|
||||
let textNode = pageNode.nextSibling;
|
||||
|
||||
is(textNode.nodeName, "#text", "Sibling is a text element");
|
||||
is(textNode.data, " hanging", "New text node has expected text content");
|
||||
}
|
||||
},
|
||||
{
|
||||
selector: "#badMarkup2",
|
||||
oldHTML: '<div id="badMarkup2">badMarkup2</div>',
|
||||
newHTML: '<div id="badMarkup2">badMarkup2</div> hanging<div></div></div></div></body>',
|
||||
validate: function(pageNode, selectedNode) {
|
||||
is(pageNode, selectedNode, "Original element is selected");
|
||||
|
||||
let textNode = pageNode.nextSibling;
|
||||
|
||||
is(textNode.nodeName, "#text", "Sibling is a text element");
|
||||
is(textNode.data, " hanging", "New text node has expected text content");
|
||||
}
|
||||
},
|
||||
{
|
||||
selector: "#badMarkup3",
|
||||
oldHTML: '<div id="badMarkup3">badMarkup3</div>',
|
||||
newHTML: '<div id="badMarkup3">badMarkup3 <em>Emphasized <strong> and strong</div>',
|
||||
validate: function(pageNode, selectedNode) {
|
||||
is(pageNode, selectedNode, "Original element is selected");
|
||||
|
||||
let em = getNode("#badMarkup3 em");
|
||||
let strong = getNode("#badMarkup3 strong");
|
||||
|
||||
is(em.textContent, "Emphasized and strong", "<em> was auto created");
|
||||
is(strong.textContent, " and strong", "<strong> was auto created");
|
||||
}
|
||||
},
|
||||
{
|
||||
selector: "#badMarkup4",
|
||||
oldHTML: '<div id="badMarkup4">badMarkup4</div>',
|
||||
newHTML: '<div id="badMarkup4">badMarkup4</p>',
|
||||
validate: function(pageNode, selectedNode) {
|
||||
is(pageNode, selectedNode, "Original element is selected");
|
||||
|
||||
let div = getNode("#badMarkup4");
|
||||
let p = getNode("#badMarkup4 p");
|
||||
|
||||
is(div.textContent, "badMarkup4", "textContent is correct");
|
||||
is(div.tagName, "DIV", "did not change to <p> tag");
|
||||
is(p.textContent, "", "The <p> tag has no children");
|
||||
is(p.tagName, "P", "Created an empty <p> tag");
|
||||
}
|
||||
},
|
||||
{
|
||||
selector: "#badMarkup5",
|
||||
oldHTML: '<p id="badMarkup5">badMarkup5</p>',
|
||||
newHTML: '<p id="badMarkup5">badMarkup5 <div>with a nested div</div></p>',
|
||||
validate: function(pageNode, selectedNode) {
|
||||
is(pageNode, selectedNode, "Original element is selected");
|
||||
|
||||
let p = getNode("#badMarkup5");
|
||||
let nodiv = getNode("#badMarkup5 div");
|
||||
let div = getNode("#badMarkup5 ~ div");
|
||||
|
||||
ok(!nodiv, "The invalid markup got created as a sibling");
|
||||
is(p.textContent, "badMarkup5 ", "The <p> tag does not take in the <div> content");
|
||||
is(p.tagName, "P", "Did not change to a <div> tag");
|
||||
is(div.textContent, "with a nested div", "textContent is correct");
|
||||
is(div.tagName, "DIV", "Did not change to <p> tag");
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
const TEST_URL = "data:text/html," +
|
||||
"<!DOCTYPE html>" +
|
||||
"<head><meta charset='utf-8' /></head>" +
|
||||
"<body>" +
|
||||
[outer.oldHTML for (outer of TEST_DATA)].join("\n") +
|
||||
"</body>" +
|
||||
"</html>";
|
||||
|
||||
let test = asyncTest(function*() {
|
||||
let {inspector} = yield addTab(TEST_URL).then(openInspector);
|
||||
inspector.markup._frame.focus();
|
||||
yield runEditOuterHTMLTests(TEST_DATA, inspector);
|
||||
});
|
|
@ -0,0 +1,178 @@
|
|||
/* vim: set ts=2 et sw=2 tw=80: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
// Test that outerHTML editing keybindings work as expected and that *special*
|
||||
// elements like <html>, <body> and <head> can be edited correctly.
|
||||
|
||||
const TEST_URL = "data:text/html," +
|
||||
"<!DOCTYPE html>" +
|
||||
"<head><meta charset='utf-8' /></head>" +
|
||||
"<body>" +
|
||||
"<div id=\"keyboard\"></div>" +
|
||||
"</body>" +
|
||||
"</html>";
|
||||
const SELECTOR = "#keyboard";
|
||||
const OLD_HTML = '<div id="keyboard"></div>';
|
||||
const NEW_HTML = '<div id="keyboard">Edited</div>';
|
||||
|
||||
let test = asyncTest(function*() {
|
||||
let {inspector} = yield addTab(TEST_URL).then(openInspector);
|
||||
|
||||
inspector.markup._frame.focus();
|
||||
|
||||
info("Checking that pressing escape cancels edits");
|
||||
yield testEscapeCancels(inspector);
|
||||
|
||||
info("Checking that pressing F2 commits edits");
|
||||
yield testF2Commits(inspector);
|
||||
|
||||
info("Checking that editing the <body> element works like other nodes");
|
||||
yield testBody(inspector);
|
||||
|
||||
info("Checking that editing the <head> element works like other nodes");
|
||||
yield testHead(inspector);
|
||||
|
||||
info("Checking that editing the <html> element works like other nodes");
|
||||
yield testDocumentElement(inspector);
|
||||
|
||||
info("Checking (again) that editing the <html> element works like other nodes");
|
||||
yield testDocumentElement2(inspector);
|
||||
});
|
||||
|
||||
function testEscapeCancels(inspector) {
|
||||
let def = promise.defer();
|
||||
let node = getNode(SELECTOR);
|
||||
|
||||
selectNode(node, inspector).then(() => {
|
||||
inspector.markup.htmlEditor.on("popupshown", function onPopupShown() {
|
||||
inspector.markup.htmlEditor.off("popupshown", onPopupShown);
|
||||
|
||||
ok(inspector.markup.htmlEditor._visible, "HTML Editor is visible");
|
||||
is(node.outerHTML, OLD_HTML, "The node is starting with old HTML.");
|
||||
|
||||
inspector.markup.htmlEditor.on("popuphidden", function onPopupHidden() {
|
||||
inspector.markup.htmlEditor.off("popuphidden", onPopupHidden);
|
||||
ok(!inspector.markup.htmlEditor._visible, "HTML Editor is not visible");
|
||||
|
||||
let node = getNode(SELECTOR);
|
||||
is(node.outerHTML, OLD_HTML, "Escape cancels edits");
|
||||
def.resolve();
|
||||
});
|
||||
|
||||
inspector.markup.htmlEditor.editor.setText(NEW_HTML);
|
||||
|
||||
EventUtils.sendKey("ESCAPE", inspector.markup.htmlEditor.doc.defaultView);
|
||||
});
|
||||
|
||||
EventUtils.sendKey("F2", inspector.markup._frame.contentWindow);
|
||||
});
|
||||
|
||||
return def.promise;
|
||||
}
|
||||
|
||||
function testF2Commits(inspector) {
|
||||
let def = promise.defer();
|
||||
let node = getNode(SELECTOR);
|
||||
|
||||
inspector.markup.htmlEditor.on("popupshown", function onPopupShown() {
|
||||
inspector.markup.htmlEditor.off("popupshown", onPopupShown);
|
||||
|
||||
ok(inspector.markup.htmlEditor._visible, "HTML Editor is visible");
|
||||
is(node.outerHTML, OLD_HTML, "The node is starting with old HTML.");
|
||||
|
||||
inspector.once("markupmutation", (e, aMutations) => {
|
||||
ok(!inspector.markup.htmlEditor._visible, "HTML Editor is not visible");
|
||||
|
||||
let node = getNode(SELECTOR);
|
||||
is(node.outerHTML, NEW_HTML, "F2 commits edits - the node has new HTML.");
|
||||
def.resolve();
|
||||
});
|
||||
|
||||
inspector.markup.htmlEditor.editor.setText(NEW_HTML);
|
||||
EventUtils.sendKey("F2", inspector.markup._frame.contentWindow);
|
||||
});
|
||||
|
||||
inspector.markup._frame.contentDocument.documentElement.focus();
|
||||
EventUtils.sendKey("F2", inspector.markup._frame.contentWindow);
|
||||
|
||||
return def.promise;
|
||||
}
|
||||
|
||||
function testBody(inspector) {
|
||||
let body = getNode("body");
|
||||
let bodyHTML = '<body id="updated"><p></p></body>';
|
||||
let bodyFront = inspector.markup.walker.frontForRawNode(body);
|
||||
let doc = content.document;
|
||||
|
||||
let mutated = inspector.once("markupmutation");
|
||||
inspector.markup.updateNodeOuterHTML(bodyFront, bodyHTML, body.outerHTML);
|
||||
|
||||
return mutated.then(mutations => {
|
||||
is(getNode("body").outerHTML, bodyHTML, "<body> HTML has been updated");
|
||||
is(doc.querySelectorAll("head").length, 1, "no extra <head>s have been added");
|
||||
return inspector.once("inspector-updated");
|
||||
});
|
||||
}
|
||||
|
||||
function testHead(inspector) {
|
||||
let head = getNode("head");
|
||||
let headHTML = '<head id="updated"><title>New Title</title><script>window.foo="bar";</script></head>';
|
||||
let headFront = inspector.markup.walker.frontForRawNode(head);
|
||||
let doc = content.document;
|
||||
|
||||
let mutated = inspector.once("markupmutation");
|
||||
inspector.markup.updateNodeOuterHTML(headFront, headHTML, head.outerHTML);
|
||||
|
||||
return mutated.then(mutations => {
|
||||
is(doc.title, "New Title", "New title has been added");
|
||||
is(doc.defaultView.foo, undefined, "Script has not been executed");
|
||||
is(doc.querySelector("head").outerHTML, headHTML, "<head> HTML has been updated");
|
||||
is(doc.querySelectorAll("body").length, 1, "no extra <body>s have been added");
|
||||
return inspector.once("inspector-updated");
|
||||
});
|
||||
}
|
||||
|
||||
function testDocumentElement(inspector) {
|
||||
let doc = content.document;
|
||||
let docElement = doc.documentElement;
|
||||
let docElementHTML = '<html id="updated" foo="bar"><head><title>Updated from document element</title><script>window.foo="bar";</script></head><body><p>Hello</p></body></html>';
|
||||
let docElementFront = inspector.markup.walker.frontForRawNode(docElement);
|
||||
|
||||
let mutated = inspector.once("markupmutation");
|
||||
inspector.markup.updateNodeOuterHTML(docElementFront, docElementHTML, docElement.outerHTML);
|
||||
|
||||
return mutated.then(mutations => {
|
||||
is(doc.title, "Updated from document element", "New title has been added");
|
||||
is(doc.defaultView.foo, undefined, "Script has not been executed");
|
||||
is(doc.documentElement.id, "updated", "<html> ID has been updated");
|
||||
is(doc.documentElement.className, "", "<html> class has been updated");
|
||||
is(doc.documentElement.getAttribute("foo"), "bar", "<html> attribute has been updated");
|
||||
is(doc.documentElement.outerHTML, docElementHTML, "<html> HTML has been updated");
|
||||
is(doc.querySelectorAll("head").length, 1, "no extra <head>s have been added");
|
||||
is(doc.querySelectorAll("body").length, 1, "no extra <body>s have been added");
|
||||
is(doc.body.textContent, "Hello", "document.body.textContent has been updated");
|
||||
});
|
||||
}
|
||||
|
||||
function testDocumentElement2(inspector) {
|
||||
let doc = content.document;
|
||||
let docElement = doc.documentElement;
|
||||
let docElementHTML = '<html class="updated" id="somethingelse"><head><title>Updated again from document element</title><script>window.foo="bar";</script></head><body><p>Hello again</p></body></html>';
|
||||
let docElementFront = inspector.markup.walker.frontForRawNode(docElement);
|
||||
|
||||
let mutated = inspector.once("markupmutation");
|
||||
inspector.markup.updateNodeOuterHTML(docElementFront, docElementHTML, docElement.outerHTML);
|
||||
|
||||
return mutated.then(mutations => {
|
||||
is(doc.title, "Updated again from document element", "New title has been added");
|
||||
is(doc.defaultView.foo, undefined, "Script has not been executed");
|
||||
is(doc.documentElement.id, "somethingelse", "<html> ID has been updated");
|
||||
is(doc.documentElement.className, "updated", "<html> class has been updated");
|
||||
is(doc.documentElement.getAttribute("foo"), null, "<html> attribute has been removed");
|
||||
is(doc.documentElement.outerHTML, docElementHTML, "<html> HTML has been updated");
|
||||
is(doc.querySelectorAll("head").length, 1, "no extra <head>s have been added");
|
||||
is(doc.querySelectorAll("body").length, 1, "no extra <body>s have been added");
|
||||
is(doc.body.textContent, "Hello again", "document.body.textContent has been updated");
|
||||
});
|
||||
}
|
|
@ -2,16 +2,14 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
let {PanelFactory} = devtools.require("devtools/shared/widgets/Tooltip");
|
||||
|
||||
let contentDoc;
|
||||
let inspector;
|
||||
let markup;
|
||||
// Test that image preview tooltips are shown on img and canvas tags in the
|
||||
// markup-view and that the tooltip actually contains an image and shows the
|
||||
// right dimension label
|
||||
|
||||
const PAGE_CONTENT = [
|
||||
'<img class="local" src="chrome://branding/content/about-logo.png" />',
|
||||
'<img class="data" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAADI5JREFUeNrsWwuQFNUVPf1m5z87szv7HWSWj8CigBFMEFZKiQsB1PgJwUAZg1HBpIQsKmokEhNjWUnFVPnDWBT+KolJYbRMoqUVq0yCClpqiX8sCchPWFwVlt2db7+X93pez7zu6Vn2NxsVWh8987p7pu+9555z7+tZjTGGY3kjOMa34w447oBjfKsY7i/UNM3Y8eFSAkD50Plgw03K5P9gvGv7U5ieeR3PszeREiPNX3/0DL4hjslzhm8THh+OITfXk3dhiv4GDtGPVzCaeJmPLYzuu5qJuWfuw2QTlcN1X9pwQU7LhdZ/ZAseD45cOh9hHvDkc/yAF/DNhdb5Mrr3PvBMaAYW8fMSIi2G497IMEK/YutGtAYr6+ej+nxu/NN8Ks3N7AR6HgcLz0Eg1Ljg1UcxZzi5qewIkMYLRweTr2Kzp+nmyXAd5pS3XQDd+N/4h4zgu9FI7brlXf90nMEnuwQxlvv+hosE3TuexmWeysmT4W+WxkMaLzf9Y8ATgjcUn7T9H1gqrpFq8eV1gMn6t16NhngjfoX6q4DUP032Rd4LJgpSLwJ1yzFqBG69eRkah0MVyo0Acfe+yy9AG4nMiYCkeM53KKFXncBLAXqEm+wCqZwaueq7WCmuLTcKSJmj737ol2hurA9eq9VdyiO8yWa3NNyog+SB5CZodSsQq/dfu34tJpYbBaTMzvVddDZu16q5smXf4G8zEvqm4cyaAmJPuTJk3oJWdS4WzcVtfMZbThSQckb/pYfRGgo3zNOqZnEHbJPGK4abaDCQIIsT8V/qTaBqHkLh6LzXH8XZQhbLhYKyyCC/WeHYcNdmvOgfe8skzbWL270/T3wf7tSx/lGCbTu8xlzzmCSWLc5iwmgikcCHi3Mga0Ry913vBFvQwg90l6M4ImWKfsWOp7DSWxmfpPlCFuPFfsNfKrCnPYpQKIRgqBK7D0SxYaNHwkEiJMtl0ReDp3Lc5D3PGoTo/sKngCl7a5chFqvBatKwjBd7WwqIlzB/78NcoUcp5VSgGxm+7b8eqQRGnHMO634epO4S1EZww09/iFg5UmGoESDuznP1xVhTUX1WWHPzjpd25wyH0hRxI3LGM75nxmuNEEUVpAN0XgxmPoKralakbQnWlIMQyVBD/w+3orkq4lvualjKyWwzt4MaxqspQHVhPOWG64bxYuhZXSFGWhipbSDVragOu5Y9eAsmDDUKyBA703vemVhHoueD6e9wAzJK1WfmN0Umk5GGM4kEMZcuIECqgjm0nldAqmbjwtm4VxZH5AvlADP6mx9Eqy9Q0+KqW8Ch+47FaMMYmnNGfY1iPMshoC6qFxme4wQ+0p+ARE6H3+9veWEDWgUhDhUKyFARn4jM5BNxT0XsMg7bfymGK1ov3wtjDfhL4w0HVGUVBEjDaaE+QNdrcNWch1PG4W6xrjBUXECGivg++Cva3JUT4iQUz3V2RsSVaKLwOuDT89A3HdBQoxhNC+fnVm74ual2EG893P6G+PuP4SfiO4cCBWQooL9qCWKNXPbcI37Aa/lnlZxXRt4RFONGwSDCPAHqOuqjWct1QiEMw5mChM5X4K47FyNqcd3aK9AwFH0CGYLoe1ctxk2eWi57rg5JfGp9rzC6ggCdFlAgHBDw5Yxlcg6G8SyHCjMlsgmDD9zhSeHlF+JnAgWDTQUy2NxfdwOao1UVV3pi3+bE97YSbWpLAbn6zefHNQkp1PMpIBwwvslKgIYTKM2nEpNzrGcH3FXTEal0L38kJ4uDQgEZbO4vnI173LXf5NHZaiUxtaCxyZuo/rK6LpUg54yg3zTWRAArvDcRIPZ6BqzrQ1REpmL+DNw32OKIDCb3X1qPVn8wNNMT4w2bvs+q4bAZrqBh2skaL3yyhhIIZ4i6oHkUK0RckcB8GigEyRIH4A6Mgc8fatl0/+BkkQxC9gIT4ljna1rIZW9rEdNbjJcNjsnoYj7LHWCUwpITzEgzRQKZ3XAFHbTzA3hrz8TEUUZxFBhoKpABQt/97p+w0hMZG68I8R6FtlsJT3FELndZntjM+VMnylKYq8GJI3UZaRMpquGSGFVOEfv0YZBMNzz+uvjbfzS6xQERIhlI9FcvQWNdFVb7x1zCb+QNK8vb9NsiifmI5hBgVoOCBC1sb0ab5RomqENxLO3eA1/0NDRU47q2RQNbRCUDIb7lF2CNL3ZGxEV4n08TVvZWYG4pZyV0zUdS45tyCBByOHWiyvZmxFXDCyRo1ge5+Sy0TA+8lWMiP/6O0S32exGV9Jf4fr8azdUR3zL/CZz4MtvzdX5uOYs6NDOmpkuj5Huh+7qUQSYl0ThHzw0YQzcGo6bhzEqoYq5rN3yRiYiG3Vfe2Ybm/qKA9NNZ3nNm4F7/yDkg9AN+U1mHiBcXP8zuDN76jj8hg1QyiWQigalj02BJPhK8I0zxijAjhp5zhlpLUDvS+BCy2HMAvvB4XDgL9/SXC0g/ou/5+6/xLX8w0uJrOIkXfPvyhY0F6gr7M8H0KWFYikcqAXakB+xwD9CdREBLoau7Gz3cAdSIdLFxFtJTCqRChSjnutvhDcREtzjz2Tswtz+yeNRFUeXZXtWux7C1fuoVcbd3J//ipDX3uZZDLGrwweS+UBLL5TDliVBnF8P7H+XI8aRRGsIBJg/Zlslt1+W+D1JWoSyi+kD9jfhs78t7mhZhSl+fLfY1Bdyv3I8V/qpY3B1McgN7ZFT5/vNO0I5DPLLdPBIJA8qc4h2I0QplYfDpJwHT+aj0246r5S8rToG8OjCle8wk4OLvvYGa+Ovr84uo2qBSwJS9G5egoZFLTfiEqWDtbwGfHgKOdPHcS+ai7XDzMPW/FJRLGGcxnBbK4YJC2K+h+T6Bdu5CqHqCWERd3bawb7JI+iJ735+LNaHaprBLLHBm08U3XxShEsdt+f3eTh3v7aC95Dct4RCWL5OZWh/oXBZThxAIxyOXLzBk8aiEWJID8rK3CpPOmeHaGpvCS+7EHv5FujVHUSJPLXvIFeHcNc+9xrB2gws9KZdxuLFax/WLM5gzzSm/lTXF/OdAcapyvjxPqxqHjr2v4ckX2bS2dRBrc5lSdpKjEJ9/9tdwX2WMd53ZQ2IVo3RES+UwVSpCPvYepNx4gmTGDUKIMQ4eduPnD7mx9xOn/KZKOlFbStjONxHTtR+BYAPmnoZ1Zp8wkBRwP/EL3u0F/C2hGl7vpz7vW37T3vP7if8wroKuoh8ribknX9BK5rcF+mo1qKaKyRPJTgTDjbzY8szcuLb3bpH00u35T47j7prRpwDJTxzyG0dHgxPp5bPG8VdkpfPbUg3SgoOo2mwVukb98D5EqpswZTTulCggTk4gpYhv0++wIhCJxr0+Hq1sondis0SE2oxQe3qWXwWyO4DSQg9gJ8Iiw1VFcGqXxet0N9xE4ygIxv/9W6wo9WyROEX/R+eiobYSq2vHTOR631Eiv2lRfh9dvxkumkXh92Qsx8XrAJ+7YGbWuhxOi/U+31NQmzyqNYG8N/3wfo6CRtRHcN01FzkvojohwLu0VVvDa56IS/xcj2b7nN+O+m0jqpE1wMPXZxAN9iCVThtDvH7gmiRGRpU8Lspv1Uhq4wIVdQoyuGSLNYPKUCS8+CzNURbzMmjK3i8u0U793lmuV0ef9nWQ5MGC/DiUqEUSaCtXna9RJEspZS1lrXINK/pcq+SpT50t98QKMq1FRmDfx3vxty102k0PM4ssEnvuz5+G26Ij4yDpz6z9fV8bkyIkqBFkhej0Ib+ZQ34XJK9AfozaiimqIoX3Jp3tiISrcfYpuN2+iFph/02P36PNC9fVcCnp6H9jYouKyfaWufz5Tp9tVxcUniw7IohZv4dZz81/ns67z3AYPrc2n0+Ix2q8k0PWjgBy88XaibnfK9A+5LdDY2Ivhy36fbT8Zv3Lb1U1qLqUxorXEEXIs0mjjrtxoTZWtdvigNs2sgPiujTv6DIZLld6b/V5742JZV3fUsUVFy5gdsNtKWFzUCEVbNepD1MkSMVbsb6SZm7jI3/zODtQKgUMsOw8wDZ63t5xcV1TnaEAxoc6wrqY+Fj+N4DsqOnhOIdicrQSm1MPYCPlIqHn5bbHg8/bj2D3QfZnCX3mpAICDZV8jH5kpbZqTD0W+DxaA74CWzLN2nd14OlL72J38Lf7+TjC7dadZFDoZJQPrtaIKL/G0L6ktptPZVJ8fMqHYPZOKYPMyQGadIJfDvdXwAFiZOTvDBPydf5vk4rWA+RfdhBlaF/yDDBRoMu9pfnSjv/p7DG+HXfAcQcc49v/BBgAcFAO4DmB2GQAAAAASUVORK5CYII=" />',
|
||||
'<img class="remote" src="http://mochi.test:8888/browser/browser/devtools/markupview/test/browser_inspector_markup_765105_tooltip.png" />',
|
||||
'<img class="remote" src="' + TEST_URL_ROOT + 'doc_markup_tooltip.png" />',
|
||||
'<canvas class="canvas" width="600" height="600"></canvas>'
|
||||
].join("\n");
|
||||
|
||||
|
@ -22,32 +20,27 @@ const TEST_NODES = [
|
|||
{selector: ".canvas", size: "600 x 600"}
|
||||
];
|
||||
|
||||
function test() {
|
||||
waitForExplicitFinish();
|
||||
let test = asyncTest(function*() {
|
||||
yield addTab("data:text/html,markup view tooltip test");
|
||||
createPage();
|
||||
|
||||
gBrowser.selectedTab = gBrowser.addTab();
|
||||
gBrowser.selectedBrowser.addEventListener("load", function(evt) {
|
||||
gBrowser.selectedBrowser.removeEventListener(evt.type, arguments.callee, true);
|
||||
contentDoc = content.document;
|
||||
waitForFocus(createDocument, content);
|
||||
}, true);
|
||||
let {inspector} = yield openInspector();
|
||||
|
||||
content.location = "data:text/html,markup view tooltip test";
|
||||
}
|
||||
info("Selecting the first <img> tag");
|
||||
yield selectNode("img", inspector);
|
||||
|
||||
function createDocument() {
|
||||
contentDoc.body.innerHTML = PAGE_CONTENT;
|
||||
for (let testNode of TEST_NODES) {
|
||||
let target = getImageTooltipTarget(testNode, inspector);
|
||||
yield assertTooltipShownOn(target, inspector);
|
||||
checkImageTooltip(testNode, inspector);
|
||||
}
|
||||
});
|
||||
|
||||
var target = TargetFactory.forTab(gBrowser.selectedTab);
|
||||
gDevTools.showToolbox(target, "inspector").then(function(toolbox) {
|
||||
inspector = toolbox.getCurrentPanel();
|
||||
markup = inspector.markup;
|
||||
startTests();
|
||||
});
|
||||
}
|
||||
function createPage() {
|
||||
info("Fill the page with the test content");
|
||||
content.document.body.innerHTML = PAGE_CONTENT;
|
||||
|
||||
function startTests() {
|
||||
// Draw something in the canvas :)
|
||||
info("Fill the canvas");
|
||||
let doc = content.document;
|
||||
let context = doc.querySelector(".canvas").getContext("2d");
|
||||
|
||||
|
@ -58,68 +51,35 @@ function startTests() {
|
|||
context.closePath();
|
||||
context.fillStyle = "#ffc821";
|
||||
context.fill();
|
||||
|
||||
// Actually start testing
|
||||
inspector.selection.setNode(contentDoc.querySelector("img"));
|
||||
inspector.once("inspector-updated", () => {
|
||||
testImageTooltip(0);
|
||||
});
|
||||
}
|
||||
|
||||
function endTests() {
|
||||
contentDoc = inspector = markup = null;
|
||||
gBrowser.removeCurrentTab();
|
||||
finish();
|
||||
}
|
||||
|
||||
function testImageTooltip(index) {
|
||||
if (index === TEST_NODES.length) {
|
||||
return endTests();
|
||||
}
|
||||
|
||||
let node = contentDoc.querySelector(TEST_NODES[index].selector);
|
||||
ok(node, "We have the [" + TEST_NODES[index].selector + "] image node to test for tooltip");
|
||||
function getImageTooltipTarget({selector}, inspector) {
|
||||
let node = getNode(selector);
|
||||
let isImg = node.tagName.toLowerCase() === "img";
|
||||
|
||||
let container = getContainerForRawNode(markup, node);
|
||||
let container = getContainerForRawNode(node, inspector);
|
||||
|
||||
let target = container.editor.tag;
|
||||
if (isImg) {
|
||||
target = container.editor.getAttributeElement("src");
|
||||
}
|
||||
|
||||
assertTooltipShownOn(target).then(() => {
|
||||
let images = markup.tooltip.panel.getElementsByTagName("image");
|
||||
is(images.length, 1,
|
||||
"Tooltip for [" + TEST_NODES[index].selector + "] contains an image");
|
||||
|
||||
let label = markup.tooltip.panel.querySelector(".devtools-tooltip-caption");
|
||||
is(label.textContent, TEST_NODES[index].size,
|
||||
"Tooltip label for [" + TEST_NODES[index].selector + "] displays the right image size")
|
||||
|
||||
testImageTooltip(index + 1);
|
||||
});
|
||||
let target = container.editor.tag;
|
||||
if (isImg) {
|
||||
target = container.editor.getAttributeElement("src");
|
||||
}
|
||||
return target;
|
||||
}
|
||||
|
||||
function compareImageData(img, imgData) {
|
||||
let canvas = content.document.createElement("canvas");
|
||||
canvas.width = img.naturalWidth;
|
||||
canvas.height = img.naturalHeight;
|
||||
let ctx = canvas.getContext("2d");
|
||||
let data = "";
|
||||
try {
|
||||
ctx.drawImage(img, 0, 0);
|
||||
data = canvas.toDataURL("image/png");
|
||||
} catch (e) {}
|
||||
|
||||
is(data, imgData, "Tooltip image has the right content");
|
||||
}
|
||||
|
||||
function assertTooltipShownOn(element, cb) {
|
||||
function assertTooltipShownOn(element, {markup}) {
|
||||
return Task.spawn(function*() {
|
||||
info("Is the element a valid hover target");
|
||||
|
||||
let isValid = yield markup.tooltip.isValidHoverTarget(element);
|
||||
ok(isValid, "The element is a valid hover target for the image tooltip");
|
||||
});
|
||||
}
|
||||
|
||||
function checkImageTooltip({selector, size}, {markup}) {
|
||||
let images = markup.tooltip.panel.getElementsByTagName("image");
|
||||
is(images.length, 1, "Tooltip for [" + selector + "] contains an image");
|
||||
|
||||
let label = markup.tooltip.panel.querySelector(".devtools-tooltip-caption");
|
||||
is(label.textContent, size, "Tooltip label for [" + selector + "] displays the right image size");
|
||||
|
||||
markup.tooltip.hide();
|
||||
}
|
|
@ -0,0 +1,154 @@
|
|||
/* vim: set ts=2 et sw=2 tw=80: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Tests that various mutations to the dom update the markup view correctly.
|
||||
* The test for comparing the markup view to the real dom is a bit weird:
|
||||
* - Select the text in the markup view
|
||||
* - Parse that as innerHTML in a document we've created for the purpose.
|
||||
* - Remove extraneous whitespace in that tree
|
||||
* - Compare it to the real dom with isEqualNode.
|
||||
*/
|
||||
|
||||
const TEST_URL = TEST_URL_ROOT + "doc_markup_mutation.html";
|
||||
// All the mutation types we want to test.
|
||||
const TEST_DATA = [
|
||||
{
|
||||
desc: "Adding an attribute",
|
||||
test: () => {
|
||||
let node1 = getNode("#node1");
|
||||
node1.setAttribute("newattr", "newattrval");
|
||||
}
|
||||
},
|
||||
{
|
||||
desc: "Removing an attribute",
|
||||
test: () => {
|
||||
let node1 = getNode("#node1");
|
||||
node1.removeAttribute("newattr");
|
||||
}
|
||||
},
|
||||
{
|
||||
desc: "Updating the text-content",
|
||||
test: () => {
|
||||
let node1 = getNode("#node1");
|
||||
node1.textContent = "newtext";
|
||||
}
|
||||
},
|
||||
{
|
||||
desc: "Updating the innerHTML",
|
||||
test: () => {
|
||||
let node2 = getNode("#node2");
|
||||
node2.innerHTML = "<div><span>foo</span></div>";
|
||||
}
|
||||
},
|
||||
{
|
||||
desc: "Removing child nodes",
|
||||
test: () => {
|
||||
let node4 = getNode("#node4");
|
||||
while (node4.firstChild) {
|
||||
node4.removeChild(node4.firstChild);
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
desc: "Appending a child to a different parent",
|
||||
test: () => {
|
||||
let node17 = getNode("#node17");
|
||||
let node1 = getNode("#node2");
|
||||
node1.appendChild(node17);
|
||||
}
|
||||
},
|
||||
{
|
||||
desc: "Swapping a parent and child element, putting them in the same tree",
|
||||
// body
|
||||
// node1
|
||||
// node18
|
||||
// node19
|
||||
// node20
|
||||
// node21
|
||||
// will become:
|
||||
// body
|
||||
// node1
|
||||
// node20
|
||||
// node21
|
||||
// node18
|
||||
// node19
|
||||
test: () => {
|
||||
let node18 = getNode("#node18");
|
||||
let node20 = getNode("#node20");
|
||||
|
||||
let node1 = getNode("#node1");
|
||||
|
||||
node1.appendChild(node20);
|
||||
node20.appendChild(node18);
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
let test = asyncTest(function*() {
|
||||
info("Creating the helper tab for parsing");
|
||||
let parseTab = yield addTab("data:text/html,<html></html>");
|
||||
let parseDoc = content.document;
|
||||
|
||||
info("Creating the test tab");
|
||||
let contentTab = yield addTab(TEST_URL);
|
||||
let doc = content.document;
|
||||
// Strip whitespace in the document for easier comparison
|
||||
stripWhitespace(doc.documentElement);
|
||||
|
||||
let {inspector} = yield openInspector();
|
||||
let markup = inspector.markup;
|
||||
|
||||
info("Expanding all markup-view nodes");
|
||||
yield markup.expandAll();
|
||||
|
||||
for (let step of TEST_DATA) {
|
||||
info("Starting test: " + step.desc);
|
||||
|
||||
info("Executing the test markup mutation, listening for inspector-updated before moving on");
|
||||
let updated = inspector.once("inspector-updated");
|
||||
step.test();
|
||||
yield updated;
|
||||
|
||||
info("Expanding all markup-view nodes to make sure new nodes are imported");
|
||||
yield markup.expandAll();
|
||||
|
||||
info("Comparing the markup-view markup with the content document");
|
||||
compareMarkup(parseDoc, inspector);
|
||||
}
|
||||
});
|
||||
|
||||
function stripWhitespace(node) {
|
||||
node.normalize();
|
||||
let iter = node.ownerDocument.createNodeIterator(node,
|
||||
NodeFilter.SHOW_TEXT + NodeFilter.SHOW_COMMENT, null);
|
||||
|
||||
while ((node = iter.nextNode())) {
|
||||
node.nodeValue = node.nodeValue.replace(/\s+/g, '');
|
||||
if (node.nodeType == Node.TEXT_NODE &&
|
||||
!/[^\s]/.exec(node.nodeValue)) {
|
||||
node.parentNode.removeChild(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function compareMarkup(parseDoc, inspector) {
|
||||
// Grab the text from the markup panel...
|
||||
let markupContainerEl = getContainerForRawNode("body", inspector).elt;
|
||||
let sel = markupContainerEl.ownerDocument.defaultView.getSelection();
|
||||
sel.selectAllChildren(markupContainerEl);
|
||||
|
||||
// Parse it
|
||||
let parseNode = parseDoc.querySelector("body");
|
||||
parseNode.outerHTML = sel;
|
||||
parseNode = parseDoc.querySelector("body");
|
||||
|
||||
// Pull whitespace out of text and comment nodes, there will
|
||||
// be minor unimportant differences.
|
||||
stripWhitespace(parseNode);
|
||||
|
||||
// console.log(contentNode.innerHTML, parseNode.innerHTML);
|
||||
ok(getNode("body").isEqualNode(parseNode),
|
||||
"Markup panel matches what's in the content document.");
|
||||
}
|
|
@ -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/ */
|
||||
|
||||
// Test that markup-containers in the markup-view do flash when their
|
||||
// corresponding DOM nodes mutate
|
||||
|
||||
const TEST_URL = TEST_URL_ROOT + "doc_markup_flashing.html";
|
||||
// The test data contains a list of mutations to test.
|
||||
// Each item is an object:
|
||||
// - desc: a description of the test step, for better logging
|
||||
// - mutate: a function that should make changes to the content DOM
|
||||
// - shouldFlash: a function that returns the element that should be the one flashing
|
||||
const TEST_DATA = [{
|
||||
desc: "Adding a new node should flash the new node",
|
||||
mutate: (doc, rootNode) => {
|
||||
let newLi = doc.createElement("LI");
|
||||
newLi.textContent = "new list item";
|
||||
rootNode.appendChild(newLi);
|
||||
},
|
||||
shouldFlash: rootNode => rootNode.lastElementChild
|
||||
}, {
|
||||
desc: "Removing a node should flash its parent",
|
||||
mutate: (doc, rootNode) => {
|
||||
rootNode.removeChild(rootNode.lastElementChild);
|
||||
},
|
||||
shouldFlash: rootNode => rootNode
|
||||
}, {
|
||||
desc: "Re-appending an existing node should only flash this node",
|
||||
mutate: (doc, rootNode) => {
|
||||
rootNode.appendChild(rootNode.firstElementChild);
|
||||
},
|
||||
shouldFlash: rootNode => rootNode.lastElementChild
|
||||
}, {
|
||||
desc: "Adding an attribute should flash the node",
|
||||
mutate: (doc, rootNode) => {
|
||||
rootNode.setAttribute("name-" + Date.now(), "value-" + Date.now());
|
||||
},
|
||||
shouldFlash: rootNode => rootNode
|
||||
}, {
|
||||
desc: "Editing an attribute should flash the node",
|
||||
mutate: (doc, rootNode) => {
|
||||
rootNode.setAttribute("class", "list value-" + Date.now());
|
||||
},
|
||||
shouldFlash: rootNode => rootNode
|
||||
}, {
|
||||
desc: "Removing an attribute should flash the node",
|
||||
mutate: (doc, rootNode) => {
|
||||
rootNode.removeAttribute("class");
|
||||
},
|
||||
shouldFlash: rootNode => rootNode
|
||||
}];
|
||||
|
||||
let test = asyncTest(function*() {
|
||||
let {inspector} = yield addTab(TEST_URL).then(openInspector);
|
||||
|
||||
info("Getting the <ul.list> root node to test mutations on");
|
||||
let rootNode = getNode(".list");
|
||||
|
||||
info("Selecting the last element of the root node before starting");
|
||||
yield selectNode(rootNode.lastElementChild, inspector);
|
||||
|
||||
for (let {mutate, shouldFlash, desc} of TEST_DATA) {
|
||||
info("Starting test: " + desc);
|
||||
|
||||
info("Mutating the DOM and listening for markupmutation event");
|
||||
let mutated = inspector.once("markupmutation");
|
||||
let updated = inspector.once("inspector-updated");
|
||||
mutate(content.document, rootNode);
|
||||
yield mutated;
|
||||
|
||||
info("Asserting that the correct markup-container is flashing");
|
||||
assertNodeFlashing(shouldFlash(rootNode), inspector);
|
||||
|
||||
// Making sure the inspector has finished updating before moving on
|
||||
yield updated;
|
||||
}
|
||||
});
|
||||
|
||||
function assertNodeFlashing(node, inspector) {
|
||||
let container = getContainerForRawNode(node, inspector);
|
||||
|
||||
if (!container) {
|
||||
ok(false, "Node not found");
|
||||
} else {
|
||||
ok(container.tagState.classList.contains("theme-bg-contrast"),
|
||||
"Node is flashing");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,134 @@
|
|||
/* vim: set ts=2 et sw=2 tw=80: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
// Test that the markup-view nodes can be navigated to with the keyboard
|
||||
|
||||
const TEST_URL = TEST_URL_ROOT + "doc_markup_navigation.html";
|
||||
const TEST_DATA = [
|
||||
["pageup", "*doctype*"],
|
||||
["down", "html"],
|
||||
["down", "head"],
|
||||
["down", "body"],
|
||||
["down", "node0"],
|
||||
["right", "node0"],
|
||||
["down", "node1"],
|
||||
["down", "node2"],
|
||||
["down", "node3"],
|
||||
["down", "*comment*"],
|
||||
["down", "node4"],
|
||||
["right", "node4"],
|
||||
["down", "*text*"],
|
||||
["down", "node5"],
|
||||
["down", "node6"],
|
||||
["down", "*comment*"],
|
||||
["down" , "node7"],
|
||||
["right", "node7"],
|
||||
["down", "*text*"],
|
||||
["down", "node8"],
|
||||
["left", "node7"],
|
||||
["left", "node7"],
|
||||
["right", "node7"],
|
||||
["right", "*text*"],
|
||||
["down", "node8"],
|
||||
["right", "node8"],
|
||||
["left", "node8"],
|
||||
["down", "node9"],
|
||||
["down", "node10"],
|
||||
["down", "node11"],
|
||||
["down", "node12"],
|
||||
["right", "node12"],
|
||||
["down", "*text*"],
|
||||
["down", "node13"],
|
||||
["down", "node14"],
|
||||
["down", "node15"],
|
||||
["down", "node15"],
|
||||
["down", "node15"],
|
||||
["up", "node14"],
|
||||
["up", "node13"],
|
||||
["up", "*text*"],
|
||||
["up", "node12"],
|
||||
["left", "node12"],
|
||||
["down", "node14"],
|
||||
["home", "*doctype*"],
|
||||
["pagedown", "*text*"],
|
||||
["down", "node5"],
|
||||
["down", "node6"],
|
||||
["down", "*comment*"],
|
||||
["down", "node7"],
|
||||
["left", "node7"],
|
||||
["down", "node9"],
|
||||
["down", "node10"],
|
||||
["pageup", "node2"],
|
||||
["pageup", "*doctype*"],
|
||||
["down", "html"],
|
||||
["left", "html"],
|
||||
["down", "html"]
|
||||
];
|
||||
|
||||
let test = asyncTest(function*() {
|
||||
let {inspector} = yield addTab(TEST_URL).then(openInspector);
|
||||
|
||||
info("Making sure the markup-view frame is focused");
|
||||
inspector.markup._frame.focus();
|
||||
|
||||
info("Starting to iterate through the test data");
|
||||
for (let [key, className] of TEST_DATA) {
|
||||
info("Testing step: " + key + " to navigate to " + className);
|
||||
pressKey(key);
|
||||
|
||||
info("Making sure markup-view children get updated");
|
||||
yield waitForChildrenUpdated(inspector);
|
||||
|
||||
info("Checking the right node is selected");
|
||||
checkSelectedNode(key, className, inspector);
|
||||
}
|
||||
});
|
||||
|
||||
function pressKey(key) {
|
||||
switch(key) {
|
||||
case "right":
|
||||
EventUtils.synthesizeKey("VK_RIGHT", {});
|
||||
break;
|
||||
case "down":
|
||||
EventUtils.synthesizeKey("VK_DOWN", {});
|
||||
break;
|
||||
case "left":
|
||||
EventUtils.synthesizeKey("VK_LEFT", {});
|
||||
break;
|
||||
case "up":
|
||||
EventUtils.synthesizeKey("VK_UP", {});
|
||||
break;
|
||||
case "pageup":
|
||||
EventUtils.synthesizeKey("VK_PAGE_UP", {});
|
||||
break;
|
||||
case "pagedown":
|
||||
EventUtils.synthesizeKey("VK_PAGE_DOWN", {});
|
||||
break;
|
||||
case "home":
|
||||
EventUtils.synthesizeKey("VK_HOME", {});
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function waitForChildrenUpdated(inspector) {
|
||||
let def = promise.defer();
|
||||
inspector.markup._waitForChildren().then(() => {
|
||||
executeSoon(def.resolve);
|
||||
});
|
||||
return def.promise;
|
||||
}
|
||||
|
||||
function checkSelectedNode(key, className, inspector) {
|
||||
let node = inspector.selection.node;
|
||||
|
||||
if (className == "*comment*") {
|
||||
is(node.nodeType, Node.COMMENT_NODE, "Found a comment after pressing " + key);
|
||||
} else if (className == "*text*") {
|
||||
is(node.nodeType, Node.TEXT_NODE, "Found text after pressing " + key);
|
||||
} else if (className == "*doctype*") {
|
||||
is(node.nodeType, Node.DOCUMENT_TYPE_NODE, "Found the doctype after pressing " + key);
|
||||
} else {
|
||||
is(node.className, className, "Found node: " + className + " after pressing " + key);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,84 @@
|
|||
/* vim: set ts=2 et sw=2 tw=80: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
// Tests that the markup view loads only as many nodes as specified by the
|
||||
// devtools.markup.pagesize preference.
|
||||
|
||||
Services.prefs.setIntPref("devtools.markup.pagesize", 5);
|
||||
|
||||
const TEST_URL = TEST_URL_ROOT + "doc_markup_pagesize_01.html";
|
||||
const TEST_DATA = [{
|
||||
desc: "Select the last item",
|
||||
selector: "#z",
|
||||
expected: "*more*vwxyz"
|
||||
}, {
|
||||
desc: "Select the first item",
|
||||
selector: "#a",
|
||||
expected: "abcde*more*"
|
||||
}, {
|
||||
desc: "Select the last item",
|
||||
selector: "#z",
|
||||
expected: "*more*vwxyz"
|
||||
}, {
|
||||
desc: "Select an already-visible item",
|
||||
selector: "#v",
|
||||
// Because "v" was already visible, we shouldn't have loaded
|
||||
// a different page.
|
||||
expected: "*more*vwxyz"
|
||||
}, {
|
||||
desc: "Verify childrenDirty reloads the page",
|
||||
selector: "#w",
|
||||
forceReload: true,
|
||||
// But now that we don't already have a loaded page, selecting
|
||||
// w should center around w.
|
||||
expected: "*more*uvwxy*more*"
|
||||
}];
|
||||
|
||||
let test = asyncTest(function*() {
|
||||
let {inspector} = yield addTab(TEST_URL).then(openInspector);
|
||||
|
||||
info("Start iterating through the test data");
|
||||
for (let step of TEST_DATA) {
|
||||
info("Start test: " + step.desc);
|
||||
|
||||
if (step.forceReload) {
|
||||
forceReload(inspector);
|
||||
}
|
||||
info("Selecting the node that corresponds to " + step.selector);
|
||||
yield selectNode(step.selector, inspector);
|
||||
|
||||
info("Checking that the right nodes are shwon");
|
||||
assertChildren(step.expected, inspector);
|
||||
}
|
||||
|
||||
info("Checking that clicking the more button loads everything");
|
||||
clickShowMoreNodes(inspector);
|
||||
yield inspector.markup._waitForChildren();
|
||||
assertChildren("abcdefghijklmnopqrstuvwxyz", inspector);
|
||||
});
|
||||
|
||||
function assertChildren(expected, inspector) {
|
||||
let container = getContainerForRawNode("body", inspector);
|
||||
let found = "";
|
||||
for (let child of container.children.children) {
|
||||
if (child.classList.contains("more-nodes")) {
|
||||
found += "*more*";
|
||||
} else {
|
||||
found += child.container.node.getAttribute("id");
|
||||
}
|
||||
}
|
||||
is(found, expected, "Got the expected children.");
|
||||
}
|
||||
|
||||
function forceReload(inspector) {
|
||||
let container = getContainerForRawNode("body", inspector);
|
||||
container.childrenDirty = true;
|
||||
}
|
||||
|
||||
function clickShowMoreNodes(inspector) {
|
||||
let container = getContainerForRawNode("body", inspector);
|
||||
let button = container.elt.querySelector("button");
|
||||
let win = button.ownerDocument.defaultView;
|
||||
EventUtils.sendMouseEvent({type: "click"}, button, win);
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
/* vim: set ts=2 et sw=2 tw=80: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
// Tests that the markup view loads only as many nodes as specified
|
||||
// by the devtools.markup.pagesize preference and that pressing the "show all nodes"
|
||||
// actually shows the nodes
|
||||
|
||||
const TEST_URL = TEST_URL_ROOT + "doc_markup_pagesize_02.html";
|
||||
|
||||
// Make sure nodes are hidden when there are more than 5 in a row
|
||||
Services.prefs.setIntPref("devtools.markup.pagesize", 5);
|
||||
|
||||
let test = asyncTest(function*() {
|
||||
let {inspector} = yield addTab(TEST_URL).then(openInspector);
|
||||
|
||||
info("Selecting the UL node");
|
||||
yield clickContainer("ul", inspector);
|
||||
info("Reloading the page with the UL node selected will expand its children");
|
||||
yield reloadPage(inspector);
|
||||
yield inspector.markup._waitForChildren();
|
||||
|
||||
info("Click on the 'show all nodes' button in the UL's list of children");
|
||||
yield showAllNodes(inspector);
|
||||
|
||||
assertAllNodesAreVisible(inspector);
|
||||
});
|
||||
|
||||
function showAllNodes(inspector) {
|
||||
let container = getContainerForRawNode("ul", inspector);
|
||||
let button = container.elt.querySelector("button");
|
||||
ok(button, "All nodes button is here");
|
||||
let win = button.ownerDocument.defaultView;
|
||||
|
||||
EventUtils.sendMouseEvent({type: "click"}, button, win);
|
||||
return inspector.markup._waitForChildren();
|
||||
}
|
||||
|
||||
function assertAllNodesAreVisible(inspector) {
|
||||
let ul = getNode("ul");
|
||||
let container = getContainerForRawNode(ul, inspector);
|
||||
ok(!container.elt.querySelector("button"), "All nodes button isn't here anymore");
|
||||
is(container.children.childNodes.length, ul.children.length);
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
/* vim: set ts=2 et sw=2 tw=80: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
// Test that searching for nodes using the selector-search input expands and
|
||||
// selects the right nodes in the markup-view, even when those nodes are deeply
|
||||
// nested (and therefore not attached yet when the markup-view is initialized).
|
||||
|
||||
const TEST_URL = TEST_URL_ROOT + "doc_markup_search.html";
|
||||
|
||||
let test = asyncTest(function*() {
|
||||
let {inspector, toolbox} = yield addTab(TEST_URL).then(openInspector);
|
||||
|
||||
ok(!getContainerForRawNode("em", inspector),
|
||||
"The <em> tag isn't present yet in the markup-view");
|
||||
|
||||
// Searching for the innermost element first makes sure that the inspector
|
||||
// back-end is able to attach the resulting node to the tree it knows at the
|
||||
// moment. When the inspector is started, the <body> is the default selected
|
||||
// node, and only the parents up to the ROOT are known, and its direct children
|
||||
info("searching for the innermost child: <em>");
|
||||
let updated = inspector.once("inspector-updated");
|
||||
searchUsingSelectorSearch("em", inspector);
|
||||
yield updated;
|
||||
|
||||
ok(getContainerForRawNode("em", inspector),
|
||||
"The <em> tag is now imported in the markup-view");
|
||||
is(inspector.selection.node, getNode("em"),
|
||||
"The <em> tag is the currently selected node");
|
||||
|
||||
info("searching for other nodes too");
|
||||
for (let node of ["span", "li", "ul"]) {
|
||||
let updated = inspector.once("inspector-updated");
|
||||
searchUsingSelectorSearch(node, inspector);
|
||||
yield updated;
|
||||
is(inspector.selection.node, getNode(node),
|
||||
"The <" + node + "> tag is the currently selected node");
|
||||
}
|
||||
});
|
|
@ -0,0 +1,66 @@
|
|||
/* 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 editing various markup-containers' attribute fields
|
||||
|
||||
const TEST_URL = TEST_URL_ROOT + "doc_markup_edit.html";
|
||||
let TEST_DATA = [{
|
||||
desc: "Change an attribute",
|
||||
node: "#node1",
|
||||
originalAttributes: {
|
||||
id: "node1",
|
||||
class: "node1"
|
||||
},
|
||||
name: "class",
|
||||
value: 'class="changednode1"',
|
||||
expectedAttributes: {
|
||||
id: "node1",
|
||||
class: "changednode1"
|
||||
}
|
||||
}, {
|
||||
desc: 'Try changing an attribute to a quote (") - this should result ' +
|
||||
'in it being set to an empty string',
|
||||
node: "#node22",
|
||||
originalAttributes: {
|
||||
id: "node22",
|
||||
class: "unchanged"
|
||||
},
|
||||
name: "class",
|
||||
value: 'class="""',
|
||||
expectedAttributes: {
|
||||
id: "node22",
|
||||
class: ""
|
||||
}
|
||||
}, {
|
||||
desc: "Remove an attribute",
|
||||
node: "#node4",
|
||||
originalAttributes: {
|
||||
id: "node4",
|
||||
class: "node4"
|
||||
},
|
||||
name: "class",
|
||||
value: "",
|
||||
expectedAttributes: {
|
||||
id: "node4"
|
||||
}
|
||||
}, {
|
||||
desc: "Try add attributes by adding to an existing attribute's entry",
|
||||
node: "#node24",
|
||||
originalAttributes: {
|
||||
id: "node24"
|
||||
},
|
||||
name: "id",
|
||||
value: 'id="node24" class="""',
|
||||
expectedAttributes: {
|
||||
id: "node24",
|
||||
class: ""
|
||||
}
|
||||
}];
|
||||
|
||||
let test = asyncTest(function*() {
|
||||
let {inspector} = yield addTab(TEST_URL).then(openInspector);
|
||||
yield runEditAttributesTests(TEST_DATA, inspector);
|
||||
});
|
|
@ -0,0 +1,46 @@
|
|||
/* 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";
|
||||
|
||||
// Tests that an existing attribute can be modified
|
||||
|
||||
const TEST_URL = "data:text/html,<div id='test-div'>Test modifying my ID attribute</div>";
|
||||
|
||||
let test = asyncTest(function*() {
|
||||
info("Opening the inspector on the test page");
|
||||
let {toolbox, inspector} = yield addTab(TEST_URL).then(openInspector);
|
||||
|
||||
info("Selecting the test node");
|
||||
let node = content.document.getElementById("test-div");
|
||||
yield selectNode(node, inspector);
|
||||
|
||||
info("Verify attributes, only ID should be there for now");
|
||||
assertAttributes(node, {
|
||||
id: "test-div"
|
||||
});
|
||||
|
||||
info("Focus the ID attribute and change its content");
|
||||
let editor = getContainerForRawNode(node, inspector).editor;
|
||||
let attr = editor.attrs["id"].querySelector(".editable");
|
||||
let mutated = inspector.once("markupmutation");
|
||||
setEditableFieldValue(attr,
|
||||
attr.textContent + ' class="newclass" style="color:green"', inspector);
|
||||
yield mutated;
|
||||
|
||||
info("Verify attributes, should have ID, class and style");
|
||||
assertAttributes(node, {
|
||||
id: "test-div",
|
||||
class: "newclass",
|
||||
style: "color:green"
|
||||
});
|
||||
|
||||
info("Trying to undo the change");
|
||||
yield undoChange(inspector);
|
||||
assertAttributes(node, {
|
||||
id: "test-div"
|
||||
});
|
||||
|
||||
yield inspector.once("inspector-updated");
|
||||
});
|
|
@ -0,0 +1,39 @@
|
|||
/* 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";
|
||||
|
||||
// Tests that a node's tagname can be edited in the markup-view
|
||||
|
||||
const TEST_URL = "data:text/html,<div id='retag-me'><div id='retag-me-2'></div></div>";
|
||||
|
||||
let test = asyncTest(function*() {
|
||||
let {toolbox, inspector} = yield addTab(TEST_URL).then(openInspector);
|
||||
|
||||
yield inspector.markup.expandAll();
|
||||
|
||||
info("Selecting the test node");
|
||||
let node = content.document.getElementById("retag-me");
|
||||
let child = content.document.querySelector("#retag-me-2");
|
||||
yield selectNode(node, inspector);
|
||||
|
||||
let container = getContainerForRawNode(node, inspector);
|
||||
is(node.tagName, "DIV", "We've got #retag-me element, it's a DIV");
|
||||
ok(container.expanded, "It is expanded");
|
||||
is(child.parentNode, node, "Child #retag-me-2 is inside #retag-me");
|
||||
|
||||
info("Changing the tagname");
|
||||
let mutated = inspector.once("markupmutation");
|
||||
let tagEditor = container.editor.tag;
|
||||
setEditableFieldValue(tagEditor, "p", inspector);
|
||||
yield mutated;
|
||||
|
||||
info("Checking that the tagname change was done");
|
||||
let node = content.document.getElementById("retag-me");
|
||||
let container = getContainerForRawNode(node, inspector);
|
||||
is(node.tagName, "P", "We've got #retag-me, it should now be a P");
|
||||
ok(container.expanded, "It is still expanded");
|
||||
ok(container.selected, "It is still selected");
|
||||
is(child.parentNode, node, "Child #retag-me-2 is still inside #retag-me");
|
||||
});
|
|
@ -0,0 +1,30 @@
|
|||
/* 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";
|
||||
|
||||
// Tests that a node can be deleted from the markup-view with the delete key
|
||||
|
||||
const TEST_URL = "data:text/html,<div id='delete-me'></div>";
|
||||
|
||||
let test = asyncTest(function*() {
|
||||
let {toolbox, inspector} = yield addTab(TEST_URL).then(openInspector);
|
||||
|
||||
info("Selecting the test node by clicking on it to make sure it receives focus");
|
||||
let node = content.document.getElementById("delete-me");
|
||||
yield clickContainer(node, inspector);
|
||||
|
||||
info("Deleting the element with the keyboard");
|
||||
let mutated = inspector.once("markupmutation");
|
||||
EventUtils.sendKey("delete", inspector.panelWin);
|
||||
yield mutated;
|
||||
|
||||
info("Checking that it's gone, baby gone!");
|
||||
ok(!content.document.getElementById("delete-me"), "The test node does not exist");
|
||||
|
||||
yield undoChange(inspector);
|
||||
ok(content.document.getElementById("delete-me"), "The test node is back!");
|
||||
|
||||
yield inspector.once("inspector-updated");
|
||||
});
|
|
@ -0,0 +1,76 @@
|
|||
/* Any copyright", " is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Tests that adding various types of attributes to nodes in the markup-view
|
||||
* works as expected. Also checks that the changes are properly undoable and
|
||||
* redoable. For each step in the test, we:
|
||||
* - Create a new DIV
|
||||
* - Make the change, check that the change was made as we expect
|
||||
* - Undo the change, check that the node is back in its original state
|
||||
* - Redo the change, check that the node change was made again correctly.
|
||||
*/
|
||||
|
||||
let TEST_URL = "data:text/html,<div>markup-view attributes addition test</div>";
|
||||
let TEST_DATA = [{
|
||||
desc: "Add an attribute value without closing \"",
|
||||
text: 'style="display: block;',
|
||||
expectedAttributes: {
|
||||
style: "display: block;"
|
||||
}
|
||||
}, {
|
||||
desc: "Add an attribute value without closing '",
|
||||
text: "style='display: inline;",
|
||||
expectedAttributes: {
|
||||
style: "display: inline;"
|
||||
}
|
||||
}, {
|
||||
desc: "Add an attribute wrapped with with double quotes double quote in it",
|
||||
text: 'style="display: "inline',
|
||||
expectedAttributes: {
|
||||
style: "display: ",
|
||||
inline: ""
|
||||
}
|
||||
}, {
|
||||
desc: "Add an attribute wrapped with single quotes with single quote in it",
|
||||
text: "style='display: 'inline",
|
||||
expectedAttributes: {
|
||||
style: "display: ",
|
||||
inline: ""
|
||||
}
|
||||
}, {
|
||||
desc: "Add an attribute with no value",
|
||||
text: "disabled",
|
||||
expectedAttributes: {
|
||||
disabled: ""
|
||||
}
|
||||
}, {
|
||||
desc: "Add multiple attributes with no value",
|
||||
text: "disabled autofocus",
|
||||
expectedAttributes: {
|
||||
disabled: "",
|
||||
autofocus: ""
|
||||
}
|
||||
}, {
|
||||
desc: "Add multiple attributes with no value, and some with value",
|
||||
text: "disabled name='name' data-test='test' autofocus",
|
||||
expectedAttributes: {
|
||||
disabled: "",
|
||||
autofocus: "",
|
||||
name: "name",
|
||||
'data-test': "test"
|
||||
}
|
||||
}, {
|
||||
desc: "Add attribute with xmlns",
|
||||
text: "xmlns:edi='http://ecommerce.example.org/schema'",
|
||||
expectedAttributes: {
|
||||
'xmlns:edi': "http://ecommerce.example.org/schema"
|
||||
}
|
||||
}];
|
||||
|
||||
let test = asyncTest(function*() {
|
||||
let {inspector} = yield addTab(TEST_URL).then(openInspector);
|
||||
yield runAddAttributesTests(TEST_DATA, "div", inspector)
|
||||
});
|
|
@ -0,0 +1,83 @@
|
|||
/* Any copyright", " is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Tests that adding various types of attributes to nodes in the markup-view
|
||||
* works as expected. Also checks that the changes are properly undoable and
|
||||
* redoable. For each step in the test, we:
|
||||
* - Create a new DIV
|
||||
* - Make the change, check that the change was made as we expect
|
||||
* - Undo the change, check that the node is back in its original state
|
||||
* - Redo the change, check that the node change was made again correctly.
|
||||
*/
|
||||
|
||||
let TEST_URL = "data:text/html,<div>markup-view attributes addition test</div>";
|
||||
let TEST_DATA = [{
|
||||
desc: "Mixed single and double quotes",
|
||||
text: "name=\"hi\" maxlength='not a number'",
|
||||
expectedAttributes: {
|
||||
maxlength: "not a number",
|
||||
name: "hi"
|
||||
}
|
||||
}, {
|
||||
desc: "Invalid attribute name",
|
||||
text: "x='y' <why-would-you-do-this>=\"???\"",
|
||||
expectedAttributes: {
|
||||
x: "y"
|
||||
}
|
||||
}, {
|
||||
desc: "Double quote wrapped in single quotes",
|
||||
text: "x='h\"i'",
|
||||
expectedAttributes: {
|
||||
x: "h\"i"
|
||||
}
|
||||
}, {
|
||||
desc: "Single quote wrapped in double quotes",
|
||||
text: "x=\"h'i\"",
|
||||
expectedAttributes: {
|
||||
x: "h'i"
|
||||
}
|
||||
}, {
|
||||
desc: "No quote wrapping",
|
||||
text: "a=b x=y data-test=Some spaced data",
|
||||
expectedAttributes: {
|
||||
a: "b",
|
||||
x: "y",
|
||||
"data-test": "Some",
|
||||
spaced: "",
|
||||
data: ""
|
||||
}
|
||||
}, {
|
||||
desc: "Duplicate Attributes",
|
||||
text: "a=b a='c' a=\"d\"",
|
||||
expectedAttributes: {
|
||||
a: "b"
|
||||
}
|
||||
}, {
|
||||
desc: "Inline styles",
|
||||
text: "style=\"font-family: 'Lucida Grande', sans-serif; font-size: 75%;\"",
|
||||
expectedAttributes: {
|
||||
style: "font-family: 'Lucida Grande', sans-serif; font-size: 75%;"
|
||||
}
|
||||
}, {
|
||||
desc: "Object attribute names",
|
||||
text: "toString=\"true\" hasOwnProperty=\"false\"",
|
||||
expectedAttributes: {
|
||||
toString: "true",
|
||||
hasOwnProperty: "false"
|
||||
}
|
||||
}, {
|
||||
desc: "Add event handlers",
|
||||
text: "onclick=\"javascript: throw new Error('wont fire');\" onload=\"alert('here');\"",
|
||||
expectedAttributes: {
|
||||
onclick: "javascript: throw new Error('wont fire');",
|
||||
onload: "alert('here');"
|
||||
}
|
||||
}];
|
||||
|
||||
let test = asyncTest(function*() {
|
||||
let {inspector} = yield addTab(TEST_URL).then(openInspector);
|
||||
yield runAddAttributesTests(TEST_DATA, "div", inspector)
|
||||
});
|
|
@ -0,0 +1,77 @@
|
|||
/* Any copyright", " is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
// One more test testing various add-attributes configurations
|
||||
// Some of the test data below asserts that long attributes get collapsed
|
||||
|
||||
const LONG_ATTRIBUTE = "ABCDEFGHIJKLMNOPQRSTUVWXYZ-ABCDEFGHIJKLMNOPQRSTUVWXYZ-ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ-ABCDEFGHIJKLMNOPQRSTUVWXYZ-ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
||||
const LONG_ATTRIBUTE_COLLAPSED = "ABCDEFGHIJKLMNOPQRSTUVWXYZ-ABCDEFGHIJKLMNOPQRSTUVWXYZ-ABCDEF\u2026UVWXYZ-ABCDEFGHIJKLMNOPQRSTUVWXYZ-ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
||||
const DATA_URL_INLINE_STYLE='color: red; background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAABlBMVEUAAAD///+l2Z/dAAAAM0lEQVR4nGP4/5/h/1+G/58ZDrAz3D/McH8yw83NDDeNGe4Ug9C9zwz3gVLMDA/A6P9/AFGGFyjOXZtQAAAAAElFTkSuQmCC");';
|
||||
const DATA_URL_INLINE_STYLE_COLLAPSED='color: red; background: url("data:image/png;base64,iVBORw0KG\u2026NDDeNGe4Ug9C9zwz3gVLMDA/A6P9/AFGGFyjOXZtQAAAAAElFTkSuQmCC");';
|
||||
const DATA_URL_ATTRIBUTE = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAABlBMVEUAAAD///+l2Z/dAAAAM0lEQVR4nGP4/5/h/1+G/58ZDrAz3D/McH8yw83NDDeNGe4Ug9C9zwz3gVLMDA/A6P9/AFGGFyjOXZtQAAAAAElFTkSuQmCC";
|
||||
const DATA_URL_ATTRIBUTE_COLLAPSED = "data:image/png;base64,iVBORw0K\u20269/AFGGFyjOXZtQAAAAAElFTkSuQmCC";
|
||||
|
||||
let TEST_URL = "data:text/html,<div>markup-view attributes addition test</div>";
|
||||
let TEST_DATA = [{
|
||||
desc: "Add an attribute value containing < > ü \" & '",
|
||||
text: 'src="somefile.html?param1=<a>¶m2=ü¶m3=\'"\'"',
|
||||
expectedAttributes: {
|
||||
src: "somefile.html?param1=<a>¶m2=\xfc¶m3='\"'"
|
||||
}
|
||||
}, {
|
||||
desc: "Add an attribute by clicking the empty space after a node",
|
||||
text: 'class="newclass" style="color:green"',
|
||||
expectedAttributes: {
|
||||
class: "newclass",
|
||||
style: "color:green"
|
||||
}
|
||||
}, {
|
||||
desc: 'Try add an attribute containing a quote (") attribute by ' +
|
||||
'clicking the empty space after a node - this should result ' +
|
||||
'in it being set to an empty string',
|
||||
text: 'class="newclass" style="""',
|
||||
expectedAttributes: {
|
||||
class: "newclass",
|
||||
style: ""
|
||||
}
|
||||
}, {
|
||||
desc: "Try to add long data URL to make sure it is collapsed in attribute editor.",
|
||||
text: "style='"+DATA_URL_INLINE_STYLE+"'",
|
||||
expectedAttributes: {
|
||||
'style': DATA_URL_INLINE_STYLE
|
||||
},
|
||||
validate: (element, container, inspector) => {
|
||||
let editor = container.editor;
|
||||
let visibleAttrText = editor.attrs["style"].querySelector(".attr-value").textContent;
|
||||
is (visibleAttrText, DATA_URL_INLINE_STYLE_COLLAPSED);
|
||||
}
|
||||
}, {
|
||||
desc: "Try to add long attribute to make sure it is collapsed in attribute editor.",
|
||||
text: 'data-long="'+LONG_ATTRIBUTE+'"',
|
||||
expectedAttributes: {
|
||||
'data-long':LONG_ATTRIBUTE
|
||||
},
|
||||
validate: (element, container, inspector) => {
|
||||
let editor = container.editor;
|
||||
let visibleAttrText = editor.attrs["data-long"].querySelector(".attr-value").textContent;
|
||||
is (visibleAttrText, LONG_ATTRIBUTE_COLLAPSED)
|
||||
}
|
||||
}, {
|
||||
desc: "Try to add long data URL to make sure it is collapsed in attribute editor.",
|
||||
text: 'src="'+DATA_URL_ATTRIBUTE+'"',
|
||||
expectedAttributes: {
|
||||
"src": DATA_URL_ATTRIBUTE
|
||||
},
|
||||
validate: (element, container, inspector) => {
|
||||
let editor = container.editor;
|
||||
let visibleAttrText = editor.attrs["src"].querySelector(".attr-value").textContent;
|
||||
is (visibleAttrText, DATA_URL_ATTRIBUTE_COLLAPSED);
|
||||
}
|
||||
}];
|
||||
|
||||
let test = asyncTest(function*() {
|
||||
let {inspector} = yield addTab(TEST_URL).then(openInspector);
|
||||
yield runAddAttributesTests(TEST_DATA, "div", inspector)
|
||||
});
|
|
@ -0,0 +1,130 @@
|
|||
/* 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 editing various markup-containers' attribute fields, in particular
|
||||
// attributes with long values and quotes
|
||||
|
||||
const TEST_URL = TEST_URL_ROOT + "doc_markup_edit.html";
|
||||
const LONG_ATTRIBUTE = "ABCDEFGHIJKLMNOPQRSTUVWXYZ-ABCDEFGHIJKLMNOPQRSTUVWXYZ-ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ-ABCDEFGHIJKLMNOPQRSTUVWXYZ-ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
||||
const LONG_ATTRIBUTE_COLLAPSED = "ABCDEFGHIJKLMNOPQRSTUVWXYZ-ABCDEFGHIJKLMNOPQRSTUVWXYZ-ABCDEF\u2026UVWXYZ-ABCDEFGHIJKLMNOPQRSTUVWXYZ-ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
||||
|
||||
let test = asyncTest(function*() {
|
||||
let {inspector} = yield addTab(TEST_URL).then(openInspector);
|
||||
|
||||
yield inspector.markup.expandAll();
|
||||
yield testCollapsedLongAttribute(inspector);
|
||||
yield testModifyInlineStyleWithQuotes(inspector);
|
||||
yield testEditingAttributeWithMixedQuotes(inspector);
|
||||
});
|
||||
|
||||
function* testCollapsedLongAttribute(inspector) {
|
||||
info("Try to modify the collapsed long attribute, making sure it expands.");
|
||||
|
||||
info("Adding test attributes to the node");
|
||||
let onMutated = inspector.once("markupmutation");
|
||||
let node = getNode("#node24");
|
||||
node.setAttribute("class", "");
|
||||
node.setAttribute("data-long", LONG_ATTRIBUTE);
|
||||
yield onMutated;
|
||||
|
||||
assertAttributes("#node24", {
|
||||
id: "node24",
|
||||
"class": "",
|
||||
"data-long": LONG_ATTRIBUTE
|
||||
});
|
||||
|
||||
let editor = getContainerForRawNode("#node24", inspector).editor;
|
||||
let attr = editor.attrs["data-long"].querySelector(".editable");
|
||||
|
||||
// Check to make sure it has expanded after focus
|
||||
attr.focus();
|
||||
EventUtils.sendKey("return", inspector.panelWin);
|
||||
let input = inplaceEditor(attr).input;
|
||||
is (input.value, 'data-long="' + LONG_ATTRIBUTE + '"');
|
||||
EventUtils.sendKey("escape", inspector.panelWin);
|
||||
|
||||
setEditableFieldValue(attr, input.value + ' data-short="ABC"', inspector);
|
||||
yield inspector.once("markupmutation");
|
||||
|
||||
let visibleAttrText = editor.attrs["data-long"].querySelector(".attr-value").textContent;
|
||||
is (visibleAttrText, LONG_ATTRIBUTE_COLLAPSED)
|
||||
|
||||
assertAttributes("#node24", {
|
||||
id: "node24",
|
||||
class: "",
|
||||
'data-long': LONG_ATTRIBUTE,
|
||||
"data-short": "ABC"
|
||||
});
|
||||
}
|
||||
|
||||
function* testModifyInlineStyleWithQuotes(inspector) {
|
||||
info("Modify inline style containing \"");
|
||||
|
||||
assertAttributes("#node26", {
|
||||
id: "node26",
|
||||
style: 'background-image: url("moz-page-thumb://thumbnail?url=http%3A%2F%2Fwww.mozilla.org%2F");'
|
||||
});
|
||||
|
||||
let onMutated = inspector.once("markupmutation");
|
||||
let editor = getContainerForRawNode("#node26", inspector).editor;
|
||||
let attr = editor.attrs["style"].querySelector(".editable");
|
||||
|
||||
attr.focus();
|
||||
EventUtils.sendKey("return", inspector.panelWin);
|
||||
|
||||
let input = inplaceEditor(attr).input;
|
||||
let value = input.value;
|
||||
|
||||
is (value,
|
||||
"style='background-image: url(\"moz-page-thumb://thumbnail?url=http%3A%2F%2Fwww.mozilla.org%2F\");'",
|
||||
"Value contains actual double quotes"
|
||||
);
|
||||
|
||||
value = value.replace(/mozilla\.org/, "mozilla.com");
|
||||
input.value = value;
|
||||
|
||||
EventUtils.sendKey("return", inspector.panelWin);
|
||||
|
||||
yield onMutated;
|
||||
|
||||
assertAttributes("#node26", {
|
||||
id: "node26",
|
||||
style: 'background-image: url("moz-page-thumb://thumbnail?url=http%3A%2F%2Fwww.mozilla.com%2F");'
|
||||
});
|
||||
}
|
||||
|
||||
function* testEditingAttributeWithMixedQuotes(inspector) {
|
||||
info("Modify class containing \" and \'");
|
||||
|
||||
assertAttributes("#node27", {
|
||||
"id": "node27",
|
||||
"class": 'Double " and single \''
|
||||
});
|
||||
|
||||
let onMutated = inspector.once("markupmutation");
|
||||
let editor = getContainerForRawNode("#node27", inspector).editor;
|
||||
let attr = editor.attrs["class"].querySelector(".editable");
|
||||
|
||||
attr.focus();
|
||||
EventUtils.sendKey("return", inspector.panelWin);
|
||||
|
||||
let input = inplaceEditor(attr).input;
|
||||
let value = input.value;
|
||||
|
||||
is (value, "class=\"Double " and single '\"", "Value contains "");
|
||||
|
||||
value = value.replace(/Double/, """).replace(/single/, "'");
|
||||
input.value = value;
|
||||
|
||||
EventUtils.sendKey("return", inspector.panelWin);
|
||||
|
||||
yield onMutated;
|
||||
|
||||
assertAttributes("#node27", {
|
||||
id: "node27",
|
||||
class: '" " and \' \''
|
||||
});
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
/* 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 editing a node's text content
|
||||
|
||||
const TEST_URL = TEST_URL_ROOT + "doc_markup_edit.html";
|
||||
|
||||
let test = asyncTest(function*() {
|
||||
let {inspector} = yield addTab(TEST_URL).then(openInspector);
|
||||
|
||||
info("Expanding all nodes");
|
||||
yield inspector.markup.expandAll();
|
||||
|
||||
let node = getNode(".node6").firstChild;
|
||||
is(node.nodeValue, "line6", "The test node's text content is correct");
|
||||
|
||||
info("Changing the text content");
|
||||
|
||||
info("Listening to the markupmutation event");
|
||||
let onMutated = inspector.once("markupmutation");
|
||||
let editor = getContainerForRawNode(node, inspector).editor;
|
||||
let field = editor.elt.querySelector("pre");
|
||||
setEditableFieldValue(field, "New text", inspector);
|
||||
yield onMutated;
|
||||
|
||||
is(node.nodeValue, "New text", "Test test node's text content has changed");
|
||||
|
||||
yield inspector.once("inspector-updated");
|
||||
});
|
До Ширина: | Высота: | Размер: 1.1 KiB После Ширина: | Высота: | Размер: 1.1 KiB |
|
@ -3,62 +3,100 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
const Cu = Components.utils;
|
||||
|
||||
let {devtools} = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
|
||||
let TargetFactory = devtools.TargetFactory;
|
||||
let {console} = Cu.import("resource://gre/modules/devtools/Console.jsm", {});
|
||||
let promise = devtools.require("sdk/core/promise");
|
||||
let {getInplaceEditorForSpan: inplaceEditor} = devtools.require("devtools/shared/inplace-editor");
|
||||
|
||||
// All test are asynchronous
|
||||
waitForExplicitFinish();
|
||||
|
||||
//Services.prefs.setBoolPref("devtools.dump.emit", true);
|
||||
|
||||
// Set the testing flag on gDevTools and reset it when the test ends
|
||||
gDevTools.testing = true;
|
||||
SimpleTest.registerCleanupFunction(() => {
|
||||
gDevTools.testing = false;
|
||||
});
|
||||
registerCleanupFunction(() => gDevTools.testing = false);
|
||||
|
||||
// Clear preferences that may be set during the course of tests.
|
||||
function clearUserPrefs() {
|
||||
registerCleanupFunction(() => {
|
||||
Services.prefs.clearUserPref("devtools.inspector.htmlPanelOpen");
|
||||
Services.prefs.clearUserPref("devtools.inspector.sidebarOpen");
|
||||
Services.prefs.clearUserPref("devtools.inspector.activeSidebar");
|
||||
Services.prefs.clearUserPref("devtools.dump.emit");
|
||||
}
|
||||
Services.prefs.clearUserPref("devtools.markup.pagesize");
|
||||
});
|
||||
|
||||
registerCleanupFunction(clearUserPrefs);
|
||||
// Auto close the toolbox and close the test tabs when the test ends
|
||||
registerCleanupFunction(() => {
|
||||
try {
|
||||
let target = TargetFactory.forTab(gBrowser.selectedTab);
|
||||
gDevTools.closeToolbox(target);
|
||||
} catch (ex) {
|
||||
dump(ex);
|
||||
}
|
||||
while (gBrowser.tabs.length > 1) {
|
||||
gBrowser.removeCurrentTab();
|
||||
}
|
||||
});
|
||||
|
||||
const TEST_URL_ROOT = "http://mochi.test:8888/browser/browser/devtools/markupview/test/";
|
||||
|
||||
/**
|
||||
* Define an async test based on a generator function
|
||||
*/
|
||||
function asyncTest(generator) {
|
||||
return () => Task.spawn(generator).then(null, ok.bind(null, false)).then(finish);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new test tab in the browser and load the given url.
|
||||
* @param {String} url The url to be loaded in the new tab
|
||||
* @return a promise that resolves when the url is loaded
|
||||
* @return a promise that resolves to the tab object when the url is loaded
|
||||
*/
|
||||
function addTab(url) {
|
||||
info("Adding a new tab with URL: '" + url + "'");
|
||||
let def = promise.defer();
|
||||
|
||||
gBrowser.selectedTab = gBrowser.addTab();
|
||||
let tab = gBrowser.selectedTab = gBrowser.addTab();
|
||||
gBrowser.selectedBrowser.addEventListener("load", function onload() {
|
||||
gBrowser.selectedBrowser.removeEventListener("load", onload, true);
|
||||
info("URL " + url + " loading complete into new test tab");
|
||||
waitForFocus(def.resolve, content);
|
||||
info("URL '" + url + "' loading complete");
|
||||
waitForFocus(() => {
|
||||
def.resolve(tab);
|
||||
}, content);
|
||||
}, true);
|
||||
content.location = url;
|
||||
|
||||
return def.promise;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reload the current page
|
||||
* @return a promise that resolves when the inspector has emitted the event
|
||||
* new-root
|
||||
*/
|
||||
function reloadPage(inspector) {
|
||||
info("Reloading the page");
|
||||
let newRoot = inspector.once("new-root");
|
||||
content.location.reload();
|
||||
return newRoot;
|
||||
}
|
||||
|
||||
/**
|
||||
* Open the toolbox, with the inspector tool visible.
|
||||
* @return a promise that resolves when the inspector is ready
|
||||
*/
|
||||
function openInspector() {
|
||||
info("Opening the inspector panel");
|
||||
let def = promise.defer();
|
||||
|
||||
let target = TargetFactory.forTab(gBrowser.selectedTab);
|
||||
gDevTools.showToolbox(target, "inspector").then(function(toolbox) {
|
||||
info("Toolbox open");
|
||||
info("The toolbox is open");
|
||||
let inspector = toolbox.getCurrentPanel();
|
||||
inspector.once("inspector-updated", () => {
|
||||
info("Inspector panel active and ready");
|
||||
info("The inspector panel is active and ready");
|
||||
def.resolve({toolbox: toolbox, inspector: inspector});
|
||||
});
|
||||
}).then(null, console.error);
|
||||
|
@ -66,19 +104,6 @@ function openInspector() {
|
|||
return def.promise;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the MarkupContainer object instance that corresponds to the given
|
||||
* HTML node
|
||||
* @param {MarkupView} markupView The instance of MarkupView currently loaded into the inspector panel
|
||||
* @param {DOMNode} rawNode The DOM node for which the container is required
|
||||
* @return {MarkupContainer}
|
||||
*/
|
||||
function getContainerForRawNode(markupView, rawNode) {
|
||||
let front = markupView.walker.frontForRawNode(rawNode);
|
||||
let container = markupView.getContainer(front);
|
||||
return container;
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple DOM node accesor function that takes either a node or a string css
|
||||
* selector as argument and returns the corresponding node
|
||||
|
@ -86,14 +111,10 @@ function getContainerForRawNode(markupView, rawNode) {
|
|||
* @return {DOMNode}
|
||||
*/
|
||||
function getNode(nodeOrSelector) {
|
||||
let node = nodeOrSelector;
|
||||
|
||||
if (typeof nodeOrSelector === "string") {
|
||||
node = content.document.querySelector(nodeOrSelector);
|
||||
ok(node, "A node was found for selector " + nodeOrSelector);
|
||||
}
|
||||
|
||||
return node;
|
||||
info("Getting the node for '" + nodeOrSelector + "'");
|
||||
return typeof nodeOrSelector === "string" ?
|
||||
content.document.querySelector(nodeOrSelector) :
|
||||
nodeOrSelector;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -106,13 +127,29 @@ function getNode(nodeOrSelector) {
|
|||
* node
|
||||
*/
|
||||
function selectNode(nodeOrSelector, inspector, reason="test") {
|
||||
info("Selecting the node " + nodeOrSelector);
|
||||
info("Selecting the node for '" + nodeOrSelector + "'");
|
||||
let node = getNode(nodeOrSelector);
|
||||
let updated = inspector.once("inspector-updated");
|
||||
inspector.selection.setNode(node, reason);
|
||||
return updated;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the MarkupContainer object instance that corresponds to the given
|
||||
* HTML node
|
||||
* @param {DOMNode|String} nodeOrSelector The DOM node for which the
|
||||
* container is required
|
||||
* @param {InspectorPanel} inspector The instance of InspectorPanel currently
|
||||
* loaded in the toolbox
|
||||
* @return {MarkupContainer}
|
||||
*/
|
||||
function getContainerForRawNode(nodeOrSelector, {markup}) {
|
||||
let front = markup.walker.frontForRawNode(getNode(nodeOrSelector));
|
||||
let container = markup.getContainer(front);
|
||||
info("Markup-container object for " + nodeOrSelector + " " + container);
|
||||
return container;
|
||||
}
|
||||
|
||||
/**
|
||||
* Simulate a mouse-over on the markup-container (a line in the markup-view)
|
||||
* that corresponds to the node or selector passed.
|
||||
|
@ -124,7 +161,7 @@ function selectNode(nodeOrSelector, inspector, reason="test") {
|
|||
function hoverContainer(nodeOrSelector, inspector) {
|
||||
info("Hovering over the markup-container for node " + nodeOrSelector);
|
||||
let highlit = inspector.toolbox.once("node-highlight");
|
||||
let container = getContainerForRawNode(inspector.markup, getNode(nodeOrSelector));
|
||||
let container = getContainerForRawNode(getNode(nodeOrSelector), inspector);
|
||||
EventUtils.synthesizeMouseAtCenter(container.tagLine, {type: "mousemove"},
|
||||
inspector.markup.doc.defaultView);
|
||||
return highlit;
|
||||
|
@ -140,7 +177,7 @@ function hoverContainer(nodeOrSelector, inspector) {
|
|||
function clickContainer(nodeOrSelector, inspector) {
|
||||
info("Clicking on the markup-container for node " + nodeOrSelector);
|
||||
let updated = inspector.once("inspector-updated");
|
||||
let container = getContainerForRawNode(inspector.markup, getNode(nodeOrSelector));
|
||||
let container = getContainerForRawNode(getNode(nodeOrSelector), inspector);
|
||||
EventUtils.synthesizeMouseAtCenter(container.tagLine, {type: "mousedown"},
|
||||
inspector.markup.doc.defaultView);
|
||||
EventUtils.synthesizeMouseAtCenter(container.tagLine, {type: "mouseup"},
|
||||
|
@ -179,9 +216,11 @@ function mouseLeaveMarkupView(inspector) {
|
|||
|
||||
/**
|
||||
* Focus a given editable element, enter edit mode, set value, and commit
|
||||
* @param {DOMNode} field The element that gets editable after receiving focus and <ENTER> keypress
|
||||
* @param {DOMNode} field The element that gets editable after receiving focus
|
||||
* and <ENTER> keypress
|
||||
* @param {String} value The string value to be set into the edited field
|
||||
* @param {InspectorPanel} inspector The instance of InspectorPanel currently loaded in the toolbox
|
||||
* @param {InspectorPanel} inspector The instance of InspectorPanel currently
|
||||
* loaded in the toolbox
|
||||
*/
|
||||
function setEditableFieldValue(field, value, inspector) {
|
||||
field.focus();
|
||||
|
@ -192,21 +231,45 @@ function setEditableFieldValue(field, value, inspector) {
|
|||
EventUtils.sendKey("return", inspector.panelWin);
|
||||
}
|
||||
|
||||
/**
|
||||
* Focus the new-attribute inplace-editor field of the nodeOrSelector's markup
|
||||
* container, and enters the given text, then wait for it to be applied and the
|
||||
* for the node to mutates (when new attribute(s) is(are) created)
|
||||
* @param {DOMNode|String} nodeOrSelector The node or node selector to edit.
|
||||
* @param {String} text The new attribute text to be entered (e.g. "id='test'")
|
||||
* @param {InspectorPanel} inspector The instance of InspectorPanel currently
|
||||
* loaded in the toolbox
|
||||
* @return a promise that resolves when the node has mutated
|
||||
*/
|
||||
function addNewAttributes(nodeOrSelector, text, inspector) {
|
||||
info("Entering text '" + text + "' in node '" + nodeOrSelector + "''s new attribute field");
|
||||
|
||||
let container = getContainerForRawNode(nodeOrSelector, inspector);
|
||||
ok(container, "The container for '" + nodeOrSelector + "' was found");
|
||||
|
||||
info("Listening for the markupmutation event");
|
||||
let nodeMutated = inspector.once("markupmutation");
|
||||
setEditableFieldValue(container.editor.newAttr, text, inspector);
|
||||
return nodeMutated;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that a node has the given attributes
|
||||
*
|
||||
* @param {HTMLNode} element The node to check.
|
||||
* @param {DOMNode|String} nodeOrSelector The node or node selector to check.
|
||||
* @param {Object} attrs An object containing the attributes to check.
|
||||
* e.g. {id: "id1", class: "someclass"}
|
||||
*
|
||||
* Note that node.getAttribute() returns attribute values provided by the HTML
|
||||
* parser. The parser only provides unescaped entities so & will return &.
|
||||
*/
|
||||
function assertAttributes(element, attrs) {
|
||||
is(element.attributes.length, Object.keys(attrs).length,
|
||||
function assertAttributes(nodeOrSelector, attrs) {
|
||||
let node = getNode(nodeOrSelector);
|
||||
|
||||
is(node.attributes.length, Object.keys(attrs).length,
|
||||
"Node has the correct number of attributes.");
|
||||
for (let attr in attrs) {
|
||||
is(element.getAttribute(attr), attrs[attr],
|
||||
is(node.getAttribute(attr), attrs[attr],
|
||||
"Node has the correct " + attr + " attribute.");
|
||||
}
|
||||
}
|
||||
|
@ -214,7 +277,8 @@ function assertAttributes(element, attrs) {
|
|||
/**
|
||||
* Undo the last markup-view action and wait for the corresponding mutation to
|
||||
* occur
|
||||
* @param {InspectorPanel} inspector The instance of InspectorPanel currently loaded in the toolbox
|
||||
* @param {InspectorPanel} inspector The instance of InspectorPanel currently
|
||||
* loaded in the toolbox
|
||||
* @return a promise that resolves when the markup-mutation has been treated or
|
||||
* rejects if no undo action is possible
|
||||
*/
|
||||
|
@ -233,7 +297,8 @@ function undoChange(inspector) {
|
|||
/**
|
||||
* Redo the last markup-view action and wait for the corresponding mutation to
|
||||
* occur
|
||||
* @param {InspectorPanel} inspector The instance of InspectorPanel currently loaded in the toolbox
|
||||
* @param {InspectorPanel} inspector The instance of InspectorPanel currently
|
||||
* loaded in the toolbox
|
||||
* @return a promise that resolves when the markup-mutation has been treated or
|
||||
* rejects if no redo action is possible
|
||||
*/
|
||||
|
@ -271,3 +336,229 @@ function searchUsingSelectorSearch(selector, inspector) {
|
|||
field.value = selector;
|
||||
EventUtils.sendKey("return", inspector.panelWin);
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a series of add-attributes tests.
|
||||
* This function will iterate over the provided tests array and run each test.
|
||||
* Each test's goal is to provide some text to be entered into the test node's
|
||||
* new-attribute field and check that the given attributes have been created.
|
||||
* After each test has run, the markup-view's undo command will be called and
|
||||
* the test runner will check if all the new attributes are gone.
|
||||
* @param {Array} tests See runAddAttributesTest for the structure
|
||||
* @param {DOMNode|String} nodeOrSelector The node or node selector
|
||||
* corresponding to an element on the current test page that has *no attributes*
|
||||
* when the test starts. It will be used to add and remove attributes.
|
||||
* @param {InspectorPanel} inspector The instance of InspectorPanel currently
|
||||
* opened
|
||||
* @return a promise that resolves when the tests have run
|
||||
*/
|
||||
function runAddAttributesTests(tests, nodeOrSelector, inspector) {
|
||||
info("Running " + tests.length + " add-attributes tests");
|
||||
return Task.spawn(function*() {
|
||||
info("Selecting the test node");
|
||||
let div = getNode("div");
|
||||
yield selectNode(div, inspector);
|
||||
|
||||
for (let test of tests) {
|
||||
yield runAddAttributesTest(test, div, inspector);
|
||||
}
|
||||
|
||||
yield inspector.once("inspector-updated");
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a single add-attribute test.
|
||||
* See runAddAttributesTests for a description.
|
||||
* @param {Object} test A test object should contain the following properties:
|
||||
* - desc {String} a textual description for that test, to help when
|
||||
* reading logs
|
||||
* - text {String} the string to be inserted into the new attribute field
|
||||
* - expectedAttributes {Object} a key/value pair object that will be
|
||||
* used to check the attributes on the test element
|
||||
* - validate {Function} optional extra function that will be called after
|
||||
* the attributes have been added and which should be used to assert some
|
||||
* more things this test runner might not be checking. The function will
|
||||
* be called with the following arguments:
|
||||
* - {DOMNode} The element being tested
|
||||
* - {MarkupContainer} The corresponding container in the markup-view
|
||||
* - {InspectorPanel} The instance of the InspectorPanel opened
|
||||
* @param {DOMNode|String} nodeOrSelector The node or node selector
|
||||
* corresponding to the test element
|
||||
* @param {InspectorPanel} inspector The instance of InspectorPanel currently
|
||||
* opened
|
||||
*/
|
||||
function* runAddAttributesTest(test, nodeOrSelector, inspector) {
|
||||
let element = getNode(nodeOrSelector);
|
||||
|
||||
info("Starting add-attribute test: " + test.desc);
|
||||
yield addNewAttributes(element, test.text, inspector);
|
||||
|
||||
info("Assert that the attribute(s) has/have been applied correctly");
|
||||
assertAttributes(element, test.expectedAttributes);
|
||||
|
||||
if (test.validate) {
|
||||
test.validate(element, getContainerForRawNode(element, inspector), inspector);
|
||||
}
|
||||
|
||||
info("Undo the change");
|
||||
yield undoChange(inspector);
|
||||
|
||||
info("Assert that the attribute(s) has/have been removed correctly");
|
||||
assertAttributes(element, {});
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a series of edit-attributes tests.
|
||||
* This function will iterate over the provided tests array and run each test.
|
||||
* Each test's goal is to locate a given element on the current test page, assert
|
||||
* its current attributes, then provide the name of one of them and a value to
|
||||
* be set into it, and then check if the new attributes are correct.
|
||||
* After each test has run, the markup-view's undo and redo commands will be
|
||||
* called and the test runner will assert again that the attributes are correct.
|
||||
* @param {Array} tests See runEditAttributesTest for the structure
|
||||
* @param {InspectorPanel} inspector The instance of InspectorPanel currently
|
||||
* opened
|
||||
* @return a promise that resolves when the tests have run
|
||||
*/
|
||||
function runEditAttributesTests(tests, inspector) {
|
||||
info("Running " + tests.length + " edit-attributes tests");
|
||||
return Task.spawn(function*() {
|
||||
info("Expanding all nodes in the markup-view");
|
||||
yield inspector.markup.expandAll();
|
||||
|
||||
for (let test of tests) {
|
||||
yield runEditAttributesTest(test, inspector);
|
||||
}
|
||||
|
||||
yield inspector.once("inspector-updated");
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a single edit-attribute test.
|
||||
* See runEditAttributesTests for a description.
|
||||
* @param {Object} test A test object should contain the following properties:
|
||||
* - desc {String} a textual description for that test, to help when
|
||||
* reading logs
|
||||
* - node {String} a css selector that will be used to select the node
|
||||
* which will be tested during this iteration
|
||||
* - originalAttributes {Object} a key/value pair object that will be
|
||||
* used to check the attributes of the node before the test runs
|
||||
* - name {String} the name of the attribute to focus the editor for
|
||||
* - value {String} the new value to be typed in the focused editor
|
||||
* - expectedAttributes {Object} a key/value pair object that will be
|
||||
* used to check the attributes on the test element
|
||||
* @param {InspectorPanel} inspector The instance of InspectorPanel currently
|
||||
* opened
|
||||
*/
|
||||
function* runEditAttributesTest(test, inspector) {
|
||||
info("Starting edit-attribute test: " + test.desc);
|
||||
|
||||
info("Selecting the test node " + test.node);
|
||||
yield selectNode(test.node, inspector);
|
||||
|
||||
info("Asserting that the node has the right attributes to start with");
|
||||
assertAttributes(test.node, test.originalAttributes);
|
||||
|
||||
info("Editing attribute " + test.name + " with value " + test.value);
|
||||
|
||||
let container = getContainerForRawNode(test.node, inspector);
|
||||
ok(container && container.editor, "The markup-container for " + test.node +
|
||||
" was found");
|
||||
|
||||
info("Listening for the markupmutation event");
|
||||
let nodeMutated = inspector.once("markupmutation");
|
||||
let attr = container.editor.attrs[test.name].querySelector(".editable");
|
||||
setEditableFieldValue(attr, test.value, inspector);
|
||||
yield nodeMutated;
|
||||
|
||||
info("Asserting the new attributes after edition");
|
||||
assertAttributes(test.node, test.expectedAttributes);
|
||||
|
||||
info("Undo the change and assert that the attributes have been changed back");
|
||||
yield undoChange(inspector);
|
||||
assertAttributes(test.node, test.originalAttributes);
|
||||
|
||||
info("Redo the change and assert that the attributes have been changed again");
|
||||
yield redoChange(inspector);
|
||||
assertAttributes(test.node, test.expectedAttributes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a series of edit-outer-html tests.
|
||||
* This function will iterate over the provided tests array and run each test.
|
||||
* Each test's goal is to provide a node (a selector) and a new outer-HTML to be
|
||||
* inserted in place of the current one for that node.
|
||||
* This test runner will wait for the mutation event to be fired and will check
|
||||
* a few things. Each test may also provide its own validate function to perform
|
||||
* assertions and verify that the new outer html is correct.
|
||||
* @param {Array} tests See runEditOuterHTMLTest for the structure
|
||||
* @param {InspectorPanel} inspector The instance of InspectorPanel currently
|
||||
* opened
|
||||
* @return a promise that resolves when the tests have run
|
||||
*/
|
||||
function runEditOuterHTMLTests(tests, inspector) {
|
||||
info("Running " + tests.length + " edit-outer-html tests");
|
||||
return Task.spawn(function* () {
|
||||
for (let step of TEST_DATA) {
|
||||
yield runEditOuterHTMLTest(step, inspector);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a single edit-outer-html test.
|
||||
* See runEditOuterHTMLTests for a description.
|
||||
* @param {Object} test A test object should contain the following properties:
|
||||
* - selector {String} a css selector targeting the node to edit
|
||||
* - oldHTML {String}
|
||||
* - newHTML {String}
|
||||
* - validate {Function} will be executed when the edition test is done,
|
||||
* after the new outer-html has been inserted. Should be used to verify
|
||||
* the actual DOM, see if it corresponds to the newHTML string provided
|
||||
* @param {InspectorPanel} inspector The instance of InspectorPanel currently
|
||||
* opened
|
||||
*/
|
||||
function* runEditOuterHTMLTest(test, inspector) {
|
||||
info("Running an edit outerHTML test on '" + test.selector + "'");
|
||||
yield selectNode(test.selector, inspector);
|
||||
let oldNodeFront = inspector.selection.nodeFront;
|
||||
|
||||
info("Listening for the markupmutation event");
|
||||
// This event fires once the outerHTML is set, with a target as the parent node and a type of "childList".
|
||||
let mutated = inspector.once("markupmutation");
|
||||
info("Editing the outerHTML");
|
||||
inspector.markup.updateNodeOuterHTML(inspector.selection.nodeFront, test.newHTML, test.oldHTML);
|
||||
let mutations = yield mutated;
|
||||
ok(true, "The markupmutation event has fired, mutation done");
|
||||
|
||||
info("Check to make the sure the correct mutation event was fired, and that the parent is selected");
|
||||
let nodeFront = inspector.selection.nodeFront;
|
||||
let mutation = mutations[0];
|
||||
let isFromOuterHTML = mutation.removed.some(n => n === oldNodeFront);
|
||||
|
||||
ok(isFromOuterHTML, "The node is in the 'removed' list of the mutation");
|
||||
is(mutation.type, "childList", "Mutation is a childList after updating outerHTML");
|
||||
is(mutation.target, nodeFront, "Parent node is selected immediately after setting outerHTML");
|
||||
|
||||
// Wait for node to be reselected after outerHTML has been set
|
||||
yield inspector.selection.once("new-node");
|
||||
|
||||
// Typically selectedNode will === pageNode, but if a new element has been injected in front
|
||||
// of it, this will not be the case. If this happens.
|
||||
let selectedNode = inspector.selection.node;
|
||||
let nodeFront = inspector.selection.nodeFront;
|
||||
let pageNode = getNode(test.selector);
|
||||
|
||||
if (test.validate) {
|
||||
test.validate(pageNode, selectedNode);
|
||||
} else {
|
||||
is(pageNode, selectedNode, "Original node (grabbed by selector) is selected");
|
||||
is(pageNode.outerHTML, test.newHTML, "Outer HTML has been updated");
|
||||
}
|
||||
|
||||
// Wait for the inspector to be fully updated to avoid causing errors by
|
||||
// abruptly closing hanging requests when the test ends
|
||||
yield inspector.once("inspector-updated");
|
||||
}
|
||||
|
|
|
@ -23,6 +23,7 @@ DIRS += [
|
|||
'styleeditor',
|
||||
'styleinspector',
|
||||
'tilt',
|
||||
'webaudioeditor',
|
||||
'webconsole',
|
||||
]
|
||||
|
||||
|
@ -31,4 +32,4 @@ EXTRA_COMPONENTS += [
|
|||
'devtools-clhandler.manifest',
|
||||
]
|
||||
|
||||
JAR_MANIFESTS += ['jar.mn']
|
||||
JAR_MANIFESTS += ['jar.mn']
|
||||
|
|
|
@ -203,6 +203,10 @@ function InplaceEditor(aOptions, aEvent)
|
|||
}
|
||||
this.input.focus();
|
||||
|
||||
if (this.contentType == CONTENT_TYPES.CSS_VALUE && this.input.value == "") {
|
||||
this._maybeSuggestCompletion(true);
|
||||
}
|
||||
|
||||
this.input.addEventListener("blur", this._onBlur, false);
|
||||
this.input.addEventListener("keypress", this._onKeyPress, false);
|
||||
this.input.addEventListener("input", this._onInput, false);
|
||||
|
@ -853,6 +857,7 @@ InplaceEditor.prototype = {
|
|||
if (increment && this._incrementValue(increment) ) {
|
||||
this._updateSize();
|
||||
prevent = true;
|
||||
cycling = true;
|
||||
} else if (increment && this.popup && this.popup.isOpen) {
|
||||
cycling = true;
|
||||
prevent = true;
|
||||
|
@ -891,6 +896,12 @@ InplaceEditor.prototype = {
|
|||
|
||||
// Now we don't want to suggest anything as we are moving out.
|
||||
this._preventSuggestions = true;
|
||||
// But we still want to show suggestions for css values. i.e. moving out
|
||||
// of css property input box in forward direction
|
||||
if (this.contentType == CONTENT_TYPES.CSS_PROPERTY &&
|
||||
direction == FOCUS_FORWARD) {
|
||||
this._preventSuggestions = false;
|
||||
}
|
||||
|
||||
let input = this.input;
|
||||
|
||||
|
@ -992,8 +1003,16 @@ InplaceEditor.prototype = {
|
|||
|
||||
/**
|
||||
* Handles displaying suggestions based on the current input.
|
||||
*
|
||||
* @param {boolean} aNoAutoInsert
|
||||
* true if you don't want to automatically insert the first suggestion
|
||||
*/
|
||||
_maybeSuggestCompletion: function() {
|
||||
_maybeSuggestCompletion: function(aNoAutoInsert) {
|
||||
// Input can be null in cases when you intantaneously switch out of it.
|
||||
if (!this.input) {
|
||||
return;
|
||||
}
|
||||
let preTimeoutQuery = this.input.value;
|
||||
// Since we are calling this method from a keypress event handler, the
|
||||
// |input.value| does not include currently typed character. Thus we perform
|
||||
// this method async.
|
||||
|
@ -1005,15 +1024,26 @@ InplaceEditor.prototype = {
|
|||
if (this.contentType == CONTENT_TYPES.PLAIN_TEXT) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.input) {
|
||||
return;
|
||||
}
|
||||
let input = this.input;
|
||||
// Input can be null in cases when you intantaneously switch out of it.
|
||||
if (!input) {
|
||||
// The length of input.value should be increased by 1
|
||||
if (input.value.length - preTimeoutQuery.length > 1) {
|
||||
return;
|
||||
}
|
||||
let query = input.value.slice(0, input.selectionStart);
|
||||
let startCheckQuery = query;
|
||||
if (!query) {
|
||||
if (query == null) {
|
||||
return;
|
||||
}
|
||||
// If nothing is selected and there is a non-space character after the
|
||||
// cursor, do not autocomplete.
|
||||
if (input.selectionStart == input.selectionEnd &&
|
||||
input.selectionStart < input.value.length &&
|
||||
input.value.slice(input.selectionStart)[0] != " ") {
|
||||
// This emit is mainly to make the test flow simpler.
|
||||
this.emit("after-suggest", "nothing to autocomplete");
|
||||
return;
|
||||
}
|
||||
let list = [];
|
||||
|
@ -1030,43 +1060,54 @@ InplaceEditor.prototype = {
|
|||
|
||||
list =
|
||||
["!important", ...domUtils.getCSSValuesForProperty(this.property.name)];
|
||||
|
||||
if (query == "") {
|
||||
// Do not suggest '!important' without any manually typed character.
|
||||
list.splice(0, 1);
|
||||
}
|
||||
} else if (this.contentType == CONTENT_TYPES.CSS_MIXED &&
|
||||
/^\s*style\s*=/.test(query)) {
|
||||
// Detecting if cursor is at property or value;
|
||||
let match = query.match(/([:;"'=]?)\s*([^"';:=]+)$/);
|
||||
if (match && match.length == 3) {
|
||||
let match = query.match(/([:;"'=]?)\s*([^"';:=]+)?$/);
|
||||
if (match && match.length >= 2) {
|
||||
if (match[1] == ":") { // We are in CSS value completion
|
||||
let propertyName =
|
||||
query.match(/[;"'=]\s*([^"';:= ]+)\s*:\s*[^"';:=]+$/)[1];
|
||||
query.match(/[;"'=]\s*([^"';:= ]+)\s*:\s*[^"';:=]*$/)[1];
|
||||
list =
|
||||
["!important;", ...domUtils.getCSSValuesForProperty(propertyName)];
|
||||
let matchLastQuery = /([^\s,.\/]+$)/.exec(match[2]);
|
||||
let matchLastQuery = /([^\s,.\/]+$)/.exec(match[2] || "");
|
||||
if (matchLastQuery) {
|
||||
startCheckQuery = matchLastQuery[0];
|
||||
} else {
|
||||
startCheckQuery = "";
|
||||
}
|
||||
if (!match[2]) {
|
||||
// Don't suggest '!important' without any manually typed character
|
||||
list.splice(0, 1);
|
||||
}
|
||||
} else if (match[1]) { // We are in CSS property name completion
|
||||
list = CSSPropertyList;
|
||||
startCheckQuery = match[2];
|
||||
}
|
||||
if (!startCheckQuery) {
|
||||
if (startCheckQuery == null) {
|
||||
// This emit is mainly to make the test flow simpler.
|
||||
this.emit("after-suggest", "nothing to autocomplete");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
list.some(item => {
|
||||
if (startCheckQuery && item.startsWith(startCheckQuery)) {
|
||||
input.value = query + item.slice(startCheckQuery.length) +
|
||||
input.value.slice(query.length);
|
||||
input.setSelectionRange(query.length, query.length + item.length -
|
||||
startCheckQuery.length);
|
||||
this._updateSize();
|
||||
return true;
|
||||
}
|
||||
});
|
||||
if (!aNoAutoInsert) {
|
||||
list.some(item => {
|
||||
if (startCheckQuery != null && item.startsWith(startCheckQuery)) {
|
||||
input.value = query + item.slice(startCheckQuery.length) +
|
||||
input.value.slice(query.length);
|
||||
input.setSelectionRange(query.length, query.length + item.length -
|
||||
startCheckQuery.length);
|
||||
this._updateSize();
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (!this.popup) {
|
||||
// This emit is mainly to make the test flow simpler.
|
||||
|
@ -1076,7 +1117,7 @@ InplaceEditor.prototype = {
|
|||
let finalList = [];
|
||||
let length = list.length;
|
||||
for (let i = 0, count = 0; i < length && count < MAX_POPUP_ENTRIES; i++) {
|
||||
if (startCheckQuery && list[i].startsWith(startCheckQuery)) {
|
||||
if (startCheckQuery != null && list[i].startsWith(startCheckQuery)) {
|
||||
count++;
|
||||
finalList.push({
|
||||
preLabel: startCheckQuery,
|
||||
|
@ -1088,7 +1129,7 @@ InplaceEditor.prototype = {
|
|||
// which would have started with query, assuming that list is sorted.
|
||||
break;
|
||||
}
|
||||
else if (list[i][0] > startCheckQuery[0]) {
|
||||
else if (startCheckQuery != null && list[i][0] > startCheckQuery[0]) {
|
||||
// We have crossed all possible matches alphabetically.
|
||||
break;
|
||||
}
|
||||
|
@ -1100,6 +1141,9 @@ InplaceEditor.prototype = {
|
|||
this.inputCharWidth;
|
||||
this.popup.setItems(finalList);
|
||||
this.popup.openPopup(this.input, x);
|
||||
if (aNoAutoInsert) {
|
||||
this.popup.selectedIndex = -1;
|
||||
}
|
||||
} else {
|
||||
this.popup.hidePopup();
|
||||
}
|
||||
|
|
|
@ -188,7 +188,21 @@ function onEditorKeypress({ ed, Editor }, event) {
|
|||
function getPopup({ ed }) {
|
||||
return privates.get(ed).popup;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns contextual information about the token covered by the caret if the
|
||||
* implementation of completer supports it.
|
||||
*/
|
||||
function getInfoAt({ ed }, caret) {
|
||||
let completer = privates.get(ed).completer;
|
||||
if (completer && completer.getInfoAt)
|
||||
return completer.getInfoAt(ed.getText(), caret);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// Export functions
|
||||
|
||||
module.exports.setupAutoCompletion = setupAutoCompletion;
|
||||
module.exports.getAutocompletionPopup = getPopup;
|
||||
module.exports.getInfoAt = getInfoAt;
|
||||
|
|
|
@ -160,6 +160,7 @@ CSSCompleter.prototype = {
|
|||
let selectorState = SELECTOR_STATES.null;
|
||||
let propertyName = null;
|
||||
let scopeStack = [];
|
||||
let selectors = [];
|
||||
|
||||
// Fetch the closest null state line, ch from cached null state locations
|
||||
let matchedStateIndex = this.findNearestNullState(line);
|
||||
|
@ -193,7 +194,7 @@ CSSCompleter.prototype = {
|
|||
let cursor = 0;
|
||||
// This will maintain a stack of paired elements like { & }, @m & }, : & ; etc
|
||||
let token = null;
|
||||
let selectorBeforeNot = "";
|
||||
let selectorBeforeNot = null;
|
||||
while (cursor <= tokIndex && (token = tokens[cursor++])) {
|
||||
switch (_state) {
|
||||
case CSS_STATES.property:
|
||||
|
@ -216,6 +217,7 @@ CSSCompleter.prototype = {
|
|||
_state = CSS_STATES.frame;
|
||||
} else {
|
||||
selector = "";
|
||||
selectors = [];
|
||||
_state = CSS_STATES.null;
|
||||
}
|
||||
}
|
||||
|
@ -244,6 +246,7 @@ CSSCompleter.prototype = {
|
|||
_state = CSS_STATES.frame;
|
||||
} else {
|
||||
selector = "";
|
||||
selectors = [];
|
||||
_state = CSS_STATES.null;
|
||||
}
|
||||
}
|
||||
|
@ -257,6 +260,8 @@ CSSCompleter.prototype = {
|
|||
if (token.tokenType == "{") {
|
||||
scopeStack.push("{");
|
||||
_state = CSS_STATES.property;
|
||||
selectors.push(selector);
|
||||
selector = "";
|
||||
break;
|
||||
}
|
||||
switch(selectorState) {
|
||||
|
@ -286,6 +291,7 @@ CSSCompleter.prototype = {
|
|||
selector += token.value;
|
||||
} else if (token.value == ",") {
|
||||
selectorState = SELECTOR_STATES.null;
|
||||
selectors.push(selector);
|
||||
selector = "";
|
||||
}
|
||||
break;
|
||||
|
@ -299,10 +305,14 @@ CSSCompleter.prototype = {
|
|||
token = tokens[cursor++];
|
||||
switch(token.tokenType) {
|
||||
case "FUNCTION":
|
||||
if (token.value == "not") {
|
||||
selectorBeforeNot = selector;
|
||||
selector = "";
|
||||
scopeStack.push("(");
|
||||
} else {
|
||||
selector += token.value + "(";
|
||||
}
|
||||
selectorState = SELECTOR_STATES.null;
|
||||
selectorBeforeNot = selector;
|
||||
selector = "";
|
||||
scopeStack.push("(");
|
||||
break;
|
||||
|
||||
case "IDENT":
|
||||
|
@ -321,8 +331,11 @@ CSSCompleter.prototype = {
|
|||
if (peek(scopeStack) == "(") {
|
||||
scopeStack.pop();
|
||||
selector = selectorBeforeNot + "not(" + selector + ")";
|
||||
selectorState = SELECTOR_STATES.null;
|
||||
selectorBeforeNot = null;
|
||||
} else {
|
||||
selector += ")";
|
||||
}
|
||||
selectorState = SELECTOR_STATES.null;
|
||||
break;
|
||||
|
||||
case "WHITESPACE":
|
||||
|
@ -365,6 +378,7 @@ CSSCompleter.prototype = {
|
|||
selector += token.value;
|
||||
} else if (token.value == ",") {
|
||||
selectorState = SELECTOR_STATES.null;
|
||||
selectors.push(selector);
|
||||
selector = "";
|
||||
}
|
||||
break;
|
||||
|
@ -378,10 +392,14 @@ CSSCompleter.prototype = {
|
|||
token = tokens[cursor++];
|
||||
switch(token.tokenType) {
|
||||
case "FUNCTION":
|
||||
if (token.value == "not") {
|
||||
selectorBeforeNot = selector;
|
||||
selector = "";
|
||||
scopeStack.push("(");
|
||||
} else {
|
||||
selector += token.value + "(";
|
||||
}
|
||||
selectorState = SELECTOR_STATES.null;
|
||||
selectorBeforeNot = selector;
|
||||
selector = "";
|
||||
scopeStack.push("(");
|
||||
break;
|
||||
|
||||
case "IDENT":
|
||||
|
@ -400,8 +418,11 @@ CSSCompleter.prototype = {
|
|||
if (peek(scopeStack) == "(") {
|
||||
scopeStack.pop();
|
||||
selector = selectorBeforeNot + "not(" + selector + ")";
|
||||
selectorState = SELECTOR_STATES.null;
|
||||
selectorBeforeNot = null;
|
||||
} else {
|
||||
selector += ")";
|
||||
}
|
||||
selectorState = SELECTOR_STATES.null;
|
||||
break;
|
||||
|
||||
case "WHITESPACE":
|
||||
|
@ -418,6 +439,7 @@ CSSCompleter.prototype = {
|
|||
selector += token.value;
|
||||
} else if (token.value == ",") {
|
||||
selectorState = SELECTOR_STATES.null;
|
||||
selectors.push(selector);
|
||||
selector = "";
|
||||
}
|
||||
break;
|
||||
|
@ -431,10 +453,14 @@ CSSCompleter.prototype = {
|
|||
token = tokens[cursor++];
|
||||
switch(token.tokenType) {
|
||||
case "FUNCTION":
|
||||
if (token.value == "not") {
|
||||
selectorBeforeNot = selector;
|
||||
selector = "";
|
||||
scopeStack.push("(");
|
||||
} else {
|
||||
selector += token.value + "(";
|
||||
}
|
||||
selectorState = SELECTOR_STATES.null;
|
||||
selectorBeforeNot = selector;
|
||||
selector = "";
|
||||
scopeStack.push("(");
|
||||
break;
|
||||
|
||||
case "IDENT":
|
||||
|
@ -558,10 +584,14 @@ CSSCompleter.prototype = {
|
|||
token = tokens[cursor++];
|
||||
switch(token.tokenType) {
|
||||
case "FUNCTION":
|
||||
if (token.value == "not") {
|
||||
selectorBeforeNot = selector;
|
||||
selector = "";
|
||||
scopeStack.push("(");
|
||||
} else {
|
||||
selector += token.value + "(";
|
||||
}
|
||||
selectorState = SELECTOR_STATES.null;
|
||||
selectorBeforeNot = selector;
|
||||
selector = "";
|
||||
scopeStack.push("(");
|
||||
break;
|
||||
|
||||
case "IDENT":
|
||||
|
@ -638,8 +668,17 @@ CSSCompleter.prototype = {
|
|||
}
|
||||
}
|
||||
this.state = _state;
|
||||
if (!token)
|
||||
return _state;
|
||||
this.propertyName = _state == CSS_STATES.value ? propertyName : null;
|
||||
this.selectorState = _state == CSS_STATES.selector ? selectorState : null;
|
||||
this.selectorBeforeNot = selectorBeforeNot == null ? null: selectorBeforeNot;
|
||||
if (token) {
|
||||
selector = selector.slice(0, selector.length + token.loc.end.column - ch);
|
||||
this.selector = selector;
|
||||
}
|
||||
else {
|
||||
this.selector = "";
|
||||
}
|
||||
this.selectors = selectors;
|
||||
|
||||
if (token && token.tokenType != "WHITESPACE") {
|
||||
this.completing = ((token.value || token.repr || token.tokenType) + "")
|
||||
|
@ -654,14 +693,10 @@ CSSCompleter.prototype = {
|
|||
this.completing = "";
|
||||
|
||||
// Special check for !important; case.
|
||||
if (tokens[cursor - 2] && tokens[cursor - 2].value == "!" &&
|
||||
if (token && tokens[cursor - 2] && tokens[cursor - 2].value == "!" &&
|
||||
this.completing == "important".slice(0, this.completing.length)) {
|
||||
this.completing = "!" + this.completing;
|
||||
}
|
||||
this.propertyName = _state == CSS_STATES.value ? propertyName : null;
|
||||
selector = selector.slice(0, selector.length + token.loc.end.column - ch);
|
||||
this.selector = _state == CSS_STATES.selector ? selector : null;
|
||||
this.selectorState = _state == CSS_STATES.selector ? selectorState : null;
|
||||
return _state;
|
||||
},
|
||||
|
||||
|
@ -869,6 +904,247 @@ CSSCompleter.prototype = {
|
|||
*/
|
||||
invalidateCache: function(line) {
|
||||
this.nullStates.length = this.findNearestNullState(line) + 1;
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the state information about a token surrounding the {line, ch} position
|
||||
*
|
||||
* @param {string} source
|
||||
* The complete source of the CSS file. Unlike resolve state method,
|
||||
* this method requires the full source.
|
||||
* @param {object} caret
|
||||
* The line, ch position of the caret.
|
||||
*
|
||||
* @returns {object}
|
||||
* An object containing the state of token covered by the caret.
|
||||
* The object has following properties when the the state is
|
||||
* "selector", "value" or "property", null otherwise:
|
||||
* - state {string} one of CSS_STATES - "selector", "value" etc.
|
||||
* - selector {string} The selector at the caret when `state` is
|
||||
* selector. OR
|
||||
* - selectors {[string]} Array of selector strings in case when
|
||||
* `state` is "value" or "property"
|
||||
* - propertyName {string} The property name at the current caret or
|
||||
* the property name corresponding to the value at
|
||||
* the caret.
|
||||
* - value {string} The css value at the current caret.
|
||||
* - loc {object} An object containing the starting and the ending
|
||||
* caret position of the whole selector, value or property.
|
||||
* - { start: {line, ch}, end: {line, ch}}
|
||||
*/
|
||||
getInfoAt: function(source, caret) {
|
||||
// Limits the input source till the {line, ch} caret position
|
||||
function limit(source, {line, ch}) {
|
||||
line++;
|
||||
let list = source.split("\n");
|
||||
if (list.length < line)
|
||||
return source;
|
||||
if (line == 1)
|
||||
return list[0].slice(0, ch);
|
||||
return [...list.slice(0, line - 1), list[line - 1].slice(0, ch)].join("\n");
|
||||
}
|
||||
|
||||
// Get the state at the given line, ch
|
||||
let state = this.resolveState(limit(source, caret), caret);
|
||||
let propertyName = this.propertyName;
|
||||
let {line, ch} = caret;
|
||||
let sourceArray = source.split("\n");
|
||||
let limitedSource = limit(source, caret);
|
||||
|
||||
/**
|
||||
* Method to traverse forwards from the caret location to figure out the
|
||||
* ending point of a selector or css value.
|
||||
*
|
||||
* @param {function} check
|
||||
* A method which takes the current state as an input and determines
|
||||
* whether the state changed or not.
|
||||
*/
|
||||
let traverseForward = check => {
|
||||
let location;
|
||||
// Backward loop to determine the beginning location of the selector.
|
||||
do {
|
||||
let lineText = sourceArray[line];
|
||||
if (line == caret.line)
|
||||
lineText = lineText.substring(caret.ch);
|
||||
|
||||
let tokens = cssTokenizer(lineText, {loc: true});
|
||||
let found = false;
|
||||
let ech = line == caret.line ? caret.ch : 0;
|
||||
for (let i = 0; i < tokens.length; i++) {
|
||||
let token = tokens[i];
|
||||
// If the line is completely spaces, handle it differently
|
||||
if (lineText.trim() == "") {
|
||||
limitedSource += lineText;
|
||||
} else {
|
||||
limitedSource += sourceArray[line]
|
||||
.substring(ech + token.loc.start.column,
|
||||
ech + token.loc.end.column);
|
||||
}
|
||||
|
||||
// Whitespace cannot change state.
|
||||
if (token.tokenType == "WHITESPACE")
|
||||
continue;
|
||||
|
||||
let state = this.resolveState(limitedSource, {
|
||||
line: line,
|
||||
ch: token.loc.end.column + ech
|
||||
});
|
||||
if (check(state)) {
|
||||
if (tokens[i - 1] && tokens[i - 1].tokenType == "WHITESPACE")
|
||||
token = tokens[i - 1];
|
||||
location = {
|
||||
line: line,
|
||||
ch: token.loc.start.column + ech
|
||||
};
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
limitedSource += "\n";
|
||||
if (found)
|
||||
break;
|
||||
} while (line++ < sourceArray.length);
|
||||
return location;
|
||||
};
|
||||
|
||||
/**
|
||||
* Method to traverse backwards from the caret location to figure out the
|
||||
* starting point of a selector or css value.
|
||||
*
|
||||
* @param {function} check
|
||||
* A method which takes the current state as an input and determines
|
||||
* whether the state changed or not.
|
||||
* @param {boolean} isValue
|
||||
* true if the traversal is being done for a css value state.
|
||||
*/
|
||||
let traverseBackwards = (check, isValue) => {
|
||||
let location;
|
||||
// Backward loop to determine the beginning location of the selector.
|
||||
do {
|
||||
let lineText = sourceArray[line];
|
||||
if (line == caret.line)
|
||||
lineText = lineText.substring(0, caret.ch);
|
||||
|
||||
let tokens = cssTokenizer(lineText, {loc: true});
|
||||
let found = false;
|
||||
let ech = 0;
|
||||
for (let i = tokens.length - 2; i >= 0; i--) {
|
||||
let token = tokens[i];
|
||||
// If the line is completely spaces, handle it differently
|
||||
if (lineText.trim() == "") {
|
||||
limitedSource = limitedSource.slice(0, -1 * lineText.length);
|
||||
} else {
|
||||
let length = token.loc.end.column - token.loc.start.column;
|
||||
limitedSource = limitedSource.slice(0, -1 * length);
|
||||
}
|
||||
|
||||
// Whitespace cannot change state.
|
||||
if (token.tokenType == "WHITESPACE")
|
||||
continue;
|
||||
|
||||
let state = this.resolveState(limitedSource, {
|
||||
line: line,
|
||||
ch: token.loc.start.column
|
||||
});
|
||||
if (check(state)) {
|
||||
if (tokens[i + 1] && tokens[i + 1].tokenType == "WHITESPACE")
|
||||
token = tokens[i + 1];
|
||||
location = {
|
||||
line: line,
|
||||
ch: isValue ? token.loc.end.column: token.loc.start.column
|
||||
};
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
limitedSource = limitedSource.slice(0, -1);
|
||||
if (found)
|
||||
break;
|
||||
} while (line-- >= 0);
|
||||
return location;
|
||||
};
|
||||
|
||||
if (state == CSS_STATES.selector) {
|
||||
// For selector state, the ending and starting point of the selector is
|
||||
// either when the state changes or the selector becomes empty and a
|
||||
// single selector can span multiple lines.
|
||||
// Backward loop to determine the beginning location of the selector.
|
||||
let start = traverseBackwards(state => {
|
||||
return (state != CSS_STATES.selector ||
|
||||
(this.selector == "" && this.selectorBeforeNot == null));
|
||||
});
|
||||
|
||||
line = caret.line;
|
||||
limitedSource = limit(source, caret);
|
||||
// Forward loop to determine the ending location of the selector.
|
||||
let end = traverseForward(state => {
|
||||
return (state != CSS_STATES.selector ||
|
||||
(this.selector == "" && this.selectorBeforeNot == null));
|
||||
});
|
||||
|
||||
// Since we have start and end positions, figure out the whole selector.
|
||||
let selector = source.split("\n").slice(start.line, end.line + 1);
|
||||
selector[selector.length - 1] =
|
||||
selector[selector.length - 1].substring(0, end.ch);
|
||||
selector[0] = selector[0].substring(start.ch);
|
||||
selector = selector.join("\n");
|
||||
return {
|
||||
state: state,
|
||||
selector: selector,
|
||||
loc: {
|
||||
start: start,
|
||||
end: end
|
||||
}
|
||||
};
|
||||
}
|
||||
else if (state == CSS_STATES.property) {
|
||||
// A property can only be a single word and thus very easy to calculate.
|
||||
let tokens = cssTokenizer(sourceArray[line], {loc: true});
|
||||
for (let token of tokens) {
|
||||
if (token.loc.start.column <= ch && token.loc.end.column >= ch) {
|
||||
return {
|
||||
state: state,
|
||||
propertyName: token.value,
|
||||
selectors: this.selectors,
|
||||
loc: {
|
||||
start: {
|
||||
line: line,
|
||||
ch: token.loc.start.column
|
||||
},
|
||||
end: {
|
||||
line: line,
|
||||
ch: token.loc.end.column
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (state == CSS_STATES.value) {
|
||||
// CSS value can be multiline too, so we go forward and backwards to
|
||||
// determine the bounds of the value at caret
|
||||
let start = traverseBackwards(state => state != CSS_STATES.value, true);
|
||||
|
||||
line = caret.line;
|
||||
limitedSource = limit(source, caret);
|
||||
let end = traverseForward(state => state != CSS_STATES.value);
|
||||
|
||||
let value = source.split("\n").slice(start.line, end.line + 1);
|
||||
value[value.length - 1] = value[value.length - 1].substring(0, end.ch);
|
||||
value[0] = value[0].substring(start.ch);
|
||||
value = value.join("\n");
|
||||
return {
|
||||
state: state,
|
||||
propertyName: propertyName,
|
||||
selectors: this.selectors,
|
||||
value: value,
|
||||
loc: {
|
||||
start: start,
|
||||
end: end
|
||||
}
|
||||
};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -23,6 +23,7 @@ support-files =
|
|||
[browser_editor_addons.js]
|
||||
[browser_codemirror.js]
|
||||
[browser_css_autocompletion.js]
|
||||
[browser_css_getInfo.js]
|
||||
[browser_css_statemachine.js]
|
||||
[browser_vimemacs.js]
|
||||
skip-if = os == 'linux'&&debug # bug 981707
|
||||
|
|
|
@ -21,15 +21,18 @@ function test() {
|
|||
var win = browser.contentWindow.wrappedJSObject;
|
||||
var doc = win.document;
|
||||
var out = doc.getElementById("status");
|
||||
if (out && !win.mozilla_setStatus)
|
||||
win.mozilla_setStatus = codeMirror_setStatus;
|
||||
|
||||
if (!out || !out.classList.contains("done"))
|
||||
return void setTimeout(check, 100);
|
||||
|
||||
ok(!win.failed, "CodeMirror tests all passed");
|
||||
win.mozilla_setStatus = null;
|
||||
|
||||
while (gBrowser.tabs.length > 1) gBrowser.removeCurrentTab();
|
||||
finish();
|
||||
}
|
||||
|
||||
setTimeout(check, 100);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,177 @@
|
|||
/* 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";
|
||||
|
||||
const cssAutoCompleter = require("devtools/sourceeditor/css-autocompleter");
|
||||
|
||||
const source = [
|
||||
".devtools-toolbar {",
|
||||
" -moz-appearance: none;",
|
||||
" padding:4px 3px;border-bottom-width: 1px;",
|
||||
" border-bottom-style: solid;",
|
||||
"}",
|
||||
"",
|
||||
"#devtools-menu.devtools-menulist,",
|
||||
".devtools-toolbarbutton#devtools-menu {",
|
||||
" -moz-appearance: none;",
|
||||
" -moz-box-align: center;",
|
||||
" min-width: 78px;",
|
||||
" min-height: 22px;",
|
||||
" text-shadow: 0 -1px 0 hsla(210,8%,5%,.45);",
|
||||
" border: 1px solid hsla(210,8%,5%,.45);",
|
||||
" border-radius: 3px;",
|
||||
" background: linear-gradient(hsla(212,7%,57%,.35),",
|
||||
" hsla(212,7%,57%,.1)) padding-box;",
|
||||
" margin: 0 3px;",
|
||||
" color: inherit;",
|
||||
"}",
|
||||
"",
|
||||
".devtools-toolbarbutton > hbox.toolbarbutton-menubutton-button {",
|
||||
" -moz-box-orient: horizontal;",
|
||||
"}",
|
||||
"",
|
||||
".devtools-menulist:active,",
|
||||
"#devtools-toolbarbutton:focus {",
|
||||
" outline: 1px dotted hsla(210,30%,85%,0.7);",
|
||||
" outline-offset : -4px;",
|
||||
"}",
|
||||
"",
|
||||
".devtools-toolbarbutton:not([label]) {",
|
||||
" min-width: 32px;",
|
||||
"}",
|
||||
"",
|
||||
".devtools-toolbarbutton:not([label]) > .toolbarbutton-text, .devtools-toolbar {",
|
||||
" display: none;",
|
||||
"}",
|
||||
].join("\n");
|
||||
|
||||
// Format of test cases :
|
||||
// [
|
||||
// {line, ch}, - The caret position at which the getInfo call should be made
|
||||
// expectedState, - The expected state at the caret
|
||||
// expectedSelector, - The expected selector for the state
|
||||
// expectedProperty, - The expected property name for states value and property
|
||||
// expectedValue, - If state is value, then the expected value
|
||||
// ]
|
||||
const tests = [
|
||||
[{line: 0, ch: 13}, "selector", ".devtools-toolbar"],
|
||||
[{line: 8, ch: 13}, "property", ["#devtools-menu.devtools-menulist",
|
||||
".devtools-toolbarbutton#devtools-menu "], "-moz-appearance"],
|
||||
[{line: 28, ch: 25}, "value", [".devtools-menulist:active",
|
||||
"#devtools-toolbarbutton:focus "], "outline-offset", "-4px"],
|
||||
[{line: 4, ch: 1}, "null"],
|
||||
[{line: 5, ch: 0}, "null"],
|
||||
[{line: 31, ch: 13}, "selector", ".devtools-toolbarbutton:not([label])"],
|
||||
[{line: 35, ch: 23}, "selector", ".devtools-toolbarbutton:not([label]) > .toolbarbutton-text"],
|
||||
[{line: 35, ch: 70}, "selector", ".devtools-toolbar"],
|
||||
[{line: 27, ch: 14}, "value", [".devtools-menulist:active",
|
||||
"#devtools-toolbarbutton:focus "], "outline", "1px dotted hsla(210,30%,85%,0.7)"],
|
||||
[{line: 16, ch: 16}, "value", ["#devtools-menu.devtools-menulist",
|
||||
".devtools-toolbarbutton#devtools-menu "], "background",
|
||||
"linear-gradient(hsla(212,7%,57%,.35),\n hsla(212,7%,57%,.1)) padding-box"],
|
||||
[{line: 16, ch: 3}, "value", ["#devtools-menu.devtools-menulist",
|
||||
".devtools-toolbarbutton#devtools-menu "], "background",
|
||||
"linear-gradient(hsla(212,7%,57%,.35),\n hsla(212,7%,57%,.1)) padding-box"],
|
||||
[{line: 15, ch: 25}, "value", ["#devtools-menu.devtools-menulist",
|
||||
".devtools-toolbarbutton#devtools-menu "], "background",
|
||||
"linear-gradient(hsla(212,7%,57%,.35),\n hsla(212,7%,57%,.1)) padding-box"],
|
||||
];
|
||||
|
||||
const TEST_URI = "data:text/html;charset=UTF-8," + encodeURIComponent(
|
||||
["<!DOCTYPE html>",
|
||||
"<html>",
|
||||
" <head>",
|
||||
" <title>CSS contextual information tests.</title>",
|
||||
" <style type='text/css'>",
|
||||
"#progress {",
|
||||
" width: 500px; height: 30px;",
|
||||
" border: 1px solid black;",
|
||||
" position: relative",
|
||||
"}",
|
||||
"#progress div {",
|
||||
" width: 0%; height: 100%;",
|
||||
" background: green;",
|
||||
" position: absolute;",
|
||||
" z-index: -1; top: 0",
|
||||
"}",
|
||||
"#progress.failed div {",
|
||||
" background: red !important;",
|
||||
"}",
|
||||
"#progress.failed:after {",
|
||||
" content: 'Some tests failed';",
|
||||
" color: white",
|
||||
"}",
|
||||
"#progress:before {",
|
||||
" content: 'Running test ' attr(data-progress) ' of " + tests.length + "';",
|
||||
" color: white;",
|
||||
" text-shadow: 0 0 2px darkgreen;",
|
||||
"}",
|
||||
" </style>",
|
||||
" </head>",
|
||||
" <body>",
|
||||
" <h2>State machine tests for CSS autocompleter.</h2><br>",
|
||||
" <div id='progress' data-progress='0'>",
|
||||
" <div></div>",
|
||||
" </div>",
|
||||
" </body>",
|
||||
" </html>"
|
||||
].join("\n"));
|
||||
|
||||
let doc = null;
|
||||
function test() {
|
||||
waitForExplicitFinish();
|
||||
gBrowser.selectedTab = gBrowser.addTab();
|
||||
gBrowser.selectedBrowser.addEventListener("load", function onload() {
|
||||
gBrowser.selectedBrowser.removeEventListener("load", onload, true);
|
||||
doc = content.document;
|
||||
runTests();
|
||||
}, true);
|
||||
content.location = TEST_URI;
|
||||
}
|
||||
|
||||
function runTests() {
|
||||
let completer = new cssAutoCompleter();
|
||||
let matches = (arr, toCheck) => !arr.some((x, i) => x != toCheck[i]);
|
||||
let checkState = (expected, actual) => {
|
||||
if (expected[0] == "null" && actual == null) {
|
||||
return true;
|
||||
} else if (expected[0] == actual.state && expected[0] == "selector" &&
|
||||
expected[1] == actual.selector) {
|
||||
return true;
|
||||
} else if (expected[0] == actual.state && expected[0] == "property" &&
|
||||
matches(expected[1], actual.selectors) &&
|
||||
expected[2] == actual.propertyName) {
|
||||
return true;
|
||||
} else if (expected[0] == actual.state && expected[0] == "value" &&
|
||||
matches(expected[1], actual.selectors) &&
|
||||
expected[2] == actual.propertyName &&
|
||||
expected[3] == actual.value) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
let progress = doc.getElementById("progress");
|
||||
let progressDiv = doc.querySelector("#progress > div");
|
||||
let i = 0;
|
||||
for (let expected of tests) {
|
||||
let caret = expected.splice(0, 1)[0];
|
||||
progress.dataset.progress = ++i;
|
||||
progressDiv.style.width = 100*i/tests.length + "%";
|
||||
let actual = completer.getInfoAt(source, caret);
|
||||
if (checkState(expected, actual)) {
|
||||
ok(true, "Test " + i + " passed. ");
|
||||
}
|
||||
else {
|
||||
ok(false, "Test " + i + " failed. Expected state : [" + expected + "] " +
|
||||
"but found [" + actual.state + ", " +
|
||||
(actual.selector || actual.selectors) + ", " +
|
||||
actual.propertyName + ", " + actual.value + "].");
|
||||
progress.classList.add("failed");
|
||||
}
|
||||
}
|
||||
gBrowser.removeCurrentTab();
|
||||
finish();
|
||||
}
|
|
@ -20,15 +20,18 @@ function test() {
|
|||
var win = browser.contentWindow.wrappedJSObject;
|
||||
var doc = win.document;
|
||||
var out = doc.getElementById("status");
|
||||
if (out && !win.mozilla_setStatus)
|
||||
win.mozilla_setStatus = codeMirror_setStatus;
|
||||
|
||||
if (!out || !out.classList.contains("done"))
|
||||
return void setTimeout(check, 100);
|
||||
|
||||
ok(!win.failed, "CodeMirror tests all passed");
|
||||
win.mozilla_setStatus = null;
|
||||
|
||||
while (gBrowser.tabs.length > 1) gBrowser.removeCurrentTab();
|
||||
finish();
|
||||
}
|
||||
|
||||
setTimeout(check, 100);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -202,6 +202,8 @@
|
|||
progressTotal.nodeValue = '';
|
||||
customMessage = true; // Hack to avoid adding to output
|
||||
}
|
||||
if (window.mozilla_setStatus)
|
||||
mozilla_setStatus(message, type, customMessage);
|
||||
if (verbose && !customMessage) customMessage = message;
|
||||
setStatus(message, type);
|
||||
if (customMessage && customMessage.length > 0) {
|
||||
|
|
|
@ -80,3 +80,28 @@ function read(url) {
|
|||
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* This function is called by the CodeMirror test runner to report status
|
||||
* messages from the CM tests.
|
||||
* @see codemirror.html
|
||||
*/
|
||||
function codeMirror_setStatus(statusMsg, type, customMsg) {
|
||||
switch (type) {
|
||||
case "expected":
|
||||
case "ok":
|
||||
ok(1, statusMsg);
|
||||
break;
|
||||
case "error":
|
||||
case "fail":
|
||||
ok(0, statusMsg);
|
||||
break;
|
||||
default:
|
||||
info(statusMsg);
|
||||
break;
|
||||
}
|
||||
|
||||
if (customMsg && typeof customMsg == "string" && customMsg != statusMsg) {
|
||||
info(customMsg);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -202,6 +202,8 @@
|
|||
progressTotal.nodeValue = '';
|
||||
customMessage = true; // Hack to avoid adding to output
|
||||
}
|
||||
if (window.mozilla_setStatus)
|
||||
mozilla_setStatus(message, type, customMessage);
|
||||
if (verbose && !customMessage) customMessage = message;
|
||||
setStatus(message, type);
|
||||
if (customMessage && customMessage.length > 0) {
|
||||
|
|
|
@ -1384,9 +1384,13 @@ CssRuleView.prototype = {
|
|||
|
||||
this._elementStyle = new ElementStyle(aElement, this.store, this.pageStyle);
|
||||
return this._populate().then(() => {
|
||||
this._elementStyle.onChanged = () => {
|
||||
this._changed();
|
||||
};
|
||||
// A new node may already be selected, in which this._elementStyle will
|
||||
// be null.
|
||||
if (this._elementStyle) {
|
||||
this._elementStyle.onChanged = () => {
|
||||
this._changed();
|
||||
};
|
||||
}
|
||||
}).then(null, console.error);
|
||||
},
|
||||
|
||||
|
|
|
@ -48,6 +48,9 @@ let testData = [
|
|||
["VK_BACK_SPACE", "", -1, 0],
|
||||
["f", "fill", 0, MAX_ENTRIES],
|
||||
["i", "fill", 0, 4],
|
||||
["VK_LEFT", "fill", -1, 0],
|
||||
["VK_LEFT", "fill", -1, 0],
|
||||
["i", "fiill", -1, 0],
|
||||
["VK_ESCAPE", null, -1, 0],
|
||||
];
|
||||
|
||||
|
@ -90,7 +93,7 @@ function checkStateAndMoveOn(index) {
|
|||
|
||||
info("pressing key " + key + " to get result: [" + testData[index].slice(1) +
|
||||
"] for state " + state);
|
||||
if (/(right|back_space|escape)/ig.test(key)) {
|
||||
if (/(left|right|back_space|escape)/ig.test(key)) {
|
||||
info("added event listener for right|back_space|escape keys");
|
||||
editor.input.addEventListener("keypress", function onKeypress() {
|
||||
if (editor.input) {
|
||||
|
|
|
@ -26,7 +26,7 @@ let testData = [
|
|||
["VK_DOWN", {}, "blanchedalmond", 1, 4],
|
||||
["VK_DOWN", {}, "blue", 2, 4],
|
||||
["VK_RIGHT", {}, "blue", -1, 0],
|
||||
[" ", {}, "blue ", -1, 0],
|
||||
[" ", {}, "blue !important", 0, 10],
|
||||
["!", {}, "blue !important", 0, 0],
|
||||
["VK_BACK_SPACE", {}, "blue !", -1, 0],
|
||||
["VK_BACK_SPACE", {}, "blue ", -1, 0],
|
||||
|
|
|
@ -23,7 +23,7 @@ let brace;
|
|||
let testData = [
|
||||
["d", {}, "direction", 0, 3],
|
||||
["VK_DOWN", {}, "display", 1, 3],
|
||||
["VK_TAB", {}, "", -1, 0],
|
||||
["VK_TAB", {}, "", -1, 10],
|
||||
["n", {}, "none", -1, 0],
|
||||
["VK_TAB", {shiftKey: true}, "display", -1, 0],
|
||||
["VK_BACK_SPACE", {}, "", -1, 0],
|
||||
|
@ -36,7 +36,7 @@ let testData = [
|
|||
["VK_DOWN", {}, "rosybrown", 3, 5],
|
||||
["VK_DOWN", {}, "royalblue", 4, 5],
|
||||
["VK_RIGHT", {}, "royalblue", -1, 0],
|
||||
[" ", {}, "royalblue ", -1, 0],
|
||||
[" ", {}, "royalblue !important", 0, 10],
|
||||
["!", {}, "royalblue !important", 0, 0],
|
||||
["VK_ESCAPE", {}, null, -1, 0]
|
||||
];
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
# vim: set filetype=python:
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
TEST_DIRS += ['test']
|
||||
|
||||
JS_MODULES_PATH = 'modules/devtools/webaudioeditor'
|
||||
|
||||
EXTRA_JS_MODULES += [
|
||||
]
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
[DEFAULT]
|
||||
|
||||
support-files =
|
||||
doc_simple-context.html
|
||||
doc_complex-context.html
|
||||
doc_simple-node-creation.html
|
||||
head.js
|
||||
|
||||
[browser_webaudio-actor-simple.js]
|
||||
[browser_audionode-actor-get-set-param.js]
|
||||
[browser_audionode-actor-is-source.js]
|
||||
[browser_audionode-actor-get-type.js]
|
|
@ -0,0 +1,51 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Test AudioNode#getParam() / AudioNode#setParam()
|
||||
*/
|
||||
|
||||
function spawnTest () {
|
||||
let [target, debuggee, front] = yield initBackend(SIMPLE_CONTEXT_URL);
|
||||
let [_, [destNode, oscNode, gainNode]] = yield Promise.all([
|
||||
front.setup({ reload: true }),
|
||||
get3(front, "create-node")
|
||||
]);
|
||||
|
||||
let freq = yield oscNode.getParam("frequency");
|
||||
info(typeof freq);
|
||||
ise(freq, 440, "AudioNode:getParam correctly fetches AudioParam");
|
||||
|
||||
let type = yield oscNode.getParam("type");
|
||||
ise(type, "sine", "AudioNode:getParam correctly fetches non-AudioParam");
|
||||
|
||||
let type = yield oscNode.getParam("not-a-valid-param");
|
||||
is(type, undefined, "AudioNode:getParam correctly returns false for invalid param");
|
||||
|
||||
let resSuccess = yield oscNode.setParam("frequency", "220", "number");
|
||||
let freq = yield oscNode.getParam("frequency");
|
||||
ise(freq, 220, "AudioNode:setParam correctly sets a `number` AudioParam");
|
||||
is(resSuccess, undefined, "AudioNode:setParam returns undefined for correctly set AudioParam");
|
||||
|
||||
resSuccess = yield oscNode.setParam("type", "square", "string");
|
||||
let type = yield oscNode.getParam("type");
|
||||
ise(type, "square", "AudioNode:setParam correctly sets a `string` non-AudioParam");
|
||||
is(resSuccess, undefined, "AudioNode:setParam returns undefined for correctly set AudioParam");
|
||||
|
||||
resSuccess = yield oscNode.setParam("type", "\"triangle\"", "string");
|
||||
type = yield oscNode.getParam("type");
|
||||
ise(type, "triangle", "AudioNode:setParam correctly removes quotes in `string` non-AudioParam");
|
||||
|
||||
try {
|
||||
yield oscNode.setParam("frequency", "hello", "string");
|
||||
ok(false, "setParam with invalid types should throw");
|
||||
} catch (e) {
|
||||
ok(/is not a finite floating-point/.test(e.message), "AudioNode:setParam returns error with correct message when attempting an invalid assignment");
|
||||
is(e.type, "TypeError", "AudioNode:setParam returns error with correct type when attempting an invalid assignment");
|
||||
freq = yield oscNode.getParam("frequency");
|
||||
ise(freq, 220, "AudioNode:setParam does not modify value when an error occurs");
|
||||
}
|
||||
|
||||
yield removeTab(target.tab);
|
||||
finish();
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Test AudioNode#getType()
|
||||
*/
|
||||
|
||||
function spawnTest () {
|
||||
let [target, debuggee, front] = yield initBackend(SIMPLE_NODES_URL);
|
||||
let [_, nodes] = yield Promise.all([
|
||||
front.setup({ reload: true }),
|
||||
getN(front, "create-node", 14)
|
||||
]);
|
||||
|
||||
let actualTypes = yield Promise.all(nodes.map(node => node.getType()));
|
||||
let expectedTypes = [
|
||||
"AudioDestinationNode",
|
||||
"AudioBufferSourceNode", "ScriptProcessorNode", "AnalyserNode", "GainNode",
|
||||
"DelayNode", "BiquadFilterNode", "WaveShaperNode", "PannerNode", "ConvolverNode",
|
||||
"ChannelSplitterNode", "ChannelMergerNode", "DynamicsCompressorNode", "OscillatorNode"
|
||||
];
|
||||
|
||||
expectedTypes.forEach((type, i) => {
|
||||
is(actualTypes[i], type, type + " successfully created with correct type");
|
||||
});
|
||||
|
||||
yield removeTab(target.tab);
|
||||
finish();
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Test AudioNode#isSource()
|
||||
*/
|
||||
|
||||
function spawnTest () {
|
||||
let [target, debuggee, front] = yield initBackend(SIMPLE_NODES_URL);
|
||||
let [_, nodes] = yield Promise.all([
|
||||
front.setup({ reload: true }),
|
||||
getN(front, "create-node", 14)
|
||||
]);
|
||||
|
||||
let actualTypes = yield Promise.all(nodes.map(node => node.getType()));
|
||||
let isSourceResult = yield Promise.all(nodes.map(node => node.isSource()));
|
||||
|
||||
actualTypes.forEach((type, i) => {
|
||||
let shouldBeSource = type === "AudioBufferSourceNode" || type === "OscillatorNode";
|
||||
if (shouldBeSource)
|
||||
is(isSourceResult[i], true, type + "'s isSource() yields into `true`");
|
||||
else
|
||||
is(isSourceResult[i], false, type + "'s isSource() yields into `false`");
|
||||
});
|
||||
|
||||
yield removeTab(target.tab);
|
||||
finish();
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Test basic communication of Web Audio actor
|
||||
*/
|
||||
|
||||
function spawnTest () {
|
||||
let [target, debuggee, front] = yield initBackend(SIMPLE_CONTEXT_URL);
|
||||
let [_, __, [destNode, oscNode, gainNode], [connect1, connect2]] = yield Promise.all([
|
||||
front.setup({ reload: true }),
|
||||
once(front, "start-context"),
|
||||
get3(front, "create-node"),
|
||||
get2(front, "connect-node")
|
||||
]);
|
||||
|
||||
let destType = yield destNode.getType();
|
||||
let oscType = yield oscNode.getType();
|
||||
let gainType = yield gainNode.getType();
|
||||
|
||||
is(destType, "AudioDestinationNode", "WebAudioActor:create-node returns AudioNodeActor for AudioDestination");
|
||||
is(oscType, "OscillatorNode", "WebAudioActor:create-node returns AudioNodeActor");
|
||||
is(gainType, "GainNode", "WebAudioActor:create-node returns AudioNodeActor");
|
||||
|
||||
let { source, dest } = connect1;
|
||||
is(source.actorID, oscNode.actorID, "WebAudioActor:connect-node returns correct actor with ID on source (osc->gain)");
|
||||
is(dest.actorID, gainNode.actorID, "WebAudioActor:connect-node returns correct actor with ID on dest (osc->gain)");
|
||||
|
||||
let { source, dest } = connect2;
|
||||
is(source.actorID, gainNode.actorID, "WebAudioActor:connect-node returns correct actor with ID on source (gain->dest)");
|
||||
is(dest.actorID, destNode.actorID, "WebAudioActor:connect-node returns correct actor with ID on dest (gain->dest)");
|
||||
|
||||
yield removeTab(target.tab);
|
||||
finish();
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
<!-- Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ -->
|
||||
<!doctype html>
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<title>Web Audio Editor test page</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<script type="text/javascript;version=1.8">
|
||||
"use strict";
|
||||
|
||||
/*
|
||||
↱ proc
|
||||
osc → gain →
|
||||
osc → gain → destination
|
||||
buffer →↳ filter →
|
||||
*/
|
||||
let ctx = new AudioContext();
|
||||
let osc1 = ctx.createOscillator();
|
||||
let gain1 = ctx.createGain();
|
||||
let proc = ctx.createScriptProcessor();
|
||||
osc1.connect(gain1);
|
||||
osc1.connect(proc);
|
||||
gain1.connect(ctx.destination);
|
||||
|
||||
let osc2 = ctx.createOscillator();
|
||||
let gain2 = ctx.createGain();
|
||||
osc2.connect(gain2);
|
||||
gain2.connect(ctx.destination);
|
||||
|
||||
let buf = ctx.createBufferSource();
|
||||
let filter = ctx.createBiquadFilter();
|
||||
buf.connect(filter);
|
||||
osc2.connect(filter);
|
||||
filter.connect(ctx.destination);
|
||||
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
|
@ -0,0 +1,26 @@
|
|||
<!-- Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ -->
|
||||
<!doctype html>
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<title>Web Audio Editor test page</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<script type="text/javascript;version=1.8">
|
||||
"use strict";
|
||||
|
||||
let ctx = new AudioContext();
|
||||
let osc = ctx.createOscillator();
|
||||
let gain = ctx.createGain();
|
||||
gain.gain.value = 0;
|
||||
osc.connect(gain);
|
||||
gain.connect(ctx.destination);
|
||||
osc.start(0);
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
|
@ -0,0 +1,28 @@
|
|||
<!-- Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ -->
|
||||
<!doctype html>
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<title>Web Audio Editor test page</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<script type="text/javascript;version=1.8">
|
||||
"use strict";
|
||||
|
||||
let ctx = new AudioContext();
|
||||
let NODE_CREATION_METHODS = [
|
||||
"createBufferSource", "createScriptProcessor", "createAnalyser",
|
||||
"createGain", "createDelay", "createBiquadFilter", "createWaveShaper",
|
||||
"createPanner", "createConvolver", "createChannelSplitter", "createChannelMerger",
|
||||
"createDynamicsCompressor", "createOscillator"
|
||||
];
|
||||
let nodes = NODE_CREATION_METHODS.map(method => ctx[method]());
|
||||
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
|
@ -0,0 +1,160 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
"use strict";
|
||||
|
||||
const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
|
||||
|
||||
let { Services } = Cu.import("resource://gre/modules/Services.jsm", {});
|
||||
|
||||
// Enable logging for all the tests. Both the debugger server and frontend will
|
||||
// be affected by this pref.
|
||||
let gEnableLogging = Services.prefs.getBoolPref("devtools.debugger.log");
|
||||
Services.prefs.setBoolPref("devtools.debugger.log", true);
|
||||
|
||||
let { Task } = Cu.import("resource://gre/modules/Task.jsm", {});
|
||||
let { Promise } = Cu.import("resource://gre/modules/Promise.jsm", {});
|
||||
let { gDevTools } = Cu.import("resource:///modules/devtools/gDevTools.jsm", {});
|
||||
let { devtools } = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
|
||||
let { DebuggerServer } = Cu.import("resource://gre/modules/devtools/dbg-server.jsm", {});
|
||||
let { DebuggerClient } = Cu.import("resource://gre/modules/devtools/dbg-client.jsm", {});
|
||||
|
||||
let { WebAudioFront } = devtools.require("devtools/server/actors/webaudio");
|
||||
let TargetFactory = devtools.TargetFactory;
|
||||
let Toolbox = devtools.Toolbox;
|
||||
|
||||
const EXAMPLE_URL = "http://example.com/browser/browser/devtools/webaudioeditor/test/";
|
||||
const SIMPLE_CONTEXT_URL = EXAMPLE_URL + "doc_simple-context.html";
|
||||
const COMPLEX_CONTEXT_URL = EXAMPLE_URL + "doc_complex-context.html";
|
||||
const SIMPLE_NODES_URL = EXAMPLE_URL + "doc_simple-node-creation.html";
|
||||
|
||||
// All tests are asynchronous.
|
||||
waitForExplicitFinish();
|
||||
|
||||
let gToolEnabled = Services.prefs.getBoolPref("devtools.webaudioeditor.enabled");
|
||||
|
||||
registerCleanupFunction(() => {
|
||||
info("finish() was called, cleaning up...");
|
||||
Services.prefs.setBoolPref("devtools.debugger.log", gEnableLogging);
|
||||
Services.prefs.setBoolPref("devtools.webaudioeditor.enabled", gToolEnabled);
|
||||
Cu.forceGC();
|
||||
});
|
||||
|
||||
function addTab(aUrl, aWindow) {
|
||||
info("Adding tab: " + aUrl);
|
||||
|
||||
let deferred = Promise.defer();
|
||||
let targetWindow = aWindow || window;
|
||||
let targetBrowser = targetWindow.gBrowser;
|
||||
|
||||
targetWindow.focus();
|
||||
let tab = targetBrowser.selectedTab = targetBrowser.addTab(aUrl);
|
||||
let linkedBrowser = tab.linkedBrowser;
|
||||
|
||||
linkedBrowser.addEventListener("load", function onLoad() {
|
||||
linkedBrowser.removeEventListener("load", onLoad, true);
|
||||
info("Tab added and finished loading: " + aUrl);
|
||||
deferred.resolve(tab);
|
||||
}, true);
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
function removeTab(aTab, aWindow) {
|
||||
info("Removing tab.");
|
||||
|
||||
let deferred = Promise.defer();
|
||||
let targetWindow = aWindow || window;
|
||||
let targetBrowser = targetWindow.gBrowser;
|
||||
let tabContainer = targetBrowser.tabContainer;
|
||||
|
||||
tabContainer.addEventListener("TabClose", function onClose(aEvent) {
|
||||
tabContainer.removeEventListener("TabClose", onClose, false);
|
||||
info("Tab removed and finished closing.");
|
||||
deferred.resolve();
|
||||
}, false);
|
||||
|
||||
targetBrowser.removeTab(aTab);
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
function handleError(aError) {
|
||||
ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
|
||||
finish();
|
||||
}
|
||||
|
||||
function once(aTarget, aEventName, aUseCapture = false) {
|
||||
info("Waiting for event: '" + aEventName + "' on " + aTarget + ".");
|
||||
|
||||
let deferred = Promise.defer();
|
||||
|
||||
for (let [add, remove] of [
|
||||
["on", "off"], // Use event emitter before DOM events for consistency
|
||||
["addEventListener", "removeEventListener"],
|
||||
["addListener", "removeListener"]
|
||||
]) {
|
||||
if ((add in aTarget) && (remove in aTarget)) {
|
||||
aTarget[add](aEventName, function onEvent(...aArgs) {
|
||||
aTarget[remove](aEventName, onEvent, aUseCapture);
|
||||
deferred.resolve(...aArgs);
|
||||
}, aUseCapture);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
function test () {
|
||||
Task.spawn(spawnTest).then(finish, handleError);
|
||||
}
|
||||
|
||||
function initBackend(aUrl) {
|
||||
info("Initializing a web audio editor front.");
|
||||
|
||||
if (!DebuggerServer.initialized) {
|
||||
DebuggerServer.init(() => true);
|
||||
DebuggerServer.addBrowserActors();
|
||||
}
|
||||
|
||||
return Task.spawn(function*() {
|
||||
let tab = yield addTab(aUrl);
|
||||
let target = TargetFactory.forTab(tab);
|
||||
let debuggee = target.window.wrappedJSObject;
|
||||
|
||||
yield target.makeRemote();
|
||||
|
||||
let front = new WebAudioFront(target.client, target.form);
|
||||
return [target, debuggee, front];
|
||||
});
|
||||
}
|
||||
|
||||
// Due to web audio will fire most events synchronously back-to-back,
|
||||
// and we can't yield them in a chain without missing actors, this allows
|
||||
// us to listen for `n` events and return a promise resolving to them.
|
||||
//
|
||||
// Takes a `front` object that is an event emitter, the number of
|
||||
// programs that should be listened to and waited on, and an optional
|
||||
// `onAdd` function that calls with the entire actors array on program link
|
||||
function getN (front, eventName, count, spread) {
|
||||
let actors = [];
|
||||
let deferred = Promise.defer();
|
||||
front.on(eventName, function onEvent (...args) {
|
||||
let actor = args[0];
|
||||
if (actors.length !== count) {
|
||||
actors.push(spread ? args : actor);
|
||||
}
|
||||
if (actors.length === count) {
|
||||
front.off(eventName, onEvent);
|
||||
deferred.resolve(actors);
|
||||
}
|
||||
});
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
function get (front, eventName) { return getN(front, eventName, 1); }
|
||||
function get2 (front, eventName) { return getN(front, eventName, 2); }
|
||||
function get3 (front, eventName) { return getN(front, eventName, 3); }
|
||||
function getSpread (front, eventName) { return getN(front, eventName, 1, true); }
|
||||
function get2Spread (front, eventName) { return getN(front, eventName, 2, true); }
|
||||
function get3Spread (front, eventName) { return getN(front, eventName, 3, true); }
|
||||
function getNSpread (front, eventName, count) { return getN(front, eventName, count, true); }
|
|
@ -0,0 +1,6 @@
|
|||
# vim: set filetype=python:
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
BROWSER_CHROME_MANIFESTS += ['browser.ini']
|
Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше
Загрузка…
Ссылка в новой задаче