merge fx-team to mozilla-central a=merge
|
@ -1695,6 +1695,10 @@ pref("identity.fxaccounts.remote.signin.uri", "https://accounts.firefox.com/sign
|
|||
// "identity.fxaccounts.remote.signup.uri" pref.
|
||||
pref("identity.fxaccounts.settings.uri", "https://accounts.firefox.com/settings");
|
||||
|
||||
// Migrate any existing Firefox Account data from the default profile to the
|
||||
// Developer Edition profile.
|
||||
pref("identity.fxaccounts.migrateToDevEdition", false);
|
||||
|
||||
// On GTK, we now default to showing the menubar only when alt is pressed:
|
||||
#ifdef MOZ_WIDGET_GTK
|
||||
pref("ui.key.menuAccessKeyFocuses", true);
|
||||
|
|
|
@ -331,8 +331,11 @@ function init() {
|
|||
show("remote");
|
||||
wrapper.init(url, entryPoint);
|
||||
});
|
||||
} else if (window.location.href.contains("action=migrateToDevEdition") &&
|
||||
user == null) {
|
||||
migrateToDevEdition(user, entryPoint);
|
||||
} else {
|
||||
// No action specified
|
||||
// No action specified, or migration request when we already have a user.
|
||||
if (user) {
|
||||
show("stage", "manage");
|
||||
let sb = Services.strings.createBundle("chrome://browser/locale/syncSetup.properties");
|
||||
|
@ -372,6 +375,48 @@ function show(id, childId) {
|
|||
}
|
||||
}
|
||||
|
||||
// Migrate sync data from the default profile to the dev-edition profile.
|
||||
function migrateToDevEdition(user, entryPoint) {
|
||||
let migrateSyncCreds = false;
|
||||
try {
|
||||
migrateSyncCreds = Services.prefs.getBoolPref("identity.fxaccounts.migrateToDevEdition");
|
||||
} catch (e) {}
|
||||
if (migrateSyncCreds) {
|
||||
Cu.import("resource://gre/modules/osfile.jsm");
|
||||
let fxAccountsStorage = OS.Path.join(window.getDefaultProfilePath(), fxAccountsCommon.DEFAULT_STORAGE_FILENAME);
|
||||
return OS.File.read(fxAccountsStorage, { encoding: "utf-8" }).then(text => {
|
||||
let accountData = JSON.parse(text).accountData;
|
||||
return fxAccounts.setSignedInUser(accountData);
|
||||
}).then(() => {
|
||||
return fxAccounts.promiseAccountsForceSigninURI().then(url => {
|
||||
show("remote");
|
||||
wrapper.init(url, entryPoint);
|
||||
});
|
||||
}).then(null, error => {
|
||||
log("Failed to migrate FX Account: " + error);
|
||||
show("stage", "intro");
|
||||
// load the remote frame in the background
|
||||
wrapper.init(fxAccounts.getAccountsSignUpURI(), entryPoint);
|
||||
}).then(() => {
|
||||
// Reset the pref after migration.
|
||||
Services.prefs.setBoolPref("identity.fxaccounts.migrateToDevEdition", false);
|
||||
});
|
||||
} else {
|
||||
show("stage", "intro");
|
||||
// load the remote frame in the background
|
||||
wrapper.init(fxAccounts.getAccountsSignUpURI(), entryPoint);
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function that returns the path of the default profile on disk. Will be
|
||||
// overridden in tests.
|
||||
function getDefaultProfilePath() {
|
||||
let defaultProfile = Cc["@mozilla.org/toolkit/profile-service;1"]
|
||||
.getService(Ci.nsIToolkitProfileService)
|
||||
.defaultProfile;
|
||||
return defaultProfile.rootDir.path;
|
||||
}
|
||||
|
||||
document.addEventListener("DOMContentLoaded", function onload() {
|
||||
document.removeEventListener("DOMContentLoaded", onload, true);
|
||||
init();
|
||||
|
|
|
@ -22,8 +22,12 @@ searchbar {
|
|||
-moz-binding: url("chrome://browser/content/search/search.xml#searchbar");
|
||||
}
|
||||
|
||||
.browserStack > browser {
|
||||
-moz-binding: url("chrome://browser/content/tabbrowser.xml#tabbrowser-browser");
|
||||
}
|
||||
|
||||
.browserStack > browser[remote="true"] {
|
||||
-moz-binding: url("chrome://global/content/bindings/remote-browser.xml#remote-browser");
|
||||
-moz-binding: url("chrome://browser/content/tabbrowser.xml#tabbrowser-remote-browser");
|
||||
}
|
||||
|
||||
toolbar[customizable="true"] {
|
||||
|
|
|
@ -799,34 +799,62 @@ function gKeywordURIFixup({ target: browser, data: fixupInfo }) {
|
|||
}
|
||||
}
|
||||
|
||||
// Called when a docshell has attempted to load a page in an incorrect process.
|
||||
// This function is responsible for loading the page in the correct process.
|
||||
function RedirectLoad({ target: browser, data }) {
|
||||
// A shared function used by both remote and non-remote browser XBL bindings to
|
||||
// load a URI or redirect it to the correct process.
|
||||
function _loadURIWithFlags(browser, uri, flags, referrer, charset, postdata) {
|
||||
if (!uri) {
|
||||
uri = "about:blank";
|
||||
}
|
||||
|
||||
if (!(flags & browser.webNavigation.LOAD_FLAGS_FROM_EXTERNAL)) {
|
||||
browser.userTypedClear++;
|
||||
}
|
||||
|
||||
try {
|
||||
let shouldBeRemote = gMultiProcessBrowser &&
|
||||
E10SUtils.shouldBrowserBeRemote(uri);
|
||||
if (browser.isRemoteBrowser == shouldBeRemote) {
|
||||
browser.webNavigation.loadURI(uri, flags, referrer, postdata, null);
|
||||
} else {
|
||||
LoadInOtherProcess(browser, {
|
||||
uri: uri,
|
||||
flags: flags,
|
||||
referrer: referrer ? referrer.spec : null,
|
||||
});
|
||||
}
|
||||
} finally {
|
||||
if (browser.userTypedClear) {
|
||||
browser.userTypedClear--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Starts a new load in the browser first switching the browser to the correct
|
||||
// process
|
||||
function LoadInOtherProcess(browser, loadOptions, historyIndex = -1) {
|
||||
let tab = gBrowser.getTabForBrowser(browser);
|
||||
// Flush the tab state before getting it
|
||||
TabState.flush(browser);
|
||||
let tabState = JSON.parse(SessionStore.getTabState(tab));
|
||||
|
||||
if (data.historyIndex < 0) {
|
||||
// Add a pseudo-history state for the new url to load
|
||||
let newEntry = {
|
||||
url: data.uri,
|
||||
referrer: data.referrer,
|
||||
};
|
||||
|
||||
tabState.entries = tabState.entries.slice(0, tabState.index);
|
||||
tabState.entries.push(newEntry);
|
||||
tabState.index++;
|
||||
if (historyIndex < 0) {
|
||||
tabState.userTypedValue = null;
|
||||
// Tell session history the new page to load
|
||||
SessionStore._restoreTabAndLoad(tab, JSON.stringify(tabState), loadOptions);
|
||||
}
|
||||
else {
|
||||
// Update the history state to point to the requested index
|
||||
tabState.index = data.historyIndex + 1;
|
||||
tabState.index = historyIndex + 1;
|
||||
// SessionStore takes care of setting the browser remoteness before restoring
|
||||
// history into it.
|
||||
SessionStore.setTabState(tab, JSON.stringify(tabState));
|
||||
}
|
||||
}
|
||||
|
||||
// SessionStore takes care of setting the browser remoteness before restoring
|
||||
// history into it.
|
||||
SessionStore.setTabState(tab, JSON.stringify(tabState));
|
||||
// Called when a docshell has attempted to load a page in an incorrect process.
|
||||
// This function is responsible for loading the page in the correct process.
|
||||
function RedirectLoad({ target: browser, data }) {
|
||||
LoadInOtherProcess(browser, data.loadOptions, data.historyIndex);
|
||||
}
|
||||
|
||||
var gBrowserInit = {
|
||||
|
|
|
@ -5377,4 +5377,42 @@
|
|||
</implementation>
|
||||
</binding>
|
||||
|
||||
<binding id="tabbrowser-browser"
|
||||
extends="chrome://global/content/bindings/browser.xml#browser">
|
||||
<implementation>
|
||||
<!-- throws exception for unknown schemes -->
|
||||
<method name="loadURIWithFlags">
|
||||
<parameter name="aURI"/>
|
||||
<parameter name="aFlags"/>
|
||||
<parameter name="aReferrerURI"/>
|
||||
<parameter name="aCharset"/>
|
||||
<parameter name="aPostData"/>
|
||||
<body>
|
||||
<![CDATA[
|
||||
_loadURIWithFlags(this, aURI, aFlags, aReferrerURI, aCharset, aPostData);
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
</implementation>
|
||||
</binding>
|
||||
|
||||
<binding id="tabbrowser-remote-browser"
|
||||
extends="chrome://global/content/bindings/remote-browser.xml#remote-browser">
|
||||
<implementation>
|
||||
<!-- throws exception for unknown schemes -->
|
||||
<method name="loadURIWithFlags">
|
||||
<parameter name="aURI"/>
|
||||
<parameter name="aFlags"/>
|
||||
<parameter name="aReferrerURI"/>
|
||||
<parameter name="aCharset"/>
|
||||
<parameter name="aPostData"/>
|
||||
<body>
|
||||
<![CDATA[
|
||||
_loadURIWithFlags(this, aURI, aFlags, aReferrerURI, aCharset, aPostData);
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
</implementation>
|
||||
</binding>
|
||||
|
||||
</bindings>
|
||||
|
|
|
@ -231,7 +231,6 @@ skip-if = e10s # Bug ?????? - test directly manipulates content
|
|||
[browser_bug579872.js]
|
||||
[browser_bug580638.js]
|
||||
[browser_bug580956.js]
|
||||
skip-if = e10s # Bug 516755 - SessionStore disabled for e10s
|
||||
[browser_bug581242.js]
|
||||
skip-if = e10s # Bug 930863 - pageshow issues ("TypeError: charset is undefined" in pageshow listener, as document is null)
|
||||
[browser_bug581253.js]
|
||||
|
@ -254,7 +253,6 @@ skip-if = e10s # Bug 516755 - SessionStore disabled for e10s (calls duplicateTab
|
|||
[browser_bug623155.js]
|
||||
skip-if = e10s # Bug ?????? - URLBar issues (apparently issues with redirection)
|
||||
[browser_bug623893.js]
|
||||
skip-if = e10s # Bug 916974 - Session history doesn't work in e10s
|
||||
[browser_bug624734.js]
|
||||
[browser_bug633691.js]
|
||||
skip-if = e10s # Bug ?????? - test directly manipulates content (eg, var expertDiv = gBrowser.contentDocument.getElementById("expertContent");)
|
||||
|
@ -272,6 +270,7 @@ skip-if = e10s # Bug ?????? - test directly manipulates content (doc.querySelect
|
|||
[browser_bug719271.js]
|
||||
skip-if = e10s # Bug 691614 - no e10s zoom support yet
|
||||
[browser_bug724239.js]
|
||||
skip-if = e10s # Bug 1077738
|
||||
[browser_bug734076.js]
|
||||
skip-if = e10s # Bug ?????? - test directly manipulates content
|
||||
[browser_bug735471.js]
|
||||
|
@ -287,7 +286,6 @@ skip-if = e10s # Bug ?????? - test directly manipulates content
|
|||
[browser_bug816527.js]
|
||||
skip-if = e10s # Bug 916974 - Session history doesn't work in e10s
|
||||
[browser_bug817947.js]
|
||||
skip-if = e10s # Bug 916974 - Session history doesn't work in e10s
|
||||
[browser_bug822367.js]
|
||||
[browser_bug832435.js]
|
||||
[browser_bug839103.js]
|
||||
|
@ -494,7 +492,6 @@ skip-if = e10s # Bug 516755 - SessionStore disabled for e10s
|
|||
[browser_registerProtocolHandler_notification.js]
|
||||
skip-if = e10s # Bug 940206 - nsIWebContentHandlerRegistrar::registerProtocolHandler doesn't work in e10s
|
||||
[browser_no_mcb_on_http_site.js]
|
||||
skip-if = e10s # Bug 516755 - SessionStore disabled for e10s
|
||||
[browser_bug1003461-switchtab-override.js]
|
||||
skip-if = e10s
|
||||
[browser_bug1024133-switchtab-override-keynav.js]
|
||||
|
|
|
@ -15,6 +15,8 @@ XPCOMUtils.defineLazyModuleGetter(this, "Task",
|
|||
"resource://gre/modules/Task.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "fxAccounts",
|
||||
"resource://gre/modules/FxAccounts.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
|
||||
"resource://gre/modules/FileUtils.jsm");
|
||||
|
||||
const CHROME_BASE = "chrome://mochitests/content/browser/browser/base/content/test/general/";
|
||||
// Preference helpers.
|
||||
|
@ -180,6 +182,107 @@ let gTests = [
|
|||
is(url, expected, "action=reauth got the expected URL");
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Test action=migrateToDevEdition (success)",
|
||||
teardown: function* () {
|
||||
gBrowser.removeCurrentTab();
|
||||
yield signOut();
|
||||
},
|
||||
run: function* ()
|
||||
{
|
||||
let fxAccountsCommon = {};
|
||||
Cu.import("resource://gre/modules/FxAccountsCommon.js", fxAccountsCommon);
|
||||
const pref = "identity.fxaccounts.migrateToDevEdition";
|
||||
changedPrefs.add(pref);
|
||||
Services.prefs.setBoolPref(pref, true);
|
||||
|
||||
// Create the signedInUser.json file that will be used as the source of
|
||||
// migrated user data.
|
||||
let signedInUser = {
|
||||
version: 1,
|
||||
accountData: {
|
||||
email: "foo@example.com",
|
||||
uid: "1234@lcip.org",
|
||||
sessionToken: "dead",
|
||||
verified: true
|
||||
}
|
||||
};
|
||||
// We use a sub-dir of the real profile dir as the "pretend" profile dir
|
||||
// for this test.
|
||||
let profD = Services.dirsvc.get("ProfD", Ci.nsIFile);
|
||||
let mockDir = profD.clone();
|
||||
mockDir.append("about-accounts-mock-profd");
|
||||
mockDir.createUnique(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
|
||||
let fxAccountsStorage = OS.Path.join(mockDir.path, fxAccountsCommon.DEFAULT_STORAGE_FILENAME);
|
||||
yield OS.File.writeAtomic(fxAccountsStorage, JSON.stringify(signedInUser));
|
||||
info("Wrote file " + fxAccountsStorage);
|
||||
|
||||
// this is a little subtle - we load about:blank so we get a tab, then
|
||||
// we send a message which does both (a) load the URL we want and (b) mocks
|
||||
// the default profile path used by about:accounts.
|
||||
let tab = yield promiseNewTabLoadEvent("about:blank?");
|
||||
let readyPromise = promiseOneMessage(tab, "test:load-with-mocked-profile-path-response");
|
||||
|
||||
let mm = tab.linkedBrowser.messageManager;
|
||||
mm.sendAsyncMessage("test:load-with-mocked-profile-path", {
|
||||
url: "about:accounts?action=migrateToDevEdition",
|
||||
profilePath: mockDir.path,
|
||||
});
|
||||
|
||||
let response = yield readyPromise;
|
||||
// We are expecting the iframe to be on the "force reauth" URL
|
||||
let expected = yield fxAccounts.promiseAccountsForceSigninURI();
|
||||
is(response.data.url, expected);
|
||||
|
||||
let userData = yield fxAccounts.getSignedInUser();
|
||||
SimpleTest.isDeeply(userData, signedInUser.accountData, "All account data were migrated");
|
||||
// The migration pref will have been switched off by now.
|
||||
is(Services.prefs.getBoolPref(pref), false, pref + " got the expected value");
|
||||
|
||||
yield OS.File.remove(fxAccountsStorage);
|
||||
yield OS.File.removeEmptyDir(mockDir.path);
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Test action=migrateToDevEdition (no user to migrate)",
|
||||
teardown: function* () {
|
||||
gBrowser.removeCurrentTab();
|
||||
yield signOut();
|
||||
},
|
||||
run: function* ()
|
||||
{
|
||||
const pref = "identity.fxaccounts.migrateToDevEdition";
|
||||
changedPrefs.add(pref);
|
||||
Services.prefs.setBoolPref(pref, true);
|
||||
|
||||
let profD = Services.dirsvc.get("ProfD", Ci.nsIFile);
|
||||
let mockDir = profD.clone();
|
||||
mockDir.append("about-accounts-mock-profd");
|
||||
mockDir.createUnique(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
|
||||
// but leave it empty, so we don't think a user is logged in.
|
||||
|
||||
let tab = yield promiseNewTabLoadEvent("about:blank?");
|
||||
let readyPromise = promiseOneMessage(tab, "test:load-with-mocked-profile-path-response");
|
||||
|
||||
let mm = tab.linkedBrowser.messageManager;
|
||||
mm.sendAsyncMessage("test:load-with-mocked-profile-path", {
|
||||
url: "about:accounts?action=migrateToDevEdition",
|
||||
profilePath: mockDir.path,
|
||||
});
|
||||
|
||||
let response = yield readyPromise;
|
||||
// We are expecting the iframe to be on the "signup" URL
|
||||
let expected = fxAccounts.getAccountsSignUpURI();
|
||||
is(response.data.url, expected);
|
||||
|
||||
// and expect no signed in user.
|
||||
let userData = yield fxAccounts.getSignedInUser();
|
||||
is(userData, null);
|
||||
// The migration pref should have still been switched off.
|
||||
is(Services.prefs.getBoolPref(pref), false, pref + " got the expected value");
|
||||
yield OS.File.removeEmptyDir(mockDir.path);
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Test observers about:accounts",
|
||||
teardown: function() {
|
||||
|
@ -323,5 +426,6 @@ function setSignedInUser(data) {
|
|||
}
|
||||
|
||||
function signOut() {
|
||||
return fxAccounts.signOut();
|
||||
// we always want a "localOnly" signout here...
|
||||
return fxAccounts.signOut(true);
|
||||
}
|
||||
|
|
|
@ -52,7 +52,8 @@ let check_history = Task.async(function*() {
|
|||
// Waits for a load and updates the known history
|
||||
let waitForLoad = Task.async(function*(uri) {
|
||||
info("Loading " + uri);
|
||||
gBrowser.loadURI(uri);
|
||||
// Longwinded but this ensures we don't just shortcut to LoadInNewProcess
|
||||
gBrowser.selectedBrowser.webNavigation.loadURI(uri, Ci.nsIWebNavigation.LOAD_FLAGS_NONE, null, null, null);
|
||||
|
||||
yield waitForDocLoadComplete();
|
||||
gExpectedHistory.index++;
|
||||
|
@ -78,7 +79,7 @@ let forward = Task.async(function*() {
|
|||
|
||||
// Tests that navigating from a page that should be in the remote process and
|
||||
// a page that should be in the main process works and retains history
|
||||
add_task(function*() {
|
||||
add_task(function* test_navigation() {
|
||||
SimpleTest.requestCompleteLog();
|
||||
|
||||
let remoting = Services.prefs.getBoolPref("browser.tabs.remote.autostart");
|
||||
|
@ -138,5 +139,59 @@ add_task(function*() {
|
|||
yield check_history();
|
||||
|
||||
info("9");
|
||||
yield back();
|
||||
is(gBrowser.selectedTab.getAttribute("remote"), "", "Remote attribute should be correct");
|
||||
is(gBrowser.selectedBrowser.permanentKey, permanentKey, "browser.permanentKey is still the same");
|
||||
yield check_history();
|
||||
|
||||
info("10");
|
||||
// Load a new remote page, this should replace the last history entry
|
||||
gExpectedHistory.entries.splice(gExpectedHistory.entries.length - 1, 1);
|
||||
yield waitForLoad("http://example.com/" + DUMMY_PATH);
|
||||
is(gBrowser.selectedTab.getAttribute("remote"), expectedRemote, "Remote attribute should be correct");
|
||||
is(gBrowser.selectedBrowser.permanentKey, permanentKey, "browser.permanentKey is still the same");
|
||||
yield check_history();
|
||||
|
||||
info("11");
|
||||
gBrowser.removeCurrentTab();
|
||||
});
|
||||
|
||||
// Tests that calling gBrowser.loadURI or browser.loadURI to load a page in a
|
||||
// different process updates the browser synchronously
|
||||
add_task(function* test_synchronous() {
|
||||
let remoting = Services.prefs.getBoolPref("browser.tabs.remote.autostart");
|
||||
let expectedRemote = remoting ? "true" : "";
|
||||
|
||||
info("1");
|
||||
// Create a tab and load a remote page in it
|
||||
gBrowser.selectedTab = gBrowser.addTab("about:blank", {skipAnimation: true});
|
||||
let {permanentKey} = gBrowser.selectedBrowser;
|
||||
yield waitForLoad("http://example.org/" + DUMMY_PATH);
|
||||
is(gBrowser.selectedTab.getAttribute("remote"), expectedRemote, "Remote attribute should be correct");
|
||||
is(gBrowser.selectedBrowser.permanentKey, permanentKey, "browser.permanentKey is still the same");
|
||||
|
||||
info("2");
|
||||
// Load another page
|
||||
info("Loading about:robots");
|
||||
gBrowser.selectedBrowser.loadURI("about:robots");
|
||||
is(gBrowser.selectedTab.getAttribute("remote"), "", "Remote attribute should be correct");
|
||||
is(gBrowser.selectedBrowser.permanentKey, permanentKey, "browser.permanentKey is still the same");
|
||||
|
||||
yield waitForDocLoadComplete();
|
||||
is(gBrowser.selectedTab.getAttribute("remote"), "", "Remote attribute should be correct");
|
||||
is(gBrowser.selectedBrowser.permanentKey, permanentKey, "browser.permanentKey is still the same");
|
||||
|
||||
info("3");
|
||||
// Load the remote page again
|
||||
info("Loading http://example.org/" + DUMMY_PATH);
|
||||
gBrowser.loadURI("http://example.org/" + DUMMY_PATH);
|
||||
is(gBrowser.selectedTab.getAttribute("remote"), expectedRemote, "Remote attribute should be correct");
|
||||
is(gBrowser.selectedBrowser.permanentKey, permanentKey, "browser.permanentKey is still the same");
|
||||
|
||||
yield waitForDocLoadComplete();
|
||||
is(gBrowser.selectedTab.getAttribute("remote"), expectedRemote, "Remote attribute should be correct");
|
||||
is(gBrowser.selectedBrowser.permanentKey, permanentKey, "browser.permanentKey is still the same");
|
||||
|
||||
info("4");
|
||||
gBrowser.removeCurrentTab();
|
||||
});
|
||||
|
|
|
@ -5,6 +5,8 @@
|
|||
// This file is loaded as a "content script" for browser_aboutAccounts tests
|
||||
"use strict";
|
||||
|
||||
const {interfaces: Ci, utils: Cu} = Components;
|
||||
|
||||
addEventListener("load", function load(event) {
|
||||
if (event.target != content.document) {
|
||||
return;
|
||||
|
@ -16,6 +18,10 @@ addEventListener("load", function load(event) {
|
|||
addEventListener("DOMContentLoaded", function domContentLoaded(event) {
|
||||
removeEventListener("DOMContentLoaded", domContentLoaded, true);
|
||||
let iframe = content.document.getElementById("remote");
|
||||
if (!iframe) {
|
||||
// at least one test initially loads about:blank - in that case, we are done.
|
||||
return;
|
||||
}
|
||||
iframe.addEventListener("load", function iframeLoaded(event) {
|
||||
if (iframe.contentWindow.location.href == "about:blank" ||
|
||||
event.target != iframe) {
|
||||
|
@ -46,3 +52,23 @@ addMessageListener("test:check-visibilities", function (message) {
|
|||
}
|
||||
sendAsyncMessage("test:check-visibilities-response", result);
|
||||
});
|
||||
|
||||
addMessageListener("test:load-with-mocked-profile-path", function (message) {
|
||||
addEventListener("DOMContentLoaded", function domContentLoaded(event) {
|
||||
removeEventListener("DOMContentLoaded", domContentLoaded, true);
|
||||
content.getDefaultProfilePath = () => message.data.profilePath;
|
||||
// now wait for the iframe to load.
|
||||
let iframe = content.document.getElementById("remote");
|
||||
iframe.addEventListener("load", function iframeLoaded(event) {
|
||||
if (iframe.contentWindow.location.href == "about:blank" ||
|
||||
event.target != iframe) {
|
||||
return;
|
||||
}
|
||||
iframe.removeEventListener("load", iframeLoaded, true);
|
||||
sendAsyncMessage("test:load-with-mocked-profile-path-response",
|
||||
{url: iframe.getAttribute("src")});
|
||||
}, true);
|
||||
});
|
||||
let webNav = docShell.QueryInterface(Ci.nsIWebNavigation);
|
||||
webNav.loadURI(message.data.url, webNav.LOAD_FLAGS_NONE, null, null, null);
|
||||
}, true);
|
||||
|
|
|
@ -38,6 +38,7 @@ const kPrefCustomizationAutoAdd = "browser.uiCustomization.autoAdd";
|
|||
const kPrefCustomizationDebug = "browser.uiCustomization.debug";
|
||||
const kPrefDrawInTitlebar = "browser.tabs.drawInTitlebar";
|
||||
const kPrefDeveditionTheme = "browser.devedition.theme.enabled";
|
||||
const kPrefWebIDEInNavbar = "devtools.webide.widget.inNavbarByDefault";
|
||||
|
||||
/**
|
||||
* The keys are the handlers that are fired when the event type (the value)
|
||||
|
@ -198,18 +199,24 @@ let CustomizableUIInternal = {
|
|||
}, true);
|
||||
PanelWideWidgetTracker.init();
|
||||
|
||||
let navbarPlacements = [
|
||||
"urlbar-container",
|
||||
"search-container",
|
||||
"bookmarks-menu-button",
|
||||
"downloads-button",
|
||||
"home-button",
|
||||
"loop-call-button",
|
||||
];
|
||||
|
||||
if (Services.prefs.getBoolPref(kPrefWebIDEInNavbar)) {
|
||||
navbarPlacements.push("webide-button");
|
||||
}
|
||||
|
||||
this.registerArea(CustomizableUI.AREA_NAVBAR, {
|
||||
legacy: true,
|
||||
type: CustomizableUI.TYPE_TOOLBAR,
|
||||
overflowable: true,
|
||||
defaultPlacements: [
|
||||
"urlbar-container",
|
||||
"search-container",
|
||||
"bookmarks-menu-button",
|
||||
"downloads-button",
|
||||
"home-button",
|
||||
"loop-call-button",
|
||||
],
|
||||
defaultPlacements: navbarPlacements,
|
||||
defaultCollapsed: false,
|
||||
}, true);
|
||||
#ifndef XP_MACOSX
|
||||
|
|
|
@ -48,7 +48,7 @@ const extractFieldsFromNode = function(fieldMap, node, ns = null, target = {}, w
|
|||
if (!nodeList[0].firstChild) {
|
||||
continue;
|
||||
}
|
||||
let value = nodeList[0].firstChild.nodeValue;
|
||||
let value = nodeList[0].textContent;
|
||||
target[field] = wrapInArray ? [value] : value;
|
||||
}
|
||||
}
|
||||
|
@ -168,8 +168,8 @@ this.GoogleImporter.prototype = {
|
|||
Task.spawn(function* () {
|
||||
let code = yield this._promiseAuthCode(windowRef);
|
||||
let tokenSet = yield this._promiseTokenSet(code);
|
||||
let contactEntries = yield this._promiseContactEntries(tokenSet);
|
||||
let {total, success, ids} = yield this._processContacts(contactEntries, db);
|
||||
let contactEntries = yield this._getContactEntries(tokenSet);
|
||||
let {total, success, ids} = yield this._processContacts(contactEntries, db, tokenSet);
|
||||
yield this._purgeContacts(ids, db);
|
||||
|
||||
return {
|
||||
|
@ -286,22 +286,12 @@ this.GoogleImporter.prototype = {
|
|||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Fetches all the contacts in a users' address book.
|
||||
*
|
||||
* @see https://developers.google.com/google-apps/contacts/v3/#retrieving_all_contacts
|
||||
*
|
||||
* @param {Object} tokenSet OAuth tokenset used to authenticate the request
|
||||
* @returns An `Error` object upon failure or an Array of contact XML nodes.
|
||||
*/
|
||||
_promiseContactEntries: function(tokenSet) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
_promiseRequestXML: function(URL, tokenSet) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let request = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]
|
||||
.createInstance(Ci.nsIXMLHttpRequest);
|
||||
|
||||
request.open("GET", getUrlParam("https://www.google.com/m8/feeds/contacts/default/full",
|
||||
"loop.oauth.google.getContactsURL",
|
||||
false) + "?max-results=" + kContactsMaxResults);
|
||||
request.open("GET", URL);
|
||||
|
||||
request.setRequestHeader("Content-Type", "application/xml; charset=utf-8");
|
||||
request.setRequestHeader("GData-Version", "3.0");
|
||||
|
@ -310,19 +300,17 @@ this.GoogleImporter.prototype = {
|
|||
request.onload = function() {
|
||||
if (request.status < 400) {
|
||||
let doc = request.responseXML;
|
||||
// First get the profile id.
|
||||
// First get the profile id, which is present in each XML request.
|
||||
let currNode = doc.documentElement.firstChild;
|
||||
while (currNode) {
|
||||
if (currNode.nodeType == 1 && currNode.localName == "id") {
|
||||
gProfileId = currNode.firstChild.nodeValue;
|
||||
gProfileId = currNode.textContent;
|
||||
break;
|
||||
}
|
||||
currNode = currNode.nextSibling;
|
||||
}
|
||||
|
||||
// Then kick of the importing of contact entries.
|
||||
let entries = Array.prototype.slice.call(doc.querySelectorAll("entry"));
|
||||
resolve(entries);
|
||||
resolve(doc);
|
||||
} else {
|
||||
reject(new Error(request.status + " " + request.statusText));
|
||||
}
|
||||
|
@ -336,6 +324,46 @@ this.GoogleImporter.prototype = {
|
|||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Fetches all the contacts in a users' address book.
|
||||
*
|
||||
* @see https://developers.google.com/google-apps/contacts/v3/#retrieving_all_contacts
|
||||
*
|
||||
* @param {Object} tokenSet OAuth tokenset used to authenticate the request
|
||||
* @returns An `Error` object upon failure or an Array of contact XML nodes.
|
||||
*/
|
||||
_getContactEntries: Task.async(function* (tokenSet) {
|
||||
let URL = getUrlParam("https://www.google.com/m8/feeds/contacts/default/full",
|
||||
"loop.oauth.google.getContactsURL",
|
||||
false) + "?max-results=" + kContactsMaxResults;
|
||||
let xmlDoc = yield this._promiseRequestXML(URL, tokenSet);
|
||||
// Then kick of the importing of contact entries.
|
||||
return Array.prototype.slice.call(xmlDoc.querySelectorAll("entry"));
|
||||
}),
|
||||
|
||||
/**
|
||||
* Fetches the default group from a users' address book, called 'Contacts'.
|
||||
*
|
||||
* @see https://developers.google.com/google-apps/contacts/v3/#retrieving_all_contact_groups
|
||||
*
|
||||
* @param {Object} tokenSet OAuth tokenset used to authenticate the request
|
||||
* @returns An `Error` object upon failure or the String group ID.
|
||||
*/
|
||||
_getContactsGroupId: Task.async(function* (tokenSet) {
|
||||
let URL = getUrlParam("https://www.google.com/m8/feeds/groups/default/full",
|
||||
"loop.oauth.google.getGroupsURL",
|
||||
false) + "?max-results=" + kContactsMaxResults;
|
||||
let xmlDoc = yield this._promiseRequestXML(URL, tokenSet);
|
||||
let contactsEntry = xmlDoc.querySelector("systemGroup[id=\"Contacts\"]");
|
||||
if (!contactsEntry) {
|
||||
throw new Error("Contacts group not present");
|
||||
}
|
||||
// Select the actual <entry> node, which is the parent of the <systemGroup>
|
||||
// node we just selected.
|
||||
contactsEntry = contactsEntry.parentNode;
|
||||
return contactsEntry.getElementsByTagName("id")[0].textContent;
|
||||
}),
|
||||
|
||||
/**
|
||||
* Process the contact XML nodes that Google provides, convert them to the MozContact
|
||||
* format, check if the contact already exists in the database and when it doesn't,
|
||||
|
@ -343,21 +371,28 @@ this.GoogleImporter.prototype = {
|
|||
* During this process statistics are collected about the amount of successful
|
||||
* imports. The consumer of this class may use these statistics to inform the
|
||||
* user.
|
||||
* Note: only contacts that are part of the 'Contacts' system group will be
|
||||
* imported.
|
||||
*
|
||||
* @param {Array} contactEntries List of XML DOMNodes contact entries.
|
||||
* @param {LoopContacts} db Instance of the LoopContacts database
|
||||
* object, which will store the newly found
|
||||
* contacts.
|
||||
* @param {Object} tokenSet OAuth tokenset used to authenticate a
|
||||
* request
|
||||
* @returns An `Error` object upon failure or an Object with statistics in the
|
||||
* following format: `{ total: 25, success: 13, ids: {} }`.
|
||||
*/
|
||||
_processContacts: Task.async(function* (contactEntries, db) {
|
||||
_processContacts: Task.async(function* (contactEntries, db, tokenSet) {
|
||||
let stats = {
|
||||
total: contactEntries.length,
|
||||
success: 0,
|
||||
ids: {}
|
||||
};
|
||||
|
||||
// Contacts that are _not_ part of the 'Contacts' group will be ignored.
|
||||
let contactsGroupId = yield this._getContactsGroupId(tokenSet);
|
||||
|
||||
for (let entry of contactEntries) {
|
||||
let contact = this._processContactFields(entry);
|
||||
|
||||
|
@ -367,6 +402,12 @@ this.GoogleImporter.prototype = {
|
|||
yield db.promise("remove", existing._guid);
|
||||
}
|
||||
|
||||
// After contact removal, check if the entry is part of the correct group.
|
||||
if (!entry.querySelector("groupMembershipInfo[deleted=\"false\"][href=\"" +
|
||||
contactsGroupId + "\"]")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// If the contact contains neither email nor phone number, then it is not
|
||||
// useful in the Loop address book: do not add.
|
||||
if (!("email" in contact) && !("tel" in contact)) {
|
||||
|
@ -450,7 +491,7 @@ this.GoogleImporter.prototype = {
|
|||
for (let [,phoneNode] of Iterator(phoneNodes)) {
|
||||
let phoneNumber = phoneNode.hasAttribute("uri") ?
|
||||
phoneNode.getAttribute("uri").replace("tel:", "") :
|
||||
phoneNode.firstChild.nodeValue;
|
||||
phoneNode.textContent;
|
||||
contact.tel.push({
|
||||
pref: (phoneNode.getAttribute("primary") == "true"),
|
||||
type: [getFieldType(phoneNode)],
|
||||
|
@ -466,8 +507,8 @@ this.GoogleImporter.prototype = {
|
|||
for (let [,orgNode] of Iterator(orgNodes)) {
|
||||
let orgElement = orgNode.getElementsByTagNameNS(kNS_GD, "orgName")[0];
|
||||
let titleElement = orgNode.getElementsByTagNameNS(kNS_GD, "orgTitle")[0];
|
||||
contact.org.push(orgElement ? orgElement.firstChild.nodeValue : "")
|
||||
contact.jobTitle.push(titleElement ? titleElement.firstChild.nodeValue : "");
|
||||
contact.org.push(orgElement ? orgElement.textContent : "")
|
||||
contact.jobTitle.push(titleElement ? titleElement.textContent : "");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -68,7 +68,7 @@ let LoopRoomsInternal = {
|
|||
}
|
||||
|
||||
Task.spawn(function* () {
|
||||
yield MozLoopService.register();
|
||||
yield MozLoopService.promiseRegisteredWithServers();
|
||||
|
||||
if (!gDirty) {
|
||||
callback(null, [...this.rooms.values()]);
|
||||
|
|
|
@ -369,7 +369,7 @@ function injectLoopAPI(targetWindow) {
|
|||
value: function(callback) {
|
||||
// We translate from a promise to a callback, as we can't pass promises from
|
||||
// Promise.jsm across the priv versus unpriv boundary.
|
||||
MozLoopService.register().then(() => {
|
||||
MozLoopService.promiseRegisteredWithServers().then(() => {
|
||||
callback(null);
|
||||
}, err => {
|
||||
callback(cloneValueInto(err, targetWindow));
|
||||
|
|
|
@ -30,6 +30,7 @@ Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
|||
Cu.import("resource://gre/modules/Promise.jsm");
|
||||
Cu.import("resource://gre/modules/osfile.jsm", this);
|
||||
Cu.import("resource://gre/modules/Task.jsm");
|
||||
Cu.import("resource://gre/modules/Timer.jsm");
|
||||
Cu.import("resource://gre/modules/FxAccountsOAuthClient.jsm");
|
||||
|
||||
Cu.importGlobalProperties(["URL"]);
|
||||
|
@ -111,7 +112,6 @@ function getJSONPref(aName) {
|
|||
let gRegisteredDeferred = null;
|
||||
let gHawkClient = null;
|
||||
let gLocalizedStrings = null;
|
||||
let gInitializeTimer = null;
|
||||
let gFxAEnabled = true;
|
||||
let gFxAOAuthClientPromise = null;
|
||||
let gFxAOAuthClient = null;
|
||||
|
@ -309,7 +309,7 @@ let MozLoopServiceInternal = {
|
|||
|
||||
/**
|
||||
* Starts registration of Loop with the push server, and then will register
|
||||
* with the Loop server. It will return early if already registered.
|
||||
* with the Loop server as a GUEST. It will return early if already registered.
|
||||
*
|
||||
* @returns {Promise} a promise that is resolved with no params on completion, or
|
||||
* rejected with an error code or string.
|
||||
|
@ -871,42 +871,17 @@ let MozLoopServiceInternal = {
|
|||
};
|
||||
Object.freeze(MozLoopServiceInternal);
|
||||
|
||||
|
||||
let gInitializeTimerFunc = (deferredInitialization) => {
|
||||
// Kick off the push notification service into registering after a timeout.
|
||||
// This ensures we're not doing too much straight after the browser's finished
|
||||
// starting up.
|
||||
gInitializeTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
|
||||
gInitializeTimer.initWithCallback(Task.async(function* initializationCallback() {
|
||||
yield MozLoopService.register().then(Task.async(function*() {
|
||||
if (!MozLoopServiceInternal.fxAOAuthTokenData) {
|
||||
log.debug("MozLoopService: Initialized without an already logged-in account");
|
||||
deferredInitialization.resolve("initialized to guest status");
|
||||
return;
|
||||
}
|
||||
|
||||
log.debug("MozLoopService: Initializing with already logged-in account");
|
||||
let registeredPromise =
|
||||
MozLoopServiceInternal.registerWithLoopServer(
|
||||
LOOP_SESSION_TYPE.FXA, {
|
||||
calls: MozLoopServiceInternal.pushHandler.registeredChannels[MozLoopService.channelIDs.callsFxA],
|
||||
rooms: MozLoopServiceInternal.pushHandler.registeredChannels[MozLoopService.channelIDs.roomsFxA]
|
||||
});
|
||||
registeredPromise.then(() => {
|
||||
deferredInitialization.resolve("initialized to logged-in status");
|
||||
}, error => {
|
||||
log.debug("MozLoopService: error logging in using cached auth token");
|
||||
MozLoopServiceInternal.setError("login", error);
|
||||
deferredInitialization.reject("error logging in using cached auth token");
|
||||
});
|
||||
}), error => {
|
||||
log.debug("MozLoopService: Failure of initial registration", error);
|
||||
deferredInitialization.reject(error);
|
||||
});
|
||||
gInitializeTimer = null;
|
||||
}),
|
||||
MozLoopServiceInternal.initialRegistrationDelayMilliseconds, Ci.nsITimer.TYPE_ONE_SHOT);
|
||||
setTimeout(MozLoopService.delayedInitialize.bind(MozLoopService, deferredInitialization),
|
||||
MozLoopServiceInternal.initialRegistrationDelayMilliseconds);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Public API
|
||||
*/
|
||||
|
@ -962,13 +937,60 @@ this.MozLoopService = {
|
|||
let deferredInitialization = Promise.defer();
|
||||
gInitializeTimerFunc(deferredInitialization);
|
||||
|
||||
return deferredInitialization.promise.catch(error => {
|
||||
return deferredInitialization.promise;
|
||||
}),
|
||||
|
||||
/**
|
||||
* The core of the initialization work that happens once the browser is ready
|
||||
* (after a timer when called during startup).
|
||||
*
|
||||
* Can be called more than once (e.g. if the initial setup fails at some phase).
|
||||
* @param {Deferred} deferredInitialization
|
||||
*/
|
||||
delayedInitialize: Task.async(function*(deferredInitialization) {
|
||||
// Set or clear an error depending on how deferredInitialization gets resolved.
|
||||
// We do this first so that it can handle the early returns below.
|
||||
let completedPromise = deferredInitialization.promise.then(result => {
|
||||
MozLoopServiceInternal.clearError("initialization");
|
||||
return result;
|
||||
},
|
||||
error => {
|
||||
// If we get a non-object then setError was already called for a different error type.
|
||||
if (typeof(error) == "object") {
|
||||
// This never gets cleared since there is no UI to recover. Only restarting will work.
|
||||
MozLoopServiceInternal.setError("initialization", error);
|
||||
}
|
||||
throw error;
|
||||
});
|
||||
|
||||
try {
|
||||
yield this.promiseRegisteredWithServers();
|
||||
} catch (ex) {
|
||||
log.debug("MozLoopService: Failure of initial registration", ex);
|
||||
deferredInitialization.reject(ex);
|
||||
yield completedPromise;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!MozLoopServiceInternal.fxAOAuthTokenData) {
|
||||
log.debug("MozLoopService: Initialized without an already logged-in account");
|
||||
deferredInitialization.resolve("initialized to guest status");
|
||||
yield completedPromise;
|
||||
return;
|
||||
}
|
||||
|
||||
log.debug("MozLoopService: Initializing with already logged-in account");
|
||||
let pushURLs = {
|
||||
calls: MozLoopServiceInternal.pushHandler.registeredChannels[this.channelIDs.callsFxA],
|
||||
rooms: MozLoopServiceInternal.pushHandler.registeredChannels[this.channelIDs.roomsFxA]
|
||||
};
|
||||
|
||||
MozLoopServiceInternal.registerWithLoopServer(LOOP_SESSION_TYPE.FXA, pushURLs).then(() => {
|
||||
deferredInitialization.resolve("initialized to logged-in status");
|
||||
}, error => {
|
||||
log.debug("MozLoopService: error logging in using cached auth token");
|
||||
MozLoopServiceInternal.setError("login", error);
|
||||
deferredInitialization.reject("error logging in using cached auth token");
|
||||
});
|
||||
yield completedPromise;
|
||||
}),
|
||||
|
||||
/**
|
||||
|
@ -1081,25 +1103,10 @@ this.MozLoopService = {
|
|||
Services.tm.mainThread);
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Starts registration of Loop with the push server, and then will register
|
||||
* with the Loop server. It will return early if already registered.
|
||||
*
|
||||
* @returns {Promise} a promise that is resolved with no params on completion, or
|
||||
* rejected with an error code or string.
|
||||
* @see MozLoopServiceInternal.promiseRegisteredWithServers
|
||||
*/
|
||||
register: function() {
|
||||
log.debug("registering");
|
||||
// Don't do anything if loop is not enabled.
|
||||
if (!Services.prefs.getBoolPref("loop.enabled")) {
|
||||
throw new Error("Loop is not enabled");
|
||||
}
|
||||
|
||||
if (Services.prefs.getBoolPref("loop.throttled")) {
|
||||
throw new Error("Loop is disabled by the soft-start mechanism");
|
||||
}
|
||||
|
||||
promiseRegisteredWithServers: function() {
|
||||
return MozLoopServiceInternal.promiseRegisteredWithServers();
|
||||
},
|
||||
|
||||
|
|
|
@ -471,8 +471,13 @@ loop.panel = (function(_, mozL10n) {
|
|||
room: React.PropTypes.instanceOf(loop.store.Room).isRequired
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
return { urlCopied: false };
|
||||
},
|
||||
|
||||
shouldComponentUpdate: function(nextProps, nextState) {
|
||||
return nextProps.room.ctime > this.props.room.ctime;
|
||||
return (nextProps.room.ctime > this.props.room.ctime) ||
|
||||
(nextState.urlCopied !== this.state.urlCopied);
|
||||
},
|
||||
|
||||
handleClickRoom: function(event) {
|
||||
|
@ -480,6 +485,16 @@ loop.panel = (function(_, mozL10n) {
|
|||
this.props.openRoom(this.props.room);
|
||||
},
|
||||
|
||||
handleCopyButtonClick: function(event) {
|
||||
event.preventDefault();
|
||||
navigator.mozLoop.copyString(this.props.room.roomUrl);
|
||||
this.setState({urlCopied: true});
|
||||
},
|
||||
|
||||
handleMouseLeave: function(event) {
|
||||
this.setState({urlCopied: false});
|
||||
},
|
||||
|
||||
_isActive: function() {
|
||||
// XXX bug 1074679 will implement this properly
|
||||
return this.props.room.currSize > 0;
|
||||
|
@ -491,12 +506,18 @@ loop.panel = (function(_, mozL10n) {
|
|||
"room-entry": true,
|
||||
"room-active": this._isActive()
|
||||
});
|
||||
var copyButtonClasses = React.addons.classSet({
|
||||
'copy-link': true,
|
||||
'checked': this.state.urlCopied
|
||||
});
|
||||
|
||||
return (
|
||||
React.DOM.div({className: roomClasses},
|
||||
React.DOM.div({className: roomClasses, onMouseLeave: this.handleMouseLeave},
|
||||
React.DOM.h2(null,
|
||||
React.DOM.span({className: "room-notification"}),
|
||||
room.roomName
|
||||
room.roomName,
|
||||
React.DOM.button({className: copyButtonClasses,
|
||||
onClick: this.handleCopyButtonClick})
|
||||
),
|
||||
React.DOM.p(null,
|
||||
React.DOM.a({ref: "room", href: "#", onClick: this.handleClickRoom},
|
||||
|
@ -762,6 +783,7 @@ loop.panel = (function(_, mozL10n) {
|
|||
AvailabilityDropdown: AvailabilityDropdown,
|
||||
CallUrlResult: CallUrlResult,
|
||||
PanelView: PanelView,
|
||||
RoomEntry: RoomEntry,
|
||||
RoomList: RoomList,
|
||||
SettingsDropdown: SettingsDropdown,
|
||||
ToSView: ToSView
|
||||
|
|
|
@ -471,8 +471,13 @@ loop.panel = (function(_, mozL10n) {
|
|||
room: React.PropTypes.instanceOf(loop.store.Room).isRequired
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
return { urlCopied: false };
|
||||
},
|
||||
|
||||
shouldComponentUpdate: function(nextProps, nextState) {
|
||||
return nextProps.room.ctime > this.props.room.ctime;
|
||||
return (nextProps.room.ctime > this.props.room.ctime) ||
|
||||
(nextState.urlCopied !== this.state.urlCopied);
|
||||
},
|
||||
|
||||
handleClickRoom: function(event) {
|
||||
|
@ -480,6 +485,16 @@ loop.panel = (function(_, mozL10n) {
|
|||
this.props.openRoom(this.props.room);
|
||||
},
|
||||
|
||||
handleCopyButtonClick: function(event) {
|
||||
event.preventDefault();
|
||||
navigator.mozLoop.copyString(this.props.room.roomUrl);
|
||||
this.setState({urlCopied: true});
|
||||
},
|
||||
|
||||
handleMouseLeave: function(event) {
|
||||
this.setState({urlCopied: false});
|
||||
},
|
||||
|
||||
_isActive: function() {
|
||||
// XXX bug 1074679 will implement this properly
|
||||
return this.props.room.currSize > 0;
|
||||
|
@ -491,12 +506,18 @@ loop.panel = (function(_, mozL10n) {
|
|||
"room-entry": true,
|
||||
"room-active": this._isActive()
|
||||
});
|
||||
var copyButtonClasses = React.addons.classSet({
|
||||
'copy-link': true,
|
||||
'checked': this.state.urlCopied
|
||||
});
|
||||
|
||||
return (
|
||||
<div className={roomClasses}>
|
||||
<div className={roomClasses} onMouseLeave={this.handleMouseLeave}>
|
||||
<h2>
|
||||
<span className="room-notification" />
|
||||
{room.roomName}
|
||||
{room.roomName}
|
||||
<button className={copyButtonClasses}
|
||||
onClick={this.handleCopyButtonClick}/>
|
||||
</h2>
|
||||
<p>
|
||||
<a ref="room" href="#" onClick={this.handleClickRoom}>
|
||||
|
@ -762,6 +783,7 @@ loop.panel = (function(_, mozL10n) {
|
|||
AvailabilityDropdown: AvailabilityDropdown,
|
||||
CallUrlResult: CallUrlResult,
|
||||
PanelView: PanelView,
|
||||
RoomEntry: RoomEntry,
|
||||
RoomList: RoomList,
|
||||
SettingsDropdown: SettingsDropdown,
|
||||
ToSView: ToSView
|
||||
|
|
|
@ -79,8 +79,8 @@
|
|||
|
||||
.fx-embedded-answer-btn-text {
|
||||
vertical-align: bottom;
|
||||
/* don't stretch the button if the localized text is too big */
|
||||
max-width: 80%;
|
||||
/* always leave space for the icon (width and margin) */
|
||||
max-width: calc(100% - .8rem - .2rem);
|
||||
}
|
||||
|
||||
.fx-embedded-btn-icon-video,
|
||||
|
@ -91,6 +91,7 @@
|
|||
height: .8rem;
|
||||
background-repeat: no-repeat;
|
||||
cursor: pointer;
|
||||
-moz-margin-start: .2rem;
|
||||
}
|
||||
|
||||
.fx-embedded-btn-icon-video,
|
||||
|
|
|
@ -202,6 +202,49 @@ body {
|
|||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.room-list > .room-entry > h2 > .copy-link {
|
||||
display: inline-block;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border: none;
|
||||
margin: .1em .5em; /* relative to _this_ line's font, not the document's */
|
||||
background-color: transparent; /* override browser default for button tags */
|
||||
}
|
||||
|
||||
@keyframes drop-and-fade-in {
|
||||
from { opacity: 0; transform: translateY(-10px); }
|
||||
to { opacity: 100; transform: translateY(0); }
|
||||
}
|
||||
|
||||
.room-list > .room-entry:hover > h2 > .copy-link {
|
||||
background: transparent url(../img/svg/copy-16x16.svg);
|
||||
cursor: pointer;
|
||||
animation: drop-and-fade-in 0.4s;
|
||||
animation-fill-mode: forwards;
|
||||
}
|
||||
|
||||
/* scale this up to 1.1x and then back to the original size */
|
||||
@keyframes pulse {
|
||||
0%, 100% { transform: scale(1.0); }
|
||||
50% { transform: scale(1.1); }
|
||||
}
|
||||
|
||||
.room-list > .room-entry > h2 > .copy-link.checked {
|
||||
background: transparent url(../img/svg/checkmark-16x16.svg);
|
||||
animation: pulse .250s;
|
||||
animation-timing-function: ease-in-out;
|
||||
}
|
||||
|
||||
.room-list > .room-entry > h2 {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
/* keep the various room-entry row pieces aligned with each other */
|
||||
.room-list > .room-entry > h2 > button,
|
||||
.room-list > .room-entry > h2 > span {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
/* Buttons */
|
||||
|
||||
.button-group {
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 18.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 16 16" enable-background="new 0 0 16 16" xml:space="preserve">
|
||||
<circle fill-rule="evenodd" clip-rule="evenodd" fill="#0096DD" cx="8"
|
||||
cy="8" r="8"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" fill="#FFFFFF"
|
||||
d="M7.236,12L12,5.007L10.956,4L7.224,9.465l-2.14-2.326L4,8.146L7.236,12z"/>
|
||||
</svg>
|
После Ширина: | Высота: | Размер: 597 B |
|
@ -0,0 +1,28 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 18.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 16 16" enable-background="new 0 0 16 16" xml:space="preserve">
|
||||
<circle fill-rule="evenodd" clip-rule="evenodd" fill="#0096DD" cx="8" cy="8"
|
||||
r="8"/>
|
||||
<g>
|
||||
<g>
|
||||
<g>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" fill="none"
|
||||
stroke="#FFFFFF" stroke-width="0.75" stroke-miterlimit="10"
|
||||
d="M10.815,6.286H7.556c-0.164,0-0.296,0.128-0.296,0.286v5.143C7.259,11.872,7.392,12,7.556,12h4.148
|
||||
C11.867,12,12,11.872,12,11.714V7.429L10.815,6.286z
|
||||
M8.741,6.275V5.143L7.556,4H7.528C6.509,4,4.593,4,4.593,4H4.296
|
||||
C4.133,4,4,4.128,4,4.286v5.143c0,0.158,0.133,0.286,0.296,0.286H7.25V6.561c0-0.158,0.133-0.286,0.296-0.286H8.741z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<polygon fill-rule="evenodd" clip-rule="evenodd"
|
||||
fill="#FFFFFF" points="10.222,8 10.222,6.857 11.407,8"/>
|
||||
</g>
|
||||
<g>
|
||||
<polygon fill-rule="evenodd" clip-rule="evenodd" fill="#FFFFFF"
|
||||
points="6.963,5.714 6.963,4.571 8.148,5.714"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
После Ширина: | Высота: | Размер: 1.3 KiB |
|
@ -48,6 +48,8 @@ browser.jar:
|
|||
content/browser/loop/shared/img/svg/glyph-account-16x16.svg (content/shared/img/svg/glyph-account-16x16.svg)
|
||||
content/browser/loop/shared/img/svg/glyph-signin-16x16.svg (content/shared/img/svg/glyph-signin-16x16.svg)
|
||||
content/browser/loop/shared/img/svg/glyph-signout-16x16.svg (content/shared/img/svg/glyph-signout-16x16.svg)
|
||||
content/browser/loop/shared/img/svg/copy-16x16.svg (content/shared/img/svg/copy-16x16.svg)
|
||||
content/browser/loop/shared/img/svg/checkmark-16x16.svg (content/shared/img/svg/checkmark-16x16.svg)
|
||||
content/browser/loop/shared/img/audio-call-avatar.svg (content/shared/img/audio-call-avatar.svg)
|
||||
content/browser/loop/shared/img/beta-ribbon.svg (content/shared/img/beta-ribbon.svg)
|
||||
content/browser/loop/shared/img/icons-10x10.svg (content/shared/img/icons-10x10.svg)
|
||||
|
|
|
@ -264,7 +264,6 @@ loop.webapp = (function($, _, OT, mozL10n) {
|
|||
var PendingConversationView = React.createClass({displayName: 'PendingConversationView',
|
||||
mixins: [sharedMixins.AudioMixin],
|
||||
|
||||
|
||||
getInitialState: function() {
|
||||
return {
|
||||
callState: "connecting"
|
||||
|
@ -571,6 +570,12 @@ loop.webapp = (function($, _, OT, mozL10n) {
|
|||
});
|
||||
|
||||
var FailedConversationView = React.createClass({displayName: 'FailedConversationView',
|
||||
mixins: [sharedMixins.AudioMixin],
|
||||
|
||||
componentDidMount: function() {
|
||||
this.play("failure");
|
||||
},
|
||||
|
||||
render: function() {
|
||||
document.title = mozL10n.get("standalone_title_with_status",
|
||||
{clientShortname: mozL10n.get("clientShortname2"),
|
||||
|
|
|
@ -264,7 +264,6 @@ loop.webapp = (function($, _, OT, mozL10n) {
|
|||
var PendingConversationView = React.createClass({
|
||||
mixins: [sharedMixins.AudioMixin],
|
||||
|
||||
|
||||
getInitialState: function() {
|
||||
return {
|
||||
callState: "connecting"
|
||||
|
@ -571,6 +570,12 @@ loop.webapp = (function($, _, OT, mozL10n) {
|
|||
});
|
||||
|
||||
var FailedConversationView = React.createClass({
|
||||
mixins: [sharedMixins.AudioMixin],
|
||||
|
||||
componentDidMount: function() {
|
||||
this.play("failure");
|
||||
},
|
||||
|
||||
render: function() {
|
||||
document.title = mozL10n.get("standalone_title_with_status",
|
||||
{clientShortname: mozL10n.get("clientShortname2"),
|
||||
|
|
|
@ -637,6 +637,72 @@ describe("loop.panel", function() {
|
|||
});
|
||||
});
|
||||
|
||||
describe("loop.panel.RoomEntry", function() {
|
||||
var buttonNode, roomData, roomEntry, roomStore, dispatcher;
|
||||
|
||||
beforeEach(function() {
|
||||
dispatcher = new loop.Dispatcher();
|
||||
roomData = {
|
||||
roomToken: "QzBbvGmIZWU",
|
||||
roomUrl: "http://sample/QzBbvGmIZWU",
|
||||
roomName: "Second Room Name",
|
||||
maxSize: 2,
|
||||
participants: [
|
||||
{ displayName: "Alexis", account: "alexis@example.com",
|
||||
roomConnectionId: "2a1787a6-4a73-43b5-ae3e-906ec1e763cb" },
|
||||
{ displayName: "Adam",
|
||||
roomConnectionId: "781f012b-f1ea-4ce1-9105-7cfc36fb4ec7" }
|
||||
],
|
||||
ctime: 1405517418
|
||||
};
|
||||
roomStore = new loop.store.Room(roomData);
|
||||
roomEntry = mountRoomEntry();
|
||||
buttonNode = roomEntry.getDOMNode().querySelector("button.copy-link");
|
||||
});
|
||||
|
||||
function mountRoomEntry() {
|
||||
return TestUtils.renderIntoDocument(loop.panel.RoomEntry({
|
||||
openRoom: sandbox.stub(),
|
||||
room: roomStore
|
||||
}));
|
||||
}
|
||||
|
||||
it("should not display copy-link button by default", function() {
|
||||
expect(buttonNode).to.not.equal(null);
|
||||
});
|
||||
|
||||
it("should copy the URL when the click event fires", function() {
|
||||
TestUtils.Simulate.click(buttonNode);
|
||||
|
||||
sinon.assert.calledOnce(navigator.mozLoop.copyString);
|
||||
sinon.assert.calledWithExactly(navigator.mozLoop.copyString,
|
||||
roomData.roomUrl);
|
||||
});
|
||||
|
||||
it("should set state.urlCopied when the click event fires", function() {
|
||||
TestUtils.Simulate.click(buttonNode);
|
||||
|
||||
expect(roomEntry.state.urlCopied).to.equal(true);
|
||||
});
|
||||
|
||||
it("should switch to displaying a check icon when the URL has been copied",
|
||||
function() {
|
||||
TestUtils.Simulate.click(buttonNode);
|
||||
|
||||
expect(buttonNode.classList.contains("checked")).eql(true);
|
||||
});
|
||||
|
||||
it("should not display a check icon after mouse leaves the entry",
|
||||
function() {
|
||||
var roomNode = roomEntry.getDOMNode();
|
||||
TestUtils.Simulate.click(buttonNode);
|
||||
|
||||
TestUtils.SimulateNative.mouseOut(roomNode);
|
||||
|
||||
expect(buttonNode.classList.contains("checked")).eql(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("loop.panel.RoomList", function() {
|
||||
var roomListStore, dispatcher;
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
support-files =
|
||||
fixtures/google_auth.txt
|
||||
fixtures/google_contacts.txt
|
||||
fixtures/google_groups.txt
|
||||
fixtures/google_token.txt
|
||||
google_service.sjs
|
||||
head.js
|
||||
|
|
|
@ -17,23 +17,22 @@ function promiseImport() {
|
|||
});
|
||||
}
|
||||
|
||||
const kContactsCount = 7;
|
||||
const kIncomingTotalContactsCount = 8;
|
||||
const kExpectedImportCount = 7;
|
||||
|
||||
add_task(function* test_GoogleImport() {
|
||||
let stats;
|
||||
// An error may throw and the test will fail when that happens.
|
||||
stats = yield promiseImport();
|
||||
|
||||
let contactsCount = mockDb.size;
|
||||
|
||||
// Assert the world.
|
||||
Assert.equal(stats.total, contactsCount, "Five contacts should get processed");
|
||||
Assert.equal(stats.success, contactsCount, "Five contacts should be imported");
|
||||
Assert.equal(stats.total, kIncomingTotalContactsCount, kIncomingTotalContactsCount + " contacts should get processed");
|
||||
Assert.equal(stats.success, kExpectedImportCount, kExpectedImportCount + " contacts should be imported");
|
||||
|
||||
yield promiseImport();
|
||||
Assert.equal(Object.keys(mockDb._store).length, contactsCount, "Database should be the same size after reimport");
|
||||
Assert.equal(mockDb.size, kExpectedImportCount, "Database should be the same size after reimport");
|
||||
|
||||
let currentContact = contactsCount;
|
||||
let currentContact = kExpectedImportCount;
|
||||
|
||||
let c = mockDb._store[mockDb._next_guid - currentContact];
|
||||
Assert.equal(c.name[0], "John Smith", "Full name should match");
|
||||
|
@ -96,4 +95,7 @@ add_task(function* test_GoogleImport() {
|
|||
Assert.equal(c.tel[0].pref, false, "Pref should match");
|
||||
Assert.equal(c.category[0], "google", "Category should match");
|
||||
Assert.equal(c.id, "http://www.google.com/m8/feeds/contacts/tester%40mochi.com/base/6", "UID should match and be scoped to provider");
|
||||
|
||||
c = yield mockDb.promise("getByServiceId", "http://www.google.com/m8/feeds/contacts/tester%40mochi.com/base/9");
|
||||
Assert.equal(c, null, "Contacts that are not part of the default group should not be imported");
|
||||
});
|
||||
|
|
|
@ -253,7 +253,7 @@ add_task(function* basicAuthorizationAndRegistration() {
|
|||
mockPushHandler.registrationPushURL = "https://localhost/pushUrl/guest";
|
||||
// Notification observed due to the error being cleared upon successful registration.
|
||||
let statusChangedPromise = promiseObserverNotified("loop-status-changed");
|
||||
yield MozLoopService.register();
|
||||
yield MozLoopService.promiseRegisteredWithServers();
|
||||
yield statusChangedPromise;
|
||||
|
||||
// Normally the same pushUrl would be registered but we change it in the test
|
||||
|
@ -318,7 +318,7 @@ add_task(function* loginWithParams401() {
|
|||
test_error: "params_401",
|
||||
};
|
||||
yield promiseOAuthParamsSetup(BASE_URL, params);
|
||||
yield MozLoopService.register();
|
||||
yield MozLoopService.promiseRegisteredWithServers();
|
||||
|
||||
let loginPromise = MozLoopService.logInToFxA();
|
||||
yield loginPromise.then(tokenData => {
|
||||
|
|
|
@ -34,6 +34,7 @@
|
|||
</gd:name>
|
||||
<gd:email address="john.smith@example.com" primary="true" rel="http://schemas.google.com/g/2005#other"/>
|
||||
<gContact:website href="http://www.google.com/profiles/109576547678240773721" rel="profile"/>
|
||||
<gContact:groupMembershipInfo deleted="false" href="http://www.google.com/m8/feeds/groups/tester%40mochi.com/base/6"/>
|
||||
</entry>
|
||||
<entry gd:etag=""R3YyejRVLit7I2A9WhJWEkkNQwc."">
|
||||
<id>http://www.google.com/m8/feeds/contacts/tester%40mochi.com/base/1</id>
|
||||
|
@ -51,6 +52,7 @@
|
|||
</gd:name>
|
||||
<gd:email address="jane.smith@example.com" primary="true" rel="http://schemas.google.com/g/2005#other"/>
|
||||
<gContact:website href="http://www.google.com/profiles/112886528199784431028" rel="profile"/>
|
||||
<gContact:groupMembershipInfo deleted="false" href="http://www.google.com/m8/feeds/groups/tester%40mochi.com/base/6"/>
|
||||
</entry>
|
||||
<entry gd:etag=""R3YyejRVLit7I2A9WhJWEkkNQwc."">
|
||||
<id>http://www.google.com/m8/feeds/contacts/tester%40mochi.com/base/2</id>
|
||||
|
@ -68,6 +70,7 @@
|
|||
</gd:name>
|
||||
<gd:email address="davy.jones@example.com" primary="true" rel="http://schemas.google.com/g/2005#other"/>
|
||||
<gContact:website href="http://www.google.com/profiles/109710625881478599011" rel="profile"/>
|
||||
<gContact:groupMembershipInfo deleted="false" href="http://www.google.com/m8/feeds/groups/tester%40mochi.com/base/6"/>
|
||||
</entry>
|
||||
<entry gd:etag=""Q3w7ezVSLit7I2A9WB5WGUkNRgE."">
|
||||
<id>http://www.google.com/m8/feeds/contacts/tester%40mochi.com/base/3</id>
|
||||
|
@ -79,6 +82,7 @@
|
|||
<link href="https://www.google.com/m8/feeds/contacts/tester%40mochi.com/full/3" rel="self" type="application/atom+xml"/>
|
||||
<link href="https://www.google.com/m8/feeds/contacts/tester%40mochi.com/full/3" rel="edit" type="application/atom+xml"/>
|
||||
<gd:email address="noname@example.com" primary="true" rel="http://schemas.google.com/g/2005#other"/>
|
||||
<gContact:groupMembershipInfo deleted="false" href="http://www.google.com/m8/feeds/groups/tester%40mochi.com/base/6"/>
|
||||
</entry>
|
||||
<entry gd:etag=""Q3w7ezVSLit7I2A9WB5WGUkNRgE."">
|
||||
<id>http://www.google.com/m8/feeds/contacts/tester%40mochi.com/base/7</id>
|
||||
|
@ -90,6 +94,7 @@
|
|||
<link href="https://www.google.com/m8/feeds/contacts/tester%40mochi.com/full/7" rel="self" type="application/atom+xml"/>
|
||||
<link href="https://www.google.com/m8/feeds/contacts/tester%40mochi.com/full/7" rel="edit" type="application/atom+xml"/>
|
||||
<gd:email address="lycnix" primary="true" rel="http://schemas.google.com/g/2005#other"/>
|
||||
<gContact:groupMembershipInfo deleted="false" href="http://www.google.com/m8/feeds/groups/tester%40mochi.com/base/6"/>
|
||||
</entry>
|
||||
<entry gd:etag=""RXkzfjVSLit7I2A9XRdRGUgITgA."">
|
||||
<id>http://www.google.com/m8/feeds/contacts/tester%40mochi.com/base/8</id>
|
||||
|
@ -101,6 +106,7 @@
|
|||
<link href="https://www.google.com/m8/feeds/contacts/tester%40mochi.com/full/8" rel="self" type="application/atom+xml"/>
|
||||
<link href="https://www.google.com/m8/feeds/contacts/tester%40mochi.com/full/8" rel="edit" type="application/atom+xml"/>
|
||||
<gd:phoneNumber rel="http://schemas.google.com/g/2005#mobile" uri="tel:+31-6-12345678">0612345678</gd:phoneNumber>
|
||||
<gContact:groupMembershipInfo deleted="false" href="http://www.google.com/m8/feeds/groups/tester%40mochi.com/base/6"/>
|
||||
</entry>
|
||||
<entry gd:etag=""SX8-ejVSLit7I2A9XRdQFUkDRgY."">
|
||||
<id>http://www.google.com/m8/feeds/contacts/tester%40mochi.com/base/6</id>
|
||||
|
@ -114,4 +120,21 @@
|
|||
<gd:phoneNumber rel="http://schemas.google.com/g/2005#mobile">215234523452345</gd:phoneNumber>
|
||||
<gContact:groupMembershipInfo deleted="false" href="http://www.google.com/m8/feeds/groups/tester%40mochi.com/base/6"/>
|
||||
</entry>
|
||||
<entry gd:etag=""Rn8zejVSLit7I2A9WhVRFUQOQQc."">
|
||||
<id>http://www.google.com/m8/feeds/contacts/tester%40mochi.com/base/9</id>
|
||||
<updated>2012-03-24T13:10:37.182Z</updated>
|
||||
<app:edited xmlns:app="http://www.w3.org/2007/app">2012-03-24T13:10:37.182Z</app:edited>
|
||||
<category scheme="http://schemas.google.com/g/2005#kind" term="http://schemas.google.com/contact/2008#contact"/>
|
||||
<title>Little Smith</title>
|
||||
<link href="https://www.google.com/m8/feeds/photos/media/tester%40mochi.com/9" rel="http://schemas.google.com/contacts/2008/rel#photo" type="image/*"/>
|
||||
<link href="https://www.google.com/m8/feeds/contacts/tester%40mochi.com/full/9" rel="self" type="application/atom+xml"/>
|
||||
<link href="https://www.google.com/m8/feeds/contacts/tester%40mochi.com/full/9" rel="edit" type="application/atom+xml"/>
|
||||
<gd:name>
|
||||
<gd:fullName>Little Smith</gd:fullName>
|
||||
<gd:givenName>Little</gd:givenName>
|
||||
<gd:familyName>Smith</gd:familyName>
|
||||
</gd:name>
|
||||
<gd:email address="littlebabysmith@example.com" primary="true" rel="http://schemas.google.com/g/2005#other"/>
|
||||
<gContact:website href="http://www.google.com/profiles/111456826635924971693" rel="profile"/>
|
||||
</entry>
|
||||
</feed>
|
||||
|
|
|
@ -0,0 +1,56 @@
|
|||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<feed gd:etag="W/"CEIAQngzfyt7I2A9XRdXFEQ."" xmlns="http://www.w3.org/2005/Atom" xmlns:batch="http://schemas.google.com/gdata/batch" xmlns:gContact="http://schemas.google.com/contact/2008" xmlns:gd="http://schemas.google.com/g/2005" xmlns:openSearch="http://a9.com/-/spec/opensearch/1.1/">
|
||||
<id>tester@mochi.com</id>
|
||||
<updated>2014-10-28T10:35:43.687Z</updated>
|
||||
<category scheme="http://schemas.google.com/g/2005#kind" term="http://schemas.google.com/contact/2008#group"/>
|
||||
<title>Mochi Tester's Contact Groups</title>
|
||||
<link href="http://www.google.com/" rel="alternate" type="text/html"/>
|
||||
<link href="https://www.google.com/m8/feeds/groups/tester%40mochi.com/full" rel="http://schemas.google.com/g/2005#feed" type="application/atom+xml"/>
|
||||
<link href="https://www.google.com/m8/feeds/groups/tester%40mochi.com/full" rel="http://schemas.google.com/g/2005#post" type="application/atom+xml"/>
|
||||
<link href="https://www.google.com/m8/feeds/groups/tester%40mochi.com/full/batch" rel="http://schemas.google.com/g/2005#batch" type="application/atom+xml"/>
|
||||
<link href="https://www.google.com/m8/feeds/groups/tester%40mochi.com/full?max-results=10000000" rel="self" type="application/atom+xml"/>
|
||||
<author>
|
||||
<name>Mochi Tester</name>
|
||||
<email>tester@mochi.com</email>
|
||||
</author>
|
||||
<generator uri="http://www.google.com/m8/feeds" version="1.0">Contacts</generator>
|
||||
<openSearch:totalResults>4</openSearch:totalResults>
|
||||
<openSearch:startIndex>1</openSearch:startIndex>
|
||||
<openSearch:itemsPerPage>10000000</openSearch:itemsPerPage>
|
||||
<entry gd:etag=""YDwreyM."">
|
||||
<id>http://www.google.com/m8/feeds/groups/tester%40mochi.com/base/6</id>
|
||||
<updated>1970-01-01T00:00:00.000Z</updated>
|
||||
<category scheme="http://schemas.google.com/g/2005#kind" term="http://schemas.google.com/contact/2008#group"/>
|
||||
<title>System Group: My Contacts</title>
|
||||
<content>System Group: My Contacts</content>
|
||||
<link href="https://www.google.com/m8/feeds/groups/tester%40mochi.com/full/6" rel="self" type="application/atom+xml"/>
|
||||
<gContact:systemGroup id="Contacts"/>
|
||||
</entry>
|
||||
<entry gd:etag=""YDwreyM."">
|
||||
<id>http://www.google.com/m8/feeds/groups/tester%40mochi.com/base/d</id>
|
||||
<updated>1970-01-01T00:00:00.000Z</updated>
|
||||
<category scheme="http://schemas.google.com/g/2005#kind" term="http://schemas.google.com/contact/2008#group"/>
|
||||
<title>System Group: Friends</title>
|
||||
<content>System Group: Friends</content>
|
||||
<link href="https://www.google.com/m8/feeds/groups/tester%40mochi.com/full/d" rel="self" type="application/atom+xml"/>
|
||||
<gContact:systemGroup id="Friends"/>
|
||||
</entry>
|
||||
<entry gd:etag=""YDwreyM."">
|
||||
<id>http://www.google.com/m8/feeds/groups/tester%40mochi.com/base/e</id>
|
||||
<updated>1970-01-01T00:00:00.000Z</updated>
|
||||
<category scheme="http://schemas.google.com/g/2005#kind" term="http://schemas.google.com/contact/2008#group"/>
|
||||
<title>System Group: Family</title>
|
||||
<content>System Group: Family</content>
|
||||
<link href="https://www.google.com/m8/feeds/groups/tester%40mochi.com/full/e" rel="self" type="application/atom+xml"/>
|
||||
<gContact:systemGroup id="Family"/>
|
||||
</entry>
|
||||
<entry gd:etag=""YDwreyM."">
|
||||
<id>http://www.google.com/m8/feeds/groups/tester%40mochi.com/base/f</id>
|
||||
<updated>1970-01-01T00:00:00.000Z</updated>
|
||||
<category scheme="http://schemas.google.com/g/2005#kind" term="http://schemas.google.com/contact/2008#group"/>
|
||||
<title>System Group: Coworkers</title>
|
||||
<content>System Group: Coworkers</content>
|
||||
<link href="https://www.google.com/m8/feeds/groups/tester%40mochi.com/full/f" rel="self" type="application/atom+xml"/>
|
||||
<gContact:systemGroup id="Coworkers"/>
|
||||
</entry>
|
||||
</feed>
|
|
@ -143,5 +143,15 @@ const methodHandlers = {
|
|||
}
|
||||
|
||||
respondWithFile(res, "google_contacts.txt", "text/xml");
|
||||
},
|
||||
|
||||
groups: function(req, res, params) {
|
||||
try {
|
||||
checkAuth(req);
|
||||
} catch (ex) {
|
||||
sendError(res, ex, ex.code);
|
||||
}
|
||||
|
||||
respondWithFile(res, "google_groups.txt", "text/xml");
|
||||
}
|
||||
};
|
||||
|
|
|
@ -501,6 +501,41 @@ describe("loop.webapp", function() {
|
|||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("FailedConversationView", function() {
|
||||
var view, conversation, client, fakeAudio;
|
||||
|
||||
beforeEach(function() {
|
||||
fakeAudio = {
|
||||
play: sinon.spy(),
|
||||
pause: sinon.spy(),
|
||||
removeAttribute: sinon.spy()
|
||||
};
|
||||
sandbox.stub(window, "Audio").returns(fakeAudio);
|
||||
|
||||
client = new loop.StandaloneClient({
|
||||
baseServerUrl: "http://fake.example.com"
|
||||
});
|
||||
conversation = new sharedModels.ConversationModel({}, {
|
||||
sdk: {}
|
||||
});
|
||||
conversation.set("loopToken", "fakeToken");
|
||||
|
||||
view = React.addons.TestUtils.renderIntoDocument(
|
||||
loop.webapp.FailedConversationView({
|
||||
conversation: conversation,
|
||||
client: client,
|
||||
notifications: notifications
|
||||
}));
|
||||
});
|
||||
|
||||
it("should play a failure sound, once", function() {
|
||||
sinon.assert.calledOnce(window.Audio);
|
||||
sinon.assert.calledWithExactly(window.Audio,
|
||||
"shared/sounds/failure.ogg");
|
||||
expect(fakeAudio.loop).to.equal(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("WebappRootView", function() {
|
||||
|
|
|
@ -31,6 +31,11 @@ var loopServer;
|
|||
Services.prefs.setBoolPref("loop.enabled", true);
|
||||
Services.prefs.setBoolPref("loop.throttled", false);
|
||||
|
||||
// Cleanup function for all tests
|
||||
do_register_cleanup(() => {
|
||||
MozLoopService.errors.clear();
|
||||
});
|
||||
|
||||
function setupFakeLoopServer() {
|
||||
loopServer = new HttpServer();
|
||||
loopServer.start(-1);
|
||||
|
|
|
@ -135,7 +135,7 @@ const compareRooms = function(room1, room2) {
|
|||
};
|
||||
|
||||
add_task(function* test_getAllRooms() {
|
||||
yield MozLoopService.register(mockPushHandler);
|
||||
yield MozLoopService.promiseRegisteredWithServers();
|
||||
|
||||
let rooms = yield LoopRooms.promise("getAll");
|
||||
Assert.equal(rooms.length, 3);
|
||||
|
@ -145,7 +145,7 @@ add_task(function* test_getAllRooms() {
|
|||
});
|
||||
|
||||
add_task(function* test_getRoom() {
|
||||
yield MozLoopService.register(mockPushHandler);
|
||||
yield MozLoopService.promiseRegisteredWithServers();
|
||||
|
||||
let roomToken = "_nxD4V4FflQ";
|
||||
let room = yield LoopRooms.promise("get", roomToken);
|
||||
|
|
|
@ -24,7 +24,7 @@ let msgHandler = function(msg) {
|
|||
add_test(function test_busy_2guest_calls() {
|
||||
actionReceived = false;
|
||||
|
||||
MozLoopService.register().then(() => {
|
||||
MozLoopService.promiseRegisteredWithServers().then(() => {
|
||||
let opened = 0;
|
||||
Chat.open = function() {
|
||||
opened++;
|
||||
|
@ -47,7 +47,7 @@ add_test(function test_busy_2guest_calls() {
|
|||
add_test(function test_busy_1fxa_1guest_calls() {
|
||||
actionReceived = false;
|
||||
|
||||
MozLoopService.register().then(() => {
|
||||
MozLoopService.promiseRegisteredWithServers().then(() => {
|
||||
let opened = 0;
|
||||
Chat.open = function() {
|
||||
opened++;
|
||||
|
@ -71,7 +71,7 @@ add_test(function test_busy_1fxa_1guest_calls() {
|
|||
add_test(function test_busy_2fxa_calls() {
|
||||
actionReceived = false;
|
||||
|
||||
MozLoopService.register().then(() => {
|
||||
MozLoopService.promiseRegisteredWithServers().then(() => {
|
||||
let opened = 0;
|
||||
Chat.open = function() {
|
||||
opened++;
|
||||
|
@ -94,7 +94,7 @@ add_test(function test_busy_2fxa_calls() {
|
|||
add_test(function test_busy_1guest_1fxa_calls() {
|
||||
actionReceived = false;
|
||||
|
||||
MozLoopService.register().then(() => {
|
||||
MozLoopService.promiseRegisteredWithServers().then(() => {
|
||||
let opened = 0;
|
||||
Chat.open = function() {
|
||||
opened++;
|
||||
|
|
|
@ -31,7 +31,7 @@ add_test(function test_set_do_not_disturb() {
|
|||
add_test(function test_do_not_disturb_disabled_should_open_chat_window() {
|
||||
MozLoopService.doNotDisturb = false;
|
||||
|
||||
MozLoopService.register().then(() => {
|
||||
MozLoopService.promiseRegisteredWithServers().then(() => {
|
||||
let opened = false;
|
||||
Chat.open = function() {
|
||||
opened = true;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
var startTimerCalled = false;
|
||||
let startTimerCalled = false;
|
||||
|
||||
/**
|
||||
* Tests that registration doesn't happen when the expiry time is
|
||||
|
@ -23,7 +23,7 @@ add_task(function test_initialize_no_expiry() {
|
|||
*/
|
||||
add_task(function test_initialize_expiry_past() {
|
||||
// Set time to be 2 seconds in the past.
|
||||
var nowSeconds = Date.now() / 1000;
|
||||
let nowSeconds = Date.now() / 1000;
|
||||
Services.prefs.setIntPref("loop.urlsExpiryTimeSeconds", nowSeconds - 2);
|
||||
startTimerCalled = false;
|
||||
|
||||
|
@ -39,7 +39,7 @@ add_task(function test_initialize_expiry_past() {
|
|||
*/
|
||||
add_task(function test_initialize_starts_timer() {
|
||||
// Set time to be 1 minute in the future
|
||||
var nowSeconds = Date.now() / 1000;
|
||||
let nowSeconds = Date.now() / 1000;
|
||||
Services.prefs.setIntPref("loop.urlsExpiryTimeSeconds", nowSeconds + 60);
|
||||
startTimerCalled = false;
|
||||
|
||||
|
@ -49,8 +49,7 @@ add_task(function test_initialize_starts_timer() {
|
|||
"should start the timer when expiry time is in the future");
|
||||
});
|
||||
|
||||
function run_test()
|
||||
{
|
||||
function run_test() {
|
||||
setupFakeLoopServer();
|
||||
|
||||
// Override MozLoopService's initializeTimer, so that we can verify the timeout is called
|
||||
|
|
|
@ -10,7 +10,7 @@ let openChatOrig = Chat.open;
|
|||
add_test(function test_openChatWindow_on_notification() {
|
||||
Services.prefs.setCharPref("loop.seenToS", "unseen");
|
||||
|
||||
MozLoopService.register().then(() => {
|
||||
MozLoopService.promiseRegisteredWithServers().then(() => {
|
||||
let opened = false;
|
||||
Chat.open = function() {
|
||||
opened = true;
|
||||
|
|
|
@ -17,7 +17,7 @@ Cu.import("resource://services-common/utils.js");
|
|||
add_test(function test_register_websocket_success_loop_server_fail() {
|
||||
mockPushHandler.registrationResult = "404";
|
||||
|
||||
MozLoopService.register().then(() => {
|
||||
MozLoopService.promiseRegisteredWithServers().then(() => {
|
||||
do_throw("should not succeed when loop server registration fails");
|
||||
}, (err) => {
|
||||
// 404 is an expected failure indicated by the lack of route being set
|
||||
|
@ -49,7 +49,7 @@ add_test(function test_register_success() {
|
|||
response.processAsync();
|
||||
response.finish();
|
||||
});
|
||||
MozLoopService.register().then(() => {
|
||||
MozLoopService.promiseRegisteredWithServers().then(() => {
|
||||
run_next_test();
|
||||
}, err => {
|
||||
do_throw("shouldn't error on a successful request");
|
||||
|
|
|
@ -75,12 +75,17 @@ add_task(function test_initialize_with_invalid_fxa_token() {
|
|||
"FXA pref should be cleared if token was invalid");
|
||||
Assert.equal(Services.prefs.getCharPref(LOOP_FXA_PROFILE_PREF), "",
|
||||
"FXA profile pref should be cleared if token was invalid");
|
||||
Assert.ok(MozLoopServiceInternal.errors.has("login"),
|
||||
"Initialization error should have been reported to UI");
|
||||
});
|
||||
});
|
||||
|
||||
add_task(function test_initialize_with_fxa_token() {
|
||||
Services.prefs.setCharPref(LOOP_FXA_PROFILE_PREF, FAKE_FXA_PROFILE);
|
||||
Services.prefs.setCharPref(LOOP_FXA_TOKEN_PREF, FAKE_FXA_TOKEN_DATA);
|
||||
|
||||
MozLoopService.errors.clear();
|
||||
|
||||
loopServer.registerPathHandler("/registration", (request, response) => {
|
||||
response.setStatusLine(null, 200, "OK");
|
||||
});
|
||||
|
@ -90,6 +95,7 @@ add_task(function test_initialize_with_fxa_token() {
|
|||
"FXA pref should still be set after initialization");
|
||||
Assert.equal(Services.prefs.getCharPref(LOOP_FXA_PROFILE_PREF), FAKE_FXA_PROFILE,
|
||||
"FXA profile should still be set after initialization");
|
||||
Assert.ok(!MozLoopServiceInternal.errors.has("login"), "Initialization error should not exist");
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -31,7 +31,7 @@ add_test(function test_registration_invalid_token() {
|
|||
response.finish();
|
||||
});
|
||||
|
||||
MozLoopService.register().then(() => {
|
||||
MozLoopService.promiseRegisteredWithServers().then(() => {
|
||||
// Due to the way the time stamp checking code works in hawkclient, we expect a couple
|
||||
// of authorization requests before we reset the token.
|
||||
Assert.equal(authorizationAttempts, 2);
|
||||
|
|
|
@ -16,7 +16,7 @@ add_test(function test_registration_returns_hawk_session_token() {
|
|||
response.finish();
|
||||
});
|
||||
|
||||
MozLoopService.register().then(() => {
|
||||
MozLoopService.promiseRegisteredWithServers().then(() => {
|
||||
var hawkSessionPref;
|
||||
try {
|
||||
hawkSessionPref = Services.prefs.getCharPref("loop.hawk-session-token");
|
||||
|
|
|
@ -24,7 +24,7 @@ add_test(function test_registration_uses_hawk_session_token() {
|
|||
response.finish();
|
||||
});
|
||||
|
||||
MozLoopService.register().then(() => {
|
||||
MozLoopService.promiseRegisteredWithServers().then(() => {
|
||||
run_next_test();
|
||||
}, err => {
|
||||
do_throw("shouldn't error on a succesful request");
|
||||
|
|
|
@ -16,7 +16,7 @@ add_test(function test_registration_handles_bogus_hawk_token() {
|
|||
response.finish();
|
||||
});
|
||||
|
||||
MozLoopService.register().then(() => {
|
||||
MozLoopService.promiseRegisteredWithServers().then(() => {
|
||||
do_throw("should not succeed with a bogus token");
|
||||
}, err => {
|
||||
|
||||
|
|
|
@ -57,6 +57,7 @@ navigator.mozLoop = {
|
|||
}
|
||||
},
|
||||
releaseCallData: function() {},
|
||||
copyString: function() {},
|
||||
contacts: {
|
||||
getAll: function(callback) {
|
||||
callback(null, []);
|
||||
|
|
|
@ -159,7 +159,7 @@ ContentRestoreInternal.prototype = {
|
|||
* Start loading the current page. When the data has finished loading from the
|
||||
* network, finishCallback is called. Returns true if the load was successful.
|
||||
*/
|
||||
restoreTabContent: function (finishCallback) {
|
||||
restoreTabContent: function (loadArguments, finishCallback) {
|
||||
let tabData = this._tabData;
|
||||
this._tabData = null;
|
||||
|
||||
|
@ -188,7 +188,19 @@ ContentRestoreInternal.prototype = {
|
|||
webNavigation.setCurrentURI(Utils.makeURI("about:blank"));
|
||||
|
||||
try {
|
||||
if (tabData.userTypedValue && tabData.userTypedClear) {
|
||||
if (loadArguments) {
|
||||
// A load has been redirected to a new process so get history into the
|
||||
// same state it was before the load started then trigger the load.
|
||||
let activeIndex = tabData.index - 1;
|
||||
if (activeIndex > 0) {
|
||||
// Go to the right history entry, but don't load anything yet.
|
||||
history.getEntryAtIndex(activeIndex, true);
|
||||
}
|
||||
let referrer = loadArguments.referrer ?
|
||||
Utils.makeURI(loadArguments.referrer) : null;
|
||||
webNavigation.loadURI(loadArguments.uri, loadArguments.loadFlags,
|
||||
referrer, null, null);
|
||||
} else if (tabData.userTypedValue && tabData.userTypedClear) {
|
||||
// If the user typed a URL into the URL bar and hit enter right before
|
||||
// we crashed, we want to start loading that page again. A non-zero
|
||||
// userTypedClear value means that the load had started.
|
||||
|
|
|
@ -67,7 +67,7 @@ let SessionHistoryInternal = {
|
|||
let data = {entries: []};
|
||||
let isPinned = docShell.isAppTab;
|
||||
let webNavigation = docShell.QueryInterface(Ci.nsIWebNavigation);
|
||||
let history = webNavigation.sessionHistory;
|
||||
let history = webNavigation.sessionHistory.QueryInterface(Ci.nsISHistoryInternal);
|
||||
|
||||
if (history && history.count > 0) {
|
||||
let oldest;
|
||||
|
@ -88,17 +88,28 @@ let SessionHistoryInternal = {
|
|||
newest = history.count - 1;
|
||||
}
|
||||
|
||||
try {
|
||||
for (let i = oldest; i <= newest; i++) {
|
||||
let shEntry = history.getEntryAtIndex(i, false);
|
||||
let entry = this.serializeEntry(shEntry, isPinned);
|
||||
data.entries.push(entry);
|
||||
}
|
||||
} catch (ex) {
|
||||
// In some cases, getEntryAtIndex will throw. This seems to be due to
|
||||
// history.count being higher than it should be. By doing this in a
|
||||
// try-catch, we'll update history to where it breaks, print an error
|
||||
// message, and still save sessionstore.js.
|
||||
// Loop over the transaction linked list directly so we can get the
|
||||
// persist property for each transaction.
|
||||
let txn = history.rootTransaction;
|
||||
let i = 0;
|
||||
while (txn && i < oldest) {
|
||||
txn = txn.next;
|
||||
i++;
|
||||
}
|
||||
|
||||
while (txn && i <= newest) {
|
||||
let shEntry = txn.sHEntry;
|
||||
let entry = this.serializeEntry(shEntry, isPinned);
|
||||
entry.persist = txn.persist;
|
||||
data.entries.push(entry);
|
||||
txn = txn.next;
|
||||
i++;
|
||||
}
|
||||
|
||||
if (i <= newest) {
|
||||
// In some cases, there don't seem to be as many history entries as
|
||||
// history.count claims. we'll save whatever history we can, print an
|
||||
// error message, and still save sessionstore.js.
|
||||
debug("SessionStore failed gathering complete history " +
|
||||
"for the focused window/tab. See bug 669196.");
|
||||
}
|
||||
|
@ -297,11 +308,12 @@ let SessionHistoryInternal = {
|
|||
let idMap = { used: {} };
|
||||
let docIdentMap = {};
|
||||
for (let i = 0; i < tabData.entries.length; i++) {
|
||||
let entry = tabData.entries[i];
|
||||
//XXXzpao Wallpaper patch for bug 514751
|
||||
if (!tabData.entries[i].url)
|
||||
if (!entry.url)
|
||||
continue;
|
||||
history.addEntry(this.deserializeEntry(tabData.entries[i],
|
||||
idMap, docIdentMap), true);
|
||||
let persist = "persist" in entry ? entry.persist : true;
|
||||
history.addEntry(this.deserializeEntry(entry, idMap, docIdentMap), persist);
|
||||
}
|
||||
},
|
||||
|
||||
|
|
|
@ -192,6 +192,16 @@ this.SessionStore = {
|
|||
SessionStoreInternal.setTabState(aTab, aState);
|
||||
},
|
||||
|
||||
// This should not be used by external code, the intention is to remove it
|
||||
// once a better fix is in place for process switching in e10s.
|
||||
// See bug 1075658 for context.
|
||||
_restoreTabAndLoad: function ss_restoreTabAndLoad(aTab, aState, aLoadArguments) {
|
||||
SessionStoreInternal.setTabState(aTab, aState, {
|
||||
restoreImmediately: true,
|
||||
loadArguments: aLoadArguments
|
||||
});
|
||||
},
|
||||
|
||||
duplicateTab: function ss_duplicateTab(aWindow, aTab, aDelta = 0) {
|
||||
return SessionStoreInternal.duplicateTab(aWindow, aTab, aDelta);
|
||||
},
|
||||
|
@ -1572,7 +1582,7 @@ let SessionStoreInternal = {
|
|||
return this._toJSONString(tabState);
|
||||
},
|
||||
|
||||
setTabState: function ssi_setTabState(aTab, aState) {
|
||||
setTabState: function ssi_setTabState(aTab, aState, aOptions) {
|
||||
// Remove the tab state from the cache.
|
||||
// Note that we cannot simply replace the contents of the cache
|
||||
// as |aState| can be an incomplete state that will be completed
|
||||
|
@ -1597,7 +1607,7 @@ let SessionStoreInternal = {
|
|||
this._resetTabRestoringState(aTab);
|
||||
}
|
||||
|
||||
this.restoreTab(aTab, tabState);
|
||||
this.restoreTab(aTab, tabState, aOptions);
|
||||
},
|
||||
|
||||
duplicateTab: function ssi_duplicateTab(aWindow, aTab, aDelta = 0) {
|
||||
|
@ -1623,7 +1633,9 @@ let SessionStoreInternal = {
|
|||
aWindow.gBrowser.addTab(null, {relatedToCurrent: true, ownerTab: aTab}) :
|
||||
aWindow.gBrowser.addTab();
|
||||
|
||||
this.restoreTab(newTab, tabState, true /* Load this tab right away. */);
|
||||
this.restoreTab(newTab, tabState, {
|
||||
restoreImmediately: true /* Load this tab right away. */
|
||||
});
|
||||
return newTab;
|
||||
},
|
||||
|
||||
|
@ -2527,7 +2539,9 @@ let SessionStoreInternal = {
|
|||
},
|
||||
|
||||
// Restores the given tab state for a given tab.
|
||||
restoreTab(tab, tabData, restoreImmediately = false) {
|
||||
restoreTab(tab, tabData, options = {}) {
|
||||
let restoreImmediately = options.restoreImmediately;
|
||||
let loadArguments = options.loadArguments;
|
||||
let browser = tab.linkedBrowser;
|
||||
let window = tab.ownerDocument.defaultView;
|
||||
let tabbrowser = window.gBrowser;
|
||||
|
@ -2595,6 +2609,9 @@ let SessionStoreInternal = {
|
|||
// attribute so that it runs in a content process.
|
||||
let activePageData = tabData.entries[activeIndex] || null;
|
||||
let uri = activePageData ? activePageData.url || null : null;
|
||||
if (loadArguments) {
|
||||
uri = loadArguments.uri;
|
||||
}
|
||||
tabbrowser.updateBrowserRemotenessByURL(browser, uri);
|
||||
|
||||
// Start a new epoch and include the epoch in the restoreHistory
|
||||
|
@ -2630,8 +2647,8 @@ let SessionStoreInternal = {
|
|||
|
||||
// This could cause us to ignore MAX_CONCURRENT_TAB_RESTORES a bit, but
|
||||
// it ensures each window will have its selected tab loaded.
|
||||
if (restoreImmediately || tabbrowser.selectedBrowser == browser) {
|
||||
this.restoreTabContent(tab);
|
||||
if (restoreImmediately || tabbrowser.selectedBrowser == browser || loadArguments) {
|
||||
this.restoreTabContent(tab, loadArguments);
|
||||
} else {
|
||||
TabRestoreQueue.add(tab);
|
||||
this.restoreNextTab();
|
||||
|
@ -2658,7 +2675,7 @@ let SessionStoreInternal = {
|
|||
*
|
||||
* @returns true/false indicating whether or not a load actually happened
|
||||
*/
|
||||
restoreTabContent: function (aTab) {
|
||||
restoreTabContent: function (aTab, aLoadArguments = null) {
|
||||
let window = aTab.ownerDocument.defaultView;
|
||||
let browser = aTab.linkedBrowser;
|
||||
let tabData = browser.__SS_data;
|
||||
|
@ -2687,7 +2704,8 @@ let SessionStoreInternal = {
|
|||
|
||||
browser.__SS_restore_tab = aTab;
|
||||
|
||||
browser.messageManager.sendAsyncMessage("SessionStore:restoreTabContent");
|
||||
browser.messageManager.sendAsyncMessage("SessionStore:restoreTabContent",
|
||||
{loadArguments: aLoadArguments});
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
|
@ -145,7 +145,7 @@ let MessageListener = {
|
|||
};
|
||||
|
||||
// We need to pass the value of didStartLoad back to SessionStore.jsm.
|
||||
let didStartLoad = gContentRestore.restoreTabContent(finishCallback);
|
||||
let didStartLoad = gContentRestore.restoreTabContent(data.loadArguments, finishCallback);
|
||||
|
||||
sendAsyncMessage("SessionStore:restoreTabContentStarted", {epoch: epoch});
|
||||
|
||||
|
|
|
@ -66,7 +66,6 @@ support-files =
|
|||
[browser_capabilities.js]
|
||||
[browser_cleaner.js]
|
||||
[browser_dying_cache.js]
|
||||
skip-if = e10s
|
||||
[browser_dynamic_frames.js]
|
||||
[browser_form_restore_events.js]
|
||||
[browser_formdata.js]
|
||||
|
@ -77,19 +76,19 @@ skip-if = buildapp == 'mulet'
|
|||
[browser_frame_history.js]
|
||||
[browser_global_store.js]
|
||||
[browser_history_cap.js]
|
||||
[browser_history_persist.js]
|
||||
[browser_label_and_icon.js]
|
||||
[browser_merge_closed_tabs.js]
|
||||
[browser_pageStyle.js]
|
||||
[browser_privatetabs.js]
|
||||
[browser_scrollPositions.js]
|
||||
[browser_sessionHistory.js]
|
||||
# Disabled because of bug 1077581
|
||||
skip-if = e10s
|
||||
[browser_sessionStorage.js]
|
||||
skip-if = e10s
|
||||
[browser_swapDocShells.js]
|
||||
skip-if = e10s # See bug 918634
|
||||
[browser_telemetry.js]
|
||||
skip-if = e10s
|
||||
[browser_upgrade_backup.js]
|
||||
[browser_windowRestore_perwindowpb.js]
|
||||
[browser_248970_b_perwindowpb.js]
|
||||
|
@ -107,10 +106,8 @@ skip-if = true
|
|||
skip-if = true
|
||||
[browser_394759_behavior.js]
|
||||
[browser_394759_perwindowpb.js]
|
||||
skip-if = e10s
|
||||
[browser_394759_purge.js]
|
||||
[browser_423132.js]
|
||||
skip-if = e10s
|
||||
[browser_447951.js]
|
||||
[browser_454908.js]
|
||||
[browser_456342.js]
|
||||
|
@ -124,7 +121,6 @@ skip-if = e10s
|
|||
[browser_467409-backslashplosion.js]
|
||||
[browser_477657.js]
|
||||
[browser_480893.js]
|
||||
skip-if = e10s
|
||||
[browser_485482.js]
|
||||
[browser_485563.js]
|
||||
[browser_490040.js]
|
||||
|
@ -197,6 +193,6 @@ skip-if = true
|
|||
|
||||
# Disabled on OS X:
|
||||
[browser_625016.js]
|
||||
skip-if = os == "mac" || e10s
|
||||
skip-if = os == "mac"
|
||||
|
||||
[browser_911547.js]
|
||||
|
|
|
@ -25,48 +25,51 @@ function test() {
|
|||
newWin.addEventListener("load", function (aEvent) {
|
||||
newWin.removeEventListener("load", arguments.callee, false);
|
||||
|
||||
newWin.gBrowser.loadURI(testURL, null, null);
|
||||
// Wait for sessionstore to be ready to restore this window
|
||||
executeSoon(function() {
|
||||
newWin.gBrowser.loadURI(testURL, null, null);
|
||||
|
||||
whenBrowserLoaded(newWin.gBrowser.selectedBrowser, function() {
|
||||
// get the sessionstore state for the window
|
||||
TabState.flush(newWin.gBrowser.selectedBrowser);
|
||||
let state = ss.getWindowState(newWin);
|
||||
whenBrowserLoaded(newWin.gBrowser.selectedBrowser, function() {
|
||||
// get the sessionstore state for the window
|
||||
TabState.flush(newWin.gBrowser.selectedBrowser);
|
||||
let state = ss.getWindowState(newWin);
|
||||
|
||||
// verify our cookie got set during pageload
|
||||
let e = cs.enumerator;
|
||||
let cookie;
|
||||
let i = 0;
|
||||
while (e.hasMoreElements()) {
|
||||
cookie = e.getNext().QueryInterface(Ci.nsICookie);
|
||||
i++;
|
||||
}
|
||||
is(i, 1, "expected one cookie");
|
||||
// verify our cookie got set during pageload
|
||||
let e = cs.enumerator;
|
||||
let cookie;
|
||||
let i = 0;
|
||||
while (e.hasMoreElements()) {
|
||||
cookie = e.getNext().QueryInterface(Ci.nsICookie);
|
||||
i++;
|
||||
}
|
||||
is(i, 1, "expected one cookie");
|
||||
|
||||
// remove the cookie
|
||||
cs.removeAll();
|
||||
// remove the cookie
|
||||
cs.removeAll();
|
||||
|
||||
// restore the window state
|
||||
ss.setWindowState(newWin, state, true);
|
||||
// restore the window state
|
||||
ss.setWindowState(newWin, state, true);
|
||||
|
||||
// at this point, the cookie should be restored...
|
||||
e = cs.enumerator;
|
||||
let cookie2;
|
||||
while (e.hasMoreElements()) {
|
||||
cookie2 = e.getNext().QueryInterface(Ci.nsICookie);
|
||||
if (cookie.name == cookie2.name)
|
||||
break;
|
||||
}
|
||||
is(cookie.name, cookie2.name, "cookie name successfully restored");
|
||||
is(cookie.value, cookie2.value, "cookie value successfully restored");
|
||||
is(cookie.path, cookie2.path, "cookie path successfully restored");
|
||||
// at this point, the cookie should be restored...
|
||||
e = cs.enumerator;
|
||||
let cookie2;
|
||||
while (e.hasMoreElements()) {
|
||||
cookie2 = e.getNext().QueryInterface(Ci.nsICookie);
|
||||
if (cookie.name == cookie2.name)
|
||||
break;
|
||||
}
|
||||
is(cookie.name, cookie2.name, "cookie name successfully restored");
|
||||
is(cookie.value, cookie2.value, "cookie value successfully restored");
|
||||
is(cookie.path, cookie2.path, "cookie path successfully restored");
|
||||
|
||||
// clean up
|
||||
if (gPrefService.prefHasUserValue("browser.sessionstore.interval"))
|
||||
gPrefService.clearUserPref("browser.sessionstore.interval");
|
||||
cs.removeAll();
|
||||
newWin.close();
|
||||
finish();
|
||||
}, true, testURL);
|
||||
// clean up
|
||||
if (gPrefService.prefHasUserValue("browser.sessionstore.interval"))
|
||||
gPrefService.clearUserPref("browser.sessionstore.interval");
|
||||
cs.removeAll();
|
||||
newWin.close();
|
||||
finish();
|
||||
}, true, testURL);
|
||||
});
|
||||
}, false);
|
||||
}
|
||||
|
||||
|
|
|
@ -175,9 +175,9 @@ function testOnWindow(aIsPrivate, aCallback) {
|
|||
|
||||
function waitForTabLoad(aWin, aURL, aCallback) {
|
||||
let browser = aWin.gBrowser.selectedBrowser;
|
||||
browser.loadURI(aURL);
|
||||
whenBrowserLoaded(browser, function () {
|
||||
TabState.flush(browser);
|
||||
executeSoon(aCallback);
|
||||
});
|
||||
browser.loadURI(aURL);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,79 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Ensure that history entries that should not be persisted are restored in the
|
||||
* same state.
|
||||
*/
|
||||
add_task(function check_history_not_persisted() {
|
||||
// Create an about:blank tab
|
||||
let tab = gBrowser.addTab("about:blank");
|
||||
let browser = tab.linkedBrowser;
|
||||
yield promiseBrowserLoaded(browser);
|
||||
|
||||
// Retrieve the tab state.
|
||||
TabState.flush(browser);
|
||||
let state = JSON.parse(ss.getTabState(tab));
|
||||
ok(!state.entries[0].persist, "Should have collected the persistence state");
|
||||
gBrowser.removeTab(tab);
|
||||
browser = null;
|
||||
|
||||
// Open a new tab to restore into.
|
||||
tab = gBrowser.addTab("about:blank");
|
||||
browser = tab.linkedBrowser;
|
||||
yield promiseTabState(tab, state);
|
||||
let sessionHistory = browser.sessionHistory;
|
||||
|
||||
is(sessionHistory.count, 1, "Should be a single history entry");
|
||||
is(sessionHistory.getEntryAtIndex(0, false).URI.spec, "about:blank", "Should be the right URL");
|
||||
|
||||
// Load a new URL into the tab, it should replace the about:blank history entry
|
||||
browser.loadURI("about:robots");
|
||||
yield promiseBrowserLoaded(browser);
|
||||
sessionHistory = browser.sessionHistory;
|
||||
is(sessionHistory.count, 1, "Should be a single history entry");
|
||||
is(sessionHistory.getEntryAtIndex(0, false).URI.spec, "about:robots", "Should be the right URL");
|
||||
|
||||
// Cleanup.
|
||||
gBrowser.removeTab(tab);
|
||||
});
|
||||
|
||||
/**
|
||||
* Check that entries default to being persisted when the attribute doesn't
|
||||
* exist
|
||||
*/
|
||||
add_task(function check_history_default_persisted() {
|
||||
// Create an about:blank tab
|
||||
let tab = gBrowser.addTab("about:blank");
|
||||
let browser = tab.linkedBrowser;
|
||||
yield promiseBrowserLoaded(browser);
|
||||
|
||||
// Retrieve the tab state.
|
||||
TabState.flush(browser);
|
||||
let state = JSON.parse(ss.getTabState(tab));
|
||||
delete state.entries[0].persist;
|
||||
gBrowser.removeTab(tab);
|
||||
browser = null;
|
||||
|
||||
// Open a new tab to restore into.
|
||||
tab = gBrowser.addTab("about:blank");
|
||||
browser = tab.linkedBrowser;
|
||||
yield promiseTabState(tab, state);
|
||||
let sessionHistory = browser.sessionHistory;
|
||||
|
||||
is(sessionHistory.count, 1, "Should be a single history entry");
|
||||
is(sessionHistory.getEntryAtIndex(0, false).URI.spec, "about:blank", "Should be the right URL");
|
||||
|
||||
// Load a new URL into the tab, it should replace the about:blank history entry
|
||||
browser.loadURI("about:robots");
|
||||
yield promiseBrowserLoaded(browser);
|
||||
sessionHistory = browser.sessionHistory;
|
||||
is(sessionHistory.count, 2, "Should be two history entries");
|
||||
is(sessionHistory.getEntryAtIndex(0, false).URI.spec, "about:blank", "Should be the right URL");
|
||||
is(sessionHistory.getEntryAtIndex(1, false).URI.spec, "about:robots", "Should be the right URL");
|
||||
|
||||
// Cleanup.
|
||||
gBrowser.removeTab(tab);
|
||||
});
|
|
@ -193,6 +193,10 @@ function waitForTabState(aTab, aState, aCallback) {
|
|||
ss.setTabState(aTab, JSON.stringify(aState));
|
||||
}
|
||||
|
||||
function promiseTabState(tab, state) {
|
||||
return new Promise(resolve => waitForTabState(tab, state, resolve));
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait for a content -> chrome message.
|
||||
*/
|
||||
|
|
|
@ -74,10 +74,17 @@ let WindowMessageHandler = {
|
|||
let isImageDocument = (content.document instanceof Ci.nsIImageDocument);
|
||||
|
||||
sendAsyncMessage(cx.name, {isImageDocument: isImageDocument});
|
||||
}
|
||||
},
|
||||
|
||||
waitForDocumentLoad: function WMH_waitForDocumentLoad() {
|
||||
addEventListener("load", function listener() {
|
||||
removeEventListener("load", listener, true);
|
||||
sendAsyncMessage("Panorama:documentLoaded");
|
||||
}, true);
|
||||
},
|
||||
};
|
||||
|
||||
// add message listeners
|
||||
addMessageListener("Panorama:isDocumentLoaded", WindowMessageHandler.isDocumentLoaded);
|
||||
addMessageListener("Panorama:isImageDocument", WindowMessageHandler.isImageDocument);
|
||||
|
||||
addMessageListener("Panorama:waitForDocumentLoad", WindowMessageHandler.waitForDocumentLoad);
|
||||
|
|
|
@ -17,6 +17,7 @@ support-files =
|
|||
[browser_tabview_bug587231.js]
|
||||
skip-if = buildapp == 'mulet'
|
||||
[browser_tabview_bug587276.js]
|
||||
skip-if = e10s # Bug 1091200
|
||||
[browser_tabview_bug587351.js]
|
||||
[browser_tabview_bug587503.js]
|
||||
[browser_tabview_bug587990.js]
|
||||
|
|
|
@ -62,9 +62,13 @@ function test4() {
|
|||
EventUtils.synthesizeKey("t", { accelKey: true, shiftKey: true }, contentWindow);
|
||||
is(gBrowser.tabs.length, 2, "There are two tabs after restoring one");
|
||||
|
||||
gBrowser.tabs[0].linkedBrowser.loadURI("about:blank");
|
||||
gBrowser.selectedTab = gBrowser.tabs[0];
|
||||
test8();
|
||||
gBrowser.tabs[1].linkedBrowser.addEventListener("load", function listener() {
|
||||
gBrowser.tabs[1].linkedBrowser.removeEventListener("load", listener, true);
|
||||
|
||||
gBrowser.tabs[0].linkedBrowser.loadURI("about:blank");
|
||||
gBrowser.selectedTab = gBrowser.tabs[0];
|
||||
test8();
|
||||
}, true);
|
||||
});
|
||||
};
|
||||
gBrowser.tabContainer.addEventListener("TabClose", onTabClose, true);
|
||||
|
|
|
@ -797,10 +797,11 @@ let UI = {
|
|||
if (this.restoredClosedTab) {
|
||||
// when the tab view UI is being displayed, update the thumb for the
|
||||
// restored closed tab after the page load
|
||||
tab.linkedBrowser.addEventListener("load", function onLoad(event) {
|
||||
tab.linkedBrowser.removeEventListener("load", onLoad, true);
|
||||
tab.linkedBrowser.messageManager.addMessageListener("Panorama:documentLoaded", function onLoad() {
|
||||
tab.linkedBrowser.messageManager.removeMessageListener("Panorama:documentLoaded", onLoad);
|
||||
TabItems._update(tab);
|
||||
}, true);
|
||||
});
|
||||
tab.linkedBrowser.messageManager.sendAsyncMessage("Panorama:waitForDocumentLoad");
|
||||
}
|
||||
this._closedLastVisibleTab = false;
|
||||
this._closedSelectedTabInTabView = false;
|
||||
|
|
|
@ -18,6 +18,9 @@ XPCOMUtils.defineLazyModuleGetter(this, "promise",
|
|||
XPCOMUtils.defineLazyModuleGetter(this, "console",
|
||||
"resource://gre/modules/devtools/Console.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "CustomizableUI",
|
||||
"resource:///modules/CustomizableUI.jsm");
|
||||
|
||||
const EventEmitter = devtools.require("devtools/toolkit/event-emitter");
|
||||
const FORBIDDEN_IDS = new Set(["toolbox", ""]);
|
||||
const MAX_ORDINAL = 99;
|
||||
|
@ -566,6 +569,13 @@ let gDevToolsBrowser = {
|
|||
let webIDEEnabled = Services.prefs.getBoolPref("devtools.webide.enabled");
|
||||
toggleCmd("Tools:WebIDE", webIDEEnabled);
|
||||
|
||||
let showWebIDEWidget = Services.prefs.getBoolPref("devtools.webide.widget.enabled");
|
||||
if (webIDEEnabled && showWebIDEWidget) {
|
||||
gDevToolsBrowser.installWebIDEWidget();
|
||||
} else {
|
||||
gDevToolsBrowser.uninstallWebIDEWidget();
|
||||
}
|
||||
|
||||
// Enable App Manager?
|
||||
let appMgrEnabled = Services.prefs.getBoolPref("devtools.appmanager.enabled");
|
||||
toggleCmd("Tools:DevAppMgr", !webIDEEnabled && appMgrEnabled);
|
||||
|
@ -667,11 +677,59 @@ let gDevToolsBrowser = {
|
|||
if (win) {
|
||||
win.focus();
|
||||
} else {
|
||||
let ww = Cc["@mozilla.org/embedcomp/window-watcher;1"].getService(Ci.nsIWindowWatcher);
|
||||
ww.openWindow(null, "chrome://webide/content/", "webide", "chrome,centerscreen,resizable", null);
|
||||
Services.ww.openWindow(null, "chrome://webide/content/", "webide", "chrome,centerscreen,resizable", null);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Install WebIDE widget
|
||||
*/
|
||||
installWebIDEWidget: function() {
|
||||
if (this.isWebIDEWidgetInstalled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
let defaultArea;
|
||||
if (Services.prefs.getBoolPref("devtools.webide.widget.inNavbarByDefault")) {
|
||||
defaultArea = CustomizableUI.AREA_NAVBAR;
|
||||
} else {
|
||||
defaultArea = CustomizableUI.AREA_PANEL;
|
||||
}
|
||||
|
||||
CustomizableUI.createWidget({
|
||||
id: "webide-button",
|
||||
shortcutId: "key_webide",
|
||||
label: "devtools-webide-button2.label",
|
||||
tooltiptext: "devtools-webide-button2.tooltiptext",
|
||||
defaultArea: defaultArea,
|
||||
onCommand: function(aEvent) {
|
||||
gDevToolsBrowser.openWebIDE();
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
isWebIDEWidgetInstalled: function() {
|
||||
let widgetWrapper = CustomizableUI.getWidget("webide-button");
|
||||
return !!(widgetWrapper && widgetWrapper.instances.some(i => !!i.node));
|
||||
},
|
||||
|
||||
/**
|
||||
* Uninstall WebIDE widget
|
||||
*/
|
||||
uninstallWebIDEWidget: function() {
|
||||
if (this.isWebIDEWidgetInstalled()) {
|
||||
CustomizableUI.removeWidgetFromArea("webide-button");
|
||||
}
|
||||
CustomizableUI.destroyWidget("webide-button");
|
||||
},
|
||||
|
||||
/**
|
||||
* Move WebIDE widget to the navbar
|
||||
*/
|
||||
moveWebIDEWidgetInNavbar: function() {
|
||||
CustomizableUI.addWidgetToArea("webide-button", CustomizableUI.AREA_NAVBAR);
|
||||
},
|
||||
|
||||
/**
|
||||
* Add this DevTools's presence to a browser window's document
|
||||
*
|
||||
|
|
|
@ -89,6 +89,12 @@ let UI = {
|
|||
|
||||
this.lastConnectedRuntime = Services.prefs.getCharPref("devtools.webide.lastConnectedRuntime");
|
||||
|
||||
if (Services.prefs.getBoolPref("devtools.webide.widget.autoinstall") &&
|
||||
!Services.prefs.getBoolPref("devtools.webide.widget.enabled")) {
|
||||
Services.prefs.setBoolPref("devtools.webide.widget.enabled", true);
|
||||
gDevToolsBrowser.moveWebIDEWidgetInNavbar();
|
||||
}
|
||||
|
||||
this.setupDeck();
|
||||
},
|
||||
|
||||
|
|
|
@ -8,3 +8,4 @@ support-files =
|
|||
|
||||
[browser_tabs.js]
|
||||
skip-if = e10s # Bug 1072167 - browser_tabs.js test fails under e10s
|
||||
[browser_widget.js]
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
"use strict";
|
||||
|
||||
function test() {
|
||||
waitForExplicitFinish();
|
||||
Task.spawn(function() {
|
||||
let win = yield openWebIDE();
|
||||
ok(document.querySelector("#webide-button"), "Found WebIDE button");
|
||||
Services.prefs.setBoolPref("devtools.webide.widget.enabled", false);
|
||||
ok(!document.querySelector("#webide-button"), "WebIDE button uninstalled");
|
||||
yield closeWebIDE(win);
|
||||
Services.prefs.clearUserPref("devtools.webide.widget.enabled");
|
||||
}).then(finish, handleError);
|
||||
}
|
|
@ -64,6 +64,8 @@ function closeWebIDE(win) {
|
|||
|
||||
let deferred = promise.defer();
|
||||
|
||||
Services.prefs.clearUserPref("devtools.webide.widget.enabled");
|
||||
|
||||
win.addEventListener("unload", function onUnload() {
|
||||
win.removeEventListener("unload", onUnload);
|
||||
info("WebIDE closed");
|
||||
|
|
|
@ -18,3 +18,6 @@ pref("devtools.webide.adaptersAddonID", "fxdevtools-adapters@mozilla.org");
|
|||
pref("devtools.webide.monitorWebSocketURL", "ws://localhost:9000");
|
||||
pref("devtools.webide.lastConnectedRuntime", "");
|
||||
pref("devtools.webide.lastSelectedProject", "");
|
||||
pref("devtools.webide.widget.autoinstall", true);
|
||||
pref("devtools.webide.widget.enabled", false);
|
||||
pref("devtools.webide.widget.inNavbarByDefault", false);
|
||||
|
|
|
@ -113,5 +113,6 @@ web-apps-button.tooltiptext = Discover Apps
|
|||
|
||||
# LOCALIZATION NOTE(devtools-webide-button.label, devtools-webide-button.tooltiptext):
|
||||
# widget is only visible after WebIDE has been started once (Tools > Web Developers > WebIDE)
|
||||
devtools-webide-button.label = WebIDE
|
||||
devtools-webide-button.tooltiptext = Open WebIDE
|
||||
# %S is the keyboard shortcut
|
||||
devtools-webide-button2.label = WebIDE
|
||||
devtools-webide-button2.tooltiptext = Open WebIDE (%S)
|
||||
|
|
|
@ -51,8 +51,11 @@ this.E10SUtils = {
|
|||
let sessionHistory = aDocShell.getInterface(Ci.nsIWebNavigation).sessionHistory;
|
||||
|
||||
messageManager.sendAsyncMessage("Browser:LoadURI", {
|
||||
uri: aURI.spec,
|
||||
referrer: aReferrer ? aReferrer.spec : null,
|
||||
loadOptions: {
|
||||
uri: aURI.spec,
|
||||
flags: Ci.nsIWebNavigation.LOAD_FLAGS_NONE,
|
||||
referrer: aReferrer ? aReferrer.spec : null,
|
||||
},
|
||||
historyIndex: sessionHistory.requestedIndex,
|
||||
});
|
||||
return false;
|
||||
|
|
Двоичные данные
browser/themes/linux/menuPanel.png
До Ширина: | Высота: | Размер: 16 KiB После Ширина: | Высота: | Размер: 17 KiB |
|
@ -765,6 +765,10 @@ toolbar .toolbarbutton-1 > .toolbarbutton-menubutton-dropmarker > .dropmarker-ic
|
|||
-moz-image-region: rect(18px, 342px, 36px, 324px);
|
||||
}
|
||||
|
||||
#webide-button@toolbarButtonPressed@ {
|
||||
-moz-image-region: rect(18px, 342px, 36px, 324px);
|
||||
}
|
||||
|
||||
#new-tab-button@toolbarButtonPressed@ {
|
||||
-moz-image-region: rect(18px, 360px, 36px, 342px);
|
||||
}
|
||||
|
@ -1012,6 +1016,14 @@ toolbar .toolbarbutton-1 > .toolbarbutton-menubutton-dropmarker > .dropmarker-ic
|
|||
transform: scaleY(-1);
|
||||
}
|
||||
|
||||
#webide-button[cui-areatype="toolbar"] {
|
||||
-moz-image-region: rect(0, 1476px, 36px, 1440px);
|
||||
}
|
||||
|
||||
#webide-button[cui-areatype="toolbar"]:hover:active:not([disabled="true"]) {
|
||||
-moz-image-region: rect(36px, 1476px, 72px, 1440px);
|
||||
}
|
||||
|
||||
#new-tab-button[cui-areatype="toolbar"] {
|
||||
-moz-image-region: rect(0, 720px, 36px, 684px);
|
||||
}
|
||||
|
@ -1281,6 +1293,11 @@ toolbar .toolbarbutton-1 > .toolbarbutton-menubutton-dropmarker > .dropmarker-ic
|
|||
-moz-image-region: rect(0px, 1024px, 64px, 960px);
|
||||
}
|
||||
|
||||
#webide-button[cui-areatype="menu-panel"],
|
||||
toolbarpaletteitem[place="palette"] > #webide-button {
|
||||
-moz-image-region: rect(0px, 1920px, 64px, 1856px);
|
||||
}
|
||||
|
||||
#new-tab-button[cui-areatype="menu-panel"],
|
||||
toolbarpaletteitem[place="palette"] > #new-tab-button {
|
||||
-moz-image-region: rect(0px, 1088px, 64px, 1024px);
|
||||
|
|
Двоичные данные
browser/themes/osx/menuPanel-yosemite.png
До Ширина: | Высота: | Размер: 21 KiB После Ширина: | Высота: | Размер: 22 KiB |
Двоичные данные
browser/themes/osx/menuPanel-yosemite@2x.png
До Ширина: | Высота: | Размер: 47 KiB После Ширина: | Высота: | Размер: 50 KiB |
Двоичные данные
browser/themes/osx/menuPanel.png
До Ширина: | Высота: | Размер: 28 KiB После Ширина: | Высота: | Размер: 29 KiB |
Двоичные данные
browser/themes/osx/menuPanel@2x.png
До Ширина: | Высота: | Размер: 64 KiB После Ширина: | Высота: | Размер: 68 KiB |
|
@ -2,7 +2,7 @@
|
|||
|
||||
% Note that zoom-reset-button is a bit different since it doesn't use an image and thus has the image with display: none.
|
||||
%define nestedButtons #zoom-out-button, #zoom-reset-button, #zoom-in-button, #cut-button, #copy-button, #paste-button
|
||||
%define primaryToolbarButtons #back-button, #forward-button, #home-button, #print-button, #downloads-button, #bookmarks-menu-button, #new-tab-button, #new-window-button, #fullscreen-button, #sync-button, #feed-button, #tabview-button, #social-share-button, #open-file-button, #find-button, #developer-button, #preferences-button, #privatebrowsing-button, #save-page-button, #switch-to-metro-button, #add-ons-button, #history-panelmenu, #nav-bar-overflow-button, #PanelUI-menu-button, #characterencoding-button, #email-link-button, #sidebar-button, @nestedButtons@, #e10s-button, #panic-button, #web-apps-button
|
||||
%define primaryToolbarButtons #back-button, #forward-button, #home-button, #print-button, #downloads-button, #bookmarks-menu-button, #new-tab-button, #new-window-button, #fullscreen-button, #sync-button, #feed-button, #tabview-button, #social-share-button, #open-file-button, #find-button, #developer-button, #preferences-button, #privatebrowsing-button, #save-page-button, #switch-to-metro-button, #add-ons-button, #history-panelmenu, #nav-bar-overflow-button, #PanelUI-menu-button, #characterencoding-button, #email-link-button, #sidebar-button, @nestedButtons@, #e10s-button, #panic-button, #web-apps-button, #webide-button
|
||||
|
||||
%ifdef XP_MACOSX
|
||||
% Prior to 10.7 there wasn't a native fullscreen button so we use #restore-button to exit fullscreen
|
||||
|
|
|
@ -166,6 +166,11 @@ toolbarpaletteitem[place="palette"] > #web-apps-button {
|
|||
-moz-image-region: rect(0, 928px, 32px, 896px);
|
||||
}
|
||||
|
||||
#webide-button[cui-areatype="menu-panel"],
|
||||
toolbarpaletteitem[place="palette"] > #webide-button {
|
||||
-moz-image-region: rect(0px, 960px, 32px, 928px);
|
||||
}
|
||||
|
||||
toolbaritem[sdkstylewidget="true"] > toolbarbutton {
|
||||
-moz-image-region: rect(0, 832px, 32px, 800px);
|
||||
}
|
||||
|
@ -243,4 +248,4 @@ toolbarpaletteitem[place="palette"] > #zoom-controls > #zoom-in-button {
|
|||
#add-share-provider {
|
||||
list-style-image: url(chrome://browser/skin/menuPanel-small.png);
|
||||
-moz-image-region: rect(0px, 96px, 16px, 80px);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -233,3 +233,7 @@ toolbar[brighttext] #loop-call-button > .toolbarbutton-badge-container {
|
|||
#loop-call-button:not([disabled="true"])[state="active"]:-moz-any(:hover,:hover:active,[open]) > .toolbarbutton-badge-container {
|
||||
-moz-image-region: rect(0, 126px, 18px, 108px);
|
||||
}
|
||||
|
||||
#webide-button[cui-areatype="toolbar"] {
|
||||
-moz-image-region: rect(0, 738px, 18px, 720px);
|
||||
}
|
||||
|
|
Двоичные данные
browser/themes/windows/menuPanel-aero.png
До Ширина: | Высота: | Размер: 31 KiB После Ширина: | Высота: | Размер: 34 KiB |
Двоичные данные
browser/themes/windows/menuPanel.png
До Ширина: | Высота: | Размер: 15 KiB После Ширина: | Высота: | Размер: 16 KiB |
|
@ -43,8 +43,6 @@ public class ReadingListRow extends LinearLayout {
|
|||
|
||||
LayoutInflater.from(context).inflate(R.layout.reading_list_row_view, this);
|
||||
|
||||
setOrientation(LinearLayout.VERTICAL);
|
||||
|
||||
resources = context.getResources();
|
||||
|
||||
title = (TextView) findViewById(R.id.title);
|
||||
|
|
|
@ -360,10 +360,10 @@ size. -->
|
|||
|
||||
<!ENTITY reading_list_added "Page added to your Reading List">
|
||||
|
||||
<!-- Localization note (reading_list_time_minutes) : This string is used in the "Reading List"
|
||||
<!-- Localization note (reading_list_time_minutes2) : This string is used in the "Reading List"
|
||||
panel on the home page to give the user an estimate of how many minutes it will take to
|
||||
read an article. The word "minute" should be abbreviated if possible. -->
|
||||
<!ENTITY reading_list_time_minutes "&formatD;min">
|
||||
<!ENTITY reading_list_time_minutes2 "&formatD; min">
|
||||
<!ENTITY reading_list_time_over_an_hour "Over an hour">
|
||||
|
||||
<!-- Localization note : These strings are used as alternate text for accessibility.
|
||||
|
|
|
@ -4,7 +4,6 @@
|
|||
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
||||
|
||||
<org.mozilla.gecko.home.ReadingListRow xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
style="@style/Widget.BookmarkItemView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="10dp"/>
|
||||
android:layout_height="@dimen/reading_list_row_height"
|
||||
android:layout_gravity="center_vertical"/>
|
||||
|
|
|
@ -6,29 +6,34 @@
|
|||
<merge xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal">
|
||||
android:layout_width="0dip"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:paddingLeft="@dimen/reading_list_row_padding_left"
|
||||
android:paddingRight="@dimen/reading_list_row_padding_right"
|
||||
android:orientation="vertical"
|
||||
android:gravity="center_vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/title"
|
||||
android:layout_width="0dip"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingBottom="4dp"
|
||||
style="@style/Widget.ReadingListRow.Title" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/read_time"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
style="@style/Widget.ReadingListRow.ReadTime" />
|
||||
android:id="@+id/excerpt"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
style="@style/Widget.ReadingListRow.Description" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/excerpt"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
style="@style/Widget.ReadingListRow.Description" />
|
||||
android:id="@+id/read_time"
|
||||
android:layout_width="64dp"
|
||||
android:layout_height="match_parent"
|
||||
android:gravity="center"
|
||||
style="@style/Widget.ReadingListRow.ReadTime" />
|
||||
|
||||
</merge>
|
||||
|
|
|
@ -11,4 +11,7 @@
|
|||
<dimen name="tabs_counter_size">26sp</dimen>
|
||||
<dimen name="panel_grid_view_column_width">200dp</dimen>
|
||||
|
||||
<dimen name="reading_list_row_height">96dp</dimen>
|
||||
<dimen name="reading_list_row_padding_right">15dp</dimen>
|
||||
|
||||
</resources>
|
||||
|
|
|
@ -132,6 +132,13 @@
|
|||
|
||||
<style name="Widget.ReadingListView" parent="Widget.BookmarksListView"/>
|
||||
|
||||
<style name="Widget.ReadingListRow.Description">
|
||||
<item name="android:textAppearance">@style/TextAppearance.Widget.Home.ItemDescription</item>
|
||||
<item name="android:maxLines">2</item>
|
||||
<item name="android:ellipsize">end</item>
|
||||
<item name="android:lineSpacingMultiplier">1.3</item>
|
||||
</style>
|
||||
|
||||
<style name="Widget.HomeBanner">
|
||||
<item name="android:paddingLeft">32dp</item>
|
||||
<item name="android:paddingRight">32dp</item>
|
||||
|
|
|
@ -17,12 +17,6 @@
|
|||
<item name="android:fontFamily">sans-serif-light</item>
|
||||
</style>
|
||||
|
||||
<style name="Widget.ReadingListRow.ReadTime">
|
||||
<item name="android:textStyle">italic</item>
|
||||
<item name="android:textColor">#FF9400</item>
|
||||
<item name="android:fontFamily">sans-serif-condensed</item>
|
||||
</style>
|
||||
|
||||
<style name="OnboardStartTextAppearance.Subtext">
|
||||
<item name="android:textSize">18sp</item>
|
||||
<item name="android:fontFamily">sans-serif-light</item>
|
||||
|
|
|
@ -61,9 +61,14 @@
|
|||
<dimen name="new_tablet_site_security_unknown_inset_top">1dp</dimen>
|
||||
<dimen name="new_tablet_site_security_unknown_inset_bottom">-1dp</dimen>
|
||||
|
||||
<!-- Page Row height -->
|
||||
<!-- Regular page row on about:home -->
|
||||
<dimen name="page_row_height">64dp</dimen>
|
||||
|
||||
<!-- Reading list row on about:home -->
|
||||
<dimen name="reading_list_row_height">128dp</dimen>
|
||||
<dimen name="reading_list_row_padding_left">15dp</dimen>
|
||||
<dimen name="reading_list_row_padding_right">10dp</dimen>
|
||||
|
||||
<!-- Remote Tabs static view top padding. Less in landscape on phones. -->
|
||||
<dimen name="home_remote_tabs_top_padding">48dp</dimen>
|
||||
|
||||
|
|
|
@ -137,12 +137,12 @@
|
|||
|
||||
<style name="Widget.ReadingListRow.Description">
|
||||
<item name="android:textAppearance">@style/TextAppearance.Widget.Home.ItemDescription</item>
|
||||
<item name="android:maxLines">4</item>
|
||||
<item name="android:maxLines">3</item>
|
||||
<item name="android:ellipsize">end</item>
|
||||
<item name="android:lineSpacingMultiplier">1.3</item>
|
||||
</style>
|
||||
|
||||
<style name="Widget.ReadingListRow.ReadTime">
|
||||
<item name="android:textStyle">italic</item>
|
||||
<item name="android:textColor">@color/text_color_highlight</item>
|
||||
</style>
|
||||
|
||||
|
|
|
@ -294,7 +294,7 @@
|
|||
<string name="site_settings_no_settings">&site_settings_no_settings;</string>
|
||||
|
||||
<string name="reading_list_added">&reading_list_added;</string>
|
||||
<string name="reading_list_time_minutes">&reading_list_time_minutes;</string>
|
||||
<string name="reading_list_time_minutes">&reading_list_time_minutes2;</string>
|
||||
<string name="reading_list_time_over_an_hour">&reading_list_time_over_an_hour;</string>
|
||||
|
||||
<string name="page_action_dropmarker_description">&page_action_dropmarker_description;</string>
|
||||
|
|
|
@ -4,11 +4,9 @@
|
|||
# disabled on Android 2.3; bug 975187
|
||||
skip-if = android_version == "10"
|
||||
[testAddonManager]
|
||||
# disabled on x86 only; bug 936216
|
||||
#skip-if = processor == "x86"
|
||||
# disabled across the board - bug 941624, bug 1063509, bug 1073374, bug 1087221,
|
||||
# bug 1088023, bug 1088027, bug 1090206
|
||||
skip-if = true
|
||||
# disabled on x86; bug 936216
|
||||
# disabled on 2.3; bug 941624, bug 1063509, bug 1073374, bug 1087221, bug 1088023, bug 1088027, bug 1090206
|
||||
skip-if = android_version == "10" || processor == "x86"
|
||||
[testAddSearchEngine]
|
||||
# disabled on Android 2.3; bug 979552
|
||||
skip-if = android_version == "10"
|
||||
|
|
|
@ -252,6 +252,7 @@ user_pref("loop.enabled", true);
|
|||
user_pref("loop.throttled", false);
|
||||
user_pref("loop.oauth.google.URL", "http://%(server)s/browser/browser/components/loop/test/mochitest/google_service.sjs?action=");
|
||||
user_pref("loop.oauth.google.getContactsURL", "http://%(server)s/browser/browser/components/loop/test/mochitest/google_service.sjs?action=contacts");
|
||||
user_pref("loop.oauth.google.getGroupsURL", "http://%(server)s/browser/browser/components/loop/test/mochitest/google_service.sjs?action=groups");
|
||||
user_pref("loop.CSP","default-src 'self' about: file: chrome: data: wss://* http://* https://*");
|
||||
|
||||
// Ensure UITour won't hit the network
|
||||
|
|
|
@ -133,14 +133,6 @@
|
|||
if (!aURI)
|
||||
aURI = "about:blank";
|
||||
|
||||
if (aCharset) {
|
||||
try {
|
||||
this.docShell.parentCharset = aCharset;
|
||||
}
|
||||
catch (e) {
|
||||
}
|
||||
}
|
||||
|
||||
if (!(aFlags & this.webNavigation.LOAD_FLAGS_FROM_EXTERNAL))
|
||||
this.userTypedClear++;
|
||||
|
||||
|
|