Merge fx-team to m-c

This commit is contained in:
Wes Kocher 2013-12-12 18:14:23 -08:00
commit ed870a6a06
101 changed files with 2448 additions and 648 deletions

View File

@ -18,4 +18,9 @@
# Modifying this file will now automatically clobber the buildbot machines \o/
#
Bug 946047 - Windows needs a clobber for webidl changes.
Bug 942231 needs a clobber -- JNI wrappers need to be re-generated.
and
Bug 934646 needs a clobber -- the icon resources previously copied
into $OBJDIR/mobile/android/base/res will conflict with those in
$BRANDING_DIRECTORY/res.

View File

@ -102,9 +102,9 @@ var StarUI = {
this.cancelButtonOnCommand();
break;
case KeyEvent.DOM_VK_RETURN:
if (aEvent.target.className == "expander-up" ||
aEvent.target.className == "expander-down" ||
aEvent.target.id == "editBMPanel_newFolderButton") {
if (aEvent.target.classList.contains("expander-up") ||
aEvent.target.classList.contains("expander-down") ||
aEvent.target.id == "editBMPanel_newFolderButton") {
//XXX Why is this necessary? The defaultPrevented check should
// be enough.
break;

View File

@ -2936,27 +2936,29 @@ const BrowserSearch = {
let searchBar = this.searchBar;
let placement = CustomizableUI.getPlacementOfWidget("search-container");
let focusSearchBar = () => {
searchBar = this.searchBar;
searchBar.select();
openSearchPageIfFieldIsNotActive(searchBar);
};
if (placement && placement.area == CustomizableUI.AREA_PANEL) {
PanelUI.show().then(() => {
// The panel is not constructed until the first time it is shown.
searchBar = this.searchBar;
searchBar.select();
openSearchPageIfFieldIsNotActive(searchBar);
});
return;
} else if (placement.area == CustomizableUI.AREA_NAVBAR && searchBar &&
searchBar.parentNode.classList.contains("overflowedItem")) {
let navBar = document.getElementById(CustomizableUI.AREA_NAVBAR);
navBar.overflowable.show().then(() => {
// The searchBar gets moved when the overflow panel opens.
searchBar = this.searchBar;
searchBar.select();
openSearchPageIfFieldIsNotActive(searchBar);
focusSearchBar();
});
return;
}
if (searchBar && window.fullScreen) {
FullScreen.mouseoverToggle(true);
if (placement.area == CustomizableUI.AREA_NAVBAR && searchBar &&
searchBar.parentNode.classList.contains("overflowedItem")) {
let navBar = document.getElementById(CustomizableUI.AREA_NAVBAR);
navBar.overflowable.show().then(() => {
focusSearchBar();
});
return;
}
if (searchBar) {
if (window.fullScreen)
FullScreen.mouseoverToggle(true);
searchBar.select();
}
openSearchPageIfFieldIsNotActive(searchBar);

View File

@ -17,3 +17,8 @@ browser.jar:
content/branding/identity-icons-brand.png (identity-icons-brand.png)
content/branding/identity-icons-brand@2x.png (identity-icons-brand@2x.png)
content/branding/aboutDialog.css (aboutDialog.css)
#ifdef MOZ_METRO
content/branding/metro-about.css (metro-about.css)
content/branding/metro-about-footer.png (metro-about-footer.png)
content/branding/metro-about-wordmark.png (metro-about-wordmark.png)
#endif

Binary file not shown.

After

Width:  |  Height:  |  Size: 101 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

View File

@ -0,0 +1,14 @@
/* 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/. */
#about-flyoutpanel {
background-color: #331e54;
color: white;
}
#about-policy-label:hover,
#about-policy-label:active {
background: #181327;
}

View File

@ -17,3 +17,8 @@ browser.jar:
content/branding/identity-icons-brand.png (identity-icons-brand.png)
content/branding/identity-icons-brand@2x.png (identity-icons-brand@2x.png)
content/branding/aboutDialog.css (aboutDialog.css)
#ifdef MOZ_METRO
content/branding/metro-about.css (metro-about.css)
content/branding/metro-about-footer.png (metro-about-footer.png)
content/branding/metro-about-wordmark.png (metro-about-wordmark.png)
#endif

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

View File

@ -0,0 +1,14 @@
/* 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/. */
#about-flyoutpanel {
background-color: #002147;
color: white;
}
#about-policy-label:hover,
#about-policy-label:active {
background: #0a111c;
}

View File

@ -16,3 +16,8 @@ browser.jar:
content/branding/identity-icons-brand.png (identity-icons-brand.png)
content/branding/identity-icons-brand@2x.png (identity-icons-brand@2x.png)
content/branding/aboutDialog.css (aboutDialog.css)
#ifdef MOZ_METRO
content/branding/metro-about.css (metro-about.css)
content/branding/metro-about-footer.png (metro-about-footer.png)
content/branding/metro-about-wordmark.png (metro-about-wordmark.png)
#endif

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

View File

@ -0,0 +1,14 @@
/* 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/. */
#about-flyoutpanel {
background-color: #0095dd;
color: white;
}
#about-policy-label:hover,
#about-policy-label:active {
background: #036fa4;
}

View File

@ -17,3 +17,8 @@ browser.jar:
content/branding/identity-icons-brand.png (identity-icons-brand.png)
content/branding/identity-icons-brand@2x.png (identity-icons-brand@2x.png)
content/branding/aboutDialog.css (aboutDialog.css)
#ifdef MOZ_METRO
content/branding/metro-about.css (metro-about.css)
content/branding/metro-about-footer.png (metro-about-footer.png)
content/branding/metro-about-wordmark.png (metro-about-wordmark.png)
#endif

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

View File

@ -0,0 +1,14 @@
/* 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/. */
#about-flyoutpanel {
background-color: #002147;
color: white;
}
#about-policy-label:hover,
#about-policy-label:active {
background: #0a111c;
}

View File

@ -85,6 +85,18 @@ let gTests = [
ok(!navbar.hasAttribute("overflowing"), "Should not have an overflowing toolbar.");
}
},
{
desc: "Ctrl+K should focus the search bar if it is in the navbar and not overflowing.",
run: function() {
let searchbar = document.getElementById("searchbar");
let placement = CustomizableUI.getPlacementOfWidget("search-container");
is(placement.area, CustomizableUI.AREA_NAVBAR, "Should be in nav-bar");
sendWebSearchKeyCommand();
logActiveElement();
is(document.activeElement, searchbar.textbox.inputField, "The searchbar should be focused");
},
},
];
function test() {

View File

@ -275,7 +275,7 @@ var gAdvancedPane = {
{
openDialog("chrome://browser/content/preferences/connection.xul",
"mozilla:connectionmanager",
"model=yes",
"modal=yes",
null);
},
@ -441,7 +441,7 @@ var gAdvancedPane = {
introText : bundlePreferences.getString("offlinepermissionstext") };
openDialog("chrome://browser/content/preferences/permissions.xul",
"Browser:Permissions",
"model=yes",
"modal=yes",
params);
},
@ -814,7 +814,7 @@ var gAdvancedPane = {
{
openDialog("chrome://pippki/content/certManager.xul",
"mozilla:certmanager",
"model=yes", null);
"modal=yes", null);
},
/**
@ -824,7 +824,7 @@ var gAdvancedPane = {
{
openDialog("chrome://mozapps/content/preferences/ocsp.xul",
"mozilla:crlmanager",
"model=yes", null);
"modal=yes", null);
},
/**
@ -834,7 +834,7 @@ var gAdvancedPane = {
{
openDialog("chrome://pippki/content/device_manager.xul",
"mozilla:devicemanager",
"model=yes", null);
"modal=yes", null);
}
#ifdef HAVE_SHELL_SERVICE
,

View File

@ -471,7 +471,7 @@ var gPrivacyPane = {
introText : bundlePreferences.getString("cookiepermissionstext") };
openDialog("chrome://browser/content/preferences/permissions.xul",
"Browser:Permissions",
"model=yes", params);
"modal=yes", params);
},
/**
@ -481,7 +481,7 @@ var gPrivacyPane = {
{
openDialog("chrome://browser/content/preferences/cookies.xul",
"Browser:Cookies",
"model=yes", null);
"modal=yes", null);
},
// CLEAR PRIVATE DATA
@ -500,7 +500,7 @@ var gPrivacyPane = {
showClearPrivateDataSettings: function ()
{
openDialog("chrome://browser/content/preferences/sanitize.xul",
"model=yes", null);
"modal=yes", null);
},

View File

@ -57,7 +57,7 @@ var gSecurityPane = {
openDialog("chrome://browser/content/preferences/permissions.xul",
"Browser:Permissions",
"model=yes",
"modal=yes",
params);
},
@ -111,7 +111,7 @@ var gSecurityPane = {
{
openDialog("chrome://passwordmgr/content/passwordManagerExceptions.xul",
"Toolkit:PasswordManagerExceptions",
"model=yes",
"modal=yes",
null);
},

View File

@ -86,10 +86,6 @@ const BROWSER_EVENTS = [
// The number of milliseconds in a day
const MS_PER_DAY = 1000.0 * 60.0 * 60.0 * 24.0;
#ifndef XP_WIN
#define BROKEN_WM_Z_ORDER
#endif
Cu.import("resource://gre/modules/Services.jsm", this);
Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);
Cu.import("resource://gre/modules/TelemetryTimestamps.jsm", this);
@ -104,14 +100,16 @@ XPCOMUtils.defineLazyServiceGetter(this, "gSessionStartup",
XPCOMUtils.defineLazyServiceGetter(this, "gScreenManager",
"@mozilla.org/gfx/screenmanager;1", "nsIScreenManager");
XPCOMUtils.defineLazyModuleGetter(this, "ScratchpadManager",
"resource:///modules/devtools/scratchpad-manager.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "DocShellCapabilities",
"resource:///modules/sessionstore/DocShellCapabilities.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Messenger",
"resource:///modules/sessionstore/Messenger.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PageStyle",
"resource:///modules/sessionstore/PageStyle.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "RecentWindow",
"resource:///modules/RecentWindow.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "ScratchpadManager",
"resource:///modules/devtools/scratchpad-manager.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "SessionSaver",
"resource:///modules/sessionstore/SessionSaver.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "SessionStorage",
@ -2478,8 +2476,24 @@ let SessionStoreInternal = {
this._windows[aWindow.__SSi].extData[key] = winData.extData[key];
}
}
let newClosedTabsData = winData._closedTabs || [];
if (overwriteTabs || firstWindow) {
this._windows[aWindow.__SSi]._closedTabs = winData._closedTabs || [];
// Overwrite existing closed tabs data when overwriteTabs=true
// or we're the first window to be restored.
this._windows[aWindow.__SSi]._closedTabs = newClosedTabsData;
} else if (this._max_tabs_undo > 0) {
// If we merge tabs, we also want to merge closed tabs data. We'll assume
// the restored tabs were closed more recently and append the current list
// of closed tabs to the new one...
newClosedTabsData =
newClosedTabsData.concat(this._windows[aWindow.__SSi]._closedTabs);
// ... and make sure that we don't exceed the max number of closed tabs
// we can restore.
this._windows[aWindow.__SSi]._closedTabs =
newClosedTabsData.slice(0, this._max_tabs_undo);
}
this.restoreTabs(aWindow, tabs, winData.tabs,
@ -3199,32 +3213,7 @@ let SessionStoreInternal = {
* @returns Window reference
*/
_getMostRecentBrowserWindow: function ssi_getMostRecentBrowserWindow() {
var win = Services.wm.getMostRecentWindow("navigator:browser");
if (!win)
return null;
if (!win.closed)
return win;
#ifdef BROKEN_WM_Z_ORDER
win = null;
var windowsEnum = Services.wm.getEnumerator("navigator:browser");
// this is oldest to newest, so this gets a bit ugly
while (windowsEnum.hasMoreElements()) {
let nextWin = windowsEnum.getNext();
if (!nextWin.closed)
win = nextWin;
}
return win;
#else
var windowsEnum =
Services.wm.getZOrderDOMWindowEnumerator("navigator:browser", true);
while (windowsEnum.hasMoreElements()) {
win = windowsEnum.getNext();
if (!win.closed)
return win;
}
return null;
#endif
return RecentWindow.getMostRecentBrowserWindow({ allowPopups: true });
},
/**

View File

@ -56,6 +56,7 @@ support-files =
[browser_formdata_format.js]
[browser_global_store.js]
[browser_input.js]
[browser_merge_closed_tabs.js]
[browser_pageshow.js]
[browser_pageStyle.js]
[browser_sessionStorage.js]

View File

@ -1,53 +1,38 @@
/* 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/. */
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
function test() {
/** Test for Bug 597071 **/
/**
* Bug 597071 - Closed windows should only be resurrected when there is a single
* popup window
*/
add_task(function test_close_last_nonpopup_window() {
// Purge the list of closed windows.
while (ss.getClosedWindowCount()) {
ss.forgetClosedWindow(0);
}
waitForExplicitFinish();
let oldState = ss.getWindowState(window);
// set the pref to 1 greater than it currently is so we have room for an extra
// closed window
let closedWindowCount = ss.getClosedWindowCount();
Services.prefs.setIntPref("browser.sessionstore.max_windows_undo",
closedWindowCount + 1);
let popupState = {windows: [
{tabs: [{entries: []}], isPopup: true, hidden: "toolbar"}
]};
let currentState = ss.getBrowserState();
let popupState = { windows:[
{ tabs:[ {entries:[] }], isPopup: true, hidden: "toolbar" }
] };
// set this window to be a popup.
// Set this window to be a popup.
ss.setWindowState(window, JSON.stringify(popupState), true);
// open a new non-popup window
let newWin = openDialog(location, "", "chrome,all,dialog=no", "http://example.com");
newWin.addEventListener("load", function(aEvent) {
newWin.removeEventListener("load", arguments.callee, false);
// Open a new window with a tab.
let win = yield promiseNewWindowLoaded({private: false});
let tab = win.gBrowser.addTab("http://example.com/");
yield promiseBrowserLoaded(tab.linkedBrowser);
newWin.gBrowser.addEventListener("load", function(aEvent) {
newWin.gBrowser.removeEventListener("load", arguments.callee, true);
// Make sure sessionstore sees this window.
let state = JSON.parse(ss.getBrowserState());
is(state.windows.length, 2, "sessionstore knows about this window");
newWin.gBrowser.addTab().linkedBrowser.stop();
// make sure sessionstore sees this window
let state = JSON.parse(ss.getBrowserState());
is(state.windows.length, 2, "sessionstore knows about this window");
newWin.close();
newWin.addEventListener("unload", function(aEvent) {
newWin.removeEventListener("unload", arguments.callee, false);
is(ss.getClosedWindowCount(), closedWindowCount + 1,
"increased closed window count");
Services.prefs.clearUserPref("browser.sessionstore.max_windows_undo");
ss.setBrowserState(currentState);
executeSoon(finish);
}, false);
}, true);
}, false);
}
// Closed the window and check the closed window count.
yield promiseWindowClosed(win);
is(ss.getClosedWindowCount(), 1, "correct closed window count");
// Cleanup.
ss.setWindowState(window, oldState, true);
});

View File

@ -0,0 +1,71 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* This test ensures that closed tabs are merged when restoring
* a window state without overwriting tabs.
*/
add_task(function () {
const initialState = {
windows: [{
tabs: [
{ entries: [{ url: "about:blank" }] }
],
_closedTabs: [
{ state: { entries: [{ ID: 1000, url: "about:blank" }]} },
{ state: { entries: [{ ID: 1001, url: "about:blank" }]} }
]
}]
}
const restoreState = {
windows: [{
tabs: [
{ entries: [{ url: "about:robots" }] }
],
_closedTabs: [
{ state: { entries: [{ ID: 1002, url: "about:robots" }]} },
{ state: { entries: [{ ID: 1003, url: "about:robots" }]} },
{ state: { entries: [{ ID: 1004, url: "about:robots" }]} }
]
}]
}
const maxTabsUndo = 4;
gPrefService.setIntPref("browser.sessionstore.max_tabs_undo", maxTabsUndo);
// Open a new window and restore it to an initial state.
let win = yield promiseNewWindowLoaded({private: false});
SessionStore.setWindowState(win, JSON.stringify(initialState), true);
is(SessionStore.getClosedTabCount(win), 2, "2 closed tabs after restoring initial state");
// Restore the new state but do not overwrite existing tabs (this should
// cause the closed tabs to be merged).
SessionStore.setWindowState(win, JSON.stringify(restoreState), false);
// Verify the windows closed tab data is correct.
let iClosed = initialState.windows[0]._closedTabs;
let rClosed = restoreState.windows[0]._closedTabs;
let cData = JSON.parse(SessionStore.getClosedTabData(win));
is(cData.length, Math.min(iClosed.length + rClosed.length, maxTabsUndo),
"Number of closed tabs is correct");
// When the closed tabs are merged the restored tabs are considered to be
// closed more recently.
for (let i = 0; i < cData.length; i++) {
if (i < rClosed.length) {
is(cData[i].state.entries[0].ID, rClosed[i].state.entries[0].ID,
"Closed tab entry matches");
} else {
is(cData[i].state.entries[0].ID, iClosed[i - rClosed.length].state.entries[0].ID,
"Closed tab entry matches");
}
}
// Clean up.
gPrefService.clearUserPref("browser.sessionstore.max_tabs_undo");
win.close();
});

View File

@ -412,6 +412,29 @@ function whenNewWindowLoaded(aOptions, aCallback) {
whenDelayedStartupFinished(win, () => aCallback(win));
return win;
}
function promiseNewWindowLoaded(aOptions) {
let deferred = Promise.defer();
whenNewWindowLoaded(aOptions, deferred.resolve);
return deferred.promise;
}
/**
* Chrome windows aren't closed synchronously. Provide a helper method to close
* a window and wait until we received the "domwindowclosed" notification for it.
*/
function promiseWindowClosed(win) {
let deferred = Promise.defer();
Services.obs.addObserver(function obs(subject, topic) {
if (subject == win) {
Services.obs.removeObserver(obs, topic);
deferred.resolve();
}
}, "domwindowclosed", false);
win.close();
return deferred.promise;
}
/**
* This waits for the browser-delayed-startup-finished notification of a given

View File

@ -1698,7 +1698,13 @@ Breakpoints.prototype = {
// By default, new breakpoints are always enabled. Disabled breakpoints
// are, in fact, removed from the server but preserved in the frontend,
// so that they may not be forgotten across target navigations.
this._disabled.delete(identifier);
let disabledPromise = this._disabled.get(identifier);
if (disabledPromise) {
disabledPromise.then(({ conditionalExpression: previousValue }) => {
aBreakpointClient.conditionalExpression = previousValue;
});
this._disabled.delete(identifier);
}
// Preserve information about the breakpoint's line text, to display it
// in the sources pane without requiring fetching the source (for example,

View File

@ -53,3 +53,7 @@
#body[layout=horizontal] #vertical-layout-panes-container {
display: none;
}
#body[layout=vertical] #stackframes {
visibility: hidden;
}

View File

@ -51,6 +51,7 @@ support-files =
doc_random-javascript.html
doc_recursion-stack.html
doc_scope-variable.html
doc_scope-variable-2.html
doc_script-switching-01.html
doc_script-switching-02.html
doc_step-out.html
@ -94,6 +95,7 @@ support-files =
[browser_dbg_cmd-dbg.js]
[browser_dbg_conditional-breakpoints-01.js]
[browser_dbg_conditional-breakpoints-02.js]
[browser_dbg_conditional-breakpoints-03.js]
[browser_dbg_controller-evaluate-01.js]
[browser_dbg_controller-evaluate-02.js]
[browser_dbg_debugger-statement.js]
@ -210,6 +212,8 @@ support-files =
[browser_dbg_variables-view-frozen-sealed-nonext.js]
[browser_dbg_variables-view-hide-non-enums.js]
[browser_dbg_variables-view-large-array-buffer.js]
[browser_dbg_variables-view-override-01.js]
[browser_dbg_variables-view-override-02.js]
[browser_dbg_variables-view-popup-01.js]
[browser_dbg_variables-view-popup-02.js]
[browser_dbg_variables-view-popup-03.js]

View File

@ -0,0 +1,82 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Tests that conditional breakpoint expressions survive disabled breakpoints.
*/
const TAB_URL = EXAMPLE_URL + "doc_conditional-breakpoints.html";
function test() {
let gTab, gDebuggee, gPanel, gDebugger;
let gSources, gBreakpoints, gLocation;
initDebugger(TAB_URL).then(([aTab, aDebuggee, aPanel]) => {
gTab = aTab;
gDebuggee = aDebuggee;
gPanel = aPanel;
gDebugger = gPanel.panelWin;
gSources = gDebugger.DebuggerView.Sources;
gBreakpoints = gDebugger.DebuggerController.Breakpoints;
gLocation = { url: gSources.selectedValue, line: 18 };
waitForSourceAndCaretAndScopes(gPanel, ".html", 17)
.then(addBreakpoint)
.then(setConditional)
.then(() => {
let finished = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.BREAKPOINT_REMOVED);
toggleBreakpoint();
return finished;
})
.then(() => {
let finished = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.BREAKPOINT_ADDED);
toggleBreakpoint();
return finished;
})
.then(testConditionalExpressionOnClient)
.then(() => {
let finished = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.CONDITIONAL_BREAKPOINT_POPUP_SHOWING);
openConditionalPopup();
return finished;
})
.then(testConditionalExpressionInPopup)
.then(() => resumeDebuggerThenCloseAndFinish(gPanel))
.then(null, aError => {
ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
});
gDebuggee.ermahgerd();
});
function addBreakpoint() {
return gPanel.addBreakpoint(gLocation);
}
function setConditional(aClient) {
aClient.conditionalExpression = "hello";
}
function toggleBreakpoint() {
EventUtils.sendMouseEvent({ type: "click" },
gDebugger.document.querySelector(".dbg-breakpoint-checkbox"),
gDebugger);
}
function openConditionalPopup() {
EventUtils.sendMouseEvent({ type: "click" },
gDebugger.document.querySelector(".dbg-breakpoint"),
gDebugger);
}
function testConditionalExpressionOnClient() {
return gBreakpoints._getAdded(gLocation).then(aClient => {
is(aClient.conditionalExpression, "hello", "The expression is correct (1).");
});
}
function testConditionalExpressionInPopup() {
let textbox = gDebugger.document.getElementById("conditional-breakpoint-panel-textbox");
is(textbox.value, "hello", "The expression is correct (2).")
}
}

