diff --git a/packages/image_picker/image_picker_for_web/CHANGELOG.md b/packages/image_picker/image_picker_for_web/CHANGELOG.md
new file mode 100644
index 000000000000..18ff7e526b11
--- /dev/null
+++ b/packages/image_picker/image_picker_for_web/CHANGELOG.md
@@ -0,0 +1,3 @@
+# 0.1.0
+
+* Initial open-source release.
diff --git a/packages/image_picker/image_picker_for_web/LICENSE b/packages/image_picker/image_picker_for_web/LICENSE
new file mode 100644
index 000000000000..0c382ce171cc
--- /dev/null
+++ b/packages/image_picker/image_picker_for_web/LICENSE
@@ -0,0 +1,27 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/packages/image_picker/image_picker_for_web/README.md b/packages/image_picker/image_picker_for_web/README.md
new file mode 100644
index 000000000000..81452e290984
--- /dev/null
+++ b/packages/image_picker/image_picker_for_web/README.md
@@ -0,0 +1,92 @@
+# image_picker_for_web
+
+A web implementation of [`image_picker`][1].
+
+## Browser Support
+
+Since Web Browsers don't offer direct access to their users' file system,
+this plugin provides a `PickedFile` abstraction to make access access uniform
+across platforms.
+
+The web version of the plugin puts network-accessible URIs as the `path`
+in the returned `PickedFile`.
+
+### URL.createObjectURL()
+
+The `PickedFile` object in web is backed by [`URL.createObjectUrl` Web API](https://developer.mozilla.org/en-US/docs/Web/API/URL/createObjectURL),
+which is reasonably well supported across all browsers:
+
+![Data on support for the bloburls feature across the major browsers from caniuse.com](https://caniuse.bitsofco.de/image/bloburls.png)
+
+However, the returned `path` attribute of the `PickedFile` points to a `network` resource, and not a
+local path in your users' drive. See **Use the plugin** below for some examples on how to use this
+return value in a cross-platform way.
+
+### input file "accept"
+
+In order to filter only video/image content, some browsers offer an [`accept` attribute](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/accept) in their `input type="file"` form elements:
+
+![Data on support for the input-file-accept feature across the major browsers from caniuse.com](https://caniuse.bitsofco.de/image/input-file-accept.png)
+
+This feature is just a convenience for users, **not validation**.
+
+Users can override this setting on their browsers. You must validate in your app (or server)
+that the user has picked the file type that you can handle.
+
+### input file "capture"
+
+In order to "take a photo", some mobile browsers offer a [`capture` attribute](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/capture):
+
+![Data on support for the html-media-capture feature across the major browsers from caniuse.com](https://caniuse.bitsofco.de/image/html-media-capture.png)
+
+Each browser may implement `capture` any way they please, so it may (or may not) make a
+difference in your users' experience.
+
+## Usage
+
+### Import the package
+
+This package is an unendorsed web platform implementation of `image_picker`.
+
+In order to use this, you'll need to depend in `image_picker: ^0.6.7` (which was the first version of the plugin that allowed federation), and `image_picker_for_web: ^0.1.0`.
+
+```yaml
+...
+dependencies:
+ ...
+ image_picker: ^0.6.7
+ image_picker_for_web: ^0.1.0
+ ...
+...
+```
+
+### Use the plugin
+
+You should be able to use `package:image_picker` _almost_ as normal.
+
+Once the user has picked a file, the returned `PickedFile` instance will contain a
+`network`-accessible URL (pointing to a location within the browser).
+
+The instace will also let you retrieve the bytes of the selected file across all platforms.
+
+If you want to use the path directly, your code would need look like this:
+
+```dart
+...
+if (kIsWeb) {
+ Image.network(pickedFile.path);
+} else {
+ Image.file(File(pickedFile.path));
+}
+...
+```
+
+Or, using bytes:
+
+```dart
+...
+Image.memory(await pickedFile.readAsBytes())
+...
+```
+
+[1]: https://pub.dev/packages/image_picker
diff --git a/packages/image_picker/image_picker_for_web/android/.gitignore b/packages/image_picker/image_picker_for_web/android/.gitignore
new file mode 100644
index 000000000000..c6cbe562a427
--- /dev/null
+++ b/packages/image_picker/image_picker_for_web/android/.gitignore
@@ -0,0 +1,8 @@
+*.iml
+.gradle
+/local.properties
+/.idea/workspace.xml
+/.idea/libraries
+.DS_Store
+/build
+/captures
diff --git a/packages/image_picker/image_picker_for_web/android/build.gradle b/packages/image_picker/image_picker_for_web/android/build.gradle
new file mode 100644
index 000000000000..6d8d50eb7b6d
--- /dev/null
+++ b/packages/image_picker/image_picker_for_web/android/build.gradle
@@ -0,0 +1,33 @@
+group 'io.flutter.image_picker_for_web'
+version '1.0'
+
+buildscript {
+ repositories {
+ google()
+ jcenter()
+ }
+
+ dependencies {
+ classpath 'com.android.tools.build:gradle:3.5.0'
+ }
+}
+
+rootProject.allprojects {
+ repositories {
+ google()
+ jcenter()
+ }
+}
+
+apply plugin: 'com.android.library'
+
+android {
+ compileSdkVersion 28
+
+ defaultConfig {
+ minSdkVersion 16
+ }
+ lintOptions {
+ disable 'InvalidPackage'
+ }
+}
diff --git a/packages/image_picker/image_picker_for_web/android/gradle.properties b/packages/image_picker/image_picker_for_web/android/gradle.properties
new file mode 100644
index 000000000000..7be3d8b46841
--- /dev/null
+++ b/packages/image_picker/image_picker_for_web/android/gradle.properties
@@ -0,0 +1,2 @@
+org.gradle.jvmargs=-Xmx1536M
+android.enableR8=true
diff --git a/packages/image_picker/image_picker_for_web/android/gradle/wrapper/gradle-wrapper.properties b/packages/image_picker/image_picker_for_web/android/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 000000000000..019065d1d650
--- /dev/null
+++ b/packages/image_picker/image_picker_for_web/android/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,5 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.2-all.zip
diff --git a/packages/image_picker/image_picker_for_web/android/settings.gradle b/packages/image_picker/image_picker_for_web/android/settings.gradle
new file mode 100644
index 000000000000..07e3728d1fe7
--- /dev/null
+++ b/packages/image_picker/image_picker_for_web/android/settings.gradle
@@ -0,0 +1 @@
+rootProject.name = 'image_picker_for_web'
diff --git a/packages/image_picker/image_picker_for_web/android/src/main/AndroidManifest.xml b/packages/image_picker/image_picker_for_web/android/src/main/AndroidManifest.xml
new file mode 100644
index 000000000000..b6f6992b3fb9
--- /dev/null
+++ b/packages/image_picker/image_picker_for_web/android/src/main/AndroidManifest.xml
@@ -0,0 +1,3 @@
+
+
diff --git a/packages/image_picker/image_picker_for_web/android/src/main/java/io/flutter/image_picker_for_web/ImagePickerWebPlugin.java b/packages/image_picker/image_picker_for_web/android/src/main/java/io/flutter/image_picker_for_web/ImagePickerWebPlugin.java
new file mode 100644
index 000000000000..18b5bf21144b
--- /dev/null
+++ b/packages/image_picker/image_picker_for_web/android/src/main/java/io/flutter/image_picker_for_web/ImagePickerWebPlugin.java
@@ -0,0 +1,28 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package io.flutter.image_picker_for_web;
+
+import io.flutter.embedding.engine.plugins.FlutterPlugin;
+import io.flutter.plugin.common.PluginRegistry.Registrar;
+
+/** ImagePickerWebPlugin */
+public class ImagePickerWebPlugin implements FlutterPlugin {
+ @Override
+ public void onAttachedToEngine(FlutterPluginBinding flutterPluginBinding) {}
+
+ // This static function is optional and equivalent to onAttachedToEngine. It supports the old
+ // pre-Flutter-1.12 Android projects. You are encouraged to continue supporting
+ // plugin registration via this function while apps migrate to use the new Android APIs
+ // post-flutter-1.12 via https://flutter.dev/go/android-project-migration.
+ //
+ // It is encouraged to share logic between onAttachedToEngine and registerWith to keep
+ // them functionally equivalent. Only one of onAttachedToEngine or registerWith will be called
+ // depending on the user's project. onAttachedToEngine or registerWith must both be defined
+ // in the same class.
+ public static void registerWith(Registrar registrar) {}
+
+ @Override
+ public void onDetachedFromEngine(FlutterPluginBinding binding) {}
+}
diff --git a/packages/image_picker/image_picker_for_web/ios/image_picker_for_web.podspec b/packages/image_picker/image_picker_for_web/ios/image_picker_for_web.podspec
new file mode 100644
index 000000000000..23fb795d1cc2
--- /dev/null
+++ b/packages/image_picker/image_picker_for_web/ios/image_picker_for_web.podspec
@@ -0,0 +1,20 @@
+#
+# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html
+#
+Pod::Spec.new do |s|
+ s.name = 'image_picker_for_web'
+ s.version = '0.0.1'
+ s.summary = 'No-op implementation of image_picker_for_web plugin to avoid build issues on iOS'
+ s.description = <<-DESC
+temp fake image_picker_for_web plugin
+ DESC
+ s.homepage = 'https://github.com/flutter/plugins/tree/master/packages/image_picker/image_picker_for_web'
+ s.license = { :file => '../LICENSE' }
+ s.author = { 'Flutter Team' => 'flutter-dev@googlegroups.com' }
+ s.source = { :path => '.' }
+ s.source_files = 'Classes/**/*'
+ s.public_header_files = 'Classes/**/*.h'
+ s.dependency 'Flutter'
+
+ s.ios.deployment_target = '8.0'
+end
diff --git a/packages/image_picker/image_picker_for_web/lib/image_picker_for_web.dart b/packages/image_picker/image_picker_for_web/lib/image_picker_for_web.dart
new file mode 100644
index 000000000000..ce99dd6d5fc6
--- /dev/null
+++ b/packages/image_picker/image_picker_for_web/lib/image_picker_for_web.dart
@@ -0,0 +1,184 @@
+import 'dart:async';
+import 'dart:html' as html;
+
+import 'package:flutter_web_plugins/flutter_web_plugins.dart';
+import 'package:meta/meta.dart';
+import 'package:image_picker_platform_interface/image_picker_platform_interface.dart';
+
+final String _kImagePickerInputsDomId = '__image_picker_web-file-input';
+final String _kAcceptImageMimeType = 'image/*';
+// TODO The value below seems to not be enough for Safari (https://github.com/flutter/flutter/issues/58532)
+final String _kAcceptVideoMimeType = 'video/*';
+
+/// The web implementation of [ImagePickerPlatform].
+///
+/// This class implements the `package:image_picker` functionality for the web.
+class ImagePickerPlugin extends ImagePickerPlatform {
+ final ImagePickerPluginTestOverrides _overrides;
+ bool get _hasOverrides => _overrides != null;
+
+ html.Element _target;
+
+ /// A constructor that allows tests to override the function that creates file inputs.
+ ImagePickerPlugin({
+ @visibleForTesting ImagePickerPluginTestOverrides overrides,
+ }) : _overrides = overrides {
+ _target = _ensureInitialized(_kImagePickerInputsDomId);
+ }
+
+ /// Registers this class as the default instance of [ImagePickerPlatform].
+ static void registerWith(Registrar registrar) {
+ ImagePickerPlatform.instance = ImagePickerPlugin();
+ }
+
+ @override
+ Future pickImage({
+ @required ImageSource source,
+ double maxWidth,
+ double maxHeight,
+ int imageQuality,
+ CameraDevice preferredCameraDevice = CameraDevice.rear,
+ }) {
+ String capture = computeCaptureAttribute(source, preferredCameraDevice);
+ return pickFile(accept: _kAcceptImageMimeType, capture: capture);
+ }
+
+ @override
+ Future pickVideo({
+ @required ImageSource source,
+ CameraDevice preferredCameraDevice = CameraDevice.rear,
+ Duration maxDuration,
+ }) {
+ String capture = computeCaptureAttribute(source, preferredCameraDevice);
+ return pickFile(accept: _kAcceptVideoMimeType, capture: capture);
+ }
+
+ /// Injects a file input with the specified accept+capture attributes, and
+ /// returns the PickedFile that the user selected locally.
+ ///
+ /// `capture` is only supported in mobile browsers.
+ /// See https://caniuse.com/#feat=html-media-capture
+ @visibleForTesting
+ Future pickFile({
+ String accept,
+ String capture,
+ }) {
+ html.FileUploadInputElement input = createInputElement(accept, capture);
+ _injectAndActivate(input);
+ return _getSelectedFile(input);
+ }
+
+ // DOM methods
+
+ /// Converts plugin configuration into a proper value for the `capture` attribute.
+ ///
+ /// See: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/file#capture
+ @visibleForTesting
+ String computeCaptureAttribute(ImageSource source, CameraDevice device) {
+ if (source == ImageSource.camera) {
+ return (device == CameraDevice.front) ? 'user' : 'environment';
+ }
+ return null;
+ }
+
+ html.File _getFileFromInput(html.FileUploadInputElement input) {
+ if (_hasOverrides) {
+ return _overrides.getFileFromInput(input);
+ }
+ return input?.files?.first;
+ }
+
+ /// Handles the OnChange event from a FileUploadInputElement object
+ /// Returns the objectURL of the selected file.
+ String _handleOnChangeEvent(html.Event event) {
+ final html.FileUploadInputElement input = event?.target;
+ final html.File file = _getFileFromInput(input);
+
+ if (file != null) {
+ return html.Url.createObjectUrl(file);
+ }
+ return null;
+ }
+
+ /// Monitors an and returns the selected file.
+ Future _getSelectedFile(html.FileUploadInputElement input) {
+ final Completer _completer = Completer();
+ // Observe the input until we can return something
+ input.onChange.first.then((event) {
+ final objectUrl = _handleOnChangeEvent(event);
+ if (!_completer.isCompleted) {
+ _completer.complete(PickedFile(objectUrl));
+ }
+ });
+ input.onError.first.then((event) {
+ if (!_completer.isCompleted) {
+ _completer.completeError(event);
+ }
+ });
+ // Note that we don't bother detaching from these streams, since the
+ // "input" gets re-created in the DOM every time the user needs to
+ // pick a file.
+ return _completer.future;
+ }
+
+ /// Initializes a DOM container where we can host input elements.
+ html.Element _ensureInitialized(String id) {
+ var target = html.querySelector('#${id}');
+ if (target == null) {
+ final html.Element targetElement =
+ html.Element.tag('flt-image-picker-inputs')..id = id;
+
+ html.querySelector('body').children.add(targetElement);
+ target = targetElement;
+ }
+ return target;
+ }
+
+ /// Creates an input element that accepts certain file types, and
+ /// allows to `capture` from the device's cameras (where supported)
+ @visibleForTesting
+ html.Element createInputElement(String accept, String capture) {
+ if (_hasOverrides) {
+ return _overrides.createInputElement(accept, capture);
+ }
+
+ html.Element element = html.FileUploadInputElement()..accept = accept;
+
+ if (capture != null) {
+ element.setAttribute('capture', capture);
+ }
+
+ return element;
+ }
+
+ /// Injects the file input element, and clicks on it
+ void _injectAndActivate(html.Element element) {
+ _target.children.clear();
+ _target.children.add(element);
+ element.click();
+ }
+}
+
+// Some tools to override behavior for unit-testing
+/// A function that creates a file input with the passed in `accept` and `capture` attributes.
+@visibleForTesting
+typedef OverrideCreateInputFunction = html.Element Function(
+ String accept,
+ String capture,
+);
+
+/// A function that extracts a [html.File] from the file `input` passed in.
+@visibleForTesting
+typedef OverrideExtractFilesFromInputFunction = html.File Function(
+ html.Element input,
+);
+
+/// Overrides for some of the functionality above.
+@visibleForTesting
+class ImagePickerPluginTestOverrides {
+ /// Override the creation of the input element.
+ OverrideCreateInputFunction createInputElement;
+
+ /// Override the extraction of the selected file from an input element.
+ OverrideExtractFilesFromInputFunction getFileFromInput;
+}
diff --git a/packages/image_picker/image_picker_for_web/pubspec.yaml b/packages/image_picker/image_picker_for_web/pubspec.yaml
new file mode 100644
index 000000000000..d25da73847e8
--- /dev/null
+++ b/packages/image_picker/image_picker_for_web/pubspec.yaml
@@ -0,0 +1,32 @@
+name: image_picker_for_web
+description: Web platform implementation of image_picker
+homepage: https://github.com/flutter/plugins/tree/master/packages/image_picker/image_picker_for_web
+# 0.1.y+z is compatible with 1.0.0, if you land a breaking change bump
+# the version to 2.0.0.
+# See more details: https://github.com/flutter/flutter/wiki/Package-migration-to-1.0.0
+version: 0.1.0
+
+flutter:
+ plugin:
+ platforms:
+ web:
+ pluginClass: ImagePickerPlugin
+ fileName: image_picker_for_web.dart
+
+dependencies:
+ image_picker_platform_interface: ^1.1.0
+ flutter:
+ sdk: flutter
+ flutter_web_plugins:
+ sdk: flutter
+ meta: ^1.1.7
+ js: ^0.6.0
+
+dev_dependencies:
+ flutter_test:
+ sdk: flutter
+ pedantic: ^1.8.0
+
+environment:
+ sdk: ">=2.5.0 <3.0.0"
+ flutter: ">=1.10.0 <2.0.0"
diff --git a/packages/image_picker/image_picker_for_web/test/image_picker_for_web_test.dart b/packages/image_picker/image_picker_for_web/test/image_picker_for_web_test.dart
new file mode 100644
index 000000000000..96d048dd2a8e
--- /dev/null
+++ b/packages/image_picker/image_picker_for_web/test/image_picker_for_web_test.dart
@@ -0,0 +1,84 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+@TestOn('chrome') // Uses dart:html
+
+import 'dart:convert';
+import 'dart:html' as html;
+import 'dart:typed_data';
+
+import 'package:flutter_test/flutter_test.dart';
+import 'package:image_picker_for_web/image_picker_for_web.dart';
+import 'package:image_picker_platform_interface/image_picker_platform_interface.dart';
+
+final String expectedStringContents = "Hello, world!";
+final Uint8List bytes = utf8.encode(expectedStringContents);
+final html.File textFile = html.File([bytes], "hello.txt");
+
+void main() {
+ // Under test...
+ ImagePickerPlugin plugin;
+
+ setUp(() {
+ plugin = ImagePickerPlugin();
+ });
+
+ test('Can select a file', () async {
+ final mockInput = html.FileUploadInputElement();
+
+ final overrides = ImagePickerPluginTestOverrides()
+ ..createInputElement = ((_, __) => mockInput)
+ ..getFileFromInput = ((_) => textFile);
+
+ final plugin = ImagePickerPlugin(overrides: overrides);
+
+ // Init the pick file dialog...
+ final file = plugin.pickFile();
+
+ // Mock the browser behavior of selecting a file...
+ mockInput.dispatchEvent(html.Event('change'));
+
+ // Now the file should be available
+ expect(file, completes);
+ // And readable
+ expect((await file).readAsBytes(), completion(isNotEmpty));
+ });
+
+ // There's no good way of detecting when the user has "aborted" the selection.
+
+ test('computeCaptureAttribute', () {
+ expect(
+ plugin.computeCaptureAttribute(ImageSource.gallery, CameraDevice.front),
+ isNull,
+ );
+ expect(
+ plugin.computeCaptureAttribute(ImageSource.gallery, CameraDevice.rear),
+ isNull,
+ );
+ expect(
+ plugin.computeCaptureAttribute(ImageSource.camera, CameraDevice.front),
+ 'user',
+ );
+ expect(
+ plugin.computeCaptureAttribute(ImageSource.camera, CameraDevice.rear),
+ 'environment',
+ );
+ });
+
+ group('createInputElement', () {
+ test('accept: any, capture: null', () {
+ html.Element input = plugin.createInputElement('any', null);
+
+ expect(input.attributes, containsPair('accept', 'any'));
+ expect(input.attributes, isNot(contains('capture')));
+ });
+
+ test('accept: any, capture: something', () {
+ html.Element input = plugin.createInputElement('any', 'something');
+
+ expect(input.attributes, containsPair('accept', 'any'));
+ expect(input.attributes, containsPair('capture', 'something'));
+ });
+ });
+}