Merge m-c to inbound.

This commit is contained in:
Ryan VanderMeulen 2014-02-14 15:32:02 -05:00
commit d6e9eaf736
95 changed files with 3273 additions and 2775 deletions

View File

@ -15,6 +15,12 @@ XPCOMUtils.defineLazyGetter(this, 'WebConsoleUtils', function() {
return devtools.require("devtools/toolkit/webconsole/utils").Utils;
});
XPCOMUtils.defineLazyGetter(this, 'EventLoopLagFront', function() {
const {devtools} = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
return devtools.require("devtools/server/actors/eventlooplag").EventLoopLagFront;
});
/**
* The Widget Panel is an on-device developer tool that displays widgets,
* showing visual debug information about apps. Each widget corresponds to a
@ -358,4 +364,67 @@ let consoleWatcher = {
return source;
}
};
devtoolsWidgetPanel.registerWatcher(consoleWatcher);
let jankWatcher = {
_client: null,
_fronts: new Map(),
_active: false,
init: function(client) {
this._client = client;
SettingsListener.observe('devtools.hud.jank', false,
this.settingsListener.bind(this));
},
settingsListener: function(value) {
if (this._active == value) {
return;
}
this._active = value;
// Toggle the state of existing fronts.
let fronts = this._fronts;
for (let app of fronts.keys()) {
if (value) {
fronts.get(app).start();
} else {
fronts.get(app).stop();
app.metrics.set('jank', 0);
app.display();
}
}
},
trackApp: function(app) {
app.metrics.set('jank', 0);
let front = new EventLoopLagFront(this._client, app.actor);
this._fronts.set(app, front);
front.on('event-loop-lag', time => {
app.metrics.set('jank', time);
if (!app.display()) {
devtoolsWidgetPanel.log('jank: ' + time + 'ms');
}
});
if (this._active) {
front.start();
}
},
untrackApp: function(app) {
let fronts = this._fronts;
if (fronts.has(app)) {
fronts.get(app).destroy();
fronts.delete(app);
}
}
};
devtoolsWidgetPanel.registerWatcher(jankWatcher);

View File

@ -50,6 +50,9 @@
<command id="cmd_findPrevious"
oncommand="gFindBar.onFindAgainCommand(true);"
observes="isImage"/>
#ifdef XP_MACOSX
<command id="cmd_findSelection" oncommand="gFindBar.onFindSelectionCommand();"/>
#endif
<!-- work-around bug 392512 -->
<command id="Browser:AddBookmarkAs"
oncommand="PlacesCommandHook.bookmarkCurrentPage(true, PlacesUtils.bookmarksMenuFolderId);"/>
@ -350,6 +353,9 @@
<key id="key_find" key="&findOnCmd.commandkey;" command="cmd_find" modifiers="accel"/>
<key id="key_findAgain" key="&findAgainCmd.commandkey;" command="cmd_findAgain" modifiers="accel"/>
<key id="key_findPrevious" key="&findAgainCmd.commandkey;" command="cmd_findPrevious" modifiers="accel,shift"/>
#ifdef XP_MACOSX
<key id="key_findSelection" key="&findSelectionCmd.commandkey;" command="cmd_findSelection" modifiers="accel"/>
#endif
<key keycode="&findAgainCmd.commandkey2;" command="cmd_findAgain"/>
<key keycode="&findAgainCmd.commandkey2;" command="cmd_findPrevious" modifiers="shift"/>

View File

@ -3037,17 +3037,7 @@
filter.addProgressListener(tabListener, nsIWebProgress.NOTIFY_ALL);
this.mTabListeners[0] = tabListener;
this.mTabFilters[0] = filter;
try {
// We assume this can only fail because mCurrentBrowser's docShell
// hasn't been created, yet. This may be caused by code accessing
// gBrowser before the window has finished loading.
this._addProgressListenerForInitialTab();
} catch (e) {
// The binding was constructed too early, wait until the initial
// tab's document is ready, then add the progress listener.
this._waitForInitialContentDocument();
}
this.webProgress.addProgressListener(filter, nsIWebProgress.NOTIFY_ALL);
this.style.backgroundColor =
Services.prefs.getBoolPref("browser.display.use_system_colors") ?
@ -3083,31 +3073,6 @@
]]></body>
</method>
<method name="_addProgressListenerForInitialTab">
<body><![CDATA[
this.webProgress.addProgressListener(this.mTabFilters[0], Ci.nsIWebProgress.NOTIFY_ALL);
]]></body>
</method>
<method name="_waitForInitialContentDocument">
<body><![CDATA[
let obs = (subject, topic) => {
if (this.browsers[0].contentWindow == subject) {
Services.obs.removeObserver(obs, topic);
this._addProgressListenerForInitialTab();
}
};
// We use content-document-global-created as an approximation for
// "docShell is initialized". We can do this because in the
// mTabProgressListener we care most about the STATE_STOP notification
// that will reset mBlank. That means it's important to at least add
// the progress listener before the initial about:blank load stops
// if we can't do it before the load starts.
Services.obs.addObserver(obs, "content-document-global-created", false);
]]></body>
</method>
<destructor>
<![CDATA[
for (var i = 0; i < this.mTabListeners.length; ++i) {

View File

@ -11,6 +11,9 @@ let texts = [
"To err is human; to forgive is not company policy."
];
let Clipboard = Cc["@mozilla.org/widget/clipboard;1"].getService(Ci.nsIClipboard);
let HasFindClipboard = Clipboard.supportsFindClipboard();
function addTabWithText(aText, aCallback) {
let newTab = gBrowser.addTab("data:text/html,<h1 id='h1'>" + aText + "</h1>");
tabs.push(newTab);
@ -61,7 +64,9 @@ function continueTests1() {
// Confirm the first tab is still correct, ensure re-hiding works as expected
gBrowser.selectedTab = tabs[0];
ok(!gFindBar.hidden, "First tab shows find bar!");
is(gFindBar._findField.value, texts[0], "First tab persists find value!");
// When the Find Clipboard is supported, this test not relevant.
if (!HasFindClipboard)
is(gFindBar._findField.value, texts[0], "First tab persists find value!");
ok(gFindBar.getElement("highlight").checked,
"Highlight button state persists!");
@ -94,7 +99,8 @@ function continueTests2() {
ok(gFindBar.hidden, "Fourth tab doesn't show find bar!");
is(gFindBar, gBrowser.getFindBar(), "Find bar is right one!");
gFindBar.open();
is(gFindBar._findField.value, texts[1],
let toTest = HasFindClipboard ? texts[2] : texts[1];
is(gFindBar._findField.value, toTest,
"Fourth tab has second tab's find value!");
newWindow = gBrowser.replaceTabWithWindow(tabs.pop());
@ -104,7 +110,8 @@ function continueTests2() {
// Test that findbar gets restored when a tab is moved to a new window.
function checkNewWindow() {
ok(!newWindow.gFindBar.hidden, "New window shows find bar!");
is(newWindow.gFindBar._findField.value, texts[1],
let toTest = HasFindClipboard ? texts[2] : texts[1];
is(newWindow.gFindBar._findField.value, toTest,
"New window find bar has correct find value!");
ok(!newWindow.gFindBar.getElement("find-next").disabled,
"New window findbar has enabled buttons!");

View File

@ -2,7 +2,10 @@
* http://creativecommons.org/publicdomain/zero/1.0/
*/
let Ci = Components.interfaces;
const {Ci: interfaces, Cc: classes} = Components;
let Clipboard = Cc["@mozilla.org/widget/clipboard;1"].getService(Ci.nsIClipboard);
let HasFindClipboard = Clipboard.supportsFindClipboard();
function test() {
waitForExplicitFinish();
@ -37,7 +40,10 @@ function onFocus(win) {
let findBar = win.gFindBar;
selectText(win.content);
findBar.onFindCommand();
is(findBar._findField.value, "Select Me", "Findbar is initialized with selection");
// When the OS supports the Find Clipboard (OSX), the find field value is
// persisted across Fx sessions, thus not useful to test.
if (!HasFindClipboard)
is(findBar._findField.value, "Select Me", "Findbar is initialized with selection");
findBar.close();
win.close();
finish();

View File

@ -614,6 +614,7 @@ you can use these alternative items. Otherwise, their values should be empty. -
<!ENTITY findAgainCmd.accesskey "g">
<!ENTITY findAgainCmd.commandkey "g">
<!ENTITY findAgainCmd.commandkey2 "VK_F3">
<!ENTITY findSelectionCmd.commandkey "e">
<!ENTITY spellAddDictionaries.label "Add Dictionaries…">
<!ENTITY spellAddDictionaries.accesskey "A">

View File

@ -2434,11 +2434,11 @@ richlistitem[type~="action"][actiontype="switchtab"][selected="true"] > .ac-url-
.panel-promo-box {
margin: 8px -16px -16px -16px;
padding: 8px 16px;
background: #e5e5e5;
border-top: 1px solid hsla(0,0%,0%,.1);
background-color: hsla(210,4%,10%,.07);
border-top: 1px solid hsla(210,4%,10%,.12);
border-radius: 0 0 5px 5px;
box-shadow: 0 -1px hsla(0,0%,100%,.5) inset, 0 1px 1px hsla(0,0%,0%,.03) inset;
color: #808080;
color: hsl(0,0%,30%);
}
.panel-promo-icon {
@ -3214,6 +3214,10 @@ toolbarbutton.chevron > .toolbarbutton-menu-dropmarker {
}
}
#identity-popup {
margin-top: 1px;
}
#identity-popup > .panel-arrowcontainer > .panel-arrowcontent {
padding: 0;
}
@ -3223,7 +3227,8 @@ toolbarbutton.chevron > .toolbarbutton-menu-dropmarker {
}
#identity-popup-button-container {
background: linear-gradient(to bottom, rgba(0,0,0,0.04) 60%, transparent);
background-color: hsla(210,4%,10%,.07);
border-top: 1px solid hsla(210,4%,10%,.12);
padding: 16px;
margin-top: 5px;
}
@ -4022,7 +4027,6 @@ toolbar[mode="icons"] > *|* > .toolbarbutton-badge[badge]:not([badge=""]):-moz-l
}
#social-share-panel {
margin-top: 3px;
max-height: 600px;
min-height: 100px;
max-width: 800px;

View File

@ -4,6 +4,10 @@
/*** Panel and outer controls ***/
#downloadsPanel {
margin-top: -1px;
}
#downloadsPanel > .panel-arrowcontainer > .panel-arrowcontent {
padding: 0;
}
@ -19,8 +23,8 @@
}
#downloadsFooter {
border-bottom-left-radius: 6px;
border-bottom-right-radius: 6px;
border-bottom-left-radius: 4px;
border-bottom-right-radius: 4px;
}
#downloadsHistory {
@ -29,11 +33,6 @@
cursor: pointer;
}
#downloadsPanel:not([hasdownloads]) > #downloadsFooter > #downloadsHistory {
border-top-left-radius: 6px;
border-top-right-radius: 6px;
}
#downloadsPanel[hasdownloads] > #downloadsFooter {
background: #e5e5e5;
border-top: 1px solid hsla(0,0%,0%,.1);
@ -49,18 +48,13 @@
#downloadsPanel[keyfocus] > #downloadsFooter > #downloadsHistory:focus {
outline: 2px -moz-mac-focusring solid;
outline-offset: -2px;
-moz-outline-radius-bottomleft: 5px;
-moz-outline-radius-bottomright: 5px;
-moz-outline-radius-bottomleft: 4px;
-moz-outline-radius-bottomright: 4px;
}
#downloadsPanel:not([hasdownloads]) > #downloadsFooter > #downloadsHistory:focus {
-moz-outline-radius-topleft: 5px;
-moz-outline-radius-topright: 5px;
}
#downloadsPanel:not([hasdownloads]) > #downloadsFooter > #downloadsHistory:focus > .button-box {
border-bottom-left-radius: 6px;
border-bottom-right-radius: 6px;
-moz-outline-radius-topleft: 4px;
-moz-outline-radius-topright: 4px;
}
/*** Downloads Summary and List items ***/

View File

@ -30,11 +30,10 @@
padding: 4px;
background-color: hsla(0,0%,100%,.97);
background-clip: padding-box;
border-right: 1px solid hsla(210,4%,10%,.2);
border-left: 1px solid hsla(210,4%,10%,.2);
border-left: 1px solid hsla(210,4%,10%,.3);
box-shadow: 0 3px 5px hsla(210,4%,10%,.1),
0 0 7px hsla(210,4%,10%,.1);
color: #222426;
color: hsl(0,0%,15%);
-moz-margin-start: @exitSubviewGutterWidth@;
}
@ -68,19 +67,13 @@
.panel-subview-header,
.subviewbutton.panel-subview-footer {
padding: 12px;
background-color: hsla(210,4%,10%,.04);
}
.panel-subview-header {
margin: -4px -4px 4px;
box-shadow: 0 -1px 0 hsla(210,4%,10%,.08) inset;
color: #797c80;
}
.subviewbutton.panel-subview-footer {
margin: 4px -4px -4px;
box-shadow: 0 1px 0 hsla(210,4%,10%,.08) inset;
border-radius: 0;
background-color: hsla(210,4%,10%,.05);
box-shadow: 0 -1px 0 hsla(210,4%,10%,.05) inset;
color: hsl(0,0%,50%);
}
.cui-widget-panelview .panel-subview-header {
@ -201,8 +194,8 @@ toolbaritem[cui-areatype="menu-panel"][sdkstylewidget="true"]:not(.panel-wide-it
}
/* Help SDK buttons fit in. */
toolbarpaletteitem[place="palette"] > toolbarbutton[sdk-button="true"] > .toolbarbutton-icon,
toolbarbutton[sdk-button="true"][cui-areatype="menu-panel"] > .toolbarbutton-icon {
toolbarpaletteitem[place="palette"] > #personal-bookmarks > #bookmarks-toolbar-placeholder,
#personal-bookmarks[cui-areatype="menu-panel"] > #bookmarks-toolbar-placeholder {
height: 32px;
width: 32px;
}
@ -320,20 +313,19 @@ toolbarpaletteitem[place="palette"] > toolbaritem > toolbarbutton {
display: flex;
flex-shrink: 0;
flex-direction: column;
background-color: rgba(0, 0, 0, 0.05);
box-shadow: 0 -1px 0 rgba(0,0,0,.15);
background-color: hsla(210,4%,10%,.07);
padding: 0;
margin: 0;
}
#PanelUI-footer-inner {
display: flex;
box-shadow: 0 -1px 0 rgba(0,0,0,.15);
border-top: 1px solid hsla(210,4%,10%,.14);
}
#PanelUI-footer-inner > toolbarseparator {
border: 0;
border-left: 1px solid rgba(0,0,0,0.1);
border-left: 1px solid hsla(210,4%,10%,.14);
margin: 7px 0 7px;
-moz-appearance: none;
}
@ -352,15 +344,16 @@ toolbarpaletteitem[place="palette"] > toolbaritem > toolbarbutton {
-moz-appearance: none;
box-shadow: none;
background-image: none;
border: 1px solid transparent;
border-bottom-style: none;
border: none;
border-radius: 0;
transition: background-color;
-moz-box-orient: horizontal;
}
#PanelUI-fxa-status {
border-bottom-style: solid;
border-top: 1px solid hsla(210,4%,10%,.14);
border-bottom: 1px solid transparent;
margin-bottom: -1px;
}
#PanelUI-fxa-status > .toolbarbutton-text {
@ -451,18 +444,26 @@ toolbarpaletteitem[place="palette"] > toolbaritem > toolbarbutton {
opacity: 0.4;
}
#PanelUI-fxa-status:not([disabled]):hover,
#PanelUI-help:not([disabled]):hover,
#PanelUI-customize:hover,
#PanelUI-quit:not([disabled]):hover {
outline: 1px solid rgba(0,0,0,0.1);
background-color: rgba(0,0,0,0.1);
box-shadow: none;
outline: 1px solid hsla(210,4%,10%,.07);
background-color: hsla(210,4%,10%,.07);
}
#PanelUI-fxa-status:not([disabled]):hover {
background-color: rgba(0,0,0,0.1);
border-bottom-color: rgba(0,0,0,0.1);
box-shadow: 0 -1px 0 rgba(0,0,0,0.2);
#PanelUI-fxa-status:not([disabled]):hover:active,
#PanelUI-help:not([disabled]):hover:active,
#PanelUI-customize:hover:active,
#PanelUI-quit:not([disabled]):hover:active {
outline: 1px solid hsla(210,4%,10%,.12);
background-color: hsla(210,4%,10%,.12);
box-shadow: 0 1px 0 hsla(210,4%,10%,.05) inset;
}
#PanelUI-fxa-status:not([disabled]):hover,
#PanelUI-fxa-status:not([disabled]):hover:active {
outline: none;
}
#PanelUI-quit:not([disabled]):hover {
@ -472,6 +473,7 @@ toolbarpaletteitem[place="palette"] > toolbaritem > toolbarbutton {
#PanelUI-quit:not([disabled]):hover:active {
background-color: #ad3434;
outline-color: #992e2e;
}
#customization-panelHolder #PanelUI-customize {
@ -552,12 +554,12 @@ panelview .toolbarbutton-1@buttonStateHover@,
#edit-controls@inAnyPanel@ > toolbarbutton@buttonStateHover@,
#zoom-controls@inAnyPanel@ > toolbarbutton@buttonStateHover@ {
background-color: hsla(210,4%,10%,.08);
border-color: hsla(210,4%,10%,.1);
border-color: hsla(210,4%,10%,.11);
}
#edit-controls@inAnyPanel@@buttonStateHover@,
#zoom-controls@inAnyPanel@@buttonStateHover@ {
border-color: hsla(210,4%,10%,.1);
border-color: hsla(210,4%,10%,.11);
}
panelview .toolbarbutton-1@buttonStateActive@,
@ -565,9 +567,28 @@ panelview .toolbarbutton-1@buttonStateActive@,
.widget-overflow-list .toolbarbutton-1@buttonStateActive@,
#edit-controls@inAnyPanel@ > toolbarbutton@buttonStateActive@,
#zoom-controls@inAnyPanel@ > toolbarbutton@buttonStateActive@ {
background-color: hsla(210,4%,10%,.12);
border-color: hsla(210,4%,10%,.14);
box-shadow: 0 1px 0 hsla(210,4%,10%,.03) inset;
}
.subviewbutton.panel-subview-footer {
margin: 4px -4px -4px;
background-color: hsla(210,4%,10%,.07);
border-top: 1px solid hsla(210,4%,10%,.12);
border-radius: 0;
color: hsl(0,0%,25%)
}
.subviewbutton.panel-subview-footer@buttonStateHover@ {
background-color: hsla(210,4%,10%,.1);
border-top: 1px solid hsla(210,4%,10%,.12);
}
.subviewbutton.panel-subview-footer@buttonStateActive@ {
background-color: hsla(210,4%,10%,.15);
border-color: hsla(210,4%,10%,.15);
box-shadow: 0 1px 0 0 hsla(210,4%,10%,.05) inset;
border-top: 1px solid hsla(210,4%,10%,.12);
box-shadow: 0 1px 0 hsla(210,4%,10%,.05) inset;
}
#BMB_bookmarksPopup > .subviewbutton {

