Skip to content
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

Pasting trouble #175

Open
benlong100 opened this issue Dec 23, 2023 · 37 comments
Open

Pasting trouble #175

benlong100 opened this issue Dec 23, 2023 · 37 comments

Comments

@benlong100
Copy link

I'm having trouble getting the editor to paste a larger (roughly 1500 words) quantity of text. In both my app and the demo app, it gets through the first paragraph, which is exactly 50 words, and then nothing after that shows up.

@stevengharris
Copy link
Owner

Go into MarkupWKWebView.pasteText(_:handler:) or MarkupWKWebView.pasteHtml(_:handler:) to grab the text or html being passed to it, and then provide it here. Probably there is an issue with the "preprocessing" being done to remove things that mess up the MarkupEditor when arbitrary html is pasted-in. As I find cases that mess it up, I have been including them in the test suite. The cleanup is done on the JavaScript side in _patchPasteHTML if you are curious.

@benlong100
Copy link
Author

Okay, I've done some more fiddling. It seems to be okay with pasting large quantities of plain text (though I need to hammer it a little more to be sure). The problem is pasting HTML. If I grab some text from a random wikipedia page and try to paste it in, with a breakpoint in the MarkupWKWebView.pasteHTML handler, the breakpoint never triggers. It's like the app is completely ignoring the paste if it's a bunch of HTML.

@stevengharris
Copy link
Owner

stevengharris commented Dec 28, 2023

I would just back up further in the call chain, perhaps to MarkupWKWebView.paste(_:) or even to MarkupWKWebView.canPerformAction(_:withSender:) and track what's going on. Wikipedia content works fine for me in the SwiftUIDemo, sometimes uglier than other times, but the content arrives. I suppose you've already made sure it pastes properly into, say, Notes or Email, so you're sure the pasteboard is properly populated before pasting it into the MarkupEditor. I'm using the SwiftUIDemo on a Mac running 14.1.2 (23B92).

@benlong100
Copy link
Author

Okay, a breakpoint in the first line of MarkupWKWebView.pasteableType will stop execution. It correctly identifies the stuff as HTML and returns that, which gets it back to the canPerformAction handler, but when that's done is goes back to pasteableType and identifies the html again, then it just seems to be caught in an infinite loop there.

@stevengharris
Copy link
Owner

Yah, I don't know why that happens, but it's some kind of debugger breakpoint weirdness. If you remove the breakpoint, it won't be in an infinite loop. How about breaking at if let data = pasteboard.data(forPasteboardType: "public.html") { in MarkupWKWebView.paste(_:). Here's another issue where the guy dug into the paste operation that might be useful to look at, too: #128 (comment). I haven't had a chance to chase that one down yet.

@benlong100
Copy link
Author

That helped. In the paste function, it's going into the HTML case and if I look at my data there, everything's fine – it's all intact. Going further in I find that it's surviving intact all the way through the escaping that you call in the evaluateJavaScript call in pasteHTML. I tried setting a breakpoint inside MU.pasteHTML so that I could look at the data there, but Xcode ignored it. Is that normal?

@stevengharris
Copy link
Owner

