Merge fx-team to m-c

This commit is contained in:
Wes Kocher 2014-01-09 16:50:03 -08:00
commit c5ca8fd03c
116 changed files with 5645 additions and 3987 deletions

View File

@ -22,4 +22,4 @@
# changes to stick? As of bug 928195, this shouldn't be necessary! Please
# don't change CLOBBER for WebIDL changes any more.
Bug 934756 causes the Fennec build to fail unless we clobber.
Bug 944533 requires clobber to force a Proguard refresh

View File

@ -4,6 +4,8 @@
* You can obtain one at http://mozilla.org/MPL/2.0/. */
#endif
Cu.import("resource://gre/modules/NewTabUtils.jsm");
/**
* Keeps thumbnails of open web pages up-to-date.
*/
@ -120,6 +122,11 @@ let gBrowserThumbnails = {
},
_shouldCapture: function Thumbnails_shouldCapture(aBrowser) {
// Capture only if it's a top site in about:newtab.
if (!NewTabUtils.links.getLinks().some(
(link) => link && link.url == aBrowser.currentURI.spec))
return false;
// Capture only if it's the currently selected tab.
if (aBrowser != gBrowser.selectedBrowser)
return false;

View File

@ -1,15 +1,11 @@
/* 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/. */
.highlighter-container {
pointer-events: none;
}
.highlighter-controls {
position: relative;
}
.highlighter-outline-container {
overflow: hidden;
position: relative;
@ -34,20 +30,23 @@
/*
* Node Infobar
*/
.highlighter-nodeinfobar-container {
position: relative;
}
.highlighter-nodeinfobar-positioner {
position: absolute;
max-width: 95%;
}
.highlighter-nodeinfobar-container[hidden] {
.highlighter-nodeinfobar-positioner[hidden] {
opacity: 0;
pointer-events: none;
display: -moz-box;
}
.highlighter-nodeinfobar-container:not([disable-transitions]),
.highlighter-nodeinfobar-container[disable-transitions][force-transitions] {
.highlighter-nodeinfobar-positioner:not([disable-transitions]),
.highlighter-nodeinfobar-positioner[disable-transitions][force-transitions] {
transition-property: transform, opacity, top, left;
transition-duration: 0.1s;
transition-timing-function: linear;
@ -60,19 +59,6 @@
direction: ltr;
}
.highlighter-nodeinfobar-button > .toolbarbutton-text {
display: none;
}
.highlighter-nodeinfobar-container:not([locked]):not(:hover) > .highlighter-nodeinfobar > .highlighter-nodeinfobar-button {
visibility: hidden;
}
.highlighter-nodeinfobar-container[locked] > .highlighter-nodeinfobar,
.highlighter-nodeinfobar-container:not([locked]):hover > .highlighter-nodeinfobar {
pointer-events: auto;
}
html|*.highlighter-nodeinfobar-id,
html|*.highlighter-nodeinfobar-classes,
html|*.highlighter-nodeinfobar-pseudo-classes,
@ -86,15 +72,15 @@ html|*.highlighter-nodeinfobar-tagname {
display: none;
}
.highlighter-nodeinfobar-container[position="top"]:not([hide-arrow]) > .highlighter-nodeinfobar-arrow-bottom {
.highlighter-nodeinfobar-positioner[position="top"]:not([hide-arrow]) > .highlighter-nodeinfobar-arrow-bottom {
display: block;
}
.highlighter-nodeinfobar-container[position="bottom"]:not([hide-arrow]) > .highlighter-nodeinfobar-arrow-top {
.highlighter-nodeinfobar-positioner[position="bottom"]:not([hide-arrow]) > .highlighter-nodeinfobar-arrow-top {
display: block;
}
.highlighter-nodeinfobar-container[disabled] {
.highlighter-nodeinfobar-positioner[disabled] {
visibility: hidden;
}

View File

@ -1106,6 +1106,10 @@ CustomizeMode.prototype = {
}
CustomizableUI.removeWidgetFromArea(aDraggedItemId);
// Special widgets are removed outright, we can return here:
if (CustomizableUI.isSpecialWidget(aDraggedItemId)) {
return;
}
}
draggedItem = draggedItem.parentNode;

View File

@ -59,4 +59,5 @@ skip-if = os == "linux"
[browser_947987_removable_default.js]
[browser_948985_non_removable_defaultArea.js]
[browser_952963_areaType_getter_no_area.js]
[browser_956602_remove_special_widget.js]
[browser_panel_toggle.js]

View File

@ -0,0 +1,31 @@
/* 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";
// Adding a separator and then dragging it out of the navbar shouldn't throw
add_task(function() {
try {
let navbar = document.getElementById("nav-bar");
let separatorSelector = "toolbarseparator[id^=customizableui-special-separator]";
ok(!navbar.querySelector(separatorSelector), "Shouldn't be a separator in the navbar");
CustomizableUI.addWidgetToArea('separator', 'nav-bar');
yield startCustomizing();
let separator = navbar.querySelector(separatorSelector);
ok(separator, "There should be a separator in the navbar now.");
let palette = document.getElementById("customization-palette");
simulateItemDrag(separator, palette);
ok(!palette.querySelector(separatorSelector), "No separator in the palette.");
} catch (ex) {
Cu.reportError(ex);
ok(false, "Shouldn't throw an exception moving an item to the navbar.");
} finally {
yield endCustomizing();
}
});
add_task(function asyncCleanup() {
resetCustomization();
});

View File

@ -583,25 +583,6 @@ nsBrowserContentHandler.prototype = {
overridePage = overridePage.replace("%OLD_VERSION%", old_mstone);
break;
// Temporary case for Australis whatsnew
case OVERRIDE_NEW_BUILD_ID:
let locale = "en-US";
try {
locale = Services.prefs.getCharPref("general.useragent.locale");
} catch (e) {}
let showedAustralisWhatsNew = false;
try {
showedAustralisWhatsNew = Services.prefs.getBoolPref("browser.showedAustralisWhatsNew");
} catch(e) {}
// Show the Australis whatsnew page for en-US if we haven't yet shown it
if (!showedAustralisWhatsNew && locale == "en-US") {
Services.prefs.setBoolPref("browser.showedAustralisWhatsNew", true);
overridePage = "https://www.mozilla.org/en-US/firefox/29.0a1/whatsnew/";
}
break;
}
}
} catch (ex) {}

View File

@ -87,6 +87,10 @@
this._addedObserver = true;
this.searchService.init((function search_init_cb(aStatus) {
// Bail out if the binding's been destroyed
if (this._destroyed)
return;
if (Components.isSuccessCode(aStatus)) {
// Refresh the display (updating icon, etc)
this.updateDisplay();
@ -97,6 +101,8 @@
]]></constructor>
<destructor><![CDATA[
this._destroyed = true;
if (this._addedObserver) {
var os = Components.classes["@mozilla.org/observer-service;1"]
.getService(Components.interfaces.nsIObserverService);

View File

@ -132,7 +132,7 @@ let UI = {
}
}
if (!icon)
return null;
return "chrome://browser/skin/devtools/app-manager/default-app-icon.png";
if (project.type == "hosted") {
let manifestURL = Services.io.newURI(project.location, null, null);
let origin = Services.io.newURI(manifestURL.prePath, null, null);

View File

@ -31,11 +31,12 @@ ToolbarView.prototype = {
dumpn("Initializing the ToolbarView");
this._instrumentsPaneToggleButton = document.getElementById("instruments-pane-toggle");
this._resumeOrderPanel = document.getElementById("resumption-order-panel");
this._resumeButton = document.getElementById("resume");
this._stepOverButton = document.getElementById("step-over");
this._stepInButton = document.getElementById("step-in");
this._stepOutButton = document.getElementById("step-out");
this._resumeOrderTooltip = new Tooltip(document);
this._resumeOrderTooltip.defaultPosition = TOOLBAR_ORDER_POPUP_POSITION;
let resumeKey = ShortcutUtils.prettifyShortcut(document.getElementById("resumeKey"));
let stepOverKey = ShortcutUtils.prettifyShortcut(document.getElementById("stepOverKey"));
@ -80,10 +81,8 @@ ToolbarView.prototype = {
*/
showResumeWarning: function(aPausedUrl) {
let label = L10N.getFormatStr("resumptionOrderPanelTitle", aPausedUrl);
let descriptionNode = document.getElementById("resumption-panel-desc");
descriptionNode.setAttribute("value", label);
this._resumeOrderPanel.openPopup(this._resumeButton);
this._resumeOrderTooltip.setTextContent([label]);
this._resumeOrderTooltip.show(this._resumeButton);
},
/**
@ -163,11 +162,11 @@ ToolbarView.prototype = {
},
_instrumentsPaneToggleButton: null,
_resumeOrderPanel: null,
_resumeButton: null,
_stepOverButton: null,
_stepInButton: null,
_stepOutButton: null,
_resumeOrderTooltip: null,
_resumeTooltip: "",
_pauseTooltip: "",
_stepOverTooltip: "",

View File

@ -28,6 +28,7 @@ const SEARCH_LINE_FLAG = ":";
const SEARCH_VARIABLE_FLAG = "*";
const EDITOR_VARIABLE_HOVER_DELAY = 350; // ms
const EDITOR_VARIABLE_POPUP_POSITION = "topcenter bottomleft";
const TOOLBAR_ORDER_POPUP_POSITION = "topcenter bottomleft";
/**
* Object defining the debugger view components.

View File

@ -513,15 +513,4 @@
</vbox>
</panel>
<panel id="resumption-order-panel"
type="arrow"
position="before_start"
noautofocus="true"
consumeoutsideclicks="false">
<hbox align="start">
<image class="alert-icon"/>
<label id="resumption-panel-desc" class="description"/>
</hbox>
</panel>
</window>

View File

@ -20,12 +20,8 @@ FontInspector.prototype = {
init: function FI_init() {
this.update = this.update.bind(this);
this.onNewNode = this.onNewNode.bind(this);
this.onHighlighterLocked = this.onHighlighterLocked.bind(this);
this.inspector.selection.on("new-node", this.onNewNode);
this.inspector.sidebar.on("fontinspector-selected", this.onNewNode);
if (this.inspector.highlighter) {
this.inspector.highlighter.on("locked", this.onHighlighterLocked);
}
this.update();
},
@ -44,9 +40,6 @@ FontInspector.prototype = {
this.chromeDoc = null;
this.inspector.sidebar.off("layoutview-selected", this.onNewNode);
this.inspector.selection.off("new-node", this.onNewNode);
if (this.inspector.highlighter) {
this.inspector.highlighter.off("locked", this.onHighlighterLocked);
}
},
/**
@ -56,8 +49,7 @@ FontInspector.prototype = {
if (this.isActive() &&
this.inspector.selection.isLocal() &&
this.inspector.selection.isConnected() &&
this.inspector.selection.isElementNode() &&
this.inspector.selection.reason != "highlighter") {
this.inspector.selection.isElementNode()) {
this.undim();
this.update();
} else {
@ -65,14 +57,6 @@ FontInspector.prototype = {
}
},
/**
* Highlighter 'locked' event handler
*/
onHighlighterLocked: function FI_onHighlighterLocked() {
this.undim();
this.update();
},
/**
* Hide the font list. No node are selected.
*/
@ -210,7 +194,7 @@ FontInspector.prototype = {
!this.inspector.selection.isElementNode()) {
return;
}
let node = this.inspector.selection.node;
let node = this.inspector.selection.nodeFront;
let contentDocument = node.ownerDocument;
let root = contentDocument.documentElement;
if (contentDocument.body) {

View File

@ -17,6 +17,7 @@ function test()
gBrowser.selectedBrowser.addEventListener("load", function onload() {
gBrowser.selectedBrowser.removeEventListener("load", onload, true);
doc = content.document;
node = doc.querySelector("h1");
waitForFocus(setupKeyBindingsTest, content);
}, true);
@ -63,24 +64,37 @@ function test()
keysetMap.inspector.synthesizeKey();
}
function moveMouseOver(aElement, aInspector, cb)
{
EventUtils.synthesizeMouse(aElement, 2, 2, {type: "mousemove"},
aElement.ownerDocument.defaultView);
aInspector.toolbox.once("picker-node-hovered", () => {
executeSoon(cb);
});
}
function isHighlighting()
{
let outline = gBrowser.selectedBrowser.parentNode
.querySelector(".highlighter-container .highlighter-outline");
return outline && !outline.hasAttribute("hidden");
}
function inspectorShouldBeOpenAndHighlighting(aInspector, aToolbox)
{
is (aToolbox.currentToolId, "inspector", "Correct tool has been loaded");
is (aInspector.highlighter.locked, true, "Highlighter should be locked");
aInspector.highlighter.once("unlocked", () => {
is (aInspector.highlighter.locked, false, "Highlighter should be unlocked");
aToolbox.once("picker-started", () => {
ok(true, "picker-started event received, highlighter started");
keysetMap.inspector.synthesizeKey();
is (aInspector.highlighter.locked, true, "Highlighter should be locked");
keysetMap.inspector.synthesizeKey();
is (aInspector.highlighter.locked, false, "Highlighter should be unlocked");
keysetMap.inspector.synthesizeKey();
is (aInspector.highlighter.locked, true, "Highlighter should be locked");
aToolbox.once("webconsole-ready", (e, panel) => {
webconsoleShouldBeSelected(aToolbox, panel);
aToolbox.once("picker-stopped", () => {
ok(true, "picker-stopped event received, highlighter stopped");
aToolbox.once("webconsole-ready", (e, panel) => {
webconsoleShouldBeSelected(aToolbox, panel);
});
keysetMap.webconsole.synthesizeKey();
});
keysetMap.webconsole.synthesizeKey();
});
}

View File

@ -46,6 +46,9 @@ loader.lazyGetter(this, "Requisition", () => {
return require("gcli/cli").Requisition;
});
loader.lazyGetter(this, "Selection", () => require("devtools/framework/selection").Selection);
loader.lazyGetter(this, "InspectorFront", () => require("devtools/server/actors/inspector").InspectorFront);
/**
* A "Toolbox" is the component that holds all the tools for one specific
* target. Visually, it's a document that includes the tools tabs and all
@ -183,6 +186,42 @@ Toolbox.prototype = {
return parseFloat(Services.prefs.getCharPref(ZOOM_PREF));
},
/**
* Get the toolbox highlighter front. Note that it may not always have been
* initialized first. Use `initInspector()` if needed.
*/
get highlighter() {
if (this.isRemoteHighlightable) {
return this._highlighter;
} else {
return null;
}
},
/**
* Get the toolbox's inspector front. Note that it may not always have been
* initialized first. Use `initInspector()` if needed.
*/
get inspector() {
return this._inspector;
},
/**
* Get the toolbox's walker front. Note that it may not always have been
* initialized first. Use `initInspector()` if needed.
*/
get walker() {
return this._walker;
},
/**
* Get the toolbox's node selection. Note that it may not always have been
* initialized first. Use `initInspector()` if needed.
*/
get selection() {
return this._selection;
},
/**
* Get the toggled state of the split console
*/
@ -214,11 +253,14 @@ Toolbox.prototype = {
this._addZoomKeys();
this._loadInitialZoom();
this._telemetry.toolOpened("toolbox");
// Load the toolbox-level actor fronts and utilities now
this._target.makeRemote().then(() => {
this._telemetry.toolOpened("toolbox");
this.selectTool(this._defaultToolId).then(panel => {
this.emit("ready");
deferred.resolve();
this.selectTool(this._defaultToolId).then(panel => {
this.emit("ready");
deferred.resolve();
});
});
};
@ -415,7 +457,6 @@ Toolbox.prototype = {
}
},
/**
* Handle any custom key events. Returns true if there was a custom key binding run
* @param {string} toolId
@ -484,9 +525,11 @@ Toolbox.prototype = {
},
/**
* Add buttons to the UI as specified in the devtools.window.toolbarSpec pref
* Add buttons to the UI as specified in the devtools.toolbox.toolbarSpec pref
*/
_buildButtons: function() {
this._buildPickerButton();
if (!this.target.isLocalTab) {
return;
}
@ -500,6 +543,23 @@ Toolbox.prototype = {
buttons.forEach(container.appendChild.bind(container));
},
/**
* Adding the element picker button is done here unlike the other buttons
* since we want it to work for remote targets too
*/
_buildPickerButton: function() {
this._pickerButton = this.doc.createElement("toolbarbutton");
this._pickerButton.id = "command-button-pick";
this._pickerButton.className = "command-button";
this._pickerButton.setAttribute("tooltiptext", toolboxStrings("pickButton.tooltip"));
let container = this.doc.querySelector("#toolbox-buttons");
container.appendChild(this._pickerButton);
this.togglePicker = this.togglePicker.bind(this);
this._pickerButton.addEventListener("command", this.togglePicker, false);
},
/**
* Build a tab for one tool definition and add to the toolbox
*
@ -600,6 +660,12 @@ Toolbox.prototype = {
* The id of the tool to load.
*/
loadTool: function(id) {
if (id === "inspector" && !this._inspector) {
return this.initInspector().then(() => {
return this.loadTool(id);
});
}
let deferred = promise.defer();
let iframe = this.doc.getElementById("toolbox-panel-iframe-" + id);
@ -957,6 +1023,154 @@ Toolbox.prototype = {
}
},
/**
* Initialize the inspector/walker/selection/highlighter fronts.
* Returns a promise that resolves when the fronts are initialized
*/
initInspector: function() {
let deferred = promise.defer();
if (!this._inspector) {
this._inspector = InspectorFront(this._target.client, this._target.form);
this._inspector.getWalker().then(walker => {
this._walker = walker;
this._selection = new Selection(this._walker);
if (this.isRemoteHighlightable) {
this._inspector.getHighlighter().then(highlighter => {
this._highlighter = highlighter;
deferred.resolve();
});
} else {
deferred.resolve();
}
});
} else {
deferred.resolve();
}
return deferred.promise;
},
/**
* Destroy the inspector/walker/selection fronts
* Returns a promise that resolves when the fronts are destroyed
*/
destroyInspector: function() {
let deferred = promise.defer();
if (this._inspector) {
this._selection.destroy();
this._selection = null;
this._walker.release().then(
() => {
this._inspector.destroy();
this._highlighter.destroy();
},
(e) => {
console.error("Walker.release() failed: " + e);
this._inspector.destroy();
return this._highlighter.destroy();
}
).then(() => {
this._inspector = null;
this._highlighter = null;
this._walker = null;
deferred.resolve();
});
} else {
deferred.resolve();
}
return deferred.promise;
},
/**
* Start/stop the element picker on the debuggee target.
*/
togglePicker: function() {
if (this._isPicking) {
return this.stopPicker();
} else {
return this.startPicker();
}
},
get isRemoteHighlightable() {
return this._target.client.traits.highlightable;
},
/**
* Start the element picker on the debuggee target.
* This will request the inspector actor to start listening for mouse/touch
* events on the target to highlight the hovered/picked element.
* Depending on the server-side capabilities, this may fire events when nodes
* are hovered.
* @return A promise that resolves when the picker has started
*/
startPicker: function() {
let deferred = promise.defer();
let done = () => {
this.emit("picker-started");
deferred.resolve();
};
this.initInspector().then(() => {
this._isPicking = true;
this._pickerButton.setAttribute("checked", "true");
if (this.isRemoteHighlightable) {
this.highlighter.pick().then(done);
this._onPickerNodeHovered = res => {
this.emit("picker-node-hovered", res.node);
};
this.walker.on("picker-node-hovered", this._onPickerNodeHovered);
this._onPickerNodePicked = res => {
this.selection.setNodeFront(res.node, "picker-node-picked");
this.stopPicker();
};
this.walker.on("picker-node-picked", this._onPickerNodePicked);
} else {
this.walker.pick().then(node => {
this.selection.setNodeFront(node, "picker-node-picked");
this.stopPicker();
});
done();
}
});
return deferred.promise;
},
/**
* Stop the element picker
* @return A promise that resolves when the picker has stopped
*/
stopPicker: function() {
let deferred = promise.defer();
let done = () => {
this.emit("picker-stopped");
deferred.resolve();
};
this.initInspector().then(() => {
this._isPicking = false;
this._pickerButton.removeAttribute("checked");
if (this.isRemoteHighlightable) {
this.highlighter.cancelPick().then(done);
this.walker.off("picker-node-hovered", this._onPickerNodeHovered);
this.walker.off("picker-node-picked", this._onPickerNodePicked);
} else {
this.walker.cancelPick().then(done);
}
});
return deferred.promise;
},
/**
* Get the toolbox's notification box
*
@ -1004,6 +1218,12 @@ Toolbox.prototype = {
}
}
// Destroying the walker and inspector fronts
outstanding.push(this.destroyInspector());
// Removing buttons
this._pickerButton.removeEventListener("command", this.togglePicker, false);
this._pickerButton = null;
let container = this.doc.getElementById("toolbox-buttons");
while (container.firstChild) {
container.removeChild(container.firstChild);

View File

@ -1,877 +0,0 @@
/* -*- Mode: Javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
/* 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/. */
const {Cu, Cc, Ci} = require("chrome");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/devtools/LayoutHelpers.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
let EventEmitter = require("devtools/shared/event-emitter");
const PSEUDO_CLASSES = [":hover", ":active", ":focus"];
// add ":visited" and ":link" after bug 713106 is fixed
exports._forceBasic = {value: false};
exports.Highlighter = function Highlighter(aTarget, aInspector, aToolbox) {
if (aTarget.isLocalTab && !exports._forceBasic.value) {
return new LocalHighlighter(aTarget, aInspector, aToolbox);
} else {
return new BasicHighlighter(aTarget, aInspector, aToolbox);
}
}
exports.LocalHighlighter = LocalHighlighter;
exports.BasicHighlighter = BasicHighlighter;
/**
* A highlighter mechanism.
*
* The highlighter is built dynamically into the browser element.
* The caller is in charge of destroying the highlighter (ie, the highlighter
* won't be destroyed if a new tab is selected for example).
*
* API:
*
* // Constructor and destructor.
* Highlighter(aTab, aInspector)
* void destroy();
*
* // Show and hide the highlighter
* void show();
* void hide();
* boolean isHidden();
*
* // Redraw the highlighter if the visible portion of the node has changed.
* void invalidateSize(aScroll);
*
* Events:
*
* "closed" - Highlighter is closing
* "highlighting" - Highlighter is highlighting
* "locked" - The selected node has been locked
* "unlocked" - The selected ndoe has been unlocked
*
* Structure:
* <stack class="highlighter-container">
* <box class="highlighter-outline-container">
* <box class="highlighter-outline" locked="true/false"/>
* </box>
* <box class="highlighter-controls">
* <box class="highlighter-nodeinfobar-container" position="top/bottom" locked="true/false">
* <box class="highlighter-nodeinfobar-arrow highlighter-nodeinfobar-arrow-top"/>
* <hbox class="highlighter-nodeinfobar">
* <toolbarbutton class="highlighter-nodeinfobar-inspectbutton highlighter-nodeinfobar-button"/>
* <hbox class="highlighter-nodeinfobar-text">tagname#id.class1.class2</hbox>
* <toolbarbutton class="highlighter-nodeinfobar-menu highlighter-nodeinfobar-button"></toolbarbutton>
* </hbox>
* <box class="highlighter-nodeinfobar-arrow highlighter-nodeinfobar-arrow-bottom"/>
* </box>
* </box>
* </stack>
*
*/
/**
* Constructor.
*
* @param aTarget The inspection target.
* @param aInspector Inspector panel.
* @param aToolbox The toolbox holding the inspector.
*/
function LocalHighlighter(aTarget, aInspector, aToolbox)
{
this.target = aTarget;
this.tab = aTarget.tab;
this.toolbox = aToolbox;
this.browser = this.tab.linkedBrowser;
this.chromeDoc = this.tab.ownerDocument;
this.chromeWin = this.chromeDoc.defaultView;
this.inspector = aInspector
this.layoutHelpers = new LayoutHelpers(this.browser.contentWindow);
EventEmitter.decorate(this);
this._init();
}
LocalHighlighter.prototype = {
get selection() {
return this.inspector.selection;
},
_init: function LocalHighlighter__init()
{
this.toggleLockState = this.toggleLockState.bind(this);
this.unlockAndFocus = this.unlockAndFocus.bind(this);
this.updateInfobar = this.updateInfobar.bind(this);
this.highlight = this.highlight.bind(this);
let stack = this.browser.parentNode;
this.win = this.browser.contentWindow;
this._highlighting = false;
this.highlighterContainer = this.chromeDoc.createElement("stack");
this.highlighterContainer.className = "highlighter-container";
this.outline = this.chromeDoc.createElement("box");
this.outline.className = "highlighter-outline";
let outlineContainer = this.chromeDoc.createElement("box");
outlineContainer.appendChild(this.outline);
outlineContainer.className = "highlighter-outline-container";
// The controlsBox will host the different interactive
// elements of the highlighter (buttons, toolbars, ...).
let controlsBox = this.chromeDoc.createElement("box");
controlsBox.className = "highlighter-controls";
this.highlighterContainer.appendChild(outlineContainer);
this.highlighterContainer.appendChild(controlsBox);
// Insert the highlighter right after the browser
stack.insertBefore(this.highlighterContainer, stack.childNodes[1]);
this.buildInfobar(controlsBox);
this.transitionDisabler = null;
this.pageEventsMuter = null;
this.selection.on("new-node", this.highlight);
this.selection.on("new-node", this.updateInfobar);
this.selection.on("pseudoclass", this.updateInfobar);
this.selection.on("attribute-changed", this.updateInfobar);
this.onToolSelected = function(event, id) {
if (id != "inspector") {
this.chromeWin.clearTimeout(this.pageEventsMuter);
this.detachMouseListeners();
this.disabled = true;
this.hide();
} else {
if (!this.locked) {
this.attachMouseListeners();
}
this.disabled = false;
this.show();
}
}.bind(this);
this.toolbox.on("select", this.onToolSelected);
this.hidden = true;
this.highlight();
},
/**
* Destroy the nodes. Remove listeners.
*/
destroy: function LocalHighlighter_destroy()
{
this.inspectButton.removeEventListener("command", this.unlockAndFocus);
this.inspectButton = null;
this.toolbox.off("select", this.onToolSelected);
this.toolbox = null;
this.selection.off("new-node", this.highlight);
this.selection.off("new-node", this.updateInfobar);
this.selection.off("pseudoclass", this.updateInfobar);
this.selection.off("attribute-changed", this.updateInfobar);
this.detachMouseListeners();
this.detachPageListeners();
this.chromeWin.clearTimeout(this.transitionDisabler);
this.chromeWin.clearTimeout(this.pageEventsMuter);
this.boundCloseEventHandler = null;
this._contentRect = null;
this._highlightRect = null;
this._highlighting = false;
this.outline = null;
this.nodeInfo = null;
this.highlighterContainer.parentNode.removeChild(this.highlighterContainer);
this.highlighterContainer = null;
this.win = null
this.browser = null;
this.chromeDoc = null;
this.chromeWin = null;
this.tabbrowser = null;
this.emit("closed");
},
/**
* Show the outline, and select a node.
*/
highlight: function LocalHighlighter_highlight()
{
if (this.selection.reason != "highlighter") {
this.lock();
}
let canHighlightNode = this.selection.isNode() &&
this.selection.isConnected() &&
this.selection.isElementNode();
if (canHighlightNode) {
if (this.selection.reason != "navigateaway") {
this.disabled = false;
}
this.show();
this.updateInfobar();
this.invalidateSize();
if (!this._highlighting &&
this.selection.reason != "highlighter") {
this.layoutHelpers.scrollIntoViewIfNeeded(this.selection.node);
}
} else {
this.disabled = true;
this.hide();
}
},
/**
* Update the highlighter size and position.
*/
invalidateSize: function LocalHighlighter_invalidateSize()
{
let canHiglightNode = this.selection.isNode() &&
this.selection.isConnected() &&
this.selection.isElementNode();
if (!canHiglightNode)
return;
// The highlighter runs locally while the selection runs remotely,
// so we can't quite trust the selection's isConnected to protect us
// here, do the check manually.
if (!this.selection.node ||
!this.selection.node.ownerDocument ||
!this.selection.node.ownerDocument.defaultView) {
return;
}
let clientRect = this.selection.node.getBoundingClientRect();
let rect = this.layoutHelpers.getDirtyRect(this.selection.node);
this.highlightRectangle(rect);
this.moveInfobar();
if (this._highlighting) {
this.showOutline();
this.emit("highlighting");
}
},
/**
* Show the highlighter if it has been hidden.
*/
show: function() {
if (!this.hidden || this.disabled) return;
this.showOutline();
this.showInfobar();
this.computeZoomFactor();
this.attachPageListeners();
this.invalidateSize();
this.hidden = false;
},
/**
* Hide the highlighter, the outline and the infobar.
*/
hide: function() {
if (this.hidden) return;
this.hideOutline();
this.hideInfobar();
this.detachPageListeners();
this.hidden = true;
},
/**
* Is the highlighter visible?
*
* @return boolean
*/
isHidden: function() {
return this.hidden;
},
/**
* Lock a node. Stops the inspection.
*/
lock: function() {
if (this.locked === true) return;
this.outline.setAttribute("locked", "true");
this.nodeInfo.container.setAttribute("locked", "true");
this.detachMouseListeners();
this.locked = true;
this.emit("locked");
},
/**
* Start inspecting.
* Unlock the current node (if any), and select any node being hovered.
*/
unlock: function() {
if (this.locked === false) return;
this.outline.removeAttribute("locked");
this.nodeInfo.container.removeAttribute("locked");
this.attachMouseListeners();
this.locked = false;
if (this.selection.isElementNode() &&
this.selection.isConnected()) {
this.showOutline();
}
this.emit("unlocked");
},
/**
* Toggle between locked and unlocked
*/
toggleLockState: function() {
if (this.locked) {
this.startNode = this.selection.node;
this.unlockAndFocus();
} else {
this.selection.setNode(this.startNode);
this.lock();
}
},
/**
* Focus the browser before unlocking.
*/
unlockAndFocus: function LocalHighlighter_unlockAndFocus() {
if (this.locked === false) return;
this.chromeWin.focus();
this.unlock();
},
/**
* Hide the infobar
*/
hideInfobar: function LocalHighlighter_hideInfobar() {
this.nodeInfo.container.setAttribute("force-transitions", "true");
this.nodeInfo.container.setAttribute("hidden", "true");
},
/**
* Show the infobar
*/
showInfobar: function LocalHighlighter_showInfobar() {
this.nodeInfo.container.removeAttribute("hidden");
this.moveInfobar();
this.nodeInfo.container.removeAttribute("force-transitions");
},
/**
* Hide the outline
*/
hideOutline: function LocalHighlighter_hideOutline() {
this.outline.setAttribute("hidden", "true");
},
/**
* Show the outline
*/
showOutline: function LocalHighlighter_showOutline() {
if (this._highlighting)
this.outline.removeAttribute("hidden");
},
/**
* Build the node Infobar.
*
* <box class="highlighter-nodeinfobar-container">
* <box class="highlighter-nodeinfobar-arrow-top"/>
* <hbox class="highlighter-nodeinfobar">
* <toolbarbutton class="highlighter-nodeinfobar-button highlighter-nodeinfobar-inspectbutton"/>
* <hbox class="highlighter-nodeinfobar-text">
* <xhtml:span class="highlighter-nodeinfobar-tagname"/>
* <xhtml:span class="highlighter-nodeinfobar-id"/>
* <xhtml:span class="highlighter-nodeinfobar-classes"/>
* <xhtml:span class="highlighter-nodeinfobar-pseudo-classes"/>
* </hbox>
* <toolbarbutton class="highlighter-nodeinfobar-button highlighter-nodeinfobar-menu"/>
* </hbox>
* <box class="highlighter-nodeinfobar-arrow-bottom"/>
* </box>
*
* @param nsIDOMElement aParent
* The container of the infobar.
*/
buildInfobar: function LocalHighlighter_buildInfobar(aParent)
{
let container = this.chromeDoc.createElement("box");
container.className = "highlighter-nodeinfobar-container";
container.setAttribute("position", "top");
container.setAttribute("disabled", "true");
let nodeInfobar = this.chromeDoc.createElement("hbox");
nodeInfobar.className = "highlighter-nodeinfobar";
let arrowBoxTop = this.chromeDoc.createElement("box");
arrowBoxTop.className = "highlighter-nodeinfobar-arrow highlighter-nodeinfobar-arrow-top";
let arrowBoxBottom = this.chromeDoc.createElement("box");
arrowBoxBottom.className = "highlighter-nodeinfobar-arrow highlighter-nodeinfobar-arrow-bottom";
let tagNameLabel = this.chromeDoc.createElementNS("http://www.w3.org/1999/xhtml", "span");
tagNameLabel.className = "highlighter-nodeinfobar-tagname";
let idLabel = this.chromeDoc.createElementNS("http://www.w3.org/1999/xhtml", "span");
idLabel.className = "highlighter-nodeinfobar-id";
let classesBox = this.chromeDoc.createElementNS("http://www.w3.org/1999/xhtml", "span");
classesBox.className = "highlighter-nodeinfobar-classes";
let pseudoClassesBox = this.chromeDoc.createElementNS("http://www.w3.org/1999/xhtml", "span");
pseudoClassesBox.className = "highlighter-nodeinfobar-pseudo-classes";
// Add some content to force a better boundingClientRect down below.
pseudoClassesBox.textContent = "&nbsp;";
// Create buttons
this.inspectButton = this.chromeDoc.createElement("toolbarbutton");
this.inspectButton.className = "highlighter-nodeinfobar-button highlighter-nodeinfobar-inspectbutton"
let toolbarInspectButton = this.inspector.panelDoc.getElementById("inspector-inspect-toolbutton");
this.inspectButton.setAttribute("tooltiptext", toolbarInspectButton.getAttribute("tooltiptext"));
this.inspectButton.addEventListener("command", this.toggleLockState);
let nodemenu = this.chromeDoc.createElement("toolbarbutton");
nodemenu.setAttribute("type", "menu");
nodemenu.className = "highlighter-nodeinfobar-button highlighter-nodeinfobar-menu"
nodemenu.setAttribute("tooltiptext",
this.strings.GetStringFromName("nodeMenu.tooltiptext"));
nodemenu.onclick = function() {
this.inspector.showNodeMenu(nodemenu, "after_start");
}.bind(this);
// <hbox class="highlighter-nodeinfobar-text"/>
let texthbox = this.chromeDoc.createElement("hbox");
texthbox.className = "highlighter-nodeinfobar-text";
texthbox.setAttribute("align", "center");
texthbox.setAttribute("flex", "1");
texthbox.addEventListener("mousedown", function(aEvent) {
// On click, show the node:
if (this.selection.isElementNode()) {
this.layoutHelpers.scrollIntoViewIfNeeded(this.selection.node);
}
}.bind(this), true);
texthbox.appendChild(tagNameLabel);
texthbox.appendChild(idLabel);
texthbox.appendChild(classesBox);
texthbox.appendChild(pseudoClassesBox);
nodeInfobar.appendChild(this.inspectButton);
nodeInfobar.appendChild(texthbox);
nodeInfobar.appendChild(nodemenu);
container.appendChild(arrowBoxTop);
container.appendChild(nodeInfobar);
container.appendChild(arrowBoxBottom);
aParent.appendChild(container);
let barHeight = container.getBoundingClientRect().height;
this.nodeInfo = {
tagNameLabel: tagNameLabel,
idLabel: idLabel,
classesBox: classesBox,
pseudoClassesBox: pseudoClassesBox,
container: container,
barHeight: barHeight,
};
},
/**
* Highlight a rectangular region.
*
* @param object aRect
* The rectangle region to highlight.
* @returns boolean
* True if the rectangle was highlighted, false otherwise.
*/
highlightRectangle: function LocalHighlighter_highlightRectangle(aRect)
{
if (!aRect) {
this.unhighlight();
return;
}
let oldRect = this._contentRect;
if (oldRect && aRect.top == oldRect.top && aRect.left == oldRect.left &&
aRect.width == oldRect.width && aRect.height == oldRect.height) {
return; // same rectangle
}
let aRectScaled = this.layoutHelpers.getZoomedRect(this.win, aRect);
if (aRectScaled.left >= 0 && aRectScaled.top >= 0 &&
aRectScaled.width > 0 && aRectScaled.height > 0) {
this.showOutline();
// The bottom div and the right div are flexibles (flex=1).
// We don't need to resize them.
let top = "top:" + aRectScaled.top + "px;";
let left = "left:" + aRectScaled.left + "px;";
let width = "width:" + aRectScaled.width + "px;";
let height = "height:" + aRectScaled.height + "px;";
this.outline.setAttribute("style", top + left + width + height);
this._highlighting = true;
} else {
this.unhighlight();
}
this._contentRect = aRect; // save orig (non-scaled) rect
this._highlightRect = aRectScaled; // and save the scaled rect.
return;
},
/**
* Clear the highlighter surface.
*/
unhighlight: function LocalHighlighter_unhighlight()
{
this._highlighting = false;
this.hideOutline();
},
/**
* Update node information (tagName#id.class)
*/
updateInfobar: function LocalHighlighter_updateInfobar()
{
if (!this.selection.isElementNode()) {
this.nodeInfo.tagNameLabel.textContent = "";
this.nodeInfo.idLabel.textContent = "";
this.nodeInfo.classesBox.textContent = "";
this.nodeInfo.pseudoClassesBox.textContent = "";
return;
}
let node = this.selection.node;
// Tag name
this.nodeInfo.tagNameLabel.textContent = node.tagName;
// ID
this.nodeInfo.idLabel.textContent = node.id ? "#" + node.id : "";
// Classes
let classes = this.nodeInfo.classesBox;
classes.textContent = node.classList.length ?
"." + Array.join(node.classList, ".") : "";
// Pseudo-classes
let pseudos = PSEUDO_CLASSES.filter(function(pseudo) {
return DOMUtils.hasPseudoClassLock(node, pseudo);
}, this);
let pseudoBox = this.nodeInfo.pseudoClassesBox;
pseudoBox.textContent = pseudos.join("");
},
/**
* Move the Infobar to the right place in the highlighter.
*/
moveInfobar: function LocalHighlighter_moveInfobar()
{
if (this._highlightRect) {
let winHeight = this.win.innerHeight * this.zoom;
let winWidth = this.win.innerWidth * this.zoom;
let rect = {top: this._highlightRect.top,
left: this._highlightRect.left,
width: this._highlightRect.width,
height: this._highlightRect.height};
rect.top = Math.max(rect.top, 0);
rect.left = Math.max(rect.left, 0);
rect.width = Math.max(rect.width, 0);
rect.height = Math.max(rect.height, 0);
rect.top = Math.min(rect.top, winHeight);
rect.left = Math.min(rect.left, winWidth);
this.nodeInfo.container.removeAttribute("disabled");
// Can the bar be above the node?
if (rect.top < this.nodeInfo.barHeight) {
// No. Can we move the toolbar under the node?
if (rect.top + rect.height +
this.nodeInfo.barHeight > winHeight) {
// No. Let's move it inside.
this.nodeInfo.container.style.top = rect.top + "px";
this.nodeInfo.container.setAttribute("position", "overlap");
} else {
// Yes. Let's move it under the node.
this.nodeInfo.container.style.top = rect.top + rect.height + "px";
this.nodeInfo.container.setAttribute("position", "bottom");
}
} else {
// Yes. Let's move it on top of the node.
this.nodeInfo.container.style.top =
rect.top - this.nodeInfo.barHeight + "px";
this.nodeInfo.container.setAttribute("position", "top");
}
let barWidth = this.nodeInfo.container.getBoundingClientRect().width;
let left = rect.left + rect.width / 2 - barWidth / 2;
// Make sure the whole infobar is visible
if (left < 0) {
left = 0;
this.nodeInfo.container.setAttribute("hide-arrow", "true");
} else {
if (left + barWidth > winWidth) {
left = winWidth - barWidth;
this.nodeInfo.container.setAttribute("hide-arrow", "true");
} else {
this.nodeInfo.container.removeAttribute("hide-arrow");
}
}
this.nodeInfo.container.style.left = left + "px";
} else {
this.nodeInfo.container.style.left = "0";
this.nodeInfo.container.style.top = "0";
this.nodeInfo.container.setAttribute("position", "top");
this.nodeInfo.container.setAttribute("hide-arrow", "true");
}
},
/**
* Store page zoom factor.
*/
computeZoomFactor: function LocalHighlighter_computeZoomFactor() {
this.zoom =
this.win.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils)
.fullZoom;
},
/////////////////////////////////////////////////////////////////////////
//// Event Handling
attachMouseListeners: function LocalHighlighter_attachMouseListeners()
{
this.browser.addEventListener("mousemove", this, true);
this.browser.addEventListener("click", this, true);
this.browser.addEventListener("dblclick", this, true);
this.browser.addEventListener("mousedown", this, true);
this.browser.addEventListener("mouseup", this, true);
},
detachMouseListeners: function LocalHighlighter_detachMouseListeners()
{
this.browser.removeEventListener("mousemove", this, true);
this.browser.removeEventListener("click", this, true);
this.browser.removeEventListener("dblclick", this, true);
this.browser.removeEventListener("mousedown", this, true);
this.browser.removeEventListener("mouseup", this, true);
},
attachPageListeners: function LocalHighlighter_attachPageListeners()
{
this.browser.addEventListener("resize", this, true);
this.browser.addEventListener("scroll", this, true);
this.browser.addEventListener("MozAfterPaint", this, true);
},
detachPageListeners: function LocalHighlighter_detachPageListeners()
{
this.browser.removeEventListener("resize", this, true);
this.browser.removeEventListener("scroll", this, true);
this.browser.removeEventListener("MozAfterPaint", this, true);
},
/**
* Generic event handler.
*
* @param nsIDOMEvent aEvent
* The DOM event object.
*/
handleEvent: function LocalHighlighter_handleEvent(aEvent)
{
switch (aEvent.type) {
case "click":
this.handleClick(aEvent);
break;
case "mousemove":
this.brieflyIgnorePageEvents();
this.handleMouseMove(aEvent);
break;
case "resize":
this.computeZoomFactor();
break;
case "MozAfterPaint":
case "scroll":
this.brieflyDisableTransitions();
this.invalidateSize();
break;
case "dblclick":
case "mousedown":
case "mouseup":
aEvent.stopPropagation();
aEvent.preventDefault();
break;
}
},
/**
* Disable the CSS transitions for a short time to avoid laggy animations
* during scrolling or resizing.
*/
brieflyDisableTransitions: function LocalHighlighter_brieflyDisableTransitions()
{
if (this.transitionDisabler) {
this.chromeWin.clearTimeout(this.transitionDisabler);
} else {
this.outline.setAttribute("disable-transitions", "true");
this.nodeInfo.container.setAttribute("disable-transitions", "true");
}
this.transitionDisabler =
this.chromeWin.setTimeout(function() {
this.outline.removeAttribute("disable-transitions");
this.nodeInfo.container.removeAttribute("disable-transitions");
this.transitionDisabler = null;
}.bind(this), 500);
},
/**
* Don't listen to page events while inspecting with the mouse.
*/
brieflyIgnorePageEvents: function LocalHighlighter_brieflyIgnorePageEvents()
{
// The goal is to keep smooth animations while inspecting.
// CSS Transitions might be interrupted because of a MozAfterPaint
// event that would triger an invalidateSize() call.
// So we don't listen to events that would trigger an invalidateSize()
// call.
//
// Side effect, zoom levels are not updated during this short period.
// It's very unlikely this would happen, but just in case, we call
// computeZoomFactor() when reattaching the events.
if (this.pageEventsMuter) {
this.chromeWin.clearTimeout(this.pageEventsMuter);
} else {
this.detachPageListeners();
}
this.pageEventsMuter =
this.chromeWin.setTimeout(function() {
this.attachPageListeners();
// Just in case the zoom level changed while ignoring the paint events
this.computeZoomFactor();
this.pageEventsMuter = null;
}.bind(this), 500);
},
/**
* Handle clicks.
*
* @param nsIDOMEvent aEvent
* The DOM event.
*/
handleClick: function LocalHighlighter_handleClick(aEvent)
{
// Stop inspection when the user clicks on a node.
if (aEvent.button == 0) {
this.lock();
let node = this.selection.node;
this.selection.setNode(node, "highlighter-lock");
aEvent.preventDefault();
aEvent.stopPropagation();
}
},
/**
* Handle mousemoves in panel.
*
* @param nsiDOMEvent aEvent
* The MouseEvent triggering the method.
*/
handleMouseMove: function LocalHighlighter_handleMouseMove(aEvent)
{
let doc = aEvent.target.ownerDocument;
// This should never happen, but just in case, we don't let the
// highlighter highlight browser nodes.
if (doc && doc != this.chromeDoc) {
let element = this.layoutHelpers.getElementFromPoint(aEvent.target.ownerDocument,
aEvent.clientX, aEvent.clientY);
if (element && element != this.selection.node) {
this.selection.setNode(element, "highlighter");
}
}
},
};
// BasicHighlighter. Doesn't implement any fancy features. Just change
// the outline of the selected node. Works with remote target.
function BasicHighlighter(aTarget, aInspector)
{
this.walker = aInspector.walker;
this.selection = aInspector.selection;
this.highlight = this.highlight.bind(this);
this.selection.on("new-node-front", this.highlight);
EventEmitter.decorate(this);
this.locked = true;
}
BasicHighlighter.prototype = {
destroy: function() {
this.walker.highlight(null);
this.selection.off("new-node-front", this.highlight);
this.walker = null;
this.selection = null;
},
toggleLockState: function() {
this.locked = !this.locked;
if (this.locked) {
this.walker.cancelPick();
} else {
this.emit("unlocked");
this.walker.pick().then(
(node) => this._onPick(node),
() => this._onPick(null)
);
}
},
highlight: function() {
this.walker.highlight(this.selection.nodeFront);
},
_onPick: function(node) {
if (node) {
this.selection.setNodeFront(node);
}
this.locked = true;
this.emit("locked");
},
hide: function() {},
show: function() {},
}
///////////////////////////////////////////////////////////////////////////
XPCOMUtils.defineLazyGetter(this, "DOMUtils", function () {
return Cc["@mozilla.org/inspector/dom-utils;1"].getService(Ci.inIDOMUtils)
});
XPCOMUtils.defineLazyGetter(LocalHighlighter.prototype, "strings", function () {
return Services.strings.createBundle(
"chrome://browser/locale/devtools/inspector.properties");
});

View File

@ -13,20 +13,16 @@ let EventEmitter = require("devtools/shared/event-emitter");
let {CssLogic} = require("devtools/styleinspector/css-logic");
loader.lazyGetter(this, "MarkupView", () => require("devtools/markupview/markup-view").MarkupView);
loader.lazyGetter(this, "Selection", () => require("devtools/inspector/selection").Selection);
loader.lazyGetter(this, "HTMLBreadcrumbs", () => require("devtools/inspector/breadcrumbs").HTMLBreadcrumbs);
loader.lazyGetter(this, "Highlighter", () => require("devtools/inspector/highlighter").Highlighter);
loader.lazyGetter(this, "ToolSidebar", () => require("devtools/framework/sidebar").ToolSidebar);
loader.lazyGetter(this, "SelectorSearch", () => require("devtools/inspector/selector-search").SelectorSearch);
loader.lazyGetter(this, "InspectorFront", () => require("devtools/server/actors/inspector").InspectorFront);
const LAYOUT_CHANGE_TIMER = 250;
/**
* Represents an open instance of the Inspector for a tab.
* The inspector controls the highlighter, the breadcrumbs,
* the markup view, and the sidebar (computed view, rule view
* and layout view).
* The inspector controls the breadcrumbs, the markup view, and the sidebar
* (computed view, rule view, font view and layout view).
*
* Events:
* - ready
@ -77,7 +73,7 @@ InspectorPanel.prototype = {
*/
open: function InspectorPanel_open() {
return this.target.makeRemote().then(() => {
return this._getWalker();
return this._getPageStyle();
}).then(() => {
return this._getDefaultNodeForSelection();
}).then(defaultSelection => {
@ -85,21 +81,29 @@ InspectorPanel.prototype = {
}).then(null, console.error);
},
get toolbox() {
return this._toolbox;
},
get inspector() {
if (!this._target.form) {
throw new Error("Target.inspector requires an initialized remote actor.");
}
if (!this._inspector) {
this._inspector = InspectorFront(this._target.client, this._target.form);
}
return this._inspector;
return this._toolbox.inspector;
},
get walker() {
return this._toolbox.walker;
},
get selection() {
return this._toolbox.selection;
},
get isOuterHTMLEditable() {
return this._target.client.traits.editOuterHTML;
},
_deferredOpen: function(defaultSelection) {
let deferred = promise.defer();
this.outerHTMLEditable = this._target.client.traits.editOuterHTML;
this.onNewRoot = this.onNewRoot.bind(this);
this.walker.on("new-root", this.onNewRoot);
@ -110,8 +114,6 @@ InspectorPanel.prototype = {
this.nodemenu.addEventListener("popupshowing", this._setupNodeMenu, true);
this.nodemenu.addEventListener("popuphiding", this._resetNodeMenu, true);
// Create an empty selection
this._selection = new Selection(this.walker);
this.onNewSelection = this.onNewSelection.bind(this);
this.selection.on("new-node-front", this.onNewSelection);
this.onBeforeNewSelection = this.onBeforeNewSelection.bind(this);
@ -154,19 +156,6 @@ InspectorPanel.prototype = {
this.updateDebuggerPausedWarning();
}
this.highlighter = new Highlighter(this.target, this, this._toolbox);
let button = this.panelDoc.getElementById("inspector-inspect-toolbutton");
this.onLockStateChanged = function() {
if (this.highlighter.locked) {
button.removeAttribute("checked");
this._toolbox.raise();
} else {
button.setAttribute("checked", "true");
}
}.bind(this);
this.highlighter.on("locked", this.onLockStateChanged);
this.highlighter.on("unlocked", this.onLockStateChanged);
this._initMarkup();
this.isReady = false;
@ -174,7 +163,7 @@ InspectorPanel.prototype = {
this.isReady = true;
// All the components are initialized. Let's select a node.
this._selection.setNodeFront(defaultSelection);
this.selection.setNodeFront(defaultSelection, "inspector-open");
this.markup.expandNode(this.selection.nodeFront);
@ -195,11 +184,8 @@ InspectorPanel.prototype = {
this.isDirty = false;
},
_getWalker: function() {
return this.inspector.getWalker().then(walker => {
this.walker = walker;
return this.inspector.getPageStyle();
}).then(pageStyle => {
_getPageStyle: function() {
return this._toolbox.inspector.getPageStyle().then(pageStyle => {
this.pageStyle = pageStyle;
});
},
@ -238,13 +224,6 @@ InspectorPanel.prototype = {
});
},
/**
* Selection object (read only)
*/
get selection() {
return this._selection;
},
/**
* Target getter.
*/
@ -314,7 +293,6 @@ InspectorPanel.prototype = {
}.bind(this);
this.sidebar.on("select", this._setDefaultSidebar);
this.toggleHighlighter = this.toggleHighlighter.bind(this);
this.sidebar.addTab("ruleview",
"chrome://browser/content/devtools/cssruleview.xhtml",
@ -335,8 +313,6 @@ InspectorPanel.prototype = {
"layoutview" == defaultTab);
let ruleViewTab = this.sidebar.getTab("ruleview");
ruleViewTab.addEventListener("mouseover", this.toggleHighlighter, false);
ruleViewTab.addEventListener("mouseout", this.toggleHighlighter, false);
this.sidebar.show();
},
@ -351,14 +327,11 @@ InspectorPanel.prototype = {
this.isDirty = false;
this._getDefaultNodeForSelection().then(defaultNode => {
if (this._destroyPromise) {
return;
}
this.selection.setNodeFront(defaultNode, "navigateaway");
this._initMarkup();
this.once("markuploaded", () => {
if (this._destroyPromise) {
if (!this.markup) {
return;
}
this.markup.expandNode(this.selection.nodeFront);
@ -399,6 +372,10 @@ InspectorPanel.prototype = {
* When a new node is selected.
*/
onNewSelection: function InspectorPanel_onNewSelection(event, value, reason) {
if (reason === "selection-destroy") {
return;
}
this.cancelLayoutChange();
// Wait for all the known tools to finish updating and then let the
@ -500,34 +477,14 @@ InspectorPanel.prototype = {
* Destroy the inspector.
*/
destroy: function InspectorPanel__destroy() {
if (this._destroyPromise) {
return this._destroyPromise;
if (this._panelDestroyer) {
return this._panelDestroyer.promise;
}
if (this.highlighter) {
this.highlighter.off("locked", this.onLockStateChanged);
this.highlighter.off("unlocked", this.onLockStateChanged);
this.highlighter.destroy();
}
delete this.onLockStateChanged;
this._panelDestroyer = promise.defer();
if (this.walker) {
this.walker.off("new-root", this.onNewRoot);
this._destroyPromise = this.walker.release()
.then(() => this._inspector.destroy(),
(e) => {
console.error("Walker.release() failed: " + e);
return this._inspector.destroy();
})
.then(() => {
this._inspector = null;
}, console.error);
delete this.walker;
delete this.pageStyle;
} else {
this._destroyPromise = promise.resolve(null);
this.pageStyle = null;
}
this.cancelUpdate();
@ -544,8 +501,6 @@ InspectorPanel.prototype = {
this.target.off("thread-resumed", this.updateDebuggerPausedWarning);
this._toolbox.off("select", this.updateDebuggerPausedWarning);
this._toolbox = null;
this.sidebar.off("select", this._setDefaultSidebar);
this.sidebar.destroy();
this.sidebar = null;
@ -554,14 +509,12 @@ InspectorPanel.prototype = {
this.nodemenu.removeEventListener("popuphiding", this._resetNodeMenu, true);
this.breadcrumbs.destroy();
this.searchSuggestions.destroy();
delete this.searchBox;
this.searchBox = null;
this.selection.off("new-node-front", this.onNewSelection);
this.selection.off("before-new-node", this.onBeforeNewSelection);
this.selection.off("before-new-node-front", this.onBeforeNewSelection);
this.selection.off("detached-front", this.onDetached);
this._destroyMarkup();
this._selection.destroy();
this._selection = null;
this.panelWin.inspector = null;
this.target = null;
this.panelDoc = null;
@ -570,9 +523,10 @@ InspectorPanel.prototype = {
this.searchSuggestions = null;
this.lastNodemenuItem = null;
this.nodemenu = null;
this.highlighter = null;
this._toolbox = null;
return this._destroyPromise;
this._panelDestroyer.resolve(null);
return this._panelDestroyer.promise;
},
/**
@ -633,7 +587,7 @@ InspectorPanel.prototype = {
}
let editHTML = this.panelDoc.getElementById("node-menu-edithtml");
if (this.outerHTMLEditable && selectionIsElement) {
if (this.isOuterHTMLEditable && selectionIsElement) {
editHTML.removeAttribute("disabled");
} else {
editHTML.setAttribute("disabled", "true");
@ -686,17 +640,17 @@ InspectorPanel.prototype = {
_destroyMarkup: function InspectorPanel__destroyMarkup() {
if (this._boundMarkupFrameLoad) {
this._markupFrame.removeEventListener("load", this._boundMarkupFrameLoad, true);
delete this._boundMarkupFrameLoad;
this._boundMarkupFrameLoad = null;
}
if (this.markup) {
this.markup.destroy();
delete this.markup;
this.markup = null;
}
if (this._markupFrame) {
this._markupFrame.parentNode.removeChild(this._markupFrame);
delete this._markupFrame;
this._markupFrame = null;
}
this._markupBox = null;
@ -709,11 +663,11 @@ InspectorPanel.prototype = {
if (this.selection.isElementNode()) {
let node = this.selection.nodeFront;
if (node.hasPseudoClassLock(aPseudo)) {
return this.walker.removePseudoClassLock(node, aPseudo, { parents: true });
return this.walker.removePseudoClassLock(node, aPseudo, {parents: true});
}
let hierarchical = aPseudo == ":hover" || aPseudo == ":active";
return this.walker.addPseudoClassLock(node, aPseudo, { parents: hierarchical });
return this.walker.addPseudoClassLock(node, aPseudo, {parents: hierarchical});
}
},
@ -727,22 +681,6 @@ InspectorPanel.prototype = {
return this.walker.clearPseudoClassLocks().then(null, console.error);
},
/**
* Toggle the highlighter when ruleview is hovered.
*/
toggleHighlighter: function InspectorPanel_toggleHighlighter(event)
{
if (!this.highlighter) {
return;
}
if (event.type == "mouseover") {
this.highlighter.hide();
}
else if (event.type == "mouseout") {
this.highlighter.show();
}
},
/**
* Edit the outerHTML of the selected Node.
*/

View File

@ -75,10 +75,6 @@
<toolbar id="inspector-toolbar"
class="devtools-toolbar"
nowindowdrag="true">
<toolbarbutton id="inspector-inspect-toolbutton"
tooltiptext="&inspector.selectButton.tooltip;"
class="devtools-toolbarbutton"
oncommand="inspector.highlighter.toggleLockState()"/>
<arrowscrollbox id="inspector-breadcrumbs"
class="breadcrumbs-widget-container"
flex="1" orient="horizontal"

View File

@ -24,14 +24,12 @@ support-files =
[browser_inspector_bug_831693_input_suggestion.js]
# [browser_inspector_bug_831693_searchbox_panel_navigation.js]
# Disabled for too many intermittent failures (bug 851349)
[browser_inspector_bug_835722_infobar_reappears.js]
[browser_inspector_bug_840156_destroy_after_navigation.js]
[browser_inspector_changes.js]
[browser_inspector_cmd_inspect.js]
[browser_inspector_dead_node_exception.js]
[browser_inspector_destroyselection.js]
[browser_inspector_highlighter.js]
[browser_inspector_highlighter_autohide.js]
[browser_inspector_iframeTest.js]
[browser_inspector_infobar.js]
[browser_inspector_initialization.js]

View File

@ -2,13 +2,12 @@
* 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/. */
function test() {
let inspector, doc;
let inspector, doc, toolbox;
let {devtools} = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
let {require} = devtools;
let promise = require("sdk/core/promise");
let { Task } = Cu.import("resource://gre/modules/Task.jsm", {});
let {Task} = Cu.import("resource://gre/modules/Task.jsm", {});
waitForExplicitFinish();
@ -19,75 +18,67 @@ function test() {
waitForFocus(setupTest, content);
}, true);
content.location = "data:text/html,<h1>foo<h1><h2>bar</h2>";
content.location = "data:text/html,<h1>foo</h1><h2>bar</h2>";
function setupTest() {
let h = require("devtools/inspector/highlighter");
h._forceBasic.value = true;
openInspector(runTests);
openInspector((aInspector, aToolbox) => {
toolbox = aToolbox;
inspector = aInspector;
inspector.selection.setNode(doc.querySelector("h2"), null);
inspector.once("inspector-updated", runTests);
});
}
function runTests(aInspector) {
inspector = aInspector;
getHighlighterOutline().setAttribute("disable-transitions", "true");
Task.spawn(function() {
yield selectH1();
yield verifyH1Selected();
yield deselect();
yield verifyNoNodeSelected();
yield selectH1();
yield verifyH1Selected();
yield destroyInspector();
yield verifyNoNodeSelected();
yield hoverH1InMarkupView();
yield assertH1Highlighted();
yield mouseLeaveMarkupView();
yield assertNoNodeHighlighted();
finishUp();
}).then(null, Cu.reportError);
}
function selectH1() {
function hoverH1InMarkupView() {
let deferred = promise.defer();
let h1 = doc.querySelector("h1");
inspector.selection.once("new-node-front", () => {
executeSoon(deferred.resolve);
});
inspector.selection.setNode(h1);
let container = getContainerForRawNode(inspector.markup, doc.querySelector("h1"));
EventUtils.synthesizeMouse(container.tagLine, 2, 2, {type: "mousemove"},
inspector.markup.doc.defaultView);
inspector.markup.once("node-highlight", deferred.resolve);
return deferred.promise;
}
function verifyH1Selected() {
let h1 = doc.querySelector("h1");
let nodes = doc.querySelectorAll(":-moz-devtools-highlighted");
is(nodes.length, 1, "only one node selected");
is(nodes[0], h1, "h1 selected");
function assertH1Highlighted() {
ok(isHighlighting(), "The highlighter is shown on a markup container hover");
is(getHighlitNode(), doc.querySelector("h1"), "The highlighter highlights the right node");
return promise.resolve();
}
function deselect() {
function mouseLeaveMarkupView() {
let deferred = promise.defer();
inspector.selection.once("new-node-front", () => {
executeSoon(deferred.resolve);
});
inspector.selection.setNode(null);
// Find another element to mouseover over in order to leave the markup-view
let btn = toolbox.doc.querySelector(".toolbox-dock-button");
EventUtils.synthesizeMouse(btn, 2, 2, {type: "mousemove"},
toolbox.doc.defaultView);
executeSoon(deferred.resolve);
return deferred.promise;
}
function destroyInspector() {
return inspector.destroy();
}
function verifyNoNodeSelected() {
is(doc.querySelectorAll(":-moz-devtools-highlighted").length, 0, "no node selected");
function assertNoNodeHighlighted() {
ok(!isHighlighting(), "After the mouse left the markup view, the highlighter is hidden");
return promise.resolve();
}
function finishUp() {
let h = require("devtools/inspector/highlighter");
h._forceBasic.value = false;
inspector = doc = null;
inspector = doc = toolbox = null;
gBrowser.removeCurrentTab();
finish();
}
}

View File

@ -57,7 +57,10 @@ function test()
if (cursor >= nodes.length) {
inspector.off("breadcrumbs-updated", nodeSelected);
finishUp();
// breadcrumbs-updated is an event that is fired before the rest of the
// inspector is updated, so there'll be hanging connections if we finish
// up before waiting for everything to end.
inspector.once("inspector-updated", finishUp);
} else {
let node = nodes[cursor].node;
inspector.selection.setNode(node);

View File

@ -1,9 +1,7 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
function test()
{
function test() {
waitForExplicitFinish();
ignoreAllUncaughtExceptions();
@ -19,22 +17,18 @@ function test()
content.location = "data:text/html,<object style='padding: 100px'><p>foobar</p></object>";
function setupObjectInspectionTest()
{
function setupObjectInspectionTest() {
objectNode = doc.querySelector("object");
ok(objectNode, "we have the object node");
openInspector(runObjectInspectionTest);
}
function runObjectInspectionTest(inspector)
{
inspector.highlighter.once("locked", performTestComparison);
inspector.highlighter.unlock();
function runObjectInspectionTest(inspector) {
inspector.once("inspector-updated", performTestComparison);
inspector.selection.setNode(objectNode, "");
}
function performTestComparison()
{
function performTestComparison() {
is(getActiveInspector().selection.node, objectNode, "selection matches node");
let target = TargetFactory.forTab(gBrowser.selectedTab);
executeSoon(function() {
@ -43,7 +37,6 @@ function test()
});
}
function finishUp() {
doc = objectNode = null;
gBrowser.removeCurrentTab();

View File

@ -7,6 +7,7 @@ function test()
let doc;
let iframeNode, iframeBodyNode;
let inspector;
let iframeSrc = "<style>" +
"body {" +
@ -45,57 +46,63 @@ function test()
iframeBodyNode = iframeNode.contentDocument.querySelector("body");
ok(iframeNode, "we have the iframe node");
ok(iframeBodyNode, "we have the body node");
openInspector(runTests);
}
function runTests(inspector)
{
inspector.highlighter.unlock();
executeSoon(function() {
inspector.highlighter.once("highlighting", isTheIframeSelected);
moveMouseOver(iframeNode, 1, 1);
openInspector(aInspector => {
inspector = aInspector;
// Make sure the highlighter is shown so we can disable transitions
inspector.toolbox.highlighter.showBoxModel(getNodeFront(doc.body)).then(() => {
getHighlighterOutline().setAttribute("disable-transitions", "true");
runTests();
});
});
}
function isTheIframeSelected()
function runTests()
{
let inspector = getActiveInspector();
inspector.toolbox.startPicker().then(() => {
moveMouseOver(iframeNode, 1, 1, isTheIframeHighlighted);
});
}
function isTheIframeHighlighted()
{
let outlineRect = getHighlighterOutlineRect();
let iframeRect = iframeNode.getBoundingClientRect();
for (let dim of ["width", "height", "top", "left"]) {
is(Math.floor(outlineRect[dim]), Math.floor(iframeRect[dim]), "Outline dimension is correct");
}
is(inspector.selection.node, iframeNode, "selection matches node");
iframeNode.style.marginBottom = doc.defaultView.innerHeight + "px";
doc.defaultView.scrollBy(0, 40);
executeSoon(function() {
inspector.selection.once("new-node", isTheIframeContentSelected);
moveMouseOver(iframeNode, 40, 40);
moveMouseOver(iframeNode, 40, 40, isTheIframeContentHighlighted);
}
function isTheIframeContentHighlighted()
{
is(getHighlitNode(), iframeBodyNode, "highlighter shows the right node");
// 184 == 200 + 11(border) + 13(padding) - 40(scroll)
let outlineRect = getHighlighterOutlineRect();
is(outlineRect.height, 184, "highlighter height");
inspector.toolbox.stopPicker().then(() => {
let target = TargetFactory.forTab(gBrowser.selectedTab);
gDevTools.closeToolbox(target);
finishUp();
});
}
function isTheIframeContentSelected()
function finishUp()
{
let inspector = getActiveInspector();
is(inspector.selection.node, iframeBodyNode, "selection matches node");
// 184 == 200 + 11(border) + 13(padding) - 40(scroll)
is(inspector.highlighter._highlightRect.height, 184,
"highlighter height");
let target = TargetFactory.forTab(gBrowser.selectedTab);
gDevTools.closeToolbox(target);
finishUp();
}
function finishUp() {
doc = iframeNode = iframeBodyNode = null;
doc = inspector = iframeNode = iframeBodyNode = null;
gBrowser.removeCurrentTab();
finish();
}
function moveMouseOver(aElement, x, y)
function moveMouseOver(aElement, x, y, cb)
{
EventUtils.synthesizeMouse(aElement, x, y, {type: "mousemove"},
aElement.ownerDocument.defaultView);
inspector.toolbox.once("picker-node-hovered", cb);
}
}

View File

@ -8,21 +8,37 @@ function test() {
let inspector;
function startTest() {
openInspector(runInspectorTests);
openInspector(aInspector => {
inspector = aInspector;
runInspectorTests();
});
}
function runInspectorTests(aInspector) {
inspector = aInspector;
function showHighlighter(cb) {
inspector.toolbox.startPicker().then(() => {
EventUtils.synthesizeMouse(content.document.body, 1, 1,
{type: "mousemove"}, content);
inspector.toolbox.once("picker-node-hovered", () => {
executeSoon(() => {
getHighlighterOutline().setAttribute("disable-transitions", "true");
cb();
});
});
});
}
function runInspectorTests() {
iframe = content.document.querySelector("iframe");
ok(iframe, "found the iframe element");
ok(inspector.highlighter._highlighting, "Inspector is highlighting");
showHighlighter(() => {
ok(isHighlighting(), "Inspector is highlighting");
iframe.addEventListener("load", onIframeLoad, false);
iframe.addEventListener("load", onIframeLoad, false);
executeSoon(function() {
iframe.contentWindow.location = "javascript:location.reload()";
executeSoon(function() {
iframe.contentWindow.location = "javascript:location.reload()";
});
});
}
@ -36,7 +52,7 @@ function test() {
iframe.removeEventListener("load", onIframeLoad, false);
ok(inspector.highlighter._highlighting, "Inspector is highlighting after iframe nav");
ok(isHighlighting(), "Inspector is highlighting after iframe nav");
checksAfterLoads = true;
@ -47,9 +63,11 @@ function test() {
is(iframeLoads, 2, "iframe loads");
ok(checksAfterLoads, "the Inspector tests got the chance to run after iframe reloads");
iframe = null;
gBrowser.removeCurrentTab();
executeSoon(finish);
inspector.toolbox.stopPicker().then(() => {
iframe = null;
gBrowser.removeCurrentTab();
executeSoon(finish);
});
}
waitForExplicitFinish();

View File

@ -1,103 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
function test() {
let inspector, utils;
function startLocationTests() {
openInspector(runInspectorTests);
}
function runInspectorTests(aInspector) {
inspector = aInspector;
utils = inspector.panelWin
.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils);
ok(utils, "utils is defined");
executeSoon(function() {
inspector.selection.once("new-node", onNewSelection);
info("selecting the DOCTYPE node");
inspector.selection.setNode(content.document.doctype, "test");
});
}
function sendMouseEvent(node, type, x, y) {
let rect = node.getBoundingClientRect();
let left = rect.left + x;
let top = rect.top + y;
utils.sendMouseEventToWindow(type, left, top, 0, 1, 0, false, 0, 0);
}
function onNewSelection() {
is(inspector.highlighter.isHidden(), true,
"The infobar should be hidden now on selecting a non element node.");
inspector.sidebar.select("ruleview");
let ruleView = inspector.sidebar.getTab("ruleview");
ruleView.addEventListener("mouseover", function onMouseOver() {
ruleView.removeEventListener("mouseover", onMouseOver, false);
is(inspector.highlighter.isHidden(), true,
"The infobar was hidden so mouseover on the rules view did nothing");
executeSoon(mouseOutAndContinue);
}, false);
sendMouseEvent(ruleView, "mouseover", 10, 10);
}
function mouseOutAndContinue() {
let ruleView = inspector.sidebar.getTab("ruleview");
info("adding mouseout listener");
ruleView.addEventListener("mouseout", function onMouseOut() {
info("mouseout happened");
ruleView.removeEventListener("mouseout", onMouseOut, false);
is(inspector.highlighter.isHidden(), true,
"The infobar should not be visible after we mouseout of rules view");
switchToWebConsole();
}, false);
info("Synthesizing mouseout on " + ruleView);
sendMouseEvent(inspector._markupBox, "mousemove", 50, 50);
info("mouseout synthesized");
}
function switchToWebConsole() {
inspector.selection.once("new-node", function() {
is(inspector.highlighter.isHidden(), false,
"The infobar should be visible after we select a div.");
gDevTools.showToolbox(inspector.target, "webconsole").then(function() {
is(inspector.highlighter.isHidden(), true,
"The infobar should not be visible after we switched to webconsole");
reloadAndWait();
});
});
inspector.selection.setNode(content.document.querySelector("div"), "test");
}
function reloadAndWait() {
gBrowser.selectedBrowser.addEventListener("load", function onBrowserLoad() {
gBrowser.selectedBrowser.removeEventListener("load", onBrowserLoad, true);
waitForFocus(testAfterReload, content);
}, true);
content.location.reload();
}
function testAfterReload() {
is(inspector.highlighter.isHidden(), true,
"The infobar should not be visible after we reload with webconsole shown");
testEnd();
}
function testEnd() {
gBrowser.removeCurrentTab();
utils = null;
executeSoon(finish);
}
waitForExplicitFinish();
gBrowser.selectedTab = gBrowser.addTab();
gBrowser.selectedBrowser.addEventListener("load", function onBrowserLoad() {
gBrowser.selectedBrowser.removeEventListener("load", onBrowserLoad, true);
waitForFocus(startLocationTests, content);
}, true);
content.location = "data:text/html,<!DOCTYPE html><div>Infobar should not " +
"reappear</div><p>init</p>";
}

View File

@ -33,6 +33,13 @@ function test() {
// select the inspector
.then(function () toolbox.selectTool("inspector"))
// wait until inspector ready
.then(function () {
let deferred = promise.defer();
toolbox.getPanel("inspector").once("inspector-updated", deferred.resolve);
return deferred.promise;
})
// navigate to URL_2
.then(function () {
let deferred = promise.defer();

View File

@ -139,8 +139,5 @@ function test() {
let breadcrumbs = inspector.panelDoc.getElementById("inspector-breadcrumbs");
is(breadcrumbs.querySelector("button[checked=true]").textContent, crumbLabel,
"The right breadcrumb is selected");
// Highlighter is shown?
ok(!inspector.highlighter.isHidden(), "The highlighter is shown");
}
}

View File

@ -34,7 +34,6 @@ function test() {
// select the inspector
return toolbox.selectTool("inspector").then(i => {
inspector = i;
// Verify we are on page one
let testNode = content.document.querySelector("#one");
ok(testNode, "We have the test node on page 1");

View File

@ -32,7 +32,7 @@ function test() {
function runInspectorTests(aInspector)
{
inspector = aInspector;
inspector.sidebar.once("computedview-ready", function() {
inspector.sidebar.once("computedview-ready", () => {
info("Computed View ready");
inspector.sidebar.select("computedview");
@ -41,8 +41,9 @@ function test() {
testDiv.style.fontSize = "10px";
// Start up the style inspector panel...
inspector.once("computed-view-refreshed", computedStylePanelTests);
inspector.once("computed-view-refreshed", () => {
executeSoon(computedStylePanelTests);
});
inspector.selection.setNode(testDiv);
});
}
@ -59,7 +60,9 @@ function test() {
// Wait until layout-change fires from mutation to skip earlier refresh event
inspector.once("layout-change", () => {
inspector.once("computed-view-refreshed", computedStylePanelAfterChange);
inspector.once("computed-view-refreshed", () => {
executeSoon(computedStylePanelAfterChange);
});
});
}
@ -79,12 +82,11 @@ function test() {
// Tests changes made while the style panel is not active.
inspector.sidebar.select("ruleview");
testDiv.style.fontSize = "20px";
testDiv.style.color = "blue";
testDiv.style.textAlign = "center";
testDiv.style.cssText = "font-size: 20px; color: blue; text-align: center";
inspector.once("computed-view-refreshed", computedStylePanelAfterSwitch);
inspector.sidebar.select("computedview");
inspector.once("computed-view-refreshed", () => {
executeSoon(computedStylePanelAfterSwitch);
});
}
function computedStylePanelAfterSwitch()
@ -110,13 +112,11 @@ function test() {
let propView = getInspectorRuleProp("text-align");
is(propView.value, "center", "Style inspector should be showing the new text align.");
testDiv.style.textAlign = "right";
testDiv.style.color = "lightgoldenrodyellow";
testDiv.style.fontSize = "3em";
testDiv.style.textTransform = "uppercase";
testDiv.style.cssText = "font-size: 3em; color: lightgoldenrodyellow; text-align: right; text-transform: uppercase";
inspector.once("rule-view-refreshed", rulePanelAfterChange);
inspector.once("rule-view-refreshed", () => {
executeSoon(rulePanelAfterChange);
});
}
function rulePanelAfterChange()

View File

@ -4,7 +4,6 @@
function test()
{
waitForExplicitFinish();
//ignoreAllUncaughtExceptions();
let node, iframe, inspector;
@ -28,20 +27,24 @@ function test()
inspector = aInspector;
inspector.selection.setNode(node);
iframe.parentNode.removeChild(iframe);
iframe = null;
inspector.once("inspector-updated", () => {
iframe.parentNode.removeChild(iframe);
iframe = null;
let tmp = {};
Cu.import("resource://gre/modules/devtools/LayoutHelpers.jsm", tmp);
let lh = new tmp.LayoutHelpers(window.content);
ok(!lh.isNodeConnected(node), "Node considered as disconnected.");
ok(!inspector.selection.isConnected(), "Selection considered as disconnected");
let tmp = {};
Cu.import("resource://gre/modules/devtools/LayoutHelpers.jsm", tmp);
let lh = new tmp.LayoutHelpers(window.content);
ok(!lh.isNodeConnected(node), "Node considered as disconnected.");
ok(!inspector.selection.isConnected(), "Selection considered as disconnected");
finishUp();
inspector.once("inspector-updated", () => {
finishUp();
});
});
}
function finishUp() {
node = null;
node = inspector = null;
gBrowser.removeCurrentTab();
finish();
}

View File

@ -6,12 +6,11 @@
let doc;
let h1;
let div;
let inspector;
function createDocument()
{
function createDocument() {
let div = doc.createElement("div");
let h1 = doc.createElement("h1");
h1 = doc.createElement("h1");
let p1 = doc.createElement("p");
let p2 = doc.createElement("p");
let div2 = doc.createElement("div");
@ -48,80 +47,38 @@ function createDocument()
doc.body.appendChild(div2);
doc.body.appendChild(div3);
openInspector(setupHighlighterTests);
}
function setupHighlighterTests()
{
h1 = doc.querySelector("h1");
ok(h1, "we have the header");
let i = getActiveInspector();
i.selection.setNode(div);
i.highlighter.unlockAndFocus();
i.highlighter.outline.setAttribute("disable-transitions", "true");
executeSoon(function() {
i.selection.once("new-node", performToggleComparisons);
EventUtils.synthesizeMouse(h1, 2, 2, {type: "mousemove"}, content);
openInspector(aInspector => {
inspector = aInspector;
inspector.selection.setNode(div, null);
inspector.once("inspector-updated", () => {
getHighlighterOutline().setAttribute("disable-transitions", "true");
inspector.toolbox.startPicker().then(testMouseOverH1Highlights);
});
});
}
function performToggleComparisons(evt)
{
let i = getActiveInspector();
i.highlighter.toggleLockState();
ok(i.highlighter.locked, "highlighter locks");
is(i.selection.node, div);
i.highlighter.toggleLockState();
ok(!i.highlighter.locked, "highlighter unlocks");
i.highlighter.toggleLockState();
ok(i.highlighter.locked, "highlighter locks if selection is unchanged");
i.highlighter.toggleLockState();
executeSoon(function() {
i.selection.once("new-node", performTestComparisons);
EventUtils.synthesizeMouse(h1, 2, 2, {type: "mousemove"}, content);
function testMouseOverH1Highlights() {
inspector.toolbox.once("picker-node-hovered", () => {
ok(isHighlighting(), "Highlighter is shown");
is(getHighlitNode(), h1, "Highlighter's outline correspond to the selected node");
testOutlineDimensions();
});
EventUtils.synthesizeMouse(h1, 2, 2, {type: "mousemove"}, content);
}
function performTestComparisons(evt)
{
let i = getActiveInspector();
i.highlighter.lock();
ok(isHighlighting(), "highlighter is highlighting");
is(getHighlitNode(), h1, "highlighter matches selection")
is(i.selection.node, h1, "selection matches node");
is(i.selection.node, getHighlitNode(), "selection matches highlighter");
function testOutlineDimensions() {
let h1Dims = h1.getBoundingClientRect();
let h1Width = h1Dims.width;
let h1Height = h1Dims.height;
div = doc.querySelector("div#checkOutThisWickedSpread");
executeSoon(function() {
i.selection.once("new-node", finishTestComparisons);
i.selection.setNode(div);
});
}
function finishTestComparisons()
{
let i = getActiveInspector();
// get dimensions of div element
let divDims = div.getBoundingClientRect();
let divWidth = divDims.width;
let divHeight = divDims.height;
// get dimensions of the outline
let outlineDims = i.highlighter.outline.getBoundingClientRect();
let outlineDims = getHighlighterOutlineRect();
let outlineWidth = outlineDims.width;
let outlineHeight = outlineDims.height;
// Disabled due to bug 716245
//is(outlineWidth, divWidth, "outline width matches dimensions of element (no zoom)");
//is(outlineHeight, divHeight, "outline height matches dimensions of element (no zoom)");
is(outlineWidth, h1Width, "outline width matches dimensions of element (no zoom)");
is(outlineHeight, h1Height, "outline height matches dimensions of element (no zoom)");
// zoom the page by a factor of 2
let contentViewer = gBrowser.selectedBrowser.docShell.contentViewer
@ -132,38 +89,36 @@ function finishTestComparisons()
// resize event
window.setTimeout(function() {
// check what zoom factor we're at, should be 2
let zoom = i.highlighter.zoom;
is(zoom, 2, "zoom is 2?");
// simulate the zoomed dimensions of the div element
let divDims = div.getBoundingClientRect();
let divWidth = divDims.width * zoom;
let divHeight = divDims.height * zoom;
let h1Dims = h1.getBoundingClientRect();
// There seems to be some very minor differences in the floats, so let's
// floor the values
let h1Width = Math.floor(h1Dims.width * contentViewer.fullZoom);
let h1Height = Math.floor(h1Dims.height * contentViewer.fullZoom);
// now zoomed, get new dimensions the outline
let outlineDims = i.highlighter.outline.getBoundingClientRect();
let outlineWidth = outlineDims.width;
let outlineHeight = outlineDims.height;
let outlineDims = getHighlighterOutlineRect();
let outlineWidth = Math.floor(outlineDims.width);
let outlineHeight = Math.floor(outlineDims.height);
// Disabled due to bug 716245
//is(outlineWidth, divWidth, "outline width matches dimensions of element (no zoom)");
//is(outlineHeight, divHeight, "outline height matches dimensions of element (no zoom)");
is(outlineWidth, h1Width, "outline width matches dimensions of element (zoomed)");
is(outlineHeight, h1Height, "outline height matches dimensions of element (zoomed)");
doc = h1 = div = null;
executeSoon(finishUp);
}, 500);
}
function finishUp() {
let target = TargetFactory.forTab(gBrowser.selectedTab);
gDevTools.closeToolbox(target);
gBrowser.removeCurrentTab();
finish();
inspector.toolbox.stopPicker().then(() => {
doc = h1 = inspector = null;
let target = TargetFactory.forTab(gBrowser.selectedTab);
gDevTools.closeToolbox(target);
gBrowser.removeCurrentTab();
finish();
});
}
function test()
{
function test() {
waitForExplicitFinish();
gBrowser.selectedTab = gBrowser.addTab();
gBrowser.selectedBrowser.addEventListener("load", function() {
@ -174,4 +129,3 @@ function test()
content.location = "data:text/html,basic tests for inspector";
}

View File

@ -1,46 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
function test()
{
let toolbox;
let inspector;
waitForExplicitFinish();
gBrowser.selectedTab = gBrowser.addTab();
gBrowser.selectedBrowser.addEventListener("load", function onload() {
gBrowser.selectedBrowser.removeEventListener("load", onload, true);
waitForFocus(startInspector, content);
}, true);
content.location = "data:text/html,mop"
function startInspector() {
info("Tab loaded");
openInspector(function(aInspector) {
inspector = aInspector;
ok(!inspector.highlighter.hidden, "Highlighter is visible");
toolbox = inspector._toolbox;
toolbox.once("webconsole-selected", onWebConsoleSelected);
toolbox.selectTool("webconsole");
});
}
function onWebConsoleSelected() {
executeSoon(function() {
ok(inspector.highlighter.hidden, "Highlighter is hidden");
toolbox.once("inspector-selected", onInspectorSelected);
toolbox.selectTool("inspector");
});
}
function onInspectorSelected() {
executeSoon(function() {
ok(!inspector.highlighter.hidden, "Highlighter is visible once inspector reopen");
gBrowser.removeCurrentTab();
finish();
});
}
}

View File

@ -9,9 +9,9 @@ let div1;
let div2;
let iframe1;
let iframe2;
let inspector;
function createDocument()
{
function createDocument() {
doc.title = "Inspector iframe Tests";
iframe1 = doc.createElement('iframe');
@ -32,7 +32,11 @@ function createDocument()
div2.textContent = 'nested div';
iframe2.contentDocument.body.appendChild(div2);
openInspector(runIframeTests);
// Open the inspector, start the picker mode, and start the tests
openInspector(aInspector => {
inspector = aInspector;
inspector.toolbox.startPicker().then(runTests);
});
}, false);
iframe2.src = 'data:text/html,nested iframe';
@ -43,58 +47,56 @@ function createDocument()
doc.body.appendChild(iframe1);
}
function moveMouseOver(aElement)
{
function moveMouseOver(aElement, cb) {
EventUtils.synthesizeMouse(aElement, 2, 2, {type: "mousemove"},
aElement.ownerDocument.defaultView);
}
function runIframeTests()
{
getActiveInspector().highlighter.unlock();
getActiveInspector().selection.once("new-node", performTestComparisons1);
moveMouseOver(div1)
}
function performTestComparisons1()
{
let i = getActiveInspector();
is(i.selection.node, div1, "selection matches div1 node");
is(getHighlitNode(), div1, "highlighter matches selection");
i.selection.once("new-node", performTestComparisons2);
executeSoon(function() {
moveMouseOver(div2);
inspector.toolbox.once("picker-node-hovered", () => {
executeSoon(cb);
});
}
function performTestComparisons2()
{
let i = getActiveInspector();
is(i.selection.node, div2, "selection matches div2 node");
is(getHighlitNode(), div2, "highlighter matches selection");
selectRoot();
function runTests() {
testDiv1Highlighter();
}
function selectRoot()
{
function testDiv1Highlighter() {
moveMouseOver(div1, () => {
getHighlighterOutline().setAttribute("disable-transitions", "true");
is(getHighlitNode(), div1, "highlighter matches selection");
testDiv2Highlighter();
});
}
function testDiv2Highlighter() {
moveMouseOver(div2, () => {
is(getHighlitNode(), div2, "highlighter matches selection");
selectRoot();
});
}
function selectRoot() {
// Select the root document element to clear the breadcrumbs.
let i = getActiveInspector();
i.selection.setNode(doc.documentElement);
i.once("inspector-updated", selectIframe);
inspector.selection.setNode(doc.documentElement);
inspector.once("inspector-updated", selectIframe);
}
function selectIframe()
{
function selectIframe() {
// Directly select an element in an iframe (without navigating to it
// with mousemoves).
let i = getActiveInspector();
i.selection.setNode(div2);
i.once("inspector-updated", () => {
let breadcrumbs = i.breadcrumbs;
inspector.selection.setNode(div2);
inspector.once("inspector-updated", () => {
let breadcrumbs = inspector.breadcrumbs;
is(breadcrumbs.nodeHierarchy.length, 9, "Should have 9 items");
finishUp();
});
}
function finishUp() {
inspector.toolbox.stopPicker().then(() => {
doc = div1 = div2 = iframe1 = iframe2 = inspector = null;
let target = TargetFactory.forTab(gBrowser.selectedTab);
gDevTools.closeToolbox(target);
gBrowser.removeCurrentTab();
finish();
});
}
@ -111,11 +113,4 @@ function test() {
}, true);
content.location = "data:text/html,iframe tests for inspector";
registerCleanupFunction(function () {
let target = TargetFactory.forTab(gBrowser.selectedTab);
gDevTools.closeToolbox(target);
gBrowser.removeCurrentTab();
});
}

View File

@ -1,8 +1,7 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
function test()
{
function test() {
waitForExplicitFinish();
ignoreAllUncaughtExceptions();
@ -23,8 +22,7 @@ function test()
content.location = "data:text/html," + encodeURIComponent(html);
function setupInfobarTest()
{
function setupInfobarTest() {
nodes = [
{node: doc.querySelector("#top"), position: "bottom", tag: "DIV", id: "#top", classes: ".class1.class2"},
{node: doc.querySelector("#vertical"), position: "overlap", tag: "DIV", id: "#vertical", classes: ""},
@ -40,18 +38,25 @@ function test()
openInspector(runTests);
}
function runTests(aInspector)
{
function mouseOverContainerToShowHighlighter(node, cb) {
let container = getContainerForRawNode(inspector.markup, node);
EventUtils.synthesizeMouse(container.tagLine, 2, 2, {type: "mousemove"},
inspector.markup.doc.defaultView);
executeSoon(cb);
}
function runTests(aInspector) {
inspector = aInspector;
cursor = 0;
executeSoon(function() {
inspector.selection.setNode(nodes[0].node, "");
nodeSelected();
inspector.selection.setNode(content.document.querySelector("body"));
inspector.once("inspector-updated", () => {
cursor = 0;
executeSoon(function() {
mouseOverContainerToShowHighlighter(nodes[0].node, nodeSelected);
});
});
}
function nodeSelected()
{
function nodeSelected() {
executeSoon(function() {
performTest();
cursor++;
@ -59,18 +64,16 @@ function test()
finishUp();
} else {
let node = nodes[cursor].node;
inspector.selection.setNode(node, "");
nodeSelected();
mouseOverContainerToShowHighlighter(node, nodeSelected);
}
});
}
function performTest()
{
function performTest() {
let browser = gBrowser.selectedBrowser;
let stack = browser.parentNode;
let container = stack.querySelector(".highlighter-nodeinfobar-container");
let container = stack.querySelector(".highlighter-nodeinfobar-positioner");
is(container.getAttribute("position"), nodes[cursor].position, "node " + cursor + ": position matches.");
let tagNameLabel = stack.querySelector(".highlighter-nodeinfobar-tagname");
@ -89,4 +92,3 @@ function test()
finish();
}
}

View File

@ -35,13 +35,11 @@ function startInspectorTests(toolbox)
ok(inspector, "Inspector instance is accessible");
ok(inspector.isReady, "Inspector instance is ready");
is(inspector.target.tab, gBrowser.selectedTab, "Valid target");
ok(inspector.highlighter, "Highlighter is up");
let p = doc.querySelector("p");
inspector.selection.setNode(p);
inspector.once("inspector-updated", () => {
testHighlighter(p);
testMarkupView(p);
testBreadcrumbs(p);
@ -50,7 +48,6 @@ function startInspectorTests(toolbox)
inspector.selection.setNode(span);
inspector.once("inspector-updated", () => {
testHighlighter(span);
testMarkupView(span);
testBreadcrumbs(span);
@ -65,13 +62,6 @@ function startInspectorTests(toolbox)
});
}
function testHighlighter(node)
{
ok(isHighlighting(), "Highlighter is highlighting");
is(getHighlitNode(), node, "Right node is highlighted");
}
let callNo = 0;
function testMarkupView(node)
{
@ -112,7 +102,6 @@ function runContextMenuTest()
}
function testInitialNodeIsSelected() {
testHighlighter(salutation);
testMarkupView(salutation);
testBreadcrumbs(salutation);
inspectNodesFromContextTestWhileOpen();
@ -124,10 +113,9 @@ function inspectNodesFromContextTestWhileOpen()
getActiveInspector().selection.once("new-node", function() {
ok(true, "Get selection's 'new-node' selection");
executeSoon(function() {
testHighlighter(closing);
testMarkupView(closing);
testBreadcrumbs(closing);
finishInspectorTests();
getActiveInspector().once("inspector-updated", finishInspectorTests)
}
)});
_clickOnInspectMenuItem(closing);

View File

@ -2,39 +2,42 @@
http://creativecommons.org/publicdomain/zero/1.0/ */
function test() {
let doc;
let div;
let inspector;
function createDocument()
{
function createDocument() {
div = doc.createElement("div");
div.setAttribute("style", "width: 100px; height: 100px; background:yellow;");
doc.body.appendChild(div);
openInspector(runTest);
openInspector(aInspector => {
inspector = aInspector;
inspector.toolbox.highlighter.showBoxModel(getNodeFront(div)).then(runTest);
});
}
function runTest(inspector)
{
inspector.selection.setNode(div);
function runTest() {
let outline = getHighlighterOutline();
is(outline.style.width, "100px", "outline has the right width");
executeSoon(function() {
let outline = inspector.highlighter.outline;
is(outline.style.width, "100px", "selection has the right width");
div.style.width = "200px";
function pollTest() {
if (outline.style.width == "100px") {
setTimeout(pollTest, 10);
return;
}
is(outline.style.width, "200px", "selection updated");
gBrowser.removeCurrentTab();
finish();
div.style.width = "200px";
function pollTest() {
if (outline.style.width == "100px") {
setTimeout(pollTest, 10);
return;
}
setTimeout(pollTest, 10);
is(outline.style.width, "200px", "outline updated");
finishUp();
}
setTimeout(pollTest, 10);
}
function finishUp() {
inspector.toolbox.highlighter.hideBoxModel().then(() => {
doc = div = inspector = null;
gBrowser.removeCurrentTab();
finish();
});
}

View File

@ -103,7 +103,7 @@ function test() {
let deleteNode = inspector.panelDoc.getElementById("node-menu-delete");
ok(deleteNode, "the popup menu has a delete menu item");
inspector.once("markupmutation", deleteTest);
inspector.once("inspector-updated", deleteTest);
let commandEvent = document.createEvent("XULCommandEvent");
commandEvent.initCommandEvent("command", true, true, window, 0, false, false,
@ -120,12 +120,15 @@ function test() {
function deleteRootNode() {
inspector.selection.setNode(doc.documentElement);
let deleteNode = inspector.panelDoc.getElementById("node-menu-delete");
let commandEvent = inspector.panelDoc.createEvent("XULCommandEvent");
commandEvent.initCommandEvent("command", true, true, window, 0, false, false,
false, false, null);
deleteNode.dispatchEvent(commandEvent);
executeSoon(isRootStillAlive);
inspector.once("inspector-updated", () => {
let deleteNode = inspector.panelDoc.getElementById("node-menu-delete");
let commandEvent = inspector.panelDoc.createEvent("XULCommandEvent");
commandEvent.initCommandEvent("command", true, true, window, 0, false, false,
false, false, null);
deleteNode.dispatchEvent(commandEvent);
executeSoon(isRootStillAlive);
});
}
function isRootStillAlive() {

View File

@ -37,7 +37,7 @@ function test() {
{
inspector = aInspector;
inspector.selection.setNode(div);
performTests();
inspector.once("inspector-updated", performTests);
}
function performTests()

View File

@ -67,22 +67,24 @@ function performTests()
// Wait for the "pseudoclass" event so we know the
// inspector has been told of the pseudoclass lock change.
inspector.selection.once("pseudoclass", () => {
// Give the rule view time to update.
inspector.once("rule-view-refreshed", () => {
testAdded();
// Change the pseudo class and give the rule view time to update.
inspector.togglePseudoClass(pseudo);
inspector.selection.once("pseudoclass", () => {
inspector.once("rule-view-refreshed", () => {
testRemoved();
testRemovedFromUI();
// toggle it back on
inspector.togglePseudoClass(pseudo);
inspector.selection.once("pseudoclass", () => {
testNavigate(() => {
// close the inspector
finishUp();
testAdded(() => {
// Change the pseudo class and give the rule view time to update.
inspector.togglePseudoClass(pseudo);
inspector.selection.once("pseudoclass", () => {
inspector.once("rule-view-refreshed", () => {
testRemoved();
testRemovedFromUI(() => {
// toggle it back on
inspector.togglePseudoClass(pseudo);
inspector.selection.once("pseudoclass", () => {
inspector.once("rule-view-refreshed", () => {
testNavigate(() => {
// close the inspector
finishUp();
});
});
});
});
});
});
@ -98,46 +100,52 @@ function testNavigate(callback)
// make sure it's still on after naving to parent
is(DOMUtils.hasPseudoClassLock(div, pseudo), true,
"pseudo-class lock is still applied after inspecting ancestor");
"pseudo-class lock is still applied after inspecting ancestor");
inspector.selection.setNode(div2);
inspector.selection.once("pseudoclass", () => {
// make sure it's removed after naving to a non-hierarchy node
is(DOMUtils.hasPseudoClassLock(div, pseudo), false,
"pseudo-class lock is removed after inspecting sibling node");
"pseudo-class lock is removed after inspecting sibling node");
// toggle it back on
inspector.selection.setNode(div);
inspector.once("inspector-updated", () => {
inspector.togglePseudoClass(pseudo);
inspector.selection.once("pseudoclass", () => {
callback();
});
inspector.once("computed-view-refreshed", callback);
});
});
});
}
function testAdded()
function showPickerOn(node, cb)
{
let highlighter = inspector.toolbox.highlighter;
highlighter.showBoxModel(getNodeFront(node)).then(cb);
}
function testAdded(cb)
{
// lock is applied to it and ancestors
let node = div;
do {
is(DOMUtils.hasPseudoClassLock(node, pseudo), true,
"pseudo-class lock has been applied");
"pseudo-class lock has been applied");
node = node.parentNode;
} while (node.parentNode)
// infobar selector contains pseudo-class
let pseudoClassesBox = getActiveInspector().highlighter.nodeInfo.pseudoClassesBox;
is(pseudoClassesBox.textContent, pseudo, "pseudo-class in infobar selector");
// ruleview contains pseudo-class rule
is(ruleview.element.children.length, 3,
"rule view is showing 3 rules for pseudo-class locked div");
let rules = ruleview.element.querySelectorAll(".ruleview-rule.theme-separator");
is(rules.length, 3, "rule view is showing 3 rules for pseudo-class locked div");
is(rules[1]._ruleEditor.rule.selectorText, "div:hover", "rule view is showing " + pseudo + " rule");
is(ruleview.element.children[1]._ruleEditor.rule.selectorText,
"div:hover", "rule view is showing " + pseudo + " rule");
// Show the highlighter by starting the pick mode and hovering over the div
showPickerOn(div, () => {
// infobar selector contains pseudo-class
let pseudoClassesBox = getHighlighter().querySelector(".highlighter-nodeinfobar-pseudo-classes");
is(pseudoClassesBox.textContent, pseudo, "pseudo-class in infobar selector");
cb();
});
}
function testRemoved()
@ -151,15 +159,17 @@ function testRemoved()
} while (node.parentNode)
}
function testRemovedFromUI()
function testRemovedFromUI(cb)
{
// infobar selector doesn't contain pseudo-class
let pseudoClassesBox = getActiveInspector().highlighter.nodeInfo.pseudoClassesBox;
is(pseudoClassesBox.textContent, "", "pseudo-class removed from infobar selector");
// ruleview no longer contains pseudo-class rule
is(ruleview.element.children.length, 2,
"rule view is showing 2 rules after removing lock");
let rules = ruleview.element.querySelectorAll(".ruleview-rule.theme-separator");
is(rules.length, 2, "rule view is showing 2 rules after removing lock");
showPickerOn(div, () => {
let pseudoClassesBox = getHighlighter().querySelector(".highlighter-nodeinfobar-pseudo-classes");
is(pseudoClassesBox.textContent, "", "pseudo-class removed from infobar selector");
cb();
});
}
function finishUp()

View File

@ -44,6 +44,7 @@ function test() {
inspector.once("inspector-updated", () => {
is(inspector.selection.node, p, "Node re-selected.");
toolbox.destroy();
toolbox = inspector = null;
gBrowser.removeCurrentTab();
finish();
});

View File

@ -33,9 +33,8 @@ function inspectNode(aInspector)
{
inspector = aInspector;
inspector.highlighter.once("locked", performScrollingTest);
inspector.once("inspector-updated", performScrollingTest);
executeSoon(function() {
inspector.highlighter.unlock();
inspector.selection.setNode(div, "");
});
}

View File

@ -30,9 +30,8 @@ function test() {
}
function endTests() {
inspector.destroy().then(() =>
toolbox.destroy()
).then(() => {
inspector.destroy();
toolbox.destroy().then(() => {
toolbox = inspector = page1 = page2 = null;
gBrowser.removeCurrentTab();
finish();
@ -40,7 +39,7 @@ function test() {
}
function loadPageAnd(page, callback) {
inspector.once("markuploaded", () => {
inspector.once("new-root", () => {
callback();
});

View File

@ -6,11 +6,10 @@ const Cu = Components.utils;
const Ci = Components.interfaces;
const Cc = Components.classes;
Services.prefs.setBoolPref("devtools.debugger.log", true);
SimpleTest.registerCleanupFunction(() => {
Services.prefs.clearUserPref("devtools.debugger.log");
});
// Services.prefs.setBoolPref("devtools.debugger.log", true);
// SimpleTest.registerCleanupFunction(() => {
// Services.prefs.clearUserPref("devtools.debugger.log");
// });
let tempScope = {};
Cu.import("resource://gre/modules/devtools/LayoutHelpers.jsm", tempScope);
@ -52,40 +51,63 @@ function getNodeFront(node)
return inspector.walker.frontForRawNode(node);
}
function getHighlighter()
{
return gBrowser.selectedBrowser.parentNode.querySelector(".highlighter-container");
}
function getHighlighterOutline()
{
let h = getHighlighter();
if (h) {
return h.querySelector(".highlighter-outline");
}
}
function getHighlighterOutlineRect() {
let helper = new LayoutHelpers(window.content);
let outline = getHighlighterOutline();
if (outline) {
let browserOffsetRect = helper.getDirtyRect(gBrowser.selectedBrowser);
let outlineRect = helper.getDirtyRect(outline);
outlineRect.top -= browserOffsetRect.top;
outlineRect.left -= browserOffsetRect.left;
return outlineRect;
}
}
function isHighlighting()
{
let outline = getActiveInspector().highlighter.outline;
return !(outline.getAttribute("hidden") == "true");
let outline = getHighlighterOutline();
return outline && !outline.hasAttribute("hidden");
}
function getHighlitNode()
{
let h = getActiveInspector().highlighter;
if (!isHighlighting() || !h._contentRect)
return null;
if (isHighlighting()) {
let helper = new LayoutHelpers(window.content);
let outlineRect = getHighlighterOutlineRect();
let a = {
x: h._contentRect.left,
y: h._contentRect.top
};
let a = {
x: outlineRect.left,
y: outlineRect.top
};
let b = {
x: a.x + h._contentRect.width,
y: a.y + h._contentRect.height
};
let b = {
x: a.x + outlineRect.width,
y: a.y + outlineRect.height
};
// Get midpoint of diagonal line.
let midpoint = midPoint(a, b);
let lh = new LayoutHelpers(window.content);
return lh.getElementFromPoint(h.win.document, midpoint.x,
midpoint.y);
let {x, y} = getMidPoint(a, b);
return helper.getElementFromPoint(window.content.document, x, y);
}
}
function midPoint(aPointA, aPointB)
function getMidPoint(aPointA, aPointB)
{
let pointC = { };
let pointC = {};
pointC.x = (aPointB.x - aPointA.x) / 2 + aPointA.x;
pointC.y = (aPointB.y - aPointA.y) / 2 + aPointA.y;
return pointC;
@ -189,6 +211,13 @@ function getComputedPropertyValue(aName)
}
}
function getContainerForRawNode(markupView, rawNode)
{
let front = markupView.walker.frontForRawNode(rawNode);
let container = markupView.getContainer(front);
return container;
}
SimpleTest.registerCleanupFunction(function () {
let target = TargetFactory.forTab(gBrowser.selectedTab);
gDevTools.closeToolbox(target);

View File

@ -34,12 +34,8 @@ LayoutView.prototype = {
this.update = this.update.bind(this);
this.onNewNode = this.onNewNode.bind(this);
this.onNewSelection = this.onNewSelection.bind(this);
this.onHighlighterLocked = this.onHighlighterLocked.bind(this);
this.inspector.selection.on("new-node-front", this.onNewSelection);
this.inspector.sidebar.on("layoutview-selected", this.onNewNode);
if (this.inspector.highlighter) {
this.inspector.highlighter.on("locked", this.onHighlighterLocked);
}
// Store for the different dimensions of the node.
// 'selector' refers to the element that holds the value in view.xhtml;
@ -106,9 +102,6 @@ LayoutView.prototype = {
if (this.browser) {
this.browser.removeEventListener("MozAfterPaint", this.update, true);
}
if (this.inspector.highlighter) {
this.inspector.highlighter.off("locked", this.onHighlighterLocked);
}
this.sizeHeadingLabel = null;
this.sizeLabel = null;
this.inspector = null;
@ -126,8 +119,7 @@ LayoutView.prototype = {
onNewNode: function LV_onNewNode() {
if (this.isActive() &&
this.inspector.selection.isConnected() &&
this.inspector.selection.isElementNode() &&
this.inspector.selection.reason != "highlighter") {
this.inspector.selection.isElementNode()) {
this.undim();
} else {
this.dim();
@ -135,14 +127,6 @@ LayoutView.prototype = {
return this.update();
},
/**
* Highlighter 'locked' event handler
*/
onHighlighterLocked: function LV_onHighlighterLocked() {
this.undim();
this.update();
},
/**
* Hide the layout boxes. No node are selected.
*/

View File

@ -109,9 +109,7 @@ Tools.inspector = {
preventClosingOnKey: true,
onkey: function(panel) {
if (panel.highlighter) {
panel.highlighter.toggleLockState();
}
panel.toolbox.togglePicker();
},
isTargetSupported: function(target) {

View File

@ -70,7 +70,7 @@
/* This extra element placed in each tag is positioned absolutely to cover the
* whole tag line and is used for background styling (when a selection is made
* or when the tag is flashing) */
.tag-line .highlighter {
.tag-line .tag-state {
position: absolute;
left: -1000em;
right: 0;
@ -120,7 +120,7 @@
margin-right: 0;
}
.highlighter.flash-out {
.tag-state.flash-out {
transition: background .5s;
}

View File

@ -15,6 +15,7 @@ const COLLAPSE_DATA_URL_REGEX = /^data.+base64/;
const COLLAPSE_DATA_URL_LENGTH = 60;
const CONTAINER_FLASHING_DURATION = 500;
const IMAGE_PREVIEW_MAX_DIM = 400;
const NEW_SELECTION_HIGHLIGHTER_TIMER = 1000;
const {UndoStack} = require("devtools/shared/undo");
const {editableField, InplaceEditor} = require("devtools/shared/inplace-editor");
@ -23,6 +24,7 @@ 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");
const EventEmitter = require("devtools/shared/event-emitter");
Cu.import("resource://gre/modules/devtools/LayoutHelpers.jsm");
Cu.import("resource://gre/modules/devtools/Templater.jsm");
@ -52,6 +54,10 @@ loader.lazyGetter(this, "AutocompletePopup", () => {
* The inspector we're watching.
* @param iframe aFrame
* An iframe in which the caller has kindly loaded markup-view.xhtml.
*
* Fires the following events:
* - node-highlight: When a node in the markup-view is hovered and the
* corresponding node in the content gets highlighted
*/
function MarkupView(aInspector, aFrame, aControllerWindow) {
this._inspector = aInspector;
@ -100,10 +106,10 @@ function MarkupView(aInspector, aFrame, aControllerWindow) {
gDevTools.on("pref-changed", this._handlePrefChange);
this._initPreview();
this._initTooltips();
this._initHighlighter();
this.tooltip = new Tooltip(this._inspector.panelDoc);
this.tooltip.startTogglingOnHover(this._elt,
this._buildTooltipContent.bind(this));
EventEmitter.decorate(this);
}
exports.MarkupView = MarkupView;
@ -111,6 +117,121 @@ exports.MarkupView = MarkupView;
MarkupView.prototype = {
_selectedContainer: null,
_initTooltips: function() {
this.tooltip = new Tooltip(this._inspector.panelDoc);
this.tooltip.startTogglingOnHover(this._elt,
this._buildTooltipContent.bind(this));
},
_initHighlighter: function() {
// Show the box model on markup-view mousemove
this._onMouseMove = this._onMouseMove.bind(this);
this._elt.addEventListener("mousemove", this._onMouseMove, false);
this._onMouseLeave = this._onMouseLeave.bind(this);
this._elt.addEventListener("mouseleave", this._onMouseLeave, false);
// Show markup-containers as hovered on toolbox "picker-node-hovered" event
// which happens when the "pick" button is pressed
this._onToolboxPickerHover = (event, nodeFront) => {
this.showNode(nodeFront, true).then(() => {
this._showContainerAsHovered(nodeFront);
});
}
this._inspector.toolbox.on("picker-node-hovered", this._onToolboxPickerHover);
},
_onMouseMove: function(event) {
let target = event.target;
// Search target for a markupContainer reference, if not found, walk up
while (!target.container) {
if (target.tagName.toLowerCase() === "body") {
return;
}
target = target.parentNode;
}
let container = target.container;
if (this._hoveredNode !== container.node) {
if (container.node.nodeType !== Ci.nsIDOMNode.TEXT_NODE) {
this._showBoxModel(container.node);
} else {
this._hideBoxModel();
}
}
this._showContainerAsHovered(container.node);
},
_hoveredNode: null,
_showContainerAsHovered: function(nodeFront) {
if (this._hoveredNode !== nodeFront) {
if (this._hoveredNode) {
this._containers.get(this._hoveredNode).hovered = false;
}
this._containers.get(nodeFront).hovered = true;
this._hoveredNode = nodeFront;
}
},
_onMouseLeave: function() {
this._hideBoxModel();
},
_showBoxModel: function(nodeFront, options={}) {
let toolbox = this._inspector.toolbox;
// If the remote highlighter exists on the target, use it
if (toolbox.isRemoteHighlightable) {
toolbox.initInspector().then(() => {
toolbox.highlighter.showBoxModel(nodeFront, options).then(() => {
this.emit("node-highlight", nodeFront);
});
});
}
// Else, revert to the "older" version of the highlighter in the walker
// actor
else {
this.walker.highlight(nodeFront).then(() => {
this.emit("node-highlight", nodeFront);
});
}
},
_hideBoxModel: function() {
let deferred = promise.defer();
let toolbox = this._inspector.toolbox;
// If the remote highlighter exists on the target, use it
if (toolbox.isRemoteHighlightable) {
toolbox.initInspector().then(() => {
toolbox.highlighter.hideBoxModel().then(deferred.resolve);
});
} else {
deferred.resolve();
}
// If not, no need to unhighlight as the older highlight method uses a
// setTimeout to hide itself
return deferred.promise;
},
_briefBoxModelTimer: null,
_brieflyShowBoxModel: function(nodeFront, options) {
let win = this._frame.contentWindow;
if (this._briefBoxModelTimer) {
win.clearTimeout(this._briefBoxModelTimer);
this._briefBoxModelTimer = null;
}
this._showBoxModel(nodeFront, options);
this._briefBoxModelTimer = this._frame.contentWindow.setTimeout(() => {
this._hideBoxModel();
}, NEW_SELECTION_HIGHLIGHTER_TIMER);
},
template: function(aName, aDest, aOptions={stack: "markup-view.xhtml"}) {
let node = this.doc.getElementById("template-" + aName).cloneNode(true);
node.removeAttribute("id");
@ -176,11 +297,22 @@ MarkupView.prototype = {
* Highlight the inspector selected node.
*/
_onNewSelection: function() {
let selection = this._inspector.selection;
this.htmlEditor.hide();
let done = this._inspector.updating("markup-view");
if (this._inspector.selection.isNode()) {
this.showNode(this._inspector.selection.nodeFront, true).then(() => {
this.markNodeAsSelected(this._inspector.selection.nodeFront);
if (selection.isNode()) {
let reason = selection.reason;
if (reason && reason !== "inspector-open" && reason !== "navigateaway") {
this._brieflyShowBoxModel(selection.nodeFront, {
scrollIntoView: true
});
}
this.showNode(selection.nodeFront, true).then(() => {
if (selection.reason !== "treepanel") {
this.markNodeAsSelected(selection.nodeFront);
}
done();
});
} else {
@ -370,11 +502,6 @@ MarkupView.prototype = {
let node = aContainer.node;
this.markNodeAsSelected(node, "treepanel");
// This event won't be fired if the node is the same. But the highlighter
// need to lock the node if it wasn't.
this._inspector.selection.emit("new-node");
this._inspector.selection.emit("new-node-front");
if (!aIgnoreFocus) {
aContainer.focus();
}
@ -940,24 +1067,31 @@ MarkupView.prototype = {
destroy: function() {
gDevTools.off("pref-changed", this._handlePrefChange);
delete this._outputParser;
// Note that if the toolbox is closed, this will work fine, but will fail
// in case the browser is closed and will trigger a noSuchActor message.
this._hideBoxModel();
this._hoveredNode = null;
this._inspector.toolbox.off("picker-node-hovered", this._onToolboxPickerHover);
this._outputParser = null;
this.htmlEditor.destroy();
delete this.htmlEditor;
this.htmlEditor = null;
this.undo.destroy();
delete this.undo;
this.undo = null;
this.popup.destroy();
delete this.popup;
this.popup = null;
this._frame.removeEventListener("focus", this._boundFocus, false);
delete this._boundFocus;
this._boundFocus = null;
if (this._boundUpdatePreview) {
this._frame.contentWindow.removeEventListener("scroll",
this._boundUpdatePreview, true);
delete this._boundUpdatePreview;
this._boundUpdatePreview = null;
}
if (this._boundResizePreview) {
@ -967,28 +1101,30 @@ MarkupView.prototype = {
this._boundResizePreview, true);
this._frame.contentWindow.removeEventListener("underflow",
this._boundResizePreview, true);
delete this._boundResizePreview;
this._boundResizePreview = null;
}
this._frame.contentWindow.removeEventListener("keydown",
this._boundKeyDown, false);
delete this._boundKeyDown;
this._boundKeyDown = null;
this._inspector.selection.off("new-node-front", this._boundOnNewSelection);
delete this._boundOnNewSelection;
this._boundOnNewSelection = null;
this.walker.off("mutations", this._boundMutationObserver)
delete this._boundMutationObserver;
this._boundMutationObserver = null;
delete this._elt;
this._elt.removeEventListener("mousemove", this._onMouseMove, false);
this._elt.removeEventListener("mouseleave", this._onMouseLeave, false);
this._elt = null;
for (let [key, container] of this._containers) {
container.destroy();
}
delete this._containers;
this._containers = null;
this.tooltip.destroy();
delete this.tooltip;
this.tooltip = null;
},
/**
@ -1114,7 +1250,7 @@ function MarkupContainer(aMarkupView, aNode, aInspector) {
// The template will fill the following properties
this.elt = null;
this.expander = null;
this.highlighter = null;
this.tagState = null;
this.tagLine = null;
this.children = null;
this.markup.template("container", this);
@ -1126,15 +1262,6 @@ function MarkupContainer(aMarkupView, aNode, aInspector) {
this.elt.addEventListener("dblclick", this._onToggle, false);
this.expander.addEventListener("click", this._onToggle, false);
// Dealing with the highlighting of the row via javascript rather than :hover
// This is to allow highlighting the closing tag-line as well as reusing the
// theme css classes (which wouldn't have been possible with a :hover pseudo)
this._onMouseOver = this._onMouseOver.bind(this);
this.elt.addEventListener("mouseover", this._onMouseOver, false);
this._onMouseOut = this._onMouseOut.bind(this);
this.elt.addEventListener("mouseout", this._onMouseOut, false);
// Appending the editor element and attaching event listeners
this.tagLine.appendChild(this.editor.elt);
@ -1235,13 +1362,11 @@ MarkupContainer.prototype = {
let line = this.markup.doc.createElement("div");
line.classList.add("tag-line");
let highlighter = this.markup.doc.createElement("div");
highlighter.classList.add("highlighter");
line.appendChild(highlighter);
let tagState = this.markup.doc.createElement("div");
tagState.classList.add("tag-state");
line.appendChild(tagState);
line.appendChild(closingTag.cloneNode(true));
line.addEventListener("mouseover", this._onMouseOver, false);
line.addEventListener("mouseout", this._onMouseOut, false);
this.closeTagLine = line;
}
@ -1250,7 +1375,7 @@ MarkupContainer.prototype = {
}
this.elt.classList.remove("collapsed");
this.expander.setAttribute("open", "");
this.highlighted = false;
this.hovered = false;
} else if (!aValue) {
if (this.editor instanceof ElementEditor && this.closeTagLine) {
this.elt.removeChild(this.closeTagLine);
@ -1268,19 +1393,9 @@ MarkupContainer.prototype = {
event.stopPropagation();
},
_onMouseOver: function(event) {
this.highlighted = true;
event.stopPropagation();
},
_onMouseOut: function(event) {
this.highlighted = false;
event.stopPropagation();
},
_onMouseDown: function(event) {
if (event.target.nodeName !== "a") {
this.highlighted = false;
this.hovered = false;
this.markup.navigate(this);
event.stopPropagation();
}
@ -1319,10 +1434,10 @@ MarkupContainer.prototype = {
set flashed(aValue) {
if (aValue) {
// Make sure the animation class is not here
this.highlighter.classList.remove("flash-out");
this.tagState.classList.remove("flash-out");
// Change the background
this.highlighter.classList.add("theme-bg-contrast");
this.tagState.classList.add("theme-bg-contrast");
// Change the text color
this.editor.elt.classList.add("theme-fg-contrast");
@ -1332,10 +1447,10 @@ MarkupContainer.prototype = {
);
} else {
// Add the animation class to smoothly remove the background
this.highlighter.classList.add("flash-out");
this.tagState.classList.add("flash-out");
// Remove the background
this.highlighter.classList.remove("theme-bg-contrast");
this.tagState.classList.remove("theme-bg-contrast");
// Remove the text color
this.editor.elt.classList.remove("theme-fg-contrast");
@ -1346,27 +1461,27 @@ MarkupContainer.prototype = {
}
},
_highlighted: false,
_hovered: false,
/**
* Highlight the currently hovered tag + its closing tag if necessary
* (that is if the tag is expanded)
*/
set highlighted(aValue) {
this.highlighter.classList.remove("flash-out");
this._highlighted = aValue;
set hovered(aValue) {
this.tagState.classList.remove("flash-out");
this._hovered = aValue;
if (aValue) {
if (!this.selected) {
this.highlighter.classList.add("theme-bg-darker");
this.tagState.classList.add("theme-bg-darker");
}
if (this.closeTagLine) {
this.closeTagLine.querySelector(".highlighter").classList.add(
this.closeTagLine.querySelector(".tag-state").classList.add(
"theme-bg-darker");
}
} else {
this.highlighter.classList.remove("theme-bg-darker");
this.tagState.classList.remove("theme-bg-darker");
if (this.closeTagLine) {
this.closeTagLine.querySelector(".highlighter").classList.remove(
this.closeTagLine.querySelector(".tag-state").classList.remove(
"theme-bg-darker");
}
}
@ -1389,15 +1504,15 @@ MarkupContainer.prototype = {
},
set selected(aValue) {
this.highlighter.classList.remove("flash-out");
this.tagState.classList.remove("flash-out");
this._selected = aValue;
this.editor.selected = aValue;
if (this._selected) {
this.tagLine.setAttribute("selected", "");
this.highlighter.classList.add("theme-selected");
this.tagState.classList.add("theme-selected");
} else {
this.tagLine.removeAttribute("selected");
this.highlighter.classList.remove("theme-selected");
this.tagState.classList.remove("theme-selected");
}
},

View File

@ -23,7 +23,7 @@
<ul class="children">
<li id="template-container" save="${elt}" class="child collapsed">
<div save="${tagLine}" class="tag-line"><span save="${highlighter}" class="highlighter"></span><span save="${expander}" class="theme-twisty expander"></span></div>
<div save="${tagLine}" class="tag-line"><span save="${tagState}" class="tag-state"></span><span save="${expander}" class="theme-twisty expander"></span></div>
<ul save="${children}" class="children"></ul>
</li>

View File

@ -681,9 +681,6 @@ function test() {
assertAttributes(doc.querySelector("#node18"), {
id: "node18",
});
is(inspector.highlighter.nodeInfo.classesBox.textContent, "",
"No classes in the infobar before edit.");
},
execute: function(after) {
inspector.once("markupmutation", function() {
@ -701,9 +698,6 @@ function test() {
class: "newclass",
style: "color:green"
});
is(inspector.highlighter.nodeInfo.classesBox.textContent, ".newclass",
"Correct classes in the infobar after edit.");
}
};
testAsyncSetup(test, editTagName);

View File

@ -123,7 +123,7 @@ function test() {
if(!container) {
ok(false, "Node not found");
} else {
ok(container.highlighter.classList.contains("theme-bg-contrast"),
ok(container.tagState.classList.contains("theme-bg-contrast"),
"Node is flashing");
}
}

View File

@ -391,9 +391,6 @@ CssHtmlTree.prototype = {
onItem: (aPropView) => {
aPropView.refresh();
},
onCancel: () => {
deferred.reject("refresh cancelled");
},
onDone: () => {
this._refreshProcess = null;
this.noResults.hidden = this.numVisibleProperties > 0;

View File

@ -1384,7 +1384,7 @@ CssRuleView.prototype = {
let elementStyle = this._elementStyle;
return this._elementStyle.populate().then(() => {
if (this._elementStyle != elementStyle) {
return promise.reject("element changed");
return;
}
this._createEditors();

View File

@ -82,9 +82,6 @@ function RuleViewTool(aInspector, aWindow, aIFrame)
this.inspector.on("layout-change", this.refresh);
this.inspector.selection.on("pseudoclass", this.refresh);
if (this.inspector.highlighter) {
this.inspector.highlighter.on("locked", this._onSelect);
}
this.onSelect();
}
@ -102,15 +99,6 @@ RuleViewTool.prototype = {
}
if (!aEvent || aEvent == "new-node-front") {
if (this.inspector.selection.reason == "highlighter") {
this.view.highlight(null);
} else {
let done = this.inspector.updating("rule-view");
this.view.highlight(this.inspector.selection.nodeFront).then(done, done);
}
}
if (aEvent == "locked") {
let done = this.inspector.updating("rule-view");
this.view.highlight(this.inspector.selection.nodeFront).then(done, done);
}
@ -124,9 +112,6 @@ RuleViewTool.prototype = {
this.inspector.off("layout-change", this.refresh);
this.inspector.selection.off("pseudoclass", this.refresh);
this.inspector.selection.off("new-node-front", this._onSelect);
if (this.inspector.highlighter) {
this.inspector.highlighter.off("locked", this._onSelect);
}
this.view.element.removeEventListener("CssRuleViewCSSLinkClicked",
this._cssLinkHandler);
@ -159,9 +144,6 @@ function ComputedViewTool(aInspector, aWindow, aIFrame)
this._onSelect = this.onSelect.bind(this);
this.inspector.selection.on("detached", this._onSelect);
this.inspector.selection.on("new-node-front", this._onSelect);
if (this.inspector.highlighter) {
this.inspector.highlighter.on("locked", this._onSelect);
}
this.refresh = this.refresh.bind(this);
this.inspector.on("layout-change", this.refresh);
this.inspector.selection.on("pseudoclass", this.refresh);
@ -185,17 +167,6 @@ ComputedViewTool.prototype = {
}
if (!aEvent || aEvent == "new-node-front") {
if (this.inspector.selection.reason == "highlighter") {
// FIXME: We should hide view's content
} else {
let done = this.inspector.updating("computed-view");
this.view.highlight(this.inspector.selection.nodeFront).then(() => {
done();
});
}
}
if (aEvent == "locked" && this.inspector.selection.nodeFront != this.view.viewedElement) {
let done = this.inspector.updating("computed-view");
this.view.highlight(this.inspector.selection.nodeFront).then(() => {
done();
@ -213,9 +184,6 @@ ComputedViewTool.prototype = {
this.inspector.sidebar.off("computedview-selected", this.refresh);
this.inspector.selection.off("pseudoclass", this.refresh);
this.inspector.selection.off("new-node-front", this._onSelect);
if (this.inspector.highlighter) {
this.inspector.highlighter.off("locked", this._onSelect);
}
this.view.destroy();
delete this.view;

View File

@ -67,17 +67,18 @@ function testModifyRules()
rule.editor.addProperty("font-weight", "bold", "");
}
reselectElement(doc.querySelector("#target"), () => {
executeSoon(() => {
reselectElement(doc.querySelector("#target"), () => {
for (let rule of ruleView._elementStyle.rules) {
let lastRule = rule.textProps[rule.textProps.length - 1];
for (let rule of ruleView._elementStyle.rules) {
let lastRule = rule.textProps[rule.textProps.length - 1];
is (lastRule.name, "font-weight", "Last rule name is font-weight");
is (lastRule.value, "bold", "Last rule value is bold");
}
is (lastRule.name, "font-weight", "Last rule name is font-weight");
is (lastRule.value, "bold", "Last rule value is bold");
}
gBrowser.removeCurrentTab();
openXUL();
gBrowser.removeCurrentTab();
openXUL();
});
});
}

View File

@ -20,34 +20,35 @@ function simpleOverride(aInspector, aRuleView)
let styleNode = addStyle(doc, style);
doc.body.innerHTML = '<div id="testid" class="testclass">Styled Node</div>';
inspector.once("markupmutation", () => {
inspector.selection.setNode(doc.getElementById("testid"));
inspector.once("inspector-updated", () => {
let elementStyle = view._elementStyle;
inspector.selection.setNode(doc.getElementById("testid"));
inspector.once("inspector-updated", () => {
let elementStyle = view._elementStyle;
let idRule = elementStyle.rules[1];
let idProp = idRule.textProps[0];
is(idProp.name, "background-color", "First ID prop should be background-color");
ok(!idProp.overridden, "ID prop should not be overridden.");
let idRule = elementStyle.rules[1];
let idProp = idRule.textProps[0];
is(idProp.name, "background-color", "First ID prop should be background-color");
ok(!idProp.overridden, "ID prop should not be overridden.");
let classRule = elementStyle.rules[2];
let classProp = classRule.textProps[0];
is(classProp.name, "background-color", "First class prop should be background-color");
ok(classProp.overridden, "Class property should be overridden.");
let classRule = elementStyle.rules[2];
let classProp = classRule.textProps[0];
is(classProp.name, "background-color", "First class prop should be background-color");
ok(classProp.overridden, "Class property should be overridden.");
// Override background-color by changing the element style.
let elementRule = elementStyle.rules[0];
elementRule.createProperty("background-color", "purple", "");
promiseDone(elementRule._applyingModifications.then(() => {
let elementProp = elementRule.textProps[0];
is(classProp.name, "background-color", "First element prop should now be background-color");
ok(!elementProp.overridden, "Element style property should not be overridden");
ok(idProp.overridden, "ID property should be overridden");
ok(classProp.overridden, "Class property should be overridden");
// Override background-color by changing the element style.
let elementRule = elementStyle.rules[0];
elementRule.createProperty("background-color", "purple", "");
promiseDone(elementRule._applyingModifications.then(() => {
let elementProp = elementRule.textProps[0];
is(classProp.name, "background-color", "First element prop should now be background-color");
ok(!elementProp.overridden, "Element style property should not be overridden");
ok(idProp.overridden, "ID property should be overridden");
ok(classProp.overridden, "Class property should be overridden");
styleNode.parentNode.removeChild(styleNode);
partialOverride();
}));
styleNode.parentNode.removeChild(styleNode);
partialOverride();
}));
});
});
}
@ -65,25 +66,26 @@ function partialOverride()
let styleNode = addStyle(doc, style);
doc.body.innerHTML = '<div id="testid" class="testclass">Styled Node</div>';
inspector.once("markupmutation", () => {
inspector.selection.setNode(doc.getElementById("testid"));
inspector.once("inspector-updated", () => {
let elementStyle = view._elementStyle;
inspector.selection.setNode(doc.getElementById("testid"));
inspector.once("inspector-updated", () => {
let elementStyle = view._elementStyle;
let classRule = elementStyle.rules[2];
let classProp = classRule.textProps[0];
ok(!classProp.overridden, "Class prop shouldn't be overridden, some props are still being used.");
for (let computed of classProp.computed) {
if (computed.name.indexOf("margin-left") == 0) {
ok(computed.overridden, "margin-left props should be overridden.");
} else {
ok(!computed.overridden, "Non-margin-left props should not be overridden.");
let classRule = elementStyle.rules[2];
let classProp = classRule.textProps[0];
ok(!classProp.overridden, "Class prop shouldn't be overridden, some props are still being used.");
for (let computed of classProp.computed) {
if (computed.name.indexOf("margin-left") == 0) {
ok(computed.overridden, "margin-left props should be overridden.");
} else {
ok(!computed.overridden, "Non-margin-left props should not be overridden.");
}
}
}
styleNode.parentNode.removeChild(styleNode);
styleNode.parentNode.removeChild(styleNode);
importantOverride();
importantOverride();
});
});
}
@ -100,29 +102,30 @@ function importantOverride()
'}';
let styleNode = addStyle(doc, style);
doc.body.innerHTML = '<div id="testid" class="testclass">Styled Node</div>';
inspector.once("markupmutation", () => {
inspector.selection.setNode(doc.getElementById("testid"));
inspector.once("inspector-updated", () => {
let elementStyle = view._elementStyle;
inspector.selection.setNode(doc.getElementById("testid"));
inspector.once("inspector-updated", () => {
let elementStyle = view._elementStyle;
let idRule = elementStyle.rules[1];
let idProp = idRule.textProps[0];
ok(idProp.overridden, "Not-important rule should be overridden.");
let idRule = elementStyle.rules[1];
let idProp = idRule.textProps[0];
ok(idProp.overridden, "Not-important rule should be overridden.");
let classRule = elementStyle.rules[2];
let classProp = classRule.textProps[0];
ok(!classProp.overridden, "Important rule should not be overridden.");
let classRule = elementStyle.rules[2];
let classProp = classRule.textProps[0];
ok(!classProp.overridden, "Important rule should not be overridden.");
styleNode.parentNode.removeChild(styleNode);
styleNode.parentNode.removeChild(styleNode);
let elementRule = elementStyle.rules[0];
let elementProp = elementRule.createProperty("background-color", "purple", "important");
promiseDone(elementRule._applyingModifications.then(() => {
ok(classProp.overridden, "New important prop should override class property.");
ok(!elementProp.overridden, "New important prop should not be overriden.");
let elementRule = elementStyle.rules[0];
let elementProp = elementRule.createProperty("background-color", "purple", "important");
promiseDone(elementRule._applyingModifications.then(() => {
ok(classProp.overridden, "New important prop should override class property.");
ok(!elementProp.overridden, "New important prop should not be overriden.");
disableOverride();
}));
disableOverride();
}));
});
});
}
@ -137,23 +140,24 @@ function disableOverride()
'}';
let styleNode = addStyle(doc, style);
doc.body.innerHTML = '<div id="testid" class="testclass">Styled Node</div>';
inspector.once("markupmutation", () => {
inspector.selection.setNode(doc.getElementById("testid"));
inspector.once("inspector-updated", () => {
let elementStyle = view._elementStyle;
inspector.selection.setNode(doc.getElementById("testid"));
inspector.once("inspector-updated", () => {
let elementStyle = view._elementStyle;
let idRule = elementStyle.rules[1];
let idProp = idRule.textProps[0];
idProp.setEnabled(false);
promiseDone(idRule._applyingModifications.then(() => {
let classRule = elementStyle.rules[2];
let classProp = classRule.textProps[0];
ok(!classProp.overridden, "Class prop should not be overridden after id prop was disabled.");
let idRule = elementStyle.rules[1];
let idProp = idRule.textProps[0];
idProp.setEnabled(false);
promiseDone(idRule._applyingModifications.then(() => {
let classRule = elementStyle.rules[2];
let classProp = classRule.textProps[0];
ok(!classProp.overridden, "Class prop should not be overridden after id prop was disabled.");
styleNode.parentNode.removeChild(styleNode);
styleNode.parentNode.removeChild(styleNode);
finishTest();
}));
finishTest();
}));
});
});
}

View File

@ -272,10 +272,11 @@ TiltVisualizer.prototype = {
}
let nodeIndex = this.presenter._currentSelection;
if (nodeIndex < 0) {
this.inspector.selection.setNode(null, "tilt");
this.inspector.selection.setNodeFront(null, "tilt");
}
let node = this.presenter._traverseData.nodes[nodeIndex];
this.inspector.selection.setNode(node, "tilt");
node = this.inspector.walker.frontForRawNode(node);
this.inspector.selection.setNodeFront(node, "tilt");
},
};

View File

@ -5,11 +5,13 @@
// Tests that the $0 console helper works as intended.
let inspector, h1;
function createDocument()
{
let doc = content.document;
let div = doc.createElement("div");
let h1 = doc.createElement("h1");
h1 = doc.createElement("h1");
let p1 = doc.createElement("p");
let p2 = doc.createElement("p");
let div2 = doc.createElement("div");
@ -42,39 +44,37 @@ function createDocument()
function setupHighlighterTests()
{
let h1 = content.document.querySelector("h1");
ok(h1, "we have the header node");
openInspector(runSelectionTests);
}
function runSelectionTests(aInspector)
{
aInspector.highlighter.unlockAndFocus();
aInspector.highlighter.outline.setAttribute("disable-transitions", "true");
executeSoon(function() {
aInspector.selection.once("new-node", performTestComparisons);
let h1 = content.document.querySelector("h1");
inspector = aInspector;
inspector.toolbox.startPicker();
inspector.toolbox.once("picker-started", () => {
EventUtils.synthesizeMouse(h1, 2, 2, {type: "mousemove"}, content);
inspector.toolbox.once("picker-node-hovered", () => {
executeSoon(performTestComparisons);
});
});
}
function getHighlighterOutline()
{
return gBrowser.selectedBrowser.parentNode
.querySelector(".highlighter-container .highlighter-outline");
}
function performTestComparisons()
{
let target = TargetFactory.forTab(gBrowser.selectedTab);
let inspector = gDevTools.getToolbox(target).getPanel("inspector");
inspector.highlighter.lock();
let outline = getHighlighterOutline();
ok(outline && !outline.hasAttribute("hidden"), "inspector is highlighting");
let isHighlighting =
!(inspector.highlighter.outline.getAttribute("hidden") == "true");
ok(isHighlighting, "inspector is highlighting");
let h1 = content.document.querySelector("h1");
is(inspector.selection.node, h1, "selection matches node");
openConsole(gBrowser.selectedTab, performWebConsoleTests);
EventUtils.synthesizeMouseAtCenter(h1, {}, content);
inspector.toolbox.once("picker-stopped", () => {
openConsole(gBrowser.selectedTab, performWebConsoleTests);
});
}
function performWebConsoleTests(hud)
@ -98,10 +98,10 @@ function performWebConsoleTests(hud)
{
isnot(node.textContent.indexOf("bug653531"), -1,
"correct output for $0.textContent");
let inspector = gDevTools.getToolbox(target).getPanel("inspector");
is(inspector.selection.node.textContent, "bug653531",
"node successfully updated");
inspector = h1 = null;
gBrowser.removeCurrentTab();
finishTest();
}
@ -110,6 +110,7 @@ function performWebConsoleTests(hud)
function test()
{
waitForExplicitFinish();
gBrowser.selectedTab = gBrowser.addTab();
gBrowser.selectedBrowser.addEventListener("load", function onLoad() {
gBrowser.selectedBrowser.removeEventListener("load", onLoad, true);
@ -118,4 +119,3 @@ function test()
content.location = "data:text/html;charset=utf-8,test for highlighter helper in web console";
}

View File

@ -188,6 +188,16 @@ function isShumwayEnabledFor(actions) {
return true;
}
function fallbackToNativePlugin(window, userAction, activateCTP) {
var obj = window.frameElement;
var doc = obj.ownerDocument;
var e = doc.createEvent("CustomEvent");
e.initCustomEvent("MozPlayPlugin", true, true, activateCTP);
obj.dispatchEvent(e);
ShumwayTelemetry.onFallback(userAction);
}
// All the priviledged actions.
function ChromeActions(url, window, document) {
this.url = url;
@ -356,13 +366,8 @@ ChromeActions.prototype = {
});
},
fallback: function(automatic) {
var obj = this.window.frameElement;
var doc = obj.ownerDocument;
var e = doc.createEvent("CustomEvent");
e.initCustomEvent("MozPlayPlugin", true, true, null);
obj.dispatchEvent(e);
ShumwayTelemetry.onFallback(!automatic);
automatic = !!automatic; // cast to boolean
fallbackToNativePlugin(this.window, !automatic, automatic);
},
setClipboard: function (data) {
if (typeof data !== 'string' ||
@ -902,7 +907,7 @@ ShumwayStreamConverterBase.prototype = {
converter.getUrlHint(originalURI));
if (!isShumwayEnabledFor(actions)) {
actions.fallback(true);
fallbackToNativePlugin(domWindow, false, true);
return;
}

View File

@ -187,32 +187,12 @@ function cloneObject(obj) {
clone[prop] = obj[prop];
return clone;
}
function sortByDepth(a, b) {
var levelA = a._level;
var levelB = b._level;
if (a._parent !== b._parent && a._index > -1 && b._index > -1) {
while (a._level > levelB) {
a = a._parent;
}
while (b._level > levelA) {
b = b._parent;
}
while (a._level > 1) {
if (a._parent === b._parent) {
break;
}
a = a._parent;
b = b._parent;
}
}
if (a === b) {
return levelA - levelB;
}
return a._index - b._index;
}
function sortNumeric(a, b) {
return a - b;
}
function sortByZindex(a, b) {
return a._zindex - b._zindex;
}
function rgbaObjToStr(color) {
return 'rgba(' + color.red + ',' + color.green + ',' + color.blue + ',' + color.alpha / 255 + ')';
}
@ -296,158 +276,336 @@ function randomStyle() {
}
return randomStyleCache[nextStyle++ % randomStyleCache.length];
}
var Promise = function PromiseClosure() {
function isPromise(obj) {
return typeof obj === 'object' && obj !== null && typeof obj.then === 'function';
}
function defaultOnFulfilled(value) {
return value;
}
function defaultOnRejected(reason) {
throw reason;
}
function propagateFulfilled(subject, value) {
subject.subpromisesValue = value;
var subpromises = subject.subpromises;
if (!subpromises) {
return;
}
for (var i = 0; i < subpromises.length; i++) {
subpromises[i].fulfill(value);
}
delete subject.subpromises;
}
function propagateRejected(subject, reason) {
subject.subpromisesReason = reason;
var subpromises = subject.subpromises;
if (!subpromises) {
if (!true) {
console.warn(reason);
}
return;
}
for (var i = 0; i < subpromises.length; i++) {
subpromises[i].reject(reason);
}
delete subject.subpromises;
}
function performCall(callback, arg, subject) {
try {
var value = callback(arg);
if (isPromise(value)) {
value.then(function Promise_queueCall_onFulfilled(value) {
propagateFulfilled(subject, value);
}, function Promise_queueCall_onRejected(reason) {
propagateRejected(subject, reason);
(function PromiseClosure() {
var global = Function('return this')();
if (global.Promise) {
if (typeof global.Promise.all !== 'function') {
global.Promise.all = function (iterable) {
var count = 0, results = [], resolve, reject;
var promise = new global.Promise(function (resolve_, reject_) {
resolve = resolve_;
reject = reject_;
});
return;
iterable.forEach(function (p, i) {
count++;
p.then(function (result) {
results[i] = result;
count--;
if (count === 0) {
resolve(results);
}
}, reject);
});
if (count === 0) {
resolve(results);
}
propagateFulfilled(subject, value);
} catch (ex) {
propagateRejected(subject, ex);
}
return promise;
};
}
var queue = [];
function processQueue() {
while (queue.length > 0) {
var task = queue[0];
if (task.directCallback) {
task.callback.call(task.subject, task.arg);
} else {
performCall(task.callback, task.arg, task.subject);
}
queue.shift();
}
if (typeof global.Promise.resolve !== 'function') {
global.Promise.resolve = function (x) {
return new global.Promise(function (resolve) {
resolve(x);
});
};
}
function queueCall(callback, arg, subject, directCallback) {
if (queue.length === 0) {
setTimeout(processQueue, 0);
return;
}
function getDeferred(C) {
if (typeof C !== 'function') {
throw new TypeError('Invalid deferred constructor');
}
var resolver = createDeferredConstructionFunctions();
var promise = new C(resolver);
var resolve = resolver.resolve;
if (typeof resolve !== 'function') {
throw new TypeError('Invalid resolve construction function');
}
var reject = resolver.reject;
if (typeof reject !== 'function') {
throw new TypeError('Invalid reject construction function');
}
return {
promise: promise,
resolve: resolve,
reject: reject
};
}
function updateDeferredFromPotentialThenable(x, deferred) {
if (typeof x !== 'object' || x === null) {
return false;
}
try {
var then = x.then;
if (typeof then !== 'function') {
return false;
}
queue.push({
callback: callback,
arg: arg,
subject: subject,
directCallback: directCallback
var thenCallResult = then.call(x, deferred.resolve, deferred.reject);
} catch (e) {
var reject = deferred.reject;
reject(e);
}
return true;
}
function isPromise(x) {
return typeof x === 'object' && x !== null && typeof x.promiseStatus !== 'undefined';
}
function rejectPromise(promise, reason) {
if (promise.promiseStatus !== 'unresolved') {
return;
}
var reactions = promise.rejectReactions;
promise.result = reason;
promise.resolveReactions = undefined;
promise.rejectReactions = undefined;
promise.promiseStatus = 'has-rejection';
triggerPromiseReactions(reactions, reason);
}
function resolvePromise(promise, resolution) {
if (promise.promiseStatus !== 'unresolved') {
return;
}
var reactions = promise.resolveReactions;
promise.result = resolution;
promise.resolveReactions = undefined;
promise.rejectReactions = undefined;
promise.promiseStatus = 'has-resolution';
triggerPromiseReactions(reactions, resolution);
}
function triggerPromiseReactions(reactions, argument) {
for (var i = 0; i < reactions.length; i++) {
queueMicrotask({
reaction: reactions[i],
argument: argument
});
}
function Promise(onFulfilled, onRejected) {
this.state = 'pending';
this.onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : defaultOnFulfilled;
this.onRejected = typeof onRejected === 'function' ? onRejected : defaultOnRejected;
}
function queueMicrotask(task) {
if (microtasksQueue.length === 0) {
setTimeout(handleMicrotasksQueue, 0);
}
Promise.prototype = {
fulfill: function Promise_resolve(value) {
if (this.state !== 'pending') {
return;
microtasksQueue.push(task);
}
function executePromiseReaction(reaction, argument) {
var deferred = reaction.deferred;
var handler = reaction.handler;
var handlerResult, updateResult;
try {
handlerResult = handler(argument);
} catch (e) {
var reject = deferred.reject;
return reject(e);
}
if (handlerResult === deferred.promise) {
var reject = deferred.reject;
return reject(new TypeError('Self resolution'));
}
try {
updateResult = updateDeferredFromPotentialThenable(handlerResult, deferred);
if (!updateResult) {
var resolve = deferred.resolve;
return resolve(handlerResult);
}
} catch (e) {
var reject = deferred.reject;
return reject(e);
}
}
var microtasksQueue = [];
function handleMicrotasksQueue() {
while (microtasksQueue.length > 0) {
var task = microtasksQueue[0];
try {
executePromiseReaction(task.reaction, task.argument);
} catch (e) {
if (typeof Promise.onerror === 'function') {
Promise.onerror(e);
}
this.state = 'fulfilled';
this.value = value;
queueCall(this.onFulfilled, value, this, false);
},
reject: function Promise_reject(reason) {
if (this.state !== 'pending') {
return;
}
microtasksQueue.shift();
}
}
function throwerFunction(e) {
throw e;
}
function identityFunction(x) {
return x;
}
function createRejectPromiseFunction(promise) {
return function (reason) {
rejectPromise(promise, reason);
};
}
function createResolvePromiseFunction(promise) {
return function (resolution) {
resolvePromise(promise, resolution);
};
}
function createDeferredConstructionFunctions() {
var fn = function (resolve, reject) {
fn.resolve = resolve;
fn.reject = reject;
};
return fn;
}
function createPromiseResolutionHandlerFunctions(promise, fulfillmentHandler, rejectionHandler) {
return function (x) {
if (x === promise) {
return rejectionHandler(new TypeError('Self resolution'));
}
var cstr = promise.promiseConstructor;
if (isPromise(x)) {
var xConstructor = x.promiseConstructor;
if (xConstructor === cstr) {
return x.then(fulfillmentHandler, rejectionHandler);
}
this.state = 'rejected';
this.reason = reason;
queueCall(this.onRejected, reason, this, false);
},
then: function Promise_then(onFulfilled, onRejected) {
var promise = new Promise(onFulfilled, onRejected);
if ('subpromisesValue' in this) {
queueCall(promise.fulfill, this.subpromisesValue, promise, true);
} else if ('subpromisesReason' in this) {
queueCall(promise.reject, this.subpromisesReason, promise, true);
} else {
var subpromises = this.subpromises || (this.subpromises = []);
subpromises.push(promise);
}
return promise;
},
get resolved() {
return this.state === 'fulfilled';
},
resolve: function (value) {
this.fulfill(value);
}
var deferred = getDeferred(cstr);
var updateResult = updateDeferredFromPotentialThenable(x, deferred);
if (updateResult) {
var deferredPromise = deferred.promise;
return deferredPromise.then(fulfillmentHandler, rejectionHandler);
}
return fulfillmentHandler(x);
};
}
function createPromiseAllCountdownFunction(index, values, deferred, countdownHolder) {
return function (x) {
values[index] = x;
countdownHolder.countdown--;
if (countdownHolder.countdown === 0) {
deferred.resolve(values);
}
};
Promise.when = function Promise_when() {
var promise = new Promise();
if (arguments.length === 0) {
promise.resolve();
return promise;
}
function Promise(resolver) {
if (typeof resolver !== 'function') {
throw new TypeError('resolver is not a function');
}
var promise = this;
if (typeof promise !== 'object') {
throw new TypeError('Promise to initialize is not an object');
}
promise.promiseStatus = 'unresolved';
promise.resolveReactions = [];
promise.rejectReactions = [];
promise.result = undefined;
var resolve = createResolvePromiseFunction(promise);
var reject = createRejectPromiseFunction(promise);
try {
var result = resolver(resolve, reject);
} catch (e) {
rejectPromise(promise, e);
}
promise.promiseConstructor = Promise;
return promise;
}
Promise.all = function (iterable) {
var deferred = getDeferred(this);
var values = [];
var countdownHolder = {
countdown: 0
};
var index = 0;
iterable.forEach(function (nextValue) {
var nextPromise = this.cast(nextValue);
var fn = createPromiseAllCountdownFunction(index, values, deferred, countdownHolder);
nextPromise.then(fn, deferred.reject);
index++;
countdownHolder.countdown++;
}, this);
if (index === 0) {
deferred.resolve(values);
}
return deferred.promise;
};
Promise.cast = function (x) {
if (isPromise(x)) {
return x;
}
var deferred = getDeferred(this);
deferred.resolve(x);
return deferred.promise;
};
Promise.reject = function (r) {
var deferred = getDeferred(this);
var rejectResult = deferred.reject(r);
return deferred.promise;
};
Promise.resolve = function (x) {
var deferred = getDeferred(this);
var rejectResult = deferred.resolve(x);
return deferred.promise;
};
Promise.prototype = {
'catch': function (onRejected) {
this.then(undefined, onRejected);
},
then: function (onFulfilled, onRejected) {
var promise = this;
if (!isPromise(promise)) {
throw new TypeError('this is not a Promises');
}
var promises = slice.call(arguments, 0);
var result = [];
var i = 1;
function fulfill(value) {
result.push(value);
if (i < promises.length) {
promises[i++].then(fulfill, reject);
} else {
promise.resolve(result);
}
return value;
var cstr = promise.promiseConstructor;
var deferred = getDeferred(cstr);
var rejectionHandler = typeof onRejected === 'function' ? onRejected : throwerFunction;
var fulfillmentHandler = typeof onFulfilled === 'function' ? onFulfilled : identityFunction;
var resolutionHandler = createPromiseResolutionHandlerFunctions(promise, fulfillmentHandler, rejectionHandler);
var resolveReaction = {
deferred: deferred,
handler: resolutionHandler
};
var rejectReaction = {
deferred: deferred,
handler: rejectionHandler
};
switch (promise.promiseStatus) {
case 'unresolved':
promise.resolveReactions.push(resolveReaction);
promise.rejectReactions.push(rejectReaction);
break;
case 'has-resolution':
var resolution = promise.result;
queueMicrotask({
reaction: resolveReaction,
argument: resolution
});
break;
case 'has-rejection':
var rejection = promise.result;
queueMicrotask({
reaction: rejectReaction,
argument: rejection
});
break;
}
function reject(reason) {
promise.reject(reason);
}
promises[0].then(fulfill, reject);
return promise;
};
return Promise;
}();
var QuadTree = function (x, y, width, height, level) {
return deferred.promise;
}
};
global.Promise = Promise;
}());
var QuadTree = function (x, y, width, height, parent) {
this.x = x | 0;
this.y = y | 0;
this.width = width | 0;
this.height = height | 0;
this.level = level | 0;
this.stuckObjects = [];
this.objects = [];
if (parent) {
this.root = parent.root;
this.parent = parent;
this.level = parent.level + 1;
} else {
this.root = this;
this.parent = null;
this.level = 0;
}
this.reset();
};
QuadTree.prototype.reset = function () {
this.stuckObjects = null;
this.objects = null;
this.nodes = [];
};
QuadTree.prototype._findIndex = function (xMin, yMin, xMax, yMax) {
QuadTree.prototype._findIndex = function (xMin, xMax, yMin, yMax) {
var midX = this.x + (this.width / 2 | 0);
var midY = this.y + (this.height / 2 | 0);
var top = yMin < midY && yMax < midY;
@ -470,57 +628,114 @@ QuadTree.prototype._findIndex = function (xMin, yMin, xMax, yMax) {
QuadTree.prototype.insert = function (obj) {
var nodes = this.nodes;
if (nodes.length) {
var index = this._findIndex(obj.xMin, obj.yMin, obj.xMax, obj.yMax);
var index = this._findIndex(obj.xMin, obj.xMax, obj.yMin, obj.yMax);
if (index > -1) {
nodes[index].insert(obj);
} else {
this.stuckObjects.push(obj);
obj._qtree = this;
obj.prev = null;
if (this.stuckObjects) {
obj.next = this.stuckObjects;
this.stuckObjects.prev = obj;
} else {
obj.next = null;
}
this.stuckObjects = obj;
obj.parent = this;
}
return;
}
var objects = this.objects;
objects.push(obj);
if (objects.length > 4 && this.level < 10) {
this._subdivide();
while (objects.length) {
this.insert(objects.shift());
}
return;
}
obj._qtree = this;
};
QuadTree.prototype.delete = function (obj) {
if (obj._qtree !== this) {
return;
}
var index = this.objects.indexOf(obj);
if (index > -1) {
this.objects.splice(index, 1);
var numChildren = 1;
var item = this.objects;
if (!item) {
obj.prev = null;
obj.next = null;
this.objects = obj;
} else {
index = this.stuckObjects.indexOf(obj);
this.stuckObjects.splice(index, 1);
while (item.next) {
numChildren++;
item = item.next;
}
obj.prev = item;
obj.next = null;
item.next = obj;
}
obj._qtree = null;
if (numChildren > 4 && this.level < 10) {
this._subdivide();
item = this.objects;
while (item) {
var next = item.next;
this.insert(item);
item = next;
}
this.objects = null;
return;
}
obj.parent = this;
};
QuadTree.prototype._stack = [];
QuadTree.prototype._out = [];
QuadTree.prototype.retrieve = function (xMin, yMin, xMax, yMax) {
var stack = this._stack;
var out = this._out;
out.length = 0;
QuadTree.prototype.update = function (obj) {
var node = obj.parent;
if (node) {
if (obj.xMin >= node.x && obj.xMax <= node.x + node.width && obj.yMin >= node.y && obj.yMax <= node.y + node.height) {
if (node.nodes.length) {
var index = this._findIndex(obj.xMin, obj.xMax, obj.yMin, obj.yMax);
if (index > -1) {
node.remove(obj);
node = this.nodes[index];
node.insert(obj);
}
} else {
node.remove(obj);
node.insert(obj);
}
return;
}
node.remove(obj);
}
this.root.insert(obj);
};
QuadTree.prototype.remove = function (obj) {
var prev = obj.prev;
var next = obj.next;
if (prev) {
prev.next = next;
obj.prev = null;
} else {
var node = obj.parent;
if (node.objects === obj) {
node.objects = next;
} else if (node.stuckObjects === obj) {
node.stuckObjects = next;
}
}
if (next) {
next.prev = prev;
obj.next = null;
}
obj.parent = null;
};
QuadTree.prototype.retrieve = function (xMin, xMax, yMin, yMax) {
var stack = [];
var out = [];
var node = this;
do {
if (node.nodes.length) {
var index = node._findIndex(xMin, yMin, xMax, yMax);
var index = node._findIndex(xMin, xMax, yMin, yMax);
if (index > -1) {
stack.push(node.nodes[index]);
} else {
stack.push.apply(stack, node.nodes);
}
}
out.push.apply(out, node.stuckObjects);
out.push.apply(out, node.objects);
var item = node.objects;
for (var i = 0; i < 2; i++) {
while (item) {
if (!(item.xMin > xMax || item.xMax < xMin || item.yMin > yMax || item.yMax < yMin)) {
out.push(item);
}
item = item.next;
}
item = node.stuckObjects;
}
node = stack.pop();
} while (node);
return out;
@ -530,11 +745,91 @@ QuadTree.prototype._subdivide = function () {
var halfHeight = this.height / 2 | 0;
var midX = this.x + halfWidth;
var midY = this.y + halfHeight;
var level = this.level + 1;
this.nodes[0] = new QuadTree(midX, this.y, halfWidth, halfHeight, level);
this.nodes[1] = new QuadTree(this.x, this.y, halfWidth, halfHeight, level);
this.nodes[2] = new QuadTree(this.x, midY, halfWidth, halfHeight, level);
this.nodes[3] = new QuadTree(midX, midY, halfWidth, halfHeight, level);
this.nodes[0] = new QuadTree(midX, this.y, halfWidth, halfHeight, this);
this.nodes[1] = new QuadTree(this.x, this.y, halfWidth, halfHeight, this);
this.nodes[2] = new QuadTree(this.x, midY, halfWidth, halfHeight, this);
this.nodes[3] = new QuadTree(midX, midY, halfWidth, halfHeight, this);
};
var RegionCluster = function () {
this.regions = [];
};
RegionCluster.prototype.reset = function () {
this.regions.length = 0;
};
RegionCluster.prototype.insert = function (region) {
var regions = this.regions;
if (regions.length < 3) {
regions.push({
xMin: region.xMin,
xMax: region.xMax,
yMin: region.yMin,
yMax: region.yMax
});
return;
}
var a = region;
var b = regions[0];
var c = regions[1];
var d = regions[2];
var ab = (max(a.xMax, b.xMax) - min(a.xMin, b.xMin)) * (max(a.yMax, b.yMax) - min(a.yMin, b.yMin));
var rb = regions[0];
var ac = (max(a.xMax, c.xMax) - min(a.xMin, c.xMin)) * (max(a.yMax, c.yMax) - min(a.yMin, c.yMin));
var ad = (max(a.xMax, d.xMax) - min(a.xMin, d.xMin)) * (max(a.yMax, d.yMax) - min(a.yMin, d.yMin));
if (ac < ab) {
ab = ac;
rb = c;
}
if (ad < ab) {
ab = ad;
rb = d;
}
var bc = (max(b.xMax, c.xMax) - min(b.xMin, c.xMin)) * (max(b.yMax, c.yMax) - min(b.yMin, c.yMin));
var bd = (max(b.xMax, d.xMax) - min(b.xMin, d.xMin)) * (max(b.yMax, d.yMax) - min(b.yMin, d.yMin));
var cd = (max(c.xMax, d.xMax) - min(c.xMin, d.xMin)) * (max(c.yMax, d.yMax) - min(c.yMin, d.yMin));
if (ab < bc && ab < bd && ab < cd) {
if (a.xMin < rb.xMin) {
rb.xMin = a.xMin;
}
if (a.xMax > rb.xMax) {
rb.xMax = a.xMax;
}
if (a.yMin < rb.yMin) {
rb.yMin = a.yMin;
}
if (a.yMax > rb.yMax) {
rb.yMax = a.yMax;
}
return;
}
rb = regions[0];
var rc = regions[1];
if (bd < bc) {
bc = bd;
rc = regions[2];
}
if (cd < bc) {
rb = regions[1];
rc = regions[2];
}
if (rc.xMin < rb.xMin) {
rb.xMin = rc.xMin;
}
if (rc.xMax > rb.xMax) {
rb.xMax = rc.xMax;
}
if (rc.yMin < rb.yMin) {
rb.yMin = rc.yMin;
}
if (rc.yMax > rb.yMax) {
rb.yMax = rc.yMax;
}
rc.xMin = a.xMin;
rc.xMax = a.xMax;
rc.yMin = a.yMin;
rc.yMax = a.yMax;
};
RegionCluster.prototype.retrieve = function () {
return this.regions;
};
var EXTERNAL_INTERFACE_FEATURE = 1;
var CLIPBOARD_FEATURE = 2;
@ -1852,6 +2147,9 @@ function ShapePath(fillStyle, lineStyle, commandsCount, dataLength, isMorph, tra
}
}
ShapePath.prototype = {
get isEmpty() {
return this.commands.length === 0;
},
moveTo: function (x, y) {
if (this.commands[this.commands.length - 1] === SHAPE_MOVE_TO) {
this.data[this.data.length - 2] = x;
@ -2013,6 +2311,8 @@ ShapePath.prototype = {
ctx.stroke();
ctx.restore();
}
} else {
ctx.fill();
}
ctx.closePath();
},
@ -2694,7 +2994,7 @@ function extendBoundsByY(bounds, y) {
function morph(start, end, ratio) {
return start + (end - start) * ratio;
}
function finishShapePath(path, dictionary) {
function finishShapePath(path, dictionaryResolved) {
if (path.fullyInitialized) {
return path;
}
@ -2708,8 +3008,8 @@ function finishShapePath(path, dictionary) {
}
path.buffers = null;
}
path.fillStyle && initStyle(path.fillStyle, dictionary);
path.lineStyle && initStyle(path.lineStyle, dictionary);
path.fillStyle && initStyle(path.fillStyle, dictionaryResolved);
path.lineStyle && initStyle(path.lineStyle, dictionaryResolved);
path.fullyInitialized = true;
return path;
}
@ -2769,7 +3069,7 @@ function buildBitmapPatternFactory(img, repeat) {
fn.defaultFillStyle = defaultPattern;
return fn;
}
function initStyle(style, dictionary) {
function initStyle(style, dictionaryResolved) {
if (style.type === undefined) {
return;
}
@ -2801,9 +3101,9 @@ function initStyle(style, dictionary) {
case GRAPHICS_FILL_CLIPPED_BITMAP:
case GRAPHICS_FILL_NONSMOOTHED_REPEATING_BITMAP:
case GRAPHICS_FILL_NONSMOOTHED_CLIPPED_BITMAP:
var bitmap = dictionary[style.bitmapId];
var bitmap = dictionaryResolved[style.bitmapId];
var repeat = style.type === GRAPHICS_FILL_REPEATING_BITMAP || style.type === GRAPHICS_FILL_NONSMOOTHED_REPEATING_BITMAP;
style.style = buildBitmapPatternFactory(bitmap.value.props.img, repeat ? 'repeat' : 'no-repeat');
style.style = buildBitmapPatternFactory(bitmap.props.img, repeat ? 'repeat' : 'no-repeat');
break;
default:
fail('invalid fill style', 'shape');
@ -5746,7 +6046,7 @@ BodyParser.prototype = {
var read = stream.pos;
buffer.removeHead(read);
this.totalRead += read;
if (this.totalRead >= this.length && options.oncomplete) {
if (options.oncomplete && swf.tags[swf.tags.length - 1].finalTag) {
options.oncomplete(swf);
}
}
@ -5853,6 +6153,7 @@ SWF.parse = function (buffer, options) {
bytesTotal: bytes.length
};
pipe.push(bytes, progressInfo);
pipe.close();
};
(function (global) {
global['SWF']['parse'] = SWF.parse;

File diff suppressed because it is too large Load Diff

View File

@ -1 +1 @@
0.7.806
0.7.933

View File

@ -19,7 +19,6 @@ limitations under the License.
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<base href=""/>
<title>Shumway viewer</title>
<style>
body {
@ -36,6 +35,7 @@ limitations under the License.
position:fixed !important;
left:0;top:0;bottom:0;right:0;
overflow: hidden;
line-height: 0;
}
#fallback {

View File

@ -54,3 +54,7 @@ scratchpad.keycode=VK_F4
# Used for toggling the browser console from the detached toolbox window
# Needs to match browserConsoleCmd.commandkey from browser.dtd
browserConsoleCmd.commandkey=j
# LOCALIZATION NOTE (pickButton.tooltip)
# This is the tooltip of the pick button in the toolbox toolbar
pickButton.tooltip=Pick an element from the page

View File

@ -1289,6 +1289,17 @@
]]></setter>
</property>
<field name="_remoteFinder">null</field>
<property name="finder" readonly="true">
<getter><![CDATA[
if (!this._remoteFinder) {
let jsm = "resource://gre/modules/RemoteFinder.jsm";
let RemoteFinder = Cu.import(jsm, {}).RemoteFinder;
this._remoteFinder = new RemoteFinder(this);
}
return this._remoteFinder;
]]></getter>
</property>
</implementation>
</binding>

View File

@ -63,7 +63,6 @@ var Browser = {
messageManager.loadFrameScript("chrome://browser/content/library/SelectionPrototype.js", true);
messageManager.loadFrameScript("chrome://browser/content/contenthandlers/SelectionHandler.js", true);
messageManager.loadFrameScript("chrome://browser/content/contenthandlers/ContextMenuHandler.js", true);
messageManager.loadFrameScript("chrome://browser/content/contenthandlers/FindHandler.js", true);
messageManager.loadFrameScript("chrome://browser/content/contenthandlers/ConsoleAPIObserver.js", true);
} catch (e) {
// XXX whatever is calling startup needs to dump errors!
@ -1017,7 +1016,7 @@ nsBrowserAccess.prototype = {
_getOpenAction: function _getOpenAction(aURI, aOpener, aWhere, aContext) {
let where = aWhere;
/*
* aWhere:
* aWhere:
* OPEN_DEFAULTWINDOW: default action
* OPEN_CURRENTWINDOW: current window/tab
* OPEN_NEWWINDOW: not allowed, converted to newtab below

View File

@ -1,91 +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/. */
let Ci = Components.interfaces;
let Cc = Components.classes;
dump("### FindHandler.js loaded\n");
var FindHandler = {
get _fastFind() {
delete this._fastFind;
this._fastFind = Cc["@mozilla.org/typeaheadfind;1"].createInstance(Ci.nsITypeAheadFind);
this._fastFind.init(docShell);
return this._fastFind;
},
init: function findHandlerInit() {
addMessageListener("FindAssist:Find", this);
addMessageListener("FindAssist:Next", this);
addMessageListener("FindAssist:Previous", this);
},
receiveMessage: function findHandlerReceiveMessage(aMessage) {
let findResult = Ci.nsITypeAheadFind.FIND_NOTFOUND;
let json = aMessage.json;
switch (aMessage.name) {
case "FindAssist:Find":
findResult = this._fastFind.find(json.searchString, false);
break;
case "FindAssist:Previous":
findResult = this._fastFind.findAgain(true, false);
break;
case "FindAssist:Next":
findResult = this._fastFind.findAgain(false, false);
break;
}
if (findResult == Ci.nsITypeAheadFind.FIND_NOTFOUND) {
sendAsyncMessage("FindAssist:Show", { rect: null, result: findResult });
return;
}
if (!this._fastFind.currentWindow)
return;
let selection = this._fastFind.currentWindow.getSelection();
if (!selection.rangeCount || selection.isCollapsed) {
// The selection can be into an input or a textarea element
let nodes = content.document.querySelectorAll("input[type='text'], textarea");
for (let i = 0; i < nodes.length; i++) {
let node = nodes[i];
if (node instanceof Ci.nsIDOMNSEditableElement && node.editor) {
selection = node.editor.selectionController.getSelection(Ci.nsISelectionController.SELECTION_NORMAL);
if (selection.rangeCount && !selection.isCollapsed)
break;
}
}
}
// Return the bounding selection rect in content coordinates
// including the scroll offset.
let offset = ContentScroll.getScrollOffset(content);
for (let frame = this._fastFind.currentWindow; frame != content; frame = frame.parent) {
let rect = frame.frameElement.getBoundingClientRect();
let left = frame.getComputedStyle(frame.frameElement, "").borderLeftWidth;
let top = frame.getComputedStyle(frame.frameElement, "").borderTopWidth;
offset.add(rect.left + parseInt(left), rect.top + parseInt(top));
}
let rangeRect = selection.getRangeAt(0).getBoundingClientRect();
let rect = new Rect(offset.x + rangeRect.left, offset.y + rangeRect.top,
rangeRect.width, rangeRect.height);
// Ensure the potential "scroll" event fired during a search as already fired
let timer = new Util.Timeout(function() {
sendAsyncMessage("FindAssist:Show", {
rect: rect.isEmpty() ? null: rect,
contentHeight: content.document.documentElement.scrollHeight,
result: findResult
});
});
timer.once(0);
}
};
this.FindHandler = FindHandler;
FindHandler.init();

View File

@ -15,8 +15,10 @@ var FindHelperUI = {
close: "cmd_findClose"
},
_finder: null,
_open: false,
_status: null,
_searchString: "",
/*
* Properties
@ -50,35 +52,12 @@ var FindHelperUI = {
this._textbox.addEventListener("keydown", this);
// Listen for find assistant messages from content
messageManager.addMessageListener("FindAssist:Show", this);
messageManager.addMessageListener("FindAssist:Hide", this);
// Listen for events where form assistant should be closed
Elements.tabList.addEventListener("TabSelect", this, true);
Elements.browsers.addEventListener("URLChanged", this, true);
window.addEventListener("MozAppbarShowing", this);
},
receiveMessage: function findHelperReceiveMessage(aMessage) {
let json = aMessage.json;
switch(aMessage.name) {
case "FindAssist:Show":
ContextUI.dismiss();
this.status = json.result;
// null rect implies nothing found
if (json.rect) {
this._zoom(Rect.fromRect(json.rect), json.contentHeight);
}
break;
case "FindAssist:Hide":
if (this._container.getAttribute("type") == this.type)
this.hide();
break;
}
},
handleEvent: function findHelperHandleEvent(aEvent) {
switch (aEvent.type) {
case "TabSelect":
@ -92,11 +71,8 @@ var FindHelperUI = {
case "keydown":
if (aEvent.keyCode == Ci.nsIDOMKeyEvent.DOM_VK_RETURN) {
if (aEvent.shiftKey) {
this.goToPrevious();
} else {
this.goToNext();
}
let backwardsSearch = aEvent.shiftKey;
this.searchAgain(this._searchString, backwardsSearch);
}
break;
@ -151,7 +127,10 @@ var FindHelperUI = {
this._textbox.value = "";
this.status = null;
this._open = false;
if (this._finder) {
this._finder.removeResultListener(this);
this._finder = null
}
// Restore the scroll synchronisation
Browser.selectedBrowser.scrollSync = true;
};
@ -161,22 +140,47 @@ var FindHelperUI = {
this._container.dismiss();
},
search: function findHelperSearch(aValue) {
if (!this._finder) {
this._finder = Browser.selectedBrowser.finder;
this._finder.addResultListener(this);
}
this._searchString = aValue;
if (aValue != "") {
this._finder.fastFind(aValue, false, false);
} else {
this.updateCommands();
}
},
searchAgain: function findHelperSearchAgain(aValue, aFindBackwards) {
// This can happen if the user taps next/previous after re-opening the search bar
if (!this._finder) {
this.search(aValue);
return;
}
this._finder.findAgain(aFindBackwards, false, false);
},
goToPrevious: function findHelperGoToPrevious() {
Browser.selectedBrowser.messageManager.sendAsyncMessage("FindAssist:Previous", { });
this.searchAgain(this._searchString, true);
},
goToNext: function findHelperGoToNext() {
Browser.selectedBrowser.messageManager.sendAsyncMessage("FindAssist:Next", { });
this.searchAgain(this._searchString, false);
},
search: function findHelperSearch(aValue) {
this.updateCommands(aValue);
Browser.selectedBrowser.messageManager.sendAsyncMessage("FindAssist:Find", { searchString: aValue });
onFindResult: function(aResult, aFindBackwards, aLinkURL, aRect) {
this._status = aResult;
if (aRect) {
this._zoom(aRect, Browser.selectedBrowser.contentDocumentHeight);
}
this.updateCommands();
},
updateCommands: function findHelperUpdateCommands(aValue) {
let disabled = (this._status == Ci.nsITypeAheadFind.FIND_NOTFOUND) || (aValue == "");
updateCommands: function findHelperUpdateCommands() {
let disabled = (this._status == Ci.nsITypeAheadFind.FIND_NOTFOUND) || (this._searchString == "");
this._cmdPrevious.setAttribute("disabled", disabled);
this._cmdNext.setAttribute("disabled", disabled);
},

View File

@ -103,11 +103,9 @@
<label id="crashpromptDetailedMessage">
&crashprompt.detailedMessage.firstParagraph;<br />
&crashprompt.detailedMessage.secondParagraph;<br />
&crashprompt.detailedMessage.thirdParagraph;
<a id="privacyPolicyLink"
href="javascript:togglePrivacyDetails();">
&crashprompt.privacyPolicyLink;
</a>
&crashprompt.detailedMessage.thirdParagraphBeforeLink;<!--
--><a id="privacyPolicyLink" href="javascript:togglePrivacyDetails();">&crashprompt.privacyPolicyLink;</a><!--
-->&crashprompt.detailedMessage.thirdParagraphAfterLink;
</label>
</div>
</p>

View File

@ -56,7 +56,6 @@ chrome.jar:
content/contenthandlers/ContextMenuHandler.js (content/contenthandlers/ContextMenuHandler.js)
content/contenthandlers/SelectionHandler.js (content/contenthandlers/SelectionHandler.js)
content/contenthandlers/FormHelper.js (content/contenthandlers/FormHelper.js)
content/contenthandlers/FindHandler.js (content/contenthandlers/FindHandler.js)
content/contenthandlers/ConsoleAPIObserver.js (content/contenthandlers/ConsoleAPIObserver.js)
content/contenthandlers/Content.js (content/contenthandlers/Content.js)

View File

@ -5,6 +5,7 @@
<title>Find bar tests</title>
</head>
<body>
<p>Find bar tests</title>
<p>Find bar tests</p>
<p style="position: absolute; bottom: 0;">bottom</p>
</body>
</html>

View File

@ -88,3 +88,45 @@ gTests.push({
Browser.closeTab(tab);
}
});
gTests.push({
desc: "Text at bottom of screen is not obscured by findbar",
run: function() {
let textbox = document.getElementById("findbar-textbox");
let tab = yield addTab(chromeRoot + "browser_findbar.html");
yield waitForCondition(() => BrowserUI.ready);
is(Elements.findbar.isShowing, false, "Find bar is hidden by default");
FindHelperUI.show();
yield waitForCondition(() => FindHelperUI.isActive);
EventUtils.sendString("bottom");
let event = yield waitForEvent(window, "MozDeckOffsetChanged");
ok(!(event instanceof Error), "MozDeckOffsetChanged received (1)");
ok(event.detail > 0, "Browser deck shifted upward");
textbox.select();
EventUtils.sendString("bar");
event = yield waitForEvent(window, "MozDeckOffsetChanged");
ok(!(event instanceof Error), "MozDeckOffsetChanged received (2)");
is(event.detail, 0, "Browser deck shifted back to normal");
textbox.select();
EventUtils.sendString("bottom");
event = yield waitForEvent(window, "MozDeckOffsetChanged");
ok(!(event instanceof Error), "MozDeckOffsetChanged received (3)");
ok(event.detail > 0, "Browser deck shifted upward again");
let waitForDeckOffset = waitForEvent(window, "MozDeckOffsetChanged");
let waitForTransitionEnd = waitForEvent(Elements.findbar, "transitionend");
FindHelperUI.hide();
event = yield waitForDeckOffset;
ok(!(event instanceof Error), "MozDeckOffsetChanged received (4)");
is(event.detail, 0, "Browser deck shifted back to normal when findbar hides");
// Cleanup.
yield waitForTransitionEnd;
Browser.closeTab(tab);
}
});

View File

@ -8,8 +8,13 @@
<!ENTITY crashprompt.detailsLink "What's in a crash report?">
<!ENTITY crashprompt.detailedMessage.firstParagraph "A crash report contains some details about the crash, your machine, as well as a snapshot of the state of your browser when it crashed.">
<!ENTITY crashprompt.detailedMessage.secondParagraph "This may include things like open pages, text typed into forms and the content of open messages, recent browsing history, or geolocation shared with a site.">
<!ENTITY crashprompt.detailedMessage.thirdParagraph "We use crash reports to try to fix problems and improve our products. We handle your information as we describe in our ">
<!-- LOCALIZATION NOTE (crashprompt.detailedMessage.thirdParagraphBeforeLink, crashprompt.privacyPolicyLink, crashprompt.detailedMessage.thirdParagraphAfterLink):
These three entities form a single sentence. crashprompt.detailedMessage.thirdParagraphBeforeLink is the start of the sentence,
crashprompt.privacyPolicyLink is a clickable link the middle and crashprompt.detailedMessage.thirdParagraphAfterLink is the end of the sentence.
-->
<!ENTITY crashprompt.detailedMessage.thirdParagraphBeforeLink "We use crash reports to try to fix problems and improve our products. We handle your information as we describe in our ">
<!ENTITY crashprompt.privacyPolicyLink "&brandShortName; privacy policy">
<!ENTITY crashprompt.detailedMessage.thirdParagraphAfterLink ".">
<!ENTITY crashprompt.includeURLLabel "Include the address of the page I was on">
<!ENTITY crashprompt.acceptbutton "Send reports">
<!ENTITY crashprompt.refusebutton "Don't send">

View File

@ -157,6 +157,7 @@ browser.jar:
skin/classic/browser/devtools/command-responsivemode@2x.png (../shared/devtools/images/command-responsivemode@2x.png)
skin/classic/browser/devtools/command-scratchpad@2x.png (../shared/devtools/images/command-scratchpad@2x.png)
skin/classic/browser/devtools/command-tilt@2x.png (../shared/devtools/images/command-tilt@2x.png)
skin/classic/browser/devtools/command-pick@2x.png (../shared/devtools/images/command-pick@2x.png)
skin/classic/browser/devtools/command-console@2x.png (../shared/devtools/images/command-console@2x.png)
skin/classic/browser/devtools/alerticon-warning.png (devtools/alerticon-warning.png)
skin/classic/browser/devtools/ruleview.css (devtools/ruleview.css)
@ -269,6 +270,7 @@ browser.jar:
skin/classic/browser/devtools/app-manager/index-icons.svg (../shared/devtools/app-manager/images/index-icons.svg)
skin/classic/browser/devtools/app-manager/rocket.svg (../shared/devtools/app-manager/images/rocket.svg)
skin/classic/browser/devtools/app-manager/noise.png (../shared/devtools/app-manager/images/noise.png)
skin/classic/browser/devtools/app-manager/default-app-icon.png (../shared/devtools/app-manager/images/default-app-icon.png)
#ifdef MOZ_SERVICES_SYNC
skin/classic/browser/sync-16-throbber.png
skin/classic/browser/sync-16.png

View File

@ -260,6 +260,7 @@ browser.jar:
skin/classic/browser/devtools/command-responsivemode@2x.png (../shared/devtools/images/command-responsivemode@2x.png)
skin/classic/browser/devtools/command-scratchpad@2x.png (../shared/devtools/images/command-scratchpad@2x.png)
skin/classic/browser/devtools/command-tilt@2x.png (../shared/devtools/images/command-tilt@2x.png)
skin/classic/browser/devtools/command-pick@2x.png (../shared/devtools/images/command-pick@2x.png)
skin/classic/browser/devtools/command-console@2x.png (../shared/devtools/images/command-console@2x.png)
skin/classic/browser/devtools/alerticon-warning.png (devtools/alerticon-warning.png)
skin/classic/browser/devtools/ruleview.css (devtools/ruleview.css)
@ -372,6 +373,7 @@ browser.jar:
skin/classic/browser/devtools/app-manager/index-icons.svg (../shared/devtools/app-manager/images/index-icons.svg)
skin/classic/browser/devtools/app-manager/rocket.svg (../shared/devtools/app-manager/images/rocket.svg)
skin/classic/browser/devtools/app-manager/noise.png (../shared/devtools/app-manager/images/noise.png)
skin/classic/browser/devtools/app-manager/default-app-icon.png (../shared/devtools/app-manager/images/default-app-icon.png)
#ifdef MOZ_SERVICES_SYNC
skin/classic/browser/sync-throbber.png
skin/classic/browser/sync-16.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

View File

@ -511,14 +511,6 @@
padding: 0 !important;
}
#resumption-panel-desc {
width: 200px;
}
#resumption-order-panel {
-moz-margin-start: -8px;
}
#resume {
list-style-image: url(debugger-pause.png);
-moz-image-region: rect(0px,16px,16px,0px);

View File

@ -12,22 +12,22 @@
outline-offset: -1px;
}
.highlighter-outline[locked] {
box-shadow: 0 0 0 1px rgba(0,0,0,0.3);
outline-color: rgba(255,255,255,0.7);
}
/* Highlighter - Node Infobar */
.highlighter-nodeinfobar {
color: hsl(216,33%,97%);
border-radius: 3px;
background: hsl(214,13%,24%) no-repeat padding-box;
padding: 5px;
/* Avoid cases where the infobar is smaller than the arrow, when the text is
short */
min-width: 75px;
}
/* Highlighter - Node Infobar - text */
.highlighter-nodeinfobar-text {
text-align: center;
/* 100% - size of the buttons and margins */
max-width: calc(100% - 2 * (26px + 6px));
padding-bottom: 1px;
@ -46,43 +46,6 @@ html|*.highlighter-nodeinfobar-pseudo-classes {
color: hsl(200,74%,57%);
}
/* Highlighter - Node Infobar - buttons */
.highlighter-nodeinfobar-button {
-moz-appearance: none;
border-width: 0;
padding: 0;
width: 26px;
min-height: 26px;
%if !defined(MOZ_WIDGET_GTK) && !defined(MOZ_WIDGET_QT)
background-color: transparent;
%endif
}
.highlighter-nodeinfobar-inspectbutton {
-moz-border-end: 1px solid #5a6169;
-moz-margin-end: 12px;
list-style-image: url("chrome://browser/skin/devtools/inspect-button.png");
-moz-image-region: rect(0px 16px 16px 0px);
}
.highlighter-nodeinfobar-inspectbutton:active:hover,
.highlighter-nodeinfobar-container:not([locked]) > .highlighter-nodeinfobar > .highlighter-nodeinfobar-inspectbutton {
-moz-image-region: rect(0px 32px 16px 16px);
}
.highlighter-nodeinfobar-menu {
-moz-border-start: 1px solid #5a6169;
-moz-margin-start: 12px;
}
.highlighter-nodeinfobar-menu > .toolbarbutton-menu-dropmarker {
-moz-appearance: none !important;
list-style-image: url("chrome://browser/skin/devtools/dropmarker.png");
-moz-box-align: center;
-moz-margin-start: -1px;
}
/* Highlighter - Node Infobar - box & arrow */
.highlighter-nodeinfobar-arrow {

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

View File

@ -437,6 +437,10 @@
background-size: 48px 16px;
}
#command-button-pick > image {
background-image: url("chrome://browser/skin/devtools/command-pick@2x.png");
}
#command-button-splitconsole > image {
background-image: url("chrome://browser/skin/devtools/command-console@2x.png");
}

View File

@ -262,7 +262,7 @@
/* Need to constrain the glass fog to avoid overlapping layers, see bug 886281. */
#main-window:not([customizing]) #navigator-toolbox:not(:-moz-lwtheme) {
overflow-y: hidden;
overflow: -moz-hidden-unscrollable;
}
#main-window[sizemode=normal] .tabbrowser-arrowscrollbox > .arrowscrollbox-scrollbox > .scrollbox-innerbox:not(:-moz-lwtheme) {

View File

@ -188,14 +188,15 @@ browser.jar:
skin/classic/browser/devtools/command-responsivemode@2x.png (../shared/devtools/images/command-responsivemode@2x.png)
skin/classic/browser/devtools/command-scratchpad@2x.png (../shared/devtools/images/command-scratchpad@2x.png)
skin/classic/browser/devtools/command-tilt@2x.png (../shared/devtools/images/command-tilt@2x.png)
skin/classic/browser/devtools/command-console@2x.png (../shared/devtools/images/command-console@2x.png)
skin/classic/browser/devtools/command-pick@2x.png (../shared/devtools/images/command-pick@2x.png)
skin/classic/browser/devtools/command-console@2x.png (../shared/devtools/images/command-console@2x.png)
skin/classic/browser/devtools/markup-view.css (../shared/devtools/markup-view.css)
skin/classic/browser/devtools/editor-error.png (devtools/editor-error.png)
skin/classic/browser/devtools/editor-breakpoint.png (devtools/editor-breakpoint.png)
skin/classic/browser/devtools/editor-debug-location.png (devtools/editor-debug-location.png)
* skin/classic/browser/devtools/webconsole.css (devtools/webconsole.css)
skin/classic/browser/devtools/webconsole_networkpanel.css (devtools/webconsole_networkpanel.css)
skin/classic/browser/devtools/webconsole.png (devtools/webconsole.png)
skin/classic/browser/devtools/editor-error.png (devtools/editor-error.png)
skin/classic/browser/devtools/editor-breakpoint.png (devtools/editor-breakpoint.png)
skin/classic/browser/devtools/editor-debug-location.png (devtools/editor-debug-location.png)
* skin/classic/browser/devtools/webconsole.css (devtools/webconsole.css)
skin/classic/browser/devtools/webconsole_networkpanel.css (devtools/webconsole_networkpanel.css)
skin/classic/browser/devtools/webconsole.png (devtools/webconsole.png)
skin/classic/browser/devtools/breadcrumbs-scrollbutton.png (devtools/breadcrumbs-scrollbutton.png)
skin/classic/browser/devtools/breadcrumbs/ltr-end-pressed.png (devtools/breadcrumbs/ltr-end-pressed.png)
skin/classic/browser/devtools/breadcrumbs/ltr-end-selected-pressed.png (devtools/breadcrumbs/ltr-end-selected-pressed.png)
@ -297,6 +298,7 @@ browser.jar:
skin/classic/browser/devtools/app-manager/index-icons.svg (../shared/devtools/app-manager/images/index-icons.svg)
skin/classic/browser/devtools/app-manager/rocket.svg (../shared/devtools/app-manager/images/rocket.svg)
skin/classic/browser/devtools/app-manager/noise.png (../shared/devtools/app-manager/images/noise.png)
skin/classic/browser/devtools/app-manager/default-app-icon.png (../shared/devtools/app-manager/images/default-app-icon.png)
#ifdef MOZ_SERVICES_SYNC
skin/classic/browser/sync-throbber.png
skin/classic/browser/sync-16.png
@ -498,6 +500,7 @@ browser.jar:
skin/classic/aero/browser/devtools/command-responsivemode@2x.png (../shared/devtools/images/command-responsivemode@2x.png)
skin/classic/aero/browser/devtools/command-scratchpad@2x.png (../shared/devtools/images/command-scratchpad@2x.png)
skin/classic/aero/browser/devtools/command-tilt@2x.png (../shared/devtools/images/command-tilt@2x.png)
skin/classic/aero/browser/devtools/command-pick@2x.png (../shared/devtools/images/command-pick@2x.png)
skin/classic/aero/browser/devtools/command-console@2x.png (../shared/devtools/images/command-console@2x.png)
skin/classic/aero/browser/devtools/alerticon-warning.png (devtools/alerticon-warning.png)
skin/classic/aero/browser/devtools/ruleview.css (devtools/ruleview.css)
@ -610,6 +613,8 @@ browser.jar:
skin/classic/aero/browser/devtools/app-manager/index-icons.svg (../shared/devtools/app-manager/images/index-icons.svg)
skin/classic/aero/browser/devtools/app-manager/rocket.svg (../shared/devtools/app-manager/images/rocket.svg)
skin/classic/aero/browser/devtools/app-manager/noise.png (../shared/devtools/app-manager/images/noise.png)
skin/classic/aero/browser/devtools/app-manager/default-app-icon.png (../shared/devtools/app-manager/images/default-app-icon.png)
#ifdef MOZ_SERVICES_SYNC
skin/classic/aero/browser/sync-throbber.png
skin/classic/aero/browser/sync-16.png

View File

@ -786,11 +786,11 @@ abstract public class BrowserApp extends GeckoApp
}
final OnFaviconLoadedListener listener = new GeckoAppShell.CreateShortcutFaviconLoadedListener(url, title);
Favicons.getFaviconForSize(url,
tab.getFaviconURL(),
Integer.MAX_VALUE,
LoadFaviconTask.FLAG_PERSIST,
listener);
Favicons.getSizedFavicon(url,
tab.getFaviconURL(),
Integer.MAX_VALUE,
LoadFaviconTask.FLAG_PERSIST,
listener);
return true;
}
@ -1382,8 +1382,6 @@ abstract public class BrowserApp extends GeckoApp
}
private void openUrlAndStopEditing(String url, String searchEngine, boolean newTab) {
mBrowserToolbar.setProgressVisibility(true);
int flags = Tabs.LOADURL_NONE;
if (newTab) {
flags |= Tabs.LOADURL_NEW_TAB;
@ -1415,15 +1413,16 @@ abstract public class BrowserApp extends GeckoApp
final int tabFaviconSize = getResources().getDimensionPixelSize(R.dimen.browser_toolbar_favicon_size);
int flags = (tab.isPrivate() || tab.getErrorType() != Tab.ErrorType.NONE) ? 0 : LoadFaviconTask.FLAG_PERSIST;
int id = Favicons.getFaviconForSize(tab.getURL(), tab.getFaviconURL(), tabFaviconSize, flags, sFaviconLoadedListener);
int id = Favicons.getSizedFavicon(tab.getURL(), tab.getFaviconURL(), tabFaviconSize, flags, sFaviconLoadedListener);
tab.setFaviconLoadId(id);
if (id != Favicons.LOADED &&
Tabs.getInstance().isSelectedTab(tab)) {
final Tabs tabs = Tabs.getInstance();
if (id != Favicons.LOADED && tabs.isSelectedTab(tab)) {
// We're loading the current tab's favicon from somewhere
// other than the cache.
// Display the globe favicon until then.
mBrowserToolbar.showDefaultFavicon();
// other than the cache. Display the globe favicon until then.
tab.updateFavicon(Favicons.sDefaultFavicon);
tabs.notifyListeners(tab, Tabs.TabEvents.FAVICON);
}
}

View File

@ -235,10 +235,6 @@ public class Tab {
return mHasOpenSearch;
}
public SecurityMode getSecurityMode() {
return mSiteIdentity.getSecurityMode();
}
public SiteIdentity getSiteIdentity() {
return mSiteIdentity;
}

View File

@ -766,7 +766,7 @@ public class Tabs implements GeckoEventListener {
*/
private Bitmap getAboutPageFavicon(final String url) {
int faviconSize = Math.round(mAppContext.getResources().getDimension(R.dimen.browser_toolbar_favicon_size));
return Favicons.getCachedFaviconForSize(url, faviconSize);
return Favicons.getSizedFaviconForPageFromCache(url, faviconSize);
}
/**

View File

@ -2974,7 +2974,7 @@ public class BrowserProvider extends ContentProvider {
// If no URL is provided, insert using the default one.
if (TextUtils.isEmpty(faviconUrl) && !TextUtils.isEmpty(pageUrl)) {
values.put(Favicons.URL, org.mozilla.gecko.favicons.Favicons.guessDefaultFaviconURL(pageUrl));
values.put(Favicons.URL, org.mozilla.gecko.favicons.Favicons.guessDefaultFaviconUrl(pageUrl));
}
long now = System.currentTimeMillis();

View File

@ -65,7 +65,7 @@ public class Favicons {
// doing so is not necessary.
private static final NonEvictingLruCache<String, String> sPageURLMappings = new NonEvictingLruCache<String, String>(NUM_PAGE_URL_MAPPINGS_TO_STORE);
public static String getFaviconURLForPageURLFromCache(String pageURL) {
public static String getFaviconUrlForPageUrlFromCache(String pageURL) {
return sPageURLMappings.get(pageURL);
}
@ -73,7 +73,7 @@ public class Favicons {
* Insert the given pageUrl->faviconUrl mapping into the memory cache of such mappings.
* Useful for short-circuiting local database access.
*/
public static void putFaviconURLForPageURLInCache(String pageURL, String faviconURL) {
public static void putFaviconUrlForPageUrlInCache(String pageURL, String faviconURL) {
sPageURLMappings.put(pageURL, faviconURL);
}
@ -111,7 +111,7 @@ public class Favicons {
*
* Returns null otherwise.
*/
public static Bitmap getCachedFaviconForSize(final String pageURL, int targetSize) {
public static Bitmap getSizedFaviconForPageFromCache(final String pageURL, int targetSize) {
final String faviconURL = sPageURLMappings.get(pageURL);
if (faviconURL == null) {
return null;
@ -134,7 +134,7 @@ public class Favicons {
* @return The id of the asynchronous task created, NOT_LOADING if none is created, or
* LOADED if the value could be dispatched on the current thread.
*/
public static int getFaviconForSize(String pageURL, String faviconURL, int targetSize, int flags, OnFaviconLoadedListener listener) {
public static int getSizedFavicon(String pageURL, String faviconURL, int targetSize, int flags, OnFaviconLoadedListener listener) {
// Do we know the favicon URL for this page already?
String cacheURL = faviconURL;
if (cacheURL == null) {
@ -143,7 +143,7 @@ public class Favicons {
// If there's no favicon URL given, try and hit the cache with the default one.
if (cacheURL == null) {
cacheURL = guessDefaultFaviconURL(pageURL);
cacheURL = guessDefaultFaviconUrl(pageURL);
}
// If it's something we can't even figure out a default URL for, just give up.
@ -222,6 +222,7 @@ public class Favicons {
public static int getSizedFaviconForPageFromLocal(final String pageURL, final OnFaviconLoadedListener callback) {
return getSizedFaviconForPageFromLocal(pageURL, sDefaultFaviconSize, callback);
}
/**
* Helper method to determine the URL of the Favicon image for a given page URL by querying the
* history database. Should only be called from the background thread - does database access.
@ -245,7 +246,7 @@ public class Favicons {
targetURL = BrowserDB.getFaviconUrlForHistoryUrl(sContext.getContentResolver(), pageURL);
if (targetURL == null) {
// Nothing in the history database. Fall back to the default URL and hope for the best.
targetURL = guessDefaultFaviconURL(pageURL);
targetURL = guessDefaultFaviconUrl(pageURL);
}
return targetURL;
}
@ -406,7 +407,7 @@ public class Favicons {
* @param pageURL Page URL for which a default Favicon URL is requested
* @return The default Favicon URL.
*/
public static String guessDefaultFaviconURL(String pageURL) {
public static String guessDefaultFaviconUrl(String pageURL) {
// Special-casing for about: pages. The favicon for about:pages which don't provide a link tag
// is bundled in the database, keyed only by page URL, hence the need to return the page URL
// here. If the database ever migrates to stop being silly in this way, this can plausibly

View File

@ -240,14 +240,14 @@ public class LoadFaviconTask extends UiAsyncTask<Void, Void, Bitmap> {
// If favicon is empty, fall back to the stored one.
if (TextUtils.isEmpty(mFaviconUrl)) {
// Try to get the favicon URL from the memory cache.
storedFaviconUrl = Favicons.getFaviconURLForPageURLFromCache(mPageUrl);
storedFaviconUrl = Favicons.getFaviconUrlForPageUrlFromCache(mPageUrl);
// If that failed, try to get the URL from the database.
if (storedFaviconUrl == null) {
storedFaviconUrl = Favicons.getFaviconUrlForPageUrl(mPageUrl);
if (storedFaviconUrl != null) {
// If that succeeded, cache the URL loaded from the database in memory.
Favicons.putFaviconURLForPageURLInCache(mPageUrl, storedFaviconUrl);
Favicons.putFaviconUrlForPageUrlInCache(mPageUrl, storedFaviconUrl);
}
}
@ -256,7 +256,7 @@ public class LoadFaviconTask extends UiAsyncTask<Void, Void, Bitmap> {
mFaviconUrl = storedFaviconUrl;
} else {
// If we don't have a stored one, fall back to the default.
mFaviconUrl = Favicons.guessDefaultFaviconURL(mPageUrl);
mFaviconUrl = Favicons.guessDefaultFaviconUrl(mPageUrl);
if (TextUtils.isEmpty(mFaviconUrl)) {
return null;
@ -335,7 +335,7 @@ public class LoadFaviconTask extends UiAsyncTask<Void, Void, Bitmap> {
}
// If we're not already trying the default URL, try it now.
final String guessed = Favicons.guessDefaultFaviconURL(mPageUrl);
final String guessed = Favicons.guessDefaultFaviconUrl(mPageUrl);
if (guessed == null) {
Favicons.putFaviconInFailedCache(mFaviconUrl);
return null;

View File

@ -42,7 +42,7 @@ abstract class HomeFragment extends Fragment {
private static final String LOGTAG="GeckoHomeFragment";
// Share MIME type.
private static final String SHARE_MIME_TYPE = "text/plain";
protected static final String SHARE_MIME_TYPE = "text/plain";
// Default value for "can load" hint
static final boolean DEFAULT_CAN_LOAD_HINT = false;
@ -123,9 +123,11 @@ abstract class HomeFragment extends Fragment {
if (itemId == R.id.home_share) {
if (info.url == null) {
Log.e(LOGTAG, "Can't share because URL is null");
return false;
} else {
GeckoAppShell.openUriExternal(info.url, SHARE_MIME_TYPE, "", "",
Intent.ACTION_SEND, info.getDisplayTitle());
return true;
}
}

View File

@ -16,6 +16,7 @@ import org.mozilla.gecko.db.BrowserDB;
import org.mozilla.gecko.db.BrowserDB.URLColumns;
import org.mozilla.gecko.db.BrowserDB.TopSitesCursorWrapper;
import org.mozilla.gecko.favicons.OnFaviconLoadedListener;
import org.mozilla.gecko.GeckoAppShell;
import org.mozilla.gecko.gfx.BitmapUtils;
import org.mozilla.gecko.home.HomeListView.HomeContextMenuInfo;
import org.mozilla.gecko.home.HomePager.OnUrlOpenListener;
@ -27,6 +28,7 @@ import org.mozilla.gecko.util.ThreadUtils;
import android.app.Activity;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.res.Configuration;
import android.database.Cursor;
import android.graphics.Bitmap;
@ -358,6 +360,28 @@ public class TopSitesPage extends HomeFragment {
return true;
}
if (itemId == R.id.home_share) {
if (info.url == null) {
Log.w(LOGTAG, "Share not enabled for context menu because URL is null.");
return false;
} else {
GeckoAppShell.openUriExternal(info.url, SHARE_MIME_TYPE, "", "",
Intent.ACTION_SEND, info.getDisplayTitle());
return true;
}
}
if (itemId == R.id.home_add_to_launcher) {
if (info.url == null) {
Log.w(LOGTAG, "Not enabling 'Add to home page' because URL is null.");
return false;
}
// Fetch the largest cacheable icon size.
Favicons.getLargestFaviconForPage(info.url, new GeckoAppShell.CreateShortcutFaviconLoadedListener(info.url, info.getDisplayTitle()));
return true;
}
return false;
}

View File

@ -313,8 +313,10 @@ gbjar.sources += [
'toolbar/ShapedButton.java',
'toolbar/SiteIdentityPopup.java',
'toolbar/TabCounter.java',
'toolbar/ToolbarDisplayLayout.java',
'toolbar/ToolbarEditLayout.java',
'toolbar/ToolbarEditText.java',
'toolbar/ToolbarTitlePrefs.java',
'TouchEventInterceptor.java',
'updater/UpdateService.java',
'updater/UpdateServiceHelper.java',

View File

@ -69,58 +69,10 @@
android:layout_toRightOf="@id/back"
android:layout_toLeftOf="@id/menu_items"/>
<LinearLayout android:id="@+id/url_display_container"
<org.mozilla.gecko.toolbar.ToolbarDisplayLayout android:id="@+id/display_layout"
style="@style/UrlBar.Button.Container"
android:layout_toRightOf="@id/back"
android:layout_toLeftOf="@id/menu_items">
<ImageButton android:id="@+id/favicon"
style="@style/UrlBar.ImageButton"
android:layout_width="@dimen/browser_toolbar_favicon_size"
android:layout_height="fill_parent"
android:scaleType="fitCenter"
android:layout_marginLeft="4dip"
android:paddingLeft="4dip"
android:paddingRight="4dip"
android:layout_gravity="center_vertical"
android:src="@drawable/favicon"/>
<ImageButton android:id="@+id/site_security"
style="@style/UrlBar.ImageButton"
android:layout_width="@dimen/browser_toolbar_lock_width"
android:scaleType="fitCenter"
android:layout_marginLeft="-4dip"
android:src="@drawable/site_security_level"
android:contentDescription="@string/site_security"
android:visibility="gone"/>
<org.mozilla.gecko.widget.GeckoTextView android:id="@+id/url_bar_title"
style="@style/UrlBar.Button"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_weight="1.0"
android:singleLine="true"
android:paddingRight="8dp"
android:textColor="@color/url_bar_title"
android:textColorHint="@color/url_bar_title_hint"
android:gravity="center_vertical|left"
android:layout_gravity="center_vertical"
gecko:autoUpdateTheme="false"/>
<org.mozilla.gecko.toolbar.PageActionLayout android:id="@+id/page_action_layout"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_marginRight="@dimen/browser_toolbar_button_padding"
android:visibility="gone"
android:orientation="horizontal"/>
<ImageButton android:id="@+id/stop"
style="@style/UrlBar.ImageButton.Icon"
android:src="@drawable/urlbar_stop"
android:contentDescription="@string/stop"
android:visibility="gone"/>
</LinearLayout>
android:layout_toLeftOf="@id/menu_items"/>
<LinearLayout android:id="@+id/menu_items"
android:layout_width="wrap_content"

View File

@ -93,59 +93,10 @@
android:paddingLeft="8dp"
android:visibility="gone"/>
<LinearLayout android:id="@+id/url_display_container"
<org.mozilla.gecko.toolbar.ToolbarDisplayLayout android:id="@+id/display_layout"
style="@style/UrlBar.Button"
android:layout_toLeftOf="@id/tabs"
android:layout_marginRight="-24dp"
android:orientation="horizontal">
<ImageButton android:id="@+id/favicon"
style="@style/UrlBar.ImageButton"
android:layout_width="@dimen/browser_toolbar_favicon_size"
android:layout_height="fill_parent"
android:scaleType="fitCenter"
android:layout_marginLeft="8dip"
android:paddingLeft="4dip"
android:paddingRight="4dip"
android:layout_gravity="center_vertical"/>
<ImageButton android:id="@+id/site_security"
style="@style/UrlBar.ImageButton"
android:layout_width="@dimen/browser_toolbar_lock_width"
android:scaleType="fitCenter"
android:layout_marginLeft="-4dip"
android:src="@drawable/site_security_level"
android:contentDescription="@string/site_security"
android:visibility="gone"/>
<org.mozilla.gecko.widget.GeckoTextView android:id="@+id/url_bar_title"
style="@style/UrlBar.Button"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_weight="1.0"
android:singleLine="true"
android:paddingRight="8dp"
android:textColor="@color/url_bar_title"
android:textColorHint="@color/url_bar_title_hint"
android:gravity="center_vertical|left"
android:hint="@string/url_bar_default_text"
android:layout_gravity="center_vertical"
gecko:autoUpdateTheme="false"/>
<org.mozilla.gecko.toolbar.PageActionLayout android:id="@+id/page_action_layout"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_marginRight="12dp"
android:visibility="gone"
android:orientation="horizontal"/>
<ImageButton android:id="@+id/stop"
style="@style/UrlBar.ImageButton.Icon"
android:src="@drawable/urlbar_stop"
android:contentDescription="@string/stop"
android:visibility="gone"/>
</LinearLayout>
android:layout_marginRight="-24dp"/>
<ImageView android:id="@+id/shadow"
android:layout_width="fill_parent"

View File

@ -0,0 +1,55 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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/. -->
<merge xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:gecko="http://schemas.android.com/apk/res-auto">
<ImageButton android:id="@+id/favicon"
style="@style/UrlBar.ImageButton"
android:layout_width="@dimen/browser_toolbar_favicon_size"
android:layout_height="fill_parent"
android:scaleType="fitCenter"
android:layout_marginLeft="8dip"
android:paddingLeft="4dip"
android:paddingRight="4dip"
android:layout_gravity="center_vertical"/>
<ImageButton android:id="@+id/site_security"
style="@style/UrlBar.ImageButton"
android:layout_width="@dimen/browser_toolbar_lock_width"
android:scaleType="fitCenter"
android:layout_marginLeft="-4dip"
android:src="@drawable/site_security_level"
android:contentDescription="@string/site_security"
android:visibility="gone"/>
<org.mozilla.gecko.widget.GeckoTextView android:id="@+id/url_bar_title"
style="@style/UrlBar.Button"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_weight="1.0"
android:singleLine="true"
android:paddingRight="8dp"
android:textColor="@color/url_bar_title"
android:textColorHint="@color/url_bar_title_hint"
android:gravity="center_vertical|left"
android:hint="@string/url_bar_default_text"
android:layout_gravity="center_vertical"
gecko:autoUpdateTheme="false"/>
<org.mozilla.gecko.toolbar.PageActionLayout android:id="@+id/page_action_layout"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_marginRight="@dimen/browser_toolbar_button_padding"
android:visibility="gone"
android:orientation="horizontal"/>
<ImageButton android:id="@+id/stop"
style="@style/UrlBar.ImageButton.Icon"
android:src="@drawable/urlbar_stop"
android:contentDescription="@string/stop"
android:visibility="gone"/>
</merge>

View File

@ -11,6 +11,9 @@
<item android:id="@+id/top_sites_open_private_tab"
android:title="@string/contextmenu_open_private_tab"/>
<item android:id="@+id/home_share"
android:title="@string/contextmenu_share"/>
<item android:id="@+id/top_sites_edit"
android:title="@string/contextmenu_top_sites_edit"/>
@ -20,4 +23,6 @@
<item android:id="@+id/top_sites_unpin"
android:title="@string/contextmenu_top_sites_unpin"/>
<item android:id="@+id/home_add_to_launcher"
android:title="@string/contextmenu_add_to_launcher"/>
</menu>

View File

@ -59,8 +59,8 @@ public class ToolbarComponent extends BaseComponent {
return (EditText) getToolbarView().findViewById(R.id.url_edit_text);
}
private View getUrlDisplayContainer() {
return getToolbarView().findViewById(R.id.url_display_container);
private View getUrlDisplayLayout() {
return getToolbarView().findViewById(R.id.display_layout);
}
private TextView getUrlTitleText() {
@ -106,7 +106,7 @@ public class ToolbarComponent extends BaseComponent {
}
private boolean isEditing() {
return getUrlDisplayContainer().getVisibility() != View.VISIBLE &&
return getUrlDisplayLayout().getVisibility() != View.VISIBLE &&
getUrlEditText().getVisibility() == View.VISIBLE;
}

View File

@ -5,14 +5,12 @@
package org.mozilla.gecko.toolbar;
import org.mozilla.gecko.AboutPages;
import org.mozilla.gecko.BrowserApp;
import org.mozilla.gecko.GeckoApplication;
import org.mozilla.gecko.GeckoAppShell;
import org.mozilla.gecko.GeckoProfile;
import org.mozilla.gecko.LightweightTheme;
import org.mozilla.gecko.R;
import org.mozilla.gecko.SiteIdentity;
import org.mozilla.gecko.SiteIdentity.SecurityMode;
import org.mozilla.gecko.Tab;
import org.mozilla.gecko.Tabs;
@ -21,16 +19,16 @@ import org.mozilla.gecko.animation.PropertyAnimator.PropertyAnimationListener;
import org.mozilla.gecko.animation.ViewHelper;
import org.mozilla.gecko.menu.GeckoMenu;
import org.mozilla.gecko.menu.MenuPopup;
import org.mozilla.gecko.PrefsHelper;
import org.mozilla.gecko.toolbar.ToolbarDisplayLayout.OnStopListener;
import org.mozilla.gecko.toolbar.ToolbarDisplayLayout.OnTitleChangeListener;
import org.mozilla.gecko.toolbar.ToolbarDisplayLayout.UpdateFlags;
import org.mozilla.gecko.util.Clipboard;
import org.mozilla.gecko.util.HardwareUtils;
import org.mozilla.gecko.util.ThreadUtils;
import org.mozilla.gecko.util.GeckoEventListener;
import org.mozilla.gecko.util.StringUtils;
import org.mozilla.gecko.widget.GeckoImageButton;
import org.mozilla.gecko.widget.GeckoImageView;
import org.mozilla.gecko.widget.GeckoRelativeLayout;
import org.mozilla.gecko.widget.GeckoTextView;
import org.json.JSONObject;
@ -40,11 +38,6 @@ import android.graphics.Bitmap;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.StateListDrawable;
import android.os.Build;
import android.os.SystemClock;
import android.text.style.ForegroundColorSpan;
import android.text.Spannable;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.Log;
@ -56,11 +49,7 @@ import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup.MarginLayoutParams;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.view.animation.AlphaAnimation;
import android.view.animation.Interpolator;
import android.view.animation.TranslateAnimation;
import android.view.inputmethod.InputMethodManager;
import android.widget.Button;
import android.widget.ImageButton;
@ -69,16 +58,15 @@ import android.widget.LinearLayout;
import android.widget.PopupWindow;
import java.util.Arrays;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;
public class BrowserToolbar extends GeckoRelativeLayout
implements Tabs.OnTabsChangedListener,
GeckoMenu.ActionItemBarPresenter,
Animation.AnimationListener,
GeckoEventListener {
private static final String LOGTAG = "GeckoToolbar";
public static final String PREF_TITLEBAR_MODE = "browser.chrome.titlebarMode";
public static final String PREF_TRIM_URLS = "browser.urlbar.trimURLs";
public interface OnActivateListener {
public void onActivate();
@ -104,37 +92,26 @@ public class BrowserToolbar extends GeckoRelativeLayout
public void onStopEditing();
}
private enum ForwardButtonAnimation {
enum ForwardButtonAnimation {
SHOW,
HIDE
}
private View mUrlDisplayContainer;
private ToolbarDisplayLayout mUrlDisplayLayout;
private ToolbarEditLayout mUrlEditLayout;
private View mUrlBarEntry;
private ImageView mUrlBarRightEdge;
private GeckoTextView mTitle;
private int mTitlePadding;
private boolean mSiteSecurityVisible;
private boolean mSwitchingTabs;
private ShapedButton mTabs;
private ImageButton mBack;
private ImageButton mForward;
private ImageButton mStop;
// To de-bounce sets.
private Bitmap mLastFavicon;
private ImageButton mFavicon;
private ImageButton mSiteSecurity;
private PageActionLayout mPageActionLayout;
private Animation mProgressSpinner;
private TabCounter mTabsCounter;
private GeckoImageButton mMenu;
private GeckoImageView mMenuIcon;
private LinearLayout mActionItemBar;
private MenuPopup mMenuPopup;
private List<? extends View> mFocusOrder;
private List<View> mFocusOrder;
private OnActivateListener mActivateListener;
private OnCommitListener mCommitListener;
@ -143,46 +120,21 @@ public class BrowserToolbar extends GeckoRelativeLayout
private OnStartEditingListener mStartEditingListener;
private OnStopEditingListener mStopEditingListener;
private SiteIdentityPopup mSiteIdentityPopup;
final private BrowserApp mActivity;
private boolean mHasSoftMenuButton;
private boolean mShowSiteSecurity;
private boolean mSpinnerVisible;
private boolean mIsEditing;
private boolean mAnimatingEntry;
private AlphaAnimation mLockFadeIn;
private TranslateAnimation mTitleSlideLeft;
private TranslateAnimation mTitleSlideRight;
private int mUrlBarViewOffset;
private int mDefaultForwardMargin;
private PropertyAnimator mForwardAnim = null;
private int mFaviconSize;
private PropertyAnimator mVisibilityAnimator;
private static final Interpolator sButtonsInterpolator = new AccelerateInterpolator();
private static final int TABS_CONTRACTED = 1;
private static final int TABS_EXPANDED = 2;
private static final int FORWARD_ANIMATION_DURATION = 450;
private final ForegroundColorSpan mUrlColor;
private final ForegroundColorSpan mBlockedColor;
private final ForegroundColorSpan mDomainColor;
private final ForegroundColorSpan mPrivateDomainColor;
private final LightweightTheme mTheme;
private boolean mShowUrl;
private boolean mTrimURLs;
private Integer mPrefObserverId;
public BrowserToolbar(Context context) {
this(context, null);
}
@ -199,75 +151,17 @@ public class BrowserToolbar extends GeckoRelativeLayout
Tabs.registerOnTabsChangedListener(this);
mSwitchingTabs = true;
mAnimatingEntry = false;
mShowUrl = false;
mTrimURLs = true;
final String[] prefs = {
PREF_TITLEBAR_MODE,
PREF_TRIM_URLS
};
// listen to the title bar pref.
mPrefObserverId = PrefsHelper.getPrefs(prefs, new PrefsHelper.PrefHandlerBase() {
@Override
public void prefValue(String pref, String str) {
// Handles PREF_TITLEBAR_MODE, which is always a string.
int value = Integer.parseInt(str);
boolean shouldShowUrl = (value == 1);
if (shouldShowUrl == mShowUrl) {
return;
}
mShowUrl = shouldShowUrl;
triggerTitleUpdate();
}
@Override
public void prefValue(String pref, boolean value) {
// Handles PREF_TRIM_URLS, which should usually be a boolean.
if (value == mTrimURLs) {
return;
}
mTrimURLs = value;
triggerTitleUpdate();
}
@Override
public boolean isObserver() {
// We want to be notified of changes to be able to switch mode
// without restarting.
return true;
}
private void triggerTitleUpdate() {
ThreadUtils.postToUiThread(new Runnable() {
@Override
public void run() {
updateTitle();
}
});
}
});
Resources res = getResources();
mUrlColor = new ForegroundColorSpan(res.getColor(R.color.url_bar_urltext));
mBlockedColor = new ForegroundColorSpan(res.getColor(R.color.url_bar_blockedtext));
mDomainColor = new ForegroundColorSpan(res.getColor(R.color.url_bar_domaintext));
mPrivateDomainColor = new ForegroundColorSpan(res.getColor(R.color.url_bar_domaintext_private));
registerEventListener("Reader:Click");
registerEventListener("Reader:LongClick");
mShowSiteSecurity = false;
mAnimatingEntry = false;
final Resources res = getResources();
mUrlBarViewOffset = res.getDimensionPixelSize(R.dimen.url_bar_offset_left);
mDefaultForwardMargin = res.getDimensionPixelSize(R.dimen.forward_default_offset);
mUrlDisplayContainer = findViewById(R.id.url_display_container);
mUrlDisplayLayout = (ToolbarDisplayLayout) findViewById(R.id.display_layout);
mUrlBarEntry = findViewById(R.id.url_bar_entry);
mUrlEditLayout = (ToolbarEditLayout) findViewById(R.id.edit_layout);
@ -277,9 +171,6 @@ public class BrowserToolbar extends GeckoRelativeLayout
mUrlBarRightEdge.getDrawable().setLevel(6000);
}
mTitle = (GeckoTextView) findViewById(R.id.url_bar_title);
mTitlePadding = mTitle.getPaddingRight();
mTabs = (ShapedButton) findViewById(R.id.tabs);
mTabsCounter = (TabCounter) findViewById(R.id.tabs_counter);
@ -288,26 +179,6 @@ public class BrowserToolbar extends GeckoRelativeLayout
mForward = (ImageButton) findViewById(R.id.forward);
setButtonEnabled(mForward, false);
mFavicon = (ImageButton) findViewById(R.id.favicon);
if (Build.VERSION.SDK_INT >= 11) {
if (Build.VERSION.SDK_INT >= 16) {
mFavicon.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO);
}
mFavicon.setLayerType(View.LAYER_TYPE_HARDWARE, null);
}
mFaviconSize = Math.round(res.getDimension(R.dimen.browser_toolbar_favicon_size));
mSiteSecurity = (ImageButton) findViewById(R.id.site_security);
mSiteSecurityVisible = (mSiteSecurity.getVisibility() == View.VISIBLE);
mSiteIdentityPopup = new SiteIdentityPopup(mActivity);
mSiteIdentityPopup.setAnchor(mSiteSecurity);
mProgressSpinner = AnimationUtils.loadAnimation(mActivity, R.anim.progress_spinner);
mStop = (ImageButton) findViewById(R.id.stop);
mPageActionLayout = (PageActionLayout) findViewById(R.id.page_action_layout);
mMenu = (GeckoImageButton) findViewById(R.id.menu);
mMenuIcon = (GeckoImageView) findViewById(R.id.menu_icon);
mActionItemBar = (LinearLayout) findViewById(R.id.menu_items);
@ -315,12 +186,15 @@ public class BrowserToolbar extends GeckoRelativeLayout
// We use different layouts on phones and tablets, so adjust the focus
// order appropriately.
mFocusOrder = new ArrayList<View>();
if (HardwareUtils.isTablet()) {
mFocusOrder = Arrays.asList(mTabs, mBack, mForward, this,
mSiteSecurity, mPageActionLayout, mStop, mActionItemBar, mMenu);
mFocusOrder.addAll(Arrays.asList(mTabs, mBack, mForward, this));
mFocusOrder.addAll(mUrlDisplayLayout.getFocusOrder());
mFocusOrder.addAll(Arrays.asList(mActionItemBar, mMenu));
} else {
mFocusOrder = Arrays.asList(this, mSiteSecurity, mPageActionLayout, mStop,
mTabs, mMenu);
mFocusOrder.add(this);
mFocusOrder.addAll(mUrlDisplayLayout.getFocusOrder());
mFocusOrder.addAll(Arrays.asList(mTabs, mMenu));
}
setIsEditing(false);
@ -380,6 +254,35 @@ public class BrowserToolbar extends GeckoRelativeLayout
}
});
mUrlDisplayLayout.setOnStopListener(new OnStopListener() {
@Override
public Tab onStop() {
final Tab tab = Tabs.getInstance().getSelectedTab();
if (tab != null) {
tab.doStop();
return tab;
}
return null;
}
});
mUrlDisplayLayout.setOnTitleChangeListener(new OnTitleChangeListener() {
@Override
public void onTitleChange(CharSequence title) {
final String contentDescription;
if (title != null) {
contentDescription = title.toString();
} else {
contentDescription = mActivity.getString(R.string.url_bar_default_text);
}
// The title and content description should
// always be sync.
setContentDescription(contentDescription);
}
});
mUrlEditLayout.setOnFocusChangeListener(new View.OnFocusChangeListener() {
@Override
public void onFocusChange(View v, boolean hasFocus) {
@ -421,58 +324,6 @@ public class BrowserToolbar extends GeckoRelativeLayout
}
});
Button.OnClickListener faviconListener = new Button.OnClickListener() {
@Override
public void onClick(View view) {
if (mSiteSecurity.getVisibility() != View.VISIBLE)
return;
final Tab tab = Tabs.getInstance().getSelectedTab();
final SiteIdentity siteIdentity = tab.getSiteIdentity();
if (siteIdentity.getSecurityMode() == SecurityMode.UNKNOWN) {
Log.e(LOGTAG, "Selected tab has no identity data");
return;
}
mSiteIdentityPopup.updateIdentity(siteIdentity);
mSiteIdentityPopup.show();
}
};
mFavicon.setOnClickListener(faviconListener);
mSiteSecurity.setOnClickListener(faviconListener);
mStop.setOnClickListener(new Button.OnClickListener() {
@Override
public void onClick(View v) {
Tab tab = Tabs.getInstance().getSelectedTab();
if (tab != null)
tab.doStop();
setProgressVisibility(false);
}
});
float slideWidth = getResources().getDimension(R.dimen.browser_toolbar_lock_width);
LinearLayout.LayoutParams siteSecParams = (LinearLayout.LayoutParams) mSiteSecurity.getLayoutParams();
final float scale = getResources().getDisplayMetrics().density;
slideWidth += (siteSecParams.leftMargin + siteSecParams.rightMargin) * scale + 0.5f;
mLockFadeIn = new AlphaAnimation(0.0f, 1.0f);
mLockFadeIn.setAnimationListener(this);
mTitleSlideLeft = new TranslateAnimation(slideWidth, 0, 0, 0);
mTitleSlideLeft.setAnimationListener(this);
mTitleSlideRight = new TranslateAnimation(-slideWidth, 0, 0, 0);
mTitleSlideRight.setAnimationListener(this);
final int lockAnimDuration = 300;
mLockFadeIn.setDuration(lockAnimDuration);
mTitleSlideLeft.setDuration(lockAnimDuration);
mTitleSlideRight.setDuration(lockAnimDuration);
if (mHasSoftMenuButton) {
mMenu.setVisibility(View.VISIBLE);
mMenuIcon.setVisibility(View.VISIBLE);
@ -487,11 +338,11 @@ public class BrowserToolbar extends GeckoRelativeLayout
}
public void refresh() {
dismissSiteIdentityPopup();
mUrlDisplayLayout.dismissSiteIdentityPopup();
}
public boolean onBackPressed() {
return dismissSiteIdentityPopup();
return mUrlDisplayLayout.dismissSiteIdentityPopup();
}
public boolean onKey(int keyCode, KeyEvent event) {
@ -570,39 +421,41 @@ public class BrowserToolbar extends GeckoRelativeLayout
case RESTORED:
// TabCount fixup after OOM
case SELECTED:
dismissSiteIdentityPopup();
mUrlDisplayLayout.dismissSiteIdentityPopup();
updateTabCount(tabs.getDisplayCount());
mSwitchingTabs = true;
// Fall through.
}
if (tabs.isSelectedTab(tab)) {
final EnumSet<UpdateFlags> flags = EnumSet.noneOf(UpdateFlags.class);
switch (msg) {
case TITLE:
updateTitle();
flags.add(UpdateFlags.TITLE);
break;
case START:
updateBackButton(tab);
updateForwardButton(tab);
if (tab.getState() == Tab.STATE_LOADING) {
setProgressVisibility(true);
}
setSecurityMode(tab.getSecurityMode());
setPageActionVisibility(mStop.getVisibility() == View.VISIBLE);
flags.add(UpdateFlags.PROGRESS);
break;
case STOP:
updateBackButton(tab);
updateForwardButton(tab);
setProgressVisibility(false);
// Reset the title in case we haven't navigated to a new page yet.
updateTitle();
flags.add(UpdateFlags.PROGRESS);
// Reset the title in case we haven't navigated
// to a new page yet.
flags.add(UpdateFlags.TITLE);
break;
case SELECTED:
case LOAD_ERROR:
updateTitle();
flags.add(UpdateFlags.TITLE);
// Fall through.
case LOCATION_CHANGE:
// A successful location change will cause Tab to notify
@ -617,16 +470,16 @@ public class BrowserToolbar extends GeckoRelativeLayout
break;
case FAVICON:
setFavicon(tab.getFavicon());
flags.add(UpdateFlags.FAVICON);
break;
case SECURITY_CHANGE:
setSecurityMode(tab.getSecurityMode());
flags.add(UpdateFlags.SITE_IDENTITY);
break;
}
case READER_ENABLED:
setPageActionVisibility(mStop.getVisibility() == View.VISIBLE);
break;
if (!flags.isEmpty()) {
updateDisplayLayout(tab, flags);
}
}
@ -642,55 +495,16 @@ public class BrowserToolbar extends GeckoRelativeLayout
return ViewHelper.getTranslationY(this) == 0;
}
@Override
public void setNextFocusDownId(int nextId) {
super.setNextFocusDownId(nextId);
mTabs.setNextFocusDownId(nextId);
mBack.setNextFocusDownId(nextId);
mForward.setNextFocusDownId(nextId);
mFavicon.setNextFocusDownId(nextId);
mStop.setNextFocusDownId(nextId);
mSiteSecurity.setNextFocusDownId(nextId);
mPageActionLayout.setNextFocusDownId(nextId);
mUrlDisplayLayout.setNextFocusDownId(nextId);
mMenu.setNextFocusDownId(nextId);
}
@Override
public void onAnimationStart(Animation animation) {
if (animation.equals(mLockFadeIn)) {
if (mSiteSecurityVisible)
mSiteSecurity.setVisibility(View.VISIBLE);
} else if (animation.equals(mTitleSlideLeft)) {
// These two animations may be scheduled to start while the forward
// animation is occurring. If we're showing the site security icon, make
// sure it doesn't take any space during the forward transition.
mSiteSecurity.setVisibility(View.GONE);
} else if (animation.equals(mTitleSlideRight)) {
// If we're hiding the icon, make sure that we keep its padding
// in place during the forward transition
mSiteSecurity.setVisibility(View.INVISIBLE);
}
}
@Override
public void onAnimationRepeat(Animation animation) {
}
@Override
public void onAnimationEnd(Animation animation) {
if (animation.equals(mTitleSlideRight)) {
mSiteSecurity.startAnimation(mLockFadeIn);
}
}
private boolean dismissSiteIdentityPopup() {
if (mSiteIdentityPopup != null && mSiteIdentityPopup.isShowing()) {
mSiteIdentityPopup.dismiss();
return true;
}
return false;
}
private int getUrlBarEntryTranslation() {
return getWidth() - mUrlBarEntry.getRight();
}
@ -731,7 +545,7 @@ public class BrowserToolbar extends GeckoRelativeLayout
}
}
public void updateTabCountAndAnimate(int count) {
private void updateTabCountAndAnimate(int count) {
// Don't animate if the toolbar is hidden.
if (!isVisible()) {
updateTabCount(count);
@ -751,7 +565,7 @@ public class BrowserToolbar extends GeckoRelativeLayout
}
}
public void updateTabCount(int count) {
private void updateTabCount(int count) {
// If toolbar is in edit mode on a phone, this means the entry is expanded
// and the tabs button is translated offscreen. Don't trigger tabs counter
// updates until the tabs button is back on screen.
@ -773,84 +587,22 @@ public class BrowserToolbar extends GeckoRelativeLayout
mActivity.getString(R.string.one_tab));
}
public void setProgressVisibility(boolean visible) {
Log.d(LOGTAG, "setProgressVisibility: " + visible);
// The "Throbber start" and "Throbber stop" log messages in this method
// are needed by S1/S2 tests (http://mrcote.info/phonedash/#).
// See discussion in Bug 804457. Bug 805124 tracks paring these down.
if (visible) {
mFavicon.setImageResource(R.drawable.progress_spinner);
mLastFavicon = null;
// To stop the glitch caused by multiple start() calls.
if (!mSpinnerVisible) {
setPageActionVisibility(true);
mFavicon.setAnimation(mProgressSpinner);
mProgressSpinner.start();
mSpinnerVisible = true;
}
Log.i(LOGTAG, "zerdatime " + SystemClock.uptimeMillis() + " - Throbber start");
} else {
Tab selectedTab = Tabs.getInstance().getSelectedTab();
if (selectedTab != null) {
setFavicon(selectedTab.getFavicon());
}
if (mSpinnerVisible) {
setPageActionVisibility(false);
mFavicon.setAnimation(null);
mProgressSpinner.cancel();
mSpinnerVisible = false;
}
Log.i(LOGTAG, "zerdatime " + SystemClock.uptimeMillis() + " - Throbber stop");
}
}
public void setPageActionVisibility(boolean isLoading) {
// Handle the loading mode page actions
mStop.setVisibility(isLoading ? View.VISIBLE : View.GONE);
// Handle the viewing mode page actions
setSiteSecurityVisibility(mShowSiteSecurity && !isLoading);
mPageActionLayout.setVisibility(!isLoading ? View.VISIBLE : View.GONE);
// We want title to fill the whole space available for it when there are icons
// being shown on the right side of the toolbar as the icons already have some
// padding in them. This is just to avoid wasting space when icons are shown.
mTitle.setPadding(0, 0, (!isLoading ? mTitlePadding : 0), 0);
updateFocusOrder();
}
private void setSiteSecurityVisibility(final boolean visible) {
if (visible == mSiteSecurityVisible)
return;
mSiteSecurityVisible = visible;
private void updateDisplayLayout(Tab tab, EnumSet<UpdateFlags> flags) {
if (mSwitchingTabs) {
mSiteSecurity.setVisibility(visible ? View.VISIBLE : View.GONE);
return;
flags.add(UpdateFlags.DISABLE_ANIMATIONS);
}
mTitle.clearAnimation();
mSiteSecurity.clearAnimation();
mUrlDisplayLayout.updateFromTab(tab, flags);
// If any of these animations were cancelled as a result of the
// clearAnimation() calls above, we need to reset them.
mLockFadeIn.reset();
mTitleSlideLeft.reset();
mTitleSlideRight.reset();
if (mForwardAnim != null) {
long delay = mForwardAnim.getRemainingTime();
mTitleSlideRight.setStartOffset(delay);
mTitleSlideLeft.setStartOffset(delay);
} else {
mTitleSlideRight.setStartOffset(0);
mTitleSlideLeft.setStartOffset(0);
if (flags.contains(UpdateFlags.TITLE)) {
if (!isEditing()) {
mUrlEditLayout.setText(tab.getURL());
}
}
mTitle.startAnimation(visible ? mTitleSlideRight : mTitleSlideLeft);
if (flags.contains(UpdateFlags.PROGRESS)) {
updateFocusOrder();
}
}
private void updateFocusOrder() {
@ -901,95 +653,7 @@ public class BrowserToolbar extends GeckoRelativeLayout
}
public void setTitle(CharSequence title) {
mTitle.setText(title);
setContentDescription(title != null ? title : mTitle.getHint());
}
// Sets the toolbar title according to the selected tab, obeying the mShowUrl preference.
private void updateTitle() {
final Tab tab = Tabs.getInstance().getSelectedTab();
// Keep the title unchanged if there's no selected tab, or if the tab is entering reader mode.
if (tab == null || tab.isEnteringReaderMode()) {
return;
}
final String url = tab.getURL();
if (!isEditing()) {
mUrlEditLayout.setText(url);
}
// Setting a null title will ensure we just see the "Enter Search or Address" placeholder text.
if (AboutPages.isTitlelessAboutPage(url)) {
setTitle(null);
return;
}
// Show the about:blocked page title in red, regardless of prefs
if (tab.getErrorType() == Tab.ErrorType.BLOCKED) {
String title = tab.getDisplayTitle();
SpannableStringBuilder builder = new SpannableStringBuilder(title);
builder.setSpan(mBlockedColor, 0, title.length(), Spannable.SPAN_INCLUSIVE_INCLUSIVE);
setTitle(builder);
return;
}
// If the pref to show the URL isn't set, just use the tab's display title.
if (!mShowUrl || url == null) {
setTitle(tab.getDisplayTitle());
return;
}
CharSequence title = url;
if (mTrimURLs) {
title = StringUtils.stripCommonSubdomains(StringUtils.stripScheme(url));
}
String baseDomain = tab.getBaseDomain();
if (!TextUtils.isEmpty(baseDomain)) {
SpannableStringBuilder builder = new SpannableStringBuilder(title);
int index = title.toString().indexOf(baseDomain);
if (index > -1) {
builder.setSpan(mUrlColor, 0, title.length(), Spannable.SPAN_INCLUSIVE_INCLUSIVE);
builder.setSpan(tab.isPrivate() ? mPrivateDomainColor : mDomainColor, index, index+baseDomain.length(), Spannable.SPAN_INCLUSIVE_INCLUSIVE);
title = builder;
}
}
setTitle(title);
}
public void showDefaultFavicon() {
mFavicon.setImageResource(R.drawable.favicon);
mLastFavicon = null;
}
private void setFavicon(Bitmap image) {
Log.d(LOGTAG, "setFavicon(" + image + ")");
if (Tabs.getInstance().getSelectedTab().getState() == Tab.STATE_LOADING) {
return;
}
if (image == mLastFavicon) {
Log.d(LOGTAG, "Ignoring favicon set: new favicon is identical to previous favicon.");
return;
}
mLastFavicon = image; // Cache the original so we can debounce without scaling.
if (image != null) {
image = Bitmap.createScaledBitmap(image, mFaviconSize, mFaviconSize, false);
mFavicon.setImageBitmap(image);
} else {
mFavicon.setImageDrawable(null);
}
}
private void setSecurityMode(SecurityMode mode) {
mSiteSecurity.setImageLevel(mode.ordinal());
mShowSiteSecurity = (mode != SecurityMode.UNKNOWN);
setPageActionVisibility(mStop.getVisibility() == View.VISIBLE);
mUrlDisplayLayout.setTitle(title);
}
public void prepareTabsAnimation(PropertyAnimator animator, boolean tabsAreShown) {
@ -1083,8 +747,8 @@ public class BrowserToolbar extends GeckoRelativeLayout
}
private void setUrlEditLayoutVisibility(final boolean showEditLayout, PropertyAnimator animator) {
final View viewToShow = (showEditLayout ? mUrlEditLayout : mUrlDisplayContainer);
final View viewToHide = (showEditLayout ? mUrlDisplayContainer : mUrlEditLayout);
final View viewToShow = (showEditLayout ? mUrlEditLayout : mUrlDisplayLayout);
final View viewToHide = (showEditLayout ? mUrlDisplayLayout : mUrlEditLayout);
if (showEditLayout) {
mUrlEditLayout.prepareShowAnimation(animator);
@ -1163,7 +827,7 @@ public class BrowserToolbar extends GeckoRelativeLayout
}
}
public void setIsEditing(boolean isEditing) {
private void setIsEditing(boolean isEditing) {
mIsEditing = isEditing;
mUrlEditLayout.setEnabled(isEditing);
}
@ -1225,9 +889,7 @@ public class BrowserToolbar extends GeckoRelativeLayout
// Highlight the toolbar from the start of the animation.
setSelected(true);
// Hide page actions/stop buttons immediately
ViewHelper.setAlpha(mPageActionLayout, 0);
ViewHelper.setAlpha(mStop, 0);
mUrlDisplayLayout.prepareStartEditingAnimation();
// Slide the right side elements of the toolbar
@ -1376,16 +1038,7 @@ public class BrowserToolbar extends GeckoRelativeLayout
}
PropertyAnimator buttonsAnimator = new PropertyAnimator(300);
// Fade toolbar buttons (page actions, stop) after the entry
// is schrunk back to its original size.
buttonsAnimator.attach(mPageActionLayout,
PropertyAnimator.Property.ALPHA,
1);
buttonsAnimator.attach(mStop,
PropertyAnimator.Property.ALPHA,
1);
mUrlDisplayLayout.prepareStopEditingAnimation(buttonsAnimator);
buttonsAnimator.start();
mAnimatingEntry = false;
@ -1435,17 +1088,18 @@ public class BrowserToolbar extends GeckoRelativeLayout
}
// We want the forward button to show immediately when switching tabs
mForwardAnim = new PropertyAnimator(mSwitchingTabs ? 10 : FORWARD_ANIMATION_DURATION);
final PropertyAnimator forwardAnim =
new PropertyAnimator(mSwitchingTabs ? 10 : FORWARD_ANIMATION_DURATION);
final int width = mForward.getWidth() / 2;
mForwardAnim.addPropertyAnimationListener(new PropertyAnimator.PropertyAnimationListener() {
forwardAnim.addPropertyAnimationListener(new PropertyAnimator.PropertyAnimationListener() {
@Override
public void onPropertyAnimationStart() {
if (!showing) {
// Set the margin before the transition when hiding the forward button. We
// have to do this so that the favicon isn't clipped during the transition
MarginLayoutParams layoutParams =
(MarginLayoutParams) mUrlDisplayContainer.getLayoutParams();
(MarginLayoutParams) mUrlDisplayLayout.getLayoutParams();
layoutParams.leftMargin = 0;
// Do the same on the URL edit container
@ -1463,28 +1117,25 @@ public class BrowserToolbar extends GeckoRelativeLayout
public void onPropertyAnimationEnd() {
if (showing) {
MarginLayoutParams layoutParams =
(MarginLayoutParams) mUrlDisplayContainer.getLayoutParams();
(MarginLayoutParams) mUrlDisplayLayout.getLayoutParams();
layoutParams.leftMargin = mUrlBarViewOffset;
layoutParams = (MarginLayoutParams) mUrlEditLayout.getLayoutParams();
layoutParams.leftMargin = mUrlBarViewOffset;
ViewHelper.setTranslationX(mTitle, 0);
ViewHelper.setTranslationX(mFavicon, 0);
ViewHelper.setTranslationX(mSiteSecurity, 0);
}
mUrlDisplayLayout.finishForwardAnimation();
MarginLayoutParams layoutParams = (MarginLayoutParams) mForward.getLayoutParams();
layoutParams.leftMargin = mDefaultForwardMargin + (showing ? width : 0);
ViewHelper.setTranslationX(mForward, 0);
requestLayout();
mForwardAnim = null;
}
});
prepareForwardAnimation(mForwardAnim, animation, width);
mForwardAnim.start();
prepareForwardAnimation(forwardAnim, animation, width);
forwardAnim.start();
}
public void updateForwardButton(Tab tab) {
@ -1506,22 +1157,7 @@ public class BrowserToolbar extends GeckoRelativeLayout
anim.attach(mForward,
PropertyAnimator.Property.ALPHA,
0);
anim.attach(mTitle,
PropertyAnimator.Property.TRANSLATION_X,
0);
anim.attach(mFavicon,
PropertyAnimator.Property.TRANSLATION_X,
0);
anim.attach(mSiteSecurity,
PropertyAnimator.Property.TRANSLATION_X,
0);
// We're hiding the forward button. We're going to reset the margin before
// the animation starts, so we shift these items to the right so that they don't
// appear to move initially.
ViewHelper.setTranslationX(mTitle, mUrlBarViewOffset);
ViewHelper.setTranslationX(mFavicon, mUrlBarViewOffset);
ViewHelper.setTranslationX(mSiteSecurity, mUrlBarViewOffset);
} else {
anim.attach(mForward,
PropertyAnimator.Property.TRANSLATION_X,
@ -1529,16 +1165,9 @@ public class BrowserToolbar extends GeckoRelativeLayout
anim.attach(mForward,
PropertyAnimator.Property.ALPHA,
1);
anim.attach(mTitle,
PropertyAnimator.Property.TRANSLATION_X,
mUrlBarViewOffset);
anim.attach(mFavicon,
PropertyAnimator.Property.TRANSLATION_X,
mUrlBarViewOffset);
anim.attach(mSiteSecurity,
PropertyAnimator.Property.TRANSLATION_X,
mUrlBarViewOffset);
}
mUrlDisplayLayout.prepareForwardAnimation(anim, animation, width);
}
@Override
@ -1563,17 +1192,16 @@ public class BrowserToolbar extends GeckoRelativeLayout
private void refreshState() {
Tab tab = Tabs.getInstance().getSelectedTab();
if (tab != null) {
setFavicon(tab.getFavicon());
setProgressVisibility(tab.getState() == Tab.STATE_LOADING);
setSecurityMode(tab.getSecurityMode());
setPageActionVisibility(mStop.getVisibility() == View.VISIBLE);
updateDisplayLayout(tab, EnumSet.of(UpdateFlags.FAVICON,
UpdateFlags.SITE_IDENTITY,
UpdateFlags.PROGRESS,
UpdateFlags.PRIVATE_MODE));
updateBackButton(tab);
updateForwardButton(tab);
final boolean isPrivate = tab.isPrivate();
setPrivateMode(isPrivate);
mTabs.setPrivateMode(isPrivate);
mTitle.setPrivateMode(isPrivate);
mMenu.setPrivateMode(isPrivate);
mMenuIcon.setPrivateMode(isPrivate);
mUrlEditLayout.setPrivateMode(isPrivate);
@ -1587,14 +1215,10 @@ public class BrowserToolbar extends GeckoRelativeLayout
}
public View getDoorHangerAnchor() {
return mFavicon;
return mUrlDisplayLayout.getDoorHangerAnchor();
}
public void onDestroy() {
if (mPrefObserverId != null) {
PrefsHelper.removeObserver(mPrefObserverId);
mPrefObserverId = null;
}
Tabs.unregisterOnTabsChangedListener(this);
unregisterEventListener("Reader:Click");
@ -1636,11 +1260,11 @@ public class BrowserToolbar extends GeckoRelativeLayout
return true;
}
protected void registerEventListener(String event) {
private void registerEventListener(String event) {
GeckoAppShell.getEventDispatcher().registerEventListener(event, this);
}
protected void unregisterEventListener(String event) {
private void unregisterEventListener(String event) {
GeckoAppShell.getEventDispatcher().unregisterEventListener(event, this);
}

View File

@ -37,6 +37,8 @@ public class SiteIdentityPopup extends ArrowPopup {
private static final String MIXED_CONTENT_SUPPORT_URL =
"https://support.mozilla.org/kb/how-does-content-isnt-secure-affect-my-safety";
private SiteIdentity mSiteIdentity;
private Resources mResources;
private LinearLayout mIdentity;
@ -72,27 +74,31 @@ public class SiteIdentityPopup extends ArrowPopup {
mVerifier = (TextView) mIdentity.findViewById(R.id.verifier);
}
private void setIdentity(SiteIdentity siteIdentity) {
if (siteIdentity.getSecurityMode() == SecurityMode.MIXED_CONTENT_LOADED) {
private void updateUi() {
if (!mInflated) {
init();
}
if (mSiteIdentity.getSecurityMode() == SecurityMode.MIXED_CONTENT_LOADED) {
// Hide the identity data if there isn't valid site identity data.
// Set some top padding on the popup content to create a of light blue
// between the popup arrow and the mixed content notification.
mContent.setPadding(0, (int) mResources.getDimension(R.dimen.identity_padding_top), 0, 0);
mIdentity.setVisibility(View.GONE);
} else {
mHost.setText(siteIdentity.getHost());
mHost.setText(mSiteIdentity.getHost());
String owner = siteIdentity.getOwner();
String owner = mSiteIdentity.getOwner();
// Supplemental data is optional.
final String supplemental = siteIdentity.getSupplemental();
final String supplemental = mSiteIdentity.getSupplemental();
if (!TextUtils.isEmpty(supplemental)) {
owner += "\n" + supplemental;
}
mOwner.setText(owner);
final String verifier = siteIdentity.getVerifier();
final String encrypted = siteIdentity.getEncrypted();
final String verifier = mSiteIdentity.getVerifier();
final String encrypted = mSiteIdentity.getEncrypted();
mVerifier.setText(verifier + "\n" + encrypted);
mContent.setPadding(0, 0, 0, 0);
@ -140,22 +146,31 @@ public class SiteIdentityPopup extends ArrowPopup {
/*
* @param identityData A JSONObject that holds the current tab's identity data.
*/
void updateIdentity(SiteIdentity siteIdentity) {
final SecurityMode mode = siteIdentity.getSecurityMode();
void setSiteIdentity(SiteIdentity siteIdentity) {
mSiteIdentity = siteIdentity;
}
@Override
public void show() {
if (mSiteIdentity == null) {
Log.e(LOGTAG, "Can't show site identity popup for undefined state");
return;
}
final SecurityMode mode = mSiteIdentity.getSecurityMode();
if (mode == SecurityMode.UNKNOWN) {
Log.e(LOGTAG, "Can't show site identity popup in non-identified state");
return;
}
if (!mInflated)
init();
setIdentity(siteIdentity);
updateUi();
if (mode == SecurityMode.MIXED_CONTENT_LOADED ||
mode == SecurityMode.MIXED_CONTENT_BLOCKED) {
addMixedContentNotification(mode == SecurityMode.MIXED_CONTENT_BLOCKED);
}
super.show();
}
@Override

View File

@ -0,0 +1,554 @@
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
* 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/. */
package org.mozilla.gecko.toolbar;
import org.mozilla.gecko.AboutPages;
import org.mozilla.gecko.animation.PropertyAnimator;
import org.mozilla.gecko.animation.ViewHelper;
import org.mozilla.gecko.BrowserApp;
import org.mozilla.gecko.R;
import org.mozilla.gecko.SiteIdentity;
import org.mozilla.gecko.SiteIdentity.SecurityMode;
import org.mozilla.gecko.Tab;
import org.mozilla.gecko.Tabs;
import org.mozilla.gecko.toolbar.BrowserToolbar.ForwardButtonAnimation;
import org.mozilla.gecko.util.StringUtils;
import org.mozilla.gecko.widget.GeckoLinearLayout;
import org.mozilla.gecko.widget.GeckoTextView;
import org.json.JSONObject;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.os.Build;
import android.os.SystemClock;
import android.text.style.ForegroundColorSpan;
import android.text.Spannable;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.view.animation.AlphaAnimation;
import android.view.animation.TranslateAnimation;
import android.widget.Button;
import android.widget.ImageButton;
import android.widget.LinearLayout.LayoutParams;
import java.util.Arrays;
import java.util.EnumSet;
import java.util.List;
public class ToolbarDisplayLayout extends GeckoLinearLayout
implements Animation.AnimationListener {
private static final String LOGTAG = "GeckoToolbarDisplayLayout";
enum UpdateFlags {
TITLE,
FAVICON,
PROGRESS,
SITE_IDENTITY,
PRIVATE_MODE,
DISABLE_ANIMATIONS
}
private enum UIMode {
PROGRESS,
DISPLAY
}
interface OnStopListener {
public Tab onStop();
}
interface OnTitleChangeListener {
public void onTitleChange(CharSequence title);
}
final private BrowserApp mActivity;
private UIMode mUiMode;
private GeckoTextView mTitle;
private int mTitlePadding;
private ToolbarTitlePrefs mTitlePrefs;
private OnTitleChangeListener mTitleChangeListener;
private ImageButton mSiteSecurity;
private boolean mSiteSecurityVisible;
// To de-bounce sets.
private Bitmap mLastFavicon;
private ImageButton mFavicon;
private int mFaviconSize;
private ImageButton mStop;
private OnStopListener mStopListener;
private PageActionLayout mPageActionLayout;
private Animation mProgressSpinner;
private AlphaAnimation mLockFadeIn;
private TranslateAnimation mTitleSlideLeft;
private TranslateAnimation mTitleSlideRight;
private SiteIdentityPopup mSiteIdentityPopup;
private SecurityMode mSecurityMode;
private PropertyAnimator mForwardAnim;
private final ForegroundColorSpan mUrlColor;
private final ForegroundColorSpan mBlockedColor;
private final ForegroundColorSpan mDomainColor;
private final ForegroundColorSpan mPrivateDomainColor;
public ToolbarDisplayLayout(Context context, AttributeSet attrs) {
super(context, attrs);
setOrientation(HORIZONTAL);
mActivity = (BrowserApp) context;
LayoutInflater.from(context).inflate(R.layout.toolbar_display_layout, this);
mTitle = (GeckoTextView) findViewById(R.id.url_bar_title);
mTitlePadding = mTitle.getPaddingRight();
final Resources res = getResources();
mUrlColor = new ForegroundColorSpan(res.getColor(R.color.url_bar_urltext));
mBlockedColor = new ForegroundColorSpan(res.getColor(R.color.url_bar_blockedtext));
mDomainColor = new ForegroundColorSpan(res.getColor(R.color.url_bar_domaintext));
mPrivateDomainColor = new ForegroundColorSpan(res.getColor(R.color.url_bar_domaintext_private));
mFavicon = (ImageButton) findViewById(R.id.favicon);
if (Build.VERSION.SDK_INT >= 11) {
if (Build.VERSION.SDK_INT >= 16) {
mFavicon.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO);
}
mFavicon.setLayerType(View.LAYER_TYPE_HARDWARE, null);
}
mFaviconSize = Math.round(res.getDimension(R.dimen.browser_toolbar_favicon_size));
mSiteSecurity = (ImageButton) findViewById(R.id.site_security);
mSiteSecurityVisible = (mSiteSecurity.getVisibility() == View.VISIBLE);
mSiteIdentityPopup = new SiteIdentityPopup(mActivity);
mSiteIdentityPopup.setAnchor(mSiteSecurity);
mProgressSpinner = AnimationUtils.loadAnimation(mActivity, R.anim.progress_spinner);
mStop = (ImageButton) findViewById(R.id.stop);
mPageActionLayout = (PageActionLayout) findViewById(R.id.page_action_layout);
}
@Override
public void onAttachedToWindow() {
mTitlePrefs = new ToolbarTitlePrefs();
Button.OnClickListener faviconListener = new Button.OnClickListener() {
@Override
public void onClick(View view) {
if (mSiteSecurity.getVisibility() != View.VISIBLE) {
return;
}
mSiteIdentityPopup.show();
}
};
mFavicon.setOnClickListener(faviconListener);
mSiteSecurity.setOnClickListener(faviconListener);
mStop.setOnClickListener(new Button.OnClickListener() {
@Override
public void onClick(View v) {
if (mStopListener != null) {
// Force toolbar to switch to Display mode
// immediately based on the stopped tab.
final Tab tab = mStopListener.onStop();
if (tab != null) {
updateUiMode(tab, UIMode.DISPLAY, EnumSet.noneOf(UpdateFlags.class));
}
}
}
});
float slideWidth = getResources().getDimension(R.dimen.browser_toolbar_lock_width);
LayoutParams siteSecParams = (LayoutParams) mSiteSecurity.getLayoutParams();
final float scale = getResources().getDisplayMetrics().density;
slideWidth += (siteSecParams.leftMargin + siteSecParams.rightMargin) * scale + 0.5f;
mLockFadeIn = new AlphaAnimation(0.0f, 1.0f);
mLockFadeIn.setAnimationListener(this);
mTitleSlideLeft = new TranslateAnimation(slideWidth, 0, 0, 0);
mTitleSlideLeft.setAnimationListener(this);
mTitleSlideRight = new TranslateAnimation(-slideWidth, 0, 0, 0);
mTitleSlideRight.setAnimationListener(this);
final int lockAnimDuration = 300;
mLockFadeIn.setDuration(lockAnimDuration);
mTitleSlideLeft.setDuration(lockAnimDuration);
mTitleSlideRight.setDuration(lockAnimDuration);
}
@Override
public void onDetachedFromWindow() {
mTitlePrefs.close();
}
@Override
public void onAnimationStart(Animation animation) {
if (animation.equals(mLockFadeIn)) {
if (mSiteSecurityVisible)
mSiteSecurity.setVisibility(View.VISIBLE);
} else if (animation.equals(mTitleSlideLeft)) {
// These two animations may be scheduled to start while the forward
// animation is occurring. If we're showing the site security icon, make
// sure it doesn't take any space during the forward transition.
mSiteSecurity.setVisibility(View.GONE);
} else if (animation.equals(mTitleSlideRight)) {
// If we're hiding the icon, make sure that we keep its padding
// in place during the forward transition
mSiteSecurity.setVisibility(View.INVISIBLE);
}
}
@Override
public void onAnimationRepeat(Animation animation) {
}
@Override
public void onAnimationEnd(Animation animation) {
if (animation.equals(mTitleSlideRight)) {
mSiteSecurity.startAnimation(mLockFadeIn);
}
}
@Override
public void setNextFocusDownId(int nextId) {
mFavicon.setNextFocusDownId(nextId);
mStop.setNextFocusDownId(nextId);
mSiteSecurity.setNextFocusDownId(nextId);
mPageActionLayout.setNextFocusDownId(nextId);
}
void updateFromTab(Tab tab, EnumSet<UpdateFlags> flags) {
if (flags.contains(UpdateFlags.TITLE)) {
updateTitle(tab);
}
if (flags.contains(UpdateFlags.FAVICON)) {
updateFavicon(tab);
}
if (flags.contains(UpdateFlags.SITE_IDENTITY)) {
updateSiteIdentity(tab, flags);
}
if (flags.contains(UpdateFlags.PROGRESS)) {
updateProgress(tab, flags);
}
if (flags.contains(UpdateFlags.PRIVATE_MODE)) {
mTitle.setPrivateMode(tab != null && tab.isPrivate());
}
}
void setTitle(CharSequence title) {
mTitle.setText(title);
if (mTitleChangeListener != null) {
mTitleChangeListener.onTitleChange(title);
}
}
private void updateTitle(Tab tab) {
// Keep the title unchanged if there's no selected tab,
// or if the tab is entering reader mode.
if (tab == null || tab.isEnteringReaderMode()) {
return;
}
final String url = tab.getURL();
// Setting a null title will ensure we just see the
// "Enter Search or Address" placeholder text.
if (AboutPages.isTitlelessAboutPage(url)) {
setTitle(null);
return;
}
// Show the about:blocked page title in red, regardless of prefs
if (tab.getErrorType() == Tab.ErrorType.BLOCKED) {
final String title = tab.getDisplayTitle();
final SpannableStringBuilder builder = new SpannableStringBuilder(title);
builder.setSpan(mBlockedColor, 0, title.length(), Spannable.SPAN_INCLUSIVE_INCLUSIVE);
setTitle(builder);
return;
}
// If the pref to show the URL isn't set, just use the tab's display title.
if (!mTitlePrefs.shouldShowUrl() || url == null) {
setTitle(tab.getDisplayTitle());
return;
}
CharSequence title = url;
if (mTitlePrefs.shouldTrimUrls()) {
title = StringUtils.stripCommonSubdomains(StringUtils.stripScheme(url));
}
final String baseDomain = tab.getBaseDomain();
if (!TextUtils.isEmpty(baseDomain)) {
final SpannableStringBuilder builder = new SpannableStringBuilder(title);
int index = title.toString().indexOf(baseDomain);
if (index > -1) {
builder.setSpan(mUrlColor, 0, title.length(), Spannable.SPAN_INCLUSIVE_INCLUSIVE);
builder.setSpan(tab.isPrivate() ? mPrivateDomainColor : mDomainColor,
index, index + baseDomain.length(), Spannable.SPAN_INCLUSIVE_INCLUSIVE);
title = builder;
}
}
setTitle(title);
}
private void updateFavicon(Tab tab) {
if (tab == null) {
mFavicon.setImageDrawable(null);
return;
}
if (tab.getState() == Tab.STATE_LOADING) {
return;
}
Bitmap image = tab.getFavicon();
if (image == mLastFavicon) {
Log.d(LOGTAG, "Ignoring favicon: new image is identical to previous one.");
return;
}
// Cache the original so we can debounce without scaling
mLastFavicon = image;
Log.d(LOGTAG, "updateFavicon(" + image + ")");
if (image != null) {
image = Bitmap.createScaledBitmap(image, mFaviconSize, mFaviconSize, false);
mFavicon.setImageBitmap(image);
} else {
mFavicon.setImageDrawable(null);
}
}
private void updateSiteIdentity(Tab tab, EnumSet<UpdateFlags> flags) {
final SiteIdentity siteIdentity;
if (tab == null) {
siteIdentity = null;
} else {
siteIdentity = tab.getSiteIdentity();
}
mSiteIdentityPopup.setSiteIdentity(siteIdentity);
final SecurityMode securityMode;
if (siteIdentity == null) {
securityMode = SecurityMode.UNKNOWN;
} else {
securityMode = siteIdentity.getSecurityMode();
}
if (mSecurityMode != securityMode) {
mSecurityMode = securityMode;
mSiteSecurity.setImageLevel(mSecurityMode.ordinal());
updatePageActions(flags);
}
}
private void updateProgress(Tab tab, EnumSet<UpdateFlags> flags) {
final boolean shouldShowThrobber = (tab != null &&
tab.getState() == Tab.STATE_LOADING);
updateUiMode(tab, shouldShowThrobber ? UIMode.PROGRESS : UIMode.DISPLAY, flags);
}
private void updateUiMode(Tab tab, UIMode uiMode, EnumSet<UpdateFlags> flags) {
if (mUiMode == uiMode) {
return;
}
mUiMode = uiMode;
// The "Throbber start" and "Throbber stop" log messages in this method
// are needed by S1/S2 tests (http://mrcote.info/phonedash/#).
// See discussion in Bug 804457. Bug 805124 tracks paring these down.
if (mUiMode == UIMode.PROGRESS) {
mLastFavicon = null;
mFavicon.setImageResource(R.drawable.progress_spinner);
mFavicon.setAnimation(mProgressSpinner);
mProgressSpinner.start();
Log.i(LOGTAG, "zerdatime " + SystemClock.uptimeMillis() + " - Throbber start");
} else {
updateFavicon(tab);
mFavicon.setAnimation(null);
mProgressSpinner.cancel();
Log.i(LOGTAG, "zerdatime " + SystemClock.uptimeMillis() + " - Throbber stop");
}
updatePageActions(flags);
}
private void updatePageActions(EnumSet<UpdateFlags> flags) {
final boolean isShowingProgress = (mUiMode == UIMode.PROGRESS);
mStop.setVisibility(isShowingProgress ? View.VISIBLE : View.GONE);
mPageActionLayout.setVisibility(!isShowingProgress ? View.VISIBLE : View.GONE);
boolean shouldShowSiteSecurity = (!isShowingProgress &&
mSecurityMode != SecurityMode.UNKNOWN);
setSiteSecurityVisibility(shouldShowSiteSecurity, flags);
// We want title to fill the whole space available for it when there are icons
// being shown on the right side of the toolbar as the icons already have some
// padding in them. This is just to avoid wasting space when icons are shown.
mTitle.setPadding(0, 0, (!isShowingProgress ? mTitlePadding : 0), 0);
}
private void setSiteSecurityVisibility(boolean visible, EnumSet<UpdateFlags> flags) {
if (visible == mSiteSecurityVisible) {
return;
}
mSiteSecurityVisible = visible;
if (flags.contains(UpdateFlags.DISABLE_ANIMATIONS)) {
mSiteSecurity.setVisibility(visible ? View.VISIBLE : View.GONE);
return;
}
mTitle.clearAnimation();
mSiteSecurity.clearAnimation();
// If any of these animations were cancelled as a result of the
// clearAnimation() calls above, we need to reset them.
mLockFadeIn.reset();
mTitleSlideLeft.reset();
mTitleSlideRight.reset();
if (mForwardAnim != null) {
long delay = mForwardAnim.getRemainingTime();
mTitleSlideRight.setStartOffset(delay);
mTitleSlideLeft.setStartOffset(delay);
} else {
mTitleSlideRight.setStartOffset(0);
mTitleSlideLeft.setStartOffset(0);
}
mTitle.startAnimation(visible ? mTitleSlideRight : mTitleSlideLeft);
}
List<View> getFocusOrder() {
return Arrays.asList(mSiteSecurity, mPageActionLayout, mStop);
}
void setOnStopListener(OnStopListener listener) {
mStopListener = listener;
}
void setOnTitleChangeListener(OnTitleChangeListener listener) {
mTitleChangeListener = listener;
}
View getDoorHangerAnchor() {
return mFavicon;
}
void prepareForwardAnimation(PropertyAnimator anim, ForwardButtonAnimation animation, int width) {
mForwardAnim = anim;
if (animation == ForwardButtonAnimation.HIDE) {
anim.attach(mTitle,
PropertyAnimator.Property.TRANSLATION_X,
0);
anim.attach(mFavicon,
PropertyAnimator.Property.TRANSLATION_X,
0);
anim.attach(mSiteSecurity,
PropertyAnimator.Property.TRANSLATION_X,
0);
// We're hiding the forward button. We're going to reset the margin before
// the animation starts, so we shift these items to the right so that they don't
// appear to move initially.
ViewHelper.setTranslationX(mTitle, width);
ViewHelper.setTranslationX(mFavicon, width);
ViewHelper.setTranslationX(mSiteSecurity, width);
} else {
anim.attach(mTitle,
PropertyAnimator.Property.TRANSLATION_X,
width);
anim.attach(mFavicon,
PropertyAnimator.Property.TRANSLATION_X,
width);
anim.attach(mSiteSecurity,
PropertyAnimator.Property.TRANSLATION_X,
width);
}
}
void finishForwardAnimation() {
ViewHelper.setTranslationX(mTitle, 0);
ViewHelper.setTranslationX(mFavicon, 0);
ViewHelper.setTranslationX(mSiteSecurity, 0);
mForwardAnim = null;
}
void prepareStartEditingAnimation() {
// Hide page actions/stop buttons immediately
ViewHelper.setAlpha(mPageActionLayout, 0);
ViewHelper.setAlpha(mStop, 0);
}
void prepareStopEditingAnimation(PropertyAnimator anim) {
// Fade toolbar buttons (page actions, stop) after the entry
// is schrunk back to its original size.
anim.attach(mPageActionLayout,
PropertyAnimator.Property.ALPHA,
1);
anim.attach(mStop,
PropertyAnimator.Property.ALPHA,
1);
}
boolean dismissSiteIdentityPopup() {
if (mSiteIdentityPopup != null && mSiteIdentityPopup.isShowing()) {
mSiteIdentityPopup.dismiss();
return true;
}
return false;
}
}

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