Bug 1620657 - Add a native impl of nsIExternalHelperAppService for Android r=mattwoodrow,geckoview-reviewers,droeh
authorJames Willcox <snorp@snorp.net>
Fri, 10 Apr 2020 15:53:19 +0000
changeset 523401 2a080386c304f2abfe514e1510db27879bfe83f7
parent 523400 aae7f0d27d71baefd3dde0a6fb1281ba3a95911b
child 523402 a388c7b04ba25f41bc5df440b3d7b479aab292ea
push id37302
push usercbrindusan@mozilla.com
push dateSat, 11 Apr 2020 09:34:41 +0000
treeherdermozilla-central@06ee15775ba8 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmattwoodrow, geckoview-reviewers, droeh
bugs1620657
milestone77.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1620657 - Add a native impl of nsIExternalHelperAppService for Android r=mattwoodrow,geckoview-reviewers,droeh This is needed in order to implement `CreateListener()`, which is used when DocumentChannel is enabled. Differential Revision: https://phabricator.services.mozilla.com/D67682
docshell/build/components.conf
mobile/android/components/geckoview/GeckoView.manifest
mobile/android/components/geckoview/GeckoViewExternalAppService.cpp
mobile/android/components/geckoview/GeckoViewExternalAppService.h
mobile/android/components/geckoview/GeckoViewExternalAppService.js
mobile/android/components/geckoview/components.conf
mobile/android/components/geckoview/moz.build
mobile/android/installer/package-manifest.in
--- a/docshell/build/components.conf
+++ b/docshell/build/components.conf
@@ -60,29 +60,16 @@ Classes = [
     },
     {
         'cid': '{33d75835-722f-42c0-89cc-44f328e56a86}',
         'contract_ids': ['@mozilla.org/docshell/uri-fixup-info;1'],
         'jsm': 'resource://gre/modules/URIFixup.jsm',
         'constructor': 'URIFixupInfo',
     },
     {
-        'cid': '{a7f800e0-4306-11d4-98d0-001083010e9b}',
-        'contract_ids': [
-            '@mozilla.org/mime;1',
-            '@mozilla.org/uriloader/external-helper-app-service;1',
-            '@mozilla.org/uriloader/external-protocol-service;1',
-        ],
-        'type': 'nsExternalHelperAppService',
-        'constructor': 'nsExternalHelperAppService::GetSingleton',
-        'headers': ['nsExternalHelperAppService.h'],
-        'init_method': 'Init',
-        'processes': ProcessSelector.ALLOW_IN_SOCKET_PROCESS,
-    },
-    {
         'cid': '{56ebedd4-6ccf-48e8-bdae-adc77f044567}',
         'contract_ids': [
             '@mozilla.org/network/protocol/about;1?what=%s' % path
             for path in about_pages
         ],
         'legacy_constructor': 'nsAboutRedirector::Create',
         'headers': ['/docshell/base/nsAboutRedirector.h'],
     },
@@ -160,9 +147,39 @@ if defined('MOZ_ENABLE_DBUS'):
 if buildconfig.substs['MOZ_WIDGET_TOOLKIT'] == 'android':
     Classes += [
         {
             'cid': '{4bf1f8ef-d947-4ba3-9cd3-8c9a54a63a1c}',
             'contract_ids': ['@mozilla.org/uriloader/external-url-handler-service;1'],
             'type': 'nsExternalURLHandlerService',
             'headers': ['nsExternalURLHandlerService.h'],
         },
+        # Android has its own externel-helper-app-service, so we omit
+        # that here for nsExternalHelperAppService.
+        {
+            'cid': '{a7f800e0-4306-11d4-98d0-001083010e9b}',
+            'contract_ids': [
+                '@mozilla.org/mime;1',
+                '@mozilla.org/uriloader/external-protocol-service;1',
+            ],
+            'type': 'nsExternalHelperAppService',
+            'constructor': 'nsExternalHelperAppService::GetSingleton',
+            'headers': ['nsExternalHelperAppService.h'],
+            'init_method': 'Init',
+            'processes': ProcessSelector.ALLOW_IN_SOCKET_PROCESS,
+        },
     ]