View File

@ -51,14 +51,14 @@ function initialChecks() {
is(gVariables.getScopeAtIndex(1).target, scopeNodes[1],
"getScopeAtIndex(1) didn't return the expected scope.");
is(gVariables.getScopeForNode(scopeNodes[0]).target, scopeNodes[0],
"getScopeForNode([0]) didn't return the expected scope.");
is(gVariables.getScopeForNode(scopeNodes[1]).target, scopeNodes[1],
"getScopeForNode([1]) didn't return the expected scope.");
is(gVariables.getItemForNode(scopeNodes[0]).target, scopeNodes[0],
"getItemForNode([0]) didn't return the expected scope.");
is(gVariables.getItemForNode(scopeNodes[1]).target, scopeNodes[1],
"getItemForNode([1]) didn't return the expected scope.");
is(gVariables.getScopeForNode(scopeNodes[0]).expanded, true,
is(gVariables.getItemForNode(scopeNodes[0]).expanded, true,
"The local scope should be expanded by default.");
is(gVariables.getScopeForNode(scopeNodes[1]).expanded, false,
is(gVariables.getItemForNode(scopeNodes[1]).expanded, false,
"The global scope should not be collapsed by default.");
}

View File

@ -0,0 +1,219 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Tests that VariablesView methods responsible for styling variables
* as overridden work properly.
*/
const TAB_URL = EXAMPLE_URL + "doc_scope-variable-2.html";
function test() {
Task.spawn(function() {
let [tab, debuggee, panel] = yield initDebugger(TAB_URL);
let win = panel.panelWin;
let events = win.EVENTS;
let variables = win.DebuggerView.Variables;
// Allow this generator function to yield first.
executeSoon(() => debuggee.test());
yield waitForSourceAndCaretAndScopes(panel, ".html", 23);
let firstScope = variables.getScopeAtIndex(0);
let secondScope = variables.getScopeAtIndex(1);
let thirdScope = variables.getScopeAtIndex(2);
let globalScope = variables.getScopeAtIndex(3);
ok(firstScope, "The first scope is available.");
ok(secondScope, "The second scope is available.");
ok(thirdScope, "The third scope is available.");
ok(globalScope, "The global scope is available.");
is(firstScope.name, "Function scope [secondNest]",
"The first scope's name is correct.");
is(secondScope.name, "Function scope [firstNest]",
"The second scope's name is correct.");
is(thirdScope.name, "Function scope [test]",
"The third scope's name is correct.");
is(globalScope.name, "Global scope [Window]",
"The global scope's name is correct.");
is(firstScope.expanded, true,
"The first scope's expansion state is correct.");
is(secondScope.expanded, false,
"The second scope's expansion state is correct.");
is(thirdScope.expanded, false,
"The third scope's expansion state is correct.");
is(globalScope.expanded, false,
"The global scope's expansion state is correct.");
is(firstScope._store.size, 3,
"The first scope should have all the variables available.");
is(secondScope._store.size, 3,
"The second scope shoild have all the variables available.");
is(thirdScope._store.size, 3,
"The third scope shoild have all the variables available.");
is(globalScope._store.size, 0,
"The global scope shoild have no variables available.");
// Test getOwnerScopeForVariableOrProperty with simple variables.
let thisVar = firstScope.get("this");
let thisOwner = variables.getOwnerScopeForVariableOrProperty(thisVar);
is(thisOwner, firstScope,
"The getOwnerScopeForVariableOrProperty method works properly (1).");
let someVar1 = firstScope.get("a");
let someOwner1 = variables.getOwnerScopeForVariableOrProperty(someVar1);
is(someOwner1, firstScope,
"The getOwnerScopeForVariableOrProperty method works properly (2).");
// Test getOwnerScopeForVariableOrProperty with first-degree properties.
let argsVar1 = firstScope.get("arguments");
let fetched = waitForDebuggerEvents(panel, events.FETCHED_PROPERTIES);
argsVar1.expand();
yield fetched;
let calleeProp1 = argsVar1.get("callee");
let calleeOwner1 = variables.getOwnerScopeForVariableOrProperty(calleeProp1);
is(calleeOwner1, firstScope,
"The getOwnerScopeForVariableOrProperty method works properly (3).");
// Test getOwnerScopeForVariableOrProperty with second-degree properties.
let protoVar1 = argsVar1.get("__proto__");
let fetched = waitForDebuggerEvents(panel, events.FETCHED_PROPERTIES);
protoVar1.expand();
yield fetched;
let constrProp1 = protoVar1.get("constructor");
let constrOwner1 = variables.getOwnerScopeForVariableOrProperty(constrProp1);
is(constrOwner1, firstScope,
"The getOwnerScopeForVariableOrProperty method works properly (4).");
// Test getOwnerScopeForVariableOrProperty with a simple variable
// from non-topmost scopes.
let someVar2 = secondScope.get("a");
let someOwner2 = variables.getOwnerScopeForVariableOrProperty(someVar2);
is(someOwner2, secondScope,
"The getOwnerScopeForVariableOrProperty method works properly (5).");
let someVar3 = thirdScope.get("a");
let someOwner3 = variables.getOwnerScopeForVariableOrProperty(someVar3);
is(someOwner3, thirdScope,
"The getOwnerScopeForVariableOrProperty method works properly (6).");
// Test getOwnerScopeForVariableOrProperty with first-degree properies
// from non-topmost scopes.
let argsVar2 = secondScope.get("arguments");
let fetched = waitForDebuggerEvents(panel, events.FETCHED_PROPERTIES);
argsVar2.expand();
yield fetched;
let calleeProp2 = argsVar2.get("callee");
let calleeOwner2 = variables.getOwnerScopeForVariableOrProperty(calleeProp2);
is(calleeOwner2, secondScope,
"The getOwnerScopeForVariableOrProperty method works properly (7).");
let argsVar3 = thirdScope.get("arguments");
let fetched = waitForDebuggerEvents(panel, events.FETCHED_PROPERTIES);
argsVar3.expand();
yield fetched;
let calleeProp3 = argsVar3.get("callee");
let calleeOwner3 = variables.getOwnerScopeForVariableOrProperty(calleeProp3);
is(calleeOwner3, thirdScope,
"The getOwnerScopeForVariableOrProperty method works properly (8).");
// Test getOwnerScopeForVariableOrProperty with second-degree properties
// from non-topmost scopes.
let protoVar2 = argsVar2.get("__proto__");
let fetched = waitForDebuggerEvents(panel, events.FETCHED_PROPERTIES);
protoVar2.expand();
yield fetched;
let constrProp2 = protoVar2.get("constructor");
let constrOwner2 = variables.getOwnerScopeForVariableOrProperty(constrProp2);
is(constrOwner2, secondScope,
"The getOwnerScopeForVariableOrProperty method works properly (9).");
let protoVar3 = argsVar3.get("__proto__");
let fetched = waitForDebuggerEvents(panel, events.FETCHED_PROPERTIES);
protoVar3.expand();
yield fetched;
let constrProp3 = protoVar3.get("constructor");
let constrOwner3 = variables.getOwnerScopeForVariableOrProperty(constrProp3);
is(constrOwner3, thirdScope,
"The getOwnerScopeForVariableOrProperty method works properly (10).");
// Test getParentScopesForVariableOrProperty with simple variables.
let varOwners1 = variables.getParentScopesForVariableOrProperty(someVar1);
let varOwners2 = variables.getParentScopesForVariableOrProperty(someVar2);
let varOwners3 = variables.getParentScopesForVariableOrProperty(someVar3);
is(varOwners1.length, 0,
"There should be no owner scopes for the first variable.");
is(varOwners2.length, 1,
"There should be one owner scope for the second variable.");
is(varOwners2[0], firstScope,
"The only owner scope for the second variable is correct.");
is(varOwners3.length, 2,
"There should be two owner scopes for the third variable.");
is(varOwners3[0], firstScope,
"The first owner scope for the third variable is correct.");
is(varOwners3[1], secondScope,
"The second owner scope for the third variable is correct.");
// Test getParentScopesForVariableOrProperty with first-degree properties.
let propOwners1 = variables.getParentScopesForVariableOrProperty(calleeProp1);
let propOwners2 = variables.getParentScopesForVariableOrProperty(calleeProp2);
let propOwners3 = variables.getParentScopesForVariableOrProperty(calleeProp3);
is(propOwners1.length, 0,
"There should be no owner scopes for the first property.");
is(propOwners2.length, 1,
"There should be one owner scope for the second property.");
is(propOwners2[0], firstScope,
"The only owner scope for the second property is correct.");
is(propOwners3.length, 2,
"There should be two owner scopes for the third property.");
is(propOwners3[0], firstScope,
"The first owner scope for the third property is correct.");
is(propOwners3[1], secondScope,
"The second owner scope for the third property is correct.");
// Test getParentScopesForVariableOrProperty with second-degree properties.
let secPropOwners1 = variables.getParentScopesForVariableOrProperty(constrProp1);
let secPropOwners2 = variables.getParentScopesForVariableOrProperty(constrProp2);
let secPropOwners3 = variables.getParentScopesForVariableOrProperty(constrProp3);
is(secPropOwners1.length, 0,
"There should be no owner scopes for the first inner property.");
is(secPropOwners2.length, 1,
"There should be one owner scope for the second inner property.");
is(secPropOwners2[0], firstScope,
"The only owner scope for the second inner property is correct.");
is(secPropOwners3.length, 2,
"There should be two owner scopes for the third inner property.");
is(secPropOwners3[0], firstScope,
"The first owner scope for the third inner property is correct.");
is(secPropOwners3[1], secondScope,
"The second owner scope for the third inner property is correct.");
yield resumeDebuggerThenCloseAndFinish(panel);
});
}

View File

@ -0,0 +1,54 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Tests that overridden variables in the VariablesView are styled properly.
*/
const TAB_URL = EXAMPLE_URL + "doc_scope-variable-2.html";
function test() {
Task.spawn(function() {
let [tab, debuggee, panel] = yield initDebugger(TAB_URL);
let win = panel.panelWin;
let events = win.EVENTS;
let variables = win.DebuggerView.Variables;
// Allow this generator function to yield first.
executeSoon(() => debuggee.test());
yield waitForSourceAndCaretAndScopes(panel, ".html", 23);
// Wait for the hierarchy to be committed by the VariablesViewController.
let committed = promise.defer();
variables.oncommit = committed.resolve;
yield committed.promise;
let firstScope = variables.getScopeAtIndex(0);
let secondScope = variables.getScopeAtIndex(1);
let thirdScope = variables.getScopeAtIndex(2);
let someVar1 = firstScope.get("a");
let someVar2 = secondScope.get("a");
let someVar3 = thirdScope.get("a");
let argsVar1 = firstScope.get("arguments");
let argsVar2 = secondScope.get("arguments");
let argsVar3 = thirdScope.get("arguments");
is(someVar1.target.hasAttribute("overridden"), false,
"The first 'a' variable should not be marked as being overridden.");
is(someVar2.target.hasAttribute("overridden"), true,
"The second 'a' variable should be marked as being overridden.");
is(someVar3.target.hasAttribute("overridden"), true,
"The third 'a' variable should be marked as being overridden.");
is(argsVar1.target.hasAttribute("overridden"), false,
"The first 'arguments' variable should not be marked as being overridden.");
is(argsVar2.target.hasAttribute("overridden"), true,
"The second 'arguments' variable should be marked as being overridden.");
is(argsVar3.target.hasAttribute("overridden"), true,
"The third 'arguments' variable should be marked as being overridden.");
yield resumeDebuggerThenCloseAndFinish(panel);
});
}

View File

@ -23,6 +23,9 @@ function test() {
gSources = gDebugger.DebuggerView.Sources;
gVariables = gDebugger.DebuggerView.Variables;
// Always expand all items between pauses except 'window' variables.
gVariables.commitHierarchyIgnoredItems = Object.create(null, { window: { value: true } });
waitForSourceShown(gPanel, ".html")
.then(addBreakpoint)
.then(() => ensureThreadClientState(gPanel, "resumed"))

View File

@ -24,7 +24,7 @@ function test() {
gSources = gDebugger.DebuggerView.Sources;
gVariables = gDebugger.DebuggerView.Variables;
// Always expand all scopes between pauses.
// Always expand all items between pauses.
gVariables.commitHierarchyIgnoredItems = Object.create(null);
waitForSourceShown(gPanel, ".html")

View File

@ -0,0 +1,30 @@
<!-- Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ -->
<!doctype html>
<html>
<head>
<meta charset="utf-8"/>
<title>Debugger test page</title>
</head>
<body>
<script type="text/javascript">
function test() {
var a = "first scope";
firstNest();
function firstNest() {
var a = "second scope";
secondNest();
function secondNest() {
var a = "third scope";
debugger;
}
}
}
</script>
</body>
</html>

View File

@ -1173,10 +1173,11 @@ MarkupContainer.prototype = {
this.node.getImageData(IMAGE_PREVIEW_MAX_DIM).then(data => {
if (data) {
data.data.string().then(str => {
let res = {data: str, size: data.size};
// Resolving the data promise and, to always keep tooltipData.data
// as a promise, create a new one that resolves immediately
def.resolve(str, data.size);
this.tooltipData.data = promise.resolve(str, data.size);
def.resolve(res);
this.tooltipData.data = promise.resolve(res);
});
}
});
@ -1186,7 +1187,7 @@ MarkupContainer.prototype = {
_buildTooltipContent: function(target, tooltip) {
if (this.tooltipData && target === this.tooltipData.target) {
this.tooltipData.data.then((data, size) => {
this.tooltipData.data.then(({data, size}) => {
tooltip.setImageContent(data, size);
});
return true;

View File

@ -16,10 +16,10 @@ const PAGE_CONTENT = [
].join("\n");
const TEST_NODES = [
"img.local",
"img.data",
"img.remote",
".canvas"
{selector: "img.local", size: "192 x 192"},
{selector: "img.data", size: "64 x 64"},
{selector: "img.remote", size: "22 x 23"},
{selector: ".canvas", size: "600 x 600"}
];
function test() {
@ -77,8 +77,8 @@ function testImageTooltip(index) {
return endTests();
}
let node = contentDoc.querySelector(TEST_NODES[index]);
ok(node, "We have the [" + TEST_NODES[index] + "] image node to test for tooltip");
let node = contentDoc.querySelector(TEST_NODES[index].selector);
ok(node, "We have the [" + TEST_NODES[index].selector + "] image node to test for tooltip");
let isImg = node.tagName.toLowerCase() === "img";
let container = getContainerForRawNode(markup, node);
@ -90,10 +90,14 @@ function testImageTooltip(index) {
assertTooltipShownOn(target, () => {
let images = markup.tooltip.panel.getElementsByTagName("image");
is(images.length, 1, "Tooltip for [" + TEST_NODES[index] + "] contains an image");
is(images.length, 1,
"Tooltip for [" + TEST_NODES[index].selector + "] contains an image");
let label = markup.tooltip.panel.querySelector(".devtools-tooltip-caption");
is(label.textContent, TEST_NODES[index].size,
"Tooltip label for [" + TEST_NODES[index].selector + "] displays the right image size")
markup.tooltip.hide();
testImageTooltip(index + 1);
});
}

View File

@ -518,30 +518,33 @@ Tooltip.prototype = {
}
vbox.appendChild(image);
// Temporary label during image load
// Dimension label
let label = this.doc.createElement("label");
label.classList.add("devtools-tooltip-caption");
label.classList.add("theme-comment");
label.textContent = l10n.strings.GetStringFromName("previewTooltip.image.brokenImage");
if (options.naturalWidth && options.naturalHeight) {
label.textContent = this._getImageDimensionLabel(options.naturalWidth,
options.naturalHeight);
this.setSize(vbox.width, vbox.height);
} else {
// If no dimensions were provided, load the image to get them
label.textContent = l10n.strings.GetStringFromName("previewTooltip.image.brokenImage");
let imgObj = new this.doc.defaultView.Image();
imgObj.src = imageUrl;
imgObj.onload = () => {
imgObj.onload = null;
label.textContent = this._getImageDimensionLabel(imgObj.naturalWidth,
imgObj.naturalHeight);
this.setSize(vbox.width, vbox.height);
}
}
vbox.appendChild(label);
this.content = vbox;
// Load the image to get dimensions and display it when done
let imgObj = new this.doc.defaultView.Image();
imgObj.src = imageUrl;
imgObj.onload = () => {
imgObj.onload = null;
// Display dimensions
let w = options.naturalWidth || imgObj.naturalWidth;
let h = options.naturalHeight || imgObj.naturalHeight;
label.textContent = w + " x " + h;
this.setSize(vbox.width, vbox.height);
}
},
_getImageDimensionLabel: (w, h) => w + " x " + h,
/**
* Exactly the same as the `image` function but takes a css background image
* value instead : url(....)

View File

@ -16,6 +16,7 @@ const LAZY_APPEND_BATCH = 100; // nodes
const PAGE_SIZE_SCROLL_HEIGHT_RATIO = 100;
const PAGE_SIZE_MAX_JUMPS = 30;
const SEARCH_ACTION_MAX_DELAY = 300; // ms
const ITEM_FLASH_DURATION = 300 // ms
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
@ -117,6 +118,9 @@ VariablesView.prototype = {
/**
* Adds a scope to contain any inspected variables.
*
* This new scope will be considered the parent of any other scope
* added afterwards.
*
* @param string aName
* The scope's name (e.g. "Local", "Global" etc.).
* @return Scope
@ -131,6 +135,7 @@ VariablesView.prototype = {
this._itemsByElement.set(scope._target, scope);
this._currHierarchy.set(aName, scope);
scope.header = !!aName;
return scope;
},
@ -604,23 +609,6 @@ VariablesView.prototype = {
return this._store[aIndex];
},
/**
* Searches for the scope in this container displayed by the specified node.
*
* @param nsIDOMNode aNode
* The node to search for.
* @return Scope
* The matched scope, or null if nothing is found.
*/
getScopeForNode: function(aNode) {
let item = this._itemsByElement.get(aNode);
// Match only Scopes, not Variables or Properties.
if (item && !(item instanceof Variable)) {
return item;
}
return null;
},
/**
* Recursively searches this container for the scope, variable or property
* displayed by the specified node.
@ -634,6 +622,43 @@ VariablesView.prototype = {
return this._itemsByElement.get(aNode);
},
/**
* Gets the scope owning a Variable or Property.
*
* @param Variable | Property
* The variable or property to retrieven the owner scope for.
* @return Scope
* The owner scope.
*/
getOwnerScopeForVariableOrProperty: function(aItem) {
if (!aItem) {
return null;
}
// If this is a Scope, return it.
if (!(aItem instanceof Variable)) {
return aItem;
}
// If this is a Variable or Property, find its owner scope.
if (aItem instanceof Variable && aItem.ownerView) {
return this.getOwnerScopeForVariableOrProperty(aItem.ownerView);
}
return null;
},
/**
* Gets the parent scopes for a specified Variable or Property.
* The returned list will not include the owner scope.
*
* @param Variable | Property
* The variable or property for which to find the parent scopes.
* @return array
* A list of parent Scopes.
*/
getParentScopesForVariableOrProperty: function(aItem) {
let scope = this.getOwnerScopeForVariableOrProperty(aItem);
return this._store.slice(0, Math.max(this._store.indexOf(scope), 0));
},
/**
* Gets the currently focused scope, variable or property in this view.
*
@ -888,6 +913,7 @@ VariablesView.prototype = {
label.className = "variables-view-empty-notice";
label.setAttribute("value", this._emptyTextValue);
this._parent.setAttribute("empty", "");
this._parent.appendChild(label);
this._emptyTextNode = label;
},
@ -900,6 +926,7 @@ VariablesView.prototype = {
return;
}
this._parent.removeAttribute("empty");
this._parent.removeChild(this._emptyTextNode);
this._emptyTextNode = null;
},
@ -976,12 +1003,15 @@ VariablesView.prototype = {
_window: null,
_store: null,
_itemsByElement: null,
_prevHierarchy: null,
_currHierarchy: null,
_enumVisible: true,
_nonEnumVisible: true,
_alignedValues: false,
_actionsFirst: false,
_parent: null,
_list: null,
_searchboxNode: null,
@ -1244,6 +1274,7 @@ Scope.prototype = {
this._variablesView._itemsByElement.set(child._target, child);
this._variablesView._currHierarchy.set(child._absoluteName, child);
child.header = !!aName;
return child;
},
@ -1294,7 +1325,9 @@ Scope.prototype = {
view._store.splice(view._store.indexOf(this), 1);
view._itemsByElement.delete(this._target);
view._currHierarchy.delete(this._nameString);
this._target.remove();
for (let variable of this._store.values()) {
variable.remove();
}
@ -1725,7 +1758,8 @@ Scope.prototype = {
_onClick: function(e) {
if (e.button != 0 ||
e.target == this._editNode ||
e.target == this._deleteNode) {
e.target == this._deleteNode ||
e.target == this._addPropertyNode) {
return;
}
this.toggle();
@ -1935,25 +1969,6 @@ Scope.prototype = {
}
},
/**
* Gets the first search results match in this scope.
* @return Variable | Property
*/
get _firstMatch() {
for (let [, variable] of this._store) {
let match;
if (variable._isMatch) {
match = variable;
} else {
match = variable._firstMatch;
}
if (match) {
return match;
}
}
return null;
},
/**
* Find the first item in the tree of visible items in this item that matches
* the predicate. Searches in visual order (the order seen by the user).
@ -2078,11 +2093,12 @@ Scope.prototype = {
switch: null,
delete: null,
new: null,
editableValueTooltip: "",
preventDisableOnChange: false,
preventDescriptorModifiers: false,
editableNameTooltip: "",
editableValueTooltip: "",
editButtonTooltip: "",
deleteButtonTooltip: "",
preventDescriptorModifiers: false,
contextMenuId: "",
separatorStr: "",
@ -2178,7 +2194,9 @@ Variable.prototype = Heritage.extend(Scope.prototype, {
this.ownerView._store.delete(this._nameString);
this._variablesView._itemsByElement.delete(this._target);
this._variablesView._currHierarchy.delete(this._absoluteName);
this._target.remove();
for (let property of this._store.values()) {
property.remove();
}
@ -2355,6 +2373,37 @@ Variable.prototype = Heritage.extend(Scope.prototype, {
this._valueLabel.setAttribute("value", this._valueString);
},
/**
* Marks this variable as overridden.
*
* @param boolean aFlag
* Whether this variable is overridden or not.
*/
setOverridden: function(aFlag) {
if (aFlag) {
this._target.setAttribute("overridden", "");
} else {
this._target.removeAttribute("overridden");
}
},
/**
* Briefly flashes this variable.
*
* @param number aDuration [optional]
* An optional flash animation duration.
*/
flash: function(aDuration = ITEM_FLASH_DURATION) {
let fadeInDelay = this._variablesView.lazyEmptyDelay + 1;
let fadeOutDelay = fadeInDelay + aDuration;
setNamedTimeout("vview-flash-in" + this._absoluteName,
fadeInDelay, () => this._target.setAttribute("changed", ""));
setNamedTimeout("vview-flash-out" + this._absoluteName,
fadeOutDelay, () => this._target.removeAttribute("changed"));
},
/**
* Initializes this variable's id, view and binds event listeners.
*
@ -2405,7 +2454,7 @@ Variable.prototype = Heritage.extend(Scope.prototype, {
let separatorLabel = this._separatorLabel = document.createElement("label");
separatorLabel.className = "plain separator";
separatorLabel.setAttribute("value", this.ownerView.separatorStr);
separatorLabel.setAttribute("value", this.ownerView.separatorStr + " ");
let valueLabel = this._valueLabel = document.createElement("label");
valueLabel.className = "plain value";
@ -2428,6 +2477,8 @@ Variable.prototype = Heritage.extend(Scope.prototype, {
separatorLabel.hidden = true;
}
// If this is a getter/setter property, create two child pseudo-properties
// called "get" and "set" that display the corresponding functions.
if (descriptor.get || descriptor.set) {
separatorLabel.hidden = true;
valueLabel.hidden = true;
@ -2480,15 +2531,16 @@ Variable.prototype = Heritage.extend(Scope.prototype, {
this._title.appendChild(deleteNode);
}
let { actionsFirst } = this._variablesView;
if (ownerView.new || actionsFirst) {
if (ownerView.new) {
let addPropertyNode = this._addPropertyNode = this.document.createElement("toolbarbutton");
addPropertyNode.className = "plain variables-view-add-property";
addPropertyNode.addEventListener("mousedown", this._onAddProperty.bind(this), false);
if (actionsFirst && VariablesView.isPrimitive(descriptor)) {
this._title.appendChild(addPropertyNode);
// Can't add properties to primitive values, hide the node in those cases.
if (VariablesView.isPrimitive(descriptor)) {
addPropertyNode.setAttribute("invisible", "");
}
this._title.appendChild(addPropertyNode);
}
if (ownerView.contextMenuId) {
@ -2554,11 +2606,12 @@ Variable.prototype = Heritage.extend(Scope.prototype, {
let labels = [
"configurable", "enumerable", "writable",
"frozen", "sealed", "extensible", "WebIDL"];
"frozen", "sealed", "extensible", "overridden", "WebIDL"];
for (let label of labels) {
for (let type of labels) {
let labelElement = this.document.createElement("label");
labelElement.setAttribute("value", label);
labelElement.className = type;
labelElement.setAttribute("value", STR.GetStringFromName(type + "Tooltip"));
tooltip.appendChild(labelElement);
}
@ -2623,17 +2676,25 @@ Variable.prototype = Heritage.extend(Scope.prototype, {
if (descriptor && "getterValue" in descriptor) {
target.setAttribute("safe-getter", "");
}
if (name == "this") {
target.setAttribute("self", "");
}
else if (name == "<exception>") {
target.setAttribute("exception", "");
target.setAttribute("pseudo-item", "");
}
else if (name == "<return>") {
target.setAttribute("return", "");
target.setAttribute("pseudo-item", "");
}
else if (name == "__proto__") {
target.setAttribute("proto", "");
target.setAttribute("pseudo-item", "");
}
if (Object.keys(descriptor).length == 0) {
target.setAttribute("pseudo-item", "");
}
},
@ -2774,8 +2835,8 @@ Variable.prototype = Heritage.extend(Scope.prototype, {
_absoluteName: "",
_initialDescriptor: null,
_separatorLabel: null,
_spacer: null,
_valueLabel: null,
_spacer: null,
_editNode: null,
_deleteNode: null,
_addPropertyNode: null,
@ -2877,69 +2938,101 @@ VariablesView.prototype.createHierarchy = function() {
* scope/variable/property hierarchies and reopen previously expanded nodes.
*/
VariablesView.prototype.commitHierarchy = function() {
let prevHierarchy = this._prevHierarchy;
let currHierarchy = this._currHierarchy;
for (let [absoluteName, currVariable] of currHierarchy) {
// Ignore variables which were already commmitted.
if (currVariable._committed) {
continue;
}
for (let [, currItem] of this._currHierarchy) {
// Avoid performing expensive operations.
if (this.commitHierarchyIgnoredItems[currVariable._nameString]) {
if (this.commitHierarchyIgnoredItems[currItem._nameString]) {
continue;
}
// Try to get the previous instance of the inspected variable to
// determine the difference in state.
let prevVariable = prevHierarchy.get(absoluteName);
let expanded = false;
let changed = false;
// If the inspected variable existed in a previous hierarchy, check if
// the displayed value (a representation of the grip) has changed and if
// it was previously expanded.
if (prevVariable) {
expanded = prevVariable._isExpanded;
// Only analyze Variables and Properties for displayed value changes.
if (currVariable instanceof Variable) {
changed = prevVariable._valueString != currVariable._valueString;
}
let overridden = this.isOverridden(currItem);
if (overridden) {
currItem.setOverridden(true);
}
// Make sure this variable is not handled in ulteror commits for the
// same hierarchy.
currVariable._committed = true;
// Re-expand the variable if not previously collapsed.
let expanded = !currItem._committed && this.wasExpanded(currItem);
if (expanded) {
currVariable.expand();
currItem.expand();
}
// This variable was either not changed or removed, no need to continue.
if (!changed) {
continue;
let changed = !currItem._committed && this.hasChanged(currItem);
if (changed) {
currItem.flash();
}
// Apply an attribute determining the flash type and duration.
// Dispatch this action after all the nodes have been drawn, so that
// the transition efects can take place.
this.window.setTimeout(function(aTarget) {
aTarget.addEventListener("transitionend", function onEvent() {
aTarget.removeEventListener("transitionend", onEvent, false);
aTarget.removeAttribute("changed");
}, false);
aTarget.setAttribute("changed", "");
}.bind(this, currVariable.target), this.lazyEmptyDelay + 1);
currItem._committed = true;
}
if (this.oncommit) {
this.oncommit(this);
}
};
// Some variables are likely to contain a very large number of properties.
// It would be a bad idea to re-expand them or perform expensive operations.
VariablesView.prototype.commitHierarchyIgnoredItems = Object.create(null, {
"window": { value: true }
VariablesView.prototype.commitHierarchyIgnoredItems = Heritage.extend(null, {
"window": true,
"this": true
});
/**
* Checks if the an item was previously expanded, if it existed in a
* previous hierarchy.
*
* @param Scope | Variable | Property aItem
* The item to verify.
* @return boolean
* Whether the item was expanded.
*/
VariablesView.prototype.wasExpanded = function(aItem) {
if (!(aItem instanceof Scope)) {
return false;
}
let prevItem = this._prevHierarchy.get(aItem._absoluteName || aItem._nameString);
return prevItem ? prevItem._isExpanded : false;
};
/**
* Checks if the an item's displayed value (a representation of the grip)
* has changed, if it existed in a previous hierarchy.
*
* @param Variable | Property aItem
* The item to verify.
* @return boolean
* Whether the item has changed.
*/
VariablesView.prototype.hasChanged = function(aItem) {
// Only analyze Variables and Properties for displayed value changes.
// Scopes are just collections of Variables and Properties and
// don't have a "value", so they can't change.
if (!(aItem instanceof Variable)) {
return false;
}
let prevItem = this._prevHierarchy.get(aItem._absoluteName);
return prevItem ? prevItem._valueString != aItem._valueString : false;
};
/**
* Checks if the an item was previously expanded, if it existed in a
* previous hierarchy.
*
* @param Scope | Variable | Property aItem
* The item to verify.
* @return boolean
* Whether the item was expanded.
*/
VariablesView.prototype.isOverridden = function(aItem) {
// Only analyze Variables for being overridden in different Scopes.
if (!(aItem instanceof Variable) || aItem instanceof Property) {
return false;
}
let currVariableName = aItem._nameString;
let parentScopes = this.getParentScopesForVariableOrProperty(aItem);
for (let otherScope of parentScopes) {
for (let [otherVariableName] of otherScope) {
if (otherVariableName == currVariableName) {
return true;
}
}
}
return false;
};
/**
* Returns true if the descriptor represents an undefined, null or
* primitive value.
@ -3247,9 +3340,6 @@ Editable.prototype = {
let input = this._input = this._variable.document.createElement("textbox");
input.className = "plain " + this.className;
input.setAttribute("value", initialString);
if (!this._variable._variablesView.alignedValues) {
input.setAttribute("flex", "1");
}
// Replace the specified label with a textbox input element.
label.parentNode.replaceChild(input, label);

View File

@ -352,13 +352,19 @@ VariablesViewController.prototype = {
aTarget.showArrow();
}
// Make sure that properties are always available on expansion.
aTarget.onexpand = () => this.expand(aTarget, aSource);
if (aSource.type == "block" || aSource.type == "function") {
// Block and function environments already contain scope arguments and
// corresponding variables as bindings.
this.populate(aTarget, aSource);
} else {
// Make sure that properties are always available on expansion.
aTarget.onexpand = () => this.populate(aTarget, aSource);
// Some variables are likely to contain a very large number of properties.
// It's a good idea to be prepared in case of an expansion.
if (aTarget.shouldPrefetch) {
aTarget.addEventListener("mouseover", aTarget.onexpand, false);
// Some variables are likely to contain a very large number of properties.
// It's a good idea to be prepared in case of an expansion.
if (aTarget.shouldPrefetch) {
aTarget.addEventListener("mouseover", aTarget.onexpand, false);
}
}
// Register all the actors that this controller now depends on.
@ -373,6 +379,8 @@ VariablesViewController.prototype = {
* Adds properties to a Scope, Variable, or Property in the view. Triggered
* when a scope is expanded or certain variables are hovered.
*
* This does not expand the target, it only populates it.
*
* @param Scope aTarget
* The Scope to be expanded.
* @param object aSource
@ -380,7 +388,7 @@ VariablesViewController.prototype = {
* @return Promise
* The promise that is resolved once the target has been expanded.
*/
expand: function(aTarget, aSource) {
populate: function(aTarget, aSource) {
// Fetch the variables only once.
if (aTarget._fetched) {
return aTarget._fetched;
@ -510,16 +518,17 @@ VariablesViewController.prototype = {
scope.locked = true; // Prevent collpasing the scope.
let variable = scope.addItem("", { enumerable: true });
let expanded;
let populated;
if (aOptions.objectActor) {
expanded = this.expand(variable, aOptions.objectActor);
populated = this.populate(variable, aOptions.objectActor);
variable.expand();
} else if (aOptions.rawObject) {
variable.populate(aOptions.rawObject, { expanded: true });
expanded = promise.resolve();
populated = promise.resolve();
}
return { variable: variable, expanded: expanded };
return { variable: variable, expanded: populated };
},
};

View File

@ -56,10 +56,15 @@
display: none;
}
.variable-or-property:not([safe-getter]) > tooltip > label[value=WebIDL],
.variable-or-property:not([non-extensible]) > tooltip > label[value=extensible],
.variable-or-property:not([frozen]) > tooltip > label[value=frozen],
.variable-or-property:not([sealed]) > tooltip > label[value=sealed] {
.variable-or-property:not([safe-getter]) > tooltip > label.WebIDL,
.variable-or-property:not([overridden]) > tooltip > label.overridden,
.variable-or-property:not([non-extensible]) > tooltip > label.extensible,
.variable-or-property:not([frozen]) > tooltip > label.frozen,
.variable-or-property:not([sealed]) > tooltip > label.sealed {
display: none;
}
.variable-or-property[pseudo-item] > tooltip {
display: none;
}
@ -73,6 +78,6 @@
display: none;
}
.variables-view-container[aligned-values] .title > [optional-visibility] {
.variables-view-container[aligned-values] [optional-visibility] {
display: none;
}

View File

@ -72,15 +72,26 @@ StyleEditorPanel.prototype = {
*
* @param {string} event
* Type of event
* @param {string} errorCode
* @param {string} code
* Error code of error to report
* @param {string} message
* Extra message to append to error message
*/
_showError: function(event, errorCode) {
let message = _(errorCode);
_showError: function(event, code, message) {
if (!this._toolbox) {
// could get an async error after we've been destroyed
return;
}
let errorMessage = _(code);
if (message) {
errorMessage += " " + message;
}
let notificationBox = this._toolbox.getNotificationBox();
let notification = notificationBox.getNotificationWithValue("styleeditor-error");
if (!notification) {
notificationBox.appendNotification(message,
notificationBox.appendNotification(errorMessage,
"styleeditor-error", "", notificationBox.PRIORITY_CRITICAL_LOW);
}
},

View File

@ -42,7 +42,7 @@ function highlightNode(aInspector, aComputedView)
is(inspector.selection.node, div, "selection matches the div element");
expandProperty(0, testComputedViewLink);
}).then(null, console.error);
});
}
function testComputedViewLink() {

View File

@ -66,7 +66,7 @@ function highlightNode()
inspector.once("inspector-updated", () => {
is(inspector.selection.node, div, "selection matches the div element");
testInlineStyle();
}).then(null, console.error);
});
}
function testInlineStyle()

View File

@ -48,7 +48,7 @@ function highlightNode()
inspector.once("inspector-updated", () => {
is(inspector.selection.node, div, "selection matches the div element");
testRuleViewLink();
}).then(null, console.error);
});
}
function testRuleViewLink() {

View File

@ -233,6 +233,23 @@ variablesCloseButtonTooltip=Click to remove
# in the variables list on a getter or setter which can be edited.
variablesEditButtonTooltip=Click to set value
# LOCALIZATION NOTE (configurable|...|Tooltip): The text that is displayed
# in the variables list on certain variables or properties as tooltips.
# Expanations of what these represent can be found at the following links:
# https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty
# https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/isExtensible
# https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/isFrozen
# https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/isSealed
# It's probably best to keep these in English.
configurableTooltip=configurable
enumerableTooltip=enumerable
writableTooltip=writable
frozenTooltip=frozen
sealedTooltip=sealed
extensibleTooltip=extensible
overriddenTooltip=overridden
WebIDLTooltip=WebIDL
# LOCALIZATION NOTE (variablesSeparatorLabel): The text that is displayed
# in the variables list as a separator between the name and value.
variablesSeparatorLabel=:

View File

@ -209,6 +209,7 @@ let Util = {
aURL == "about:blank" ||
aURL == "about:empty" ||
aURL == "about:home" ||
aURL == "about:newtab" ||
aURL == "about:start");
},

View File

@ -347,6 +347,12 @@ let WebNavigation = {
let history = docShell.QueryInterface(Ci.nsIWebNavigation).sessionHistory;
for (let i = 0; i < history.count; i++) {
let entry = this._serializeHistoryEntry(history.getEntryAtIndex(i, false));
// If someone directly navigates to one of these URLs and they switch to Desktop,
// we need to make the page load-able.
if (entry.url == "about:home" || entry.url == "about:start") {
entry.url = "about:newtab";
}
entries.push(entry);
}
let index = history.index + 1;

View File

@ -308,7 +308,7 @@ var BrowserUI = {
isStartURI: function isStartURI(aURI) {
aURI = aURI || Browser.selectedBrowser.currentURI.spec;
return aURI == kStartURI || aURI == "about:home";
return aURI == kStartURI || aURI == "about:start" || aURI == "about:home";
},
updateStartURIAttributes: function (aURI) {

View File

@ -11,7 +11,7 @@ let Cr = Components.results;
Cu.import("resource://gre/modules/PageThumbs.jsm");
// Page for which the start UI is shown
const kStartURI = "about:start";
const kStartURI = "about:newtab";
// allow panning after this timeout on pages with registered touch listeners
const kTouchTimeout = 300;

View File

@ -9,6 +9,7 @@
<?xml-stylesheet href="chrome://browser/content/browser.css" type="text/css"?>
<?xml-stylesheet href="chrome://browser/skin/cssthrobber.css" type="text/css"?>
<?xml-stylesheet href="chrome://browser/skin/tiles.css" type="text/css"?>
<?xml-stylesheet href="chrome://branding/content/metro-about.css" type="text/css"?>
<!DOCTYPE window [
<!ENTITY % globalDTD SYSTEM "chrome://global/locale/global.dtd">
@ -384,7 +385,9 @@ Desktop browser's sync prefs.
</appbar>
<autoscroller class="autoscroller" id="autoscrollerid"/>
<flyoutpanel id="about-flyoutpanel" class="flyout-narrow" headertext="&aboutHeader.title;">
<label id="about-product-label" linewrap="true" value="&aboutHeader.product.label;"/>
<label align="start">
<html:img src="chrome://branding/content/metro-about-wordmark.png" alt="&aboutHeader.product.label;"/>
</label>
<label value="&aboutHeader.company.label;" linewrap="true"/>
#expand <label id="about-version-label">__MOZ_APP_VERSION__</label>
<vbox id="updateBox">

View File

@ -8,7 +8,7 @@ const Ci = Components.interfaces;
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
let modules = {
start: {
newtab: {
uri: "chrome://browser/content/Start.xul",
privileged: true
},
@ -37,8 +37,12 @@ let modules = {
uri: "chrome://browser/content/aboutCertError.xhtml",
privileged: true
},
start: {
uri: "about:newtab",
privileged: true
},
home: {
uri: "about:start",
uri: "about:newtab",
privileged: true
},
#ifdef MOZ_CRASHREPORTER

View File

@ -73,7 +73,7 @@ function needHomepageOverride() {
}
function getHomePage() {
let url = "about:start";
let url = "about:newtab";
try {
url = Services.prefs.getComplexValue("browser.startup.homepage", Ci.nsIPrefLocalizedString).data;
} catch (e) { }

View File

@ -232,7 +232,7 @@ SessionStore.prototype = {
// Clear all data about closed tabs
for (let [ssid, win] in Iterator(this._windows))
win.closedTabs = [];
win._closedTabs = [];
if (this._loadState == STATE_RUNNING) {
// Save the purged state immediately
@ -286,7 +286,7 @@ SessionStore.prototype = {
// Assign it a unique identifier (timestamp) and create its data object
aWindow.__SSID = "window" + Date.now();
this._windows[aWindow.__SSID] = { tabs: [], selected: 0, closedTabs: [] };
this._windows[aWindow.__SSID] = { tabs: [], selected: 0, _closedTabs: [] };
// Perform additional initialization when the first window is loading
if (this._loadState == STATE_STOPPED) {
@ -374,12 +374,15 @@ SessionStore.prototype = {
// NB: The access to aBrowser.__SS_extdata throws during automation (in
// browser_msgmgr_01). See bug 888736.
let data = aBrowser.__SS_data;
if (!data) {
return; // Cannot restore an empty tab.
}
try { data.extData = aBrowser.__SS_extdata; } catch (e) { }
this._windows[aWindow.__SSID].closedTabs.unshift(data);
let length = this._windows[aWindow.__SSID].closedTabs.length;
this._windows[aWindow.__SSID]._closedTabs.unshift({ state: data });
let length = this._windows[aWindow.__SSID]._closedTabs.length;
if (length > this._maxTabsUndo)
this._windows[aWindow.__SSID].closedTabs.splice(this._maxTabsUndo, length - this._maxTabsUndo);
this._windows[aWindow.__SSID]._closedTabs.splice(this._maxTabsUndo, length - this._maxTabsUndo);
}
},
@ -577,21 +580,21 @@ SessionStore.prototype = {
if (!aWindow || !aWindow.__SSID)
return 0; // not a browser window, or not otherwise tracked by SS.
return this._windows[aWindow.__SSID].closedTabs.length;
return this._windows[aWindow.__SSID]._closedTabs.length;
},
getClosedTabData: function ss_getClosedTabData(aWindow) {
if (!aWindow.__SSID)
throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG);
return JSON.stringify(this._windows[aWindow.__SSID].closedTabs);
return JSON.stringify(this._windows[aWindow.__SSID]._closedTabs);
},
undoCloseTab: function ss_undoCloseTab(aWindow, aIndex) {
if (!aWindow.__SSID)
throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG);
let closedTabs = this._windows[aWindow.__SSID].closedTabs;
let closedTabs = this._windows[aWindow.__SSID]._closedTabs;
if (!closedTabs)
return null;
@ -604,13 +607,13 @@ SessionStore.prototype = {
let closedTab = closedTabs.splice(aIndex, 1).shift();
// create a new tab and bring to front
let tab = aWindow.Browser.addTab(closedTab.entries[closedTab.index - 1].url, true);
let tab = aWindow.Browser.addTab(closedTab.state.entries[closedTab.state.index - 1].url, true);
tab.browser.messageManager.sendAsyncMessage("WebNavigation:LoadURI", {
uri: closedTab.entries[closedTab.index - 1].url,
uri: closedTab.state.entries[closedTab.state.index - 1].url,
flags: null,
entries: closedTab.entries,
index: closedTab.index
entries: closedTab.state.entries,
index: closedTab.state.index
});
// Put back the extra data
@ -623,7 +626,7 @@ SessionStore.prototype = {
if (!aWindow.__SSID)
throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG);
let closedTabs = this._windows[aWindow.__SSID].closedTabs;
let closedTabs = this._windows[aWindow.__SSID]._closedTabs;
// default to the most-recently closed tab
aIndex = aIndex || 0;
@ -723,6 +726,10 @@ SessionStore.prototype = {
let tabs = data.windows[0].tabs;
let selected = data.windows[0].selected;
if (data.windows[0]._closedTabs)
this._windows[window.__SSID]._closedTabs = data.windows[0]._closedTabs;
if (selected > tabs.length) // Clamp the selected index if it's bogus
selected = 1;
@ -757,7 +764,7 @@ SessionStore.prototype = {
}
notifyObservers();
});
}.bind(this));
} catch (ex) {
Cu.reportError("SessionStore: Could not read from sessionstore.bak file: " + ex);
notifyObservers("fail");

View File

@ -5,6 +5,7 @@ contract @mozilla.org/network/protocol/about;1?what=empty {433d2d75-5923-49b0-85
contract @mozilla.org/network/protocol/about;1?what=firefox {433d2d75-5923-49b0-854d-f37267b03dc7}
contract @mozilla.org/network/protocol/about;1?what=rights {433d2d75-5923-49b0-854d-f37267b03dc7}
contract @mozilla.org/network/protocol/about;1?what=certerror {433d2d75-5923-49b0-854d-f37267b03dc7}
contract @mozilla.org/network/protocol/about;1?what=newtab {433d2d75-5923-49b0-854d-f37267b03dc7}
contract @mozilla.org/network/protocol/about;1?what=home {433d2d75-5923-49b0-854d-f37267b03dc7}
#ifdef MOZ_CRASHREPORTER
contract @mozilla.org/network/protocol/about;1?what=crashprompt {433d2d75-5923-49b0-854d-f37267b03dc7}

View File

@ -203,6 +203,8 @@ pref("layout.spellcheckDefault", 1);
/* extension manager and xpinstall */
// Completely disable extensions
pref("extensions.defaultProviders.enabled", false);
// Disable version checks making addons compatible by default
pref("extensions.strictCompatibility", false);
// Disable all add-on locations other than the profile
pref("extensions.enabledScopes", 1);
// Auto-disable any add-ons that are "dropped in" to the profile

View File

@ -29,12 +29,12 @@
#tabs-container {
background: @panel_dark_color@ @panel_dark_background@;
padding: 0;
-moz-padding-start: @metro_spacing_xnormal@;
-moz-padding-start: @metro_spacing_normal@;
width: 100%;
}
#tabs {
-moz-padding-start: @metro_spacing_large@;
-moz-padding-start: 0;
}
#tabs .tabs-list {
@ -46,17 +46,22 @@
overflow: auto;
}
#tabs > .tabs-scrollbox > .arrowscrollbox-scrollbox {
overflow: hidden;
#tabs[input="precise"] > .tabs-scrollbox > .scrollbutton-up {
visibility: visible !important;
}
#tabs[input="imprecise"] > .tabs-scrollbox > .scrollbutton-up {
visibility: hidden !important;
pointer-events: none;
}
#tabs[input="imprecise"] > .tabs-scrollbox > .scrollbutton-up,
#tabs[input="imprecise"] > .tabs-scrollbox > .scrollbutton-down {
visibility: collapse !important;
}
#tabs > .tabs-scrollbox > .scrollbutton-up {
list-style-image: url("images/tab-arrows.png") !important;
-moz-image-region: rect(15px 58px 63px 14px) !important;
padding-right: 13px;
}
#tabs > .tabs-scrollbox > .scrollbutton-up:hover {
-moz-image-region: rect(14px 102px 62px 58px) !important;
@ -71,6 +76,7 @@
#tabs > .tabs-scrollbox > .scrollbutton-down {
list-style-image: url("images/tab-arrows.png") !important;
-moz-image-region: rect(73px 58px 121px 14px) !important;
padding-left: 16px;
}
#tabs > .tabs-scrollbox > .scrollbutton-down:hover {
-moz-image-region: rect(72px 102px 120px 58px) !important;
@ -182,7 +188,7 @@ documenttab[selected] .documenttab-selection {
list-style-image: url(chrome://browser/skin/images/newtab-default.png);
/* Add some extra padding for a larger target */
padding: 18px 25px 30px;
padding: 18px 20px 30px 20px;
}
/* Start UI ----------------------------------------------------------------- */
@ -1167,25 +1173,34 @@ flyoutpanel > settings:first-child {
/* About flyout pane ------------------------------------------------------- */
/* Colors are defined in /browser/branding/<dir>/content/metro-about.css */
#about-flyoutpanel {
background-image:url('chrome://browser/skin/images/about-footer.png');
background-image: url('chrome://branding/content/metro-about-footer.png');
background-repeat: no-repeat;
background-attachment: fixed;
background-position: right bottom;
}
#about-product-label {
font-weight: bold;
#about-flyoutpanel .text-link {
color: inherit;
}
#about-flyoutpanel > .flyoutpanel-wrapper > .flyoutpanel-header,
#about-flyoutpanel > .flyoutpanel-wrapper > .flyoutpanel-contents {
background-color: inherit;
border: none;
}
#about-policy-label {
padding: 1.5em @metro_spacing_large@;
margin: 1em -@metro_spacing_large@;
}
#about-version-label {
margin-top: 11pt;
}
#about-policy-label {
margin-top: 24pt;
}
#currentChannel {
margin: 0;
padding: 0;

View File

@ -63,7 +63,7 @@ flyoutpanel[visible] {
border-width: 1px;
-moz-border-start-style: solid;
border-color: #c2c2c2;
padding: 40px;
padding: @metro_spacing_large@;
width: 100%;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 127 KiB

View File

@ -95,7 +95,6 @@ chrome.jar:
skin/images/aboutAddonsBackground.png (images/aboutAddonsBackground.png)
skin/images/aboutAddonsBackgroundFillSlice.png (images/aboutAddonsBackgroundFillSlice.png)
skin/images/flyout-back-button.png (images/flyout-back-button.png)
skin/images/about-footer.png (images/about-footer.png)
skin/images/arrowleft-16.png (images/arrowleft-16.png)
skin/images/arrowright-16.png (images/arrowright-16.png)
skin/images/arrowup-16.png (images/arrowup-16.png)

View File

@ -11,9 +11,11 @@ const Cu = Components.utils;
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/PrivateBrowsingUtils.jsm");
#ifndef XP_WIN
#ifdef XP_UNIX
#ifndef XP_MACOSX
#define BROKEN_WM_Z_ORDER
#endif
#endif
this.RecentWindow = {
/*

View File

@ -68,12 +68,10 @@
/* Toolbars */
.devtools-toolbar {
height: 26px;
.splitview-main > .devtools-toolbar {
background-origin: border-box;
background-clip: border-box;
border-bottom: 1px solid hsla(210,8%,5%,.65);
padding: 3px;
}
.splitview-main > toolbar:-moz-locale-dir(ltr) {
@ -84,7 +82,7 @@
border-left: 1px solid hsla(210,8%,5%,.5);
}
.devtools-toolbarbutton {
.splitview-main > .devtools-toolbarbutton {
font-size: 11px;
padding: 0 8px;
width: auto;

View File

@ -269,6 +269,11 @@
/* SideMenuWidget */
.side-menu-widget-container {
/* Hack: force hardware acceleration */
transform: translateX(0px);
}
.side-menu-widget-container[theme="dark"] {
background: url(background-noise-toolbar.png), hsl(208,11%,27%);
color: #fff;
@ -432,6 +437,11 @@
/* VariablesView */
.variables-view-container:not([empty]) {
/* Hack: force hardware acceleration */
transform: translateX(1px);
}
.variables-view-empty-notice {
color: GrayText;
padding: 2px;
@ -446,32 +456,7 @@
-moz-margin-end: 1px;
}
/* Generic traits applied to both variables and properties */
.variable-or-property {
transition: background 1s ease-in-out, color 1s ease-in-out;
}
.variable-or-property[changed] {
color: black;
transition-duration: .4s;
}
.variable-or-property > .title > .value {
-moz-box-flex: 1;
-moz-padding-start: 6px;
-moz-padding-end: 4px;
}
.variable-or-property[editable] > .title > .value {
cursor: text;
}
.variable-or-property:not([untitled]) > .variables-view-element-details {
-moz-margin-start: 10px;
}
/* Custom variables and properties traits */
/* Generic variables traits */
.variables-view-variable {
-moz-margin-start: 1px;
@ -486,26 +471,70 @@
font-weight: 600;
}
/* Generic variables *and* properties traits */
.variable-or-property:focus > .title > label {
color: inherit !important;
}
.variable-or-property > .title > .value {
-moz-box-flex: 1;
}
.variable-or-property:not([untitled]) > .variables-view-element-details {
-moz-margin-start: 10px;
}
/* Traits applied when variables or properties are changed or overridden */
.variable-or-property:not([overridden]) {
transition: background 1s ease-in-out;
}
.variable-or-property:not([overridden])[changed] {
transition-duration: .4s;
}
.variable-or-property[overridden] {
background: rgba(128,128,128,0.05);
}
.variable-or-property[overridden] .title > label {
/* Cross out the title for this variable and all child properties. */
font-style: italic;
text-decoration: line-through;
border-bottom: none !important;
color: rgba(128,128,128,0.9);
opacity: 0.7;
}
/* Traits applied when variables or properties are editable */
.variable-or-property[editable] > .title > .value {
cursor: text;
}
.variable-or-property[overridden] .title > .value {
/* Disallow editing this variable and all child properties. */
pointer-events: none;
}
/* Custom configurable/enumerable/writable or frozen/sealed/extensible
* variables and properties */
.variable-or-property[non-enumerable]:not([self]):not([exception]):not([return]):not([scope]) > .title > .name {
.variable-or-property[non-enumerable]:not([self]):not([pseudo-item]) > .title > .name {
opacity: 0.6;
}
.variable-or-property[non-configurable] > .title > .name {
.variable-or-property[non-configurable]:not([pseudo-item]) > .title > .name {
border-bottom: 1px dashed #99f;
}
.variable-or-property[non-writable] > .title > .name {
.variable-or-property[non-writable]:not([pseudo-item]) > .title > .name {
border-bottom: 1px dashed #f99;
}
.variable-or-property[safe-getter] > .title > .name {
.variable-or-property[safe-getter]:not([pseudo-item]) > .title > .name {
border-bottom: 1px dashed #8b0;
}
@ -567,15 +596,20 @@
margin: 0 2px 0 2px;
}
.variable-or-property[non-enumerable] > tooltip > label[value=enumerable],
.variable-or-property[non-configurable] > tooltip > label[value=configurable],
.variable-or-property[non-writable] > tooltip > label[value=writable],
.variable-or-property[non-extensible] > tooltip > label[value=extensible] {
.variable-or-property[non-enumerable] > tooltip > label.enumerable,
.variable-or-property[non-configurable] > tooltip > label.configurable,
.variable-or-property[non-writable] > tooltip > label.writable,
.variable-or-property[non-extensible] > tooltip > label.extensible {
color: #800;
text-decoration: line-through;
}
.variable-or-property[safe-getter] > tooltip > label[value=WebIDL] {
.variable-or-property[overridden] > tooltip > label.overridden {
-moz-padding-start: 4px;
-moz-border-start: 1px dotted #000;
}
.variable-or-property[safe-getter] > tooltip > label.WebIDL {
-moz-padding-start: 4px;
-moz-border-start: 1px dotted #000;
color: #080;
@ -610,7 +644,7 @@
}
.element-value-input {
-moz-margin-start: 4px !important;
-moz-margin-start: -2px !important;
-moz-margin-end: 2px !important;
}
@ -623,6 +657,7 @@
.element-value-input,
.element-name-input {
border: 1px solid rgba(128, 128, 128, .5) !important;
border-radius: 0;
color: inherit;
}

View File

@ -68,12 +68,10 @@
/* Toolbars */
.devtools-toolbar {
height: 26px;
.splitview-main > .devtools-toolbar {
background-origin: border-box;
background-clip: border-box;
border-bottom: 1px solid hsla(210,8%,5%,.65);
padding: 3px;
}
.splitview-main > toolbar:-moz-locale-dir(ltr) {
@ -84,7 +82,7 @@
border-left: 1px solid hsla(210,8%,5%,.5);
}
.devtools-toolbarbutton {
.splitview-main > .devtools-toolbarbutton {
font-size: 11px;
padding: 0 8px;
width: auto;

View File

@ -269,6 +269,11 @@
/* SideMenuWidget */
.side-menu-widget-container {
/* Hack: force hardware acceleration */
transform: translateX(0px);
}
.side-menu-widget-container[theme="dark"] {
background: url(background-noise-toolbar.png), hsl(208,11%,27%);
color: #fff;
@ -426,6 +431,11 @@
/* VariablesView */
.variables-view-container:not([empty]) {
/* Hack: force hardware acceleration */
transform: translateX(1px);
}
.variables-view-empty-notice {
color: GrayText;
padding: 2px;
@ -440,32 +450,7 @@
-moz-margin-end: 1px;
}
/* Generic traits applied to both variables and properties */
.variable-or-property {
transition: background 1s ease-in-out, color 1s ease-in-out;
}
.variable-or-property[changed] {
color: black;
transition-duration: .4s;
}
.variable-or-property > .title > .value {
-moz-box-flex: 1;
-moz-padding-start: 6px;
-moz-padding-end: 4px;
}
.variable-or-property[editable] > .title > .value {
cursor: text;
}
.variable-or-property:not([untitled]) > .variables-view-element-details {
-moz-margin-start: 10px;
}
/* Custom variables and properties traits */
/* Generic variables traits */
.variables-view-variable {
-moz-margin-start: 1px;
@ -480,26 +465,70 @@
font-weight: 600;
}
/* Generic variables *and* properties traits */
.variable-or-property:focus > .title > label {
color: inherit !important;
}
.variable-or-property > .title > .value {
-moz-box-flex: 1;
}
.variable-or-property:not([untitled]) > .variables-view-element-details {
-moz-margin-start: 10px;
}
/* Traits applied when variables or properties are changed or overridden */
.variable-or-property:not([overridden]) {
transition: background 1s ease-in-out;
}
.variable-or-property:not([overridden])[changed] {
transition-duration: .4s;
}
.variable-or-property[overridden] {
background: rgba(128,128,128,0.05);
}
.variable-or-property[overridden] .title > label {
/* Cross out the title for this variable and all child properties. */
font-style: italic;
text-decoration: line-through;
border-bottom: none !important;
color: rgba(128,128,128,0.9);
opacity: 0.7;
}
/* Traits applied when variables or properties are editable */
.variable-or-property[editable] > .title > .value {
cursor: text;
}
.variable-or-property[overridden] .title > .value {
/* Disallow editing this variable and all child properties. */
pointer-events: none;
}
/* Custom configurable/enumerable/writable or frozen/sealed/extensible
* variables and properties */
.variable-or-property[non-enumerable]:not([self]):not([exception]):not([return]):not([scope]) > .title > .name {
.variable-or-property[non-enumerable]:not([self]):not([pseudo-item]) > .title > .name {
opacity: 0.6;
}
.variable-or-property[non-configurable] > .title > .name {
.variable-or-property[non-configurable]:not([pseudo-item]) > .title > .name {
border-bottom: 1px dashed #99f;
}
.variable-or-property[non-writable] > .title > .name {
.variable-or-property[non-writable]:not([pseudo-item]) > .title > .name {
border-bottom: 1px dashed #f99;
}
.variable-or-property[safe-getter] > .title > .name {
.variable-or-property[safe-getter]:not([pseudo-item]) > .title > .name {
border-bottom: 1px dashed #8b0;
}
@ -561,15 +590,20 @@
margin: 0 2px 0 2px;
}
.variable-or-property[non-enumerable] > tooltip > label[value=enumerable],
.variable-or-property[non-configurable] > tooltip > label[value=configurable],
.variable-or-property[non-writable] > tooltip > label[value=writable],
.variable-or-property[non-extensible] > tooltip > label[value=extensible] {
.variable-or-property[non-enumerable] > tooltip > label.enumerable,
.variable-or-property[non-configurable] > tooltip > label.configurable,
.variable-or-property[non-writable] > tooltip > label.writable,
.variable-or-property[non-extensible] > tooltip > label.extensible {
color: #800;
text-decoration: line-through;
}
.variable-or-property[safe-getter] > tooltip > label[value=WebIDL] {
.variable-or-property[overridden] > tooltip > label.overridden {
-moz-padding-start: 4px;
-moz-border-start: 1px dotted #000;
}
.variable-or-property[safe-getter] > tooltip > label.WebIDL {
-moz-padding-start: 4px;
-moz-border-start: 1px dotted #000;
color: #080;
@ -604,7 +638,7 @@
}
.element-value-input {
-moz-margin-start: 4px !important;
-moz-margin-start: -2px !important;
-moz-margin-end: 2px !important;
}
@ -617,6 +651,7 @@
.element-value-input,
.element-name-input {
border: 1px solid rgba(128, 128, 128, .5) !important;
border-radius: 0;
color: inherit;
}

View File

@ -25,8 +25,9 @@
.devtools-horizontal-splitter {
-moz-appearance: none;
background-image: none;
border-top: 1px solid black;
border-bottom-width: 0;
background-color: transparent;
border: 1px solid black;
border-width: 1px 0 0 0;
min-height: 3px;
height: 3px;
margin-bottom: -3px;

View File

@ -52,7 +52,7 @@
}
.theme-bg-contrast,
.variable-or-property[changed] { /* contrast bg color to attract attention on a container */
.variable-or-property:not([overridden])[changed] { /* contrast bg color to attract attention on a container */
background: #a18650;
}
@ -93,7 +93,8 @@
.theme-fg-color1,
.cm-s-mozilla .cm-number,
.variable-or-property .token-number,
.variable-or-property[return] > .title > .name { /* green */
.variable-or-property[return] > .title > .name,
.variable-or-property[scope] > .title > .name { /* green */
color: #5c9966;
}
@ -103,8 +104,7 @@
.cm-s-mozilla .cm-def,
.cm-s-mozilla .cm-property,
.cm-s-mozilla .cm-qualifier,
.variables-view-variable > .title > .name,
.variable-or-property[scope] > .title > .name { /* blue */
.variables-view-variable > .title > .name { /* blue */
color: #3689b2;
}
@ -112,8 +112,7 @@
.cm-s-mozilla .cm-builtin,
.cm-s-mozilla .cm-tag,
.cm-s-mozilla .cm-header,
.variables-view-property > .title > .name,
.variable-or-property[safe-getter] > .title > .name { /* pink/lavender */
.variables-view-property > .title > .name { /* pink/lavender */
color: #a673bf;
}

View File

@ -52,7 +52,7 @@
}
.theme-bg-contrast,
.variable-or-property[changed] { /* contrast bg color to attract attention on a container */
.variable-or-property:not([overridden])[changed] { /* contrast bg color to attract attention on a container */
background: #a18650;
}
@ -92,7 +92,8 @@
.theme-fg-color1,
.cm-s-mozilla .cm-number,
.variable-or-property .token-number,
.variable-or-property[return] > .title > .name { /* green */
.variable-or-property[return] > .title > .name,
.variable-or-property[scope] > .title > .name { /* green */
color: hsl(72,100%,27%);
}
@ -102,8 +103,7 @@
.cm-s-mozilla .cm-def,
.cm-s-mozilla .cm-property,
.cm-s-mozilla .cm-qualifier,
.variables-view-variable > .title > .name,
.variable-or-property[scope] > .title > .name { /* blue */
.variables-view-variable > .title > .name { /* blue */
color: hsl(208,56%,40%);
}
@ -111,8 +111,7 @@
.cm-s-mozilla .cm-variable,
.cm-s-mozilla .cm-tag,
.cm-s-mozilla .cm-header,
.variables-view-property > .title > .name,
.variable-or-property[safe-getter] > .title > .name { /* dark blue */
.variables-view-property > .title > .name { /* dark blue */
color: hsl(208,81%,21%)
}
@ -297,4 +296,4 @@ div.CodeMirror span.eval-text {
border-bottom: 0;
}
%include toolbars.inc.css
%include toolbars.inc.css

View File

@ -39,6 +39,10 @@
outline-offset: -4px;
}
.devtools-toolbarbutton > .toolbarbutton-icon {
margin: 0;
}
.devtools-toolbarbutton:not([label]) {
min-width: 32px;
}
@ -130,7 +134,11 @@
margin: 0 3px;
min-height: 22px;
border: 1px solid hsla(210,8%,5%,.6);
%ifdef XP_MACOSX
border-radius: 20px;
%else
border-radius: 2px;
%endif
background-color: transparent;
background-image: linear-gradient(hsla(210,16%,76%,.15), hsla(210,16%,76%,.35));
padding: 3px;
@ -322,6 +330,7 @@
#toolbox-controls > toolbarbutton,
#toolbox-dock-buttons > toolbarbutton {
-moz-appearance: none;
border: none;
margin: 0 4px;
min-width: 16px;
width: 16px;
@ -372,6 +381,7 @@
.command-button {
-moz-appearance: none;
border: none;
padding: 0 8px;
margin: 0;
width: 16px;

View File

@ -841,6 +841,16 @@ menuitem.bookmark-item {
color: black;
}
#urlbar:-moz-lwtheme:hover:not([focused]):not([readonly]),
.searchbar-textbox:-moz-lwtheme:hover:not([focused]) {
background-color: rgba(255,255,255,.9);
}
#urlbar:-moz-lwtheme[focused],
.searchbar-textbox:-moz-lwtheme[focused] {
background-color: white;
}
@conditionalForwardWithUrlbar@ > #urlbar-wrapper {
padding-left: @conditionalForwardWithUrlbarWidth@px;
-moz-margin-start: -@conditionalForwardWithUrlbarWidth@px;
@ -898,11 +908,6 @@ html|*.urlbar-input:-moz-lwtheme::-moz-placeholder,
color: #777;
}
#urlbar:-moz-lwtheme[focused="true"],
.searchbar-textbox:-moz-lwtheme[focused="true"] {
background-color: white;
}
#urlbar-container {
-moz-box-align: center;
}

View File

@ -68,13 +68,10 @@
/* Toolbars */
.devtools-toolbar {
height: 26px;
.splitview-main > .devtools-toolbar {
background-origin: border-box;
background-clip: border-box;
border-top-width: 0;
border-bottom: 1px solid hsla(210,8%,5%,.65);
padding: 3px;
}
.splitview-main > toolbar:-moz-locale-dir(ltr) {
@ -85,7 +82,7 @@
border-left: 1px solid hsla(210,8%,5%,.5);
}
.devtools-toolbarbutton {
.splitview-main > .devtools-toolbarbutton {
font-size: 11px;
padding: 0 8px;
width: auto;

View File

@ -273,6 +273,11 @@
/* SideMenuWidget */
.side-menu-widget-container {
/* Hack: force hardware acceleration */
transform: translateX(0px);
}
.side-menu-widget-container[theme="dark"] {
background: url(background-noise-toolbar.png), hsl(208,11%,27%);
color: #fff;
@ -429,6 +434,11 @@
/* VariablesView */
.variables-view-container:not([empty]) {
/* Hack: force hardware acceleration */
transform: translateX(1px);
}
.variables-view-empty-notice {
color: GrayText;
padding: 2px;
@ -443,32 +453,7 @@
-moz-margin-end: 1px;
}
/* Generic traits applied to both variables and properties */
.variable-or-property {
transition: background 1s ease-in-out, color 1s ease-in-out;
}
.variable-or-property[changed] {
color: black;
transition-duration: .4s;
}
.variable-or-property > .title > .value {
-moz-box-flex: 1;
-moz-padding-start: 6px;
-moz-padding-end: 4px;
}
.variable-or-property[editable] > .title > .value {
cursor: text;
}
.variable-or-property:not([untitled]) > .variables-view-element-details {
-moz-margin-start: 10px;
}
/* Custom variables and properties traits */
/* Generic variables traits */
.variables-view-variable {
-moz-margin-start: 1px;
@ -483,26 +468,70 @@
font-weight: 600;
}
/* Generic variables *and* properties traits */
.variable-or-property:focus > .title > label {
color: inherit !important;
}
.variable-or-property > .title > .value {
-moz-box-flex: 1;
}
.variable-or-property:not([untitled]) > .variables-view-element-details {
-moz-margin-start: 10px;
}
/* Traits applied when variables or properties are changed or overridden */
.variable-or-property:not([overridden]) {
transition: background 1s ease-in-out;
}
.variable-or-property:not([overridden])[changed] {
transition-duration: .4s;
}
.variable-or-property[overridden] {
background: rgba(128,128,128,0.05);
}
.variable-or-property[overridden] .title > label {
/* Cross out the title for this variable and all child properties. */
font-style: italic;
text-decoration: line-through;
border-bottom: none !important;
color: rgba(128,128,128,0.9);
opacity: 0.7;
}
/* Traits applied when variables or properties are editable */
.variable-or-property[editable] > .title > .value {
cursor: text;
}
.variable-or-property[overridden] .title > .value {
/* Disallow editing this variable and all child properties. */
pointer-events: none;
}
/* Custom configurable/enumerable/writable or frozen/sealed/extensible
* variables and properties */
.variable-or-property[non-enumerable]:not([self]):not([exception]):not([return]):not([scope]) > .title > .name {
.variable-or-property[non-enumerable]:not([self]):not([pseudo-item]) > .title > .name {
opacity: 0.6;
}
.variable-or-property[non-configurable] > .title > .name {
.variable-or-property[non-configurable]:not([pseudo-item]) > .title > .name {
border-bottom: 1px dashed #99f;
}
.variable-or-property[non-writable] > .title > .name {
.variable-or-property[non-writable]:not([pseudo-item]) > .title > .name {
border-bottom: 1px dashed #f99;
}
.variable-or-property[safe-getter] > .title > .name {
.variable-or-property[safe-getter]:not([pseudo-item]) > .title > .name {
border-bottom: 1px dashed #8b0;
}
@ -564,15 +593,20 @@
margin: 0 2px 0 2px;
}
.variable-or-property[non-enumerable] > tooltip > label[value=enumerable],
.variable-or-property[non-configurable] > tooltip > label[value=configurable],
.variable-or-property[non-writable] > tooltip > label[value=writable],
.variable-or-property[non-extensible] > tooltip > label[value=extensible] {
.variable-or-property[non-enumerable] > tooltip > label.enumerable,
.variable-or-property[non-configurable] > tooltip > label.configurable,
.variable-or-property[non-writable] > tooltip > label.writable,
.variable-or-property[non-extensible] > tooltip > label.extensible {
color: #800;
text-decoration: line-through;
}
.variable-or-property[safe-getter] > tooltip > label[value=WebIDL] {
.variable-or-property[overridden] > tooltip > label.overridden {
-moz-padding-start: 4px;
-moz-border-start: 1px dotted #000;
}
.variable-or-property[safe-getter] > tooltip > label.WebIDL {
-moz-padding-start: 4px;
-moz-border-start: 1px dotted #000;
color: #080;
@ -607,7 +641,7 @@
}
.element-value-input {
-moz-margin-start: 4px !important;
-moz-margin-start: -2px !important;
-moz-margin-end: 2px !important;
}
@ -620,6 +654,7 @@
.element-value-input,
.element-name-input {
border: 1px solid rgba(128, 128, 128, .5) !important;
border-radius: 0;
color: inherit;
}

View File

@ -24,6 +24,10 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=938023
SimpleTest.waitForExplicitFinish();
var index = -1;
var todayDate = new Date();
var baseServeURL = "http://mochi.test:8888/tests/dom/downloads/tests/";
var baseDownloadPath = "/mnt/sdcard/downloads/";
var lastKnownCurrentBytes = 0;
function next() {
index += 1;
@ -38,16 +42,58 @@ function next() {
}
}
function checkConsistentDownloadAttributes(download) {
var href = document.getElementById("download1").getAttribute("href");
var expectedServeURL = baseServeURL + href;
var expectedDownloadPath = baseDownloadPath + "test.bin";
// bug 945323: Download path isn't honoring download attribute
todo(download.path === expectedDownloadPath,
"Download path = " + expectedDownloadPath);
// bug 948287: Accessing startTime attribute at download start fires
// NS_ERROR_UNEXPECTED in emulator
//ok(download.startTime >= todayDate,
// "Download start time should be greater than or equal to today");
// bug 945366: Accessing error attribute at download start fires
// NS_ERROR_UNEXPECTED in emulator
//is(download.error, null, "Download does not have an error");
is(download.url, expectedServeURL,
"Download URL = " + expectedServeURL);
ok(download.id !== null, "Download id is defined");
is(download.contentType, "application/octet-stream",
"Download content type is application/octet-stream");
}
function downloadChange(evt) {
var download = evt.download;
if (download.state == "succeeded") {
ok(download.totalBytes == 1024, "Download size is 1024 bytes.");
ok(download.contentType == "application/octet-stream",
"contentType is application/octet-stream.");
checkConsistentDownloadAttributes(download);
is(download.totalBytes, 1024, "Download total size is 1024 bytes");
if (download.state === "succeeded") {
is(download.currentBytes, 1024, "Download current size is 1024 bytes");
SimpleTest.finish();
} else if (download.state === "downloading") {
ok(download.currentBytes > lastKnownCurrentBytes,
"Download current size is larger than last download change event");
lastKnownCurrentBytes = download.currentBytes;
} else {
ok(false, "Unexpected download state = " + download.state);
}
}
function downloadStart(evt) {
var download = evt.download;
checkConsistentDownloadAttributes(download);
is(download.currentBytes, 0, "Download current size is zero");
is(download.state, "downloading", "Download state is downloading");
download.onstatechange = downloadChange;
}
var steps = [
// Start by setting the pref to true.
function() {
@ -61,11 +107,7 @@ var steps = [
SpecialPowers.pushPermissions([
{type: "downloads", allow: true, context: document}
], function() {
navigator.mozDownloadManager.ondownloadstart =
function(evt) {
ok(true, "Download started");
evt.download.addEventListener("statechange", downloadChange);
}
navigator.mozDownloadManager.ondownloadstart = downloadStart;
next();
});
},
@ -82,3 +124,4 @@ next();
</pre>
</body>
</html>

View File

@ -572,6 +572,11 @@ pref("media.preload.auto", 2); // preload metadata if preload=auto
pref("image.mem.decodeondraw", true);
pref("image.mem.min_discard_timeout_ms", 10000);
#ifdef NIGHTLY_BUILD
// Shumway component (SWF player) is disabled by default. Also see bug 904346.
pref("shumway.disabled", true);
#endif
// enable touch events interfaces
pref("dom.w3c_touch_events.enabled", 1);

View File

@ -1467,7 +1467,7 @@ abstract public class BrowserApp extends GeckoApp
animator.setUseHardwareLayer(false);
mBrowserToolbar.startEditing(url, animator);
showHomePagerWithAnimator(HomePager.Page.TOP_SITES, animator);
showHomePagerWithAnimator(animator);
animator.start();
}
@ -1618,16 +1618,12 @@ abstract public class BrowserApp extends GeckoApp
}
}
private void showHomePager(HomePager.Page page) {
showHomePagerWithAnimator(page, null);
}
@Override
public void onLocaleReady(final String locale) {
super.onLocaleReady(locale);
if (mHomePager != null) {
if (isHomePagerVisible()) {
// Blow it away and rebuild it with the right strings.
mHomePager.redisplay(getSupportFragmentManager());
mHomePager.redisplay(getSupportLoaderManager(), getSupportFragmentManager());
}
if (mMenu != null) {
@ -1636,6 +1632,16 @@ abstract public class BrowserApp extends GeckoApp
}
}
private void showHomePager(HomePager.Page page) {
showHomePagerWithAnimator(page, null);
}
private void showHomePagerWithAnimator(PropertyAnimator animator) {
// Passing null here means the default page will be defined
// by the HomePager's configuration.
showHomePagerWithAnimator(null, animator);
}
private void showHomePagerWithAnimator(HomePager.Page page, PropertyAnimator animator) {
if (isHomePagerVisible()) {
return;
@ -1655,7 +1661,9 @@ abstract public class BrowserApp extends GeckoApp
mHomePager = (HomePager) homePagerStub.inflate();
}
mHomePager.show(getSupportFragmentManager(), page, animator);
mHomePager.show(getSupportLoaderManager(),
getSupportFragmentManager(),
page, animator);
// Hide the web content so it cannot be focused by screen readers.
hideWebContentOnPropertyAnimationEnd(animator);

View File

@ -95,7 +95,7 @@ public class Tab {
mUserSearch = "";
mExternal = external;
mParentId = parentId;
mAboutHomePage = HomePager.Page.TOP_SITES;
mAboutHomePage = null;
mTitle = title == null ? "" : title;
mFavicon = null;
mFaviconUrl = null;
@ -155,7 +155,6 @@ public class Tab {
mAboutHomePage = page;
}
// may be null if user-entered query hasn't yet been resolved to a URI
public synchronized String getURL() {
return mUrl;
@ -660,6 +659,8 @@ public class Tab {
final String homePage = message.getString("aboutHomePage");
if (!TextUtils.isEmpty(homePage)) {
setAboutHomePage(HomePager.Page.valueOf(homePage));
} else {
setAboutHomePage(null);
}
Tabs.getInstance().notifyListeners(this, Tabs.TabEvents.LOCATION_CHANGE, oldUrl);

View File

@ -475,7 +475,7 @@ class JavaPanZoomController
case TOUCHING:
// Don't allow panning if there is an element in full-screen mode. See bug 775511.
if (mTarget.isFullScreen() || panDistance(event) < PAN_THRESHOLD) {
if ((mTarget.isFullScreen() && !mSubscroller.scrolling()) || panDistance(event) < PAN_THRESHOLD) {
return false;
}
cancelTouch();

View File

@ -0,0 +1,191 @@
/* -*- 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.home;
import org.mozilla.gecko.home.HomeConfig.PageEntry;
import org.mozilla.gecko.home.HomeConfig.PageType;
import org.mozilla.gecko.home.HomePager;
import org.mozilla.gecko.home.HomePager.Page;
import android.content.Context;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentStatePagerAdapter;
import android.view.ViewGroup;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
class HomeAdapter extends FragmentStatePagerAdapter {
private final Context mContext;
private final ArrayList<PageInfo> mPageInfos;
private final HashMap<String, Fragment> mPages;
private boolean mCanLoadHint;
private OnAddPageListener mAddPageListener;
interface OnAddPageListener {
public void onAddPage(String title);
}
public HomeAdapter(Context context, FragmentManager fm) {
super(fm);
mContext = context;
mCanLoadHint = HomeFragment.DEFAULT_CAN_LOAD_HINT;
mPageInfos = new ArrayList<PageInfo>();
mPages = new HashMap<String, Fragment>();
}
@Override
public int getCount() {
return mPageInfos.size();
}
@Override
public Fragment getItem(int position) {
PageInfo info = mPageInfos.get(position);
return Fragment.instantiate(mContext, info.getClassName(), info.getArgs());
}
@Override
public CharSequence getPageTitle(int position) {
if (mPageInfos.size() > 0) {
PageInfo info = mPageInfos.get(position);
return info.getTitle().toUpperCase();
}
return null;
}
@Override
public Object instantiateItem(ViewGroup container, int position) {
Fragment fragment = (Fragment) super.instantiateItem(container, position);
mPages.put(mPageInfos.get(position).getId(), fragment);
return fragment;
}
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
super.destroyItem(container, position, object);
mPages.remove(mPageInfos.get(position).getId());
}
public void setOnAddPageListener(OnAddPageListener listener) {
mAddPageListener = listener;
}
public int getItemPosition(Page page) {
for (int i = 0; i < mPageInfos.size(); i++) {
final Page infoPage = mPageInfos.get(i).toPage();
if (infoPage == page) {
return i;
}
}
return -1;
}
public Page getPageAtPosition(int position) {
// getPageAtPosition() might be called before HomeAdapter
// has got its initial list of PageEntries. Just bail.
if (mPageInfos.isEmpty()) {
return null;
}
PageInfo info = mPageInfos.get(position);
return info.toPage();
}
private void addPage(PageInfo info) {
mPageInfos.add(info);
if (mAddPageListener != null) {
mAddPageListener.onAddPage(info.getTitle());
}
}
public void update(List<PageEntry> pageEntries) {
mPages.clear();
mPageInfos.clear();
if (pageEntries != null) {
for (PageEntry pageEntry : pageEntries) {
final PageInfo info = new PageInfo(pageEntry);
addPage(info);
}
}
notifyDataSetChanged();
}
public boolean getCanLoadHint() {
return mCanLoadHint;
}
public void setCanLoadHint(boolean canLoadHint) {
// We cache the last hint value so that we can use it when
// creating new pages. See PageInfo.getArgs().
mCanLoadHint = canLoadHint;
// Enable/disable loading on all existing pages
for (Fragment page : mPages.values()) {
final HomeFragment homePage = (HomeFragment) page;
homePage.setCanLoadHint(canLoadHint);
}
}
private final class PageInfo {
private final String mId;
private final PageEntry mPageEntry;
PageInfo(PageEntry pageEntry) {
mId = pageEntry.getType() + "-" + pageEntry.getId();
mPageEntry = pageEntry;
}
public String getId() {
return mId;
}
public String getTitle() {
return mPageEntry.getTitle();
}
public String getClassName() {
final PageType type = mPageEntry.getType();
return type.getPageClass().getName();
}
public Bundle getArgs() {
final Bundle args = new Bundle();
args.putBoolean(HomePager.CAN_LOAD_ARG, mCanLoadHint);
// Only list pages need the page entry
if (mPageEntry.getType() == PageType.LIST) {
args.putParcelable(HomePager.PAGE_ENTRY_ARG, mPageEntry);
}
return args;
}
public Page toPage() {
final PageType type = mPageEntry.getType();
if (type == PageType.LIST) {
return null;
}
return Page.valueOf(type);
}
}
}

View File

@ -0,0 +1,224 @@
/* -*- 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.home;
import org.mozilla.gecko.home.HomePager.Page;
import android.content.Context;
import android.os.Parcel;
import android.os.Parcelable;
import android.text.TextUtils;
import java.util.EnumSet;
import java.util.List;
final class HomeConfig {
public static enum PageType implements Parcelable {
TOP_SITES("top_sites", TopSitesPage.class),
BOOKMARKS("bookmarks", BookmarksPage.class),
HISTORY("history", HistoryPage.class),
READING_LIST("reading_list", ReadingListPage.class),
LIST("list", ListPage.class);
private final String mId;
private final Class<?> mPageClass;
PageType(String id, Class<?> pageClass) {
mId = id;
mPageClass = pageClass;
}
public static PageType valueOf(Page page) {
switch(page) {
case TOP_SITES:
return PageType.TOP_SITES;
case BOOKMARKS:
return PageType.BOOKMARKS;
case HISTORY:
return PageType.HISTORY;
case READING_LIST:
return PageType.READING_LIST;
default:
throw new IllegalArgumentException("Could not convert unrecognized Page");
}
}
public static PageType fromId(String id) {
if (id == null) {
throw new IllegalArgumentException("Could not convert null String to PageType");
}
for (PageType page : PageType.values()) {
if (TextUtils.equals(page.mId, id.toLowerCase())) {
return page;
}
}
throw new IllegalArgumentException("Could not convert String id to PageType");
}
@Override
public String toString() {
return mId;
}
public Class<?> getPageClass() {
return mPageClass;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(ordinal());
}
public static final Creator<PageType> CREATOR = new Creator<PageType>() {
@Override
public PageType createFromParcel(final Parcel source) {
return PageType.values()[source.readInt()];
}
@Override
public PageType[] newArray(final int size) {
return new PageType[size];
}
};
}
public static class PageEntry implements Parcelable {
private final PageType mType;
private final String mTitle;
private final String mId;
private final EnumSet<Flags> mFlags;
public enum Flags {
DEFAULT_PAGE
}
@SuppressWarnings("unchecked")
public PageEntry(Parcel in) {
mType = (PageType) in.readParcelable(getClass().getClassLoader());
mTitle = in.readString();
mId = in.readString();
mFlags = (EnumSet<Flags>) in.readSerializable();
}
public PageEntry(PageType type, String title) {
this(type, title, EnumSet.noneOf(Flags.class));
}
public PageEntry(PageType type, String title, EnumSet<Flags> flags) {
this(type, title, type.toString(), flags);
}
public PageEntry(PageType type, String title, String id) {
this(type, title, id, EnumSet.noneOf(Flags.class));
}
public PageEntry(PageType type, String title, String id, EnumSet<Flags> flags) {
if (type == null) {
throw new IllegalArgumentException("Can't create PageEntry with null type");
}
mType = type;
if (title == null) {
throw new IllegalArgumentException("Can't create PageEntry with null title");
}
mTitle = title;
if (id == null) {
throw new IllegalArgumentException("Can't create PageEntry with null id");
}
mId = id;
if (flags == null) {
throw new IllegalArgumentException("Can't create PageEntry with null flags");
}
mFlags = flags;
}
public PageType getType() {
return mType;
}
public String getTitle() {
return mTitle;
}
public String getId() {
return mId;
}
public boolean isDefault() {
return mFlags.contains(Flags.DEFAULT_PAGE);
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeParcelable(mType, 0);
dest.writeString(mTitle);
dest.writeString(mId);
dest.writeSerializable(mFlags);
}
public static final Creator<PageEntry> CREATOR = new Creator<PageEntry>() {
@Override
public PageEntry createFromParcel(final Parcel in) {
return new PageEntry(in);
}
@Override
public PageEntry[] newArray(final int size) {
return new PageEntry[size];
}
};
}
public interface OnChangeListener {
public void onChange();
}
public interface HomeConfigBackend {
public List<PageEntry> load();
public void save(List<PageEntry> entries);
public void setOnChangeListener(OnChangeListener listener);
}
private final HomeConfigBackend mBackend;
public HomeConfig(HomeConfigBackend backend) {
mBackend = backend;
}
public List<PageEntry> load() {
return mBackend.load();
}
public void save(List<PageEntry> entries) {
mBackend.save(entries);
}
public void setOnChangeListener(OnChangeListener listener) {
mBackend.setOnChangeListener(listener);
}
public static HomeConfig getDefault(Context context) {
return new HomeConfig(new HomeConfigMemBackend(context));
}
}

View File

@ -0,0 +1,83 @@
/* -*- 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.home;
import org.mozilla.gecko.home.HomeConfig.PageEntry;
import org.mozilla.gecko.home.HomeConfig.OnChangeListener;
import android.content.Context;
import android.support.v4.content.AsyncTaskLoader;
import java.util.List;
public class HomeConfigLoader extends AsyncTaskLoader<List<PageEntry>> {
private final HomeConfig mConfig;
private List<PageEntry> mPageEntries;
public HomeConfigLoader(Context context, HomeConfig homeConfig) {
super(context);
mConfig = homeConfig;
}
@Override
public List<PageEntry> loadInBackground() {
return mConfig.load();
}
@Override
public void deliverResult(List<PageEntry> pageEntries) {
if (isReset()) {
mPageEntries = null;
return;
}
mPageEntries = pageEntries;
mConfig.setOnChangeListener(new ForceLoadChangeListener());
if (isStarted()) {
super.deliverResult(pageEntries);
}
}
@Override
protected void onStartLoading() {
if (mPageEntries != null) {
deliverResult(mPageEntries);
}
if (takeContentChanged() || mPageEntries == null) {
forceLoad();
}
}
@Override
protected void onStopLoading() {
cancelLoad();
}
@Override
public void onCanceled(List<PageEntry> pageEntries) {
mPageEntries = null;
}
@Override
protected void onReset() {
super.onReset();
// Ensure the loader is stopped.
onStopLoading();
mPageEntries = null;
mConfig.setOnChangeListener(null);
}
private class ForceLoadChangeListener implements OnChangeListener {
@Override
public void onChange() {
onContentChanged();
}
}
}

View File

@ -0,0 +1,67 @@
/* -*- 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.home;
import org.mozilla.gecko.R;
import org.mozilla.gecko.home.HomeConfig.HomeConfigBackend;
import org.mozilla.gecko.home.HomeConfig.OnChangeListener;
import org.mozilla.gecko.home.HomeConfig.PageEntry;
import org.mozilla.gecko.home.HomeConfig.PageType;
import org.mozilla.gecko.util.HardwareUtils;
import android.content.Context;
import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumSet;
import java.util.List;
class HomeConfigMemBackend implements HomeConfigBackend {
private final Context mContext;
public HomeConfigMemBackend(Context context) {
mContext = context;
}
public List<PageEntry> load() {
final ArrayList<PageEntry> pageEntries = new ArrayList<PageEntry>();
pageEntries.add(new PageEntry(PageType.TOP_SITES,
mContext.getString(R.string.home_top_sites_title),
EnumSet.of(PageEntry.Flags.DEFAULT_PAGE)));
pageEntries.add(new PageEntry(PageType.BOOKMARKS,
mContext.getString(R.string.bookmarks_title)));
// We disable reader mode support on low memory devices. Hence the
// reading list page should not show up on such devices.
if (!HardwareUtils.isLowMemoryPlatform()) {
pageEntries.add(new PageEntry(PageType.READING_LIST,
mContext.getString(R.string.reading_list_title)));
}
final PageEntry historyEntry = new PageEntry(PageType.HISTORY,
mContext.getString(R.string.home_history_title));
// On tablets, the history page is the last.
// On phones, the history page is the first one.
if (HardwareUtils.isTablet()) {
pageEntries.add(historyEntry);
} else {
pageEntries.add(0, historyEntry);
}
return Collections.unmodifiableList(pageEntries);
}
public void save(List<PageEntry> entries) {
// This is a static backend, do nothing.
}
public void setOnChangeListener(OnChangeListener listener) {
// This is a static backend, do nothing.
}
}

View File

@ -44,6 +44,9 @@ abstract class HomeFragment extends Fragment {
// Share MIME type.
private static final String SHARE_MIME_TYPE = "text/plain";
// Default value for "can load" hint
static final boolean DEFAULT_CAN_LOAD_HINT = false;
// Whether the fragment can load its content or not
// This is used to defer data loading until the editing
// mode animation ends.
@ -58,9 +61,9 @@ abstract class HomeFragment extends Fragment {
final Bundle args = getArguments();
if (args != null) {
mCanLoadHint = args.getBoolean(HomePager.CAN_LOAD_ARG, false);
mCanLoadHint = args.getBoolean(HomePager.CAN_LOAD_ARG, DEFAULT_CAN_LOAD_HINT);
} else {
mCanLoadHint = false;
mCanLoadHint = DEFAULT_CAN_LOAD_HINT;
}
mIsLoaded = false;

View File

@ -8,6 +8,9 @@ package org.mozilla.gecko.home;
import org.mozilla.gecko.R;
import org.mozilla.gecko.animation.PropertyAnimator;
import org.mozilla.gecko.animation.ViewHelper;
import org.mozilla.gecko.home.HomeAdapter.OnAddPageListener;
import org.mozilla.gecko.home.HomeConfig.PageEntry;
import org.mozilla.gecko.home.HomeConfig.PageType;
import org.mozilla.gecko.mozglue.RobocopTarget;
import org.mozilla.gecko.util.HardwareUtils;
@ -16,8 +19,9 @@ import android.os.Build;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentStatePagerAdapter;
import android.support.v4.view.PagerAdapter;
import android.support.v4.app.LoaderManager;
import android.support.v4.app.LoaderManager.LoaderCallbacks;
import android.support.v4.content.Loader;
import android.support.v4.view.ViewPager;
import android.view.ViewGroup.LayoutParams;
import android.util.AttributeSet;
@ -25,17 +29,24 @@ import android.view.MotionEvent;
import android.view.ViewGroup;
import android.view.View;
import java.util.ArrayList;
import java.util.EnumMap;
import java.util.EnumSet;
import java.util.List;
public class HomePager extends ViewPager {
// Subpage fragment tag
public static final String SUBPAGE_TAG = "home_pager_subpage";
private static final int LOADER_ID_CONFIG = 0;
private final Context mContext;
private volatile boolean mLoaded;
private Decor mDecor;
private View mTabStrip;
private final OnAddPageListener mAddPageListener;
private final HomeConfig mConfig;
private ConfigLoaderCallbacks mConfigLoaderCallbacks;
private Page mInitialPage;
// List of pages in order.
@RobocopTarget
@ -43,7 +54,26 @@ public class HomePager extends ViewPager {
HISTORY,
TOP_SITES,
BOOKMARKS,
READING_LIST
READING_LIST;
static Page valueOf(PageType page) {
switch(page) {
case TOP_SITES:
return Page.TOP_SITES;
case BOOKMARKS:
return Page.BOOKMARKS;
case HISTORY:
return Page.HISTORY;
case READING_LIST:
return Page.READING_LIST;
default:
throw new IllegalArgumentException("Could not convert unrecognized PageType");
}
}
}
// This is mostly used by UI tests to easily fetch
@ -56,8 +86,6 @@ public class HomePager extends ViewPager {
static final String LIST_TAG_LAST_TABS = "last_tabs";
static final String LIST_TAG_BROWSER_SEARCH = "browser_search";
private EnumMap<Page, Fragment> mPages = new EnumMap<Page, Fragment>(Page.class);
public interface OnUrlOpenListener {
public enum Flags {
ALLOW_SWITCH_TO_TAB
@ -86,6 +114,7 @@ public class HomePager extends ViewPager {
}
static final String CAN_LOAD_ARG = "canLoad";
static final String PAGE_ENTRY_ARG = "pageEntry";
public HomePager(Context context) {
this(context, null);
@ -95,6 +124,18 @@ public class HomePager extends ViewPager {
super(context, attrs);
mContext = context;
mConfig = HomeConfig.getDefault(mContext);
mConfigLoaderCallbacks = new ConfigLoaderCallbacks();
mAddPageListener = new OnAddPageListener() {
@Override
public void onAddPage(String title) {
if (mDecor != null) {
mDecor.onAddPagerView(title);
}
}
};
// This is to keep all 4 pages in memory after they are
// selected in the pager.
setOffscreenPageLimit(3);
@ -112,6 +153,15 @@ public class HomePager extends ViewPager {
if (child instanceof Decor) {
((ViewPager.LayoutParams) params).isDecor = true;
mDecor = (Decor) child;
mTabStrip = child;
mDecor.setOnTitleClickListener(new OnTitleClickListener() {
@Override
public void onTitleClicked(int index) {
setCurrentItem(index, true);
}
});
setOnPageChangeListener(new ViewPager.OnPageChangeListener() {
@Override
public void onPageSelected(int position) {
@ -126,14 +176,27 @@ public class HomePager extends ViewPager {
@Override
public void onPageScrollStateChanged(int state) { }
});
} else if (child instanceof HomePagerTabStrip) {
mTabStrip = child;
}
super.addView(child, index, params);
}
public void redisplay(FragmentManager fm) {
final TabsAdapter adapter = (TabsAdapter) getAdapter();
show(fm, adapter.getCurrentPage(), null);
public void redisplay(LoaderManager lm, FragmentManager fm) {
final HomeAdapter adapter = (HomeAdapter) getAdapter();
// If mInitialPage is non-null, this means the HomePager hasn't
// finished loading its config yet. Simply re-show() with the
// current target page.
final Page currentPage;
if (mInitialPage != null) {
currentPage = mInitialPage;
} else {
currentPage = adapter.getPageAtPosition(getCurrentItem());
}
show(lm, fm, currentPage, null);
}
/**
@ -141,38 +204,27 @@ public class HomePager extends ViewPager {
*
* @param fm FragmentManager for the adapter
*/
public void show(FragmentManager fm, Page page, PropertyAnimator animator) {
public void show(LoaderManager lm, FragmentManager fm, Page page, PropertyAnimator animator) {
mLoaded = true;
final TabsAdapter adapter = new TabsAdapter(fm);
mInitialPage = page;
// Only animate on post-HC devices, when a non-null animator is given
final boolean shouldAnimate = (animator != null && Build.VERSION.SDK_INT >= 11);
adapter.addTab(Page.TOP_SITES, TopSitesPage.class, new Bundle(),
getContext().getString(R.string.home_top_sites_title));
adapter.addTab(Page.BOOKMARKS, BookmarksPage.class, new Bundle(),
getContext().getString(R.string.bookmarks_title));
// We disable reader mode support on low memory devices. Hence the
// reading list page should not show up on such devices.
if (!HardwareUtils.isLowMemoryPlatform()) {
adapter.addTab(Page.READING_LIST, ReadingListPage.class, new Bundle(),
getContext().getString(R.string.reading_list_title));
}
// On phones, the history tab is the first tab. On tablets, the
// history tab is the last tab.
adapter.addTab(HardwareUtils.isTablet() ? -1 : 0,
Page.HISTORY, HistoryPage.class, new Bundle(),
getContext().getString(R.string.home_history_title));
final HomeAdapter adapter = new HomeAdapter(mContext, fm);
adapter.setOnAddPageListener(mAddPageListener);
adapter.setCanLoadHint(!shouldAnimate);
setAdapter(adapter);
setCurrentItem(adapter.getItemPosition(page), false);
setVisibility(VISIBLE);
// Don't show the tabs strip until we have the
// list of pages in place.
mTabStrip.setVisibility(View.INVISIBLE);
// Load list of pages from configuration
lm.initLoader(LOADER_ID_CONFIG, null, mConfigLoaderCallbacks);
if (shouldAnimate) {
animator.addPropertyAnimationListener(new PropertyAnimator.PropertyAnimationListener() {
@Override
@ -225,122 +277,6 @@ public class HomePager extends ViewPager {
}
}
class TabsAdapter extends FragmentStatePagerAdapter
implements OnTitleClickListener {
private final ArrayList<TabInfo> mTabs = new ArrayList<TabInfo>();
final class TabInfo {
private final Page page;
private final Class<?> clss;
private final Bundle args;
private final String title;
TabInfo(Page page, Class<?> clss, Bundle args, String title) {
this.page = page;
this.clss = clss;
this.args = args;
this.title = title;
}
}
public TabsAdapter(FragmentManager fm) {
super(fm);
if (mDecor != null) {
mDecor.removeAllPagerViews();
mDecor.setOnTitleClickListener(this);
}
}
public void addTab(Page page, Class<?> clss, Bundle args, String title) {
addTab(-1, page, clss, args, title);
}
public void addTab(int index, Page page, Class<?> clss, Bundle args, String title) {
TabInfo info = new TabInfo(page, clss, args, title);
if (index >= 0) {
mTabs.add(index, info);
} else {
mTabs.add(info);
}
notifyDataSetChanged();
if (mDecor != null) {
mDecor.onAddPagerView(title);
}
}
@Override
public void onTitleClicked(int index) {
setCurrentItem(index, true);
}
public int getItemPosition(Page page) {
for (int i = 0; i < mTabs.size(); i++) {
TabInfo info = mTabs.get(i);
if (info.page == page) {
return i;
}
}
return -1;
}
public Page getCurrentPage() {
int currentItem = getCurrentItem();
TabInfo info = mTabs.get(currentItem);
return info.page;
}
@Override
public int getCount() {
return mTabs.size();
}
@Override
public Fragment getItem(int position) {
TabInfo info = mTabs.get(position);
return Fragment.instantiate(mContext, info.clss.getName(), info.args);
}
@Override
public CharSequence getPageTitle(int position) {
TabInfo info = mTabs.get(position);
return info.title.toUpperCase();
}
@Override
public Object instantiateItem(ViewGroup container, int position) {
Fragment fragment = (Fragment) super.instantiateItem(container, position);
mPages.put(mTabs.get(position).page, fragment);
return fragment;
}
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
super.destroyItem(container, position, object);
mPages.remove(mTabs.get(position).page);
}
public void setCanLoadHint(boolean canLoadHint) {
// Update fragment arguments for future instances
for (TabInfo info : mTabs) {
info.args.putBoolean(CAN_LOAD_ARG, canLoadHint);
}
// Enable/disable loading on all existing pages
for (Fragment page : mPages.values()) {
final HomeFragment homePage = (HomeFragment) page;
homePage.setCanLoadHint(canLoadHint);
}
}
}
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
@ -350,4 +286,66 @@ public class HomePager extends ViewPager {
return super.onInterceptTouchEvent(event);
}
private void updateUiFromPageEntries(List<PageEntry> pageEntries) {
// We only care about the adapter if HomePager is currently
// loaded, which means it's visible in the activity.
if (!mLoaded) {
return;
}
if (mDecor != null) {
mDecor.removeAllPagerViews();
}
final HomeAdapter adapter = (HomeAdapter) getAdapter();
// Disable loading until the final current item is defined
// after loading the page entries. This is to stop any temporary
// active item from loading.
boolean originalCanLoadHint = adapter.getCanLoadHint();
adapter.setCanLoadHint(false);
// Update the adapter with the new page entries
adapter.update(pageEntries);
final int count = (pageEntries != null ? pageEntries.size() : 0);
mTabStrip.setVisibility(count > 0 ? View.VISIBLE : View.INVISIBLE);
// Use the default page as defined in the HomePager's configuration
// if the initial page wasn't explicitly set by the show() caller.
if (mInitialPage != null) {
setCurrentItem(adapter.getItemPosition(mInitialPage), false);
mInitialPage = null;
} else {
for (int i = 0; i < count; i++) {
final PageEntry pageEntry = pageEntries.get(i);
if (pageEntry.isDefault()) {
setCurrentItem(i, false);
break;
}
}
}
// Restore canLoadHint now that we have the final
// state in HomePager.
adapter.setCanLoadHint(originalCanLoadHint);
}
private class ConfigLoaderCallbacks implements LoaderCallbacks<List<PageEntry>> {
@Override
public Loader<List<PageEntry>> onCreateLoader(int id, Bundle args) {
return new HomeConfigLoader(mContext, mConfig);
}
@Override
public void onLoadFinished(Loader<List<PageEntry>> loader, List<PageEntry> pageEntries) {
updateUiFromPageEntries(pageEntries);
}
@Override
public void onLoaderReset(Loader<List<PageEntry>> loader) {
updateUiFromPageEntries(null);
}
}
}

View File

@ -0,0 +1,185 @@
/* -*- 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.home;
import org.mozilla.gecko.R;
import org.mozilla.gecko.home.HomePager.OnUrlOpenListener;
import org.mozilla.gecko.home.HomeConfig.PageEntry;
import android.app.Activity;
import android.content.Context;
import android.content.res.Configuration;
import android.database.Cursor;
import android.os.Bundle;
import android.support.v4.app.LoaderManager.LoaderCallbacks;
import android.support.v4.content.Loader;
import android.support.v4.widget.CursorAdapter;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ListView;
import java.util.EnumSet;
/**
* Fragment that displays custom lists.
*/
public class ListPage extends HomeFragment {
// Cursor loader ID for the lists
private static final int LOADER_ID_LIST = 0;
// The page entry associated with this page
private PageEntry mPageEntry;
// Adapter for the list
private HomeListAdapter mAdapter;
// The view shown by the fragment
private ListView mList;
// Callbacks used for the list loader
private CursorLoaderCallbacks mCursorLoaderCallbacks;
// On URL open listener
private OnUrlOpenListener mUrlOpenListener;
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
try {
mUrlOpenListener = (OnUrlOpenListener) activity;
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString()
+ " must implement HomePager.OnUrlOpenListener");
}
}
@Override
public void onDetach() {
super.onDetach();
mUrlOpenListener = null;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
final Bundle args = getArguments();
if (args != null) {
mPageEntry = (PageEntry) args.getParcelable(HomePager.PAGE_ENTRY_ARG);
}
if (mPageEntry == null) {
throw new IllegalStateException("Can't create a ListPage without a PageEntry");
}
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
mList = new HomeListView(getActivity());
return mList;
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
registerForContextMenu(mList);
}
@Override
public void onDestroyView() {
super.onDestroyView();
mList = null;
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
// Detach and reattach the fragment as the layout changes.
if (isVisible()) {
getFragmentManager().beginTransaction()
.detach(this)
.attach(this)
.commitAllowingStateLoss();
}
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
mAdapter = new HomeListAdapter(getActivity(), null);
mList.setAdapter(mAdapter);
// Create callbacks before the initial loader is started.
mCursorLoaderCallbacks = new CursorLoaderCallbacks();
loadIfVisible();
}
@Override
protected void load() {
getLoaderManager().initLoader(LOADER_ID_LIST, null, mCursorLoaderCallbacks);
}
/**
* Cursor loader for the lists.
*/
private static class HomeListLoader extends SimpleCursorLoader {
public HomeListLoader(Context context) {
super(context);
}
@Override
public Cursor loadCursor() {
// Do nothing
return null;
}
}
/**
* Cursor adapter for the list.
*/
private class HomeListAdapter extends CursorAdapter {
public HomeListAdapter(Context context, Cursor cursor) {
super(context, cursor, 0);
}
@Override
public void bindView(View view, Context context, Cursor cursor) {
final TwoLinePageRow row = (TwoLinePageRow) view;
row.updateFromCursor(cursor);
}
@Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
return LayoutInflater.from(parent.getContext()).inflate(R.layout.bookmark_item_row, parent, false);
}
}
/**
* LoaderCallbacks implementation that interacts with the LoaderManager.
*/
private class CursorLoaderCallbacks implements LoaderCallbacks<Cursor> {
@Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
return new HomeListLoader(getActivity());
}
@Override
public void onLoadFinished(Loader<Cursor> loader, Cursor c) {
mAdapter.swapCursor(c);
}
@Override
public void onLoaderReset(Loader<Cursor> loader) {
mAdapter.swapCursor(null);
}
}
}

View File

@ -208,12 +208,17 @@ gbjar.sources += [
'home/BrowserSearch.java',
'home/FadedTextView.java',
'home/HistoryPage.java',
'home/HomeAdapter.java',
'home/HomeBanner.java',
'home/HomeConfig.java',
'home/HomeConfigLoader.java',
'home/HomeConfigMemBackend.java',
'home/HomeFragment.java',
'home/HomeListView.java',
'home/HomePager.java',
'home/HomePagerTabStrip.java',
'home/LastTabsPage.java',
'home/ListPage.java',
'home/MostRecentPage.java',
'home/MultiTypeCursorAdapter.java',
'home/PinSiteDialog.java',

View File

@ -52,6 +52,13 @@ public class StringHelper {
"Add to Home Screen"
};
public static final String[] CONTEXT_MENU_ITEMS_IN_URL_BAR = new String[] {
"Share",
"Copy Address",
"Edit Site Settings",
"Add to Home Screen"
};
public static final String TITLE_PLACE_HOLDER = "Enter Search or Address";
// Robocop page urls

View File

@ -1,8 +1,17 @@
package org.mozilla.gecko.tests;
import android.view.View;
import org.mozilla.gecko.*;
import java.util.ArrayList;
/**
* This patch tests the clear private data options:
* - clear history option by: adding and checking that clear private
* data option removes the history items but not the users bookmarks
* - clear site settings and clear saved password by: checking
* each option present in the doorhanger and clearing the settings from
* the URL bar context menu and settings menu
*/
public class testClearPrivateData extends PixelTest {
private final int TEST_WAIT_MS = 10000;
@ -14,21 +23,25 @@ public class testClearPrivateData extends PixelTest {
public void testClearPrivateData() {
blockForGeckoReady();
clearHistory();
clearSiteSettings();
clearPassword();
}
private void clearHistory() {
// Loading a page and adding a second one as bookmark to have user made bookmarks and history
String blank1 = getAbsoluteUrl(StringHelper.ROBOCOP_BLANK_PAGE_01_URL);
String blank2 = getAbsoluteUrl(StringHelper.ROBOCOP_BLANK_PAGE_02_URL);
loadAndPaint(blank1);
waitForText(StringHelper.ROBOCOP_BLANK_PAGE_01_TITLE);
String title = StringHelper.ROBOCOP_BLANK_PAGE_01_TITLE;
inputAndLoadUrl(blank1);
verifyPageTitle(title);
mDatabaseHelper.addOrUpdateMobileBookmark(StringHelper.ROBOCOP_BLANK_PAGE_02_TITLE, blank2);
// Checking that the history list is not empty
verifyHistoryCount(1);
clearPrivateData();
//clear and check for device
checkDevice(title);
// Checking that history list is empty
verifyHistoryCount(0);
@ -45,4 +58,60 @@ public class testClearPrivateData extends PixelTest {
}, TEST_WAIT_MS);
mAsserter.ok(match, "Checking that the number of history items is correct", String.valueOf(expectedCount) + " history items present in the database");
}
public void clearSiteSettings() {
String shareStrings[] = {"Share your location with", "Share", "Don't share", "There are no settings to clear"};
String titleGeolocation = StringHelper.ROBOCOP_GEOLOCATION_TITLE;
String url = getAbsoluteUrl(StringHelper.ROBOCOP_GEOLOCATION_URL);
loadCheckDismiss(shareStrings[1], url, shareStrings[0]);
checkOption(shareStrings[1], "Clear");
checkOption(shareStrings[3], "Cancel");
loadCheckDismiss(shareStrings[2], url, shareStrings[0]);
checkOption(shareStrings[2], "Cancel");
checkDevice(titleGeolocation);
}
public void clearPassword(){
String passwordStrings[] = {"Save password", "Save", "Don't save"};
String title = StringHelper.ROBOCOP_BLANK_PAGE_01_TITLE;
String loginUrl = getAbsoluteUrl(StringHelper.ROBOCOP_LOGIN_URL);
loadCheckDismiss(passwordStrings[1], loginUrl, passwordStrings[0]);
checkOption(passwordStrings[1], "Clear");
loadCheckDismiss(passwordStrings[2], loginUrl, passwordStrings[0]);
checkDevice(title);
}
// clear private data and verify the device type because for phone there is an extra back action to exit the settings menu
public void checkDevice(String title) {
clearPrivateData();
if (mDevice.type.equals("phone")) {
mActions.sendSpecialKey(Actions.SpecialKey.BACK);
mAsserter.ok(waitForText(StringHelper.PRIVACY_SECTION_LABEL), "waiting to perform one back", "one back");
mActions.sendSpecialKey(Actions.SpecialKey.BACK);
verifyPageTitle(title);
}
else {
mActions.sendSpecialKey(Actions.SpecialKey.BACK);
verifyPageTitle(title);
}
}
// Load a URL, verify that the doorhanger appears and dismiss it
public void loadCheckDismiss(String option, String url, String message) {
inputAndLoadUrl(url);
waitForText(message);
mAsserter.is(mSolo.searchText(message), true, "Doorhanger:" + message + " has been displayed");
mSolo.clickOnButton(option);
mAsserter.is(mSolo.searchText(message), false, "Doorhanger:" + message + " has been hidden");
}
//Verify if there are settings to be clear if so clear them from the URL bar context menu
public void checkOption(String option, String button) {
final View toolbarView = mSolo.getView("browser_toolbar");
mSolo.clickLongOnView(toolbarView);
mAsserter.ok(waitForText(StringHelper.CONTEXT_MENU_ITEMS_IN_URL_BAR[2]), "Waiting for the pop-up to open", "Pop up was openend");
mSolo.clickOnText(StringHelper.CONTEXT_MENU_ITEMS_IN_URL_BAR[2]);
mAsserter.ok(waitForText(option), "Verify that the option: " + option + " is in the list", "The option is in the list. There are settings to clear");
mSolo.clickOnButton(button);
}
}

View File

@ -65,6 +65,11 @@ XPCOMUtils.defineLazyServiceGetter(this, "uuidgen",
"@mozilla.org/uuid-generator;1",
"nsIUUIDGenerator");
#ifdef NIGHTLY_BUILD
XPCOMUtils.defineLazyModuleGetter(this, "ShumwayUtils",
"resource://shumway/ShumwayUtils.jsm");
#endif
// Lazily-loaded browser scripts:
[
["SelectHelper", "chrome://browser/content/SelectHelper.js"],
@ -341,6 +346,9 @@ var BrowserApp = {
#ifdef ACCESSIBILITY
AccessFu.attach(window);
#endif
#ifdef NIGHTLY_BUILD
ShumwayUtils.init();
#endif
// Init LoginManager
Cc["@mozilla.org/login-manager;1"].getService(Ci.nsILoginManager);

View File

@ -0,0 +1,27 @@
# 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/.
include $(topsrcdir)/config/rules.mk
SHUMWAY_BROWSER_EXTENSION = $(topsrcdir)/browser/extensions/shumway
exclude_files = \
test \
$(NULL)
ifdef NIGHTLY_BUILD
$(FINAL_TARGET)/chrome/shumway.manifest: $(GLOBAL_DEPS)
$(call py_action,buildlist,$@ "manifest shumway/chrome.manifest")
libs:: $(SHUMWAY_BROWSER_EXTENSION) $(GLOBAL_DEPS)
$(PYTHON) $(topsrcdir)/config/nsinstall.py \
$(SHUMWAY_BROWSER_EXTENSION) \
$(foreach exclude,$(exclude_files), -X $(SHUMWAY_BROWSER_EXTENSION)/$(exclude)) \
$(FINAL_TARGET)/chrome
libs:: $(FINAL_TARGET)/chrome/shumway.manifest
$(call py_action,buildlist,$(FINAL_TARGET)/chrome.manifest "manifest chrome/shumway.manifest")
endif

View File

@ -0,0 +1,6 @@
# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=python:
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

View File

@ -442,6 +442,10 @@
; [Browser Chrome Files]
@BINPATH@/chrome/browser@JAREXT@
@BINPATH@/chrome/browser.manifest
#ifdef NIGHTLY_BUILD
@BINPATH@/chrome/shumway.manifest
@BINPATH@/chrome/shumway/*
#endif
@BINPATH@/extensions/{972ce4c6-7e08-4474-a285-3208198ce6fd}/install.rdf
@BINPATH@/extensions/{972ce4c6-7e08-4474-a285-3208198ce6fd}/icon.png
@BINPATH@/extensions/{972ce4c6-7e08-4474-a285-3208198ce6fd}/preview.png

View File

@ -17,6 +17,7 @@ DIRS += [
'app',
'fonts',
'geckoview_library',
'extensions',
]
if not CONFIG['LIBXUL_SDK']:

View File

@ -130,6 +130,14 @@ InternalMethods.prototype = {
}
},
signOut: function signOut() {
this.abortExistingFlow();
this.signedInUser = null; // clear in-memory cache
return this.signedInUserStorage.set(null).then(() => {
this.notifyObservers("fxaccounts:onlogout");
});
},
/**
* Fetch encryption keys for the signed-in-user from the FxA API server.
*
@ -582,11 +590,7 @@ this.FxAccounts.prototype = Object.freeze({
* The promise is rejected if a storage error occurs.
*/
signOut: function signOut() {
internal.abortExistingFlow();
internal.signedInUser = null; // clear in-memory cache
return internal.signedInUserStorage.set(null).then(() => {
internal.notifyObservers("fxaccounts:onlogout");
});
return internal.signOut();
},
// Return the URI of the remote UI flows.

View File

@ -291,6 +291,29 @@ add_task(function test_getKeys() {
});
});
/*
* getKeys with no keyFetchToken should trigger signOut
*/
add_test(function test_getKeys_no_token() {
do_test_pending();
let fxa = new MockFxAccounts();
let user = getTestUser("lettuce.protheroe");
delete user.keyFetchToken
makeObserver("fxaccounts:onlogout", function() {
log.debug("test_getKeys_no_token observed logout");
fxa.internal.getUserAccountData().then(user => {
do_test_finished();
run_next_test();
});
});
fxa.setSignedInUser(user).then((user) => {
fxa.internal.getKeys();
});
});
/*
* Alice (User A) signs up but never verifies her email. Then Bob (User B)
* signs in with a verified email. Ensure that no sign-in events are triggered

View File

@ -129,14 +129,6 @@
"dom/imptests/editing/conformancetest/test_runtest.html": "",
"dom/imptests/editing/selecttest/test_addRange.html": "bug 775227",
"dom/imptests/html/webgl": "WebGL",
"dom/imptests/html/dom/ranges/test_Range-cloneContents.html": "bug 775227",
"dom/imptests/html/dom/ranges/test_Range-compareBoundaryPoints.html": "bug 775227",
"dom/imptests/html/dom/ranges/test_Range-deleteContents.html": "bug 775227",
"dom/imptests/html/dom/ranges/test_Range-extractContents.html": "bug 775227",
"dom/imptests/html/dom/ranges/test_Range-insertNode.html": "bug 775227",
"dom/imptests/html/dom/ranges/test_Range-mutations.html": "bug 775227",
"dom/imptests/html/dom/ranges/test_Range-set.html": "bug 775227",
"dom/imptests/html/dom/ranges/test_Range-surroundContents.html": "bug 775227",
"dom/imptests/webapps/WebStorage/tests/submissions/Infraware/test_storage_local_key.html": "bug 775227",
"dom/indexedDB/ipc/test_ipc.html": "bug 783513",
"dom/indexedDB/test/test_third_party.html": "TIMED_OUT",

View File

@ -206,14 +206,6 @@
"dom/imptests/editing/conformancetest/test_runtest.html": "",
"dom/imptests/editing/selecttest/test_addRange.html": "bug 775227",
"dom/imptests/html/webgl": "WebGL",
"dom/imptests/html/dom/ranges/test_Range-cloneContents.html": "bug 775227",
"dom/imptests/html/dom/ranges/test_Range-compareBoundaryPoints.html": "bug 775227",
"dom/imptests/html/dom/ranges/test_Range-deleteContents.html": "bug 775227",
"dom/imptests/html/dom/ranges/test_Range-extractContents.html": "bug 775227",
"dom/imptests/html/dom/ranges/test_Range-insertNode.html": "bug 775227",
"dom/imptests/html/dom/ranges/test_Range-mutations.html": "bug 775227",
"dom/imptests/html/dom/ranges/test_Range-set.html": "bug 775227",
"dom/imptests/html/dom/ranges/test_Range-surroundContents.html": "bug 775227",
"dom/imptests/webapps/WebStorage/tests/submissions/Infraware/test_storage_local_key.html": "bug 775227",
"dom/indexedDB/ipc/test_ipc.html": "bug 783513",
"dom/indexedDB/test/test_third_party.html": "TIMED_OUT",

View File

@ -35,7 +35,7 @@ this.SafeBrowsing = {
return;
}
Services.prefs.addObserver("browser.safebrowsing", this.readPrefs, false);
Services.prefs.addObserver("browser.safebrowsing", this.readPrefs.bind(this), false);
this.readPrefs();
// Register our two types of tables, and add custom Mozilla entries

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