Merge f-t to m-c

This commit is contained in:
Phil Ringnalda 2013-10-25 19:24:55 -07:00
commit cef532b380
40 changed files with 1063 additions and 316 deletions

View File

@ -179,18 +179,3 @@ function MixedTest6D() {
ok(content.document.getElementById('f1').contentDocument.getElementById('p1').innerHTML == "hello","Mixed script didn't load in Test 6");
MixedTestsCompleted();
}
function waitForCondition(condition, nextTest, errorMsg) {
var tries = 0;
var interval = setInterval(function() {
if (tries >= 30) {
ok(false, errorMsg);
moveOn();
}
if (condition()) {
moveOn();
}
tries++;
}, 100);
var moveOn = function() { clearInterval(interval); nextTest(); };
}

View File

@ -41,26 +41,6 @@ function cleanUpAfterTests() {
window.focus();
finish();
}
/*
* Whenever we disable the Mixed Content Blocker of the page
* we have to make sure that our condition is properly loaded.
*/
function waitForCondition(condition, nextTest, errorMsg) {
var tries = 0;
var interval = setInterval(function() {
if (tries >= 30) {
ok(false, errorMsg);
moveOn();
}
if (condition()) {
moveOn();
}
tries++;
}, 100);
var moveOn = function() {
clearInterval(interval); nextTest();
};
}
//------------------------ Test 1 ------------------------------

View File

