merge m-c to fx-team

This commit is contained in:
Tim Taubert 2013-02-12 19:08:06 +01:00
commit 43c911f3aa
32 changed files with 917 additions and 106 deletions

View File

@ -12,7 +12,7 @@ const Ci = Components.interfaces;
const HTML_NAMESPACE = "http://www.w3.org/1999/xhtml";
const PREF_STORAGE_VERSION = "browser.pagethumbnails.storage_version";
const LATEST_STORAGE_VERSION = 2;
const LATEST_STORAGE_VERSION = 3;
const EXPIRATION_MIN_CHUNK_SIZE = 50;
const EXPIRATION_INTERVAL_SECS = 3600;
@ -364,37 +364,41 @@ let PageThumbsStorageMigrator = {
migrate: function Migrator_migrate() {
let version = this.currentVersion;
if (version < 1) {
this.removeThumbnailsFromRoamingProfile();
}
if (version < 2) {
this.renameThumbnailsFolder();
// Storage version 1 never made it to beta.
// At the time of writing only Windows had (ProfD != ProfLD) and we
// needed to move thumbnails from the roaming profile to the locale
// one so that they're not needlessly included in backups and/or
// written via SMB.
// Storage version 2 also never made it to beta.
// The thumbnail folder structure has been changed and old thumbnails
// were not migrated. Instead, we just renamed the current folder to
// "<name>-old" and will remove it later.
if (version < 3) {
this.migrateToVersion3();
}
this.currentVersion = LATEST_STORAGE_VERSION;
},
removeThumbnailsFromRoamingProfile:
function Migrator_removeThumbnailsFromRoamingProfile() {
let local = FileUtils.getDir("ProfLD", [THUMBNAIL_DIRECTORY]);
/**
* Bug 239254 added support for having the disk cache and thumbnail
* directories on a local path (i.e. ~/.cache/) under Linux. We'll first
* try to move the old thumbnails to their new location. If that's not
* possible (because ProfD might be on a different file system than
* ProfLD) we'll just discard them.
*/
migrateToVersion3: function Migrator_migrateToVersion3() {
let local = FileUtils.getDir("ProfLD", [THUMBNAIL_DIRECTORY], true);
let roaming = FileUtils.getDir("ProfD", [THUMBNAIL_DIRECTORY]);
if (!roaming.equals(local) && roaming.exists()) {
roaming.followLinks = false;
try {
roaming.remove(true);
} catch (e) {
// The directory might not exist or we're not permitted to remove it.
}
}
},
renameThumbnailsFolder: function Migrator_renameThumbnailsFolder() {
let dir = FileUtils.getDir("ProfLD", [THUMBNAIL_DIRECTORY]);
try {
dir.moveTo(null, dir.leafName + "-old");
} catch (e) {
// The directory might not exist or we're not permitted to rename it.
if (!roaming.equals(local)) {
PageThumbsWorker.postMessage({
type: "moveOrDeleteAllThumbnails",
from: roaming.path,
to: local.path
});
}
}
};

View File

@ -25,6 +25,9 @@ let PageThumbsWorker = {
case "expireFilesInDirectory":
data.result = this.expireFilesInDirectory(msg);
break;
case "moveOrDeleteAllThumbnails":
data.result = this.moveOrDeleteAllThumbnails(msg);
break;
default:
data.result = false;
data.detail = "message not understood";
@ -67,6 +70,38 @@ let PageThumbsWorker = {
return [entry
for (entry in iter)
if (!entry.isDir && !entry.isSymLink && !skip.has(entry.name))];
},
moveOrDeleteAllThumbnails:
function Worker_moveOrDeleteAllThumbnails(msg) {
if (!OS.File.exists(msg.from))
return true;
let iter = new OS.File.DirectoryIterator(msg.from);
for (let entry in iter) {
if (entry.isDir || entry.isSymLink) {
continue;
}
let from = OS.Path.join(msg.from, entry.name);
let to = OS.Path.join(msg.to, entry.name);
try {
OS.File.move(from, to, {noOverwrite: true, noCopy: true});
} catch (e) {
OS.File.remove(from);
}
}
iter.close();
try {
OS.File.removeEmptyDir(msg.from);
} catch (e) {
// This could fail if there's something in
// the folder we're not permitted to remove.
}
return true;
}
};

View File

@ -17,6 +17,7 @@ _BROWSER_FILES = \
browser_thumbnails_privacy.js \
browser_thumbnails_redirect.js \
browser_thumbnails_storage.js \
browser_thumbnails_storage_migrate3.js \
browser_thumbnails_bug726727.js \
head.js \
background_red.html \

View File

@ -20,13 +20,3 @@ function runTests() {
yield whenFileExists(URL);
yield checkThumbnailColor(URL, 255, 0, 0, "referrer has a red thumbnail");
}
function whenFileExists(aURL) {
let callback = next;
let file = PageThumbsStorage.getFileForURL(aURL);
if (!file.exists())
callback = function () whenFileExists(aURL);
executeSoon(callback);
}

View File

@ -89,20 +89,9 @@ function clearHistory(aUseRange) {
function createThumbnail() {
addTab(URL, function () {
whenFileExists(function () {
whenFileExists(URL, function () {
gBrowser.removeTab(gBrowser.selectedTab);
next();
});
});
}
function whenFileExists(aCallback) {
let callback;
if (thumbnailExists(URL)) {
callback = aCallback;
} else {
callback = function () whenFileExists(aCallback);
}
executeSoon(callback);
}

View File

@ -0,0 +1,100 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
const URL = "http://mochi.test:8888/migration3";
const URL2 = URL + "#2";
const URL3 = URL + "#3";
const THUMBNAIL_DIRECTORY = "thumbnails";
const PREF_STORAGE_VERSION = "browser.pagethumbnails.storage_version";
let tmp = {};
Cc["@mozilla.org/moz/jssubscript-loader;1"]
.getService(Ci.mozIJSSubScriptLoader)
.loadSubScript("resource:///modules/PageThumbs.jsm", tmp);
let {PageThumbsStorageMigrator} = tmp;
XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
"resource://gre/modules/FileUtils.jsm");
XPCOMUtils.defineLazyServiceGetter(this, "gDirSvc",
"@mozilla.org/file/directory_service;1", "nsIProperties");
/**
* This test makes sure we correctly migrate to thumbnail storage version 3.
* This means copying existing thumbnails from the roaming to the local profile
* directory and should just apply to Linux.
*/
function runTests() {
let dirSvc = Cc["@mozilla.org/file/directory_service;1"]
.getService(Ci.nsIProperties);
// Prepare a local profile directory.
let localProfile = FileUtils.getDir("ProfD", ["local-test"], true);
changeLocation("ProfLD", localProfile);
let local = FileUtils.getDir("ProfLD", [THUMBNAIL_DIRECTORY], true);
let roaming = FileUtils.getDir("ProfD", [THUMBNAIL_DIRECTORY], true);
// Set up some data in the roaming profile.
let name = PageThumbsStorage.getLeafNameForURL(URL);
let file = FileUtils.getFile("ProfD", [THUMBNAIL_DIRECTORY, name]);
writeDummyFile(file);
name = PageThumbsStorage.getLeafNameForURL(URL2);
file = FileUtils.getFile("ProfD", [THUMBNAIL_DIRECTORY, name]);
writeDummyFile(file);
name = PageThumbsStorage.getLeafNameForURL(URL3);
file = FileUtils.getFile("ProfD", [THUMBNAIL_DIRECTORY, name]);
writeDummyFile(file);
// Pretend to have one of the thumbnails
// already in place at the new storage site.
name = PageThumbsStorage.getLeafNameForURL(URL3);
file = FileUtils.getFile("ProfLD", [THUMBNAIL_DIRECTORY, name]);
writeDummyFile(file, "no-overwrite-plz");
// Kick off thumbnail storage migration.
PageThumbsStorageMigrator.migrateToVersion3();
ok(true, "migration finished");
// Wait until the first thumbnail was moved to its new location.
yield whenFileExists(URL);
ok(true, "first thumbnail moved");
// Wait for the second thumbnail to be moved as well.
yield whenFileExists(URL2);
ok(true, "second thumbnail moved");
yield whenFileRemoved(roaming);
ok(true, "roaming thumbnail directory removed");
// Check that our existing thumbnail wasn't overwritten.
is(getFileContents(file), "no-overwrite-plz",
"existing thumbnail was not overwritten");
}
function changeLocation(aLocation, aNewDir) {
let oldDir = gDirSvc.get(aLocation, Ci.nsILocalFile);
gDirSvc.undefine(aLocation);
gDirSvc.set(aLocation, aNewDir);
registerCleanupFunction(function () {
gDirSvc.undefine(aLocation);
gDirSvc.set(aLocation, oldDir);
});
}
function writeDummyFile(aFile, aContents) {
let fos = FileUtils.openSafeFileOutputStream(aFile);
let data = aContents || "dummy";
fos.write(data, data.length);
FileUtils.closeSafeFileOutputStream(fos);
}
function getFileContents(aFile) {
let istream = Cc["@mozilla.org/network/file-input-stream;1"]
.createInstance(Ci.nsIFileInputStream);
istream.init(aFile, FileUtils.MODE_RDONLY, FileUtils.PERMS_FILE, 0);
return NetUtil.readInputStreamToString(istream, istream.available());
}

View File

@ -214,3 +214,36 @@ function addVisits(aPlaceInfo, aCallback) {
);
}
/**
* Calls a given callback when the thumbnail for a given URL has been found
* on disk. Keeps trying until the thumbnail has been created.
*
* @param aURL The URL of the thumbnail's page.
* @param [optional] aCallback
* Function to be invoked on completion.
*/
function whenFileExists(aURL, aCallback) {
let callback = aCallback;
if (!thumbnailExists(aURL)) {
callback = function () whenFileExists(aURL, aCallback);
}
executeSoon(callback || next);
}
/**
* Calls a given callback when the given file has been removed.
* Keeps trying until the file is removed.
*
* @param aFile The file that is being removed
* @param [optional] aCallback
* Function to be invoked on completion.
*/
function whenFileRemoved(aFile, aCallback) {
let callback = aCallback;
if (aFile.exists()) {
callback = function () whenFileRemoved(aFile, aCallback);
}
executeSoon(callback || next);
}

View File

@ -32,6 +32,7 @@ MOCHITEST_BROWSER_TESTS = \
browser_dbg_propertyview-08.js \
browser_dbg_propertyview-09.js \
browser_dbg_propertyview-10.js \
browser_dbg_propertyview-11.js \
browser_dbg_propertyview-edit-value.js \
browser_dbg_propertyview-edit-watch.js \
browser_dbg_propertyview-data-big.js \

View File

@ -25,6 +25,7 @@
}
var button = document.querySelector("button");
button.addEventListener("click", load, false);
var buttonAsProto = Object.create(button);
});
</script>

View File

@ -0,0 +1,282 @@
/* vim:set ts=2 sw=2 sts=2 et: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Make sure that the property view correctly displays WebIDL attributes in DOM
* objects.
*/
const TAB_URL = EXAMPLE_URL + "browser_dbg_frame-parameters.html";
let gPane = null;
let gTab = null;
let gDebugger = null;
function test()
{
debug_tab_pane(TAB_URL, function(aTab, aDebuggee, aPane) {
gTab = aTab;
gPane = aPane;
gDebugger = gPane.panelWin;
gDebugger.DebuggerController.StackFrames.autoScopeExpand = true;
gDebugger.DebuggerView.Variables.nonEnumVisible = true;
testFrameParameters();
});
}
function testFrameParameters()
{
let count = 0;
gDebugger.addEventListener("Debugger:FetchedVariables", function test() {
// We expect 2 Debugger:FetchedVariables events, one from the global object
// scope and the regular one.
if (++count < 2) {
info("Number of received Debugger:FetchedVariables events: " + count);
return;
}
gDebugger.removeEventListener("Debugger:FetchedVariables", test, false);
Services.tm.currentThread.dispatch({ run: function() {
let anonymousScope = gDebugger.DebuggerView.Variables._list.querySelectorAll(".scope")[1],
globalScope = gDebugger.DebuggerView.Variables._list.querySelectorAll(".scope")[2],
anonymousNodes = anonymousScope.querySelector(".details").childNodes,
globalNodes = globalScope.querySelector(".details").childNodes,
gVars = gDebugger.DebuggerView.Variables;
is(gDebugger.DebuggerController.activeThread.state, "paused",
"Should only be getting stack frames while paused.");
is(anonymousNodes[1].querySelector(".name").getAttribute("value"), "button",
"Should have the right property name for |button|.");
is(anonymousNodes[1].querySelector(".value").getAttribute("value"), "[object HTMLButtonElement]",
"Should have the right property value for |button|.");
is(anonymousNodes[2].querySelector(".name").getAttribute("value"), "buttonAsProto",
"Should have the right property name for |buttonAsProto|.");
is(anonymousNodes[2].querySelector(".value").getAttribute("value"), "[object Object]",
"Should have the right property value for |buttonAsProto|.");
is(globalNodes[3].querySelector(".name").getAttribute("value"), "document",
"Should have the right property name for |document|.");
is(globalNodes[3].querySelector(".value").getAttribute("value"), "[object HTMLDocument]",
"Should have the right property value for |document|.");
let buttonNode = gVars.getItemForNode(anonymousNodes[1]);
let buttonAsProtoNode = gVars.getItemForNode(anonymousNodes[2]);
let documentNode = gVars.getItemForNode(globalNodes[3]);
is(buttonNode.expanded, false,
"The buttonNode should not be expanded at this point.");
is(buttonAsProtoNode.expanded, false,
"The buttonAsProtoNode should not be expanded at this point.");
is(documentNode.expanded, false,
"The documentNode should not be expanded at this point.");
// Expand the 'button', 'buttonAsProto' and 'document' tree nodes. This
// causes their properties to be retrieved and displayed.
buttonNode.expand();
buttonAsProtoNode.expand();
documentNode.expand();
is(buttonNode.expanded, true,
"The buttonNode should be expanded at this point.");
is(buttonAsProtoNode.expanded, true,
"The buttonAsProtoNode should be expanded at this point.");
is(documentNode.expanded, true,
"The documentNode should be expanded at this point.");
// Poll every few milliseconds until the properties are retrieved.
// It's important to set the timer in the chrome window, because the
// content window timers are disabled while the debuggee is paused.
let count1 = 0;
let intervalID = window.setInterval(function(){
info("count1: " + count1);
if (++count1 > 50) {
ok(false, "Timed out while polling for the properties.");
window.clearInterval(intervalID);
return resumeAndFinish();
}
if (!buttonNode._retrieved ||
!buttonAsProtoNode._retrieved ||
!documentNode._retrieved) {
return;
}
window.clearInterval(intervalID);
// Test the prototypes of these objects.
is(buttonNode.get("__proto__").target.querySelector(".name")
.getAttribute("value"), "__proto__",
"Should have the right property name for '__proto__' in buttonNode.");
ok(buttonNode.get("__proto__").target.querySelector(".value")
.getAttribute("value").search(/object/) != -1,
"'__proto__' in buttonNode should be an object.");
is(buttonAsProtoNode.get("__proto__").target.querySelector(".name")
.getAttribute("value"), "__proto__",
"Should have the right property name for '__proto__' in buttonAsProtoNode.");
ok(buttonAsProtoNode.get("__proto__").target.querySelector(".value")
.getAttribute("value").search(/object/) != -1,
"'__proto__' in buttonAsProtoNode should be an object.");
is(documentNode.get("__proto__").target.querySelector(".name")
.getAttribute("value"), "__proto__",
"Should have the right property name for '__proto__' in documentNode.");
ok(documentNode.get("__proto__").target.querySelector(".value")
.getAttribute("value").search(/object/) != -1,
"'__proto__' in documentNode should be an object.");
let buttonProtoNode = buttonNode.get("__proto__");
let buttonAsProtoProtoNode = buttonAsProtoNode.get("__proto__");
let documentProtoNode = documentNode.get("__proto__");
is(buttonProtoNode.expanded, false,
"The buttonProtoNode should not be expanded at this point.");
is(buttonAsProtoProtoNode.expanded, false,
"The buttonAsProtoProtoNode should not be expanded at this point.");
is(documentProtoNode.expanded, false,
"The documentProtoNode should not be expanded at this point.");
// Expand the prototypes of 'button', 'buttonAsProto' and 'document'
// tree nodes. This causes their properties to be retrieved and
// displayed.
buttonProtoNode.expand();
buttonAsProtoProtoNode.expand();
documentProtoNode.expand();
is(buttonProtoNode.expanded, true,
"The buttonProtoNode should be expanded at this point.");
is(buttonAsProtoProtoNode.expanded, true,
"The buttonAsProtoProtoNode should be expanded at this point.");
is(documentProtoNode.expanded, true,
"The documentProtoNode should be expanded at this point.");
// Poll every few milliseconds until the properties are retrieved.
// It's important to set the timer in the chrome window, because the
// content window timers are disabled while the debuggee is paused.
let count2 = 0;
let intervalID1 = window.setInterval(function(){
info("count2: " + count2);
if (++count2 > 50) {
ok(false, "Timed out while polling for the properties.");
window.clearInterval(intervalID1);
return resumeAndFinish();
}
if (!buttonProtoNode._retrieved ||
!buttonAsProtoProtoNode._retrieved ||
!documentProtoNode._retrieved) {
return;
}
window.clearInterval(intervalID1);
// Now the main course: make sure that the native getters for WebIDL
// attributes have been called and a value has been returned.
is(buttonProtoNode.get("type").target.querySelector(".name")
.getAttribute("value"), "type",
"Should have the right property name for 'type' in buttonProtoNode.");
is(buttonProtoNode.get("type").target.querySelector(".value")
.getAttribute("value"), '"submit"',
"'type' in buttonProtoNode should have the right value.");
is(buttonProtoNode.get("formMethod").target.querySelector(".name")
.getAttribute("value"), "formMethod",
"Should have the right property name for 'formMethod' in buttonProtoNode.");
is(buttonProtoNode.get("formMethod").target.querySelector(".value")
.getAttribute("value"), '"get"',
"'formMethod' in buttonProtoNode should have the right value.");
is(documentProtoNode.get("baseURI").target.querySelector(".name")
.getAttribute("value"), "baseURI",
"Should have the right property name for 'baseURI' in documentProtoNode.");
is(documentProtoNode.get("baseURI").target.querySelector(".value")
.getAttribute("value"), '"' + TAB_URL + '"',
"'baseURI' in documentProtoNode should have the right value.");
is(documentProtoNode.get("URL").target.querySelector(".name")
.getAttribute("value"), "URL",
"Should have the right property name for 'URL' in documentProtoNode.");
is(documentProtoNode.get("URL").target.querySelector(".value")
.getAttribute("value"), '"' + TAB_URL + '"',
"'URL' in documentProtoNode should have the right value.");
let buttonAsProtoProtoProtoNode = buttonAsProtoProtoNode.get("__proto__");
is(buttonAsProtoProtoProtoNode.expanded, false,
"The buttonAsProtoProtoProtoNode should not be expanded at this point.");
// Expand the prototype of the prototype of 'buttonAsProto' tree
// node. This causes its properties to be retrieved and displayed.
buttonAsProtoProtoProtoNode.expand();
is(buttonAsProtoProtoProtoNode.expanded, true,
"The buttonAsProtoProtoProtoNode should be expanded at this point.");
// Poll every few milliseconds until the properties are retrieved.
// It's important to set the timer in the chrome window, because the
// content window timers are disabled while the debuggee is paused.
let count3 = 0;
let intervalID2 = window.setInterval(function(){
info("count3: " + count3);
if (++count3 > 50) {
ok(false, "Timed out while polling for the properties.");
window.clearInterval(intervalID2);
return resumeAndFinish();
}
if (!buttonAsProtoProtoProtoNode._retrieved) {
return;
}
window.clearInterval(intervalID2);
// Test this more involved case that reuses an object that is
// present in another cache line.
is(buttonAsProtoProtoProtoNode.get("type").target.querySelector(".name")
.getAttribute("value"), "type",
"Should have the right property name for 'type' in buttonAsProtoProtoProtoNode.");
is(buttonAsProtoProtoProtoNode.get("type").target.querySelector(".value")
.getAttribute("value"), '"submit"',
"'type' in buttonAsProtoProtoProtoNode should have the right value.");
is(buttonAsProtoProtoProtoNode.get("formMethod").target.querySelector(".name")
.getAttribute("value"), "formMethod",
"Should have the right property name for 'formMethod' in buttonAsProtoProtoProtoNode.");
is(buttonAsProtoProtoProtoNode.get("formMethod").target.querySelector(".value")
.getAttribute("value"), '"get"',
"'formMethod' in buttonAsProtoProtoProtoNode should have the right value.");
resumeAndFinish();
}, 100);
}, 100);
}, 100);
}}, 0);
}, false);
EventUtils.sendMouseEvent({ type: "click" },
content.document.querySelector("button"),
content.window);
}
function resumeAndFinish() {
gDebugger.addEventListener("Debugger:AfterFramesCleared", function listener() {
gDebugger.removeEventListener("Debugger:AfterFramesCleared", listener, true);
Services.tm.currentThread.dispatch({ run: function() {
let frames = gDebugger.DebuggerView.StackFrames._container._list;
is(frames.querySelectorAll(".dbg-stackframe").length, 0,
"Should have no frames.");
closeDebuggerAndFinish();
}}, 0);
}, true);
gDebugger.DebuggerController.activeThread.resume();
}
registerCleanupFunction(function() {
removeTab(gTab);
gPane = null;
gTab = null;
gDebugger = null;
});

View File

@ -12,6 +12,7 @@ Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js");
Cu.import("resource:///modules/devtools/EventEmitter.jsm");
Cu.import("resource:///modules/devtools/CssLogic.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "MarkupView",
"resource:///modules/devtools/MarkupView.jsm");
@ -645,6 +646,21 @@ InspectorPanel.prototype = {
}
},
/**
* Copy a unique selector of the selected Node to the clipboard.
*/
copyUniqueSelector: function InspectorPanel_copyUniqueSelector()
{
if (!this.selection.isNode()) {
return;
}
let toCopy = CssLogic.findCssSelector(this.selection.node);
if (toCopy) {
clipboardHelper.copyString(toCopy);
}
},
/**
* Delete the selected node.
*/

