Merge m-c to b2g-inbound despite the CLOSED TREE

This commit is contained in:
Wes Kocher 2014-03-25 20:22:16 -07:00
Родитель 2406bcb027 618b2c258b
Коммит 05f118b4e6
579 изменённых файлов: 7740 добавлений и 4325 удалений

Просмотреть файл

@ -394,11 +394,7 @@ HTMLComboboxAccessible::InvalidateChildren()
void
HTMLComboboxAccessible::CacheChildren()
{
nsIFrame* frame = GetFrame();
if (!frame)
return;
nsIComboboxControlFrame* comboFrame = do_QueryFrame(frame);
nsIComboboxControlFrame* comboFrame = do_QueryFrame(GetFrame());
if (!comboFrame)
return;
@ -506,11 +502,7 @@ HTMLComboboxAccessible::GetActionName(uint8_t aIndex, nsAString& aName)
if (aIndex != HTMLComboboxAccessible::eAction_Click) {
return NS_ERROR_INVALID_ARG;
}
nsIFrame* frame = GetFrame();
if (!frame) {
return NS_ERROR_FAILURE;
}
nsIComboboxControlFrame* comboFrame = do_QueryFrame(frame);
nsIComboboxControlFrame* comboFrame = do_QueryFrame(GetFrame());
if (!comboFrame) {
return NS_ERROR_FAILURE;
}
@ -602,12 +594,9 @@ nsIFrame*
HTMLComboboxListAccessible::GetFrame() const
{
nsIFrame* frame = HTMLSelectListAccessible::GetFrame();
if (frame) {
nsIComboboxControlFrame* comboBox = do_QueryFrame(frame);
if (comboBox) {
return comboBox->GetDropDown();
}
nsIComboboxControlFrame* comboBox = do_QueryFrame(frame);
if (comboBox) {
return comboBox->GetDropDown();
}
return nullptr;

Просмотреть файл

@ -17,9 +17,6 @@ if not CONFIG['LIBXUL_SDK']:
# a console application.
WIN32_EXE_LDFLAGS += ['-ENTRY:wmainCRTStartup']
if CONFIG['ENABLE_MARIONETTE']:
DEFINES['ENABLE_MARIONETTE'] = 1
DEFINES['XPCOM_GLUE'] = True
for var in ('MOZ_APP_NAME', 'MOZ_APP_VERSION', 'MOZ_UPDATER', 'MOZ_SERVICES_FXACCOUNTS'):

Просмотреть файл

@ -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) {

Просмотреть файл

@ -26,7 +26,6 @@ export MOZ_TELEMETRY_REPORTING=1
#B2G options
ac_add_options --enable-application=b2g
ENABLE_MARIONETTE=1
ac_add_options --disable-elf-hack
export CXXFLAGS=-DMOZ_ENABLE_JS_DUMP

Просмотреть файл

@ -26,7 +26,6 @@ export MOZ_TELEMETRY_REPORTING=1
#B2G options
ac_add_options --enable-application=b2g
ENABLE_MARIONETTE=1
ac_add_options --disable-elf-hack
export CXXFLAGS=-DMOZ_ENABLE_JS_DUMP

Просмотреть файл

@ -21,8 +21,6 @@ ac_add_options --enable-warnings-as-errors
ac_add_options --enable-application=b2g
ac_add_options --enable-debug-symbols
. "$topsrcdir/build/mozconfig.cache"
ENABLE_MARIONETTE=1
export CXXFLAGS=-DMOZ_ENABLE_JS_DUMP
GAIADIR=$topsrcdir/gaia

Просмотреть файл

@ -21,8 +21,6 @@ fi
# B2G Options
ac_add_options --enable-application=b2g
ENABLE_MARIONETTE=1
export CXXFLAGS=-DMOZ_ENABLE_JS_DUMP
GAIADIR=$topsrcdir/gaia

Просмотреть файл

@ -757,7 +757,7 @@ bin/components/@DLL_PREFIX@nkgnomevfs@DLL_SUFFIX@
@BINPATH@/chrome/chrome.manifest
@BINPATH@/components/B2GComponents.manifest
@BINPATH@/@DLL_PREFIX@omxplugin@DLL_SUFFIX@
#ifdef ENABLE_MARIONETTE
#if defined(ENABLE_MARIONETTE) || !defined(MOZ_WIDGET_GONK)
@BINPATH@/chrome/marionette@JAREXT@
@BINPATH@/chrome/marionette.manifest
@BINPATH@/components/MarionetteComponents.manifest

Просмотреть файл

@ -12,9 +12,6 @@ SOURCES += [
'nsBrowserApp.cpp',
]
if CONFIG['ENABLE_MARIONETTE']:
DEFINES['ENABLE_MARIONETTE'] = 1
DEFINES['APP_VERSION'] = CONFIG['MOZ_APP_VERSION']
for var in ('MOZILLA_OFFICIAL', 'LIBXUL_SDK'):

Просмотреть файл

@ -828,12 +828,12 @@ pref("urlclassifier.gethashnoise", 4);
// the database.
pref("urlclassifier.max-complete-age", 2700);
// Tables for application reputation.
pref("urlclassifier.download_block_table", "goog-badbinurl-shavar");
pref("urlclassifier.downloadBlockTable", "goog-badbinurl-shavar");
#ifdef XP_WIN
// Only download the whitelist on Windows, since the whitelist is
// only useful for suppressing remote lookups for signed binaries which we can
// only verify on Windows (Bug 974579).
pref("urlclassifier.download_allow_table", "goog-downloadwhite-digest256");
pref("urlclassifier.downloadAllowTable", "goog-downloadwhite-digest256");
#endif
#endif
@ -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);

Просмотреть файл

@ -57,6 +57,7 @@ const NOT_FOUND_STRING = "n/a";
*/
function Template(root, store, l10nResolver) {
this._store = store;
this._rootResolver = new Resolver(this._store.object);
this._l10n = l10nResolver;
// Listeners are stored in Maps.
@ -69,6 +70,8 @@ function Template(root, store, l10nResolver) {
this._root = root;
this._doc = this._root.ownerDocument;
this._queuedNodeRegistrations = [];
this._storeChanged = this._storeChanged.bind(this);
this._store.on("set", this._storeChanged);
}
@ -76,6 +79,7 @@ function Template(root, store, l10nResolver) {
Template.prototype = {
start: function() {
this._processTree(this._root);
this._registerQueuedNodes();
},
destroy: function() {
@ -84,39 +88,6 @@ Template.prototype = {
this._doc = null;
},
_resolvePath: function(path, defaultValue=null) {
// From the store, get the value of an object located
// at @path.
//
// For example, if the store is designed as:
//
// {
// foo: {
// bar: [
// {},
// {},
// {a: 2}
// }
// }
//
// _resolvePath("foo.bar.2.a") will return "2".
//
// Array indexes are not surrounded by brackets.
let chunks = path.split(".");
let obj = this._store.object;
for (let word of chunks) {
if ((typeof obj) == "object" &&
(word in obj)) {
obj = obj[word];
} else {
return defaultValue;
}
}
return obj;
},
_storeChanged: function(event, path, value) {
// The store has changed (a "set" event has been emitted).
@ -131,6 +102,8 @@ Template.prototype = {
this._invalidate(registeredPath);
}
}
this._registerQueuedNodes();
},
_invalidate: function(path) {
@ -159,28 +132,42 @@ Template.prototype = {
}
},
_registerNode: function(path, element) {
// Delay node registration until the last step of starting / updating the UI.
// This allows us to avoid doing double work in _storeChanged where the first
// call to |_invalidate| registers new nodes, which would then be visited a
// second time when it iterates over node listeners.
_queueNodeRegistration: function(path, element) {
this._queuedNodeRegistrations.push([path, element]);
},
// We map a node to a path.
// If the value behind this path is updated,
// we get notified from the ObjectEmitter,
// and then we know which objects to update.
if (!this._nodeListeners.has(path)) {
this._nodeListeners.set(path, new Set());
_registerQueuedNodes: function() {
for (let [path, element] of this._queuedNodeRegistrations) {
// We map a node to a path.
// If the value behind this path is updated,
// we get notified from the ObjectEmitter,
// and then we know which objects to update.
if (!this._nodeListeners.has(path)) {
this._nodeListeners.set(path, new Set());
}
let set = this._nodeListeners.get(path);
set.add(element);
}
let set = this._nodeListeners.get(path);
set.add(element);
this._queuedNodeRegistrations.length = 0;
},
_unregisterNodes: function(nodes) {
for (let [registeredPath, set] of this._nodeListeners) {
for (let e of nodes) {
for (let e of nodes) {
for (let registeredPath of e.registeredPaths) {
let set = this._nodeListeners.get(registeredPath);
if (!set) {
continue;
}
set.delete(e);
if (set.size === 0) {
this._nodeListeners.delete(registeredPath);
}
}
if (set.size == 0) {
this._nodeListeners.delete(registeredPath);
}
e.registeredPaths = null;
}
},
@ -200,22 +187,18 @@ Template.prototype = {
set.add(element);
},
_processNode: function(element, rootPath="") {
_processNode: function(element, resolver=this._rootResolver) {
// The actual magic.
// The element has a template attribute.
// The value is supposed to be a JSON string.
// rootPath is the prefex to the path used by
// these elements (if children of template-loop);
// resolver is a helper object that is used to retrieve data
// from the template's data store, give the current path into
// the data store, or descend down another level of the store.
// See the Resolver object below.
let e = element;
let str = e.getAttribute("template");
if (rootPath) {
// We will prefix paths with this rootPath.
// It needs to end with a dot.
rootPath = rootPath + ".";
}
try {
let json = JSON.parse(str);
// Sanity check
@ -225,12 +208,12 @@ Template.prototype = {
if (json.rootPath) {
// If node has been generated through a loop, we stored
// previously its rootPath.
rootPath = json.rootPath;
resolver = this._rootResolver.descend(json.rootPath);
}
// paths is an array that will store all the paths we needed
// to expand the node. We will then, via _registerNode, link
// this element to these paths.
// to expand the node. We will then, via
// _registerQueuedNodes, link this element to these paths.
let paths = [];
@ -240,16 +223,16 @@ Template.prototype = {
!("path" in json)) {
throw new Error("missing property");
}
e.setAttribute(json.name, this._resolvePath(rootPath + json.path, NOT_FOUND_STRING));
paths.push(rootPath + json.path);
e.setAttribute(json.name, resolver.get(json.path, NOT_FOUND_STRING));
paths.push(resolver.rootPathTo(json.path));
break;
}
case "textContent": {
if (!("path" in json)) {
throw new Error("missing property");
}
e.textContent = this._resolvePath(rootPath + json.path, NOT_FOUND_STRING);
paths.push(rootPath + json.path);
e.textContent = resolver.get(json.path, NOT_FOUND_STRING);
paths.push(resolver.rootPathTo(json.path));
break;
}
case "localizedContent": {
@ -258,30 +241,32 @@ Template.prototype = {
throw new Error("missing property");
}
let params = json.paths.map((p) => {
paths.push(rootPath + p);
let str = this._resolvePath(rootPath + p, NOT_FOUND_STRING);
paths.push(resolver.rootPathTo(p));
let str = resolver.get(p, NOT_FOUND_STRING);
return str;
});
e.textContent = this._l10n(json.property, params);
break;
}
}
if (rootPath) {
if (resolver !== this._rootResolver) {
// We save the rootPath if any.
json.rootPath = rootPath;
json.rootPath = resolver.path;
e.setAttribute("template", JSON.stringify(json));
}
if (paths.length > 0) {
for (let path of paths) {
this._registerNode(path, e);
this._queueNodeRegistration(path, e);
}
}
// Store all the paths on the node, to speed up unregistering later
e.registeredPaths = paths;
} catch(exception) {
console.error("Invalid template: " + e.outerHTML + " (" + exception + ")");
}
},
_processLoop: function(element, rootPath="") {
_processLoop: function(element, resolver=this._rootResolver) {
// The element has a template-loop attribute.
// The related path must be an array. We go
// through the array, and build one child per
@ -296,9 +281,7 @@ Template.prototype = {
!("childSelector" in json)) {
throw new Error("missing property");
}
if (rootPath) {
json.arrayPath = rootPath + "." + json.arrayPath;
}
let descendedResolver = resolver.descend(json.arrayPath);
let templateParent = this._doc.querySelector(json.childSelector);
if (!templateParent) {
throw new Error("can't find child");
@ -306,7 +289,7 @@ Template.prototype = {
template = this._doc.createElement("div");
template.innerHTML = templateParent.innerHTML;
template = template.firstElementChild;
let array = this._resolvePath(json.arrayPath, []);
let array = descendedResolver.get("", []);
if (!Array.isArray(array)) {
console.error("referenced array is not an array");
}
@ -315,11 +298,11 @@ Template.prototype = {
let fragment = this._doc.createDocumentFragment();
for (let i = 0; i < count; i++) {
let node = template.cloneNode(true);
this._processTree(node, json.arrayPath + "." + i);
this._processTree(node, descendedResolver.descend(i));
fragment.appendChild(node);
}
this._registerLoop(json.arrayPath, e);
this._registerLoop(json.arrayPath + ".length", e);
this._registerLoop(descendedResolver.path, e);
this._registerLoop(descendedResolver.rootPathTo("length"), e);
this._unregisterNodes(e.querySelectorAll("[template]"));
e.innerHTML = "";
e.appendChild(fragment);
@ -328,7 +311,7 @@ Template.prototype = {
}
},
_processFor: function(element, rootPath="") {
_processFor: function(element, resolver=this._rootResolver) {
let e = element;
try {
let template;
@ -339,10 +322,6 @@ Template.prototype = {
throw new Error("missing property");
}
if (rootPath) {
json.path = rootPath + "." + json.path;
}
if (!json.path) {
// Nothing to show.
this._unregisterNodes(e.querySelectorAll("[template]"));
@ -350,6 +329,7 @@ Template.prototype = {
return;
}
let descendedResolver = resolver.descend(json.path);
let templateParent = this._doc.querySelector(json.childSelector);
if (!templateParent) {
throw new Error("can't find child");
@ -358,10 +338,10 @@ Template.prototype = {
content.innerHTML = templateParent.innerHTML;
content = content.firstElementChild;
this._processTree(content, json.path);
this._processTree(content, descendedResolver);
this._unregisterNodes(e.querySelectorAll("[template]"));
this._registerFor(json.path, e);
this._registerFor(descendedResolver.path, e);
e.innerHTML = "";
e.appendChild(content);
@ -371,21 +351,56 @@ Template.prototype = {
}
},
_processTree: function(parent, rootPath="") {
_processTree: function(parent, resolver=this._rootResolver) {
let loops = parent.querySelectorAll(":not(template) [template-loop]");
let fors = parent.querySelectorAll(":not(template) [template-for]");
let nodes = parent.querySelectorAll(":not(template) [template]");
for (let e of loops) {
this._processLoop(e, rootPath);
for (let i = 0; i < loops.length; i++) {
this._processLoop(loops[i], resolver);
}
for (let e of fors) {
this._processFor(e, rootPath);
for (let i = 0; i < fors.length; i++) {
this._processFor(fors[i], resolver);
}
for (let e of nodes) {
this._processNode(e, rootPath);
for (let i = 0; i < nodes.length; i++) {
this._processNode(nodes[i], resolver);
}
if (parent.hasAttribute("template")) {
this._processNode(parent, rootPath);
this._processNode(parent, resolver);
}
},
};
function Resolver(object, path = "") {
this._object = object;
this.path = path;
}
Resolver.prototype = {
get: function(path, defaultValue = null) {
let obj = this._object;
if (path === "") {
return obj;
}
let chunks = path.toString().split(".");
for (let i = 0; i < chunks.length; i++) {
let word = chunks[i];
if ((typeof obj) == "object" &&
(word in obj)) {
obj = obj[word];
} else {
return defaultValue;
}
}
return obj;
},
rootPathTo: function(path) {
return this.path ? this.path + "." + path : path;
},
descend: function(path) {
return new Resolver(this.get(path), this.rootPathTo(path));
}
};

Просмотреть файл

@ -22,17 +22,17 @@
<span template='{"type":"textContent","path":"title"}'>ttt</span>
<span title="ttt" template='{"type":"attribute","name":"title","path":"title"}'></span>
<span template='{"type":"localizedContent","paths":["foo2.foo_l10n","foo2.bar_l10n"],"property":"foo2"}'>foo2:foo_l10n/bar_l10n</span>
<div template-for='{"path":"mop","childSelector":"#template-for"}'><span template='{"type":"textContent","path":"name","rootPath":"mop."}'>meh</span></div>
<div template-for='{"path":"mop","childSelector":"#template-for"}'><span template='{"type":"textContent","path":"name","rootPath":"mop"}'>meh</span></div>
<div template-loop='{"arrayPath":"foo1.bar1","childSelector":"#template-loop"}'>
<div>
<span template='{"type":"textContent","path":"idx","rootPath":"foo1.bar1.0."}'>xx0</span>
<span template='{"type":"textContent","path":"a","rootPath":"foo1.bar1.0."}'>a</span>
<span template='{"type":"textContent","path":"b","rootPath":"foo1.bar1.0."}'>b</span>
<span template='{"type":"textContent","path":"idx","rootPath":"foo1.bar1.0"}'>xx0</span>
<span template='{"type":"textContent","path":"a","rootPath":"foo1.bar1.0"}'>a</span>
<span template='{"type":"textContent","path":"b","rootPath":"foo1.bar1.0"}'>b</span>
</div>
<div>
<span template='{"type":"textContent","path":"idx","rootPath":"foo1.bar1.1."}'>xx1</span>
<span template='{"type":"textContent","path":"a","rootPath":"foo1.bar1.1."}'>a</span>
<span template='{"type":"textContent","path":"b","rootPath":"foo1.bar1.1."}'>b</span>
<span template='{"type":"textContent","path":"idx","rootPath":"foo1.bar1.1"}'>xx1</span>
<span template='{"type":"textContent","path":"a","rootPath":"foo1.bar1.1"}'>a</span>
<span template='{"type":"textContent","path":"b","rootPath":"foo1.bar1.1"}'>b</span>
</div>
</div>
</div>
@ -42,17 +42,17 @@
<span template='{"type":"textContent","path":"title"}'>xxx</span>
<span title="xxx" template='{"type":"attribute","name":"title","path":"title"}'></span>
<span template='{"type":"localizedContent","paths":["foo2.foo_l10n","foo2.bar_l10n"],"property":"foo2"}'>foo2:foo2_l10n/bar_l10n</span>
<div template-for='{"path":"mop","childSelector":"#template-for"}'><span template='{"type":"textContent","path":"name","rootPath":"mop."}'>meh2</span></div>
<div template-for='{"path":"mop","childSelector":"#template-for"}'><span template='{"type":"textContent","path":"name","rootPath":"mop"}'>meh2</span></div>
<div template-loop='{"arrayPath":"foo1.bar1","childSelector":"#template-loop"}'>
<div>
<span template='{"type":"textContent","path":"idx","rootPath":"foo1.bar1.0."}'>xx0</span>
<span template='{"type":"textContent","path":"a","rootPath":"foo1.bar1.0."}'>a</span>
<span template='{"type":"textContent","path":"b","rootPath":"foo1.bar1.0."}'>b</span>
<span template='{"type":"textContent","path":"idx","rootPath":"foo1.bar1.0"}'>xx0</span>
<span template='{"type":"textContent","path":"a","rootPath":"foo1.bar1.0"}'>a</span>
<span template='{"type":"textContent","path":"b","rootPath":"foo1.bar1.0"}'>b</span>
</div>
<div>
<span template='{"type":"textContent","path":"idx","rootPath":"foo1.bar1.1."}'>xx1</span>
<span template='{"type":"textContent","path":"a","rootPath":"foo1.bar1.1."}'>a</span>
<span template='{"type":"textContent","path":"b","rootPath":"foo1.bar1.1."}'>b</span>
<span template='{"type":"textContent","path":"idx","rootPath":"foo1.bar1.1"}'>xx1</span>
<span template='{"type":"textContent","path":"a","rootPath":"foo1.bar1.1"}'>a</span>
<span template='{"type":"textContent","path":"b","rootPath":"foo1.bar1.1"}'>b</span>
</div>
</div>
</div>
@ -64,14 +64,14 @@
<div template-for='{"path":"","childSelector":"#template-for"}'></div>
<div template-loop='{"arrayPath":"foo1.bar1","childSelector":"#template-loop"}'>
<div>
<span template='{"type":"textContent","path":"idx","rootPath":"foo1.bar1.0."}'>xx0</span>
<span template='{"type":"textContent","path":"a","rootPath":"foo1.bar1.0."}'>a</span>
<span template='{"type":"textContent","path":"b","rootPath":"foo1.bar1.0."}'>b</span>
<span template='{"type":"textContent","path":"idx","rootPath":"foo1.bar1.0"}'>xx0</span>
<span template='{"type":"textContent","path":"a","rootPath":"foo1.bar1.0"}'>a</span>
<span template='{"type":"textContent","path":"b","rootPath":"foo1.bar1.0"}'>b</span>
</div>
<div>
<span template='{"type":"textContent","path":"idx","rootPath":"foo1.bar1.1."}'>xx1</span>
<span template='{"type":"textContent","path":"a","rootPath":"foo1.bar1.1."}'>a</span>
<span template='{"type":"textContent","path":"b","rootPath":"foo1.bar1.1."}'>b</span>
<span template='{"type":"textContent","path":"idx","rootPath":"foo1.bar1.1"}'>xx1</span>
<span template='{"type":"textContent","path":"a","rootPath":"foo1.bar1.1"}'>a</span>
<span template='{"type":"textContent","path":"b","rootPath":"foo1.bar1.1"}'>b</span>
</div>
</div>
</div>
@ -83,29 +83,29 @@
<div template-for='{"path":"","childSelector":"#template-for"}'></div>
<div template-loop='{"arrayPath":"foo1.bar1","childSelector":"#template-loop"}'>
<div>
<span template='{"type":"textContent","path":"idx","rootPath":"foo1.bar1.0."}'>xx0</span>
<span template='{"type":"textContent","path":"a","rootPath":"foo1.bar1.0."}'>a</span>
<span template='{"type":"textContent","path":"b","rootPath":"foo1.bar1.0."}'>b</span>
<span template='{"type":"textContent","path":"idx","rootPath":"foo1.bar1.0"}'>xx0</span>
<span template='{"type":"textContent","path":"a","rootPath":"foo1.bar1.0"}'>a</span>
<span template='{"type":"textContent","path":"b","rootPath":"foo1.bar1.0"}'>b</span>
</div>
<div>
<span template='{"type":"textContent","path":"idx","rootPath":"foo1.bar1.1."}'>xx1</span>
<span template='{"type":"textContent","path":"a","rootPath":"foo1.bar1.1."}'>a</span>
<span template='{"type":"textContent","path":"b","rootPath":"foo1.bar1.1."}'>b</span>
<span template='{"type":"textContent","path":"idx","rootPath":"foo1.bar1.1"}'>xx1</span>
<span template='{"type":"textContent","path":"a","rootPath":"foo1.bar1.1"}'>a</span>
<span template='{"type":"textContent","path":"b","rootPath":"foo1.bar1.1"}'>b</span>
</div>
<div>
<span template='{"type":"textContent","path":"idx","rootPath":"foo1.bar1.2."}'>xx2</span>
<span template='{"type":"textContent","path":"a","rootPath":"foo1.bar1.2."}'>a</span>
<span template='{"type":"textContent","path":"b","rootPath":"foo1.bar1.2."}'>b</span>
<span template='{"type":"textContent","path":"idx","rootPath":"foo1.bar1.2"}'>xx2</span>
<span template='{"type":"textContent","path":"a","rootPath":"foo1.bar1.2"}'>a</span>
<span template='{"type":"textContent","path":"b","rootPath":"foo1.bar1.2"}'>b</span>
</div>
<div>
<span template='{"type":"textContent","path":"idx","rootPath":"foo1.bar1.3."}'>xx3</span>
<span template='{"type":"textContent","path":"a","rootPath":"foo1.bar1.3."}'>a</span>
<span template='{"type":"textContent","path":"b","rootPath":"foo1.bar1.3."}'>b</span>
<span template='{"type":"textContent","path":"idx","rootPath":"foo1.bar1.3"}'>xx3</span>
<span template='{"type":"textContent","path":"a","rootPath":"foo1.bar1.3"}'>a</span>
<span template='{"type":"textContent","path":"b","rootPath":"foo1.bar1.3"}'>b</span>
</div>
<div>
<span template='{"type":"textContent","path":"idx","rootPath":"foo1.bar1.4."}'>xx4</span>
<span template='{"type":"textContent","path":"a","rootPath":"foo1.bar1.4."}'>a</span>
<span template='{"type":"textContent","path":"b","rootPath":"foo1.bar1.4."}'>b</span>
<span template='{"type":"textContent","path":"idx","rootPath":"foo1.bar1.4"}'>xx4</span>
<span template='{"type":"textContent","path":"a","rootPath":"foo1.bar1.4"}'>a</span>
<span template='{"type":"textContent","path":"b","rootPath":"foo1.bar1.4"}'>b</span>
</div>
</div>
</div>
@ -117,24 +117,24 @@
<div template-for='{"path":"","childSelector":"#template-for"}'></div>
<div template-loop='{"arrayPath":"foo1.bar1","childSelector":"#template-loop"}'>
<div>
<span template='{"type":"textContent","path":"idx","rootPath":"foo1.bar1.0."}'>xx0</span>
<span template='{"type":"textContent","path":"a","rootPath":"foo1.bar1.0."}'>a</span>
<span template='{"type":"textContent","path":"b","rootPath":"foo1.bar1.0."}'>b</span>
<span template='{"type":"textContent","path":"idx","rootPath":"foo1.bar1.0"}'>xx0</span>
<span template='{"type":"textContent","path":"a","rootPath":"foo1.bar1.0"}'>a</span>
<span template='{"type":"textContent","path":"b","rootPath":"foo1.bar1.0"}'>b</span>
</div>
<div>
<span template='{"type":"textContent","path":"idx","rootPath":"foo1.bar1.1."}'>xx1</span>
<span template='{"type":"textContent","path":"a","rootPath":"foo1.bar1.1."}'>a</span>
<span template='{"type":"textContent","path":"b","rootPath":"foo1.bar1.1."}'>b</span>
<span template='{"type":"textContent","path":"idx","rootPath":"foo1.bar1.1"}'>xx1</span>
<span template='{"type":"textContent","path":"a","rootPath":"foo1.bar1.1"}'>a</span>
<span template='{"type":"textContent","path":"b","rootPath":"foo1.bar1.1"}'>b</span>
</div>
<div>
<span template='{"type":"textContent","path":"idx","rootPath":"foo1.bar1.2."}'>xx2</span>
<span template='{"type":"textContent","path":"a","rootPath":"foo1.bar1.2."}'>a</span>
<span template='{"type":"textContent","path":"b","rootPath":"foo1.bar1.2."}'>b</span>
<span template='{"type":"textContent","path":"idx","rootPath":"foo1.bar1.2"}'>xx2</span>
<span template='{"type":"textContent","path":"a","rootPath":"foo1.bar1.2"}'>a</span>
<span template='{"type":"textContent","path":"b","rootPath":"foo1.bar1.2"}'>b</span>
</div>
<div>
<span template='{"type":"textContent","path":"idx","rootPath":"foo1.bar1.3."}'>xx3</span>
<span template='{"type":"textContent","path":"a","rootPath":"foo1.bar1.3."}'>a</span>
<span template='{"type":"textContent","path":"b","rootPath":"foo1.bar1.3."}'>b</span>
<span template='{"type":"textContent","path":"idx","rootPath":"foo1.bar1.3"}'>xx3</span>
<span template='{"type":"textContent","path":"a","rootPath":"foo1.bar1.3"}'>a</span>
<span template='{"type":"textContent","path":"b","rootPath":"foo1.bar1.3"}'>b</span>
</div>
</div>
</div>

Просмотреть файл

@ -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 < > &uuml; \" & '",
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>&param2=&uuml;&param3=\'&quot;\'"', inspector);
},
after: function() {
assertAttributes(doc.querySelector("#node25"), {
id: "node25",
src: "somefile.html?param1=<a>&param2=\xfc&param3='\"'"
});
}
},
{
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 &quot; and single '\"", "Value contains &quot;");
value = value.replace(/Double/, "&quot;").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 < > &uuml; \" & '",
text: 'src="somefile.html?param1=<a>&param2=&uuml;&param3=\'&quot;\'"',
expectedAttributes: {
src: "somefile.html?param1=<a>&param2=\xfc&param3='\"'"
}
}, {
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 &quot; and single '\"", "Value contains &quot;");
value = value.replace(/Double/, "&quot;").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 &amp; 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 += [
]

Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше