Merge fx-team to m-c. a=merge

This commit is contained in:
Ryan VanderMeulen 2014-09-25 16:31:19 -04:00
commit d89a05d20b
70 changed files with 1097 additions and 247 deletions

View File

@ -13,6 +13,8 @@ Cu.import("resource://gre/modules/WindowsPrefSync.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "BrowserUITelemetry",
"resource:///modules/BrowserUITelemetry.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "E10SUtils",
"resource:///modules/E10SUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "BrowserUtils",
"resource://gre/modules/BrowserUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Task",
@ -166,6 +168,9 @@ XPCOMUtils.defineLazyModuleGetter(this, "SitePermissions",
XPCOMUtils.defineLazyModuleGetter(this, "SessionStore",
"resource:///modules/sessionstore/SessionStore.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "TabState",
"resource:///modules/sessionstore/TabState.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "fxAccounts",
"resource://gre/modules/FxAccounts.jsm");
@ -766,6 +771,36 @@ function gKeywordURIFixup({ target: browser, data: fixupInfo }) {
gDNSService.asyncResolve(hostName, 0, onLookupComplete, Services.tm.mainThread);
}
// 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 }) {
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++;
tabState.userTypedValue = null;
}
else {
// Update the history state to point to the requested index
tabState.index = data.historyIndex + 1;
}
// SessionStore takes care of setting the browser remoteness before restoring
// history into it.
SessionStore.setTabState(tab, JSON.stringify(tabState));
}
var gBrowserInit = {
delayedStartupFinished: false,
@ -1064,6 +1099,7 @@ var gBrowserInit = {
Services.obs.addObserver(gXPInstallObserver, "addon-install-failed", false);
Services.obs.addObserver(gXPInstallObserver, "addon-install-complete", false);
window.messageManager.addMessageListener("Browser:URIFixup", gKeywordURIFixup);
window.messageManager.addMessageListener("Browser:LoadURI", RedirectLoad);
BrowserOffline.init();
OfflineApps.init();
@ -1372,6 +1408,7 @@ var gBrowserInit = {
Services.obs.removeObserver(gXPInstallObserver, "addon-install-failed");
Services.obs.removeObserver(gXPInstallObserver, "addon-install-complete");
window.messageManager.removeMessageListener("Browser:URIFixup", gKeywordURIFixup);
window.messageManager.removeMessageListener("Browser:LoadURI", RedirectLoad);
try {
gPrefService.removeObserver(gHomeButton.prefDomain, gHomeButton);
@ -3551,6 +3588,28 @@ var XULBrowserWindow = {
return target;
},
// Check whether this URI should load in the current process
shouldLoadURI: function(aDocShell, aURI, aReferrer) {
if (!gMultiProcessBrowser)
return true;
let browser = aDocShell.QueryInterface(Ci.nsIDocShellTreeItem)
.sameTypeRootTreeItem
.QueryInterface(Ci.nsIDocShell)
.chromeEventHandler;
// Ignore loads that aren't in the main tabbrowser
if (browser.localName != "browser" || browser.getTabBrowser() != gBrowser)
return true;
if (!E10SUtils.shouldLoadURI(aDocShell, aURI, aReferrer)) {
E10SUtils.redirectLoad(aDocShell, aURI, aReferrer);
return false;
}
return true;
},
onProgressChange: function (aWebProgress, aRequest,
aCurSelfProgress, aMaxSelfProgress,
aCurTotalProgress, aMaxTotalProgress) {

View File

@ -9,6 +9,8 @@ Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource:///modules/ContentWebRTC.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "E10SUtils",
"resource:///modules/E10SUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "BrowserUtils",
"resource://gre/modules/BrowserUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "ContentLinkHandler",
@ -683,6 +685,16 @@ let WebBrowserChrome = {
onBeforeLinkTraversal: function(originalTarget, linkURI, linkNode, isAppTab) {
return BrowserUtils.onBeforeLinkTraversal(originalTarget, linkURI, linkNode, isAppTab);
},
// Check whether this URI should load in the current process
shouldLoadURI: function(aDocShell, aURI, aReferrer) {
if (!E10SUtils.shouldLoadURI(aDocShell, aURI, aReferrer)) {
E10SUtils.redirectLoad(aDocShell, aURI, aReferrer);
return false;
}
return true;
},
};
if (Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_CONTENT) {

View File

@ -142,6 +142,9 @@ let gPage = {
*/
handleEvent: function Page_handleEvent(aEvent) {
switch (aEvent.type) {
case "load":
this.onPageVisibleAndLoaded();
break;
case "unload":
gAllPages.unregister(this);
break;
@ -183,39 +186,41 @@ let gPage = {
}
}
// Allow the document to reflow so the page has sizing info
let i = 0;
let checkSizing = _ => setTimeout(_ => {
if (document.documentElement.clientWidth == 0) {
checkSizing();
}
else {
this.onPageFirstSized();
}
});
checkSizing();
if (document.readyState == "complete") {
this.onPageVisibleAndLoaded();
} else {
addEventListener("load", this);
}
},
onPageFirstSized: function() {
// Work backwards to find the first visible site from the end
let {sites} = gGrid;
let lastIndex = sites.length;
while (lastIndex-- > 0) {
let site = sites[lastIndex];
if (site) {
let {node} = site;
let rect = node.getBoundingClientRect();
let target = document.elementFromPoint(rect.x + rect.width / 2,
rect.y + rect.height / 2);
if (node.contains(target)) {
break;
onPageVisibleAndLoaded() {
// Send the index of the last visible tile.
this.reportLastVisibleTileIndex();
// Show the panel now that anchors are sized
gIntro.showIfNecessary();
},
reportLastVisibleTileIndex() {
let cwu = window.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils);
let rect = cwu.getBoundsWithoutFlushing(gGrid.node);
let nodes = cwu.nodesFromRect(rect.left, rect.top, 0, rect.width,
rect.height, 0, true, false);
let i = -1;
let lastIndex = -1;
let sites = gGrid.sites;
for (let node of nodes) {
if (node.classList && node.classList.contains("newtab-cell")) {
if (sites[++i]) {
lastIndex = i;
}
}
}
DirectoryLinksProvider.reportSitesAction(gGrid.sites, "view", lastIndex);
// Show the panel now that anchors are sized
gIntro.showIfNecessary();
DirectoryLinksProvider.reportSitesAction(sites, "view", lastIndex);
}
};

View File

@ -9,11 +9,6 @@
%tabBrowserDTD;
]>
# MAKE_E10S_WORK surrounds code needed to have the front-end try to be smart
# about using non-remote browsers for loading certain URIs when remote tabs
# (browser.tabs.remote) are enabled.
#define MAKE_E10S_WORK 1
<bindings id="tabBrowserBindings"
xmlns="http://www.mozilla.org/xbl"
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
@ -1474,49 +1469,18 @@
</body>
</method>
#ifdef MAKE_E10S_WORK
<method name="updateBrowserRemotenessByURL">
<parameter name="aBrowser"/>
<parameter name="aURL"/>
<body>
<![CDATA[
let shouldBeRemote = this._shouldBrowserBeRemote(aURL);
let shouldBeRemote = gMultiProcessBrowser &&
E10SUtils.shouldBrowserBeRemote(aURL);
return this.updateBrowserRemoteness(aBrowser, shouldBeRemote);
]]>
</body>
</method>
<!--
Returns true if we want to load the content for this URL in a
remote process. Eventually this should just check whether aURL
is unprivileged. Right now, though, we would like to load
some unprivileged URLs (like about:neterror) in the main
process since they interact with chrome code through
BrowserOnClick.
-->
<method name="_shouldBrowserBeRemote">
<parameter name="aURL"/>
<body>
<![CDATA[
if (!gMultiProcessBrowser)
return false;
// loadURI in browser.xml treats null as about:blank
if (!aURL)
aURL = "about:blank";
if (aURL.startsWith("about:") &&
aURL.toLowerCase() != "about:home" &&
aURL.toLowerCase() != "about:blank") {
return false;
}
return true;
]]>
</body>
</method>
#endif
<method name="addTab">
<parameter name="aURI"/>
<parameter name="aReferrerURI"/>
@ -1558,11 +1522,7 @@
t.setAttribute("onerror", "this.removeAttribute('image');");
t.className = "tabbrowser-tab";
#ifdef MAKE_E10S_WORK
let remote = this._shouldBrowserBeRemote(aURI);
#else
let remote = gMultiProcessBrowser;
#endif
let remote = gMultiProcessBrowser && E10SUtils.shouldBrowserBeRemote(aURI);
if (remote)
t.setAttribute("remote", "true");
@ -2790,18 +2750,7 @@
<parameter name="aCharset"/>
<body>
<![CDATA[
#ifdef MAKE_E10S_WORK
this.updateBrowserRemotenessByURL(this.mCurrentBrowser, aURI);
try {
#endif
return this.mCurrentBrowser.loadURI(aURI, aReferrerURI, aCharset);
#ifdef MAKE_E10S_WORK
} catch (e) {
let url = this.mCurrentBrowser.currentURI.spec;
this.updateBrowserRemotenessByURL(this.mCurrentBrowser, url);
throw e;
}
#endif
]]>
</body>
</method>
@ -2815,18 +2764,7 @@
<parameter name="aPostData"/>
<body>
<![CDATA[
#ifdef MAKE_E10S_WORK
this.updateBrowserRemotenessByURL(this.mCurrentBrowser, aURI);
try {
#endif
return this.mCurrentBrowser.loadURIWithFlags(aURI, aFlags, aReferrerURI, aCharset, aPostData);
#ifdef MAKE_E10S_WORK
} catch (e) {
let url = this.mCurrentBrowser.currentURI.spec;
this.updateBrowserRemotenessByURL(this.mCurrentBrowser, url);
throw e;
}
#endif
]]>
</body>
</method>

View File

@ -483,4 +483,6 @@ skip-if = e10s
[browser_addCertException.js]
skip-if = e10s # Bug ?????? - test directly manipulates content (content.document.getElementById)
[browser_bug1045809.js]
skip-if = e10s
skip-if = e10s # Bug 1068360 - [e10s] Mixed content blocker doorhanger doesn't work
[browser_e10s_switchbrowser.js]

View File

@ -1,7 +1,7 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
const URL = "about:robots";
const URL = "http://example.com/browser/browser/base/content/test/general/dummy_page.html";
function test() {
let win;

View File

@ -0,0 +1,104 @@
const DUMMY_PATH = "browser/browser/base/content/test/general/dummy_page.html";
const gExpectedHistory = {
index: -1,
entries: []
};
function check_history() {
let webNav = gBrowser.webNavigation;
let sessionHistory = webNav.sessionHistory;
let count = sessionHistory.count;
is(count, gExpectedHistory.entries.length, "Should have the right number of history entries");
is(sessionHistory.index, gExpectedHistory.index, "Should have the right history index");
for (let i = 0; i < count; i++) {
let entry = sessionHistory.getEntryAtIndex(i, false);
is(entry.URI.spec, gExpectedHistory.entries[i].uri, "Should have the right URI");
is(entry.title, gExpectedHistory.entries[i].title, "Should have the right title");
}
}
// Waits for a load and updates the known history
let waitForLoad = Task.async(function*(uri) {
info("Loading " + uri);
gBrowser.loadURI(uri);
yield waitForDocLoadComplete();
gExpectedHistory.index++;
gExpectedHistory.entries.push({
uri: gBrowser.currentURI.spec,
title: gBrowser.contentTitle
});
});
let back = Task.async(function*() {
info("Going back");
gBrowser.goBack();
yield waitForDocLoadComplete();
gExpectedHistory.index--;
});
let forward = Task.async(function*() {
info("Going forward");
gBrowser.goForward();
yield waitForDocLoadComplete();
gExpectedHistory.index++;
});
// 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*() {
SimpleTest.requestCompleteLog();
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});
yield waitForLoad("http://example.org/" + DUMMY_PATH);
is(gBrowser.selectedTab.getAttribute("remote"), expectedRemote, "Remote attribute should be correct");
info("2");
// Load another page
yield waitForLoad("http://example.com/" + DUMMY_PATH);
is(gBrowser.selectedTab.getAttribute("remote"), expectedRemote, "Remote attribute should be correct");
check_history();
info("3");
// Load a non-remote page
yield waitForLoad("about:robots");
is(gBrowser.selectedTab.getAttribute("remote"), "", "Remote attribute should be correct");
check_history();
info("4");
// Load a remote page
yield waitForLoad("http://example.org/" + DUMMY_PATH);
is(gBrowser.selectedTab.getAttribute("remote"), expectedRemote, "Remote attribute should be correct");
check_history();
info("5");
yield back();
is(gBrowser.selectedTab.getAttribute("remote"), "", "Remote attribute should be correct");
check_history();
info("6");
yield back();
is(gBrowser.selectedTab.getAttribute("remote"), expectedRemote, "Remote attribute should be correct");
check_history();
info("7");
yield forward();
is(gBrowser.selectedTab.getAttribute("remote"), "", "Remote attribute should be correct");
check_history();
info("8");
yield forward();
is(gBrowser.selectedTab.getAttribute("remote"), expectedRemote, "Remote attribute should be correct");
check_history();
info("9");
gBrowser.removeCurrentTab();
});

View File

@ -23,9 +23,6 @@ const EXPECTED_REFLOWS = [
"openUILinkIn@chrome://browser/content/utilityOverlay.js|" +
"BrowserOpenTab@chrome://browser/content/browser.js|",
// unpreloaded newtab pages explicitly waits for reflows for sizing
"gPage.onPageFirstVisible/checkSizing/<@chrome://browser/content/newtab/newTab.js|",
// accessing element.scrollPosition in _fillTrailingGap() flushes layout
"get_scrollPosition@chrome://global/content/bindings/scrollbox.xml|" +
"_fillTrailingGap@chrome://browser/content/tabbrowser.xml|" +

View File

@ -454,12 +454,15 @@ function waitForDocLoadComplete(aBrowser=gBrowser) {
let deferred = Promise.defer();
let progressListener = {
onStateChange: function (webProgress, req, flags, status) {
let docStart = Ci.nsIWebProgressListener.STATE_IS_NETWORK |
Ci.nsIWebProgressListener.STATE_STOP;
info("Saw state " + flags.toString(16));
if ((flags & docStart) == docStart) {
let docStop = Ci.nsIWebProgressListener.STATE_IS_NETWORK |
Ci.nsIWebProgressListener.STATE_STOP;
info("Saw state " + flags.toString(16) + " and status " + status.toString(16));
// When a load needs to be retargetted to a new process it is cancelled
// with NS_BINDING_ABORTED so ignore that case
if ((flags & docStop) == docStop && status != Cr.NS_BINDING_ABORTED) {
aBrowser.removeProgressListener(progressListener);
info("Browser loaded");
info("Browser loaded " + aBrowser.contentWindow.location);
deferred.resolve();
}
},

View File

@ -28,6 +28,9 @@ skip-if = os == "mac" # Intermittent failures, bug 898317
[browser_newtab_intro.js]
[browser_newtab_perwindow_private_browsing.js]
[browser_newtab_reportLinkAction.js]
[browser_newtab_reflow_load.js]
support-files =
content-reflows.js
[browser_newtab_reset.js]
[browser_newtab_search.js]
support-files =

View File

@ -0,0 +1,38 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
const FRAME_SCRIPT = getRootDirectory(gTestPath) + "content-reflows.js";
const ADDITIONAL_WAIT_MS = 2000;
/*
* Ensure that loading about:newtab doesn't cause uninterruptible reflows.
*/
function runTests() {
gBrowser.selectedTab = gBrowser.addTab("about:blank", {animate: false});
let browser = gBrowser.selectedBrowser;
yield whenBrowserLoaded(browser);
let mm = browser.messageManager;
mm.loadFrameScript(FRAME_SCRIPT, true);
mm.addMessageListener("newtab-reflow", ({data: stack}) => {
ok(false, `unexpected uninterruptible reflow ${stack}`);
});
browser.loadURI("about:newtab");
yield whenBrowserLoaded(browser);
// Wait some more to catch sync reflows after the page has loaded.
yield setTimeout(TestRunner.next, ADDITIONAL_WAIT_MS);
// Clean up.
gBrowser.removeCurrentTab({animate: false});
}
function whenBrowserLoaded(browser) {
browser.addEventListener("load", function onLoad() {
browser.removeEventListener("load", onLoad, true);
executeSoon(TestRunner.next);
}, true);
}

View File

@ -0,0 +1,26 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
(function () {
"use strict";
const Ci = Components.interfaces;
docShell.addWeakReflowObserver({
reflow() {
// Gather information about the current code path.
let path = (new Error().stack).split("\n").slice(1).join("\n");
if (path) {
sendSyncMessage("newtab-reflow", path);
}
},
reflowInterruptible() {
// We're not interested in interruptible reflows.
},
QueryInterface: XPCOMUtils.generateQI([Ci.nsIReflowObserver,
Ci.nsISupportsWeakReference])
});
})();

View File

@ -235,12 +235,12 @@
flex-direction: column;
align-items: center;
justify-content: space-between;
min-height: 264px;
min-height: 230px;
}
.incoming-call-action-group {
display: flex;
padding: 2.5em 0;
padding: 2.5em 0 0 0;
width: 100%;
justify-content: space-around;
}
@ -363,13 +363,16 @@
padding: 14px;
}
.feedback p {
margin: 0px;
}
.feedback h3 {
color: #666;
font-size: 12px;
font-weight: 700;
text-align: center;
margin-bottom: 14px;
margin-top: 14px;
margin: 0 0 1em 0;
}
.feedback .faces {

View File

@ -366,12 +366,15 @@ loop.shared.models = (function(l10n) {
},
/**
* Adds a l10n rror notification to the stack and renders it.
* Adds a l10n error notification to the stack and renders it.
*
* @param {String} messageId L10n message id
* @param {Object} [l10nProps] An object with variables to be interpolated
* into the translation. All members' values must be
* strings or numbers.
*/
errorL10n: function(messageId) {
this.error(l10n.get(messageId));
errorL10n: function(messageId, l10nProps) {
this.error(l10n.get(messageId, l10nProps));
}
});

View File

@ -58,6 +58,15 @@ loop.shared.utils = (function() {
return platform.indexOf("Firefox") !== -1;
},
isFirefoxOS: function(platform) {
// So far WebActivities are exposed only in FxOS, but they may be
// exposed in Firefox Desktop soon, so we check for its existence
// and also check if the UA belongs to a mobile platform.
// XXX WebActivities are also exposed in WebRT on Firefox for Android,
// so we need a better check. Bug 1065403.
return !!window.MozActivity && /mobi/i.test(platform);
},
isIOS: function(platform) {
return this._iOSRegex.test(platform);
},

View File

@ -73,3 +73,6 @@ config:
@echo "loop.config.serverUrl = '`echo $(LOOP_SERVER_URL)`';" >> content/config.js
@echo "loop.config.feedbackApiUrl = '`echo $(LOOP_FEEDBACK_API_URL)`';" >> content/config.js
@echo "loop.config.feedbackProductName = '`echo $(LOOP_FEEDBACK_PRODUCT_NAME)`';" >> content/config.js
@echo "loop.config.fxosApp = loop.config.fxosApp || {};" >> content/config.js
@echo "loop.config.fxosApp.name = 'Loop';" >> content/config.js
@echo "loop.config.fxosApp.manifestUrl = 'http://fake-market.herokuapp.com/apps/packagedApp/manifest.webapp';" >> content/config.js

View File

@ -4,7 +4,7 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
/* global loop:true, React */
/* global loop:true, React, MozActivity */
/* jshint newcap:false, maxlen:false */
var loop = loop || {};
@ -122,6 +122,95 @@ loop.webapp = (function($, _, OT, mozL10n) {
}
});
/**
* The Firefox Marketplace exposes a web page that contains a postMesssage
* based API that wraps a small set of functionality from the WebApps API
* that allow us to request the installation of apps given their manifest
* URL. We will be embedding the content of this web page within an hidden
* iframe in case that we need to request the installation of the FxOS Loop
* client.
*/
var FxOSHiddenMarketplace = React.createClass({displayName: 'FxOSHiddenMarketplace',
render: function() {
return React.DOM.iframe({id: "marketplace", src: this.props.marketplaceSrc, hidden: true});
},
componentDidUpdate: function() {
// This happens only once when we change the 'src' property of the iframe.
if (this.props.onMarketplaceMessage) {
// The reason for listening on the global window instead of on the
// iframe content window is because the Marketplace is doing a
// window.top.postMessage.
window.addEventListener("message", this.props.onMarketplaceMessage);
}
}
});
var FxOSConversationModel = Backbone.Model.extend({
setupOutgoingCall: function() {
// The FxOS Loop client exposes a "loop-call" activity. If we get the
// activity onerror callback it means that there is no "loop-call"
// activity handler available and so no FxOS Loop client installed.
var request = new MozActivity({
name: "loop-call",
data: {
type: "loop/token",
token: this.get("loopToken"),
callerId: this.get("callerId"),
callType: this.get("callType")
}
});
request.onsuccess = function() {};
request.onerror = (function(event) {
if (event.target.error.name !== "NO_PROVIDER") {
console.error ("Unexpected " + event.target.error.name);
this.trigger("session:error", "fxos_app_needed", {
fxosAppName: loop.config.fxosApp.name
});
return;
}
this.trigger("fxos:app-needed");
}).bind(this);
},
onMarketplaceMessage: function(event) {
var message = event.data;
switch (message.name) {
case "loaded":
var marketplace = window.document.getElementById("marketplace");
// Once we have it loaded, we request the installation of the FxOS
// Loop client app. We will be receiving the result of this action
// via postMessage from the child iframe.
marketplace.contentWindow.postMessage({
"name": "install-package",
"data": {
"product": {
"name": loop.config.fxosApp.name,
"manifest_url": loop.config.fxosApp.manifestUrl,
"is_packaged": true
}
}
}, "*");
break;
case "install-package":
window.removeEventListener("message", this.onMarketplaceMessage);
if (message.error) {
console.error(message.error.error);
this.trigger("session:error", "fxos_app_needed", {
fxosAppName: loop.config.fxosApp.name
});
return;
}
// We installed the FxOS app \o/, so we can continue with the call
// process.
this.setupOutgoingCall();
break;
}
}
});
var ConversationHeader = React.createClass({displayName: 'ConversationHeader',
render: function() {
var cx = React.addons.classSet;
@ -233,8 +322,10 @@ loop.webapp = (function($, _, OT, mozL10n) {
*/
var StartConversationView = React.createClass({displayName: 'StartConversationView',
propTypes: {
model: React.PropTypes.instanceOf(sharedModels.ConversationModel)
.isRequired,
model: React.PropTypes.oneOfType([
React.PropTypes.instanceOf(sharedModels.ConversationModel),
React.PropTypes.instanceOf(FxOSConversationModel)
]).isRequired,
// XXX Check more tightly here when we start injecting window.loop.*
notifications: React.PropTypes.object.isRequired,
client: React.PropTypes.object.isRequired
@ -257,15 +348,29 @@ loop.webapp = (function($, _, OT, mozL10n) {
window.addEventListener("click", this.clickHandler);
this.props.model.listenTo(this.props.model, "session:error",
this._onSessionError);
this.props.model.listenTo(this.props.model, "fxos:app-needed",
this._onFxOSAppNeeded);
this.props.client.requestCallUrlInfo(this.props.model.get("loopToken"),
this._setConversationTimestamp);
},
_onSessionError: function(error) {
console.error(error);
this.props.notifications.errorL10n("unable_retrieve_call_info");
_onSessionError: function(error, l10nProps) {
var errorL10n = error || "unable_retrieve_call_info";
this.props.notifications.errorL10n(errorL10n, l10nProps);
console.error(errorL10n);
},
_onFxOSAppNeeded: function() {
this.setState({
marketplaceSrc: loop.config.marketplaceUrl
});
this.setState({
onMarketplaceMessage: this.props.model.onMarketplaceMessage.bind(
this.props.model
)
});
},
/**
* Initiates the call.
* Takes in a call type parameter "audio" or "audio-video" and returns
@ -330,6 +435,10 @@ loop.webapp = (function($, _, OT, mozL10n) {
"terms-service": true,
hide: (localStorage.getItem("has-seen-tos") === "true")
});
var chevronClasses = React.addons.classSet({
"btn-chevron": true,
"disabled": this.state.disableCallButton
});
return (
React.DOM.div({className: "container"},
@ -360,7 +469,7 @@ loop.webapp = (function($, _, OT, mozL10n) {
React.DOM.span({className: "standalone-call-btn-video-icon"})
),
React.DOM.div({className: "btn-chevron",
React.DOM.div({className: chevronClasses,
onClick: this._toggleCallOptionsMenu}
)
@ -388,6 +497,10 @@ loop.webapp = (function($, _, OT, mozL10n) {
dangerouslySetInnerHTML: {__html: tosHTML}})
),
FxOSHiddenMarketplace({
marketplaceSrc: this.state.marketplaceSrc,
onMarketplaceMessage: this.state.onMarketplaceMessage}),
ConversationFooter(null)
)
);
@ -434,8 +547,10 @@ loop.webapp = (function($, _, OT, mozL10n) {
var OutgoingConversationView = React.createClass({displayName: 'OutgoingConversationView',
propTypes: {
client: React.PropTypes.instanceOf(loop.StandaloneClient).isRequired,
conversation: React.PropTypes.instanceOf(sharedModels.ConversationModel)
.isRequired,
conversation: React.PropTypes.oneOfType([
React.PropTypes.instanceOf(sharedModels.ConversationModel),
React.PropTypes.instanceOf(FxOSConversationModel)
]).isRequired,
helper: React.PropTypes.instanceOf(sharedUtils.Helper).isRequired,
notifications: React.PropTypes.instanceOf(sharedModels.NotificationCollection)
.isRequired,
@ -689,8 +804,10 @@ loop.webapp = (function($, _, OT, mozL10n) {
var WebappRootView = React.createClass({displayName: 'WebappRootView',
propTypes: {
client: React.PropTypes.instanceOf(loop.StandaloneClient).isRequired,
conversation: React.PropTypes.instanceOf(sharedModels.ConversationModel)
.isRequired,
conversation: React.PropTypes.oneOfType([
React.PropTypes.instanceOf(sharedModels.ConversationModel),
React.PropTypes.instanceOf(FxOSConversationModel)
]).isRequired,
helper: React.PropTypes.instanceOf(sharedUtils.Helper).isRequired,
notifications: React.PropTypes.instanceOf(sharedModels.NotificationCollection)
.isRequired,
@ -736,9 +853,15 @@ loop.webapp = (function($, _, OT, mozL10n) {
baseServerUrl: loop.config.serverUrl
});
var notifications = new sharedModels.NotificationCollection();
var conversation = new sharedModels.ConversationModel({}, {
sdk: OT
});
var conversation
if (helper.isFirefoxOS(navigator.userAgent)) {
conversation = new FxOSConversationModel();
} else {
conversation = new sharedModels.ConversationModel({}, {
sdk: OT
});
}
var feedbackApiClient = new loop.FeedbackAPIClient(
loop.config.feedbackApiUrl, {
product: loop.config.feedbackProductName,
@ -777,6 +900,7 @@ loop.webapp = (function($, _, OT, mozL10n) {
UnsupportedDeviceView: UnsupportedDeviceView,
init: init,
PromoteFirefoxView: PromoteFirefoxView,
WebappRootView: WebappRootView
WebappRootView: WebappRootView,
FxOSConversationModel: FxOSConversationModel
};
})(jQuery, _, window.OT, navigator.mozL10n);

View File

@ -4,7 +4,7 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
/* global loop:true, React */
/* global loop:true, React, MozActivity */
/* jshint newcap:false, maxlen:false */
var loop = loop || {};
@ -122,6 +122,95 @@ loop.webapp = (function($, _, OT, mozL10n) {
}
});
/**
* The Firefox Marketplace exposes a web page that contains a postMesssage
* based API that wraps a small set of functionality from the WebApps API
* that allow us to request the installation of apps given their manifest
* URL. We will be embedding the content of this web page within an hidden
* iframe in case that we need to request the installation of the FxOS Loop
* client.
*/
var FxOSHiddenMarketplace = React.createClass({
render: function() {
return <iframe id="marketplace" src={this.props.marketplaceSrc} hidden/>;
},
componentDidUpdate: function() {
// This happens only once when we change the 'src' property of the iframe.
if (this.props.onMarketplaceMessage) {
// The reason for listening on the global window instead of on the
// iframe content window is because the Marketplace is doing a
// window.top.postMessage.
window.addEventListener("message", this.props.onMarketplaceMessage);
}
}
});
var FxOSConversationModel = Backbone.Model.extend({
setupOutgoingCall: function() {
// The FxOS Loop client exposes a "loop-call" activity. If we get the
// activity onerror callback it means that there is no "loop-call"
// activity handler available and so no FxOS Loop client installed.
var request = new MozActivity({
name: "loop-call",
data: {
type: "loop/token",
token: this.get("loopToken"),
callerId: this.get("callerId"),
callType: this.get("callType")
}
});
request.onsuccess = function() {};
request.onerror = (function(event) {
if (event.target.error.name !== "NO_PROVIDER") {
console.error ("Unexpected " + event.target.error.name);
this.trigger("session:error", "fxos_app_needed", {
fxosAppName: loop.config.fxosApp.name
});
return;
}
this.trigger("fxos:app-needed");
}).bind(this);
},
onMarketplaceMessage: function(event) {
var message = event.data;
switch (message.name) {
case "loaded":
var marketplace = window.document.getElementById("marketplace");
// Once we have it loaded, we request the installation of the FxOS
// Loop client app. We will be receiving the result of this action
// via postMessage from the child iframe.
marketplace.contentWindow.postMessage({
"name": "install-package",
"data": {
"product": {
"name": loop.config.fxosApp.name,
"manifest_url": loop.config.fxosApp.manifestUrl,
"is_packaged": true
}
}
}, "*");
break;
case "install-package":
window.removeEventListener("message", this.onMarketplaceMessage);
if (message.error) {
console.error(message.error.error);
this.trigger("session:error", "fxos_app_needed", {
fxosAppName: loop.config.fxosApp.name
});
return;
}
// We installed the FxOS app \o/, so we can continue with the call
// process.
this.setupOutgoingCall();
break;
}
}
});
var ConversationHeader = React.createClass({
render: function() {
var cx = React.addons.classSet;
@ -233,8 +322,10 @@ loop.webapp = (function($, _, OT, mozL10n) {
*/
var StartConversationView = React.createClass({
propTypes: {
model: React.PropTypes.instanceOf(sharedModels.ConversationModel)
.isRequired,
model: React.PropTypes.oneOfType([
React.PropTypes.instanceOf(sharedModels.ConversationModel),
React.PropTypes.instanceOf(FxOSConversationModel)
]).isRequired,
// XXX Check more tightly here when we start injecting window.loop.*
notifications: React.PropTypes.object.isRequired,
client: React.PropTypes.object.isRequired
@ -257,15 +348,29 @@ loop.webapp = (function($, _, OT, mozL10n) {
window.addEventListener("click", this.clickHandler);
this.props.model.listenTo(this.props.model, "session:error",
this._onSessionError);
this.props.model.listenTo(this.props.model, "fxos:app-needed",
this._onFxOSAppNeeded);
this.props.client.requestCallUrlInfo(this.props.model.get("loopToken"),
this._setConversationTimestamp);
},
_onSessionError: function(error) {
console.error(error);
this.props.notifications.errorL10n("unable_retrieve_call_info");
_onSessionError: function(error, l10nProps) {
var errorL10n = error || "unable_retrieve_call_info";
this.props.notifications.errorL10n(errorL10n, l10nProps);
console.error(errorL10n);
},
_onFxOSAppNeeded: function() {
this.setState({
marketplaceSrc: loop.config.marketplaceUrl
});
this.setState({
onMarketplaceMessage: this.props.model.onMarketplaceMessage.bind(
this.props.model
)
});
},
/**
* Initiates the call.
* Takes in a call type parameter "audio" or "audio-video" and returns
@ -330,6 +435,10 @@ loop.webapp = (function($, _, OT, mozL10n) {
"terms-service": true,
hide: (localStorage.getItem("has-seen-tos") === "true")
});
var chevronClasses = React.addons.classSet({
"btn-chevron": true,
"disabled": this.state.disableCallButton
});
return (
<div className="container">
@ -360,7 +469,7 @@ loop.webapp = (function($, _, OT, mozL10n) {
<span className="standalone-call-btn-video-icon"></span>
</button>
<div className="btn-chevron"
<div className={chevronClasses}
onClick={this._toggleCallOptionsMenu}>
</div>
@ -388,6 +497,10 @@ loop.webapp = (function($, _, OT, mozL10n) {
dangerouslySetInnerHTML={{__html: tosHTML}}></p>
</div>
<FxOSHiddenMarketplace
marketplaceSrc={this.state.marketplaceSrc}
onMarketplaceMessage= {this.state.onMarketplaceMessage} />
<ConversationFooter />
</div>
);
@ -434,8 +547,10 @@ loop.webapp = (function($, _, OT, mozL10n) {
var OutgoingConversationView = React.createClass({
propTypes: {
client: React.PropTypes.instanceOf(loop.StandaloneClient).isRequired,
conversation: React.PropTypes.instanceOf(sharedModels.ConversationModel)
.isRequired,
conversation: React.PropTypes.oneOfType([
React.PropTypes.instanceOf(sharedModels.ConversationModel),
React.PropTypes.instanceOf(FxOSConversationModel)
]).isRequired,
helper: React.PropTypes.instanceOf(sharedUtils.Helper).isRequired,
notifications: React.PropTypes.instanceOf(sharedModels.NotificationCollection)
.isRequired,
@ -689,8 +804,10 @@ loop.webapp = (function($, _, OT, mozL10n) {
var WebappRootView = React.createClass({
propTypes: {
client: React.PropTypes.instanceOf(loop.StandaloneClient).isRequired,
conversation: React.PropTypes.instanceOf(sharedModels.ConversationModel)
.isRequired,
conversation: React.PropTypes.oneOfType([
React.PropTypes.instanceOf(sharedModels.ConversationModel),
React.PropTypes.instanceOf(FxOSConversationModel)
]).isRequired,
helper: React.PropTypes.instanceOf(sharedUtils.Helper).isRequired,
notifications: React.PropTypes.instanceOf(sharedModels.NotificationCollection)
.isRequired,
@ -736,9 +853,15 @@ loop.webapp = (function($, _, OT, mozL10n) {
baseServerUrl: loop.config.serverUrl
});
var notifications = new sharedModels.NotificationCollection();
var conversation = new sharedModels.ConversationModel({}, {
sdk: OT
});
var conversation
if (helper.isFirefoxOS(navigator.userAgent)) {
conversation = new FxOSConversationModel();
} else {
conversation = new sharedModels.ConversationModel({}, {
sdk: OT
});
}
var feedbackApiClient = new loop.FeedbackAPIClient(
loop.config.feedbackApiUrl, {
product: loop.config.feedbackProductName,
@ -777,6 +900,7 @@ loop.webapp = (function($, _, OT, mozL10n) {
UnsupportedDeviceView: UnsupportedDeviceView,
init: init,
PromoteFirefoxView: PromoteFirefoxView,
WebappRootView: WebappRootView
WebappRootView: WebappRootView,
FxOSConversationModel: FxOSConversationModel
};
})(jQuery, _, window.OT, navigator.mozL10n);

View File

@ -46,6 +46,7 @@ clientShortname=WebRTC!
call_url_creation_date_label=(from {{call_url_creation_date}})
call_progress_connecting_description=Connecting…
call_progress_ringing_description=Ringing…
fxos_app_needed=Please install the {{fxosAppName}} app from the Firefox Marketplace.
feedback_call_experience_heading2=How was your conversation?
feedback_what_makes_you_sad=What makes you sad?

View File

@ -21,6 +21,12 @@ function getConfigFile(req, res) {
"loop.config.serverUrl = 'http://localhost:" + loopServerPort + "';",
"loop.config.feedbackApiUrl = '" + feedbackApiUrl + "';",
"loop.config.feedbackProductName = '" + feedbackProductName + "';",
// XXX Update with the real marketplace url once the FxOS Loop app is
// uploaded to the marketplace bug 1053424
"loop.config.marketplaceUrl = 'http://fake-market.herokuapp.com/iframe-install.html'",
"loop.config.fxosApp = loop.config.fxosApp || {};",
"loop.config.fxosApp.name = 'Loop';",
"loop.config.fxosApp.manifestUrl = 'http://fake-market.herokuapp.com/apps/packagedApp/manifest.webapp';"
].join("\n"));
}

View File

@ -355,8 +355,8 @@ describe("loop.shared.models", function() {
beforeEach(function() {
collection = new sharedModels.NotificationCollection();
sandbox.stub(l10n, "get", function(x) {
return "translated:" + x;
sandbox.stub(l10n, "get", function(x, y) {
return "translated:" + x + (y ? ':' + y : '');
});
notifData = {level: "error", message: "plop"};
testNotif = new sharedModels.NotificationModel(notifData);
@ -400,6 +400,15 @@ describe("loop.shared.models", function() {
expect(collection.at(0).get("level")).eql("error");
expect(collection.at(0).get("message")).eql("translated:fakeId");
});
it("should notify an error using a l10n string id + l10n properties",
function() {
collection.errorL10n("fakeId", "fakeProp");
expect(collection).to.have.length.of(1);
expect(collection.at(0).get("level")).eql("error");
expect(collection.at(0).get("message")).eql("translated:fakeId:fakeProp");
});
});
});

View File

@ -53,6 +53,39 @@ describe("loop.shared.utils", function() {
expect(helper.isFirefox("Opera")).eql(false);
});
});
describe("#isFirefoxOS", function() {
describe("without mozActivities", function() {
it("shouldn't detect FirefoxOS on mobile platform", function() {
expect(helper.isFirefoxOS("mobi")).eql(false);
});
it("shouldn't detect FirefoxOS on non mobile platform", function() {
expect(helper.isFirefoxOS("whatever")).eql(false);
});
});
describe("with mozActivities", function() {
var realMozActivity;
before(function() {
realMozActivity = window.MozActivity;
window.MozActivity = {};
});
after(function() {
window.MozActivity = realMozActivity;
});
it("should detect FirefoxOS on mobile platform", function() {
expect(helper.isFirefoxOS("mobi")).eql(true);
});
it("shouldn't detect FirefoxOS on non mobile platform", function() {
expect(helper.isFirefoxOS("whatever")).eql(false);
});
});
});
});
describe("#getBoolPreference", function() {

View File

@ -20,6 +20,9 @@ describe("loop.shared.views", function() {
beforeEach(function() {
sandbox = sinon.sandbox.create();
sandbox.useFakeTimers(); // exposes sandbox.clock as a fake timer
sandbox.stub(l10n, "get", function(x) {
return "translated:" + x;
});
});
afterEach(function() {
@ -424,9 +427,6 @@ describe("loop.shared.views", function() {
var comp, fakeFeedbackApiClient;
beforeEach(function() {
sandbox.stub(l10n, "get", function(x) {
return x;
});
fakeFeedbackApiClient = {send: sandbox.stub()};
comp = TestUtils.renderIntoDocument(sharedViews.FeedbackView({
feedbackApiClient: fakeFeedbackApiClient
@ -601,9 +601,6 @@ describe("loop.shared.views", function() {
}
beforeEach(function() {
sandbox.stub(l10n, "get", function(x) {
return "translated:" + x;
});
coll = new sharedModels.NotificationCollection();
view = mountTestComponent({notifications: coll});
testNotif = {level: "warning", message: "foo"};

View File

@ -384,7 +384,7 @@ describe("loop.webapp", function() {
sinon.assert.notCalled(ocView._websocket.mediaUp);
});
it("should notify the websocket that media is up if both streams" +
it("should notify tloadhe websocket that media is up if both streams" +
"are connected", function() {
conversation.set("publishedStream", true);
conversation.set("subscribedStream", true);
@ -691,7 +691,8 @@ describe("loop.webapp", function() {
sdk: {}
});
sandbox.spy(conversation, "listenTo");
conversation.onMarketplaceMessage = function() {};
sandbox.stub(notifications, "errorL10n");
requestCallUrlInfo = sandbox.stub();
view = React.addons.TestUtils.renderIntoDocument(
@ -701,6 +702,8 @@ describe("loop.webapp", function() {
client: {requestCallUrlInfo: requestCallUrlInfo}
})
);
loop.config.marketplaceUrl = "http://market/";
});
it("should call requestCallUrlInfo", function() {
@ -710,21 +713,47 @@ describe("loop.webapp", function() {
sinon.match.func);
});
it("should listen for session:error events", function() {
sinon.assert.calledOnce(conversation.listenTo);
sinon.assert.calledWithExactly(conversation.listenTo, conversation,
"session:error", sinon.match.func);
});
it("should trigger a notication when a session:error model event is " +
" received", function() {
sandbox.stub(notifications, "errorL10n");
conversation.trigger("session:error", "tech error");
it("should add a notification when a session:error model event is " +
" received without an argument", function() {
conversation.trigger("session:error");
sinon.assert.calledOnce(notifications.errorL10n);
sinon.assert.calledWithExactly(notifications.errorL10n,
"unable_retrieve_call_info");
sinon.match.string, undefined);
});
it("should add a notification with the custom message id when a " +
"session:error event is fired with an argument", function() {
conversation.trigger("session:error", "tech_error");
sinon.assert.calledOnce(notifications.errorL10n);
sinon.assert.calledWithExactly(notifications.errorL10n,
"tech_error", undefined);
});
it("should add a notification with the custom message id when a " +
"session:error event is fired with an argument and parameters",
function() {
conversation.trigger("session:error", "tech_error", {param: "value"});
sinon.assert.calledOnce(notifications.errorL10n);
sinon.assert.calledWithExactly(notifications.errorL10n,
"tech_error", { param: "value" });
});
it("should set marketplace hidden iframe src when fxos:app-needed is " +
"triggered", function(done) {
var marketplace = view.getDOMNode().querySelector("#marketplace");
expect(marketplace.src).to.be.equal("");
conversation.trigger("fxos:app-needed");
view.forceUpdate(function() {
expect(marketplace.src).to.be.equal(loop.config.marketplaceUrl);
done();
});
});
});
describe("#render", function() {
@ -826,4 +855,198 @@ describe("loop.webapp", function() {
});
});
});
describe("Firefox OS", function() {
var conversation, client;
before(function() {
client = new loop.StandaloneClient({
baseServerUrl: "http://fake.example.com"
});
sandbox.stub(client, "requestCallInfo");
conversation = new sharedModels.ConversationModel({}, {
sdk: {},
pendingCallTimeout: 1000
});
});
describe("Setup call", function() {
var conversation, setupOutgoingCall, view, requestCallUrlInfo;
beforeEach(function() {
conversation = new loop.webapp.FxOSConversationModel({
loopToken: "fakeToken"
});
setupOutgoingCall = sandbox.stub(conversation, "setupOutgoingCall");
var standaloneClientStub = {
requestCallUrlInfo: function(token, cb) {
cb(null, {urlCreationDate: 0});
},
settings: {baseServerUrl: loop.webapp.baseServerUrl}
};
view = React.addons.TestUtils.renderIntoDocument(
loop.webapp.StartConversationView({
model: conversation,
notifications: notifications,
client: standaloneClientStub
})
);
});
it("should start the conversation establishment process", function() {
var button = view.getDOMNode().querySelector("button");
React.addons.TestUtils.Simulate.click(button);
sinon.assert.calledOnce(setupOutgoingCall);
});
});
describe("FxOSConversationModel", function() {
var model, realMozActivity;
before(function() {
model = new loop.webapp.FxOSConversationModel({
loopToken: "fakeToken",
callerId: "callerId",
callType: "callType"
});
realMozActivity = window.MozActivity;
loop.config.fxosApp = {
name: "Firefox Hello"
};
});
after(function() {
window.MozActivity = realMozActivity;
});
describe("setupOutgoingCall", function() {
var _activityProps, _onerror, trigger;
function fireError(errorName) {
_onerror({
target: {
error: {
name: errorName
}
}
});
}
before(function() {
window.MozActivity = function(activityProps) {
_activityProps = activityProps;
return {
set onerror(callback) {
_onerror = callback;
}
};
};
});
after(function() {
window.MozActivity = realMozActivity;
});
beforeEach(function() {
trigger = sandbox.stub(model, "trigger");
});
afterEach(function() {
trigger.restore();
});
it("Activity properties", function() {
expect(_activityProps).to.not.exist;
model.setupOutgoingCall();
expect(_activityProps).to.exist;
expect(_activityProps).eql({
name: "loop-call",
data: {
type: "loop/token",
token: "fakeToken",
callerId: "callerId",
callType: "callType"
}
});
});
it("NO_PROVIDER activity error should trigger fxos:app-needed",
function() {
sinon.assert.notCalled(trigger);
model.setupOutgoingCall();
fireError("NO_PROVIDER");
sinon.assert.calledOnce(trigger);
sinon.assert.calledWithExactly(trigger, "fxos:app-needed");
}
);
it("Other activity error should trigger session:error",
function() {
sinon.assert.notCalled(trigger);
model.setupOutgoingCall();
fireError("whatever");
sinon.assert.calledOnce(trigger);
sinon.assert.calledWithExactly(trigger, "session:error",
"fxos_app_needed", { fxosAppName: loop.config.fxosApp.name });
}
);
});
describe("onMarketplaceMessage", function() {
var view, setupOutgoingCall, trigger;
before(function() {
view = React.addons.TestUtils.renderIntoDocument(
loop.webapp.StartConversationView({
model: model,
notifications: notifications,
client: {requestCallUrlInfo: sandbox.stub()}
})
);
});
beforeEach(function() {
setupOutgoingCall = sandbox.stub(model, "setupOutgoingCall");
trigger = sandbox.stub(model, "trigger");
});
afterEach(function() {
setupOutgoingCall.restore();
trigger.restore();
});
it("We should call trigger a FxOS outgoing call if we get " +
"install-package message without error", function() {
sinon.assert.notCalled(setupOutgoingCall);
model.onMarketplaceMessage({
data: {
name: "install-package"
}
});
sinon.assert.calledOnce(setupOutgoingCall);
});
it("We should trigger a session:error event if we get " +
"install-package message with an error", function() {
sinon.assert.notCalled(trigger);
sinon.assert.notCalled(setupOutgoingCall);
model.onMarketplaceMessage({
data: {
name: "install-package",
error: "error"
}
});
sinon.assert.notCalled(setupOutgoingCall);
sinon.assert.calledOnce(trigger);
sinon.assert.calledWithExactly(trigger, "session:error",
"fxos_app_needed", { fxosAppName: loop.config.fxosApp.name });
});
});
});
});
});

View File

@ -149,14 +149,14 @@
),
Section({name: "IncomingCallView"},
Example({summary: "Default / incoming video call", dashed: "true", style: {width: "280px"}},
Example({summary: "Default / incoming video call", dashed: "true", style: {width: "260px", height: "254px"}},
React.DOM.div({className: "fx-embedded"},
IncomingCallView({model: mockConversationModel,
video: true})
)
),
Example({summary: "Default / incoming audio only call", dashed: "true", style: {width: "280px"}},
Example({summary: "Default / incoming audio only call", dashed: "true", style: {width: "260px", height: "254px"}},
React.DOM.div({className: "fx-embedded"},
IncomingCallView({model: mockConversationModel,
video: false})
@ -165,7 +165,7 @@
),
Section({name: "IncomingCallView-ActiveState"},
Example({summary: "Default", dashed: "true", style: {width: "280px"}},
Example({summary: "Default", dashed: "true", style: {width: "260px", height: "254px"}},
React.DOM.div({className: "fx-embedded"},
IncomingCallView({model: mockConversationModel,
showDeclineMenu: true,
@ -319,13 +319,13 @@
React.DOM.strong(null, "Note:"), " For the useable demo, you can access submitted data at ",
React.DOM.a({href: "https://input.allizom.org/"}, "input.allizom.org"), "."
),
Example({summary: "Default (useable demo)", dashed: "true", style: {width: "280px"}},
Example({summary: "Default (useable demo)", dashed: "true", style: {width: "260px"}},
FeedbackView({feedbackApiClient: stageFeedbackApiClient})
),
Example({summary: "Detailed form", dashed: "true", style: {width: "280px"}},
Example({summary: "Detailed form", dashed: "true", style: {width: "260px"}},
FeedbackView({feedbackApiClient: stageFeedbackApiClient, step: "form"})
),
Example({summary: "Thank you!", dashed: "true", style: {width: "280px"}},
Example({summary: "Thank you!", dashed: "true", style: {width: "260px"}},
FeedbackView({feedbackApiClient: stageFeedbackApiClient, step: "finished"})
)
),

View File

@ -149,14 +149,14 @@
</Section>
<Section name="IncomingCallView">
<Example summary="Default / incoming video call" dashed="true" style={{width: "280px"}}>
<Example summary="Default / incoming video call" dashed="true" style={{width: "260px", height: "254px"}}>
<div className="fx-embedded">
<IncomingCallView model={mockConversationModel}
video={true} />
</div>
</Example>
<Example summary="Default / incoming audio only call" dashed="true" style={{width: "280px"}}>
<Example summary="Default / incoming audio only call" dashed="true" style={{width: "260px", height: "254px"}}>
<div className="fx-embedded">
<IncomingCallView model={mockConversationModel}
video={false} />
@ -165,7 +165,7 @@
</Section>
<Section name="IncomingCallView-ActiveState">
<Example summary="Default" dashed="true" style={{width: "280px"}}>
<Example summary="Default" dashed="true" style={{width: "260px", height: "254px"}}>
<div className="fx-embedded" >
<IncomingCallView model={mockConversationModel}
showDeclineMenu={true}
@ -319,13 +319,13 @@
<strong>Note:</strong> For the useable demo, you can access submitted data at&nbsp;
<a href="https://input.allizom.org/">input.allizom.org</a>.
</p>
<Example summary="Default (useable demo)" dashed="true" style={{width: "280px"}}>
<Example summary="Default (useable demo)" dashed="true" style={{width: "260px"}}>
<FeedbackView feedbackApiClient={stageFeedbackApiClient} />
</Example>
<Example summary="Detailed form" dashed="true" style={{width: "280px"}}>
<Example summary="Detailed form" dashed="true" style={{width: "260px"}}>
<FeedbackView feedbackApiClient={stageFeedbackApiClient} step="form" />
</Example>
<Example summary="Thank you!" dashed="true" style={{width: "280px"}}>
<Example summary="Thank you!" dashed="true" style={{width: "260px"}}>
<FeedbackView feedbackApiClient={stageFeedbackApiClient} step="finished" />
</Example>
</Section>

View File

@ -1,5 +1,4 @@
[DEFAULT]
skip-if = e10s # Bug 1058879 - canvas debugger tests disabled with e10s
subsuite = devtools
support-files =
doc_simple-canvas.html
@ -20,6 +19,7 @@ support-files =
[browser_canvas-actor-test-08.js]
[browser_canvas-actor-test-09.js]
[browser_canvas-actor-test-10.js]
skip-if = e10s # Bug 1058879 - canvas debugger tests disabled with e10s
[browser_canvas-frontend-call-highlight.js]
[browser_canvas-frontend-call-list.js]
[browser_canvas-frontend-call-search.js]

View File

@ -7,10 +7,9 @@
*/
function ifTestingSupported() {
let [target, debuggee, front] = yield initCallWatcherBackend(SIMPLE_CANVAS_URL);
let { target, front } = yield initCallWatcherBackend(SIMPLE_CANVAS_URL);
ok(target, "Should have a target available.");
ok(debuggee, "Should have a debuggee available.");
ok(front, "Should have a protocol front available.");
yield removeTab(target.tab);

View File

@ -7,7 +7,7 @@
*/
function ifTestingSupported() {
let [target, debuggee, front] = yield initCallWatcherBackend(SIMPLE_CANVAS_URL);
let { target, front } = yield initCallWatcherBackend(SIMPLE_CANVAS_URL);
let navigated = once(target, "navigate");

View File

@ -7,7 +7,7 @@
*/
function ifTestingSupported() {
let [target, debuggee, front] = yield initCanavsDebuggerBackend(SIMPLE_CANVAS_URL);
let { target, front } = yield initCanvasDebuggerBackend(SIMPLE_CANVAS_URL);
let navigated = once(target, "navigate");

View File

@ -7,7 +7,7 @@
*/
function ifTestingSupported() {
let [target, debuggee, front] = yield initCanavsDebuggerBackend(SIMPLE_CANVAS_URL);
let { target, front } = yield initCanvasDebuggerBackend(SIMPLE_CANVAS_URL);
let navigated = once(target, "navigate");

View File

@ -7,7 +7,7 @@
*/
function ifTestingSupported() {
let [target, debuggee, front] = yield initCanavsDebuggerBackend(SIMPLE_CANVAS_URL);
let { target, front } = yield initCanvasDebuggerBackend(SIMPLE_CANVAS_URL);
let navigated = once(target, "navigate");

View File

@ -6,7 +6,7 @@
*/
function ifTestingSupported() {
let [target, debuggee, front] = yield initCanavsDebuggerBackend(SIMPLE_CANVAS_TRANSPARENT_URL);
let { target, front } = yield initCanvasDebuggerBackend(SIMPLE_CANVAS_TRANSPARENT_URL);
let navigated = once(target, "navigate");

View File

@ -7,7 +7,7 @@
*/
function ifTestingSupported() {
let [target, debuggee, front] = yield initCanavsDebuggerBackend(SIMPLE_CANVAS_URL);
let { target, front } = yield initCanvasDebuggerBackend(SIMPLE_CANVAS_URL);
let navigated = once(target, "navigate");

View File

@ -7,7 +7,7 @@
*/
function ifTestingSupported() {
let [target, debuggee, front] = yield initCanavsDebuggerBackend(SIMPLE_BITMASKS_URL);
let { target, front } = yield initCanvasDebuggerBackend(SIMPLE_BITMASKS_URL);
let navigated = once(target, "navigate");

View File

@ -7,7 +7,7 @@
*/
function ifTestingSupported() {
let [target, debuggee, front] = yield initCanavsDebuggerBackend(WEBGL_ENUM_URL);
let { target, front } = yield initCanvasDebuggerBackend(WEBGL_ENUM_URL);
let navigated = once(target, "navigate");

View File

@ -7,7 +7,9 @@
*/
function ifTestingSupported() {
let [target, debuggee, front] = yield initCanavsDebuggerBackend(WEBGL_BINDINGS_URL);
let { target, front } = yield initCanvasDebuggerBackend(WEBGL_BINDINGS_URL);
// XXX - use of |debuggee| here is incompatible with e10s - bug 1058879.
let debuggee = target.window.wrappedJSObject
let navigated = once(target, "navigate");

View File

@ -6,7 +6,7 @@
*/
function ifTestingSupported() {
let [target, debuggee, panel] = yield initCanavsDebuggerFrontend(SIMPLE_CANVAS_URL);
let { target, panel } = yield initCanvasDebuggerFrontend(SIMPLE_CANVAS_URL);
let { window, $, EVENTS, SnapshotsListView, CallsListView } = panel.panelWin;
yield reload(target);

View File

@ -7,7 +7,7 @@
*/
function ifTestingSupported() {
let [target, debuggee, panel] = yield initCanavsDebuggerFrontend(SIMPLE_CANVAS_URL);
let { target, panel } = yield initCanvasDebuggerFrontend(SIMPLE_CANVAS_URL);
let { window, $, EVENTS, SnapshotsListView, CallsListView } = panel.panelWin;
yield reload(target);

View File

@ -6,7 +6,7 @@
*/
function ifTestingSupported() {
let [target, debuggee, panel] = yield initCanavsDebuggerFrontend(SIMPLE_CANVAS_URL);
let { target, panel } = yield initCanvasDebuggerFrontend(SIMPLE_CANVAS_URL);
let { window, $, EVENTS, SnapshotsListView, CallsListView } = panel.panelWin;
let searchbox = $("#calls-searchbox");

View File

@ -6,7 +6,7 @@
*/
function ifTestingSupported() {
let [target, debuggee, panel] = yield initCanavsDebuggerFrontend(SIMPLE_CANVAS_DEEP_STACK_URL);
let { target, panel } = yield initCanvasDebuggerFrontend(SIMPLE_CANVAS_DEEP_STACK_URL);
let { window, $, $all, EVENTS, SnapshotsListView, CallsListView } = panel.panelWin;
yield reload(target);

View File

@ -7,7 +7,7 @@
*/
function ifTestingSupported() {
let [target, debuggee, panel] = yield initCanavsDebuggerFrontend(SIMPLE_CANVAS_DEEP_STACK_URL);
let { target, panel } = yield initCanvasDebuggerFrontend(SIMPLE_CANVAS_DEEP_STACK_URL);
let { window, $, $all, EVENTS, SnapshotsListView, CallsListView } = panel.panelWin;
yield reload(target);

View File

@ -7,7 +7,7 @@
*/
function ifTestingSupported() {
let [target, debuggee, panel] = yield initCanavsDebuggerFrontend(SIMPLE_CANVAS_DEEP_STACK_URL);
let { target, panel } = yield initCanvasDebuggerFrontend(SIMPLE_CANVAS_DEEP_STACK_URL);
let { window, $, $all, EVENTS, SnapshotsListView, CallsListView } = panel.panelWin;
yield reload(target);

View File

@ -6,7 +6,7 @@
*/
function ifTestingSupported() {
let [target, debuggee, panel] = yield initCanavsDebuggerFrontend(SIMPLE_CANVAS_URL);
let { target, panel } = yield initCanvasDebuggerFrontend(SIMPLE_CANVAS_URL);
let { window, EVENTS, SnapshotsListView } = panel.panelWin;
yield reload(target);

View File

@ -6,7 +6,7 @@
*/
function ifTestingSupported() {
let [target, debuggee, panel] = yield initCanavsDebuggerFrontend(SIMPLE_CANVAS_URL);
let { target, panel } = yield initCanvasDebuggerFrontend(SIMPLE_CANVAS_URL);
let { window, $, EVENTS, SnapshotsListView } = panel.panelWin;
yield reload(target);

View File

@ -6,7 +6,7 @@
*/
function ifTestingSupported() {
let [target, debuggee, panel] = yield initCanavsDebuggerFrontend(SIMPLE_CANVAS_URL);
let { target, panel } = yield initCanvasDebuggerFrontend(SIMPLE_CANVAS_URL);
let { window, $, $all, EVENTS, SnapshotsListView } = panel.panelWin;
yield reload(target);

View File

@ -7,7 +7,7 @@
*/
function ifTestingSupported() {
let [target, debuggee, panel] = yield initCanavsDebuggerFrontend(SIMPLE_CANVAS_URL);
let { target, panel } = yield initCanvasDebuggerFrontend(SIMPLE_CANVAS_URL);
let { window, $, $all, EVENTS, SnapshotsListView, CallsListView } = panel.panelWin;
yield reload(target);

View File

@ -6,7 +6,7 @@
*/
function ifTestingSupported() {
let [target, debuggee, panel] = yield initCanavsDebuggerFrontend(SIMPLE_CANVAS_URL);
let { target, panel } = yield initCanvasDebuggerFrontend(SIMPLE_CANVAS_URL);
let { $ } = panel.panelWin;
is($("#snapshots-pane").hasAttribute("hidden"), false,

View File

@ -6,7 +6,7 @@
*/
function ifTestingSupported() {
let [target, debuggee, panel] = yield initCanavsDebuggerFrontend(SIMPLE_CANVAS_URL);
let { target, panel } = yield initCanvasDebuggerFrontend(SIMPLE_CANVAS_URL);
let { window, EVENTS, $, SnapshotsListView } = panel.panelWin;
yield reload(target);

View File

@ -6,7 +6,7 @@
*/
function ifTestingSupported() {
let [target, debuggee, panel] = yield initCanavsDebuggerFrontend(SIMPLE_CANVAS_URL);
let { target, panel } = yield initCanvasDebuggerFrontend(SIMPLE_CANVAS_URL);
let { window, EVENTS, L10N, $, SnapshotsListView } = panel.panelWin;
yield reload(target);

View File

@ -7,7 +7,7 @@
*/
function ifTestingSupported() {
let [target, debuggee, panel] = yield initCanavsDebuggerFrontend(SIMPLE_CANVAS_URL);
let { target, panel } = yield initCanvasDebuggerFrontend(SIMPLE_CANVAS_URL);
let { window, EVENTS, $, SnapshotsListView } = panel.panelWin;
yield reload(target);

View File

@ -6,7 +6,7 @@
*/
function ifTestingSupported() {
let [target, debuggee, panel] = yield initCanavsDebuggerFrontend(SIMPLE_CANVAS_URL);
let { target, panel } = yield initCanvasDebuggerFrontend(SIMPLE_CANVAS_URL);
let { window, $, EVENTS } = panel.panelWin;
let reset = once(window, EVENTS.UI_RESET);

View File

@ -6,7 +6,7 @@
*/
function ifTestingSupported() {
let [target, debuggee, panel] = yield initCanavsDebuggerFrontend(SIMPLE_CANVAS_URL);
let { target, panel } = yield initCanvasDebuggerFrontend(SIMPLE_CANVAS_URL);
let { window, $, $all, EVENTS, SnapshotsListView, CallsListView } = panel.panelWin;
is(SnapshotsListView.itemCount, 0,

View File

@ -6,7 +6,7 @@
*/
function ifTestingSupported() {
let [target, debuggee, panel] = yield initCanavsDebuggerFrontend(SIMPLE_CANVAS_URL);
let { target, panel } = yield initCanvasDebuggerFrontend(SIMPLE_CANVAS_URL);
let { window, $, EVENTS, SnapshotsListView, CallsListView } = panel.panelWin;
yield reload(target);

View File

@ -6,7 +6,7 @@
*/
function ifTestingSupported() {
let [target, debuggee, panel] = yield initCanavsDebuggerFrontend(SIMPLE_CANVAS_URL);
let { target, panel } = yield initCanvasDebuggerFrontend(SIMPLE_CANVAS_URL);
let { window, $, EVENTS, gFront, SnapshotsListView, CallsListView } = panel.panelWin;
yield reload(target);

View File

@ -7,7 +7,7 @@
*/
function ifTestingSupported() {
let [target, debuggee, panel] = yield initCanavsDebuggerFrontend(SIMPLE_CANVAS_URL);
let { target, panel } = yield initCanvasDebuggerFrontend(SIMPLE_CANVAS_URL);
let { window, $, EVENTS, SnapshotsListView, CallsListView } = panel.panelWin;
yield reload(target);

View File

@ -6,7 +6,7 @@
*/
function ifTestingSupported() {
let [target, debuggee, panel] = yield initCanavsDebuggerFrontend(SIMPLE_CANVAS_URL);
let { target, panel } = yield initCanvasDebuggerFrontend(SIMPLE_CANVAS_URL);
let { window, $, EVENTS, SnapshotsListView, CallsListView } = panel.panelWin;
yield reload(target);

View File

@ -185,45 +185,42 @@ function initCallWatcherBackend(aUrl) {
return Task.spawn(function*() {
let tab = yield addTab(aUrl);
let target = TargetFactory.forTab(tab);
let debuggee = target.window.wrappedJSObject;
yield target.makeRemote();
let front = new CallWatcherFront(target.client, target.form);
return [target, debuggee, front];
return { target, front };
});
}
function initCanavsDebuggerBackend(aUrl) {
function initCanvasDebuggerBackend(aUrl) {
info("Initializing a canvas debugger front.");
initServer();
return Task.spawn(function*() {
let tab = yield addTab(aUrl);
let target = TargetFactory.forTab(tab);
let debuggee = target.window.wrappedJSObject;
yield target.makeRemote();
let front = new CanvasFront(target.client, target.form);
return [target, debuggee, front];
return { target, front };
});
}
function initCanavsDebuggerFrontend(aUrl) {
function initCanvasDebuggerFrontend(aUrl) {
info("Initializing a canvas debugger pane.");
return Task.spawn(function*() {
let tab = yield addTab(aUrl);
let target = TargetFactory.forTab(tab);
let debuggee = target.window.wrappedJSObject;
yield target.makeRemote();
Services.prefs.setBoolPref("devtools.canvasdebugger.enabled", true);
let toolbox = yield gDevTools.showToolbox(target, "canvasdebugger");
let panel = toolbox.getCurrentPanel();
return [target, debuggee, panel];
return { target, panel };
});
}

View File

@ -633,6 +633,7 @@ exports.AppManager = AppManager = {
discovery.on("devtools-device-added", this._updateWiFiRuntimes);
discovery.on("devtools-device-updated", this._updateWiFiRuntimes);
discovery.on("devtools-device-removed", this._updateWiFiRuntimes);
this._updateWiFiRuntimes();
},
untrackWiFiRuntimes: function() {
if (!this.isWiFiScanningEnabled) {

View File

@ -0,0 +1,59 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
this.EXPORTED_SYMBOLS = ["E10SUtils"];
const {interfaces: Ci, utils: Cu, classes: Cc} = Components;
Cu.import("resource://gre/modules/Services.jsm");
this.E10SUtils = {
shouldBrowserBeRemote: function(aURL) {
// loadURI in browser.xml treats null as about:blank
if (!aURL)
aURL = "about:blank";
if (aURL.startsWith("about:") &&
aURL.toLowerCase() != "about:home" &&
aURL.toLowerCase() != "about:blank" &&
!aURL.toLowerCase().startsWith("about:neterror")) {
return false;
}
return true;
},
shouldLoadURI: function(aDocShell, aURI, aReferrer) {
// about:blank is the initial document and can load anywhere
if (aURI.spec == "about:blank")
return true;
// Inner frames should always load in the current process
if (aDocShell.QueryInterface(Ci.nsIDocShellTreeItem).sameTypeParent)
return true;
// If the URI can be loaded in the current process then continue
let isRemote = Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_CONTENT;
if (this.shouldBrowserBeRemote(aURI.spec) == isRemote)
return true;
return false;
},
redirectLoad: function(aDocShell, aURI, aReferrer) {
// Retarget the load to the correct process
let messageManager = aDocShell.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIContentFrameMessageManager);
let sessionHistory = aDocShell.getInterface(Ci.nsIWebNavigation).sessionHistory;
messageManager.sendAsyncMessage("Browser:LoadURI", {
uri: aURI.spec,
referrer: aReferrer ? aReferrer.spec : null,
historyIndex: sessionHistory.requestedIndex,
});
return false;
},
};

View File

@ -96,7 +96,6 @@ this.TabCrashReporter = {
return;
let url = browser.currentURI.spec;
browser.getTabBrowser().updateBrowserRemotenessByURL(browser, url);
browser.loadURIWithFlags(url, Ci.nsIWebNavigation.LOAD_FLAGS_NONE, null, null, null);
},

View File

@ -17,6 +17,7 @@ EXTRA_JS_MODULES += [
'ContentSearch.jsm',
'ContentWebRTC.jsm',
'CustomizationTabPreloader.jsm',
'E10SUtils.jsm',
'Feeds.jsm',
'FormSubmitObserver.jsm',
'FormValidationHandler.jsm',

View File

@ -9838,7 +9838,18 @@ nsDocShell::InternalLoad(nsIURI * aURI,
return NS_OK;
}
}
// Check if the webbrowser chrome wants the load to proceed; this can be
// used to cancel attempts to load URIs in the wrong process.
nsCOMPtr<nsIWebBrowserChrome3> browserChrome3 = do_GetInterface(mTreeOwner);
if (browserChrome3) {
bool shouldLoad;
rv = browserChrome3->ShouldLoadURI(this, aURI, aReferrer, &shouldLoad);
if (NS_SUCCEEDED(rv) && !shouldLoad) {
return NS_OK;
}
}
// mContentViewer->PermitUnload can destroy |this| docShell, which
// causes the next call of CanSavePresentation to crash.
// Hold onto |this| until we return, to prevent a crash from happening.

View File

@ -80,7 +80,6 @@
#include "UnitTransforms.h"
#include "ClientLayerManager.h"
#include "LayersLogging.h"
#include "nsIWebBrowserChrome3.h"
#include "nsColorPickerProxy.h"

View File

@ -6,10 +6,13 @@
#include "nsIURI.idl"
#include "nsIDOMNode.idl"
interface nsIDocShell;
interface nsIInputStream;
/**
* nsIWebBrowserChrome3 is an extension to nsIWebBrowserChrome2.
*/
[scriptable, uuid(7f2aa813-b250-4e46-afeb-97b1e91bc9a5)]
[scriptable, uuid(9e6c2372-5d9d-4ce8-ab9e-c5df1494dc84)]
interface nsIWebBrowserChrome3 : nsIWebBrowserChrome2
{
/**
@ -30,4 +33,18 @@ interface nsIWebBrowserChrome3 : nsIWebBrowserChrome2
in nsIURI linkURI,
in nsIDOMNode linkNode,
in boolean isAppTab);
/**
* Determines whether a load should continue.
*
* @param aDocShell
* The docshell performing the load.
* @param aURI
* The URI being loaded.
* @param aReferrer
* The referrer of the load.
*/
bool shouldLoadURI(in nsIDocShell aDocShell,
in nsIURI aURI,
in nsIURI aReferrer);
};

View File

@ -28,6 +28,8 @@ import org.mozilla.gecko.Telemetry;
import org.mozilla.gecko.TelemetryContract;
import org.mozilla.search.providers.SearchEngine;
import java.net.URISyntaxException;
public class PostSearchFragment extends Fragment {
private static final String LOG_TAG = "PostSearchFragment";
@ -89,25 +91,38 @@ public class PostSearchFragment extends Fragment {
public void onPageStarted(WebView view, final String url, Bitmap favicon) {
// Reset the error state.
networkError = false;
}
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
// We keep URLs in the webview that are either about:blank or a search engine result page.
if (TextUtils.equals(url, Constants.ABOUT_BLANK) || engine.isSearchResultsPage(url)) {
// Keeping the URL in the webview is a noop.
return;
return false;
}
webview.stopLoading();
try {
// If the url URI does not have an intent scheme, the intent data will be the entire
// URI and its action will be ACTION_VIEW.
final Intent i = Intent.parseUri(url, Intent.URI_INTENT_SCHEME);
Telemetry.sendUIEvent(TelemetryContract.Event.LOAD_URL,
TelemetryContract.Method.CONTENT, "search-result");
// If the intent URI didn't specify a package, open this in Fennec.
if (i.getPackage() == null) {
i.setClassName(AppConstants.ANDROID_PACKAGE_NAME, AppConstants.BROWSER_INTENT_CLASS_NAME);
Telemetry.sendUIEvent(TelemetryContract.Event.LOAD_URL,
TelemetryContract.Method.CONTENT, "search-result");
} else {
Telemetry.sendUIEvent(TelemetryContract.Event.LAUNCH,
TelemetryContract.Method.INTENT, "search-result");
}
final Intent i = new Intent(Intent.ACTION_VIEW);
startActivity(i);
return true;
} catch (URISyntaxException e) {
Log.e(LOG_TAG, "Error parsing intent URI", e);
}
// This sends the URL directly to fennec, rather than to Android.
i.setClassName(AppConstants.ANDROID_PACKAGE_NAME, AppConstants.BROWSER_INTENT_CLASS_NAME);
i.setData(Uri.parse(url));
startActivity(i);
}
return false;
}
@Override
public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {

View File

@ -5,6 +5,7 @@
package org.mozilla.search.autocomplete;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffColorFilter;
@ -127,19 +128,23 @@ public class SearchBar extends FrameLayout {
}
public void setEngine(SearchEngine engine) {
int color = engine.getColor();
if (color == Color.TRANSPARENT) {
// Fall back to default orange if the search engine doesn't specify a color.
color = getResources().getColor(R.color.highlight_orange);
}
// Update the focused background color.
focusedBackground.setColorFilter(new PorterDuffColorFilter(color, PorterDuff.Mode.MULTIPLY));
final String iconURL = engine.getIconURL();
final BitmapDrawable d = new BitmapDrawable(getResources(), BitmapUtils.getBitmapFromDataURI(iconURL));
final Bitmap bitmap = BitmapUtils.getBitmapFromDataURI(iconURL);
final BitmapDrawable d = new BitmapDrawable(getResources(), bitmap);
engineIcon.setImageDrawable(d);
engineIcon.setContentDescription(engine.getName());
// Update the focused background color.
int color = BitmapUtils.getDominantColor(bitmap);
// BitmapUtils#getDominantColor ignores black and white pixels, but it will
// return white if no dominant color was found. We don't want to create a
// white underline for the search bar, so we default to black instead.
if (color == Color.WHITE) {
color = Color.BLACK;
}
focusedBackground.setColorFilter(new PorterDuffColorFilter(color, PorterDuff.Mode.MULTIPLY));
editText.setHint(getResources().getString(R.string.search_bar_hint, engine.getName()));
}

View File

@ -4,7 +4,6 @@
package org.mozilla.search.providers;
import android.graphics.Color;
import android.net.Uri;
import android.util.Log;
import android.util.Xml;
@ -195,14 +194,6 @@ public class SearchEngine {
return iconURL;
}
public int getColor() {
// TOOD: Add brand colors to search plugin XML.
if (identifier.equals("yahoo")) {
return 0xFF500095;
}
return Color.TRANSPARENT;
}
/**
* Determine whether a particular url belongs to this search engine. If not,
* the url will be sent to Fennec.

View File

@ -445,6 +445,23 @@ NS_IMETHODIMP nsContentTreeOwner::OnBeforeLinkTraversal(const nsAString &origina
return NS_OK;
}
NS_IMETHODIMP nsContentTreeOwner::ShouldLoadURI(nsIDocShell *aDocShell,
nsIURI *aURI,
nsIURI *aReferrer,
bool *_retval)
{
NS_ENSURE_STATE(mXULWindow);
nsCOMPtr<nsIXULBrowserWindow> xulBrowserWindow;
mXULWindow->GetXULBrowserWindow(getter_AddRefs(xulBrowserWindow));
if (xulBrowserWindow)
return xulBrowserWindow->ShouldLoadURI(aDocShell, aURI, aReferrer, _retval);
*_retval = true;
return NS_OK;
}
//*****************************************************************************
// nsContentTreeOwner::nsIWebBrowserChrome2
//*****************************************************************************

View File

@ -10,13 +10,15 @@
interface nsIRequest;
interface nsIDOMElement;
interface nsIInputStream;
interface nsIDocShell;
/**
* The nsIXULBrowserWindow supplies the methods that may be called from the
* internals of the browser area to tell the containing xul window to update
* its ui.
*/
[scriptable, uuid(e4ee85a0-645d-11e3-949a-0800200c9a66)]
[scriptable, uuid(162d3378-a7d5-410c-8635-fe80e32020fc)]
interface nsIXULBrowserWindow : nsISupports
{
/**
@ -38,6 +40,19 @@ interface nsIXULBrowserWindow : nsISupports
in nsIDOMNode linkNode,
in boolean isAppTab);
/**
* Determines whether a load should continue.
*
* @param aDocShell
* The docshell performing the load.
* @param aURI
* The URI being loaded.
* @param aReferrer
* The referrer of the load.
*/
bool shouldLoadURI(in nsIDocShell aDocShell,
in nsIURI aURI,
in nsIURI aReferrer);
/**
* Show/hide a tooltip (when the user mouses over a link, say).
*/