View File

@ -39,6 +39,10 @@
label="&inspectorHTMLCopyOuter.label;"
accesskey="&inspectorHTMLCopyOuter.accesskey;"
oncommand="inspector.copyOuterHTML()"/>
<menuitem id="node-menu-copyuniqueselector"
label="&inspectorCopyUniqueSelector.label;"
accesskey="&inspectorCopyUniqueSelector.accesskey;"
oncommand="inspector.copyUniqueSelector()"/>
<menuseparator/>
<menuitem id="node-menu-delete"
label="&inspectorHTMLDelete.label;"

View File

@ -56,6 +56,15 @@ function test() {
waitForClipboard("<p>This is some example text</p>",
function() { copyOuter.doCommand(); },
testCopyUniqueSelectorMenu, testCopyUniqueSelectorMenu);
}
function testCopyUniqueSelectorMenu() {
let copyUniqueSelector = inspector.panelDoc.getElementById("node-menu-copyuniqueselector");
ok(copyUniqueSelector, "the popup menu has a copy unique selector menu item");
waitForClipboard("body > div:nth-child(1) > p:nth-child(2)",
function() { copyUniqueSelector.doCommand(); },
testDeleteNode, testDeleteNode);
}

View File

@ -821,6 +821,80 @@ CssLogic.shortSource = function CssLogic_shortSource(aSheet)
return dataUrl ? dataUrl[1] : aSheet.href;
}
/**
* Find the position of [element] in [nodeList].
* @returns an index of the match, or -1 if there is no match
*/
function positionInNodeList(element, nodeList) {
for (var i = 0; i < nodeList.length; i++) {
if (element === nodeList[i]) {
return i;
}
}
return -1;
}
/**
* Find a unique CSS selector for a given element
* @returns a string such that ele.ownerDocument.querySelector(reply) === ele
* and ele.ownerDocument.querySelectorAll(reply).length === 1
*/
CssLogic.findCssSelector = function CssLogic_findCssSelector(ele) {
var document = ele.ownerDocument;
if (ele.id && document.getElementById(ele.id) === ele) {
return '#' + ele.id;
}
// Inherently unique by tag name
var tagName = ele.tagName.toLowerCase();
if (tagName === 'html') {
return 'html';
}
if (tagName === 'head') {
return 'head';
}
if (tagName === 'body') {
return 'body';
}
if (ele.parentNode == null) {
console.log('danger: ' + tagName);
}
// We might be able to find a unique class name
var selector, index, matches;
if (ele.classList.length > 0) {
for (var i = 0; i < ele.classList.length; i++) {
// Is this className unique by itself?
selector = '.' + ele.classList.item(i);
matches = document.querySelectorAll(selector);
if (matches.length === 1) {
return selector;
}
// Maybe it's unique with a tag name?
selector = tagName + selector;
matches = document.querySelectorAll(selector);
if (matches.length === 1) {
return selector;
}
// Maybe it's unique using a tag name and nth-child
index = positionInNodeList(ele, ele.parentNode.children) + 1;
selector = selector + ':nth-child(' + index + ')';
matches = document.querySelectorAll(selector);
if (matches.length === 1) {
return selector;
}
}
}
// So we can be unique w.r.t. our parent, and use recursion
index = positionInNodeList(ele, ele.parentNode.children) + 1;
selector = CssLogic_findCssSelector(ele.parentNode) + ' > ' +
tagName + ':nth-child(' + index + ')';
return selector;
};
/**
* A safe way to access cached bits of information about a stylesheet.
*

View File

@ -4,6 +4,9 @@
<!ENTITY inspectorHTMLCopyOuter.label "Copy Outer HTML">
<!ENTITY inspectorHTMLCopyOuter.accesskey "O">
<!ENTITY inspectorCopyUniqueSelector.label "Copy Unique Selector">
<!ENTITY inspectorCopyUniqueSelector.accesskey "U">
<!ENTITY inspectorHTMLDelete.label "Delete Node">
<!ENTITY inspectorHTMLDelete.accesskey "D">

View File

@ -2130,6 +2130,10 @@ html|*#gcli-output-frame {
background-color: transparent;
}
.gcli-panel {
padding: 0;
}
#gcli-output,
#gcli-tooltip {
border-width: 0;

View File

@ -72,6 +72,10 @@
font-size: 80%;
}
.gcli-row-out td {
white-space: nowrap;
}
.gcli-out-shortcut,
.gcli-help-synopsis {
padding: 0 3px;

View File

@ -174,8 +174,6 @@
}
.devtools-no-search-result {
transition-property: box-shadow, border-color, background-image;
transition-duration: 150ms;
box-shadow: inset 0 0 0 1px hsla(0,68%,6%,.35);
border-color: hsl(10,70%,40%) hsl(10,75%,37%) hsl(10,80%,35%) !important;
background-image: url(magnifying-glass.png), -moz-linear-gradient(hsla(1,16%,76%,.45), hsla(1,16%,76%,.75));

View File

@ -225,7 +225,7 @@
#inspector-searchbox:not([focused]):not([filled]) {
max-width: 20px !important;
-moz-padding-end: 6px;
-moz-padding-end: 5px;
-moz-padding-start: 22px;
background-position: 8px center, top left, top left;
}

View File

@ -55,15 +55,30 @@
background-position: center;
background-repeat: no-repeat;
}
.annotationHTML.task {
.annotation.task .annotationHTML {
background-image: url("chrome://browser/skin/devtools/orion-task.png");
}
.annotationHTML.breakpoint {
.annotation.breakpoint .annotationHTML {
background-image: url("chrome://browser/skin/devtools/orion-breakpoint.png");
}
.annotationHTML.debugLocation {
.annotation.debugLocation .annotationHTML {
background-image: url("chrome://browser/skin/devtools/orion-debug-location.png");
}
.annotation.breakpoint.debugLocation .annotationHTML,
.annotation.task.debugLocation .annotationHTML {
background-position: center, center;
background-repeat: no-repeat, no-repeat;
background-size: 75%, 100%;
}
.annotation.breakpoint.debugLocation .annotationHTML {
background-image: url("chrome://browser/skin/devtools/orion-debug-location.png"),
url("chrome://browser/skin/devtools/orion-breakpoint.png");
}
.annotation.task.debugLocation .annotationHTML {
background-image: url("chrome://browser/skin/devtools/orion-debug-location.png"),
url("chrome://browser/skin/devtools/orion-task.png");
}
/* Styles for the overview ruler */
.annotationOverview {

View File

@ -74,6 +74,10 @@
font-size: 80%;
}
.gcli-row-out td {
white-space: nowrap;
}
.gcli-out-shortcut,
.gcli-help-synopsis {
padding: 0 3px;

View File

@ -179,8 +179,6 @@
}
.devtools-no-search-result {
transition-property: box-shadow, border-color, background-image;
transition-duration: 150ms;
box-shadow: inset 0 0 0 1px hsla(0,68%,6%,.35);
border-color: hsl(10,70%,40%) hsl(10,75%,37%) hsl(10,80%,35%) !important;
background-image: url(magnifying-glass.png), -moz-linear-gradient(hsla(1,16%,76%,.45), hsla(1,16%,76%,.75));

View File

@ -55,15 +55,30 @@
background-position: center;
background-repeat: no-repeat;
}
.annotationHTML.task {
.annotation.task .annotationHTML {
background-image: url("chrome://browser/skin/devtools/orion-task.png");
}
.annotationHTML.breakpoint {
.annotation.breakpoint .annotationHTML {
background-image: url("chrome://browser/skin/devtools/orion-breakpoint.png");
}
.annotationHTML.debugLocation {
.annotation.debugLocation .annotationHTML {
background-image: url("chrome://browser/skin/devtools/orion-debug-location.png");
}
.annotation.breakpoint.debugLocation .annotationHTML,
.annotation.task.debugLocation .annotationHTML {
background-position: center, center;
background-repeat: no-repeat, no-repeat;
background-size: 75%, 100%;
}
.annotation.breakpoint.debugLocation .annotationHTML {
background-image: url("chrome://browser/skin/devtools/orion-debug-location.png"),
url("chrome://browser/skin/devtools/orion-breakpoint.png");
}
.annotation.task.debugLocation .annotationHTML {
background-image: url("chrome://browser/skin/devtools/orion-debug-location.png"),
url("chrome://browser/skin/devtools/orion-task.png");
}
/* Styles for the overview ruler */
.annotationOverview {

View File

@ -72,6 +72,10 @@
font-size: 80%;
}
.gcli-row-out td {
white-space: nowrap;
}
.gcli-out-shortcut,
.gcli-help-synopsis {
padding: 0 3px;

View File

@ -185,7 +185,6 @@
}
.devtools-no-search-result {
transition-property: box-shadow, border-color, background-image;
box-shadow: inset 0 0 0 1px hsla(0,68%,6%,.35);
border-color: hsl(10,70%,40%) hsl(10,75%,37%) hsl(10,80%,35%) !important;
background-image: url(magnifying-glass.png), -moz-linear-gradient(hsla(1,16%,76%,.45), hsla(1,16%,76%,.75));

View File

@ -55,15 +55,30 @@
background-position: center;
background-repeat: no-repeat;
}
.annotationHTML.task {
.annotation.task .annotationHTML {
background-image: url("chrome://browser/skin/devtools/orion-task.png");
}
.annotationHTML.breakpoint {
.annotation.breakpoint .annotationHTML {
background-image: url("chrome://browser/skin/devtools/orion-breakpoint.png");
}
.annotationHTML.debugLocation {
.annotation.debugLocation .annotationHTML {
background-image: url("chrome://browser/skin/devtools/orion-debug-location.png");
}
.annotation.breakpoint.debugLocation .annotationHTML,
.annotation.task.debugLocation .annotationHTML {
background-position: center, center;
background-repeat: no-repeat, no-repeat;
background-size: 75%, 100%;
}
.annotation.breakpoint.debugLocation .annotationHTML {
background-image: url("chrome://browser/skin/devtools/orion-debug-location.png"),
url("chrome://browser/skin/devtools/orion-breakpoint.png");
}
.annotation.task.debugLocation .annotationHTML {
background-image: url("chrome://browser/skin/devtools/orion-debug-location.png"),
url("chrome://browser/skin/devtools/orion-task.png");
}
/* Styles for the overview ruler */
.annotationOverview {

View File

@ -719,17 +719,8 @@ nsCacheProfilePrefObserver::ReadPrefs(nsIPrefBranch* branch)
if (!directory)
directory = profDir;
else if (profDir) {
bool same;
if (NS_SUCCEEDED(profDir->Equals(directory, &same)) && !same) {
// We no longer store the cache directory in the main
// profile directory, so we should cleanup the old one.
rv = profDir->AppendNative(NS_LITERAL_CSTRING("Cache"));
if (NS_SUCCEEDED(rv)) {
bool exists;
if (NS_SUCCEEDED(profDir->Exists(&exists)) && exists)
nsDeleteDir::DeleteDir(profDir, false);
}
}
nsCacheService::MoveOrRemoveDiskCache(profDir, directory,
"Cache");
}
}
// use file cache in build tree only if asked, to avoid cache dir litter
@ -795,6 +786,10 @@ nsCacheProfilePrefObserver::ReadPrefs(nsIPrefBranch* branch)
getter_AddRefs(directory));
if (!directory)
directory = profDir;
else if (profDir) {
nsCacheService::MoveOrRemoveDiskCache(profDir, directory,
"OfflineCache");
}
}
#if DEBUG
if (!directory) {
@ -3008,6 +3003,57 @@ nsCacheService::SetDiskSmartSize_Locked()
return NS_OK;
}
void
nsCacheService::MoveOrRemoveDiskCache(nsIFile *aOldCacheDir,
nsIFile *aNewCacheDir,
const char *aCacheSubdir)
{
bool same;
if (NS_FAILED(aOldCacheDir->Equals(aNewCacheDir, &same)) || same)
return;
nsCOMPtr<nsIFile> aOldCacheSubdir;
aOldCacheDir->Clone(getter_AddRefs(aOldCacheSubdir));
nsresult rv = aOldCacheSubdir->AppendNative(
nsDependentCString(aCacheSubdir));
if (NS_FAILED(rv))
return;
bool exists;
if (NS_FAILED(aOldCacheSubdir->Exists(&exists)) || !exists)
return;
nsCOMPtr<nsIFile> aNewCacheSubdir;
aNewCacheDir->Clone(getter_AddRefs(aNewCacheSubdir));
rv = aNewCacheSubdir->AppendNative(nsDependentCString(aCacheSubdir));
if (NS_FAILED(rv))
return;
nsAutoCString newPath;
rv = aNewCacheSubdir->GetNativePath(newPath);
if (NS_FAILED(rv))
return;
if (NS_SUCCEEDED(aNewCacheSubdir->Exists(&exists)) && !exists) {
// New cache directory does not exist, try to move the old one here
// rename needs an empty target directory
rv = aNewCacheSubdir->Create(nsIFile::DIRECTORY_TYPE, 0777);
if (NS_SUCCEEDED(rv)) {
nsAutoCString oldPath;
rv = aOldCacheSubdir->GetNativePath(oldPath);
if (NS_FAILED(rv))
return;
if(rename(oldPath.get(), newPath.get()) == 0)
return;
}
}
// Delay delete by 1 minute to avoid IO thrash on startup.
nsDeleteDir::DeleteDir(aOldCacheSubdir, false, 60000);
}
static bool
IsEntryPrivate(nsCacheEntry* entry)
{

View File

@ -196,6 +196,10 @@ public:
// Starts smart cache size computation if disk device is available
static nsresult SetDiskSmartSize();
static void MoveOrRemoveDiskCache(nsIFile *aOldCacheDir,
nsIFile *aNewCacheDir,
const char *aCacheSubdir);
nsresult Init();
void Shutdown();

View File

@ -181,6 +181,20 @@ StartupCache::Init()
return rv;
}
nsCOMPtr<nsIFile> profDir;
NS_GetSpecialDirectory("ProfDS", getter_AddRefs(profDir));
if (profDir) {
bool same;
if (NS_SUCCEEDED(profDir->Equals(file, &same)) && !same) {
// We no longer store the startup cache in the main profile
// directory, so we should cleanup the old one.
if (NS_SUCCEEDED(
profDir->AppendNative(NS_LITERAL_CSTRING("startupCache")))) {
profDir->Remove(true);
}
}
}
rv = file->AppendNative(NS_LITERAL_CSTRING("startupCache"));
NS_ENSURE_SUCCESS(rv, rv);

View File

@ -53,6 +53,17 @@ function ThreadActor(aHooks, aGlobal)
*/
this._scripts = {};
// A cache of prototype chains for objects that have received a
// prototypeAndProperties request. Due to the way the debugger frontend works,
// this corresponds to a cache of prototype chains that the user has been
// inspecting in the variables tree view. This allows the debugger to evaluate
// native getter methods for WebIDL attributes that are meant to be called on
// the instace and not on the prototype.
//
// The map keys are Debugger.Object instances requested by the client and the
// values are arrays of Debugger.Objects that make up their prototype chain.
this._protoChains = new Map();
this.findGlobals = this.globalManager.findGlobals.bind(this);
this.onNewGlobal = this.globalManager.onNewGlobal.bind(this);
}
@ -182,6 +193,7 @@ ThreadActor.prototype = {
this._state = "exited";
this._protoChains.clear();
this.clearDebuggees();
if (!this.dbg) {
@ -1223,6 +1235,51 @@ ThreadActor.prototype = {
}
}
return true;
},
/**
* Finds the prototype chain cache for the provided object and returns the
* full cache entry, or null if the object is not found in the cache.
*
* @param aObject Debugger.Object
* The object to look up.
* @returns the array of objects that correspond to the found cache entry.
*/
_findProtoChain: function TA__findProtoChain(aObject) {
if (this._protoChains.has(aObject)) {
return this._protoChains.get(aObject);
}
for (let [obj, chain] of this._protoChains) {
if (chain.indexOf(aObject) != -1) {
return chain;
}
}
return null;
},
/**
* Removes the specified object and its prototype chain from the prototype
* chain cache. Returns true if the removal was successful and false if the
* object was not found in the cache.
*
* @param aObject Debugger.Object
* The object to remove from the cache.
* @returns true if the object was removed, false if it was not found.
*/
_removeFromProtoChain:function TA__removeFromProtoChain(aObject) {
let retval = false;
if (this._protoChains.has(aObject)) {
this._protoChains.delete(aObject);
retval = true;
}
for (let [obj, chain] of this._protoChains) {
let index = chain.indexOf(aObject);
if (index != -1) {
chain.splice(index);
retval = true;
}
}
return retval;
}
};
@ -1532,6 +1589,11 @@ update(ObjectActor.prototype, {
release: function OA_release() {
this.registeredPool.objectActors.delete(this.obj);
this.registeredPool.removeActor(this);
this.disconnect();
},
disconnect: function OA_disconnect() {
this.threadActor._removeFromProtoChain(this.obj);
},
/**
@ -1556,17 +1618,27 @@ update(ObjectActor.prototype, {
*/
onPrototypeAndProperties:
PauseScopedActor.withPaused(function OA_onPrototypeAndProperties(aRequest) {
let ownProperties = {};
for each (let name in this.obj.getOwnPropertyNames()) {
try {
let desc = this.obj.getOwnPropertyDescriptor(name);
ownProperties[name] = this._propertyDescriptor(desc);
} catch (e if e.name == "NS_ERROR_XPC_BAD_OP_ON_WN_PROTO") {
// Calling getOwnPropertyDescriptor on wrapped native prototypes is not
// allowed.
dumpn("Error while getting the property descriptor for " + name +
": " + e.name);
if (this.obj.proto) {
// Store the object and its prototype to the prototype chain cache, so that
// we can evaluate native getter methods for WebIDL attributes that are
// meant to be called on the instace and not on the prototype.
//
// TODO: after bug 801084, we could restrict the cache to objects where
// this.obj.hostAnnotations.isWebIDLObject == true
let chain = this.threadActor._findProtoChain(this.obj);
if (!chain) {
chain = [];
this.threadActor._protoChains.set(this.obj, chain);
chain.push(this.obj);
}
if (chain.indexOf(this.obj.proto) == -1) {
chain.push(this.obj.proto);
}
}
let ownProperties = {};
for (let name of this.obj.getOwnPropertyNames()) {
ownProperties[name] = this._propertyDescriptor(name);
}
return { from: this.actorID,
prototype: this.threadActor.createValueGrip(this.obj.proto),
@ -1597,30 +1669,88 @@ update(ObjectActor.prototype, {
message: "no property name was specified" };
}
let desc = this.obj.getOwnPropertyDescriptor(aRequest.name);
return { from: this.actorID,
descriptor: this._propertyDescriptor(desc) };
descriptor: this._propertyDescriptor(aRequest.name) };
}),
/**
* A helper method that creates a property descriptor for the provided object,
* properly formatted for sending in a protocol response.
*
* @param aObject object
* The object that the descriptor is generated for.
* @param string aName
* The property that the descriptor is generated for.
*/
_propertyDescriptor: function OA_propertyDescriptor(aObject) {
let descriptor = {};
descriptor.configurable = aObject.configurable;
descriptor.enumerable = aObject.enumerable;
if (aObject.value !== undefined) {
descriptor.writable = aObject.writable;
descriptor.value = this.threadActor.createValueGrip(aObject.value);
} else {
descriptor.get = this.threadActor.createValueGrip(aObject.get);
descriptor.set = this.threadActor.createValueGrip(aObject.set);
_propertyDescriptor: function OA_propertyDescriptor(aName) {
let desc;
try {
desc = this.obj.getOwnPropertyDescriptor(aName);
} catch (e) {
// Calling getOwnPropertyDescriptor on wrapped native prototypes is not
// allowed (bug 560072). Inform the user with a bogus, but hopefully
// explanatory, descriptor.
return {
configurable: false,
writable: false,
enumerable: false,
value: e.name
};
}
return descriptor;
let retval = {
configurable: desc.configurable,
enumerable: desc.enumerable
};
if (desc.value !== undefined) {
retval.writable = desc.writable;
retval.value = this.threadActor.createValueGrip(desc.value);
} else {
if ("get" in desc) {
let fn = desc.get;
if (fn && fn.callable && fn.class == "Function" &&
fn.script === undefined) {
// Maybe this is a DOM getter. Try calling it on every object in the
// prototype chain, until it doesn't throw.
let rv, chain = this.threadActor._findProtoChain(this.obj);
let index = chain.indexOf(this.obj);
for (let i = index; i >= 0; i--) {
// If we had hostAnnotations (bug 801084) we would have been able to
// filter on chain[i].hostAnnotations.isWebIDLObject or similar.
rv = fn.call(chain[i]);
// If the error D.O. wasn't completely opaque (bug 812764?), we
// could perhaps treat other errors differently.
if (rv && !("throw" in rv)) {
// If calling the getter produced a return value, create a data
// property descriptor.
if ("return" in rv) {
retval.value = this.threadActor.createValueGrip(rv.return);
} else if ("yield" in rv) {
retval.value = this.threadActor.createValueGrip(rv.yield);
}
break;
}
}
// If calling the getter didn't produce a data property descriptor,
// use the original accessor property descriptor.
if (!("value" in retval)) {
retval.get = this.threadActor.createValueGrip(fn);
}
} else {
// It doesn't look like a WebIDL attribute getter, just use the getter
// from the original accessor property descriptor.
retval.get = this.threadActor.createValueGrip(fn);
}
}
// If we couldn't convert it to a data property and there is a setter in
// the original property descriptor, use it.
if ("set" in desc && !("value" in retval)) {
retval.set = this.threadActor.createValueGrip(desc.set);
}
}
return retval;
},
/**

View File

@ -1142,8 +1142,22 @@ nsXREDirProvider::GetUserDataDirectoryHome(nsIFile** aFile, bool aLocal)
if (!homeDir || !*homeDir)
return NS_ERROR_FAILURE;
rv = NS_NewNativeLocalFile(nsDependentCString(homeDir), true,
getter_AddRefs(localDir));
if (aLocal) {
// If $XDG_CACHE_HOME is defined use it, otherwise use $HOME/.cache.
const char* cacheHome = getenv("XDG_CACHE_HOME");
if (cacheHome && *cacheHome) {
rv = NS_NewNativeLocalFile(nsDependentCString(cacheHome), true,
getter_AddRefs(localDir));
} else {
rv = NS_NewNativeLocalFile(nsDependentCString(homeDir), true,
getter_AddRefs(localDir));
if (NS_SUCCEEDED(rv))
rv = localDir->AppendNative(NS_LITERAL_CSTRING(".cache"));
}
} else {
rv = NS_NewNativeLocalFile(nsDependentCString(homeDir), true,
getter_AddRefs(localDir));
}
#else
#error "Don't know how to get product dir on your platform"
#endif
@ -1228,7 +1242,7 @@ nsXREDirProvider::GetUserDataDirectory(nsIFile** aFile, bool aLocal,
nsresult rv = GetUserDataDirectoryHome(getter_AddRefs(localDir), aLocal);
NS_ENSURE_SUCCESS(rv, rv);
rv = AppendProfilePath(localDir, aProfileName, aAppName, aVendorName);
rv = AppendProfilePath(localDir, aProfileName, aAppName, aVendorName, aLocal);
NS_ENSURE_SUCCESS(rv, rv);
#ifdef DEBUG_jungshik
@ -1349,7 +1363,8 @@ nsresult
nsXREDirProvider::AppendProfilePath(nsIFile* aFile,
const nsACString* aProfileName,
const nsACString* aAppName,
const nsACString* aVendorName)
const nsACString* aVendorName,
PRBool aLocal)
{
NS_ASSERTION(aFile, "Null pointer!");
@ -1411,8 +1426,11 @@ nsXREDirProvider::AppendProfilePath(nsIFile* aFile,
rv = aFile->AppendNative(nsDependentCString("mozilla"));
NS_ENSURE_SUCCESS(rv, rv);
#elif defined(XP_UNIX)
// Make it hidden (i.e. using the ".")
nsAutoCString folder(".");
nsAutoCString folder;
// Make it hidden (by starting with "."), except when local (the
// profile is already under ~/.cache or XDG_CACHE_HOME).
if (!aLocal)
folder.Assign('.');
if (!profile.IsEmpty()) {
// Skip any leading path characters
@ -1422,7 +1440,7 @@ nsXREDirProvider::AppendProfilePath(nsIFile* aFile,
// On the off chance that someone wanted their folder to be hidden don't
// let it become ".."
if (*profileStart == '.')
if (*profileStart == '.' && !aLocal)
profileStart++;
folder.Append(profileStart);

View File

@ -114,7 +114,8 @@ protected:
static nsresult AppendProfilePath(nsIFile* aFile,
const nsACString* aProfileName,
const nsACString* aAppName,
const nsACString* aVendorName);
const nsACString* aVendorName,
PRBool aLocal);
static nsresult AppendSysUserExtensionPath(nsIFile* aFile);