View File

@ -175,12 +175,6 @@
box-shadow: 0 0 0 1px rgba(0,0,0,0.5);
}
.variables-view-scope:focus > .title,
.variable-or-property:focus > .title {
background: #3689b2; /* fg-color2 */
color: white;
}
/* CodeMirror specific styles.
* Best effort to match the existing theme, some of the colors
* are duplicated here to prevent weirdness in the main theme. */

View File

@ -174,12 +174,6 @@
box-shadow: 0 0 0 1px #EFEFEF;
}
.variables-view-scope:focus > .title,
.variable-or-property:focus > .title {
background: #4c9ed9; /* fg-color2 */
color: #f5f7fa;
}
/* CodeMirror specific styles.
* Best effort to match the existing theme, some of the colors
* are duplicated here to prevent weirdness in the main theme. */

View File

@ -561,6 +561,18 @@
color: #585959; /* Grey foreground text */
}
.theme-dark .variables-view-scope:focus > .title,
.theme-dark .variable-or-property:focus > .title {
background-color: #1d4f73; /* Selection colors */
color: #f5f7fa;
}
.theme-light .variables-view-scope:focus > .title,
.theme-light .variable-or-property:focus > .title {
background-color: #4c9ed9; /* Selection colors */
color: #f5f7fa;
}
.variables-view-scope > .title {
border-top-width: 1px;
border-top-style: solid;

View File

@ -26,7 +26,8 @@
}
.click-to-play-plugins-notification-button-container {
background: linear-gradient(rgba(0,0,0,0.04) 60%, transparent);
background-color: hsla(210,4%,10%,.07);
border-top: 1px solid hsla(210,4%,10%,.12);
padding: 10px;
margin-top: 5px;
}

View File

@ -11,7 +11,9 @@
#include "ImageContainer.h"
#include "ImageTypes.h"
#include "prmem.h"
#include "nsContentUtils.h"
#include "nsIFilePicker.h"
#include "nsIPrefService.h"
#include "nsIPrefBranch.h"
@ -164,18 +166,38 @@ MediaEngineDefaultVideoSource::Snapshot(uint32_t aDuration, nsIDOMFile** aFile)
#ifndef MOZ_WIDGET_ANDROID
return NS_ERROR_NOT_IMPLEMENTED;
#else
if (!AndroidBridge::Bridge()) {
return NS_ERROR_UNEXPECTED;
nsAutoString filePath;
nsCOMPtr<nsIFilePicker> filePicker = do_CreateInstance("@mozilla.org/filepicker;1");
if (!filePicker)
return NS_ERROR_FAILURE;
nsXPIDLString title;
nsContentUtils::GetLocalizedString(nsContentUtils::eFORMS_PROPERTIES, "Browse", title);
int16_t mode = static_cast<int16_t>(nsIFilePicker::modeOpen);
nsresult rv = filePicker->Init(nullptr, title, mode);
NS_ENSURE_SUCCESS(rv, rv);
filePicker->AppendFilters(nsIFilePicker::filterImages);
// XXX - This API should be made async
PRInt16 dialogReturn;
rv = filePicker->Show(&dialogReturn);
NS_ENSURE_SUCCESS(rv, rv);
if (dialogReturn == nsIFilePicker::returnCancel) {
*aFile = nullptr;
return NS_OK;
}
nsAutoString filePath;
AndroidBridge::Bridge()->ShowFilePickerForMimeType(filePath, NS_LITERAL_STRING("image/*"));
nsCOMPtr<nsIFile> localFile;
filePicker->GetFile(getter_AddRefs(localFile));
nsCOMPtr<nsIFile> file;
nsresult rv = NS_NewLocalFile(filePath, false, getter_AddRefs(file));
NS_ENSURE_SUCCESS(rv, rv);
if (!localFile) {
*aFile = nullptr;
return NS_OK;
}
NS_ADDREF(*aFile = new nsDOMFileFile(file));
nsCOMPtr<nsIDOMFile> domFile = new nsDOMFileFile(localFile);
domFile.forget(aFile);
return NS_OK;
#endif
}

View File