+else:
+    Classes += [
+        {
+            'cid': '{a7f800e0-4306-11d4-98d0-001083010e9b}',
+            'contract_ids': [
+                '@mozilla.org/mime;1',
+                '@mozilla.org/uriloader/external-helper-app-service;1',
+                '@mozilla.org/uriloader/external-protocol-service;1',
+            ],
+            'type': 'nsExternalHelperAppService',
+            'constructor': 'nsExternalHelperAppService::GetSingleton',
+            'headers': ['nsExternalHelperAppService.h'],
+            'init_method': 'Init',
+            'processes': ProcessSelector.ALLOW_IN_SOCKET_PROCESS,
+        },
+    ]
\ No newline at end of file
--- a/mobile/android/components/geckoview/GeckoView.manifest
+++ b/mobile/android/components/geckoview/GeckoView.manifest
@@ -25,15 +25,11 @@ contract @mozilla.org/filepicker;1 {e456
 # GeckoViewPrompt.js ShareDelegate
 component {1201d357-8417-4926-a694-e6408fbedcf8} GeckoViewPrompt.js process=main
 contract @mozilla.org/sharepicker;1 {1201d357-8417-4926-a694-e6408fbedcf8} process=main
 
 # GeckoViewPrompt.js LoginStorageDelegate
 component {3d765750-1c3d-11ea-aaef-0800200c9a66} GeckoViewPrompt.js process=main
 contract @mozilla.org/login-manager/prompter;1 {3d765750-1c3d-11ea-aaef-0800200c9a66} process=main
 
-# GeckoViewExternalAppService.js
-component {a89eeec6-6608-42ee-a4f8-04d425992f45} GeckoViewExternalAppService.js
-contract @mozilla.org/uriloader/external-helper-app-service;1 {a89eeec6-6608-42ee-a4f8-04d425992f45}
-
 # GeckoViewPush.js
 component {a54d84d7-98a4-4fec-b664-e42e512ae9cc} GeckoViewPush.js
 contract @mozilla.org/push/Service;1 {a54d84d7-98a4-4fec-b664-e42e512ae9cc}
new file mode 100644
--- /dev/null
+++ b/mobile/android/components/geckoview/GeckoViewExternalAppService.cpp
@@ -0,0 +1,135 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "GeckoViewExternalAppService.h"
+
+#include "mozilla/dom/BrowsingContext.h"
+#include "mozilla/dom/CanonicalBrowsingContext.h"
+#include "mozilla/dom/WindowGlobalParent.h"
+
+#include "mozilla/widget/EventDispatcher.h"
+#include "mozilla/widget/nsWindow.h"
+
+#include "JavaBuiltins.h"
+
+static const char16_t kExternalResponseMessage[] =
+    u"GeckoView:ExternalResponse";
+
+mozilla::StaticRefPtr<GeckoViewExternalAppService>
+    GeckoViewExternalAppService::sService;
+
+/* static */
+already_AddRefed<GeckoViewExternalAppService>
+GeckoViewExternalAppService::GetSingleton() {
+  if (!sService) {
+    sService = new GeckoViewExternalAppService();
+  }
+  RefPtr<GeckoViewExternalAppService> service = sService;
+  return service.forget();
+}
+
+GeckoViewExternalAppService::GeckoViewExternalAppService() {}
+
+NS_IMPL_ISUPPORTS(GeckoViewExternalAppService, nsIExternalHelperAppService);
+
+NS_IMETHODIMP GeckoViewExternalAppService::DoContent(
+    const nsACString& aMimeContentType, nsIRequest* aRequest,
+    nsIInterfaceRequestor* aContentContext, bool aForceSave,
+    nsIInterfaceRequestor* aWindowContext,
+    nsIStreamListener** aStreamListener) {
+  return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP GeckoViewExternalAppService::CreateListener(
+    const nsACString& aMimeContentType, nsIRequest* aRequest,
+    mozilla::dom::BrowsingContext* aContentContext, bool aForceSave,
+    nsIInterfaceRequestor* aWindowContext,
+    nsExternalAppHandler** aStreamListener) {
+  using namespace mozilla;
+  using namespace mozilla::dom;
+  MOZ_ASSERT(XRE_IsParentProcess());
+
+  // We currently never want to read the channel, so cancel it immediately.
+  aRequest->Cancel(NS_ERROR_ABORT);
+
+  nsresult rv;
+  nsCOMPtr<nsIChannel> channel(do_QueryInterface(aRequest, &rv));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsCOMPtr<nsIWidget> widget =
+      aContentContext->Canonical()->GetParentProcessWidgetContaining();
+  if (!widget) {
+    return NS_ERROR_ABORT;
+  }
+
+  RefPtr<nsWindow> window = nsWindow::From(widget);
+  MOZ_ASSERT(window);
+
+  widget::EventDispatcher* dispatcher = window->GetEventDispatcher();
+  MOZ_ASSERT(dispatcher);
+
+  if (!dispatcher->HasListener(kExternalResponseMessage)) {
+    return NS_ERROR_ABORT;
+  }
+
+  AutoTArray<jni::String::LocalRef, 4> keys;
+  AutoTArray<jni::Object::LocalRef, 4> values;
+
+  nsCOMPtr<nsIURI> uri;
+  if (NS_WARN_IF(NS_FAILED(channel->GetURI(getter_AddRefs(uri))))) {
+    return NS_ERROR_ABORT;
+  }
+
+  nsAutoCString uriSpec;
+  if (NS_WARN_IF(NS_FAILED(uri->GetDisplaySpec(uriSpec)))) {
+    return NS_ERROR_ABORT;
+  }
+
+  keys.AppendElement(jni::StringParam(NS_LITERAL_STRING("uri")));
+  values.AppendElement(jni::StringParam(uriSpec));
+
+  nsCString contentType;
+  if (NS_WARN_IF(NS_FAILED(channel->GetContentType(contentType)))) {
+    return NS_ERROR_ABORT;
+  }
+
+  keys.AppendElement(jni::StringParam(NS_LITERAL_STRING("contentType")));
+  values.AppendElement(jni::StringParam(contentType));
+
+  int64_t contentLength = 0;
+  if (NS_WARN_IF(NS_FAILED(channel->GetContentLength(&contentLength)))) {
+    return NS_ERROR_ABORT;
+  }
+
+  keys.AppendElement(jni::StringParam(NS_LITERAL_STRING("contentLength")));
+  values.AppendElement(java::sdk::Integer::ValueOf(contentLength));
+
+  nsString filename;
+  if (NS_SUCCEEDED(channel->GetContentDispositionFilename(filename))) {
+    keys.AppendElement(jni::StringParam(NS_LITERAL_STRING("filename")));
+    values.AppendElement(jni::StringParam(filename));
+  }
+
+  auto bundleKeys = jni::ObjectArray::New<jni::String>(keys.Length());
+  auto bundleValues = jni::ObjectArray::New<jni::Object>(values.Length());
+  for (size_t i = 0; i < keys.Length(); ++i) {
+    bundleKeys->SetElement(i, keys[i]);
+    bundleValues->SetElement(i, values[i]);
+  }
+  auto bundle = java::GeckoBundle::New(bundleKeys, bundleValues);
+
+  Unused << NS_WARN_IF(
+      NS_FAILED(dispatcher->Dispatch(kExternalResponseMessage, bundle)));
+
+  return NS_ERROR_ABORT;
+}
+
+NS_IMETHODIMP GeckoViewExternalAppService::ApplyDecodingForExtension(
+    const nsACString& aExtension, const nsACString& aEncodingType,
+    bool* aApplyDecoding) {
+  // This currently doesn't matter, because we never read the stream.
+  *aApplyDecoding = true;
+  return NS_OK;
+}
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/mobile/android/components/geckoview/GeckoViewExternalAppService.h
@@ -0,0 +1,26 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * 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/. */
+
+#ifndef GeckoViewExternalAppService_h__
+#define GeckoViewExternalAppService_h__
+
+#include "nsIExternalHelperAppService.h"
+#include "mozilla/StaticPtr.h"
+
+class GeckoViewExternalAppService : public nsIExternalHelperAppService {
+ public:
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSIEXTERNALHELPERAPPSERVICE
+
+  GeckoViewExternalAppService();
+
+  static already_AddRefed<GeckoViewExternalAppService> GetSingleton();
+
+ private:
+  virtual ~GeckoViewExternalAppService() {}
+  static mozilla::StaticRefPtr<GeckoViewExternalAppService> sService;
+};
+
+#endif
\ No newline at end of file
deleted file mode 100644
--- a/mobile/android/components/geckoview/GeckoViewExternalAppService.js
+++ /dev/null
@@ -1,65 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-"use strict";
-
-const { XPCOMUtils } = ChromeUtils.import(
-  "resource://gre/modules/XPCOMUtils.jsm"
-);
-const { GeckoViewUtils } = ChromeUtils.import(
-  "resource://gre/modules/GeckoViewUtils.jsm"
-);
-
-const { debug, warn } = GeckoViewUtils.initLogging("ExternalAppService"); // eslint-disable-line no-unused-vars
-
-ChromeUtils.defineModuleGetter(
-  this,
-  "EventDispatcher",
-  "resource://gre/modules/Messaging.jsm"
-);
-
-function ExternalAppService() {
-  this.wrappedJSObject = this;
-}
-
-ExternalAppService.prototype = {
-  classID: Components.ID("{a89eeec6-6608-42ee-a4f8-04d425992f45}"),
-  QueryInterface: ChromeUtils.generateQI([Ci.nsIExternalHelperAppService]),
-
-  doContent(mimeType, request, context, forceSave) {
-    const channel = request.QueryInterface(Ci.nsIChannel);
-    debug`doContent: uri=${channel.URI.displaySpec}
-                      contentType=${channel.contentType}`;
-
-    let filename = null;
-    try {
-      filename = channel.contentDispositionFilename;
-    } catch (e) {
-      // This throws NS_ERROR_NOT_AVAILABLE if there is not
-      // Content-disposition header.
-    }
-
-    GeckoViewUtils.getDispatcherForWindow(context).sendRequest({
-      type: "GeckoView:ExternalResponse",
-      uri: channel.URI.displaySpec,
-      contentType: channel.contentType,
-      contentLength: channel.contentLength,
-      filename,
-    });
-
-    request.cancel(Cr.NS_ERROR_ABORT);
-    Components.returnCode = Cr.NS_ERROR_ABORT;
-  },
-
-  applyDecodingForExtension(ext, encoding) {
-    debug`applyDecodingForExtension: extension=${ext}
-                                      encoding=${encoding}`;
-
-    // This doesn't matter for us right now because
-    // we shouldn't end up reading the stream.
-    return true;
-  },
-};
-
-this.NSGetFactory = XPCOMUtils.generateNSGetFactory([ExternalAppService]);
--- a/mobile/android/components/geckoview/components.conf
+++ b/mobile/android/components/geckoview/components.conf
@@ -8,9 +8,19 @@ Classes = [
     {
         'cid': '{0937a705-91a6-417a-8292-b22eb10da86c}',
         'contract_ids': ['@mozilla.org/browser/history;1'],
         'singleton': True,
         'type': 'GeckoViewHistory',
         'headers': ['GeckoViewHistory.h'],
         'constructor': 'GeckoViewHistory::GetSingleton',
     },
+    {
+        'cid': '{91455c77-64a1-4c37-be00-f94eb9c7b8e1}',
+        'contract_ids': [
+            '@mozilla.org/uriloader/external-helper-app-service;1',
+        ],
+        'type': 'GeckoViewExternalAppService',
+        'constructor': 'GeckoViewExternalAppService::GetSingleton',
+        'headers': ['GeckoViewExternalAppService.h'],
+        'processes': ProcessSelector.ALLOW_IN_SOCKET_PROCESS,
+    },
 ]
--- a/mobile/android/components/geckoview/moz.build
+++ b/mobile/android/components/geckoview/moz.build
@@ -1,28 +1,30 @@
 # -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
+SOURCES += ['GeckoViewExternalAppService.cpp']
+EXPORTS += ['GeckoViewExternalAppService.h']
+
 if CONFIG['MOZ_ANDROID_HISTORY']:
     EXPORTS += [
         'GeckoViewHistory.h',
     ]
     SOURCES += [
         'GeckoViewHistory.cpp',
     ]
     XPCOM_MANIFESTS += [
         'components.conf',
     ]
     include('/ipc/chromium/chromium-config.mozbuild')
 
 EXTRA_COMPONENTS += [
     'GeckoView.manifest',
-    'GeckoViewExternalAppService.js',
     'GeckoViewPermission.js',
     'GeckoViewPrompt.js',
     'GeckoViewPush.js',
     'GeckoViewStartup.js',
 ]
 
 FINAL_LIBRARY = 'xul'
--- a/mobile/android/installer/package-manifest.in
+++ b/mobile/android/installer/package-manifest.in
@@ -210,17 +210,16 @@
 #endif
 
 [mobile]
 @BINPATH@/chrome/geckoview@JAREXT@
 @BINPATH@/chrome/geckoview.manifest
 
 #ifdef MOZ_GECKOVIEW_JAR
 @BINPATH@/components/GeckoView.manifest
-@BINPATH@/components/GeckoViewExternalAppService.js
 @BINPATH@/components/GeckoViewPrompt.js
 @BINPATH@/components/GeckoViewPush.js
 @BINPATH@/components/GeckoViewPermission.js
 @BINPATH@/components/GeckoViewStartup.js
 #else
 @BINPATH@/chrome/chrome@JAREXT@
 @BINPATH@/chrome/chrome.manifest
 #endif