@ -89,7 +89,14 @@ function waitForCondition(condition, nextTest, errorMsg) {
ok(false, errorMsg);
moveOn();
}
if (condition()) {
var conditionPassed;
try {
conditionPassed = condition();
} catch (e) {
ok(false, e + "\n" + e.stack);
conditionPassed = false;
}
if (conditionPassed) {
moveOn();
}
tries++;

View File

@ -18,7 +18,14 @@ function waitForCondition(condition, nextTest, errorMsg) {
ok(false, errorMsg);
moveOn();
}
if (condition()) {
var conditionPassed;
try {
conditionPassed = condition();
} catch (e) {
ok(false, e + "\n" + e.stack);
conditionPassed = false;
}
if (conditionPassed) {
moveOn();
}
tries++;

View File

@ -102,6 +102,7 @@
<button class="action-primary" onclick="UI.installSimulator()" title="&connection.installOneSimulatorTooltip;">&connection.installOneSimulator;</button>
</div>
<div class="found-simulator">
<span>&connection.startRegisteredSimulator;</span>
<span template-loop='{"arrayPath":"simulators.versions","childSelector":"#simulator-item-template"}'></span>
<button class="action-primary" onclick="UI.installSimulator()" title="&connection.installAnotherSimulatorTooltip;">&connection.installAnotherSimulator;</button>
</div>
@ -124,7 +125,7 @@
<template id="simulator-item-template">
<span>
<button class="simulator-item" onclick="UI.startSimulator(this.dataset.version)" template='{"type":"attribute","path":"version","name":"data-version"}' title="&connection.startSimulatorTooltip;">
<button class="simulator-item action-primary" onclick="UI.startSimulator(this.dataset.version)" template='{"type":"attribute","path":"version","name":"data-version"}' title="&connection.startSimulatorTooltip;">
<span template='{"type":"textContent", "path":"version"}'></span>
</button>
</span>

View File

@ -21,6 +21,7 @@ const {gDevTools} = Cu.import("resource:///modules/devtools/gDevTools.jsm", {});
const {HTMLEditor} = require("devtools/markupview/html-editor");
const {OutputParser} = require("devtools/output-parser");
const promise = require("sdk/core/promise");
const {Tooltip} = require("devtools/shared/widgets/Tooltip");
Cu.import("resource://gre/modules/devtools/LayoutHelpers.jsm");
Cu.import("resource://gre/modules/devtools/Templater.jsm");
@ -374,7 +375,7 @@ MarkupView.prototype = {
this._elt.appendChild(container.elt);
this._rootNode = aNode;
} else {
var container = new MarkupContainer(this, aNode);
var container = new MarkupContainer(this, aNode, this._inspector);
if (aFlashNode) {
container.flashMutation();
}
@ -1046,12 +1047,15 @@ MarkupView.prototype = {
* The markup view that owns this container.
* @param DOMNode aNode
* The node to display.
* @param Inspector aInspector
* The inspector tool container the markup-view
*/
function MarkupContainer(aMarkupView, aNode) {
function MarkupContainer(aMarkupView, aNode, aInspector) {
this.markup = aMarkupView;
this.doc = this.markup.doc;
this.undo = this.markup.undo;
this.node = aNode;
this._inspector = aInspector;
if (aNode.nodeType == Ci.nsIDOMNode.TEXT_NODE) {
this.editor = new TextEditor(this, aNode, "text");
@ -1094,6 +1098,9 @@ function MarkupContainer(aMarkupView, aNode) {
this._onMouseDown = this._onMouseDown.bind(this);
this.elt.addEventListener("mousedown", this._onMouseDown, false);
this.tooltip = null;
this._attachTooltipIfNeeded();
}
MarkupContainer.prototype = {
@ -1101,6 +1108,39 @@ MarkupContainer.prototype = {
return "[MarkupContainer for " + this.node + "]";
},
_attachTooltipIfNeeded: function() {
if (this.node.tagName) {
let tagName = this.node.tagName.toLowerCase();
let isImage = tagName === "img" &&
this.editor.getAttributeElement("src");
let isCanvas = tagName && tagName === "canvas";
// Get the image data for later so that when the user actually hovers over
// the element, the tooltip does contain the image
if (isImage || isCanvas) {
this.tooltip = new Tooltip(this._inspector.panelDoc);
this.node.getImageData().then(data => {
if (data) {
data.string().then(str => {
this.tooltip.setImageContent(str);
});
}
});
}
// If it's an image, show the tooltip on the src attribute
if (isImage) {
this.tooltip.startTogglingOnHover(this.editor.getAttributeElement("src"));
}
// If it's a canvas, show it on the tag
if (isCanvas) {
this.tooltip.startTogglingOnHover(this.editor.tag);
}
}
},
/**
* True if the current node has children. The MarkupView
* will set this attribute for the MarkupContainer.
@ -1335,6 +1375,12 @@ MarkupContainer.prototype = {
// Destroy my editor
this.editor.destroy();
// Destroy the tooltip if any
if (this.tooltip) {
this.tooltip.destroy();
this.tooltip = null;
}
}
};
@ -1348,7 +1394,7 @@ function RootContainer(aMarkupView, aNode) {
this.elt.container = this;
this.children = this.elt;
this.node = aNode;
this.toString = function() { return "[root container]"}
this.toString = () => "[root container]";
}
RootContainer.prototype = {
@ -1578,6 +1624,16 @@ ElementEditor.prototype = {
return this.node.startModifyingAttributes();
},
/**
* Get the element used for one of the attributes of this element
* @param string attrName The name of the attribute to get the element for
* @return DOMElement
*/
getAttributeElement: function(attrName) {
return this.attrList.querySelector(
".attreditor[data-attr=" + attrName + "] .attr-value");
},
_createAttribute: function(aAttr, aBefore = null) {
// Create the template editor, which will save some variables here.
let data = {

View File

@ -15,3 +15,5 @@ skip-if = true
[browser_inspector_markup_navigation.js]
[browser_inspector_markup_subset.html]
[browser_inspector_markup_subset.js]
[browser_inspector_markup_765105_tooltip.js]
[browser_inspector_markup_765105_tooltip.png]

View File

@ -0,0 +1,137 @@
/* vim: set ts=2 et sw=2 tw=80: */
/* 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;
const PAGE_CONTENT = [
'<img class="local" src="chrome://branding/content/about-logo.png" />',
'<img class="data" src="data:image/png;base64,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" />',
'<img class="remote" src="http://mochi.test:8888/browser/browser/devtools/markupview/test/browser_inspector_markup_765105_tooltip.png" />',
'<canvas class="canvas" width="600" height="600"></canvas>'
].join("\n");
const TEST_NODES = [
"img.local",
"img.data",
"img.remote",
".canvas"
];
function test() {
waitForExplicitFinish();
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);
content.location = "data:text/html,markup view tooltip test";
}
function createDocument() {
contentDoc.body.innerHTML = PAGE_CONTENT;
var target = TargetFactory.forTab(gBrowser.selectedTab);
gDevTools.showToolbox(target, "inspector").then(function(toolbox) {
inspector = toolbox.getCurrentPanel();
markup = inspector.markup;
startTests();
});
}
function startTests() {
// Draw something in the canvas :)
let doc = content.document;
let context = doc.querySelector(".canvas").getContext("2d");
context.beginPath();
context.moveTo(300, 0);
context.lineTo(600, 600);
context.lineTo(0, 600);
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]);
ok(node, "We have the [" + TEST_NODES[index] + "] image node to test for tooltip");
let isImg = node.tagName.toLowerCase() === "img";
let container = getContainerForRawNode(markup, node);
let target = container.editor.tag;
if (isImg) {
target = container.editor.getAttributeElement("src");
}
assertTooltipShownOn(container.tooltip, target, () => {
let images = container.tooltip.panel.getElementsByTagName("image");
is(images.length, 1, "Tooltip for [" + TEST_NODES[index] + "] contains an image");
if (isImg) {
compareImageData(node, images[0].src);
}
container.tooltip.hide();
testImageTooltip(index + 1);
});
}
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(tooltip, element, cb) {
// If there is indeed a show-on-hover on element, the xul panel will be shown
tooltip.panel.addEventListener("popupshown", function shown() {
tooltip.panel.removeEventListener("popupshown", shown, true);
// Poll until the image gets loaded in the tooltip. This is required because
// markup containers only load images in their associated tooltips when
// the image data comes back from the server. However, this test is executed
// synchronously as soon as "inspector-updated" is fired, which is before
// the data for images is known.
let hasImage = () => tooltip.panel.getElementsByTagName("image").length;
let poll = setInterval(() => {
if (hasImage()) {
clearInterval(poll);
cb();
}
}, 200);
}, true);
tooltip._showOnHover(element);
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -9,3 +9,4 @@ libs::
$(NSINSTALL) $(srcdir)/*.jsm $(FINAL_TARGET)/modules/devtools
$(NSINSTALL) $(srcdir)/widgets/*.jsm $(FINAL_TARGET)/modules/devtools
$(NSINSTALL) $(srcdir)/*.js $(FINAL_TARGET)/modules/devtools/shared
$(NSINSTALL) $(srcdir)/widgets/*.js $(FINAL_TARGET)/modules/devtools/shared/widgets

View File

@ -0,0 +1,420 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const {Cc, Cu, Ci} = require("chrome");
const promise = require("sdk/core/promise");
const IOService = Cc["@mozilla.org/network/io-service;1"]
.getService(Ci.nsIIOService);
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource:///modules/devtools/ViewHelpers.jsm");
const GRADIENT_RE = /\b(repeating-)?(linear|radial)-gradient\(((rgb|hsl)a?\(.+?\)|[^\)])+\)/gi;
const BORDERCOLOR_RE = /^border-[-a-z]*color$/ig;
const BORDER_RE = /^border(-(top|bottom|left|right))?$/ig;
const BACKGROUND_IMAGE_RE = /url\([\'\"]?(.*?)[\'\"]?\)/;
/**
* Tooltip widget.
*
* This widget is intended at any tool that may need to show rich content in the
* form of floating panels.
* A common use case is image previewing in the CSS rule view, but more complex
* use cases may include color pickers, object inspection, etc...
*
* Tooltips are based on XUL (namely XUL arrow-type <panel>s), and therefore
* need a XUL Document to live in.
* This is pretty much the only requirement they have on their environment.
*
* The way to use a tooltip is simply by instantiating a tooltip yourself and
* attaching some content in it, or using one of the ready-made content types.
*
* A convenient `startTogglingOnHover` method may avoid having to register event
* handlers yourself if the tooltip has to be shown when hovering over a
* specific element or group of elements (which is usually the most common case)
*/
/**
* The low level structure of a tooltip is a XUL element (a <panel>, although
* <tooltip> is supported too, it won't have the nice arrow shape).
*/
let PanelFactory = {
get: function(doc, xulTag="panel") {
// Create the tooltip
let panel = doc.createElement(xulTag);
panel.setAttribute("hidden", true);
if (xulTag === "panel") {
// Prevent the click used to close the panel from being consumed
panel.setAttribute("consumeoutsideclicks", false);
panel.setAttribute("type", "arrow");
panel.setAttribute("level", "top");
}
panel.setAttribute("class", "devtools-tooltip devtools-tooltip-" + xulTag);
doc.querySelector("window").appendChild(panel);
return panel;
}
};
/**
* Tooltip class.
*
* Basic usage:
* let t = new Tooltip(xulDoc);
* t.content = someXulContent;
* t.show();
* t.hide();
* t.destroy();
*
* Better usage:
* let t = new Tooltip(xulDoc);
* t.startTogglingOnHover(container, target => {
* if (<condition based on target>) {
* t.setImageContent("http://image.png");
* return true;
* }
* });
* t.destroy();
*
* @param XULDocument doc
* The XUL document hosting this tooltip
*/
function Tooltip(doc) {
this.doc = doc;
this.panel = PanelFactory.get(doc);
// Used for namedTimeouts in the mouseover handling
this.uid = "tooltip-" + Date.now();
}
module.exports.Tooltip = Tooltip;
Tooltip.prototype = {
/**
* Show the tooltip. It might be wise to append some content first if you
* don't want the tooltip to be empty. You may access the content of the
* tooltip by setting a XUL node to t.tooltip.content.
* @param {node} anchor
* Which node should the tooltip be shown on
* @param {string} position
* https://developer.mozilla.org/en-US/docs/XUL/PopupGuide/Positioning
* Defaults to before_start
*/
show: function(anchor, position="before_start") {
this.panel.hidden = false;
this.panel.openPopup(anchor, position);
},
/**
* Hide the tooltip
*/
hide: function() {
this.panel.hidden = true;
this.panel.hidePopup();
},
/**
* Empty the tooltip's content
*/
empty: function() {
while (this.panel.hasChildNodes()) {
this.panel.removeChild(this.panel.firstChild);
}
},
/**
* Get rid of references and event listeners
*/
destroy: function () {
this.hide();
this.content = null;
this.doc = null;
this.panel.parentNode.removeChild(this.panel);
this.panel = null;
if (this._basedNode) {
this.stopTogglingOnHover();
}
},
/**
* Show/hide the tooltip when the mouse hovers over particular nodes.
*
* 2 Ways to make this work:
* - Provide a single node to attach the tooltip to, as the baseNode, and
* omit the second targetNodeCb argument
* - Provide a baseNode that is the container of possibly numerous children
* elements that may receive a tooltip. In this case, provide the second
* targetNodeCb argument to decide wether or not a child should receive
* a tooltip.
*
* This works by tracking mouse movements on a base container node (baseNode)
* and showing the tooltip when the mouse stops moving. The targetNodeCb
* callback is used to know whether or not the particular element being
* hovered over should indeed receive the tooltip. If you don't provide it
* it's equivalent to a function that always returns true.
*
* Note that if you call this function a second time, it will itself call
* stopTogglingOnHover before adding mouse tracking listeners again.
*
* @param {node} baseNode
* The container for all target nodes
* @param {Function} targetNodeCb
* A function that accepts a node argument and returns true or false
* to signify if the tooltip should be shown on that node or not.
* Additionally, the function receives a second argument which is the
* tooltip instance itself, to be used to add/modify the content of the
* tooltip if needed. If omitted, the tooltip will be shown everytime.
* @param {Number} showDelay
* An optional delay that will be observed before showing the tooltip.
* Defaults to 750ms
*/
startTogglingOnHover: function(baseNode, targetNodeCb, showDelay = 750) {
if (this._basedNode) {
this.stopTogglingOnHover();
}
this._basedNode = baseNode;
this._showDelay = showDelay;
this._targetNodeCb = targetNodeCb || (() => true);
this._onBaseNodeMouseMove = this._onBaseNodeMouseMove.bind(this);
this._onBaseNodeMouseLeave = this._onBaseNodeMouseLeave.bind(this);
baseNode.addEventListener("mousemove", this._onBaseNodeMouseMove, false);
baseNode.addEventListener("mouseleave", this._onBaseNodeMouseLeave, false);
},
/**
* If the startTogglingOnHover function has been used previously, and you want
* to get rid of this behavior, then call this function to remove the mouse
* movement tracking
*/
stopTogglingOnHover: function() {
clearNamedTimeout(this.uid);
this._basedNode.removeEventListener("mousemove",
this._onBaseNodeMouseMove, false);
this._basedNode.removeEventListener("mouseleave",
this._onBaseNodeMouseLeave, false);
this._basedNode = null;
this._targetNodeCb = null;
this._lastHovered = null;
},
_onBaseNodeMouseMove: function(event) {
if (event.target !== this._lastHovered) {
this.hide();
this._lastHovered = null;
setNamedTimeout(this.uid, this._showDelay, () => {
this._showOnHover(event.target);
});
}
},
_showOnHover: function(target) {
if (this._targetNodeCb && this._targetNodeCb(target, this)) {
this.show(target);
this._lastHovered = target;
}
},
_onBaseNodeMouseLeave: function() {
clearNamedTimeout(this.uid);
this._lastHovered = null;
},
/**
* Set the content of this tooltip. Will first empty the tooltip and then
* append the new content element.
* Consider using one of the set<type>Content() functions instead.
* @param {node} content
* A node that can be appended in the tooltip XUL element
*/
set content(content) {
this.empty();
if (content) {
this.panel.appendChild(content);
}
},
get content() {
return this.panel.firstChild;
},
/**
* Fill the tooltip with an image, displayed over a tiled background useful
* for transparent images.
* Also adds the image dimension as a label at the bottom.
*/
setImageContent: function(imageUrl, maxDim=400) {
// Main container
let vbox = this.doc.createElement("vbox");
vbox.setAttribute("align", "center")
// Transparency tiles (image will go in there)
let tiles = createTransparencyTiles(this.doc, vbox);
// Temporary label during image load
let label = this.doc.createElement("label");
label.classList.add("devtools-tooltip-caption");
label.textContent = l10n.strings.GetStringFromName("previewTooltip.image.brokenImage");
vbox.appendChild(label);
// Display the image
let image = this.doc.createElement("image");
image.setAttribute("src", imageUrl);
if (maxDim) {
image.style.maxWidth = maxDim + "px";
image.style.maxHeight = maxDim + "px";
}
tiles.appendChild(image);
this.content = vbox;
// Load the image to get dimensions and display it when done
let imgObj = new this.doc.defaultView.Image();
imgObj.src = imageUrl;
imgObj.onload = () => {
imgObj.onload = null;
// Display dimensions
label.textContent = imgObj.naturalWidth + " x " + imgObj.naturalHeight;
if (imgObj.naturalWidth > maxDim ||
imgObj.naturalHeight > maxDim) {
label.textContent += " *";
}
}
},
/**
* Exactly the same as the `image` function but takes a css background image
* value instead : url(....)
*/
setCssBackgroundImageContent: function(cssBackground, sheetHref, maxDim=400) {
let uri = getBackgroundImageUri(cssBackground, sheetHref);
if (uri) {
this.setImageContent(uri, maxDim);
}
},
setCssGradientContent: function(cssGradient) {
let tiles = createTransparencyTiles(this.doc);
let gradientBox = this.doc.createElement("box");
gradientBox.width = "100";
gradientBox.height = "100";
gradientBox.style.background = this.cssGradient;
gradientBox.style.borderRadius = "2px";
gradientBox.style.boxShadow = "inset 0 0 4px #333";
tiles.appendChild(gradientBox)
this.content = tiles;
},
_setSimpleCssPropertiesContent: function(properties, width, height) {
let tiles = createTransparencyTiles(this.doc);
let box = this.doc.createElement("box");
box.width = width + "";
box.height = height + "";
properties.forEach(({name, value}) => {
box.style[name] = value;
});
tiles.appendChild(box);
this.content = tiles;
},
setCssColorContent: function(cssColor) {
this._setSimpleCssPropertiesContent([
{name: "background", value: cssColor},
{name: "borderRadius", value: "2px"},
{name: "boxShadow", value: "inset 0 0 4px #333"},
], 50, 50);
},
setCssBoxShadowContent: function(cssBoxShadow) {
this._setSimpleCssPropertiesContent([
{name: "background", value: "white"},
{name: "boxShadow", value: cssBoxShadow}
], 80, 80);
},
setCssBorderContent: function(cssBorder) {
this._setSimpleCssPropertiesContent([
{name: "background", value: "white"},
{name: "border", value: cssBorder}
], 80, 80);
}
};
/**
* Internal utility function that creates a tiled background useful for
* displaying semi-transparent images
*/
function createTransparencyTiles(doc, parentEl) {
let tiles = doc.createElement("box");
tiles.classList.add("devtools-tooltip-tiles");
if (parentEl) {
parentEl.appendChild(tiles);
}
return tiles;
}
/**
* Internal util, checks whether a css declaration is a gradient
*/
function isGradientRule(property, value) {
return (property === "background" || property === "background-image") &&
value.match(GRADIENT_RE);
}
/**
* Internal util, checks whether a css declaration is a color
*/
function isColorOnly(property, value) {
return property === "background-color" ||
property === "color" ||
property.match(BORDERCOLOR_RE);
}
/**
* Internal util, returns the background image uri if any
*/
function getBackgroundImageUri(value, sheetHref) {
let uriMatch = BACKGROUND_IMAGE_RE.exec(value);
let uri = null;
if (uriMatch && uriMatch[1]) {
uri = uriMatch[1];
if (sheetHref) {
let sheetUri = IOService.newURI(sheetHref, null, null);
uri = sheetUri.resolve(uri);
}
}
return uri;
}
/**
* L10N utility class
*/
function L10N() {}
L10N.prototype = {};
let l10n = new L10N();
loader.lazyGetter(L10N.prototype, "strings", () => {
return Services.strings.createBundle(
"chrome://browser/locale/devtools/inspector.properties");
});

View File

@ -11,8 +11,8 @@ let {CssLogic} = require("devtools/styleinspector/css-logic");
let {ELEMENT_STYLE} = require("devtools/server/actors/styles");
let promise = require("sdk/core/promise");
let {EventEmitter} = require("devtools/shared/event-emitter");
const {OutputParser} = require("devtools/output-parser");
const {Tooltip} = require("devtools/shared/widgets/Tooltip");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/PluralForm.jsm");
@ -169,6 +169,11 @@ function CssHtmlTree(aStyleInspector, aPageStyle)
// The element that we're inspecting, and the document that it comes from.
this.viewedElement = null;
// Properties preview tooltip
this.tooltip = new Tooltip(this.styleInspector.inspector.panelDoc);
this.tooltip.startTogglingOnHover(this.propertyContainer,
this._buildTooltipContent.bind(this));
this._buildContextMenu();
this.createStyleViews();
}
@ -490,6 +495,29 @@ CssHtmlTree.prototype = {
win.focus();
},
/**
* Verify that target is indeed a css value we want a tooltip on, and if yes
* prepare some content for the tooltip
*/
_buildTooltipContent: function(target)
{
// If the hovered element is not a property view and is not a background
// image, then don't show a tooltip
let isPropertyValue = target.classList.contains("property-value");
if (!isPropertyValue) {
return false;
}
let propName = target.parentNode.querySelector(".property-name");
let isBackgroundImage = propName.textContent === "background-image";
if (!isBackgroundImage) {
return false;
}
// Fill some content
this.tooltip.setCssBackgroundImageContent(target.textContent);
return true;
},
/**
* Create a context menu.
*/
@ -648,6 +676,9 @@ CssHtmlTree.prototype = {
this._contextmenu = null;
}
this.tooltip.stopTogglingOnHover(this.propertyContainer);
this.tooltip.destroy();
// Remove bound listeners
this.styleDocument.removeEventListener("contextmenu", this._onContextMenu);
this.styleDocument.removeEventListener("copy", this._onCopy);
@ -831,9 +862,8 @@ PropertyView.prototype = {
{
let doc = this.tree.styleDocument;
this.onMatchedToggle = this.onMatchedToggle.bind(this);
// Build the container element
this.onMatchedToggle = this.onMatchedToggle.bind(this);
this.element = doc.createElementNS(HTML_NS, "div");
this.element.setAttribute("class", this.propertyHeaderClassName);
this.element.addEventListener("dblclick", this.onMatchedToggle, false);
@ -858,6 +888,8 @@ PropertyView.prototype = {
this.matchedExpander.addEventListener("click", this.onMatchedToggle, false);
this.element.appendChild(this.matchedExpander);
this.focusElement = () => this.element.focus();
// Build the style name element
this.nameNode = doc.createElementNS(HTML_NS, "div");
this.nameNode.setAttribute("class", "property-name theme-fg-color5");
@ -1034,7 +1066,7 @@ PropertyView.prototype = {
};
/**
* A container to view us easy access to display data from a CssRule
* A container to give us easy access to display data from a CssRule
* @param CssHtmlTree aTree, the owning CssHtmlTree
* @param aSelectorInfo
*/

View File

@ -13,6 +13,7 @@ let {CssLogic} = require("devtools/styleinspector/css-logic");
let {InplaceEditor, editableField, editableItem} = require("devtools/shared/inplace-editor");
let {ELEMENT_STYLE, PSEUDO_ELEMENTS} = require("devtools/server/actors/styles");
let {gDevTools} = Cu.import("resource:///modules/devtools/gDevTools.jsm", {});
let {Tooltip} = require("devtools/shared/widgets/Tooltip");
const {OutputParser} = require("devtools/output-parser");
@ -1029,6 +1030,7 @@ TextProperty.prototype = {
* apply to a given element. After construction, the 'element'
* property will be available with the user interface.
*
* @param {Inspector} aInspector
* @param {Document} aDoc
* The document that will contain the rule view.
* @param {object} aStore
@ -1039,8 +1041,9 @@ TextProperty.prototype = {
* The PageStyleFront for communicating with the remote server.
* @constructor
*/
function CssRuleView(aDoc, aStore, aPageStyle)
function CssRuleView(aInspector, aDoc, aStore, aPageStyle)
{
this.inspector = aInspector;
this.doc = aDoc;
this.store = aStore || {};
this.pageStyle = aPageStyle;
@ -1067,6 +1070,9 @@ function CssRuleView(aDoc, aStore, aPageStyle)
};
this.popup = new AutocompletePopup(aDoc.defaultView.parent.document, options);
this.tooltip = new Tooltip(this.inspector.panelDoc);
this.tooltip.startTogglingOnHover(this.element, this._buildTooltipContent.bind(this));
this._buildContextMenu();
this._showEmpty();
}
@ -1107,6 +1113,37 @@ CssRuleView.prototype = {
popupset.appendChild(this._contextmenu);
},
/**
* Verify that target is indeed a css value we want a tooltip on, and if yes
* prepare some content for the tooltip
*/
_buildTooltipContent: function(target) {
let isValueWithImage = target.classList.contains("ruleview-propertyvalue") &&
target.querySelector(".theme-link");
let isImageHref = target.classList.contains("theme-link") &&
target.parentNode.classList.contains("ruleview-propertyvalue");
if (isImageHref) {
target = target.parentNode;
}
let isEditing = this.isEditing;
// If the inplace-editor is visible or if this is not a background image
// don't show the tooltip
if (this.isEditing || (!isImageHref && !isValueWithImage)) {
return false;
}
// Retrieve the TextProperty for the hovered element
let property = target.textProperty;
let href = property.rule.domRule.href;
// Fill some content
this.tooltip.setCssBackgroundImageContent(property.value, href);
return true;
},
/**
* Update the context menu. This means enabling or disabling menuitems as
* appropriate.
@ -1240,6 +1277,9 @@ CssRuleView.prototype = {
// We manage the popupNode ourselves so we also need to destroy it.
this.doc.popupNode = null;
this.tooltip.stopTogglingOnHover(this.element);
this.tooltip.destroy();
if (this.element.parentNode) {
this.element.parentNode.removeChild(this.element);
}
@ -1307,7 +1347,6 @@ CssRuleView.prototype = {
}
this._createEditors();
// Notify anyone that cares that we refreshed.
var evt = this.doc.createEvent("Events");
evt.initEvent("CssRuleViewRefreshed", true, false);
@ -1853,6 +1892,10 @@ TextPropertyEditor.prototype = {
tabindex: "0",
});
// Storing the TextProperty on the valuespan for easy access
// (for instance by the tooltip)
this.valueSpan.textProperty = this.prop;
// Save the initial value as the last committed value,
// for restoring after pressing escape.
this.committed = { name: this.prop.name,
@ -1971,7 +2014,6 @@ TextPropertyEditor.prototype = {
});
a.addEventListener("click", (aEvent) => {
// Clicks within the link shouldn't trigger editing.
aEvent.stopPropagation();
aEvent.preventDefault();
@ -2133,6 +2175,7 @@ TextPropertyEditor.prototype = {
{
this.element.parentNode.removeChild(this.element);
this.ruleEditor.rule.editClosestTextProperty(this.prop);
this.valueSpan.textProperty = null;
this.prop.remove();
},

View File

@ -25,7 +25,7 @@ function RuleViewTool(aInspector, aWindow, aIFrame)
this.doc = aWindow.document;
this.outerIFrame = aIFrame;
this.view = new RuleView.CssRuleView(this.doc);
this.view = new RuleView.CssRuleView(aInspector, this.doc);
this.doc.documentElement.appendChild(this.view.element);
this._changeHandler = () => {

View File

@ -38,6 +38,7 @@ MOCHITEST_BROWSER_FILES = \
browser_ruleview_pseudoelement.js \
browser_computedview_bug835808_keyboard_nav.js \
browser_bug913014_matched_expand.js \
browser_bug765105_background_image_tooltip.js \
head.js \
$(NULL)

View File

@ -0,0 +1,162 @@
/* vim: set ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
let contentDoc;
let inspector;
let ruleView;
let computedView;
const PAGE_CONTENT = [
'<style type="text/css">',
' body {',
' padding: 1em;',
' background-image: url(data:image/png;base64,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);',
' background-repeat: repeat-y;',
' background-position: right top;',
' }',
' .test-element {',
' font-family: verdana;',
' color: #333;',
' background: url(chrome://global/skin/icons/warning-64.png) no-repeat left center;',
' padding-left: 70px;',
' }',
'</style>',
'<div class="test-element">test element</div>',
'<div class="test-element-2">test element 2</div>'
].join("\n");
function test() {
waitForExplicitFinish();
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);
content.location = "data:text/html,rule view tooltip test";
}
function createDocument() {
contentDoc.body.innerHTML = PAGE_CONTENT;
openRuleView((aInspector, aRuleView) => {
inspector = aInspector;
ruleView = aRuleView;
startTests();
});
}
function startTests() {
// let testElement = contentDoc.querySelector(".test-element");
inspector.selection.setNode(contentDoc.body);
inspector.once("inspector-updated", testBodyRuleView);
}
function endTests() {
contentDoc = inspector = ruleView = computedView = null;
gBrowser.removeCurrentTab();
finish();
}
function assertTooltipShownOn(tooltip, element, cb) {
// If there is indeed a show-on-hover on element, the xul panel will be shown
tooltip.panel.addEventListener("popupshown", function shown() {
tooltip.panel.removeEventListener("popupshown", shown, true);
cb();
}, true);
tooltip._showOnHover(element);
}
function testBodyRuleView() {
info("Testing tooltips in the rule view");
let panel = ruleView.tooltip.panel;
// Check that the rule view has a tooltip and that a XUL panel has been created
ok(ruleView.tooltip, "Tooltip instance exists");
ok(panel, "XUL panel exists");
// Get the background-image property inside the rule view
let {nameSpan, valueSpan} = getRuleViewProperty("background-image");
// And verify that the tooltip gets shown on this property
assertTooltipShownOn(ruleView.tooltip, valueSpan, () => {
let images = panel.getElementsByTagName("image");
is(images.length, 1, "Tooltip contains an image");
ok(images[0].src.indexOf("iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHe") !== -1, "The image URL seems fine");
ruleView.tooltip.hide();
inspector.selection.setNode(contentDoc.querySelector(".test-element"));
inspector.once("inspector-updated", testDivRuleView);
});
}
function testDivRuleView() {
let panel = ruleView.tooltip.panel;
// Get the background property inside the rule view
let {nameSpan, valueSpan} = getRuleViewProperty("background");
let uriSpan = valueSpan.querySelector(".theme-link");
// And verify that the tooltip gets shown on this property
assertTooltipShownOn(ruleView.tooltip, uriSpan, () => {
let images = panel.getElementsByTagName("image");
is(images.length, 1, "Tooltip contains an image");
ok(images[0].src === "chrome://global/skin/icons/warning-64.png");
ruleView.tooltip.hide();
testComputedView();
});
}
function testComputedView() {
info("Testing tooltips in the computed view");
inspector.sidebar.select("computedview");
computedView = inspector.sidebar.getWindowForTab("computedview").computedview.view;
let doc = computedView.styleDocument;
let panel = computedView.tooltip.panel;
let {nameSpan, valueSpan} = getComputedViewProperty("background-image");
assertTooltipShownOn(computedView.tooltip, valueSpan, () => {
let images = panel.getElementsByTagName("image");
is(images.length, 1, "Tooltip contains an image");
ok(images[0].src === "chrome://global/skin/icons/warning-64.png");
computedView.tooltip.hide();
endTests();
});
}
function getRuleViewProperty(name) {
let prop = null;
[].forEach.call(ruleView.doc.querySelectorAll(".ruleview-property"), property => {
let nameSpan = property.querySelector(".ruleview-propertyname");
let valueSpan = property.querySelector(".ruleview-propertyvalue");
if (nameSpan.textContent === name) {
prop = {nameSpan: nameSpan, valueSpan: valueSpan};
}
});
return prop;
}
function getComputedViewProperty(name) {
let prop = null;
[].forEach.call(computedView.styleDocument.querySelectorAll(".property-view"), property => {
let nameSpan = property.querySelector(".property-name");
let valueSpan = property.querySelector(".property-value");
if (nameSpan.textContent === name) {
prop = {nameSpan: nameSpan, valueSpan: valueSpan};
}
});
return prop;
}

View File

@ -57,6 +57,7 @@
<!ENTITY connection.installOneSimulatorTooltip "Install a version of the Simulator by downloading the relevant add-on.">
<!ENTITY connection.installAnotherSimulator "Add">
<!ENTITY connection.installAnotherSimulatorTooltip "Install an additional version of the Simulator by downloading the relevant add-on.">
<!ENTITY connection.startRegisteredSimulator "Start:">
<!ENTITY projects.localApps "Local Apps">
<!ENTITY projects.addApp "Add">

View File

@ -31,7 +31,6 @@ debuggerPausedWarning.message=Debugger is paused. Some features like mouse selec
# the node is selected.
nodeMenu.tooltiptext=Node operations
# LOCALIZATION NOTE (inspector.*)
# Used for the menuitem in the tool menu
inspector.label=Inspector
@ -44,3 +43,6 @@ inspector.accesskey=I
markupView.more.showing=Some nodes were hidden.
markupView.more.showAll=Show All %S Nodes
inspector.tooltip=DOM and Style Inspector
#LOCALIZATION NOTE: Used in the image preview tooltip when the image could not be loaded
previewTooltip.image.brokenImage=Could not load the image

View File

@ -17,6 +17,13 @@
-moz-border-start-color: transparent;
}
/* Sources toolbar */
#sources-toolbar {
border: none; /* Remove the devtools-toolbar's black bottom border. */
-moz-border-end: 1px solid #222426; /* Match the sources list's dark margin. */
}
#pretty-print {
min-width: 0;
font-weight: bold;
@ -435,3 +442,8 @@
#body[layout=vertical] .side-menu-widget-item-arrow {
background-image: none !important;
}
#body[layout=vertical] .side-menu-widget-group,
#body[layout=vertical] .side-menu-widget-item {
-moz-margin-end: 0;
}

View File

@ -279,6 +279,8 @@
color: #000;
}
/* SideMenuWidget container */
.side-menu-widget-container[with-arrows=true]:-moz-locale-dir(ltr) {
box-shadow: inset -1px 0 0 #222426;
}
@ -287,6 +289,18 @@
box-shadow: inset 1px 0 0 #222426;
}
.side-menu-widget-container[with-arrows=true] .side-menu-widget-group {
/* To allow visibility of the dark margin shadow. */
-moz-margin-end: 1px;
}
.side-menu-widget-container[with-arrows=true] .side-menu-widget-item {
/* To compensate for the arrow image's dark margin. */
-moz-margin-end: -1px;
}
/* SideMenuWidget groups */
.side-menu-widget-group-title {
padding: 4px;
}
@ -308,6 +322,8 @@
padding: 0;
}
/* SideMenuWidget items */
.side-menu-widget-item[theme="dark"] {
border-top: 1px solid hsla(210,8%,5%,.25);
border-bottom: 1px solid hsla(210,16%,76%,.1);
@ -343,15 +359,17 @@
}
.side-menu-widget-item.selected > .side-menu-widget-item-arrow:-moz-locale-dir(ltr) {
background-image: url(itemArrow-ltr.png), linear-gradient(to right, black, black);
background-image: url(itemArrow-ltr.png), linear-gradient(to right, #222426, #222426);
background-position: center right, top right;
}
.side-menu-widget-item.selected > .side-menu-widget-item-arrow:-moz-locale-dir(rtl) {
background-image: url(itemArrow-rtl.png), linear-gradient(to right, black, black);
background-image: url(itemArrow-rtl.png), linear-gradient(to right, #222426, #222426);
background-position: center left, top left;
}
/* SideMenuWidget items contents */
.side-menu-widget-item-label {
padding: 4px 0px;
}
@ -385,6 +403,8 @@
text-shadow: 0 1px 1px #111;
}
/* SideMenuWidget misc */
.side-menu-widget-empty-notice-container {
padding: 12px;
}

View File

@ -19,6 +19,13 @@
-moz-border-start-color: transparent;
}
/* Sources toolbar */
#sources-toolbar {
border: none; /* Remove the devtools-toolbar's black bottom border. */
-moz-border-end: 1px solid #222426; /* Match the sources list's dark margin. */
}
#pretty-print {
min-width: 0;
font-weight: bold;
@ -437,3 +444,8 @@
#body[layout=vertical] .side-menu-widget-item-arrow {
background-image: none !important;
}
#body[layout=vertical] .side-menu-widget-group,
#body[layout=vertical] .side-menu-widget-item {
-moz-margin-end: 0;
}

View File

@ -279,6 +279,8 @@
color: #000;
}
/* SideMenuWidget container */
.side-menu-widget-container[with-arrows=true]:-moz-locale-dir(ltr) {
box-shadow: inset -1px 0 0 #222426;
}
@ -287,6 +289,18 @@
box-shadow: inset 1px 0 0 #222426;
}
.side-menu-widget-container[with-arrows=true] .side-menu-widget-group {
/* To allow visibility of the dark margin shadow. */
-moz-margin-end: 1px;
}
.side-menu-widget-container[with-arrows=true] .side-menu-widget-item {
/* To compensate for the arrow image's dark margin. */
-moz-margin-end: -1px;
}
/* SideMenuWidget groups */
.side-menu-widget-group-title {
padding: 4px;
}
@ -308,6 +322,8 @@
padding: 0;
}
/* SideMenuWidget items */
.side-menu-widget-item[theme="dark"] {
border-top: 1px solid hsla(210,8%,5%,.25);
border-bottom: 1px solid hsla(210,16%,76%,.1);
@ -343,15 +359,17 @@
}
.side-menu-widget-item.selected > .side-menu-widget-item-arrow:-moz-locale-dir(ltr) {
background-image: url(itemArrow-ltr.png), linear-gradient(to right, black, black);
background-image: url(itemArrow-ltr.png), linear-gradient(to right, #222426, #222426);
background-position: center right, top right;
}
.side-menu-widget-item.selected > .side-menu-widget-item-arrow:-moz-locale-dir(rtl) {
background-image: url(itemArrow-rtl.png), linear-gradient(to right, black, black);
background-image: url(itemArrow-rtl.png), linear-gradient(to right, #222426, #222426);
background-position: center left, top left;
}
/* SideMenuWidget items contents */
.side-menu-widget-item-label {
padding: 4px 0px;
}
@ -385,6 +403,8 @@
text-shadow: 0 1px 1px #111;
}
/* SideMenuWidget misc */
.side-menu-widget-empty-notice-container {
padding: 12px;
}

View File

@ -111,3 +111,25 @@
max-height: 75vh;
}
}
/* Tooltip widget (see browser/devtools/shared/widgets/Tooltip.js) */
.devtools-tooltip.devtools-tooltip-tooltip {
/* If the tooltip uses a <tooltip> XUL element */
-moz-appearance: none;
padding: 4px;
background: #eee;
border-radius: 3px;
}
.devtools-tooltip.devtools-tooltip-panel .panel-arrowcontent {
/* If the tooltip uses a <panel> XUL element instead */
padding: 4px;
}
.devtools-tooltip-tiles {
background-color: #eee;
background-image: linear-gradient(45deg, #ccc 25%, transparent 25%, transparent 75%, #ccc 75%, #ccc),
linear-gradient(45deg, #ccc 25%, transparent 25%, transparent 75%, #ccc 75%, #ccc);
background-size: 20px 20px;
background-position: 0 0, 10px 10px;
}

View File

@ -17,6 +17,13 @@
-moz-border-start-color: transparent;
}
/* Sources toolbar */
#sources-toolbar {
border: none; /* Remove the devtools-toolbar's black bottom border. */
-moz-border-end: 1px solid #222426; /* Match the sources list's dark margin. */
}
#pretty-print {
min-width: 0;
font-weight: bold;
@ -440,3 +447,8 @@
#body[layout=vertical] .side-menu-widget-item-arrow {
background-image: none !important;
}
#body[layout=vertical] .side-menu-widget-group,
#body[layout=vertical] .side-menu-widget-item {
-moz-margin-end: 0;
}

View File

@ -283,6 +283,8 @@
color: #000;
}
/* SideMenuWidget container */
.side-menu-widget-container[with-arrows=true]:-moz-locale-dir(ltr) {
box-shadow: inset -1px 0 0 #222426;
}
@ -291,6 +293,18 @@
box-shadow: inset 1px 0 0 #222426;
}
.side-menu-widget-container[with-arrows=true] .side-menu-widget-group {
/* To allow visibility of the dark margin shadow. */
-moz-margin-end: 1px;
}
.side-menu-widget-container[with-arrows=true] .side-menu-widget-item {
/* To compensate for the arrow image's dark margin. */
-moz-margin-end: -1px;
}
/* SideMenuWidget groups */
.side-menu-widget-group-title {
padding: 4px;
}
@ -312,6 +326,8 @@
padding: 0;
}
/* SideMenuWidget items */
.side-menu-widget-item[theme="dark"] {
border-top: 1px solid hsla(210,8%,5%,.25);
border-bottom: 1px solid hsla(210,16%,76%,.1);
@ -347,15 +363,17 @@
}
.side-menu-widget-item.selected > .side-menu-widget-item-arrow:-moz-locale-dir(ltr) {
background-image: url(itemArrow-ltr.png), linear-gradient(to right, black, black);
background-image: url(itemArrow-ltr.png), linear-gradient(to right, #222426, #222426);
background-position: center right, top right;
}
.side-menu-widget-item.selected > .side-menu-widget-item-arrow:-moz-locale-dir(rtl) {
background-image: url(itemArrow-rtl.png), linear-gradient(to right, black, black);
background-image: url(itemArrow-rtl.png), linear-gradient(to right, #222426, #222426);
background-position: center left, top left;
}
/* SideMenuWidget items contents */
.side-menu-widget-item-label {
padding: 4px 0px;
}
@ -388,6 +406,8 @@
color: #f5f7fa;
}
/* SideMenuWidget misc */
.side-menu-widget-empty-notice-container {
padding: 12px;
}

View File

@ -631,6 +631,7 @@ public class FaviconCache {
startWrite();
try {
mCurrentSize.set(0);
mBackingMap.clear();
mOrdering.clear();
} finally {

View File

@ -56,25 +56,6 @@ var provider = {
};
dirSvc.QueryInterface(Ci.nsIDirectoryService).registerProvider(provider);
/**
* Imports a download test file to use. Works with rdf and sqlite files.
*
* @param aFName
* The name of the file to import. This file should be located in the
* same directory as this file.
*/
function importDownloadsFile(aFName)
{
var file = do_get_file(aFName);
var newFile = dirSvc.get("ProfD", Ci.nsIFile);
if (/\.rdf$/i.test(aFName))
file.copyTo(newFile, "downloads.rdf");
else if (/\.sqlite$/i.test(aFName))
file.copyTo(newFile, "downloads.sqlite");
else
do_throw("Unexpected filename!");
}
var gDownloadCount = 0;
/**
* Adds a download to the DM, and starts it.

View File

@ -1,36 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
// This tests that downloads in the scanning state are set to a completed state
// upon service initialization.
importDownloadsFile("bug_401582_downloads.sqlite");
const nsIDownloadManager = Ci.nsIDownloadManager;
const dm = Cc["@mozilla.org/download-manager;1"].getService(nsIDownloadManager);
function test_noScanningDownloads()
{
var stmt = dm.DBConnection.createStatement(
"SELECT * " +
"FROM moz_downloads " +
"WHERE state = ?1");
stmt.bindByIndex(0, nsIDownloadManager.DOWNLOAD_SCANNING);
do_check_false(stmt.executeStep());
stmt.reset();
stmt.finalize();
}
var tests = [test_noScanningDownloads];
function run_test()
{
if (oldDownloadManagerDisabled()) {
return;
}
for (var i = 0; i < tests.length; i++)
tests[i]();
}

View File

@ -1,24 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
// This file ensures that the download manager service can be instantiated with
// a certain downloads.sqlite file that had incorrect data.
importDownloadsFile("bug_409179_downloads.sqlite");
function run_test()
{
if (oldDownloadManagerDisabled()) {
return;
}
var caughtException = false;
try {
var dm = Cc["@mozilla.org/download-manager;1"].
getService(Ci.nsIDownloadManager);
} catch (e) {
caughtException = true;
}
do_check_false(caughtException);
}

View File

@ -1,24 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
// Make sure we remove old, now-unused downloads.rdf (pre-Firefox 3 storage)
// when starting the download manager.
function run_test()
{
if (oldDownloadManagerDisabled()) {
return;
}
// Create the downloads.rdf file
importDownloadsFile("empty_downloads.rdf");
// Make sure it got created
let rdfFile = dirSvc.get("DLoads", Ci.nsIFile);
do_check_true(rdfFile.exists());
// Initialize the download manager, which will delete downloads.rdf
Cc["@mozilla.org/download-manager;1"].getService(Ci.nsIDownloadManager);
do_check_false(rdfFile.exists());
}

View File

@ -3,10 +3,7 @@ head = head_download_manager.js
tail = tail_download_manager.js
firefox-appdir = browser
support-files =
bug_401582_downloads.sqlite
bug_409179_downloads.sqlite
downloads_manifest.js
empty_downloads.rdf
test_downloads.manifest
data/digest.chunk
@ -15,9 +12,7 @@ support-files =
[test_bug_384744.js]
[test_bug_395092.js]
[test_bug_401430.js]
[test_bug_401582.js]
[test_bug_406857.js]
[test_bug_409179.js]
[test_bug_420230.js]
[test_cancel_download_files_removed.js]
# Bug 676989: test hangs consistently on Android
@ -29,7 +24,6 @@ skip-if = os == "android"
[test_guid.js]
[test_history_expiration.js]
[test_offline_support.js]
[test_old_download_files_removed.js]
[test_private_resume.js]
[test_privatebrowsing.js]
[test_privatebrowsing_cancel.js]

View File

@ -362,7 +362,7 @@ this.DownloadIntegration = {
#else
// For Metro mode on Windows 8, we want searchability for documents
// that the user chose to open with an external application.
if (this._isImmersiveProcess()) {
if (Services.metro && Services.metro.immersive) {
directoryPath = yield this.getSystemDownloadsDirectory();
} else {
directoryPath = this._getDirectory("TmpD");
@ -454,14 +454,6 @@ this.DownloadIntegration = {
}.bind(this));
},
/**
* Determines whether it's a Windows Metro app.
*/
_isImmersiveProcess: function() {
// TODO: to be implemented
return false;
},
/*
* Launches a file represented by the target of a download. This can
* open the file with the default application for the target MIME type

View File

@ -248,6 +248,47 @@ var NodeActor = protocol.ActorClass({
response: {}
}),
/**
* Get the node's image data if any (for canvas and img nodes).
* Returns a LongStringActor with the image or canvas' image data as png
* a data:image/png;base64,.... string
* A null return value means the node isn't an image
* An empty string return value means the node is an image but image data
* could not be retrieved (missing/broken image).
*/
getImageData: method(function() {
let isImg = this.rawNode.tagName.toLowerCase() === "img";
let isCanvas = this.rawNode.tagName.toLowerCase() === "canvas";
if (!isImg && !isCanvas) {
return null;
}
let imageData;
if (isImg) {
let canvas = this.rawNode.ownerDocument.createElement("canvas");
canvas.width = this.rawNode.naturalWidth;
canvas.height = this.rawNode.naturalHeight;
let ctx = canvas.getContext("2d");
try {
// This will fail if the image is missing
ctx.drawImage(this.rawNode, 0, 0);
imageData = canvas.toDataURL("image/png");
} catch (e) {
imageData = "";
}
} else if (isCanvas) {
imageData = this.rawNode.toDataURL("image/png");
}
return LongStringActor(this.conn, imageData);
}, {
request: {},
response: {
data: RetVal("nullable:longstring")
}
}),
/**
* Modify a node's attributes. Passed an array of modifications
* similar in format to "attributes" mutations.
@ -283,8 +324,7 @@ var NodeActor = protocol.ActorClass({
modifications: Arg(0, "array:json")
},
response: {}
}),
})
});
/**

View File

@ -1,137 +0,0 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
* vim: sw=2 ts=2 sts=2
* 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 added with bug 460086 to test the behavior of the new API that was added
* to remove all traces of visiting a site.
*/
Components.utils.import("resource://gre/modules/Services.jsm");
Components.utils.import("resource://gre/modules/ForgetAboutSite.jsm");
////////////////////////////////////////////////////////////////////////////////
//// Utility Functions
/**
* Creates an nsIURI object for the given file.
*
* @param aFile
* The nsIFile of the URI to create.
* @returns an nsIURI representing aFile.
*/
function uri(aFile)
{
return Cc["@mozilla.org/network/io-service;1"].
getService(Ci.nsIIOService).
newFileURI(aFile);
}
/**
* Checks to ensure a URI string is in download history or not.
*
* @param aURIString
* The string of the URI to check.
* @param aIsActive
* True if the URI should be actively downloaded, false otherwise.
*/
function check_active_download(aURIString, aIsActive)
{
let dm = Cc["@mozilla.org/download-manager;1"].
getService(Ci.nsIDownloadManager);
let enumerator = dm.activeDownloads;
let found = false;
while (enumerator.hasMoreElements()) {
let dl = enumerator.getNext().QueryInterface(Ci.nsIDownload);
if (dl.source.spec == aURIString)
found = true;
}
let checker = aIsActive ? do_check_true : do_check_false;
checker(found);
}
////////////////////////////////////////////////////////////////////////////////
//// Test Functions
let destFile = dirSvc.get("TmpD", Ci.nsIFile);
destFile.append("dm-test-file");
destFile = uri(destFile);
let data = [
{ source: "http://mozilla.org/direct_match",
target: destFile.spec,
removed: true
},
{ source: "http://www.mozilla.org/subdomain",
target: destFile.spec,
removed: true
},
{ source: "http://ilovemozilla.org/contains_domain",
target: destFile.spec,
removed: false
},
];
function makeGUID() {
let guid = "";
for (var i = 0; i < 12; i++)
guid += Math.floor(Math.random() * 10);
return guid;
}
function run_test()
{
if (oldDownloadManagerDisabled()) {
return;
}
// We add this data to the database first, but we cannot instantiate the
// download manager service, otherwise these downloads will not be placed in
// the active downloads array.
// Copy the empty downloads database to our profile directory
let downloads = do_get_file("downloads.empty.sqlite");
downloads.copyTo(dirSvc.get("ProfD", Ci.nsIFile), "downloads.sqlite");
// Open the database
let ss = Cc["@mozilla.org/storage/service;1"].
getService(Ci.mozIStorageService);
let file = dirSvc.get("ProfD", Ci.nsIFile);
file.append("downloads.sqlite");
let db = ss.openDatabase(file);
// Insert the data
let stmt = db.createStatement(
"INSERT INTO moz_downloads (source, target, state, autoResume, entityID, guid) " +
"VALUES (:source, :target, :state, :autoResume, :entityID, :guid)"
);
for (let i = 0; i < data.length; i++) {
stmt.params.source = data[i].source;
stmt.params.target = data[i].target;
stmt.params.state = Ci.nsIDownloadManager.DOWNLOAD_PAUSED;
stmt.params.autoResume = 0; // DONT_RESUME is 0
stmt.params.entityID = "foo" // just has to be non-null for our test
stmt.params.guid = makeGUID();
stmt.execute();
stmt.reset();
}
stmt.finalize();
stmt = null;
db.close();
db = null;
// Check to make sure it's all there
for (let i = 0; i < data.length; i++)
check_active_download(data[i].source, true);
// Dispatch the remove call
ForgetAboutSite.removeDataFromDomain("mozilla.org");
// And check our data
for (let i = 0; i < data.length; i++)
check_active_download(data[i].source, !data[i].removed);
// Shutdown the download manager.
Services.obs.notifyObservers(null, "quit-application", null);
}

View File

@ -1,7 +1,5 @@
[DEFAULT]
head = head_forgetaboutsite.js
tail =
support-files = downloads.empty.sqlite
[test_removeDataFromDomain.js]
[test_removeDataFromDomain_activeDownloads.js]

View File

@ -1241,7 +1241,14 @@ function waitForCondition(condition, nextTest, errorMsg) {
ok(false, errorMsg);
moveOn();
}
if (condition()) {
var conditionPassed;
try {
conditionPassed = condition();
} catch (e) {
ok(false, e + "\n" + e.stack);
conditionPassed = false;
}
if (conditionPassed) {
moveOn();
}
tries++;