@ -238,82 +238,6 @@ public class ActivityHandlerHelper implements GeckoEventListener {
});
}
private Intent getFilePickerIntent(Context context, String aMimeType) {
ArrayList<Intent> intents = new ArrayList<Intent>();
final Prompt.PromptListItem[] items =
getItemsAndIntentsForFilePicker(context, aMimeType, intents);
if (intents.size() == 0) {
Log.i(LOGTAG, "no activities for the file picker!");
return null;
}
if (intents.size() == 1) {
return intents.get(0);
}
final PromptService ps = GeckoAppShell.getGeckoInterface().getPromptService();
final String title = getFilePickerTitle(context, aMimeType);
// Runnable has to be called to show an intent-like
// context menu UI using the PromptService.
ThreadUtils.postToUiThread(new Runnable() {
@Override public void run() {
ps.show(title, "", items, false, null);
}
});
String promptServiceResult = ps.getResponse(null);
int itemId = -1;
try {
itemId = new JSONObject(promptServiceResult).getInt("button");
if (itemId == -1) {
return null;
}
} catch (JSONException e) {
Log.e(LOGTAG, "result from promptservice was invalid: ", e);
return null;
}
return intents.get(itemId);
}
boolean showFilePicker(Activity parentActivity, String aMimeType, ActivityResultHandler handler) {
Intent intent = getFilePickerIntent(parentActivity, aMimeType);
if (intent == null) {
return false;
}
parentActivity.startActivityForResult(intent, mActivityResultHandlerMap.put(handler));
return true;
}
String showFilePicker(Activity parentActivity, String aMimeType) {
Intent intent = getFilePickerIntent(parentActivity, aMimeType);
if (intent == null) {
return "";
}
if (intent.getAction().equals(MediaStore.ACTION_IMAGE_CAPTURE)) {
parentActivity.startActivityForResult(intent, mActivityResultHandlerMap.put(mCameraImageResultHandler));
} else if (intent.getAction().equals(MediaStore.ACTION_VIDEO_CAPTURE)) {
parentActivity.startActivityForResult(intent, mActivityResultHandlerMap.put(mCameraVideoResultHandler));
} else if (intent.getAction().equals(Intent.ACTION_GET_CONTENT)) {
parentActivity.startActivityForResult(intent, mActivityResultHandlerMap.put(mFilePickerResultHandlerSync));
} else {
Log.e(LOGTAG, "We should not get an intent with another action!");
return "";
}
String filePickerResult;
while (null == (filePickerResult = mFilePickerResult.poll())) {
GeckoAppShell.processNextNativeEvent(true);
}
return filePickerResult;
}
/* Allows the user to pick an activity to load files from using a list prompt. Then opens the activity and
* sends the file returned to the passed in handler. If a null handler is passed in, will still
* pick and launch the file picker, but will throw away the result.

View File

@ -31,7 +31,7 @@ import org.mozilla.gecko.prompts.Prompt;
import org.mozilla.gecko.toolbar.AutocompleteHandler;
import org.mozilla.gecko.toolbar.BrowserToolbar;
import org.mozilla.gecko.util.Clipboard;
import org.mozilla.gecko.util.EventDispatcher;
import org.mozilla.gecko.EventDispatcher;
import org.mozilla.gecko.util.GamepadUtils;
import org.mozilla.gecko.util.HardwareUtils;
import org.mozilla.gecko.util.MenuUtils;

View File

@ -6,7 +6,7 @@ package org.mozilla.gecko;
import org.mozilla.gecko.gfx.Layer;
import org.mozilla.gecko.gfx.LayerView;
import org.mozilla.gecko.util.EventDispatcher;
import org.mozilla.gecko.EventDispatcher;
import org.mozilla.gecko.util.GeckoEventListener;
import org.mozilla.gecko.util.ThreadUtils;

View File

@ -2,7 +2,11 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.gecko.util;
package org.mozilla.gecko;
import org.mozilla.gecko.GeckoAppShell;
import org.mozilla.gecko.GeckoEvent;
import org.mozilla.gecko.util.GeckoEventListener;
import org.json.JSONObject;
@ -14,6 +18,7 @@ import java.util.concurrent.CopyOnWriteArrayList;
public final class EventDispatcher {
private static final String LOGTAG = "GeckoEventDispatcher";
private static final String GUID = "__guid__";
private final Map<String, CopyOnWriteArrayList<GeckoEventListener>> mEventListeners
= new HashMap<String, CopyOnWriteArrayList<GeckoEventListener>>();
@ -52,18 +57,16 @@ public final class EventDispatcher {
}
}
public String dispatchEvent(String message) {
public void dispatchEvent(String message) {
try {
JSONObject json = new JSONObject(message);
return dispatchEvent(json);
dispatchEvent(json);
} catch (Exception e) {
Log.e(LOGTAG, "dispatchEvent: malformed JSON.", e);
}
return "";
}
public String dispatchEvent(JSONObject json) {
public void dispatchEvent(JSONObject json) {
// {
// "type": "value",
// "event_specific": "value",
@ -87,29 +90,29 @@ public final class EventDispatcher {
if (listeners == null || listeners.size() == 0) {
Log.d(LOGTAG, "dispatchEvent: no listeners registered for event '" + type + "'");
return "";
return;
}
String response = null;
for (GeckoEventListener listener : listeners) {
listener.handleMessage(type, json);
if (listener instanceof GeckoEventResponder) {
String newResponse = ((GeckoEventResponder)listener).getResponse(json);
if (response != null && newResponse != null) {
Log.e(LOGTAG, "Received two responses for message of type " + type);
}
response = newResponse;
}
}
if (response != null)
return response;
} catch (Exception e) {
Log.e(LOGTAG, "handleGeckoMessage throws " + e, e);
}
return "";
}
public static void sendResponse(JSONObject message, JSONObject response) {
try {
response.put(GUID, message.getString(GUID));
GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent(message.getString("type") + ":Return", response.toString()));
} catch(Exception ex) { }
}
public static void sendError(JSONObject message, JSONObject error) {
try {
error.put(GUID, message.getString(GUID));
GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent(message.getString("type") + ":Error", error.toString()));
} catch(Exception ex) { }
}
}

View File

@ -23,9 +23,7 @@ import org.mozilla.gecko.preferences.GeckoPreferences;
import org.mozilla.gecko.updater.UpdateService;
import org.mozilla.gecko.updater.UpdateServiceHelper;
import org.mozilla.gecko.util.ActivityResultHandler;
import org.mozilla.gecko.util.EventDispatcher;
import org.mozilla.gecko.util.GeckoEventListener;
import org.mozilla.gecko.util.GeckoEventResponder;
import org.mozilla.gecko.util.HardwareUtils;
import org.mozilla.gecko.util.ThreadUtils;
import org.mozilla.gecko.util.UiAsyncTask;
@ -134,7 +132,6 @@ public abstract class GeckoApp
ContextGetter,
GeckoAppShell.GeckoInterface,
GeckoEventListener,
GeckoEventResponder,
GeckoMenu.Callback,
GeckoMenu.MenuPresenter,
LocationListener,
@ -187,7 +184,6 @@ public abstract class GeckoApp
protected GeckoProfile mProfile;
public static int mOrientation;
protected boolean mIsRestoringActivity;
private String mCurrentResponse = "";
private ContactService mContactService;
private PromptService mPromptService;
@ -685,7 +681,7 @@ public abstract class GeckoApp
}
JSONObject handlersJSON = new JSONObject();
handlersJSON.put("apps", new JSONArray(appList));
mCurrentResponse = handlersJSON.toString();
EventDispatcher.sendResponse(message, handlersJSON);
} else if (event.equals("Intent:Open")) {
GeckoAppShell.openUriExternal(message.optString("url"),
message.optString("mime"), message.optString("packageName"),
@ -693,7 +689,9 @@ public abstract class GeckoApp
} else if (event.equals("Locale:Set")) {
setLocale(message.getString("locale"));
} else if (event.equals("NativeApp:IsDebuggable")) {
mCurrentResponse = getIsDebuggable() ? "true" : "false";
JSONObject ret = new JSONObject();
ret.put("isDebuggable", getIsDebuggable() ? "true" : "false");
EventDispatcher.sendResponse(message, ret);
} else if (event.equals("SystemUI:Visibility")) {
setSystemUiVisible(message.getBoolean("visible"));
}
@ -702,12 +700,6 @@ public abstract class GeckoApp
}
}
public String getResponse(JSONObject origMessage) {
String res = mCurrentResponse;
mCurrentResponse = "";
return res;
}
void onStatePurged() { }
/**

View File

@ -18,7 +18,6 @@ import org.mozilla.gecko.mozglue.JNITarget;
import org.mozilla.gecko.mozglue.RobocopTarget;
import org.mozilla.gecko.prompts.PromptService;
import org.mozilla.gecko.util.ActivityResultHandler;
import org.mozilla.gecko.util.EventDispatcher;
import org.mozilla.gecko.util.GeckoEventListener;
import org.mozilla.gecko.util.HardwareUtils;
import org.mozilla.gecko.util.ProxySelector;
@ -1427,20 +1426,6 @@ public class GeckoAppShell
getGeckoInterface().setFullScreen(fullscreen);
}
@WrapElementForJNI(stubName = "ShowFilePickerForExtensionsWrapper")
public static String showFilePickerForExtensions(String aExtensions) {
if (getGeckoInterface() != null)
return sActivityHelper.showFilePicker(getGeckoInterface().getActivity(), getMimeTypeFromExtensions(aExtensions));
return "";
}
@WrapElementForJNI(stubName = "ShowFilePickerForMimeTypeWrapper")
public static String showFilePickerForMimeType(String aMimeType) {
if (getGeckoInterface() != null)
return sActivityHelper.showFilePicker(getGeckoInterface().getActivity(), aMimeType);
return "";
}
@WrapElementForJNI
public static void performHapticFeedback(boolean aIsLongPress) {
// Don't perform haptic feedback if a vibration is currently playing,
@ -2345,8 +2330,8 @@ public class GeckoAppShell
}
@WrapElementForJNI(stubName = "HandleGeckoMessageWrapper")
public static String handleGeckoMessage(String message) {
return sEventDispatcher.dispatchEvent(message);
public static void handleGeckoMessage(String message) {
sEventDispatcher.dispatchEvent(message);
}
@WrapElementForJNI
@ -2630,17 +2615,6 @@ public class GeckoAppShell
return true;
}
static native void notifyFilePickerResult(String filePath, long id);
@WrapElementForJNI(stubName = "ShowFilePickerAsyncWrapper")
public static void showFilePickerAsync(String aMimeType, final long id) {
sActivityHelper.showFilePickerAsync(getGeckoInterface().getActivity(), aMimeType, new ActivityHandlerHelper.FileResultHandler() {
public void gotFile(String filename) {
GeckoAppShell.notifyFilePickerResult(filename, id);
}
});
}
@WrapElementForJNI
public static void notifyWakeLockChanged(String topic, String state) {
if (getGeckoInterface() != null)

View File

@ -5,9 +5,7 @@
package org.mozilla.gecko;
import org.mozilla.gecko.util.EventDispatcher;
import org.mozilla.gecko.util.GeckoEventListener;
import org.mozilla.gecko.util.GeckoEventResponder;
import org.json.JSONException;
import org.json.JSONObject;
@ -140,7 +138,7 @@ class JavaAddonManager implements GeckoEventListener {
}
}
private static class CallbackWrapper implements GeckoEventResponder {
private static class CallbackWrapper implements GeckoEventListener {
private final Handler.Callback mDelegate;
private Bundle mBundle;
@ -184,16 +182,14 @@ class JavaAddonManager implements GeckoEventListener {
Message msg = new Message();
msg.setData(mBundle);
mDelegate.handleMessage(msg);
JSONObject obj = new JSONObject();
obj.put("response", mBundle.getString("response"));
EventDispatcher.sendResponse(json, obj);
mBundle = null;
} catch (Exception e) {
Log.e(LOGTAG, "Caught exception thrown from wrapped addon message handler", e);
}
}
@Override
public String getResponse(JSONObject origMessage) {
String response = mBundle.getString("response");
mBundle = null;
return response;
}
}
}

View File

@ -6,7 +6,7 @@
package org.mozilla.gecko;
import org.mozilla.gecko.background.common.GlobalConstants;
import org.mozilla.gecko.util.EventDispatcher;
import org.mozilla.gecko.EventDispatcher;
import org.mozilla.gecko.util.GeckoEventListener;
import org.json.JSONException;

View File

@ -5,8 +5,8 @@
package org.mozilla.gecko;
import org.mozilla.gecko.util.EventDispatcher;
import org.mozilla.gecko.util.GeckoEventResponder;
import org.mozilla.gecko.EventDispatcher;
import org.mozilla.gecko.util.GeckoEventListener;
import org.json.JSONArray;
import org.json.JSONException;
@ -24,14 +24,12 @@ import java.util.HashMap;
* Helper class to get, set, and observe Android Shared Preferences.
*/
public final class SharedPreferencesHelper
implements GeckoEventResponder
implements GeckoEventListener
{
public static final String LOGTAG = "GeckoAndSharedPrefs";
protected final Context mContext;
protected String mResponse;
// mListeners is not synchronized because it is only updated in
// handleObserve, which is called from Gecko serially.
protected final Map<String, SharedPreferences.OnSharedPreferenceChangeListener> mListeners;
@ -117,7 +115,7 @@ public final class SharedPreferencesHelper
* must include a String name, and a String type in ["bool", "int",
* "string"].
*/
private String handleGet(JSONObject message) throws JSONException {
private JSONArray handleGet(JSONObject message) throws JSONException {
if (!message.has("branch")) {
Log.e(LOGTAG, "No branch specified for SharedPreference:Get; aborting.");
return null;
@ -156,7 +154,7 @@ public final class SharedPreferencesHelper
jsonValues.put(jsonValue);
}
return jsonValues.toString();
return jsonValues;
}
private static class ChangeListener
@ -225,8 +223,6 @@ public final class SharedPreferencesHelper
public void handleMessage(String event, JSONObject message) {
// Everything here is synchronous and serial, so we need not worry about
// overwriting an in-progress response.
mResponse = null;
try {
if (event.equals("SharedPreferences:Set")) {
if (Log.isLoggable(LOGTAG, Log.VERBOSE)) {
@ -237,9 +233,9 @@ public final class SharedPreferencesHelper
if (Log.isLoggable(LOGTAG, Log.VERBOSE)) {
Log.v(LOGTAG, "Got SharedPreferences:Get message.");
}
// Synchronous and serial, so we are the only consumer of
// mResponse and can write to it freely.
mResponse = handleGet(message);
JSONObject obj = new JSONObject();
obj.put("values", handleGet(message));
EventDispatcher.sendResponse(message, obj);
} else if (event.equals("SharedPreferences:Observe")) {
if (Log.isLoggable(LOGTAG, Log.VERBOSE)) {
Log.v(LOGTAG, "Got SharedPreferences:Observe message.");
@ -254,9 +250,4 @@ public final class SharedPreferencesHelper
return;
}
}
@Override
public String getResponse(JSONObject origMessage) {
return mResponse;
}
}

View File

@ -10,7 +10,7 @@ import org.mozilla.gecko.gfx.Layer;
import org.mozilla.gecko.gfx.LayerView;
import org.mozilla.gecko.menu.GeckoMenu;
import org.mozilla.gecko.menu.GeckoMenuItem;
import org.mozilla.gecko.util.EventDispatcher;
import org.mozilla.gecko.EventDispatcher;
import org.mozilla.gecko.util.FloatUtils;
import org.mozilla.gecko.util.GeckoEventListener;
import org.mozilla.gecko.util.ThreadUtils;

View File

@ -101,6 +101,8 @@ public class BrowserContract {
public static final class Favicons implements CommonColumns, DateSyncColumns {
private Favicons() {}
public static final String TABLE_NAME = "favicons";
public static final Uri CONTENT_URI = Uri.withAppendedPath(AUTHORITY_URI, "favicons");
public static final String URL = "url";
@ -112,6 +114,8 @@ public class BrowserContract {
public static final class Thumbnails implements CommonColumns {
private Thumbnails() {}
public static final String TABLE_NAME = "thumbnails";
public static final Uri CONTENT_URI = Uri.withAppendedPath(AUTHORITY_URI, "thumbnails");
public static final String URL = "url";
@ -122,6 +126,10 @@ public class BrowserContract {
public static final class Bookmarks implements CommonColumns, URLColumns, FaviconColumns, SyncColumns {
private Bookmarks() {}
public static final String TABLE_NAME = "bookmarks";
public static final String VIEW_WITH_FAVICONS = "bookmarks_with_favicons";
public static final int FIXED_ROOT_ID = 0;
public static final int FAKE_DESKTOP_FOLDER_ID = -1;
public static final int FIXED_READING_LIST_ID = -2;
@ -173,6 +181,11 @@ public class BrowserContract {
@RobocopTarget
public static final class History implements CommonColumns, URLColumns, HistoryColumns, FaviconColumns, SyncColumns {
private History() {}
public static final String TABLE_NAME = "history";
public static final String VIEW_WITH_FAVICONS = "history_with_favicons";
public static final Uri CONTENT_URI = Uri.withAppendedPath(AUTHORITY_URI, "history");
public static final Uri CONTENT_OLD_URI = Uri.withAppendedPath(AUTHORITY_URI, "history/old");
public static final String CONTENT_TYPE = "vnd.android.cursor.dir/browser-history";
@ -183,6 +196,11 @@ public class BrowserContract {
@RobocopTarget
public static final class Combined implements CommonColumns, URLColumns, HistoryColumns, FaviconColumns {
private Combined() {}
public static final String VIEW_NAME = "combined";
public static final String VIEW_WITH_FAVICONS = "combined_with_favicons";
public static final Uri CONTENT_URI = Uri.withAppendedPath(AUTHORITY_URI, "combined");
public static final int DISPLAY_NORMAL = 0;
@ -316,4 +334,47 @@ public class BrowserContract {
public static final String IMAGE_URL = "image_url";
public static final String CREATED = "created";
}
/*
* Contains names and schema definitions for tables and views
* no longer being used by current ContentProviders. These values are used
* to make incremental updates to the schema during a database upgrade. Will be
* removed with bug 947018.
*/
static final class Obsolete {
public static final String TABLE_IMAGES = "images";
public static final String VIEW_BOOKMARKS_WITH_IMAGES = "bookmarks_with_images";
public static final String VIEW_HISTORY_WITH_IMAGES = "history_with_images";
public static final String VIEW_COMBINED_WITH_IMAGES = "combined_with_images";
public static final class Images implements CommonColumns, SyncColumns {
private Images() {}
public static final String URL = "url_key";
public static final String FAVICON_URL = "favicon_url";
public static final String FAVICON = "favicon";
public static final String THUMBNAIL = "thumbnail";
public static final String _ID = "_id";
public static final String GUID = "guid";
public static final String DATE_CREATED = "created";
public static final String DATE_MODIFIED = "modified";
public static final String IS_DELETED = "deleted";
}
public static final class Combined {
private Combined() {}
public static final String THUMBNAIL = "thumbnail";
}
static final String TABLE_BOOKMARKS_JOIN_IMAGES = Bookmarks.TABLE_NAME + " LEFT OUTER JOIN " +
Obsolete.TABLE_IMAGES + " ON " + DBUtils.qualifyColumn(Bookmarks.TABLE_NAME, Bookmarks.URL) + " = " +
DBUtils.qualifyColumn(Obsolete.TABLE_IMAGES, Obsolete.Images.URL);
static final String TABLE_HISTORY_JOIN_IMAGES = History.TABLE_NAME + " LEFT OUTER JOIN " +
Obsolete.TABLE_IMAGES + " ON " + DBUtils.qualifyColumn(Bookmarks.TABLE_NAME, History.URL) + " = " +
DBUtils.qualifyColumn(Obsolete.TABLE_IMAGES, Obsolete.Images.URL);
static final String FAVICON_DB = "favicon_urls.db";
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -81,4 +81,19 @@ public class DBUtils {
Log.d(LOGTAG, "Failed to unlock database");
GeckoAppShell.listOfOpenFiles();
}
/**
* Verifies that 0-byte arrays aren't added as favicon or thumbnail data.
* @param values ContentValues of query
* @param columnName Name of data column to verify
*/
public static void stripEmptyByteArray(ContentValues values, String columnName) {
if (values.containsKey(columnName)) {
byte[] data = values.getAsByteArray(columnName);
if (data == null || data.length == 0) {
Log.w(LOGTAG, "Tried to insert an empty or non-byte-array image. Ignoring.");
values.putNull(columnName);
}
}
}
}

View File

@ -0,0 +1,270 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.gecko.db;
import org.mozilla.gecko.db.PerProfileDatabases.DatabaseHelperFactory;
import android.content.ContentProvider;
import android.content.ContentValues;
import android.content.Context;
import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.net.Uri;
import android.os.Build;
import android.text.TextUtils;
import android.util.Log;
/*
* Abstract class containing methods needed to make a SQLite-based content provider with a
* database helper of type T. Abstract methods insertInTransaction, deleteInTransaction and
* updateInTransaction all called within a DB transaction so failed modifications can be rolled-back.
*/
public abstract class TransactionalProvider<T extends SQLiteOpenHelper> extends ContentProvider {
private static final String LOGTAG = "GeckoTransProvider";
protected Context mContext;
protected PerProfileDatabases<T> mDatabases;
/*
* Returns the name of the database file. Used to get a path
* to the DB file.
*
* @return name of the database file
*/
abstract protected String getDatabaseName();
/*
* Creates and returns an instance of a DB helper. Given a
* context and a path to the DB file
*
* @param context to use to create the database helper
* @param databasePath path to the DB file
* @return instance of the database helper
*/
abstract protected T createDatabaseHelper(Context context, String databasePath);
/*
* Inserts an item into the database within a DB transaction.
*
* @param uri query URI
* @param values column values to be inserted
* @return a URI for the newly inserted item
*/
abstract protected Uri insertInTransaction(Uri uri, ContentValues values);
/*
* Deletes items from the database within a DB transaction.
*
* @param uri query URI
* @param selection An optional filter to match rows to update.
* @param selectionArgs arguments for the selection
* @return number of rows impacted by the deletion
*/
abstract protected int deleteInTransaction(Uri uri, String selection, String[] selectionArgs);
/*
* Updates the database within a DB transaction.
*
* @param uri Query URI
* @param values A set of column_name/value pairs to add to the database.
* @param selection An optional filter to match rows to update.
* @param selectionArgs Arguments for the selection
* @return number of rows impacted by the update
*/
abstract protected int updateInTransaction(Uri uri, ContentValues values, String selection, String[] selectionArgs);
/*
* Fetches a readable database based on the profile indicated in the
* passed URI. If the URI does not contain a profile param, the default profile
* is used.
*
* @param uri content URI optionally indicating the profile of the user
* @return instance of a readable SQLiteDatabase
*/
protected SQLiteDatabase getReadableDatabase(Uri uri) {
String profile = null;
if (uri != null) {
profile = uri.getQueryParameter(BrowserContract.PARAM_PROFILE);
}
return mDatabases.getDatabaseHelperForProfile(profile, isTest(uri)).getReadableDatabase();
}
/*
* Fetches a writeable database based on the profile indicated in the
* passed URI. If the URI does not contain a profile param, the default profile
* is used
*
* @param uri content URI optionally indicating the profile of the user
* @return instance of a writeable SQLiteDatabase
*/
protected SQLiteDatabase getWritableDatabase(Uri uri) {
String profile = null;
if (uri != null) {
profile = uri.getQueryParameter(BrowserContract.PARAM_PROFILE);
}
return mDatabases.getDatabaseHelperForProfile(profile, isTest(uri)).getWritableDatabase();
}
@Override
public boolean onCreate() {
synchronized (this) {
mContext = getContext();
mDatabases = new PerProfileDatabases<T>(
getContext(), getDatabaseName(), new DatabaseHelperFactory<T>() {
@Override
public T makeDatabaseHelper(Context context, String databasePath) {
return createDatabaseHelper(context, databasePath);
}
});
}
return true;
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
trace("Calling delete on URI: " + uri);
final SQLiteDatabase db = getWritableDatabase(uri);
int deleted = 0;
if (Build.VERSION.SDK_INT >= 11) {
trace("Beginning delete transaction: " + uri);
db.beginTransaction();
try {
deleted = deleteInTransaction(uri, selection, selectionArgs);
db.setTransactionSuccessful();
trace("Successful delete transaction: " + uri);
} finally {
db.endTransaction();
}
} else {
deleted = deleteInTransaction(uri, selection, selectionArgs);
}
if (deleted > 0) {
getContext().getContentResolver().notifyChange(uri, null);
}
return deleted;
}
@Override
public Uri insert(Uri uri, ContentValues values) {
trace("Calling insert on URI: " + uri);
final SQLiteDatabase db = getWritableDatabase(uri);
Uri result = null;
try {
if (Build.VERSION.SDK_INT >= 11) {
trace("Beginning insert transaction: " + uri);
db.beginTransaction();
try {
result = insertInTransaction(uri, values);
db.setTransactionSuccessful();
trace("Successful insert transaction: " + uri);
} finally {
db.endTransaction();
}
} else {
result = insertInTransaction(uri, values);
}
} catch (SQLException sqle) {
Log.e(LOGTAG, "exception in DB operation", sqle);
} catch (UnsupportedOperationException uoe) {
Log.e(LOGTAG, "don't know how to perform that insert", uoe);
}
if (result != null) {
getContext().getContentResolver().notifyChange(uri, null);
}
return result;
}
@Override
public int update(Uri uri, ContentValues values, String selection,
String[] selectionArgs) {
trace("Calling update on URI: " + uri);
final SQLiteDatabase db = getWritableDatabase(uri);
int updated = 0;
if (Build.VERSION.SDK_INT >= 11) {
trace("Beginning update transaction: " + uri);
db.beginTransaction();
try {
updated = updateInTransaction(uri, values, selection, selectionArgs);
db.setTransactionSuccessful();
trace("Successful update transaction: " + uri);
} finally {
db.endTransaction();
}
} else {
updated = updateInTransaction(uri, values, selection, selectionArgs);
}
if (updated > 0) {
getContext().getContentResolver().notifyChange(uri, null);
}
return updated;
}
@Override
public int bulkInsert(Uri uri, ContentValues[] values) {
if (values == null) {
return 0;
}
int numValues = values.length;
int successes = 0;
final SQLiteDatabase db = getWritableDatabase(uri);
db.beginTransaction();
try {
for (int i = 0; i < numValues; i++) {
insertInTransaction(uri, values[i]);
successes++;
}
trace("Flushing DB bulkinsert...");
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
if (successes > 0) {
mContext.getContentResolver().notifyChange(uri, null);
}
return successes;
}
protected boolean isTest(Uri uri) {
String isTest = uri.getQueryParameter(BrowserContract.PARAM_IS_TEST);
return !TextUtils.isEmpty(isTest);
}
// Calculate these once, at initialization. isLoggable is too expensive to
// have in-line in each log call.
private static boolean logDebug = Log.isLoggable(LOGTAG, Log.DEBUG);
private static boolean logVerbose = Log.isLoggable(LOGTAG, Log.VERBOSE);
protected static void trace(String message) {
if (logVerbose) {
Log.v(LOGTAG, message);
}
}
protected static void debug(String message) {
if (logDebug) {
Log.d(LOGTAG, message);
}
}
}

View File

@ -12,7 +12,7 @@ import org.mozilla.gecko.Tabs;
import org.mozilla.gecko.ZoomConstraints;
import org.mozilla.gecko.mozglue.RobocopTarget;
import org.mozilla.gecko.mozglue.generatorannotations.WrapElementForJNI;
import org.mozilla.gecko.util.EventDispatcher;
import org.mozilla.gecko.EventDispatcher;
import org.mozilla.gecko.util.FloatUtils;
import android.content.Context;

View File

@ -11,7 +11,7 @@ import org.mozilla.gecko.PrefsHelper;
import org.mozilla.gecko.Tab;
import org.mozilla.gecko.Tabs;
import org.mozilla.gecko.ZoomConstraints;
import org.mozilla.gecko.util.EventDispatcher;
import org.mozilla.gecko.EventDispatcher;
import org.mozilla.gecko.util.FloatUtils;
import org.mozilla.gecko.util.GamepadUtils;
import org.mozilla.gecko.util.GeckoEventListener;

View File

@ -16,7 +16,7 @@ import org.mozilla.gecko.TouchEventInterceptor;
import org.mozilla.gecko.ZoomConstraints;
import org.mozilla.gecko.mozglue.generatorannotations.WrapElementForJNI;
import org.mozilla.gecko.mozglue.RobocopTarget;
import org.mozilla.gecko.util.EventDispatcher;
import org.mozilla.gecko.EventDispatcher;
import android.content.Context;
import android.graphics.Bitmap;

View File

@ -8,7 +8,7 @@ package org.mozilla.gecko.gfx;
import org.mozilla.gecko.GeckoEvent;
import org.mozilla.gecko.GeckoThread;
import org.mozilla.gecko.mozglue.generatorannotations.WrapElementForJNI;
import org.mozilla.gecko.util.EventDispatcher;
import org.mozilla.gecko.EventDispatcher;
import org.mozilla.gecko.util.GeckoEventListener;
import org.json.JSONObject;

View File

@ -6,7 +6,7 @@
package org.mozilla.gecko.gfx;
import org.mozilla.gecko.GeckoAppShell;
import org.mozilla.gecko.util.EventDispatcher;
import org.mozilla.gecko.EventDispatcher;
import android.graphics.PointF;
import android.view.KeyEvent;

View File

@ -7,7 +7,7 @@ package org.mozilla.gecko.gfx;
import org.mozilla.gecko.GeckoAppShell;
import org.mozilla.gecko.GeckoEvent;
import org.mozilla.gecko.util.EventDispatcher;
import org.mozilla.gecko.EventDispatcher;
import org.mozilla.gecko.util.GeckoEventListener;
import org.json.JSONException;

View File

@ -26,7 +26,7 @@ import org.mozilla.gecko.background.healthreport.HealthReportStorage.Measurement
import org.mozilla.gecko.background.healthreport.HealthReportStorage.MeasurementFields.FieldSpec;
import org.mozilla.gecko.background.healthreport.ProfileInformationCache;
import org.mozilla.gecko.util.EventDispatcher;
import org.mozilla.gecko.EventDispatcher;
import org.mozilla.gecko.util.GeckoEventListener;
import org.mozilla.gecko.util.ThreadUtils;

View File

@ -95,7 +95,6 @@ public class BrowserSearch extends HomeFragment
private HomeListView mList;
// Client that performs search suggestion queries
@RobocopTarget
private volatile SuggestClient mSuggestClient;
// List of search engines from gecko
@ -519,8 +518,8 @@ public class BrowserSearch extends HomeFragment
// set yet. e.g. Robocop tests might set it directly before search
// engines are loaded.
if (mSuggestClient == null && !isPrivate) {
mSuggestClient = new SuggestClient(getActivity(), suggestTemplate,
SUGGESTION_TIMEOUT, SUGGESTION_MAX);
setSuggestClient(new SuggestClient(getActivity(), suggestTemplate,
SUGGESTION_TIMEOUT, SUGGESTION_MAX));
}
} else {
searchEngines.add(engine);
@ -545,6 +544,20 @@ public class BrowserSearch extends HomeFragment
filterSuggestions();
}
/**
* Sets the private SuggestClient instance. Should only be called if the suggestClient is
* null (i.e. has not yet been initialized or has been nulled). Non-private access is
* for testing purposes only.
*/
@RobocopTarget
public void setSuggestClient(final SuggestClient client) {
if (mSuggestClient != null) {
throw new IllegalStateException("Can only set the SuggestClient if it has not " +
"yet been initialized!");
}
mSuggestClient = client;
}
private void showSuggestionsOptIn() {
// Return if the ViewStub was already inflated - an inflated ViewStub is removed from the
// View hierarchy so a second call to findViewById will return null.

View File

@ -26,7 +26,7 @@ import java.util.ArrayList;
/**
* Use network-based search suggestions.
*/
class SuggestClient {
public class SuggestClient {
private static final String LOGTAG = "GeckoSuggestClient";
private static final String USER_AGENT = GeckoAppShell.getGeckoInterface().getDefaultUAString();
@ -39,7 +39,7 @@ class SuggestClient {
// the maximum number of suggestions to return
private final int mMaxResults;
// used by robocop for testing; referenced via reflection
// used by robocop for testing
private boolean mCheckNetwork;
// used to make suggestions appear instantly after opt-in

View File

@ -41,12 +41,10 @@ gujar.sources += [
'util/ActivityResultHandler.java',
'util/ActivityResultHandlerMap.java',
'util/Clipboard.java',
'util/EventDispatcher.java',
'util/FloatUtils.java',
'util/GamepadUtils.java',
'util/GeckoBackgroundThread.java',
'util/GeckoEventListener.java',
'util/GeckoEventResponder.java',
'util/GeckoJarReader.java',
'util/HardwareUtils.java',
'util/INIParser.java',
@ -112,6 +110,7 @@ gbjar.sources += [
'CustomEditText.java',
'DataReportingNotification.java',
'db/BrowserContract.java',
'db/BrowserDatabaseHelper.java',
'db/BrowserDB.java',
'db/BrowserProvider.java',
'db/DBUtils.java',
@ -122,9 +121,11 @@ gbjar.sources += [
'db/PerProfileDatabases.java',
'db/SQLiteBridgeContentProvider.java',
'db/TabsProvider.java',
'db/TransactionalProvider.java',
'Distribution.java',
'DoorHangerPopup.java',
'EditBookmarkDialog.java',
'EventDispatcher.java',
'favicons/cache/FaviconCache.java',
'favicons/cache/FaviconCacheElement.java',
'favicons/cache/FaviconsForURL.java',

View File

@ -5,7 +5,6 @@
package org.mozilla.gecko.prompts;
import org.mozilla.gecko.util.GeckoEventResponder;
import org.mozilla.gecko.GeckoEvent;
import org.mozilla.gecko.util.ThreadUtils;
import org.mozilla.gecko.widget.DateTimePicker;
@ -54,7 +53,6 @@ import android.widget.TimePicker;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.TimeUnit;
public class Prompt implements OnClickListener, OnCancelListener, OnItemClickListener {
@ -66,7 +64,6 @@ public class Prompt implements OnClickListener, OnCancelListener, OnItemClickLis
private AlertDialog mDialog;
private final LayoutInflater mInflater;
private ConcurrentLinkedQueue<String> mPromptQueue;
private final Context mContext;
private PromptCallback mCallback;
private String mGuid;
@ -80,16 +77,9 @@ public class Prompt implements OnClickListener, OnCancelListener, OnItemClickLis
private static int mInputPaddingSize;
private static int mMinRowSize;
public Prompt(Context context, ConcurrentLinkedQueue<String> queue) {
this(context);
mCallback = null;
mPromptQueue = queue;
}
public Prompt(Context context, PromptCallback callback) {
this(context);
mCallback = callback;
mPromptQueue = null;
}
private Prompt(Context context) {
@ -415,10 +405,6 @@ public class Prompt implements OnClickListener, OnCancelListener, OnItemClickLis
aReturn.put("guid", mGuid);
} catch(JSONException ex) { }
if (mPromptQueue != null) {
mPromptQueue.offer(aReturn.toString());
}
// poke the Gecko thread in case it's waiting for new events
GeckoAppShell.sendEventToGecko(GeckoEvent.createNoOpEvent());

View File

@ -5,7 +5,6 @@
package org.mozilla.gecko.prompts;
import org.mozilla.gecko.util.GeckoEventResponder;
import org.mozilla.gecko.util.ThreadUtils;
import org.mozilla.gecko.widget.AllCapsTextView;
import org.mozilla.gecko.widget.DateTimePicker;

View File

@ -5,27 +5,28 @@
package org.mozilla.gecko.prompts;
import org.mozilla.gecko.EventDispatcher;
import org.mozilla.gecko.GeckoAppShell;
import org.mozilla.gecko.GeckoEvent;
import org.mozilla.gecko.util.GeckoEventResponder;
import org.mozilla.gecko.util.ThreadUtils;
import org.mozilla.gecko.util.GeckoEventListener;
import org.json.JSONObject;
import org.json.JSONException;
import android.content.Context;
import android.util.Log;
import java.util.concurrent.ConcurrentLinkedQueue;
public class PromptService implements GeckoEventResponder {
public class PromptService implements GeckoEventListener {
private static final String LOGTAG = "GeckoPromptService";
private final ConcurrentLinkedQueue<String> mPromptQueue;
private final Context mContext;
public PromptService(Context context) {
GeckoAppShell.getEventDispatcher().registerEventListener("Prompt:Show", this);
GeckoAppShell.getEventDispatcher().registerEventListener("Prompt:ShowTop", this);
mPromptQueue = new ConcurrentLinkedQueue<String>();
mContext = context;
}
@ -41,11 +42,7 @@ public class PromptService implements GeckoEventResponder {
@Override
public void run() {
Prompt p;
if (callback != null) {
p = new Prompt(mContext, callback);
} else {
p = new Prompt(mContext, mPromptQueue);
}
p = new Prompt(mContext, callback);
p.show(aTitle, aText, aMenuList, aMultipleSelection);
}
});
@ -58,35 +55,18 @@ public class PromptService implements GeckoEventResponder {
ThreadUtils.postToUiThread(new Runnable() {
@Override
public void run() {
boolean isAsync = message.optBoolean("async");
Prompt p;
if (isAsync) {
p = new Prompt(mContext, new Prompt.PromptCallback() {
public void onPromptFinished(String jsonResult) {
GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Prompt:Reply", jsonResult));
p = new Prompt(mContext, new Prompt.PromptCallback() {
public void onPromptFinished(String jsonResult) {
try {
EventDispatcher.sendResponse(message, new JSONObject(jsonResult));
} catch(JSONException ex) {
Log.i(LOGTAG, "Error building json response", ex);
}
});
} else {
p = new Prompt(mContext, mPromptQueue);
}
}
});
p.show(message);
}
});
}
// GeckoEventResponder implementation
@Override
public String getResponse(final JSONObject origMessage) {
if (origMessage.optBoolean("async")) {
return "";
}
// we only handle one kind of message in handleMessage, and this is the
// response we provide for that message
String result;
while (null == (result = mPromptQueue.poll())) {
GeckoAppShell.processNextNativeEvent(true);
}
return result;
}
}

View File

@ -58,6 +58,7 @@ abstract class UITest extends ActivityInstrumentationTestCase2<Activity>
private String mBaseIpUrl;
protected AboutHomeComponent mAboutHome;
protected AppMenuComponent mAppMenu;
protected ToolbarComponent mToolbar;
static {
@ -120,6 +121,7 @@ abstract class UITest extends ActivityInstrumentationTestCase2<Activity>
private void initComponents() {
mAboutHome = new AboutHomeComponent(this);
mAppMenu = new AppMenuComponent(this);
mToolbar = new ToolbarComponent(this);
}
@ -163,6 +165,9 @@ abstract class UITest extends ActivityInstrumentationTestCase2<Activity>
case ABOUTHOME:
return mAboutHome;
case APPMENU:
return mAppMenu;
case TOOLBAR:
return mToolbar;

View File

@ -21,6 +21,7 @@ public interface UITestContext {
public static enum ComponentType {
ABOUTHOME,
APPMENU,
TOOLBAR
}

View File

@ -0,0 +1,143 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.gecko.tests.components;
import static org.mozilla.gecko.tests.helpers.AssertionHelper.*;
import org.mozilla.gecko.menu.MenuItemActionBar;
import org.mozilla.gecko.menu.MenuItemDefault;
import org.mozilla.gecko.tests.helpers.*;
import org.mozilla.gecko.tests.UITestContext;
import org.mozilla.gecko.util.HardwareUtils;
import org.mozilla.gecko.R;
import com.jayway.android.robotium.solo.Condition;
import com.jayway.android.robotium.solo.RobotiumUtils;
import com.jayway.android.robotium.solo.Solo;
import android.view.View;
import java.util.List;
/**
* A class representing any interactions that take place on the app menu.
*/
public class AppMenuComponent extends BaseComponent {
public enum MenuItem {
FORWARD(R.string.forward),
NEW_TAB(R.string.new_tab);
private final int resourceID;
private String stringResource;
MenuItem(final int resourceID) {
this.resourceID = resourceID;
}
public String getString(final Solo solo) {
if (stringResource == null) {
stringResource = solo.getString(resourceID);
}
return stringResource;
}
};
public AppMenuComponent(final UITestContext testContext) {
super(testContext);
}
private void assertMenuIsNotOpen() {
assertFalse("Menu is not open", isMenuOpen());
}
private View getOverflowMenuButtonView() {
return mSolo.getView(R.id.menu);
}
/**
* Try to find a MenuItemActionBar/MenuItemDefault with the given text set as contentDescription / text.
*
* Will return null when the Android legacy menu is in use.
*
* This method is dependent on not having two views with equivalent contentDescription / text.
*/
private View findAppMenuItemView(String text) {
final List<View> views = mSolo.getViews();
final List<MenuItemActionBar> menuItemActionBarList = RobotiumUtils.filterViews(MenuItemActionBar.class, views);
for (MenuItemActionBar menuItem : menuItemActionBarList) {
if (menuItem.getContentDescription().equals(text)) {
return menuItem;
}
}
final List<MenuItemDefault> menuItemDefaultList = RobotiumUtils.filterViews(MenuItemDefault.class, views);
for (MenuItemDefault menuItem : menuItemDefaultList) {
if (menuItem.getText().equals(text)) {
return menuItem;
}
}
return null;
}
public void pressMenuItem(MenuItem menuItem) {
openAppMenu();
final String text = menuItem.getString(mSolo);
final View menuItemView = findAppMenuItemView(text);
if (menuItemView != null) {
assertTrue("The menu item is enabled", menuItemView.isEnabled());
assertEquals("The menu item is visible", View.VISIBLE, menuItemView.getVisibility());
mSolo.clickOnView(menuItemView);
} else {
// We could not find a view representing this menu item: Let's let Robotium try to
// locate and click it in the legacy Android menu (devices with Android 2.x).
//
// Even though we already opened the menu to see if we can locate the menu item,
// Robotium will also try to open the menu if it doesn't find an open dialog (Does
// not happen in this case).
mSolo.clickOnMenuItem(text, true);
}
}
private void openAppMenu() {
assertMenuIsNotOpen();
if (HardwareUtils.hasMenuButton()) {
mSolo.sendKey(Solo.MENU);
} else {
pressOverflowMenuButton();
}
waitForMenuOpen();
}
private void pressOverflowMenuButton() {
final View overflowMenuButton = getOverflowMenuButtonView();
assertTrue("The overflow menu button is enabled", overflowMenuButton.isEnabled());
assertEquals("The overflow menu button is visible", View.VISIBLE, overflowMenuButton.getVisibility());
mSolo.clickOnView(overflowMenuButton, true);
}
private boolean isMenuOpen() {
// The presence of the "New tab" menu item is our best guess about whether
// the menu is open or not.
return mSolo.searchText(MenuItem.NEW_TAB.getString(mSolo));
}
private void waitForMenuOpen() {
WaitHelper.waitFor("menu to open", new Condition() {
@Override
public boolean isSatisfied() {
return isMenuOpen();
}
});
}
}

View File

@ -6,6 +6,7 @@ package org.mozilla.gecko.tests.helpers;
import static org.mozilla.gecko.tests.helpers.AssertionHelper.*;
import org.mozilla.gecko.tests.components.AppMenuComponent;
import org.mozilla.gecko.tests.components.ToolbarComponent;
import org.mozilla.gecko.tests.UITestContext;
import org.mozilla.gecko.tests.UITestContext.ComponentType;
@ -22,12 +23,14 @@ final public class NavigationHelper {
private static UITestContext sContext;
private static Solo sSolo;
private static AppMenuComponent sAppMenu;
private static ToolbarComponent sToolbar;
protected static void init(final UITestContext context) {
sContext = context;
sSolo = context.getSolo();
sAppMenu = (AppMenuComponent) context.getComponent(ComponentType.APPMENU);
sToolbar = (ToolbarComponent) context.getComponent(ComponentType.TOOLBAR);
}
@ -81,8 +84,7 @@ final public class NavigationHelper {
WaitHelper.waitForPageLoad(new Runnable() {
@Override
public void run() {
// TODO: Press forward with APPMENU component
throw new UnsupportedOperationException("Not yet implemented.");
sAppMenu.pressMenuItem(AppMenuComponent.MenuItem.FORWARD);
}
});
}

View File

@ -4,13 +4,10 @@
<meta name="viewport" content="initial-scale=1.0"/>
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
<script type="application/javascript">
Components.utils.import("resource://gre/modules/Messaging.jsm");
Components.utils.import("resource://gre/modules/Services.jsm");
Components.utils.import("resource://gre/modules/Home.jsm");
function sendMessageToJava(msg) {
return Services.androidBridge.handleGeckoMessage(JSON.stringify(msg));
}
const TEXT = "The quick brown fox jumps over the lazy dog.";
function start() {

View File

@ -1,15 +1,15 @@
package org.mozilla.gecko.tests;
import org.mozilla.gecko.*;
import org.mozilla.gecko.home.BrowserSearch;
import org.mozilla.gecko.home.SuggestClient;
import android.app.Activity;
import android.content.Context;
import android.support.v4.app.Fragment;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.RuntimeException;
import java.util.ArrayList;
import java.util.HashMap;
@ -98,34 +98,20 @@ public class testSearchSuggestions extends BaseTest {
}
private void connectSuggestClient(final Activity activity) {
try {
// create a SuggestClient that uses robocop_suggestions.sjs
ClassLoader classLoader = getActivity().getApplicationContext().getClassLoader();
Class suggestClass = classLoader.loadClass("org.mozilla.gecko.home.SuggestClient");
Constructor suggestConstructor = suggestClass.getConstructor(
new Class[] { Context.class, String.class, int.class });
String suggestTemplate = getAbsoluteRawUrl(SUGGESTION_TEMPLATE);
waitForTest(new BooleanTest() {
@Override
public boolean test() {
final Fragment browserSearch = getBrowserSearch();
return (browserSearch != null);
}
}, SUGGESTION_TIMEOUT);
Object client = suggestConstructor.newInstance(activity, suggestTemplate, SUGGESTION_TIMEOUT);
final BrowserSearch browserSearch = (BrowserSearch) getBrowserSearch();
// replace mSuggestClient with test client
final Class browserSearchClass = classLoader.loadClass("org.mozilla.gecko.home.BrowserSearch");
final Field suggestClientField = browserSearchClass.getDeclaredField("mSuggestClient");
suggestClientField.setAccessible(true);
waitForTest(new BooleanTest() {
@Override
public boolean test() {
final Fragment browserSearch = getBrowserSearch();
return (browserSearch != null);
}
}, SUGGESTION_TIMEOUT);
final Fragment browserSearch = getBrowserSearch();
suggestClientField.set(browserSearch, client);
} catch (Exception e) {
throw new RuntimeException("Error setting SuggestClient", e);
}
final String suggestTemplate = getAbsoluteRawUrl(SUGGESTION_TEMPLATE);
final SuggestClient client = new SuggestClient(activity, suggestTemplate,
SUGGESTION_TIMEOUT);
browserSearch.setSuggestClient(client);
}
}

View File

@ -26,11 +26,11 @@ public class testSessionHistory extends UITest {
NavigationHelper.goBack();
mToolbar.assertTitle(StringHelper.ROBOCOP_BLANK_PAGE_01_TITLE);
// TODO: Implement this functionality and uncomment.
/*
NavigationHelper.goForward();
mToolbar.assertTitle(StringHelper.ROBOCOP_BLANK_PAGE_02_TITLE);
// TODO: Implement this functionality and uncomment.
/*
NavigationHelper.reload();
mToolbar.assertTitle(StringHelper.ROBOCOP_BLANK_PAGE_02_TITLE);
*/

View File

@ -1,16 +0,0 @@
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
* ***** BEGIN LICENSE BLOCK *****
*
* 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/.
*
* ***** END LICENSE BLOCK ***** */
package org.mozilla.gecko.util;
import org.json.JSONObject;
public interface GeckoEventResponder extends GeckoEventListener {
String getResponse(JSONObject response);
}

View File

@ -11,9 +11,8 @@ import org.mozilla.gecko.GeckoProfile;
import org.mozilla.gecko.favicons.decoders.FaviconDecoder;
import org.mozilla.gecko.gfx.BitmapUtils;
import org.mozilla.gecko.util.ActivityResultHandler;
import org.mozilla.gecko.util.EventDispatcher;
import org.mozilla.gecko.EventDispatcher;
import org.mozilla.gecko.util.GeckoEventListener;
import org.mozilla.gecko.util.GeckoEventResponder;
import org.mozilla.gecko.util.ThreadUtils;
import org.mozilla.gecko.WebappAllocator;
@ -38,14 +37,13 @@ import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
public class EventListener implements GeckoEventListener, GeckoEventResponder {
public class EventListener implements GeckoEventListener {
private static final String LOGTAG = "GeckoWebappEventListener";
private EventListener() { }
private static EventListener mEventListener;
private String mCurrentResponse = "";
private static EventListener getEventListener() {
if (mEventListener == null) {
@ -110,23 +108,20 @@ public class EventListener implements GeckoEventListener, GeckoEventResponder {
String manifestURL = message.getString("manifestURL");
String origin = message.getString("origin");
// preInstallWebapp will return a File object pointing to the profile directory of the webapp
mCurrentResponse = preInstallWebapp(name, manifestURL, origin).toString();
JSONObject obj = new JSONObject();
obj.put("profile", preInstallWebapp(name, manifestURL, origin).toString());
EventDispatcher.sendResponse(message, obj);
} else if (event.equals("WebApps:GetApkVersions")) {
mCurrentResponse = getApkVersions(GeckoAppShell.getGeckoInterface().getActivity(),
message.getJSONArray("packageNames")).toString();
JSONObject obj = new JSONObject();
obj.put("versions", getApkVersions(GeckoAppShell.getGeckoInterface().getActivity(),
message.getJSONArray("packageNames")).toString());
EventDispatcher.sendResponse(message, obj);
}
} catch (Exception e) {
Log.e(LOGTAG, "Exception handling message \"" + event + "\":", e);
}
}
public String getResponse(JSONObject origMessage) {
String res = mCurrentResponse;
mCurrentResponse = "";
return res;
}
// Not used by MOZ_ANDROID_SYNTHAPKS.
public static File preInstallWebapp(String aTitle, String aURI, String aOrigin) {
int index = WebappAllocator.getInstance(GeckoAppShell.getContext()).findAndAllocateIndex(aOrigin, aTitle, (String) null);

View File

@ -9,16 +9,13 @@ let Ci = Components.interfaces;
let Cu = Components.utils;
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/Messaging.jsm");
document.addEventListener("DOMContentLoaded", init, false);
function dump(a) {
Services.console.logStringMessage(a);
}
function sendMessageToJava(aMessage) {
Services.androidBridge.handleGeckoMessage(JSON.stringify(aMessage));
}
function init() {
let anchors = document.querySelectorAll(".maybe-later");
for(let anchor of anchors) {

View File

@ -9,6 +9,7 @@
const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/Messaging.jsm");
Cu.import("resource://gre/modules/SharedPreferences.jsm");
// Name of Android SharedPreference controlling whether to upload
@ -24,10 +25,6 @@ const WRAPPER_VERSION = 1;
const EVENT_HEALTH_REQUEST = "HealthReport:Request";
const EVENT_HEALTH_RESPONSE = "HealthReport:Response";
function sendMessageToJava(message) {
return Services.androidBridge.handleGeckoMessage(JSON.stringify(message));
}
// about:healthreport prefs are stored in Firefox's default Android
// SharedPreferences.
let sharedPrefs = new SharedPreferences();

View File

@ -27,6 +27,9 @@ Cu.import("resource://gre/modules/accessibility/AccessFu.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PluralForm",
"resource://gre/modules/PluralForm.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "sendMessageToJava",
"resource://gre/modules/Messaging.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "DebuggerServer",
"resource://gre/modules/devtools/dbg-server.jsm");
@ -169,10 +172,6 @@ function dump(a) {
Services.console.logStringMessage(a);
}
function sendMessageToJava(aMessage) {
return Services.androidBridge.handleGeckoMessage(JSON.stringify(aMessage));
}
function doChangeMaxLineBoxWidth(aWidth) {
gReflowPending = null;
let webNav = BrowserApp.selectedTab.window.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIWebNavigation);
@ -7138,18 +7137,20 @@ var WebappsUI = {
doInstall: function doInstall(aData) {
let jsonManifest = aData.isPackage ? aData.app.updateManifest : aData.app.manifest;
let manifest = new ManifestHelper(jsonManifest, aData.app.origin);
let showPrompt = true;
if (!showPrompt || Services.prompt.confirm(null, Strings.browser.GetStringFromName("webapps.installTitle"), manifest.name + "\n" + aData.app.origin)) {
if (Services.prompt.confirm(null, Strings.browser.GetStringFromName("webapps.installTitle"), manifest.name + "\n" + aData.app.origin)) {
// Get a profile for the app to be installed in. We'll download everything before creating the icons.
let origin = aData.app.origin;
let profilePath = sendMessageToJava({
type: "Webapps:Preinstall",
name: manifest.name,
manifestURL: aData.app.manifestURL,
origin: origin
});
if (profilePath) {
sendMessageToJava({
type: "Webapps:Preinstall",
name: manifest.name,
manifestURL: aData.app.manifestURL,
origin: origin
}, (data) => {
let profilePath = JSON.parse(data).profile;
if (!profilePath)
return;
let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile);
file.initWithPath(profilePath);
@ -8032,12 +8033,12 @@ var ExternalApps = {
},
updatePageAction: function updatePageAction(uri) {
let apps = HelperApps.getAppsForUri(uri);
if (apps.length > 0)
this._setUriForPageAction(uri, apps);
else
this._removePageAction();
HelperApps.getAppsForUri(uri, { filterHttp: true }, (apps) => {
if (apps.length > 0)
this._setUriForPageAction(uri, apps);
else
this._removePageAction();
});
},
_setUriForPageAction: function setUriForPageAction(uri, apps) {

View File

@ -8,10 +8,7 @@ const Cc = Components.classes;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
function sendMessageToJava(aMessage) {
return Services.androidBridge.handleGeckoMessage(JSON.stringify(aMessage));
}
Cu.import("resource://gre/modules/Messaging.jsm");
function ContentDispatchChooser() {}

View File

@ -65,11 +65,6 @@ PaymentUI.prototype = {
return this.bundle = Services.strings.createBundle("chrome://browser/locale/payments.properties");
},
sendMessageToJava: function(aMsg) {
let data = Services.androidBridge.handleGeckoMessage(JSON.stringify(aMsg));
return JSON.parse(data);
},
confirmPaymentRequest: function confirmPaymentRequest(aRequestId,
aRequests,
aSuccessCb,

View File

@ -805,11 +805,6 @@ let PromptUtils = {
return hostname;
},
sendMessageToJava: function(aMsg) {
let data = Services.androidBridge.handleGeckoMessage(JSON.stringify(aMsg));
return JSON.parse(data);
},
fireDialogEvent: function(aDomWin, aEventName) {
// accessing the document object can throw if this window no longer exists. See bug 789888.
try {

View File

@ -17,9 +17,10 @@ XPCOMUtils.defineLazyServiceGetter(this, "CrashReporter",
XPCOMUtils.defineLazyModuleGetter(this, "Task", "resource://gre/modules/Task.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "OS", "resource://gre/modules/osfile.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "sendMessageToJava", "resource://gre/modules/Messaging.jsm");
function dump(a) {
Cc["@mozilla.org/consoleservice;1"].getService(Ci.nsIConsoleService).logStringMessage(a);
Services.console.logStringMessage(a);
}
// -----------------------------------------------------------------------
@ -63,11 +64,6 @@ SessionStore.prototype = {
OS.File.remove(this._sessionFileBackup.path);
},
_sendMessageToJava: function (aMsg) {
let data = Services.androidBridge.handleGeckoMessage(JSON.stringify(aMsg));
return JSON.parse(data);
},
observe: function ss_observe(aSubject, aTopic, aData) {
let self = this;
let observerService = Services.obs;
@ -132,7 +128,7 @@ SessionStore.prototype = {
}
// Let Java know we're done restoring tabs so tabs added after this can be animated
this._sendMessageToJava({
sendMessageToJava({
type: "Session:RestoreEnd"
});
}.bind(this)
@ -415,7 +411,7 @@ SessionStore.prototype = {
// If we have private data, send it to Java; otherwise, send null to
// indicate that there is no private data
this._sendMessageToJava({
sendMessageToJava({
type: "PrivateBrowsing:Data",
session: (privateData.windows[0].tabs.length > 0) ? JSON.stringify(privateData) : null
});

View File

@ -8,6 +8,7 @@ const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/Prompt.jsm");
Cu.import("resource://gre/modules/Messaging.jsm");
XPCOMUtils.defineLazyGetter(this, "ContentAreaUtils", function() {
let ContentAreaUtils = {};
@ -35,7 +36,19 @@ App.prototype = {
var HelperApps = {
get defaultHttpHandlers() {
delete this.defaultHttpHandlers;
return this.defaultHttpHandlers = this.getAppsForProtocol("http");
this.defaultHttpHandlers = this.getAppsForProtocol("http");
return this.defaultHttpHandlers;
},
get defaultHtmlHandlers() {
delete this.defaultHtmlHandlers;
let handlers = this.getAppsForUri(Services.io.newURI("http://www.example.com/index.html", null, null));
this.defaultHtmlHandlers = {};
handlers.forEach(function(app) {
this.defaultHtmlHandlers[app.name] = app;
}, this);
return this.defaultHtmlHandlers;
},
get protoSvc() {
@ -70,29 +83,54 @@ var HelperApps = {
return results;
},
getAppsForUri: function getAppsForUri(uri, flags = { filterHttp: true }) {
getAppsForUri: function getAppsForUri(uri, flags = { filterHttp: true, filterHtml: true }, callback) {
flags.filterHttp = "filterHttp" in flags ? flags.filterHttp : true;
flags.filterHtml = "filterHtml" in flags ? flags.filterHtml : true;
// Query for apps that can/can't handle the mimetype
let msg = this._getMessage("Intent:GetHandlers", uri, flags);
let data = this._sendMessage(msg);
if (!data)
return [];
let parseData = (d) => {
let apps = []
let apps = this._parseApps(data.apps);
if (!d)
return apps;
if (flags.filterHttp) {
apps = apps.filter(function(app) {
return app.name && !this.defaultHttpHandlers[app.name];
}, this);
apps = this._parseApps(d.apps);
if (flags.filterHttp) {
apps = apps.filter(function(app) {
return app.name && !this.defaultHttpHandlers[app.name];
}, this);
}
if (flags.filterHtml) {
// Matches from the first '.' to the end of the string, '?', or '#'
let ext = /\.([^\?#]*)/.exec(uri.path);
if (ext && (ext[1] === "html" || ext[1] === "htm")) {
apps = apps.filter(function(app) {
return app.name && !this.defaultHtmlHandlers[app.name];
}, this);
}
}
return apps;
};
if (!callback) {
let data = this._sendMessageSync(msg);
if (!data)
return [];
return parseData(JSON.parse(data));
} else {
sendMessageToJava(msg, function(data) {
callback(parseData(JSON.parse(data)));
});
}
return apps;
},
launchUri: function launchUri(uri) {
let msg = this._getMessage("Intent:Open", uri);
this._sendMessage(msg);
sendMessageToJava(msg);
},
_parseApps: function _parseApps(appInfo) {
@ -132,11 +170,19 @@ var HelperApps = {
className: app.activityName
});
this._sendMessage(msg);
sendMessageToJava(msg);
},
_sendMessage: function(msg) {
let res = Services.androidBridge.handleGeckoMessage(JSON.stringify(msg));
return JSON.parse(res);
_sendMessageSync: function(msg) {
let res = null;
sendMessageToJava(msg, function(data) {
res = data;
});
let thread = Services.tm.currentThread;
while (res == null)
thread.processNextEvent(true);
return res;
},
};

View File

@ -11,6 +11,7 @@ const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/SharedPreferences.jsm");
Cu.import("resource://gre/modules/Messaging.jsm");
// See bug 915424
function resolveGeckoURI(aURI) {
@ -27,10 +28,6 @@ function resolveGeckoURI(aURI) {
return aURI;
}
function sendMessageToJava(message) {
return Services.androidBridge.handleGeckoMessage(JSON.stringify(message));
}
function BannerMessage(options) {
let uuidgen = Cc["@mozilla.org/uuid-generator;1"].getService(Ci.nsIUUIDGenerator);
this.id = uuidgen.generateUUID().toString();
@ -48,32 +45,21 @@ function BannerMessage(options) {
this.onclick = options.onclick;
}
let HomeBanner = Object.freeze({
let HomeBanner = (function () {
// Holds the messages that will rotate through the banner.
_messages: {},
let _messages = {};
// A queue used to keep track of which message to show next.
_queue: [],
let _queue = [];
observe: function(subject, topic, data) {
switch(topic) {
case "HomeBanner:Get":
this._handleGet();
break;
case "HomeBanner:Click":
this._handleClick(data);
break;
}
},
_handleGet: function() {
let _handleGet = function() {
// Get the message from the front of the queue, then add it back
// to the end of the queue to show it again later.
let id = this._queue.shift();
this._queue.push(id);
let id = _queue.shift();
_queue.push(id);
let message = this._messages[id];
let message = _messages[id];
sendMessageToJava({
type: "HomeBanner:Data",
id: message.id,
@ -83,59 +69,73 @@ let HomeBanner = Object.freeze({
if (message.onshown)
message.onshown();
},
};
_handleClick: function(id) {
let message = this._messages[id];
let _handleClick = function(id) {
let message = _messages[id];
if (message.onclick)
message.onclick();
},
};
/**
* Adds a new banner message to the rotation.
*
* @return id Unique identifer for the message.
*/
add: function(options) {
let message = new BannerMessage(options);
this._messages[message.id] = message;
return Object.freeze({
observe: function(subject, topic, data) {
switch(topic) {
case "HomeBanner:Get":
_handleGet();
break;
// Add the new message to the end of the queue.
this._queue.push(message.id);
case "HomeBanner:Click":
_handleClick(data);
break;
}
},
// If this is the first message we're adding, add
// observers to listen for requests from the Java UI.
if (Object.keys(this._messages).length == 1) {
Services.obs.addObserver(this, "HomeBanner:Get", false);
Services.obs.addObserver(this, "HomeBanner:Click", false);
/**
* Adds a new banner message to the rotation.
*
* @return id Unique identifer for the message.
*/
add: function(options) {
let message = new BannerMessage(options);
_messages[message.id] = message;
// Send a message to Java, in case there's an active HomeBanner
// waiting for a response.
this._handleGet();
// Add the new message to the end of the queue.
_queue.push(message.id);
// If this is the first message we're adding, add
// observers to listen for requests from the Java UI.
if (Object.keys(_messages).length == 1) {
Services.obs.addObserver(this, "HomeBanner:Get", false);
Services.obs.addObserver(this, "HomeBanner:Click", false);
// Send a message to Java, in case there's an active HomeBanner
// waiting for a response.
_handleGet();
}
return message.id;
},
/**
* Removes a banner message from the rotation.
*
* @param id The id of the message to remove.
*/
remove: function(id) {
delete _messages[id];
// Remove the message from the queue.
let index = _queue.indexOf(id);
_queue.splice(index, 1);
// If there are no more messages, remove the observers.
if (Object.keys(_messages).length == 0) {
Services.obs.removeObserver(this, "HomeBanner:Get");
Services.obs.removeObserver(this, "HomeBanner:Click");
}
}
return message.id;
},
/**
* Removes a banner message from the rotation.
*
* @param id The id of the message to remove.
*/
remove: function(id) {
delete this._messages[id];
// Remove the message from the queue.
let index = this._queue.indexOf(id);
this._queue.splice(index, 1);
// If there are no more messages, remove the observers.
if (Object.keys(this._messages).length == 0) {
Services.obs.removeObserver(this, "HomeBanner:Get");
Services.obs.removeObserver(this, "HomeBanner:Click");
}
}
});
});
})();
function Panel(options) {
if ("id" in options)
@ -151,53 +151,30 @@ function Panel(options) {
this.views = options.views;
}
let HomePanels = Object.freeze({
// Valid layouts for a panel.
Layout: Object.freeze({
FRAME: "frame"
}),
// Valid types of views for a dataset.
View: Object.freeze({
LIST: "list",
GRID: "grid"
}),
// Valid actions for a panel.
Action: Object.freeze({
INSTALL: "install",
REFRESH: "refresh"
}),
// Valid item handlers for a panel view.
ItemHandler: Object.freeze({
BROWSER: "browser",
INTENT: "intent"
}),
let HomePanels = (function () {
// Holds the currrent set of registered panels.
_panels: {},
let _panels = {};
_panelToJSON : function(panel) {
let _panelToJSON = function(panel) {
return {
id: panel.id,
title: panel.title,
layout: panel.layout,
views: panel.views
};
},
};
_handleGet: function(data) {
let _handleGet = function(data) {
let requestId = data.requestId;
let ids = data.ids || null;
let panels = [];
for (let id in this._panels) {
let panel = this._panels[id];
for (let id in _panels) {
let panel = _panels[id];
// Null ids means we want to fetch all available panels
if (ids == null || ids.indexOf(panel.id) >= 0) {
panels.push(this._panelToJSON(panel));
panels.push(_panelToJSON(panel));
}
}
@ -206,92 +183,117 @@ let HomePanels = Object.freeze({
panels: panels,
requestId: requestId
});
},
add: function(options) {
let panel = new Panel(options);
if (!panel.id || !panel.title) {
throw "Home.panels: Can't create a home panel without an id and title!";
}
let action = options.action;
// Bail if the panel already exists, except when we're refreshing
// an existing panel instance.
if (panel.id in this._panels && action != this.Action.REFRESH) {
throw "Home.panels: Panel already exists: id = " + panel.id;
}
if (!this._valueExists(this.Layout, panel.layout)) {
throw "Home.panels: Invalid layout for panel: panel.id = " + panel.id + ", panel.layout =" + panel.layout;
}
for (let view of panel.views) {
if (!this._valueExists(this.View, view.type)) {
throw "Home.panels: Invalid view type: panel.id = " + panel.id + ", view.type = " + view.type;
}
if (!view.itemHandler) {
// Use BROWSER item handler by default
view.itemHandler = this.ItemHandler.BROWSER;
} else if (!this._valueExists(this.ItemHandler, view.itemHandler)) {
throw "Home.panels: Invalid item handler: panel.id = " + panel.id + ", view.itemHandler = " + view.itemHandler;
}
if (!view.dataset) {
throw "Home.panels: No dataset provided for view: panel.id = " + panel.id + ", view.type = " + view.type;
}
}
this._panels[panel.id] = panel;
if (action) {
let messageType;
switch(action) {
case this.Action.INSTALL:
messageType = "HomePanels:Install";
break;
case this.Action.REFRESH:
messageType = "HomePanels:Refresh";
break;
default:
throw "Home.panels: Invalid action for panel: panel.id = " + panel.id + ", action = " + action;
}
sendMessageToJava({
type: messageType,
panel: this._panelToJSON(panel)
});
}
},
remove: function(id) {
if (!(id in this._panels)) {
throw "Home.panels: Panel doesn't exist: id = " + id;
}
let panel = this._panels[id];
delete this._panels[id];
sendMessageToJava({
type: "HomePanels:Remove",
panel: this._panelToJSON(panel)
});
},
};
// Helper function used to see if a value is in an object.
_valueExists: function(obj, value) {
let _valueExists = function(obj, value) {
for (let key in obj) {
if (obj[key] == value) {
return true;
}
}
return false;
}
});
};
return Object.freeze({
// Valid layouts for a panel.
Layout: Object.freeze({
FRAME: "frame"
}),
// Valid types of views for a dataset.
View: Object.freeze({
LIST: "list",
GRID: "grid"
}),
// Valid actions for a panel.
Action: Object.freeze({
INSTALL: "install",
REFRESH: "refresh"
}),
// Valid item handlers for a panel view.
ItemHandler: Object.freeze({
BROWSER: "browser",
INTENT: "intent"
}),
add: function(options) {
let panel = new Panel(options);
if (!panel.id || !panel.title) {
throw "Home.panels: Can't create a home panel without an id and title!";
}
let action = options.action;
// Bail if the panel already exists, except when we're refreshing
// an existing panel instance.
if (panel.id in _panels && action != this.Action.REFRESH) {
throw "Home.panels: Panel already exists: id = " + panel.id;
}
if (!_valueExists(this.Layout, panel.layout)) {
throw "Home.panels: Invalid layout for panel: panel.id = " + panel.id + ", panel.layout =" + panel.layout;
}
for (let view of panel.views) {
if (!_valueExists(this.View, view.type)) {
throw "Home.panels: Invalid view type: panel.id = " + panel.id + ", view.type = " + view.type;
}
if (!view.itemHandler) {
// Use BROWSER item handler by default
view.itemHandler = this.ItemHandler.BROWSER;
} else if (!_valueExists(this.ItemHandler, view.itemHandler)) {
throw "Home.panels: Invalid item handler: panel.id = " + panel.id + ", view.itemHandler = " + view.itemHandler;
}
if (!view.dataset) {
throw "Home.panels: No dataset provided for view: panel.id = " + panel.id + ", view.type = " + view.type;
}
}
_panels[panel.id] = panel;
if (action) {
let messageType;
switch(action) {
case this.Action.INSTALL:
messageType = "HomePanels:Install";
break;
case this.Action.REFRESH:
messageType = "HomePanels:Refresh";
break;
default:
throw "Home.panels: Invalid action for panel: panel.id = " + panel.id + ", action = " + action;
}
sendMessageToJava({
type: messageType,
panel: _panelToJSON(panel)
});
}
},
remove: function(id) {
if (!(id in _panels)) {
throw "Home.panels: Panel doesn't exist: id = " + id;
}
let panel = _panels[id];
delete _panels[id];
sendMessageToJava({
type: "HomePanels:Remove",
panel: _panelToJSON(panel)
});
}
});
})();
// Public API
this.Home = Object.freeze({

View File

@ -0,0 +1,41 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict"
const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
this.EXPORTED_SYMBOLS = ["sendMessageToJava"];
XPCOMUtils.defineLazyServiceGetter(this, "uuidgen",
"@mozilla.org/uuid-generator;1",
"nsIUUIDGenerator");
function sendMessageToJava(aMessage, aCallback) {
if (aCallback) {
let id = uuidgen.generateUUID().toString();
let obs = {
observe: function(aSubject, aTopic, aData) {
let data = JSON.parse(aData);
if (data.__guid__ != id) {
return;
}
Services.obs.removeObserver(obs, aMessage.type + ":Return", false);
Services.obs.removeObserver(obs, aMessage.type + ":Error", false);
aCallback(aTopic == aMessage.type + ":Return" ? aData : null,
aTopic == aMessage.type + ":Error" ? aData : null)
}
}
aMessage.__guid__ = id;
Services.obs.addObserver(obs, aMessage.type + ":Return", false);
Services.obs.addObserver(obs, aMessage.type + ":Error", false);
}
return Services.androidBridge.handleGeckoMessage(JSON.stringify(aMessage));
}

View File

@ -10,10 +10,7 @@ const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
// For adding observers.
Cu.import("resource://gre/modules/Services.jsm");
function sendMessageToJava(message) {
return Services.androidBridge.handleGeckoMessage(JSON.stringify(message));
}
Cu.import("resource://gre/modules/Messaging.jsm");
let _callbackId = 1;

View File

@ -7,6 +7,7 @@ let Cc = Components.classes;
let Ci = Components.interfaces;
Components.utils.import("resource://gre/modules/Services.jsm");
Components.utils.import("resource://gre/modules/Messaging.jsm");
this.EXPORTED_SYMBOLS = ["Prompt"];
@ -36,8 +37,6 @@ function Prompt(aOptions) {
this.msg.hint = aOptions.hint;
let idService = Cc["@mozilla.org/uuid-generator;1"].getService(Ci.nsIUUIDGenerator);
this.guid = idService.generateUUID().toString();
this.msg.guid = this.guid;
}
Prompt.prototype = {
@ -152,24 +151,20 @@ Prompt.prototype = {
show: function(callback) {
this.callback = callback;
log("Sending message");
Services.obs.addObserver(this, "Prompt:Reply", false);
Services.obs.addObserver(this, "Prompt:Return", false);
this._innerShow();
},
_innerShow: function() {
Services.androidBridge.handleGeckoMessage(JSON.stringify(this.msg));
},
sendMessageToJava(this.msg, (aData) => {
log("observe " + aData);
let data = JSON.parse(aData);
observe: function(aSubject, aTopic, aData) {
log("observe " + aData);
let data = JSON.parse(aData);
if (data.guid != this.guid)
return;
Services.obs.removeObserver(this, "Prompt:Return", false);
Services.obs.removeObserver(this, "Prompt:Reply", false);
if (this.callback)
this.callback(data);
if (this.callback)
this.callback(data);
});
},
_setListItems: function(aItems) {

View File

@ -11,15 +11,12 @@ Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/LoadContextInfo.jsm");
Cu.import("resource://gre/modules/FormHistory.jsm");
Cu.import("resource://gre/modules/Messaging.jsm");
function dump(a) {
Services.console.logStringMessage(a);
}
function sendMessageToJava(aMessage) {
return Services.androidBridge.handleGeckoMessage(JSON.stringify(aMessage));
}
this.EXPORTED_SYMBOLS = ["Sanitizer"];
let downloads = {

View File

@ -11,10 +11,7 @@ const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
// For adding observers.
Cu.import("resource://gre/modules/Services.jsm");
function sendMessageToJava(message) {
return Services.androidBridge.handleGeckoMessage(JSON.stringify(message));
}
Cu.import("resource://gre/modules/Messaging.jsm");
/**
* Create an interface to an Android SharedPreferences branch.
@ -62,13 +59,21 @@ SharedPreferences.prototype = Object.freeze({
this._setOne(prefName, value, "int");
},
_get: function _get(prefs) {
let values = sendMessageToJava({
_get: function _get(prefs, callback) {
let result = null;
sendMessageToJava({
type: "SharedPreferences:Get",
preferences: prefs,
branch: this._branch,
}, (data) => {
result = JSON.parse(data).values;
});
return JSON.parse(values);
let thread = Services.tm.currentThread;
while (result == null)
thread.processNextEvent(true);
return result;
},
_getOne: function _getOne(prefName, type) {

View File

@ -284,10 +284,9 @@ this.WebappManager = {
}
// Map APK names to APK versions.
let apkNameToVersion = JSON.parse(sendMessageToJava({
type: "Webapps:GetApkVersions",
packageNames: installedApps.map(app => app.packageName).filter(packageName => !!packageName)
}));
let apkNameToVersion = yield this._getAPKVersions(installedApps.map(app =>
app.packageName).filter(packageName => !!packageName)
);
// Map manifest URLs to APK versions, which is what the service needs
// in order to tell us which apps are outdated; and also map them to app
@ -336,6 +335,17 @@ this.WebappManager = {
}
}).bind(this)); },
_getAPKVersions: function(packageNames) {
let deferred = Promise.defer();
sendMessageToJava({
type: "Webapps:GetApkVersions",
packageNames: packageNames
}, data => deferred.resolve(JSON.parse(data).versions));
return deferred.promise;
},
_getInstalledApps: function() {
let deferred = Promise.defer();
DOMApplicationRegistry.getAll(apps => deferred.resolve(apps));

View File

@ -11,6 +11,7 @@ EXTRA_JS_MODULES += [
'HomeProvider.jsm',
'JNI.jsm',
'LightweightThemeConsumer.jsm',
'Messaging.jsm',
'Notifications.jsm',
'OrderedBroadcast.jsm',
'Prompt.jsm',

View File

@ -305,25 +305,6 @@ Java_org_mozilla_gecko_GeckoAppShell_cameraCallbackBridge(JNIEnv * arg0, jclass
#ifdef JNI_STUBS
typedef void (*Java_org_mozilla_gecko_GeckoAppShell_notifyFilePickerResult_t)(JNIEnv *, jclass, jstring, jlong);
static Java_org_mozilla_gecko_GeckoAppShell_notifyFilePickerResult_t f_Java_org_mozilla_gecko_GeckoAppShell_notifyFilePickerResult;
extern "C" NS_EXPORT void JNICALL
Java_org_mozilla_gecko_GeckoAppShell_notifyFilePickerResult(JNIEnv * arg0, jclass arg1, jstring arg2, jlong arg3) {
if (!f_Java_org_mozilla_gecko_GeckoAppShell_notifyFilePickerResult) {
arg0->ThrowNew(arg0->FindClass("java/lang/UnsupportedOperationException"),
"JNI Function called before it was loaded");
return ;
}
f_Java_org_mozilla_gecko_GeckoAppShell_notifyFilePickerResult(arg0, arg1, arg2, arg3);
}
#endif
#ifdef JNI_BINDINGS
xul_dlsym("Java_org_mozilla_gecko_GeckoAppShell_notifyFilePickerResult", &f_Java_org_mozilla_gecko_GeckoAppShell_notifyFilePickerResult);
#endif
#ifdef JNI_STUBS
typedef jdouble (*Java_org_mozilla_gecko_GeckoJavaSampler_getProfilerTime_t)(JNIEnv *, jclass);
static Java_org_mozilla_gecko_GeckoJavaSampler_getProfilerTime_t f_Java_org_mozilla_gecko_GeckoJavaSampler_getProfilerTime;
extern "C" NS_EXPORT jdouble JNICALL

View File

@ -51,6 +51,10 @@
oncommand="document.getElementById('FindToolbar').onFindAgainCommand(false);"/>
<command id="cmd_findPrevious"
oncommand="document.getElementById('FindToolbar').onFindAgainCommand(true);"/>
#ifdef XP_MACOSX
<command id="cmd_findSelection"
oncommand="document.getElementById('FindToolbar').onFindSelectionCommand();"/>
#endif
<command id="cmd_reload" oncommand="ViewSourceReload();"/>
<command id="cmd_goToLine" oncommand="ViewSourceGoToLine();" disabled="true"/>
<command id="cmd_highlightSyntax" oncommand="highlightSyntax();"/>
@ -86,6 +90,9 @@
<key id="key_find" key="&findOnCmd.commandkey;" command="cmd_find" modifiers="accel"/>
<key id="key_findAgain" key="&findAgainCmd.commandkey;" command="cmd_findAgain" modifiers="accel"/>
<key id="key_findPrevious" key="&findAgainCmd.commandkey;" command="cmd_findPrevious" modifiers="accel,shift"/>
#ifdef XP_MACOSX
<key id="key_findSelection" key="&findSelectionCmd.commandkey;" command="cmd_findSelection" modifiers="accel"/>
#endif
<key keycode="&findAgainCmd.commandkey2;" command="cmd_findAgain"/>
<key keycode="&findAgainCmd.commandkey2;" command="cmd_findPrevious" modifiers="shift"/>

View File

@ -58,6 +58,7 @@
// Simulate typical input
textbox.focus();
gFindBar.clear();
sendChar("m");
ok(gFindBar.canClear, "canClear property true after input");
let preSelection = gBrowser.contentWindow.getSelection();

View File

@ -30,6 +30,9 @@
var gFindBar = null;
var gBrowser;
var gClipboard = Cc["@mozilla.org/widget/clipboard;1"].getService("nsIClipboard");
var gHasFindClipboard = gClipboard.supportsFindClipboard();
var gStatusText;
var gXULBrowserWindow = {
QueryInterface: function(aIID) {
@ -94,7 +97,8 @@
testFindbarSelection();
testDrop();
testQuickFindLink();
testStatusText();
if (gHasFindClipboard)
testStatusText();
testQuickFindClose();
}
@ -104,8 +108,12 @@
ok(!gFindBar.hidden, "testFindbarSelection: failed to open findbar: " + aTestName);
ok(document.commandDispatcher.focusedElement == gFindBar._findField.inputField,
"testFindbarSelection: find field is not focused: " + aTestName);
ok(gFindBar._findField.value == aExpSelection,
"Incorrect selection in testFindbarSelection: " + aTestName + ". Selection: " + gFindBar._findField.value);
if (gHasFindClipboard) {
ok(gFindBar._findField.value == aExpSelection,
"Incorrect selection in testFindbarSelection: " + aTestName +
". Selection: " + gFindBar._findField.value);
}
testClipboardSearchString(aExpSelection);
// Clear the value, close the findbar
gFindBar._findField.value = "";
@ -185,12 +193,14 @@
enterStringIntoFindField(searchStr);
ok(gBrowser.contentWindow.getSelection().toString().toLowerCase() == searchStr,
"testNormalFind: failed to find '" + searchStr + "'");
testClipboardSearchString(gBrowser.contentWindow.getSelection().toString());
if (!matchCaseCheckbox.hidden) {
matchCaseCheckbox.click();
enterStringIntoFindField("t");
ok(gBrowser.contentWindow.getSelection() != searchStr,
"testNormalFind: Case-sensitivy is broken '" + searchStr + "'");
testClipboardSearchString(gBrowser.contentWindow.getSelection().toString());
matchCaseCheckbox.click();
}
}
@ -228,6 +238,7 @@
ok(gBrowser.contentWindow.getSelection().toString().toLowerCase() != searchStr,
"testNormalFindWithComposition: text shouldn't be found during composition");
testClipboardSearchString(gBrowser.contentWindow.getSelection().toString());
synthesizeText(
{ "composition":
@ -243,6 +254,7 @@
ok(gBrowser.contentWindow.getSelection().toString().toLowerCase() == searchStr,
"testNormalFindWithComposition: text should be found after committing composition");
testClipboardSearchString(gBrowser.contentWindow.getSelection().toString());
if (clicked) {
matchCaseCheckbox.click();
@ -302,6 +314,7 @@
enterStringIntoFindField(searchStr);
ok(gBrowser.contentWindow.getSelection() == searchStr,
"testQuickFindLink: failed to find sample link");
testClipboardSearchString(searchStr);
}
function testQuickFindText() {
@ -319,6 +332,18 @@
enterStringIntoFindField(SEARCH_TEXT);
ok(gBrowser.contentWindow.getSelection() == SEARCH_TEXT,
"testQuickFindText: failed to find '" + SEARCH_TEXT + "'");
testClipboardSearchString(SEARCH_TEXT);
}
function testClipboardSearchString(aExpected) {
if (!gHasFindClipboard)
return;
if (!aExpected)
aExpected = "";
var searchStr = gFindBar.browser.finder.clipboardSearchString;
ok(searchStr == aExpected, "testClipboardSearchString: search string not " +
"set to '" + aExpected + "', instead found '" + searchStr + "'");
}
]]></script>

View File

@ -110,6 +110,13 @@
findbar.browser.finder.enableSelection();
]]></handler>
#ifdef XP_MACOSX
<handler event="focus"><![CDATA[
let findbar = this.findbar;
findbar._onFindFieldFocus();
]]></handler>
#endif
<handler event="compositionstart"><![CDATA[
// Don't close the find toolbar while IME is composing.
let findbar = this.findbar;
@ -1045,8 +1052,17 @@
userWantsPrefill =
prefsvc.getBoolPref("accessibility.typeaheadfind.prefillwithselection");
let initialString = (this.prefillWithSelection && userWantsPrefill) ?
this._getInitialSelection() : null;
let initialString = null;
if (this.prefillWithSelection && userWantsPrefill)
initialString = this._getInitialSelection();
#ifdef XP_MACOSX
if (!initialString) {
let clipboardSearchString = this.browser.finder.clipboardSearchString;
if (clipboardSearchString)
initialString = clipboardSearchString;
}
#endif
if (initialString)
this._findField.value = initialString;
@ -1102,6 +1118,32 @@
]]></body>
</method>
#ifdef XP_MACOSX
<!--
- Fetches the currently selected text and sets that as the text to search
- next. This is a MacOS specific feature.
-->
<method name="onFindSelectionCommand">
<body><![CDATA[
let searchString = this.browser.finder.setSearchStringToSelection();
if (searchString)
this._findField.value = searchString;
]]></body>
</method>
<method name="_onFindFieldFocus">
<body><![CDATA[
let clipboardSearchString = this._browser.finder.clipboardSearchString;
if (clipboardSearchString && this._findField.value != clipboardSearchString) {
this._findField.value = clipboardSearchString;
// Changing the search string makes the previous status invalid, so
// we better clear it here.
this._updateStatusUI();
}
]]></body>
</method>
#endif
<!--
- This handles all the result changes for both
- type-ahead-find and highlighting.
@ -1119,6 +1161,8 @@
<method name="onFindResult">
<parameter name="aData"/>
<body><![CDATA[
if (this._findField.value != this.browser.finder.searchString)
this._findField.value = this.browser.finder.searchString;
this._updateStatusUI(aData.result, aData.findBackwards);
this._updateStatusUIBar(aData.linkURL);

View File

@ -71,6 +71,7 @@ you can use these alternative items. Otherwise, their values should be empty. -
<!ENTITY findAgainCmd.accesskey "g">
<!ENTITY findAgainCmd.commandkey "g">
<!ENTITY findAgainCmd.commandkey2 "VK_F3">
<!ENTITY findSelectionCmd.commandkey "e">
<!ENTITY backCmd.label "Back">
<!ENTITY backCmd.accesskey "B">

View File

@ -5,11 +5,6 @@
/**
* Managing safe shutdown of asynchronous services.
*
* THIS API IS EXPERIMENTAL AND SUBJECT TO CHANGE WITHOUT PRIOR NOTICE
* IF YOUR CODE USES IT, IT MAY HAVE STOPPED WORKING ALREADY
* YOU HAVE BEEN WARNED
*
*
* Firefox shutdown is composed of phases that take place
* sequentially. Typically, each shutdown phase removes some
* capabilities from the application. For instance, at the end of

View File

@ -12,6 +12,16 @@ Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Geometry.jsm");
Cu.import("resource://gre/modules/Services.jsm");
XPCOMUtils.defineLazyServiceGetter(this, "TextToSubURIService",
"@mozilla.org/intl/texttosuburi;1",
"nsITextToSubURI");
XPCOMUtils.defineLazyServiceGetter(this, "Clipboard",
"@mozilla.org/widget/clipboard;1",
"nsIClipboard");
XPCOMUtils.defineLazyServiceGetter(this, "ClipboardHelper",
"@mozilla.org/widget/clipboardhelper;1",
"nsIClipboardHelper");
function Finder(docShell) {
this._fastFind = Cc["@mozilla.org/typeaheadfind;1"].createInstance(Ci.nsITypeAheadFind);
this._fastFind.init(docShell);
@ -38,6 +48,7 @@ Finder.prototype = {
_notify: function (aSearchString, aResult, aFindBackwards, aDrawOutline) {
this._searchString = aSearchString;
this.clipboardSearchString = aSearchString
this._outlineLink(aDrawOutline);
let foundLink = this._fastFind.foundLink;
@ -48,12 +59,7 @@ Finder.prototype = {
if (ownerDoc)
docCharset = ownerDoc.characterSet;
if (!this._textToSubURIService) {
this._textToSubURIService = Cc["@mozilla.org/intl/texttosuburi;1"]
.getService(Ci.nsITextToSubURI);
}
linkURL = this._textToSubURIService.unEscapeURIForUI(docCharset, foundLink.href);
linkURL = TextToSubURIService.unEscapeURIForUI(docCharset, foundLink.href);
}
let data = {
@ -70,9 +76,48 @@ Finder.prototype = {
},
get searchString() {
if (!this._searchString && this._fastFind.searchString)
this._searchString = this._fastFind.searchString;
return this._searchString;
},
get clipboardSearchString() {
let searchString = "";
if (!Clipboard.supportsFindClipboard())
return searchString;
try {
let trans = Cc["@mozilla.org/widget/transferable;1"]
.createInstance(Ci.nsITransferable);
trans.init(this._getWindow()
.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIWebNavigation)
.QueryInterface(Ci.nsILoadContext));
trans.addDataFlavor("text/unicode");
Clipboard.getData(trans, Ci.nsIClipboard.kFindClipboard);
let data = {};
let dataLen = {};
trans.getTransferData("text/unicode", data, dataLen);
if (data.value) {
data = data.value.QueryInterface(Ci.nsISupportsString);
searchString = data.toString();
}
} catch (ex) {}
return searchString;
},
set clipboardSearchString(aSearchString) {
if (!aSearchString || !Clipboard.supportsFindClipboard())
return;
ClipboardHelper.copyStringToClipboard(aSearchString,
Ci.nsIClipboard.kFindClipboard,
this._getWindow().document);
},
set caseSensitive(aSensitive) {
this._fastFind.caseSensitive = aSensitive;
},
@ -105,6 +150,25 @@ Finder.prototype = {
this._notify(searchString, result, aFindBackwards, aDrawOutline);
},
/**
* Forcibly set the search string of the find clipboard to the currently
* selected text in the window, on supported platforms (i.e. OSX).
*/
setSearchStringToSelection: function() {
// Find the selected text.
let selection = this._getWindow().getSelection();
// Don't go for empty selections.
if (!selection.rangeCount)
return null;
let searchString = (selection.toString() || "").trim();
// Empty strings are rather useless to search for.
if (!searchString.length)
return null;
this.clipboardSearchString = searchString;
return searchString;
},
highlight: function (aHighlight, aWord) {
let found = this._highlight(aHighlight, aWord, null);
if (aHighlight) {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 403 B

After

Width:  |  Height:  |  Size: 117 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 285 B

After

Width:  |  Height:  |  Size: 267 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 382 B

After

Width:  |  Height:  |  Size: 133 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 366 B

After

Width:  |  Height:  |  Size: 227 B

View File

@ -56,71 +56,69 @@ panel[type="arrow"] {
panel[type="arrow"][side="top"],
panel[type="arrow"][side="bottom"] {
margin-left: -26px;
margin-right: -26px;
margin-left: -25px;
margin-right: -25px;
}
panel[type="arrow"][side="left"],
panel[type="arrow"][side="right"] {
margin-top: -26px;
margin-bottom: -26px;
margin-top: -25px;
margin-bottom: -25px;
}
.panel-arrowcontent {
-moz-appearance: none;
background: #f5f5f5;
border-radius: 5px;
box-shadow: 0 0 0 1px rgba(255,255,255,.5) inset,
0 1px 0 rgba(255,255,255,.05) inset,
0 0 0 1px rgba(0,0,0,.25);
color: #333;
background-image: linear-gradient(hsla(0,0%,99%,1), hsla(0,0%,99%,.975) 10%, hsla(0,0%,98%,.975));
border-radius: 3.5px;
box-shadow: 0 0 0 1px hsla(210,4%,10%,.05);
color: hsl(0,0%,10%);
padding: 16px;
margin: 1px;
}
.panel-arrow[side="top"] {
list-style-image: url("chrome://global/skin/arrow/panelarrow-vertical.png");
margin-left: 6px;
margin-right: 6px;
margin-bottom: -2px;
margin-left: 16px;
margin-right: 16px;
margin-bottom: -1px;
}
.panel-arrow[side="bottom"] {
list-style-image: url("chrome://global/skin/arrow/panelarrow-vertical.png");
-moz-transform: scaleY(-1);
margin-left: 6px;
margin-right: 6px;
margin-top: -2px;
margin-left: 16px;
margin-right: 16px;
margin-top: -1px;
}
.panel-arrow[side="left"] {
list-style-image: url("chrome://global/skin/arrow/panelarrow-horizontal.png");
margin-top: 6px;
margin-bottom: 6px;
margin-right: -2px;
margin-top: 16px;
margin-bottom: 16px;
margin-right: -1px;
}
.panel-arrow[side="right"] {
list-style-image: url("chrome://global/skin/arrow/panelarrow-horizontal.png");
transform: scaleX(-1);
margin-top: 6px;
margin-bottom: 6px;
margin-left: -2px;
margin-top: 16px;
margin-bottom: 16px;
margin-left: -1px;
}
@media (min-resolution: 2dppx) {
.panel-arrow[side="top"],
.panel-arrow[side="bottom"] {
list-style-image: url("chrome://global/skin/arrow/panelarrow-vertical@2x.png");
width: 39px;
height: 16px;
width: 18px;
height: 10px;
}
.panel-arrow[side="left"],
.panel-arrow[side="right"] {
list-style-image: url("chrome://global/skin/arrow/panelarrow-horizontal@2x.png");
width: 16px;
height: 39px;
width: 10px;
height: 18px;
}
}

View File

@ -159,6 +159,6 @@ XXX: apply styles to all themes until bug 509642 is fixed
%endif
.popup-notification-closebutton {
-moz-margin-end: -14px;
margin-top: -10px;
-moz-margin-end: -8px;
margin-top: -4px;
}

View File

@ -42,8 +42,6 @@ using namespace mozilla;
using namespace mozilla::widget::android;
using namespace mozilla::gfx;
NS_IMPL_ISUPPORTS0(nsFilePickerCallback)
StaticRefPtr<AndroidBridge> AndroidBridge::sBridge;
static unsigned sJavaEnvThreadIndex = 0;
static jobject sGlobalContext = nullptr;
@ -486,41 +484,6 @@ AndroidBridge::GetScreenDepth()
return sDepth;
}
void
AndroidBridge::ShowFilePickerForExtensions(nsAString& aFilePath, const nsAString& aExtensions)
{
JNIEnv *env = GetJNIEnv();
AutoLocalJNIFrame jniFrame(env, 1);
jstring jstr = GeckoAppShell::ShowFilePickerForExtensionsWrapper(aExtensions);
if (jstr == nullptr) {
return;
}
aFilePath.Assign(nsJNIString(jstr, env));
}
void
AndroidBridge::ShowFilePickerForMimeType(nsAString& aFilePath, const nsAString& aMimeType)
{
JNIEnv *env = GetJNIEnv();
AutoLocalJNIFrame jniFrame(env, 1);
jstring jstr = GeckoAppShell::ShowFilePickerForMimeTypeWrapper(aMimeType);
if (jstr == nullptr) {
return;
}
aFilePath.Assign(nsJNIString(jstr, env));
}
void
AndroidBridge::ShowFilePickerAsync(const nsAString& aMimeType, nsFilePickerCallback* callback)
{
callback->AddRef();
GeckoAppShell::ShowFilePickerAsyncWrapper(aMimeType, (int64_t) callback);
}
void
AndroidBridge::Vibrate(const nsTArray<uint32_t>& aPattern)
{
@ -999,21 +962,14 @@ AndroidBridge::GetCurrentBatteryInformation(hal::BatteryInformation* aBatteryInf
}
void
AndroidBridge::HandleGeckoMessage(const nsAString &aMessage, nsAString &aRet)
AndroidBridge::HandleGeckoMessage(const nsAString &aMessage)
{
ALOG_BRIDGE("%s", __PRETTY_FUNCTION__);
JNIEnv *env = GetJNIEnv();
AutoLocalJNIFrame jniFrame(env, 1);
jstring returnMessage = GeckoAppShell::HandleGeckoMessageWrapper(aMessage);
if (!returnMessage)
return;
nsJNIString jniStr(returnMessage, env);
aRet.Assign(jniStr);
ALOG_BRIDGE("leaving %s", __PRETTY_FUNCTION__);
GeckoAppShell::HandleGeckoMessageWrapper(aMessage);
}
nsresult
@ -1534,9 +1490,9 @@ nsAndroidBridge::~nsAndroidBridge()
}
/* void handleGeckoEvent (in AString message); */
NS_IMETHODIMP nsAndroidBridge::HandleGeckoMessage(const nsAString & message, nsAString &aRet)
NS_IMETHODIMP nsAndroidBridge::HandleGeckoMessage(const nsAString & message)
{
AndroidBridge::Bridge()->HandleGeckoMessage(message, aRet);
AndroidBridge::Bridge()->HandleGeckoMessage(message);
return NS_OK;
}

View File

@ -282,7 +282,7 @@ public:
bool LockWindow(void *window, unsigned char **bits, int *width, int *height, int *format, int *stride);
bool UnlockWindow(void *window);
void HandleGeckoMessage(const nsAString& message, nsAString &aRet);
void HandleGeckoMessage(const nsAString& message);
bool InitCamera(const nsCString& contentType, uint32_t camera, uint32_t *width, uint32_t *height, uint32_t *fps);

View File

@ -81,9 +81,6 @@ jmethodID GeckoAppShell::jSetFullScreen = 0;
jmethodID GeckoAppShell::jSetKeepScreenOn = 0;
jmethodID GeckoAppShell::jSetURITitle = 0;
jmethodID GeckoAppShell::jShowAlertNotificationWrapper = 0;
jmethodID GeckoAppShell::jShowFilePickerAsyncWrapper = 0;
jmethodID GeckoAppShell::jShowFilePickerForExtensionsWrapper = 0;
jmethodID GeckoAppShell::jShowFilePickerForMimeTypeWrapper = 0;
jmethodID GeckoAppShell::jShowInputMethodPicker = 0;
jmethodID GeckoAppShell::jUnlockProfile = 0;
jmethodID GeckoAppShell::jUnlockScreenOrientation = 0;
@ -132,7 +129,7 @@ void GeckoAppShell::InitStubs(JNIEnv *jEnv) {
jGetScreenOrientationWrapper = getStaticMethod("getScreenOrientation", "()S");
jGetShowPasswordSetting = getStaticMethod("getShowPasswordSetting", "()Z");
jGetSystemColoursWrapper = getStaticMethod("getSystemColors", "()[I");
jHandleGeckoMessageWrapper = getStaticMethod("handleGeckoMessage", "(Ljava/lang/String;)Ljava/lang/String;");
jHandleGeckoMessageWrapper = getStaticMethod("handleGeckoMessage", "(Ljava/lang/String;)V");
jHandleUncaughtException = getStaticMethod("handleUncaughtException", "(Ljava/lang/Thread;Ljava/lang/Throwable;)V");
jHideProgressDialog = getStaticMethod("hideProgressDialog", "()V");
jInitCameraWrapper = getStaticMethod("initCamera", "(Ljava/lang/String;III)[I");
@ -163,9 +160,6 @@ void GeckoAppShell::InitStubs(JNIEnv *jEnv) {
jSetKeepScreenOn = getStaticMethod("setKeepScreenOn", "(Z)V");
jSetURITitle = getStaticMethod("setUriTitle", "(Ljava/lang/String;Ljava/lang/String;)V");
jShowAlertNotificationWrapper = getStaticMethod("showAlertNotification", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V");
jShowFilePickerAsyncWrapper = getStaticMethod("showFilePickerAsync", "(Ljava/lang/String;J)V");
jShowFilePickerForExtensionsWrapper = getStaticMethod("showFilePickerForExtensions", "(Ljava/lang/String;)Ljava/lang/String;");
jShowFilePickerForMimeTypeWrapper = getStaticMethod("showFilePickerForMimeType", "(Ljava/lang/String;)Ljava/lang/String;");
jShowInputMethodPicker = getStaticMethod("showInputMethodPicker", "()V");
jUnlockProfile = getStaticMethod("unlockProfile", "()Z");
jUnlockScreenOrientation = getStaticMethod("unlockScreenOrientation", "()V");
@ -703,19 +697,18 @@ jintArray GeckoAppShell::GetSystemColoursWrapper() {
return ret;
}
jstring GeckoAppShell::HandleGeckoMessageWrapper(const nsAString& a0) {
void GeckoAppShell::HandleGeckoMessageWrapper(const nsAString& a0) {
JNIEnv *env = AndroidBridge::GetJNIEnv();
if (env->PushLocalFrame(2) != 0) {
if (env->PushLocalFrame(1) != 0) {
AndroidBridge::HandleUncaughtException(env);
MOZ_ASSUME_UNREACHABLE("Exception should have caused crash.");
}
jstring j0 = AndroidBridge::NewJavaString(env, a0);
jobject temp = env->CallStaticObjectMethod(mGeckoAppShellClass, jHandleGeckoMessageWrapper, j0);
env->CallStaticVoidMethod(mGeckoAppShellClass, jHandleGeckoMessageWrapper, j0);
AndroidBridge::HandleUncaughtException(env);
jstring ret = static_cast<jstring>(env->PopLocalFrame(temp));
return ret;
env->PopLocalFrame(nullptr);
}
void GeckoAppShell::HandleUncaughtException(jobject a0, jthrowable a1) {
@ -1136,50 +1129,6 @@ void GeckoAppShell::ShowAlertNotificationWrapper(const nsAString& a0, const nsAS
env->PopLocalFrame(nullptr);
}
void GeckoAppShell::ShowFilePickerAsyncWrapper(const nsAString& a0, int64_t a1) {
JNIEnv *env = AndroidBridge::GetJNIEnv();
if (env->PushLocalFrame(1) != 0) {
AndroidBridge::HandleUncaughtException(env);
MOZ_ASSUME_UNREACHABLE("Exception should have caused crash.");
}
jstring j0 = AndroidBridge::NewJavaString(env, a0);
env->CallStaticVoidMethod(mGeckoAppShellClass, jShowFilePickerAsyncWrapper, j0, a1);
AndroidBridge::HandleUncaughtException(env);
env->PopLocalFrame(nullptr);
}
jstring GeckoAppShell::ShowFilePickerForExtensionsWrapper(const nsAString& a0) {
JNIEnv *env = AndroidBridge::GetJNIEnv();
if (env->PushLocalFrame(2) != 0) {
AndroidBridge::HandleUncaughtException(env);
MOZ_ASSUME_UNREACHABLE("Exception should have caused crash.");
}
jstring j0 = AndroidBridge::NewJavaString(env, a0);
jobject temp = env->CallStaticObjectMethod(mGeckoAppShellClass, jShowFilePickerForExtensionsWrapper, j0);
AndroidBridge::HandleUncaughtException(env);
jstring ret = static_cast<jstring>(env->PopLocalFrame(temp));
return ret;
}
jstring GeckoAppShell::ShowFilePickerForMimeTypeWrapper(const nsAString& a0) {
JNIEnv *env = AndroidBridge::GetJNIEnv();
if (env->PushLocalFrame(2) != 0) {
AndroidBridge::HandleUncaughtException(env);
MOZ_ASSUME_UNREACHABLE("Exception should have caused crash.");
}
jstring j0 = AndroidBridge::NewJavaString(env, a0);
jobject temp = env->CallStaticObjectMethod(mGeckoAppShellClass, jShowFilePickerForMimeTypeWrapper, j0);
AndroidBridge::HandleUncaughtException(env);
jstring ret = static_cast<jstring>(env->PopLocalFrame(temp));
return ret;
}
void GeckoAppShell::ShowInputMethodPicker() {
JNIEnv *env = AndroidBridge::GetJNIEnv();
if (env->PushLocalFrame(0) != 0) {

View File

@ -57,7 +57,7 @@ public:
static int16_t GetScreenOrientationWrapper();
static bool GetShowPasswordSetting();
static jintArray GetSystemColoursWrapper();
static jstring HandleGeckoMessageWrapper(const nsAString& a0);
static void HandleGeckoMessageWrapper(const nsAString& a0);
static void HandleUncaughtException(jobject a0, jthrowable a1);
static void HideProgressDialog();
static jintArray InitCameraWrapper(const nsAString& a0, int32_t a1, int32_t a2, int32_t a3);
@ -88,9 +88,6 @@ public:
static void SetKeepScreenOn(bool a0);
static void SetURITitle(const nsAString& a0, const nsAString& a1);
static void ShowAlertNotificationWrapper(const nsAString& a0, const nsAString& a1, const nsAString& a2, const nsAString& a3, const nsAString& a4);
static void ShowFilePickerAsyncWrapper(const nsAString& a0, int64_t a1);
static jstring ShowFilePickerForExtensionsWrapper(const nsAString& a0);
static jstring ShowFilePickerForMimeTypeWrapper(const nsAString& a0);
static void ShowInputMethodPicker();
static bool UnlockProfile();
static void UnlockScreenOrientation();
@ -169,9 +166,6 @@ protected:
static jmethodID jSetKeepScreenOn;
static jmethodID jSetURITitle;
static jmethodID jShowAlertNotificationWrapper;
static jmethodID jShowFilePickerAsyncWrapper;
static jmethodID jShowFilePickerForExtensionsWrapper;
static jmethodID jShowFilePickerForMimeTypeWrapper;
static jmethodID jShowInputMethodPicker;
static jmethodID jUnlockProfile;
static jmethodID jUnlockScreenOrientation;

View File

@ -109,3 +109,9 @@ nsClipboard::SupportsSelectionClipboard(bool *aIsSupported)
return NS_OK;
}
NS_IMETHODIMP
nsClipboard::SupportsFindClipboard(bool* _retval)
{
*_retval = false;
return NS_OK;
}

View File

@ -63,10 +63,10 @@ interface nsIAndroidDisplayport : nsISupports {
attribute float resolution;
};
[scriptable, uuid(5aa0cfa5-377c-4f5e-8dcf-59ebd9482d65)]
[scriptable, uuid(2af84552-4f74-480d-b637-ce2bcd7148fc)]
interface nsIAndroidBridge : nsISupports
{
AString handleGeckoMessage(in AString message);
void handleGeckoMessage(in AString message);
attribute nsIAndroidBrowserApp browserApp;
nsIAndroidDisplayport getDisplayPort(in boolean aPageSizeUpdate, in boolean isBrowserContentDisplayed, in int32_t tabId, in nsIAndroidViewport metrics);
void contentDocumentChanged();

View File

@ -23,6 +23,7 @@ public:
// nsIClipboard
NS_IMETHOD HasDataMatchingFlavors(const char** aFlavorList, uint32_t aLength,
int32_t aWhichClipboard, bool *_retval);
NS_IMETHOD SupportsFindClipboard(bool *_retval);
// Helper methods, used also by nsDragService
static NSDictionary* PasteboardDictFromTransferable(nsITransferable *aTransferable);
@ -37,7 +38,12 @@ protected:
NS_IMETHOD GetNativeClipboardData(nsITransferable * aTransferable, int32_t aWhichClipboard);
private:
int mChangeCount; // this is always set to the native change count after any clipboard modifications
// This is always set to the native change count after any modification of the
// general clipboard.
int mChangeCountGeneral;
// This is always set to the native change count after any modification of the
// find clipboard.
int mChangeCountFind;
};

View File

@ -35,7 +35,8 @@ extern void EnsureLogInitialized();
nsClipboard::nsClipboard() : nsBaseClipboard()
{
mChangeCount = 0;
mChangeCountGeneral = 0;
mChangeCountFind = 0;
EnsureLogInitialized();
}
@ -65,7 +66,7 @@ nsClipboard::SetNativeClipboardData(int32_t aWhichClipboard)
{
NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
if ((aWhichClipboard != kGlobalClipboard) || !mTransferable)
if ((aWhichClipboard != kGlobalClipboard && aWhichClipboard != kFindClipboard) || !mTransferable)
return NS_ERROR_FAILURE;
mIgnoreEmptyNotification = true;
@ -74,28 +75,44 @@ nsClipboard::SetNativeClipboardData(int32_t aWhichClipboard)
if (!pasteboardOutputDict)
return NS_ERROR_FAILURE;
// write everything out to the general pasteboard
unsigned int outputCount = [pasteboardOutputDict count];
NSArray* outputKeys = [pasteboardOutputDict allKeys];
NSPasteboard* generalPBoard = [NSPasteboard generalPasteboard];
[generalPBoard declareTypes:outputKeys owner:nil];
NSPasteboard* cocoaPasteboard;
if (aWhichClipboard == kFindClipboard) {
cocoaPasteboard = [NSPasteboard pasteboardWithName:NSFindPboard];
[cocoaPasteboard declareTypes:[NSArray arrayWithObject:NSStringPboardType] owner:nil];
} else {
// Write everything else out to the general pasteboard.
cocoaPasteboard = [NSPasteboard generalPasteboard];
[cocoaPasteboard declareTypes:outputKeys owner:nil];
}
for (unsigned int i = 0; i < outputCount; i++) {
NSString* currentKey = [outputKeys objectAtIndex:i];
id currentValue = [pasteboardOutputDict valueForKey:currentKey];
if (currentKey == NSStringPboardType ||
currentKey == kCorePboardType_url ||
currentKey == kCorePboardType_urld ||
currentKey == kCorePboardType_urln) {
[generalPBoard setString:currentValue forType:currentKey];
} else if (currentKey == NSHTMLPboardType) {
[generalPBoard setString:(nsClipboard::WrapHtmlForSystemPasteboard(currentValue))
forType:currentKey];
if (aWhichClipboard == kFindClipboard) {
if (currentKey == NSStringPboardType)
[cocoaPasteboard setString:currentValue forType:currentKey];
} else {
[generalPBoard setData:currentValue forType:currentKey];
if (currentKey == NSStringPboardType ||
currentKey == kCorePboardType_url ||
currentKey == kCorePboardType_urld ||
currentKey == kCorePboardType_urln) {
[cocoaPasteboard setString:currentValue forType:currentKey];
} else if (currentKey == NSHTMLPboardType) {
[cocoaPasteboard setString:(nsClipboard::WrapHtmlForSystemPasteboard(currentValue))
forType:currentKey];
} else {
[cocoaPasteboard setData:currentValue forType:currentKey];
}
}
}
mChangeCount = [generalPBoard changeCount];
if (aWhichClipboard == kFindClipboard) {
mChangeCountFind = [cocoaPasteboard changeCount];
} else {
mChangeCountGeneral = [cocoaPasteboard changeCount];
}
mIgnoreEmptyNotification = false;
@ -238,10 +255,15 @@ nsClipboard::GetNativeClipboardData(nsITransferable* aTransferable, int32_t aWhi
{
NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
if ((aWhichClipboard != kGlobalClipboard) || !aTransferable)
if ((aWhichClipboard != kGlobalClipboard && aWhichClipboard != kFindClipboard) || !aTransferable)
return NS_ERROR_FAILURE;
NSPasteboard* cocoaPasteboard = [NSPasteboard generalPasteboard];
NSPasteboard* cocoaPasteboard;
if (aWhichClipboard == kFindClipboard) {
cocoaPasteboard = [NSPasteboard pasteboardWithName:NSFindPboard];
} else {
cocoaPasteboard = [NSPasteboard generalPasteboard];
}
if (!cocoaPasteboard)
return NS_ERROR_FAILURE;
@ -254,9 +276,10 @@ nsClipboard::GetNativeClipboardData(nsITransferable* aTransferable, int32_t aWhi
uint32_t flavorCount;
flavorList->Count(&flavorCount);
int changeCount = (aWhichClipboard == kFindClipboard) ? mChangeCountFind : mChangeCountGeneral;
// If we were the last ones to put something on the pasteboard, then just use the cached
// transferable. Otherwise clear it because it isn't relevant any more.
if (mChangeCount == [cocoaPasteboard changeCount]) {
if (changeCount == [cocoaPasteboard changeCount]) {
if (mTransferable) {
for (uint32_t i = 0; i < flavorCount; i++) {
nsCOMPtr<nsISupports> genericFlavor;
@ -277,9 +300,8 @@ nsClipboard::GetNativeClipboardData(nsITransferable* aTransferable, int32_t aWhi
}
}
}
}
else {
nsBaseClipboard::EmptyClipboard(kGlobalClipboard);
} else {
nsBaseClipboard::EmptyClipboard(aWhichClipboard);
}
// at this point we can't satisfy the request from cache data so let's look
@ -358,6 +380,14 @@ nsClipboard::HasDataMatchingFlavors(const char** aFlavorList, uint32_t aLength,
NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
}
NS_IMETHODIMP
nsClipboard::SupportsFindClipboard(bool *_retval)
{
NS_ENSURE_ARG_POINTER(_retval);
*_retval = true;
return NS_OK;
}
// This function converts anything that other applications might understand into the system format
// and puts it into a dictionary which it returns.
// static

View File

@ -448,6 +448,13 @@ nsClipboard::SupportsSelectionClipboard(bool *_retval)
return NS_OK;
}
NS_IMETHODIMP
nsClipboard::SupportsFindClipboard(bool* _retval)
{
*_retval = false;
return NS_OK;
}
/* static */
GdkAtom
nsClipboard::GetSelectionAtom(int32_t aWhichClipboard)

View File

@ -11,11 +11,12 @@
interface nsIArray;
[scriptable, uuid(38984945-8674-4d04-b786-5c0ca9434457)]
[scriptable, uuid(ceaa0047-647f-4b8e-ad1c-aff9fa62aa51)]
interface nsIClipboard : nsISupports
{
const long kSelectionClipboard = 0;
const long kGlobalClipboard = 1;
const long kFindClipboard = 2;
/**
* Given a transferable, set the data on the native clipboard
@ -73,6 +74,14 @@ interface nsIClipboard : nsISupports
* @result NS_OK if successful.
*/
boolean supportsSelectionClipboard ( ) ;
/**
* Allows clients to determine if the implementation supports the concept of a
* separate clipboard for find search strings.
*
* @result NS_OK if successful.
*/
boolean supportsFindClipboard ( ) ;
};

View File

@ -556,3 +556,12 @@ nsClipboard::SupportsSelectionClipboard(bool *_retval)
return NS_OK;
}
NS_IMETHODIMP
nsClipboard::SupportsFindClipboard(bool* _retval)
{
NS_ENSURE_ARG_POINTER(_retval);
*_retval = false;
return NS_OK;
}

View File

@ -22,6 +22,7 @@ nsBaseClipboard::~nsBaseClipboard()
{
EmptyClipboard(kSelectionClipboard);
EmptyClipboard(kGlobalClipboard);
EmptyClipboard(kFindClipboard);
}
NS_IMPL_ISUPPORTS1(nsBaseClipboard, nsIClipboard)
@ -39,7 +40,9 @@ NS_IMETHODIMP nsBaseClipboard::SetData(nsITransferable * aTransferable, nsIClipb
return NS_OK;
bool selectClipPresent;
SupportsSelectionClipboard(&selectClipPresent);
if ( !selectClipPresent && aWhichClipboard != kGlobalClipboard )
bool findClipPresent;
SupportsFindClipboard(&findClipPresent);
if ( !selectClipPresent && !findClipPresent && aWhichClipboard != kGlobalClipboard )
return NS_ERROR_FAILURE;
mEmptyingForSetData = true;
@ -75,10 +78,12 @@ NS_IMETHODIMP nsBaseClipboard::SetData(nsITransferable * aTransferable, nsIClipb
NS_IMETHODIMP nsBaseClipboard::GetData(nsITransferable * aTransferable, int32_t aWhichClipboard)
{
NS_ASSERTION ( aTransferable, "clipboard given a null transferable" );
bool selectClipPresent;
SupportsSelectionClipboard(&selectClipPresent);
if ( !selectClipPresent && aWhichClipboard != kGlobalClipboard )
bool findClipPresent;
SupportsFindClipboard(&findClipPresent);
if ( !selectClipPresent && !findClipPresent && aWhichClipboard != kGlobalClipboard )
return NS_ERROR_FAILURE;
if ( aTransferable )
@ -91,7 +96,9 @@ NS_IMETHODIMP nsBaseClipboard::EmptyClipboard(int32_t aWhichClipboard)
{
bool selectClipPresent;
SupportsSelectionClipboard(&selectClipPresent);
if ( !selectClipPresent && aWhichClipboard != kGlobalClipboard )
bool findClipPresent;
SupportsFindClipboard(&findClipPresent);
if ( !selectClipPresent && !findClipPresent && aWhichClipboard != kGlobalClipboard )
return NS_ERROR_FAILURE;
if (mIgnoreEmptyNotification)
@ -123,3 +130,10 @@ nsBaseClipboard::SupportsSelectionClipboard(bool* _retval)
*_retval = false; // we don't support the selection clipboard by default.
return NS_OK;
}
NS_IMETHODIMP
nsBaseClipboard::SupportsFindClipboard(bool* _retval)
{
*_retval = false; // we don't support the find clipboard by default.
return NS_OK;
}

View File

@ -51,16 +51,25 @@ nsClipboardHelper::CopyStringToClipboard(const nsAString& aString,
NS_ENSURE_SUCCESS(rv, rv);
NS_ENSURE_TRUE(clipboard, NS_ERROR_FAILURE);
bool clipboardSupported;
// don't go any further if they're asking for the selection
// clipboard on a platform which doesn't support it (i.e., unix)
if (nsIClipboard::kSelectionClipboard == aClipboardID) {
bool clipboardSupported;
rv = clipboard->SupportsSelectionClipboard(&clipboardSupported);
NS_ENSURE_SUCCESS(rv, rv);
if (!clipboardSupported)
return NS_ERROR_FAILURE;
}
// don't go any further if they're asking for the find clipboard on a platform
// which doesn't support it (i.e., non-osx)
if (nsIClipboard::kFindClipboard == aClipboardID) {
rv = clipboard->SupportsFindClipboard(&clipboardSupported);
NS_ENSURE_SUCCESS(rv, rv);
if (!clipboardSupported)
return NS_ERROR_FAILURE;
}
// create a transferable for putting data on the clipboard
nsCOMPtr<nsITransferable>
trans(do_CreateInstance("@mozilla.org/widget/transferable;1", &rv));

View File

@ -91,3 +91,10 @@ nsClipboardProxy::SupportsSelectionClipboard(bool *aIsSupported)
return NS_OK;
}
NS_IMETHODIMP
nsClipboardProxy::SupportsFindClipboard(bool *aIsSupported)
{
*aIsSupported = false;
return NS_OK;
}