To set a breakpoint in MU.pasteHTML, you have to use the Safari Web Inspector from the Developer Tools menu in Safari (https://developer.apple.com/documentation/safari-developer-tools/web-inspector). It's primitive, but an alternative is to insert _consoleLog() calls in the JavaScript methods to print strings in the Xcode console.

@stevengharris
Copy link
Owner

@benlong100 I recently put in some fixes for pasting. If this is still broken on main, can you let me know, or otherwise, close the issue. Thanks.

@benlong100
Copy link
Author

Sorry for the delay, I've been mired in another part of the code. I had not forked your code before – I was just running it as a Local Dependency. This morning I set things up properly for syncing to your changes, and got my alterations moved into the code. When I invoke the MarkupEditor it's not loading my text – the editor and all my toolbars show properly, but there's no document or caret. Has something changed in the way that I need to pass it source text? If not then I must have missed something when I moved my changes over.

@stevengharris
Copy link
Owner

stevengharris commented Jan 18, 2024

No, nothing should have changed. Since I'm often mucking around in markup.js, this is a behavior I see when there is a JavaScript error. If so, it is easily identified by opening the Safari Web Inspector (Develop->Choose device->Choose web page) and hitting the reload button. A more top-down approach is to put a break in markupDidLoad in your MarkupDelegate to make sure it's called, or at a level deeper in MarkupCoordinator.userContentController(_:didReceive:).

@benlong100
Copy link
Author

My app is not showing up in the Device menu in Develop. This implies that I need to make the WKWebView inspectable. If that's true, where's the best place to put the .isInspectable = true?

@stevengharris
Copy link
Owner

I do this where I initialize other things about the MarkupEditor (like toolbar stuff) by doing:

        #if DEBUG
        MarkupEditor.isInspectable = true
        #endif

@benlong100
Copy link
Author

Okay, so the WebInspector is working now. If there were errors, were might I see them? Right now it's showing the html, which looks like this:

<html><head>
        <meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=no">
        <meta name="supported-color-schemes" content="light dark">
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <link rel="stylesheet" type="text/css" href="markup.css">
    </head>
    <body>
        <div id="editor"></div>
        <script src="markup.js"></script>
<input aria-hidden="true" id="hiddenInput" tabindex="-1" style="caret-color: blue; opacity: 0; position: fixed; top: -1000px; pointer-events: none; visibility: hidden;"></body></html>

As expected, my content isn't in there. Hitting refresh in the Web Inspector doesn't change anything. In the section with the checkboxes for active, focus, focus-visible, and so on, nothing is checked.

In the MarkupDelegate file, I put a breakpoint in the MarkupDidLoad handler, but it's never triggering.

In the web inspector, when I mouse over the body tag, on the iPad it highlights a very thin strip across the top of the WKWebView.

Sorry if these are silly questions, I've never used this WebInspector before. Seems a bit of a hack, but it's cool.

@stevengharris
Copy link
Owner

You would not see any errors until you hit the reload button (3rd from the left in the top bar of the window). Reloading will cause the .js to load again, and if there was some change that doesn't compile properly, you would see an error show up in the console indicating the line number. The thin strip being highlighted is just showing the body.

Maybe at this point a better approach is to debug more top-down. Maybe this kind of sequence:

  1. Is it easy just to open DemoContentView instead of whatever your top-level view is? If so, then it should show the demo.html contents and would give us some confidence that the basic MarkupEditor mechanics are working properly.
  2. If that's not easy, then maybe break in MarkupWKWebView.initForEditing(). Make sure html has your content. Then step into MarkupWKWebView.initForEditing() to make sure the root files are found properly.
  3. So if all that is working, then see if the callbacks to the MarkupCoordinator are working by breaking in MarkupCoordinator.userContentController(_:didReceive:). The first case that is triggered is the ready case which in turn triggers loadedUserFiles:
webView.setTopLevelAttributes() {
    webView.loadInitialHtml()
}

Assuming you get this far, you can set a break on the webView.loadInitialHtml() statement. When webView.loadInitialHtml() executes, your content is loaded in and markupDidLoad is called if the MarkupDelegate is set.

From what you're describing, something is broken in this sequence, so hopefully this gives you a path to narrowing it down.

@benlong100
Copy link
Author

Okay, that was good stuff. In MarkupWKWebView.initForEditing I found that html does contain my stuff. Where it comes apart is in MarkupCoordinator. loadedUserFiles isn't loading anything – both scriptFile and CSSFile are null. From what I see in your comments, that prevents loadInitialHTML?

I have a customCSS file that is stored in a specific user directory. I pass that as the resourcesURL when I call MarkupEditorView. Should I be putting that somewhere else?

And I have no idea what scriptFile might be referring to.

@benlong100
Copy link
Author

Aha! I just realized you've added a lot of new documentation to the Code page here – I haven't looked at it in a long time. I'll dig through that and see if it clears this up. Sorry I didn't see that before.

@stevengharris
Copy link
Owner

The scriptFile and cssFile are normally nil, but the callback to trigger the loadedUserFiles should still happen. This is the case for the standard demo, where they are both nil. Starting with the standard DemoViewController, you can put a break in MarkupWKWebView.loadUserFiles(_:), where both scriptFile and cssFile are nil. This invokes MU.loadUserFiles in markup.js, which ends up doing a callback to loadedUserFiles regardless of them being null on the JavaScript side.

This was definitely a change I made in the most recent round to allow custom scripts and css that I also added to the README. With this most recent change, you should be able to set your custom css file as discussed in the new README section. I am not sure how you are loading your custom CSS, but maybe the new work I did to formalize how to customize CSS is at cross purposes to what you did. In any case, if the loadedUserFiles case is never invoked in MarkupCoordinator.userContentController(_:didReceive:), but you can watch MarkupWKWebView.loadUserFiles(_:) be executed on the Swift side, then we know where the issue is. In markup.js we execute:

/**
 * Called to load user script and CSS before loading html.
 *
 * The scriptFile and cssFile are loaded in sequence, with the single 'loadedUserFiles'
 * callback only happening after their load events trigger. If neither scriptFile
 * nor cssFile are specified, then the 'loadedUserFiles' callback happens anyway,
 * since this ends up driving the loading process further.
 */
MU.loadUserFiles = function(scriptFile, cssFile) {
    if (scriptFile) {
        if (cssFile) {
            _loadUserScriptFile(scriptFile, function() { _loadUserCSSFile(cssFile) });
        } else {
            _loadUserScriptFile(scriptFile, function() { _loadedUserFiles() });
        }
    } else if (cssFile) {
        _loadUserCSSFile(cssFile);
    } else {
        _loadedUserFiles();
    }
};

const _loadedUserFiles = function() {
    _callback('loadedUserFiles');
}

In your case, with both scriptFile and cssFile null, it will fall thru and execute the _callback('loadedUserFiles');, which causes the MarkupCoordinator.userContentController(_:didReceive:) to fire on the Swift side and trigger the loadedUserFiles case. So there are only a few lines of JavaScript open to failing, or maybe the markup.js isn't the most recent version?

@benlong100
Copy link
Author

Okay, I've gone through your new readme. The new customization stuff is cool! And I've been wondering about changing the caret color but hadn't got around to it.

As for a custom style sheet, when I set markupConfiguration.userCssFile can that be a URL to a file? Or is it always going to look in the Bundle? Because of some custom font stuff, I have to modify my CSS file at runtime, so I can't keep an accurate one in the bundle.

@stevengharris
Copy link
Owner

It will always look for it in your app bundle. I'd be surprised if you can do much with files outside of the app bundle because of sandboxing rules.

@benlong100
Copy link
Author

I have some custom fonts in my app bundle and I want to be able to reference them from the CSS file. To do that the CSS file has to know the path to the font file. Unfortunately, at runtime, the path to the bundle is never the same. For example, it might be something like this:

/Library/Developer/CoreSimulator/Devices/C88793FA-075D-424F-92CE-2D6C29D6CBEF/data/Containers/Bundle/Application/A8EE7C9A-A80D-48C1-90F4-176971E4B117/theApp.app/Futura-Condensed.otf
but the UUIDs in the path change with each launch. So, every time I launch the app, I figure out what the current path to the bundle is and re-write the CSS file so that the font path is updated. Have I over-engineered this? Is there some other way that I could be referencing bundled fonts in a CSS file?

@stevengharris
Copy link
Owner

If you look at the SwiftUIDemo files like (now) custom.css, the file is part of the bundle, as is steve.png that I stuck in their for demo purposes so I could reference it from the demo.html. Can't you just put Future-Condensed.otf in the bundle like these other files, and then I would think your CSS can reference it without any path since it will be in the same place as the CSS.

@benlong100
Copy link
Author

The fonts are in the bundle, so that will work for the Markup editor. However I have some other web views that run off of documents stored in the app's Documents folder, so I need a style sheet in there, as well. That's where I get into the trouble of the bundle address changing every time.

Also, I have some preferences that allow the user to change the style sheet. Since the bundle is read-only, I can't work out of a bundle-based style sheet for that.

@stevengharris
Copy link
Owner

stevengharris commented Jan 19, 2024

I'm doing all the editing from the cache directory, copying things from the bundle in initForEditing. Maybe you can do the something similar for your files? I'm about to call it a night here so will check-in in the morning.

@benlong100
Copy link
Author

Okay, I've got my app changed around so that it does all of its work out of the ApplicationSupport directory. I have some things that I have to ensure will persist, so I'm hesitant to put them in the Caches folder.

What I was doing with the old version of MarkupEditor was putting the CSS file and all of the images associated with the document I wanted to edit into a folder, and then passing a URL to that folder through the resourcesURL parameter in my MarkupEditorView call. I assume that, with the new scheme, that no longer works. I will still need to copy the images into the appropriate place, and from the Readme, it looks like that would be into the ID folder in the caches folder. I assume I could copy my custom css into there as well? What I'm not clear on is where I should add this copying code. I don't want to copy over a CSS file and then have something downstream write over it with the one from the bundle.

@stevengharris
Copy link
Owner

stevengharris commented Jan 19, 2024

I think specifying resourcesURL should still work for your purposes. It seems like to me you should specify the CSS file in MarkupWKWebViewConfiguration.customCss and just include the font and images in the resourcesUrl location. This way the customCss should just be able to reference the font without worrying about a path. Ideally this would "just work", and you don't have to deal with copying any files around yourself or relying on the specific setup (e.g., the use of the caches directory) under MarkupWKWebView.

As a last resort, I was thinking about exposing the MarkupWKWebView baseUrl (i.e., what is currently the private method cacheUrl()) publicly. At least then you could copy to the baseUrl, although there is still the issue of when to do the copying. Currently there is no notification via MarkupDelegate that comes before the initial HTML is loaded, and presumably you want to accomplish the copying before that happens (which is what goes on with the preferred approach of using customCSS and resourcesUrl). If I do this, then I'd probably have to include something like MarkupDelegate.markupWillLoad() that you could use to drive the copying yourself. I am actually finding a similar need in the work I am discussing in #178. There is still lots of room for screw ups via file name conflicts with things like markup.html, but frankly those are already possible via resourcesUrl.

@benlong100
Copy link
Author

Ah, copying the fonts into resourcesURL is very clever, that seems like the easiest solution. I wasn't clear on whether things were still being copied over from resourcesURL, so that's good news. I think that's a good mechanism and as long as it keeps working I can't think of a reason to expose the MarkupWKWebView baseUrl.

So now I'm back to the problem of the MarkupEditor not loading properly. I can watch LoadUserFiles and see that both the scriptFile and cssFile are set to null and I can see it call evaluateJavaScript, but I gather it's not possible to set breakpoints in Javascript code in Xcode? 'Cause it's not letting me do that.

In answer to your previous question of whether I have the most recent JS code, I just installed all this yesterday, so I should be in good shape.

@stevengharris
Copy link
Owner

You can put in breaks in markup.js from the WebInspector, but since we are dealing with it in a state where it really hasn't fully loaded everything, it's hard to do that. Can you put in a break in MarkupWKWebView.loadUserFiles to see if there is an error? I should be logging an error here if one occurred.

image

@benlong100
Copy link
Author

Okay, it's doing that weird Xcode thing where it runs through the code twice. The first time, there's no error, but then it comes back through and the console shows:
Error Internal: Break at MUError('Internal'... in Safari Web Inspector to debug.
If I fire up the Web Inspector in Safari and hit reload, then the Console there shows:
TypeError: MU.loadUserFiles is not a function. (In 'MU.loadUserFiles(null, null)', 'MU.loadUserFiles' is undefined)

So then if I go look in the markup.js file I find a lovely MU.loadUserFile function that looks perfectly intact.

@stevengharris
Copy link
Owner

stevengharris commented Jan 19, 2024

You can see markup.js in the Web Inspector and that markup.js includes MU.loadUserFiles like I can see below when running the DemoViewController? We really should be sure the demo works properly in your environment.

image

@benlong100
Copy link
Author

Aha! I see markup.js as a source in the Web Inspector but when I search it for loadUserFiles there's nothing there. Eyeballed around manually for a bit and couldn't find it. How can it be showing up in Xcode but not here?

@stevengharris
Copy link
Owner

The one in Xcode is not what was found in the bundle and copied to the cacheUrl() location in initRootFiles(). Maybe the first thing to do is Clean Build Folder, and make sure you build MarkupEditor build target again. If I had to guess, as you said, your app has a dependency on the local build of MarkupEditor, but you didn't rebuild it when you updated. So just rebuilding MarkupEditor might do the trick. If not, then you are down to debugging in initRootFiles and figuring out where it's finding markup.js and whether the contents are correct.

    private func initRootFiles() {
        guard
            let rootHtml = url(forResource: "markup", withExtension: "html"),
            let rootCss = url(forResource: "markup", withExtension: "css"),
            let rootJs = url(forResource: "markup", withExtension: "js") else {
            assertionFailure("Could not find markup.html, css, and js for this bundle.")
            return
        }
        var srcUrls = [rootHtml, rootCss, rootJs]
        // If specified, the userCSS comes from the app's main bundle, not something MarkupEditor provides
        if let userCssFile, let userCss = url(forResource: userCssFile, withExtension: nil) {
            srcUrls.append(userCss)
        }
        if let userScriptFile, let userScript = url(forResource: userScriptFile, withExtension: nil) {
            srcUrls.append(userScript)
        }
        let fileManager = FileManager.default
        // The cacheDir is a "id" subdirectory below the app's cache directory
        // If not supplied, then id will be a UUID().uuidString
        let cacheUrl = cacheUrl()
        let cacheUrlPath = cacheUrl.path
        do {
            try fileManager.createDirectory(atPath: cacheUrlPath, withIntermediateDirectories: true, attributes: nil)
            for srcUrl in srcUrls {
                let dstUrl = cacheUrl.appendingPathComponent(srcUrl.lastPathComponent)
                try? fileManager.removeItem(at: dstUrl)
                try fileManager.copyItem(at: srcUrl, to: dstUrl)
            }
        } catch let error {
            assertionFailure("Failed to set up cacheDir with root resource files: \(error.localizedDescription)")
        }
    }

@benlong100
Copy link
Author

benlong100 commented Jan 19, 2024 via email

@stevengharris
Copy link
Owner

If you cloned the repo, then open Xcode on the MarkupEditor project and build it. That creates the framework you are referencing in your app as a dependency, so the import MarkupEditor finds what you built. I am usually using an Xcode workspace that includes my app and the MarkupEditor project, so the MarkupEditor target is easy to find and rebuild.

If you use the Swift Package dependency, it needs to be updated to the latest version (0.6.2 I think)

@benlong100
Copy link
Author

Okay, that worked halfway! I rebuilt the MarkupEditor target and cleaned out the Package Cache in my app. As you may recall, I'm running two different editors and now one of them is working! The other is still throwing the same error and finding the same lack of MU.loadUserFiles, but I'll poke around and see if I can figure out what might be different. Thank you thank you!

@benlong100
Copy link
Author

I have figured out what the problem was. As I mentioned before, before calling the editor I uses the resourcesURL to copy over a style sheet and any images my document needs. What I hadn't mentioned before is that I also copy over a markup.html file. I don't remember the details, but with the old version I seem to have discovered that, if I'm doing that, then I also have to copy markup.js through the resources directory, or MarkupEditor won't have one. To do that, I had made a copy of your markup.js file into my app bundle, and I was copying that into the resources directory when summoning a markup editor. What was happening earlier today was that, at runtime, it was still copying that OLD version of markup.js over.

I deleted that old version, and commented out the sections that copied it into the resources directory and then and had a very strange experience. The editors started working, and so I started testing. After four or five entries everything stopped working – the editor would no longer load at all. I hadn't made any changes to the code – this all happened during a single execution of the app. After that it wouldn't work at all and the only way that I've been able to get it working again is to put a copy of the new markup.js folder back into my bundle and return to letting my code copy that over via the resources directory. Now everything is working fine. If I don't do that, then when I execute the code and view it in the Web Inspector in Safari I find that there's no JS source at all in the Markup Editor.

So the behavior I seem to be seeing here is that, if you use resourcesURL to copy over a markup html and css file then you also have to copy over a js file. I'm fine with doing this and it's all working now, and perhaps I'm coming to the wrong conclusion, but that's what seems to be happening.

And now, finally, after all these weeks I can address the original point of this whole thread: whatever you've changed through these recent updates has fixed my pasting problem! Thank you!

@benlong100
Copy link
Author

Here's an odd one: occasionally, with large pastes, it's moving the first paragraph of the text I'm pasting to the very end. I'll see if I can find an example online that does this. Also, should Command-V be working as a shortcut?

@stevengharris
Copy link
Owner

The issue with pasting misplacing the paragraph is going to have something to do with the structure of what's being pasted, not really the length. If you find a case that reproduces it, what would help me debug is the html passed to MarkupWKWebView.pasteHtml(_:handler:).

On command+V, yes it should work. You should see it in your Edit menu, and the item should be enabled. If it's not in the Edit menu or it's disabled, then it won't work. You need to have built the menu, tho. See AppDelegate.buildMenu(with:) in the SharedDemo content to see where that should be done.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants