-
-
Notifications
You must be signed in to change notification settings - Fork 185
URL: reimplement URL.createObjectURL() #1150
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,91 @@ | ||
| /* | ||
| * Copyright (c) 2002-2026 Gargoyle Software Inc. | ||
| * | ||
| * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| * you may not use this file except in compliance with the License. | ||
| * You may obtain a copy of the License at | ||
| * https://www.apache.org/licenses/LICENSE-2.0 | ||
| * | ||
| * Unless required by applicable law or agreed to in writing, software | ||
| * distributed under the License is distributed on an "AS IS" BASIS, | ||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| * See the License for the specific language governing permissions and | ||
| * limitations under the License. | ||
| */ | ||
| package org.htmlunit; | ||
|
|
||
| import java.util.Map; | ||
| import java.util.concurrent.ConcurrentHashMap; | ||
|
|
||
| import org.htmlunit.javascript.host.file.Blob; | ||
|
|
||
| /** | ||
| * The user-agent-wide blob URL store defined by | ||
| * <a href="https://w3c.github.io/FileAPI/#BlobURLStore">Blob URL Store</a>. | ||
| * | ||
| * @author Lai Quang Duong | ||
| */ | ||
| public class BlobUrlStore { | ||
|
|
||
| private static final class Entry { | ||
| private final Blob object_; | ||
| private final Page owningPage_; | ||
|
|
||
| Entry(final Blob object, final Page owningPage) { | ||
| object_ = object; | ||
| owningPage_ = owningPage; | ||
| } | ||
| } | ||
|
|
||
| private final Map<String, Entry> store_ = new ConcurrentHashMap<>(); | ||
|
Check warning on line 40 in src/main/java/org/htmlunit/BlobUrlStore.java
|
||
|
|
||
| /** | ||
| * Adds an entry. | ||
| * @param blobUrl the generated {@code blob:} URL | ||
| * @param object the {@link Blob} (or {@code File}) the URL refers to | ||
| * @param owningPage the page that created the URL | ||
| */ | ||
| public void put(final String blobUrl, final Blob object, final Page owningPage) { | ||
| store_.put(blobUrl, new Entry(object, owningPage)); | ||
| } | ||
|
|
||
| /** | ||
| * <a href="https://w3c.github.io/FileAPI/#blob-url-resolve">Resolve a blob URL</a>. | ||
| * @param blobUrl the {@code blob:} URL to resolve | ||
| * @return the stored {@link Blob}, or {@code null} if there is no entry | ||
| */ | ||
| public Blob resolve(final String blobUrl) { | ||
| final Entry entry = store_.get(excludeFragment(blobUrl)); | ||
| return entry == null ? null : entry.object_; | ||
| } | ||
|
|
||
| /** | ||
| * Removes an entry. | ||
| * @param blobUrl the {@code blob:} URL to remove | ||
| */ | ||
| public void remove(final String blobUrl) { | ||
| store_.remove(excludeFragment(blobUrl)); | ||
| } | ||
|
|
||
| /** | ||
| * See <a href="https://w3c.github.io/FileAPI/#lifeTime">Lifetime of blob URLs</a>. | ||
| * @param owningPage the unloading page | ||
| */ | ||
| void removeForPage(final Page owningPage) { | ||
| store_.values().removeIf(entry -> entry.owningPage_ == owningPage); | ||
| } | ||
|
|
||
| /** | ||
| * <span style="color:red">INTERNAL API - SUBJECT TO CHANGE AT ANY TIME - USE AT YOUR OWN RISK.</span><br> | ||
| * | ||
| * <p>Removes all entries. | ||
|
Check failure on line 81 in src/main/java/org/htmlunit/BlobUrlStore.java
|
||
| */ | ||
| public void clear() { | ||
| store_.clear(); | ||
| } | ||
|
|
||
| private static String excludeFragment(final String blobUrl) { | ||
| final int fragmentIndex = blobUrl.indexOf('#'); | ||
| return fragmentIndex >= 0 ? blobUrl.substring(0, fragmentIndex) : blobUrl; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -18,8 +18,11 @@ | |
|
|
||
| import java.net.MalformedURLException; | ||
| import java.util.List; | ||
| import java.util.UUID; | ||
|
|
||
| import org.apache.commons.lang3.StringUtils; | ||
| import org.htmlunit.Page; | ||
| import org.htmlunit.WebWindow; | ||
| import org.htmlunit.corejs.javascript.Context; | ||
| import org.htmlunit.corejs.javascript.Scriptable; | ||
| import org.htmlunit.javascript.HtmlUnitScriptable; | ||
|
|
@@ -83,32 +86,51 @@ | |
| /** | ||
| * The URL.createObjectURL() static method creates a DOMString containing a URL | ||
| * representing the object given in parameter. | ||
| * The URL lifetime is tied to the document in the window on which it was created. | ||
| * The new object URL represents the specified File object or Blob object. | ||
| * The new object URL represents the specified {@link File} object or {@link Blob} object | ||
| * and is registered in the {@link org.htmlunit.BlobUrlStore user-agent-wide blob URL store}, | ||
| * so it can be resolved from any document of the same client. | ||
| * | ||
| * @param fileOrBlob Is a File object or a Blob object to create a object URL for. | ||
| * @return the url | ||
| */ | ||
| @JsxStaticFunction | ||
| public static String createObjectURL(final Object fileOrBlob) { | ||
| if (fileOrBlob instanceof File file) { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe i'm blind but looks like the file support is no longer there?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh the support is still there. The previous implementation doing both check was redundant because File itself is a Blob |
||
| return getWindow(file).getDocument().generateBlobUrl(file); | ||
| if (!(fileOrBlob instanceof Blob blob)) { | ||
| throw JavaScriptEngine.typeError("URL.createObjectURL: argument 1 is not a Blob."); | ||
| } | ||
|
|
||
| if (fileOrBlob instanceof Blob blob) { | ||
| return getWindow(blob).getDocument().generateBlobUrl(blob); | ||
| final WebWindow webWindow = getWindow(blob).getWebWindow(); | ||
| final Page page = webWindow.getEnclosedPage(); | ||
| final java.net.URL pageUrl = page.getUrl(); | ||
|
|
||
| String origin = "null"; | ||
| if (pageUrl != UrlUtils.URL_ABOUT_BLANK) { | ||
| final int port = pageUrl.getPort(); | ||
| if (port < 0 || port == pageUrl.getDefaultPort()) { | ||
| origin = pageUrl.getProtocol() + "://" + pageUrl.getHost(); | ||
|
Check warning on line 110 in src/main/java/org/htmlunit/javascript/host/URL.java
|
||
| } | ||
| else { | ||
| origin = pageUrl.getProtocol() + "://" + pageUrl.getHost() + ':' + port; | ||
| } | ||
| } | ||
|
|
||
| return null; | ||
| final String blobUrl = "blob:" + origin + "/" + UUID.randomUUID(); | ||
| webWindow.getWebClient().getBlobUrlStore().put(blobUrl, blob, page); | ||
| return blobUrl; | ||
| } | ||
|
|
||
| /** | ||
| * @param objectURL String representing the object URL that was | ||
| * created by calling URL.createObjectURL(). | ||
| * Revokes a {@code blob:} URL, removing its entry from the blob URL store. | ||
| * | ||
| * @param objectURL the object URL previously returned by {@link #createObjectURL(Object)} | ||
| */ | ||
| @JsxStaticFunction | ||
| public static void revokeObjectURL(final Scriptable objectURL) { | ||
| getWindow(objectURL).getDocument().revokeBlobUrl(Context.toString(objectURL)); | ||
| final String url = Context.toString(objectURL); | ||
| if (!url.startsWith("blob:")) { | ||
| return; | ||
| } | ||
| getWindow(objectURL).getWebWindow().getWebClient().getBlobUrlStore().remove(url); | ||
| } | ||
|
|
||
| /** | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great to have a separate class for this.
Maybe we should add a clear() method to support testing use cases where we like to remove urls to test error cases. I guess the page for removal is not always at hand.